Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Doctrine\DBAL\Schema;
4
 
5
use Doctrine\DBAL\Exception;
6
use Doctrine\DBAL\Schema\Exception\InvalidTableName;
7
use Doctrine\DBAL\Schema\Visitor\Visitor;
8
use Doctrine\DBAL\Types\Type;
9
use Doctrine\Deprecations\Deprecation;
10
 
11
use function array_filter;
12
use function array_keys;
13
use function array_merge;
14
use function in_array;
15
use function preg_match;
16
use function strlen;
17
use function strtolower;
18
 
19
use const ARRAY_FILTER_USE_KEY;
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
    protected $_indexes = [];
31
 
32
    /** @var string|null */
33
    protected $_primaryKeyName;
34
 
35
    /** @var UniqueConstraint[] */
36
    protected $uniqueConstraints = [];
37
 
38
    /** @var ForeignKeyConstraint[] */
39
    protected $_fkConstraints = [];
40
 
41
    /** @var mixed[] */
42
    protected $_options = [
43
        'create_options' => [],
44
    ];
45
 
46
    /** @var SchemaConfig|null */
47
    protected $_schemaConfig;
48
 
49
    /** @var Index[] */
50
    private array $implicitIndexes = [];
51
 
52
    /**
53
     * @param Column[]               $columns
54
     * @param Index[]                $indexes
55
     * @param UniqueConstraint[]     $uniqueConstraints
56
     * @param ForeignKeyConstraint[] $fkConstraints
57
     * @param mixed[]                $options
58
     *
59
     * @throws SchemaException
60
     * @throws Exception
61
     */
62
    public function __construct(
63
        string $name,
64
        array $columns = [],
65
        array $indexes = [],
66
        array $uniqueConstraints = [],
67
        array $fkConstraints = [],
68
        array $options = []
69
    ) {
70
        if ($name === '') {
71
            throw InvalidTableName::new($name);
72
        }
73
 
74
        $this->_setName($name);
75
 
76
        foreach ($columns as $column) {
77
            $this->_addColumn($column);
78
        }
79
 
80
        foreach ($indexes as $idx) {
81
            $this->_addIndex($idx);
82
        }
83
 
84
        foreach ($uniqueConstraints as $uniqueConstraint) {
85
            $this->_addUniqueConstraint($uniqueConstraint);
86
        }
87
 
88
        foreach ($fkConstraints as $constraint) {
89
            $this->_addForeignKeyConstraint($constraint);
90
        }
91
 
92
        $this->_options = array_merge($this->_options, $options);
93
    }
94
 
95
    /** @return void */
96
    public function setSchemaConfig(SchemaConfig $schemaConfig)
97
    {
98
        $this->_schemaConfig = $schemaConfig;
99
    }
100
 
101
    /** @return int */
102
    protected function _getMaxIdentifierLength()
103
    {
104
        if ($this->_schemaConfig instanceof SchemaConfig) {
105
            return $this->_schemaConfig->getMaxIdentifierLength();
106
        }
107
 
108
        return 63;
109
    }
110
 
111
    /**
112
     * Sets the Primary Key.
113
     *
114
     * @param string[]     $columnNames
115
     * @param string|false $indexName
116
     *
117
     * @return self
118
     *
119
     * @throws SchemaException
120
     */
121
    public function setPrimaryKey(array $columnNames, $indexName = false)
122
    {
123
        if ($indexName === false) {
124
            $indexName = 'primary';
125
        }
126
 
127
        $this->_addIndex($this->_createIndex($columnNames, $indexName, true, true));
128
 
129
        foreach ($columnNames as $columnName) {
130
            $column = $this->getColumn($columnName);
131
            $column->setNotnull(true);
132
        }
133
 
134
        return $this;
135
    }
136
 
137
    /**
138
     * @param string[] $columnNames
139
     * @param string[] $flags
140
     * @param mixed[]  $options
141
     *
142
     * @return self
143
     *
144
     * @throws SchemaException
145
     */
146
    public function addIndex(array $columnNames, ?string $indexName = null, array $flags = [], array $options = [])
147
    {
148
        $indexName ??= $this->_generateIdentifierName(
149
            array_merge([$this->getName()], $columnNames),
150
            'idx',
151
            $this->_getMaxIdentifierLength(),
152
        );
153
 
154
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options));
155
    }
156
 
