Doctrine second level result caching not working with deep fetch join query #5598

Open
opened 2026-01-22 15:12:24 +01:00 by admin · 6 comments
Owner

Originally created by @aimfeld on GitHub (Jul 4, 2017).

I have posted this question on stackoverflow here. It might be a doctrine bug (or a known limitation), so I hope it's okay if I ask here as well.

I have the following query in a doctrine repository:

/**
 * @return AbstractSurveyPage[]
 */
public function getResultCacheTestPages(): array
{
    $select = $this->createQueryBuilder('page')
        ->select(['page', 'pageGroup', 'groupLabelText'])
        ->leftJoin('page.group', 'pageGroup')
        ->leftJoin('pageGroup.labelText', 'groupLabelText');

    // Both useResultCache(true) and setCacheable(true) is needed
    return $select->getQuery()->useResultCache(true)
                  ->setCacheable(true)->getResult();
}

The query fetches page objects, their group objects and the group label eagerly.

However, I get the following error:

File /home/vagrant/web-projects/blueprint/vendor/doctrine/orm/lib/Doctrine/ORM/Cache/DefaultQueryCache.php:263
Message Undefined index: labelText

It seems that doctrine cannot find the labelText association of the group object. When I remove 'groupLabelText' from the select clause, the query and the second level result caching work fine (but the group label objects are not fetched eagerly anymore). When I disable caching with setCacheable(false), the query works fine.

It seems that doctrine is unable to cache results of fetch join queries with joins of more than one level.

Is there a way to get this working? Or should I not use deep fetch join queries, and use lazy fetching for the joined objects? Entity and association second level caching works fine for me, so maybe the performance hit is not that bad. Also, the code would become easier, since the fetch join queries are quite complicated (I use up to 25 joins and elements in the select clause in real code).

Originally created by @aimfeld on GitHub (Jul 4, 2017). I have posted this question on stackoverflow [here](https://stackoverflow.com/questions/44900551/doctrine-second-level-result-caching-not-working-with-deep-fetch-join-query). It might be a doctrine bug (or a known limitation), so I hope it's okay if I ask here as well. I have the following query in a doctrine repository: ``` /** * @return AbstractSurveyPage[] */ public function getResultCacheTestPages(): array { $select = $this->createQueryBuilder('page') ->select(['page', 'pageGroup', 'groupLabelText']) ->leftJoin('page.group', 'pageGroup') ->leftJoin('pageGroup.labelText', 'groupLabelText'); // Both useResultCache(true) and setCacheable(true) is needed return $select->getQuery()->useResultCache(true) ->setCacheable(true)->getResult(); } ``` The query fetches page objects, their group objects and the group label eagerly. However, I get the following error: > File /home/vagrant/web-projects/blueprint/vendor/doctrine/orm/lib/Doctrine/ORM/Cache/DefaultQueryCache.php:263 > Message Undefined index: labelText It seems that doctrine cannot find the labelText association of the group object. When I remove `'groupLabelText'` from the select clause, the query and the second level result caching work fine (but the group label objects are not fetched eagerly anymore). When I disable caching with `setCacheable(false)`, the query works fine. It seems that doctrine is unable to cache results of fetch join queries with joins of more than one level. Is there a way to get this working? Or should I not use deep fetch join queries, and use lazy fetching for the joined objects? Entity and association second level caching works fine for me, so maybe the performance hit is not that bad. Also, the code would become easier, since the fetch join queries are quite complicated (I use up to 25 joins and elements in the select clause in real code).
admin added the BugMissing Tests labels 2026-01-22 15:12:24 +01:00
Author
Owner

@lcobucci commented on GitHub (Aug 6, 2017):

@aimfeld sorry for taking that long to get back to you... is this happening on v2.5.x or v2.6.x-dev? We have some known bugs related to L2C on v2.5.x that got fixed on v2.6.x-dev only (due to multiple reasons).

If the issue still happens on v2.6.x-dev, could you please send us a failing test case that reproduces that behaviour? It would help us a lot to identify and fix the issue you're describing.

You can find examples on 388afb46d0/tests/Doctrine/Tests/ORM/Functional/Ticket

@lcobucci commented on GitHub (Aug 6, 2017): @aimfeld sorry for taking that long to get back to you... is this happening on `v2.5.x` or `v2.6.x-dev`? We have some known bugs related to L2C on `v2.5.x` that got fixed on `v2.6.x-dev` only (due to multiple reasons). If the issue still happens on `v2.6.x-dev`, could you please send us a failing test case that reproduces that behaviour? It would help us a lot to identify and fix the issue you're describing. You can find examples on https://github.com/doctrine/doctrine2/tree/388afb46d0cb3ed0c51332e8df0de9e942c2690b/tests/Doctrine/Tests/ORM/Functional/Ticket
Author
Owner

@aimfeld commented on GitHub (Aug 6, 2017):

@lcobucci The problem occurs with v2.5.6. I have now tried v2.6.x-dev and I don't get the exception anymore. The query result can now be cached and retrieved properly.

However, when I checked with the debugger I noticed that only the main objects (page) and the object of the first join (pageGroup) are actually retrieved from the cached query result. The objects of the second join (groupLabelText) are only retrieved as proxy objects, they don't seem to be included in the cached query result. So I don't think there's a difference if I include 'groupLabelText' in the select query.

I have already removed the fetch join queries from my code. I guess they don't result in much of a performance gain anyway, if I have all the associations cached.

@aimfeld commented on GitHub (Aug 6, 2017): @lcobucci The problem occurs with `v2.5.6`. I have now tried `v2.6.x-dev` and I don't get the exception anymore. The query result can now be cached and retrieved properly. However, when I checked with the debugger I noticed that only the main objects (`page`) and the object of the first join (`pageGroup`) are actually retrieved from the cached query result. The objects of the second join (`groupLabelText`) are only retrieved as proxy objects, they don't seem to be included in the cached query result. So I don't think there's a difference if I include `'groupLabelText'` in the select query. I have already removed the fetch join queries from my code. I guess they don't result in much of a performance gain anyway, if I have all the associations cached.
Author
Owner

@lcobucci commented on GitHub (Aug 6, 2017):

@aimfeld that's kind of weird, all the associations - that are mapped as cached - should have been correctly fetched from the cache layer. Would it be possible to send PR that reproduces that scenario?

@lcobucci commented on GitHub (Aug 6, 2017): @aimfeld that's kind of weird, all the associations - that are mapped as cached - should have been correctly fetched from the cache layer. Would it be possible to send PR that reproduces that scenario?
Author
Owner

@aimfeld commented on GitHub (Aug 7, 2017):

@lcobucci I don't have the time to set up the environment and write a test right now, but here are my stripped down models, hope these help (I'm working with MappedSuperclass):

