Failed Conditions
Pull Request — develop (#3348)
by Sergei
10:40
created

Table   F

Complexity

Total Complexity 114

Size/Duplication

Total Lines 895
Duplicated Lines 0 %

Test Coverage

Coverage 81.17%

Importance

Changes 0
Metric Value
wmc 114
eloc 262
dl 0
loc 895
ccs 250
cts 308
cp 0.8117
rs 2
c 0
b 0
f 0

49 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 31 6
A getOption() 0 3 1
A hasForeignKey() 0 5 1
A hasUniqueConstraint() 0 5 1
A getPrimaryKeyColumns() 0 9 2
A getUniqueConstraints() 0 3 1
A addUniqueConstraint() 0 11 2
A addIndex() 0 11 2
A getForeignKey() 0 9 2
A renameIndex() 0 32 6
A addColumn() 0 7 1
A addNamedForeignKeyConstraint() 0 25 6
A removeForeignKey() 0 9 2
A removeUniqueConstraint() 0 9 2
A hasIndex() 0 5 1
A _addForeignKeyConstraint() 0 36 3
B _addIndex() 0 31 10
A hasColumn() 0 5 1
A setPrimaryKey() 0 10 3
A getIndexes() 0 3 1
A hasPrimaryKey() 0 3 2
A _getMaxIdentifierLength() 0 5 2
A getOptions() 0 3 1
A renameColumn() 0 6 1
A visit() 0 14 4
A columnsAreIndexed() 0 10 3
A normalizeIdentifier() 0 7 2
A _addColumn() 0 10 2
A dropPrimaryKey() 0 4 1
A getColumns() 0 24 3
A _createUniqueConstraint() 0 19 5
A addOption() 0 5 1
A getUniqueConstraint() 0 9 2
A getColumn() 0 9 2
A dropColumn() 0 7 1
A getForeignKeys() 0 3 1
A getName() 0 6 1
A addUnnamedForeignKeyConstraint() 0 3 1
A changeColumn() 0 7 1
A getIndex() 0 9 2
A hasOption() 0 3 1
A addUniqueIndex() 0 11 2
A _createIndex() 0 19 5
A addForeignKeyConstraint() 0 11 2
A _addUniqueConstraint() 0 32 3
A dropIndex() 0 9 2
A setSchemaConfig() 0 3 1
A __clone() 0 13 4
A getPrimaryKey() 0 5 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
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Schema;
6
7
use Doctrine\DBAL\DBALException;
8
use Doctrine\DBAL\Schema\Visitor\Visitor;
9
use Doctrine\DBAL\Types\Type;
10
use function array_keys;
11
use function array_merge;
12
use function array_search;
13
use function array_unique;
14
use function assert;
15
use function in_array;
16
use function is_string;
17
use function preg_match;
18
use function strlen;
19
use function strtolower;
20
use function uksort;
21
22
/**
23
 * Object Representation of a table.
24
 */
25
class Table extends AbstractAsset
26
{
27
    /** @var Column[] */
28
    protected $_columns = [];
29
30
    /** @var Index[] */
31
    private $implicitIndexes = [];
32
33
    /** @var Index[] */
34
    protected $_indexes = [];
35
36
    /** @var string */
37
    protected $_primaryKeyName = false;
38
39
    /** @var UniqueConstraint[] */
40
    protected $_uniqueConstraints = [];
41
42
    /** @var ForeignKeyConstraint[] */
43
    protected $_fkConstraints = [];
44
45
    /** @var mixed[] */
46
    protected $_options = [];
47
48
    /** @var SchemaConfig|null */
49
    protected $_schemaConfig = null;
50
51
    /**
52
     * @param Column[]               $columns
53
     * @param Index[]                $indexes
54
     * @param UniqueConstraint[]     $uniqueConstraints
55
     * @param ForeignKeyConstraint[] $fkConstraints
56
     * @param mixed[]                $options
57
     *
58
     * @throws DBALException
59
     */
60 18602
    public function __construct(
61
        string $tableName,
62
        array $columns = [],
63
        array $indexes = [],
64
        array $uniqueConstraints = [],
65
        array $fkConstraints = [],
66
        array $options = []
67
    ) {
68 18602
        if (strlen($tableName) === 0) {
69 1691
            throw DBALException::invalidTableName($tableName);
70
        }
71
72 18601
        $this->_setName($tableName);
73
74 18601
        foreach ($columns as $column) {
75 16941
            $this->_addColumn($column);
76
        }
77
78 18600
        foreach ($indexes as $idx) {
79 16606
            $this->_addIndex($idx);
80
        }
81
82 18598
        foreach ($uniqueConstraints as $uniqueConstraint) {
83
            $this->_addUniqueConstraint($uniqueConstraint);
84
        }
85
86 18598
        foreach ($fkConstraints as $fkConstraint) {
87 15859
            $this->_addForeignKeyConstraint($fkConstraint);
88
        }
89
90 18598
        $this->_options = $options;
91 18598
    }
92
93 18577
    public function getName() : string
94
    {
95 18577
        $name = parent::getName();
96 18577
        assert(is_string($name));
97
98 18577
        return $name;
99
    }
100
101 17189
    public function setSchemaConfig(SchemaConfig $schemaConfig) : void
102
    {
103 17189
        $this->_schemaConfig = $schemaConfig;
104 17189
    }
105
106
    /**
107
     * Sets the Primary Key.
108
     *
109
     * @param string[]     $columnNames
110
     * @param string|false $indexName
111
     */
112 18202
    public function setPrimaryKey(array $columnNames, $indexName = false) : self
113
    {
114 18202
        $this->_addIndex($this->_createIndex($columnNames, $indexName ?: 'primary', true, true));
115
116 18202
        foreach ($columnNames as $columnName) {
117 18202
            $column = $this->getColumn($columnName);
118 18202
            $column->setNotnull(true);
119
        }
120
121 18202
        return $this;
122
    }
123
124
    /**
125
     * @param mixed[]  $columnNames
126
     * @param string[] $flags
127
     * @param mixed[]  $options
128
     */
129
    public function addUniqueConstraint(array $columnNames, ?string $indexName = null, array $flags = [], array $options = []) : self
130
    {
131
        if ($indexName === null) {
132
            $indexName = $this->_generateIdentifierName(
133
                array_merge([$this->getName()], $columnNames),
134
                'uniq',
135
                $this->_getMaxIdentifierLength()
136
            );
137
        }
138
139
        return $this->_addUniqueConstraint($this->_createUniqueConstraint($columnNames, $indexName, $flags, $options));
140
    }
141
142
    /**
143
     * @param string[] $columnNames
144
     * @param string[] $flags
145
     * @param mixed[]  $options
146
     */
147 16673
    public function addIndex(array $columnNames, ?string $indexName = null, array $flags = [], array $options = []) : self
148
    {
149 16673
        if ($indexName === null) {
150 16348
            $indexName = $this->_generateIdentifierName(
151 16348
                array_merge([$this->getName()], $columnNames),
152 16348
                'idx',
153 16348
                $this->_getMaxIdentifierLength()
154
            );
155
        }
156
157 16673
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options));
158
    }
