Lines 85.00% 17 / 20
Functions and Methods 66.66% 2 / 3
Classes and Traits 0.00% 0 / 1
Name Lines Functions and Methods CRAP Classes and Traits
SqliteConnector 85.00% 17 / 20 66.66% 2 / 3 8.22 0.00% 0 / 1
 defaultOptions 100.00% 3 / 3 100.00% 1 / 1 1
 connect 70.00% 7 / 10 0.00% 0 / 1 2.11
 validConfig 100.00% 7 / 7 100.00% 1 / 1 5
1<?php
2
3declare(strict_types=1);
4
5namespace BlueprintAU\Radiant\Database\Connectors;
6
7use BlueprintAU\Radiant\Database\Connections\SqlConnection;
8use BlueprintAU\Radiant\Database\Connections\SqliteConnection;
9use Override;
10
11/**
12 * SQLite connector — builds a PDO SQLite connection from config.
13 *
14 * @see \BlueprintAU\Radiant\Database\Connections\SqlConnection
15 *
16 * @phpstan-import-type PdoOptions from \BlueprintAU\Radiant\Database\Connectors\SqlConnector
17 */
18final class SqliteConnector extends SqlConnector
19{
20    /**
21     * SQLite-specific default PDO attributes — merged over the base
22     * defaults.
23     *
24     * The busy timeout is set via \PDO::ATTR_TIMEOUT, which pdo_sqlite
25     * maps to SQLite's busy timeout in seconds.
26     *
27     * @return array<int, int|bool>
28     */
29    #[\Override]
30    protected function defaultOptions(): array
31    {
32        return [
33            \PDO::ATTR_TIMEOUT => 5, // seconds → SQLite busy timeout (5000ms)
34        ];
35    }
36
37    /**
38     * Create a SQLite connection from the given config.
39     *
40     * @param  array{database: mixed, options?: PdoOptions, ...<mixed>}  $config
41     * @return SqliteConnection
42     * @throws \InvalidArgumentException
43     */
44    #[Override]
45    public function connect(array $config): SqlConnection
46    {
47        $this->validConfig($config);
48        $path = $config['database'];
49
50        if (!is_string($path)) {
51            throw new \InvalidArgumentException(
52                'SQLite database must be a path string; got ' . get_debug_type($path) . '.'
53            );
54        }
55
56        $options = $config['options'] ?? [];
57        $pdo = $this->createPdo("sqlite:{$path}", null, null, $options);
58
59        // SQLite ships with foreign key enforcement OFF by default — orphaned
60        // rows and no cascades unless it is enabled explicitly. This is a
61        // post-connect PRAGMA (not a PDO attribute), so it runs here rather
62        // than in the options layers. It is forced unconditionally: a
63        // database relying on broken FK data is the user's own setup problem,
64        // but a library silently dropping FK guarantees would be the library's.
65        $pdo->exec('PRAGMA foreign_keys = ON');
66
67        return new SqliteConnection($pdo);
68    }
69
70    /**
71     * Validate the shape of a SQLite connection config.
72     *
73     * @param  array<string,mixed>  $config
74     * @throws \InvalidArgumentException
75     */
76    #[Override]
77    public function validConfig(array $config): void
78    {
79        parent::validConfig($config);
80
81        if (!isset($config['database']) || !is_string($config['database']) || $config['database'] === '') {
82            throw new \InvalidArgumentException(
83                'SQLite database must be a non-empty path string; got '
84                . (isset($config['database']) ? get_debug_type($config['database']) : 'nothing')
85                . '.'
86            );
87        }
88    }
89}