Failed Conditions
Push — develop ( 55bd22...e46c09 )
by Sergei
33s queued 23s
created

Table::__construct()   A

Complexity

Conditions 6
Paths 17

Size

Total Lines 31
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 31
rs 9.2222
c 0
b 0
f 0
ccs 13
cts 13
cp 1
cc 6
nc 17
nop 6
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Schema;
6
7
use Doctrine\DBAL\DBALException;
8
use Doctrine\DBAL\Schema\Exception\ColumnAlreadyExists;
9
use Doctrine\DBAL\Schema\Exception\ColumnDoesNotExist;
10
use Doctrine\DBAL\Schema\Exception\ForeignKeyDoesNotExist;
11
use Doctrine\DBAL\Schema\Exception\IndexAlreadyExists;
12
use Doctrine\DBAL\Schema\Exception\IndexDoesNotExist;
13
use Doctrine\DBAL\Schema\Exception\IndexNameInvalid;
14
use Doctrine\DBAL\Schema\Exception\InvalidTableName;
15
use Doctrine\DBAL\Schema\Exception\UniqueConstraintDoesNotExist;
16
use Doctrine\DBAL\Schema\Visitor\Visitor;
17
use Doctrine\DBAL\Types\Type;
18
use function array_keys;
19
use function array_merge;
20
use function array_search;
21
use function array_unique;
22
use function in_array;
23
use function is_string;
24
use function preg_match;
25
use function strlen;
26
use function strtolower;
27
use function uksort;
28
29
/**
30
 * Object Representation of a table.
31
 */
32
class Table extends AbstractAsset
33
{
34
    /** @var Column[] */
35
    protected $_columns = [];
36
37
    /** @var Index[] */
38
    private $implicitIndexes = [];
39
40
    /** @var Index[] */
41
    protected $_indexes = [];
42
43
    /** @var string */
44
    protected $_primaryKeyName = false;
45
46
    /** @var UniqueConstraint[] */
47
    protected $_uniqueConstraints = [];
48
49
    /** @var ForeignKeyConstraint[] */
50
    protected $_fkConstraints = [];
51
52
    /** @var mixed[] */
53
    protected $_options = [];
54
55
    /** @var SchemaConfig|null */
56
    protected $_schemaConfig = null;
57
58
    /**
59
     * @param string                 $tableName
60
     * @param Column[]               $columns
61
     * @param Index[]                $indexes
62
     * @param UniqueConstraint[]     $uniqueConstraints
63
     * @param ForeignKeyConstraint[] $fkConstraints
64
     * @param mixed[]                $options
65
     *
66
     * @throws DBALException
67
     */
68 19176
    public function __construct(
69
        $tableName,
70
        array $columns = [],
71
        array $indexes = [],
72
        array $uniqueConstraints = [],
73
        array $fkConstraints = [],
74
        array $options = []
75
    ) {
76 19176
        if (strlen($tableName) === 0) {
77 1627
            throw InvalidTableName::new($tableName);
78
        }
79
80 19174
        $this->_setName($tableName);
81
82 19174
        foreach ($columns as $column) {
83 17055
            $this->_addColumn($column);
84
        }
85
86 19172
        foreach ($indexes as $idx) {
87 16959
            $this->_addIndex($idx);
88
        }
89
90 19168
        foreach ($uniqueConstraints as $uniqueConstraint) {
91 202
            $this->_addUniqueConstraint($uniqueConstraint);
92
        }
93
94 19168
        foreach ($fkConstraints as $fkConstraint) {
95 15912
            $this->_addForeignKeyConstraint($fkConstraint);
96
        }
97
98 19168
        $this->_options = $options;
99 19168
    }
100
101
    /**
102
     * @return void
103
     */
104 17626
    public function setSchemaConfig(SchemaConfig $schemaConfig)
105
    {
106 17626
        $this->_schemaConfig = $schemaConfig;
107 17626
    }
108
109
    /**
110
     * Sets the Primary Key.
111
     *
112
     * @param string[]     $columnNames
113
     * @param string|false $indexName
114
     *
115
     * @return self
116
     */
117 18374
    public function setPrimaryKey(array $columnNames, $indexName = false)
118
    {
119 18374
        $this->_addIndex($this->_createIndex($columnNames, $indexName ?: 'primary', true, true));
120
121 18374
        foreach ($columnNames as $columnName) {
122 18374
            $column = $this->getColumn($columnName);
123 18374
            $column->setNotnull(true);
124
        }
125
126 18374
        return $this;
127
    }
128
129
    /**
130
     * @param mixed[]     $columnNames
131
     * @param string|null $indexName
132
     * @param string[]    $flags
133
     * @param mixed[]     $options
134
     *
135
     * @return self
136
     */
137
    public function addUniqueConstraint(array $columnNames, $indexName = null, array $flags = [], array $options = [])
138
    {
139
        if ($indexName === null) {
140
            $indexName = $this->_generateIdentifierName(
141
                array_merge([$this->getName()], $columnNames),
142
                'uniq',
143
                $this->_getMaxIdentifierLength()
144
            );
145
        }
146
147
        return $this->_addUniqueConstraint($this->_createUniqueConstraint($columnNames, $indexName, $flags, $options));
148
    }
149
150
    /**
151
     * @param string[]    $columnNames
152
     * @param string|null $indexName
153
     * @param string[]    $flags
154
     * @param mixed[]     $options
155
     *
156
     * @return self
157
     */
158 16577
    public function addIndex(array $columnNames, $indexName = null, array $flags = [], array $options = [])
159
    {
160 16577
        if ($indexName === null) {
161 16278
            $indexName = $this->_generateIdentifierName(
162 16278
                array_merge([$this->getName()], $columnNames),
163 16278
                'idx',
164 16278
                $this->_getMaxIdentifierLength()
165
            );
166
        }
167
168 16577
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options));
169
    }
