77use Illuminate \Database \Eloquent \ModelNotFoundException ;
88use Illuminate \Database \Query \Builder as Query ;
99use Illuminate \Database \Query \Builder as BaseQueryBuilder ;
10+ use Illuminate \Support \Arr ;
1011use LogicException ;
1112use Illuminate \Database \Query \Expression ;
1213
@@ -726,9 +727,9 @@ public function isBroken()
726727 /**
727728 * Fixes the tree based on parentage info.
728729 *
729- * Requires at least one root node. This will not update nodes with invalid parent .
730+ * Nodes with invalid parent are saved as roots .
730731 *
731- * @return int The number of fixed nodes.
732+ * @return int The number of fixed nodes
732733 */
733734 public function fixTree ()
734735 {
@@ -739,54 +740,64 @@ public function fixTree()
739740 $ this ->model ->getRgtName (),
740741 ];
741742
742- $ nodes = $ this ->model
743- ->newNestedSetQuery ()
744- ->defaultOrder ()
745- ->get ($ columns )
746- ->groupBy ($ this ->model ->getParentIdName ());
743+ $ dictionary = $ this ->defaultOrder ()
744+ ->get ($ columns )
745+ ->groupBy ($ this ->model ->getParentIdName ())
746+ ->all ();
747747
748+ return self ::fixNodes ($ dictionary );
749+ }
750+
751+ /**
752+ * @param array $dictionary
753+ *
754+ * @return int
755+ */
756+ protected static function fixNodes (array &$ dictionary )
757+ {
748758 $ fixed = 0 ;
749759
750- $ cut = self ::reorderNodes ($ nodes , $ fixed );
760+ $ cut = self ::reorderNodes ($ dictionary , $ fixed );
751761
752- // Saved nodes that have invalid parent as roots
753- while ( ! $ nodes -> isEmpty ( )) {
754- $ parentId = $ nodes -> keys ()-> first ( );
762+ // Save nodes that have invalid parent as roots
763+ while ( ! empty ( $ dictionary )) {
764+ $ dictionary [ null ] = reset ( $ dictionary );
755765
756- foreach ($ nodes [$ parentId ] as $ model ) {
757- $ model ->setParentId (null );
758- }
766+ unset($ dictionary [key ($ dictionary )]);
759767
760- $ cut = self ::reorderNodes ($ nodes , $ fixed , $ parentId , $ cut );
768+ $ cut = self ::reorderNodes ($ dictionary , $ fixed , null , $ cut );
761769 }
762770
763771 return $ fixed ;
764772 }
765773
766774 /**
767- * @param Collection $models
775+ * @param array $dictionary
768776 * @param int $fixed
769777 * @param $parentId
770778 * @param int $cut
771779 *
772780 * @return int
773781 */
774- protected static function reorderNodes (Collection $ models , &$ fixed ,
782+ protected static function reorderNodes (array & $ dictionary , &$ fixed ,
775783 $ parentId = null , $ cut = 1
776784 ) {
777- if ( ! isset ($ models [$ parentId ])) {
785+ if ( ! isset ($ dictionary [$ parentId ])) {
778786 return $ cut ;
779787 }
780788
781- /** @var Model|self $model */
782- foreach ($ models [$ parentId ] as $ model ) {
783- $ model -> setLft ( $ cut) ;
789+ /** @var Model|NodeTrait $model */
790+ foreach ($ dictionary [$ parentId ] as $ model ) {
791+ $ lft = $ cut ;
784792
785- $ cut = self ::reorderNodes ($ models , $ fixed , $ model ->getKey (), $ cut + 1 );
793+ $ cut = self ::reorderNodes ($ dictionary ,
794+ $ fixed ,
795+ $ model ->getKey (),
796+ $ cut + 1 );
786797
787- $ model -> setRgt ( $ cut) ;
798+ $ rgt = $ cut ;
788799
789- if ($ model ->isDirty ()) {
800+ if ($ model ->rawNode ( $ lft , $ rgt , $ parentId )-> isDirty ()) {
790801 $ model ->save ();
791802
792803 $ fixed ++;
@@ -795,13 +806,89 @@ protected static function reorderNodes(Collection $models, &$fixed,
795806 ++$ cut ;
796807 }
797808
798- unset($ models [$ parentId ]);
809+ unset($ dictionary [$ parentId ]);
799810
800811 return $ cut ;
801812 }
802813
803814 /**
804- * @param null $table
815+ * Rebuild the tree based on raw data.
816+ *
817+ * If item data does not contain primary key, new node will be created.
818+ *
819+ * @param array $data
820+ * @param bool $delete Whether to delete nodes that exists but not in the data
821+ * array
822+ *
823+ * @return int
824+ */
825+ public function rebuildTree (array $ data , $ delete = false )
826+ {
827+ $ existing = $ this ->get ()->getDictionary ();
828+ $ dictionary = [];
829+
830+ $ this ->buildRebuildDictionary ($ dictionary , $ data , $ existing );
831+
832+ if ( ! empty ($ existing )) {
833+ if ($ delete ) {
834+ $ this ->model
835+ ->newScopedQuery ()
836+ ->whereIn ($ this ->model ->getKeyName (), array_keys ($ existing ))
837+ ->forceDelete ();
838+ } else {
839+ foreach ($ existing as $ model ) {
840+ $ dictionary [$ model ->getParentId ()][] = $ model ;
841+ }
842+ }
843+ }
844+
845+ return $ this ->fixNodes ($ dictionary );
846+ }
847+
848+ /**
849+ * @param array $dictionary
850+ * @param array $data
851+ * @param array $existing
852+ * @param mixed $parentId
853+ */
854+ protected function buildRebuildDictionary (array &$ dictionary ,
855+ array $ data ,
856+ array &$ existing ,
857+ $ parentId = null
858+ ) {
859+ $ keyName = $ this ->model ->getKeyName ();
860+
861+ foreach ($ data as $ itemData ) {
862+ if ( ! isset ($ itemData [$ keyName ])) {
863+ $ model = $ this ->model ->newInstance ();
864+
865+ // We will save it as raw node since tree will be fixed
866+ $ model ->rawNode (0 , 0 , $ parentId );
867+ } else {
868+ if ( ! isset ($ existing [$ key = $ itemData [$ keyName ]])) {
869+ throw new ModelNotFoundException ;
870+ }
871+
872+ $ model = $ existing [$ key ];
873+
874+ unset($ existing [$ key ]);
875+ }
876+
877+ $ model ->fill ($ itemData )->save ();
878+
879+ $ dictionary [$ parentId ][] = $ model ;
880+
881+ if ( ! isset ($ itemData ['children ' ])) continue ;
882+
883+ $ this ->buildRebuildDictionary ($ dictionary ,
884+ $ itemData ['children ' ],
885+ $ existing ,
886+ $ model ->getKey ());
887+ }
888+ }
889+
890+ /**
891+ * @param string|null $table
805892 *
806893 * @return $this
807894 */
0 commit comments