PHP 8.0 feature focus: match() expressions

Larry Garfield
Larry Garfield
Director of Developer Experience
24 Sep 2020

In our last episode, we discussed coming improvements to PHP’s type system. Today we look at a new construct that makes branching logic more powerful.

PHP has had a switch statement since the dawn of time, modeled on the same construct in C. You’ve surely seen it before:

<?php
switch ($var) {
    case 'a':
        $message = "The variable was a.";
        break;
    case 'b':
        $message = "The variable was c.";
        break;
    case 'c':
        $message = "The variable was c.";
        break;
    default:
        $message = "The variable was something else.";
        break;
}

print $message;

While it gets the job done, the classic switch has a number of limitations. Most newer languages like Go or Rust have opted for more robust, less error-prone structures that go by a variety of names. And now, PHP has one, too.

Enter match. Unlike switch, match is an expression. That means it evaluates to a value, like so:

<?php
$message = match($var) {
    'a' => 'The variable was a',
    'b' => 'The variable was b',
    'c' => 'The variable was c',
    default => 'The variable was something else',
};

print $message;

There are several things to point out here:

  1. With switch, each case is compared with loose equality ==. With match, each branch is compared with strict equality, ===. That means the types must match, too.
  2. Each branch of the match is a single expression only. No multi-line statements, just a single expression that gets evaluated. If you need complex logic, make it a function or method that you call.
  3. There is no fall through from one branch to the next, so there’s no need for a break.
  4. match returns a value. That’s its whole reason to exist.
  5. Because it’s an expression, a closing ; is needed at the very end, just like with closure definitions.
  6. match is exhaustive. If $var is not === any of the provided values and there is no default, an error will be thrown.

match branches can also be compound and comma-delimited for “OR” like behavior, like so:

<?php
echo match($operator) {
    '+', '-', '*', '/' => 'Basic arithmetic',
    '%' => 'Modulus',
    '!' => 'Negation',
};

match was pitched as a modernized, more useful switch, but I am not sure that’s accurate. Rather, I think of match as a more powerful ternary.

In the past, I’ve often found myself in binary cases where a variable can be assigned to one of two values depending on some condition. The easiest way to write that, I’ve found, is like this:

<?php
$display = $user->isAdmin()
    ? $user->name() . ' (admin)'
    : $user->name() . ' (muggle)'
;

That’s completely valid code and quite useful. If the logic in the condition or in either of the branches is too complex to be easily readable, that’s a hint the logic should be refactored out to its own function or method. I’ve found that to be a really good heuristic for situations like this, as well as leading to very readable, compact, and testable code.

That only works for true/false, however. match, in contrast, works for any number of options but has the same incentives to nudge you toward well-factored, clean code.

If you need more complex conditions than simple identity, you can use match on true:

<?php
$count = get_count();
$size = match(true) {
    $count > 0 && $count <=10 => 'small',
    $count <=50 => 'medium',
    $count >50 => 'huge',
};

Note that since there is no default, a non-match (in this case if $count is 0 or negative) will result in a thrown error rather than silently assigning to null and moving on. That’s good, because errors can’t propagate to pollute later code.

The match expression RFC comes to us courtesy of Ilija Tovilo.

Tune in next week as we look at another new feature in PHP 8.0 that finally completes work that began in 7.4: Weak Maps.

You can try out pre-release copies of PHP 8.0 today on Platform.sh, with just a one-line change. Give it a whirl, and let us know what your favorite features are.