170
171
    /**
172
     * Drops the primary key from this table.
173
     *
174
     * @return void
175
     */
176 16160
    public function dropPrimaryKey()
177
    {
178 16160
        $this->dropIndex($this->_primaryKeyName);
179 16160
        $this->_primaryKeyName = false;
0 ignored issues
show
Documentation Bug introduced by
The property $_primaryKeyName was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
180 16160
    }
181
182
    /**
183
     * Drops an index from this table.
184
     *
185
     * @param string $indexName The index name.
186
     *
187
     * @return void
188
     *
189
     * @throws SchemaException If the index does not exist.
190
     */
191 16191
    public function dropIndex($indexName)
192
    {
193 16191
        $indexName = $this->normalizeIdentifier($indexName);
194
195 16191
        if (! $this->hasIndex($indexName)) {
196
            throw IndexDoesNotExist::new($indexName, $this->_name);
197
        }
198
199 16191
        unset($this->_indexes[$indexName]);
200 16191
    }
201
202
    /**
203
     * @param string[]    $columnNames
204
     * @param string|null $indexName
205
     * @param mixed[]     $options
206
     *
207
     * @return self
208
     */
209 17471
    public function addUniqueIndex(array $columnNames, $indexName = null, array $options = [])
210
    {
211 17471
        if ($indexName === null) {
212 17453
            $indexName = $this->_generateIdentifierName(
213 17453
                array_merge([$this->getName()], $columnNames),
214 17453
                'uniq',
215 17453
                $this->_getMaxIdentifierLength()
216
            );
217
        }
218
219 17471
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options));
220
    }
221
222
    /**
223
     * Renames an index.
224
     *
225
     * @param string      $oldIndexName The name of the index to rename from.
226
     * @param string|null $newIndexName The name of the index to rename to.
227
     *                                  If null is given, the index name will be auto-generated.
228
     *
229
     * @return self This table instance.
230
     *
231
     * @throws SchemaException If no index exists for the given current name
232
     *                         or if an index with the given new name already exists on this table.
233
     */
234 15015
    public function renameIndex($oldIndexName, $newIndexName = null)
