Skip to content

Commit e3a360d

Browse files
committed
Fix @SequenceGeneratorDefinition inheritance, take 1
#10927 reported that #10455 broke the way how the default `@SequenceGeneratorDefinition` is created and inherited by subclasses for ID columns using `@GeneratedValue(strategy="SEQUENCE")`. First, I had to understand how `@SequenceGeneratorDefinition` has been handled before #10455 when entity inheritance comes into play: * Entity and mapped superclasses inherit the ID generator type (as given by `@GeneratedValue`) from their parent classes * `@SequenceGeneratorDefinition`, however, is not generally inherited * ... instead, a default sequence generator definition is created for every class when no explicit configuration is given. In this case, sequence names are based on the current class' table name. * Once a root entity has been identified, all subclasses inherit its sequence generator definition unchanged. #### Why did #10455 break this? When I implemented #10455, I was mislead by two tests `BasicInheritanceMappingTest::testGeneratedValueFromMappedSuperclass` and `BasicInheritanceMappingTest::testMultipleMappedSuperclasses`. These tests check the sequence generator definition that is inherited by an entity class from a mapped superclass, either directly or through an additional (intermediate) mapped superclass. The tests expect the sequence generator definition on the entity _to be the same_ as on the base mapped superclass. The reason why the tests worked before was the quirky behaviour of the annotation and attribute drivers that #10455 was aiming at: The drivers did not report the `@SequenceGeneratorDefinition` on the base mapped superclass where it was actually defined. Instead, they reported this `@SequenceGeneratorDefinition` for the entity class only. This means the inheritance rules stated above did not take effect, since the ID field with the sequence generator was virtually pushed down to the entity class. In #10455, I did not realize that these failing tests had to do with the quirky and changed mapping driver behaviour. Instead, I tried to "fix" the inheritance rules by passing along the sequence generator definition unchanged once the ID column had been defined. #### Consequences of the change suggested here This PR reverts the changes made to `@SequenceGeneratorDefinition` inheritance behaviour that were done in #10455. This means that with the new "report fields where declared" driver mode (which is active in our functional tests) we can not expect the sequence generator definition to be inherited from mapped superclasses. The two test cases from `BasicInheritanceMappingTest` are removed. I will leave a notice in #10455 to indicate that the new driver mode also affects sequence generator definitions. The `GH1927Test` test case validates the sequence names generated in a few cases. In fact, I wrote this test against the `2.15.x` branch to make sure we get results that are consistent with the previous behaviour. This also means `@SequenceGeneratorDefinition` on mapped superclasses is pointless: The mapped superclass does not make use of the definition itself (it has no table), and the setting is never inherited to child classes.
1 parent 609647a commit e3a360d

File tree

3 files changed

+130
-35
lines changed

3 files changed

+130
-35
lines changed

lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonS
113113
if ($parent) {
114114
$class->setInheritanceType($parent->inheritanceType);
115115
$class->setDiscriminatorColumn($parent->discriminatorColumn);
116-
$this->inheritIdGeneratorMapping($class, $parent);
116+
$class->setIdGeneratorType($parent->generatorType);
117117
$this->addInheritedFields($class, $parent);
118118
$this->addInheritedRelations($class, $parent);
119119
$this->addInheritedEmbeddedClasses($class, $parent);
@@ -141,8 +141,12 @@ protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonS
141141
throw MappingException::reflectionFailure($class->getName(), $e);
142142
}
143143

