Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[POSSIBLE BUG] setFetchMode not overriding fetch annotation #7860

Open
yesdevnull opened this issue Oct 9, 2019 · 5 comments
Open

[POSSIBLE BUG] setFetchMode not overriding fetch annotation #7860

yesdevnull opened this issue Oct 9, 2019 · 5 comments

Comments

@yesdevnull
Copy link
Contributor

Bug Report

Q A
BC Break no
Version 2.6.4

Summary

I have the following entities:

  • IntervalGroup w/ oneToMany relation to IntervalType (fetch: EXTRA_LAZY)
  • IntervalType w/ oneToMany relation to Interval (fetch: EXTRA_LAZY)
  • Interval w/ oneToMany relation to Task (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!).

@SenseException
Copy link
Member

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.

@yesdevnull
Copy link
Contributor Author

Thanks for the link, @SenseException, I can't believe I missed that part of the documentation 😓

I've been able to (partially) get around this by using JMSSerializer groups to avoid unnecessary resolution and hydration of relationships and properties.

@christophermichaelthomasmillar

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.

@xepozz

This comment was marked as spam.

@Housik
Copy link

Housik commented Mar 19, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants