Completed
Pull Request — master (#3365)
by Benjamin
21:11
created

Table::getForeignKeys()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
crap 1
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 is_numeric;
13
use function is_string;
14
use function preg_match;
15
use function strlen;
16
use function strtolower;
17
18
/**
19
 * Object Representation of a table.
20
 */
21
class Table extends AbstractAsset
22
{
23
    /** @var string */
24
    protected $_name = null;
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 ForeignKeyConstraint[] */
39
    protected $_fkConstraints = [];
40
41
    /** @var mixed[] */
42
    protected $_options = [];
43
44
    /** @var SchemaConfig|null */
45
    protected $_schemaConfig = null;
46
47
    /**
48
     * @param string                 $tableName
49
     * @param Column[]               $columns
50
     * @param Index[]                $indexes
51
     * @param ForeignKeyConstraint[] $fkConstraints
52
     * @param int                    $idGeneratorType
53
     * @param mixed[]                $options
54
     *
55
     * @throws DBALException
56
     */
57 21456
    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

57
    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...
58
    {
59 21456
        if (strlen($tableName) === 0) {
60 24
            throw DBALException::invalidTableName($tableName);
61
        }
62
63 21432
        $this->_setName($tableName);
64
65 21432
        foreach ($columns as $column) {
66 1998
            $this->_addColumn($column);
67
        }
68
69 21408
        foreach ($indexes as $idx) {
70 703
            $this->_addIndex($idx);
71
        }
72
73 21360
        foreach ($fkConstraints as $constraint) {
74 280
            $this->_addForeignKeyConstraint($constraint);
75
        }
76
77 21360
        $this->_options = $options;
78 21360
    }
79
80
    /**
81
     * @return void
82
     */
83 1602
    public function setSchemaConfig(SchemaConfig $schemaConfig)
84
    {
85 1602
        $this->_schemaConfig = $schemaConfig;
86 1602
    }
87
88
    /**
89
     * @return int
90
     */
91 4049
    protected function _getMaxIdentifierLength()
92
    {
93 4049
        if ($this->_schemaConfig instanceof SchemaConfig) {
94 247
            return $this->_schemaConfig->getMaxIdentifierLength();
95
        }
96
97 3848
        return 63;
98
    }
99
100
    /**
101
     * Sets the Primary Key.
102
     *
103
     * @param mixed[][]   $columns
104
     * @param string|bool $indexName
105
     *
106
     * @return self
107
     */
108 8714
    public function setPrimaryKey(array $columns, $indexName = false)
109
    {
110 8714
        $this->_addIndex($this->_createIndex($columns, $indexName ?: 'primary', true, true));
0 ignored issues
show
Bug introduced by
It seems like $indexName ?: 'primary' can also be of type true; however, parameter $indexName of Doctrine\DBAL\Schema\Table::_createIndex() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

110
        $this->_addIndex($this->_createIndex($columns, /** @scrutinizer ignore-type */ $indexName ?: 'primary', true, true));
Loading history...
111
112 8714
        foreach ($columns as $columnName) {
113 8714
            $column = $this->getColumn($columnName);
0 ignored issues
show
Bug introduced by
$columnName of type array<mixed,mixed> is incompatible with the type string expected by parameter $columnName of Doctrine\DBAL\Schema\Table::getColumn(). ( Ignorable by Annotation )

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

113
            $column = $this->getColumn(/** @scrutinizer ignore-type */ $columnName);
Loading history...
114 8714
            $column->setNotnull(true);
115
        }
116
117 8714
        return $this;
118
    }
119
120
    /**
121
     * @param mixed[][]   $columnNames
122
     * @param string|null $indexName
123
     * @param string[]    $flags
124
     * @param mixed[]     $options
125
     *
126
     * @return self
127
     */
128 2635
    public function addIndex(array $columnNames, $indexName = null, array $flags = [], array $options = [])
129
    {
130 2635
        if ($indexName === null) {
131 693
            $indexName = $this->_generateIdentifierName(
132 693
                array_merge([$this->getName()], $columnNames),
133 693
                'idx',
134 693
                $this->_getMaxIdentifierLength()
135
            );
136
        }
137
138 2635
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options));
139
    }
140
141
    /**
142
     * Drops the primary key from this table.
143
     *
144
     * @return void
145
     */
146 444
    public function dropPrimaryKey()
147
    {
148 444
        $this->dropIndex($this->_primaryKeyName);
149 444
        $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...
150 444
    }
151
152
    /**
153
     * Drops an index from this table.
154
     *
155
     * @param string $indexName The index name.
156
     *
157
     * @return void
158
     *
159
     * @throws SchemaException If the index does not exist.
160
     */
161 768
    public function dropIndex($indexName)
