match Expression (PHP 8.0)
debt(d3/e3/b3/t7)
Closest to 'default linter catches the common case' (d3). The term's detection_hints list rector and phpcs, both of which can detect switch blocks that should be replaced with match (code_pattern: switch\s*\() and flag missing default arms. This is standard linter/fixer territory, not requiring a specialist SAST tool.
Closest to 'simple parameterised fix' (e3). The quick_fix describes replacing switch blocks with match expressions, adding default arms, and converting fall-through groups to comma-separated patterns. This is a mechanical but non-trivial refactor within individual components — not a one-liner, but not cross-cutting either. Rector can automate much of it.
Closest to 'localised tax' (b3). Match applies to web, cli, and queue-worker contexts, giving it moderate reach. However, the burden is localised: each switch-to-match conversion is a self-contained change and the choice doesn't impose a persistent productivity tax on unrelated parts of the codebase.
Closest to 'serious trap' (t7). The misconception field explicitly states: 'match is just syntactic sugar for switch' — but it uses strict === comparison (no type coercion), returns a value, throws UnhandledMatchError on no match, and has no fall-through. These are multiple significant behavioural differences from switch, contradicting how a nearly identical construct (switch) works in PHP and most other languages. A developer migrating switch logic to match without knowing these differences can introduce silent type-strictness bugs or unexpected exceptions.
TL;DR
Explanation
match($x) { pattern => value, ... } differences from switch: strict comparison (===, no coercion), each arm is a single expression (no statements, no break needed), match returns a value (expression, not statement), no fall-through (comma-separated patterns for multiple matches), throws UnhandledMatchError with no match and no default. Multiple patterns per arm: 1, 2 => 'low'. Combining with null coalescing: $result = match(true) { $a > 0 => 'positive', default => 'zero or negative' }. Works with enums (PHP 8.1): match($status) { Status::Active => ... }.
Common Misconception
Why It Matters
Common Mistakes
- Forgetting default arm — throws UnhandledMatchError.
- Trying multi-statement arms — each arm is one expression.
- Not knowing match uses === — helps avoid type coercion bugs common in switch.
Code Examples
switch ($status) {
case 'active': $label = 'Active'; break;
case 'inactive': $label = 'Inactive'; break;
default: $label = 'Unknown';
}
$label = match($status) {
'active' => 'Active',
'inactive' => 'Inactive',
default => throw new InvalidArgumentException("Unknown: $status")
};
// Multiple patterns:
$type = match($code) {
200, 201, 202 => 'success',
400, 422 => 'client error',
500, 503 => 'server error',
};