From 9695f2e28aca5b04d830d9f335d9a1f39f279d75 Mon Sep 17 00:00:00 2001 From: Daniel Causebrook Date: Mon, 4 Apr 2022 13:47:35 +0100 Subject: [PATCH 1/4] Fix check for associations declared in MappedSuperclasses Doctrine does not allow toMany associations to be declared on MappedSuperclasses. The check for this occurs on the child class, which correctly checks whether the parent is a MappedSuperclass. However, it does not check that the field in question was actually declared on that class. This causes an incorrect exception if there is an inheritance structure like so: Root: Joined/Single Table Inheritance Entity Child: Mapped Superclass Grandchild: Entity When there is a toMany association on Root, the Grandchild throws an exception, assuming that the association has come from Child. This fix updates the check to also include whether the association has come from the parent MappedSuperclass in question. --- .../ORM/Mapping/ClassMetadataFactory.php | 13 +++---- .../ChildMappedSuperclass.php | 18 ++++++++++ .../GrandchildMappedSuperclass.php | 14 ++++++++ .../GreatGrandchildEntity.php | 14 ++++++++ .../InvalidAssociatedEntity.php | 27 ++++++++++++++ .../RootEntity.php | 35 +++++++++++++++++++ .../ValidToManyOnRoot/AssociatedEntity.php | 28 +++++++++++++++ .../ChildMappedSuperclass.php | 15 ++++++++ .../ValidToManyOnRoot/GrandchildEntity.php | 14 ++++++++ .../ORM/Mapping/ClassMetadataFactoryTest.php | 33 +++++++++++++++++ 10 files changed, 205 insertions(+), 6 deletions(-) create mode 100644 tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/ChildMappedSuperclass.php create mode 100644 tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/GrandchildMappedSuperclass.php create mode 100644 tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/GreatGrandchildEntity.php create mode 100644 tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/InvalidAssociatedEntity.php create mode 100644 tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/RootEntity.php create mode 100644 tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ValidToManyOnRoot/AssociatedEntity.php create mode 100644 tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ValidToManyOnRoot/ChildMappedSuperclass.php create mode 100644 tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ValidToManyOnRoot/GrandchildEntity.php diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 38188b472c8..6d6a0c4d320 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -377,7 +377,12 @@ private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $pare private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass): void { foreach ($parentClass->associationMappings as $field => $mapping) { - if ($parentClass->isMappedSuperclass) { + if (! isset($mapping['declared'])) { + $mapping['declared'] = $parentClass->name; + } + + $declaredInMappedSuperclass = $parentClass->isMappedSuperclass && $mapping['declared'] === $parentClass->name; + if ($declaredInMappedSuperclass) { if ($mapping['type'] & ClassMetadata::TO_MANY && ! $mapping['isOwningSide']) { throw MappingException::illegalToManyAssociationOnMappedSuperclass($parentClass->name, $field); } @@ -386,14 +391,10 @@ private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $p } //$subclassMapping = $mapping; - if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) { + if (! isset($mapping['inherited']) && ! $declaredInMappedSuperclass) { $mapping['inherited'] = $parentClass->name; } - if (! isset($mapping['declared'])) { - $mapping['declared'] = $parentClass->name; - } - $subClass->addInheritedAssociationMapping($mapping); } } diff --git a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/ChildMappedSuperclass.php b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/ChildMappedSuperclass.php new file mode 100644 index 00000000000..0783ec7f908 --- /dev/null +++ b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/ChildMappedSuperclass.php @@ -0,0 +1,18 @@ +getMetadataFor($cm1->name); } + public function testGetMetadataForJoinedSuperclassWithToManyAssociationAndChildMappedSuperclass(): void + { + $cmf = new ClassMetadataFactory(); + $driver = $this->createAnnotationDriver([__DIR__ . '/../../Models/JoinedInheritanceType/']); + $em = $this->createEntityManager($driver); + $cmf->setEntityManager($em); + + $rootMetadata = $cmf->getMetadataFor(RootEntity::class); + $childMetadata = $cmf->getMetadataFor(ValidToManyOnRoot\ChildMappedSuperclass::class); + $grandchildMetadata = $cmf->getMetadataFor(ValidToManyOnRoot\GrandchildEntity::class); + + self::assertTrue($rootMetadata->hasAssociation('toManyAssociation')); + self::assertTrue($childMetadata->hasAssociation('toManyAssociation')); + self::assertTrue($grandchildMetadata->hasAssociation('toManyAssociation')); + } + + public function testThrowsExceptionForToManyOnGrandparentMappedSuperclass(): void + { + $cmf = new ClassMetadataFactory(); + $driver = $this->createAnnotationDriver([__DIR__ . '/../../Models/JoinedInheritanceType/']); + $em = $this->createEntityManager($driver); + $cmf->setEntityManager($em); + + $rootMetadata = $cmf->getMetadataFor(RootEntity::class); + self::expectException(MappingException::class); + $mappedSuperclass1Metadata = $cmf->getMetadataFor(InvalidToManyOnMappedSuperclass\ChildMappedSuperclass::class); + $mappedSuperclass2Metadata = $cmf->getMetadataFor(InvalidToManyOnMappedSuperclass\GrandchildMappedSuperclass::class); + $grandchildMetadata = $cmf->getMetadataFor(InvalidToManyOnMappedSuperclass\GreatGrandchildEntity::class); + } + public function testHasGetMetadataNamespaceSeparatorIsNotNormalized(): void { require_once __DIR__ . '/../../Models/Global/GlobalNamespaceModel.php'; From a8242462e6fcd5dd8fe377dcec2f0817a5c8a65c Mon Sep 17 00:00:00 2001 From: Daniel Causebrook Date: Sat, 30 Apr 2022 19:54:21 +0100 Subject: [PATCH 2/4] fixup! Fix check for associations declared in MappedSuperclasses --- .../InvalidAssociatedEntity.php | 3 ++- .../Models/JoinedInheritanceTypeWithAssociation/RootEntity.php | 3 ++- .../ValidToManyOnRoot/AssociatedEntity.php | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/InvalidAssociatedEntity.php b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/InvalidAssociatedEntity.php index f36fd9cf3d9..b7f79c61f1b 100644 --- a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/InvalidAssociatedEntity.php +++ b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/InvalidAssociatedEntity.php @@ -16,11 +16,12 @@ class InvalidAssociatedEntity { /** + * @var int * @Column(type="integer") * @Id * @GeneratedValue */ - public int $id; + public $id; /** @ManyToOne(targetEntity=ChildMappedSuperclass::class, inversedBy="invalidToManyAssociation") */ private ChildMappedSuperclass $childMappedSuperclass; diff --git a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/RootEntity.php b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/RootEntity.php index 6b3fd1abe53..d13a13d7bf0 100644 --- a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/RootEntity.php +++ b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/RootEntity.php @@ -24,11 +24,12 @@ class RootEntity { /** + * @var int * @Column(type="integer") * @Id * @GeneratedValue */ - public int $id; + public $id; /** @OneToMany(targetEntity=AssociatedEntity::class, mappedBy="root") */ public AssociatedEntity $toManyAssociation; diff --git a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ValidToManyOnRoot/AssociatedEntity.php b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ValidToManyOnRoot/AssociatedEntity.php index 2e98c21ba0e..a2398b99cfa 100644 --- a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ValidToManyOnRoot/AssociatedEntity.php +++ b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ValidToManyOnRoot/AssociatedEntity.php @@ -17,11 +17,12 @@ class AssociatedEntity { /** + * @var int * @Column(type="integer") * @Id * @GeneratedValue */ - public int $id; + public $id; /** @ManyToOne(targetEntity=RootEntity::class, inversedBy="toManyAssociation") */ private RootEntity $root; From 984b23ba1cbf4915961c65955b4d6936bb15ae34 Mon Sep 17 00:00:00 2001 From: Daniel Causebrook Date: Sat, 30 Apr 2022 20:05:56 +0100 Subject: [PATCH 3/4] fixup! Fix check for associations declared in MappedSuperclasses --- .../ChildMappedSuperclass.php | 7 +++++-- .../InvalidAssociatedEntity.php | 7 +++++-- .../JoinedInheritanceTypeWithAssociation/RootEntity.php | 7 +++++-- .../ValidToManyOnRoot/AssociatedEntity.php | 7 +++++-- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/ChildMappedSuperclass.php b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/ChildMappedSuperclass.php index 0783ec7f908..ce439b35253 100644 --- a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/ChildMappedSuperclass.php +++ b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/ChildMappedSuperclass.php @@ -13,6 +13,9 @@ */ abstract class ChildMappedSuperclass extends RootEntity { - /** @OneToMany(targetEntity=InvalidAssociatedEntity::class, mappedBy="childMappedSuperclass") */ - private InvalidAssociatedEntity $invalidToManyAssociation; + /** + * @var InvalidAssociatedEntity + * @OneToMany(targetEntity=InvalidAssociatedEntity::class, mappedBy="childMappedSuperclass") + */ + private $invalidToManyAssociation; } diff --git a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/InvalidAssociatedEntity.php b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/InvalidAssociatedEntity.php index b7f79c61f1b..b00f18ab638 100644 --- a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/InvalidAssociatedEntity.php +++ b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/InvalidToManyOnMappedSuperclass/InvalidAssociatedEntity.php @@ -23,6 +23,9 @@ class InvalidAssociatedEntity */ public $id; - /** @ManyToOne(targetEntity=ChildMappedSuperclass::class, inversedBy="invalidToManyAssociation") */ - private ChildMappedSuperclass $childMappedSuperclass; + /** + * @var ChildMappedSuperclass + * @ManyToOne(targetEntity=ChildMappedSuperclass::class, inversedBy="invalidToManyAssociation") + */ + private $childMappedSuperclass; } diff --git a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/RootEntity.php b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/RootEntity.php index d13a13d7bf0..1ffc60c2372 100644 --- a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/RootEntity.php +++ b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/RootEntity.php @@ -31,6 +31,9 @@ class RootEntity */ public $id; - /** @OneToMany(targetEntity=AssociatedEntity::class, mappedBy="root") */ - public AssociatedEntity $toManyAssociation; + /** + * @var AssociatedEntity + * @OneToMany(targetEntity=AssociatedEntity::class, mappedBy="root") + */ + public $toManyAssociation; } diff --git a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ValidToManyOnRoot/AssociatedEntity.php b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ValidToManyOnRoot/AssociatedEntity.php index a2398b99cfa..c111a624576 100644 --- a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ValidToManyOnRoot/AssociatedEntity.php +++ b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ValidToManyOnRoot/AssociatedEntity.php @@ -24,6 +24,9 @@ class AssociatedEntity */ public $id; - /** @ManyToOne(targetEntity=RootEntity::class, inversedBy="toManyAssociation") */ - private RootEntity $root; + /** + * @var RootEntity + * @ManyToOne(targetEntity=RootEntity::class, inversedBy="toManyAssociation") + */ + private $root; } From 4095ff610f9780d085023befd700be368bb416fe Mon Sep 17 00:00:00 2001 From: Daniel Causebrook Date: Sat, 30 Apr 2022 22:55:31 +0100 Subject: [PATCH 4/4] fixup! Fix check for associations declared in MappedSuperclasses --- .../ORM/Mapping/ClassMetadataFactory.php | 6 ++-- .../RootEntity.php | 3 +- .../AssociatedEntity.php | 31 +++++++++++++++++++ .../ChildMappedSuperclass.php | 21 +++++++++++++ .../GrandchildMappedSuperclass.php | 14 +++++++++ .../GreatGrandchildEntity.php | 14 +++++++++ .../ORM/Mapping/ClassMetadataFactoryTest.php | 12 +++++++ 7 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ToOneOnMappedSuperclass/AssociatedEntity.php create mode 100644 tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ToOneOnMappedSuperclass/ChildMappedSuperclass.php create mode 100644 tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ToOneOnMappedSuperclass/GrandchildMappedSuperclass.php create mode 100644 tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ToOneOnMappedSuperclass/GreatGrandchildEntity.php diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 6d6a0c4d320..8cfc73daf8f 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -381,8 +381,8 @@ private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $p $mapping['declared'] = $parentClass->name; } - $declaredInMappedSuperclass = $parentClass->isMappedSuperclass && $mapping['declared'] === $parentClass->name; - if ($declaredInMappedSuperclass) { + $declaredInParent = $mapping['declared'] === $parentClass->name; + if ($parentClass->isMappedSuperclass && $declaredInParent) { if ($mapping['type'] & ClassMetadata::TO_MANY && ! $mapping['isOwningSide']) { throw MappingException::illegalToManyAssociationOnMappedSuperclass($parentClass->name, $field); } @@ -391,7 +391,7 @@ private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $p } //$subclassMapping = $mapping; - if (! isset($mapping['inherited']) && ! $declaredInMappedSuperclass) { + if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) { $mapping['inherited'] = $parentClass->name; } diff --git a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/RootEntity.php b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/RootEntity.php index 1ffc60c2372..da0c1a83147 100644 --- a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/RootEntity.php +++ b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/RootEntity.php @@ -16,7 +16,8 @@ /** * @DiscriminatorMap({ * "validToManyOnRoot" = ValidToManyOnRoot\GrandchildEntity::class, - * "invalidToManyOnMappedSuperclass" = InvalidToManyOnMappedSuperclass\GreatGrandchildEntity::class + * "invalidToManyOnMappedSuperclass" = InvalidToManyOnMappedSuperclass\GreatGrandchildEntity::class, + * "toOneOnMappedSuperclass" = ToOneOnMappedSuperclass\GreatGrandchildEntity::class * }) * @Entity * @InheritanceType("JOINED") diff --git a/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ToOneOnMappedSuperclass/AssociatedEntity.php b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ToOneOnMappedSuperclass/AssociatedEntity.php new file mode 100644 index 00000000000..104bedc5554 --- /dev/null +++ b/tests/Doctrine/Tests/Models/JoinedInheritanceTypeWithAssociation/ToOneOnMappedSuperclass/AssociatedEntity.php @@ -0,0 +1,31 @@ +getMetadataFor(InvalidToManyOnMappedSuperclass\GreatGrandchildEntity::class); } + public function testInheritedNotSetForToOneOnGrandparentMappedSuperclass(): void + { + $cmf = new ClassMetadataFactory(); + $driver = $this->createAnnotationDriver([__DIR__ . '/../../Models/JoinedInheritanceType/']); + $em = $this->createEntityManager($driver); + $cmf->setEntityManager($em); + + $grandchildMetadata = $cmf->getMetadataFor(ToOneOnMappedSuperclass\GreatGrandchildEntity::class); + self::assertFalse($grandchildMetadata->isInheritedAssociation('toOneAssociation')); + } + public function testHasGetMetadataNamespaceSeparatorIsNotNormalized(): void { require_once __DIR__ . '/../../Models/Global/GlobalNamespaceModel.php';