Doctrine ORM 2.9: MappedSuperclass in entity hierarchy #6761

Closed
opened 2026-01-22 15:38:09 +01:00 by admin · 16 comments
Owner

Originally created by @BenMorel on GitHub (Jun 16, 2021).

We have an entity hierarchy similar to:

/**
 * @ORM\Entity
 * @ORM\Table(name="AbstractInvoiceItem")
 *
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="type")
 * @ORM\DiscriminatorMap({
 *     "SetupFee" = SetupFee::class
 * })
 */
abstract class AbstractInvoiceItem
{
}

/**
 * @MappedSuperclass
 */
abstract class AbstractProductInvoiceItem extends AbstractInvoiceItem
{
    /**
     * @ORM\ManyToOne(targetEntity=AbstractProduct::class)
     */
    protected ?AbstractProduct $product;
}

/**
 * @ORM\Entity
 * @ORM\Table(name=SetupFee")
 */
class SetupFee extends AbstractProductInvoiceItem
{
}

This was working fine with Doctrine 2.8.

Since we upgraded to Doctrine 2.9 however, bin/console doctrine:schema:validate started failing with:

[FAIL] The entity-class App(...)\AbstractProductInvoiceItem mapping is invalid:

  • Entity class 'App(...)\AbstractProductInvoiceItem' is part of inheritance hierarchy, but is not mapped in the root entity 'App(...)\AbstractInvoiceItem' discriminator map. All subclasses must be listed in the discriminator map.

I tried adding a "fake" entry to the discriminator map for the abstract class:

/*
 * @ORM\DiscriminatorMap({
 *     "AbstractProductInvoiceItem" = AbstractProductInvoiceItem::class,
 *     "SetupFee" = SetupFee::class
 * })
 */

But now Doctrine attempts to JOIN a non-existing table when loading an entity in the hierarchy:

SQLSTATE[42S02]: Base table or view not found: 1146 Table 'abstract_product_invoice_item' doesn't exist

How can I fix this?

A solution would be to make the intermediate abstract class an entity, but this would mean that $product would be moved from leaf entities to a new AbstractProductInvoiceItem table, which adds an extra JOIN to every query, and would mean a rather scary migration to do.

Is there a better solution? Or are MappedSuperclasses not allowed in the middle of an entity hierarchy anymore?

Originally created by @BenMorel on GitHub (Jun 16, 2021). We have an entity hierarchy similar to: ```php /** * @ORM\Entity * @ORM\Table(name="AbstractInvoiceItem") * * @ORM\InheritanceType("JOINED") * @ORM\DiscriminatorColumn(name="type") * @ORM\DiscriminatorMap({ * "SetupFee" = SetupFee::class * }) */ abstract class AbstractInvoiceItem { } /** * @MappedSuperclass */ abstract class AbstractProductInvoiceItem extends AbstractInvoiceItem { /** * @ORM\ManyToOne(targetEntity=AbstractProduct::class) */ protected ?AbstractProduct $product; } /** * @ORM\Entity * @ORM\Table(name=SetupFee") */ class SetupFee extends AbstractProductInvoiceItem { } ``` This was working fine with Doctrine 2.8. Since we upgraded to Doctrine 2.9 however, `bin/console doctrine:schema:validate` started failing with: > [FAIL] The entity-class App\(...)\AbstractProductInvoiceItem mapping is invalid: > * Entity class 'App\(...)\AbstractProductInvoiceItem' is part of inheritance hierarchy, but is not mapped in the root entity 'App\(...)\AbstractInvoiceItem' discriminator map. All subclasses must be listed in the discriminator map. I tried adding a "fake" entry to the discriminator map for the abstract class: ```php /* * @ORM\DiscriminatorMap({ * "AbstractProductInvoiceItem" = AbstractProductInvoiceItem::class, * "SetupFee" = SetupFee::class * }) */ ``` But now Doctrine attempts to `JOIN` a non-existing table when loading an entity in the hierarchy: > SQLSTATE[42S02]: Base table or view not found: 1146 Table 'abstract_product_invoice_item' doesn't exist **How can I fix this?** A solution would be to make the intermediate abstract class an entity, but this would mean that `$product` would be moved from leaf entities to a new `AbstractProductInvoiceItem` table, which adds an extra `JOIN` to every query, and would mean a rather scary migration to do. Is there a better solution? Or **are MappedSuperclasses not allowed in the middle of an entity hierarchy anymore?**
admin closed this issue 2026-01-22 15:38:09 +01:00
Author
Owner