159
160
    /**
161
     * Drops the primary key from this table.
162
     */
163 15555
    public function dropPrimaryKey() : void
164
    {
165 15555
        $this->dropIndex($this->_primaryKeyName);
166 15555
        $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...
167 15555
    }
168
169
    /**
170
     * Drops an index from this table.
171
     *
172
     * @param string $indexName The index name.
173
     *
174
     * @throws SchemaException If the index does not exist.
175
     */
176 15580
    public function dropIndex(string $indexName) : void
177
    {
178 15580
        $indexName = $this->normalizeIdentifier($indexName);
179
180 15580
        if (! $this->hasIndex($indexName)) {
181
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
182
        }
183
184 15580
        unset($this->_indexes[$indexName]);
185 15580
    }
186
187
    /**
188
     * @param string[] $columnNames
189
     * @param mixed[]  $options
190
     */
191 17475
    public function addUniqueIndex(array $columnNames, ?string $indexName = null, array $options = []) : self
192
    {
193 17475
        if ($indexName === null) {
194 17142
            $indexName = $this->_generateIdentifierName(
195 17142
                array_merge([$this->getName()], $columnNames),
196 17142
                'uniq',
197 17142
                $this->_getMaxIdentifierLength()
198
            );
199
        }
200
201 17475
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options));
202
    }
203
204
    /**
205
     * Renames an index.
206
     *
207
     * @param string      $oldIndexName The name of the index to rename from.
208
     * @param string|null $newIndexName The name of the index to rename to.
209
     *                                  If null is given, the index name will be auto-generated.
210
     *
211
     * @return self This table instance.
212
     *
213
     * @throws SchemaException If no index exists for the given current name
214
     *                         or if an index with the given new name already exists on this table.
215
     */
216 14861
    public function renameIndex(string $oldIndexName, ?string $newIndexName = null) : self
217
    {
218 14861
        $oldIndexName           = $this->normalizeIdentifier($oldIndexName);
219 14861
        $normalizedNewIndexName = $this->normalizeIdentifier($newIndexName);
220
221 14861
        if ($oldIndexName === $normalizedNewIndexName) {
222 477
            return $this;
223
        }
224
225 14861
        if (! $this->hasIndex($oldIndexName)) {
226 391
            throw SchemaException::indexDoesNotExist($oldIndexName, $this->_name);
227
        }
228
229 14860
        if ($this->hasIndex($normalizedNewIndexName)) {
230 365
            throw SchemaException::indexAlreadyExists($normalizedNewIndexName, $this->_name);
231
        }
232
233 14859
        $oldIndex = $this->_indexes[$oldIndexName];
234
235 14859
        if ($oldIndex->isPrimary()) {
236 469
            $this->dropPrimaryKey();
237
238 469
            return $this->setPrimaryKey($oldIndex->getColumns(), $newIndexName ?? false);
239
        }
240
241 14859
        unset($this->_indexes[$oldIndexName]);
242
243 14859
        if ($oldIndex->isUnique()) {
244 470
            return $this->addUniqueIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getOptions());
245
        }
246
247 14858
        return $this->addIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getFlags(), $oldIndex->getOptions());
248
    }
249
250
    /**
251
     * Checks if an index begins in the order of the given columns.
252
     *
253
     * @param string[] $columnNames
254
     */
255 10946
    public function columnsAreIndexed(array $columnNames) : bool
256
    {
257 10946
        foreach ($this->getIndexes() as $index) {
258
            /** @var $index Index */
259 10946
            if ($index->spansColumns($columnNames)) {
260 10946
                return true;
261
            }
262
        }
263
264
        return false;
265
    }
266
267
    /**
268
     * @param mixed[] $options
269
     */
270 18482
    public function addColumn(string $columnName, string $typeName, array $options = []) : Column
271
    {
272 18482
        $column = new Column($columnName, Type::getType($typeName), $options);
273
274 18482
        $this->_addColumn($column);
275
276 18482
        return $column;
277
    }
278
279
    /**
280
     * Renames a Column.
281
     *
282
     * @deprecated
283
     *
284
     * @throws DBALException
285
     */
286
    public function renameColumn(string $oldColumnName, string $newColumnName) : void
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

286
    public function renameColumn(/** @scrutinizer ignore-unused */ string $oldColumnName, string $newColumnName) : void

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

286
    public function renameColumn(string $oldColumnName, /** @scrutinizer ignore-unused */ string $newColumnName) : void

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...
287
    {
288
        throw new DBALException(
289
            'Table#renameColumn() was removed, because it drops and recreates the column instead. ' .
290
            'There is no fix available, because a schema diff cannot reliably detect if a column ' .
291
            'was renamed or one column was created and another one dropped.'
292
        );
293
    }
294
295
    /**
296
     * Change Column Details.
297
     *
298
     * @param mixed[] $options
299
     */
300 14728
    public function changeColumn(string $columnName, array $options) : self
