PHP · Intermediate
Associative Arrays and Sorting in PHP
In short: An associative array maps keys to values: ['name' => 'Kit', 'age' => 3]. Read with $a['name'], test with isset or array_key_exists, and loop with foreach ($a as $key => $value). sort orders values and renumbers keys, asort and ksort keep the key associations, and usort takes a comparison callback, usually written with the spaceship operator <=>. Since PHP 8.0 every sort is.
Keys that mean something
Any PHP array can use strings as keys, and when it does it works as a record or a lookup table. $nest = ['species' => 'wren', 'eggs' => 5] reads like a small object: $nest['eggs'] is 5, assigning to $nest['ringed'] adds a field, unset($nest['eggs']) removes one, and foreach ($nest as $key => $value) walks the fields in the order they were added. PHP keeps insertion order for every array, which is why a tally built in a loop prints in first-seen order.
Keys are normalised. The string "7" becomes the integer 7, a float key is truncated, true becomes 1 and null becomes "". Strings that do not look like integers, such as "07" or "7a", stay strings. This rarely matters until you use numeric identifiers as keys and then wonder why "7" and 7 hit the same slot.
Reading a key that does not exist gives null with a warning, so check first. isset($a['k']) is true when the key exists and the value is not null; array_key_exists('k', $a) is true whenever the key exists, even with a null value. The null-coalescing operator $a['k'] ?? 0 reads a key with a default and no warning, which makes the counting pattern a one-liner: $tally[$word] = ($tally[$word] ?? 0) + 1. Nesting gives you records inside lists: an array of ['name' => ..., 'price' => ...] arrays is the usual shape for a table of data, and array_column($rows, 'price') pulls one column out, or array_column($rows, 'price', 'name') builds a name-to-price lookup.
Sorting comes in families that differ in what they order and whether they keep keys. sort and rsort order by value and renumber the keys from 0, so they destroy an associative array's keys. asort and arsort order by value and keep the keys. ksort and krsort order by key. usort, uasort and uksort take a callback that receives two elements (or two keys) and returns a negative number, zero or a positive number; the u variants follow the same key rules as their plain counterparts. All of them sort in place and return true, so the sorted array is the variable you passed, not the return value.
The spaceship operator <=> returns -1, 0 or 1 and is the natural body of a comparison callback: fn($a, $b) => $a['price'] <=> $b['price']. Swap the operands to sort descending. To sort by several fields, compare arrays: [$a['price'], $a['name']] <=> [$b['price'], $b['name']] compares prices first and names only when prices tie. Since PHP 8.0 all sorting functions are stable, so elements that compare equal keep their original relative order, which makes multi-pass sorting predictable. Flags such as SORT_STRING, SORT_NUMERIC and SORT_NATURAL change how plain sort compares mixed or numbered strings.
Syntax
$stock = ["walnut" => 12, "birch" => 40];
$stock["oak"] = 40; // add or replace
unset($stock["walnut"]); // remove
$n = $stock["oak"]; // read
$n = $stock["pine"] ?? 0; // read with default, no warning
isset($stock["oak"]); array_key_exists("oak", $stock);
foreach ($stock as $wood => $qty) { ... }
array_keys($stock); array_values($stock); array_column($rows, "price", "name");
sort($a); rsort($a); // by value, keys renumbered
asort($a); arsort($a); // by value, keys kept
ksort($a); krsort($a); // by key
usort($rows, fn($x, $y) => $x["price"] <=> $y["price"]); // custom, renumbered
uasort($a, fn($x, $y) => $y <=> $x); // custom, keys kept
usort($rows, fn($x, $y) => [$y["price"], $x["name"]] <=> [$x["price"], $y["name"]]); // several fieldsSorting functions modify the array in place and return true. The callback must return an int: use <=>, not subtraction.
Records and tallies
Bird sightings are counted into an associative array, then a nest record shows isset against array_key_exists.
<?php
$visits = ["kestrel", "robin", "kestrel", "wren", "robin", "kestrel"];
$tally = [];
foreach ($visits as $bird) {
$tally[$bird] = ($tally[$bird] ?? 0) + 1;
}
foreach ($tally as $bird => $count) {
echo $bird, ": ", $count, "\n";
}
echo isset($tally["owl"]) ? "owl seen" : "no owl", "\n";
$nest = ["species" => "wren", "eggs" => 5, "ringed" => null];
var_dump(isset($nest["ringed"]), array_key_exists("ringed", $nest));
unset($nest["eggs"]);
echo implode(", ", array_keys($nest)), "\n";Output
kestrel: 3 robin: 2 wren: 1 no owl bool(false) bool(true) species, ringed
The ?? 0 supplies a starting count the first time a bird is seen, so no key needs to be created in advance, and the tally prints in first-seen order because PHP preserves insertion order. ringed exists but holds null, which is exactly the case where isset says false and array_key_exists says true. After unset, the remaining keys are listed in their original order.
sort, asort, arsort, ksort and krsort
The same timber stock sorted five ways; json_encode shows whether keys survived.
<?php
$stock = ["walnut" => 12, "birch" => 40, "ash" => 25, "oak" => 40];
$copy = $stock;
sort($copy);
echo json_encode($copy), "\n";
asort($stock);
echo json_encode($stock), "\n";
arsort($stock);
echo json_encode($stock), "\n";
ksort($stock);
echo json_encode($stock), "\n";
krsort($stock);
echo implode(" > ", array_keys($stock)), "\n";Output
[12,25,40,40]
{"walnut":12,"ash":25,"birch":40,"oak":40}
{"birch":40,"oak":40,"ash":25,"walnut":12}
{"ash":25,"birch":40,"oak":40,"walnut":12}
walnut > oak > birch > ashsort throws the wood names away and leaves a plain list, which is why it is applied to a copy. asort keeps them and orders by quantity; birch and oak tie at 40 and stay in their original order because sorting is stable. arsort reverses the order of values, and again birch stays ahead of oak. ksort and krsort ignore the values and order alphabetically by key.
usort with one rule, then two
Nursery plants are sorted by price, then by price descending with name as the tie-breaker, and finally looked up by name.
<?php
$plants = [
["name" => "Fern", "price" => 6.5, "stock" => 3],
["name" => "Aloe", "price" => 4.0, "stock" => 12],
["name" => "Ivy", "price" => 6.5, "stock" => 8],
["name" => "Basil", "price" => 2.5, "stock" => 0],
];
usort($plants, fn($a, $b) => $a["price"] <=> $b["price"]);
echo implode(", ", array_column($plants, "name")), "\n";
usort($plants, fn($a, $b) => [$b["price"], $a["name"]] <=> [$a["price"], $b["name"]]);
foreach ($plants as $p) {
printf("%-6s %5.2f %3d\n", $p["name"], $p["price"], $p["stock"]);
}
$inStock = array_filter($plants, fn($p) => $p["stock"] > 0);
echo count($inStock), " in stock\n";
$stockByName = array_column($plants, "stock", "name");
echo "Ivy: ", $stockByName["Ivy"], "\n";Output
Basil, Aloe, Fern, Ivy Fern 6.50 3 Ivy 6.50 8 Aloe 4.00 12 Basil 2.50 0 3 in stock Ivy: 8
The first sort orders ascending by price; Fern and Ivy tie and keep their original order. The second compares two-element arrays: prices are compared first with the operands swapped for descending order, and names, in normal order, decide ties, which puts Fern before Ivy. array_filter keeps the records with stock, and array_column with a third argument turns the list of records into a name-to-stock lookup.
Common mistakes
Using sort on an associative array
Why it goes wrong:
sortrenumbers the keys from 0, so['ash' => 25, 'oak' => 40]becomes[25, 40]and the names are gone.Fix: Use
asort/arsortto order by value orksort/krsortby key; they keep the associations.PHP · fix<?php $stock = ["oak" => 40, "ash" => 25]; asort($stock); foreach ($stock as $wood => $qty) { echo "$wood $qty\n"; }Testing for a key with isset when the value may be null
Why it goes wrong:
isset($row['note'])is false for a note that exists but is null, so the code treats an intentionally empty field as missing.Fix: Use
array_key_existswhen null is a legitimate value; useissetor??when null should count as absent.Returning a subtraction from a usort callback
Why it goes wrong: The callback must return an int.
fn($a, $b) => $a['price'] - $b['price']returns a float for prices such as 6.5 and 6.0, and PHP casts 0.5 to 0, treating them as equal.Fix: Return
$a['price'] <=> $b['price'], which is always -1, 0 or 1.Using the return value of a sort function
Why it goes wrong:
$sorted = sort($a);storestrue, because sorting happens in place and the function returns a boolean.Fix: Sort the variable and then use that variable; copy it first if you need the original order.
Sorting functions
| Function | Orders by | Keys | Direction |
|---|---|---|---|
| sort / rsort | value | renumbered from 0 | ascending / descending |
| asort / arsort | value | kept | ascending / descending |
| ksort / krsort | key | kept | ascending / descending |
| usort | callback on values | renumbered from 0 | as the callback says |
| uasort | callback on values | kept | as the callback says |
| uksort | callback on keys | kept | as the callback says |
Where you use this
Grouping is the pattern that appears in almost every data task: read records, then collect them under a key. $byRoom[$row['room']][] = $row['name'] builds a room-to-names map in one line, and $totals[$cat] = ($totals[$cat] ?? 0) + $amount sums per category. The output usually has to be ordered, and that is where choosing between ksort (alphabetical categories) and arsort (largest first) decides what the report looks like. For records with several fields, usort with an array comparison gives a full sort order in a single expression.
$totals = [];
while (($line = fgets(STDIN)) !== false) {
[$category, $amount] = explode(",", trim($line));
$totals[$category] = ($totals[$category] ?? 0) + (float) $amount;
}
arsort($totals);
foreach ($totals as $category => $sum) {
printf("%-10s %8.2f\n", $category, $sum);
}Key points
['key' => value]builds a record; insertion order is preserved."7"and 7 are the same key;"07"is not.issetis false for null values;array_key_existsonly cares about the key;??reads with a default.$tally[$k] = ($tally[$k] ?? 0) + 1counts;$groups[$k][] = $vgroups.sortrenumbers keys;asort/arsortandksort/krsortkeep them.usorttakes a callback returning an int; use<=>and compare arrays for several fields.- Sorting is in place, returns true, and has been stable since PHP 8.0.
Try it yourself
Each input line holds a name and a score. The program collects them into $scores keyed by name and prints them in input order. Change it to print them from highest score to lowest, keeping the input order for equal scores.
<?php
$scores = [];
while (($line = fgets(STDIN)) !== false) {
[$name, $score] = explode(" ", trim($line));
$scores[$name] = (int) $score;
}
foreach ($scores as $name => $score) {
echo $name, " ", $score, "\n";
}
mira 42 ↵ leo 58 ↵ suki 42 ↵ dev 71dev 71
leo 58
mira 42
suki 42<?php
$scores = [];
while (($line = fgets(STDIN)) !== false) {
[$name, $score] = explode(" ", trim($line));
$scores[$name] = (int) $score;
}
arsort($scores);
foreach ($scores as $name => $score) {
echo $name, " ", $score, "\n";
}
Practise this
- Fun run results tableMedium
Sort race finishers by time and then by name with usort and the spaceship operator, and print a ranked results table in PHP.
SortingNot started
- Ingredient tally for the weekMedium
Total ingredient quantities across recipes with a PHP associative array, then print them sorted by name using ksort.
Associative arraysNot started
Related lessons
- Arrays in PHPHow PHP arrays hold ordered lists: literals, indexing, appending, count, foreach, searching, slicing and the array_map, array_filter and array_sum helpers.9 min
- Strings in PHPSingle and double quotes, interpolation and heredoc, the string functions you reach for daily, multibyte text with mb_ functions and sprintf formatting in PHP.10 min
- JSON and Data Handling in PHPEncode PHP arrays to JSON and decode JSON into arrays, choose the right flags, avoid the list-versus-object trap, and turn raw input lines into structured.11 min
Frequently asked questions
What is the difference between isset and array_key_exists in PHP?
isset($a['k']) returns true only when the key exists and its value is not null. array_key_exists('k', $a) returns true whenever the key exists, whatever the value. If null is a meaningful value in your data, use array_key_exists; otherwise isset, or the ?? operator, is shorter and also avoids warnings.
Does sort() keep the keys of a PHP array?
No. sort and rsort reindex the result from 0, which is fine for lists and destructive for associative arrays. asort, arsort, ksort, krsort, uasort and uksort all preserve the key-value pairs; usort reindexes like sort.
Is sorting in PHP stable?
Yes, since PHP 8.0. Elements that compare as equal keep their original relative order in every sorting function, including usort. Before 8.0 the order of equal elements was not guaranteed. Stability means you can sort by a secondary field first and a primary field second and get a correct combined order, although a single usort with an array comparison is clearer.
How this page was checked. Every program on it was run with PHP 8.4 at build time by the publishing checks, and the output shown is what it printed. Running PHP inside the browser is not available yet, so the Run button is absent rather than pretending; copy the code and run it with PHP 8.4 locally.