235
    {
236 15015
        $oldIndexName           = $this->normalizeIdentifier($oldIndexName);
237 15015
        $normalizedNewIndexName = $this->normalizeIdentifier($newIndexName);
238
239 15015
        if ($oldIndexName === $normalizedNewIndexName) {
240 493
            return $this;
241
        }
242
243 15015
        if (! $this->hasIndex($oldIndexName)) {
244 402
            throw IndexDoesNotExist::new($oldIndexName, $this->_name);
245
        }
246
247 15013
        if ($this->hasIndex($normalizedNewIndexName)) {
248 377
            throw IndexAlreadyExists::new($normalizedNewIndexName, $this->_name);
249
        }
250
251 15011
        $oldIndex = $this->_indexes[$oldIndexName];
252
253 15011
        if ($oldIndex->isPrimary()) {
254 477
            $this->dropPrimaryKey();
255
256 477
            return $this->setPrimaryKey($oldIndex->getColumns(), $newIndexName ?? false);
257
        }
258
259 15011
        unset($this->_indexes[$oldIndexName]);
260
261 15011
        if ($oldIndex->isUnique()) {
262 479
            return $this->addUniqueIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getOptions());
263
        }
264
265 15009
        return $this->addIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getFlags(), $oldIndex->getOptions());
266
    }
267
268
    /**
269
     * Checks if an index begins in the order of the given columns.
270
     *
271
     * @param string[] $columnNames
272
     *
273
     * @return bool
274
     */
275 15188
    public function columnsAreIndexed(array $columnNames)
276
    {
277 15188
        foreach ($this->getIndexes() as $index) {
278
            /** @var $index Index */
279 15188
            if ($index->spansColumns($columnNames)) {
280 15188
                return true;
281
            }
282
        }
283
284
        return false;
285
    }
286
287
    /**
288
     * @param string  $columnName
289
     * @param string  $typeName
290
     * @param mixed[] $options
291
     *
292
     * @return Column
293
     */
294 18934
    public function addColumn($columnName, $typeName, array $options = [])
295
    {
296 18934
        $column = new Column($columnName, Type::getType($typeName), $options);
297
298 18934
        $this->_addColumn($column);
299
300 18934
        return $column;
301
    }
302
303
    /**
304
     * Renames a Column.
305
     *
306
     * @deprecated
307
     *
308
     * @param string $oldColumnName
309
     * @param string $newColumnName
310
     *
311
     * @throws DBALException
312
     */
313
    public function renameColumn($oldColumnName, $newColumnName)
0 ignored issues
show
Unused Code introduced by
The parameter $newColumnName is not used and could be removed. ( Ignorable by Annotation )

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

313
    public function renameColumn($oldColumnName, /** @scrutinizer ignore-unused */ $newColumnName)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $oldColumnName is not used and could be removed. ( Ignorable by Annotation )

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

313
    public function renameColumn(/** @scrutinizer ignore-unused */ $oldColumnName, $newColumnName)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
314
    {
315
        throw new DBALException(
316
            'Table#renameColumn() was removed, because it drops and recreates the column instead. ' .
317
            'There is no fix available, because a schema diff cannot reliably detect if a column ' .
318
            'was renamed or one column was created and another one dropped.'
319
        );
320
    }
321
322
    /**
323
     * Change Column Details.
324
     *
325
     * @param string  $columnName
326
     * @param mixed[] $options
327
     *
328
     * @return self
329
     */
330 15439
    public function changeColumn($columnName, array $options)
331
    {
332 15439
        $column = $this->getColumn($columnName);
333
334 15439
        $column->setOptions($options);
335
336 15439
        return $this;
337
    }
338
339
    /**
340
     * Drops a Column from the Table.
341
     *
342
     * @param string $columnName
343
     *
344
     * @return self
345
     */
346 1518
    public function dropColumn($columnName)
347
    {
348 1518
        $columnName = $this->normalizeIdentifier($columnName);
349
350 1518
        unset($this->_columns[$columnName]);
351
352 1518
        return $this;
353
    }
354
355
    /**
356
     * Adds a foreign key constraint.
357
     *
358
     * Name is inferred from the local columns.
359
     *
360
     * @param Table|string $foreignTable       Table schema instance or table name
361
     * @param string[]     $localColumnNames
362
     * @param string[]     $foreignColumnNames
363
     * @param mixed[]      $options
364
     * @param string|null  $constraintName
365
     *
366
     * @return self
367
     */
368 17641
    public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [], $constraintName = null)
