Completed
Push — 2.9 ( 5d4f93...7ce636 )
by Sergei
35:51
created

Table   F

Complexity

Total Complexity 97

Size/Duplication

Total Lines 814
Duplicated Lines 0 %

Test Coverage

Coverage 96.33%

Importance

Changes 0
Metric Value
wmc 97
eloc 204
dl 0
loc 814
ccs 236
cts 245
cp 0.9633
rs 2
c 0
b 0
f 0

43 Methods

Rating   Name   Duplication   Size   Complexity  
A getPrimaryKeyColumns() 0 6 2
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
A hasIndex() 0 5 1
B _addIndex() 0 31 10
A _addForeignKeyConstraint() 0 35 4
A setPrimaryKey() 0 10 3
A hasColumn() 0 5 1
A getIndexes() 0 3 1
A hasPrimaryKey() 0 3 2
A getOption() 0 3 1
A _getMaxIdentifierLength() 0 7 2
A hasForeignKey() 0 5 1
A getOptions() 0 3 1
A renameColumn() 0 5 1
A visit() 0 14 4
A columnsAreIndexed() 0 10 3
A normalizeIdentifier() 0 3 1
A _addColumn() 0 10 2
A getColumns() 0 8 2
A dropPrimaryKey() 0 4 1
A filterColumns() 0 5 1
A addOption() 0 5 1
A getForeignKeyColumns() 0 7 2
A getColumn() 0 8 2
A dropColumn() 0 6 1
A getForeignKeys() 0 3 1
A getIndex() 0 8 2
A changeColumn() 0 6 1
A addUnnamedForeignKeyConstraint() 0 3 1
A hasOption() 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 __clone() 0 11 4
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 string */
22
    protected $_name = null;
23
24
    /** @var Column[] */
25
    protected $_columns = [];
26
27
    /** @var Index[] */
28
    private $implicitIndexes = [];
29
30
    /** @var Index[] */
31
    protected $_indexes = [];
32
33
    /** @var string */
34
    protected $_primaryKeyName = false;
35
36
    /** @var ForeignKeyConstraint[] */
37
    protected $_fkConstraints = [];
38
39
    /** @var mixed[] */
40
    protected $_options = [];
41
42
    /** @var SchemaConfig|null */
43
    protected $_schemaConfig = null;
44
45
    /**
46
     * @param string                 $tableName
47
     * @param Column[]               $columns
48
     * @param Index[]                $indexes
49
     * @param ForeignKeyConstraint[] $fkConstraints
50
     * @param int                    $idGeneratorType
51
     * @param mixed[]                $options
52
     *
53
     * @throws DBALException
54
     */
55 24207
    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

55
    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...
56
    {
57 24207
        if (strlen($tableName) === 0) {
58 27
            throw DBALException::invalidTableName($tableName);
59
        }
60
61 24180
        $this->_setName($tableName);
62
63 24180
        foreach ($columns as $column) {
64 2268
            $this->_addColumn($column);
65
        }
66
67 24153
        foreach ($indexes as $idx) {
68 784
            $this->_addIndex($idx);
69
        }
70
71 24099
        foreach ($fkConstraints as $constraint) {
72 312
            $this->_addForeignKeyConstraint($constraint);
73
        }
74
75 24099
        $this->_options = $options;
76 24099
    }
77
78
    /**
79
     * @return void
80
     */
81 1805
    public function setSchemaConfig(SchemaConfig $schemaConfig)
82
    {
83 1805
        $this->_schemaConfig = $schemaConfig;
84 1805
    }
85
86
    /**
87
     * @return int
88
     */
89 4554
    protected function _getMaxIdentifierLength()
90
    {
91 4554
        if ($this->_schemaConfig instanceof SchemaConfig) {
92 279
            return $this->_schemaConfig->getMaxIdentifierLength();
93
        }
94
95 4327
        return 63;
96
    }
97
98
    /**
99
     * Sets the Primary Key.
100
     *
101
     * @param string[]    $columnNames
102
     * @param string|bool $indexName
103
     *
104
     * @return self
105
     */
106 9849
    public function setPrimaryKey(array $columnNames, $indexName = false)
107
    {
108 9849
        $this->_addIndex($this->_createIndex($columnNames, $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

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

306
    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

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

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