DDC-2575: Hydration bug #3232

Closed
opened 2026-01-22 14:16:02 +01:00 by admin · 14 comments
Owner

Originally created by @doctrinebot on GitHub (Jul 27, 2013).

Originally assigned to: @guilhermeblanco on GitHub.

Jira issue originally created by user nbottarini:

I have the following class mappings:

class A
{
    /****
     * @Id
     * @Column(type="integer")
     * @GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /****
     * @Column(type="string", length=100, nullable=FALSE)
     */    
    protected $sampleField;

    /****
     * @OneToOne(targetEntity="B", mappedBy="aRelation")
     ****/     
    protected $bRelation;
}
class B
{
    /****
     * @Id
     * @OneToOne(targetEntity="A", inversedBy="bRelation")
     * @JoinColumn(name="a_id", referencedColumnName="id", nullable=FALSE, onDelete="CASCADE")
     */
    protected $aRelation;

    /****
     * @ManyToOne(targetEntity="C")
     * @JoinColumn(name="c_id", referencedColumnName="id", nullable=FALSE, onDelete="CASCADE")
     */
    protected $cRelation;

}
class C
{
    /****
     * @Id
     * @Column(type="integer")
     * @GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /****
     * @Column(type="string", length=100, nullable=FALSE)
     */    
    protected $sampleField;
}

Then I make the following query:

$qb = $em->createQueryBuilder();
$qb = $qb->select('a, b, c')
      ->from('A','a')
      ->leftJoin('a.bRelation', 'b')
      ->leftJoin('b.cRelation', 'c');

$result = $qb->getQuery()->getResult();

The result contains a collection of instances of class A with the a.bRelation field populated and the a.bRelation.cRelation field populated in all rows except the last one.

The problem is an hydration problem. The Parser constructs the select statement with the fields of class A, then the fields of class C and last the fields of class B. The hydrator don't work correctly because when it's hydrating class C it doesn't find the class B (because it appears last in the select statement).
I think the problem is because class B only contains associations. If i put an extra field (an string for example) in class B it works as expected.

Originally created by @doctrinebot on GitHub (Jul 27, 2013). Originally assigned to: @guilhermeblanco on GitHub. Jira issue originally created by user nbottarini: I have the following class mappings: ``` none class A { /**** * @Id * @Column(type="integer") * @GeneratedValue(strategy="AUTO") */ protected $id; /**** * @Column(type="string", length=100, nullable=FALSE) */ protected $sampleField; /**** * @OneToOne(targetEntity="B", mappedBy="aRelation") ****/ protected $bRelation; } ``` ``` none class B { /**** * @Id * @OneToOne(targetEntity="A", inversedBy="bRelation") * @JoinColumn(name="a_id", referencedColumnName="id", nullable=FALSE, onDelete="CASCADE") */ protected $aRelation; /**** * @ManyToOne(targetEntity="C") * @JoinColumn(name="c_id", referencedColumnName="id", nullable=FALSE, onDelete="CASCADE") */ protected $cRelation; } ``` ``` none class C { /**** * @Id * @Column(type="integer") * @GeneratedValue(strategy="AUTO") */ protected $id; /**** * @Column(type="string", length=100, nullable=FALSE) */ protected $sampleField; } ``` Then I make the following query: ``` none $qb = $em->createQueryBuilder(); $qb = $qb->select('a, b, c') ->from('A','a') ->leftJoin('a.bRelation', 'b') ->leftJoin('b.cRelation', 'c'); $result = $qb->getQuery()->getResult(); ``` The result contains a collection of instances of class A with the a.bRelation field populated and the a.bRelation.cRelation field populated in all rows except the last one. The problem is an hydration problem. The Parser constructs the select statement with the fields of class A, then the fields of class C and last the fields of class B. The hydrator don't work correctly because when it's hydrating class C it doesn't find the class B (because it appears last in the select statement). I think the problem is because class B only contains associations. If i put an extra field (an string for example) in class B it works as expected.
admin added the Bug label 2026-01-22 14:16:02 +01:00
admin closed this issue 2026-01-22 14:16:09 +01:00
Author
Owner

@doctrinebot commented on GitHub (Oct 28, 2013):

