Development

#1796: sfPropelActAsNestedSetBehavior.class.php

You must first sign up to be able to contribute.

Ticket #1796: sfPropelActAsNestedSetBehavior.class.php

File sfPropelActAsNestedSetBehavior.class.php, 36.8 kB (added by Goulwen.REBOUX, 2 years ago)

New deleteDescendants method

Line 
1 <?php
2 /*
3  * This file is part of the sfPropelActAsNestedSetBehavior package.
4  *
5  * (c) 2006-2007 Tristan Rivoallan <tristan@rivoallan.net>
6  *
7  * For the full copyright and license information, please view the LICENSE
8  * file that was distributed with this source code.
9  */
10
11 /**
12  * This behavior adds necessary logic for a propel model to behave as a nested set.
13  *
14  * To enable this behavior add this after Propel stub class declaration :
15  *
16  * <code>
17  *   $columns_map = array('left'   => MyClassPeer::TREE_LEFT,
18  *                        'right'  => MyClassPeer::TREE_RIGHT,
19  *                        'parent' => MyClassPeer::TREE_PARENT,
20  *                        'scope'  => MyClassPeer::TOPIC_ID);
21  *
22  *   sfPropelBehavior::add('MyClass', array('actasnestedset' => array('columns' => $columns_map)));
23  * </code>
24  *
25  * Column map values signification :
26  *
27  *  - left : Model column holding nested set left value for a row
28  *  - right : Model column holding nested set right value for a row
29  *  - parent : Model column holding row's parent id
30  * (this is necessary because we use adjacency list tree traversal for some methods)
31  *  - scope : Model column holding row's scope id. The scope is used to differenciate trees stored in
32  * the same table
33  * 
34  * @author   Tristan Rivoallan   <tristan@rivoallan.net>
35  * @author   Heltem              (http://propel.phpdb.org/trac/ticket/312)
36  * @author   Joe Simms           (http://www.symfony-project.com/forum/index.php/m/20657/)
37  *
38  * @see      http://www.symfony-project.com/trac/wiki/sfPropelActAsNestedSetBehaviorPlugin
39  */
40 class sfPropelActAsNestedSetBehavior
41 {
42
43 # -- PROPERTIES
44
45   /**
46    * Nested set related columns.
47    *
48    * @var array
49    */
50   private static $columns = array();
51  
52   /**
53    * Holds SQL queries propel objects that need to be processed before a node is saved.
54    * It acts as a FIFO stack.
55    *
56    * @var array
57    */
58   private $presave_stack = array();
59  
60   /**
61    * Holds SQL queries propel objects that need to be processed before a node is deleted.
62    * It acts as a FIFO stack.
63    *
64    * @var array
65    */
66   private $predelete_stack = array(); 
67  
68   /**
69     * Store infos required to update the tree after a node deletion
70     * @var array 
71     */
72   private $node_infos = array() ;
73  
74
75 # -- HOOKS
76
77   /**
78    * Runs necessary queries before node is saved.
79    *
80    * @param      BaseObject  $node
81    */
82   public function preSave(BaseObject $node)
83   {
84     $this->processPreSaveStack();
85   }
86
87   /**
88     * Runs queries to update tree after a node is deleted
89     */
90   public function postDelete ()
91   {
92     $peer_name = $this->node_infos['peer_name'] ;
93     $stub_name = $this->node_infos['stub_name'] ;
94     $scope_id_value = $this->node_infos['scope_id_value'] ;
95     $right_value = $this->node_infos['right_value'] ;
96     if (isset($this->node_infos['delta']))
97     {
98       $delta = $this->node_infos['delta'] ;
99     }
100     else
101     {
102       /* no child nodes */
103         $delta = 2 ;
104     }
105     $con = Propel::getConnection();
106     
107     /* Update the left value of nodes which have a left value greater than node
108        right's value */
109     $query = sprintf('UPDATE %s SET %s = %s - 2 WHERE %s = %d AND %s > (%d - %d)',
110                        constant("$peer_name::TABLE_NAME"),
111                        self::getColumnConstant($stub_name, 'left'),
112                        self::getColumnConstant($stub_name, 'left'),
113                        self::getColumnConstant($stub_name, 'scope'),
114                        $scope_id_value,
115                        self::getColumnConstant($stub_name, 'left'),
116                        $right_value,
117                        $delta);
118     $statement = $con->createStatement();
119     $statement->execute($query);
120     
121     /* Update the right value of nodes which have a right value greater than node
122        right's value */
123     $query = sprintf('UPDATE %s SET %s = %s - 2 WHERE %s = %d AND %s > (%d - %d + 1)',
124                        constant("$peer_name::TABLE_NAME"),
125                        self::getColumnConstant($stub_name, 'right'),
126                        self::getColumnConstant($stub_name, 'right'),
127                        self::getColumnConstant($stub_name, 'scope'),
128                        $scope_id_value,
129                        self::getColumnConstant($stub_name, 'right'),
130                        $right_value,
131                        $delta);
132     $con = Propel::getConnection();
133     $statement = $con->createStatement();
134     $statement->execute($query);
135   } // postDelete
136
137   /**
138     * Store informations required to update the tree after a node deletion
139     *
140     */
141   public function preDelete (BaseObject $node)
142   {
143     $this->node_infos['peer_name'] = get_class($node->getPeer());
144     $this->node_infos['stub_name'] = get_class($node);
145     $this->node_infos['scope_id_value'] = $node->getScopeIdValue() ;
146     $this->node_infos['right_value'] = $node->getRightValue() ;
147   } //
148
149 # -- GETTERS AND SETTERS
150
151   /**
152    * Proxy method used to access column holding nested set's left value getter.
153    *
154    * @param      BaseObject  $node
155    */
156   public function getLeftValue(BaseObject $node)
157   {
158     $getter = self::forgeMethodName($node, 'get', 'left');
159     return $node->$getter();
160   }
161
162   /**
163    * Proxy method used to access column holding nested set's left value setter.
164    *
165    * @param      BaseObject  $node
166    * @param      integer     $value
167    */
168   public function setLeftValue(BaseObject $node, $value)
169   {
170     $setter = self::forgeMethodName($node, 'set', 'left');
171     return $node->$setter($value);   
172   }
173
174   /**
175    * Proxy method used to access column holding nested set's right value setter.
176    *
177    * @param      BaseObject  $node
178    * @param      integer     $value
179    */
180   public function setRightValue(BaseObject $node, $value)
181   {
182     $setter = self::forgeMethodName($node, 'set', 'right');
183     return $node->$setter($value);   
184   }
185
186   /**
187    * Proxy method used to access column holding nested set's right value getter.
188    *
189    * @param      BaseObject  $node
190    */
191   public function getRightValue(BaseObject $node)
192   {
193     $getter = self::forgeMethodName($node, 'get', 'right');
194     return $node->$getter();   
195   }
196  
197   /**
198    * Proxy method used to access column holding nested set's scope value setter.
199    *
200    * @param      BaseObject  $node
201    * @param      integer     $value
202    */
203   public function setScopeIdValue(BaseObject $node, $value)
204   {
205     $setter = self::forgeMethodName($node, 'set', 'scope');
206     return $node->$setter($value);   
207   }
208  
209   /**
210    * Proxy method used to access column holding nested set's scope value getter.
211    *
212    * @param      BaseObject  $node
213    */
214   public function getScopeIdValue(BaseObject $node)
215   {
216     $getter = self::forgeMethodName($node, 'get', 'scope');
217     return $node->$getter();   
218   } 
219
220   /**
221    * Proxy method used to access column holding nested set's parent value setter.
222    *
223    * @param      BaseObject  $node
224    * @param      integer     $value
225    */
226   public function setParentIdValue(BaseObject $node, $value)
227   {
228     $setter = self::forgeMethodName($node, 'set', 'parent');
229     return $node->$setter($value);
230   }
231  
232   /**
233    * Proxy method used to access column holding nested set's parent value getter.
234    *
235    * @param      BaseObject  $node
236    */
237   public function getParentIdValue(BaseObject $node)
238   {
239     $getter = self::forgeMethodName($node, 'get', 'parent');
240     return $node->$getter();   
241   } 
242
243 # -- NESTED SETS PUBLIC API
244
245   /**
246    * Sets node properties to make it a root node.
247    *
248    * @param      BaseObject  $node
249    * @throws     Exception   When trying to turn an existing non-root node into a root node
250    */
251   public function makeRoot(BaseObject $node)
252   {
253     if ((bool)$node->getLeftValue())
254     {
255       throw new Exception('Cannot turn an existing node into a root node.');
256     }
257     
258     $node->setLeftValue(1);
259     $node->setRightValue(2);
260   }
261
262 # ---- INSERTION METHODS
263  
264   /**
265    * Inserts node as first child of given node.
266    *
267    * @param      BaseObject     $node
268    * @param      BaseObject     $dest_node
269    */
270   public function insertAsFirstChildOf(BaseObject $node, BaseObject $dest_node)
271   {
272     $node->setLeftValue($dest_node->getLeftValue() + 1);
273     $node->setRightValue($dest_node->getLeftValue() + 2);
274     $node->setScopeIdValue($dest_node->getScopeIdValue());
275     $node->setParentIdValue($dest_node->getPrimaryKey());
276     $this->addPreSaveStackEntries($this->shiftRLValues(get_class($node->getPeer()),
277                                                        $node->getLeftValue(),
278                                                        2,
279                                                        $dest_node->getScopeIdValue()));
280     
281     $dest_node->setRightValue($dest_node->getRightValue() + 2);
282     $this->addPreSaveStackEntries(array($dest_node));
283   }
284
285   /**
286    * Inserts node as last child of given node.
287    *
288    * @param      BaseObject    $node
289    * @param      BaseObject    $dest_node
290    */
291   public function insertAsLastChildOf(BaseObject $node, BaseObject $dest_node)
292   {
293     $node->setLeftValue($dest_node->getRightValue());
294     $node->setRightValue($dest_node->getRightValue() + 1);
295     $node->setScopeIdValue($dest_node->getScopeIdValue());
296     $node->setParentIdValue($dest_node->getPrimaryKey());
297     $this->addPreSaveStackEntries($this->shiftRLValues(get_class($node->getPeer()),
298                                   $node->getLeftValue(),
299                                   2,
300                                   $dest_node->getScopeIdValue()));
301     
302     $dest_node->setRightValue($dest_node->getRightValue() + 2);
303     $this->addPreSaveStackEntries(array($dest_node));
304   }
305  
306   /**
307    * Inserts node as next sibling of given node.
308    *
309    * @param      BaseObject  $node
310    * @param      BaseObject  $dest_node
311    * @throws     Exception   When trying to create a sibling to a root node
312    */
313   public function insertAsNextSiblingOf(BaseObject $node, BaseObject $dest_node)
314   {
315     if ($dest_node->isRoot())
316     {
317       $msg = 'Root nodes cannot have siblings';
318       throw new Exception($msg);
319     }
320
321     $node->setLeftValue($dest_node->getRightValue() + 1);
322     $node->setRightValue($dest_node->getRightValue() + 2);
323     $node->setScopeIdValue($dest_node->getScopeIdValue());
324     $node->setParentIdValue($dest_node->getParentIdValue());
325     
326     $this->addPreSaveStackEntries($this->shiftRLValues(get_class($node->getPeer()),
327                                   $node->getLeftValue(),
328                                   2,
329                                   $dest_node->getScopeIdValue()));
330   }
331
332   /**
333    * Inserts node as previous sibling of given node.
334    *
335    * @param      BaseObject     $node
336    * @param      BaseObject     $dest_node
337    * @throws     Exception      When trying to create a sibling to a root node
338    */
339
340   public function insertAsPrevSiblingOf(BaseObject $node, BaseObject $dest_node)
341   {
342     if ($dest_node->isRoot())
343     {
344       $msg = 'Root nodes cannot have siblings';
345       throw new Exception($msg);
346     }
347
348     $node->setLeftValue($dest_node->getLeftValue());
349     $node->setRightValue($dest_node->getLeftValue() + 1);
350     $node->setScopeIdValue($dest_node->getScopeIdValue());
351     $node->setParentIdValue($dest_node->getParentIdValue());
352     
353     $this->addPreSaveStackEntries($this->shiftRLValues(get_class($node->getPeer()),
354                                   $node->getLeftValue(),
355                                   2,
356                                   $dest_node->getScopeIdValue()));
357
358     $dest_node->setLeftValue($dest_node->getLeftValue() + 2);
359     $dest_node->setRightValue($dest_node->getRightValue() + 2);
360     $this->addPreSaveStackEntries(array($dest_node));
361   }
362
363   /**
364    * Inserts node as parent of given node.
365    *
366    * @param      BaseObject     $node
367    * @param      BaseObject     $dest_node
368    * @throws     Exception      When trying to insert node as parent of a root node
369    */
370   public function insertAsParentOf(BaseObject $node, BaseObject $dest_node)
371   {
372     if ($dest_node->isRoot())
373     {
374       $msg = 'Impossible to insert a node as parent of a root node';
375       throw new Exception($msg);
376     }
377
378     $peer_name = get_class($node->getPeer());
379
380     $this->addPreSaveStackEntries(self::shiftRLValues($peer_name, $dest_node->getLeftValue(), 1, $dest_node->getScopeIdValue()));
381     $this->addPreSaveStackEntries(self::shiftRLValues($peer_name, $dest_node->getRightValue() + 2, -1, $dest_node->getScopeIdValue()));
382     
383     $node->setLeftValue($dest_node->getLeftValue());
384     $node->setRightValue($dest_node->getRightValue() + 2);
385
386     $previous_parent = $dest_node->getParentIdValue();
387     $node->setParentIdValue($previous_parent);
388     $dest_node->setParentIdValue($node->getPrimaryKey());
389
390     $this->addPreSaveStackEntries(array($dest_node));
391     
392   }
393
394 # ---- INFORMATIONAL METHODS
395
396   /**
397    * Returns true if given node as one or several children.
398    *
399    * @param      BaseObject  $node
400    * @return     bool
401    */
402   public function hasChildren(BaseObject $node)
403   {
404     return $node->getRightValue() - $node->getLeftValue() > 1;
405   }
406
407   /**
408    * Returns true if given node is a root node.
409    *
410    * @param      BaseObject      $node
411    * @return     bool
412    */
413   public function isRoot(BaseObject $node)
414   {
415     return $node->getLeftValue() == 1;
416   }
417
418   /**
419    * Returns true if given node has a parent node.
420    *
421    * @param      BaseObject      $node
422    * @return     bool
423    */
424   public function hasParent(BaseObject $node)
425   {
426     return $node->getParentIdValue();
427   }
428
429   /**
430    * Returns true if given node has a next sibling.
431    *
432    * @param      BaseObject      $node
433    * @return     bool
434    */
435   public function hasNextSibling(BaseObject $node)
436   {
437     return (bool)$node->retrieveNextSibling();
438   }
439
440   /**
441    * Returns true if given node has a previous sibling.
442    *
443    * @param      BaseObject      $node
444    * @return     bool
445    */
446   public function hasPrevSibling(BaseObject $node)
447   {
448     return (bool)$node->retrievePrevSibling();
449   }
450
451   /**
452    * Returns true if given node does not have children.
453    *
454    * @param      BaseObject      $node
455    * @return     bool
456    */
457   public function isLeaf(BaseObject $node)
458   {
459     return $node->getRightValue() - $node->getLeftValue() == 1;
460   }
461
462   /**
463    * Returns true if given node is identical to node.
464    *
465    * @param      BaseObject      $node
466    * @param      BaseObject      $compared_node
467    * @return     bool
468    */
469   public function isEqualTo(BaseObject $node, BaseObject $compared_node)
470   {
471     return ($node->getLeftValue()        === $compared_node->getLeftValue()
472             && $node->getRightValue()    === $compared_node->getRightValue()
473             && $node->getScopeIdValue()  === $compared_node->getScopeIdValue()
474             && $node->getParentIdValue() === $compared_node->getParentIdValue());
475   }
476
477   /**
478    * Returns true if given node is parent of node.
479    *
480    * @param      BaseObject      $node
481    * @param      BaseObject      $parent_node
482    */
483   public function isChildOf(BaseObject $node, BaseObject $parent_node)
484   {
485     return ($node->getParentIdValue() === $parent_node->getPrimaryKey()
486             && $node->getScopeIdValue() === $parent_node->getScopeIdValue());
487   }
488
489   /**
490    * Returns given node number of direct children.
491    *
492    * @param      BaseObject  $node
493    * @return     integer
494    */
495   public function getNumberOfChildren(BaseObject $node)
496   {
497     $peer_name = get_class($node->getPeer());
498     $con = Propel::getConnection();
499     $scope_sql = '';
500     if (!is_null($node->getScopeIdValue()))
501     {
502       $scope_sql = sprintf(' AND %s = %d',
503                            self::getColumnConstant(get_class($node), 'scope'),
504                            $node->getScopeIdValue());
505     }
506     
507     $sql = sprintf('SELECT COUNT(*) AS num_children FROM %s WHERE %s = %s %s',
508       constant("$peer_name::TABLE_NAME"),
509       self::getColumnConstant(get_class($node), 'parent'),
510       $node->getPrimaryKey(),
511       $scope_sql
512     );
513
514     $stmt = $con->createStatement();
515     $rs = $stmt->executeQuery($sql);
516     $rs->next();
517     
518     return $rs->getInt('num_children');
519   }
520
521   /**
522    * Returns given node number of descendants (n level).
523    *
524    * @param      BaseObject  $node
525    * @return     integer
526    */
527   public function getNumberOfDescendants(BaseObject $node)
528   {
529     $right = $node->getRightValue();
530     $left = $node->getLeftValue();
531     $num = ($right - $left - 1) / 2;
532     
533     return $num;
534   }
535
536   /**
537    * Returns given node level.
538    *
539    * @param      BaseObject     $node
540    * @return     integer
541    */
542   public function getLevel(BaseObject $node)
543   {
544     if (!isset($node->level))
545     {
546       $peer_name = get_class($node->getPeer());
547  
548       $con = Propel::getConnection();
549       $stmt = $con->createStatement();
550       
551       $scope_sql = '';
552       if (!is_null($node->getScopeIdValue())) {
553         $scope_sql = sprintf(' AND %s = %d', self::getColumnConstant(get_class($node), 'scope'), $node->getScopeIdValue());
554       }
555       
556       $sql = sprintf('SELECT COUNT(*) AS level FROM %s WHERE (%s < %d AND %s > %d) %s',
557         constant("$peer_name::TABLE_NAME"),
558         self::getColumnConstant(get_class($node), 'left'),
559         $node->getLeftValue(),
560         self::getColumnConstant(get_class($node), 'right'),
561         $node->getRightValue(),
562         $scope_sql
563       );
564       
565       $rs = $stmt->executeQuery($sql);
566       $rs->next();
567       
568       $level = $rs->getInt('level');
569       $this->level = $level;
570     }
571     else
572     {
573       $level = $node->level;
574     }
575     
576     return $level;
577   }
578  
579   /**
580    * Sets node level.
581    *
582    * @param    BaseObject    $node
583