@@ -29,10 +29,12 @@ class QueryBuilder extends Builder
2929 */
3030 public function getNodeData ($ id , $ required = false )
3131 {
32- $ this -> query -> where ( $ this ->model -> getKeyName (), ' = ' , $ id );
32+ $ query = $ this ->toBase ( );
3333
34- $ data = $ this ->query ->first ([ $ this ->model ->getLftName (),
35- $ this ->model ->getRgtName () ]);
34+ $ query ->where ($ this ->model ->getKeyName (), '= ' , $ id );
35+
36+ $ data = $ query ->first ([ $ this ->model ->getLftName (),
37+ $ this ->model ->getRgtName () ]);
3638
3739 if ( ! $ data && $ required ) {
3840 throw new ModelNotFoundException ;
@@ -88,11 +90,13 @@ public function whereAncestorOf($id)
8890
8991 $ id = $ id ->getKey ();
9092 } else {
91- $ valueQuery = $ this ->model ->newQuery ()->getQuery ()
92- ->select ("_. " .$ this ->model ->getLftName ())
93- ->from ($ this ->model ->getTable ().' as _ ' )
94- ->where ($ keyName , '= ' , $ id )
95- ->limit (1 );
93+ $ valueQuery = $ this ->model
94+ ->newQuery ()
95+ ->toBase ()
96+ ->select ("_. " .$ this ->model ->getLftName ())
97+ ->from ($ this ->model ->getTable ().' as _ ' )
98+ ->where ($ keyName , '= ' , $ id )
99+ ->limit (1 );
96100
97101 $ this ->query ->mergeBindings ($ valueQuery );
98102
@@ -247,11 +251,12 @@ protected function whereIsBeforeOrAfter($id, $operator, $boolean)
247251
248252 $ this ->query ->addBinding ($ id ->getLft ());
249253 } else {
250- $ valueQuery = $ this ->model ->newQuery ()
251- ->getQuery ()
252- ->select ('_n. ' .$ this ->model ->getLftName ())
253- ->from ($ this ->model ->getTable ().' as _n ' )
254- ->where ('_n. ' .$ this ->model ->getKeyName (), '= ' , $ id );
254+ $ valueQuery = $ this ->model
255+ ->newQuery ()
256+ ->toBase ()
257+ ->select ('_n. ' .$ this ->model ->getLftName ())
258+ ->from ($ this ->model ->getTable ().' as _n ' )
259+ ->where ('_n. ' .$ this ->model ->getKeyName (), '= ' , $ id );
255260
256261 $ this ->query ->mergeBindings ($ valueQuery );
257262
@@ -306,16 +311,18 @@ public function withDepth($as = 'depth')
306311 {
307312 if ($ this ->query ->columns === null ) $ this ->query ->columns = [ '* ' ];
308313
309- $ this ->query ->selectSub (function (BaseQueryBuilder $ q ) {
310- $ table = $ this ->wrappedTable ();
314+ $ table = $ this ->wrappedTable ();
315+
316+ list ($ lft , $ rgt ) = $ this ->wrappedColumns ();
311317
312- list ($ lft , $ rgt ) = $ this ->wrappedColumns ();
318+ $ query = $ this ->model
319+ ->newQuery ()
320+ ->toBase ()
321+ ->selectRaw ('count(1) - 1 ' )
322+ ->from ($ this ->model ->getTable ().' as _d ' )
323+ ->whereRaw ("{$ table }. {$ lft } between _d. {$ lft } and _d. {$ rgt }" );
313324
314- $ q
315- ->selectRaw ('count(1) - 1 ' )
316- ->from ($ this ->model ->getTable ().' as _d ' )
317- ->whereRaw ("{$ table }. {$ lft } between _d. {$ lft } and _d. {$ rgt }" );
318- }, $ as );
325+ $ this ->query ->selectSub ($ query , $ as );
319326
320327 return $ this ;
321328 }
@@ -331,11 +338,10 @@ protected function wrappedColumns()
331338 {
332339 $ grammar = $ this ->query ->getGrammar ();
333340
334- return
335- [
336- $ grammar ->wrap ($ this ->model ->getLftName ()),
337- $ grammar ->wrap ($ this ->model ->getRgtName ()),
338- ];
341+ return [
342+ $ grammar ->wrap ($ this ->model ->getLftName ()),
343+ $ grammar ->wrap ($ this ->model ->getRgtName ()),
344+ ];
339345 }
340346
341347 /**
@@ -460,15 +466,21 @@ public function moveNode($key, $position)
460466 $ distance = $ to - $ from + 1 - $ height ;
461467
462468 // If no distance to travel, just return
463- if ($ distance === 0 ) return 0 ;
469+ if ($ distance === 0 ) {
470+ return 0 ;
471+ }
464472
465- if ($ position > $ lft ) $ height *= -1 ; else $ distance *= -1 ;
473+ if ($ position > $ lft ) {
474+ $ height *= -1 ;
475+ } else {
476+ $ distance *= -1 ;
477+ }
466478
467479 $ params = compact ('lft ' , 'rgt ' , 'from ' , 'to ' , 'height ' , 'distance ' );
468480
469481 $ boundary = [ $ from , $ to ];
470482
471- $ query = $ this ->query ->where (function (Query $ inner ) use ($ boundary ) {
483+ $ query = $ this ->toBase () ->where (function (Query $ inner ) use ($ boundary ) {
472484 $ inner ->whereBetween ($ this ->model ->getLftName (), $ boundary );
473485 $ inner ->orWhereBetween ($ this ->model ->getRgtName (), $ boundary );
474486 });
@@ -490,12 +502,12 @@ public function makeGap($cut, $height)
490502 {
491503 $ params = compact ('cut ' , 'height ' );
492504
493- $ this ->query ->whereNested (function (Query $ inner ) use ($ cut ) {
505+ $ query = $ this ->toBase () ->whereNested (function (Query $ inner ) use ($ cut ) {
494506 $ inner ->where ($ this ->model ->getLftName (), '>= ' , $ cut );
495507 $ inner ->orWhere ($ this ->model ->getRgtName (), '>= ' , $ cut );
496508 });
497509
498- return $ this -> query ->update ($ this ->patch ($ params ));
510+ return $ query ->update ($ this ->patch ($ params ));
499511 }
500512
501513 /**
@@ -511,7 +523,7 @@ protected function patch(array $params)
511523 {
512524 $ grammar = $ this ->query ->getGrammar ();
513525
514- $ columns = array () ;
526+ $ columns = [] ;
515527
516528 foreach ([ $ this ->model ->getLftName (), $ this ->model ->getRgtName () ] as $ col ) {
517529 $ columns [$ col ] = $ this ->columnPatch ($ grammar ->wrap ($ col ), $ params );
@@ -564,31 +576,149 @@ protected function columnPatch($col, array $params)
564576 */
565577 public function countErrors ()
566578 {
567- $ table = $ this ->wrappedTable ();
568- list ($ lft , $ rgt ) = $ this ->wrappedColumns ();
569-
570- $ checks = array ();
579+ $ checks = [];
571580
572581 // Check if lft and rgt values are ok
573- $ checks ['oddness ' ] = " from { $ table } where { $ lft } >= { $ rgt } or ( { $ rgt } - { $ lft } ) % 2 = 0 " ;
582+ $ checks ['oddness ' ] = $ this -> getOdnessQuery () ;
574583
575584 // Check if lft and rgt values are unique
576- $ checks ['duplicates ' ] = "from {$ table } c1, {$ table } c2 where c1.id <> c2.id and " .
577- "(c1. {$ lft }=c2. {$ lft } or c1. {$ rgt }=c2. {$ rgt } or c1. {$ lft }=c2. {$ rgt } or c1. {$ rgt }=c2. {$ lft }) " ;
585+ $ checks ['duplicates ' ] = $ this ->getDuplicatesQuery ();
578586
579587 // Check if parent_id is set correctly
580- $ checks ['wrong_parent ' ] =
581- "from {$ table } c, {$ table } p, $ table m " .
582- "where c.parent_id=p.id and m.id <> p.id and m.id <> c.id and " .
583- "(c. {$ lft } not between p. {$ lft } and p. {$ rgt } or c. {$ lft } between m. {$ lft } and m. {$ rgt } and m. {$ lft } between p. {$ lft } and p. {$ rgt }) " ;
588+ $ checks ['wrong_parent ' ] = $ this ->getWrongParentQuery ();
584589
585- $ query = $ this ->query -> newQuery ();
590+ $ query = $ this ->toBase ();
586591
587- foreach ($ checks as $ key => $ check ) {
588- $ query ->addSelect (new Expression ('(select count(1) ' .$ check .') as ' .$ key ));
592+ foreach ($ checks as $ key => $ inner ) {
593+ $ inner ->selectRaw ('count(1) ' );
594+
595+ $ query ->selectSub ($ inner , $ key );
589596 }
590597
591598 return (array )$ query ->first ();
592599 }
593600
601+ /**
602+ * @return BaseQueryBuilder
603+ */
604+ protected function getOdnessQuery ()
605+ {
606+ return $ this ->model
607+ ->newServiceQuery ()
608+ ->toBase ()
609+ ->whereNested (function (BaseQueryBuilder $ inner ) {
610+ list ($ lft , $ rgt ) = $ this ->wrappedColumns ();
611+
612+ $ inner ->whereRaw ("{$ lft } >= {$ rgt }" )
613+ ->orWhereRaw ("( {$ rgt } - {$ lft }) % 2 = 0 " );
614+ });
615+ }
616+
617+ /**
618+ * @return BaseQueryBuilder
619+ */
620+ protected function getDuplicatesQuery ()
621+ {
622+ $ table = $ this ->wrappedTable ();
623+
624+ return $ this ->model
625+ ->newServiceQuery ()
626+ ->toBase ()
627+ ->from ($ this ->query ->raw ("{$ table } c1, {$ table } c2 " ))
628+ ->whereRaw ("c1.id <> c2.id " )
629+ ->whereNested (function (BaseQueryBuilder $ inner ) {
630+ list ($ lft , $ rgt ) = $ this ->wrappedColumns ();
631+
632+ $ inner ->orWhereRaw ("c1. {$ lft }=c2. {$ lft }" )
633+ ->orWhereRaw ("c1. {$ rgt }=c2. {$ rgt }" )
634+ ->orWhereRaw ("c1. {$ lft }=c2. {$ rgt }" )
635+ ->orWhereRaw ("c1. {$ rgt }=c2. {$ lft }" );
636+ });
637+ }
638+
639+ /**
640+ * @return BaseQueryBuilder
641+ */
642+ protected function getWrongParentQuery ()
643+ {
644+ $ table = $ this ->wrappedTable ();
645+ $ keyName = $ this ->wrappedKey ();
646+ $ parentIdName = $ this ->query ->raw ($ this ->model ->getParentIdName ());
647+
648+ return $ this ->model
649+ ->newServiceQuery ()
650+ ->toBase ()
651+ ->from ($ this ->query ->raw ("{$ table } c, {$ table } p, $ table m " ))
652+ ->whereRaw ("c. {$ parentIdName }=p. {$ keyName }" )
653+ ->whereRaw ("m. {$ keyName } <> p. {$ keyName }" )
654+ ->whereRaw ("m. {$ keyName } <> c. {$ keyName }" )
655+ ->whereNested (function (BaseQueryBuilder $ inner ) {
656+ list ($ lft , $ rgt ) = $ this ->wrappedColumns ();
657+
658+ $ inner ->whereRaw ("c. {$ lft } not between p. {$ lft } and p. {$ rgt }" )
659+ ->orWhereRaw ("c. {$ lft } between m. {$ lft } and m. {$ rgt }" )
660+ ->whereRaw ("m. {$ lft } between p. {$ lft } and p. {$ rgt }" );
661+ });
662+
663+ }
664+
665+ /**
666+ * Fixes the tree based on parentage info.
667+ *
668+ * Requires at least one root node. This will not update nodes with invalid parent.
669+ *
670+ * @return int The number of fixed nodes.
671+ */
672+ public function fixTree ()
673+ {
674+ $ columns = [
675+ $ this ->model ->getKeyName (),
676+ $ this ->model ->getParentIdName (),
677+ $ this ->model ->getLftName (),
678+ $ this ->model ->getRgtName (),
679+ ];
680+
681+ $ nodes = $ this ->model
682+ ->newQuery ()
683+ ->defaultOrder ()
684+ ->get ($ columns )
685+ ->groupBy ($ this ->model ->getParentIdName ());
686+
687+ $ fixed = 0 ;
688+
689+ self ::reorderNodes ($ nodes , $ fixed );
690+
691+ return $ fixed ;
692+ }
693+
694+ /**
695+ * @param Collection $models
696+ * @param int $fixed
697+ * @param $parentId
698+ * @param int $cut
699+ *
700+ * @return int
701+ */
702+ protected static function reorderNodes (Collection $ models , &$ fixed ,
703+ $ parentId = null , $ cut = 1
704+ ) {
705+ /** @var Model|self $model */
706+ foreach ($ models ->get ($ parentId , [ ]) as $ model ) {
707+ $ model ->setLft ($ cut );
708+
709+ $ cut = self ::reorderNodes ($ models , $ fixed , $ model ->getKey (), $ cut + 1 );
710+
711+ $ model ->setRgt ($ cut );
712+
713+ if ($ model ->isDirty ()) {
714+ $ model ->save ();
715+
716+ $ fixed ++;
717+ }
718+
719+ ++$ cut ;
720+ }
721+
722+ return $ cut ;
723+ }
594724}
0 commit comments