You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Extend Illuminate\Support\Collection with sortBySequence method.
#60323
How method works function sortBySequence(array|Collection $orderArray, string $path = '*')
Replicates the order of given array|Collection by following rules of the string $path parameter as example:
* - iterate whole first level and and sort based on it
*.name - iterate whole level and sort by its name property
*.classification.tags.*.name - iterate whole first level, go through classification property path iterate whole level of the nested property tags and sort by their name
implementation:
functionsortBySequence(array|Illuminate\Support\Collection$sequence, string$path = '*') : self {
// 1. Path Normalization// If '*' is passed, primitive values are being compared directly.// The internal path is set to null so data_get() knows to pull the entire item.// Otherwise, any leading '*.' is stripped out via regex to cleanly match // Laravel's standard nested dot-notation.$itemPath = $path === '*' ? null : preg_replace('/^\*\./', '', $path);
// 2. Building an O(1) Lookup Map// To avoid nested loop lookups—which create a massive O(N^2) performance bottleneck—// a flat lookup map is pre-compiled upfront. The extracted target value becomes // the array key, and its position index becomes the value.// Example layout: ['apple' => 0, 'banana' => 1, 'cherry' => 2]$lookupMap = [];
foreach ($sequenceas$index => $sequenceItem) {
// data_get() safely extracts nested values and cleanly bypasses missing keys.$extracted = data_get($sequenceItem, $itemPath);
// If the path uses multiple wildcards, data_get() returns nested arrays.// Arr::flatten() collapses everything down into a simple, 1D array of values.$values = is_array($extracted) ? Arr::flatten($extracted) : [$extracted];
foreach ($valuesas$value) {
// PHP array keys must be strings or integers. This scalar check prevents// "Illegal offset type" crashes if objects or nulls accidentally slip through.//// The !isset() check guarantees that if a value appears multiple times // in the sequence, only its first (highest priority) index is recorded.if (is_scalar($value) && !isset($lookupMap[$value])) {
$lookupMap[$value] = $index;
}
}
}
// 3. Sorting the Collection// A closure is passed directly to Laravel's native sortBy() engine. For every // item processed, a numerical "weight" score is calculated and returned. // Smaller numbers float to the top; larger numbers sink to the bottom.return$this->sortBy(function ($collectionItem) use ($itemPath, $lookupMap) {
// Extract and flatten the target values from the current collection item.$extracted = data_get($collectionItem, $itemPath);
$values = is_array($extracted) ? Arr::flatten($extracted) : [$extracted];
// The item weight is initialized to PHP_INT_MAX (the absolute maximum integer).// If an item has no properties matching the sequence, it retains this maximum // score, which automatically forces it to the very bottom of the sorted list.$minIndex = PHP_INT_MAX;
foreach ($valuesas$value) {
// If the value exists in the pre-compiled lookup dictionary, its index is evaluated.// For items containing multiple values (like an array of tags), min() ensures // the item is scored based on whichever tag appeared earliest in the sequence.if (is_scalar($value) && isset($lookupMap[$value])) {
$minIndex = min($minIndex, $lookupMap[$value]);
}
}
// The final calculated weight is returned back to the sorting engine.return$minIndex;
});
}
Scenario 3: Using *.classification.tags.*.name (Deeply Nested Wildcards)
This handles cases where an item matches any of the tags present in your reference schema order.
$collection = collect([
[
'id' => 101,
'classification' => ['tags' => [['name' => 'furniture'], ['name' => 'wood']]]
],
[
'id' => 102,
'classification' => ['tags' => [['name' => 'office']]]
],
]);
// The external order array dictates 'office' logic should come first$orderArray = [
['classification' => ['tags' => [['name' => 'office']]]],
['classification' => ['tags' => [['name' => 'furniture']]]],
];
$sorted = $collection->sortBySequence($orderArray, '*.classification.tags.*.name')->values()->all();
/* Result Order: 1. ID 102 (Matched 'office' at index 0) 2. ID 101 (Matched 'furniture' at index 1)*/
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
How method works
function sortBySequence(array|Collection $orderArray, string $path = '*')Replicates the order of given
array|Collectionby following rules of thestring $pathparameter as example:*- iterate whole first level and and sort based on it*.name- iterate whole level and sort by its name property*.classification.tags.*.name- iterate whole first level, go throughclassificationproperty path iterate whole level of the nested propertytagsand sort by their nameimplementation:
The example scenarios as code:
Scenario 1: Using
*(Simple Primitives)Scenario 2: Using
*.name(First Level Property)Scenario 3: Using
*.classification.tags.*.name(Deeply Nested Wildcards)This handles cases where an item matches any of the tags present in your reference schema order.
All reactions