0

It works like a charm in SQL:

SELECT * FROM person ORDER BY age DESC, name ASC

But what if we have such data in PHP array. How can we do the same? For example, if I have

$a = [
['name' => 'Alfred', 'age' => 40],
['name' => 'Mark', 'age' => 40],
['name' => 'Lue', 'age' => 45],
['name' => 'Ameli', 'age' => 38],
['name' => 'Barb', 'age' => 38],
];

and I want to sort it by age in descending order and then by name in ascending order. Just like in the SQL above. So the right sequence of names is: Lue, Alfred, Mark, Ameli, Barb. See db-fiddle.

The wrong attempt to get it in PHP is:

usort($array, function ($a, $b) {
    return -strnatcasecmp($a['age'], $b['age']); // desc
});

usort($array, function ($a, $b) {
    return strnatcasecmp($a['name'], $b['name']); // asc
});

Each of two usort calls works fine, but it overrides the previous result, while I want to combine them all together. Ideally, I'd like to have a function that accepts any amount of Callable to usort them all.

Please advise.

Update: As reasonable commented bellow, array_multisort is suitable for sorting regular array like this. But I'd like to find solution to combine comparator closures. ANY comparators, even like this one.
By the way, SQL allows to sort by expression, not only by simple field.

artoodetoo
  • 918
  • 10
  • 55
  • Does this answer your question? [PHP sort array by two field values](https://stackoverflow.com/questions/4582649/php-sort-array-by-two-field-values) – Vladimir Tarkhanov Feb 11 '20 at 19:55
  • @VladimirTarkhanov yes and no. I see how to solve this very case with name and age. But the desired result is to combine any comparator functions, suitable for usort(), not just a field names. I will update my question. Thank you. – artoodetoo Feb 11 '20 at 20:02

2 Answers2

2

Here's a way to do it:

function combineComparators(...$comparators)
{
    return function($a, $b) use($comparators)
    {
        foreach($comparators as $c)
        {
            $res = $c($a, $b);
            if($res!=0)
                return $res;
        }
        return 0;
    };
}

$a = [
['name' => 'Alfred', 'age' => 40],
['name' => 'Mark', 'age' => 40],
['name' => 'Lue', 'age' => 45],
['name' => 'Ameli', 'age' => 38],
['name' => 'Barb', 'age' => 38],
];

$cmp1 = function ($a, $b) {
    return -strnatcasecmp($a['age'], $b['age']); // desc
};

$cmp2 = function ($a, $b) {
    return strnatcasecmp($a['name'], $b['name']); // asc
};

usort($a, combineComparators($cmp1, $cmp2));

var_dump($a);
Olivier
  • 13,283
  • 1
  • 8
  • 24
  • @Oliver briliant! It seems working on my example array and functions. I will test it a bit more on other comparators and very likely mark your answer as "solution". http://sandbox.onlinephpfunctions.com/code/922863c6f7303a36df087730d682f32ae1ecabdd – artoodetoo Feb 11 '20 at 20:39
1

Somebody will have a good usort, but I prefer array_multisort.

Extract each column, sort on those in order and then sort the original by that sort order:

array_multisort(array_column($a, 'age'), SORT_DESC, array_column($a, 'name'), SORT_ASC, $a);
AbraCadaver
  • 78,200
  • 7
  • 66
  • 87