Comment created by popy:

I have the same kind of bug : i have a OneToMany relations which stays to null if I request both entities in one query, and miss an entity if I preload the related entities in a second query. It seems to occur on the last entity of the list.

If I remember well the hydrator code, there's (in the hydratation loop) something like "If we find a new root entity (or maybe on each row, i'm not sure), link the entities we didn't link". Maybe this thing is not done AFTER the loop for remaining entities.

I'll try to dig again into the hydrator code tomorrow to check this hypothesis.

@doctrinebot commented on GitHub (Oct 28, 2013): Comment created by popy: I have the same kind of bug : i have a OneToMany relations which stays to null if I request both entities in one query, and miss an entity if I preload the related entities in a second query. It seems to occur on the last entity of the list. If I remember well the hydrator code, there's (in the hydratation loop) something like "If we find a new root entity (or maybe on each row, i'm not sure), link the entities we didn't link". Maybe this thing is not done AFTER the loop for remaining entities. I'll try to dig again into the hydrator code tomorrow to check this hypothesis.
Author
Owner

@doctrinebot commented on GitHub (Oct 29, 2013):

Comment created by popy:

This bug seems more severe :

I made a test on a query with 3 entities (root, root->a, root->b, a and b relations are OneToMany, so no connections on this side), and there's the process I witness :

  • First result row
    • The hydrator finds the linked entities before the root... and just does nothing (line 359)
    • The hydrator finds the root entity, and hydrate it
  • Other result rows
    • The hydrator finds the linked entities before the root... and associate them with the previously found root entity, which is the root entity fetched on the first row
    • The hydrator finds the root entity, so trash the previous, and hydrate (without related entities, as they were linked to previous root entity)

To finish, the last row has no related entities, as its related entities were given to the previous row.

@doctrinebot commented on GitHub (Oct 29, 2013): Comment created by popy: This bug seems more severe : I made a test on a query with 3 entities (root, root->a, root->b, a and b relations are OneToMany, so no connections on this side), and there's the process I witness : - First result row - The hydrator finds the linked entities before the root... and just does nothing (line 359) - The hydrator finds the root entity, and hydrate it - Other result rows - The hydrator finds the linked entities before the root... and associate them with the previously found root entity, which is the root entity fetched on the first row - The hydrator finds the root entity, so trash the previous, and hydrate (without related entities, as they were linked to previous root entity) To finish, the last row has no related entities, as its related entities were given to the previous row.
Author
Owner

@doctrinebot commented on GitHub (Oct 29, 2013):

Comment created by popy:

Bug confirmed in a small Symfony app and Doctrine 2.3. I managed to reproduce the bug with 3 entities :

  • A (id autoincrement)
  • B (id autoincrement)
  • Root (composite id a,b which are ManyToOne relations to A and B entities)

Can provide the app to ease things.

@doctrinebot commented on GitHub (Oct 29, 2013): Comment created by popy: Bug confirmed in a small Symfony app and Doctrine 2.3. I managed to reproduce the bug with 3 entities : - A (id autoincrement) - B (id autoincrement) - Root (composite id a,b which are ManyToOne relations to A and B entities) Can provide the app to ease things.
Author
Owner

@doctrinebot commented on GitHub (Oct 29, 2013):

Comment created by popy:

Possible workaround : declaring integer fields as ID (with the same field name as relation fields) makes the thing working again (at the price of thoose two useless properties and a prePersist method to fill them with related entity ids)

@doctrinebot commented on GitHub (Oct 29, 2013): Comment created by popy: Possible workaround : declaring integer fields as ID (with the same field name as relation fields) makes the thing working again (at the price of thoose two useless properties and a prePersist method to fill them with related entity ids)
Author
Owner

@doctrinebot commented on GitHub (Dec 14, 2013):

Comment created by @beberlei:

First step here: Try to reproduce this issue with the given entities above in a Testcase

@doctrinebot commented on GitHub (Dec 14, 2013): Comment created by @beberlei: First step here: Try to reproduce this issue with the given entities above in a Testcase
Author
Owner

@doctrinebot commented on GitHub (Dec 14, 2013):

Comment created by karolhor:

