Passed
Push — phpstan-tests ( 6a2b78...974220 )
by Michael
61:35 queued 23s
created

Table::_createUniqueConstraint()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 19
rs 9.6111
c 0
b 0
f 0
cc 5
nc 6
nop 4
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 in_array;
15
use function is_string;
16
use function preg_match;
17
use function strlen;
18
use function strtolower;
19
use function uksort;
20
21
/**
22
 * Object Representation of a table.
23
 */
24
class Table extends AbstractAsset
25
{
26
    /** @var Column[] */
27
    protected $_columns = [];
28
29
    /** @var Index[] */
30
    private $implicitIndexes = [];
31
32
    /** @var Index[] */
33
    protected $_indexes = [];
34
35
    /** @var string */
36
    protected $_primaryKeyName = false;
37
38
    /** @var UniqueConstraint[] */
39
    protected $_uniqueConstraints = [];
40
41
    /** @var ForeignKeyConstraint[] */
42
    protected $_fkConstraints = [];
43
44
    /** @var mixed[] */
45
    protected $_options = [];
46
47
    /** @var SchemaConfig|null */
48
    protected $_schemaConfig = null;
49
50
    /**
51
     * @param string                 $tableName
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
    public function __construct(
61
        $tableName,
62
        array $columns = [],
63
        array $indexes = [],
64
        array $uniqueConstraints = [],
65
        array $fkConstraints = [],
66
        array $options = []
67
    ) {
68
        if (strlen($tableName) === 0) {
69
            throw DBALException::invalidTableName($tableName);
70
        }
71
72
        $this->_setName($tableName);
73
74
        foreach ($columns as $column) {
75
            $this->_addColumn($column);
76
        }
77
78
        foreach ($indexes as $idx) {
79
            $this->_addIndex($idx);
80
        }
81
82
        foreach ($uniqueConstraints as $uniqueConstraint) {
83
            $this->_addUniqueConstraint($uniqueConstraint);
84
        }
85
86
        foreach ($fkConstraints as $fkConstraint) {
87
            $this->_addForeignKeyConstraint($fkConstraint);
88
        }
89
90
        $this->_options = $options;
91
    }
92
93
    /**
94
     * @return void
95
     */
96
    public function setSchemaConfig(SchemaConfig $schemaConfig)
97
    {
98
        $this->_schemaConfig = $schemaConfig;
99
    }
100
101
    /**
102
     * Sets the Primary Key.
103
     *
104
     * @param string[]     $columnNames
105
     * @param string|false $indexName
106
     *
107
     * @return self
108
     */
109
    public function setPrimaryKey(array $columnNames, $indexName = false)
110
    {
111
        $this->_addIndex($this->_createIndex($columnNames, $indexName ?: 'primary', true, true));
112
113
        foreach ($columnNames as $columnName) {
114
            $column = $this->getColumn($columnName);
115
            $column->setNotnull(true);
116
        }
117
118
        return $this;
119
    }
120
121
    /**
122
     * @param mixed[]     $columnNames
123
     * @param string|null $indexName
124
     * @param string[]    $flags
125
     * @param mixed[]     $options
126
     *
127
     * @return self
128
     */
129
    public function addUniqueConstraint(array $columnNames, $indexName = null, array $flags = [], array $options = [])
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|null $indexName
145
     * @param string[]    $flags
146
     * @param mixed[]     $options
147
     *
148
     * @return self
149
     */
150
    public function addIndex(array $columnNames, $indexName = null, array $flags = [], array $options = [])
151
    {
152
        if ($indexName === null) {
153
            $indexName = $this->_generateIdentifierName(
154
                array_merge([$this->getName()], $columnNames),
155
                'idx',
156
                $this->_getMaxIdentifierLength()
157
            );
158
        }
159
160
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options));
161
    }
162
163
    /**
164
     * Drops the primary key from this table.
165
     *
166
     * @return void
167
     */
168
    public function dropPrimaryKey()
169
    {
170
        $this->dropIndex($this->_primaryKeyName);
171
        $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...
172
    }
173
174
    /**
175
     * Drops an index from this table.
176
     *
177
     * @param string $indexName The index name.
178
     *
179
     * @return void
180
     *
181
     * @throws SchemaException If the index does not exist.
182
     */
183
    public function dropIndex($indexName)
