Sorting with your own rule: usort
Picture a shop assistant putting goods on a shelf. They do not know how to order them: by price, by weight or by name. You hand them a card with the rule: "of two items, put the cheaper one first".
The assistant takes a pair of items, checks the card and puts them in order. Then they take the next pair, and so on until the shelf is arranged the way it should be.
That is how the PHP function usort works. It sorts an array and takes the rule for which of two elements comes first as a function:
<?php
$nums = [30, 5, 12];
usort($nums, fn($a, $b) => $a <=> $b);
echo implode(' ', $nums), "\n"; // 5 12 30Let's walk through the program line by line:
$nums = [30, 5, 12];is a list of numbers in mixed order.usort($nums, …)takes the array and a comparison function, our card with the rule.fn($a, $b) => $a <=> $btakes two elements and answers which of them comes first.- After
usort, the numbers in$numsare in ascending order,implodejoins them into the string5 12 30, andechoprints it.
The comparison function
A comparison function takes two elements, $a and $b, and returns an integer:
- negative:
$agoes before$b; - zero: the elements are equal;
- positive:
$agoes after$b.
As you already know, the <=> operator returns -1, 0 or 1, which are exactly these answers. So $a <=> $b is a ready-made ascending comparison function.
The function must return a number. If it returns true or false, as $a > $b does, PHP 8 prints a Deprecated message: this way of writing it is outdated.
usort calls the comparison function many times, for different pairs of elements. PHP decides how many calls there will be and in what order the pairs arrive. This is where the shop-assistant picture is off: the assistant sees the whole shelf at once, while the comparison function sees only the two elements usort hands it.
Sort order
The same usort function sorts in any order: the comparison function decides everything.
Ascending
$a <=> $b: the smaller element comes first, as in the first example.
Descending
To put the larger elements first, swap $a and $b. With the function fn($a, $b) => $b <=> $a, the same array becomes 30 12 5.
By a record field
Array elements can be records, that is associative arrays. Then the function compares one field of the record:
$users = [
['name' => 'Olga', 'age' => 31],
['name' => 'Ivan', 'age' => 25],
['name' => 'Anna', 'age' => 31],
['name' => 'Petr', 'age' => 19],
];
usort($users, fn($a, $b) => $a['age'] <=> $b['age']);
$names = [];
foreach ($users as $user) {
$names[] = $user['name'];
}
echo implode(' ', $names), "\n"; // Petr Ivan Olga AnnaLet's go step by step:
$a['age'] <=> $b['age']compares only the age and ignores the names.- The records go from youngest to oldest: Petr first, then Ivan, and then Olga and Anna.
- Olga and Anna are the same age,
31, and the function returns0. Since PHP 8.0 the sort is stable: equal elements keep their original order, so Olga stays before Anna.
You can also compare a computed value: strlen($a) <=> strlen($b) orders strings by length, from shortest to longest.
usort changes the array itself
usort does not return a new array. It takes the array by reference, like a parameter with &, rearranges the elements right inside it and returns true.
There is a second feature too: after usort, the keys are renumbered from 0. An associative array loses its keys and becomes a list:
$prices = ['tea' => 40, 'milk' => 25, 'jam' => 90];
usort($prices, fn($a, $b) => $a <=> $b);
echo implode(' ', $prices), "\n"; // 25 40 90
echo implode(' ', array_keys($prices)), "\n"; // 0 1 2The prices are now in ascending order, but the keys tea, milk and jam are gone: 0, 1 and 2 took their place. If you need to keep the keys, usort is not the right tool.
Interviewers like to ask what usort returns. The answer: true, while the array you passed becomes sorted itself, and its keys are renumbered.
Common mistake
The result of usort was stored in a variable as if it were the sorted array:
<?php
$nums = [30, 5, 12];
$sorted = usort($nums, fn($a, $b) => $a <=> $b); // ✗
echo implode(' ', $sorted), "\n";Fatal error: Uncaught TypeError: implode(): Argument #2 ($array) must be of type ?array, true given$sorted holds true, and implode expects an array, so it stops the program with a TypeError. It is $nums itself that is sorted, so the fix is to call usort($nums, …) on its own line and print $nums.
Key points
usort($items, $f)sorts an array by the comparison function$f, which takes two elements.- The comparison function returns a negative number, zero or a positive number;
$a <=> $bsorts ascending,$b <=> $adescending. usortchanges the array itself, returnstrueand renumbers the keys.- Since PHP 8.0 the sort is stable: equal elements keep their original order.