Skip to content

Commit

Permalink
Catch missing inheritance declarations early on
Browse files Browse the repository at this point in the history
When my understanding is correct, inheritance has to be declared as soon as one entity class extends (directly or through middle classes) another one.

Catching missing inheritance declarations early on is important to avoid weird errors further down the road, giving users a clear indication of the root cause.

The documentation is updated accordingly at #10429.
  • Loading branch information
mpdude committed Jan 19, 2023
1 parent ba7387f commit 2f476f4
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 0 deletions.
4 changes: 4 additions & 0 deletions lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonS
}

if (! $class->isMappedSuperclass) {
if ($rootEntityFound && $class->isInheritanceTypeNone()) {
throw MappingException::missingInheritanceTypeDeclaration(end($nonSuperclassParents), $class->name);
}

foreach ($class->embeddedClasses as $property => $embeddableClass) {
if (isset($embeddableClass['inherited'])) {
continue;
Expand Down
13 changes: 13 additions & 0 deletions lib/Doctrine/ORM/Mapping/MappingException.php
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,19 @@ static function ($a, $b) {
);
}

/**
* @param class-string $rootEntityClass
* @param class-string $childEntityClass
*/
public static function missingInheritanceTypeDeclaration(string $rootEntityClass, string $childEntityClass): self
{
return new self(sprintf(
"Entity class '%s' is a subclass of the root entity class '%s', but no inheritance mapping type was declared.",
$childEntityClass,
$rootEntityClass
));
}

/**
* @param string $className
*
Expand Down
117 changes: 117 additions & 0 deletions tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@
use Doctrine\Tests\Models\DDC869\DDC869Payment;
use Doctrine\Tests\Models\DDC869\DDC869PaymentRepository;
use Doctrine\Tests\OrmTestCase;
use Generator;

use function assert;
use function serialize;
use function sprintf;
use function unserialize;

class BasicInheritanceMappingTest extends OrmTestCase
Expand Down Expand Up @@ -218,6 +220,39 @@ public function testMappedSuperclassIndex(): void
self::assertArrayHasKey('IDX_MAPPED1_INDEX', $class->table['uniqueConstraints']);
self::assertArrayHasKey('IDX_MAPPED2_INDEX', $class->table['indexes']);
}

/**
* @dataProvider invalidHierarchyDeclarationClasses
*/
public function testUndeclaredHierarchyRejection(string $rootEntity, string $childClass): void
{
self::expectException(MappingException::class);
self::expectExceptionMessage(sprintf(
"Entity class '%s' is a subclass of the root entity class '%s', but no inheritance mapping type was declared.",
$childClass,
$rootEntity
));

$this->cmf->getMetadataFor($childClass);
}

public function invalidHierarchyDeclarationClasses(): Generator
{
yield 'concrete Entity root and child class, direct inheritance'
=> [InvalidEntityRoot::class, InvalidEntityRootChild::class];

yield 'concrete Entity root and abstract child class, direct inheritance'
=> [InvalidEntityRoot::class, InvalidEntityRootAbstractChild::class];

yield 'abstract Entity root and concrete child class, direct inheritance'
=> [InvalidAbstractEntityRoot::class, InvalidAbstractEntityRootChild::class];

yield 'abstract Entity root and abstract child class, direct inheritance'
=> [InvalidAbstractEntityRoot::class, InvalidAbstractEntityRootAbstractChild::class];

yield 'complex example (Entity Root -> Mapped Superclass -> transient class -> Entity)'
=> [InvalidComplexRoot::class, InvalidComplexEntity::class];
}
}

class TransientBaseClass
Expand Down Expand Up @@ -438,3 +473,85 @@ class MediumSuperclassEntity extends MediumSuperclassBase
class SubclassWithRepository extends DDC869Payment
{
}

/**
* @Entity
*
* This class misses the DiscriminatorMap declaration
*/
class InvalidEntityRoot
{
/**
* @Column(type="integer")
* @Id
* @GeneratedValue(strategy="AUTO")
* @var int
*/
public $id;
}

/** @Entity */
class InvalidEntityRootChild extends InvalidEntityRoot
{
}

/** @Entity */
abstract class InvalidEntityRootAbstractChild extends InvalidEntityRoot
{
}

/**
* @Entity
*
* This class misses the DiscriminatorMap declaration
*/
class InvalidAbstractEntityRoot
{
/**
* @Column(type="integer")
* @Id
* @GeneratedValue(strategy="AUTO")
* @var int
*/
public $id;
}

/** @Entity */
class InvalidAbstractEntityRootChild extends InvalidAbstractEntityRoot
{
}

/** @Entity */
abstract class InvalidAbstractEntityRootAbstractChild extends InvalidAbstractEntityRoot
{
}

/**
* @Entity
*
* This class misses the DiscriminatorMap declaration
*/
class InvalidComplexRoot
{
/**
* @Column(type="integer")
* @Id
* @GeneratedValue(strategy="AUTO")
* @var int
*/
public $id;
}

/** @MappedSuperclass */
class InvalidComplexMappedSuperclass extends InvalidComplexRoot
{
}

class InvalidComplexTransientClass extends InvalidComplexMappedSuperclass
{
}

/** @Entity */
class InvalidComplexEntity extends InvalidComplexTransientClass
{
}

0 comments on commit 2f476f4

Please sign in to comment.