= sfPropelActAsNestedSetBehaviorPlugin plugin = [[PageOutline]] 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 [http://dev.mysql.com/tech-resources/articles/hierarchical-data.html 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 == Installation == * Install the plugin {{{ symfony plugin-install http://plugins.symfony-project.com/sfPropelActAsNestedSetBehaviorPlugin }}} * Add new fields to your schema.xml {{{ <column name="tree_left" type="INTEGER" required="true" /> <column name="tree_right" type="INTEGER" required="true" /> <column name="tree_parent" type="INTEGER" required="true" /> <column name="topic_id" type="INTEGER" required="true" /> }}} * 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: {{{ // 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 === {{{ $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 === {{{ // 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. === 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. * {{{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 === * {{{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()}}} : Returns given node parent. * {{{array getPath()}}} : 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()}}} : '''NOT IMPLEMENTED YET''' * {{{void moveToPrevSiblingOf()}}} : '''NOT IMPLEMENTED YET''' * {{{void deleteChildren()}}} : '''NOT IMPLEMENTED YET''' * {{{void deleteTree()}}} : '''NOT IMPLEMENTED YET''' === Helper methods === * {{{void makeRoot()}}} : Sets node properties to make it a root node. * {{{BaseObject reload()}}} : Returns an up to date version of node == Changelog == === 2007-02-19 | 0.6.2 === * Fixed a bug due to wrong usage of `rtrim`. (Thanks to Krešo Kunjas) === 2007-02-15 | 0.6.1 === Fixed minor bug in getPath() === 2007-02-15 | 0.6.0 === Implemented missing node retrieval methods : * retrieveFirstChild * retrieveLastChild * retrieveParent * getPath Updated docs and unit tests accordingly === 2007-02-14 | 0.5.1 === Pear package missed plugin's config.php file. === 2007-02-14 | 0.5.0 === 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