Completed
Pull Request — 2.10.x (#4009)
by Grégoire
08:50
created

Table::renameIndex()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 33
ccs 17
cts 17
cp 1
rs 8.7697
c 0
b 0
f 0
cc 6
nc 6
nop 2
crap 6
1
<?php
2
3
namespace Doctrine\DBAL\Schema;
4
5
use Doctrine\DBAL\DBALException;
6
use Doctrine\DBAL\Schema\Visitor\Visitor;
7
use Doctrine\DBAL\Types\Type;
8
use function array_filter;
9
use function array_merge;
10
use function in_array;
11
use function preg_match;
12
use function strlen;
13
use function strtolower;
14
use const ARRAY_FILTER_USE_KEY;
15
16
/**
17
 * Object Representation of a table.
18
 */
19
class Table extends AbstractAsset
20
{
21
    /** @var Column[] */
22
    protected $_columns = [];
23
24
    /** @var Index[] */
25
    private $implicitIndexes = [];
26
27
    /** @var Index[] */
28
    protected $_indexes = [];
29
30
    /** @var string */
31
    protected $_primaryKeyName = false;
32
33
    /** @var ForeignKeyConstraint[] */
34
    protected $_fkConstraints = [];
35
36
    /** @var mixed[] */
37
    protected $_options = [
38
        'create_options' => [],
39
    ];
40
41
    /** @var SchemaConfig|null */
42
    protected $_schemaConfig = null;
43
44
    /**
45
     * @param string                 $tableName
46
     * @param Column[]               $columns
47
     * @param Index[]                $indexes
48
     * @param ForeignKeyConstraint[] $fkConstraints
49
     * @param int                    $idGeneratorType
50
     * @param mixed[]                $options
51
     *
52
     * @throws DBALException
53
     */
54 22007
    public function __construct($tableName, array $columns = [], array $indexes = [], array $fkConstraints = [], $idGeneratorType = 0, array $options = [])
0 ignored issues
show
Unused Code introduced by
The parameter $idGeneratorType is not used and could be removed.

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

Loading history...
55
    {
56 22007
        if (strlen($tableName) === 0) {
57 1432
            throw DBALException::invalidTableName($tableName);
58
        }
59
60 22006
        $this->_setName($tableName);
61
62 22006
        foreach ($columns as $column) {
63 20320
            $this->_addColumn($column);
64
        }
65
66 22005
        foreach ($indexes as $idx) {
67 20163
            $this->_addIndex($idx);
68
        }
69
70 22003
        foreach ($fkConstraints as $constraint) {
71 19550
            $this->_addForeignKeyConstraint($constraint);
72
        }
73
74 22003
        $this->_options = array_merge($this->_options, $options);
75 22003
    }
76
77
    /**
78
     * @return void
79
     */
80 20798
    public function setSchemaConfig(SchemaConfig $schemaConfig)
81
    {
82 20798
        $this->_schemaConfig = $schemaConfig;
83 20798
    }
84
85
    /**
86
     * @return int
87
     */
88 20973
    protected function _getMaxIdentifierLength()
89
    {
90 20973
        if ($this->_schemaConfig instanceof SchemaConfig) {
91 20672
            return $this->_schemaConfig->getMaxIdentifierLength();
92
        }
93
94 20952
        return 63;
95
    }
96
97
    /**
98
     * Sets the Primary Key.
99
     *
100
     * @param string[]     $columnNames
101
     * @param string|false $indexName
102
     *
103
     * @return self
104
     */
105 21463
    public function setPrimaryKey(array $columnNames, $indexName = false)
106
    {
107 21463
        $this->_addIndex($this->_createIndex($columnNames, $indexName ?: 'primary', true, true));
108
109 21463
        foreach ($columnNames as $columnName) {
110 21463
            $column = $this->getColumn($columnName);
111 21463
            $column->setNotnull(true);
112
        }
113
114 21463
        return $this;
115
    }
116
117
    /**
118
     * @param string[]    $columnNames
119
     * @param string|null $indexName
120
     * @param string[]    $flags
121
     * @param mixed[]     $options
122
     *
123
     * @return self
124
     */
125 19724
    public function addIndex(array $columnNames, $indexName = null, array $flags = [], array $options = [])
126
    {
127 19724
        if ($indexName === null) {
128 19265
            $indexName = $this->_generateIdentifierName(
129 19265
                array_merge([$this->getName()], $columnNames),
130 19265
                'idx',
131 19265
                $this->_getMaxIdentifierLength()
132
            );
133
        }
134
135 19724
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options));
136
    }