301
    {
302 14728
        $column = $this->getColumn($columnName);
303
304 14728
        $column->setOptions($options);
305
306 14728
        return $this;
307
    }
308
309
    /**
310
     * Drops a Column from the Table.
311
     */
312 1569
    public function dropColumn(string $columnName) : self
313
    {
314 1569
        $columnName = $this->normalizeIdentifier($columnName);
315
316 1569
        unset($this->_columns[$columnName]);
317
318 1569
        return $this;
319
    }
320
321
    /**
322
     * Adds a foreign key constraint.
323
     *
324
     * Name is inferred from the local columns.
325
     *
326
     * @param Table|string $foreignTable       Table schema instance or table name
327
     * @param string[]     $localColumnNames
328
     * @param string[]     $foreignColumnNames
329
     * @param mixed[]      $options
330
     */
331 17565
    public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [], ?string $constraintName = null) : self
332
    {
333 17565
        if (! $constraintName) {
334 17519
            $constraintName = $this->_generateIdentifierName(
335 17519
                array_merge((array) $this->getName(), $localColumnNames),
336 17519
                'fk',
337 17519
                $this->_getMaxIdentifierLength()
338
            );
339
        }
340
341 17565
        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

341
        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...
342
    }
343
344
    /**
345
     * Adds a foreign key constraint.
346
     *
347
     * Name is to be generated by the database itself.
348
     *
349
     * @deprecated Use {@link addForeignKeyConstraint}
350
     *
351
     * @param Table|string $foreignTable       Table schema instance or table name
352
     * @param string[]     $localColumnNames
353
     * @param string[]     $foreignColumnNames
354
     * @param mixed[]      $options
355
     */
356 11063
    public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = []) : self
357
    {
358 11063
        return $this->addForeignKeyConstraint($foreignTable, $localColumnNames, $foreignColumnNames, $options);
359
    }
360
361
    /**
362
     * Adds a foreign key constraint with a given name.
363
     *
364
     * @deprecated Use {@link addForeignKeyConstraint}
365
     *
366
     * @param Table|string $foreignTable       Table schema instance or table name
367
     * @param string[]     $localColumnNames
368
     * @param string[]     $foreignColumnNames
369
     * @param mixed[]      $options
370
     *
371
     * @throws SchemaException
372
     */
373 17566
    public function addNamedForeignKeyConstraint(string $name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = []) : self
374
    {
375 17566
        if ($foreignTable instanceof Table) {
376 17134
            foreach ($foreignColumnNames as $columnName) {
377 17134
                if (! $foreignTable->hasColumn($columnName)) {
378 1138
                    throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName());
379
                }
380
            }
381
        }
382
383 17565
        foreach ($localColumnNames as $columnName) {
384 17565
            if (! $this->hasColumn($columnName)) {
385 1208
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
386
            }
387
        }
388
389 17564
        $constraint = new ForeignKeyConstraint(
390 16935
            $localColumnNames,
391 89
            $foreignTable,
392 89
            $foreignColumnNames,
393 89
            $name,
394 718
            $options
395
        );
396
397 17564
        return $this->_addForeignKeyConstraint($constraint);
398
    }
399
400
    /**
401
     * @param mixed $value
402
     */
403 15717
    public function addOption(string $name, $value) : self
404
    {
405 15717
        $this->_options[$name] = $value;
406
407 15717
        return $this;
408
    }
409
410
    /**
411
     * Returns whether this table has a foreign key constraint with the given name.
412
     */
413 10719
    public function hasForeignKey(string $constraintName) : bool
414
    {
415 10719
        $constraintName = $this->normalizeIdentifier($constraintName);
416
417 10719
        return isset($this->_fkConstraints[$constraintName]);
418
    }
419
420
    /**
421
     * Returns the foreign key constraint with the given name.
422
     *
423
     * @param string $constraintName The constraint name.
424
     *
425
     * @throws SchemaException If the foreign key does not exist.
426
     */
427 347
    public function getForeignKey(string $constraintName) : ForeignKeyConstraint
