PHP 8.1.6 Released!

match

(PHP 8)

match 表达式基于值的一致性进行分支计算。 match表达式和 switch 语句类似, 都有一个表达式主体,可以和多个可选项进行比较。 与 switch 不同点是,它会像三元表达式一样求值。 与 switch 另一个不同点,它的比较是严格比较( ===)而不是松散比较(==)。 Match 表达式从 PHP 8.0.0 起可用。

示例 #1 match 表达式结构

<?php
$return_value 
match (subject_expression) {
    
single_conditional_expression => return_expression,
    
conditional_expression1conditional_expression2 => return_expression,
};
?>

示例 #2 match 的基础用法

<?php
$food 
'cake';

$return_value match ($food) {
    
'apple' => 'This food is an apple',
    
'bar' => 'This food is a bar',
    
'cake' => 'This food is a cake',
};

var_dump($return_value);
?>

以上例程会输出:

string(19) "This food is a cake"

注意: 不一定要使用 match 表达式的结果。

注意: match 表达式必须使用分号 ; 结尾。

match 表达式跟 switch 语句相似,但是有以下关键区别:

  • match 比较分支值,使用了严格比较 (===), 而 switch 语句使用了松散比较。
  • match 表达式会返回一个值。
  • match 的分支不会像 switch 语句一样, 落空时执行下个 case。
  • match 表达式必须彻底列举所有情况。

match 表达式和 switch 语句类似, 逐个检测匹配分支。一开始不会执行代码。 只有在所有之前的条件不匹配主体表达式时,才会执行剩下的条件表达式。 只会执行返回的表达式所对应的匹配条件表达式。 举例:

<?php
$result 
match ($x) {
    
foo() => ...,
    
$this->bar() => ..., // 如果 foo() === $x,不会执行 $this->bar()
    
$this->baz => beep(), // 只有 $x === $this->baz 时才会执行 beep() 
    // 等等
};
?>

match 表达式分支可以通过逗号分隔,包含多个表达式。 这是一个逻辑 OR,当多个分支表达式右侧相同时,就可以用这种缩写。

<?php
$result 
match ($x) {
    
// 匹配分支:
    
$a$b$c => 5,
    
// 等同于以下三个分支:
    
$a => 5,
    
$b => 5,
    
$c => 5,
};
?>

default 模式是个特殊的条件。 当之前的条件都不匹配时,会匹配到该模式。 For example:

<?php
$expressionResult 
match ($condition) {
    
1=> foo(),
    
3=> bar(),
    default => 
baz(),
};
?>

注意: 多个 default 模式将会触发 E_FATAL_ERROR 错误。

match 表达式必须详尽列出所有情况。 如果主体表达式不能被任意分支条件处理, 会抛出 UnhandledMatchError

示例 #3 match 表达式存在未处理的示例

<?php
$condition 
5;

try {
    
match ($condition) {
        
1=> foo(),
        
3=> bar(),
    };
} catch (\
UnhandledMatchError $e) {
    
var_dump($e);
}
?>

以上例程会输出:

object(UnhandledMatchError)#1 (7) {
  ["message":protected]=>
  string(33) "Unhandled match value of type int"
  ["string":"Error":private]=>
  string(0) ""
  ["code":protected]=>
  int(0)
  ["file":protected]=>
  string(9) "/in/ICgGK"
  ["line":protected]=>
  int(6)
  ["trace":"Error":private]=>
  array(0) {
  }
  ["previous":"Error":private]=>
  NULL
}

使用 match 表达式处理非一致性检查

可以使用 match 表达式将 true 作为主项表达式来处理非一致性条件的情况。

示例 #4 针对整数范围,使用宽泛的表达式匹配分支

<?php

$age 
23;

$result match (true) {
    
$age >= 65 => 'senior',
    
$age >= 25 => 'adult',
    
$age >= 18 => 'young adult',
    default => 
'kid',
};

var_dump($result);
?>

以上例程会输出:

string(11) "young adult"

示例 #5 针对字符串内容,使用宽泛的表达式匹配分支

<?php

$text 
'Bienvenue chez nous';

$result match (true) {
    
str_contains($text'Welcome') || str_contains($text'Hello') => 'en',
    
str_contains($text'Bienvenue') || str_contains($text'Bonjour') => 'fr',
    
// ...
};

var_dump($result);
?>

以上例程会输出:

string(2) "fr"
add a note add a note

User Contributed Notes 7 notes

up
24
darius dot restivan at gmail dot com
1 year ago
This will allow for a nicer FizzBuzz solution:

<?php

function fizzbuzz($num) {
    print
match (0) {
       
$num % 15 => "FizzBuzz" . PHP_EOL,
       
$num % => "Fizz" . PHP_EOL,
       
$num % => "Buzz" . PHP_EOL,
        default   =>
$num . PHP_EOL,
    };
}

