Loading meta columns in custom hydrator #5499

Open
opened 2026-01-22 15:09:15 +01:00 by admin · 11 comments
Owner

Originally created by @iluuu1994 on GitHub (Apr 12, 2017).

I'm writing a custom hydrator for Gedmo Tree.

The hydrator accesses the primary key of the parent property of a tree node:

$node->getParent()->getId()

Unfortunately, the primary key is actually null if the query doesn't contain the HINT_INCLUDE_META_COLUMNS hint. I tracked it down to these lines in the Doctrine repository. Apparently, only the object hydrator, but not its subclasses, get the meta columns.

So I was able to make it work by replacing those lines with the following:

$isObjectHydrator = $this->query->getHydrationMode() == Query::HYDRATE_OBJECT;

if (!$isObjectHydrator) {
    $customHydrationClass = $this->em->getConfiguration()->getCustomHydrationMode($this->query->getHydrationMode());
    if ($customHydrationClass !== null) {
        $hydratorClass = new \ReflectionClass($customHydrationClass);
        $isObjectHydrator = $hydratorClass->isSubclassOf('Doctrine\ORM\Internal\Hydration\ObjectHydrator');
    }
}

$addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
    $isObjectHydrator
    ||
    $isObjectHydrator &&
    $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS);

This will include the meta columns for the object hydrator or any of its subclasses.

Obviously the ReflectionClass adds some overhead. I'm not sure how relevant performance is to this particular method. Is this reasonable? Is there a better way to do this? Should I create a pull request?

Thanks for taking the time to read and respond!

