The PHP match expression (added in PHP 8.0) compares one value against a list of options with strict === and returns the result of the first option that matches. Think of it as a shorter, stricter switch that gives you a value back:
<?php
$status = 404;
$message = match ($status) {
200 => 'OK',
404 => 'Not Found',
500 => 'Server Error',
default => 'Unknown status',
};
echo $message; // Not Found
Three things to know before anything else:
-
matchis an expression, not a function and not a statement. It produces a value, so you assign it,echoit orreturnit. - It compares with
===, so the string'404'does not match the integer404. - If nothing matches and there is no
default, PHP throws anUnhandledMatchError.
Not to be confused with preg_match() - that is a function for regular expressions and has nothing to do with the match expression.
PHP match syntax
$result = match (subject) {
value1 => result1,
value2, value3 => result2,
default => result3,
};
The rules, all in one place:
- The subject in the parentheses is evaluated once.
- Each arm has the form
condition => result. Arms are separated by commas; a comma after the last arm is allowed. - One arm can list several values separated by commas (they work like "or").
- Arms are checked top to bottom and the first match wins. Nothing falls through, so there is no
break. - The right side of
=>must be a single expression, not a block of statements. -
defaultis optional, but a match can have only onedefaultarm. - The whole
matchends with a semicolon after the closing}.
PHP match examples
Basic example: map a value to text
<?php
$day = 3;
$dayName = match ($day) {
1 => 'Monday',
2 => 'Tuesday',
3 => 'Wednesday',
4 => 'Thursday',
5 => 'Friday',
6 => 'Saturday',
7 => 'Sunday',
default => 'Invalid day number',
};
echo $dayName; // Wednesday
This is the job match does best: turn one value into another. The same thing with if/elseif would take seven conditions and seven assignments.
Match multiple values in one arm
Separate the values with commas. Any of them triggers the arm:
<?php
$day = 'sat';
$type = match ($day) {
'mon', 'tue', 'wed', 'thu', 'fri' => 'weekday',
'sat', 'sun' => 'weekend',
default => 'unknown',
};
echo $type; // weekend
The default arm
default catches every value that no other arm matched. Two limits that people run into:
- You can't write two
defaultarms. PHP stops withFatal error: Match expressions may only contain one default arm. - You can't mix
defaultwith other values in one arm ('x', default => ...is a syntax error). Put it on its own line - usually the last one.
What happens when nothing matches: UnhandledMatchError
switch without a matching case quietly does nothing. match refuses to guess:
<?php
$day = 8;
$type = match ($day) {
1, 2, 3, 4, 5 => 'weekday',
6, 7 => 'weekend',
};
Output:
Fatal error: Uncaught UnhandledMatchError: Unhandled match case 8
The message includes the value that slipped through (strings are shown in quotes, e.g. Unhandled match case 'abc'), which makes the bug easy to find. The fix is either to add the missing value or to add a default arm. You can also catch this error like any other exception - you'll meet try/catch later in the course.
Strict comparison: '2' vs 2
match uses strict comparison with ===, so the type has to match as well as the value:
<?php
$input = '2'; // a string, not an integer
$result = match ($input) {
2 => 'number two', // skipped: int vs string
'2' => 'string two', // this one matches
default => 'other',
};
echo $result; // string two
The gotcha in real code: everything that comes from a form, a URL or a file arrives as a string. If your arms are integers (1 =>, 2 =>), nothing matches and you land in default or get an UnhandledMatchError. Convert the value first, for example match ((int) $page) { ... }.
match(true): ranges and other conditions
match itself only checks ===. To test conditions like "greater than", pass true as the subject - the first arm whose condition equals true wins:
<?php
$score = 87;
$grade = match (true) {
$score >= 90 => 'A',
$score >= 80 => 'B',
$score >= 70 => 'C',
$score >= 60 => 'D',
default => 'F',
};
echo $grade; // B
Two things to watch here:
-
Order matters. If
$score >= 60were the first arm, a score of 87 would get aD, because the first true condition wins. -
The condition must be exactly
true, not just "truthy". A comparison like$score >= 90orstr_contains($text, 'PHP')returns a realtrue/false, so it works. A value like1or a non-empty string does not -1 === trueisfalse. That is howpreg_match()catches people: it returns1, nottrue, so the arm never matches. Cast it with(bool) preg_match(...).
<?php
$text = 'I am learning PHP';
$topic = match (true) {
str_contains($text, 'PHP') => 'PHP',
str_contains($text, 'JavaScript') => 'JavaScript',
default => 'something else',
};
echo $topic; // PHP
return match from a function
Because match is an expression, you can return it directly. You'll write your own functions in the functions chapter; for now it's enough to see the shape:
<?php
function shippingCost(string $country): int
{
return match ($country) {
'PL' => 10,
'DE', 'FR' => 25,
default => 50,
};
}
echo shippingCost('DE'); // 25
Only the matching arm runs
PHP checks the arms one by one and stops at the first match. The results of the other arms are never evaluated, so a function call on the right side of a non-matching arm doesn't run at all. That's why an arm can safely throw an error, e.g. default => throw new InvalidArgumentException('Unknown role') - it only fires when that arm is chosen.
When not to use match
-
You need several statements per branch. The right side of
=>is one expression. If a branch has to set three variables and print something, useif/elseiforswitch. -
You rely on loose comparison on purpose. Then
switch(==) is the honest choice - but usually it's better to convert the type and keepmatch. -
Your server runs PHP 7.
matchdoesn't exist there and the file fails with a parse error such assyntax error, unexpected '=>'. Also, since PHP 8.0matchis a reserved word, so old code with a function namedmatch()breaks on upgrade.
match vs switch in one table
match |
switch |
|
|---|---|---|
| Comparison | strict === |
loose == |
| Returns a value | yes | no |
Needs break |
no | yes, or it falls through |
| No matching case | UnhandledMatchError |
nothing happens |
| Multiple statements per branch | no | yes |
match also works well with enums (PHP 8.1+). Enums are beyond this course, but there is a complete guide to PHP enums on the blog.
Common mistakes with PHP match
-
Forgetting the semicolon after the closing
}- it's an expression, so it ends like one. - Comparing a string input with integer arms - convert the type first.
-
Putting a broad condition first in
match(true)- later arms become unreachable. -
Using a truthy value in
match(true)- cast tobool. -
Leaving out
defaultfor values you don't control - user input will eventually hit a value you didn't list.
Summary
-
match(PHP 8.0+) compares a value with===and returns the result of the first matching arm. - Several values can share one arm:
'sat', 'sun' => 'weekend'. -
defaulthandles everything else; without it an unmatched value throwsUnhandledMatchError. -
match (true)turns it into a compact chain of conditions - order the arms from most to least specific. - Each arm is a single expression; for multi-step branches use
iforswitch.
In the next lesson you'll see how exit and die stop a script completely.
FAQ
Is match a function or a statement in PHP?
Neither - match is an expression. It evaluates to a value, which is why you can write $x = match (...) { ... }; or return match (...) { ... };. It is also unrelated to preg_match(), which is a regular expression function.
How do I match multiple values in a PHP match expression?
List them in one arm separated by commas: 'sat', 'sun' => 'weekend',. The arm matches if the subject is identical (===) to any of the listed values.
What happens if no arm matches in a PHP match?
If no arm matches and there is no default, PHP throws an UnhandledMatchError with a message like Unhandled match case 8. Add a default arm to handle every other value.
Can a PHP match arm run multiple lines of code?
No. The right side of => must be a single expression. For branches that need several statements, use if/elseif or switch, or put the logic in a function and call it from the arm.