Lines 96.80% 91 / 94
Methods 90.00% 9 / 10
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 dialectLabel 100.00% 1 / 1 100.00% 1 / 1 1
 makeConnection 100.00% 1 / 1 100.00% 1 / 1 1
 [BlueprintAU\Radiant\Database\Connectors\MySqlConnector] validCharset 100.00% 7 / 7 100.00% 1 / 1 5
 [BlueprintAU\Radiant\Database\Connectors\MySqlConnector] forcedOptions 100.00% 3 / 3 100.00% 1 / 1 1
 [BlueprintAU\Radiant\Database\Connectors\MySqlConnector] connect 85.00% 17 / 20 0.00% 0 / 1 4.05
 [BlueprintAU\Radiant\Database\Connectors\MySqlConnector] validConfig 100.00% 26 / 26 100.00% 1 / 1 10
 [BlueprintAU\Radiant\Database\Connectors\SqlConnector] defaultOptions 100.00% 1 / 1 100.00% 1 / 1 1
 [BlueprintAU\Radiant\Database\Connectors\SqlConnector] validDsnField 100.00% 11 / 11 100.00% 1 / 1 5
 [BlueprintAU\Radiant\Database\Connectors\SqlConnector] validateOptions 100.00% 13 / 13 100.00% 1 / 1 6
 [BlueprintAU\Radiant\Database\Connectors\SqlConnector] createPdo 100.00% 11 / 11 100.00% 1 / 1 2
23final class MariaDbConnector extends MySqlConnector
24{
25    /**
26     * The dialect's name in the connector's validation messages.
27     *
28     * @return string
29     */
30    #[Override]
31    protected function dialectLabel(): string
32    {
33        return 'MariaDB';
34    }
35
36    /**
37     * The connection class this connector builds.
38     *
39     * @param  \PDO  $pdo
40     * @return MariaDbConnection
41     */
42    #[Override]
43    protected function makeConnection(Pdo $pdo): MariaDbConnection
44    {
45        return new MariaDbConnection($pdo);
46    }
47}

Inherited from BlueprintAU\Radiant\Database\Connectors\MySqlConnector

62    private function validCharset(mixed $charset): string
63    {
64        if (!is_string($charset) || $charset === ''
65            || !in_array(strtolower($charset), self::ALLOWED_CHARSETS, true)) {
66            throw new \InvalidArgumentException(
67                $this->dialectLabel() . ' "charset" must be one of: ' . implode(', ', self::ALLOWED_CHARSETS)
68                . '; got ' . (is_string($charset) ? "[{$charset}]" : get_debug_type($charset)) . '.'
69            );
70        }
71        return strtolower($charset);
72    }
84    protected function forcedOptions(): array
85    {
86        return [
87            \PDO\Mysql::ATTR_FOUND_ROWS => true,
88        ];
89    }
101    public function connect(array $config): SqlConnection
102    {
103        $this->validConfig($config);
104
105        $host = $config['host'] ?? null;
106        $port = $config['port'] ?? null;
107        $database = $config['database'] ?? null;
108        $charset = $this->validCharset($config['charset'] ?? 'utf8mb4');
109
110        // validConfig() has already validated host, port and database â€” the
111        // combined shape check stays as defense-in-depth for direct
112        // connect() calls that skip the manager.
113        if (!is_string($host) || !is_int($port) || !is_string($database)) {
114            throw new \InvalidArgumentException(
115                $this->dialectLabel() . ' requires a string host, an integer port and a string database.'
116            );
117        }
118
119        $dsn = sprintf(
120            'mysql:host=%s;port=%d;dbname=%s;charset=%s',
121            $host,
122            $port,
123            $database,
124            $charset,
125        );
126
127        $options = $config['options'] ?? [];
128        $pdo = $this->createPdo($dsn, $config['username'] ?? null, $config['password'] ?? null, $options);
129
130        // The charset must match the connection: set it once at connect time
131        // (config-driven, so it can't be a static forced option).
132        $pdo->exec("SET NAMES {$charset}");
133
134        return $this->makeConnection($pdo);
135    }
144    public function validConfig(array $config): void
145    {
146        parent::validConfig($config);
147
148        $host = $config['host'] ?? null;
149        $port = $config['port'] ?? null;
150        $database = $config['database'] ?? null;
151
152        // The charset reaches the DSN and a raw SET NAMES statement â€” validate
153        // it at construction time (fail-fast) as well as at connect time.
154        if (array_key_exists('charset', $config)) {
155            $this->validCharset($config['charset']);
156        }
157
158        if (!is_string($host) || $host === '') {
159            throw new \InvalidArgumentException(
160                $this->dialectLabel() . ' requires a non-empty string "host"; got '
161                . ($host === null ? 'nothing' : get_debug_type($host))
162                . '.'
163            );
164        }
165
166        if (!is_int($port)) {
167            throw new \InvalidArgumentException(
168                $this->dialectLabel() . ' requires an integer "port"; got '
169                . ($port === null ? 'nothing' : get_debug_type($port))
170                . '.'
171            );
172        }
173
174        if (!is_string($database) || $database === '') {
175            throw new \InvalidArgumentException(
176                $this->dialectLabel() . ' requires a non-empty string "database"; got '
177                . ($database === null ? 'nothing' : get_debug_type($database))
178                . '.'
179            );
180        }
181
182        // host and database are interpolated into the DSN â€” metacharacters
183        // there re-bind the DSN's key-value parsing (a `;` can inject
184        // unix_socket or sslmode). The type/emptiness checks above give the
185        // driver-specific message; this adds the metacharacter bound.
186        $this->validDsnField($host, 'host');
187        $this->validDsnField($database, 'database');
188    }

