Nullsafe Operator Chaining (?->)
debt(d5/e3/b3/t5)
Closest to 'specialist tool catches it' (d5). The detection_hints list phpstan and phpcs, which are specialist static analysis tools, not default linters. The code_pattern notes chains >3 operators or duplicated chains — these require intentional configuration of specialist tools to catch; they won't surface as a compiler or default-linter warning.
Closest to 'simple parameterised fix' (e3). The quick_fix describes extracting a long chain to a named method — a small, localised refactor within one component or file. It is more than a one-line swap (e1) because it involves introducing a new method and updating call sites, but it stays within a single class or component rather than spanning multiple files.
Closest to 'localised tax' (b3). Applies broadly across web, cli, and queue-worker contexts but the structural weight is localised: a nullsafe chain is a single expression pattern. Overuse can create readability debt in the files where it appears, but it does not impose a strong gravitational pull on the rest of the codebase or shape architectural decisions.
Closest to 'notable trap' (t5). The misconception field captures the core trap: developers assume a long nullsafe chain is fully safe because null is handled at every step, but exceptions thrown for non-null invalid state are not caught by ?->. Additionally, common_mistakes highlight the silencing-errors pitfall and the misunderstanding of short-circuit evaluation against the result of a sub-chain. These are documented gotchas that competent developers eventually learn, placing it at t5 rather than t7.
Also Known As
TL;DR
Explanation
The nullsafe operator ?-> (PHP 8.0) short-circuits a method/property chain: $country = $user?->getAddress()?->getCity()?->getCountry(). If any step returns null, the entire expression evaluates to null without calling subsequent methods. This replaces verbose nested null checks or if chains that obscure business logic. Key behaviour: the right-hand side of ?-> is not evaluated at all when the left side is null — useful when those calls have side effects. Static calls with ?-> are not supported. Nullsafe chains interact cleanly with the null coalescing operator: $name = $user?->getName() ?? 'Guest'.
Common Misconception
Why It Matters
Common Mistakes
- Using nullsafe operator on every method call when only specific steps can actually return null.
- Not understanding short-circuit behaviour — if $a?->b() returns null, $a?->b()?->c() evaluates c() against null, not $a.
- Mixing nullsafe and regular -> in a chain without considering which links can realistically be null.
- Using nullsafe chains where throwing on null is the correct behaviour — nullsafe silences what should be an error.
Code Examples
// Nested null checks — verbose:
if ($user !== null && $user->getProfile() !== null) {
$city = $user->getProfile()->getAddress()->getCity();
}
// Nullsafe chain:
$city = $user?->getProfile()?->getAddress()?->getCity();
// Before PHP 8.0
$country = null;
if ($user !== null) {
$address = $user->getAddress();
if ($address !== null) { $country = $address->getCountry(); }
}
// PHP 8.0
$country = $user?->getAddress()?->getCountry();