44use \Illuminate \Database \Query \Builder ;
55use Exception ;
66
7- class Node extends Eloquent
8- {
7+ class Node extends Eloquent {
98 /**
109 * The name of "lft" column.
1110 *
@@ -94,7 +93,7 @@ public function descendants()
9493 {
9594 $ query = $ this ->newQuery ();
9695
97- return $ query ->whereBetween (' lft ' , array ($ this ->getLft () + 1 , $ this ->getRgt ()));
96+ return $ query ->whereBetween (static :: LFT , array ($ this ->getLft () + 1 , $ this ->getRgt ()));
9897 }
9998
10099 /**
@@ -162,7 +161,8 @@ public function scopeWithoutRoot($query)
162161 */
163162 public function path ()
164163 {
165- if (!$ this ->exists ) {
164+ if (!$ this ->exists )
165+ {
166166 throw new Exception ("Cannot query for path to non-existing node. " );
167167 }
168168
@@ -216,7 +216,7 @@ public function prepend(Node $node)
216216 */
217217 public function appendTo (Node $ parent )
218218 {
219- return $ this ->insertAt ($ parent ->attributes [static ::RGT ], $ parent );
219+ return $ this ->checkTarget ( $ parent )-> insertAt ($ parent ->attributes [static ::RGT ], $ parent );
220220 }
221221
222222 /**
@@ -228,7 +228,7 @@ public function appendTo(Node $parent)
228228 */
229229 public function prependTo (Node $ parent )
230230 {
231- return $ this ->insertAt ($ parent ->attributes [static ::LFT ] + 1 , $ parent );
231+ return $ this ->checkTarget ( $ parent )-> insertAt ($ parent ->attributes [static ::LFT ] + 1 , $ parent );
232232 }
233233
234234 /**
@@ -264,14 +264,15 @@ public function before(Node $node)
264264 * @param self::BEFORE|self::AFTER $dir
265265 *
266266 * @return Node
267+ * @throws Exception If $node is not a valid target.
268+ * @throws Exception If $node is the root.
267269 */
268270 protected function beforeOrAfter (Node $ node , $ dir )
269271 {
270- if (!$ node ->exists ) {
271- throw new Exception ("Cannot insert node $ dir node that does not exists. " );
272- }
272+ $ this ->checkTarget ($ node );
273273
274- if ($ node ->isRoot ()) {
274+ if ($ node ->isRoot ())
275+ {
275276 throw new Exception ("Cannot insert node $ dir root node. " );
276277 }
277278
@@ -280,18 +281,38 @@ protected function beforeOrAfter(Node $node, $dir)
280281 return $ this ->insertAt ($ position , $ node ->parent );
281282 }
282283
284+ /**
285+ * Check if target node is saved.
286+ *
287+ * @param Node $node
288+ *
289+ * @return Node
290+ * @throws Exception If target fails conditions.
291+ */
292+ protected function checkTarget (Node $ node )
293+ {
294+ if (!$ node ->exists || $ node ->isDirty (static ::LFT ))
295+ {
296+ throw new Exception ("Target node is updated but not saved. " );
297+ }
298+
299+ return $ this ;
300+ }
301+
283302 /**
284303 * Insert node at specific position and related parent.
285304 *
286305 * @param int $position
287306 * @param Node $parent
288307 *
289308 * @return Node
309+ * @throws Exceptino If node is inserted in one of it's descendants
290310 */
291311 protected function insertAt ($ position , Node $ parent )
292312 {
293- if ($ this ->exists && $ this ->getLft () < $ position && $ position < $ this ->getRgt ()) {
294- throw new Exception ("Cannot insert node into itself. " );
313+ if ($ this ->exists && $ this ->getLft () < $ position && $ position < $ this ->getRgt ())
314+ {
315+ throw new Exception ("Trying to insert node into one of it's descendants. " );
295316 }
296317
297318 $ height = $ this ->getNodeHeight ();
@@ -317,17 +338,23 @@ protected function insertAt($position, Node $parent)
317338 */
318339 public function fireModelEvent ($ event , $ halt = true )
319340 {
320- if ($ event === 'creating ' or $ event === 'updating ' and $ this ->isDirty (static ::LFT )) {
321- if (!$ this ->updateTree ()) {
341+ if ($ event === 'creating ' )
342+ {
343+ if (!isset ($ this ->attributes [static ::LFT ]) || !$ this ->updateTree ())
344+ {
322345 return false ;
323346 }
324347 }
325348
326- if ($ event === 'deleting ' and $ this ->isRoot ()) {
327- return false ;
349+ if ($ event === 'updating ' && $ this ->isDirty (static ::LFT ))
350+ {
351+ if (!$ this ->updateTree ()) return false ;
328352 }
329353
330- if ($ event === 'deleted ' and !$ this ->softDelete ) {
354+ if ($ event === 'deleting ' && $ this ->isRoot ()) return false ;
355+
356+ if ($ event === 'deleted ' && !$ this ->softDelete )
357+ {
331358 $ this ->processDeletedNode ();
332359 }
333360
@@ -345,7 +372,8 @@ protected function updateTree()
345372 $ rgt = $ this ->attributes [static ::RGT ];
346373 $ height = $ rgt - $ lft + 1 ;
347374
348- if ($ this ->exists ) {
375+ if ($ this ->exists )
376+ {
349377 $ oldLft = $ this ->original [static ::LFT ];
350378 $ oldRgt = $ this ->original [static ::RGT ];
351379
@@ -358,7 +386,8 @@ protected function updateTree()
358386 // Node is going down.
359387 // Other nodes are going up on its place.
360388 static ::shiftNodes (-$ height , $ oldRgt + 1 , $ lft - 1 , $ this );
361- } else {
389+ } else
390+ {
362391 $ shiftAmount = $ lft - $ oldLft ;
363392
364393 // Node is going up.
@@ -382,11 +411,19 @@ protected function updateTree()
382411 */
383412 protected function processDeletedNode ()
384413 {
414+ $ lft = $ this ->getLft ();
415+ $ rgt = $ this ->getRgt ();
416+
417+ // DBMS with support of foreign keys will remove descendant nodes automatically
418+ $ this ->descendants ()->delete ();
419+
385420 // We cannot use getNodeHeight because it always return 2 for non-existing
386421 // nodes.
387- $ height = $ this ->attributes [static ::RGT ] - $ this ->attributes [static ::LFT ] + 1 ;
422+ $ height = $ rgt - $ lft + 1 ;
423+
424+ $ this ->shiftNodes (-$ height , $ rgt + 1 , null , $ this );
388425
389- $ this ->shiftNodes (- $ height , $ this ->attributes [static ::RGT ] + 1 , null , $ this );
426+ unset( $ this ->attributes [ static :: LFT ] , $ this ->attributes [static ::RGT ]);
390427 }
391428
392429 /**
@@ -438,13 +475,9 @@ protected function revealNodes($shiftAmount)
438475 */
439476 static protected function shiftNodes ($ amount , $ from , $ to , $ instance = null )
440477 {
441- if ($ amount == 0 || $ from === null && $ to === null ) {
442- return ;
443- }
478+ if ($ amount == 0 || $ from === null && $ to === null ) return ;
444479
445- if ($ instance === null ) {
446- $ instance = new static ;
447- }
480+ if ($ instance === null ) $ instance = new static ;
448481
449482 $ query = $ instance ->newQueryWithDeleted ()->getQuery ();
450483 $ method = $ amount > 0 ? 'increment ' : 'decrement ' ;
@@ -453,13 +486,14 @@ static protected function shiftNodes($amount, $from, $to, $instance = null)
453486 static ::shiftNodesColumn ($ query , static ::LFT , $ method , $ amount , $ from , $ to );
454487 static ::shiftNodesColumn ($ query , static ::RGT , $ method , $ amount , $ from , $ to );
455488
456- // Update parent of the instance to support inserting multiple nodes into
457- // single parent.
458- if (isset ($ instance ->relations ['parent ' ])) {
459- $ parent = $ instance ->relations ['parent ' ];
489+ // Update all ancestors (if any) of the instance to support inserting
490+ // multiple nodes into single parent.
491+ while (isset ($ instance ->relations ['parent ' ]))
492+ {
493+ $ instance = $ instance ->relations ['parent ' ];
460494
461- $ parent ->shiftColumn (static ::LFT , $ amount , $ from , $ to );
462- $ parent ->shiftColumn (static ::RGT , $ amount , $ from , $ to );
495+ $ instance ->shiftColumn (static ::LFT , $ amount , $ from , $ to );
496+ $ instance ->shiftColumn (static ::RGT , $ amount , $ from , $ to );
463497 }
464498 }
465499
@@ -480,13 +514,9 @@ static protected function shiftNodesColumn(Builder $query, $column, $method, $am
480514 $ query ->wheres = array ();
481515 $ query ->setBindings (array ());
482516
483- if ($ from === null ) {
484- $ query ->where ($ column , '<= ' , $ to );
485- } elseif ($ to === null ) {
486- $ query ->where ($ column , '>= ' , $ from );
487- } else {
488- $ query ->whereBetween ($ column , array ($ from , $ to ));
489- }
517+ if ($ from === null ) $ query ->where ($ column , '<= ' , $ to );
518+ elseif ($ to === null ) $ query ->where ($ column , '>= ' , $ from );
519+ else $ query ->whereBetween ($ column , array ($ from , $ to ));
490520
491521 return $ query ->$ method ($ column , $ amount );
492522 }
@@ -502,9 +532,7 @@ static protected function shiftNodesColumn(Builder $query, $column, $method, $am
502532 */
503533 protected function shift ($ amount , $ from , $ to )
504534 {
505- if ($ from === null && $ to === null || $ amount == 0 ) {
506- return ;
507- }
535+ if ($ from === null && $ to === null || $ amount == 0 ) return ;
508536
509537 $ this ->shiftColumn (static ::LFT , $ amount , $ from , $ to );
510538 $ this ->shiftColumn (static ::RGT , $ amount , $ from , $ to );
@@ -545,7 +573,8 @@ protected function reveal($shiftAmount, $from, $to)
545573 {
546574 $ lft = $ this ->getLft ();
547575
548- if ($ from <= $ lft && $ lft <= $ to ) {
576+ if ($ from <= $ lft && $ lft <= $ to )
577+ {
549578 $ this ->attributes [static ::LFT ] += $ shiftAmount ;
550579 $ this ->attributes [static ::RGT ] += $ shiftAmount ;
551580 }
@@ -564,9 +593,7 @@ protected function reveal($shiftAmount, $from, $to)
564593 */
565594 static protected function hideOrRevealNodes ($ from , $ to , $ shiftAmount = 0 , $ instance = null )
566595 {
567- if ($ instance === null ) {
568- $ instance = new static ;
569- }
596+ if ($ instance === null ) $ instance = new static ;
570597
571598 $ query = $ instance ->newQueryWithDeleted ()->getQuery ();
572599 $ grammar = $ query ->getGrammar ();
@@ -577,9 +604,11 @@ static protected function hideOrRevealNodes($from, $to, $shiftAmount = 0, $insta
577604 // and "revealing" is putting nodes back in database but in new position based on shiftAmount
578605 $ updateValues = array ();
579606
580- if ($ shiftAmount == 0 ) {
607+ if ($ shiftAmount == 0 )
608+ {
581609 $ updateValues [static ::LFT ] = $ query ->raw ('- ' .$ grammar ->wrap (static ::LFT ));
582- } else {
610+ } else
611+ {
583612 $ shiftAmountStr = $ shiftAmount > 0 ? '+ ' .$ shiftAmount : $ shiftAmount ;
584613
585614 $ updateValues [static ::LFT ] = $ query ->raw ('- ' .$ grammar ->wrap (static ::LFT ).$ shiftAmountStr );
@@ -624,9 +653,7 @@ public function newCollection(array $models = array())
624653 */
625654 public function getNodeHeight ()
626655 {
627- if (!$ this ->exists ) {
628- return 2 ;
629- }
656+ if (!$ this ->exists ) return 2 ;
630657
631658 return $ this ->attributes [static ::RGT ] - $ this ->attributes [static ::LFT ] + 1 ;
632659 }
@@ -647,14 +674,12 @@ public function getDescendantCount()
647674 * Behind the scenes node is appended to found parent node.
648675 *
649676 * @param int $value
677+ * @throws Exception If parent node doesn't exists
650678 */
651- public function setParentId ($ value )
679+ public function setParentIdAttribute ($ value )
652680 {
653- if ($ this ->isRoot ()) {
654- throw new Exception ("Cannot change parent of root node. " );
655- }
656-
657- if ($ this ->attributes [static ::PARENT_ID ] != $ value ) {
681+ if (!isset ($ this ->attributes [static ::PARENT_ID ]) || $ this ->attributes [static ::PARENT_ID ] != $ value )
682+ {
658683 $ this ->appendTo (static ::findOrFail ($ value ));
659684 }
660685 }
0 commit comments