137
138
    /**
139
     * Drops the primary key from this table.
140
     *
141
     * @return void
142
     */
143 18392
    public function dropPrimaryKey()
144
    {
145 18392
        $this->dropIndex($this->_primaryKeyName);
146 18392
        $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...
147 18392
    }
148
149
    /**
150
     * Drops an index from this table.
151
     *
152
     * @param string $indexName The index name.
153
     *
154
     * @return void
155
     *
156
     * @throws SchemaException If the index does not exist.
157
     */
158 18416
    public function dropIndex($indexName)
159
    {
160 18416
        $indexName = $this->normalizeIdentifier($indexName);
161 18416
        if (! $this->hasIndex($indexName)) {
162
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
163
        }
164
165 18416
        unset($this->_indexes[$indexName]);
166 18416
    }
167
168
    /**
169
     * @param string[]    $columnNames
170
     * @param string|null $indexName
171
     * @param mixed[]     $options
172
     *
173
     * @return self
174
     */
175 20678
    public function addUniqueIndex(array $columnNames, $indexName = null, array $options = [])
176
    {
177 20678
        if ($indexName === null) {
178 20324
            $indexName = $this->_generateIdentifierName(
179 20324
                array_merge([$this->getName()], $columnNames),
180 20324
                'uniq',
181 20324
                $this->_getMaxIdentifierLength()
182
            );
183
        }
184
185 20678
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options));
186
    }
187
188
    /**
189
     * Renames an index.
190
     *
191
     * @param string      $oldIndexName The name of the index to rename from.
192
     * @param string|null $newIndexName The name of the index to rename to.
193
     *                                  If null is given, the index name will be auto-generated.
194
     *
195
     * @return self This table instance.
196
     *
197
     * @throws SchemaException If no index exists for the given current name
198
     *                         or if an index with the given new name already exists on this table.
199
     */
200 19052
    public function renameIndex($oldIndexName, $newIndexName = null)
201
    {
202 19052
        $oldIndexName           = $this->normalizeIdentifier($oldIndexName);
203 19052
        $normalizedNewIndexName = $this->normalizeIdentifier($newIndexName);
204
205 19052
        if ($oldIndexName === $normalizedNewIndexName) {
206 382
            return $this;
207
        }
208
209 19052
        if (! $this->hasIndex($oldIndexName)) {
210 305
            throw SchemaException::indexDoesNotExist($oldIndexName, $this->_name);
211
        }
212
213 19051
        if ($this->hasIndex($normalizedNewIndexName)) {
214 282
            throw SchemaException::indexAlreadyExists($normalizedNewIndexName, $this->_name);
215
        }
216
217 19050
        $oldIndex = $this->_indexes[$oldIndexName];
218
219 19050
        if ($oldIndex->isPrimary()) {
220 374
            $this->dropPrimaryKey();
221
222 374
            return $this->setPrimaryKey($oldIndex->getColumns(), $newIndexName ?? false);
223
        }
224
225 19050
        unset($this->_indexes[$oldIndexName]);
226
227 19050
        if ($oldIndex->isUnique()) {
228 375
            return $this->addUniqueIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getOptions());
229
        }
230
231 19049
        return $this->addIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getFlags(), $oldIndex->getOptions());
232
    }
233
234
    /**
235
     * Checks if an index begins in the order of the given columns.
236
     *
237
     * @param string[] $columnNames
238
     *
239
     * @return bool
240
     */
241 19226
    public function columnsAreIndexed(array $columnNames)
242
    {
243 19226
        foreach ($this->getIndexes() as $index) {
244 19226
            if ($index->spansColumns($columnNames)) {
245 19226
                return true;
246
            }
247
        }
248
249
        return false;
250
    }
