sfPropelActAsNestedSetBehaviorPlugin - 0.9.2

Propel nested set behavior

You are currently browsing
the website for symfony 1

Visit the Symfony2 website


« Back to the Plugins Home

Signin


Forgot your password?
Create an account

Tools

Stats

advanced search
Information Readme Releases Changelog Contribute
Show source | Show as Markdown

sfPropelActAsNestedSetBehaviorPlugin plugin

The sfPropelActAsNestedSetBehaviorPlugin is a symfony plugin that provides nested set capabilities to Propel objects.

Nested sets (aka modified preorder tree traversal) is a very efficient way (in terms of performances) to browse and edit a tree like structure in an RDBMS.

You can read a good introduction to nested sets on MySQL developers' zone.

Features

  • Fully unit tested
  • Possibility to store multiple trees in the same table
  • Atomic operations
  • Also maintains adjacency list properties, making your classes compatible with code based on this tree traversal methodology

Limitations

As of now, the plugin is known to work with MySQL and PostgreSQL (in trunk). Other RDBMS may work but without any guaranty. Patches are welcome, of course :)

Installation

  • Install the plugin

      symfony plugin-install http://plugins.symfony-project.com/sfPropelActAsNestedSetBehaviorPlugin
    
  • Add new fields to your schema.xml

    [xml]

    scope and tree_parent columns can be of any type.

  • Enable Propel behavior support in propel.ini:

      propel.builder.AddBehaviors = true
    

    If you have to enable the behavior support, rebuild your model:

      symfony propel-build-model
    
  • Enable the behavior for one of your Propel model:

    <?php
      // lib/model/ForumPost.php
      class ForumPost
      {
      }
     
      $columns_map = array('left'   => ForumPostPeer::TREE_LEFT,
                           'right'  => ForumPostPeer::TREE_RIGHT,
                           'parent' => ForumPostPeer::TREE_PARENT,
                           'scope'  => ForumPostPeer::TOPIC_ID);
     
      sfPropelBehavior::add('ForumPost', array('actasnestedset' => array('columns' => $columns_map)));

    The column map is used by the behavior to know which columns hold information it needs :

    • left : Model column holding nested set left value for a row
    • right : Model column holding nested set right value for a row
    • parent : Model column holding row's parent id (this is necessary because we use adjacency list tree traversal for some methods)
    • scope : Model column holding row's scope id. The scope is used to differenciate trees stored in the same table

Usage

Simple tree creation

<?php
  $root = new ForumPost();
  $root->makeRoot();
  $root->save();
 
  $p1 = new ForumPost();
  $p1->insertAsFirstChildOf($root);
  $p1->save();
 
  $p2 = new ForumPost();
  $p2->insertAsFirstChildOf($p1);
  $p2->save();
 
  /*
   * Resulting tree :
   *
   * ROOT
   * |- P1
   *    |- P2
   */

Multiple trees in a single table

<?php
  $root1 = new ForumPost();
  $root1->makeRoot();
  $root1->setTopicId(1);
  $root1->save();
 
  $root2 = new ForumPost();
  $root2->makeRoot();
  $root2->setTopicId(2);
  $root2->save();
 
  $p1 = new ForumPost();
  $p1->insertAsFirstChildOf($root1);
  $p1->save();
 
  $p2 = new ForumPost();
  $p2->insertAsFirstChildOf($root2);
  $p2->save();
 
  /*
   * Resulting trees :
   *
   * ROOT1
   * |- P1
   *
   * ROOT2
   * |- P2
   */

Lame threaded forum posts list example

<?php $root = ForumPostPeer::retrieveByPk(rootnodepk); ?>
  <ul>
    <?php echo $root->getTitle() ?>
 
    <?php foreach ($root->getDescendants() as $post): ?>
 
       <li style="padding-left: <?php echo $post->getLevel() ?>em;">
            <?php echo $post->getTitle() ?>
       </li>
 
    <?php endforeach; ?>
  </ul>

Using nested sets and sfPropelPager

