From 3d1e7d776df353ef3f8a30da45885c5e110c725e Mon Sep 17 00:00:00 2001 From: gp-lnuff Date: Thu, 2 Oct 2025 11:41:37 +0200 Subject: [PATCH 1/7] Revised changes --- src/SelectTree.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/SelectTree.php b/src/SelectTree.php index aca66f2..dfe6b45 100644 --- a/src/SelectTree.php +++ b/src/SelectTree.php @@ -223,6 +223,13 @@ private function buildTreeFromResults($results, $parent = null): Collection // Recursively build the tree starting from the root (null parent) $rootResults = $resultMap[$parent] ?? []; + + // If a modified parent query yields no root results, iterate over the whole map instead + if($this->modifyQueryUsing && empty($rootResults)) { + // Go one layer deeper to access the results + $rootResults = array_merge(...array_values($resultMap)); + } + foreach ($rootResults as $result) { // Build a node and add it to the tree $node = $this->buildNode($result, $resultMap, $disabledOptions, $hiddenOptions); From a518f0b8af5ab0db57f0fd1303725723abac73b0 Mon Sep 17 00:00:00 2001 From: gp-lnuff Date: Thu, 2 Oct 2025 16:46:32 +0200 Subject: [PATCH 2/7] Revision to the revised changes --- src/SelectTree.php | 50 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/src/SelectTree.php b/src/SelectTree.php index dfe6b45..1674b47 100644 --- a/src/SelectTree.php +++ b/src/SelectTree.php @@ -206,29 +206,55 @@ private function buildTreeFromResults($results, $parent = null): Collection // Create a mapping of results by their parent IDs for faster lookup $resultMap = []; + // Create a cache of IDs + $resultCache = []; + // Group results by their parent IDs foreach ($results as $result) { + // Cache the result ID as seen + $resultCache[$result->id]['in_set'] = 1; + // Move any cached children to the result map + if(isset($resultCache[$result->id]['children'])){ + $resultMap[$result->id] = array_merge($resultMap[$result->id], $resultCache[$result->id]['children']); + unset($resultCache[$result->id]['children']); + } $parentId = $result->{$this->getParentAttribute()}; - if (! isset($resultMap[$parentId])) { - $resultMap[$parentId] = []; + if (! isset($resultCache[$parentId])) { + // Before adding results to the map, cache the parentId to hold until the parent is confirmed to be in the result set + $resultCache[$parentId]['in_set'] = 0; + $resultCache[$parentId]['children'] = []; + } + if($resultCache[$parentId]['in_set']){ + // if the parent has been confirmed to be in the set, add directly to result map + $resultMap[$parentId][] = $result; + } else { + // otherwise, hold the result in the children cache until the parent is confirmed to be in the result set + $resultCache[$parentId]['children'][] = $result; } - $resultMap[$parentId][] = $result; } - // Define disabled options - $disabledOptions = $this->getDisabledOptions(); + // Filter the cache for missing parents in the result set and get the children + $orphanedResults = array_map( + fn($item) => $item['children'], + array_filter( + $resultCache, + fn($item) => !$item['in_set'] + ) + ); - // Define hidden options - $hiddenOptions = $this->getHiddenOptions(); + // Move any remaining children from the cache into the root of the tree, since their parents do not show up in the result set + $resultMap[$parent] = array_merge(...array_values($orphanedResults)); + + debug($resultMap); // Recursively build the tree starting from the root (null parent) $rootResults = $resultMap[$parent] ?? []; - // If a modified parent query yields no root results, iterate over the whole map instead - if($this->modifyQueryUsing && empty($rootResults)) { - // Go one layer deeper to access the results - $rootResults = array_merge(...array_values($resultMap)); - } + // Define disabled options + $disabledOptions = $this->getDisabledOptions(); + + // Define hidden options + $hiddenOptions = $this->getHiddenOptions(); foreach ($rootResults as $result) { // Build a node and add it to the tree From ab68dec8d3e7575f97e997347c19adc583f9f5db Mon Sep 17 00:00:00 2001 From: gp-lnuff Date: Thu, 2 Oct 2025 18:03:38 +0200 Subject: [PATCH 3/7] Handle custom record keys --- src/SelectTree.php | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/SelectTree.php b/src/SelectTree.php index 1674b47..435e966 100644 --- a/src/SelectTree.php +++ b/src/SelectTree.php @@ -211,25 +211,26 @@ private function buildTreeFromResults($results, $parent = null): Collection // Group results by their parent IDs foreach ($results as $result) { - // Cache the result ID as seen - $resultCache[$result->id]['in_set'] = 1; + // Cache the result as seen + $resultKey = $this->getCustomKey($result); + $resultCache[$resultKey]['in_set'] = 1; // Move any cached children to the result map - if(isset($resultCache[$result->id]['children'])){ - $resultMap[$result->id] = array_merge($resultMap[$result->id], $resultCache[$result->id]['children']); - unset($resultCache[$result->id]['children']); + if(isset($resultCache[$resultKey]['children'])){ + $resultMap[$resultKey] = array_merge($resultMap[$resultKey], $resultCache[$resultKey]['children']); + unset($resultCache[$resultKey]['children']); } - $parentId = $result->{$this->getParentAttribute()}; - if (! isset($resultCache[$parentId])) { + $parentKey = $result->{$this->getParentAttribute()}; + if (! isset($resultCache[$parentKey])) { // Before adding results to the map, cache the parentId to hold until the parent is confirmed to be in the result set - $resultCache[$parentId]['in_set'] = 0; - $resultCache[$parentId]['children'] = []; + $resultCache[$parentKey]['in_set'] = 0; + $resultCache[$parentKey]['children'] = []; } - if($resultCache[$parentId]['in_set']){ + if($resultCache[$parentKey]['in_set']){ // if the parent has been confirmed to be in the set, add directly to result map - $resultMap[$parentId][] = $result; + $resultMap[$parentKey][] = $result; } else { // otherwise, hold the result in the children cache until the parent is confirmed to be in the result set - $resultCache[$parentId]['children'][] = $result; + $resultCache[$parentKey]['children'][] = $result; } } From bd4db0aef89cd21a0bb35ed7e517fd7a1fb78218 Mon Sep 17 00:00:00 2001 From: gp-lnuff Date: Thu, 2 Oct 2025 18:06:18 +0200 Subject: [PATCH 4/7] remove debug line --- src/SelectTree.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/SelectTree.php b/src/SelectTree.php index 435e966..b5f9087 100644 --- a/src/SelectTree.php +++ b/src/SelectTree.php @@ -246,8 +246,6 @@ private function buildTreeFromResults($results, $parent = null): Collection // Move any remaining children from the cache into the root of the tree, since their parents do not show up in the result set $resultMap[$parent] = array_merge(...array_values($orphanedResults)); - debug($resultMap); - // Recursively build the tree starting from the root (null parent) $rootResults = $resultMap[$parent] ?? []; From de1c9a9c530cd04c785ae498ef3e0f90574d56ef Mon Sep 17 00:00:00 2001 From: gp-lnuff Date: Wed, 8 Oct 2025 16:15:22 +0200 Subject: [PATCH 5/7] Fix migration logic from cache to result map --- src/SelectTree.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/SelectTree.php b/src/SelectTree.php index b5f9087..9f8f948 100644 --- a/src/SelectTree.php +++ b/src/SelectTree.php @@ -216,7 +216,9 @@ private function buildTreeFromResults($results, $parent = null): Collection $resultCache[$resultKey]['in_set'] = 1; // Move any cached children to the result map if(isset($resultCache[$resultKey]['children'])){ - $resultMap[$resultKey] = array_merge($resultMap[$resultKey], $resultCache[$resultKey]['children']); + // 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), + // we don't have to preserve the previous value for that key; it is guaranteed to have been unset + $resultMap[$resultKey] = $resultCache[$resultKey]['children']; unset($resultCache[$resultKey]['children']); } $parentKey = $result->{$this->getParentAttribute()}; From fd189e0331cc68779b341e7bafc5f837564d08f6 Mon Sep 17 00:00:00 2001 From: gp-lnuff Date: Wed, 8 Oct 2025 16:16:29 +0200 Subject: [PATCH 6/7] tentative performance optimization (needs benchmarks) --- src/SelectTree.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/SelectTree.php b/src/SelectTree.php index 9f8f948..468a952 100644 --- a/src/SelectTree.php +++ b/src/SelectTree.php @@ -246,7 +246,10 @@ private function buildTreeFromResults($results, $parent = null): Collection ); // Move any remaining children from the cache into the root of the tree, since their parents do not show up in the result set - $resultMap[$parent] = array_merge(...array_values($orphanedResults)); + $resultMap[$parent] = []; + foreach($orphanedResults as $orphanedResult){ + $resultMap[$parent] += $orphanedResult; + } // Recursively build the tree starting from the root (null parent) $rootResults = $resultMap[$parent] ?? []; From ff2ee565f825f690c6a406e8d462b4e815ac0771 Mon Sep 17 00:00:00 2001 From: gp-lnuff Date: Thu, 9 Oct 2025 11:32:50 +0200 Subject: [PATCH 7/7] Use LazyCollection to handle bigger data sets --- src/SelectTree.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/SelectTree.php b/src/SelectTree.php index 468a952..33a7fd7 100644 --- a/src/SelectTree.php +++ b/src/SelectTree.php @@ -20,6 +20,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Support\Arr; use Illuminate\Support\Collection; +use Illuminate\Support\LazyCollection; use InvalidArgumentException; class SelectTree extends Field implements HasAffixActions @@ -85,7 +86,7 @@ class SelectTree extends Field implements HasAffixActions protected bool $storeResults = false; - protected Collection|array|null $results = null; + protected LazyCollection|array|null $results = null; protected Closure|bool|null $multiple = null; @@ -179,8 +180,8 @@ protected function buildTree(): Collection $nonNullParentQuery->withTrashed($this->withTrashed); } - $nullParentResults = $nullParentQuery->get(); - $nonNullParentResults = $nonNullParentQuery->get(); + $nullParentResults = $nullParentQuery->lazy(); + $nonNullParentResults = $nonNullParentQuery->lazy(); // Combine the results from both queries $combinedResults = $nullParentResults->concat($nonNullParentResults);