2

I usually fetch NestedSet tree like this:

class ModelTable extends Doctrine_Table
{
  /**
   * Gets tree elements in one query
   */
  public function getMenuTree()
  {
    $q = $this->createQuery('p')
      ->orderBy('p.root_id')
      ->addOrderBy('p.lft');

    $tree = $q->execute(array(),  Doctrine_Core::HYDRATE_RECORD_HIERARCHY);      

    return $tree;
  }
}

So I can actually display the whole tree while using only one query to the database.. until I try to traverse the tree. For example, if you call a method on a node like this:

$node->getNode()->getAncestors()

Doctrine will build up a whole new query for this (have a look at Doctrine_Node_NestedSet::getAncestors()). Other traversing methods like getChildren() also use DQL. But this is somewhat inefficient, isn't it? Once I fetched the whole tree, I don't want to query the database any more.

Maybe someone has written a driver to do it the right way? (without DQL)

Dziamid
  • 11,225
  • 12
  • 69
  • 104

1 Answers1

3

If you only want to fetch the children (which is the most likely, why would you need getAncestors() to iterate on a tree?), you could also keep the code you showed us as example, and do something like this this:

foreach ($categories->getFirst()->get('__children') as $child) {
    // ...
}

This is documented here (hard to find unless you choose to read the whole documentation).

I have once used recursive code on a whole tree with only ONE query.

1015 lib % ack --type="php" "_node"                                                                                                                                                          2011-05-15 14:26:22 greg pts/1
vendor/doctrine/Doctrine/Record.php
94:    protected $_node;
814:        unset($vars['_node']);
2403:        if ( ! isset($this->_node)) {
2404:            $this->_node = Doctrine_Node::factory($this,
2410:        return $this->_node;
liche ~/source/symfony/1.4/lib/plugins/sfDoctrinePlugin/lib

_node only seems to be set in getNode() itself, I don't know whether you can hydrate it like any other field, nor how you would do this.

I think that getNode() should only be used for modifications on the tree. If you want to display the path from the root, you should use a recursive method to display the tree, whith an argument containing the parent's path . If there is anything else for which you would need the tree functionality, tell us...

UPDATE

I think I eventually got it. You want to display a tree menu AND a breadcrumb, and you want to reuse the data of the menu in the breadcrumb, isn't it? To display your breadcrumb, you have to recurse on $tree, and display a node if and only if it is an ancestor of the current page. And there is a method for that : isAncestorOf(). So "all you have to do" is a template which does something like this:

//module/templates/_breadcrumbElement.php
foreach ($node->get('__children') as $child) :
  if ($child->isAncestorOf($pageNode)):
     echo link_to($child->getName(), $child->getUrl());
     include_partial('module/breadcrumbElement', array('node' => $child, 'pageNode' => $pageNode));
  endif;
endforeach;

Feed it the root of your tree and you'll be fine. Hopefully.

greg0ire
  • 22,714
  • 16
  • 72
  • 101
  • Glad to see someone works on Sundays =). NestedSet::fetchTree returns exactly the same Doctrine_Collection (or array, depending on hydration method). When you iterate it and need the tree functionality, you call $node = $record->getNode() on a particular record. Am I right? The problem with this however, is that $node is now unaware of your collection and hence need to query db to traverse the tree. – Dziamid May 15 '11 at 11:51
  • Why you might want to get ancestors? For example, to get the full path to node from root. Here's even a method for this: Doctrine_Node_NestedSet::getPath(). – Dziamid May 15 '11 at 11:53
  • @Dziamid: ok, sorry for the first method, it was a hunch, I'm going to update it because I think that _node is only set in `getNode()` itself. – greg0ire May 15 '11 at 12:47
  • @Dziamid: btw, I don't work on week ends, I'm only addicted to SO ;) – greg0ire May 15 '11 at 12:53
  • I completely understand the recursive method. What if I need several properties of ancestors (for example, url and name to build a breadcrumb menu)? In a "real" tree structure children usually have references to their parent object. It just makes sense, doesn't it? – Dziamid May 15 '11 at 13:49
  • @Dziamid: What does not make sense to me is why you would need the `getAncestors()` method to build a breadcrumb menu ? Wouldn't you fetch the tree of your pages, with their url and their name, and recurse over it __from the root__ (and not from the current node)? Maybe you could update your question with a very concrete example of what you are dealing with? – greg0ire May 15 '11 at 14:07
  • @Dziamid: I don't think I fully understand your problem, but if you think your doing additional requests because the tree was not enough hydrated (missing relations?), then you should probably read [this particular § about how to set tree queries so that more data is fetched](http://www.doctrine-project.org/documentation/manual/1_0/nl/hierarchical-data#nested-set:advanced-usage:fetching-a-tree-with-relations) – greg0ire May 15 '11 at 14:14
  • @Dziamid: Oh, and I know what a breadcrumb is, what a tree menu is, but what is a breadcrumb menu? I'm not sure what we are talking about. – greg0ire May 15 '11 at 14:20
  • @Dziamid: yet another udpate in my post. Hope ou like it :P – greg0ire May 15 '11 at 14:52
  • Here's my question, is very simple: http://stackoverflow.com/questions/6009163/breadcrumb-navigation-with-doctrine-nestedset – Dziamid May 15 '11 at 14:59