2024 年 PHP 日本大会

match

(PHP 8)

match 表达式根据值的恒等检查来分支评估。类似于 switch 语句,match 表达式有一个主题表达式,它与多个备选方案进行比较。与 switch 不同的是,它的返回值类似于三元表达式。与 switch 不同,比较是恒等检查(===),而不是弱相等检查(==)。从 PHP 8.0.0 开始提供匹配表达式。

示例 #1 match 表达式的结构

<?php
$return_value
= match (subject_expression) {
single_conditional_expression => return_expression,
conditional_expression1, conditional_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"

示例 #3 使用 match 和比较运算符的示例

<?php
$age
= 18;

$output = match (true) {
$age < 2 => "Baby",
$age < 13 => "Child",
$age <= 19 => "Teenager",
$age >= 40 => "Old adult",
$age > 19 => "Young adult",
};

var_dump($output);
?>

以上示例将输出

string(8) "Teenager"

注意 match 表达式的结果不需要使用。

注意 match 表达式必须以分号 ; 结尾。

match 表达式类似于 switch 语句,但有一些关键区别

  • match 分支严格地(===)比较值,而不是像 switch 语句那样松散地比较。
  • match 表达式返回一个值。
  • match 分支不会像 switch 语句那样贯穿到后面的情况。
  • match 表达式必须是穷举的。

switch 语句一样,match 表达式逐个分支执行。一开始,不执行任何代码。只有当所有之前的条件表达式都未能与主题表达式匹配时,才会评估条件表达式。只有与匹配的条件表达式对应的返回表达式才会被评估。例如

<?php
$result
= match ($x) {
foo() => ...,
$this->bar() => ..., // 如果 foo() === $x,则不会调用 $this->bar()
$this->baz => beep(), // 除非 $x === $this->baz,否则不会调用 beep()
// 等等
};
?>

match 表达式分支可以包含多个用逗号分隔的表达式。这是一个逻辑或,它是具有相同右侧的多个匹配分支的简写。

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

一个特例是 default 模式。此模式匹配之前未匹配的任何内容。例如

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

注意 多个 default 模式将引发 E_FATAL_ERROR 错误。

match 表达式必须是穷举的。如果主题表达式未被任何匹配分支处理,则会抛出 UnhandledMatchError

示例 #4 未处理的匹配表达式的示例

<?php
$condition
= 5;

try {
match (
$condition) {
1, 2 => foo(),
3, 4 => 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 作为主题表达式。

示例 #5 使用泛化 match 表达式根据整数范围分支

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

示例 #6 使用泛化匹配表达式根据字符串内容分支

<?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"
添加注释

用户贡献的注释 9 条注释

darius dot restivan at gmail dot com
3年前
这将允许一个更好的 FizzBuzz 解法

<?php

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

for (
$i = 0; $i <=100; $i++)
{
fizzbuzz($i);
}
匿名用户
3年前
<?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("无效月份"),
};
}
?>

可以更简洁地写成

<?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("无效月份"),
};
}
?>
Hayley Watson
3年前
除了类似于 switch 之外,匹配表达式还可以被认为是增强的查找表——当简单的数组查找不足以处理额外的边缘情况时,而完整的 switch 语句又显得过于冗余。

举个熟悉的例子,下面的
<?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("无效月份");
}

?>

以及结尾处的繁琐部分,可以用以下代码替换

<?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("无效月份"),
};
}
?>

这也利用了PHP 8.0中将“throw”作为表达式而不是语句来处理的优势。
thomas at zuschneid dot de
1年前
虽然match允许使用“,”连接多个条件,例如
<?php
$result
= match ($source) {
cond1, cond2 => val1,
default =>
val2
};
?>
但似乎不能用default连接条件,例如
<?php
$result
= match ($source) {
cond1 => val1,
cond2, default => val2
};
?>
Sbastien
1年前
我使用match代替存储PDOStatement::rowCount()的结果和if/elseif条件链,或者使用难看的switch/break。

<?php

$sql
= <<<SQL
INSERT INTO ...
ON DUPLICATE KEY UPDATE ...
SQL;

$upkeep = $pdo->prepare($sql);

$count_untouched = 0;
$count_inserted = 0;
$count_updated = 0;

foreach (
$data as $record) {
$upkeep->execute($record);
match (
$upkeep->rowCount()) {
0 => $count_untouched++,
1 => $count_inserted++,
2 => $count_updated++,
};
}

echo
"未修改的行数: "{$count_untouched}\r\n";
echo
"插入的行数: "{$count_inserted}\r\n";
echo
"更新的行数: "{$count_updated}\r\n";
tolga dot ulas at tolgaulas dot com
8个月前
是的,它目前不支持代码块,但是这个技巧有效

match ($foo){
'bar'=>(function(){
echo "bar";
})(),
default => (function(){
echo "baz";
})()
};
php at joren dot dev
2年前
如果要在匹配条件表达式时执行多个返回值表达式,可以通过在数组中声明所有返回值表达式来实现。

<?php
$countries
= ['比利时', '荷兰'];
$spoken_languages = [
'荷兰语' => false,
'法语' => false,
'德语' => false,
'英语' => false,
];

foreach (
$countries as $country) {
match(
$country) {
'比利时' => [
$spoken_languages['荷兰语'] = true,
$spoken_languages['法语'] = true,
$spoken_languages['德语'] = true,
],
'荷兰' => $spoken_languages['荷兰语'] = true,
'德国' => $spoken_languages['德语'] = true,
'英国' => $spoken_languages['英语'] = true,
};
}

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

?>
mark at manngo dot net
2年前
虽然不能为语言结构编写polyfill,但可以使用简单的数组来模拟基本行为。

使用上面的示例2

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

…可以使用类似的方法

<?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;
?>
tm
3年前
如果如上所述,将match表达式用于非同一性检查,请确保所使用的任何内容在成功时实际返回`true`。

使用if条件时,通常依赖于真值和假值,而这对于match则无效(例如`preg_match`)。转换为bool可以解决此问题。
To Top