251
252
    /**
253
     * @param string[] $columnNames
254
     * @param string   $indexName
255
     * @param bool     $isUnique
256
     * @param bool     $isPrimary
257
     * @param string[] $flags
258
     * @param mixed[]  $options
259
     *
260
     * @return Index
261
     *
262
     * @throws SchemaException
263
     */
264 21612
    private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = [], array $options = [])
265
    {
266 21612
        if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) {
267 995
            throw SchemaException::indexNameInvalid($indexName);
268
        }
269
270 21611
        foreach ($columnNames as $columnName) {
271 21610
            if (! $this->hasColumn($columnName)) {
272 972
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
273
            }
274
        }
275
276 21610
        return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $options);
277
    }
278
279
    /**
280
     * @param string  $columnName
281
     * @param string  $typeName
282
     * @param mixed[] $options
283
     *
284
     * @return Column
285
     */
286 21829
    public function addColumn($columnName, $typeName, array $options = [])
287
    {
288 21829
        $column = new Column($columnName, Type::getType($typeName), $options);
289
290 21829
        $this->_addColumn($column);
291
292 21829
        return $column;
293
    }
294
295
    /**
296
     * Renames a Column.
297
     *
298
     * @deprecated
299
     *
300
     * @param string $oldColumnName
301
     * @param string $newColumnName
302
     *
303
     * @return void
304
     *
305
     * @throws DBALException
306
     */
307
    public function renameColumn($oldColumnName, $newColumnName)
0 ignored issues
show
Unused Code introduced by
The parameter $oldColumnName is not used and could be removed.

This check looks from 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.

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

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

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

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

Loading history...
363
    }
364
365
    /**
366
     * Adds a foreign key constraint.
367
     *
368
     * Name is to be generated by the database itself.
369
     *
370
     * @deprecated Use {@link addForeignKeyConstraint}
371
     *
372
     * @param Table|string $foreignTable       Table schema instance or table name
373
     * @param string[]     $localColumnNames
374
     * @param string[]     $foreignColumnNames
375
     * @param mixed[]      $options
376
     *
377
     * @return self
378
     */
379 14891
    public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [])
380
    {
381 14891
        return $this->addForeignKeyConstraint($foreignTable, $localColumnNames, $foreignColumnNames, $options);
382
    }
383
384
    /**
385
     * Adds a foreign key constraint with a given name.
386
     *
387
     * @deprecated Use {@link addForeignKeyConstraint}
388
     *
389
     * @param string       $name
390
     * @param Table|string $foreignTable       Table schema instance or table name
391
     * @param string[]     $localColumnNames
392
     * @param string[]     $foreignColumnNames
393
     * @param mixed[]      $options
394
     *
395
     * @return self
396
     *
397
     * @throws SchemaException
398
     */
399 20917
    public function addNamedForeignKeyConstraint($name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [])
400
    {
401 20917
        if ($foreignTable instanceof Table) {
402 20821
            foreach ($foreignColumnNames as $columnName) {
403 20821
                if (! $foreignTable->hasColumn($columnName)) {
404 903
                    throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName());
405
                }
406
            }
407
        }
408
409 20916
        foreach ($localColumnNames as $columnName) {
410 20916
            if (! $this->hasColumn($columnName)) {
411 926
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
412
            }
413
        }
414
415 20915
        $constraint = new ForeignKeyConstraint(
416 20915
            $localColumnNames,
417
            $foreignTable,
418
            $foreignColumnNames,
419
            $name,
420
            $options
421
        );
422 20915
        $this->_addForeignKeyConstraint($constraint);
423
424 20915
        return $this;
425
    }
426
427
    /**
428
     * @param string $name
429
     * @param mixed  $value
430
     *
431
     * @return self
432
     */
433 20158
    public function addOption($name, $value)
434
    {
435 20158
        $this->_options[$name] = $value;
436
437 20158
        return $this;
438
    }
439
440
    /**
441
     * @return void
442
     *
443
     * @throws SchemaException
444
     */
445 21881
    protected function _addColumn(Column $column)
446
    {
447 21881
        $columnName = $column->getName();
448 21881
        $columnName = $this->normalizeIdentifier($columnName);
449
450 21881
        if (isset($this->_columns[$columnName])) {
451 1271
            throw SchemaException::columnAlreadyExists($this->getName(), $columnName);
452
        }
453
454 21881
        $this->_columns[$columnName] = $column;
455 21881
    }
