01 · THE IDEAREAD → TRY → LEARN
Why write your own comparator?
Built-in functions such as sort() work well for plain numbers or strings. Real programs usually hold records: players with scores, products with prices, people with first and last names. For those, you must tell PHP what "comes first" means. usort() does this by calling a function you supply.
The signature is usort(array &$array, callable $callback): true. It sorts the array in place and always returns true. It also assigns new keys, so the result is a list numbered from zero. Any original keys are discarded.
The comparator contract
Your callback receives two elements, $a and $b. It must return an integer:
- a negative number if
$ashould come before$b - zero if they are equal for sorting purposes
- a positive number if
$ashould come after$b
The spaceship operator <=> produces exactly that contract: -1, 0 or 1. Swapping the operands reverses the order, so $b <=> $a sorts descending.
$numbers = [3, 10, 1, 7];
usort($numbers, fn(int $a, int $b): int => $b <=> $a);
echo json_encode($numbers), "\n";[10,7,3,1]
Avoid returning something like $a - $b for non-integer data. The manual warns that a returned float is cast to an integer, so 0.5 becomes 0 and the two values look equal. Use <=> instead.
Sorting by several fields
When two records tie on the first field, you need a tie breaker. A neat trick is to compare arrays. PHP compares equal-length arrays element by element from left to right and stops at the first difference. So [$a['last'], $a['first']] <=> [$b['last'], $b['first']] means "last name first, then first name".
$people = [
['first' => 'Adam', 'last' => 'West'],
['first' => 'Alec', 'last' => 'Baldwin'],
['first' => 'Adam', 'last' => 'Baldwin'],
];
usort($people, fn(array $a, array $b): int => [$a['last'], $a['first']] <=> [$b['last'], $b['first']]);
echo json_encode(array_map(fn(array $p): string => $p['last'] . ', ' . $p['first'], $people), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";["Baldwin, Adam","Baldwin, Alec","West, Adam"]
To mix directions, swap the operands only for the field you want descending. For example, [$b['score'], $a['name']] <=> [$a['score'], $b['name']] means "highest score first, then name A to Z". The score sits on the swapped side, while the name keeps the normal side.
Stability and ties
Since PHP 8.0, sorting is stable: elements that compare as equal keep their original relative order. That is helpful, but it also means the output depends on input order if you skip a tie breaker. Adding an explicit final field makes the outcome independent of how the data arrived.
$items = [
['name' => 'b', 'level' => 1],
['name' => 'a', 'level' => 1],
['name' => 'c', 'level' => 0],
];
$byLevel = $items;
usort($byLevel, fn(array $x, array $y): int => $x['level'] <=> $y['level']);
$withTieBreak = $items;
usort($withTieBreak, fn(array $x, array $y): int => [$x['level'], $x['name']] <=> [$y['level'], $y['name']]);
echo json_encode(array_column($byLevel, 'name')), "\n";
echo json_encode(array_column($withTieBreak, 'name')), "\n";["c","b","a"] ["c","a","b"]
The first line keeps b before a because they tied on level. The second line orders them by name.
Common mistakes
- Forgetting that
usort()changes the array you pass. Copy it first if you need the original. - Expecting keys to survive. Use
uasort()when keys must be kept. - Returning a boolean such as
$a > $b. Return an integer from<=>. - Reversing the wrong field when mixing ascending and descending order.
- Comparing strings that differ only in case. With
<=>, uppercase ASCII letters sort before lowercase ones, so"Bob"comes before"bob".
Your exercise
Write rankPlayers(array $players): array. Each player is an array with a string name and an integer score. Return only the names, ordered by highest score first. Players with equal scores are ordered by name using normal string comparison. An empty list returns an empty list. Work on the parameter inside the function, because arrays are passed by value and your sort will not touch the caller's data. Finish by pulling the names out with array_column().
Recap
A comparator returns a negative, zero or positive integer. Use <=> to produce it, compare arrays to chain several fields, and swap operands to reverse a single field. Always include a final tie breaker so the order is fully determined.
Functions · Arrays · Types
- Sort records using spaceship comparisons and tie breakers
- Implement and test a deterministic PHP 8.4 function