157
    /**
158
     * @param string[] $columnNames
159
     * @param string[] $flags
160
     * @param mixed[]  $options
161
     *
162
     * @return self
163
     */
164
    public function addUniqueConstraint(
165
        array $columnNames,
166
        ?string $indexName = null,
167
        array $flags = [],
168
        array $options = []
169
    ): Table {
170
        $indexName ??= $this->_generateIdentifierName(
171
            array_merge([$this->getName()], $columnNames),
172
            'uniq',
173
            $this->_getMaxIdentifierLength(),
174
        );
175
 
176
        return $this->_addUniqueConstraint($this->_createUniqueConstraint($columnNames, $indexName, $flags, $options));
177
    }
178
 
179
    /**
180
     * Drops the primary key from this table.
181
     *
182
     * @return void
183
     *
184
     * @throws SchemaException
185
     */
186
    public function dropPrimaryKey()
187
    {
188
        if ($this->_primaryKeyName === null) {
189
            return;
190
        }
191
 
192
        $this->dropIndex($this->_primaryKeyName);
193
        $this->_primaryKeyName = null;
194
    }
195
 
196
    /**
197
     * Drops an index from this table.
198
     *
199
     * @param string $name The index name.
200
     *
201
     * @return void
202
     *
203
     * @throws SchemaException If the index does not exist.
204
     */
205
    public function dropIndex($name)
206
    {
207
        $name = $this->normalizeIdentifier($name);
208
 
209
        if (! $this->hasIndex($name)) {
210
            throw SchemaException::indexDoesNotExist($name, $this->_name);
211
        }
212
 
213
        unset($this->_indexes[$name]);
214
    }
215
 
216
    /**
217
     * @param string[]    $columnNames
218
     * @param string|null $indexName
219
     * @param mixed[]     $options
220
     *
221
     * @return self
222
     *
223
     * @throws SchemaException
224
     */
225
    public function addUniqueIndex(array $columnNames, $indexName = null, array $options = [])
226
    {
227
        $indexName ??= $this->_generateIdentifierName(
228
            array_merge([$this->getName()], $columnNames),
229
            'uniq',
230
            $this->_getMaxIdentifierLength(),
231
        );
232
 
233
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options));
234
    }
235
 
236
    /**
237
     * Renames an index.
238
     *
239
     * @param string      $oldName The name of the index to rename from.
240
     * @param string|null $newName The name of the index to rename to.
241
     *                                  If null is given, the index name will be auto-generated.
242
     *
243
     * @return self This table instance.
244
     *
245
     * @throws SchemaException If no index exists for the given current name
246
     *                         or if an index with the given new name already exists on this table.
247
     */
248
    public function renameIndex($oldName, $newName = null)
249
    {
250
        $oldName           = $this->normalizeIdentifier($oldName);
251
        $normalizedNewName = $this->normalizeIdentifier($newName);
252
 
253
        if ($oldName === $normalizedNewName) {
254
            return $this;
255
        }
256
 
257
        if (! $this->hasIndex($oldName)) {
258
            throw SchemaException::indexDoesNotExist($oldName, $this->_name);
259
        }
260
 
261
        if ($this->hasIndex($normalizedNewName)) {
262
            throw SchemaException::indexAlreadyExists($normalizedNewName, $this->_name);
263
        }
264
 
265
        $oldIndex = $this->_indexes[$oldName];
266
 
267
        if ($oldIndex->isPrimary()) {
268
            $this->dropPrimaryKey();
269
 
270
            return $this->setPrimaryKey($oldIndex->getColumns(), $newName ?? false);
271
        }
272
 
273
        unset($this->_indexes[$oldName]);
274
 
275
        if ($oldIndex->isUnique()) {
276
            return $this->addUniqueIndex($oldIndex->getColumns(), $newName, $oldIndex->getOptions());
277
        }
278
 
279
        return $this->addIndex($oldIndex->getColumns(), $newName, $oldIndex->getFlags(), $oldIndex->getOptions());
280
    }
281
 
282
    /**
283
     * Checks if an index begins in the order of the given columns.
284
     *
285
     * @param string[] $columnNames
286
     *
287
     * @return bool
288
     */
289
    public function columnsAreIndexed(array $columnNames)
290
    {
291
        foreach ($this->getIndexes() as $index) {
292
            if ($index->spansColumns($columnNames)) {
293
                return true;
294
            }
295
        }
296
 
297
        return false;
298
    }
299
 