369
    {
370 17641
        if (! $constraintName) {
371 17549
            $constraintName = $this->_generateIdentifierName(
372 17549
                array_merge((array) $this->getName(), $localColumnNames),
373 17549
                'fk',
374 17549
                $this->_getMaxIdentifierLength()
375
            );
376
        }
377
378 17641
        return $this->addNamedForeignKeyConstraint($constraintName, $foreignTable, $localColumnNames, $foreignColumnNames, $options);
1 ignored issue
show
Deprecated Code introduced by
The function Doctrine\DBAL\Schema\Tab...dForeignKeyConstraint() has been deprecated: Use {@link addForeignKeyConstraint} ( Ignorable by Annotation )

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

378
        return /** @scrutinizer ignore-deprecated */ $this->addNamedForeignKeyConstraint($constraintName, $foreignTable, $localColumnNames, $foreignColumnNames, $options);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
379
    }
380
381
    /**
382
     * Adds a foreign key constraint.
383
     *
384
     * Name is to be generated by the database itself.
385
     *
386
     * @deprecated Use {@link addForeignKeyConstraint}
387
     *
388
     * @param Table|string $foreignTable       Table schema instance or table name
389
     * @param string[]     $localColumnNames
390
     * @param string[]     $foreignColumnNames
391
     * @param mixed[]      $options
392
     *
393
     * @return self
394
     */
395 10058
    public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [])
396
    {
397 10058
        return $this->addForeignKeyConstraint($foreignTable, $localColumnNames, $foreignColumnNames, $options);
398
    }
399
400
    /**
401
     * Adds a foreign key constraint with a given name.
402
     *
403
     * @deprecated Use {@link addForeignKeyConstraint}
404
     *
405
     * @param string       $name
406
     * @param Table|string $foreignTable       Table schema instance or table name
407
     * @param string[]     $localColumnNames
408
     * @param string[]     $foreignColumnNames
409
     * @param mixed[]      $options
410
     *
411
     * @return self
412
     *
413
     * @throws SchemaException
414
     */
415 17643
    public function addNamedForeignKeyConstraint($name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [])
416
    {
417 17643
        if ($foreignTable instanceof Table) {
418 17529
            foreach ($foreignColumnNames as $columnName) {
419 17529
                if (! $foreignTable->hasColumn($columnName)) {
420 1142
                    throw ColumnDoesNotExist::new($columnName, $foreignTable->getName());
421
                }
422
            }
423
        }
424
425 17641
        foreach ($localColumnNames as $columnName) {
426 17641
            if (! $this->hasColumn($columnName)) {
427 1255
                throw ColumnDoesNotExist::new($columnName, $this->_name);
428
            }
429
        }
430
431 17639
        $constraint = new ForeignKeyConstraint(
432 17639
            $localColumnNames,
433 178
            $foreignTable,
434 178
            $foreignColumnNames,
435 178
            $name,
436 178
            $options
437
        );
438
439 17639
        return $this->_addForeignKeyConstraint($constraint);
440
    }
441
442
    /**
443
     * @param string $name
444
     * @param mixed  $value
445
     *
446
     * @return self
447
     */
448 16311
    public function addOption($name, $value)
449
    {
450 16311
        $this->_options[$name] = $value;
451
452 16311
        return $this;
453
    }
454
455
    /**
456
     * Returns whether this table has a foreign key constraint with the given name.
457
     *
458
     * @param string $constraintName
459
     *
460
     * @return bool
461
     */
462 15006
    public function hasForeignKey($constraintName)
463
    {
464 15006
        $constraintName = $this->normalizeIdentifier($constraintName);
465
466 15006
        return isset($this->_fkConstraints[$constraintName]);
467
    }
468
469
    /**
470
     * Returns the foreign key constraint with the given name.
471
     *
472
     * @param string $constraintName The constraint name.
473
     *
474
     * @return ForeignKeyConstraint
475
     *
476
     * @throws SchemaException If the foreign key does not exist.
477
     */
478 368
    public function getForeignKey($constraintName)
479
    {
480 368
        $constraintName = $this->normalizeIdentifier($constraintName);
481
482 368
        if (! $this->hasForeignKey($constraintName)) {
483
            throw ForeignKeyDoesNotExist::new($constraintName, $this->_name);
484
        }
485
486 368
        return $this->_fkConstraints[$constraintName];
487
    }
