Handler::createColumn()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of Cycle ORM package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Cycle\Database\Driver;
13
14
use Cycle\Database\Exception\DBALException;
15
use Cycle\Database\Exception\DriverException;
16
use Cycle\Database\Exception\HandlerException;
17
use Cycle\Database\Exception\StatementException;
18
use Cycle\Database\Schema\AbstractColumn;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\Schema\AbstractColumn was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
19
use Cycle\Database\Schema\AbstractForeignKey;
20
use Cycle\Database\Schema\AbstractIndex;
21
use Cycle\Database\Schema\AbstractTable;
22
use Cycle\Database\Schema\ComparatorInterface;
23
use Cycle\Database\Schema\ElementInterface;
24
25
abstract class Handler implements HandlerInterface
26
{
27
    protected ?DriverInterface $driver = null;
28
29 76
    public function withDriver(DriverInterface $driver): HandlerInterface
30
    {
31 76
        $handler = clone $this;
32 76
        $handler->driver = $driver;
33
34 76
        return $handler;
35
    }
36
37
    /**
38
     * Associated driver.
39
     */
40
    public function getDriver(): DriverInterface
41
    {
42
        return $this->driver;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->driver could return the type null which is incompatible with the type-hinted return Cycle\Database\Driver\DriverInterface. Consider adding an additional type-check to rule them out.
Loading history...
43
    }
44
45 1948
    public function createTable(AbstractTable $table): void
46
    {
47 1948
        $this->run($this->createStatement($table));
48
49
        //Not all databases support adding index while table creation, so we can do it after
50 1942
        foreach ($table->getIndexes() as $index) {
51 282
            $this->createIndex($table, $index);
52
        }
53 1942
    }
54
55 1930
    public function dropTable(AbstractTable $table): void
56
    {
57 1930
        $this->run(
58 1930
            "DROP TABLE {$this->identify($table->getInitialName())}",
59
        );
60 1930
    }
61
62 538
    public function syncTable(AbstractTable $table, int $operation = self::DO_ALL): void
63
    {
64 538
        $comparator = $table->getComparator();
65
66 538
        $comparator->isPrimaryChanged() and throw new DBALException('Unable to change primary keys for existed table');
67
68 538
        if ($operation & self::DO_RENAME && $comparator->isRenamed()) {
69 8
            $this->renameTable($table->getInitialName(), $table->getFullName());
70
        }
71
72
        /*
73
         * This is schema synchronization code, if you are reading it you are either experiencing
74
         * VERY weird bug, or you are very curious. Please contact me in a any scenario :)
75
         */
76 538
        $this->executeChanges($table, $operation, $comparator);
77 538
    }
78
79
    /**
80
     * @psalm-param non-empty-string $table
81
     * @psalm-param non-empty-string $name
82
     */
83 122
    public function renameTable(string $table, string $name): void
84
    {
85 122
        $this->run(
86 122
            "ALTER TABLE {$this->identify($table)} RENAME TO {$this->identify($name)}",
87
        );
88 122
    }
89
90 80
    public function createColumn(AbstractTable $table, AbstractColumn $column): void
91
    {
92 80
        $this->run(
93 80
            "ALTER TABLE {$this->identify($table)} ADD COLUMN {$column->sqlStatement($this->driver)}",
94
        );
95 80
    }
96
97 24
    public function dropColumn(AbstractTable $table, AbstractColumn $column): void
98
    {
99 24
        foreach ($column->getConstraints() as $constraint) {
100
            //We have to erase all associated constraints
101
            $this->dropConstrain($table, $constraint);
102
        }
103
104 24
        $this->run(
105 24
            "ALTER TABLE {$this->identify($table)} DROP COLUMN {$this->identify($column)}",
106
        );
107 24
    }
108
109 458
    public function createIndex(AbstractTable $table, AbstractIndex $index): void
110
    {
111 458
        $this->run("CREATE {$index->sqlStatement($this->driver)}");
0 ignored issues
show
Bug introduced by
It seems like $this->driver can also be of type null; however, parameter $driver of Cycle\Database\Schema\Ab...ctIndex::sqlStatement() does only seem to accept Cycle\Database\Driver\DriverInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

111
        $this->run("CREATE {$index->sqlStatement(/** @scrutinizer ignore-type */ $this->driver)}");
Loading history...
112 458
    }
113
114 24
    public function dropIndex(AbstractTable $table, AbstractIndex $index): void
115
    {
116 24
        $this->run("DROP INDEX {$this->identify($index)}");
117 24
    }
118
119 6
    public function alterIndex(
120
        AbstractTable $table,
121
        AbstractIndex $initial,
122
        AbstractIndex $index,
123
    ): void {
124 6
        $this->dropIndex($table, $initial);
125 6
        $this->createIndex($table, $index);
126 6
    }
127
128 132
    public function createForeignKey(AbstractTable $table, AbstractForeignKey $foreignKey): void
129
    {
130 132
        $this->run(
131 132
            "ALTER TABLE {$this->identify($table)} ADD {$foreignKey->sqlStatement($this->driver)}",
0 ignored issues
show
Bug introduced by
It seems like $this->driver can also be of type null; however, parameter $driver of Cycle\Database\Schema\Ab...eignKey::sqlStatement() does only seem to accept Cycle\Database\Driver\DriverInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

131
            "ALTER TABLE {$this->identify($table)} ADD {$foreignKey->sqlStatement(/** @scrutinizer ignore-type */ $this->driver)}",
Loading history...
132
        );
133 132
    }
134
135 112
    public function dropForeignKey(AbstractTable $table, AbstractForeignKey $foreignKey): void
136
    {
137 112
        $this->dropConstrain($table, $foreignKey->getName());
138 112
    }
139
140 42
    public function alterForeignKey(
141
        AbstractTable $table,
142
        AbstractForeignKey $initial,
143
        AbstractForeignKey $foreignKey,
144
    ): void {
145 42
        $this->dropForeignKey($table, $initial);
146 42
        $this->createForeignKey($table, $foreignKey);
147 42
    }
148
149 130
    public function dropConstrain(AbstractTable $table, string $constraint): void
150
    {
151 130
        $this->run(
152 130
            "ALTER TABLE {$this->identify($table)} DROP CONSTRAINT {$this->identify($constraint)}",
153
        );
154 130
    }
155
156
    /**
157
     * @psalm-return non-empty-string
158
     */
159 1948
    protected function createStatement(AbstractTable $table): string
160
    {
161 1948
        $statement = ["CREATE TABLE {$this->identify($table)} ("];
162 1948
        $innerStatement = [];
163
164
        //Columns
165 1948
        foreach ($table->getColumns() as $column) {
166 1948
            $this->assertValid($column);
167 1944
            $innerStatement[] = $column->sqlStatement($this->driver);
168
        }
169
170
        //Primary key
171 1944
        if ($table->getPrimaryKeys() !== []) {
172 1380
            $primaryKeys = \array_map([$this, 'identify'], $table->getPrimaryKeys());
173
174 1380
            $innerStatement[] = 'PRIMARY KEY (' . \implode(', ', $primaryKeys) . ')';
175
        }
176
177
        //Constraints and foreign keys
178 1944
        foreach ($table->getForeignKeys() as $reference) {
179 134
            $innerStatement[] = $reference->sqlStatement($this->driver);
180
        }
181
182 1944
        $statement[] = '    ' . \implode(",\n    ", $innerStatement);
183 1944
        $statement[] = ')';
184
185 1944
        return \implode("\n", $statement);
186
    }
187
188 538
    protected function executeChanges(
189
        AbstractTable $table,
190
        int $operation,
191
        ComparatorInterface $comparator,
192
    ): void {
193
        //Remove all non needed table constraints
194 538
        $this->dropConstrains($table, $operation, $comparator);
195
196 538
        if ($operation & self::CREATE_COLUMNS) {
197
            //After drops and before creations we can add new columns
198 470
            $this->createColumns($table, $comparator);
199
        }
200
201 538
        if ($operation & self::ALTER_COLUMNS) {
202
            //We can alter columns now
203 470
            $this->alterColumns($table, $comparator);
204
        }
205
206
        //Add new constrains and modify existed one
207 538
        $this->setConstrains($table, $operation, $comparator);
208 538
    }
209
210
    /**
211
     * Execute statement.
212
     *
213
     * @psalm-param non-empty-string $statement
214
     *
215
     * @throws HandlerException
216
     */
217 1946
    protected function run(string $statement, array $parameters = []): int
218
    {
219
        try {
220 1946
            return $this->driver->execute($statement, $parameters);
0 ignored issues
show
Bug introduced by
The method execute() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

220
            return $this->driver->/** @scrutinizer ignore-call */ execute($statement, $parameters);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
221 10
        } catch (StatementException $e) {
222 2
            throw new HandlerException($e);
223
        }
224
    }
225
226
    /**
227
     * Create element identifier.
228
     *
229
     * @psalm-return non-empty-string
230
     */
231 1950
    protected function identify(AbstractTable|ElementInterface|string $element): string
232
    {
233 1950
        if (\is_string($element)) {
234 1942
            return $this->driver->identifier($element);
0 ignored issues
show
Bug introduced by
The method identifier() does not exist on Cycle\Database\Driver\DriverInterface. It seems like you code against a sub-type of Cycle\Database\Driver\DriverInterface such as Cycle\Database\Driver\Driver. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

234
            return $this->driver->/** @scrutinizer ignore-call */ identifier($element);
Loading history...
235
        }
236
237 1950
        if ($element instanceof AbstractTable) {
238 1950
            return $this->driver->identifier($element->getFullName());
239
        }
240
241 178
        if ($element instanceof ElementInterface) {
0 ignored issues
show
introduced by
$element is always a sub-type of Cycle\Database\Schema\ElementInterface.
Loading history...
242 178
            return $this->driver->identifier($element->getName());
243
        }
244
245
        throw new \InvalidArgumentException('Invalid argument type');
246
    }
247
248 470
    protected function alterForeignKeys(AbstractTable $table, ComparatorInterface $comparator): void
249
    {
250 470
        foreach ($comparator->alteredForeignKeys() as $pair) {
251
            /**
252
             * @var AbstractForeignKey $initial
253
             * @var AbstractForeignKey $current
254
             */
255 42
            [$current, $initial] = $pair;
256
257 42
            $this->alterForeignKey($table, $initial, $current);
258
        }
259 470
    }
260
261 496
    protected function createForeignKeys(AbstractTable $table, ComparatorInterface $comparator): void
262
    {
263 496
        foreach ($comparator->addedForeignKeys() as $foreign) {
264 90
            $this->createForeignKey($table, $foreign);
265
        }
266 496
    }
267
268 470
    protected function alterIndexes(AbstractTable $table, ComparatorInterface $comparator): void
269
    {
270 470
        foreach ($comparator->alteredIndexes() as $pair) {
271
            /**
272
             * @var AbstractIndex $initial
273
             * @var AbstractIndex $current
274
             */
275 8
            [$current, $initial] = $pair;
276
277 8
            $this->alterIndex($table, $initial, $current);
278
        }
279 470
    }
280
281 470
    protected function createIndexes(AbstractTable $table, ComparatorInterface $comparator): void
282
    {
283 470
        foreach ($comparator->addedIndexes() as $index) {
284 180
            $this->createIndex($table, $index);
285
        }
286 470
    }
287
288 470
    protected function alterColumns(AbstractTable $table, ComparatorInterface $comparator): void
289
    {
290 470
        foreach ($comparator->alteredColumns() as $pair) {
291
            /**
292
             * @var AbstractColumn $initial
293
             * @var AbstractColumn $current
294
             */
295 116
            [$current, $initial] = $pair;
296
297 116
            $this->assertValid($current);
298 116
            $this->alterColumn($table, $initial, $current);
299
        }
300 470
    }
301
302 470
    protected function createColumns(AbstractTable $table, ComparatorInterface $comparator): void
303
    {
304 470
        foreach ($comparator->addedColumns() as $column) {
305 120
            $this->assertValid($column);
306 120
            $this->createColumn($table, $column);
307
        }
308 470
    }
309
310 470
    protected function dropColumns(AbstractTable $table, ComparatorInterface $comparator): void
311
    {
312 470
        foreach ($comparator->droppedColumns() as $column) {
313 24
            $this->dropColumn($table, $column);
314
        }
315 470
    }
316
317 484
    protected function dropIndexes(AbstractTable $table, ComparatorInterface $comparator): void
318
    {
319 484
        foreach ($comparator->droppedIndexes() as $index) {
320 40
            $this->dropIndex($table, $index);
321
        }
322 484
    }
323
324 538
    protected function dropForeignKeys(AbstractTable $table, ComparatorInterface $comparator): void
325
    {
326 538
        foreach ($comparator->droppedForeignKeys() as $foreign) {
327 168
            $this->dropForeignKey($table, $foreign);
328
        }
329 538
    }
330
331
    /**
332
     * Applied to every column in order to make sure that driver support it.
333
     *
334
     * @throws DriverException
335
     */
336 1472
    protected function assertValid(AbstractColumn $column): void
337
    {
338
        //All valid by default
339 1472
    }
340
341 538
    protected function dropConstrains(
342
        AbstractTable $table,
343
        int $operation,
344
        ComparatorInterface $comparator,
345
    ): void {
346 538
        if ($operation & self::DROP_FOREIGN_KEYS) {
347 538
            $this->dropForeignKeys($table, $comparator);
348
        }
349
350 538
        if ($operation & self::DROP_INDEXES) {
351 484
            $this->dropIndexes($table, $comparator);
352
        }
353
354 538
        if ($operation & self::DROP_COLUMNS) {
355 470
            $this->dropColumns($table, $comparator);
356
        }
357 538
    }
358
359 538
    protected function setConstrains(
360
        AbstractTable $table,
361
        int $operation,
362
        ComparatorInterface $comparator,
363
    ): void {
364 538
        if ($operation & self::CREATE_INDEXES) {
365 470
            $this->createIndexes($table, $comparator);
366
        }
367
368 538
        if ($operation & self::ALTER_INDEXES) {
369 470
            $this->alterIndexes($table, $comparator);
370
        }
371
372 538
        if ($operation & self::CREATE_FOREIGN_KEYS) {
373 496
            $this->createForeignKeys($table, $comparator);
374
        }
375
376 538
        if ($operation & self::ALTER_FOREIGN_KEYS) {
377 470
            $this->alterForeignKeys($table, $comparator);
378
        }
379 538
    }
380
}
381