PHP 8 match:严格比较、不贯穿,以及漏掉分支时的 UnhandledMatchError
match 表达式基于恒等检查(identity check)对分支求值。它和 switch 一样有 subject 表达式去跟多个候选项比较,但和 switch 不同:它像三目表达式一样求出一个值,并且比较用的是恒等检查 === 而不是弱相等 ==。match 自 PHP 8.0.0 起可用。
$return_value = match (subject_expression) {
single_conditional_expression => return_expression,
conditional_expression1, conditional_expression2 => return_expression,
};
与 switch 的关键区别
- match 分支用严格比较
===,不是弱相等。 - match 是表达式,会返回值。
- match 分支不会像 switch 那样 fall through(贯穿到后面的 case)。
- match 表达式必须穷尽(exhaustive)。
执行模型
逐分支执行,开始时什么都不执行。只有当之前所有条件表达式都没匹配 subject 时,才去求值当前条件表达式;只有匹配分支的 return 表达式会被求值。也就是说,写在前面的分支一旦命中,后面的条件表达式和返回值都不会被碰:
$result = match ($x) {
foo() => 'value',
$this->bar() => 'value', // 若 foo() === $x,则 $this->bar() 不会被调用
$this->baz => beep(), // 除非 $x === $this->baz,否则 beep() 不会被调用
};
一个分支可以含多个以逗号分隔的表达式,表示逻辑 OR:
match ($x) { $a, $b, $c => 5, }
这等价于三个分支 $a => 5、$b => 5、$c => 5。
default 匹配前面所有没匹配到的值。写多个 default 会触发 E_FATAL_ERROR。
必须穷尽
如果 subject 没有被任何分支处理,抛出 UnhandledMatchError:
$condition = 5;
try {
match ($condition) {
1, 2 => foo(),
3, 4 => bar(),
};
} catch (\UnhandledMatchError $e) {
var_dump($e); // message: "Unhandled match case 5"
}
PHPStan 提供 match.unhandled 错误标识来静态发现这种未覆盖,比如枚举漏了一个 case。
用 true 作 subject 做非恒等判断
想写条件判断而不是值匹配,就把 true 当 subject:
$result = match (true) {
$age >= 65 => 'senior',
$age >= 25 => 'adult',
$age >= 18 => 'young adult',
default => 'kid',
};
注意:这种方式下,条件表达式成功时必须真的返回 true。很多人习惯依赖真值/假值判断,例如 preg_match 返回 int 0/1,这在 match 里行不通——0 和 1 都不等于 true:
$s = 'abc123';
// 不会命中第一支:preg_match 返回 int(1),1 !== true
$r = match (true) {
preg_match('/\d/', $s) => 'has digit',
default => 'no digit',
};
// 需要显式比较或强转
$r = match (true) {
preg_match('/\d/', $s) === 1 => 'has digit',
(bool) preg_match('/\d/', $s) => 'has digit',
default => 'no digit',
};
其他细节
match 表达式作为独立语句时必须用分号结尾。
目前不支持代码块。需要多语句时,用数组返回或者 IIFE 包裹:
$r = match ($x) {
1 => (function () { /* ... */ })(),
default => 'other',
};
返回值类型不会被 match 强制。可以用一层闭包包起来强制返回类型,返回不符会抛 TypeError:
$r = (fn (): string => match ($x) {
1 => 100,
default => 'x',
})(); // TypeError
用 continue/goto 跳转 match 相关位置有编译期限制:continue 指向 match 会报 Fatal error;不允许 goto 进入 match。
设计动机
RFC 归纳了 switch 的四个长期问题:类型强制转换、无返回值、fallthrough、不穷尽(inexhaustiveness)。match 正是为解决这四点而设计的。