300
    /**
301
     * @param string[] $columnNames
302
     * @param string   $indexName
303
     * @param bool     $isUnique
304
     * @param bool     $isPrimary
305
     * @param string[] $flags
306
     * @param mixed[]  $options
307
     *
308
     * @throws SchemaException
309
     */
310
    private function _createIndex(
311
        array $columnNames,
312
        $indexName,
313
        $isUnique,
314
        $isPrimary,
315
        array $flags = [],
316
        array $options = []
317
    ): Index {
318
        if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName)) === 1) {
319
            throw SchemaException::indexNameInvalid($indexName);
320
        }
321
 
322
        foreach ($columnNames as $columnName) {
323
            if (! $this->hasColumn($columnName)) {
324
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
325
            }
326
        }
327
 
328
        return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $options);
329
    }
330
 
331
    /**
332
     * @param string  $name
333
     * @param string  $typeName
334
     * @param mixed[] $options
335
     *
336
     * @return Column
337
     *
338
     * @throws SchemaException
339
     */
340
    public function addColumn($name, $typeName, array $options = [])
341
    {
342
        $column = new Column($name, Type::getType($typeName), $options);
343
 
344
        $this->_addColumn($column);
345
 
346
        return $column;
347
    }
348
 
349
    /**
350
     * Change Column Details.
351
     *
352
     * @deprecated Use {@link modifyColumn()} instead.
353
     *
354
     * @param string  $name
355
     * @param mixed[] $options
356
     *
357
     * @return self
358
     *
359
     * @throws SchemaException
360
     */
361
    public function changeColumn($name, array $options)
362
    {
363
        Deprecation::trigger(
364
            'doctrine/dbal',
365
            'https://github.com/doctrine/dbal/pull/5747',
366
            '%s is deprecated. Use modifyColumn() instead.',
367
            __METHOD__,
368
        );
369
 
370
        return $this->modifyColumn($name, $options);
371
    }
372
 
373
    /**
374
     * @param string  $name
375
     * @param mixed[] $options
376
     *
377
     * @return self
378
     *
379
     * @throws SchemaException
380
     */
381
    public function modifyColumn($name, array $options)
382
    {
383
        $column = $this->getColumn($name);
384
        $column->setOptions($options);
385
 
386
        return $this;
387
    }
388
 
389
    /**
390
     * Drops a Column from the Table.
391
     *
392
     * @param string $name
393
     *
394
     * @return self
395
     */
396
    public function dropColumn($name)
397
    {
398
        $name = $this->normalizeIdentifier($name);
399
 
400
        unset($this->_columns[$name]);
401
 
402
        return $this;
403
    }
404
 
405
    /**
406
     * Adds a foreign key constraint.
407
     *
408
     * Name is inferred from the local columns.
409
     *
410
     * @param Table|string $foreignTable       Table schema instance or table name
411
     * @param string[]     $localColumnNames
412
     * @param string[]     $foreignColumnNames
413
     * @param mixed[]      $options
414
     * @param string|null  $name
415
     *
416
     * @return self
417
     *
418
     * @throws SchemaException
419
     */
420
    public function addForeignKeyConstraint(
421
        $foreignTable,
422
        array $localColumnNames,
423
        array $foreignColumnNames,
424
        array $options = [],
425
        $name = null
426
    ) {
427
        $name ??= $this->_generateIdentifierName(
428
            array_merge([$this->getName()], $localColumnNames),
429
            'fk',
430
            $this->_getMaxIdentifierLength(),
431
        );
432
 
433
        if ($foreignTable instanceof Table) {
434
            foreach ($foreignColumnNames as $columnName) {
435
                if (! $foreignTable->hasColumn($columnName)) {
436
                    throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName());
437
                }
438
            }
439
        }
440
 
441
        foreach ($localColumnNames as $columnName) {
442
            if (! $this->hasColumn($columnName)) {
443
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
444
            }
445
        }
446
 
447
        $constraint = new ForeignKeyConstraint(
448
            $localColumnNames,
449
            $foreignTable,
450
            $foreignColumnNames,
451
            $name,
452
            $options,
453
        );
454
 
455
        return $this->_addForeignKeyConstraint($constraint);
456
    }
457
 
458
    /**
459
     * @param string $name
460
     * @param mixed  $value
461
     *
462
     * @return self
463
     */
464
    public function addOption($name, $value)
465
    {
466
        $this->_options[$name] = $value;
467
 
468
        return $this;
469
    }
470
 
