Coverage for Doctrine_Tree_NestedSet

Back to coverage report

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