2020use Illuminate \Database \Eloquent \Relations \BelongsToMany ;
2121use Illuminate \Support \Arr ;
2222use Illuminate \Support \Collection ;
23+ use Illuminate \Support \LazyCollection ;
2324use InvalidArgumentException ;
2425
2526class SelectTree extends Field implements HasAffixActions
@@ -85,7 +86,7 @@ class SelectTree extends Field implements HasAffixActions
8586
8687 protected bool $ storeResults = false ;
8788
88- protected Collection |array |null $ results = null ;
89+ protected LazyCollection |array |null $ results = null ;
8990
9091 protected Closure |bool |null $ multiple = null ;
9192
@@ -179,8 +180,8 @@ protected function buildTree(): Collection
179180 $ nonNullParentQuery ->withTrashed ($ this ->withTrashed );
180181 }
181182
182- $ nullParentResults = $ nullParentQuery ->get ();
183- $ nonNullParentResults = $ nonNullParentQuery ->get ();
183+ $ nullParentResults = $ nullParentQuery ->lazy ();
184+ $ nonNullParentResults = $ nonNullParentQuery ->lazy ();
184185
185186 // Combine the results from both queries
186187 $ combinedResults = $ nullParentResults ->concat ($ nonNullParentResults );
@@ -206,23 +207,60 @@ private function buildTreeFromResults($results, $parent = null): Collection
206207 // Create a mapping of results by their parent IDs for faster lookup
207208 $ resultMap = [];
208209
210+ // Create a cache of IDs
211+ $ resultCache = [];
212+
209213 // Group results by their parent IDs
210214 foreach ($ results as $ result ) {
211- $ parentId = $ result ->{$ this ->getParentAttribute ()};
212- if (! isset ($ resultMap [$ parentId ])) {
213- $ resultMap [$ parentId ] = [];
215+ // Cache the result as seen
216+ $ resultKey = $ this ->getCustomKey ($ result );
217+ $ resultCache [$ resultKey ]['in_set ' ] = 1 ;
218+ // Move any cached children to the result map
219+ if (isset ($ resultCache [$ resultKey ]['children ' ])){
220+ // Since the result map won't have a key for a given result until it's confirmed to be in the set (i.e. this very moment),
221+ // we don't have to preserve the previous value for that key; it is guaranteed to have been unset
222+ $ resultMap [$ resultKey ] = $ resultCache [$ resultKey ]['children ' ];
223+ unset($ resultCache [$ resultKey ]['children ' ]);
224+ }
225+ $ parentKey = $ result ->{$ this ->getParentAttribute ()};
226+ if (! isset ($ resultCache [$ parentKey ])) {
227+ // Before adding results to the map, cache the parentId to hold until the parent is confirmed to be in the result set
228+ $ resultCache [$ parentKey ]['in_set ' ] = 0 ;
229+ $ resultCache [$ parentKey ]['children ' ] = [];
230+ }
231+ if ($ resultCache [$ parentKey ]['in_set ' ]){
232+ // if the parent has been confirmed to be in the set, add directly to result map
233+ $ resultMap [$ parentKey ][] = $ result ;
234+ } else {
235+ // otherwise, hold the result in the children cache until the parent is confirmed to be in the result set
236+ $ resultCache [$ parentKey ]['children ' ][] = $ result ;
214237 }
215- $ resultMap [$ parentId ][] = $ result ;
216238 }
217239
240+ // Filter the cache for missing parents in the result set and get the children
241+ $ orphanedResults = array_map (
242+ fn ($ item ) => $ item ['children ' ],
243+ array_filter (
244+ $ resultCache ,
245+ fn ($ item ) => !$ item ['in_set ' ]
246+ )
247+ );
248+
249+ // Move any remaining children from the cache into the root of the tree, since their parents do not show up in the result set
250+ $ resultMap [$ parent ] = [];
251+ foreach ($ orphanedResults as $ orphanedResult ){
252+ $ resultMap [$ parent ] += $ orphanedResult ;
253+ }
254+
255+ // Recursively build the tree starting from the root (null parent)
256+ $ rootResults = $ resultMap [$ parent ] ?? [];
257+
218258 // Define disabled options
219259 $ disabledOptions = $ this ->getDisabledOptions ();
220260
221261 // Define hidden options
222262 $ hiddenOptions = $ this ->getHiddenOptions ();
223263
224- // Recursively build the tree starting from the root (null parent)
225- $ rootResults = $ resultMap [$ parent ] ?? [];
226264 foreach ($ rootResults as $ result ) {
227265 // Build a node and add it to the tree
228266 $ node = $ this ->buildNode ($ result , $ resultMap , $ disabledOptions , $ hiddenOptions );
0 commit comments