/**
 * @ORM\MappedSuperclass
 */
abstract class AbstractSurveyPage extends AbstractEntity implements SkippableInterface
{
    /**
     * @var AbstractSurveyGroup|null
     *
     * @ORM\Cache("READ_ONLY")
     * @ORM\ManyToOne(targetEntity="Survey\Db\Entity\AbstractSurveyGroup")
     * @ORM\JoinColumn(name="groupID", referencedColumnName="groupID")
     */
    protected $group;
}


/**
 * @ORM\Table(name="survey_pages")
 * @ORM\Entity(repositoryClass="Survey\Db\Repository\SurveyPageRepo", readOnly=true)
 * @ORM\Cache(usage="READ_ONLY")
 */
class SurveyPage extends AbstractSurveyPage
{

}
/**
 * @ORM\MappedSuperclass
 */
abstract class AbstractSurveyGroup extends AbstractEntity
{
    /**
     * @var AbstractText|null
     *
     * @ORM\Cache("READ_ONLY")
     * @ORM\ManyToOne(targetEntity="Survey\Db\Entity\AbstractText")
     * @ORM\JoinColumn(name="labelTextID", referencedColumnName="textID")
     */
    protected $labelText;
}

/**
 * @ORM\Table(name="survey_groups")
 * @ORM\Entity(readOnly=true)
 * @ORM\Cache(usage="READ_ONLY")
 */
class SurveyGroup extends AbstractSurveyGroup
{

}
/**
 * @ORM\MappedSuperclass
 */
abstract class AbstractText extends AbstractEntity
{
    /**
     * @var string|null
     *
     * @ORM\Column(name="sourceText", type="text", length=65535, nullable=true)
     */
    protected $sourceText;
}

/**
 * @ORM\Table(name="texts")
 * @ORM\Entity(readOnly=true)
 * @ORM\Cache(usage="READ_ONLY")
 */
