Doctrine 2 - ORM
  1. Doctrine 2 - ORM
  2. DDC-1776

Abstract class marked as @Entity in a single table inheritance is wrongly hydrated

    Details

    • Type: Bug Bug
    • Status: Resolved
    • Priority: Minor Minor
    • Resolution: Duplicate
    • Affects Version/s: 2.2.2
    • Fix Version/s: None
    • Component/s: ORM
    • Labels:
      None
    • Environment:
      Irrelevant

      Description

      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.

        Activity

        Benjamin Morel created issue -
        Benjamin Morel made changes -
        Field Original Value New Value
        Description I have the following entity hierarchy:

        {code}
        /**
         * @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 {
        // ...
        }
        {code}

        Although ProspectEvent is an abstract class, I need to mark it as @Entity, so that I can query for any type of ProspectEvent:
        {code}
        SELECT e FROM ProspectEvent e WHERE e.prospect = ?
        {code}

        So far, so good.
        The problem happens when I query the root class:
        {code}
        SELECT e FROM Event e
        {code}

        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 column name 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:
        {code}
        @DiscriminatorMap({
          "prospectEvent" = "Application\Domain\Model\Event\ProspectEvent"
          "prospectEdited" = "Application\Domain\Model\Event\ProspectEditedEvent"
        })
        {code}

        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.
        I have the following entity hierarchy:

        {code}
        /**
         * @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 {
        // ...
        }
        {code}

        Although ProspectEvent is an abstract class, I need to mark it as @Entity, so that I can query for any type of ProspectEvent:
        {code}
        SELECT e FROM ProspectEvent e WHERE e.prospect = ?
        {code}

        So far, so good.
        The problem happens when I query the root class:
        {code}
        SELECT e FROM Event e
        {code}

        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:
        {code}
        @DiscriminatorMap({
          "prospectEvent" = "Application\Domain\Model\Event\ProspectEvent"
          "prospectEdited" = "Application\Domain\Model\Event\ProspectEditedEvent"
        })
        {code}

        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.
        Benjamin Eberlei made changes -
        Workflow jira [ 13630 ] jira-feedback [ 14049 ]
        Benjamin Eberlei made changes -
        Workflow jira-feedback [ 14049 ] jira-feedback2 [ 15913 ]
        Benjamin Eberlei made changes -
        Workflow jira-feedback2 [ 15913 ] jira-feedback3 [ 18168 ]
        Hide
        Benjamin Morel added a comment -

        Any feedback?

        Show
        Benjamin Morel added a comment - Any feedback?
        Hide
        Marco Pivetta added a comment -

        Benjamin Morel 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.

        Show
        Marco Pivetta added a comment - Benjamin Morel 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.
        Hide
        Marco Pivetta added a comment - - edited

        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.

        Show
        Marco Pivetta added a comment - - edited 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.
        Marco Pivetta made changes -
        Assignee Benjamin Eberlei [ beberlei ] Marco Pivetta [ ocramius ]
        Security All [ 10000 ]
        Hide
        Benjamin Morel added a comment - - edited

        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!

        Show
        Benjamin Morel added a comment - - edited 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!
        Hide
        Benjamin Eberlei added a comment -

        We fixed errors of this kind a while ago

        Show
        Benjamin Eberlei added a comment - We fixed errors of this kind a while ago
        Benjamin Eberlei made changes -
        Status Open [ 1 ] Resolved [ 5 ]
        Resolution Fixed [ 1 ]
        Benjamin Eberlei made changes -
        Resolution Fixed [ 1 ]
        Status Resolved [ 5 ] Reopened [ 4 ]
        Benjamin Eberlei made changes -
        Status Reopened [ 4 ] Resolved [ 5 ]
        Resolution Duplicate [ 3 ]

        This list may be incomplete, as errors occurred whilst retrieving source from linked applications:

        • Request to http://www.doctrine-project.org/fisheye/ failed: Error in remote call to 'FishEye 0 (http://www.doctrine-project.org/fisheye/)' (http://www.doctrine-project.org/fisheye) [AbstractRestCommand{path='/rest-service-fe/search-v1/crossRepositoryQuery', params={query=DDC-1776, expand=changesets[0:20].revisions[0:29],reviews}, methodType=GET}] : Received status code 503 (Service Temporarily Unavailable)

          People

          • Assignee:
            Marco Pivetta
            Reporter:
            Benjamin Morel
          • Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: