[5.5] Maintain original order of collection items with equal values when using sortBy() #21214
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR makes the collection
sortBy()
method perform a stable sort, such that it preserves the original order of collection items which have identical values.The problem
sortBy()
relies onasort
, which, as @sisve pointed out during this discussion in laravel/internals, includes this warning:So, for example, if you run
sortBy('letter')
on this collection:...the resulting collection swaps the order of the two
'letter' => 'a'
items:This behavior varies depending on the nature of the collection—sometimes the original order will be maintained, sometimes it won't.
Why it matters
This becomes a particular problem when trying to sort a collection by multiple criteria. One way to do so is to chain
sortBy()
clauses in reverse order, i.e. in order to sort by "foo
thenbar
thenbaz
", you should be able to call:...but, because the original position of duplicate values isn't necessarily maintained between each call to
sortBy()
, this will sometimes result in a correct multi-sort, but not always. The results are unpredictable.The solution
The simplest solution I've found involves using the amusingly-named Schwartzian Transform, which basically involves converting each value in the array you're sorting (
['b', 'a', 'a']
) into an array that includes its index ([['b', 0], ['a', 1], ['a', 2]]
)—whichasort
will handle correctly—and then stripping out those added keys once the sorting is done. Luckily, the way thesortBy()
method is written, this is easy—we only need to add the keys during the first loop.All in all, an additional 8 characters of code...and a much, much longer description. 😄