Coverage for Doctrine_Tree_NestedSet

Back to coverage report

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