summaryrefslogtreecommitdiff
path: root/vendor/doctrine/dbal/src/Schema/Table.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/doctrine/dbal/src/Schema/Table.php')
-rw-r--r--vendor/doctrine/dbal/src/Schema/Table.php753
1 files changed, 753 insertions, 0 deletions
diff --git a/vendor/doctrine/dbal/src/Schema/Table.php b/vendor/doctrine/dbal/src/Schema/Table.php
new file mode 100644
index 0000000..cc7f04d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Table.php
@@ -0,0 +1,753 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Schema\Exception\ColumnAlreadyExists;
8use Doctrine\DBAL\Schema\Exception\ColumnDoesNotExist;
9use Doctrine\DBAL\Schema\Exception\ForeignKeyDoesNotExist;
10use Doctrine\DBAL\Schema\Exception\IndexAlreadyExists;
11use Doctrine\DBAL\Schema\Exception\IndexDoesNotExist;
12use Doctrine\DBAL\Schema\Exception\IndexNameInvalid;
13use Doctrine\DBAL\Schema\Exception\InvalidTableName;
14use Doctrine\DBAL\Schema\Exception\UniqueConstraintDoesNotExist;
15use Doctrine\DBAL\Types\Type;
16
17use function array_merge;
18use function array_values;
19use function in_array;
20use function is_string;
21use function preg_match;
22use function strtolower;
23
24/**
25 * Object Representation of a table.
26 */
27class Table extends AbstractAsset
28{
29 /** @var Column[] */
30 protected array $_columns = [];
31
32 /** @var Index[] */
33 private array $implicitIndexes = [];
34
35 /** @var Index[] */
36 protected array $_indexes = [];
37
38 protected ?string $_primaryKeyName = null;
39
40 /** @var UniqueConstraint[] */
41 protected array $uniqueConstraints = [];
42
43 /** @var ForeignKeyConstraint[] */
44 protected array $_fkConstraints = [];
45
46 /** @var mixed[] */
47 protected array $_options = [
48 'create_options' => [],
49 ];
50
51 protected ?SchemaConfig $_schemaConfig = null;
52
53 /**
54 * @param array<Column> $columns
55 * @param array<Index> $indexes
56 * @param array<UniqueConstraint> $uniqueConstraints
57 * @param array<ForeignKeyConstraint> $fkConstraints
58 * @param array<string, mixed> $options
59 */
60 public function __construct(
61 string $name,
62 array $columns = [],
63 array $indexes = [],
64 array $uniqueConstraints = [],
65 array $fkConstraints = [],
66 array $options = [],
67 ) {
68 if ($name === '') {
69 throw InvalidTableName::new($name);
70 }
71
72 $this->_setName($name);
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 = array_merge($this->_options, $options);
91 }
92
93 public function setSchemaConfig(SchemaConfig $schemaConfig): void
94 {
95 $this->_schemaConfig = $schemaConfig;
96 }
97
98 /**
99 * Sets the Primary Key.
100 *
101 * @param array<int, string> $columnNames
102 */
103 public function setPrimaryKey(array $columnNames, ?string $indexName = null): self
104 {
105 if ($indexName === null) {
106 $indexName = 'primary';
107 }
108
109 $this->_addIndex($this->_createIndex($columnNames, $indexName, true, true));
110
111 foreach ($columnNames as $columnName) {
112 $column = $this->getColumn($columnName);
113 $column->setNotnull(true);
114 }
115
116 return $this;
117 }
118
119 /**
120 * @param array<int, string> $columnNames
121 * @param array<int, string> $flags
122 * @param array<string, mixed> $options
123 */
124 public function addUniqueConstraint(
125 array $columnNames,
126 ?string $indexName = null,
127 array $flags = [],
128 array $options = [],
129 ): self {
130 $indexName ??= $this->_generateIdentifierName(
131 array_merge([$this->getName()], $columnNames),
132 'uniq',
133 $this->_getMaxIdentifierLength(),
134 );
135
136 return $this->_addUniqueConstraint($this->_createUniqueConstraint($columnNames, $indexName, $flags, $options));
137 }
138
139 /**
140 * @param array<int, string> $columnNames
141 * @param array<int, string> $flags
142 * @param array<string, mixed> $options
143 */
144 public function addIndex(
145 array $columnNames,
146 ?string $indexName = null,
147 array $flags = [],
148 array $options = [],
149 ): self {
150 $indexName ??= $this->_generateIdentifierName(
151 array_merge([$this->getName()], $columnNames),
152 'idx',
153 $this->_getMaxIdentifierLength(),
154 );
155
156 return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options));
157 }
158
159 /**
160 * Drops the primary key from this table.
161 */
162 public function dropPrimaryKey(): void
163 {
164 if ($this->_primaryKeyName === null) {
165 return;
166 }
167
168 $this->dropIndex($this->_primaryKeyName);
169 $this->_primaryKeyName = null;
170 }
171
172 /**
173 * Drops an index from this table.
174 */
175 public function dropIndex(string $name): void
176 {
177 $name = $this->normalizeIdentifier($name);
178
179 if (! $this->hasIndex($name)) {
180 throw IndexDoesNotExist::new($name, $this->_name);
181 }
182
183 unset($this->_indexes[$name]);
184 }
185
186 /**
187 * @param array<int, string> $columnNames
188 * @param array<string, mixed> $options
189 */
190 public function addUniqueIndex(array $columnNames, ?string $indexName = null, array $options = []): self
191 {
192 $indexName ??= $this->_generateIdentifierName(
193 array_merge([$this->getName()], $columnNames),
194 'uniq',
195 $this->_getMaxIdentifierLength(),
196 );
197
198 return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options));
199 }
200
201 /**
202 * Renames an index.
203 *
204 * @param string $oldName The name of the index to rename from.
205 * @param string|null $newName The name of the index to rename to. If null is given, the index name
206 * will be auto-generated.
207 */
208 public function renameIndex(string $oldName, ?string $newName = null): self
209 {
210 $oldName = $this->normalizeIdentifier($oldName);
211 $normalizedNewName = $this->normalizeIdentifier($newName);
212
213 if ($oldName === $normalizedNewName) {
214 return $this;
215 }
216
217 if (! $this->hasIndex($oldName)) {
218 throw IndexDoesNotExist::new($oldName, $this->_name);
219 }
220
221 if ($this->hasIndex($normalizedNewName)) {
222 throw IndexAlreadyExists::new($normalizedNewName, $this->_name);
223 }
224
225 $oldIndex = $this->_indexes[$oldName];
226
227 if ($oldIndex->isPrimary()) {
228 $this->dropPrimaryKey();
229
230 return $this->setPrimaryKey($oldIndex->getColumns(), $newName ?? null);
231 }
232
233 unset($this->_indexes[$oldName]);
234
235 if ($oldIndex->isUnique()) {
236 return $this->addUniqueIndex($oldIndex->getColumns(), $newName, $oldIndex->getOptions());
237 }
238
239 return $this->addIndex($oldIndex->getColumns(), $newName, $oldIndex->getFlags(), $oldIndex->getOptions());
240 }
241
242 /**
243 * Checks if an index begins in the order of the given columns.
244 *
245 * @param array<int, string> $columnNames
246 */
247 public function columnsAreIndexed(array $columnNames): bool
248 {
249 foreach ($this->getIndexes() as $index) {
250 if ($index->spansColumns($columnNames)) {
251 return true;
252 }
253 }
254
255 return false;
256 }
257
258 /** @param array<string, mixed> $options */
259 public function addColumn(string $name, string $typeName, array $options = []): Column
260 {
261 $column = new Column($name, Type::getType($typeName), $options);
262
263 $this->_addColumn($column);
264
265 return $column;
266 }
267
268 /** @param array<string, mixed> $options */
269 public function modifyColumn(string $name, array $options): self
270 {
271 $column = $this->getColumn($name);
272 $column->setOptions($options);
273
274 return $this;
275 }
276
277 /**
278 * Drops a Column from the Table.
279 */
280 public function dropColumn(string $name): self
281 {
282 $name = $this->normalizeIdentifier($name);
283
284 unset($this->_columns[$name]);
285
286 return $this;
287 }
288
289 /**
290 * Adds a foreign key constraint.
291 *
292 * Name is inferred from the local columns.
293 *
294 * @param array<int, string> $localColumnNames
295 * @param array<int, string> $foreignColumnNames
296 * @param array<string, mixed> $options
297 */
298 public function addForeignKeyConstraint(
299 string $foreignTableName,
300 array $localColumnNames,
301 array $foreignColumnNames,
302 array $options = [],
303 ?string $name = null,
304 ): self {
305 $name ??= $this->_generateIdentifierName(
306 array_merge([$this->getName()], $localColumnNames),
307 'fk',
308 $this->_getMaxIdentifierLength(),
309 );
310
311 foreach ($localColumnNames as $columnName) {
312 if (! $this->hasColumn($columnName)) {
313 throw ColumnDoesNotExist::new($columnName, $this->_name);
314 }
315 }
316
317 $constraint = new ForeignKeyConstraint(
318 $localColumnNames,
319 $foreignTableName,
320 $foreignColumnNames,
321 $name,
322 $options,
323 );
324
325 return $this->_addForeignKeyConstraint($constraint);
326 }
327
328 public function addOption(string $name, mixed $value): self
329 {
330 $this->_options[$name] = $value;
331
332 return $this;
333 }
334
335 /**
336 * Returns whether this table has a foreign key constraint with the given name.
337 */
338 public function hasForeignKey(string $name): bool
339 {
340 $name = $this->normalizeIdentifier($name);
341
342 return isset($this->_fkConstraints[$name]);
343 }
344
345 /**
346 * Returns the foreign key constraint with the given name.
347 */
348 public function getForeignKey(string $name): ForeignKeyConstraint
349 {
350 $name = $this->normalizeIdentifier($name);
351
352 if (! $this->hasForeignKey($name)) {
353 throw ForeignKeyDoesNotExist::new($name, $this->_name);
354 }
355
356 return $this->_fkConstraints[$name];
357 }
358
359 /**
360 * Removes the foreign key constraint with the given name.
361 */
362 public function removeForeignKey(string $name): void
363 {
364 $name = $this->normalizeIdentifier($name);
365
366 if (! $this->hasForeignKey($name)) {
367 throw ForeignKeyDoesNotExist::new($name, $this->_name);
368 }
369
370 unset($this->_fkConstraints[$name]);
371 }
372
373 /**
374 * Returns whether this table has a unique constraint with the given name.
375 */
376 public function hasUniqueConstraint(string $name): bool
377 {
378 $name = $this->normalizeIdentifier($name);
379
380 return isset($this->uniqueConstraints[$name]);
381 }
382
383 /**
384 * Returns the unique constraint with the given name.
385 */
386 public function getUniqueConstraint(string $name): UniqueConstraint
387 {
388 $name = $this->normalizeIdentifier($name);
389
390 if (! $this->hasUniqueConstraint($name)) {
391 throw UniqueConstraintDoesNotExist::new($name, $this->_name);
392 }
393
394 return $this->uniqueConstraints[$name];
395 }
396
397 /**
398 * Removes the unique constraint with the given name.
399 */
400 public function removeUniqueConstraint(string $name): void
401 {
402 $name = $this->normalizeIdentifier($name);
403
404 if (! $this->hasUniqueConstraint($name)) {
405 throw UniqueConstraintDoesNotExist::new($name, $this->_name);
406 }
407
408 unset($this->uniqueConstraints[$name]);
409 }
410
411 /**
412 * Returns the list of table columns.
413 *
414 * @return list<Column>
415 */
416 public function getColumns(): array
417 {
418 return array_values($this->_columns);
419 }
420
421 /**
422 * Returns whether this table has a Column with the given name.
423 */
424 public function hasColumn(string $name): bool
425 {
426 $name = $this->normalizeIdentifier($name);
427
428 return isset($this->_columns[$name]);
429 }
430
431 /**
432 * Returns the Column with the given name.
433 */
434 public function getColumn(string $name): Column
435 {
436 $name = $this->normalizeIdentifier($name);
437
438 if (! $this->hasColumn($name)) {
439 throw ColumnDoesNotExist::new($name, $this->_name);
440 }
441
442 return $this->_columns[$name];
443 }
444
445 /**
446 * Returns the primary key.
447 */
448 public function getPrimaryKey(): ?Index
449 {
450 if ($this->_primaryKeyName !== null) {
451 return $this->getIndex($this->_primaryKeyName);
452 }
453
454 return null;
455 }
456
457 /**
458 * Returns whether this table has an Index with the given name.
459 */
460 public function hasIndex(string $name): bool
461 {
462 $name = $this->normalizeIdentifier($name);
463
464 return isset($this->_indexes[$name]);
465 }
466
467 /**
468 * Returns the Index with the given name.
469 */
470 public function getIndex(string $name): Index
471 {
472 $name = $this->normalizeIdentifier($name);
473
474 if (! $this->hasIndex($name)) {
475 throw IndexDoesNotExist::new($name, $this->_name);
476 }
477
478 return $this->_indexes[$name];
479 }
480
481 /** @return array<string, Index> */
482 public function getIndexes(): array
483 {
484 return $this->_indexes;
485 }
486
487 /**
488 * Returns the unique constraints.
489 *
490 * @return array<string, UniqueConstraint>
491 */
492 public function getUniqueConstraints(): array
493 {
494 return $this->uniqueConstraints;
495 }
496
497 /**
498 * Returns the foreign key constraints.
499 *
500 * @return array<string, ForeignKeyConstraint>
501 */
502 public function getForeignKeys(): array
503 {
504 return $this->_fkConstraints;
505 }
506
507 public function hasOption(string $name): bool
508 {
509 return isset($this->_options[$name]);
510 }
511
512 public function getOption(string $name): mixed
513 {
514 return $this->_options[$name] ?? null;
515 }
516
517 /** @return array<string, mixed> */
518 public function getOptions(): array
519 {
520 return $this->_options;
521 }
522
523 /**
524 * Clone of a Table triggers a deep clone of all affected assets.
525 */
526 public function __clone()
527 {
528 foreach ($this->_columns as $k => $column) {
529 $this->_columns[$k] = clone $column;
530 }
531
532 foreach ($this->_indexes as $k => $index) {
533 $this->_indexes[$k] = clone $index;
534 }
535
536 foreach ($this->_fkConstraints as $k => $fk) {
537 $this->_fkConstraints[$k] = clone $fk;
538 }
539 }
540
541 protected function _getMaxIdentifierLength(): int
542 {
543 return $this->_schemaConfig instanceof SchemaConfig
544 ? $this->_schemaConfig->getMaxIdentifierLength()
545 : 63;
546 }
547
548 protected function _addColumn(Column $column): void
549 {
550 $columnName = $column->getName();
551 $columnName = $this->normalizeIdentifier($columnName);
552
553 if (isset($this->_columns[$columnName])) {
554 throw ColumnAlreadyExists::new($this->getName(), $columnName);
555 }
556
557 $this->_columns[$columnName] = $column;
558 }
559
560 /**
561 * Adds an index to the table.
562 */
563 protected function _addIndex(Index $indexCandidate): self
564 {
565 $indexName = $indexCandidate->getName();
566 $indexName = $this->normalizeIdentifier($indexName);
567 $replacedImplicitIndexes = [];
568
569 foreach ($this->implicitIndexes as $name => $implicitIndex) {
570 if (! $implicitIndex->isFulfilledBy($indexCandidate) || ! isset($this->_indexes[$name])) {
571 continue;
572 }
573
574 $replacedImplicitIndexes[] = $name;
575 }
576
577 if (
578 (isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) ||
579 ($this->_primaryKeyName !== null && $indexCandidate->isPrimary())
580 ) {
581 throw IndexAlreadyExists::new($indexName, $this->_name);
582 }
583
584 foreach ($replacedImplicitIndexes as $name) {
585 unset($this->_indexes[$name], $this->implicitIndexes[$name]);
586 }
587
588 if ($indexCandidate->isPrimary()) {
589 $this->_primaryKeyName = $indexName;
590 }
591
592 $this->_indexes[$indexName] = $indexCandidate;
593
594 return $this;
595 }
596
597 protected function _addUniqueConstraint(UniqueConstraint $constraint): self
598 {
599 $name = $constraint->getName() !== ''
600 ? $constraint->getName()
601 : $this->_generateIdentifierName(
602 array_merge((array) $this->getName(), $constraint->getColumns()),
603 'fk',
604 $this->_getMaxIdentifierLength(),
605 );
606
607 $name = $this->normalizeIdentifier($name);
608
609 $this->uniqueConstraints[$name] = $constraint;
610
611 // If there is already an index that fulfills this requirements drop the request. In the case of __construct
612 // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates.
613 // This creates computation overhead in this case, however no duplicate indexes are ever added (column based).
614 $indexName = $this->_generateIdentifierName(
615 array_merge([$this->getName()], $constraint->getColumns()),
616 'idx',
617 $this->_getMaxIdentifierLength(),
618 );
619
620 $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, true, false);
621
622 foreach ($this->_indexes as $existingIndex) {
623 if ($indexCandidate->isFulfilledBy($existingIndex)) {
624 return $this;
625 }
626 }
627
628 $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
629
630 return $this;
631 }
632
633 protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint): self
634 {
635 $name = $constraint->getName() !== ''
636 ? $constraint->getName()
637 : $this->_generateIdentifierName(
638 array_merge((array) $this->getName(), $constraint->getLocalColumns()),
639 'fk',
640 $this->_getMaxIdentifierLength(),
641 );
642
643 $name = $this->normalizeIdentifier($name);
644
645 $this->_fkConstraints[$name] = $constraint;
646
647 // add an explicit index on the foreign key columns.
648 // If there is already an index that fulfills this requirements drop the request. In the case of __construct
649 // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates.
650 // This creates computation overhead in this case, however no duplicate indexes are ever added (column based).
651 $indexName = $this->_generateIdentifierName(
652 array_merge([$this->getName()], $constraint->getLocalColumns()),
653 'idx',
654 $this->_getMaxIdentifierLength(),
655 );
656
657 $indexCandidate = $this->_createIndex($constraint->getLocalColumns(), $indexName, false, false);
658
659 foreach ($this->_indexes as $existingIndex) {
660 if ($indexCandidate->isFulfilledBy($existingIndex)) {
661 return $this;
662 }
663 }
664
665 $this->_addIndex($indexCandidate);
666 $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
667
668 return $this;
669 }
670
671 /**
672 * Normalizes a given identifier.
673 *
674 * Trims quotes and lowercases the given identifier.
675 */
676 private function normalizeIdentifier(?string $identifier): string
677 {
678 if ($identifier === null) {
679 return '';
680 }
681
682 return $this->trimQuotes(strtolower($identifier));
683 }
684
685 public function setComment(string $comment): self
686 {
687 // For keeping backward compatibility with MySQL in previous releases, table comments are stored as options.
688 $this->addOption('comment', $comment);
689
690 return $this;
691 }
692
693 public function getComment(): ?string
694 {
695 return $this->_options['comment'] ?? null;
696 }
697
698 /**
699 * @param array<string|int, string> $columns
700 * @param array<int, string> $flags
701 * @param array<string, mixed> $options
702 */
703 private function _createUniqueConstraint(
704 array $columns,
705 string $indexName,
706 array $flags = [],
707 array $options = [],
708 ): UniqueConstraint {
709 if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName)) === 1) {
710 throw IndexNameInvalid::new($indexName);
711 }
712
713 foreach ($columns as $index => $value) {
714 if (is_string($index)) {
715 $columnName = $index;
716 } else {
717 $columnName = $value;
718 }
719
720 if (! $this->hasColumn($columnName)) {
721 throw ColumnDoesNotExist::new($columnName, $this->_name);
722 }
723 }
724
725 return new UniqueConstraint($indexName, $columns, $flags, $options);
726 }
727
728 /**
729 * @param array<int, string> $columns
730 * @param array<int, string> $flags
731 * @param array<string, mixed> $options
732 */
733 private function _createIndex(
734 array $columns,
735 string $indexName,
736 bool $isUnique,
737 bool $isPrimary,
738 array $flags = [],
739 array $options = [],
740 ): Index {
741 if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName)) === 1) {
742 throw IndexNameInvalid::new($indexName);
743 }
744
745 foreach ($columns as $columnName) {
746 if (! $this->hasColumn($columnName)) {
747 throw ColumnDoesNotExist::new($columnName, $this->_name);
748 }
749 }
750
751 return new Index($indexName, $columns, $isUnique, $isPrimary, $flags, $options);
752 }
753}