match Expressions
Many decisions follow the same pattern: look at one value and pick a result for it. An order status becomes a message, a state becomes a sales zone, marks become a grade. You could write a long elseif chain, but PHP has a cleaner tool for this job: the match expression, added in PHP 8.0.
In this lesson you will learn how match works, why its strict comparison protects you, how to handle ranges with match (true), and how match compares with the older switch statement.
Your first match
A match expression compares one value against a list of arms. Each arm has a condition on the left of => and a result on the right. The result of the first matching arm becomes the value of the whole expression:
<?php
declare(strict_types=1);
$status = 'shipped';
$message = match ($status) {
'pending' => 'We have received your order.',
'shipped' => 'Your order is on the way.',
'delivered' => 'Your order has been delivered.',
default => 'Please contact support.',
};
echo $message, "\n";Your order is on the way.
Some details to notice:
matchreturns a value, so you can store it in a variable, echo it or return it from a function.- Arms are separated by commas. A comma after the last arm is allowed and makes adding arms easier.
defaultcatches every value that no other arm matched.- The whole expression ends with a semicolon after the closing brace, because it is part of the assignment statement.
Several values in one arm
When several values should give the same result, list them in one arm, separated by commas:
<?php
declare(strict_types=1);
$state = 'Punjab';
$zone = match ($state) {
'Haryana', 'Punjab', 'Delhi' => 'North',
'Kerala', 'Tamil Nadu', 'Karnataka' => 'South',
'Maharashtra', 'Gujarat' => 'West',
default => 'Other',
};
echo "$state is in the $zone zone\n";Punjab is in the North zone
match uses strict comparison
match compares with ===, so the type must match as well as the value. This is safer than loose comparison, but it catches out beginners when the value comes from a form, because form values are always strings:
<?php
declare(strict_types=1);
$fromForm = '2';
echo match ($fromForm) {
1 => 'One ticket',
2 => 'Two tickets',
default => 'No match',
}, "\n";
echo match ((int) $fromForm) {
1 => 'One ticket',
2 => 'Two tickets',
default => 'No match',
}, "\n";No match Two tickets
The string '2' is not identical to the int 2, so the first match falls through to default. Converting the value with (int) first fixes it. Better still, validate the input properly, as you will learn in Form Validation & Escaping.
When nothing matches
If no arm matches and there is no default, PHP throws an UnhandledMatchError and stops:
Fatal error: Uncaught UnhandledMatchError: Unhandled match case 'Bihar'This is a feature, not a nuisance. Sometimes you expect every possible value to be handled, for example the fixed list of payment methods your shop accepts. Leaving out default means a new, unexpected value causes a loud error during testing, instead of a silent wrong answer on the live site.
An arm can also throw an exception itself, with a clearer message: default => throw new InvalidArgumentException("Unknown plan: $plan"),. You will learn about exceptions in Errors & Exceptions.
match (true) for ranges
The arms of match can be any expression, not only fixed values. A useful trick is match (true): PHP returns the first arm whose condition is true. This handles ranges neatly:
<?php
declare(strict_types=1);
$marks = 78;
$grade = match (true) {
$marks >= 90 => 'A+',
$marks >= 75 => 'A',
$marks >= 60 => 'B',
$marks >= 33 => 'C',
default => 'Fail',
};
echo "Marks: $marks, Grade: $grade\n";Marks: 78, Grade: A
Compare this with the same grading code in if, else & elseif. The match version is shorter and makes it clear that the only job of the code is to choose a value for $grade. As with elseif, order matters: put the highest range first.
Each arm holds a single expression. If an arm needs to do several things, move that work into a function and call it from the arm.
match versus switch
Older PHP code uses the switch statement for the same kind of choice. You still need to read it, so here is how it looks, including its most famous bug:
<?php
declare(strict_types=1);
$plan = 'gold';
switch ($plan) {
case 'silver':
echo "5 GB storage\n";
case 'gold':
echo "50 GB storage\n";
case 'platinum':
echo "500 GB storage\n";
break;
default:
echo "1 GB storage\n";
}50 GB storage 500 GB storage
The gold case has no break, so PHP falls through and also runs the platinum case. Every case in a switch needs a break unless you deliberately want fall-through. The differences:
| Feature | match | switch |
|---|---|---|
| Comparison | Strict (===) | Loose (==) |
| Returns a value | Yes | No, it is a statement |
Needs break | No | Yes, or it falls through |
| No case matches | Throws UnhandledMatchError | Silently does nothing |
| Several values per case | 'a', 'b' => | Stacked case lines |
Because switch compares loosely, switch ('0') can match case false:, which is rarely what you want. For new code that picks a value, use match. A switch is still acceptable when each case runs several statements and returns nothing, but a match that calls functions is usually clearer.
Common mistakes
- Forgetting the semicolon after the closing brace.
$x = match (...) { ... };is one statement and needs;at the end. - Comparing a form string with int arms.
'2'does not match2. Convert or validate first. - Writing several statements in one arm. An arm holds one expression. Call a function if you need more.
- Adding
defaulteverywhere. Leave it out when every value should be handled, so unexpected values cause a clear error. - Forgetting
breakin aswitch. The next case runs too.
Key points
matchcompares one value against arms and returns the result of the first match.- It uses strict
===comparison, so types must match. - One arm can list several values separated by commas.
- With no matching arm and no
default, PHP throws anUnhandledMatchError. match (true)with conditions in the arms handles ranges cleanly.- Prefer
matchoverswitch, which compares loosely and falls through withoutbreak.
Exercise
Write a script that turns a day number (1 to 7, where 1 is Monday) into the opening hours of a library in Jaipur. Monday to Friday: "9 am to 8 pm". Saturday: "10 am to 6 pm". Sunday: "Closed". Store the day in $day = 6. Use a single match with comma-separated values, and leave out default so that an invalid day number causes an error. Print the day number and the hours.
Show solution
<?php
declare(strict_types=1);
$day = 6;
$hours = match ($day) {
1, 2, 3, 4, 5 => '9 am to 8 pm',
6 => '10 am to 6 pm',
7 => 'Closed',
};
echo "Day $day: $hours\n";Day 6: 10 am to 6 pm
Without a default arm, a value such as 8 throws an UnhandledMatchError, so a bug elsewhere in the program cannot quietly show the wrong hours.
Quick Quiz
1. Which comparison does match use?
match uses ===, so '2' does not match 2.
2. What happens when no arm matches and there is no default?
A match without a matching arm throws an error instead of failing silently.
3. How do you make one arm handle the values 'Kerala' and 'Goa'?
Separate the values with commas on the left of =>.
4. In a switch, what happens when a case has no break?
Without break, switch continues into the following case, which is a common bug.
Learn PHP with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
