Lesson 51 of 100
Arrays15 min5 checksPHP 8.5

Write Custom Array Comparators

Learn how to sort PHP records with usort(), the spaceship operator and tie breakers so the result is always deterministic.

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 $a should come before $b
  • zero if they are equal for sorting purposes
  • a positive number if $a should 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.

Example
<?php
$numbers = [3, 10, 1, 7];
usort($numbers, fn(int $a, int $b): int => $b <=> $a);
echo json_encode($numbers), "\n";
Output
[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".

Example
<?php
$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";
Output
["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.

Example
<?php
$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";
Output
["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.

Before you start

Functions · Arrays · Types

You’ll practice
  • Sort records using spaceship comparisons and tie breakers
  • Implement and test a deterministic PHP 8.4 function
Your task

Make rankPlayers() pass all 5 checks. Run your code as often as you like, then check your answer.

exercise.phpSaved in this browserPHP 8.5
CONSOLE & CHECKS● Ready

Ready when you are

Your output will appear here.
Make it work. Then make it pass.

PHP loads on your first run. Each check starts fresh. Your code stays in this browser; clearing site data removes your progress. A check or the task looks wrong? Report a problem with this exercise.