PHPNull Coalescing & Spaceship

Null Coalescing & Spaceship

Two operators added in relatively recent PHP versions solve two very different problems, but both tend to appear side by side in modern codebases: the null coalescing operator ?? (and its assignment form ??=) for supplying default values cleanly, and the spaceship operator <=> for writing compact three-way comparisons, most often inside sorting callbacks.

The null coalescing operator: ??

$a ?? $b evaluates to $a if $a exists and is not null; otherwise it evaluates to $b. Crucially, ?? does not raise a warning if $a is completely undefined — it behaves as if you had wrapped the check in isset() first, which makes it the standard tool for reading optional array keys or request parameters.

?? for default values

PHP
<?php
$settings = ["theme" => "dark"];

$theme = $settings["theme"] ?? "light"; // "dark" - key exists
$fontSize = $settings["fontSize"] ?? 14; // 14 - key missing, no warning

echo "{$theme}, {$fontSize}px";
dark, 14px
?? vs the isset() ternary

Before PHP 7, the common pattern was a verbose ternary built on isset(). ?? collapses that entire pattern into one clean expression and, unlike a plain ternary on the value itself, it specifically checks for "set and not null" rather than truthiness — so a value of 0, "", or false is left alone, not replaced.

?? replaces the isset() ternary

PHP
<?php
$page = isset($_GET['page']) ? $_GET['page'] : 1; // old, verbose
$page = $_GET['page'] ?? 1;                        // same result, cleaner

$discount = 0; // a legitimate value, not "missing"
$applied = $discount ?? 10;   // 0 - ?? only replaces null/unset
$fallback = $discount ?: 10;  // 10 - ?: replaces any falsy value, including 0
?? is not the same as the short ternary ?:
`$a ?: $b` (the "Elvis operator") returns `$b` whenever `$a` is falsy — that includes `0`, `""`, `"0"`, and `false`, not just `null`. `$a ?? $b` only falls through to `$b` when `$a` is `null` or unset. Mixing these up is a real source of bugs: using `?:` to supply a default for a numeric setting will incorrectly override a legitimate `0`.
Chaining ?? and reading nested data

?? can be chained across several fallbacks, and it also short circuits on undefined nested array keys without raising warnings — which is exactly the situation where directly indexing nested arrays tends to blow up.

Chaining and nested keys

PHP
<?php
$request = ["user" => ["name" => "Sam"]];

$city = $request["user"]["address"]["city"] ?? "Unknown"; // safe, no warning
$name = $request["user"]["nickname"] ?? $request["user"]["name"] ?? "Guest";

echo "{$city}, {$name}";
Unknown, Sam
The null coalescing assignment operator: ??=

$a ??= $b is shorthand for $a = $a ?? $b: assign $b into $a only if $a is currently null or does not exist. It reads naturally as "fill this in if it's missing."

??= for filling in defaults

PHP
<?php
$options = [];
$options["retries"] ??= 3;
$options["retries"] ??= 5; // no effect, already set

echo $options["retries"]; // 3
3
The spaceship operator: <=>

$a <=> $b returns -1 when $a is less than $b, 0 when they are equal, and 1 when $a is greater than $b. It works on numbers, strings, and arrays using the same rules PHP already applies for &lt;/&gt; comparisons. Its real value shows up in sorting: any function that expects a comparator — most notably usort() — wants exactly this -1/0/1 contract.

Spaceship in a usort callback

PHP
<?php
$people = [
    ["name" => "Zoe", "age" => 25],
    ["name" => "Amir", "age" => 31],
    ["name" => "Priya", "age" => 19],
];

usort($people, fn($a, $b) => $a["age"] <=> $b["age"]);

foreach ($people as $person) {
    echo "{$person['name']} ({$person['age']})\n";
}
Priya (19)
Zoe (25)
Amir (31)

Before the spaceship operator, the same comparator was usually written as a longer conditional chain returning -1, 0, or 1 by hand. <=> produces exactly the same result in a fraction of the code, and it composes nicely for multi-key sorts.

Sorting by multiple keys

PHP
<?php
$products = [
    ["category" => "B", "price" => 20],
    ["category" => "A", "price" => 50],
    ["category" => "A", "price" => 10],
];

usort($products, function ($a, $b) {
    return $a["category"] <=> $b["category"]
        ?: $a["price"] <=> $b["price"];
});

foreach ($products as $p) {
    echo "{$p['category']} - {$p['price']}\n";
}
A - 10
A - 50
B - 20
  • ?? returns the left side unless it is null/unset, then falls back to the right side.

  • ??= assigns only when the target is null or unset.

  • ?? differs from ?:, which falls back on any falsy value, not just null.

  • <=> returns -1, 0, or 1 and is the natural fit for usort()/uasort() callbacks.

?? works even on undefined variables and array keys
Unlike directly reading an undefined variable or array offset, which raises a warning in modern PHP, `??` on the left side of the operator suppresses that warning entirely — that safety is the whole reason the operator exists.
Tip
Combine `<=>` with the null coalescing operator when sorting data that might contain missing fields: `($a["rank"] ?? PHP_INT_MAX) <=> ($b["rank"] ?? PHP_INT_MAX)` pushes items without a rank to the end instead of crashing or sorting unpredictably.