162
    {
163 768
        $indexName = $this->normalizeIdentifier($indexName);
164 768
        if (! $this->hasIndex($indexName)) {
165
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
166
        }
167 768
        unset($this->_indexes[$indexName]);
168 768
    }
169
170
    /**
171
     * @param mixed[][]   $columnNames
172
     * @param string|null $indexName
173
     * @param mixed[]     $options
174
     *
175
     * @return self
176
     */
177 785
    public function addUniqueIndex(array $columnNames, $indexName = null, array $options = [])
178
    {
179 785
        if ($indexName === null) {
180 585
            $indexName = $this->_generateIdentifierName(
181 585
                array_merge([$this->getName()], $columnNames),
182 585
                'uniq',
183 585
                $this->_getMaxIdentifierLength()
184
            );
185
        }
186
187 785
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options));
188
    }
189
190
    /**
191
     * Renames an index.
192
     *
193
     * @param string      $oldIndexName The name of the index to rename from.
194
     * @param string|null $newIndexName The name of the index to rename to.
195
     *                                  If null is given, the index name will be auto-generated.
196
     *
197
     * @return self This table instance.
198
     *
199
     * @throws SchemaException If no index exists for the given current name
200
     *                         or if an index with the given new name already exists on this table.
201
     */
202 335
    public function renameIndex($oldIndexName, $newIndexName = null)
203
    {
204 335
        $oldIndexName           = $this->normalizeIdentifier($oldIndexName);
205 335
        $normalizedNewIndexName = $this->normalizeIdentifier($newIndexName);
206
207 335
        if ($oldIndexName === $normalizedNewIndexName) {
208 216
            return $this;
209
        }
210
211 335
        if (! $this->hasIndex($oldIndexName)) {
212 24
            throw SchemaException::indexDoesNotExist($oldIndexName, $this->_name);
213
        }
214
215 311
        if ($this->hasIndex($normalizedNewIndexName)) {
216 24
            throw SchemaException::indexAlreadyExists($normalizedNewIndexName, $this->_name);
217
        }
218
219 287
        $oldIndex = $this->_indexes[$oldIndexName];
220
221 287
        if ($oldIndex->isPrimary()) {
222 24
            $this->dropPrimaryKey();
223
224 24
            return $this->setPrimaryKey($oldIndex->getColumns(), $newIndexName);
225
        }
226
227 287
        unset($this->_indexes[$oldIndexName]);
228
229 287
        if ($oldIndex->isUnique()) {
230 48
            return $this->addUniqueIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getOptions());
231
        }
232
233 263
        return $this->addIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getFlags(), $oldIndex->getOptions());
234
    }
235
236
    /**
237
     * Checks if an index begins in the order of the given columns.
238
     *
239
     * @param mixed[][] $columnsNames
240
     *
241
     * @return bool
242
     */
243 47
    public function columnsAreIndexed(array $columnsNames)
244
    {
245 47
        foreach ($this->getIndexes() as $index) {
246
            /** @var $index Index */
247 47
            if ($index->spansColumns($columnsNames)) {
248 47
                return true;
249
            }
250
        }
251
252
        return false;
253
    }
254
255
    /**
256
     * @param mixed[][] $columnNames
257
     * @param string    $indexName
258
     * @param bool      $isUnique
259
     * @param bool      $isPrimary
260
     * @param string[]  $flags
261
     * @param mixed[]   $options
262
     *
263
     * @return Index
264
     *
265
     * @throws SchemaException
266
     */
267 12314
    private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = [], array $options = [])
268
    {
269 12314
        if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) {
270 24
            throw SchemaException::indexNameInvalid($indexName);
271
        }
272
273 12290
        foreach ($columnNames as $columnName => $indexColOptions) {
274 12266
            if (is_numeric($columnName) && is_string($indexColOptions)) {
275 12266
                $columnName = $indexColOptions;
276
            }
277
278 12266
            if (! $this->hasColumn($columnName)) {
279 12266
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
280
            }
281
        }
282
283 12266
        return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $options);
284
    }
285
286
    /**
287
     * @param string  $columnName
288
     * @param string  $typeName
289
     * @param mixed[] $options
290
     *
291
     * @return Column
292
     */
293 17565
    public function addColumn($columnName, $typeName, array $options = [])
294
    {
295 17565
        $column = new Column($columnName, Type::getType($typeName), $options);
296
297 17565
        $this->_addColumn($column);
298
299 17565
        return $column;
300
    }
301
302
    /**
303
     * Renames a Column.
304
     *
305
     * @deprecated
306
     *
307
     * @param string $oldColumnName
308
     * @param string $newColumnName
309
     *
310
     * @throws DBALException
311
     */
312
    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

312
    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

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

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