Connection::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 8
dl 0
loc 14
ccs 5
cts 5
cp 1
crap 1
rs 10
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Conia\Quma;
6
7
use Conia\Quma\Util;
8
use PDO;
9
use RuntimeException;
10
use ValueError;
11
12
/**
13
 * @psalm-api
14
 *
15
 * @psalm-type MigrationDirs = list<non-empty-string>
16
 * @psalm-type SqlDirs = list<non-empty-string>
17
 * @psalm-type SqlAssoc = array<non-empty-string, non-empty-string>
18
 * @psalm-type SqlMixed = list<non-empty-string|SqlAssoc>
19
 * @psalm-type SqlConfig = non-empty-string|SqlAssoc|SqlMixed
20
 */
21
class Connection
22
{
23
    use GetsSetsPrint;
24
25
    /** @psalm-var non-empty-string */
26
    public readonly string $driver;
27
28
    /** @psalm-var SqlDirs */
29
    protected array $sql;
30
31
    /** @psalm-var MigrationDirs */
32
    protected array $migrations;
33
34
    protected string $migrationsTable = 'migrations';
35
    protected string $migrationsColumnMigration = 'migration';
36
    protected string $migrationsColumnApplied = 'applied';
37
38
    /**
39
     * @psalm-param SqlConfig $sql
40
     * @psalm-param MigrationDirs $migrations
41
     * */
42 84
    public function __construct(
43
        public readonly string $dsn,
44
        string|array $sql,
45
        string|array $migrations = null,
46
        public readonly ?string $username = null,
47
        public readonly ?string $password = null,
48
        public readonly array $options = [],
49
        public readonly int $fetchMode = PDO::FETCH_BOTH,
50
        bool $print = false
51
    ) {
52 84
        $this->driver = $this->readDriver($this->dsn);
53 83
        $this->sql = $this->readDirs($sql);
54 82
        $this->migrations = $this->readDirs($migrations ?? []);
55 81
        $this->print = $print;
56
    }
57
58 73
    public function setMigrationsTable(string $table): void
59
    {
60 73
        $this->migrationsTable = $table;
61
    }
62
63 2
    public function setMigrationsColumnMigration(string $column): void
64
    {
65 2
        $this->migrationsColumnMigration = $column;
66
    }
67
68 2
    public function setMigrationsColumnApplied(string $column): void
69
    {
70 2
        $this->migrationsColumnApplied = $column;
71
    }
72
73 40
    public function migrationsTable(): string
74
    {
75 40
        if ($this->driver === 'pgsql') {
76
            // PostgreSQL table names can contain a schema
77 9
            if (preg_match('/^([a-zA-Z0-9_]+\.)?[a-zA-Z0-9_]+$/', $this->migrationsTable)) {
78 9
                return $this->migrationsTable;
79
            }
80
        } else {
81 31
            if (preg_match('/^[a-zA-Z0-9_]+$/', $this->migrationsTable)) {
82 30
                return $this->migrationsTable;
83
            }
84
        }
85
86 1
        throw new ValueError('Invalid migrations table name: ' . $this->migrationsTable);
87
    }
88
89 40
    public function migrationsColumnMigration(): string
90
    {
91 40
        return $this->getColumnName($this->migrationsColumnMigration);
92
    }
93
94 40
    public function migrationsColumnApplied(): string
95
    {
96 40
        return $this->getColumnName($this->migrationsColumnApplied);
97
    }
98
99
    /** @psalm-param non-empty-string $migrations */
100 1
    public function addMigrationDir(string $migrations): void
101
    {
102 1
        $migrations = $this->readDirs($migrations);
103 1
        $this->migrations = array_merge($migrations, $this->migrations);
104
    }
105
106
    /** @psalm-return MigrationDirs */
107 28
    public function migrations(): array
108
    {
109 28
        return $this->migrations;
110
    }
111
112
    /** @psalm-param SqlConfig $sql */
113 1
    public function addSqlDirs(array|string $sql): void
114
    {
115 1
        $sql = $this->readDirs($sql);
116 1
        $this->sql = array_merge($sql, $this->sql);
117
    }
118
119 31
    public function sql(): array
120
    {
121 31
        return $this->sql;
122
    }
123
124
    /** @psalm-return non-empty-string */
125 83
    protected function preparePath(string $path): string
126
    {
127 83
        $result = realpath($path);
128
129 83
        if ($result) {
130 83
            return $result;
131
        }
132
133 1
        throw new ValueError("Path does not exist: {$path}");
134
    }
135
136
    /** @psalm-return non-empty-string */
137 84
    protected function readDriver(string $dsn): string
138
    {
139 84
        $driver = explode(':', $dsn)[0];
140
141 84
        if (in_array($driver, PDO::getAvailableDrivers())) {
142
            assert(!empty($driver));
143
144 83
            return $driver;
145
        }
146
147 1
        throw new RuntimeException('PDO driver not supported: ' . $driver);
148
    }
149
150
    /**
151
     * @psalm-param SqlAssoc $entry
152
     *
153
     * @psalm-return MigrationDirs
154
     */
155 7
    protected function prepareDirs(array $entry): array
156
    {
157
        /** @psalm-var MigrationDirs */
158 7
        $dirs = [];
159
160
        // Add sql scripts for the current pdo driver.
161
        // Should be the first in the list as they
162
        // may have platform specific queries.
163 7
        if (array_key_exists($this->driver, $entry)) {
164 7
            $dirs[] = $this->preparePath($entry[$this->driver]);
165
        }
166
167
        // Add sql scripts for all platforms
168 7
        if (array_key_exists('all', $entry)) {
169 6
            $dirs[] = $this->preparePath($entry['all']);
170
        }
171
172 7
        return $dirs;
173
    }
174
175
    /**
176
     * Adds the sql script paths from configuration.
177
     *
178
     * Script paths are ordered last in first out (LIFO).
179
     * Which means the last path added is the first one searched
180
     * for a SQL script.
181
     *
182
     * @psalm-param SqlConfig $sql
183
     *
184
     * @psalm-return MigrationDirs
185
     */
186 83
    protected function readDirs(string|array $sql): array
187
    {
188 83
        if (is_string($sql)) {
0 ignored issues
show
introduced by
The condition is_string($sql) is always false.
Loading history...
189
            /** @psalm-var MigrationDirs */
190 80
            return [$this->preparePath($sql)];
191
        }
192
193 16
        if (Util::isAssoc($sql)) {
194
            /** @psalm-var SqlAssoc $sql */
195 2
            return $this->prepareDirs($sql);
196
        }
197
198
        /** @psalm-var MigrationDirs */
199 16
        $dirs = [];
200
201 16
        foreach ($sql as $entry) {
202 7
            if (is_string($entry)) {
203 7
                array_unshift($dirs, $this->preparePath($entry));
204
205 7
                continue;
206
            }
207
208 6
            if (Util::isAssoc($entry)) {
209 5
                $dirs = array_merge($this->prepareDirs($entry), $dirs);
210
211 5
                continue;
212
            }
213
214 1
            throw new ValueError(
215 1
                "A single 'sql' item must be either a string or an associative array"
216 1
            );
217
        }
218
219 15
        return $dirs;
220
    }
221
222 41
    protected function getColumnName(string $column): string
223
    {
224 41
        if (preg_match('/^[a-zA-Z0-9_]+$/', $column)) {
225 39
            return $column;
226
        }
227
228 2
        throw new ValueError('Invalid migrations table column name: ' . $column);
229
    }
230
}
231