Completed
Pull Request — develop (#3533)
by
unknown
16:31 queued 01:26
created

Table::removeForeignKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 9
ccs 4
cts 5
cp 0.8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2.032
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\Visitor\Visitor;
9
use Doctrine\DBAL\Types\Type;
10
use function array_keys;
11
use function array_merge;
12
use function array_search;
13
use function array_unique;
14
use function in_array;
15
use function is_string;
16
use function preg_match;
17
use function strlen;
18
use function strtolower;
19
use function uksort;
20
21
/**
22
 * Object Representation of a table.
23
 */
24
class Table extends AbstractAsset
25
{
26
    /** @var Column[] */
27
    protected $_columns = [];
28
29
    /** @var Index[] */
30
    private $implicitIndexes = [];
31
32
    /** @var Index[] */
33
    protected $_indexes = [];
34
35
    /** @var string */
36
    protected $_primaryKeyName = false;
37
38
    /** @var UniqueConstraint[] */
39
    protected $_uniqueConstraints = [];
40
41
    /** @var ForeignKeyConstraint[] */
42
    protected $_fkConstraints = [];
43
44
    /** @var mixed[] */
45
    protected $_options = [];
46
47
    /** @var SchemaConfig|null */
48
    protected $_schemaConfig = null;
49
50
    /**
51
     * @param string                 $tableName
52
     * @param Column[]               $columns
53
     * @param Index[]                $indexes
54
     * @param UniqueConstraint[]     $uniqueConstraints
55
     * @param ForeignKeyConstraint[] $fkConstraints
56
     * @param mixed[]                $options
57
     *
58
     * @throws DBALException
59
     */
60 20121
    public function __construct(
61
        $tableName,
62
        array $columns = [],
63
        array $indexes = [],
64
        array $uniqueConstraints = [],
65
        array $fkConstraints = [],
66
        array $options = []
67
    ) {
68 20121
        if (strlen($tableName) === 0) {
69 1691
            throw DBALException::invalidTableName($tableName);
70
        }
71
72 20120
        $this->_setName($tableName);
73
74 20120
        foreach ($columns as $column) {
75 18551
            $this->_addColumn($column);
76
        }
77
78 20119
        foreach ($indexes as $idx) {
79 18477
            $this->_addIndex($idx);
80
        }
81
82 20117
        foreach ($uniqueConstraints as $uniqueConstraint) {
83 209
            $this->_addUniqueConstraint($uniqueConstraint);
84
        }
85
86 20117
        foreach ($fkConstraints as $fkConstraint) {
87 17614
            $this->_addForeignKeyConstraint($fkConstraint);
88
        }
89
90 20117
        $this->_options = $options;
91 20117
    }
92
93
    /**
94
     * @return void
95
     */
96 19074
    public function setSchemaConfig(SchemaConfig $schemaConfig)
97
    {
98 19074
        $this->_schemaConfig = $schemaConfig;
99 19074
    }
100
101
    /**
102
     * Sets the Primary Key.
103
     *
104
     * @param string[]     $columnNames
105
     * @param string|false $indexName
106
     *
107
     * @return self
108
     */
109 19701
    public function setPrimaryKey(array $columnNames, $indexName = false)
110
    {
111 19701
        $this->_addIndex($this->_createIndex($columnNames, $indexName ?: 'primary', true, true));
112
113 19701
        foreach ($columnNames as $columnName) {
114 19701
            $column = $this->getColumn($columnName);
115 19701
            $column->setNotnull(true);
116
        }
117
118 19701
        return $this;
119
    }
120
121
    /**
122
     * @param mixed[]     $columnNames
123
     * @param string|null $indexName
124
     * @param string[]    $flags
125
     * @param mixed[]     $options
126
     *
127
     * @return self
128
     */
129
    public function addUniqueConstraint(array $columnNames, $indexName = null, array $flags = [], array $options = [])
130
    {
131
        if ($indexName === null) {
132
            $indexName = $this->_generateIdentifierName(
133
                array_merge([$this->getName()], $columnNames),
134
                'uniq',
135
                $this->_getMaxIdentifierLength()
136
            );
137
        }
138
139
        return $this->_addUniqueConstraint($this->_createUniqueConstraint($columnNames, $indexName, $flags, $options));
140
    }
141
142
    /**
143
     * @param string[]    $columnNames
144
     * @param string|null $indexName
145
     * @param string[]    $flags
146
     * @param mixed[]     $options
147
     *
148
     * @return self
149
     */
150 18056
    public function addIndex(array $columnNames, $indexName = null, array $flags = [], array $options = [])
151
    {
152 18056
        if ($indexName === null) {
153 17647
            $indexName = $this->_generateIdentifierName(
154 17647
                array_merge([$this->getName()], $columnNames),
155 17647
                'idx',
156 17647
                $this->_getMaxIdentifierLength()
157
            );
158
        }
159
160 18056
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options));
161
    }
162
163
    /**
164
     * Drops the primary key from this table.
165
     *
166
     * @return void
167
     */
168 16947
    public function dropPrimaryKey()
169
    {
170 16947
        $this->dropIndex($this->_primaryKeyName);
171 16947
        $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...
172 16947
    }
173
174
    /**
175
     * Drops an index from this table.
176
     *
177
     * @param string $indexName The index name.
178
     *
179
     * @return void
180
     *
181
     * @throws SchemaException If the index does not exist.
182
     */
183 16971
    public function dropIndex($indexName)
184
    {
185 16971
        $indexName = $this->normalizeIdentifier($indexName);
186
187 16971
        if (! $this->hasIndex($indexName)) {
188
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
189
        }
190
191 16971
        unset($this->_indexes[$indexName]);
192 16971
    }