471
    /**
472
     * @return void
473
     *
474
     * @throws SchemaException
475
     */
476
    protected function _addColumn(Column $column)
477
    {
478
        $columnName = $column->getName();
479
        $columnName = $this->normalizeIdentifier($columnName);
480
 
481
        if (isset($this->_columns[$columnName])) {
482
            throw SchemaException::columnAlreadyExists($this->getName(), $columnName);
483
        }
484
 
485
        $this->_columns[$columnName] = $column;
486
    }
487
 
488
    /**
489
     * Adds an index to the table.
490
     *
491
     * @return self
492
     *
493
     * @throws SchemaException
494
     */
495
    protected function _addIndex(Index $indexCandidate)
496
    {
497
        $indexName               = $indexCandidate->getName();
498
        $indexName               = $this->normalizeIdentifier($indexName);
499
        $replacedImplicitIndexes = [];
500
 
501
        foreach ($this->implicitIndexes as $name => $implicitIndex) {
502
            if (! $implicitIndex->isFulfilledBy($indexCandidate) || ! isset($this->_indexes[$name])) {
503
                continue;
504
            }
505
 
506
            $replacedImplicitIndexes[] = $name;
507
        }
508
 
509
        if (
510
            (isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) ||
511
            ($this->_primaryKeyName !== null && $indexCandidate->isPrimary())
512
        ) {
513
            throw SchemaException::indexAlreadyExists($indexName, $this->_name);
514
        }
515
 
516
        foreach ($replacedImplicitIndexes as $name) {
517
            unset($this->_indexes[$name], $this->implicitIndexes[$name]);
518
        }
519
 
520
        if ($indexCandidate->isPrimary()) {
521
            $this->_primaryKeyName = $indexName;
522
        }
523
 
524
        $this->_indexes[$indexName] = $indexCandidate;
525
 
526
        return $this;
527
    }
528
 
529
    /** @return self */
530
    protected function _addUniqueConstraint(UniqueConstraint $constraint): Table
531
    {
532
        $mergedNames = array_merge([$this->getName()], $constraint->getColumns());
533
        $name        = strlen($constraint->getName()) > 0
534
            ? $constraint->getName()
535
            : $this->_generateIdentifierName($mergedNames, 'fk', $this->_getMaxIdentifierLength());
536
 
537
        $name = $this->normalizeIdentifier($name);
538
 
539
        $this->uniqueConstraints[$name] = $constraint;
540
 
541
        // If there is already an index that fulfills this requirements drop the request. In the case of __construct
542
        // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates.
543
        // This creates computation overhead in this case, however no duplicate indexes are ever added (column based).
544
        $indexName = $this->_generateIdentifierName($mergedNames, 'idx', $this->_getMaxIdentifierLength());
545
 
546
        $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, true, false);
547
 
548
        foreach ($this->_indexes as $existingIndex) {
549
            if ($indexCandidate->isFulfilledBy($existingIndex)) {
550
                return $this;
551
            }
552
        }
553
 
554
        $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
555
 
556
        return $this;
557
    }
558
 
559
    /** @return self */
560
    protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint)
561
    {
562
        $constraint->setLocalTable($this);
563
 
564
        if (strlen($constraint->getName()) > 0) {
565
            $name = $constraint->getName();
566
        } else {
567
            $name = $this->_generateIdentifierName(
568
                array_merge([$this->getName()], $constraint->getLocalColumns()),
569
                'fk',
570
                $this->_getMaxIdentifierLength(),
571
            );
572
        }
573
 
574
        $name = $this->normalizeIdentifier($name);
575
 
576
        $this->_fkConstraints[$name] = $constraint;
577
 
578
        /* Add an implicit index (defined by the DBAL) on the foreign key
579
           columns. If there is already a user-defined index that fulfills these
580
           requirements drop the request. In the case of __construct() calling
581
           this method during hydration from schema-details, all the explicitly
582
           added indexes lead to duplicates. This creates computation overhead in
583
           this case, however no duplicate indexes are ever added (based on
584
           columns). */
585
        $indexName = $this->_generateIdentifierName(
586
            array_merge([$this->getName()], $constraint->getColumns()),
587
            'idx',
588
            $this->_getMaxIdentifierLength(),
589
        );
590
 
591
        $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false);
592
 
593
        foreach ($this->_indexes as $existingIndex) {
594
            if ($indexCandidate->isFulfilledBy($existingIndex)) {
595
                return $this;
596
            }
597
        }
