diff --git a/UPGRADE.md b/UPGRADE.md index 0346a4d2ad9..d37fa49968e 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -12,6 +12,11 @@ now deprecated and will be removed in 3.0. # Upgrade to 2.14 +## Deprecated undeclared entity inheritance + +As soon as an entity class inherits from another entity class, inheritance has to +be declared by adding the appropriate configuration for the root entity. + ## Deprecated `Doctrine\ORM\Persisters\Exception\UnrecognizedField::byName($field)` method. Use `Doctrine\ORM\Persisters\Exception\UnrecognizedField::byFullyQualifiedName($className, $field)` instead. diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index ab87f59813b..f1b4292d291 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -148,6 +148,18 @@ protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonS } if (! $class->isMappedSuperclass) { + if ($rootEntityFound && $class->isInheritanceTypeNone()) { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/10431', + "Entity class '%s' is a subclass of the root entity class '%s', but no inheritance mapping type was declared. This is a misconfiguration and will be an error in Doctrine ORM 3.0.", + $class->name, + end($nonSuperclassParents) + ); + // enable this in 3.0 + // throw MappingException::missingInheritanceTypeDeclaration(end($nonSuperclassParents), $class->name); + } + foreach ($class->embeddedClasses as $property => $embeddableClass) { if (isset($embeddableClass['inherited'])) { continue; diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index 9fd3f032eda..4714047122d 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -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 * diff --git a/tests/Doctrine/Tests/Models/DDC1590/DDC1590Entity.php b/tests/Doctrine/Tests/Models/DDC1590/DDC1590Entity.php index 26d41781542..1078ffeb33c 100644 --- a/tests/Doctrine/Tests/Models/DDC1590/DDC1590Entity.php +++ b/tests/Doctrine/Tests/Models/DDC1590/DDC1590Entity.php @@ -6,13 +6,11 @@ use DateTime; use Doctrine\ORM\Mapping\Column; -use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\GeneratedValue; use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\MappedSuperclass; /** - * @Entity * @MappedSuperclass */ abstract class DDC1590Entity diff --git a/tests/Doctrine/Tests/Models/DDC2372/DDC2372User.php b/tests/Doctrine/Tests/Models/DDC2372/DDC2372User.php index c2ec10e6a47..f11b248e2d3 100644 --- a/tests/Doctrine/Tests/Models/DDC2372/DDC2372User.php +++ b/tests/Doctrine/Tests/Models/DDC2372/DDC2372User.php @@ -5,15 +5,21 @@ namespace Doctrine\Tests\Models\DDC2372; use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\DiscriminatorColumn; +use Doctrine\ORM\Mapping\DiscriminatorMap; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\GeneratedValue; use Doctrine\ORM\Mapping\Id; +use Doctrine\ORM\Mapping\InheritanceType; use Doctrine\ORM\Mapping\Table; use Doctrine\Tests\Models\DDC2372\Traits\DDC2372AddressAndAccessors; /** * @Entity * @Table(name="users") + * @InheritanceType("SINGLE_TABLE") + * @DiscriminatorColumn("type") + * @DiscriminatorMap({"user"="DDC2372User", "admin"="DDC2372Admin"}) */ class DDC2372User { diff --git a/tests/Doctrine/Tests/Models/DDC5934/DDC5934BaseContract.php b/tests/Doctrine/Tests/Models/DDC5934/DDC5934BaseContract.php index 5933d27f87e..71898607ff2 100644 --- a/tests/Doctrine/Tests/Models/DDC5934/DDC5934BaseContract.php +++ b/tests/Doctrine/Tests/Models/DDC5934/DDC5934BaseContract.php @@ -8,13 +8,13 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\Column; -use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\GeneratedValue; use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\ManyToMany; +use Doctrine\ORM\Mapping\MappedSuperclass; -/** @Entity */ -#[Entity] +/** @MappedSuperclass() */ +#[MappedSuperclass] class DDC5934BaseContract { /** diff --git a/tests/Doctrine/Tests/Models/Forum/ForumAdministrator.php b/tests/Doctrine/Tests/Models/Forum/ForumAdministrator.php deleted file mode 100644 index 9ce94b9755f..00000000000 --- a/tests/Doctrine/Tests/Models/Forum/ForumAdministrator.php +++ /dev/null @@ -1,18 +0,0 @@ -table['uniqueConstraints']); self::assertArrayHasKey('IDX_MAPPED2_INDEX', $class->table['indexes']); } + + /** + * @dataProvider invalidHierarchyDeclarationClasses + */ + public function testUndeclaredHierarchyRejection(string $rootEntity, string $childClass): void + { + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/10431'); + + /* on 3.0, use this instead: */ + // 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 @@ -438,3 +478,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 +{ +}