match Expression (PHP 8)
PHP 8 introduced match, a compact alternative to switch for picking one value out of several possibilities. At first glance it looks like a tidier switch, but it is built on different rules underneath: match is an expression that produces a value, every comparison is strict (===), and there is no accidental fall-through between arms. Those three differences make match the better default choice for most "pick a value based on another value" situations you would previously have reached for switch to solve.
Basic syntax
A match expression takes a subject value, compares it against a
list of arms, and evaluates to whichever arm's result matches. Each
arm is a comma-separated list of conditions, an arrow =>, and the
result for that arm.
A simple match expression
<?php
$statusCode = 404;
$message = match ($statusCode) {
200 => 'OK',
301 => 'Moved Permanently',
404 => 'Not Found',
500 => 'Internal Server Error',
};
echo $message;Not Found
match is an expression, switch is a statement
The biggest practical difference from switch is that match directly produces a value you can assign to a variable, return from a function, or pass as an argument. With switch, you have to declare a variable first and assign to it inside every case, or use return inside each branch. match removes that boilerplate entirely.
Returning directly from match
<?php
function describeHttpRange(int $code): string
{
return match (true) {
$code >= 200 && $code < 300 => 'Success',
$code >= 300 && $code < 400 => 'Redirection',
$code >= 400 && $code < 500 => 'Client Error',
$code >= 500 => 'Server Error',
default => 'Unknown',
};
}
echo describeHttpRange(201);
echo "\n";
echo describeHttpRange(403);Success Client Error
Notice the match (true) pattern above: because match arms can be any expression, not just literal values, using true as the subject lets each arm act as its own boolean condition. This is a common idiom for range checks and other logic that does not map cleanly onto a single fixed value.
Strict comparison: match uses ===, switch uses ==
This is the difference that catches the most people off guard. switch compares each case using loose equality (==), which triggers PHP's type juggling rules. match always compares using strict equality (===), so both the value and the type must agree. The classic example is comparing the string "0" against the integer 0.
switch (loose ==) vs match (strict ===)
<?php
$input = '0';
switch ($input) {
case 0:
echo "switch: matched 0 (loose ==)\n";
break;
default:
echo "switch: no match\n";
}
echo match ($input) {
0 => "match: matched 0 (strict ===)\n",
default => "match: no match, '0' is a string, not an int\n",
};switch: matched 0 (loose ==) match: no match, '0' is a string, not an int
In the switch block, PHP converts '0' for the comparison and treats it as equal to the integer 0, so the first case fires. In the match block, '0' === 0 is false because one operand is a string and the other is an integer, so control falls through to default. If your data comes from user input, query strings, or JSON decoding — all of which tend to produce strings — this difference can silently change which branch runs.
Multi-condition arms
Just like switch allows several case labels to share one block by stacking them without a break, match lets you list several conditions in a single arm separated by commas. The arm runs if the subject strictly equals any of the listed values.
Grouping several values into one arm
<?php
function dayType(string $day): string
{
return match ($day) {
'Mon', 'Tue', 'Wed', 'Thu', 'Fri' => 'Weekday',
'Sat', 'Sun' => 'Weekend',
default => 'Not a valid day',
};
}
echo dayType('Wed');
echo "\n";
echo dayType('Sun');Weekday Weekend
No fall-through, no break needed
switch executes every case after the first match until it hits a break, which is why forgetting a break is such a common source of bugs. match has no fall-through concept at all — exactly one arm's result is evaluated and returned, and execution never continues into the next arm. There is nothing equivalent to break to remember, and nothing equivalent to forget.
UnhandledMatchError when nothing matches
If none of the arms match and there is no default arm, match does not silently do nothing the way an unmatched switch would — it throws an UnhandledMatchError. This is a deliberate design choice: match is meant to represent an exhaustive decision, so an unexpected input becomes a loud, catchable error instead of a quiet no-op that might hide a bug.
An unmatched value throws UnhandledMatchError
<?php
function sizeLabel(string $size): string
{
return match ($size) {
'S' => 'Small',
'M' => 'Medium',
'L' => 'Large',
};
}
echo sizeLabel('XL');PHP Fatal error: Uncaught UnhandledMatchError: Unhandled match case 'XL'
You have two ways to deal with this: add a default arm to cover every other case, or catch UnhandledMatchError explicitly when you want to react to an invalid value rather than silently substitute one.
Option 1: add a default arm
<?php
function sizeLabel(string $size): string
{
return match ($size) {
'S' => 'Small',
'M' => 'Medium',
'L' => 'Large',
default => 'Unknown size',
};
}
echo sizeLabel('XL');Unknown size
Option 2: catch UnhandledMatchError explicitly
<?php
function sizeLabel(string $size): string
{
return match ($size) {
'S' => 'Small',
'M' => 'Medium',
'L' => 'Large',
};
}
try {
echo sizeLabel('XL');
} catch (\UnhandledMatchError $e) {
echo "Invalid size supplied: " . $e->getMessage();
}Invalid size supplied: Unhandled match case 'XL'
match with no subject value
Because match arms can hold arbitrary boolean expressions, you can write match (true) to replace a chain of if/elseif statements that each check unrelated conditions, similar to the HTTP range example earlier. This keeps the code declarative and guarantees only one branch runs, the same guarantee match gives you with simple value comparisons.
Replacing if/elseif with match (true)
<?php
$score = 82;
$grade = match (true) {
$score >= 90 => 'A',
$score >= 80 => 'B',
$score >= 70 => 'C',
$score >= 60 => 'D',
default => 'F',
};
echo "Grade: {$grade}";Grade: B
match vs switch vs if/elseif
switchis a statement: it does not produce a value, allows fall-through between cases, and compares with loose==.if/elseifis the most flexible option and the right tool when conditions are unrelated or too complex for eitherswitchormatch.matchis an expression: it always produces a value, never falls through, compares with strict===, and throwsUnhandledMatchErrorinstead of silently matching nothing.