Null Object Pattern
debt(d5/e3/b3/t5)
Closest to 'specialist tool catches it' (d5). The detection_hints list phpstan and psalm as tools, both static analysis specialists. The code_pattern identifies repeated null checks as a candidate — this is not caught by a compiler or default linter but requires a configured SAST/type-checker to surface the pattern.
Closest to 'simple parameterised fix' (e3). The quick_fix describes creating a NullLogger or GuestUser implementing the same interface — a small, focused refactor within one component. It is more than a single-line patch (you must create a new class and wire it in) but does not span multiple files cross-cuttingly in the typical case.
Closest to 'localised tax' (b3). The pattern applies to web, cli, and queue contexts broadly, but its structural burden is localised — once the NullObject class exists and is injected at one seam, the rest of the codebase is largely unaffected. It does not impose a persistent productivity tax on many work streams.
Closest to 'notable trap' (t5). The misconception field states that developers think it is just a way to avoid null checks, missing that it eliminates entire conditional branches structurally. The common_mistakes reinforce additional traps: partial interface implementation, returning null from null-object methods, and silently swallowing meaningful nulls — all documented gotchas that competent developers eventually learn.
Also Known As
TL;DR
Explanation
The Null Object pattern (Bobby Woolf) provides a do-nothing implementation of an interface that can be used wherever a null check would otherwise be required. For example, a NullLogger that implements LoggerInterface but discards all messages replaces if ($logger !== null) $logger->log(...) checks everywhere. The Null Object makes the absence of something explicit and type-safe, simplifies calling code, and follows Tell Don't Ask. PHP 8+ Null Object are often combined with union types and nullsafe operators as complementary approaches.
Common Misconception
Why It Matters
Common Mistakes
- Not implementing the full interface — callers that use methods not on the null object still get null errors.
- Null objects that return null from methods — they should return safe defaults (empty string, 0, empty array).
- Using null objects where an Optional/Maybe type better communicates the possible absence.
- Applying null objects everywhere — sometimes null carries meaning that should be handled, not silently swallowed.
Code Examples
// Null checks spread throughout call sites
$discount = $user->getDiscount();
$price = $discount !== null ? $price * (1 - $discount->rate()) : $price;
interface Discount {
public function apply(float $price): float;
}
class PercentDiscount implements Discount {
public function __construct(private float $rate) {}
public function apply(float $price): float { return $price * (1 - $this->rate); }
}
class NullDiscount implements Discount {
public function apply(float $price): float { return $price; } // no-op
}
// Now: no null check needed at call sites
$price = $user->getDiscount()->apply($price);