Doctrine 2: Cache in One-to-Many associations Doctrine 2: Cache in One-to-Many associations symfony symfony

Doctrine 2: Cache in One-to-Many associations


Association fetch-modes

The query you present only fetches "parent" Category entities, which get hydrated with an uninitialized collection for the children. When accessing that collection (by iterating over those children for example), Doctrine will load the collection, thus perform another query. It will do that for all parent categories hydrated by the first query.

Setting fetch-mode to EAGER only changes the moment these queries are done. Doctrine will do them right after hydrating the parent categories, it won't wait until you access the collection (like with fetch-mode LAZY). But it will still do those queries.

Fetch-join query

The simplest way to tell Doctrine to query and hydrate the categories with their children is to do a "fetch join" query:

$queryBuilder = $this->em->createQueryBuilder();$queryBuilder    ->select('p', 'c') // [p]arent, [c]hild    ->from('App\Entity\Category', 'p')    ->leftJoin('p.children', 'c')    ->where('p.parent IS NULL')    ->orderBy('p.priority', 'ASC');$query = $queryBuilder->getQuery();$query    ->useResultCache(true, 900, 'categories')$result = $query->getResult();

Note the select() and leftJoin() calls here.

You also don't need to alter the fetch-mode of the association (by calling setFetchMode()), because the query itself will tell Doctrine to do what you want.

The result of this is that Doctrine will perform 1 query if it isn't cached yet (or the cache is stale), or 0 queries if it is cached (and still fresh).

Assumptions

The property $parentId (in Category) is renamed to $parent. This property will contain the parent Category entity, or null, but never an id.

The property $childrenId is renamed to $children. This property will contain a collection of Category entities (which might be empty), but never a collection (or array) of ids, and certainly never a single id.

The query I suggest above takes these renames into account.

I'm completely ignoring the fact that right after your "edit" a new Attribute entity has sprung into existence. It isn't relevant to your question or this answer IMHO.

More levels

It looks/sounds like your Categories only use 2 levels (parents and children). When you introduce more levels (grandchildren, etc), reading this model can become very inefficient very quickly.

When going for 3 or more levels, you might want to look into the Nested Set model. It's heavier on the writes, but highly optimized for reads.

The DoctrineExtensions library has support for this, and there's also a Symfony Bundle.


According to the docs: http://doctrine-orm.readthedocs.io/en/latest/reference/dql-doctrine-query-language.html#temporarily-change-fetch-mode-in-dql I think that setFetchMode - EAGER will not work for you as this will produce extra queries that will not be cached.

Why don't you load your categories with attributes?

$result = $this->em->createQueryBuilder()    ->select('c, a.name, a.value')    ->from('App\Entity\Category', 'c')    ->innerJoin('App\Entity\Attribute', 'a', 'WITH', 'a.id = c.attribute')    ->where('c.parentId IS NULL')    ->orderBy('c.priority', 'ASC')    ->getQuery()    ->useResultCache(true, 900, 'categories')    ->getResult();

And it should work as expected.