From 27fc9a4d7337e9146b2802d87c0520815383fc9c Mon Sep 17 00:00:00 2001 From: Venca Krecl Date: Sun, 15 Jun 2025 09:55:08 +0200 Subject: [PATCH 1/2] chore: allow setting foreign key as deferred --- docs/en/reference/attributes-reference.rst | 1 + src/Mapping/Driver/AttributeDriver.php | 1 + src/Mapping/JoinColumnMapping.php | 3 ++- src/Mapping/JoinColumnProperties.php | 1 + src/Tools/SchemaTool.php | 4 ++++ .../ORM/Functional/SchemaTool/PostgreSqlSchemaToolTest.php | 3 ++- tests/Tests/ORM/Mapping/JoinColumnMappingTest.php | 2 ++ 7 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/en/reference/attributes-reference.rst b/docs/en/reference/attributes-reference.rst index ded3a28902d..fa38f21e04c 100644 --- a/docs/en/reference/attributes-reference.rst +++ b/docs/en/reference/attributes-reference.rst @@ -684,6 +684,7 @@ Optional parameters: affected entities and should be enforced as such on the database constraint level. Defaults to false. - **deferrable**: Determines whether this relation constraint can be deferred. Defaults to false. +- **deferred**: Determines whether this deferrable relation constraint can be IMMEDIATE or DEFERRED. Defaults to false (IMMEDIATE). - **nullable**: Determine whether the related entity is required, or if null is an allowed state for the relation. Defaults to true. - **onDelete**: Cascade Action (Database-level) diff --git a/src/Mapping/Driver/AttributeDriver.php b/src/Mapping/Driver/AttributeDriver.php index e66a837e841..8df98e7121d 100644 --- a/src/Mapping/Driver/AttributeDriver.php +++ b/src/Mapping/Driver/AttributeDriver.php @@ -691,6 +691,7 @@ private function joinColumnToArray(Mapping\JoinColumn|Mapping\InverseJoinColumn $mapping = [ 'name' => $joinColumn->name, 'deferrable' => $joinColumn->deferrable, + 'deferred' => $joinColumn->deferred, 'unique' => $joinColumn->unique, 'nullable' => $joinColumn->nullable, 'onDelete' => $joinColumn->onDelete, diff --git a/src/Mapping/JoinColumnMapping.php b/src/Mapping/JoinColumnMapping.php index bec273be46f..1ba78a827e6 100644 --- a/src/Mapping/JoinColumnMapping.php +++ b/src/Mapping/JoinColumnMapping.php @@ -14,6 +14,7 @@ final class JoinColumnMapping implements ArrayAccess use ArrayAccessImplementation; public bool|null $deferrable = null; + public bool|null $deferred = null; public bool|null $unique = null; public bool|null $quoted = null; public string|null $fieldName = null; @@ -67,7 +68,7 @@ public function __sleep(): array } } - foreach (['deferrable', 'unique', 'quoted', 'nullable'] as $boolKey) { + foreach (['deferrable', 'deferred', 'unique', 'quoted', 'nullable'] as $boolKey) { if ($this->$boolKey !== null) { $serialized[] = $boolKey; } diff --git a/src/Mapping/JoinColumnProperties.php b/src/Mapping/JoinColumnProperties.php index e7e68989f02..46bbe8d863b 100644 --- a/src/Mapping/JoinColumnProperties.php +++ b/src/Mapping/JoinColumnProperties.php @@ -11,6 +11,7 @@ public function __construct( public readonly string|null $name = null, public readonly string|null $referencedColumnName = null, public readonly bool $deferrable = false, + public readonly bool $deferred = false, public readonly bool $unique = false, public readonly bool|null $nullable = null, public readonly mixed $onDelete = null, diff --git a/src/Tools/SchemaTool.php b/src/Tools/SchemaTool.php index c20d5b05fa4..2204b428a65 100644 --- a/src/Tools/SchemaTool.php +++ b/src/Tools/SchemaTool.php @@ -728,6 +728,10 @@ private function gatherRelationJoinColumns( if (isset($joinColumn->deferrable)) { $fkOptions['deferrable'] = $joinColumn->deferrable; } + + if (isset($joinColumn->deferred)) { + $fkOptions['deferred'] = $joinColumn->deferred; + } } // Prefer unique constraints over implicit simple indexes created for foreign keys. diff --git a/tests/Tests/ORM/Functional/SchemaTool/PostgreSqlSchemaToolTest.php b/tests/Tests/ORM/Functional/SchemaTool/PostgreSqlSchemaToolTest.php index 6cb05979ba4..86eaeb414c3 100644 --- a/tests/Tests/ORM/Functional/SchemaTool/PostgreSqlSchemaToolTest.php +++ b/tests/Tests/ORM/Functional/SchemaTool/PostgreSqlSchemaToolTest.php @@ -56,6 +56,7 @@ public function testSetDeferrableForeignKey(): void self::assertCount(1, $fks); self::assertTrue($fks[0]->getOption('deferrable')); + self::assertTrue($fks[0]->getOption('deferred')); } } @@ -127,6 +128,6 @@ class EntityWithSelfReferencingAssociation private int $id; #[ManyToOne(targetEntity: self::class)] - #[JoinColumn(deferrable: true)] + #[JoinColumn(deferrable: true, deferred: true)] private self $parent; } diff --git a/tests/Tests/ORM/Mapping/JoinColumnMappingTest.php b/tests/Tests/ORM/Mapping/JoinColumnMappingTest.php index 2728b39e605..cdb5db58da6 100644 --- a/tests/Tests/ORM/Mapping/JoinColumnMappingTest.php +++ b/tests/Tests/ORM/Mapping/JoinColumnMappingTest.php @@ -18,6 +18,7 @@ public function testItSurvivesSerialization(): void $mapping = new JoinColumnMapping('foo', 'id'); $mapping->deferrable = true; + $mapping->deferred = true; $mapping->unique = true; $mapping->quoted = true; $mapping->fieldName = 'bar'; @@ -31,6 +32,7 @@ public function testItSurvivesSerialization(): void assert($resurrectedMapping instanceof JoinColumnMapping); self::assertTrue($resurrectedMapping->deferrable); + self::assertTrue($resurrectedMapping->deferred); self::assertSame('foo', $resurrectedMapping->name); self::assertTrue($resurrectedMapping->unique); self::assertTrue($resurrectedMapping->quoted); From 4e4c57cb1eddb826a613bba2fdfb306cca89c5cc Mon Sep 17 00:00:00 2001 From: Venca Date: Thu, 11 Sep 2025 20:57:24 +0200 Subject: [PATCH 2/2] Update docs/en/reference/attributes-reference.rst Co-authored-by: Simon Podlipsky --- docs/en/reference/attributes-reference.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/reference/attributes-reference.rst b/docs/en/reference/attributes-reference.rst index fa38f21e04c..73405319057 100644 --- a/docs/en/reference/attributes-reference.rst +++ b/docs/en/reference/attributes-reference.rst @@ -684,7 +684,7 @@ Optional parameters: affected entities and should be enforced as such on the database constraint level. Defaults to false. - **deferrable**: Determines whether this relation constraint can be deferred. Defaults to false. -- **deferred**: Determines whether this deferrable relation constraint can be IMMEDIATE or DEFERRED. Defaults to false (IMMEDIATE). +- **deferred**: Determines whether this deferrable relation constraint can be INITIALLY IMMEDIATE or INITIALLY DEFERRED. Defaults to false (INITIALLY IMMEDIATE). - **nullable**: Determine whether the related entity is required, or if null is an allowed state for the relation. Defaults to true. - **onDelete**: Cascade Action (Database-level)