Completed
Pull Request — master (#3512)
by David
64:39 queued 61:17
created

Table::visit()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 14
ccs 8
cts 8
cp 1
rs 10
c 0
b 0
f 0
cc 4
nc 8
nop 1
crap 4
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 const ARRAY_FILTER_USE_KEY;
9
use function array_filter;
10
use function array_merge;
11
use function in_array;
12
use function preg_match;
13
use function strlen;
14
use function strtolower;
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
39
    /** @var SchemaConfig|null */
40
    protected $_schemaConfig = null;
41
42
    /**
43
     * @param string                 $tableName
44
     * @param Column[]               $columns
45
     * @param Index[]                $indexes
46
     * @param ForeignKeyConstraint[] $fkConstraints
47
     * @param int                    $idGeneratorType
48
     * @param mixed[]                $options
49
     *
50
     * @throws DBALException
51
     */
52 24547
    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. ( Ignorable by Annotation )

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

52
    public function __construct($tableName, array $columns = [], array $indexes = [], array $fkConstraints = [], /** @scrutinizer ignore-unused */ $idGeneratorType = 0, array $options = [])

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

303
    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

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

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