<?php
  // Decide which posts to fetch
  $c = new Criteria();
  $c->add(ForumPostPeer::TOPIC_ID, $topic_id);
  $c->addAscendingOrderByColumn(ForumPostPeer::TREE_LEFT); // ForumPostPeer::TREE_LEFT is the column holding nested set's left value
 
  // Create pager
  $pager = new sfPropelPager('ForumPost', 10);
  $pager->setCriteria($c);
  $pager->setPage($this->getRequestParameter('page', 1));
  $pager->init();

Public API

Enabling the behaviors adds the following method to the Propel objects :

Insertion methods

  • void insertAsFirstChildOf(BaseObject $dest_node) : Inserts node as first child of given node.
  • void insertAsLastChildOf(BaseObject $dest_node) : Inserts node as last child of given node.
  • void insertAsNextSiblingOf(BaseObject $dest_node) : Inserts node as next sibling of given node.
  • void insertAsPrevSiblingOf(BaseObject $dest_node) : Inserts node as previous sibling of given node.
  • void insertAsParentOf(BaseObject $dest_node) : Inserts node as parent of given node

Informational methods

  • bool hasChildren() : Returns true if given node as one or several children.
  • bool isRoot() : Returns true if given node is a root node.
  • bool hasParent() : Returns true if given node has a parent node.
  • bool hasNextSibling() : Returns true if given node has a next sibling.
  • bool hasPrevSibling() : Returns true if given node has a previous sibling.
  • bool isLeaf() : Returns true if given node does not have children.
  • bool isChildOf(BaseObject $node) : Returns true if given node is parent of node.
  • bool isDescendantOf(BaseObject $node) : Returns true if given node is descendant of node.
  • integer getNumberOfChildren() : Returns given node number of direct children.
  • integer getNumberOfDescendants() : Returns given node number of descendants (n level).
  • integer getLevel() : Returns given node level.

Node retrieval methods

  • BaseObject|null getParent($peer_method = 'retrieveByPk') : Returns node parent or null if node does not have a parent.
  • array getChildren($peer_method = 'doSelect') : Returns given node direct children.
  • array getDescendants($peer_method = 'doSelect') : Returns given node descendants (n level).
  • BaseObject retrieveNextSibling() : Returns given node next sibling.
  • BaseObject retrievePrevSibling() : Returns given node previous sibling.
  • BaseObject retrieveFirstChild() : Returns given node first child.
  • BaseObject retrieveLastChild() : Returns given node last child.
  • BaseObject retrieveParent($peer_method = 'doSelectOne') : Returns given node parent.
  • array retrieveSiblings() : Returns node siblings.
  • array getPath($peer_method = 'doSelectOne') : Returns path to a specific node as an array, useful to create breadcrumbs.

Tree modification methods

  • void moveToFirstChildOf(BaseObject $dest_node) : Moves node to first child of given node.
  • void moveToLastChildOf(BaseObject $dest_node) : Moves node to last child of given node.
  • void moveToNextSiblingOf(BaseObject $dest_node) : Moves node to next sibling of given node.
  • void moveToPrevSiblingOf(BaseObject $dest_node) : Moves node to previous sibling of given node.
  • void deleteChildren() : Deletes node direct children
  • void deleteDescendants($peer_method = 'doSelect') : Deletes node descendants (n level)

Helper methods

  • void makeRoot() : Sets node properties to make it a root node.
  • BaseObject reload() : Returns an up to date version of node
  • bool isEqualTo(BaseObject $node) : Returns true if given node is equivalent to node.

Roadmap

0.10.0

Features

  • add support for symfony's i18n capabilities
  • add criteria option to more methods
  • add $peer_method as an optional parameter to getParent() and getPath()
  • add multiple connections support
  • add transactional support
  • get rid of mysql dependency : rewrite queries "criteria-like" or implement adapter.
  • add a method to copy a whole tree to another scope
  • make it possible to delete a root node that has children

Changelog

