How do I sort a Laravel Collection by multiple properties with both asc and desc?

Viewed 22673

If I have an Illuminate\Support\Collection, how do I sort by multiple properties with both asc and desc? (This is a simple hypothetical - not at all looking for tips on query building.)

$collection = User::all(); // not looking for User::orderBy()->get() solutions.  Read the question.
$sorting_insructions = [
    ['column'=>'first_name', 'order'=>'asc'],
    ['column'=>'date_of_birth', 'order'=>'desc'],
];
$collection->sort(function($a,$b) use ($sorting_instructions){
    // something...
});
5 Answers

I know this question is from a while back, but in case anyone stumbles across it, Laravel does provide this functionality now (v8.x at the time of writing).

See the official docs for more detail. (4th code block example in the sort-by section) https://laravel.com/docs/8.x/collections#method-sortby


From the docs:

If you would like to sort your collection by multiple attributes, you may pass an array of sort operations to the sortBy method. Each sort operation should be an array consisting of the attribute that you wish to sort by and the direction of the desired sort:

$collection = collect([
    ['name' => 'Taylor Otwell', 'age' => 34],
    ['name' => 'Abigail Otwell', 'age' => 30],
    ['name' => 'Taylor Otwell', 'age' => 36],
    ['name' => 'Abigail Otwell', 'age' => 32],
]);

$sorted = $collection->sortBy([
    ['name', 'asc'],
    ['age', 'desc'],
]);

$sorted->values()->all();

/*
    [
        ['name' => 'Abigail Otwell', 'age' => 32],
        ['name' => 'Abigail Otwell', 'age' => 30],
        ['name' => 'Taylor Otwell', 'age' => 36],
        ['name' => 'Taylor Otwell', 'age' => 34],
    ]
*/

Applied to the example from the question:

/*
$sorting_instructions = [
    ['column'=>'first_name', 'order'=>'asc'],
    ['column'=>'date_of_birth', 'order'=>'desc'],
];
*/

$collection = User::all(); 

$sortedCollection = $collection->sortBy([
    ['first_name','asc'],
    ['date_of_birth','desc'],
])

Whoever uses this, just keep in mind - you'll need to tweak it depending if your using collection of objects or associative arrays. Should be an easy tweak. Just change the $a[]/$b[] stuff to $a-> and $b->

    public static function multiPropertySort(Collection $collection, array $sorting_instructions){

        return $collection->sort(function ($a, $b) use ($sorting_instructions){

            //stuff starts here to answer question...

            foreach($sorting_instructions as $sorting_instruction){

                $a[$sorting_instruction['column']] = (isset($a[$sorting_instruction['column']])) ? $a[$sorting_instruction['column']] : '';
                $b[$sorting_instruction['column']] = (isset($b[$sorting_instruction['column']])) ? $b[$sorting_instruction['column']] : '';

                if(empty($sorting_instruction['order']) or strtolower($sorting_instruction['order']) == 'asc'){
                    $x = ($a[$sorting_instruction['column']] <=> $b[$sorting_instruction['column']]);
                }else{
                    $x = ($b[$sorting_instruction['column']] <=> $a[$sorting_instruction['column']]);

                }

                if($x != 0){
                    return $x;
                }

            }

            return 0;

        })->values();
    }

Simple as doing this:

return $this->items->sortBy([
   'is_hidden', 'asc',
   'title', 'asc',
]);

:)

If you are using Eloquent to get your collection instance, It would be much better to use orderBy method in your query, especially if columns were indexed:

$sorting_insructions = [
    ['column'=>'first_name', 'order'=>'asc'],
    ['column'=>'date_of_birth', 'order'=>'desc'],
];

$collection = App\User::query();

foreach ($sorting_insructions as $value) {

    $collection->orderBy($value['column'], $value['order']);

}

$users = $collection->get();

EDIT Since the question was edited telling that sort should be used outside the query builder, I think chaining sortBy and sortByDesc in reverse order from $sorting_insructions gives the same result:

$collection = App\User::all();

$sorting_insructions = [
    ['column'=>'first_name', 'order'=>'asc'],
    ['column'=>'date_of_birth', 'order'=>'desc'],
];

for ($i = count($sorting_insructions) - 1; $i >= 0 ; $i--) { 

    extract($sorting_insructions[i]);

    if ( $order === 'asc') {
        $collection = $collection->sortBy( $column );
    } else {
        $collection = $collection->sortByDesc( $column );
    }

}
public static function multiPropertySort(
    Collection $collection,
    array $rules
)
{
    return $collection->sort(
        function ($a, $b) use ($rules) {
            foreach($rules as $rule){
                $sortColumn = array_get($rule, 'column');

                array_set(
                    $a,
                    $sortColumn,
                    array_get($a, $sortColumn, '')
                );

                array_set(
                    $b,
                    $sortColumn,
                    array_get($b, $sortColumn, '')
                );

                if ($sortOrder = array_get($rule, 'order', 'asc')) {
                    $x = (array_get($a, $sortColumn) <=> array_get($b, $sortColumn));
                } else {
                    $x = (array_get($b, $sortColumn) <=> array_get($a, $sortColumn));
                }

                if ($x != 0) {
                    return $x;
                }
            }

            return 0;
        }
    );
}

Let's imagine something imperative...

Related