Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy Loading with Doctrine2 and Symfony2 using DQL

I have a tree structure with a parent field. Currently I am trying to get all parent nodes to display the path to the current node.

Basically I am doing a while-loop to process all nodes.

$current = $node->getParent();
while($current) {
  // do something
  $current = $current->getParent();
}

Using the default findById method works. Because the entity has some aggregated fields, I am using a custom repository method, to load all basic fields with one query.

public function findNodeByIdWithMeta($id) {
    return $this->getEntityManager()
        ->createQuery('
            SELECT p, a, c, cc, ca, pp FROM
            TestingNestedObjectBundle:NestedObject p
            JOIN p.actions a
            LEFT JOIN p.children c
            LEFT JOIN c.children cc
            LEFT JOIN c.actions ca
            LEFT JOIN p.parent pp
            WHERE p.id = :id
        ')
        ->setParameter('id', $id)
        ->setHint(
            \Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER,
            'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker'
        )
        ->getOneOrNullResult();
}

With that code, loading the parents fails. I only get the immediate parent (addressed by LEFT JOIN p.parent pp) but not the parents above. E.g. $node->getParent()->getParent() returns null.

Whats wrong with my code? Did I misunderstood the lazy loading thing?

Thanks a lot, Hacksteak

like image 291
hacksteak25 Avatar asked Nov 05 '11 16:11

hacksteak25


2 Answers

It looks like your are using the adjacency model for storing trees in a relational database. Which in turn means, that you will need a join for every level to get all ancestors with a single query.

As you are already using the Doctrine Extension Library I recommend to have a look at the Tree component.

like image 100
mbh Avatar answered Nov 06 '22 00:11

mbh


My Answer involves not using DQL and instead creating a NestedSetManager which has access to your DBAL connection so you can use SQL. I never felt like the ORM's did a good job with NestedSets query logic.

With a NestedSetManager, you can then write a bunch of clean methods and it's really simple because all these queries are well documented. See this link. Some of the method in my NestedSetManager are:

setNode();
setRoot();
loadNestedSet();
moveNodeUp();
modeNodeDown();
getRootNode();
addNodeSibling();
getNodesByDepth();
getParents();
getNodePath();
childExists();
addChildToNode();
renameNode();
deleteNode();
// And many more 

You can have a ball and create a lot of create NestedSet functionality if you're not tied down by an ORM's somewhat complex functionality.

Also -- Symfony2 makes all this really really easy. You create your NestedSetManager class file and reference it in your Services.yml and pass in your Dbal connection. Mine looks like this:

services:
    manager.nestedset:
        class: Acme\CoreBundle\Manager\NestedSetManager
        arguments: [ @database_connection ]

you can then access your nestedsets with:

$path = $this->get('manager.nestedset')->setNode(4)->getNodePath(); // in your controller

Moral of the story, ORM/NestedSets drove me bonkers and this solution work really well. If you're being forced to use DQL and have no other options, this answer probably wont be acceptable.

like image 2
JustinP Avatar answered Nov 06 '22 00:11

JustinP