2009-10-28 | 0.9.2

  • feature: Added a backward compatible Criteria parameter in getChildren(), getDescendants(), retrieveSiblings() and getPath() (fzaninotto)
  • feature: Added a backward compatible $peer_method in deleteDescendants() method (works the same way as ->getChildren() and ->getDescendants() $peer_method parameter)
  • feature: Allow user to change behaviour name in app.yml (Frederic Coelho)
  • converted to symfony 1.2 only

2007-07-30 | 0.9.1-beta2

  • bugfix: Implemented more reliable deleteDescendants() method (Alexander Alexandrov)

2007-07-23 | 0.9.1-beta

Bugfixes

  • fixed getLevel() cache (gordon franke)
  • fixed scope handling : scope can be any type of data (Jorn.Wagner)
  • retrieveFirstChild() and retrieveLastChild() missing references to scope node (Olivier.Mansour)
  • fixed postgresql compatibility (Maciej.Filipiak & Krasimir.Angelov)
  • added a note about supported RDBMS (tristan)
  • made roadmap clearer (tristan)
  • removed useless Propel::getConnection (Eric.Fredj)
  • fixed scope handling in deleteDescendants() (Piers.Warmers)
  • fixed new getDescendants() implementation node level caching (tristan)

Enhancements

  • added new isDescendantOf() method (Piers.Warmers)
  • implemented faster getPath() method (francois)
  • implemented faster getDescendants() (Jon.Collins)

2007-05-24 | 0.9.0-beta

  • Licence change : MIT -> LGPL
  • Please welcome a new maintainer : Gordan Franke :)
  • tree "dumper" utility method : sfPropelActAsNestedSetBehaviorUtils::dumpTree()
  • add optional select method for getPath|getParent|retrieveParent (gordon)

2007-04-18 | 0.8.2-beta

  • added getParent() method (olivier mansour)
  • added getLevel() unit tests
  • implemented caching of level in collection retrieval methods : getDescendants(), getChildren(), retrieveSiblings()
  • defined plugin roadmap

2007-03-22 | 0.8.1-beta

  • fixed #1480 : non-abstracted column name (paul markovitch)
  • fixed bug in getStubFromPeer()
  • makeRoot() should accept non new objects (peter van garderen)
  • getDescendants() should not try to get descendants if node is a leaf (peter van garderen)
  • updated unit tests
  • enabled syntax highlighting in README

2007-02-19 | 0.8.0-beta

Implemented more methods (+ unit tests) :

  • insertAsParentOf
  • retrieveSiblings
  • isEqualTo
  • isChildOf

Enhanced internal API

2007-02-19 | 0.7.0-beta

Implemented missing methods (+ unit tests) :

  • moveToPrevSiblingOf
  • moveToNextSiblingOf
  • deleteChildren
  • deleteDescendants

2007-02-19 | 0.6.2-beta

Fixed a bug due to wrong usage of rtrim. (Thanks to Krešo Kunjas)

2007-02-15 | 0.6.1-beta

Fixed minor bug in getPath()

2007-02-15 | 0.6.0-beta

Implemented missing node retrieval methods :

  • retrieveFirstChild
  • retrieveLastChild
  • retrieveParent
  • getPath

Updated docs and unit tests accordingly

2007-02-14 | 0.5.1-beta

Pear package missed plugin's config.php file.

2007-02-14 | 0.5.0-beta

Initial public release. The behavior is stable and fully unit-tested, but the API is not yet complete. Missing methods :

  • retrieveFirstChild
  • retrieveLastChild
  • moveToPrevSiblingOf
  • moveToNextSiblingOf
  • deleteChildren
  • deleteTree
  • getPath

Maintainers

Tristan Rivoallan and Gordon Franke.

Contributors

Alexander Alexandrov, Krasimir Angelov, Jon Collins, Mark Deepwell, Maciej Filipiak, Eric Fredj, Peter van Garderen, Olivier Mansour, Paul Markovitch, Krešo Kunjas, Piers Warmers, Francois Zaninotto, Romain Dorgueil.

Plugin's code is based on work from Heltem and Joe Simms.

Thanks to all of you !