PHP 8.0 : Switch to Match expression
26, June 2020

Do you use switch() often? Does long list of switches make you worried that you would introduce some bug in it and wouldn't notice because how complex and unfriendly the syntax is? Are you bothered by it type coercion behavior?

Fear not. You've gotten our savior, match() expression.


What is match expression?

match() is an expression that allows you to compare a specific value against a series of values and then returns value based on which value matches. return value can be produced by executing function/method calls.

Example:

$roleType = 5;

$role = match ($roleType) {
    0 => 'Unauthorized',
    1 => 'User',
    5 => 'Admin',
    default => 'Unauthorized',
};

echo $role;
// Admin


Differences with switch()

  • switch() does not return any value. Normally we use below approach to produce a value or fetching value from other function:
switch($roleType){
    case 0:
        $role = 'Unauthorized';
        break;
    case 1:
        $role = 'User';
        break;
    case 5:
        $role = 'Admin';
        break;
}

echo $role;
// Admin

But, with match() its fairly easy to do the same (see first example). It also make writing bug harder and we will talk about that later on.


  • The match() expression uses strict comparison (===) to compare values. So while with switch, type juggling happens like this:
switch ('foo') {
    case 0:
      $result = 'Oh no!';
      break;
    case 'foo':
      $result = 'This is what I expected';
      break;
}
echo $result;
// Oh no!

here, switch() matches 'foo' with 0 because 'foo' == 0 is true.

But match() ensures strict comparison as it does not use any type coercion. No matter what you strict_types value is, match() is always strict.

echo match ('foo') {
    0 => 'Oh no!',
    'foo' => 'This is what I expected',
};
// This is what I expected


  • Better syntax to compare with multiple values.

With switch()

$role = 'User';

switch ($role) {
    case 'Unauthorized':
        // we are skipping break; here
        // so, switch() will match both 'Unauthorized' and 'User' for next case
    case 'User':
      $isAdmin = false;
      break;
    case 'Admin':
        $isAdmin = true;
        break;
}

var_dump($isAdmin);
// bool(false)

With match()

$role = 'User';

$isAdmin = match ($role) {
    'Unauthorized', 'User' => false, 
    // We have declared multiple values before '=>'
    'Admin' => true,
};
  • With match(), we do not need to use break; anymore. Why it is a good thing? Because, It is fairly easy to miss break; somewhere in all those switch cases and may run some code that was not intended.
$grade = 'F';

switch($grade){
    case 'F':
        $message = 'Better luck next time!';
        // we've missed break; here
    case 'B':
        $message = 'Good Boy';
        break;
    case 'A':
        $message = 'Brilliant!';
        break;
}

echo $message;
// Good Boy

All the Boys with F grade will now receive message "Good Boy" even if they failed just because we missed a break. If the cases are more than this, debugging will be harder.

match() will help us to avoid this type of bugs as it does not have such use of break.

  • With switch(), we frequently face another kind of bug. Switch never complain about non-matching cases. If a value could not be matched with any value, it just silently exits where we may not want that.

consider below situation:

$int1 = 10;
$int2 = 5;

$operator = 'DIVIDE';

switch($operator){
    case 'ADD':
        $result = $int1 + $int2;
        break;
    case 'SUBTRACT':
        $result = $int1 - $int2;
        break;
}

Here, we have forgotten to implement case for 'DIVIDE', but as switch() does not complain about it, this bug may not be discovered easily and may exists for long time and cause other bugs in our system.

match() reports match throws an UnhandledMatchError if the condition isn’t matched. So any unhandled case can be caught in development period.

  • A match condition can be any arbitrary expression. match() tries to compare values from top to bottom, so as soon as it finds a match, it will stop executing other expressions.
$result = match ($x) {
    foo() => bar(), // $x will be matched with return value of foo(), after matching it will return the return value for bar()$this->bar() => $this->something(), // $this->bar() isn't called if above foo() matched with $x$this->baz => $this->other, // We can even match with class properties, and return them too// etc.
};


Future Plan

In future, using blocks and arrow functions may be available to use in match(). Currently only expressions are allowed.

Pattern matching would be cool thing too. I hope we will get that in later versions.




RFC: https://wiki.php.net/rfc/match_expression_v2