@greg0ire commented on GitHub (Jun 17, 2021):

This changed in #8378

@greg0ire commented on GitHub (Jun 17, 2021): This changed in #8378
Author
Owner

@BenMorel commented on GitHub (Jun 17, 2021):

Thanks for the pointer, @greg0ire; do you know of any solution for this issue?

@BenMorel commented on GitHub (Jun 17, 2021): Thanks for the pointer, @greg0ire; do you know of any solution for this issue?
Author
Owner

@greg0ire commented on GitHub (Jun 17, 2021):

I don't, I was just adding to this post because it might give you pointers as to who you might want to talk to about this, or whether this was deliberate or not. I think this might be worth a read too: https://github.com/doctrine/orm/pull/7825

Also, https://github.com/doctrine/orm/pull/8415 looks like it might fix your issue? I don't have a lot of time to help you right now, but you might have some helpful things to read in this repo's issues and PRs

@greg0ire commented on GitHub (Jun 17, 2021): I don't, I was just adding to this post because it might give you pointers as to who you might want to talk to about this, or whether this was deliberate or not. I think this might be worth a read too: https://github.com/doctrine/orm/pull/7825 Also, https://github.com/doctrine/orm/pull/8415 looks like it might fix your issue? I don't have a lot of time to help you right now, but you might have some helpful things to read in this repo's issues and PRs
Author
Owner

@BenMorel commented on GitHub (Jun 17, 2021):

I tried the fix from #8415, but it does not seem to solve my issue I'm afraid :(

AFAICS, I still have to choose between:

  • not putting the abstract @MappedSuperclass in the discriminator map, and getting an error in doctrine:schema:validate
  • putting a fake entry for this abstract class in the discriminator map, and getting a table not found error at runtime

Thank you again for the pointers, though! I appreciate it.

@BenMorel commented on GitHub (Jun 17, 2021): I tried the fix from #8415, but it does not seem to solve my issue I'm afraid :( AFAICS, I still have to choose between: - not putting the abstract `@MappedSuperclass` in the discriminator map, and getting an error in `doctrine:schema:validate` - putting a fake entry for this abstract class in the discriminator map, and getting a table not found error at runtime Thank you again for the pointers, though! I appreciate it.
Author
Owner

@beberlei commented on GitHub (Jun 17, 2021):

Its a bug this fails, i oversaw the mapped superclass case

@beberlei commented on GitHub (Jun 17, 2021): Its a bug this fails, i oversaw the mapped superclass case
Author
Owner

@BenMorel commented on GitHub (Jun 17, 2021):

Glad to hear it, @beberlei! If you give me a hint as to how to fix it, maybe I can help?

@BenMorel commented on GitHub (Jun 17, 2021): Glad to hear it, @beberlei! If you give me a hint as to how to fix it, maybe I can help?
Author
Owner

@beberlei commented on GitHub (Jun 17, 2021):

Just check for isMappedSuperclass in SchemaValifator to avoif the error

@beberlei commented on GitHub (Jun 17, 2021): Just check for isMappedSuperclass in SchemaValifator to avoif the error
Author
Owner

@BenMorel commented on GitHub (Jun 18, 2021):

@beberlei Thank you, it works fine for @MappedSuperclass by adding the ! $class->isMappedSuperclass check here:

