Coverage for Doctrine_Tree_NestedSet

Back to coverage report

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