428
    {
429 347
        $constraintName = $this->normalizeIdentifier($constraintName);
430
431 347
        if (! $this->hasForeignKey($constraintName)) {
432
            throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
433
        }
434
435 347
        return $this->_fkConstraints[$constraintName];
436
    }
437
438
    /**
439
     * Removes the foreign key constraint with the given name.
440
     *
441
     * @param string $constraintName The constraint name.
442
     *
443
     * @throws SchemaException
444
     */
445 348
    public function removeForeignKey(string $constraintName) : void
446
    {
447 348
        $constraintName = $this->normalizeIdentifier($constraintName);
448
449 348
        if (! $this->hasForeignKey($constraintName)) {
450
            throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
451
        }
452
453 348
        unset($this->_fkConstraints[$constraintName]);
454 348
    }
455
456
    /**
457
     * Returns whether this table has a unique constraint with the given name.
458
     */
459
    public function hasUniqueConstraint(string $constraintName) : bool
460
    {
461
        $constraintName = $this->normalizeIdentifier($constraintName);
462
463
        return isset($this->_uniqueConstraints[$constraintName]);
464
    }
465
466
    /**
467
     * Returns the unique constraint with the given name.
468
     *
469
     * @param string $constraintName The constraint name.
470
     *
471
     * @throws SchemaException If the foreign key does not exist.
472
     */
473
    public function getUniqueConstraint(string $constraintName) : UniqueConstraint
474
    {
475
        $constraintName = $this->normalizeIdentifier($constraintName);
476
477
        if (! $this->hasUniqueConstraint($constraintName)) {
478
            throw SchemaException::uniqueConstraintDoesNotExist($constraintName, $this->_name);
479
        }
480
481
        return $this->_uniqueConstraints[$constraintName];
482
    }
483
484
    /**
485
     * Removes the unique constraint with the given name.
486
     *
487
     * @param string $constraintName The constraint name.
488
     *
489
     * @throws SchemaException
490
     */
491
    public function removeUniqueConstraint(string $constraintName) : void
492
    {
493
        $constraintName = $this->normalizeIdentifier($constraintName);
494
495
        if (! $this->hasUniqueConstraint($constraintName)) {
496
            throw SchemaException::uniqueConstraintDoesNotExist($constraintName, $this->_name);
497
        }
498
499
        unset($this->_uniqueConstraints[$constraintName]);
500
    }
501
502
    /**
503
     * Returns ordered list of columns (primary keys are first, then foreign keys, then the rest)
504
     *
505
     * @return Column[]
506
     */
507 18359
    public function getColumns() : array
508
    {
509 18359
        $columns = $this->_columns;
510 18359
        $pkCols  = [];
511 18359
        $fkCols  = [];
512
513 18359
        $primaryKey = $this->getPrimaryKey();
514
515 18359
        if ($primaryKey !== null) {
516 18121
            $pkCols = $primaryKey->getColumns();
517
        }
518
519 18359
        foreach ($this->getForeignKeys() as $fk) {
520
            /** @var ForeignKeyConstraint $fk */
521 17597
            $fkCols = array_merge($fkCols, $fk->getColumns());
522
        }
523
524 18359
        $colNames = array_unique(array_merge($pkCols, $fkCols, array_keys($columns)));
525
526
        uksort($columns, static function ($a, $b) use ($colNames) : bool {
527 18198
            return array_search($a, $colNames) >= array_search($b, $colNames);
528 18359
        });
529
530 18359
        return $columns;
531
    }
532
533
    /**
534
     * Returns whether this table has a Column with the given name.
535
     *
536
     * @param string $columnName The column name.
537
     */
538 18386
    public function hasColumn(string $columnName) : bool
539
    {
540 18386
        $columnName = $this->normalizeIdentifier($columnName);
541
542 18386
        return isset($this->_columns[$columnName]);
543
    }
544
545
    /**
546
     * Returns the Column with the given name.
547
     *
548
     * @param string $columnName The column name.
549
     *
550
     * @throws SchemaException If the column does not exist.
551
     */
