From 7988b08d46663a17a2567cf5578e7060a326ebf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Pel=C3=AD=C5=A1ek?= Date: Fri, 21 Jun 2024 11:26:07 +0200 Subject: [PATCH 01/17] Update Configurator.php --- src/Configurator.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Configurator.php b/src/Configurator.php index e5b06ebe..cf3dc6f7 100644 --- a/src/Configurator.php +++ b/src/Configurator.php @@ -120,6 +120,11 @@ public function initFields(EntitySchema $entity, \ReflectionClass $class, string public function initRelations(EntitySchema $entity, \ReflectionClass $class): void { foreach ($class->getProperties() as $property) { + // ignore relations declared by parent class - otherwise all the relation columns declared in parent would be duplicated across all child tables in JTI + if ($property->getDeclaringClass()->getName() !== $class->getName()) { + continue; + } + $metadata = $this->getPropertyMetadata($property, RelationAnnotation\RelationInterface::class); foreach ($metadata as $meta) { From fb0f28f0e7ed3ae577b6f68ded1007280fd60e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Pel=C3=AD=C5=A1ek?= Date: Fri, 21 Jun 2024 11:35:25 +0200 Subject: [PATCH 02/17] Codestyle --- src/Configurator.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Configurator.php b/src/Configurator.php index cf3dc6f7..5e47e672 100644 --- a/src/Configurator.php +++ b/src/Configurator.php @@ -120,11 +120,12 @@ public function initFields(EntitySchema $entity, \ReflectionClass $class, string public function initRelations(EntitySchema $entity, \ReflectionClass $class): void { foreach ($class->getProperties() as $property) { - // ignore relations declared by parent class - otherwise all the relation columns declared in parent would be duplicated across all child tables in JTI + // ignore properties declared by parent class + // otherwise all the relation columns declared in parent would be duplicated across all child tables in JTI if ($property->getDeclaringClass()->getName() !== $class->getName()) { continue; } - + $metadata = $this->getPropertyMetadata($property, RelationAnnotation\RelationInterface::class); foreach ($metadata as $meta) { From ad298862f2a6fabd152a91b3adb9a8b4e4256b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Pel=C3=AD=C5=A1ek?= Date: Fri, 21 Jun 2024 12:13:59 +0200 Subject: [PATCH 03/17] Implement logic to identity ownership of property --- src/Configurator.php | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Configurator.php b/src/Configurator.php index 5e47e672..05c3522b 100644 --- a/src/Configurator.php +++ b/src/Configurator.php @@ -122,7 +122,7 @@ public function initRelations(EntitySchema $entity, \ReflectionClass $class): vo foreach ($class->getProperties() as $property) { // ignore properties declared by parent class // otherwise all the relation columns declared in parent would be duplicated across all child tables in JTI - if ($property->getDeclaringClass()->getName() !== $class->getName()) { + if ($this->propertyBelongsToOtherEntity($class, $property->getDeclaringClass())) { continue; } @@ -419,4 +419,26 @@ private function isOnInsertGeneratedField(Field $field): bool default => $field->isPrimary() }; } + + private function propertyBelongsToOtherEntity(\ReflectionClass $currentClass, \ReflectionClass $declaringClass): bool + { + if ($currentClass->getName() === $declaringClass->getName()) { + return false; + } + + $parentClass = $currentClass->getParentClass(); + + // not possible to happen for logical reasons, but defensively check anyway + if (!$parentClass instanceof \ReflectionClass) { + return false; + } + + // if a parent class in hierarchy is an Entity on its own, the property belongs to that Entity + if (\count($parentClass->getAttributes(Entity::class)) > 0) { + return true; + } + + // continue until we find a declaringClass or Entity attribute + return $this->propertyBelongsToOtherEntity($parentClass, $declaringClass); + } } From 8954c013030a3089868bc5edfd079e5183a8d19b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Pel=C3=AD=C5=A1ek?= Date: Fri, 21 Jun 2024 12:19:10 +0200 Subject: [PATCH 04/17] Added comment --- src/Configurator.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Configurator.php b/src/Configurator.php index 05c3522b..8c40402a 100644 --- a/src/Configurator.php +++ b/src/Configurator.php @@ -422,6 +422,7 @@ private function isOnInsertGeneratedField(Field $field): bool private function propertyBelongsToOtherEntity(\ReflectionClass $currentClass, \ReflectionClass $declaringClass): bool { + // if the current class is the same as declaring class, than the property belongs to current Entity if ($currentClass->getName() === $declaringClass->getName()) { return false; } From 90989a4e88d68e7c3504e47587ede43cc42f1ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Pel=C3=AD=C5=A1ek?= Date: Fri, 21 Jun 2024 23:48:35 +0200 Subject: [PATCH 05/17] Implement entity lookup in hierarchy, closes #101 --- src/Configurator.php | 51 +++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/src/Configurator.php b/src/Configurator.php index 8c40402a..1b38aa49 100644 --- a/src/Configurator.php +++ b/src/Configurator.php @@ -112,7 +112,7 @@ public function initFields(EntitySchema $entity, \ReflectionClass $class, string } $field = $this->initField($property->getName(), $column, $class, $columnPrefix); - $field->setEntityClass($property->getDeclaringClass()->getName()); + $field->setEntityClass($this->findOwningEntity($class, $property->getDeclaringClass())->getName()); $entity->getFields()->set($property->getName(), $field); } } @@ -120,9 +120,9 @@ public function initFields(EntitySchema $entity, \ReflectionClass $class, string public function initRelations(EntitySchema $entity, \ReflectionClass $class): void { foreach ($class->getProperties() as $property) { - // ignore properties declared by parent class + // ignore properties declared by parent entties // otherwise all the relation columns declared in parent would be duplicated across all child tables in JTI - if ($this->propertyBelongsToOtherEntity($class, $property->getDeclaringClass())) { + if ($this->findOwningEntity($class, $property->getDeclaringClass())->getName() !== $class->getName()) { continue; } @@ -420,26 +420,37 @@ private function isOnInsertGeneratedField(Field $field): bool }; } - private function propertyBelongsToOtherEntity(\ReflectionClass $currentClass, \ReflectionClass $declaringClass): bool + /** + * Function to find an owning entity class in the inheritance hierarchy. + * + * Entity classes may extend a base class and this function is needed route the properties from declaring class to the entity class. + * The function stops only when the declaring class is truly found, it does not naively stop on first entity. + * This behaviour makes it also functional in cases of Joined Table Inheritance on theoretically any number of nesting levels. + */ + private function findOwningEntity(\ReflectionClass $currentClass, \ReflectionClass $declaringClass): \ReflectionClass { - // if the current class is the same as declaring class, than the property belongs to current Entity - if ($currentClass->getName() === $declaringClass->getName()) { - return false; - } - - $parentClass = $currentClass->getParentClass(); + // latest found entityClass before declaringClass + $latestEntityClass = $currentClass; + + do { + // we found declaringClass in the hierarchy + // in most cases the execution will stop here in first loop + if ($currentClass->getName() === $declaringClass->getName()) { + return $latestEntityClass; + } - // not possible to happen for logical reasons, but defensively check anyway - if (!$parentClass instanceof \ReflectionClass) { - return false; - } + $currentClass = $currentClass->getParentClass(); - // if a parent class in hierarchy is an Entity on its own, the property belongs to that Entity - if (\count($parentClass->getAttributes(Entity::class)) > 0) { - return true; - } + // not possible to happen for logical reasons, but defensively check anyway + if (!$currentClass instanceof \ReflectionClass) { + return $latestEntityClass; + } - // continue until we find a declaringClass or Entity attribute - return $this->propertyBelongsToOtherEntity($parentClass, $declaringClass); + // if a currentClass in hierarchy is an entity on its own, the property belongs to that entity + if (\count($currentClass->getAttributes(Entity::class)) > 0) { + $latestEntityClass = $currentClass; + } + } while (true); // the inheritance hierarchy cannot be infinite } } + From 300e21d0005225594e53421d199b678d8ac08753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Pel=C3=AD=C5=A1ek?= Date: Fri, 21 Jun 2024 23:49:28 +0200 Subject: [PATCH 06/17] Codestyle --- src/Configurator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Configurator.php b/src/Configurator.php index 1b38aa49..16592e84 100644 --- a/src/Configurator.php +++ b/src/Configurator.php @@ -422,7 +422,7 @@ private function isOnInsertGeneratedField(Field $field): bool /** * Function to find an owning entity class in the inheritance hierarchy. - * + * * Entity classes may extend a base class and this function is needed route the properties from declaring class to the entity class. * The function stops only when the declaring class is truly found, it does not naively stop on first entity. * This behaviour makes it also functional in cases of Joined Table Inheritance on theoretically any number of nesting levels. From 5b965a00acc4965986bcaa811ff0c15e7796d43e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Pel=C3=AD=C5=A1ek?= Date: Sun, 23 Jun 2024 11:01:56 +0200 Subject: [PATCH 07/17] Codestyle --- src/Configurator.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Configurator.php b/src/Configurator.php index 16592e84..fb422152 100644 --- a/src/Configurator.php +++ b/src/Configurator.php @@ -453,4 +453,3 @@ private function findOwningEntity(\ReflectionClass $currentClass, \ReflectionCla } while (true); // the inheritance hierarchy cannot be infinite } } - From 3b612fcf4f9be424f9203bb9e1a548aefce48bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Pel=C3=AD=C5=A1ek?= Date: Sun, 23 Jun 2024 11:03:07 +0200 Subject: [PATCH 08/17] Downgrade mysql in tests to 8.0 --- tests/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 5ee2054f..2c2cf73c 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -11,7 +11,7 @@ services: ACCEPT_EULA: "Y" mysql_latest: - image: mysql:latest + image: mysql:8.0 restart: always command: --default-authentication-plugin=mysql_native_password ports: From 309bf55909e3118568f6d91ff4954570406baa7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Pel=C3=AD=C5=A1ek?= Date: Sun, 23 Jun 2024 11:29:04 +0200 Subject: [PATCH 09/17] Fixed dataprovider deprecations introduced in phpunit 10.5.17 --- tests/Annotated/Functional/Driver/Common/BaseTestCase.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Annotated/Functional/Driver/Common/BaseTestCase.php b/tests/Annotated/Functional/Driver/Common/BaseTestCase.php index 05fbcda0..54f3a2c6 100644 --- a/tests/Annotated/Functional/Driver/Common/BaseTestCase.php +++ b/tests/Annotated/Functional/Driver/Common/BaseTestCase.php @@ -115,14 +115,14 @@ public function getDriver(): Driver public static function singularReadersProvider(): \Traversable { - yield ['Annotation reader' => new AnnotationReader()]; - yield ['Attribute reader' => new AttributeReader()]; + yield ['reader' => new AnnotationReader()]; + yield ['reader' => new AttributeReader()]; } public static function allReadersProvider(): \Traversable { yield from static::singularReadersProvider(); - yield ['Selective reader' => new SelectiveReader([new AttributeReader(), new AnnotationReader()])]; + yield ['reader' => new SelectiveReader([new AttributeReader(), new AnnotationReader()])]; } protected function getDatabase(): Database From c7ed9bdae626d6edea6fea05307791adf9b9bb30 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 25 Jun 2024 11:15:13 +0400 Subject: [PATCH 10/17] Fix inheritance tests; revert changes in metadata reader providers --- src/TableInheritance.php | 6 +++++- tests/Annotated/Fixtures/Fixtures16/ExecutiveProxy.php | 5 ++--- tests/Annotated/Functional/Driver/Common/BaseTestCase.php | 6 +++--- .../Driver/Common/Inheritance/JoinedTableTestCase.php | 2 +- .../Functional/Driver/Common/InheritanceTestCase.php | 7 ++++++- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/TableInheritance.php b/src/TableInheritance.php index 176a36f2..3152c15f 100644 --- a/src/TableInheritance.php +++ b/src/TableInheritance.php @@ -66,7 +66,11 @@ public function run(Registry $registry): Registry // All child should be presented in a schema as separated entity // Every child will be handled according its table inheritance type - \assert($child->getRole() !== null && $entity !== null && isset($parent)); + // todo should $parent be not null? + // \assert(isset($parent)); + + \assert($child->getRole() !== null && $entity !== null); + if (!$registry->hasEntity($child->getRole())) { $registry->register($child); diff --git a/tests/Annotated/Fixtures/Fixtures16/ExecutiveProxy.php b/tests/Annotated/Fixtures/Fixtures16/ExecutiveProxy.php index c4d6356b..0adf88db 100644 --- a/tests/Annotated/Fixtures/Fixtures16/ExecutiveProxy.php +++ b/tests/Annotated/Fixtures/Fixtures16/ExecutiveProxy.php @@ -10,12 +10,11 @@ /** * This proxy class doesn't have an {@see Entity} annotation (attribute) declaration, * and it shouldn't be presented in Schema. - * Note: this behavior might be improved. There will be added support for - * annotated base class columns without Entity annotation declaration. + * But all the classes that extend this class should contain all the fields from this class. */ class ExecutiveProxy extends Employee { - /** @Column(type="string") */ + /** @Column(type="string", name="proxy") */ #[Column(type: 'string', name: 'proxy')] public ?string $proxyFieldWithAnnotation = null; diff --git a/tests/Annotated/Functional/Driver/Common/BaseTestCase.php b/tests/Annotated/Functional/Driver/Common/BaseTestCase.php index 54f3a2c6..05fbcda0 100644 --- a/tests/Annotated/Functional/Driver/Common/BaseTestCase.php +++ b/tests/Annotated/Functional/Driver/Common/BaseTestCase.php @@ -115,14 +115,14 @@ public function getDriver(): Driver public static function singularReadersProvider(): \Traversable { - yield ['reader' => new AnnotationReader()]; - yield ['reader' => new AttributeReader()]; + yield ['Annotation reader' => new AnnotationReader()]; + yield ['Attribute reader' => new AttributeReader()]; } public static function allReadersProvider(): \Traversable { yield from static::singularReadersProvider(); - yield ['reader' => new SelectiveReader([new AttributeReader(), new AnnotationReader()])]; + yield ['Selective reader' => new SelectiveReader([new AttributeReader(), new AnnotationReader()])]; } protected function getDatabase(): Database diff --git a/tests/Annotated/Functional/Driver/Common/Inheritance/JoinedTableTestCase.php b/tests/Annotated/Functional/Driver/Common/Inheritance/JoinedTableTestCase.php index 52ec0e69..64d32f53 100644 --- a/tests/Annotated/Functional/Driver/Common/Inheritance/JoinedTableTestCase.php +++ b/tests/Annotated/Functional/Driver/Common/Inheritance/JoinedTableTestCase.php @@ -125,7 +125,7 @@ public function testTableInheritance(ReaderInterface $reader): void $this->assertSame('secret', $loadedExecutive->hidden); $this->assertSame(15000, $loadedExecutive->bonus); $this->assertSame('executive', $loadedExecutive->getType()); - $this->assertNull($loadedExecutive->proxyFieldWithAnnotation); + $this->assertSame('value', $loadedExecutive->proxyFieldWithAnnotation); } #[DataProvider('allReadersProvider')] diff --git a/tests/Annotated/Functional/Driver/Common/InheritanceTestCase.php b/tests/Annotated/Functional/Driver/Common/InheritanceTestCase.php index 515fd52b..24457413 100644 --- a/tests/Annotated/Functional/Driver/Common/InheritanceTestCase.php +++ b/tests/Annotated/Functional/Driver/Common/InheritanceTestCase.php @@ -109,7 +109,12 @@ public function testTableInheritance(ReaderInterface $reader): void $this->assertSame('foo_id', $schema['executive'][SchemaInterface::PARENT_KEY]); $this->assertSame('executives', $schema['executive'][SchemaInterface::TABLE]); $this->assertSame( - ['bonus' => 'bonus', 'foo_id' => 'id', 'hidden' => 'hidden'], + [ + 'bonus' => 'bonus', + 'proxyFieldWithAnnotation' => 'proxy', + 'foo_id' => 'id', + 'hidden' => 'hidden', + ], $schema['executive'][SchemaInterface::COLUMNS] ); From eba2dba083b930ec5c2d76f2205743b55da573ee Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 25 Jun 2024 13:19:37 +0400 Subject: [PATCH 11/17] Fix data provider keys --- tests/Annotated/Functional/Driver/Common/BaseTestCase.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Annotated/Functional/Driver/Common/BaseTestCase.php b/tests/Annotated/Functional/Driver/Common/BaseTestCase.php index 05fbcda0..4d1111b9 100644 --- a/tests/Annotated/Functional/Driver/Common/BaseTestCase.php +++ b/tests/Annotated/Functional/Driver/Common/BaseTestCase.php @@ -115,14 +115,14 @@ public function getDriver(): Driver public static function singularReadersProvider(): \Traversable { - yield ['Annotation reader' => new AnnotationReader()]; - yield ['Attribute reader' => new AttributeReader()]; + yield 'Annotation reader' => [new AnnotationReader()]; + yield 'Attribute reader' => [new AttributeReader()]; } public static function allReadersProvider(): \Traversable { yield from static::singularReadersProvider(); - yield ['Selective reader' => new SelectiveReader([new AttributeReader(), new AnnotationReader()])]; + yield 'Selective reader' => [new SelectiveReader([new AttributeReader(), new AnnotationReader()])]; } protected function getDatabase(): Database From 8da78c1608b2dde5ccae09daa789bd9a86f67d7e Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 2 Jul 2024 17:08:41 +0400 Subject: [PATCH 12/17] Add test with inherited relation --- src/Configurator.php | 2 +- .../Fixtures/Fixtures16/Executive.php | 9 +++++++ .../Fixtures/Fixtures16/Executive2.php | 18 +++++++++++++ .../Annotated/Fixtures/Fixtures16/Person.php | 9 +++++++ tests/Annotated/Fixtures/Fixtures16/Tool.php | 22 ++++++++++++++++ .../Driver/Common/InheritanceTestCase.php | 26 ++++++++++++++++--- 6 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 tests/Annotated/Fixtures/Fixtures16/Executive2.php create mode 100644 tests/Annotated/Fixtures/Fixtures16/Tool.php diff --git a/src/Configurator.php b/src/Configurator.php index fb422152..4a1d9a4a 100644 --- a/src/Configurator.php +++ b/src/Configurator.php @@ -120,7 +120,7 @@ public function initFields(EntitySchema $entity, \ReflectionClass $class, string public function initRelations(EntitySchema $entity, \ReflectionClass $class): void { foreach ($class->getProperties() as $property) { - // ignore properties declared by parent entties + // ignore properties declared by parent entities // otherwise all the relation columns declared in parent would be duplicated across all child tables in JTI if ($this->findOwningEntity($class, $property->getDeclaringClass())->getName() !== $class->getName()) { continue; diff --git a/tests/Annotated/Fixtures/Fixtures16/Executive.php b/tests/Annotated/Fixtures/Fixtures16/Executive.php index 4e9f4f97..b09be115 100644 --- a/tests/Annotated/Fixtures/Fixtures16/Executive.php +++ b/tests/Annotated/Fixtures/Fixtures16/Executive.php @@ -7,6 +7,7 @@ use Cycle\Annotated\Annotation\Column; use Cycle\Annotated\Annotation\Entity; use Cycle\Annotated\Annotation\Inheritance\JoinedTable as InheritanceJoinedTable; +use Cycle\Annotated\Annotation\Relation\HasOne; /** * @Entity @@ -21,4 +22,12 @@ class Executive extends ExecutiveProxy /** @Column(type="int") */ #[Column(type: 'int')] public int $bonus; + + /** @Column(type="int", nullable=true, typecast="int") */ + #[Column(type: 'int', nullable: true, typecast: 'int')] + public ?int $added_tool_id; + + /** @HasOne(target=Tool::class, innerKey="added_tool_id", outerKey="added_tool_id", nullable=true) */ + #[HasOne(target: Tool::class, innerKey: 'added_tool_id', outerKey: 'added_tool_id', nullable: true)] + public Tool $addedTool; } diff --git a/tests/Annotated/Fixtures/Fixtures16/Executive2.php b/tests/Annotated/Fixtures/Fixtures16/Executive2.php new file mode 100644 index 00000000..571707fb --- /dev/null +++ b/tests/Annotated/Fixtures/Fixtures16/Executive2.php @@ -0,0 +1,18 @@ +foo_id; diff --git a/tests/Annotated/Fixtures/Fixtures16/Tool.php b/tests/Annotated/Fixtures/Fixtures16/Tool.php new file mode 100644 index 00000000..5a19424b --- /dev/null +++ b/tests/Annotated/Fixtures/Fixtures16/Tool.php @@ -0,0 +1,22 @@ +assertArrayHasKey('tool', $schema); + // Person $this->assertCount(3, $schema['person'][SchemaInterface::CHILDREN]); $this->assertEquals([ @@ -86,43 +90,56 @@ public function testTableInheritance(ReaderInterface $reader): void // 'bonus' => 'bonus', // JTI 'preferences' => 'preferences', 'stocks' => 'stocks', + 'tool_id' => 'tool_id', // 'teethAmount' => 'teeth_amount', // Not child ], $schema['person'][SchemaInterface::COLUMNS]); $this->assertEmpty($schema['person'][SchemaInterface::PARENT] ?? null); $this->assertEmpty($schema['person'][SchemaInterface::PARENT_KEY] ?? null); $this->assertSame('people', $schema['person'][SchemaInterface::TABLE]); + $this->assertCount(1, $schema['person'][SchemaInterface::RELATIONS]); // Employee $this->assertArrayHasKey('employee', $schema); $this->assertCount(1, $schema['employee']); $this->assertSame(Employee::class, $schema['employee'][SchemaInterface::ENTITY]); $this->assertNull($schema['employee'][SchemaInterface::TABLE] ?? null); + $this->assertCount(0, $schema['employee'][SchemaInterface::RELATIONS] ?? []); // Customer $this->assertArrayHasKey('customer', $schema); $this->assertCount(1, $schema['customer']); $this->assertSame(Customer::class, $schema['customer'][SchemaInterface::ENTITY]); $this->assertNull($schema['customer'][SchemaInterface::TABLE] ?? null); + $this->assertCount(0, $schema['customer'][SchemaInterface::RELATIONS] ?? []); // Executive $this->assertSame('employee', $schema['executive'][SchemaInterface::PARENT]); $this->assertSame('foo_id', $schema['executive'][SchemaInterface::PARENT_KEY]); $this->assertSame('executives', $schema['executive'][SchemaInterface::TABLE]); - $this->assertSame( + $this->assertEquals( [ 'bonus' => 'bonus', 'proxyFieldWithAnnotation' => 'proxy', 'foo_id' => 'id', 'hidden' => 'hidden', + 'added_tool_id' => 'added_tool_id', ], - $schema['executive'][SchemaInterface::COLUMNS] + $schema['executive'][SchemaInterface::COLUMNS], ); + $this->assertCount(1, $schema['executive'][SchemaInterface::RELATIONS]); + + // Executive2 + $this->assertSame('executive', $schema['executive2'][SchemaInterface::PARENT]); + $this->assertSame('foo_id', $schema['executive2'][SchemaInterface::PARENT_KEY]); + $this->assertEquals(['foo_id' => 'id'], $schema['executive2'][SchemaInterface::COLUMNS]); + $this->assertCount(0, $schema['executive2'][SchemaInterface::RELATIONS]); // Ceo $this->assertArrayHasKey('ceo', $schema); $this->assertCount(1, $schema['ceo']); $this->assertSame(Ceo::class, $schema['ceo'][SchemaInterface::ENTITY]); $this->assertNull($schema['ceo'][SchemaInterface::TABLE] ?? null); + $this->assertCount(0, $schema['ceo'][SchemaInterface::RELATIONS] ?? []); // Beaver $this->assertEmpty($schema['beaver'][SchemaInterface::DISCRIMINATOR] ?? null); @@ -130,13 +147,15 @@ public function testTableInheritance(ReaderInterface $reader): void $this->assertEmpty($schema['beaver'][SchemaInterface::PARENT_KEY] ?? null); $this->assertEmpty($schema['beaver'][SchemaInterface::CHILDREN] ?? null); $this->assertSame('beavers', $schema['beaver'][SchemaInterface::TABLE]); - $this->assertSame([ + $this->assertEquals([ 'teethAmount' => 'teeth_amount', 'foo_id' => 'id', 'name' => 'name', 'type' => 'type', 'hidden' => 'hidden', + 'tool_id' => 'tool_id', ], $schema['beaver'][SchemaInterface::COLUMNS]); + $this->assertCount(1, $schema['beaver'][SchemaInterface::RELATIONS] ?? []); } public function testTableInheritanceWithIncorrectClassesOrder(): void @@ -150,6 +169,7 @@ public function testTableInheritanceWithIncorrectClassesOrder(): void new \ReflectionClass(Employee::class), new \ReflectionClass(Executive::class), new \ReflectionClass(Person::class), + new \ReflectionClass(Tool::class), ]); $schema = (new Compiler())->compile($r, [ From 5e35c1da5e04936644430f9e12df7a35d71742d5 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 4 Nov 2025 08:31:11 +0000 Subject: [PATCH 13/17] style(php-cs-fixer): fix coding standards --- tests/Annotated/Fixtures/Fixtures16/Executive2.php | 4 +--- tests/Annotated/Fixtures/Fixtures16/Person.php | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/Annotated/Fixtures/Fixtures16/Executive2.php b/tests/Annotated/Fixtures/Fixtures16/Executive2.php index 571707fb..6512ef6a 100644 --- a/tests/Annotated/Fixtures/Fixtures16/Executive2.php +++ b/tests/Annotated/Fixtures/Fixtures16/Executive2.php @@ -13,6 +13,4 @@ */ #[Entity] #[InheritanceJoinedTable(outerKey: 'foo_id')] -class Executive2 extends Executive -{ -} +class Executive2 extends Executive {} diff --git a/tests/Annotated/Fixtures/Fixtures16/Person.php b/tests/Annotated/Fixtures/Fixtures16/Person.php index 805023ef..c3b5eff4 100644 --- a/tests/Annotated/Fixtures/Fixtures16/Person.php +++ b/tests/Annotated/Fixtures/Fixtures16/Person.php @@ -25,10 +25,6 @@ class Person #[Column(type: 'string')] public string $type; - /** @Column(type="primary", name="id") */ - #[Column(type: 'primary', name: 'id')] - protected int $foo_id; - /** @Column(type="int", nullable=true, typecast="int") */ #[Column(type: 'int', nullable: true, typecast: 'int')] public ?int $tool_id; @@ -37,6 +33,10 @@ class Person #[HasOne(target: Tool::class, innerKey: 'id', outerKey: 'tool_id', nullable: true)] public Tool $tool; + /** @Column(type="primary", name="id") */ + #[Column(type: 'primary', name: 'id')] + protected int $foo_id; + public function getFooId(): int { return $this->foo_id; From d2f05bc7e0019951eaaec9a2a3165072110aa54b Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 18 May 2026 12:44:52 +0400 Subject: [PATCH 14/17] fix: keep inherited relations on separate-table entities --- src/Configurator.php | 9 +- .../Fixtures/Fixtures27/BtJoined.php | 17 +++ .../Fixtures/Fixtures27/BtParent.php | 24 ++++ .../Fixtures/Fixtures27/BtTarget.php | 15 +++ .../Annotated/Fixtures/Fixtures27/JgLeaf.php | 17 +++ tests/Annotated/Fixtures/Fixtures27/JgMid.php | 21 ++++ .../Fixtures/Fixtures27/JgParent.php | 15 +++ .../Fixtures/Fixtures27/JgTarget.php | 15 +++ .../Fixtures/Fixtures27/SoParent.php | 27 ++++ .../Fixtures/Fixtures27/SoSeparate.php | 15 +++ tests/Annotated/Fixtures/Fixtures27/SoSti.php | 12 ++ .../Fixtures/Fixtures27/SoTarget.php | 15 +++ .../Annotated/Fixtures/Fixtures27/StChild.php | 21 ++++ .../Fixtures/Fixtures27/StParent.php | 20 +++ .../Fixtures/Fixtures27/StTarget.php | 15 +++ tests/Annotated/Fixtures/Fixtures27/TrJti.php | 17 +++ .../Fixtures/Fixtures27/TrParent.php | 22 ++++ .../Fixtures/Fixtures27/TrRelTrait.php | 17 +++ .../Fixtures/Fixtures27/TrTarget.php | 15 +++ .../Driver/Common/InheritanceTestCase.php | 115 ++++++++++++++++++ 20 files changed, 443 insertions(+), 1 deletion(-) create mode 100644 tests/Annotated/Fixtures/Fixtures27/BtJoined.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/BtParent.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/BtTarget.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/JgLeaf.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/JgMid.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/JgParent.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/JgTarget.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/SoParent.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/SoSeparate.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/SoSti.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/SoTarget.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/StChild.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/StParent.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/StTarget.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/TrJti.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/TrParent.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/TrRelTrait.php create mode 100644 tests/Annotated/Fixtures/Fixtures27/TrTarget.php diff --git a/src/Configurator.php b/src/Configurator.php index 4d04f0f7..f7c54c2f 100644 --- a/src/Configurator.php +++ b/src/Configurator.php @@ -9,6 +9,7 @@ use Cycle\Annotated\Annotation\Entity; use Cycle\Annotated\Annotation\ForeignKey; use Cycle\Annotated\Annotation\GeneratedValue; +use Cycle\Annotated\Annotation\Inheritance; use Cycle\Annotated\Annotation\Relation as RelationAnnotation; use Cycle\Annotated\Exception\AnnotationException; use Cycle\Annotated\Exception\AnnotationRequiredArgumentsException; @@ -131,10 +132,16 @@ public function initFields(EntitySchema $entity, \ReflectionClass $class, string public function initRelations(EntitySchema $entity, \ReflectionClass $class): void { + // Only STI/JTI children must skip relations declared by parent entities — for them, the parent table already + // owns those relations. Entities that merely extend another entity physically (separate table) must keep them. + $isInheritanceChild = $this->reader->firstClassMetadata($class, Inheritance::class) !== null; + foreach ($class->getProperties() as $property) { // ignore properties declared by parent entities // otherwise all the relation columns declared in parent would be duplicated across all child tables in JTI - if ($this->findOwningEntity($class, $property->getDeclaringClass())->getName() !== $class->getName()) { + if ($isInheritanceChild + && $this->findOwningEntity($class, $property->getDeclaringClass())->getName() !== $class->getName() + ) { continue; } diff --git a/tests/Annotated/Fixtures/Fixtures27/BtJoined.php b/tests/Annotated/Fixtures/Fixtures27/BtJoined.php new file mode 100644 index 00000000..6f00be9d --- /dev/null +++ b/tests/Annotated/Fixtures/Fixtures27/BtJoined.php @@ -0,0 +1,17 @@ +assertNull($schema['employee'][SchemaInterface::TABLE] ?? null); $this->assertSame('people', $schema['person'][SchemaInterface::TABLE]); } + + public function testJtiChildDoesNotInheritBelongsToColumn(): void + { + $schema = $this->compileWithClasses([BtTarget::class, BtParent::class, BtJoined::class]); + + // BtParent owns the BelongsTo + its FK column + $this->assertArrayHasKey('target_id', $schema['btParent'][SchemaInterface::COLUMNS]); + $this->assertCount(1, $schema['btParent'][SchemaInterface::RELATIONS]); + + // JTI child must not duplicate the BelongsTo FK column nor inherit the relation + $this->assertArrayNotHasKey('target_id', $schema['btJoined'][SchemaInterface::COLUMNS]); + $this->assertCount(0, $schema['btJoined'][SchemaInterface::RELATIONS] ?? []); + } + + public function testStiChildOwnRelationMergesIntoParent(): void + { + $schema = $this->compileWithClasses([StTarget::class, StParent::class, StChild::class]); + + // Relation declared on the STI child must end up on the parent after merge + $this->assertArrayHasKey('target_id', $schema['stParent'][SchemaInterface::COLUMNS]); + $this->assertCount(1, $schema['stParent'][SchemaInterface::RELATIONS]); + $this->assertArrayHasKey('target', $schema['stParent'][SchemaInterface::RELATIONS]); + } + + public function testSeparateEntityExtendingStiChildKeepsInheritedRelation(): void + { + $schema = $this->compileWithClasses([ + SoTarget::class, + SoParent::class, + SoSti::class, + SoSeparate::class, + ]); + + // Separate entity (no Inheritance attr) extending an STI child must keep ancestor relations + $this->assertEmpty($schema['soSeparate'][SchemaInterface::PARENT] ?? null); + $this->assertNotNull($schema['soSeparate'][SchemaInterface::TABLE] ?? null); + $this->assertArrayHasKey('target_id', $schema['soSeparate'][SchemaInterface::COLUMNS]); + $this->assertArrayHasKey('extra', $schema['soSeparate'][SchemaInterface::COLUMNS]); + $this->assertCount(1, $schema['soSeparate'][SchemaInterface::RELATIONS] ?? []); + $this->assertArrayHasKey('target', $schema['soSeparate'][SchemaInterface::RELATIONS]); + } + + public function testJtiChildDoesNotInheritRelationFromTrait(): void + { + $schema = $this->compileWithClasses([TrTarget::class, TrParent::class, TrJti::class]); + + // Trait-declared relation belongs to the entity that uses the trait + $this->assertArrayHasKey('target_id', $schema['trParent'][SchemaInterface::COLUMNS]); + $this->assertCount(1, $schema['trParent'][SchemaInterface::RELATIONS]); + + // JTI child must not duplicate the trait's relation columns + $this->assertArrayNotHasKey('target_id', $schema['trJti'][SchemaInterface::COLUMNS]); + $this->assertCount(0, $schema['trJti'][SchemaInterface::RELATIONS] ?? []); + } + + public function testJtiGrandchildDoesNotInheritMidLevelRelation(): void + { + $schema = $this->compileWithClasses([ + JgTarget::class, + JgParent::class, + JgMid::class, + JgLeaf::class, + ]); + + // Relation declared on the JTI middle entity stays on the middle table + $this->assertArrayHasKey('target_id', $schema['jgMid'][SchemaInterface::COLUMNS]); + $this->assertCount(1, $schema['jgMid'][SchemaInterface::RELATIONS]); + + // JTI grandchild must not duplicate the middle entity's relation column + $this->assertArrayNotHasKey('target_id', $schema['jgLeaf'][SchemaInterface::COLUMNS]); + $this->assertCount(0, $schema['jgLeaf'][SchemaInterface::RELATIONS] ?? []); + } + + /** + * @param list $classes + */ + private function compileWithClasses(array $classes): array + { + $reader = new AttributeReader(); + $locator = $this->createMock(ClassesInterface::class); + $locator->method('getClasses')->willReturn( + \array_map(static fn(string $c) => new \ReflectionClass($c), $classes), + ); + + return (new Compiler())->compile(new Registry($this->dbal), [ + new Embeddings(new TokenizerEmbeddingLocator($locator, $reader), $reader), + new Entities(new TokenizerEntityLocator($locator, $reader), $reader), + new TableInheritance($reader), + new ResetTables(), + new MergeColumns($reader), + new GenerateRelations(), + new RenderTables(), + new RenderRelations(), + new MergeIndexes($reader), + new SyncTables(), + new GenerateTypecast(), + ]); + } } From c7842b880033dd17d3c1a291c4e987bbe28a31b6 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 18 May 2026 08:45:37 +0000 Subject: [PATCH 15/17] style(php-cs-fixer): fix coding standards --- tests/Annotated/Fixtures/Fixtures16/Beaver.php | 4 +++- .../Fixtures22/Annotated/LocalManager.php | 4 +++- .../Fixtures22/Annotated/LocalSupplier.php | 4 +++- .../Fixtures/Fixtures22/Annotated/Person.php | 4 +++- .../Fixtures25/WithGeneratedFields.php | 18 ++++++------------ 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/tests/Annotated/Fixtures/Fixtures16/Beaver.php b/tests/Annotated/Fixtures/Fixtures16/Beaver.php index a0493069..ea115ad2 100644 --- a/tests/Annotated/Fixtures/Fixtures16/Beaver.php +++ b/tests/Annotated/Fixtures/Fixtures16/Beaver.php @@ -7,7 +7,9 @@ use Cycle\Annotated\Annotation\Column; use Cycle\Annotated\Annotation\Entity; -/** @Entity */ +/** + * @Entity + */ #[Entity] class Beaver extends Person { diff --git a/tests/Annotated/Fixtures/Fixtures22/Annotated/LocalManager.php b/tests/Annotated/Fixtures/Fixtures22/Annotated/LocalManager.php index b17d69a4..ca376f83 100644 --- a/tests/Annotated/Fixtures/Fixtures22/Annotated/LocalManager.php +++ b/tests/Annotated/Fixtures/Fixtures22/Annotated/LocalManager.php @@ -6,5 +6,7 @@ use Cycle\Annotated\Annotation\Entity; -/** @Entity*/ +/** + * @Entity + */ class LocalManager extends LocalSupplier {} diff --git a/tests/Annotated/Fixtures/Fixtures22/Annotated/LocalSupplier.php b/tests/Annotated/Fixtures/Fixtures22/Annotated/LocalSupplier.php index 7e33599d..dc37d806 100644 --- a/tests/Annotated/Fixtures/Fixtures22/Annotated/LocalSupplier.php +++ b/tests/Annotated/Fixtures/Fixtures22/Annotated/LocalSupplier.php @@ -6,5 +6,7 @@ use Cycle\Annotated\Annotation\Entity; -/** @Entity */ +/** + * @Entity + */ class LocalSupplier extends Supplier {} diff --git a/tests/Annotated/Fixtures/Fixtures22/Annotated/Person.php b/tests/Annotated/Fixtures/Fixtures22/Annotated/Person.php index a1e7239e..c69ff08a 100644 --- a/tests/Annotated/Fixtures/Fixtures22/Annotated/Person.php +++ b/tests/Annotated/Fixtures/Fixtures22/Annotated/Person.php @@ -6,5 +6,7 @@ use Cycle\Annotated\Annotation\Entity; -/** @Entity */ +/** + * @Entity + */ class Person {} diff --git a/tests/Annotated/Fixtures/Fixtures25/WithGeneratedFields.php b/tests/Annotated/Fixtures/Fixtures25/WithGeneratedFields.php index 27e5d985..0d977575 100644 --- a/tests/Annotated/Fixtures/Fixtures25/WithGeneratedFields.php +++ b/tests/Annotated/Fixtures/Fixtures25/WithGeneratedFields.php @@ -24,29 +24,23 @@ class WithGeneratedFields * @Column(type="datetime", name="created_at") * @GeneratedValue(beforeInsert=true) */ - #[ - Column(type: 'datetime', name: 'created_at'), - GeneratedValue(beforeInsert: true) - ] + #[Column(type: 'datetime', name: 'created_at'), + GeneratedValue(beforeInsert: true)] public \DateTimeImmutable $createdAt; /** * @Column(type="datetime", name="created_at_generated_by_database") * @GeneratedValue(onInsert=true) */ - #[ - Column(type: 'datetime', name: 'created_at_generated_by_database'), - GeneratedValue(onInsert: true) - ] + #[Column(type: 'datetime', name: 'created_at_generated_by_database'), + GeneratedValue(onInsert: true)] public \DateTimeImmutable $createdAtGeneratedByDatabase; /** * @Column(type="datetime", name="created_at") * @GeneratedValue(beforeInsert=true, beforeUpdate=true) */ - #[ - Column(type: 'datetime', name: 'updated_at'), - GeneratedValue(beforeInsert: true, beforeUpdate: true) - ] + #[Column(type: 'datetime', name: 'updated_at'), + GeneratedValue(beforeInsert: true, beforeUpdate: true)] public \DateTimeImmutable $updatedAt; } From a83e3f4767e815c919cd6d863d336f7d64b89fd4 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 18 May 2026 14:00:23 +0400 Subject: [PATCH 16/17] Fix psalm issues --- psalm-baseline.xml | 5 ----- src/TableInheritance.php | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 39f51773..ecb12747 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -254,11 +254,6 @@ - - getRole() !== null && $entity !== null]]> - - getRole() !== null && $entity !== null && isset($parent))]]> - diff --git a/src/TableInheritance.php b/src/TableInheritance.php index 447c109c..178f1152 100644 --- a/src/TableInheritance.php +++ b/src/TableInheritance.php @@ -69,7 +69,7 @@ public function run(Registry $registry): Registry // todo should $parent be not null? // \assert(isset($parent)); - \assert($child->getRole() !== null && $entity !== null); + \assert($child->getRole() !== null); if (!$registry->hasEntity($child->getRole())) { $registry->register($child); From 5edb66575d5abb40e1d73527ab8ef1fc7896b6aa Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 18 May 2026 14:34:08 +0400 Subject: [PATCH 17/17] Fix MSSQL tests --- tests/Annotated/Fixtures/Fixtures16/Executive.php | 4 ++-- tests/Annotated/Fixtures/Fixtures16/Person.php | 4 ++-- tests/Annotated/Fixtures/Fixtures27/JgMid.php | 2 +- tests/Annotated/Fixtures/Fixtures27/SoParent.php | 2 +- tests/Annotated/Fixtures/Fixtures27/StChild.php | 2 +- tests/Annotated/Fixtures/Fixtures27/TrRelTrait.php | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/Annotated/Fixtures/Fixtures16/Executive.php b/tests/Annotated/Fixtures/Fixtures16/Executive.php index b09be115..cbbd1804 100644 --- a/tests/Annotated/Fixtures/Fixtures16/Executive.php +++ b/tests/Annotated/Fixtures/Fixtures16/Executive.php @@ -27,7 +27,7 @@ class Executive extends ExecutiveProxy #[Column(type: 'int', nullable: true, typecast: 'int')] public ?int $added_tool_id; - /** @HasOne(target=Tool::class, innerKey="added_tool_id", outerKey="added_tool_id", nullable=true) */ - #[HasOne(target: Tool::class, innerKey: 'added_tool_id', outerKey: 'added_tool_id', nullable: true)] + /** @HasOne(target=Tool::class, innerKey="added_tool_id", outerKey="added_tool_id", nullable=true, fkCreate=false) */ + #[HasOne(target: Tool::class, innerKey: 'added_tool_id', outerKey: 'added_tool_id', nullable: true, fkCreate: false)] public Tool $addedTool; } diff --git a/tests/Annotated/Fixtures/Fixtures16/Person.php b/tests/Annotated/Fixtures/Fixtures16/Person.php index c3b5eff4..30029b0f 100644 --- a/tests/Annotated/Fixtures/Fixtures16/Person.php +++ b/tests/Annotated/Fixtures/Fixtures16/Person.php @@ -29,8 +29,8 @@ class Person #[Column(type: 'int', nullable: true, typecast: 'int')] public ?int $tool_id; - /** @HasOne(target=Tool::class, innerKey="id", outerKey="tool_id", nullable=true) */ - #[HasOne(target: Tool::class, innerKey: 'id', outerKey: 'tool_id', nullable: true)] + /** @HasOne(target=Tool::class, innerKey="id", outerKey="tool_id", nullable=true, fkCreate=false) */ + #[HasOne(target: Tool::class, innerKey: 'id', outerKey: 'tool_id', nullable: true, fkCreate: false)] public Tool $tool; /** @Column(type="primary", name="id") */ diff --git a/tests/Annotated/Fixtures/Fixtures27/JgMid.php b/tests/Annotated/Fixtures/Fixtures27/JgMid.php index 0acbc5b1..24698839 100644 --- a/tests/Annotated/Fixtures/Fixtures27/JgMid.php +++ b/tests/Annotated/Fixtures/Fixtures27/JgMid.php @@ -16,6 +16,6 @@ class JgMid extends JgParent #[Column(type: 'int', nullable: true)] public ?int $target_id = null; - #[HasOne(target: JgTarget::class, innerKey: 'target_id', outerKey: 'id', nullable: true)] + #[HasOne(target: JgTarget::class, innerKey: 'target_id', outerKey: 'id', nullable: true, fkCreate: false)] public ?JgTarget $target = null; } diff --git a/tests/Annotated/Fixtures/Fixtures27/SoParent.php b/tests/Annotated/Fixtures/Fixtures27/SoParent.php index 754f23ab..ef0febec 100644 --- a/tests/Annotated/Fixtures/Fixtures27/SoParent.php +++ b/tests/Annotated/Fixtures/Fixtures27/SoParent.php @@ -22,6 +22,6 @@ class SoParent #[Column(type: 'int', nullable: true)] public ?int $target_id = null; - #[HasOne(target: SoTarget::class, innerKey: 'target_id', outerKey: 'id', nullable: true)] + #[HasOne(target: SoTarget::class, innerKey: 'target_id', outerKey: 'id', nullable: true, fkCreate: false)] public ?SoTarget $target = null; } diff --git a/tests/Annotated/Fixtures/Fixtures27/StChild.php b/tests/Annotated/Fixtures/Fixtures27/StChild.php index 330ada00..f582808d 100644 --- a/tests/Annotated/Fixtures/Fixtures27/StChild.php +++ b/tests/Annotated/Fixtures/Fixtures27/StChild.php @@ -16,6 +16,6 @@ class StChild extends StParent #[Column(type: 'int', nullable: true)] public ?int $target_id = null; - #[HasOne(target: StTarget::class, innerKey: 'target_id', outerKey: 'id', nullable: true)] + #[HasOne(target: StTarget::class, innerKey: 'target_id', outerKey: 'id', nullable: true, fkCreate: false)] public ?StTarget $target = null; } diff --git a/tests/Annotated/Fixtures/Fixtures27/TrRelTrait.php b/tests/Annotated/Fixtures/Fixtures27/TrRelTrait.php index 113b7bfe..8bfb4dff 100644 --- a/tests/Annotated/Fixtures/Fixtures27/TrRelTrait.php +++ b/tests/Annotated/Fixtures/Fixtures27/TrRelTrait.php @@ -12,6 +12,6 @@ trait TrRelTrait #[Column(type: 'int', nullable: true)] public ?int $target_id = null; - #[HasOne(target: TrTarget::class, innerKey: 'target_id', outerKey: 'id', nullable: true)] + #[HasOne(target: TrTarget::class, innerKey: 'target_id', outerKey: 'id', nullable: true, fkCreate: false)] public ?TrTarget $target = null; }