DDC-1776: Abstract class marked as @Entity in a single table inheritance is wrongly hydrated #2232

Closed
opened 2026-01-22 13:45:44 +01:00 by admin · 12 comments
Owner

Originally created by @doctrinebot on GitHub (Apr 14, 2012).

Originally assigned to: @Ocramius on GitHub.

Jira issue originally created by user benjamin:

I have the following entity hierarchy:

/****
 * @Entity
 * @InheritanceType("SINGLE_TABLE")
 * @DiscriminatorColumn(name="type", type="string")
 * @DiscriminatorMap({
     "prospectEdited" = "Application\Domain\Model\Event\ProspectEditedEvent"
   })
 */
abstract class Event {
    // ...
}

/****
 * @Entity
 */
abstract class ProspectEvent extends Event {
    /****
     * @ManyToOne(targetEntity="Application\Domain\Model\Prospect")
     */
    protected $prospect;
}

/****
 * @Entity
 */
class ProspectEditedEvent extends ProspectEvent {
    // ...
}

Although ProspectEvent is an abstract class, I need to mark it as @Entity, so that I can query for any type of ProspectEvent:

SELECT e FROM ProspectEvent e WHERE e.prospect = ?

So far, so good.
The problem happens when I query the root class:

SELECT e FROM Event e

The query works fine, but the returned ProspectEvent entities have a NULL $prospect property.
I noticed that in that case, the DocBlock for $prospect is totally ignored (a wrong @JoinColumn name for example, doesn't throw an exception).

The workaround to make it work, is to add a "fake" entry to the discriminator map for the abstract class:

@DiscriminatorMap({
  "prospectEvent"  = "Application\Domain\Model\Event\ProspectEvent"
  "prospectEdited" = "Application\Domain\Model\Event\ProspectEditedEvent"
})

Even though there is no such "prospectEvent" discriminator entry possible, as the class is abstract.
Final point, if I remove the @Entity DocBlock on ProspectEvent, $prospect is hydrated correctly, but then it is not possible to issue a DQL query on ProspectEvent anymore.

Originally created by @doctrinebot on GitHub (Apr 14, 2012). Originally assigned to: @Ocramius on GitHub. Jira issue originally created by user benjamin: I have the following entity hierarchy: ``` /**** * @Entity * @InheritanceType("SINGLE_TABLE") * @DiscriminatorColumn(name="type", type="string") * @DiscriminatorMap({ "prospectEdited" = "Application\Domain\Model\Event\ProspectEditedEvent" }) */ abstract class Event { // ... } /**** * @Entity */ abstract class ProspectEvent extends Event { /**** * @ManyToOne(targetEntity="Application\Domain\Model\Prospect") */ protected $prospect; } /**** * @Entity */ class ProspectEditedEvent extends ProspectEvent { // ... } ``` Although ProspectEvent is an abstract class, I need to mark it as @Entity, so that I can query for any type of ProspectEvent: ``` SELECT e FROM ProspectEvent e WHERE e.prospect = ? ``` So far, so good. The problem happens when I query the root class: ``` SELECT e FROM Event e ``` The query works fine, but the returned ProspectEvent entities have a NULL $prospect property. I noticed that in that case, the DocBlock for $prospect is totally ignored (a wrong @JoinColumn name for example, doesn't throw an exception). The workaround to make it work, is to add a "fake" entry to the discriminator map for the abstract class: ``` @DiscriminatorMap({ "prospectEvent" = "Application\Domain\Model\Event\ProspectEvent" "prospectEdited" = "Application\Domain\Model\Event\ProspectEditedEvent" }) ``` Even though there is no such "prospectEvent" discriminator entry possible, as the class is abstract. Final point, if I remove the @Entity DocBlock on ProspectEvent, $prospect is hydrated correctly, but then it is not possible to issue a DQL query on ProspectEvent anymore.
admin added the Bug label 2026-01-22 13:45:44 +01:00
admin closed this issue 2026-01-22 13:45:44 +01:00
Author
Owner

@doctrinebot commented on GitHub (Nov 29, 2012):

Comment created by benjamin:

Any feedback?

@doctrinebot commented on GitHub (Nov 29, 2012): Comment created by benjamin: Any feedback?
Author
Owner

@doctrinebot commented on GitHub (Jan 23, 2013):

Comment created by @ocramius:

[~benjamin] I'd say this has to be fixed in validation. What if we enforce the fake discriminator map? The "workaround" you suggested is actually what you should write with correct mappings.

@doctrinebot commented on GitHub (Jan 23, 2013): Comment created by @ocramius: [~benjamin] I'd say this has to be fixed in validation. What if we enforce the fake discriminator map? The "workaround" you suggested is actually what you should write with correct mappings.
Author
Owner

@doctrinebot commented on GitHub (Feb 9, 2013):

Comment created by @ocramius:

Yes, this has to be reported in validation somehow. The "workaround" is not actually a workaround, but what is expected to be written by the developer.

@doctrinebot commented on GitHub (Feb 9, 2013): Comment created by @ocramius: Yes, this has to be reported in validation somehow. The "workaround" is not actually a workaround, but what is expected to be written by the developer.
Author
Owner

@doctrinebot commented on GitHub (Feb 10, 2013):

Comment created by benjamin:

Ok, I found this weird because it's impossible to store a ProspectEvent in the database (as it's abstract), so adding a discriminator entry for it looks weird to me.
I would then suggest one of these two approaches:

  • Either remove the requirement to have a discriminator entry for the parent class if it's abstract (this would be my preference).
    ** Or, document this behavior, and* throw a proper exception if we try to persist / query such a hierarchy of classes.

But leaving the error unreported, and instantiating incomplete objects, is a real danger!

@doctrinebot commented on GitHub (Feb 10, 2013): Comment created by benjamin: Ok, I found this weird because it's impossible to store a `ProspectEvent` in the database (as it's `abstract`), so adding a discriminator entry for it looks weird to me. I would then suggest one of these two approaches: - Either remove the requirement to have a discriminator entry for the parent class if it's `abstract` (this would be my preference). *\* Or, document this behavior, _and_\* throw a proper exception if we try to persist / query such a hierarchy of classes. But leaving the error unreported, and instantiating incomplete objects, is a real danger!
Author
Owner