552 18282
    public function getColumn(string $columnName) : Column
553
    {
554 18282
        $columnName = $this->normalizeIdentifier($columnName);
555
556 18282
        if (! $this->hasColumn($columnName)) {
557 1535
            throw SchemaException::columnDoesNotExist($columnName, $this->_name);
558
        }
559
560 18281
        return $this->_columns[$columnName];
561
    }
562
563
    /**
564
     * Returns the primary key.
565
     *
566
     * @return Index|null The primary key, or null if this Table has no primary key.
567
     */
568 18364
    public function getPrimaryKey() : ?Index
569
    {
570 18364
        return $this->hasPrimaryKey()
571 18126
            ? $this->getIndex($this->_primaryKeyName)
572 18364
            : null;
573
    }
574
575
    /**
576
     * Returns the primary key columns.
577
     *
578
     * @return string[]
579
     *
580
     * @throws DBALException
581
     */
582 15550
    public function getPrimaryKeyColumns() : array
583
    {
584 15550
        $primaryKey = $this->getPrimaryKey();
585
586 15550
        if ($primaryKey === null) {
587
            throw new DBALException('Table ' . $this->getName() . ' has no primary key.');
588
        }
589
590 15550
        return $primaryKey->getColumns();
591
    }
592
593
    /**
594
     * Returns whether this table has a primary key.
595
     */
596 18367
    public function hasPrimaryKey() : bool
597
    {
598 18367
        return $this->_primaryKeyName && $this->hasIndex($this->_primaryKeyName);
599
    }
600
601
    /**
602
     * Returns whether this table has an Index with the given name.
603
     *
604
     * @param string $indexName The index name.
605
     */
606 18178
    public function hasIndex(string $indexName) : bool
607
    {
608 18178
        $indexName = $this->normalizeIdentifier($indexName);
609
610 18178
        return isset($this->_indexes[$indexName]);
611
    }
612
613
    /**
614
     * Returns the Index with the given name.
615
     *
616
     * @param string $indexName The index name.
617
     *
618
     * @throws SchemaException If the index does not exist.
619
     */
620 18156
    public function getIndex(string $indexName) : Index
621
    {
622 18156
        $indexName = $this->normalizeIdentifier($indexName);
623
624 18156
        if (! $this->hasIndex($indexName)) {
625 1405
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
626
        }
627
628 18155
        return $this->_indexes[$indexName];
629
    }
630
631
    /**
632
     * @return Index[]
633
     */
634 18346
    public function getIndexes() : array
635
    {
636 18346
        return $this->_indexes;
637
    }
638
639
    /**
640
     * Returns the unique constraints.
641
     *
642
     * @return UniqueConstraint[]
643
     */
644 18235
    public function getUniqueConstraints() : array
645
    {
646 18235
        return $this->_uniqueConstraints;
647
    }
648
649
    /**
650
     * Returns the foreign key constraints.
651
     *
652
     * @return ForeignKeyConstraint[]
653
     */
654 18377
    public function getForeignKeys() : array
655
    {
656 18377
        return $this->_fkConstraints;
657
    }
658
659 15658
    public function hasOption(string $name) : bool
660
    {
661 15658
        return isset($this->_options[$name]);
662
    }
663
664
    /**
665
     * @return mixed
666
     */
667 15200
    public function getOption(string $name)
668
    {
669 15200
        return $this->_options[$name];
670
    }
671
672
    /**
673
     * @return mixed[]
674
     */
675 18249
    public function getOptions() : array
676
    {
677 18249
        return $this->_options;
678
    }
679
680 16917
    public function visit(Visitor $visitor) : void
681
    {
682 16917
        $visitor->acceptTable($this);
683
684 16917
        foreach ($this->getColumns() as $column) {
685 16910
            $visitor->acceptColumn($this, $column);
686
        }
687
688 16917
        foreach ($this->getIndexes() as $index) {
689 15365
            $visitor->acceptIndex($this, $index);
690
        }
691
692 16917
        foreach ($this->getForeignKeys() as $constraint) {
693 160
            $visitor->acceptForeignKey($this, $constraint);
694
        }
695 16917
    }