598
 
599
        $this->_addIndex($indexCandidate);
600
        $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
601
 
602
        return $this;
603
    }
604
 
605
    /**
606
     * Returns whether this table has a foreign key constraint with the given name.
607
     *
608
     * @param string $name
609
     *
610
     * @return bool
611
     */
612
    public function hasForeignKey($name)
613
    {
614
        $name = $this->normalizeIdentifier($name);
615
 
616
        return isset($this->_fkConstraints[$name]);
617
    }
618
 
619
    /**
620
     * Returns the foreign key constraint with the given name.
621
     *
622
     * @param string $name The constraint name.
623
     *
624
     * @return ForeignKeyConstraint
625
     *
626
     * @throws SchemaException If the foreign key does not exist.
627
     */
628
    public function getForeignKey($name)
629
    {
630
        $name = $this->normalizeIdentifier($name);
631
 
632
        if (! $this->hasForeignKey($name)) {
633
            throw SchemaException::foreignKeyDoesNotExist($name, $this->_name);
634
        }
635
 
636
        return $this->_fkConstraints[$name];
637
    }
638
 
639
    /**
640
     * Removes the foreign key constraint with the given name.
641
     *
642
     * @param string $name The constraint name.
643
     *
644
     * @return void
645
     *
646
     * @throws SchemaException
647
     */
648
    public function removeForeignKey($name)
649
    {
650
        $name = $this->normalizeIdentifier($name);
651
 
652
        if (! $this->hasForeignKey($name)) {
653
            throw SchemaException::foreignKeyDoesNotExist($name, $this->_name);
654
        }
655
 
656
        unset($this->_fkConstraints[$name]);
657
    }
658
 
659
    /**
660
     * Returns whether this table has a unique constraint with the given name.
661
     */
662
    public function hasUniqueConstraint(string $name): bool
663
    {
664
        $name = $this->normalizeIdentifier($name);
665
 
666
        return isset($this->uniqueConstraints[$name]);
667
    }
668
 
669
    /**
670
     * Returns the unique constraint with the given name.
671
     *
672
     * @throws SchemaException If the unique constraint does not exist.
673
     */
674
    public function getUniqueConstraint(string $name): UniqueConstraint
675
    {
676
        $name = $this->normalizeIdentifier($name);
677
 
678
        if (! $this->hasUniqueConstraint($name)) {
679
            throw SchemaException::uniqueConstraintDoesNotExist($name, $this->_name);
680
        }
681
 
682
        return $this->uniqueConstraints[$name];
683
    }
684
 
685
    /**
686
     * Removes the unique constraint with the given name.
687
     *
688
     * @throws SchemaException If the unique constraint does not exist.
689
     */
690
    public function removeUniqueConstraint(string $name): void
691
    {
692
        $name = $this->normalizeIdentifier($name);
693
 
694
        if (! $this->hasUniqueConstraint($name)) {
695
            throw SchemaException::uniqueConstraintDoesNotExist($name, $this->_name);
696
        }
697
 
698
        unset($this->uniqueConstraints[$name]);
699
    }
700
 
701
    /**
702
     * Returns ordered list of columns (primary keys are first, then foreign keys, then the rest)
703
     *
704
     * @return Column[]
705
     */
706
    public function getColumns()
707
    {
708
        $primaryKeyColumns = $this->getPrimaryKey() !== null ? $this->getPrimaryKeyColumns() : [];
709
        $foreignKeyColumns = $this->getForeignKeyColumns();
710
        $remainderColumns  = $this->filterColumns(
711
            array_merge(array_keys($primaryKeyColumns), array_keys($foreignKeyColumns)),
712
            true,
713
        );
714
 
715
        return array_merge($primaryKeyColumns, $foreignKeyColumns, $remainderColumns);
716
    }
717
 
718
    /**
719
     * Returns the foreign key columns
720
     *
721
     * @deprecated Use {@see getForeignKey()} and {@see ForeignKeyConstraint::getLocalColumns()} instead.
722
     *
723
     * @return Column[]
724
     */
725
    public function getForeignKeyColumns()
