match Expressions

PHP · Lesson 11 of 37 7 min read Updated

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
<?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";
Output
Your order is on the way.

Some details to notice:

  • match returns 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.
  • default catches 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
<?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";
Output
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
<?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";
Output
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:

Text
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
<?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";
Output
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
<?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";
}
Output
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:

Featurematchswitch
ComparisonStrict (===)Loose (==)
Returns a valueYesNo, it is a statement
Needs breakNoYes, or it falls through
No case matchesThrows UnhandledMatchErrorSilently 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 match 2. Convert or validate first.
  • Writing several statements in one arm. An arm holds one expression. Call a function if you need more.
  • Adding default everywhere. Leave it out when every value should be handled, so unexpected values cause a clear error.
  • Forgetting break in a switch. The next case runs too.

Key points

  • match compares 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 an UnhandledMatchError.
  • match (true) with conditions in the arms handles ranges cleanly.
  • Prefer match over switch, which compares loosely and falls through without break.

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
<?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";
Output
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?

2. What happens when no arm matches and there is no default?

3. How do you make one arm handle the values 'Kerala' and 'Goa'?

4. In a switch, what happens when a case has no break?

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