for (
$i = 0; $i <=100; $i++)
{
   
fizzbuzz($i);
}
up
39
Hayley Watson
1 year ago
As well as being similar to a switch, match expressions can be thought of as enhanced lookup tables — for when a simple array lookup isn't enough without extra handling of edge cases, but a full switch statement would be overweight.

For a familiar example, the following
<?php

function days_in_month(string $month): int
{
    static
$lookup = [
   
'jan' => 31,
   
'feb' => 0,
   
'mar' => 31,
   
'apr' => 30,
   
'may' => 31,
   
'jun' => 30,
   
'jul' => 31,
   
'aug' => 31,
   
'sep' => 30,
   
'oct' => 31,
   
'nov' => 30,
   
'dec' => 31
   
];

   
$name = strtolower(substr($name, 0, 3));

    if(isset(
$lookup[$name])) {
        if(
$name == 'feb') {
            return
is_leap($year) ? 29 : 28;
        } else {
            return
$lookup[$name];
        }
    }
    throw new
InvalidArgumentException("Bogus month");
}

?>

with the fiddly stuff at the end, can be replaced by

<?php
function days_in_month(string $month): int
{
    return
match(strtolower(substr($month, 0, 3))) {
       
'jan' => 31,
       
'feb' => is_leap($year) ? 29 : 28,
       
'mar' => 31,
       
'apr' => 30,
       
'may' => 31,
       
'jun' => 30,
       
'jul' => 31,
       
'aug' => 31,
       
'sep' => 30,
       
'oct' => 31,
       
'nov' => 30,
       
'dec' => 31,
        default => throw new
InvalidArgumentException("Bogus month"),
    };
}
?>

Which also takes advantage of "throw" being handled as of PHP 8.0 as an expression instead of a statement.
up
24
Anonymous
8 months ago
<?php
function days_in_month(string $month, $year): int
{
    return
match(strtolower(substr($month, 0, 3))) {
       
'jan' => 31,
       
'feb' => is_leap($year) ? 29 : 28,
       
'mar' => 31,
       
'apr' => 30,
       
'may' => 31,
       
'jun' => 30,
       
'jul' => 31,
       
'aug' => 31,
       
'sep' => 30,
       
'oct' => 31,
       
'nov' => 30,
       
'dec' => 31,
        default => throw new
InvalidArgumentException("Bogus month"),
    };
}
?>

can be more concisely written as

<?php
function days_in_month(string $month, $year): int
{
    return
match(strtolower(substr($month, 0, 3))) {
       
'apr', 'jun', 'sep''nov'  => 30,       
       
'jan', 'mar', 'may', 'jul', 'aug''oct', 'dec'  => 31,
       
'feb' => is_leap($year) ? 29 : 28,
        default => throw new
InvalidArgumentException("Bogus month"),
    };
}
?>
up
3
php at joren dot dev
2 months ago
If you want to execute multiple return expressions when matching a conditional expression, you can do so by stating all return expressions inside an array.

<?php
    $countries
= ['Belgium', 'Netherlands'];
   
$spoken_languages = [
       
'Dutch' => false,
       
'French' => false,
       
'German' => false,
       
'English' => false,
    ];

    foreach (
$countries as $country) {
       
match($country) {
           
'Belgium' => [
               
$spoken_languages['Dutch'] = true,
               
$spoken_languages['French'] = true,
               
$spoken_languages['German'] = true,
            ],
           
'Netherlands' => $spoken_languages['Dutch'] = true,
           
'Germany' => $spoken_languages['German'] = true,
           
'United Kingdom' => $spoken_languages['English'] = true,
        };
    }

   
var_export($spoken_languages);
   
// array ( 'Dutch' => true, 'French' => true, 'German' => true, 'English' => false, )

?>
up
2
tm
6 months ago
If you are using a match expression for non-identity checks as described above make sure whatever you are using is actually returning `true` on success.

Quite often you rely on truthy vs. falsy when using if conditions and that will not work for match (for example `preg_match`). Casting to bool will solve this issue.
up
1
mark at manngo dot net
3 months ago
While you can’t polyfill a language construct, you can mimic the basic behaviour with a simple array.

Using example 2 above:

<?php
    $food
= 'apple';
   
$return_value = match ($food) {
       
'apple' => 'This food is an apple',
       
'bar' => 'This food is a bar',
       
'cake' => 'This food is a cake',
    };   
    print
$return_value;
?>

… you can get something similar with:

<?php
    $food
= 'apple';   
   
$return_value = [
       
'apple' => 'This food is an apple',
       
'bar' => 'This food is a bar',
       
'cake' => 'This food is a cake',
    ][
$food];
    print
$return_value;
?>
up
-6
manoj904378 at gmail dot com
4 months ago
When & Where should you use?  that's most important part of match

<?php
    $Error_Code
=(int) 400;
   
$message=(string) match($Error_Code){
       
200=>"OK",
       
400=>"Bad Request",
       
401=>"Unauthorized",
       
403=>"Forbidden",
       
404=>"Not Found",
        default=>
"Unknown Error"
   
};
   
var_dump($message); // string(11) "Bad Request"
?>
To Top