The Null-Safe Operator and Friends: PHP's Quiet Ergonomics Wave
Modern PHP's headline features get the blog posts, but four small operators do more for everyday readability than any of them. If your codebase predates PHP 8, these are the highest-density cleanups available.
?-> - the null-safe chain
// Before: the pyramid of guards
$city = null;
if ($order !== null) {
$customer = $order->getCustomer();
if ($customer !== null) {
$address = $customer->getAddress();
if ($address !== null) {
$city = $address->city;
}
}
}
// PHP 8.0
$city = $order?->getCustomer()?->getAddress()?->city;
Any null in the chain short-circuits the rest of the chain to null - later method calls don't even evaluate their arguments. Combine with ?? for the default: $city = $order?->...?->city ?? 'Unknown';. One discipline note: chains of five ?-> are a design smell (ask why everything is nullable), but two or three replace real, ugly guard code.
??= - assign only if null
$options['timeout'] ??= 30;
$this->cache[$key] ??= $this->compute($key); // compute() skipped when cached
Lazy defaults and memoization in one token, with the same short-circuit guarantee as its JavaScript twin: the right side never runs if the value exists. Careful with one PHP-ism: ?? treats null as missing but not '' or 0 - which is exactly right for defaults, unlike the old $x = $x ?: $default that clobbered legitimate falsy values.
throw as an expression (8.0)
$user = findUser($id) ?? throw new NotFoundException("User $id");
$total = $qty > 0 ? $qty * $price : throw new InvalidArgumentException('qty');
fn ($x) => $x ?: throw new LogicException('empty');
throw used to be a statement, banished from expression positions - hence the if-null-throw三-liner at the top of every method. As an expression it slots into ??, ternaries and arrow functions, turning guard clauses into single readable lines. The ??-throw combo is the one you'll use weekly.
<=> - the spaceship
usort($products, fn ($a, $b) => $a->price <=> $b->price);
usort($rows, fn ($a, $b) =>
[$a->priority, $a->createdAt] <=> [$b->priority, $b->createdAt] // multi-key!
);
Returns -1/0/1, exactly what comparators want. The array-comparison trick in the second example - comparing tuples for multi-column sorts - deletes entire nested if-blocks and belongs in every PHP developer's pocket.
None of these four changes architecture. Together they change the texture of the code: less ceremony per idea, fewer lines whose only job is checking for null. That texture is what people are praising when they say modern PHP is pleasant - and it's available in any codebase running 8.0+, which by now should be all of them.
Full-stack web developer sharing practical tutorials and building tools that ship.
Got something on your mind?
My inbox is open - no forms disappearing into the void here.
- Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
- Hire me for a project Have something custom in mind? Let's talk scope and timelines.
- Product support Bought something here? I'll help you get it running.
I usually reply within 1-2 business days.