488
489
    /**
490
     * Removes the foreign key constraint with the given name.
491
     *
492
     * @param string $constraintName The constraint name.
493
     *
494
     * @return void
495
     *
496
     * @throws SchemaException
497
     */
498 370
    public function removeForeignKey($constraintName)
499
    {
500 370
        $constraintName = $this->normalizeIdentifier($constraintName);
501
502 370
        if (! $this->hasForeignKey($constraintName)) {
503
            throw ForeignKeyDoesNotExist::new($constraintName, $this->_name);
504
        }
505
506 370
        unset($this->_fkConstraints[$constraintName]);
507 370
    }
508
509
    /**
510
     * Returns whether this table has a unique constraint with the given name.
511
     *
512
     * @param string $constraintName
513
     *
514
     * @return bool
515
     */
516
    public function hasUniqueConstraint($constraintName)
517
    {
518
        $constraintName = $this->normalizeIdentifier($constraintName);
519
520
        return isset($this->_uniqueConstraints[$constraintName]);
521
    }
522
523
    /**
524
     * Returns the unique constraint with the given name.
525
     *
526
     * @param string $constraintName The constraint name.
527
     *
528
     * @return UniqueConstraint
529
     *
530
     * @throws SchemaException If the foreign key does not exist.
531
     */
532
    public function getUniqueConstraint($constraintName)
533
    {
534
        $constraintName = $this->normalizeIdentifier($constraintName);
535
536
        if (! $this->hasUniqueConstraint($constraintName)) {
537
            throw UniqueConstraintDoesNotExist::new($constraintName, $this->_name);
538
        }
539
540
        return $this->_uniqueConstraints[$constraintName];
541
    }
542
543
    /**
544
     * Removes the unique constraint with the given name.
545
     *
546
     * @param string $constraintName The constraint name.
547
     *
548
     * @return void
549
     *
550
     * @throws SchemaException
551
     */
552
    public function removeUniqueConstraint($constraintName)
553
    {
554
        $constraintName = $this->normalizeIdentifier($constraintName);
555
556
        if (! $this->hasUniqueConstraint($constraintName)) {
557
            throw UniqueConstraintDoesNotExist::new($constraintName, $this->_name);
558
        }
559
560
        unset($this->_uniqueConstraints[$constraintName]);
561
    }
562
563
    /**
564
     * Returns ordered list of columns (primary keys are first, then foreign keys, then the rest)
565
     *
566
     * @return Column[]
567
     */
568 18688
    public function getColumns()
569
    {
570 18688
        $columns = $this->_columns;
571 18688
        $pkCols  = [];
572 18688
        $fkCols  = [];
573
574 18688
        $primaryKey = $this->getPrimaryKey();
575
576 18688
        if ($primaryKey !== null) {
577 18212
            $pkCols = $primaryKey->getColumns();
578
        }
579
580 18688
        foreach ($this->getForeignKeys() as $fk) {
581
            /** @var ForeignKeyConstraint $fk */
582 17628
            $fkCols = array_merge($fkCols, $fk->getColumns());
583
        }
584
585 18688
        $colNames = array_unique(array_merge($pkCols, $fkCols, array_keys($columns)));
586
587
        uksort($columns, static function ($a, $b) use ($colNames) {
588 18366
            return array_search($a, $colNames) >= array_search($b, $colNames);
589 18688
        });
590
591 18688
        return $columns;
592
    }
593
594
    /**
595
     * Returns whether this table has a Column with the given name.
596
     *
597
     * @param string $columnName The column name.
598
     *
599
     * @return bool
600
     */
601 18744
    public function hasColumn($columnName)
602
    {
603 18744
        $columnName = $this->normalizeIdentifier($columnName);
604
605 18744
        return isset($this->_columns[$columnName]);
606
    }
607
608
    /**
609
     * Returns the Column with the given name.
610
     *
611
     * @param string $columnName The column name.
612
     *
613
     * @return Column
614
     *
615
     * @throws SchemaException If the column does not exist.
616
     */
617 18534
    public function getColumn($columnName)
