Source for file NestedSet.php

Documentation is available at NestedSet.php

  1. <?php
  2. /*
  3.  *  $Id: NestedSet.php 2230 2007-08-14 16:37:48Z Jonathan.Wage $
  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_Tree_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: 2230 $
  30.  * @author      Joe Simms <joe.simms@websites4.com>
  31.  */
  32. {
  33.     private $_baseQuery;
  34.     private $_baseAlias = "base";
  35.     
  36.     /**
  37.      * constructor, creates tree with reference to table and sets default root options
  38.      *
  39.      * @param object $table                     instance of Doctrine_Table
  40.      * @param array $options                    options
  41.      */
  42.     public function __construct(Doctrine_Table $table$options)
  43.     {
  44.         // set default many root attributes
  45.         $options['hasManyRoots'= isset($options['hasManyRoots']$options['hasManyRoots'false;
  46.         if($options['hasManyRoots'])
  47.             $options['rootColumnName'= isset($options['rootColumnName']$options['rootColumnName''root_id';
  48.   
  49.         parent::__construct($table$options);
  50.     }
  51.     
  52.     /**
  53.      * used to define table attributes required for the NestetSet implementation
  54.      * adds lft and rgt columns for corresponding left and right values
  55.      *
  56.      */
  57.     public function setTableDefinition()
  58.     {
  59.         if ($root $this->getAttribute('rootColumnName')) {
  60.             $this->table->setColumn($root'integer'4);
  61.         }
  62.  
  63.         $this->table->setColumn('lft''integer'4);
  64.         $this->table->setColumn('rgt''integer'4);
  65.         $this->table->setColumn('level''integer'2);
  66.     }
  67.  
  68.     /**
  69.      * creates root node from given record or from a new record
  70.      *
  71.      * @param object $record        instance of Doctrine_Record
  72.      */
  73.     public function createRoot(Doctrine_Record $record null)
  74.     {
  75.         if $record{
  76.             $record $this->table->create();
  77.         }
  78.  
  79.         // if tree is many roots, and no root id has been set, then get next root id
  80.         if ($root $this->getAttribute('hasManyRoots'&& $record->getNode()->getRootValue(<= 0{
  81.             $record->getNode()->setRootValue($this->getNextRootId());
  82.         }
  83.  
  84.         $record->set('lft''1');
  85.         $record->set('rgt''2');
  86.         $record->set('level'0);
  87.  
  88.         $record->save();
  89.  
  90.         return $record;
  91.     }
  92.  
  93.     /**
  94.      * returns root node
  95.      *
  96.      * @return object $record        instance of Doctrine_Record
  97.      * @deprecated Use fetchRoot()
  98.      */
  99.     public function findRoot($rootId 1)
  100.     {
  101.         return $this->fetchRoot($rootId);
  102.     }
  103.     
  104.     /**
  105.      * Fetches a/the root node.
  106.      *
  107.      * @param integer $rootId 
  108.      */
  109.     public function fetchRoot($rootId 1)
  110.     {
  111.         $q $this->getBaseQuery();
  112.         $q $q->addWhere($this->_baseAlias . '.lft = ?'1);
  113.         
  114.         // if tree has many roots, then specify root id
  115.         $q $this->returnQueryWithRootId($q$rootId);
  116.         $data $q->execute();
  117.  
  118.         if (count($data<= 0{
  119.             return false;
  120.         }
  121.  
  122.         if ($data instanceof Doctrine_Collection{
  123.             $root $data->getFirst();
  124.             $root['level'0;
  125.         else if (is_array($data)) {
  126.             $root array_shift($data);
  127.             $root['level'0;
  128.         else {
  129.             throw new Doctrine_Tree_Exception("Unexpected data structure returned.");
  130.         }
  131.  
  132.         return $root;
  133.     }
  134.  
  135.     /**
  136.      * Fetches a tree.
  137.      *
  138.      * @param array $options  Options
  139.      * @return mixed          The tree or FALSE if the tree could not be found.
  140.      */
  141.     public function fetchTree($options array())
  142.     {
  143.         // fetch tree
  144.         $q $this->getBaseQuery();
  145.  
  146.         $q $q->addWhere($this->_baseAlias . ".lft >= ?"1);
  147.  
  148.         // if tree has many roots, then specify root id
  149.         $rootId = isset($options['root_id']$options['root_id''1';
  150.         if (is_array($rootId)) {
  151.             $q->addOrderBy($this->_baseAlias . "." $this->getAttribute('rootColumnName'.
  152.                     ", " $this->_baseAlias . ".lft ASC");
  153.         else {
  154.             $q->addOrderBy($this->_baseAlias . ".lft ASC");
  155.         }
  156.         
  157.         $q $this->returnQueryWithRootId($q$rootId);
  158.         $tree $q->execute();
  159.         
  160.         if (count($tree<= 0{
  161.             return false;
  162.         }
  163.         
  164.         return $tree;
  165.     }
  166.  
  167.     /**
  168.      * Fetches a branch of a tree.
  169.      *
  170.      * @param mixed $pk              primary key as used by table::find() to locate node to traverse tree from
  171.      * @param array $options         Options.
  172.      * @return mixed                 The branch or FALSE if the branch could not be found.
  173.      * @todo Only fetch the lft and rgt values of the initial record. more is not needed.
  174.      */
  175.     public function fetchBranch($pk$options array())
  176.     {
  177.         $record $this->table->find($pk);
  178.         if ($record instanceof Doctrine_Record|| !$record->exists()) {
  179.             // TODO: if record doesn't exist, throw exception or similar?
  180.             return false;
  181.         }
  182.         //$depth = isset($options['depth']) ? $options['depth'] : null;
  183.         
  184.         $q $this->getBaseQuery();
  185.         $params array($record->get('lft')$record->get('rgt'));
  186.         $q->addWhere($this->_baseAlias . ".lft >= ? AND " $this->_baseAlias . ".rgt <= ?"$params)
  187.                 ->addOrderBy($this->_baseAlias . ".lft asc");
  188.         $q $this->returnQueryWithRootId($q$record->getNode()->getRootValue());
  189.         return $q->execute();
  190.     }
  191.  
  192.     /**
  193.      * Fetches all root nodes. If the tree has only one root this is the same as
  194.      * fetchRoot().
  195.      *
  196.      * @return mixed  The root nodes.
  197.      */
  198.     public function fetchRoots()
  199.     {
  200.         $q $this->getBaseQuery();
  201.         $q $q->addWhere($this->_baseAlias . '.lft = ?'1);
  202.         return $q->execute();
  203.     }
  204.  
  205.     /**
  206.      * calculates the next available root id
  207.      *
  208.      * @return integer 
  209.      */
  210.     public function getNextRootId()
  211.     {
  212.         return $this->getMaxRootId(1;
  213.     }
  214.  
  215.     /**
  216.      * calculates the current max root id
  217.      *
  218.      * @return integer 
  219.      */    
  220.     public function getMaxRootId()
  221.     {      
  222.         $component $this->table->getComponentName();
  223.         $column    $this->getAttribute('rootColumnName');
  224.  
  225.         // cannot get this dql to work, cannot retrieve result using $coll[0]->max
  226.         //$dql = "SELECT MAX(c.$column) FROM $component c";
  227.         
  228.         $dql 'SELECT c.' $column ' FROM ' $component ' c ORDER BY c.' $column ' DESC LIMIT 1';
  229.   
  230.         $coll $this->table->getConnection()->query($dql);
  231.   
  232.         $max $coll[0]->get($column);
  233.   
  234.         $max !is_null($max$max 0;
  235.   
  236.         return $max;      
  237.     }
  238.  
  239.     /**
  240.      * returns parsed query with root id where clause added if applicable
  241.      *
  242.      * @param object    $query    Doctrine_Query
  243.      * @param integer   $root_id  id of destination root
  244.      * @return object   Doctrine_Query 
  245.      */
  246.     public function returnQueryWithRootId($query$rootId 1)
  247.     {
  248.         if ($root $this->getAttribute('rootColumnName')) {
  249.             if (is_array($rootId)) {
  250.                $query->addWhere($root ' IN (' implode(','array_fill(0count($rootId)'?')) ')',
  251.                        $rootId);
  252.             else {
  253.                $query->addWhere($root ' = ?'$rootId)
  254.             }
  255.         }
  256.  
  257.         return $query;
  258.     }
  259.     
  260.     /**
  261.      * Enter description here...
  262.      *
  263.      * @param array $options 
  264.      * @return unknown 
  265.      */
  266.     public function getBaseQuery()
  267.     {
  268.         if (!isset($this->_baseQuery)) {
  269.             $this->_baseQuery = $this->_createBaseQuery();
  270.         }
  271.         return $this->_baseQuery->copy();
  272.     }
  273.     
  274.     /**
  275.      * Enter description here...
  276.      *
  277.      */
  278.     public function getBaseAlias()
  279.     {
  280.         return $this->_baseAlias;
  281.     }
  282.     
  283.     /**
  284.      * Enter description here...
  285.      *
  286.      */
  287.     private function _createBaseQuery()
  288.     {
  289.         $this->_baseAlias = "base";
  290.         $q new Doctrine_Query();
  291.         $q->select($this->_baseAlias . ".*")->from($this->getBaseComponent(" " $this->_baseAlias);
  292.         return $q;
  293.     }
  294.     
  295.     /**
  296.      * Enter description here...
  297.      *
  298.      * @param Doctrine_Query $query 
  299.      */
  300.     public function setBaseQuery(Doctrine_Query $query)
  301.     {
  302.         $this->_baseAlias = $query->getRootAlias();
  303.         $query->addSelect($this->_baseAlias . ".lft, " $this->_baseAlias . ".rgt, "$this->_baseAlias . ".level");
  304.         if ($this->getAttribute('rootColumnName')) {
  305.             $query->addSelect($this->_baseAlias . "." $this->getAttribute('rootColumnName'));
  306.         }
  307.         $this->_baseQuery = $query;
  308.     }
  309.     
  310.     /**
  311.      * Enter description here...
  312.      *
  313.      */
  314.     public function resetBaseQuery()
  315.     {
  316.         $this->_baseQuery = null;
  317.     }
  318.     
  319.     /**
  320.      * Enter description here...
  321.      *
  322.      * @param unknown_type $graph 
  323.      */
  324.     /*
  325.     public function computeLevels($tree)
  326.     {
  327.         $right = array();
  328.         $isArray = is_array($tree);
  329.         $rootColumnName = $this->getAttribute('rootColumnName');
  330.         
  331.         for ($i = 0, $count = count($tree); $i < $count; $i++) {
  332.             if ($rootColumnName && $i > 0 && $tree[$i][$rootColumnName] != $tree[$i-1][$rootColumnName]) {
  333.                 $right = array();
  334.             }
  335.             
  336.             if (count($right) > 0) {
  337.                 while (count($right) > 0 && $right[count($right)-1] < $tree[$i]['rgt']) {
  338.                     //echo count($right);
  339.                     array_pop($right);
  340.                 }
  341.             }
  342.      
  343.             if ($isArray) {
  344.                 $tree[$i]['level'] = count($right);
  345.             } else {
  346.                 $tree[$i]->getNode()->setLevel(count($right));
  347.             }
  348.     
  349.             $right[] = $tree[$i]['rgt'];
  350.         }
  351.         return $tree;
  352.     }
  353.     */
  354. }