if (! $class->isInheritanceTypeNone() && ! $class->isRootEntity() && ! $class->isMappedSuperclass && array_search($class->name, $class->discriminatorMap) === false) {

I still, however, have a [FAIL] when there is an abstract @Entity class in the middle of the inheritance hierarchy. Why would we have to create an entry in the discriminator map for a non-instantiable class?

Would you be OK with changing this for ! $class->reflClass->isAbstract() instead?

@BenMorel commented on GitHub (Jun 18, 2021): @beberlei Thank you, it works fine for `@MappedSuperclass` by adding the `! $class->isMappedSuperclass` check here: ```php if (! $class->isInheritanceTypeNone() && ! $class->isRootEntity() && ! $class->isMappedSuperclass && array_search($class->name, $class->discriminatorMap) === false) { ``` I still, however, have a `[FAIL]` when there is an abstract `@Entity` class in the middle of the inheritance hierarchy. Why would we have to create an entry in the discriminator map for a non-instantiable class? Would you be OK with changing this for `! $class->reflClass->isAbstract()` instead?
Author
Owner

@BenMorel commented on GitHub (Jul 10, 2021):

Ping @beberlei 🙂

@BenMorel commented on GitHub (Jul 10, 2021): Ping @beberlei :slightly_smiling_face:
Author
Owner

@olsavmic commented on GitHub (Aug 4, 2021):

@beberlei We're having the very same issue. It does not make much sense to me to require abstract classes to be a part of the discriminator map.

If you feel like there is a reason to require it, let us know please. Otherwise I can prepare a PR with a fix :)

Thank you!

@olsavmic commented on GitHub (Aug 4, 2021): @beberlei We're having the very same issue. It does not make much sense to me to require abstract classes to be a part of the discriminator map. If you feel like there is a reason to require it, let us know please. Otherwise I can prepare a PR with a fix :) Thank you!
Author
Owner

@olsavmic commented on GitHub (Aug 5, 2021):

@beberlei We're having the very same issue. It does not make much sense to me to require abstract classes to be a part of the discriminator map.

If you feel like there is a reason to require it, let us know please. Otherwise I can prepare a PR with a fix :)

Thank you!