726
    {
727
        Deprecation::trigger(
728
            'doctrine/dbal',
729
            'https://github.com/doctrine/dbal/pull/5731',
730
            '%s is deprecated. Use getForeignKey() and ForeignKeyConstraint::getLocalColumns() instead.',
731
            __METHOD__,
732
        );
733
 
734
        $foreignKeyColumns = [];
735
 
736
        foreach ($this->getForeignKeys() as $foreignKey) {
737
            $foreignKeyColumns = array_merge($foreignKeyColumns, $foreignKey->getLocalColumns());
738
        }
739
 
740
        return $this->filterColumns($foreignKeyColumns);
741
    }
742
 
743
    /**
744
     * Returns only columns that have specified names
745
     *
746
     * @param string[] $columnNames
747
     *
748
     * @return Column[]
749
     */
750
    private function filterColumns(array $columnNames, bool $reverse = false): array
751
    {
752
        return array_filter($this->_columns, static function (string $columnName) use ($columnNames, $reverse): bool {
753
            return in_array($columnName, $columnNames, true) !== $reverse;
754
        }, ARRAY_FILTER_USE_KEY);
755
    }
756
 
757
    /**
758
     * Returns whether this table has a Column with the given name.
759
     *
760
     * @param string $name The column name.
761
     *
762
     * @return bool
763
     */
764
    public function hasColumn($name)
765
    {
766
        $name = $this->normalizeIdentifier($name);
767
 
768
        return isset($this->_columns[$name]);
769
    }
770
 
771
    /**
772
     * Returns the Column with the given name.
773
     *
774
     * @param string $name The column name.
775
     *
776
     * @return Column
777
     *
778
     * @throws SchemaException If the column does not exist.
779
     */
780
    public function getColumn($name)
781
    {
782
        $name = $this->normalizeIdentifier($name);
783
 
784
        if (! $this->hasColumn($name)) {
785
            throw SchemaException::columnDoesNotExist($name, $this->_name);
786
        }
787
 
788
        return $this->_columns[$name];
789
    }
790
 
791
    /**
792
     * Returns the primary key.
793
     *
794
     * @return Index|null The primary key, or null if this Table has no primary key.
795
     */
796
    public function getPrimaryKey()
797
    {
798
        if ($this->_primaryKeyName !== null) {
799
            return $this->getIndex($this->_primaryKeyName);
800
        }
801
 
802
        return null;
803
    }
804
 
805
    /**
806
     * Returns the primary key columns.
807
     *
808
     * @deprecated Use {@see getPrimaryKey()} and {@see Index::getColumns()} instead.
809
     *
810
     * @return Column[]
811
     *
812
     * @throws Exception
813
     */
814
    public function getPrimaryKeyColumns()
815
    {
816
        Deprecation::trigger(
817
            'doctrine/dbal',
818
            'https://github.com/doctrine/dbal/pull/5731',
819
            '%s is deprecated. Use getPrimaryKey() and Index::getColumns() instead.',
820
            __METHOD__,
821
        );
822
 
823
        $primaryKey = $this->getPrimaryKey();
824
 
825
        if ($primaryKey === null) {
826
            throw new Exception('Table ' . $this->getName() . ' has no primary key.');
827
        }
828
 
829
        return $this->filterColumns($primaryKey->getColumns());
830
    }
831
 
832
    /**
833
     * Returns whether this table has a primary key.
834
     *
835
     * @deprecated Use {@see getPrimaryKey()} instead.
836
     *
837
     * @return bool
838
     */
839
    public function hasPrimaryKey()
840
    {
841
        Deprecation::trigger(
842
            'doctrine/dbal',
843
            'https://github.com/doctrine/dbal/pull/5731',
844
            '%s is deprecated. Use getPrimaryKey() instead.',
845
            __METHOD__,
846
        );
847
 
848
        return $this->_primaryKeyName !== null && $this->hasIndex($this->_primaryKeyName);
849
    }
850
 
851
    /**
852
     * Returns whether this table has an Index with the given name.
853
     *
854
     * @param string $name The index name.
855
     *
856
     * @return bool
857
     */
858
    public function hasIndex($name)
859
    {
860
        $name = $this->normalizeIdentifier($name);
861
 
862
        return isset($this->_indexes[$name]);
863
    }
864
 
865
    /**
866
     * Returns the Index with the given name.
867
     *
868
     * @param string $name The index name.
869
     *
870
     * @return Index
871
     *
872
     * @throws SchemaException If the index does not exist.
873
     */
874
    public function getIndex($name)
875
    {
876
        $name = $this->normalizeIdentifier($name);
877
        if (! $this->hasIndex($name)) {
878
            throw SchemaException::indexDoesNotExist($name, $this->_name);
879
        }
880
 
881
        return $this->_indexes[$name];
882
    }
