Source for file NestedSet.php

Documentation is available at NestedSet.php

  1. <?php
  2. /*
  3.  *    $Id: NestedSet.php 2263 2007-08-20 07:45:29Z romanb $
  4.  *
  5.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9.  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  11.  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  12.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  13.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  14.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  15.  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16.  *
  17.  * This software consists of voluntary contributions made by many individuals
  18.  * and is licensed under the LGPL. For more information, see
  19.  * <http://www.phpdoctrine.com>.
  20.  */
  21. /**
  22.  * Doctrine_Node_NestedSet
  23.  *
  24.  * @package    Doctrine
  25.  * @license    http://www.opensource.org/licenses/lgpl-license.php LGPL
  26.  * @category   Object Relational Mapping
  27.  * @link       www.phpdoctrine.com
  28.  * @since      1.0
  29.  * @version    $Revision: 2263 $
  30.  * @author     Joe Simms <joe.simms@websites4.com>
  31.  * @author     Roman Borschel <roman@code-factory.org>
  32.  */
  33. {
  34.     /**
  35.      * test if node has previous sibling
  36.      *
  37.      * @return bool 
  38.      */
  39.     public function hasPrevSibling()
  40.     {
  41.         return $this->isValidNode($this->getPrevSibling());        
  42.     }
  43.  
  44.     /**
  45.      * test if node has next sibling
  46.      *
  47.      * @return bool 
  48.      */ 
  49.     public function hasNextSibling()
  50.     {
  51.         return $this->isValidNode($this->getNextSibling());        
  52.     }
  53.  
  54.     /**
  55.      * test if node has children
  56.      *
  57.      * @return bool 
  58.      */
  59.     public function hasChildren()
  60.     {
  61.         return (($this->getRightValue($this->getLeftValue() ) >);        
  62.     }
  63.  
  64.     /**
  65.      * test if node has parent
  66.      *
  67.      * @return bool 
  68.      */
  69.     public function hasParent()
  70.     {
  71.         return !$this->isRoot();
  72.     }
  73.  
  74.     /**
  75.      * gets record of prev sibling or empty record
  76.      *
  77.      * @return object     Doctrine_Record 
  78.      */
  79.     public function getPrevSibling()
  80.     {
  81.         $baseAlias $this->_tree->getBaseAlias();
  82.         $q $this->_tree->getBaseQuery();
  83.         $q $q->addWhere("$baseAlias.rgt = ?"$this->getLeftValue(1);
  84.         $q $this->_tree->returnQueryWithRootId($q$this->getRootValue());
  85.         $result $q->execute();
  86.  
  87.         if (count($result<= 0{
  88.             return false;
  89.         }
  90.         
  91.         if ($result instanceof Doctrine_Collection{
  92.             $sibling $result->getFirst();
  93.         else if (is_array($result)) {
  94.             $sibling array_shift($result);
  95.         }
  96.         
  97.         return $sibling;
  98.     }
  99.  
  100.     /**
  101.      * gets record of next sibling or empty record
  102.      *
  103.      * @return object     Doctrine_Record 
  104.      */
  105.     public function getNextSibling()
  106.     {
  107.         $baseAlias $this->_tree->getBaseAlias();
  108.         $q $this->_tree->getBaseQuery();
  109.         $q $q->addWhere("$baseAlias.lft = ?"$this->getRightValue(1);
  110.         $q $this->_tree->returnQueryWithRootId($q$this->getRootValue());
  111.         $result $q->execute();
  112.  
  113.         if (count($result<= 0{
  114.             return false;
  115.         }
  116.         
  117.         if ($result instanceof Doctrine_Collection{
  118.             $sibling $result->getFirst();
  119.         else if (is_array($result)) {
  120.             $sibling array_shift($result);
  121.         }
  122.         
  123.         return $sibling;
  124.     }
  125.  
  126.     /**
  127.      * gets siblings for node
  128.      *
  129.      * @return array     array of sibling Doctrine_Record objects
  130.      */
  131.     public function getSiblings($includeNode false)
  132.     {
  133.         $parent $this->getParent();
  134.         $siblings array();
  135.         if ($parent->exists()) {
  136.             foreach ($parent->getNode()->getChildren(as $child{
  137.                 if ($this->isEqualTo($child&& !$includeNode{
  138.                     continue;
  139.                 }
  140.                 $siblings[$child;
  141.             }        
  142.         }
  143.         return $siblings;
  144.     }
  145.  
  146.     /**
  147.      * gets record of first child or empty record
  148.      *
  149.      * @return object     Doctrine_Record 
  150.      */
  151.     public function getFirstChild()
  152.     {
  153.         $baseAlias $this->_tree->getBaseAlias();
  154.         $q $this->_tree->getBaseQuery();
  155.         $q->addWhere("$baseAlias.lft = ?"$this->getLeftValue(1);
  156.         $this->_tree->returnQueryWithRootId($q$this->getRootValue());
  157.         $result $q->execute();
  158.  
  159.         if (count($result<= 0{
  160.             return false;
  161.         }
  162.         
  163.         if ($result instanceof Doctrine_Collection{
  164.             $child $result->getFirst();
  165.         else if (is_array($result)) {
  166.             $child array_shift($result);
  167.         }
  168.         
  169.         return $child;       
  170.     }
  171.  
  172.     /**
  173.      * gets record of last child or empty record
  174.      *
  175.      * @return object     Doctrine_Record 
  176.      */
  177.     public function getLastChild()
  178.     {
  179.         $baseAlias $this->_tree->getBaseAlias();
  180.         $q $this->_tree->getBaseQuery();
  181.         $q->addWhere("$baseAlias.rgt = ?"$this->getRightValue(1);
  182.         $this->_tree->returnQueryWithRootId($q$this->getRootValue());
  183.         $result $q->execute();
  184.  
  185.         if (count($result<= 0{
  186.             return false;
  187.         }
  188.         
  189.         if ($result instanceof Doctrine_Collection{
  190.             $child $result->getFirst();
  191.         else if (is_array($result)) {
  192.             $child array_shift($result);
  193.         }
  194.         
  195.         return $child;      
  196.     }
  197.  
  198.     /**
  199.      * gets children for node (direct descendants only)
  200.      *
  201.      * @return mixed The children of the node or FALSE if the node has no children.
  202.      */
  203.     public function getChildren()
  204.     
  205.         return $this->getDescendants(1);
  206.     }
  207.  
  208.     /**
  209.      * gets descendants for node (direct descendants only)
  210.      *
  211.      * @return mixed  The descendants of the node or FALSE if the node has no descendants.
  212.      * @todo Currently all descendants are fetched, no matter the depth. Maybe there is a better
  213.      *        solution with less overhead.
  214.      */
  215.     public function getDescendants($depth null$includeNode false)
  216.     {
  217.         $baseAlias $this->_tree->getBaseAlias();
  218.         $q $this->_tree->getBaseQuery();
  219.         $params array($this->record->get('lft')$this->record->get('rgt'));
  220.         
  221.         if ($includeNode{
  222.             $q->addWhere("$baseAlias.lft >= ? AND $baseAlias.rgt <= ?"$params)->addOrderBy("$baseAlias.lft asc");
  223.         else {
  224.             $q->addWhere("$baseAlias.lft > ? AND $baseAlias.rgt < ?"$params)->addOrderBy("$baseAlias.lft asc");
  225.         }
  226.         
  227.         if ($depth !== null{
  228.             $q->addWhere("$baseAlias.level <= ?"$this->record['level'$depth);
  229.         }
  230.         
  231.         $q $this->_tree->returnQueryWithRootId($q$this->getRootValue());
  232.         $result $q->execute();
  233.  
  234.         if (count($result<= 0{
  235.             return false;
  236.         }
  237.  
  238.         return $result;
  239.     }
  240.  
  241.     /**
  242.      * gets record of parent or empty record
  243.      *
  244.      * @return object     Doctrine_Record 
  245.      */
  246.     public function getParent()
  247.     {
  248.         $baseAlias $this->_tree->getBaseAlias();
  249.         $q $this->_tree->getBaseQuery();
  250.         $q->addWhere("$baseAlias.lft < ? AND $baseAlias.rgt > ?"array($this->getLeftValue()$this->getRightValue()))
  251.                 ->addOrderBy("$baseAlias.rgt asc");
  252.         $q $this->_tree->returnQueryWithRootId($q$this->getRootValue());
  253.         $result $q->execute();
  254.         
  255.         if (count($result<= 0{
  256.             return false;
  257.         }
  258.                
  259.         if ($result instanceof Doctrine_Collection{
  260.             $parent $result->getFirst();
  261.         else if (is_array($result)) {
  262.             $parent array_shift($result);
  263.         }
  264.         
  265.         return $parent;
  266.     }
  267.  
  268.     /**
  269.      * gets ancestors for node
  270.      *
  271.      * @param integer $deth  The depth 'upstairs'.
  272.      * @return mixed  The ancestors of the node or FALSE if the node has no ancestors (this
  273.      *                 basically means it's a root node).
  274.      */
  275.     public function getAncestors($depth null)
  276.     {
  277.         $baseAlias $this->_tree->getBaseAlias();
  278.         $q $this->_tree->getBaseQuery();
  279.         $q->addWhere("$baseAlias.lft < ? AND $baseAlias.rgt > ?"array($this->getLeftValue()$this->getRightValue()))
  280.                 ->addOrderBy("$baseAlias.lft asc");
  281.         if ($depth !== null{
  282.             $q->addWhere("$baseAlias.level >= ?"$this->record['level'$depth);
  283.         }
  284.         $q $this->_tree->returnQueryWithRootId($q$this->getRootValue());
  285.         $ancestors $q->execute();
  286.         if (count($ancestors<= 0{
  287.             return false;
  288.         }
  289.         return $ancestors;
  290.     }
  291.  
  292.     /**
  293.      * gets path to node from root, uses record::toString() method to get node names
  294.      *
  295.      * @param string     $seperator     path seperator
  296.      * @param bool     $includeNode     whether or not to include node at end of path
  297.      * @return string     string representation of path
  298.      */     
  299.     public function getPath($seperator ' > '$includeRecord false)
  300.     {
  301.         $path array();
  302.         $ancestors $this->getAncestors();
  303.         foreach ($ancestors as $ancestor{
  304.             $path[$ancestor->__toString();
  305.         }
  306.         if ($includeRecord{
  307.             $path[$this->getRecord()->__toString();
  308.         }
  309.             
  310.         return implode($seperator$path);
  311.     }
  312.  
  313.     /**
  314.      * gets number of children (direct descendants)
  315.      *
  316.      * @return int 
  317.      */     
  318.     public function getNumberChildren()
  319.     {
  320.         return count($this->getChildren());
  321.     }
  322.  
  323.     /**
  324.      * gets number of descendants (children and their children)
  325.      *
  326.      * @return int 
  327.      */
  328.     public function getNumberDescendants()
  329.     {
  330.         return ($this->getRightValue($this->getLeftValue(12;
  331.     }
  332.  
  333.     /**
  334.      * inserts node as parent of dest record
  335.      *
  336.      * @return bool 
  337.      * @todo Wrap in transaction
  338.      */
  339.     public function insertAsParentOf(Doctrine_Record $dest)
  340.     {
  341.         // cannot insert a node that has already has a place within the tree
  342.         if ($this->isValidNode()) {
  343.             return false;
  344.         }
  345.         // cannot insert as parent of root
  346.         if ($dest->getNode()->isRoot()) {
  347.             return false;
  348.         }
  349.         $newRoot $dest->getNode()->getRootValue();
  350.         $this->shiftRLValues($dest->getNode()->getLeftValue()1$newRoot);
  351.         $this->shiftRLValues($dest->getNode()->getRightValue(21$newRoot);
  352.         
  353.         $newLeft $dest->getNode()->getLeftValue();
  354.         $newRight $dest->getNode()->getRightValue(2;
  355.  
  356.         $this->record['level'$dest['level'1;
  357.         $this->insertNode($newLeft$newRight$newRoot);
  358.         
  359.         return true;
  360.     }
  361.  
  362.     /**
  363.      * inserts node as previous sibling of dest record
  364.      *
  365.      * @return bool 
  366.      * @todo Wrap in transaction
  367.      */
  368.     public function insertAsPrevSiblingOf(Doctrine_Record $dest)
  369.     {
  370.         // cannot insert a node that has already has a place within the tree
  371.         if ($this->isValidNode())
  372.             return false;
  373.  
  374.         $newLeft $dest->getNode()->getLeftValue();
  375.         $newRight $dest->getNode()->getLeftValue(1;
  376.         $newRoot $dest->getNode()->getRootValue();
  377.         
  378.         $this->shiftRLValues($newLeft2$newRoot);
  379.         $this->record['level'$dest['level'];
  380.         $this->insertNode($newLeft$newRight$newRoot);
  381.         // update destination left/right values to prevent a refresh
  382.         // $dest->getNode()->setLeftValue($dest->getNode()->getLeftValue() + 2);
  383.         // $dest->getNode()->setRightValue($dest->getNode()->getRightValue() + 2);
  384.                         
  385.         return true;
  386.     }
  387.  
  388.     /**
  389.      * inserts node as next sibling of dest record
  390.      *
  391.      * @return bool 
  392.      * @todo Wrap in transaction
  393.      */    
  394.     public function insertAsNextSiblingOf(Doctrine_Record $dest)
  395.     {
  396.         // cannot insert a node that has already has a place within the tree
  397.         if ($this->isValidNode())
  398.             return false;
  399.  
  400.         $newLeft $dest->getNode()->getRightValue(1;
  401.         $newRight $dest->getNode()->getRightValue(2;
  402.         $newRoot $dest->getNode()->getRootValue();
  403.  
  404.         $this->shiftRLValues($newLeft2$newRoot);
  405.         $this->record['level'$dest['level'];
  406.         $this->insertNode($newLeft$newRight$newRoot);
  407.  
  408.         // update destination left/right values to prevent a refresh
  409.         // no need, node not affected
  410.  
  411.         return true;
  412.     }
  413.  
  414.     /**
  415.      * inserts node as first child of dest record
  416.      *
  417.      * @return bool 
  418.      * @todo Wrap in transaction
  419.      */
  420.     public function insertAsFirstChildOf(Doctrine_Record $dest)
  421.     {
  422.         // cannot insert a node that has already has a place within the tree
  423.         if ($this->isValidNode())
  424.             return false;
  425.  
  426.         $newLeft $dest->getNode()->getLeftValue(1;
  427.         $newRight $dest->getNode()->getLeftValue(2;
  428.         $newRoot $dest->getNode()->getRootValue();
  429.  
  430.         $this->shiftRLValues($newLeft2$newRoot);
  431.         $this->record['level'$dest['level'1;
  432.         $this->insertNode($newLeft$newRight$newRoot);
  433.         
  434.         // update destination left/right values to prevent a refresh
  435.         // $dest->getNode()->setRightValue($dest->getNode()->getRightValue() + 2);
  436.  
  437.         return true;
  438.     }
  439.  
  440.     /**
  441.      * inserts node as last child of dest record
  442.      *
  443.      * @return bool 
  444.      * @todo Wrap in transaction
  445.      */
  446.     public function insertAsLastChildOf(Doctrine_Record $dest)
  447.     {
  448.         // cannot insert a node that has already has a place within the tree
  449.         if ($this->isValidNode())
  450.             return false;
  451.  
  452.         $newLeft $dest->getNode()->getRightValue();
  453.         $newRight $dest->getNode()->getRightValue(1;
  454.         $newRoot $dest->getNode()->getRootValue();
  455.  
  456.         $this->shiftRLValues($newLeft2$newRoot);
  457.         $this->record['level'$dest['level'1;
  458.         $this->insertNode($newLeft$newRight$newRoot);
  459.  
  460.         // update destination left/right values to prevent a refresh
  461.         // $dest->getNode()->setRightValue($dest->getNode()->getRightValue() + 2);
  462.         
  463.         return true;
  464.     }
  465.     
  466.     /**
  467.      * Accomplishes moving of nodes between different trees.
  468.      * Used by the move* methods if the root values of the two nodes are different.
  469.      *
  470.      * @param Doctrine_Record $dest 
  471.      * @param unknown_type $newLeftValue 
  472.      * @param unknown_type $moveType 
  473.      * @todo Better exception handling/wrapping
  474.      */
  475.     private function _moveBetweenTrees(Doctrine_Record $dest$newLeftValue$moveType)
  476.     {
  477.         $conn $this->record->getTable()->getConnection();
  478.             
  479.             try {
  480.                 $conn->beginTransaction();
  481.                 
  482.                 // Move between trees: Detach from old tree & insert into new tree
  483.                 $newRoot $dest->getNode()->getRootValue();
  484.                 $oldRoot $this->getRootValue();
  485.                 $oldLft $this->getLeftValue();
  486.                 $oldRgt $this->getRightValue();
  487.                 $oldLevel $this->record['level'];
  488.                 
  489.                 // Prepare target tree for insertion, make room
  490.                 $this->shiftRlValues($newLeftValue$oldRgt $oldLft 1$newRoot);
  491.                 
  492.                 // Set new root id for this node
  493.                 $this->setRootValue($newRoot);
  494.                 $this->record->save();
  495.                 
  496.                 // Close gap in old tree
  497.                 $first $oldRgt 1;
  498.                 $delta $oldLft $oldRgt 1;
  499.                 $this->shiftRLValues($first$delta$oldRoot);
  500.                 
  501.                 // Insert this node as a new node
  502.                 $this->setRightValue(0);
  503.                 $this->setLeftValue(0);
  504.                 
  505.                 switch ($moveType{
  506.                     case 'moveAsPrevSiblingOf':
  507.                         $this->insertAsPrevSiblingOf($dest);
  508.                     break;
  509.                     case 'moveAsFirstChildOf':
  510.                         $this->insertAsFirstChildOf($dest);
  511.                     break;
  512.                     case 'moveAsNextSiblingOf':
  513.                         $this->insertAsNextSiblingOf($dest);
  514.                     break;
  515.                     case 'moveAsLastChildOf':
  516.                         $this->insertAsLastChildOf($dest);
  517.                     break;
  518.                     default:
  519.                         throw new Exception("Unknown move operation$moveType.");
  520.                 }
  521.                 
  522.                 $diff $oldRgt $oldLft;
  523.                 $this->setRightValue($this->getLeftValue(($oldRgt $oldLft));
  524.                 $this->record->save();
  525.                 
  526.                 $newLevel $this->record['level'];
  527.                 $levelDiff $newLevel $oldLevel;
  528.                 
  529.                 // Relocate descendants of the node
  530.                 $diff $this->getLeftValue($oldLft;
  531.                 $componentName $this->_tree->getBaseComponent();
  532.                 $rootColName $this->record->getTable()->getTree()->getAttribute('rootColumnName');
  533.  
  534.                 // Update lft/rgt/root/level for all descendants
  535.                 $q new Doctrine_Query($conn);
  536.                 $q $q->update($componentName)
  537.                         ->set($componentName '.lft''lft + ?'$diff)
  538.                         ->set($componentName '.rgt''rgt + ?'$diff)
  539.                         ->set($componentName '.level''level + ?'$levelDiff)
  540.                         ->set($componentName '.' $rootColName'?'$newRoot)
  541.                         ->where($componentName '.lft > ? AND ' $componentName '.rgt < ?',
  542.                         array($oldLft$oldRgt));
  543.                 $q $this->_tree->returnQueryWithRootId($q$oldRoot);
  544.                 $q->execute();
  545.                 
  546.                 $conn->commit();
  547.             catch (Exception $e{
  548.                 $conn->rollback();
  549.                 throw $e;
  550.             }
  551.     }
  552.     
  553.     /**
  554.      * moves node as prev sibling of dest record
  555.      * 
  556.      */     
  557.     public function moveAsPrevSiblingOf(Doctrine_Record $dest)
  558.     {
  559.         if ($dest->getNode()->getRootValue(!= $this->getRootValue()) {
  560.             // Move between trees
  561.             $this->_moveBetweenTrees($dest$dest->getNode()->getLeftValue()__FUNCTION__);
  562.         else {
  563.             // Move within the tree
  564.             $oldLevel $this->record['level'];
  565.             $this->record['level'$dest['level'];
  566.             $this->updateNode($dest->getNode()->getLeftValue()$this->record['level'$oldLevel);
  567.         }
  568.     }
  569.  
  570.     /**
  571.      * moves node as next sibling of dest record
  572.      *        
  573.      */
  574.     public function moveAsNextSiblingOf(Doctrine_Record $dest)
  575.     {
  576.         if ($dest->getNode()->getRootValue(!= $this->getRootValue()) {
  577.             // Move between trees
  578.             $this->_moveBetweenTrees($dest$dest->getNode()->getRightValue(1__FUNCTION__);
  579.         else {
  580.             // Move within tree
  581.             $oldLevel $this->record['level'];
  582.             $this->record['level'$dest['level'];
  583.             $this->updateNode($dest->getNode()->getRightValue(1$this->record['level'$oldLevel);
  584.         }
  585.     }
  586.  
  587.     /**
  588.      * moves node as first child of dest record
  589.      *            
  590.      */
  591.     public function moveAsFirstChildOf(Doctrine_Record $dest)
  592.     {
  593.         if ($dest->getNode()->getRootValue(!= $this->getRootValue()) {
  594.             // Move between trees
  595.             $this->_moveBetweenTrees($dest$dest->getNode()->getLeftValue(1__FUNCTION__);
  596.         else {
  597.             // Move within tree
  598.             $oldLevel $this->record['level'];
  599.             $this->record['level'$dest['level'1;
  600.             $this->updateNode($dest->getNode()->getLeftValue(1$this->record['level'$oldLevel);
  601.         }
  602.     }
  603.  
  604.     /**
  605.      * moves node as last child of dest record
  606.      *        
  607.      */
  608.     public function moveAsLastChildOf(Doctrine_Record $dest)
  609.     {
  610.         if ($dest->getNode()->getRootValue(!= $this->getRootValue()) {
  611.             // Move between trees
  612.             $this->_moveBetweenTrees($dest$dest->getNode()->getRightValue()__FUNCTION__);
  613.         else {
  614.             // Move within tree
  615.             $oldLevel $this->record['level'];
  616.             $this->record['level'$dest['level'1;
  617.             $this->updateNode($dest->getNode()->getRightValue()$this->record['level'$oldLevel);
  618.         }
  619.     }
  620.     
  621.     /**
  622.      * Makes this node a root node. Only used in multiple-root trees.
  623.      *
  624.      * @todo Exception handling/wrapping
  625.      */
  626.     public function makeRoot($newRootId)
  627.     {
  628.         // TODO: throw exception instead?
  629.         if ($this->getLeftValue(== || !$this->record->getTable()->getTree()->getAttribute('hasManyRoots')) {
  630.             return false;
  631.         }
  632.         
  633.         $oldRgt $this->getRightValue();
  634.         $oldLft $this->getLeftValue();
  635.         $oldRoot $this->getRootValue();
  636.         $oldLevel $this->record['level'];
  637.         
  638.         try {
  639.             $conn $this->record->getTable()->getConnection();
  640.             $conn->beginTransaction();
  641.             
  642.             // Detach from old tree (close gap in old tree)
  643.             $first $oldRgt 1;
  644.             $delta $oldLft $oldRgt 1;
  645.             $this->shiftRLValues($first$delta$this->getRootValue());
  646.             
  647.             // Set new lft/rgt/root/level values for root node
  648.             $this->setLeftValue(1);
  649.             $this->setRightValue($oldRgt $oldLft 1);
  650.             $this->setRootValue($newRootId);
  651.             $this->record['level'0;
  652.             
  653.             // Update descendants lft/rgt/root/level values
  654.             $diff $oldLft;
  655.             $newRoot $newRootId;
  656.             $componentName $this->_tree->getBaseComponent();
  657.             $rootColName $this->record->getTable()->getTree()->getAttribute('rootColumnName');
  658.             $q new Doctrine_Query($conn);
  659.             $q $q->update($componentName)
  660.                     ->set($componentName '.lft''lft + ?'$diff)
  661.                     ->set($componentName '.rgt''rgt + ?'$diff)
  662.                     ->set($componentName '.level''level - ?'$oldLevel)
  663.                     ->set($componentName '.' $rootColName'?'$newRoot)
  664.                     ->where($componentName '.lft > ? AND ' $componentName '.rgt < ?',
  665.                     array($oldLft$oldRgt));
  666.             $q $this->_tree->returnQueryWithRootId($q$oldRoot);
  667.             $q->execute();
  668.             
  669.             $conn->commit();
  670.             
  671.         catch (Exception $e{
  672.             $conn->rollback();
  673.             throw $e;
  674.         }
  675.     }
  676.  
  677.     /**
  678.      * adds node as last child of record
  679.      *        
  680.      */
  681.     public function addChild(Doctrine_Record $record)
  682.     {
  683.         $record->getNode()->insertAsLastChildOf($this->getRecord());
  684.     }
  685.  
  686.     /**
  687.      * determines if node is leaf
  688.      *
  689.      * @return bool 
  690.      */
  691.     public function isLeaf()
  692.     {
  693.         return (($this->getRightValue($this->getLeftValue()) == 1);
  694.     }
  695.  
  696.     /**
  697.      * determines if node is root
  698.      *
  699.      * @return bool 
  700.      */
  701.     public function isRoot()
  702.     {
  703.         return ($this->getLeftValue(== 1);
  704.     }
  705.  
  706.     /**
  707.      * determines if node is equal to subject node
  708.      *
  709.      * @return bool 
  710.      */    
  711.     public function isEqualTo(Doctrine_Record $subj)
  712.     {
  713.         return (($this->getLeftValue(== $subj->getNode()->getLeftValue()) &&
  714.                 ($this->getRightValue(== $subj->getNode()->getRightValue()) && 
  715.                 ($this->getRootValue(== $subj->getNode()->getRootValue())
  716.                 );
  717.     }
  718.  
  719.     /**
  720.      * determines if node is child of subject node
  721.      *
  722.      * @return bool 
  723.      */
  724.     public function isDescendantOf(Doctrine_Record $subj)
  725.     {
  726.         return (($this->getLeftValue($subj->getNode()->getLeftValue()) &&
  727.                 ($this->getRightValue($subj->getNode()->getRightValue()) &&
  728.                 ($this->getRootValue(== $subj->getNode()->getRootValue()));
  729.     }
  730.  
  731.     /**
  732.      * determines if node is child of or sibling to subject node
  733.      *
  734.      * @return bool 
  735.      */
  736.     public function isDescendantOfOrEqualTo(Doctrine_Record $subj)
  737.     {
  738.         return (($this->getLeftValue(>= $subj->getNode()->getLeftValue()) &&
  739.                 ($this->getRightValue(<= $subj->getNode()->getRightValue()) &&
  740.                 ($this->getRootValue(== $subj->getNode()->getRootValue()));
  741.     }
  742.  
  743.     /**
  744.      * determines if node is valid
  745.      *
  746.      * @return bool 
  747.      */
  748.     public function isValidNode($record null)
  749.     {
  750.         if ($record === null{
  751.           return ($this->getRightValue($this->getLeftValue());
  752.         else if$record instanceof Doctrine_Record {
  753.           return ($record->getNode()->getRightValue($record->getNode()->getLeftValue());
  754.         else {
  755.           return false;
  756.         }
  757.     }
  758.  
  759.     /**
  760.      * deletes node and it's descendants
  761.      * @todo Delete more efficiently. Wrap in transaction if needed.
  762.      */
  763.     public function delete()
  764.     {
  765.         // TODO: add the setting whether or not to delete descendants or relocate children
  766.         $oldRoot $this->getRootValue();
  767.         $q $this->_tree->getBaseQuery();
  768.         
  769.         $baseAlias $this->_tree->getBaseAlias();
  770.         $componentName $this->_tree->getBaseComponent();
  771.  
  772.         $q $q->addWhere("$baseAlias.lft >= ? AND $baseAlias.rgt <= ?"array($this->getLeftValue()$this->getRightValue()));
  773.  
  774.         $q $this->record->getTable()->getTree()->returnQueryWithRootId($q$oldRoot);
  775.         
  776.         $coll $q->execute();
  777.  
  778.         $coll->delete();
  779.  
  780.         $first $this->getRightValue(1;
  781.         $delta $this->getLeftValue($this->getRightValue(1;
  782.         $this->shiftRLValues($first$delta$oldRoot);
  783.         
  784.         return true
  785.     }
  786.  
  787.     /**
  788.      * sets node's left and right values and save's it
  789.      *
  790.      * @param int     $destLeft     node left value
  791.      * @param int        $destRight    node right value
  792.      */    
  793.     private function insertNode($destLeft 0$destRight 0$destRoot 1)
  794.     {
  795.         $this->setLeftValue($destLeft);
  796.         $this->setRightValue($destRight);
  797.         $this->setRootValue($destRoot);
  798.         $this->record->save();    
  799.     }
  800.  
  801.     /**
  802.      * move node's and its children to location $destLeft and updates rest of tree
  803.      *
  804.      * @param int     $destLeft    destination left value
  805.      * @todo Wrap in transaction
  806.      */
  807.     private function updateNode($destLeft$levelDiff)
  808.     
  809.         $componentName $this->_tree->getBaseComponent();
  810.         $left $this->getLeftValue();
  811.         $right $this->getRightValue();
  812.         $rootId $this->getRootValue();
  813.  
  814.         $treeSize $right $left 1;
  815.  
  816.         // Make room in the new branch
  817.         $this->shiftRLValues($destLeft$treeSize$rootId);
  818.  
  819.         if ($left >= $destLeft)// src was shifted too?
  820.             $left += $treeSize;
  821.             $right += $treeSize;
  822.         }
  823.  
  824.         // update level for descendants
  825.         $q new Doctrine_Query();
  826.         $q $q->update($componentName)
  827.                 ->set($componentName '.level''level + ?')
  828.                 ->where($componentName '.lft > ? AND ' $componentName '.rgt < ?',
  829.                         array($levelDiff$left$right));
  830.         $q $this->_tree->returnQueryWithRootId($q$rootId);
  831.         $q->execute();
  832.         
  833.         // now there's enough room next to target to move the subtree
  834.         $this->shiftRLRange($left$right$destLeft $left$rootId);
  835.  
  836.         // correct values after source (close gap in old tree)
  837.         $this->shiftRLValues($right 1-$treeSize$rootId);
  838.  
  839.         $this->record->save();
  840.         $this->record->refresh();
  841.     }
  842.  
  843.     /**
  844.      * adds '$delta' to all Left and Right values that are >= '$first'. '$delta' can also be negative.
  845.      *
  846.      * @param int $first         First node to be shifted
  847.      * @param int $delta         Value to be shifted by, can be negative
  848.      */    
  849.     private function shiftRlValues($first$delta$rootId 1)
  850.     {
  851.         $qLeft  new Doctrine_Query();
  852.         $qRight new Doctrine_Query();
  853.  
  854.         // shift left columns
  855.         $componentName $this->_tree->getBaseComponent();
  856.         $qLeft $qLeft->update($componentName)
  857.                                 ->set($componentName '.lft''lft + ?')
  858.                                 ->where($componentName '.lft >= ?'array($delta$first));
  859.         
  860.         $qLeft $this->record->getTable()->getTree()->returnQueryWithRootId($qLeft$rootId);
  861.         
  862.         $resultLeft $qLeft->execute();
  863.         
  864.         // shift right columns
  865.         $resultRight $qRight->update($componentName)
  866.                                 ->set($componentName '.rgt''rgt + ?')
  867.                                 ->where($componentName '.rgt >= ?'array($delta$first));
  868.  
  869.         $qRight $this->record->getTable()->getTree()->returnQueryWithRootId($qRight$rootId);
  870.  
  871.         $resultRight $qRight->execute();
  872.     }
  873.  
  874.     /**
  875.      * adds '$delta' to all Left and Right values that are >= '$first' and <= '$last'.
  876.      * '$delta' can also be negative.
  877.      *
  878.      * @param int $first     First node to be shifted (L value)
  879.      * @param int $last     Last node to be shifted (L value)
  880.      * @param int $delta         Value to be shifted by, can be negative
  881.      */ 
  882.     private function shiftRlRange($first$last$delta$rootId 1)
  883.     {
  884.         $qLeft  new Doctrine_Query();
  885.         $qRight new Doctrine_Query();
  886.  
  887.         // shift left column values
  888.         $componentName $this->_tree->getBaseComponent();
  889.         $qLeft $qLeft->update($componentName)
  890.                                 ->set($componentName '.lft''lft + ?')
  891.                                 ->where($componentName '.lft >= ? AND ' $componentName '.lft <= ?'array($delta$first$last));
  892.         
  893.         $qLeft $this->record->getTable()->getTree()->returnQueryWithRootId($qLeft$rootId);
  894.  
  895.         $resultLeft $qLeft->execute();
  896.         
  897.         // shift right column values
  898.         $qRight $qRight->update($componentName)
  899.                                 ->set($componentName '.rgt''rgt + ?')
  900.                                 ->where($componentName '.rgt >= ? AND ' $componentName '.rgt <= ?'array($delta$first$last));
  901.  
  902.         $qRight $this->record->getTable()->getTree()->returnQueryWithRootId($qRight$rootId);
  903.  
  904.         $resultRight $qRight->execute();
  905.     }
  906.     
  907.     /**
  908.      * gets record's left value
  909.      *
  910.      * @return int 
  911.      */     
  912.     public function getLeftValue()
  913.     {
  914.         return $this->record->get('lft');
  915.     }
  916.  
  917.     /**
  918.      * sets record's left value
  919.      *
  920.      * @param int 
  921.      */     
  922.     public function setLeftValue($lft)
  923.     {
  924.         $this->record->set('lft'$lft);        
  925.     }
  926.  
  927.     /**
  928.      * gets record's right value
  929.      *
  930.      * @return int 
  931.      */     
  932.     public function getRightValue()
  933.     {
  934.         return $this->record->get('rgt');        
  935.     }
  936.  
  937.     /**
  938.      * sets record's right value
  939.      *
  940.      * @param int 
  941.      */    
  942.     public function setRightValue($rgt)
  943.     {
  944.         $this->record->set('rgt'$rgt);         
  945.     }
  946.  
  947.     /**
  948.      * gets level (depth) of node in the tree
  949.      *
  950.      * @return int 
  951.      */    
  952.     public function getLevel()
  953.     {
  954.         if (!isset($this->record['level'])) {
  955.             $baseAlias $this->_tree->getBaseAlias();
  956.             $componentName $this->_tree->getBaseComponent();
  957.             $q $this->_tree->getBaseQuery();
  958.             $q $q->addWhere("$baseAlias.lft < ? AND $baseAlias.rgt > ?"array($this->getLeftValue()$this->getRightValue()));
  959.  
  960.             $q $this->_tree->returnQueryWithRootId($q$this->getRootValue());
  961.             
  962.             $coll $q->execute();
  963.  
  964.             $this->record['level'count($collcount($coll0;
  965.         }
  966.         return $this->record['level'];
  967.     }
  968.  
  969.     /**
  970.      * get records root id value
  971.      *            
  972.      */     
  973.     public function getRootValue()
  974.     {
  975.         if ($this->_tree->getAttribute('hasManyRoots')) {
  976.             return $this->record->get($this->_tree->getAttribute('rootColumnName'));
  977.         }
  978.         return 1;
  979.     }
  980.  
  981.     /**
  982.      * sets records root id value
  983.      *
  984.      * @param int 
  985.      */
  986.     public function setRootValue($value)
  987.     {
  988.         if ($this->_tree->getAttribute('hasManyRoots')) {
  989.             $this->record->set($this->_tree->getAttribute('rootColumnName')$value);   
  990.         }    
  991.     }
  992. }