7
7
use Illuminate \Database \Eloquent \ModelNotFoundException ;
8
8
use Illuminate \Database \Query \Builder as Query ;
9
9
use Illuminate \Database \Query \Builder as BaseQueryBuilder ;
10
+ use Illuminate \Support \Arr ;
10
11
use LogicException ;
11
12
use Illuminate \Database \Query \Expression ;
12
13
@@ -726,9 +727,9 @@ public function isBroken()
726
727
/**
727
728
* Fixes the tree based on parentage info.
728
729
*
729
- * Requires at least one root node. This will not update nodes with invalid parent .
730
+ * Nodes with invalid parent are saved as roots .
730
731
*
731
- * @return int The number of fixed nodes.
732
+ * @return int The number of fixed nodes
732
733
*/
733
734
public function fixTree ()
734
735
{
@@ -739,54 +740,64 @@ public function fixTree()
739
740
$ this ->model ->getRgtName (),
740
741
];
741
742
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 ();
747
747
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
+ {
748
758
$ fixed = 0 ;
749
759
750
- $ cut = self ::reorderNodes ($ nodes , $ fixed );
760
+ $ cut = self ::reorderNodes ($ dictionary , $ fixed );
751
761
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 );
755
765
756
- foreach ($ nodes [$ parentId ] as $ model ) {
757
- $ model ->setParentId (null );
758
- }
766
+ unset($ dictionary [key ($ dictionary )]);
759
767
760
- $ cut = self ::reorderNodes ($ nodes , $ fixed , $ parentId , $ cut );
768
+ $ cut = self ::reorderNodes ($ dictionary , $ fixed , null , $ cut );
761
769
}
762
770
763
771
return $ fixed ;
764
772
}
765
773
766
774
/**
767
- * @param Collection $models
775
+ * @param array $dictionary
768
776
* @param int $fixed
769
777
* @param $parentId
770
778
* @param int $cut
771
779
*
772
780
* @return int
773
781
*/
774
- protected static function reorderNodes (Collection $ models , &$ fixed ,
782
+ protected static function reorderNodes (array & $ dictionary , &$ fixed ,
775
783
$ parentId = null , $ cut = 1
776
784
) {
777
- if ( ! isset ($ models [$ parentId ])) {
785
+ if ( ! isset ($ dictionary [$ parentId ])) {
778
786
return $ cut ;
779
787
}
780
788
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 ;
784
792
785
- $ cut = self ::reorderNodes ($ models , $ fixed , $ model ->getKey (), $ cut + 1 );
793
+ $ cut = self ::reorderNodes ($ dictionary ,
794
+ $ fixed ,
795
+ $ model ->getKey (),
796
+ $ cut + 1 );
786
797
787
- $ model -> setRgt ( $ cut) ;
798
+ $ rgt = $ cut ;
788
799
789
- if ($ model ->isDirty ()) {
800
+ if ($ model ->rawNode ( $ lft , $ rgt , $ parentId )-> isDirty ()) {
790
801
$ model ->save ();
791
802
792
803
$ fixed ++;
@@ -795,13 +806,89 @@ protected static function reorderNodes(Collection $models, &$fixed,
795
806
++$ cut ;
796
807
}
797
808
798
- unset($ models [$ parentId ]);
809
+ unset($ dictionary [$ parentId ]);
799
810
800
811
return $ cut ;
801
812
}
802
813
803
814
/**
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
805
892
*
806
893
* @return $this
807
894
*/
0 commit comments