I found your comment (https://github.com/doctrine/orm/issues/8736#issuecomment-853044536) and I even found a case when the behaviour is unexpected if the abstract class in the middle of hierarchy is not present (SchemaTool:183, relationship columns may not be created) so it's not a BC break but rather a bug fix and this issue can be closed so my issue regarding the abstract class is resolved.

Thank you! @beberlei

@olsavmic commented on GitHub (Aug 5, 2021): > @beberlei We're having the very same issue. It does not make much sense to me to require abstract classes to be a part of the discriminator map. > > If you feel like there is a reason to require it, let us know please. Otherwise I can prepare a PR with a fix :) > > Thank you! I found your comment (https://github.com/doctrine/orm/issues/8736#issuecomment-853044536) and I even found a case when the behaviour is unexpected if the abstract class in the middle of hierarchy is not present (SchemaTool:183, relationship columns may not be created) ~~so it's not a BC break but rather a bug fix and this issue can be closed~~ so my issue regarding the abstract class is resolved. Thank you! @beberlei
Author
Owner

@BenMorel commented on GitHub (Aug 5, 2021):

@olsavmic Adding a fake entry for abstract classes does not fix all issues, please re-read the first post of this thread.
This issue cannot be closed!

@BenMorel commented on GitHub (Aug 5, 2021): @olsavmic Adding a fake entry for abstract classes does not fix all issues, please re-read the first post of this thread. This issue cannot be closed!
Author
Owner

@olsavmic commented on GitHub (Aug 5, 2021):

@olsavmic Adding a fake entry for abstract classes does not fix all issues, please re-read the first post of this thread.
This issue cannot be closed!

@BenMorel I'm sorry, I was concerned only with the abstract class case since I somehow thought that the MappedSuperclass case was already resolved&merged.

Anyway, it partially responds to your question:

I still, however, have a [FAIL] when there is an abstract @Entity class in the middle of the inheritance hierarchy. Why > would we have to create an entry in the discriminator map for a non-instantiable class?

Would you be OK with changing this for ! $class->reflClass->isAbstract() instead?

Abstract entity classes (does not apply to @MappedSuperclass) should be left as is and they have to be part of the DiscriminatorMap so that everything works properly.

@olsavmic commented on GitHub (Aug 5, 2021): > @olsavmic Adding a fake entry for abstract classes does not fix all issues, please re-read the first post of this thread. > This issue cannot be closed! @BenMorel I'm sorry, I was concerned only with the abstract class case since I somehow thought that the MappedSuperclass case was already resolved&merged. Anyway, it partially responds to your question: > I still, however, have a [FAIL] when there is an abstract `@Entity` class in the middle of the inheritance hierarchy. Why > would we have to create an entry in the discriminator map for a non-instantiable class? >Would you be OK with changing this for ! $class->reflClass->isAbstract() instead? Abstract entity classes (does not apply to `@MappedSuperclass`) should be left as is and they have to be part of the DiscriminatorMap so that everything works properly.
Author
Owner

@BenMorel commented on GitHub (Aug 5, 2021):

Abstract classes should be left as is and they have to be part of the DiscriminatorMap so that everything works properly.

Not sure why it should be this way, though I don't really mind, as long as Doctrine does not attempt to join a non-existing class!

@BenMorel commented on GitHub (Aug 5, 2021): > Abstract classes should be left as is and they have to be part of the DiscriminatorMap so that everything works properly. Not sure why it *should* be this way, though I don't really mind, as long as Doctrine does not attempt to join a non-existing class!
Author
Owner

@olsavmic commented on GitHub (Aug 6, 2021):

@BenMorel I want to make sure we’re on the same page.

@MappedSuperclass
abstract class Foo extends FooParent

The issue with MappedSuperclass in the middle of hierarchy is still there, Beberlei confirmed it’s a bug and it needs to be fixed. I’d prepare a PR but it’s rather your contribution.

I just tested the case with joined table and:

@Entity
abstract class Foo extends FooParent {

}   

Needs an entry in the discriminator map and a separate table exists (will be generated by schema diff) for this abstract class (which is implied by the JOINED behaviour and is valid from the DB schema point of view).

abstract class Foo extends FooParent {

}

(Without @Entity annotation) does not need an entry in the DiscriminatorMap, a table is not generated yet the class cannot contain any fields. I'd actually say it should be marked as error by the ValidatorSchema as it's very easy to forget that the @Entity annotation is missing while adding new fields.

@olsavmic commented on GitHub (Aug 6, 2021): @BenMorel I want to make sure we’re on the same page. ``` @MappedSuperclass abstract class Foo extends FooParent ``` The issue with MappedSuperclass in the middle of hierarchy is still there, Beberlei confirmed it’s a bug and it needs to be fixed. I’d prepare a PR but it’s rather your contribution. I just tested the case with joined table and: ``` @Entity abstract class Foo extends FooParent { } ``` Needs an entry in the discriminator map and a separate table exists (will be generated by schema diff) for this abstract class (which is implied by the JOINED behaviour and is valid from the DB schema point of view). ``` abstract class Foo extends FooParent { } ``` (Without `@Entity` annotation) does not need an entry in the DiscriminatorMap, a table is not generated yet the class cannot contain any fields. I'd actually say it should be marked as error by the ValidatorSchema as it's very easy to forget that the `@Entity` annotation is missing while adding new fields.
Author
Owner

@BenMorel commented on GitHub (Oct 4, 2021):

Hi, there is still a regression in 2.9 and 2.10: we now have to add a fake entry for abstract entity classes in the discriminator map. This is quite an annoyance, can't this be avoided? Should I open a separate issue for this?

@BenMorel commented on GitHub (Oct 4, 2021): Hi, there is still a regression in `2.9` and `2.10`: we now have to add a fake entry for abstract entity classes in the discriminator map. This is quite an annoyance, can't this be avoided? Should I open a separate issue for this?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#6761