618
    {
619 18534
        $columnName = $this->normalizeIdentifier($columnName);
620
621 18534
        if (! $this->hasColumn($columnName)) {
622 1477
            throw ColumnDoesNotExist::new($columnName, $this->_name);
623
        }
624
625 18532
        return $this->_columns[$columnName];
626
    }
627
628
    /**
629
     * Returns the primary key.
630
     *
631
     * @return Index|null The primary key, or null if this Table has no primary key.
632
     */
633 18698
    public function getPrimaryKey()
634
    {
635 18698
        return $this->hasPrimaryKey()
636 18222
            ? $this->getIndex($this->_primaryKeyName)
637 18698
            : null;
638
    }
639
640
    /**
641
     * Returns the primary key columns.
642
     *
643
     * @return string[]
644
     *
645
     * @throws DBALException
646
     */
647 16150
    public function getPrimaryKeyColumns()
648
    {
649 16150
        $primaryKey = $this->getPrimaryKey();
650
651 16150
        if ($primaryKey === null) {
652
            throw new DBALException('Table ' . $this->getName() . ' has no primary key.');
653
        }
654
655 16150
        return $primaryKey->getColumns();
656
    }
657
658
    /**
659
     * Returns whether this table has a primary key.
660
     *
661
     * @return bool
662
     */
663 18704
    public function hasPrimaryKey()
664
    {
665 18704
        return $this->_primaryKeyName && $this->hasIndex($this->_primaryKeyName);
666
    }
667
668
    /**
669
     * Returns whether this table has an Index with the given name.
670
     *
671
     * @param string $indexName The index name.
672
     *
673
     * @return bool
674
     */
675 18326
    public function hasIndex($indexName)
676
    {
677 18326
        $indexName = $this->normalizeIdentifier($indexName);
678
679 18326
        return isset($this->_indexes[$indexName]);
680
    }
681
682
    /**
683
     * Returns the Index with the given name.
684
     *
685
     * @param string $indexName The index name.
686
     *
687
     * @return Index
688
     *
689
     * @throws SchemaException If the index does not exist.
690
     */
691 18282
    public function getIndex($indexName)
692
    {
693 18282
        $indexName = $this->normalizeIdentifier($indexName);
694
695 18282
        if (! $this->hasIndex($indexName)) {
696 1352
            throw IndexDoesNotExist::new($indexName, $this->_name);
697
        }
698
699 18280
        return $this->_indexes[$indexName];
700
    }
701
702
    /**
703
     * @return Index[]
704
     */
705 18662
    public function getIndexes()
706
    {
707 18662
        return $this->_indexes;
708
    }
709
710
    /**
711
     * Returns the unique constraints.
712
     *
713
     * @return UniqueConstraint[]
714
     */
715 18442
    public function getUniqueConstraints()
716
    {
717 18442
        return $this->_uniqueConstraints;
718
    }
719
720
    /**
721
     * Returns the foreign key constraints.
722
     *
723
     * @return ForeignKeyConstraint[]
724
     */
725 18724
    public function getForeignKeys()
726
    {
727 18724
        return $this->_fkConstraints;
728
    }
729
730
    /**
731
     * @param string $name
732
     *
733
     * @return bool
734
     */
735 16290
    public function hasOption($name)
736
    {
737 16290
        return isset($this->_options[$name]);
738
    }
739
740
    /**
741
     * @param string $name
742
     *
743
     * @return mixed
744
     */
745 15731
    public function getOption($name)
746
    {
747 15731
        return $this->_options[$name];
748
    }
749
750
    /**
751
     * @return mixed[]
752
     */
753 18468
    public function getOptions()
754
    {
755 18468
        return $this->_options;
756
    }
757
758
    /**
759
     * @return void
760
     */
761 17534
    public function visit(Visitor $visitor)
762
    {
763 17534
        $visitor->acceptTable($this);
764
765 17534
        foreach ($this->getColumns() as $column) {
766 17528
            $visitor->acceptColumn($this, $column);
767
        }
768
769 17534
        foreach ($this->getIndexes() as $index) {
770 17514
            $visitor->acceptIndex($this, $index);
771
        }
772
773 17534
        foreach ($this->getForeignKeys() as $constraint) {
774 158
            $visitor->acceptForeignKey($this, $constraint);
775
        }
776 17534
    }