696
697
    /**
698
     * Clone of a Table triggers a deep clone of all affected assets.
699
     */
700 16785
    public function __clone()
701
    {
702 16785
        foreach ($this->_columns as $k => $column) {
703 16784
            $this->_columns[$k] = clone $column;
704
        }
705
706 16785
        foreach ($this->_indexes as $k => $index) {
707 16773
            $this->_indexes[$k] = clone $index;
708
        }
709
710 16785
        foreach ($this->_fkConstraints as $k => $fk) {
711 15809
            $this->_fkConstraints[$k] = clone $fk;
712 15809
            $this->_fkConstraints[$k]->setLocalTable($this);
713
        }
714 16785
    }
715
716 17730
    protected function _getMaxIdentifierLength() : int
717
    {
718 17730
        return $this->_schemaConfig instanceof SchemaConfig
719 17012
            ? $this->_schemaConfig->getMaxIdentifierLength()
720 17730
            : 63;
721
    }
722
723
    /**
724
     * @throws SchemaException
725
     */
726 18511
    protected function _addColumn(Column $column) : void
727
    {
728 18511
        $columnName = $column->getName();
729 18511
        $columnName = $this->normalizeIdentifier($columnName);
730
731 18511
        if (isset($this->_columns[$columnName])) {
732 1509
            throw SchemaException::columnAlreadyExists($this->getName(), $columnName);
733
        }
734
735 18511
        $this->_columns[$columnName] = $column;
736 18511
    }
737
738
    /**
739
     * Adds an index to the table.
740
     *
741
     * @throws SchemaException
742
     */
743 18326
    protected function _addIndex(Index $indexCandidate) : self
744
    {
745 18326
        $indexName               = $indexCandidate->getName();
746 18326
        $indexName               = $this->normalizeIdentifier($indexName);
747 18326
        $replacedImplicitIndexes = [];
748
749 18326
        foreach ($this->implicitIndexes as $name => $implicitIndex) {
750 14124
            if (! $implicitIndex->isFullfilledBy($indexCandidate) || ! isset($this->_indexes[$name])) {
751 14120
                continue;
752
            }
753
754 784
            $replacedImplicitIndexes[] = $name;
755
        }
756
757 18326
        if ((isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) ||
758 18326
            ($this->_primaryKeyName !== false && $indexCandidate->isPrimary())
759
        ) {
760 1380
            throw SchemaException::indexAlreadyExists($indexName, $this->_name);
761
        }
762
763 18326
        foreach ($replacedImplicitIndexes as $name) {
764 784
            unset($this->_indexes[$name], $this->implicitIndexes[$name]);
765
        }
766
767 18326
        if ($indexCandidate->isPrimary()) {
768 18204
            $this->_primaryKeyName = $indexName;
769
        }
770
771 18326
        $this->_indexes[$indexName] = $indexCandidate;
772
773 18326
        return $this;
774
    }
775
776
    protected function _addUniqueConstraint(UniqueConstraint $constraint) : self
777
    {
778
        $name = $constraint->getName() ?? $this->_generateIdentifierName(
779
            array_merge([$this->getName()], $constraint->getLocalColumns()),
0 ignored issues
show
Bug introduced by
The method getLocalColumns() does not exist on Doctrine\DBAL\Schema\UniqueConstraint. ( Ignorable by Annotation )

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

779
            array_merge([$this->getName()], $constraint->/** @scrutinizer ignore-call */ getLocalColumns()),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
780
            'fk',
781
            $this->_getMaxIdentifierLength()
782
        );
783
784
        $name = $this->normalizeIdentifier($name);
785
786
        $this->_uniqueConstraints[$name] = $constraint;
787
788
        // If there is already an index that fulfills this requirements drop the request. In the case of __construct
789
        // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates.
790
        // This creates computation overhead in this case, however no duplicate indexes are ever added (column based).
791
        $indexName = $this->_generateIdentifierName(
792
            array_merge([$this->getName()], $constraint->getColumns()),
793
            'idx',
794
            $this->_getMaxIdentifierLength()
795
        );
796
797
        $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, true, false);