456
457
    /**
458
     * Adds an index to the table.
459
     *
460
     * @return self
461
     *
462
     * @throws SchemaException
463
     */
464 21617
    protected function _addIndex(Index $indexCandidate)
465
    {
466 21617
        $indexName               = $indexCandidate->getName();
467 21617
        $indexName               = $this->normalizeIdentifier($indexName);
468 21617
        $replacedImplicitIndexes = [];
469
470 21617
        foreach ($this->implicitIndexes as $name => $implicitIndex) {
471 17703
            if (! $implicitIndex->isFullfilledBy($indexCandidate) || ! isset($this->_indexes[$name])) {
472 17699
                continue;
473
            }
474
475 630
            $replacedImplicitIndexes[] = $name;
476
        }
477
478 21617
        if ((isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) ||
479 21617
            ($this->_primaryKeyName !== false && $indexCandidate->isPrimary())
480
        ) {
481 1157
            throw SchemaException::indexAlreadyExists($indexName, $this->_name);
482
        }
483
484 21617
        foreach ($replacedImplicitIndexes as $name) {
485 630
            unset($this->_indexes[$name], $this->implicitIndexes[$name]);
486
        }
487
488 21617
        if ($indexCandidate->isPrimary()) {
489 21465
            $this->_primaryKeyName = $indexName;
490
        }
491
492 21617
        $this->_indexes[$indexName] = $indexCandidate;
493
494 21617
        return $this;
495
    }
496
497
    /**
498
     * @return void
499
     */
500 20921
    protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint)
501
    {
502 20921
        $constraint->setLocalTable($this);
503
504 20921
        if (strlen($constraint->getName())) {
505 20920
            $name = $constraint->getName();
506
        } else {
507 1891
            $name = $this->_generateIdentifierName(
508 1891
                array_merge((array) $this->getName(), $constraint->getLocalColumns()),
509 1891
                'fk',
510 1891
                $this->_getMaxIdentifierLength()
511
            );
512
        }
513
514 20921
        $name = $this->normalizeIdentifier($name);
515
516 20921
        $this->_fkConstraints[$name] = $constraint;
517
518
        // add an explicit index on the foreign key columns. If there is already an index that fulfils this requirements drop the request.
519
        // In the case of __construct calling this method during hydration from schema-details all the explicitly added indexes
520
        // lead to duplicates. This creates computation overhead in this case, however no duplicate indexes are ever added (based on columns).
521 20921
        $indexName      = $this->_generateIdentifierName(
522 20921
            array_merge([$this->getName()], $constraint->getColumns()),
523 20921
            'idx',
524 20921
            $this->_getMaxIdentifierLength()
525
        );
526 20921
        $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false);
527
528 20921
        foreach ($this->_indexes as $existingIndex) {
529 20882
            if ($indexCandidate->isFullfilledBy($existingIndex)) {
530 19595
                return;
531
            }
532
        }
533
534 20890
        $this->_addIndex($indexCandidate);
535 20890
        $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
536 20890
    }
537
538
    /**
539
     * Returns whether this table has a foreign key constraint with the given name.
540
     *
541
     * @param string $constraintName
542
     *
543
     * @return bool
544
     */
545 19050
    public function hasForeignKey($constraintName)
546
    {
547 19050
        $constraintName = $this->normalizeIdentifier($constraintName);
548
549 19050
        return isset($this->_fkConstraints[$constraintName]);
550
    }
551
552
    /**
553
     * Returns the foreign key constraint with the given name.
554
     *
555
     * @param string $constraintName The constraint name.
556
     *
557
     * @return ForeignKeyConstraint
558
     *
559
     * @throws SchemaException If the foreign key does not exist.
560
     */
561 267
    public function getForeignKey($constraintName)
562
    {
563 267
        $constraintName = $this->normalizeIdentifier($constraintName);
564 267
        if (! $this->hasForeignKey($constraintName)) {
565
            throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
566
        }
567
568 267
        return $this->_fkConstraints[$constraintName];
569
    }
