Helloworlder's blog

Multisort array in PHP

2021-06-17 array, sorting, php

We have the following data:

Last nameFirst name
PetrovaSveta
IvanovOleg
PetrovaAnna
IvanovIvan

We need to sort it by last name and first name in ascending order. How can we do that in PHP? array_multisort can help us!

More info about array_multisort: https://www.php.net/manual/en/function.array-multisort.php

$data = [
    ['Petrova', 'Sveta'],
    ['Ivanov', 'Oleg'],
    ['Petrova', 'Anna'],
    ['Ivanov', 'Ivan'],
];

$lastNames = array_column($data, 0);
$firstNames = array_column($data, 1);
// This workaround handles cases where the columns used for sorting have equal values.
// Without it, this can lead to a fatal error: Nesting level too deep - recursive dependency?
// Thanks to Yii community https://github.com/yiisoft/yii2/issues/8348
$indexes = range(1, count($data));

array_multisort(
    $lastNames,
    SORT_ASC,
    SORT_STRING,
    $firstNames,
    SORT_ASC,
    SORT_STRING,
    $indexes,
    SORT_ASC,
    SORT_NUMERIC,
    $data
);

Result:

Last nameFirst name
IvanovIvan
IvanovOleg
PetrovaAnna
PetrovaSveta

This example is simple, and we haven't used descending order yet. Let's sort last names in ascending order and first names in descending order.

array_multisort(
    $lastNames,
    SORT_ASC,
    SORT_STRING,
    $firstNames,
    SORT_DESC,
    SORT_STRING,
    $indexes,
    SORT_ASC,
    SORT_NUMERIC,
    $data
);

Result:

Last nameFirst name
IvanovOleg
IvanovIvan
PetrovaSveta
PetrovaAnna

By the way, we don't have to use array_multisort. Below, I'll show how to sort by multiple fields without this "magic" function (thanks to the Doctrine community).

$data = [
    ['Petrova', 'Sveta'],
    ['Ivanov', 'Oleg'],
    ['Petrova', 'Anna'],
    ['Ivanov', 'Ivan'],
];

$orderings = [
    'lastName' => SORT_ASC,
    'firstName' => SORT_DESC,
];

$orderFn = null;
foreach (array_reverse($orderings) as $field => $ordering) {
    $orderFn = function (array $a, array $b) use ($orderFn, $field, $ordering): int {
        switch ($field) {
            case 'lastName':
                $column = 0;
                break;
                
            case 'firstName':
                $column = 1;
                break;
                
            default:
                return 0;
        }
        
        if ($a[$column] === $b[$column]) {
            if ($orderFn !== null) {
                return $orderFn($a, $b);
            } else {
                return 0;
            }
        }
        
        return ($a[$column] <=> $b[$column]) * ($ordering === SORT_ASC ? 1 : -1);
    };
}

uasort($data, $orderFn);

Result:

Last nameFirst name
IvanovOleg
IvanovIvan
PetrovaSveta
PetrovaAnna