@doctrinebot commented on GitHub (May 1, 2013):

Comment created by @beberlei:

We fixed errors of this kind a while ago

@doctrinebot commented on GitHub (May 1, 2013): Comment created by @beberlei: We fixed errors of this kind a while ago
Author
Owner

@doctrinebot commented on GitHub (Sep 8, 2013):

Issue was closed with resolution "Duplicate"

@doctrinebot commented on GitHub (Sep 8, 2013): Issue was closed with resolution "Duplicate"
Author
Owner

@gdelprete-tl commented on GitHub (Dec 8, 2016):

I incurred in this bug yesterday.

In my case, I have some abstract @Entity classes with simple properties (non-association ones) in my hierarchy. The schema and persisting data works correctly, but when querying all elements by using the parent, objects of child classes gets loaded but properties in the abstract @Entitys are set to null (even if the row in the database has correct values). To fix this, I need to state discriminator values for abstract @Entitys too.

@gdelprete-tl commented on GitHub (Dec 8, 2016): I incurred in this bug yesterday. In my case, I have some abstract `@Entity` classes with simple properties (non-association ones) in my hierarchy. The schema and persisting data works correctly, but when querying all elements by using the parent, objects of child classes gets loaded but properties in the abstract `@Entity`s are set to null (even if the row in the database has correct values). To fix this, I need to state discriminator values for abstract `@Entity`s too.
Author
Owner

@Ocramius commented on GitHub (Dec 8, 2016):

@gdelprete-tl that's not sufficient to analyze the problem: I suggest creating a test case as per examples in d3f6c5ec70/tests/Doctrine/Tests/ORM/Functional/Ticket

@Ocramius commented on GitHub (Dec 8, 2016): @gdelprete-tl that's not sufficient to analyze the problem: I suggest creating a test case as per examples in https://github.com/doctrine/doctrine2/tree/d3f6c5ec70aac4b029a4b61ecf1e2ba61a1a4a6d/tests/Doctrine/Tests/ORM/Functional/Ticket
Author
Owner

@BenMorel commented on GitHub (Aug 29, 2018):

I thought this had been fixed, but I had the same problem again today, on another project, using Doctrine 2.6.2. I have to add a fake entry (that can not exist in the db) to the discriminator map.