798
799
        foreach ($this->_indexes as $existingIndex) {
800
            if ($indexCandidate->isFullfilledBy($existingIndex)) {
801
                return $this;
802
            }
803
        }
804
805
        $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
806
807
        return $this;
808
    }
809
810 17647
    protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint) : self
811
    {
812 17647
        $constraint->setLocalTable($this);
813
814 17647
        $name = $constraint->getName() ?? $this->_generateIdentifierName(
815 1897
            array_merge([$this->getName()], $constraint->getLocalColumns()),
816 1897
            'fk',
817 17647
            $this->_getMaxIdentifierLength()
818
        );
819
820 17647
        $name = $this->normalizeIdentifier($name);
821
822 17647
        $this->_fkConstraints[$name] = $constraint;
823
824
        // add an explicit index on the foreign key columns.
825
        // If there is already an index that fulfills this requirements drop the request. In the case of __construct
826
        // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates.
827
        // This creates computation overhead in this case, however no duplicate indexes are ever added (column based).
828 17647
        $indexName = $this->_generateIdentifierName(
829 17647
            array_merge([$this->getName()], $constraint->getColumns()),
830 17647
            'idx',
831 17647
            $this->_getMaxIdentifierLength()
832
        );
833
834 17647
        $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false);
835
836 17647
        foreach ($this->_indexes as $existingIndex) {
837 17621
            if ($indexCandidate->isFullfilledBy($existingIndex)) {
838 16234
                return $this;
839
            }
840
        }
841
842 17623
        $this->_addIndex($indexCandidate);
843 17623
        $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
844
845 17623
        return $this;
846
    }
847
848
    /**
849
     * Normalizes a given identifier.
850
     *
851
     * Trims quotes and lowercases the given identifier.
852
     *
853
     * @param string|null $identifier The identifier to normalize.
854
     *
855
     * @return string The normalized identifier.
856
     */
857 18515
    private function normalizeIdentifier(?string $identifier) : string
858
    {
859 18515
        if ($identifier === null) {
860 469
            return '';
861
        }
862
863 18515
        return $this->trimQuotes(strtolower($identifier));
864
    }
865
866
    /**
867
     * @param mixed[] $columns
868
     * @param mixed[] $flags
869
     * @param mixed[] $options
870
     *
871
     * @throws SchemaException
872
     */
873
    private function _createUniqueConstraint(array $columns, string $indexName, array $flags = [], array $options = []) : UniqueConstraint
874
    {
875
        if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) {
876
            throw SchemaException::indexNameInvalid($indexName);
877
        }
878
879
        foreach ($columns as $index => $value) {
880
            if (is_string($index)) {
881
                $columnName = $index;
882
            } else {
883
                $columnName = $value;
884
            }
885
886
            if (! $this->hasColumn($columnName)) {
887
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
888
            }
889
        }
890
891
        return new UniqueConstraint($indexName, $columns, $flags, $options);
892
    }
893
894
    /**
895
     * @param mixed[]  $columns
896
     * @param string[] $flags
897
     * @param mixed[]  $options
898
     *
899
     * @throws SchemaException
900
     */
901 18321
    private function _createIndex(array $columns, string $indexName, bool $isUnique, bool $isPrimary, array $flags = [], array $options = []) : Index
902
    {
903 18321
        if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) {
904 1197
            throw SchemaException::indexNameInvalid($indexName);
905
        }
906
907 18320
        foreach ($columns as $index => $value) {
908 18319
            if (is_string($index)) {
909
                $columnName = $index;
910
            } else {
911 18319
                $columnName = $value;
912
            }
913
914 18319
            if (! $this->hasColumn($columnName)) {
915 1566
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
916
            }
917
        }
918
919 18319
        return new Index($indexName, $columns, $isUnique, $isPrimary, $flags, $options);
920
    }
921
}
922