I created test for this issue, but I can't reproduce id. My pull request is here https://github.com/doctrine/doctrine2/pull/878

@doctrinebot commented on GitHub (Dec 14, 2013): Comment created by karolhor: I created test for this issue, but I can't reproduce id. My pull request is here https://github.com/doctrine/doctrine2/pull/878
Author
Owner

@doctrinebot commented on GitHub (Dec 15, 2013):

Comment created by popy:

You should maybe call $this->_em->clear() at the end of your setUp method.

I still have a Symfony Bundle reproducing the bug, how can i hand it to you ?

@doctrinebot commented on GitHub (Dec 15, 2013): Comment created by popy: You should maybe call $this->_em->clear() at the end of your setUp method. I still have a Symfony Bundle reproducing the bug, how can i hand it to you ?
Author
Owner

@doctrinebot commented on GitHub (Dec 15, 2013):

Comment created by @beberlei:

[~popy] you can create a branch of that symfony standard edition, and push it to a fork of symfony-standard on your Github account. Then you can comment a link to your branch on Github.

@doctrinebot commented on GitHub (Dec 15, 2013): Comment created by @beberlei: [~popy] you can create a branch of that symfony standard edition, and push it to a fork of symfony-standard on your Github account. Then you can comment a link to your branch on Github.
Author
Owner

@doctrinebot commented on GitHub (Dec 17, 2013):

Comment created by karolhor:

After Popy's suggestion I added $this->_em->clear() and now I have failing test. My fault with this quick "everything is ok".

I tried to search what's happening in ObjectHydrator but something strange is going on in hydrateRowData method.

@doctrinebot commented on GitHub (Dec 17, 2013): Comment created by karolhor: After Popy's suggestion I added $this->_em->clear() and now I have failing test. My fault with this quick "everything is ok". I tried to search what's happening in ObjectHydrator but something strange is going on in hydrateRowData method.
Author
Owner

@doctrinebot commented on GitHub (Dec 17, 2013):

Comment created by popy:

Be carefull, headache come fast while reading this method :p

As far as I know, the problem could be solved if the hydrator started by hydrating the root entity first. Maybe.

@doctrinebot commented on GitHub (Dec 17, 2013): Comment created by popy: Be carefull, headache come fast while reading this method :p As far as I know, the problem could be solved if the hydrator started by hydrating the root entity first. Maybe.
Author
Owner

@doctrinebot commented on GitHub (Dec 19, 2013):

Comment created by karolhor:

In select statement fields are in this order Root, B, A. Relations in my test are Root 1:1 A *:1 B.
Hydrator first gets Root data and next B. But here (line 407 in ObjectHydrator) it doesn't find A parent in resultPointers.

For now I don't have any idea how to add new logic for this.

@doctrinebot commented on GitHub (Dec 19, 2013): Comment created by karolhor: In select statement fields are in this order Root, B, A. Relations in my test are Root 1:1 A *:1 B. Hydrator first gets Root data and next B. But here (line 407 in ObjectHydrator) it doesn't find A parent in resultPointers. For now I don't have any idea how to add new logic for this.
Author
Owner

@doctrinebot commented on GitHub (Apr 18, 2014):

Comment created by @doctrinebot:

A related Github Pull-Request [GH-878] was closed:
https://github.com/doctrine/doctrine2/pull/878

@doctrinebot commented on GitHub (Apr 18, 2014): Comment created by @doctrinebot: A related Github Pull-Request [GH-878] was closed: https://github.com/doctrine/doctrine2/pull/878
Author
Owner

@doctrinebot commented on GitHub (Apr 18, 2014):

Comment created by @guilhermeblanco:

As of 38b6838386 this issue is now fixed.

@doctrinebot commented on GitHub (Apr 18, 2014): Comment created by @guilhermeblanco: As of https://github.com/doctrine/doctrine2/commit/38b683838648b549aad0e38ce88c70b6393755b3 this issue is now fixed.
Author
Owner

@doctrinebot commented on GitHub (Apr 18, 2014):

Issue was closed with resolution "Fixed"

@doctrinebot commented on GitHub (Apr 18, 2014): Issue was closed with resolution "Fixed"
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#3232