diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index d71b7f1d9d3..7e524d399ea 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -30,6 +30,7 @@ use function array_filter; use function array_flip; use function array_intersect_key; +use function array_merge; use function assert; use function count; use function current; @@ -219,7 +220,21 @@ public function getSchemaFromMetadata(array $classes) $processedClasses[$parentClassName] = true; } + // Find classes that provide inherited fields but that are not in the list of subclasses. + // This is the case for abstract entity classes in the middle of the inheritance tree + // that are not included in the discriminator map. + $subclassesToProcess = []; foreach ($class->subClasses as $subClassName) { + $subclassesToProcess[] = $subClassName; + $subClass = $this->em->getClassMetadata($subClassName); + foreach (array_merge($subClass->fieldMappings, $subClass->associationMappings) as $mapping) { + if (isset($mapping['inherited']) && $mapping['inherited'] !== $class->name && ! in_array($mapping['inherited'], $subclassesToProcess)) { + $subclassesToProcess[] = $mapping['inherited']; + } + } + } + + foreach ($subclassesToProcess as $subClassName) { $subClass = $this->em->getClassMetadata($subClassName); $this->gatherColumns($subClass, $table); $this->gatherRelationsSql($subClass, $table, $schema, $addedFks, $blacklistedFks); diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH10387Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH10387Test.php new file mode 100644 index 00000000000..45fe0d819da --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH10387Test.php @@ -0,0 +1,173 @@ +getTestEntityManager(); + $schemaTool = new SchemaTool($em); + $metadata = array_map(static function (string $class) use ($em) { + return $em->getClassMetadata($class); + }, $classes); + $schema = $schemaTool->getSchemaFromMetadata([$metadata[0]]); + + self::assertNotNull($schema->getTable('root')->getColumn('middle_class_field')); + self::assertNotNull($schema->getTable('root')->getColumn('leaf_class_field')); + } + + public function classHierachies(): Generator + { + yield 'hierarchy with Entity classes only' => [[GH10387EntitiesOnlyRoot::class, GH10387EntitiesOnlyMiddle::class, GH10387EntitiesOnlyLeaf::class]]; + yield 'MappedSuperclass in the middle of the hierarchy' => [[GH10387MappedSuperclassRoot::class, GH10387MappedSuperclassMiddle::class, GH10387MappedSuperclassLeaf::class]]; + yield 'abstract entity the the root and in the middle of the hierarchy' => [[GH10387AbstractEntitiesRoot::class, GH10387AbstractEntitiesMiddle::class, GH10387AbstractEntitiesLeaf::class]]; + } +} + +/** + * @ORM\Entity + * @ORM\Table(name="root") + * @ORM\InheritanceType("SINGLE_TABLE") + * @ORM\DiscriminatorMap({ "A": "GH10387EntitiesOnlyRoot", "B": "GH10387EntitiesOnlyMiddle", "C": "GH10387EntitiesOnlyLeaf"}) + */ +class GH10387EntitiesOnlyRoot +{ + /** + * @ORM\Id + * @ORM\Column + * + * @var string + */ + private $id; +} + +/** + * @ORM\Entity + */ +class GH10387EntitiesOnlyMiddle extends GH10387EntitiesOnlyRoot +{ + /** + * @ORM\Column(name="middle_class_field") + * + * @var string + */ + private $parentValue; +} + +/** + * @ORM\Entity + */ +class GH10387EntitiesOnlyLeaf extends GH10387EntitiesOnlyMiddle +{ + /** + * @ORM\Column(name="leaf_class_field") + * + * @var string + */ + private $childValue; +} + +/** + * @ORM\Entity + * @ORM\Table(name="root") + * @ORM\InheritanceType("SINGLE_TABLE") + * @ORM\DiscriminatorMap({ "A": "GH10387MappedSuperclassRoot", "B": "GH10387MappedSuperclassLeaf"}) + * ^- This DiscriminatorMap contains the Entity classes only, not the Mapped Superclass + */ +class GH10387MappedSuperclassRoot +{ + /** + * @ORM\Id + * @ORM\Column + * + * @var string + */ + private $id; +} + +/** + * @ORM\MappedSuperclass + */ +class GH10387MappedSuperclassMiddle extends GH10387MappedSuperclassRoot +{ + /** + * @ORM\Column(name="middle_class_field") + * + * @var string + */ + private $parentValue; +} + +/** + * @ORM\Entity + */ +class GH10387MappedSuperclassLeaf extends GH10387MappedSuperclassMiddle +{ + /** + * @ORM\Column(name="leaf_class_field") + * + * @var string + */ + private $childValue; +} + + +/** + * @ORM\Entity + * @ORM\Table(name="root") + * @ORM\InheritanceType("SINGLE_TABLE") + * @ORM\DiscriminatorMap({ "A": "GH10387AbstractEntitiesLeaf"}) + * ^- This DiscriminatorMap contains the single non-abstract Entity class only + */ +abstract class GH10387AbstractEntitiesRoot +{ + /** + * @ORM\Id + * @ORM\Column + * + * @var string + */ + private $id; +} + +/** + * @ORM\Entity + */ +abstract class GH10387AbstractEntitiesMiddle extends GH10387AbstractEntitiesRoot +{ + /** + * @ORM\Column(name="middle_class_field") + * + * @var string + */ + private $parentValue; +} + +/** + * @ORM\Entity + */ +class GH10387AbstractEntitiesLeaf extends GH10387AbstractEntitiesMiddle +{ + /** + * @ORM\Column(name="leaf_class_field") + * + * @var string + */ + private $childValue; +}