class Text extends AbstractText
{

}

And this is the query:

$select = $pageRepo->createQueryBuilder('page')
    ->select(['page', 'pageGroup', 'groupLabelText'])
    ->leftJoin('page.group', 'pageGroup')
    ->leftJoin('pageGroup.labelText', 'groupLabelText');

$pages = $select->getQuery()->useResultCache(true)->setCacheable(true)->getResult();```
@aimfeld commented on GitHub (Aug 7, 2017): @lcobucci I don't have the time to set up the environment and write a test right now, but here are my stripped down models, hope these help (I'm working with `MappedSuperclass`): ``` /** * @ORM\MappedSuperclass */ abstract class AbstractSurveyPage extends AbstractEntity implements SkippableInterface { /** * @var AbstractSurveyGroup|null * * @ORM\Cache("READ_ONLY") * @ORM\ManyToOne(targetEntity="Survey\Db\Entity\AbstractSurveyGroup") * @ORM\JoinColumn(name="groupID", referencedColumnName="groupID") */ protected $group; } /** * @ORM\Table(name="survey_pages") * @ORM\Entity(repositoryClass="Survey\Db\Repository\SurveyPageRepo", readOnly=true) * @ORM\Cache(usage="READ_ONLY") */ class SurveyPage extends AbstractSurveyPage { } ``` ``` /** * @ORM\MappedSuperclass */ abstract class AbstractSurveyGroup extends AbstractEntity { /** * @var AbstractText|null * * @ORM\Cache("READ_ONLY") * @ORM\ManyToOne(targetEntity="Survey\Db\Entity\AbstractText") * @ORM\JoinColumn(name="labelTextID", referencedColumnName="textID") */ protected $labelText; } /** * @ORM\Table(name="survey_groups") * @ORM\Entity(readOnly=true) * @ORM\Cache(usage="READ_ONLY") */ class SurveyGroup extends AbstractSurveyGroup { } ``` ``` /** * @ORM\MappedSuperclass */ abstract class AbstractText extends AbstractEntity { /** * @var string|null * * @ORM\Column(name="sourceText", type="text", length=65535, nullable=true) */ protected $sourceText; } /** * @ORM\Table(name="texts") * @ORM\Entity(readOnly=true) * @ORM\Cache(usage="READ_ONLY") */ class Text extends AbstractText { } ``` And this is the query: ``` $select = $pageRepo->createQueryBuilder('page') ->select(['page', 'pageGroup', 'groupLabelText']) ->leftJoin('page.group', 'pageGroup') ->leftJoin('pageGroup.labelText', 'groupLabelText'); $pages = $select->getQuery()->useResultCache(true)->setCacheable(true)->getResult();```
Author
Owner

@githoober commented on GitHub (Jun 27, 2018):

Since nobody responded and looked into the OP's issue, I see no point to present another use case, but essentially I have a similar issue, where a *ToOne association is incorrectly determined and passed to \Doctrine\ORM\UnitOfWork::getEntityIdentifier with an error spl_object_hash() expects parameter 1 to be object, array given. I am on v2.6.

doctrine2 6 l2-bug-with-fetch-joins

doctrine2 6 l2-bug-with-fetch-joins-call-stack

@githoober commented on GitHub (Jun 27, 2018): Since nobody responded and looked into the OP's issue, I see no point to present another use case, but essentially I have a similar issue, where a *ToOne association is incorrectly determined and passed to `\Doctrine\ORM\UnitOfWork::getEntityIdentifier` with an error `spl_object_hash() expects parameter 1 to be object, array given`. I am on v2.6. ![doctrine2 6 l2-bug-with-fetch-joins](https://user-images.githubusercontent.com/1529043/41997358-5b1c864e-7a1d-11e8-9323-08b03ed51626.png) ![doctrine2 6 l2-bug-with-fetch-joins-call-stack](https://user-images.githubusercontent.com/1529043/41997533-d9f933b8-7a1d-11e8-97e5-a1d74f968b67.png)
Author
Owner

@githoober commented on GitHub (Jun 28, 2018):

I filed a new issue for my case: https://github.com/doctrine/doctrine2/issues/7277

@githoober commented on GitHub (Jun 28, 2018): I filed a new issue for my case: https://github.com/doctrine/doctrine2/issues/7277
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5598