Search code examples
phpmongodbsymfonydoctrine-odm

loading of related documents in doctrine ODM leads to too many queries


I'm stuck trying to reduce the number of database queries on a web api. My database has 3 collections : playground, widget, token

One playground has many widgets, one widget has one token. Each relationship uses referencesOne/referenceMany.

So here are my simplified models

/**
 * @MongoDB\Document()
 */
class Widget
{
    /**
     * @MongoDB\ReferenceOne(targetDocument="Token", inversedBy="widgets")
     */
    protected $token;

    /**
     * @MongoDB\ReferenceOne(targetDocument="Playground", inversedBy="widgets")
     */
    protected $playground;
}

/**
 * @MongoDB\Document()
 */
class Playground
{
    /**
     * @MongoDB\ReferenceMany(targetDocument="Widget", mappedBy="playground")
     */
    protected $widgets;
}

/**
 * @MongoDB\Document()
 */
class Token
{
    /**
     * @MongoDB\ReferenceMany(targetDocument="Widget", mappedBy="token")
     */
    protected $widgets;
}

I need to use the full playground with all its widgets and tokens but by default, Doctrine does too many queries : one to get the playground (ok), one to get all widgets of the mapping (ok) and for each widget, one query to get the token (not ok). Is there a way to query all tokens at once instead of getting them one by one ?

I've looked at prime but it does not seem to solve my problem...

Is there a way other than using the query builder and manually hydrate all objects to reduce the query count ?

Edit : As I added in my comment, what I'm looking for is get the playground and all its dependencies as a big object, json encode it and return it into the response.

What I do for now is query the playground and encode it but Doctrine populates the dependencies in a non efficient way : first there is the query to get the playgroung, then, there is one more query to get the related widgets and there is one query for each widget to get its token.

As one playground can have hundreds of widgets, this leads to hundreds of database queries.

What I'm looking for is a way to tell Doctrine to get all this data using only 3 queries (one to get the playgroung, one to get the widgets and one to get the tokens).


Solution

  • update: since the ArrayCollection in the $playground should contain all the widgets at least as proxy objects (or should get loaded when accessed), the following should work to fetch all required tokens...

    Since the document manager keeps all managed objects, it should prevent additional queries from occuring. (Notice the omitted assignment from the execute).

    $qb = $dm->createQueryBuilder('Token')->findBy(['widget' => $playground->getWidgets()]);
    $qb->getQuery()->execute();
    

    inspired by this page on how to avoid doctrine orm traps - point 5

    old/original answer

    I'm not quite familiar with mongodb, tbh, but according to doctrine's priming references, you should be able to somewhat comfortably hydrate your playground by:

    $qb = $dm->createQueryBuilder('Widget')->findBy(['playground' => $playground]);
    $qb->field('token')->prime(true);
    $widgets = $qb->getQuery()->execute();
    

    however, I might be so wrong.