Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions lib/Doctrine/ORM/Tools/SchemaTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
173 changes: 173 additions & 0 deletions tests/Doctrine/Tests/ORM/Functional/Ticket/GH10387Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Tests\OrmTestCase;
use Generator;

use function array_map;

/**
* @group GH-10387
*/
class GH10387Test extends OrmTestCase
{
/**
* @dataProvider classHierachies
*/
public function testSchemaToolCreatesColumnForFieldInTheMiddleClass(array $classes): void
{
$em = $this->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;
}