Failed Conditions
Push — master ( 30b923...92920e )
by Marco
19s queued 13s
created

Table   F

Complexity

Total Complexity 98

Size/Duplication

Total Lines 820
Duplicated Lines 0 %

Test Coverage

Coverage 96.79%

Importance

Changes 0
Metric Value
wmc 98
eloc 207
dl 0
loc 820
ccs 241
cts 249
cp 0.9679
rs 2
c 0
b 0
f 0

43 Methods

Rating   Name   Duplication   Size   Complexity  
A getIndexes() 0 3 1
A visit() 0 14 4
A __clone() 0 11 4
A getForeignKey() 0 8 2
A __construct() 0 21 5
A renameIndex() 0 32 6
A addIndex() 0 11 2
A addColumn() 0 7 1
A addNamedForeignKeyConstraint() 0 26 6
A removeForeignKey() 0 8 2
B _addIndex() 0 31 10
A _addForeignKeyConstraint() 0 35 4
A setPrimaryKey() 0 10 3
A _getMaxIdentifierLength() 0 7 2
A hasForeignKey() 0 5 1
A renameColumn() 0 5 1
A columnsAreIndexed() 0 10 3
A _addColumn() 0 10 2
A dropPrimaryKey() 0 4 1
A addOption() 0 5 1
A dropColumn() 0 6 1
A changeColumn() 0 6 1
A addUnnamedForeignKeyConstraint() 0 3 1
A addUniqueIndex() 0 11 2
A _createIndex() 0 13 4
A addForeignKeyConstraint() 0 5 2
A dropIndex() 0 7 2
A setSchemaConfig() 0 3 1
A getPrimaryKeyColumns() 0 9 2
A hasIndex() 0 5 1
A hasColumn() 0 5 1
A hasPrimaryKey() 0 3 2
A getOption() 0 3 1
A getOptions() 0 3 1
A normalizeIdentifier() 0 7 2
A getColumns() 0 10 2
A filterColumns() 0 5 1
A getForeignKeyColumns() 0 7 2
A getColumn() 0 8 2
A getForeignKeys() 0 3 1
A getIndex() 0 8 2
A hasOption() 0 3 1
A getPrimaryKey() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like Table often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Table, and based on these observations, apply Extract Interface, too.

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 24291
    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 24291
        if (strlen($tableName) === 0) {
55 27
            throw DBALException::invalidTableName($tableName);
56
        }
57
58 24264
        $this->_setName($tableName);
59
60 24264
        foreach ($columns as $column) {
61 2296
            $this->_addColumn($column);
62
        }
63
64 24237
        foreach ($indexes as $idx) {
65 798
            $this->_addIndex($idx);
66
        }
67
68 24183
        foreach ($fkConstraints as $constraint) {
69 312
            $this->_addForeignKeyConstraint($constraint);
70
        }
71
72 24183
        $this->_options = $options;
73 24183
    }
74
75
    /**
76
     * @return void
77
     */
78 1833
    public function setSchemaConfig(SchemaConfig $schemaConfig)
79
    {
80 1833
        $this->_schemaConfig = $schemaConfig;
81 1833
    }
82
83
    /**
84
     * @return int
85
     */
86 4554
    protected function _getMaxIdentifierLength()