Originally created by @iluuu1994 on GitHub (Apr 12, 2017). I'm writing a [custom hydrator](https://github.com/iluuu1994/DoctrineExtensions/blob/v2.4.x/lib/Gedmo/Tree/Hydrator/ORM/TreeObjectHydrator.php) for [Gedmo Tree](https://github.com/iluuu1994/DoctrineExtensions/tree/v2.4.x/lib/Gedmo/Tree). The hydrator accesses the primary key of the `parent` property of a tree node: ```php $node->getParent()->getId() ``` Unfortunately, the primary key is actually `null` if the query doesn't contain the `HINT_INCLUDE_META_COLUMNS` hint. I tracked it down to [these lines](https://github.com/doctrine/doctrine2/blob/09cbb9ff48f00003f8708a67d937e6b2d86cdce3/lib/Doctrine/ORM/Query/SqlWalker.php#L708-L712) in the Doctrine repository. Apparently, only the object hydrator, but not its subclasses, get the meta columns. So I was able to make it work by replacing those lines with the following: ```php $isObjectHydrator = $this->query->getHydrationMode() == Query::HYDRATE_OBJECT; if (!$isObjectHydrator) { $customHydrationClass = $this->em->getConfiguration()->getCustomHydrationMode($this->query->getHydrationMode()); if ($customHydrationClass !== null) { $hydratorClass = new \ReflectionClass($customHydrationClass); $isObjectHydrator = $hydratorClass->isSubclassOf('Doctrine\ORM\Internal\Hydration\ObjectHydrator'); } } $addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) && $isObjectHydrator || $isObjectHydrator && $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS); ``` This will include the meta columns for the object hydrator or any of its subclasses. Obviously the `ReflectionClass` adds some overhead. I'm not sure how relevant performance is to this particular method. Is this reasonable? Is there a better way to do this? Should I create a pull request? Thanks for taking the time to read and respond!
Author
Owner

@iluuu1994 commented on GitHub (Jun 1, 2017):

@Ocramius @guilhermeblanco Sorry to bother again.

Is the suggestion above appropriate? If it isn't I'll close the issue.

@iluuu1994 commented on GitHub (Jun 1, 2017): @Ocramius @guilhermeblanco Sorry to bother again. Is the suggestion above appropriate? If it isn't I'll close the issue.
Author
Owner

@Ocramius commented on GitHub (Jun 6, 2017):

@iluuu1994 you did a good analysis of what is missing in the implementation, but I still have no idea what this is for. Consider making a practical example in a test case?

@Ocramius commented on GitHub (Jun 6, 2017): @iluuu1994 you did a good analysis of what is missing in the implementation, but I still have no idea what this is for. Consider making a practical example in a test case?
Author
Owner

@iluuu1994 commented on GitHub (Jun 6, 2017):

@Ocramius Basically, the hydrator needs a way to access the ID of a ManyToOne relationship (parent in a Gedmo tree). Since most of the time you wouldn't want to map the foreign key directly the hydrator gets the ID from the parent proxy object.

Unfortunately, the proxy object is only created if you use the object hydrator or set the HINT_INCLUDE_META_COLUMNS hint. But it is not created when you inherit from the object hydrator. Thus, the solution above checks if $hydratorClass->isSubclassOf('Doctrine\ORM\Internal\Hydration\ObjectHydrator').

Consider making a practical example in a test case?

Yes, I'll do that.

@iluuu1994 commented on GitHub (Jun 6, 2017): @Ocramius Basically, the hydrator needs a way to access the ID of a `ManyToOne` relationship (`parent` in a Gedmo tree). Since most of the time you wouldn't want to map the foreign key directly the hydrator gets the ID from the `parent` proxy object. Unfortunately, the proxy object is only created if you use the object hydrator or set the `HINT_INCLUDE_META_COLUMNS` hint. But it is not created when you inherit from the object hydrator. Thus, the solution above checks if `$hydratorClass->isSubclassOf('Doctrine\ORM\Internal\Hydration\ObjectHydrator')`. > Consider making a practical example in a test case? Yes, I'll do that.
Author
Owner

@Ocramius commented on GitHub (Jun 6, 2017):

@Ocramius Basically, the hydrator needs a way to access the ID of a ManyToOne relationship (parent in a Gedmo tree). Since most of the time you wouldn't want to map the foreign key directly the hydrator gets the ID from the parent proxy object.

Specifically this bit:

Since most of the time you wouldn't want to map the foreign key directly

This is a bit unclear.

I'll have to wait for a test to see what's going on, sorry.

@Ocramius commented on GitHub (Jun 6, 2017): > @Ocramius Basically, the hydrator needs a way to access the ID of a `ManyToOne` relationship (`parent` in a Gedmo tree). Since most of the time you wouldn't want to map the foreign key directly the hydrator gets the ID from the `parent` proxy object. Specifically this bit: > Since most of the time you wouldn't want to map the foreign key directly This is a bit unclear. I'll have to wait for a test to see what's going on, sorry.
Author
Owner

@iluuu1994 commented on GitHub (Jun 6, 2017):

@Ocramius Consider a simple tree entity:

/**
 * @Table(...)
 * @Entity(...)
 */
class Node {
    /**
     * @ManyToOne(targetEntity="Node", inversedBy="children")
     */
    protected $parent;

    /**
     * @OneToMany(targetEntity="Node", mappedBy="parent")
     */
    protected $children;
}

The goal of the hydrator is to create a tree from the flat entity array returned from the query. To do this, the hydrator creates a big parent-child hashmap:

[
    rootId => [child1, child2, child3],
    child1Id => [child1_1, child1_2],
    ...
]

This is why I need to access the parent ID of the current node.

This is a bit unclear.

That just means that I don't want to add a new parent_id property to the Node but instead access the proxy object which is only possible if the $addMetaColumns columns is set to true.

@iluuu1994 commented on GitHub (Jun 6, 2017): @Ocramius Consider a simple tree entity: ```php /** * @Table(...) * @Entity(...) */ class Node { /** * @ManyToOne(targetEntity="Node", inversedBy="children") */ protected $parent; /** * @OneToMany(targetEntity="Node", mappedBy="parent") */ protected $children; } ``` The goal of the hydrator is to create a tree from the flat entity array returned from the query. To do this, the hydrator creates a big parent-child hashmap: ``` [ rootId => [child1, child2, child3], child1Id => [child1_1, child1_2], ... ] ``` This is why I need to access the parent ID of the current node. > This is a bit unclear. That just means that I don't want to add a new `parent_id` property to the Node but instead access the proxy object which is only possible if the `$addMetaColumns` columns is set to `true`.
Author
Owner

@guilhermeblanco commented on GitHub (Jun 6, 2017):

@iluuu1994 I understand the problem. Your solution is valid (I'd just make the comparison as === in the first line).
However, to properly address this issue (and make it future proof), could you try to make a PR and also include a test case supporting this fix?

@guilhermeblanco commented on GitHub (Jun 6, 2017): @iluuu1994 I understand the problem. Your solution is valid (I'd just make the comparison as `===` in the first line). However, to properly address this issue (and make it future proof), could you try to make a PR and also include a test case supporting this fix?
Author
Owner

@iluuu1994 commented on GitHub (Jun 6, 2017):

@guilhermeblanco Great! I used the == operator to match the current implementation and thus not change the behaviour. I'll create a patch including tests.

Thank you @Ocramius and @guilhermeblanco for reviewing!

@iluuu1994 commented on GitHub (Jun 6, 2017): @guilhermeblanco Great! I used the `==` operator to match the [current implementation](https://github.com/doctrine/doctrine2/blob/09cbb9ff48f00003f8708a67d937e6b2d86cdce3/lib/Doctrine/ORM/Query/SqlWalker.php#L709) and thus not change the behaviour. I'll create a patch including tests. Thank you @Ocramius and @guilhermeblanco for reviewing!
Author
Owner

@vctls commented on GitHub (Jan 31, 2019):

Hi! Any news on this subject?
I had the same issue with a custom hydrator, and the suggested fix did the trick.
I'm not sure if requiring a subclass of the ObjectHydrator is the best way to go, especially since the ObjectHydrator looks a bit scary and not very extendable, but hey, it works.

@vctls commented on GitHub (Jan 31, 2019): Hi! Any news on this subject? I had the same issue with a custom hydrator, and the suggested fix did the trick. I'm not sure if requiring a subclass of the ObjectHydrator is the best way to go, especially since the ObjectHydrator looks a bit scary and not very extendable, but hey, it works.
Author
Owner

@iluuu1994 commented on GitHub (Jan 31, 2019):

I never got around to it. Feel free to take over. The fix is really simple, all you need is the code from the comment above. Creating a test should also be rather simple.

@iluuu1994 commented on GitHub (Jan 31, 2019): I never got around to it. Feel free to take over. The fix is really simple, all you need is the code from the comment above. Creating a test should also be rather simple.
Author
Owner

@anboo commented on GitHub (Mar 12, 2021):

You can provide hint:

$query->setHint(Query::HINT_INCLUDE_META_COLUMNS, true)

And all you relations will be filled automatically by RSM parser.

@anboo commented on GitHub (Mar 12, 2021): You can provide hint: ```php $query->setHint(Query::HINT_INCLUDE_META_COLUMNS, true) ``` And all you relations will be filled automatically by RSM parser.
Author
Owner

@Tofandel commented on GitHub (May 16, 2022):

I was bashing my head wondering why it wasn't working with symfony's query builder when using $qb->getQuery()->setHint(Query::HINT_INCLUDE_META_COLUMNS, true)

It turns out that every call to ->getQuery() creates a new query and so the hints and hydration mode are lost if you write

 $qb->getQuery()->setHint(Query::HINT_INCLUDE_META_COLUMNS, true);
 $qb->getQuery()->execute();

So you need to keep the query and never use getQuery multiple times

$query = $qb->getQuery()->setHint(Query::HINT_INCLUDE_META_COLUMNS, true);
//
$query->execute();

It's a bit of a design issue on symfony's side, but it might save headaches to some other people

@Tofandel commented on GitHub (May 16, 2022): I was bashing my head wondering why it wasn't working with symfony's query builder when using `$qb->getQuery()->setHint(Query::HINT_INCLUDE_META_COLUMNS, true)` It turns out that every call to ->getQuery() creates a new query and so the hints and hydration mode are lost if you write ```php $qb->getQuery()->setHint(Query::HINT_INCLUDE_META_COLUMNS, true); $qb->getQuery()->execute(); ``` So you need to keep the query and never use getQuery multiple times ```php $query = $qb->getQuery()->setHint(Query::HINT_INCLUDE_META_COLUMNS, true); // $query->execute(); ``` It's a bit of a design issue on symfony's side, but it might save headaches to some other people
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5499