184
    {
185
        $indexName = $this->normalizeIdentifier($indexName);
186
187
        if (! $this->hasIndex($indexName)) {
188
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
189
        }
190
191
        unset($this->_indexes[$indexName]);
192
    }
193
194
    /**
195
     * @param string[]    $columnNames
196
     * @param string|null $indexName
197
     * @param mixed[]     $options
198
     *
199
     * @return self
200
     */
201
    public function addUniqueIndex(array $columnNames, $indexName = null, array $options = [])
202
    {
203
        if ($indexName === null) {
204
            $indexName = $this->_generateIdentifierName(
205
                array_merge([$this->getName()], $columnNames),
206
                'uniq',
207
                $this->_getMaxIdentifierLength()
208
            );
209
        }
210
211
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options));
212
    }
213
214
    /**
215
     * Renames an index.
216
     *
217
     * @param string      $oldIndexName The name of the index to rename from.
218
     * @param string|null $newIndexName The name of the index to rename to.
219
     *                                  If null is given, the index name will be auto-generated.
220
     *
221
     * @return self This table instance.
222
     *
223
     * @throws SchemaException If no index exists for the given current name
224
     *                         or if an index with the given new name already exists on this table.
225
     */
226
    public function renameIndex($oldIndexName, $newIndexName = null)
227
    {
228
        $oldIndexName           = $this->normalizeIdentifier($oldIndexName);
229
        $normalizedNewIndexName = $this->normalizeIdentifier($newIndexName);
230
231
        if ($oldIndexName === $normalizedNewIndexName) {
232
            return $this;
233
        }
234
235
        if (! $this->hasIndex($oldIndexName)) {
236
            throw SchemaException::indexDoesNotExist($oldIndexName, $this->_name);
237
        }
238
239
        if ($this->hasIndex($normalizedNewIndexName)) {
240
            throw SchemaException::indexAlreadyExists($normalizedNewIndexName, $this->_name);
241
        }
242
243
        $oldIndex = $this->_indexes[$oldIndexName];
244
245
        if ($oldIndex->isPrimary()) {
246
            $this->dropPrimaryKey();
247
248
            return $this->setPrimaryKey($oldIndex->getColumns(), $newIndexName ?? false);
249
        }
250
251
        unset($this->_indexes[$oldIndexName]);
252
253
        if ($oldIndex->isUnique()) {
254
            return $this->addUniqueIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getOptions());
255
        }
256
257
        return $this->addIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getFlags(), $oldIndex->getOptions());
258
    }
259
260
    /**
261
     * Checks if an index begins in the order of the given columns.
262
     *
263
     * @param string[] $columnNames
264
     *
265
     * @return bool
266
     */
267
    public function columnsAreIndexed(array $columnNames)
268
    {
269
        foreach ($this->getIndexes() as $index) {
270
            /** @var $index Index */
271
            if ($index->spansColumns($columnNames)) {
272
                return true;
273
            }
274
        }
275
276
        return false;
277
    }
278
279
    /**
280
     * @param string  $columnName
281
     * @param string  $typeName
282
     * @param mixed[] $options
283
     *
284
     * @return Column
285
     */
286
    public function addColumn($columnName, $typeName, array $options = [])
287
    {
288
        $column = new Column($columnName, Type::getType($typeName), $options);
289
290
        $this->_addColumn($column);
291
292
        return $column;
293
    }
294
295
    /**
296
     * Renames a Column.
297
     *
298
     * @deprecated
299
     *
300
     * @param string $oldColumnName
301
     * @param string $newColumnName
302
     *
303
     * @throws DBALException
304
     */
305
    public function renameColumn($oldColumnName, $newColumnName)
0 ignored issues
show
Unused Code introduced by
The parameter $oldColumnName is not used and could be removed. ( Ignorable by Annotation )

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

305
    public function renameColumn(/** @scrutinizer ignore-unused */ $oldColumnName, $newColumnName)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $newColumnName is not used and could be removed. ( Ignorable by Annotation )

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

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

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

866
                array_merge((array) $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...
867
                'fk',
868
                $this->_getMaxIdentifierLength()
869
            );
870
871
        $name = $this->normalizeIdentifier($name);
872
873
        $this->_uniqueConstraints[$name] = $constraint;
874
875
        // If there is already an index that fulfills this requirements drop the request. In the case of __construct
