Skip to content

Commit 471b129

Browse files
authored
Merge pull request #12477 from greg0ire/avoid-overwrite
Avoid adding the same foreign key twice for STI
2 parents f2530f2 + 18977e0 commit 471b129

1 file changed

Lines changed: 45 additions & 10 deletions

File tree

src/Tools/SchemaTool.php

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,8 @@ private function gatherColumn(
588588
*
589589
* @phpstan-param array<string, array{
590590
* foreignTableName: string,
591-
* foreignColumns: list<string>
591+
* foreignColumns: list<string>,
592+
* fkOptions: array{onDelete?: string, deferrable?: bool, deferred?: bool}
592593
* }> $addedFks
593594
* @phpstan-param array<string, bool> $blacklistedFks
594595
*
@@ -705,7 +706,8 @@ private function getDefiningClass(ClassMetadata $class, string $referencedColumn
705706
* @phpstan-param list<string> $primaryKeyColumns
706707
* @phpstan-param array<string, array{
707708
* foreignTableName: string,
708-
* foreignColumns: list<string>
709+
* foreignColumns: list<string>,
710+
* fkOptions: array{onDelete?: string, deferrable?: bool, deferred?: bool}
709711
* }> $addedFks
710712
* @phpstan-param array<string,bool> $blacklistedFks
711713
*
@@ -720,8 +722,9 @@ private function gatherRelationJoinColumns(
720722
array &$addedFks,
721723
array &$blacklistedFks,
722724
): void {
723-
$localColumns = [];
724-
$foreignColumns = [];
725+
$localColumns = [];
726+
$foreignColumns = [];
727+
/** @var array{onDelete?: string, deferrable?: bool, deferred?: bool} $fkOptions */
725728
$fkOptions = [];
726729
$foreignTableName = $this->quoteStrategy->getTableName($class, $this->platform);
727730
$uniqueConstraints = [];
@@ -806,11 +809,37 @@ private function gatherRelationJoinColumns(
806809
}
807810

808811
$compositeName = $this->getAssetName($theJoinTable) . '.' . implode('', $localColumns);
809-
if (
810-
isset($addedFks[$compositeName])
811-
&& ($foreignTableName !== $addedFks[$compositeName]['foreignTableName']
812-
|| 0 < count(array_diff($foreignColumns, $addedFks[$compositeName]['foreignColumns'])))
813-
) {
812+
813+
// Check if an FK constraint already exists for this composite key (table + columns)
814+
if (isset($addedFks[$compositeName])) {
815+
$existingFk = $addedFks[$compositeName];
816+
817+
// Determine if the new FK is identical to the existing one
818+
$isForeignTableIdentical = $foreignTableName === $existingFk['foreignTableName'];
819+
$areForeignColumnsIdentical = count(array_diff($foreignColumns, $existingFk['foreignColumns'])) === 0
820+
&& count(array_diff($existingFk['foreignColumns'], $foreignColumns)) === 0;
821+
822+
// Compare FK options that affect constraint identity (onDelete, deferrable, deferred)
823+
$existingOptions = $existingFk['fkOptions'];
824+
$onDeleteMatches = ($fkOptions['onDelete'] ?? null)
825+
=== ($existingOptions['onDelete'] ?? null);
826+
$deferrableMatches = ($fkOptions['deferrable'] ?? null)
827+
=== ($existingOptions['deferrable'] ?? null);
828+
$deferredMatches = ($fkOptions['deferred'] ?? null)
829+
=== ($existingOptions['deferred'] ?? null);
830+
$areOptionsIdentical = $onDeleteMatches && $deferrableMatches && $deferredMatches;
831+
832+
if ($isForeignTableIdentical && $areForeignColumnsIdentical && $areOptionsIdentical) {
833+
// Identical FK already registered - skip adding duplicate.
834+
// This prevents attempting to overwrite an existing FK constraint with an identical one.
835+
// This scenario occurs in Single Table Inheritance (STI) when multiple child entities
836+
// define their own associations using the same join column to the same target entity.
837+
// Since all STI entities share the same physical table, having identical FK constraints
838+
// is semantically correct and necessary for database normalization.
839+
return;
840+
}
841+
842+
// FK exists but is different (conflicting FK) - drop the existing one and blacklist
814843
foreach ($theJoinTable->getForeignKeys() as $fkName => $key) {
815844
if (
816845
class_exists(ForeignKeyConstraintEditor::class)
@@ -835,7 +864,13 @@ class_exists(ForeignKeyConstraintEditor::class)
835864

836865
$blacklistedFks[$compositeName] = true;
837866
} elseif (! isset($blacklistedFks[$compositeName])) {
838-
$addedFks[$compositeName] = ['foreignTableName' => $foreignTableName, 'foreignColumns' => $foreignColumns];
867+
// No existing FK and not blacklisted - add the new FK constraint
868+
// Store FK details including options that affect constraint identity
869+
$addedFks[$compositeName] = [
870+
'foreignTableName' => $foreignTableName,
871+
'foreignColumns' => $foreignColumns,
872+
'fkOptions' => $fkOptions,
873+
];
839874
$theJoinTable->addForeignKeyConstraint(
840875
$foreignTableName,
841876
$localColumns,

0 commit comments

Comments
 (0)