570
571
    /**
572
     * Removes the foreign key constraint with the given name.
573
     *
574
     * @param string $constraintName The constraint name.
575
     *
576
     * @return void
577
     *
578
     * @throws SchemaException
579
     */
580 268
    public function removeForeignKey($constraintName)
581
    {
582 268
        $constraintName = $this->normalizeIdentifier($constraintName);
583 268
        if (! $this->hasForeignKey($constraintName)) {
584
            throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
585
        }
586
587 268
        unset($this->_fkConstraints[$constraintName]);
588 268
    }
589
590
    /**
591
     * Returns ordered list of columns (primary keys are first, then foreign keys, then the rest)
592
     *
593
     * @return Column[]
594
     */
595 21656
    public function getColumns()
596
    {
597 21656
        $primaryKey        = $this->getPrimaryKey();
598 21656
        $primaryKeyColumns = [];
599
600 21656
        if ($primaryKey !== null) {
601 21345
            $primaryKeyColumns = $this->filterColumns($primaryKey->getColumns());
602
        }
603
604 21656
        return array_merge($primaryKeyColumns, $this->getForeignKeyColumns(), $this->_columns);
605
    }
606
607
    /**
608
     * Returns foreign key columns
609
     *
610
     * @return Column[]
611
     */
612 21656
    private function getForeignKeyColumns()
613
    {
614 21656
        $foreignKeyColumns = [];
615 21656
        foreach ($this->getForeignKeys() as $foreignKey) {
616 20858
            $foreignKeyColumns = array_merge($foreignKeyColumns, $foreignKey->getColumns());
617
        }
618
619 21656
        return $this->filterColumns($foreignKeyColumns);
620
    }
621
622
    /**
623
     * Returns only columns that have specified names
624
     *
625
     * @param string[] $columnNames
626
     *
627
     * @return Column[]
628
     */
629 21656
    private function filterColumns(array $columnNames)
630
    {
631
        return array_filter($this->_columns, static function ($columnName) use ($columnNames) {
632 21630
            return in_array($columnName, $columnNames, true);
633 21656
        }, ARRAY_FILTER_USE_KEY);
634
    }
635
636
    /**
637
     * Returns whether this table has a Column with the given name.
638
     *
639
     * @param string $columnName The column name.
640
     *
641
     * @return bool
642
     */
643 21711
    public function hasColumn($columnName)
644
    {
645 21711
        $columnName = $this->normalizeIdentifier($columnName);
646
647 21711
        return isset($this->_columns[$columnName]);
648
    }
649
650
    /**
651
     * Returns the Column with the given name.
652
     *
653
     * @param string $columnName The column name.
654
     *
655
     * @return Column
656
     *
657
     * @throws SchemaException If the column does not exist.
658
     */
659 21577
    public function getColumn($columnName)
660
    {
661 21577
        $columnName = $this->normalizeIdentifier($columnName);
662 21577
        if (! $this->hasColumn($columnName)) {
663 1294
            throw SchemaException::columnDoesNotExist($columnName, $this->_name);
664
        }
665
666 21576
        return $this->_columns[$columnName];
667
    }
668
669
    /**
670
     * Returns the primary key.
671
     *
672
     * @return Index|null The primary key, or null if this Table has no primary key.
673
     */
674 21661
    public function getPrimaryKey()
675
    {
676 21661
        if (! $this->hasPrimaryKey()) {
677 21126
            return null;
678
        }
679
680 21350
        return $this->getIndex($this->_primaryKeyName);
681
    }
682
683
    /**
684
     * Returns the primary key columns.
685
     *
686
     * @return string[]
687
     *
688
     * @throws DBALException
689
     */
690 18387
    public function getPrimaryKeyColumns()
691
    {
692 18387
        $primaryKey = $this->getPrimaryKey();
693
694 18387
        if ($primaryKey === null) {
695
            throw new DBALException('Table ' . $this->getName() . ' has no primary key.');
696
        }
697
698 18387
        return $primaryKey->getColumns();
699
    }
700
701
    /**
702
     * Returns whether this table has a primary key.
703
     *
704
     * @return bool
705
     */
706 21664
    public function hasPrimaryKey()
707
    {
708 21664
        return $this->_primaryKeyName && $this->hasIndex($this->_primaryKeyName);
709
    }