876
        // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates.
877
        // This creates computation overhead in this case, however no duplicate indexes are ever added (column based).
878
        $indexName = $this->_generateIdentifierName(
879
            array_merge([$this->getName()], $constraint->getColumns()),
880
            'idx',
881
            $this->_getMaxIdentifierLength()
882
        );
883
884
        $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, true, false);
885
886
        foreach ($this->_indexes as $existingIndex) {
887
            if ($indexCandidate->isFullfilledBy($existingIndex)) {
888
                return $this;
889
            }
890
        }
891
892
        $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
893
894
        return $this;
895
    }
896
897
    /**
898
     * @return self
899
     */
900
    protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint)
901
    {
902
        $constraint->setLocalTable($this);
903
904
        $name = strlen($constraint->getName())
905
            ? $constraint->getName()
906
            : $this->_generateIdentifierName(
907
                array_merge((array) $this->getName(), $constraint->getLocalColumns()),
908
                'fk',
909
                $this->_getMaxIdentifierLength()
910
            );
911
912
        $name = $this->normalizeIdentifier($name);
913
914
        $this->_fkConstraints[$name] = $constraint;
915
916
        // add an explicit index on the foreign key columns.
917
        // If there is already an index that fulfills this requirements drop the request. In the case of __construct
918
        // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates.
919
        // This creates computation overhead in this case, however no duplicate indexes are ever added (column based).
920
        $indexName = $this->_generateIdentifierName(
921
            array_merge([$this->getName()], $constraint->getColumns()),
922
            'idx',
923
            $this->_getMaxIdentifierLength()
924
        );
925
926
        $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false);
927
928
        foreach ($this->_indexes as $existingIndex) {
929
            if ($indexCandidate->isFullfilledBy($existingIndex)) {
930
                return $this;
931
            }
932
        }
933
934
        $this->_addIndex($indexCandidate);
935
        $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
936
937
        return $this;
938
    }
939
940
    /**
941
     * Normalizes a given identifier.
942
     *
943
     * Trims quotes and lowercases the given identifier.
944
     *
945
     * @param string|null $identifier The identifier to normalize.
946
     *
947
     * @return string The normalized identifier.
948
     */
949
    private function normalizeIdentifier($identifier)
950
    {
951
        if ($identifier === null) {
952
            return '';
953
        }
954
955
        return $this->trimQuotes(strtolower($identifier));
956
    }
957
958
    /**
959
     * @param mixed[] $columns
960
     * @param string  $indexName
961
     * @param mixed[] $flags
962
     * @param mixed[] $options
963
     *
964
     * @return UniqueConstraint
965
     *
966
     * @throws SchemaException
967
     */
968
    private function _createUniqueConstraint(array $columns, $indexName, array $flags = [], array $options = [])
969
    {
970
        if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) {
971
            throw SchemaException::indexNameInvalid($indexName);
972
        }
973
974
        foreach ($columns as $index => $value) {
975
            if (is_string($index)) {
976
                $columnName = $index;
977
            } else {
978
                $columnName = $value;
979
            }
980
981
            if (! $this->hasColumn($columnName)) {
982
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
983
            }
984
        }
985
986
        return new UniqueConstraint($indexName, $columns, $flags, $options);
987
    }
988
989
    /**
990
     * @param mixed[]  $columns
991
     * @param string   $indexName
992
     * @param bool     $isUnique
993
     * @param bool     $isPrimary
994
     * @param string[] $flags
995
     * @param mixed[]  $options
996
     *
997
     * @return Index
998
     *
999
     * @throws SchemaException
1000
     */
1001
    private function _createIndex(array $columns, $indexName, $isUnique, $isPrimary, array $flags = [], array $options = [])
1002
    {
1003
        if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) {
1004
            throw SchemaException::indexNameInvalid($indexName);
1005
        }
1006
1007
        foreach ($columns as $index => $value) {
1008
            if (is_string($index)) {
1009
                $columnName = $index;
1010
            } else {
1011
                $columnName = $value;
1012
            }
1013
1014
            if (! $this->hasColumn($columnName)) {
1015
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
1016
            }
1017
        }
1018
1019
        return new Index($indexName, $columns, $isUnique, $isPrimary, $flags, $options);
1020
    }
1021
}
1022