The match expression, added in PHP 8.0, is a value-returning, strictly-compared alternative to switch.
match fixes several long-standing footguns in switch:
- It returns a value, so it can be assigned directly.
- It uses strict
===comparison, avoiding type-juggling surprises. - There is no fallthrough, so no
breakstatements are needed. - An unmatched value with no
defaultthrowsUnhandledMatchErrorinstead of silently doing nothing.
<?php
$status = 404;
$message = match ($status) {
200, 201, 204 => 'Success', // multiple conditions per arm
301, 302 => 'Redirect',
404 => 'Not Found',
500 => 'Server Error',
default => 'Unknown',
};
echo $message; // Not Found| Aspect | match |
switch |
|---|---|---|
| Comparison | strict === |
loose == |
| Returns a value | yes | no |
| Fallthrough | none | yes (needs break) |
| Body per branch | single expression | statements block |
| No match, no default | throws UnhandledMatchError |
silently continues |
| Multiple values per arm | 1, 2, 3 => |
stacked case labels |
Because match uses ===, "1" (string) does not match 1 (int). This prevents the type-confusion bugs common in switch.
<?php
$input = '1'; // string from $_GET
// switch: loose == means '1' matches case 1
switch ($input) {
case 1: echo "switch: matched int 1\n"; break;
default: echo "switch: default\n";
}
// match: strict === means '1' does NOT match int 1
echo match ($input) {
1 => "match: matched int 1\n",
'1' => "match: matched string '1'\n",
default => "match: default\n",
};Expected output:
switch: matched int 1
match: matched string '1'
Matching against true lets each arm hold a boolean expression, giving you if/elseif chains that return a value.
<?php
$score = 82;
$grade = match (true) {
$score >= 90 => 'A',
$score >= 80 => 'B',
$score >= 70 => 'C',
default => 'F',
};
echo $grade; // BNever leave match without a default when the input can be attacker-controlled; an unexpected value would throw UnhandledMatchError. Either supply a default or catch the error to fail safely.
<?php
try {
$role = match ($_GET['role'] ?? '') {
'admin' => 'admin',
'user' => 'user',
};
} catch (\UnhandledMatchError) {
$role = 'guest'; // deny by default
}- Strict
===comparison removes the type-juggling foothold attackers exploit against looseswitch(==), where request strings like'1','0e123', or'true'can collide with integers or booleans. - Always handle the unmatched case for attacker-controlled input — supply a
defaultor catchUnhandledMatchError— and make that fallback deny by default (e.g.guest), never grant. - Because each arm is a single expression, avoid smuggling side-effectful or state-changing calls into arms; keep authorization decisions pure and auditable.
match is PHP 8.0's safer replacement for switch: it returns a value, compares with strict ===, has no fallthrough, and throws UnhandledMatchError on an unmatched value instead of failing silently. The match (true) form gives value-returning if/elseif chains. For security-sensitive dispatch (roles, statuses, input from $_GET/$_POST), prefer match and always provide a fail-safe default.
- Control-Structures — match is a control-flow construct
- Switch-Statements — the loose-comparison predecessor match replaces
- Logical-Expressions —
match (true)evaluates boolean arms - PHP-Ternary-Operator — another value-returning conditional form
- Secure-PHP-Development — strict comparison avoids type-juggling bugs