87
    {
88 4554
        if ($this->_schemaConfig instanceof SchemaConfig) {
89 279
            return $this->_schemaConfig->getMaxIdentifierLength();
90
        }
91
92 4327
        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 9863
    public function setPrimaryKey(array $columnNames, $indexName = false)
104
    {
105 9863
        $this->_addIndex($this->_createIndex($columnNames, $indexName ?: 'primary', true, true));
106
107 9863
        foreach ($columnNames as $columnName) {
108 9863
            $column = $this->getColumn($columnName);
109 9863
            $column->setNotnull(true);
110
        }
111
112 9863
        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 2981
    public function addIndex(array $columnNames, $indexName = null, array $flags = [], array $options = [])
124
    {
125 2981
        if ($indexName === null) {
126 779
            $indexName = $this->_generateIdentifierName(
127 779
                array_merge([$this->getName()], $columnNames),
128 779
                'idx',
129 779
                $this->_getMaxIdentifierLength()
130
            );
131
        }
132
133 2981
        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 514
    public function dropPrimaryKey()
142
    {
143 514
        $this->dropIndex($this->_primaryKeyName);
144 514
        $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 514
    }
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 879
    public function dropIndex($indexName)
157
    {
158 879
        $indexName = $this->normalizeIdentifier($indexName);
159 879
        if (! $this->hasIndex($indexName)) {
160
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
161
        }
162 879
        unset($this->_indexes[$indexName]);
163 879
    }
164
165
    /**
166
     * @param string[]    $columnNames
167
     * @param string|null $indexName
168
     * @param mixed[]     $options
169
     *
170
     * @return self
171
     */
172 884
    public function addUniqueIndex(array $columnNames, $indexName = null, array $options = [])
173
    {
174 884
        if ($indexName === null) {
175 659
            $indexName = $this->_generateIdentifierName(
176 659
                array_merge([$this->getName()], $columnNames),
177 659
                'uniq',
178 659
                $this->_getMaxIdentifierLength()
179
            );
180
        }
181
182 884
        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 377
    public function renameIndex($oldIndexName, $newIndexName = null)
198
    {
199 377
        $oldIndexName           = $this->normalizeIdentifier($oldIndexName);
200 377
        $normalizedNewIndexName = $this->normalizeIdentifier($newIndexName);
201
202 377
        if ($oldIndexName === $normalizedNewIndexName) {
203 243
            return $this;
204
        }
205
206 377
        if (! $this->hasIndex($oldIndexName)) {
207 27
            throw SchemaException::indexDoesNotExist($oldIndexName, $this->_name);
208
        }
209
210 350
        if ($this->hasIndex($normalizedNewIndexName)) {
211 27
            throw SchemaException::indexAlreadyExists($normalizedNewIndexName, $this->_name);
212
        }
213
214 323
        $oldIndex = $this->_indexes[$oldIndexName];
215
216 323
        if ($oldIndex->isPrimary()) {
217 27
            $this->dropPrimaryKey();
218
219 27
            return $this->setPrimaryKey($oldIndex->getColumns(), $newIndexName ?? false);
220
        }
221
222 323
        unset($this->_indexes[$oldIndexName]);
223
224 323
        if ($oldIndex->isUnique()) {
225 54
            return $this->addUniqueIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getOptions());
226
        }
227
228 296
        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 53
    public function columnsAreIndexed(array $columnNames)
239
    {
240 53
        foreach ($this->getIndexes() as $index) {
241
            /** @var $index Index */
242 53
            if ($index->spansColumns($columnNames)) {
243 53
                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 13928
    private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = [], array $options = [])
263
    {
264 13928
        if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) {
265 27
            throw SchemaException::indexNameInvalid($indexName);
266
        }
267
268 13901
        foreach ($columnNames as $columnName) {
269 13874
            if (! $this->hasColumn($columnName)) {
270 1021
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
271
            }
272
        }
273
274 13874
        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 19900
    public function addColumn($columnName, $typeName, array $options = [])
285
    {
286 19900
        $column = new Column($columnName, Type::getType($typeName), $options);
287
288 19900
        $this->_addColumn($column);
289
290 19900
        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 326
    public function changeColumn($columnName, array $options)
319
    {
320 326
        $column = $this->getColumn($columnName);
321 326
        $column->setOptions($options);
322
323 326
        return $this;
324
    }
325
326
    /**
327
     * Drops a Column from the Table.
328
     *
329
     * @param string $columnName
330
     *
331
     * @return self
332
     */
333 243
    public function dropColumn($columnName)
334
    {
335 243
        $columnName = $this->normalizeIdentifier($columnName);
336 243
        unset($this->_columns[$columnName]);
337
338 243
        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 3036
    public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [], $constraintName = null)
355
    {
356 3036
        $constraintName = $constraintName ?: $this->_generateIdentifierName(array_merge((array) $this->getName(), $localColumnNames), 'fk', $this->_getMaxIdentifierLength());
357
358 3036
        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 169
    public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [])
376
    {
377 169
        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 3063
    public function addNamedForeignKeyConstraint($name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [])
396
    {
397 3063
        if ($foreignTable instanceof Table) {
398 1564
            foreach ($foreignColumnNames as $columnName) {
399 1564
                if (! $foreignTable->hasColumn($columnName)) {
400 141
                    throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName());
401
                }
402
            }
403
        }
404
405 3036
        foreach ($localColumnNames as $columnName) {
406 3036
            if (! $this->hasColumn($columnName)) {
407 251
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
408
            }
409
        }
410
411 3009
        $constraint = new ForeignKeyConstraint(
412 3009
            $localColumnNames,
413 3009
            $foreignTable,
414 3009
            $foreignColumnNames,
415 3009
            $name,
416 3009
            $options
417
        );
418 3009
        $this->_addForeignKeyConstraint($constraint);
419
420 3009
        return $this;
421
    }
422
423
    /**
424
     * @param string $name
425
     * @param mixed  $value
426
     *
427
     * @return self
428
     */
429 1328
    public function addOption($name, $value)
430
    {
431 1328
        $this->_options[$name] = $value;
432
433 1328
        return $this;
434
    }
435
436
    /**
437
     * @return void
438
     *
439
     * @throws SchemaException
440
     */
441 20929
    protected function _addColumn(Column $column)
442
    {
443 20929
        $columnName = $column->getName();
444 20929
        $columnName = $this->normalizeIdentifier($columnName);
445
446 20929
        if (isset($this->_columns[$columnName])) {
447 27
            throw SchemaException::columnAlreadyExists($this->getName(), $columnName);
448
        }
449
450 20929
        $this->_columns[$columnName] = $column;
451 20929
    }
452
453
    /**
454
     * Adds an index to the table.
455
     *
456
     * @return self
457
     *
458
     * @throws SchemaException
459
     */
460 14084
    protected function _addIndex(Index $indexCandidate)
461
    {
462 14084
        $indexName               = $indexCandidate->getName();
463 14084
        $indexName               = $this->normalizeIdentifier($indexName);
464 14084
        $replacedImplicitIndexes = [];
465
466 14084
        foreach ($this->implicitIndexes as $name => $implicitIndex) {
467 894
            if (! $implicitIndex->isFullfilledBy($indexCandidate) || ! isset($this->_indexes[$name])) {
468 786
                continue;
469
            }
470
471 108
            $replacedImplicitIndexes[] = $name;
472
        }
473
474 14084
        if ((isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) ||
475 14084
            ($this->_primaryKeyName !== false && $indexCandidate->isPrimary())
476
        ) {
477 54
            throw SchemaException::indexAlreadyExists($indexName, $this->_name);
478
        }
479
480 14084
        foreach ($replacedImplicitIndexes as $name) {
481 108
            unset($this->_indexes[$name], $this->implicitIndexes[$name]);
482
        }
483
484 14084
        if ($indexCandidate->isPrimary()) {
485 9945
            $this->_primaryKeyName = $indexName;
486
        }
487
488 14084
        $this->_indexes[$indexName] = $indexCandidate;
489
490 14084
        return $this;
491
    }
492
493
    /**
494
     * @return void
495
     */
496 3139
    protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint)
497
    {
498 3139
        $constraint->setLocalTable($this);
499
500 3139
        if (strlen($constraint->getName())) {
501 3111
            $name = $constraint->getName();
502
        } else {
503 28
            $name = $this->_generateIdentifierName(
504 28
                array_merge((array) $this->getName(), $constraint->getLocalColumns()),
505 28
                'fk',
506 28
                $this->_getMaxIdentifierLength()
507
            );
508
        }
509 3139
        $name = $this->normalizeIdentifier($name);
510
511 3139
        $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 3139
        $indexName      = $this->_generateIdentifierName(
517 3139
            array_merge([$this->getName()], $constraint->getColumns()),
518 3139
            'idx',
519 3139
            $this->_getMaxIdentifierLength()
520
        );
521 3139
        $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false);
522
523 3139
        foreach ($this->_indexes as $existingIndex) {
524 2428
            if ($indexCandidate->isFullfilledBy($existingIndex)) {
525 1601
                return;
526
            }
527
        }
528
529 2346
        $this->_addIndex($indexCandidate);
530 2346
        $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
531 2346
    }
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 298
    public function hasForeignKey($constraintName)
541
    {
542 298
        $constraintName = $this->normalizeIdentifier($constraintName);
543
544 298
        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 218
    public function getForeignKey($constraintName)
557
    {
558 218
        $constraintName = $this->normalizeIdentifier($constraintName);
559 218
        if (! $this->hasForeignKey($constraintName)) {
560
            throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
561
        }
562
563 218
        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 270
    public function removeForeignKey($constraintName)
576
    {
577 270
        $constraintName = $this->normalizeIdentifier($constraintName);
578 270
        if (! $this->hasForeignKey($constraintName)) {
579
            throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
580
        }
581
582 270
        unset($this->_fkConstraints[$constraintName]);
583 270
    }
584
585
    /**
586
     * Returns ordered list of columns (primary keys are first, then foreign keys, then the rest)
587
     *
588
     * @return Column[]
589
     */
590 15180
    public function getColumns()
591
    {
592 15180
        $primaryKey        = $this->getPrimaryKey();
593 15180
        $primaryKeyColumns = [];
594
595 15180
        if ($primaryKey !== null) {
596 6684
            $primaryKeyColumns = $this->filterColumns($primaryKey->getColumns());
597
        }
598
599 15180
        return array_merge($primaryKeyColumns, $this->getForeignKeyColumns(), $this->_columns);
600
    }
601
602
    /**
603
     * Returns foreign key columns
604
     *
605
     * @return Column[]
606
     */
607 15180
    private function getForeignKeyColumns()
608
    {
609 15180
        $foreignKeyColumns = [];
610 15180
        foreach ($this->getForeignKeys() as $foreignKey) {
611 1478
            $foreignKeyColumns = array_merge($foreignKeyColumns, $foreignKey->getColumns());
612
        }
613 15180
        return $this->filterColumns($foreignKeyColumns);
614
    }
615
616
    /**
617
     * Returns only columns that have specified names
618
     *
619
     * @param string[] $columnNames
620
     *
621
     * @return Column[]
622
     */
623 15180
    private function filterColumns(array $columnNames)
624
    {
625
        return array_filter($this->_columns, static function ($columnName) use ($columnNames) {
626 14478
            return in_array($columnName, $columnNames, true);
627 15180
        }, ARRAY_FILTER_USE_KEY);
628
    }
629
630
    /**
631
     * Returns whether this table has a Column with the given name.
632
     *
633
     * @param string $columnName The column name.
634
     *
635
     * @return bool
636
     */
637 16309
    public function hasColumn($columnName)
638
    {
639 16309
        $columnName = $this->normalizeIdentifier($columnName);
640
641 16309
        return isset($this->_columns[$columnName]);
642
    }
643
644
    /**
645
     * Returns the Column with the given name.
646
     *
647
     * @param string $columnName The column name.
648
     *
649
     * @return Column
650
     *
651
     * @throws SchemaException If the column does not exist.
652
     */
653 12656
    public function getColumn($columnName)
654
    {
655 12656
        $columnName = $this->normalizeIdentifier($columnName);
656 12656
        if (! $this->hasColumn($columnName)) {
657 27
            throw SchemaException::columnDoesNotExist($columnName, $this->_name);
658
        }
659
660 12629
        return $this->_columns[$columnName];
661
    }
662
663
    /**
664
     * Returns the primary key.
665
     *
666
     * @return Index|null The primary key, or null if this Table has no primary key.
667
     */
668 15315
    public function getPrimaryKey()
669
    {
670 15315
        if (! $this->hasPrimaryKey()) {
671 8957
            return null;
672
        }
673
674 6819
        return $this->getIndex($this->_primaryKeyName);
675
    }
676
677
    /**
678
     * Returns the primary key columns.
679
     *
680
     * @return string[]
681
     *
682
     * @throws DBALException
683
     */
684 379
    public function getPrimaryKeyColumns()
685
    {
686 379
        $primaryKey = $this->getPrimaryKey();
687
688 379
        if ($primaryKey === null) {
689
            throw new DBALException('Table ' . $this->getName() . ' has no primary key.');
690
        }
691
692 379
        return $primaryKey->getColumns();
693
    }
694
695
    /**
696
     * Returns whether this table has a primary key.
697
     *
698
     * @return bool
699
     */
700 15396
    public function hasPrimaryKey()
701
    {
702 15396
        return $this->_primaryKeyName && $this->hasIndex($this->_primaryKeyName);
703
    }
704
705
    /**
706
     * Returns whether this table has an Index with the given name.
707
     *
708
     * @param string $indexName The index name.
709
     *
710
     * @return bool
711
     */
712 8258
    public function hasIndex($indexName)
713
    {
714 8258
        $indexName = $this->normalizeIdentifier($indexName);
715
716 8258
        return isset($this->_indexes[$indexName]);
717
    }
718
719
    /**
720
     * Returns the Index with the given name.
721
     *
722
     * @param string $indexName The index name.
723
     *
724
     * @return Index
725
     *
726
     * @throws SchemaException If the index does not exist.
727
     */
728 7664
    public function getIndex($indexName)
729
    {
730 7664
        $indexName = $this->normalizeIdentifier($indexName);
731 7664
        if (! $this->hasIndex($indexName)) {
732 27
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
733
        }
734
735 7637
        return $this->_indexes[$indexName];
736
    }
737
738
    /**
739
     * @return Index[]
740
     */
741 14424
    public function getIndexes()
742
    {
743 14424
        return $this->_indexes;
744
    }
745
746
    /**
747
     * Returns the foreign key constraints.
748
     *
749
     * @return ForeignKeyConstraint[]
750
     */
751 15666
    public function getForeignKeys()
752
    {
753 15666
        return $this->_fkConstraints;
754
    }
755
756
    /**
757
     * @param string $name
758
     *
759
     * @return bool
760
     */
761 2289
    public function hasOption($name)
762
    {
763 2289
        return isset($this->_options[$name]);
764
    }
765
766
    /**
767
     * @param string $name
768
     *
769
     * @return mixed
770
     */
771 232
    public function getOption($name)
772
    {
773 232
        return $this->_options[$name];
774
    }
775
776
    /**
777
     * @return mixed[]
778
     */
779 11477
    public function getOptions()
780
    {
781 11477
        return $this->_options;
782
    }
783
784
    /**
785
     * @return void
786
     */
787 480
    public function visit(Visitor $visitor)
788
    {
789 480
        $visitor->acceptTable($this);
790
791 480
        foreach ($this->getColumns() as $column) {
792 399
            $visitor->acceptColumn($this, $column);
793
        }
794
795 480
        foreach ($this->getIndexes() as $index) {
796 291
            $visitor->acceptIndex($this, $index);
797
        }
798
799 480
        foreach ($this->getForeignKeys() as $constraint) {
800 108
            $visitor->acceptForeignKey($this, $constraint);
801
        }
802 480
    }
803
804
    /**
805
     * Clone of a Table triggers a deep clone of all affected assets.
806
     *
807
     * @return void
808
     */
809 1366
    public function __clone()
810
    {
811 1366
        foreach ($this->_columns as $k => $column) {
812 1339
            $this->_columns[$k] = clone $column;
813
        }
814 1366
        foreach ($this->_indexes as $k => $index) {
815 990
            $this->_indexes[$k] = clone $index;
816
        }
817 1366
        foreach ($this->_fkConstraints as $k => $fk) {
818 215
            $this->_fkConstraints[$k] = clone $fk;
819 215
            $this->_fkConstraints[$k]->setLocalTable($this);
820
        }
821 1366
    }
822
823
    /**
824
     * Normalizes a given identifier.
825
     *
826
     * Trims quotes and lowercases the given identifier.
827
     *
828
     * @param string|null $identifier The identifier to normalize.
829
     *
830
     * @return string The normalized identifier.
831
     */
832 21037
    private function normalizeIdentifier($identifier)
833
    {
834 21037
        if ($identifier === null) {
835 27
            return '';
836
        }
837
838 21037
        return $this->trimQuotes(strtolower($identifier));
839
    }
840
}
841