match Expression (PHP 8.0)
debt(d5/e3/b3/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list rector, phpcs, and php-cs-fixer — all specialist/configurable tools rather than default linters. These tools can flag switch statements that should be match expressions, or catch misuse of match (e.g. missing default arm), but it requires deliberate tool configuration rather than out-of-the-box catching.
Closest to 'simple parameterised fix' (e3). The quick_fix describes replacing switch statements with match() — a pattern-level substitution within a single block. It's more than a one-liner because you must verify strict vs loose comparison semantics and add a default arm, but it stays within a single component and doesn't span multiple files.
Closest to 'localised tax' (b3). Match expressions apply at the individual statement level in web, cli, and queue-worker contexts, but the choice is scoped to wherever a given switch/match lives. Misuse (e.g. missing default arm causing UnhandledMatchError in production) is localised to that code path and doesn't impose a cross-cutting structural burden on the codebase.
Closest to 'serious trap' (t7). The misconception field explicitly identifies this: developers assume match is just cleaner switch syntax, but it uses strict comparison (===) instead of loose (==), and throws UnhandledMatchError for unmatched values instead of silently falling through. These are meaningful behavioural differences that contradict how the superficially similar switch statement works — a competent developer migrating switch to match without this knowledge will introduce subtle bugs.
Also Known As
TL;DR
Explanation
The match expression was introduced in PHP 8.0 as a replacement for switch with three key improvements: it uses strict (===) comparison instead of loose (==), each arm is an expression returning a value (no break needed), and it throws an UnhandledMatchError if no arm matches (unlike switch which silently falls through). Multiple conditions can be comma-separated in a single arm. match is particularly useful for mapping one value to another without mutation.
Common Misconception
Why It Matters
Common Mistakes
- Using match when you need fall-through across multiple conditions — match has no fall-through by design.
- Forgetting match throws UnhandledMatchError for unmatched values — always include a default arm if exhaustiveness is not guaranteed.
- Using match for complex multi-statement logic per arm — it returns a single expression, use if/else for multi-line branches.
- Assuming match works like switch for loose comparisons — match is always strict (===).
Code Examples
// switch with type coercion — '1' matches 1:
switch ($status) {
case 1: return 'active'; // '1' also matches — loose comparison
case 0: return 'inactive';
}
// match uses strict comparison:
return match($status) {
1 => 'active',
0 => 'inactive',
default => throw new UnhandledMatchError(),
};
// match is strict (===), exhaustive, and expression-based
$label = match($order->status) {
'pending' => 'Awaiting payment',
'paid' => 'Processing',
'shipped' => 'On the way',
'cancelled' => 'Cancelled',
default => throw new \UnexpectedValueException("Unknown status: {$order->status}"),
};
// No default = UnhandledMatchError on unmatched value — catches missing cases at runtime
// PHPStan/Psalm can also verify exhaustiveness at analysis time