Hiearchy:

  • abstract UserActivity ($id, $user)
    • abstract BookActivity ($book)
      • BookReview ($rating, $contents)

If I select entities from the root UserActivity with DQL:

SELECT a FROM UserActivity a

Then BookActivity::$book is NULL ☹️

Here is the fix that gets the entity properly hydrated:

@ORM\DiscriminatorMap({
  "BookReview" = "MyApp\Domain\Model\UserActivity\BookReview",
  ""           = "MyApp\Domain\Model\UserActivity\BookActivity"
})

I stand by my comment from 2013 that it does not make sense to have to add a discriminator entry for a value that cannot exist in the DB. Plus, why would I need to add a fake entry for the abstract BookActivity, but not for the abstract UserActivity root class, which is properly hydrated?

Anyway, would this (inconsistent) behaviour be expected (I still can't find the relevant documentation, years later), Doctrine partially hydrating an object without either throwing an exception, or raising an error when validating the schema from the command-line tool, is definitely a bug.

Is it worth creating a failing test case to hopefully fix this issue in Doctrine 2.6, or are you guys busy on version 3 (that may be radically different?) and this would be a waste of time? Cheers 👍

@BenMorel commented on GitHub (Aug 29, 2018): I thought this had been fixed, but I had the same problem again today, on another project, using Doctrine 2.6.2. I have to add a *fake* entry (that can not exist in the db) to the discriminator map. Hiearchy: - `abstract` UserActivity (`$id`, `$user`) - `abstract` BookActivity (`$book`) - BookReview (`$rating`, `$contents`) If I select entities from the root `UserActivity` with DQL: SELECT a FROM UserActivity a Then `BookActivity::$book` is NULL ☹️ Here is the fix that gets the entity properly hydrated: ``` @ORM\DiscriminatorMap({ "BookReview" = "MyApp\Domain\Model\UserActivity\BookReview", "" = "MyApp\Domain\Model\UserActivity\BookActivity" }) ``` I stand by my comment from 2013 that it does not make sense to *have* to add a discriminator entry for a value that cannot exist in the DB. Plus, why would I need to add a fake entry for the abstract `BookActivity`, but not for the abstract `UserActivity` root class, which is properly hydrated? Anyway, would this (inconsistent) behaviour be expected (I still can't find the relevant documentation, years later), Doctrine partially hydrating an object without either throwing an exception, or raising an error when validating the schema from the command-line tool, is definitely a bug. Is it worth creating a failing test case to hopefully fix this issue in Doctrine 2.6, or are you guys busy on version 3 (that may be radically different?) and this would be a waste of time? Cheers 👍
Author
Owner

@lcobucci commented on GitHub (Nov 20, 2018):

Is it worth creating a failing test case to hopefully fix this issue in Doctrine 2.6,

@BenMorel a failing test case is always helpful 😄

@lcobucci commented on GitHub (Nov 20, 2018): > Is it worth creating a failing test case to hopefully fix this issue in Doctrine 2.6, @BenMorel a failing test case is always helpful :smile:
Author
Owner

@jrcii commented on GitHub (Jan 13, 2023):

Doctrine's handling of abstract entity classes is incorrect. This comment was spot on https://github.com/doctrine/orm/issues/2428#issuecomment-162358894 The issue should not have been dismissed and closed without addressing the valid points raised. The abstract parent shouldn't be required as a discriminator entry.

Having the issue for over 10 years is one thing, doing so because we're pretending it's not a problem is another. I get the desire to reduce open issues but c'mon now.

@jrcii commented on GitHub (Jan 13, 2023): Doctrine's handling of abstract entity classes is incorrect. This comment was spot on https://github.com/doctrine/orm/issues/2428#issuecomment-162358894 The issue should not have been dismissed and closed without addressing the valid points raised. The abstract parent shouldn't be required as a discriminator entry. Having the issue for over 10 years is one thing, doing so because we're pretending it's not a problem is another. I get the desire to reduce open issues but c'mon now.
Author
Owner

@mpdude commented on GitHub (Jan 16, 2023):

I think #10411 could fill in the abstract intermediate classes that are needed for internal reasons, but do not make sense from a user's POV

@mpdude commented on GitHub (Jan 16, 2023): I think #10411 could fill in the abstract intermediate classes that are needed for internal reasons, but do not make sense from a user's POV
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#2232