777
778
    /**
779
     * Clone of a Table triggers a deep clone of all affected assets.
780
     *
781
     * @return void
782
     */
783 16832
    public function __clone()
784
    {
785 16832
        foreach ($this->_columns as $k => $column) {
786 16830
            $this->_columns[$k] = clone $column;
787
        }
788
789 16832
        foreach ($this->_indexes as $k => $index) {
790 16808
            $this->_indexes[$k] = clone $index;
791
        }
792
793 16832
        foreach ($this->_fkConstraints as $k => $fk) {
794 15479
            $this->_fkConstraints[$k] = clone $fk;
795 15479
            $this->_fkConstraints[$k]->setLocalTable($this);
796
        }
797 16832
    }
798
799
    /**
800
     * @return int
801
     */
802 17849
    protected function _getMaxIdentifierLength()
803
    {
804 17849
        return $this->_schemaConfig instanceof SchemaConfig
805 17437
            ? $this->_schemaConfig->getMaxIdentifierLength()
806 17849
            : 63;
807
    }
808
809
    /**
810
     * @return void
811
     *
812
     * @throws SchemaException
813
     */
814 18994
    protected function _addColumn(Column $column)
815
    {
816 18994
        $columnName = $column->getName();
817 18994
        $columnName = $this->normalizeIdentifier($columnName);
818
819 18994
        if (isset($this->_columns[$columnName])) {
820 1452
            throw ColumnAlreadyExists::new($this->getName(), $columnName);
821
        }
822
823 18994
        $this->_columns[$columnName] = $column;
824 18994
    }
825
826
    /**
827
     * Adds an index to the table.
828
     *
829
     * @return self
830
     *
831
     * @throws SchemaException
832
     */
833 18622
    protected function _addIndex(Index $indexCandidate)
834
    {
835 18622
        $indexName               = $indexCandidate->getName();
836 18622
        $indexName               = $this->normalizeIdentifier($indexName);
837 18622
        $replacedImplicitIndexes = [];
838
839 18622
        foreach ($this->implicitIndexes as $name => $implicitIndex) {
840 13346
            if (! $implicitIndex->isFullfilledBy($indexCandidate) || ! isset($this->_indexes[$name])) {
841 13338
                continue;
842
            }
843
844 758
            $replacedImplicitIndexes[] = $name;
845
        }
846
847 18622
        if ((isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) ||
848 18622
            ($this->_primaryKeyName !== false && $indexCandidate->isPrimary())
849
        ) {
850 1329
            throw IndexAlreadyExists::new($indexName, $this->_name);
851
        }
852
853 18622
        foreach ($replacedImplicitIndexes as $name) {
854 758
            unset($this->_indexes[$name], $this->implicitIndexes[$name]);
855
        }
856
857 18622
        if ($indexCandidate->isPrimary()) {
858 18378
            $this->_primaryKeyName = $indexName;
859
        }
860
861 18622
        $this->_indexes[$indexName] = $indexCandidate;
862
863 18622
        return $this;
864
    }
865
866
    /**
867
     * @return self
868
     */
869 202
    protected function _addUniqueConstraint(UniqueConstraint $constraint)
870
    {
871 202
        $name = strlen($constraint->getName())
872
            ? $constraint->getName()
873 202
            : $this->_generateIdentifierName(
874 202
                array_merge((array) $this->getName(), $constraint->getColumns()),
875 202
                'fk',
876 202
                $this->_getMaxIdentifierLength()
877
            );
878
879 202
        $name = $this->normalizeIdentifier($name);
880
881 202
        $this->_uniqueConstraints[$name] = $constraint;
882
883
        // If there is already an index that fulfills this requirements drop the request. In the case of __construct
884
        // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates.
885
        // This creates computation overhead in this case, however no duplicate indexes are ever added (column based).
886 202
        $indexName = $this->_generateIdentifierName(
887 202
            array_merge([$this->getName()], $constraint->getColumns()),
888 202
            'idx',
889 202
            $this->_getMaxIdentifierLength()
890
        );
891
892 202
        $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, true, false);
893
894 202
        foreach ($this->_indexes as $existingIndex) {
895
            if ($indexCandidate->isFullfilledBy($existingIndex)) {
896
                return $this;
897
            }
898
        }