144-
// Complete id generator mapping when the generator was declared/added in this class
145-
if ($class->identifier && (! $parent || ! $parent->identifier)) {
144+
// If this class has a parent the id generator strategy is inherited.
145+
// However this is only true if the hierarchy of parents contains the root entity,
146+
// if it consists of mapped superclasses these don't necessarily include the id field.
147+
if ($parent && $rootEntityFound) {
148+
$this->inheritIdGeneratorMapping($class, $parent);
149+
} else {
146150
$this->completeIdGeneratorMapping($class);
147151
}
148152

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Functional\Ticket;
6+
7+
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
8+
use Doctrine\ORM\Mapping as ORM;
9+
use Doctrine\Tests\OrmFunctionalTestCase;
10+
11+
/**
12+
* @group GH-10927
13+
*/
14+
class GH10927Test extends OrmFunctionalTestCase
15+
{
16+
protected function setUp(): void
17+
{
18+
parent::setUp();
19+
20+
$platform = $this->_em->getConnection()->getDatabasePlatform();
21+
if (! $platform instanceof PostgreSQLPlatform) {
22+
self::markTestSkipped('The ' . self::class . ' requires the use of postgresql.');
23+
}
24+
25+
$this->setUpEntitySchema([
26+
GH10927RootMappedSuperclass::class,
27+
GH10927InheritedMappedSuperclass::class,
28+
GH10927EntityA::class,
29+
GH10927EntityB::class,
30+
GH10927EntityC::class,
31+
]);
32+
}
33+
34+
public function testSequenceGeneratorDefinitionForRootMappedSuperclass(): void
35+
{
36+
$metadata = $this->_em->getClassMetadata(GH10927RootMappedSuperclass::class);
37+
38+
self::assertNull($metadata->sequenceGeneratorDefinition);
39+
}
40+
41+
public function testSequenceGeneratorDefinitionForEntityA(): void
42+
{
43+
$metadata = $this->_em->getClassMetadata(GH10927EntityA::class);
44+
45+
self::assertSame('GH10927EntityA_id_seq', $metadata->sequenceGeneratorDefinition['sequenceName']);
46+
}
47+
48+
public function testSequenceGeneratorDefinitionForInheritedMappedSuperclass(): void
49+
{
50+
$metadata = $this->_em->getClassMetadata(GH10927InheritedMappedSuperclass::class);
51+
52+
self::assertSame('GH10927InheritedMappedSuperclass_id_seq', $metadata->sequenceGeneratorDefinition['sequenceName']);
53+
}
54+
55+
public function testSequenceGeneratorDefinitionForEntityB(): void
56+
{
57+
$metadata = $this->_em->getClassMetadata(GH10927EntityB::class);
58+
59+
self::assertSame('GH10927EntityB_id_seq', $metadata->sequenceGeneratorDefinition['sequenceName']);
60+
}
61+
62+
public function testSequenceGeneratorDefinitionForEntityC(): void
63+
{
64+
$metadata = $this->_em->getClassMetadata(GH10927EntityC::class);
65+
66+
self::assertSame('GH10927EntityB_id_seq', $metadata->sequenceGeneratorDefinition['sequenceName']);
67+
}
68+
}
69+
70+
/**
71+
* @ORM\MappedSuperclass()
72+
*/
73+
class GH10927RootMappedSuperclass
74+
{
75+
}
76+
77+
/**
78+
* @ORM\Entity()
79+
*/
80+
class GH10927EntityA extends GH10927RootMappedSuperclass
81+
{
82+
/**
83+
* @ORM\Id
84+
* @ORM\GeneratedValue(strategy="SEQUENCE")
85+
* @ORM\Column(type="integer")
86+
*
87+
* @var int|null
88+
*/
89+
private $id = null;
90+
}
91+
92+
/**
93+
* @ORM\MappedSuperclass()
94+
*/
95+
class GH10927InheritedMappedSuperclass extends GH10927RootMappedSuperclass
96+
{
97+
/**
98+
* @ORM\Id
99+
* @ORM\GeneratedValue(strategy="SEQUENCE")
100+
* @ORM\Column(type="integer")
101+
*
102+
* @var int|null
103+
*/
104+
private $id = null;
105+
}
106+
107+
/**
108+
* @ORM\Entity()
109+
* @ORM\InheritanceType("JOINED")
110+
* @ORM\DiscriminatorColumn(name="discr", type="string")
111+
* @ORM\DiscriminatorMap({"B" = "GH10927EntityB", "C" = "GH10927EntityC"})
112+
*/
113+
class GH10927EntityB extends GH10927InheritedMappedSuperclass
114+
{
115+
}
116+
117+
/**
118+
* @ORM\Entity()
119+
*/
120+
class GH10927EntityC extends GH10927EntityB
121+
{
122+
}

tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -163,22 +163,7 @@ public function testMappedSuperclassWithId(): void
163163
/**
164164
* @group DDC-1156
165165
* @group DDC-1218
166-
*/
167-
public function testGeneratedValueFromMappedSuperclass(): void
168-
{
169-
$class = $this->cmf->getMetadataFor(SuperclassEntity::class);
170-
assert($class instanceof ClassMetadata);
171-
172-
self::assertInstanceOf(IdSequenceGenerator::class, $class->idGenerator);
173-
self::assertEquals(
174-
['allocationSize' => 1, 'initialValue' => 10, 'sequenceName' => 'foo'],
175-
$class->sequenceGeneratorDefinition
176-
);
177-
}
178-
179-
/**
180-
* @group DDC-1156
181-
* @group DDC-1218
166+
* @group GH-10927
182167
*/
183168
public function testSequenceDefinitionInHierarchyWithSandwichMappedSuperclass(): void
184169
{
@@ -192,22 +177,6 @@ public function testSequenceDefinitionInHierarchyWithSandwichMappedSuperclass():
192177
);
193178
}
194179

195-
/**
196-
* @group DDC-1156
197-
* @group DDC-1218
198-
*/
199-
public function testMultipleMappedSuperclasses(): void
200-
{
201-
$class = $this->cmf->getMetadataFor(MediumSuperclassEntity::class);
202-
assert($class instanceof ClassMetadata);
203-
204-
self::assertInstanceOf(IdSequenceGenerator::class, $class->idGenerator);
205-
self::assertEquals(
206-
['allocationSize' => 1, 'initialValue' => 10, 'sequenceName' => 'foo'],
207-
$class->sequenceGeneratorDefinition
208-
);
209-
}
210-
211180
/**
212181
* Ensure indexes are inherited from the mapped superclass.
213182
*

0 commit comments

Comments
 (0)