orm
orm copied to clipboard
[POSSIBLE BUG] setFetchMode not overriding fetch annotation
Bug Report
| Q | A |
|---|---|
| BC Break | no |
| Version | 2.6.4 |
Summary
I have the following entities:
IntervalGroupw/ oneToMany relation toIntervalType(fetch:EXTRA_LAZY)IntervalTypew/ oneToMany relation toInterval(fetch:EXTRA_LAZY)Intervalw/ oneToMany relation toTask(fetch:EAGER)
On the IntervalGroup repository I'm doing a DQL Query Builder query to load all IntervalGroups, and left join IntervalGroup.intervalTypes and intervalTypes.interval.
By default, the Task relation on Interval will be eager-loaded during hydration, however I don't want Tasks in this case. To counter that I'm using setFetchMode() on the Query object like so:
$query->setFetchMode(Interval::class, 'tasks', ClassMetadataInfo::FETCH_LAZY)
Which, as I understand, should prevent the ORM from fetching and hydrating those relations in that query.
Current behavior
Currently, the query hint for the fetch mode is not overriding the fetch mode on the annotation, so Doctrine loads the Interval.tasks relation.
createEntity in the UnitOfWork class is using the fetch value from the annotation, instead of the fetchMode query hint:
// Lines 2859 - 2862 of UnitOfWork.php
if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER) {
$this->loadCollection($pColl);
$pColl->takeSnapshot();
}
(as seen here)
When I dump($assoc) I can see for the targetEntity = "Task" that $assoc['fetch'] equals 3 (ClassMetadata::FETCH_EAGER) when I specified in setFetchMode() that it should be 2 (ClassMetadata::FETCH_LAZY).
How to reproduce
class IntervalGroup
{
/**
* @ORM\OneToMany(targetEntity="IntervalType", mappedBy="intervalGroup", fetch="EXTRA_LAZY")
*/
private $intervalTypes;
public function __construct()
{
$this->intervalTypes = new ArrayCollection();
}
}
class IntervalType
{
/**
* @ORM\ManyToOne(targetEntity="IntervalGroup", inversedBy="intervalTypes", fetch="EAGER")
* @ORM\JoinColumn(name="interval_group_id", referencedColumnName="id", nullable=true)
*/
private $intervalGroup;
/**
* @ORM\OneToMany(targetEntity="Interval", mappedBy="intervalType", fetch="EXTRA_LAZY")
*/
private $intervals;
public function __construct()
{
$this->intervals = new ArrayCollection();
}
}
class Interval
{
/**
* @ORM\ManyToOne(targetEntity="IntervalType", inversedBy="intervals")
* @ORM\JoinColumn(name="interval_type_id", referencedColumnName="id", nullable=false)
*/
private $intervalType;
/**
* @ORM\ManyToMany(targetEntity="Task", fetch="EAGER")
* @ORM\JoinTable(name="interval_mapping",
* joinColumns={@JoinColumn(name="interval_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="task_id", referencedColumnName="task_id", unique=true)}
* )
*/
private $tasks;
public function __construct()
{
$this->tasks = new ArrayCollection();
}
}
class IntervalGroupRepository extends EntityRepository
{
public function findAllAscendingByName()
{
$qb = $this->createQueryBuilder('ig');
$qb
->select(['ig', 'it', 'i'])
->leftJoin('ig.intervalTypes', 'it')
->leftJoin('it.intervals', 'i');
$query = $qb->getQuery();
$query
->setFetchMode(Interval::class, 'tasks', ClassMetadataInfo::FETCH_LAZY);
return $query->getResult();
}
}
Expected behavior
The fetch mode override (as set in setFetchMode()) should be respected during hydration. UnitOfWork::createEntity's $assoc['fetch'] should check the $hints array to see if it is overridden, or use the default annotation value if not.
I believe overriding a fetch annotation should be possible to do, though perhaps I'm doing it the wrong way (in which case I'm happy to be pointed in the right direction!).
The documentation mentions the behaviour of oneToMany relation when the fetch mode is set to to eager, but not how it behaves when set to lazy.
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/dql-doctrine-query-language.html#temporarily-change-fetch-mode-in-dql
Thank you for reporting this.
Thanks for the link, @SenseException, I can't believe I missed that part of the documentation :sweat:
I've been able to (partially) get around this by using JMSSerializer groups to avoid unnecessary resolution and hydration of relationships and properties.
Hi Has there been any progress on this issue? I would like to use this feature to set a lazy association to extra lazy hydration.
up
The bug still exists even in ORM 3. The hints array with fetchMode set by method $query->setFetchMode() is not read at all. I just debugged it. The code in https://github.com/doctrine/orm/blob/3.1.0/src/Query/SqlWalker.php#L909 is using fetchMode value from ClassMetadata definition, ignoring hints value.
FetchMode from ClassMetadata must be overriden from hints array here: https://github.com/doctrine/orm/blob/3.1.0/src/Query/Parser.php#L1613