899
900 202
        $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
901
902 202
        return $this;
903
    }
904
905
    /**
906
     * @return self
907
     */
908 17728
    protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint)
909
    {
910 17728
        $constraint->setLocalTable($this);
911
912 17728
        $name = strlen($constraint->getName())
913 17647
            ? $constraint->getName()
914 1848
            : $this->_generateIdentifierName(
915 1848
                array_merge((array) $this->getName(), $constraint->getLocalColumns()),
916 1848
                'fk',
917 17728
                $this->_getMaxIdentifierLength()
918
            );
919
920 17728
        $name = $this->normalizeIdentifier($name);
921
922 17728
        $this->_fkConstraints[$name] = $constraint;
923
924
        // add an explicit index on the foreign key columns.
925
        // If there is already an index that fulfills this requirements drop the request. In the case of __construct
926
        // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates.
927
        // This creates computation overhead in this case, however no duplicate indexes are ever added (column based).
928 17728
        $indexName = $this->_generateIdentifierName(
929 17728
            array_merge([$this->getName()], $constraint->getColumns()),
930 17728
            'idx',
931 17728
            $this->_getMaxIdentifierLength()
932
        );
933
934 17728
        $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false);
935
936 17728
        foreach ($this->_indexes as $existingIndex) {
937 17676
            if ($indexCandidate->isFullfilledBy($existingIndex)) {
938 15945
                return $this;
939
            }
940
        }
941
942 17680
        $this->_addIndex($indexCandidate);
943 17680
        $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
944
945 17680
        return $this;
946
    }
947
948
    /**
949
     * Normalizes a given identifier.
950
     *
951
     * Trims quotes and lowercases the given identifier.
952
     *
953
     * @param string|null $identifier The identifier to normalize.
954
     *
955
     * @return string The normalized identifier.
956
     */
957 19002
    private function normalizeIdentifier($identifier)
958
    {
959 19002
        if ($identifier === null) {
960 477
            return '';
961
        }
962
963 19002
        return $this->trimQuotes(strtolower($identifier));
964
    }
965
966
    /**
967
     * @param mixed[] $columns
968
     * @param string  $indexName
969
     * @param mixed[] $flags
970
     * @param mixed[] $options
971
     *
972
     * @return UniqueConstraint
973
     *
974
     * @throws SchemaException
975
     */
976
    private function _createUniqueConstraint(array $columns, $indexName, array $flags = [], array $options = [])
977
    {
978
        if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) {
979
            throw IndexNameInvalid::new($indexName);
980
        }
981
982
        foreach ($columns as $index => $value) {
983
            if (is_string($index)) {
984
                $columnName = $index;
985
            } else {
986
                $columnName = $value;
987
            }
988
989
            if (! $this->hasColumn($columnName)) {
990
                throw ColumnDoesNotExist::new($columnName, $this->_name);
991
            }
992
        }
993
994
        return new UniqueConstraint($indexName, $columns, $flags, $options);
995
    }
996
997
    /**
998
     * @param mixed[]  $columns
999
     * @param string   $indexName
1000
     * @param bool     $isUnique
1001
     * @param bool     $isPrimary
1002
     * @param string[] $flags
1003
     * @param mixed[]  $options
1004
     *
1005
     * @return Index
1006
     *
1007
     * @throws SchemaException
1008
     */
1009 18614
    private function _createIndex(array $columns, $indexName, $isUnique, $isPrimary, array $flags = [], array $options = [])
1010
    {
1011 18614
        if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) {
1012 1152
            throw IndexNameInvalid::new($indexName);
1013
        }
1014
1015 18612
        foreach ($columns as $index => $value) {
1016 18610
            if (is_string($index)) {
1017
                $columnName = $index;
1018
            } else {
1019 18610
                $columnName = $value;
1020
            }
1021
1022 18610
            if (! $this->hasColumn($columnName)) {
1023 1921
                throw ColumnDoesNotExist::new($columnName, $this->_name);
1024
            }
1025
        }
1026
1027 18610
        return new Index($indexName, $columns, $isUnique, $isPrimary, $flags, $options);
1028
    }
1029
}
1030