710
711
    /**
712
     * Returns whether this table has an Index with the given name.
713
     *
714
     * @param string $indexName The index name.
715
     *
716
     * @return bool
717
     */
718 21402
    public function hasIndex($indexName)
719
    {
720 21402
        $indexName = $this->normalizeIdentifier($indexName);
721
722 21402
        return isset($this->_indexes[$indexName]);
723
    }
724
725
    /**
726
     * Returns the Index with the given name.
727
     *
728
     * @param string $indexName The index name.
729
     *
730
     * @return Index
731
     *
732
     * @throws SchemaException If the index does not exist.
733
     */
734 21380
    public function getIndex($indexName)
735
    {
736 21380
        $indexName = $this->normalizeIdentifier($indexName);
737 21380
        if (! $this->hasIndex($indexName)) {
738 1179
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
739
        }
740
741 21379
        return $this->_indexes[$indexName];
742
    }
743
744
    /**
745
     * @return Index[]
746
     */
747 21628
    public function getIndexes()
748
    {
749 21628
        return $this->_indexes;
750
    }
751
752
    /**
753
     * Returns the foreign key constraints.
754
     *
755
     * @return ForeignKeyConstraint[]
756
     */
757 21674
    public function getForeignKeys()
758
    {
759 21674
        return $this->_fkConstraints;
760
    }
761
762
    /**
763
     * @param string $name
764
     *
765
     * @return bool
766
     */
767 20587
    public function hasOption($name)
768
    {
769 20587
        return isset($this->_options[$name]);
770
    }
771
772
    /**
773
     * @param string $name
774
     *
775
     * @return mixed
776
     */
777 18907
    public function getOption($name)
778
    {
779 18907
        return $this->_options[$name];
780
    }
781
782
    /**
783
     * @return mixed[]
784
     */
785 21521
    public function getOptions()
786
    {
787 21521
        return $this->_options;
788
    }
789
790
    /**
791
     * @return void
792
     */
793 20704
    public function visit(Visitor $visitor)
794
    {
795 20704
        $visitor->acceptTable($this);
796
797 20704
        foreach ($this->getColumns() as $column) {
798 20701
            $visitor->acceptColumn($this, $column);
799
        }
800
801 20704
        foreach ($this->getIndexes() as $index) {
802 20694
            $visitor->acceptIndex($this, $index);
803
        }
804
805 20704
        foreach ($this->getForeignKeys() as $constraint) {
806 19837
            $visitor->acceptForeignKey($this, $constraint);
807
        }
808 20704
    }
809
810
    /**
811
     * Clone of a Table triggers a deep clone of all affected assets.
812
     *
813
     * @return void
814
     */
815 19841
    public function __clone()
816
    {
817 19841
        foreach ($this->_columns as $k => $column) {
818 19840
            $this->_columns[$k] = clone $column;
819
        }
820
821 19841
        foreach ($this->_indexes as $k => $index) {
822 19829
            $this->_indexes[$k] = clone $index;
823
        }
824
825 19841
        foreach ($this->_fkConstraints as $k => $fk) {
826 19047
            $this->_fkConstraints[$k] = clone $fk;
827 19047
            $this->_fkConstraints[$k]->setLocalTable($this);
828
        }
829 19841
    }
830
831
    /**
832
     * Normalizes a given identifier.
833
     *
834
     * Trims quotes and lowercases the given identifier.
835
     *
836
     * @param string|null $identifier The identifier to normalize.
837
     *
838
     * @return string The normalized identifier.
839
     */
840 21885
    private function normalizeIdentifier($identifier)
841
    {
842 21885
        if ($identifier === null) {
843 374
            return '';
844
        }
845
846 21885
        return $this->trimQuotes(strtolower($identifier));
847
    }
848
849 18480
    public function setComment(?string $comment) : self
850
    {
851
        // For keeping backward compatibility with MySQL in previous releases, table comments are stored as options.
852 18480
        $this->addOption('comment', $comment);
853
854 18480
        return $this;
855
    }
856
857 18480
    public function getComment() : ?string
858
    {
859 18480
        return $this->_options['comment'] ?? null;
860
    }
861
}
862