# Coverage for Doctrine_Tree_NestedSet

Back to coverage report

1 | <?php
2 | /*
3 | * $Id: NestedSet.php 3144 2007-11-13 15:52:45Z 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.org>.
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.org
29 | * @since 1.0
30 | * @version $Revision: 3144 $
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 = $this->_createBaseQuery();
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 | }