From bd260d1be88bb1cf4c44f18d831d005d2d6b53b3 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Thu, 13 Apr 2023 11:40:03 +0200 Subject: [PATCH] feat: allow setting foreign key as deferrable --- 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 +++ .../SchemaTool/PostgreSqlSchemaToolTest.php | 32 +++++++++++++++++++ .../ORM/Mapping/JoinColumnMappingTest.php | 2 ++ 7 files changed, 43 insertions(+), 1 deletion(-) diff --git a/docs/en/reference/attributes-reference.rst b/docs/en/reference/attributes-reference.rst index d3a1eb04674..c467384fbd5 100644 --- a/docs/en/reference/attributes-reference.rst +++ b/docs/en/reference/attributes-reference.rst @@ -672,6 +672,7 @@ Optional parameters: - **unique**: Determines whether this relation is exclusive between the 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. - **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 e337d60ea55..1b558c82ee1 100644 --- a/src/Mapping/Driver/AttributeDriver.php +++ b/src/Mapping/Driver/AttributeDriver.php @@ -684,6 +684,7 @@ private function joinColumnToArray(Mapping\JoinColumn|Mapping\InverseJoinColumn { $mapping = [ 'name' => $joinColumn->name, + 'deferrable' => $joinColumn->deferrable, 'unique' => $joinColumn->unique, 'nullable' => $joinColumn->nullable, 'onDelete' => $joinColumn->onDelete, diff --git a/src/Mapping/JoinColumnMapping.php b/src/Mapping/JoinColumnMapping.php index 86ea1dc09c8..bec273be46f 100644 --- a/src/Mapping/JoinColumnMapping.php +++ b/src/Mapping/JoinColumnMapping.php @@ -13,6 +13,7 @@ final class JoinColumnMapping implements ArrayAccess { use ArrayAccessImplementation; + public bool|null $deferrable = null; public bool|null $unique = null; public bool|null $quoted = null; public string|null $fieldName = null; @@ -66,7 +67,7 @@ public function __sleep(): array } } - foreach (['unique', 'quoted', 'nullable'] as $boolKey) { + foreach (['deferrable', 'unique', 'quoted', 'nullable'] as $boolKey) { if ($this->$boolKey !== null) { $serialized[] = $boolKey; } diff --git a/src/Mapping/JoinColumnProperties.php b/src/Mapping/JoinColumnProperties.php index b231d834d79..dcf23439b5e 100644 --- a/src/Mapping/JoinColumnProperties.php +++ b/src/Mapping/JoinColumnProperties.php @@ -10,6 +10,7 @@ trait JoinColumnProperties public function __construct( public readonly string|null $name = null, public readonly string|null $referencedColumnName = null, + public readonly bool $deferrable = false, public readonly bool $unique = false, public readonly bool $nullable = true, public readonly mixed $onDelete = null, diff --git a/src/Tools/SchemaTool.php b/src/Tools/SchemaTool.php index 5057d1866a8..c4a6d087440 100644 --- a/src/Tools/SchemaTool.php +++ b/src/Tools/SchemaTool.php @@ -709,6 +709,10 @@ private function gatherRelationJoinColumns( if (isset($joinColumn->onDelete)) { $fkOptions['onDelete'] = $joinColumn->onDelete; } + + if (isset($joinColumn->deferrable)) { + $fkOptions['deferrable'] = $joinColumn->deferrable; + } } // 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 61aef5d4040..6cb05979ba4 100644 --- a/tests/Tests/ORM/Functional/SchemaTool/PostgreSqlSchemaToolTest.php +++ b/tests/Tests/ORM/Functional/SchemaTool/PostgreSqlSchemaToolTest.php @@ -16,6 +16,7 @@ use PHPUnit\Framework\Attributes\Group; use function array_filter; +use function array_values; use function implode; use function str_starts_with; @@ -42,6 +43,20 @@ public function testUpdateSchemaWithPostgreSQLSchema(): void self::assertCount(0, $sql, implode("\n", $sql)); } + + public function testSetDeferrableForeignKey(): void + { + $schema = $this->getSchemaForModels( + EntityWithSelfReferencingAssociation::class, + ); + + $table = $schema->getTable('entitywithselfreferencingassociation'); + $fks = array_values($table->getForeignKeys()); + + self::assertCount(1, $fks); + + self::assertTrue($fks[0]->getOption('deferrable')); + } } #[Table(name: 'stonewood.screen')] @@ -98,3 +113,20 @@ class DDC1657Avatar #[Column(name: 'pk', type: 'integer', nullable: false)] private int $pk; } + +#[Table(name: 'entitywithselfreferencingassociation')] +#[Entity] +class EntityWithSelfReferencingAssociation +{ + /** + * Identifier + */ + #[Id] + #[GeneratedValue(strategy: 'IDENTITY')] + #[Column(type: 'integer', nullable: false)] + private int $id; + + #[ManyToOne(targetEntity: self::class)] + #[JoinColumn(deferrable: true)] + private self $parent; +} diff --git a/tests/Tests/ORM/Mapping/JoinColumnMappingTest.php b/tests/Tests/ORM/Mapping/JoinColumnMappingTest.php index b5459db7f76..2728b39e605 100644 --- a/tests/Tests/ORM/Mapping/JoinColumnMappingTest.php +++ b/tests/Tests/ORM/Mapping/JoinColumnMappingTest.php @@ -17,6 +17,7 @@ public function testItSurvivesSerialization(): void { $mapping = new JoinColumnMapping('foo', 'id'); + $mapping->deferrable = true; $mapping->unique = true; $mapping->quoted = true; $mapping->fieldName = 'bar'; @@ -29,6 +30,7 @@ public function testItSurvivesSerialization(): void $resurrectedMapping = unserialize(serialize($mapping)); assert($resurrectedMapping instanceof JoinColumnMapping); + self::assertTrue($resurrectedMapping->deferrable); self::assertSame('foo', $resurrectedMapping->name); self::assertTrue($resurrectedMapping->unique); self::assertTrue($resurrectedMapping->quoted);