193
194
    /**
195
     * @param string[]    $columnNames
196
     * @param string|null $indexName
197
     * @param mixed[]     $options
198
     *
199
     * @return self
200
     */
201 18955
    public function addUniqueIndex(array $columnNames, $indexName = null, array $options = [])
202
    {
203 18955
        if ($indexName === null) {
204 18622
            $indexName = $this->_generateIdentifierName(
205 18622
                array_merge([$this->getName()], $columnNames),
206 18622
                'uniq',
207 18622
                $this->_getMaxIdentifierLength()
208
            );
209
        }
210
211 18955
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options));
212
    }
213
214
    /**
215
     * Renames an index.
216
     *
217
     * @param string      $oldIndexName The name of the index to rename from.
218
     * @param string|null $newIndexName The name of the index to rename to.
219
     *                                  If null is given, the index name will be auto-generated.
220
     *
221
     * @return self This table instance.
222
     *
223
     * @throws SchemaException If no index exists for the given current name
224
     *                         or if an index with the given new name already exists on this table.
225
     */
226 16612
    public function renameIndex($oldIndexName, $newIndexName = null)
227
    {
228 16612
        $oldIndexName           = $this->normalizeIdentifier($oldIndexName);
229 16612
        $normalizedNewIndexName = $this->normalizeIdentifier($newIndexName);
230
231 16612
        if ($oldIndexName === $normalizedNewIndexName) {
232 503
            return $this;
233
        }
234
235 16612
        if (! $this->hasIndex($oldIndexName)) {
236 417
            throw SchemaException::indexDoesNotExist($oldIndexName, $this->_name);
237
        }
238
239 16611
        if ($this->hasIndex($normalizedNewIndexName)) {
240 391
            throw SchemaException::indexAlreadyExists($normalizedNewIndexName, $this->_name);
241
        }
242
243 16610
        $oldIndex = $this->_indexes[$oldIndexName];
244
245 16610
        if ($oldIndex->isPrimary()) {
246 495
            $this->dropPrimaryKey();
247
248 495
            return $this->setPrimaryKey($oldIndex->getColumns(), $newIndexName ?? false);
249
        }
250
251 16610
        unset($this->_indexes[$oldIndexName]);
252
253 16610
        if ($oldIndex->isUnique()) {
254 496
            return $this->addUniqueIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getOptions());
255
        }
256
257 16609
        return $this->addIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getFlags(), $oldIndex->getOptions());
258
    }
259
260
    /**
261
     * Checks if an index begins in the order of the given columns.
262
     *
263
     * @param string[] $columnNames
264
     *
265
     * @return bool
266
     */
267 16807
    public function columnsAreIndexed(array $columnNames)
268
    {
269 16807
        foreach ($this->getIndexes() as $index) {
270
            /** @var $index Index */
271 16807
            if ($index->spansColumns($columnNames)) {
272 16807
                return true;
273
            }
274
        }
275
276
        return false;
277
    }
278
279
    /**
280
     * @param string  $columnName
281
     * @param string  $typeName
282
     * @param mixed[] $options
283
     *
284
     * @return Column
285
     */
286 19996
    public function addColumn($columnName, $typeName, array $options = [])
287
    {
288 19996
        $column = new Column($columnName, Type::getType($typeName), $options);
289
290 19996
        $this->_addColumn($column);
291
292 19996
        return $column;
293
    }
294
295
    /**
296
     * Renames a Column.
297
     *
298
     * @deprecated
299
     *
300
     * @param string $oldColumnName
301
     * @param string $newColumnName
302
     *
303
     * @throws DBALException
304
     */
305
    public function renameColumn($oldColumnName, $newColumnName)
0 ignored issues
show
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

305
    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...
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

305
    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...
306
    {
307
        throw new DBALException(
308
            'Table#renameColumn() was removed, because it drops and recreates the column instead. ' .
309
            'There is no fix available, because a schema diff cannot reliably detect if a column ' .
310
            'was renamed or one column was created and another one dropped.'
311
        );
312
    }
313
314
    /**
315
     * Change Column Details.
316
     *
317
     * @param string  $columnName
318
     * @param mixed[] $options
319
     *
320
     * @return self
321
     */
322 17069
    public function changeColumn($columnName, array $options)
323
    {
324 17069
        $column = $this->getColumn($columnName);
325
326 17069
        $column->setOptions($options);
327
328 17069
        return $this;
329
    }
330
331
    /**
332
     * Drops a Column from the Table.
333
     *
334
     * @param string $columnName
335
     *
336
     * @return self
337
     */
338 1569
    public function dropColumn($columnName)
339
    {
340 1569
        $columnName = $this->normalizeIdentifier($columnName);
341
342 1569
        unset($this->_columns[$columnName]);
343
344 1569
        return $this;
345
    }
346
347
    /**
348
     * Adds a foreign key constraint.
349
     *
350
     * Name is inferred from the local columns.
351
     *
352
     * @param Table|string $foreignTable       Table schema instance or table name
353
     * @param string[]     $localColumnNames
354
     * @param string[]     $foreignColumnNames
355
     * @param mixed[]      $options
356
     * @param string|null  $constraintName
357
     *
358
     * @return self
359
     */
360 19039
    public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [], $constraintName = null)
361
    {
362 19039
        if (! $constraintName) {
363 18990
            $constraintName = $this->_generateIdentifierName(
364 18990
                array_merge((array) $this->getName(), $localColumnNames),
365 18990
                'fk',
366 18990
                $this->_getMaxIdentifierLength()
367
            );
368
        }
369
370 19039
        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

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