Inherited from BlueprintAU\Radiant\Database\Connectors\SqlConnector

42    protected function defaultOptions(): array
43    {
44        return [];
45    }
91    protected function validDsnField(mixed $value, string $field): string
92    {
93        if (!is_string($value) || $value === '') {
94            throw new \InvalidArgumentException(
95                'The "' . $field . '" config field must be a non-empty string; got '
96                . ($value === null ? 'nothing' : get_debug_type($value)) . '.'
97            );
98        }
99        if (preg_match('/[;\s\x00-\x1f\x7f]/', $value) === 1) {
100            throw new \InvalidArgumentException(
101                'The "' . $field . '" config field must not contain semicolons, whitespace '
102                . 'or control characters (they are DSN metacharacters); got a value that does.'
103            );
104        }
105        return $value;
106    }
114    final protected function validateOptions(mixed $options): void
115    {
116        if (!is_array($options)) {
117            throw new \InvalidArgumentException(
118                'PDO options must be an array of attributes; got ' . get_debug_type($options) . '.'
119            );
120        }
121
122        foreach ($options as $key => $value) {
123            if (!is_int($key)) {
124                throw new \InvalidArgumentException(
125                    'PDO option keys must be integer attribute constants (PDO::ATTR_*); got ' . get_debug_type($key) . '.'
126                );
127            }
128
129            if (!is_scalar($value) && !is_array($value)) {
130                throw new \InvalidArgumentException(
131                    'PDO option values must be a scalar or array; got ' . get_debug_type($value) . ' for option ' . $key . '.'
132                );
133            }
134        }
135    }
152    final protected function createPdo(string $dsn, ?string $username, #[\SensitiveParameter] ?string $password, array $options): \Pdo
153    {
154        $this->validateOptions($options);
155        $options = array_replace(
156            self::DEFAULT_OPTIONS,
157            static::defaultOptions(),
158            $options,
159            self::FORCED_OPTIONS,
160            static::forcedOptions(),
161        );
162        try {
163            return \PDO::connect($dsn, $username, $password, $options);
164        } catch (\PDOException $e) {
165            throw new ConnectionException("Could not connect to database.", $e->getCode(), $e);
166        }
167    }