883
 
884
    /** @return Index[] */
885
    public function getIndexes()
886
    {
887
        return $this->_indexes;
888
    }
889
 
890
    /**
891
     * Returns the unique constraints.
892
     *
893
     * @return UniqueConstraint[]
894
     */
895
    public function getUniqueConstraints(): array
896
    {
897
        return $this->uniqueConstraints;
898
    }
899
 
900
    /**
901
     * Returns the foreign key constraints.
902
     *
903
     * @return ForeignKeyConstraint[]
904
     */
905
    public function getForeignKeys()
906
    {
907
        return $this->_fkConstraints;
908
    }
909
 
910
    /**
911
     * @param string $name
912
     *
913
     * @return bool
914
     */
915
    public function hasOption($name)
916
    {
917
        return isset($this->_options[$name]);
918
    }
919
 
920
    /**
921
     * @param string $name
922
     *
923
     * @return mixed
924
     */
925
    public function getOption($name)
926
    {
927
        return $this->_options[$name];
928
    }
929
 
930
    /** @return mixed[] */
931
    public function getOptions()
932
    {
933
        return $this->_options;
934
    }
935
 
936
    /**
937
     * @deprecated
938
     *
939
     * @return void
940
     *
941
     * @throws SchemaException
942
     */
943
    public function visit(Visitor $visitor)
944
    {
945
        Deprecation::triggerIfCalledFromOutside(
946
            'doctrine/dbal',
947
            'https://github.com/doctrine/dbal/pull/5435',
948
            'Table::visit() is deprecated.',
949
        );
950
 
951
        $visitor->acceptTable($this);
952
 
953
        foreach ($this->getColumns() as $column) {
954
            $visitor->acceptColumn($this, $column);
955
        }
956
 
957
        foreach ($this->getIndexes() as $index) {
958
            $visitor->acceptIndex($this, $index);
959
        }
960
 
961
        foreach ($this->getForeignKeys() as $constraint) {
962
            $visitor->acceptForeignKey($this, $constraint);
963
        }
964
    }
965
 
966
    /**
967
     * Clone of a Table triggers a deep clone of all affected assets.
968
     *
969
     * @return void
970
     */
971
    public function __clone()
972
    {
973
        foreach ($this->_columns as $k => $column) {
974
            $this->_columns[$k] = clone $column;
975
        }
976
 
977
        foreach ($this->_indexes as $k => $index) {
978
            $this->_indexes[$k] = clone $index;
979
        }
980
 
981
        foreach ($this->_fkConstraints as $k => $fk) {
982
            $this->_fkConstraints[$k] = clone $fk;
983
            $this->_fkConstraints[$k]->setLocalTable($this);
984
        }
985
    }
986
 
987
    /**
988
     * @param string[] $columnNames
989
     * @param string[] $flags
990
     * @param mixed[]  $options
991
     *
992
     * @throws SchemaException
993
     */
994
    private function _createUniqueConstraint(
995
        array $columnNames,
996
        string $indexName,
997
        array $flags = [],
998
        array $options = []
999
    ): UniqueConstraint {
1000
        if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName)) === 1) {
1001
            throw SchemaException::indexNameInvalid($indexName);
1002
        }
1003
 
1004
        foreach ($columnNames as $columnName) {
1005
            if (! $this->hasColumn($columnName)) {
1006
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
1007
            }
1008
        }
1009
 
1010
        return new UniqueConstraint($indexName, $columnNames, $flags, $options);
1011
    }
1012
 
1013
    /**
1014
     * Normalizes a given identifier.
1015
     *
1016
     * Trims quotes and lowercases the given identifier.
1017
     *
1018
     * @return string The normalized identifier.
1019
     */
1020
    private function normalizeIdentifier(?string $identifier): string
1021
    {
1022
        if ($identifier === null) {
1023
            return '';
1024
        }
1025
 
1026
        return $this->trimQuotes(strtolower($identifier));
1027
    }
1028
 
1029
    public function setComment(?string $comment): self
1030
    {
1031
        // For keeping backward compatibility with MySQL in previous releases, table comments are stored as options.
1032
        $this->addOption('comment', $comment);
1033
 
1034
        return $this;
1035
    }
1036
 
1037
    public function getComment(): ?string
1038
    {
1039
        return $this->_options['comment'] ?? null;
1040
    }
1041
}