PHP8.0带来的match表达式让多分支赋值场景有了更紧凑的写法。它本质上是一个表达式,执行后会得到一个值,可以直接赋给变量或作为函数返回值,而不像switch那样只是一个语句块。理解match的核心在于严格比较与全面返回值,这决定了它和switch在语义与安全性上的根本分野。

match表达式的基础语法
match的基本结构由待匹配的主体表达式和若干分支组成,每个分支用箭头指向返回值。与switch不同,match不需要break来阻止穿透,且所有分支都必须覆盖可能的返回值,否则未命中时会抛出UnhandledMatchError。这种强制完整性让调用方不必额外判断默认值。
下面是一段最简单的用法,根据数字返回对应中文星期。注意这里用的是严格等于(===),也就是类型也必须一致,字符串"1"和数字1不会被认作相同。
<?php
$day = 3;
$name = match ($day) {
1 => '星期一',
2 => '星期二',
3 => '星期三',
4 => '星期四',
5 => '星期五',
6, 7 => '周末',
default => '未知',
};
echo $name; // 输出:星期三
?>
可以看到多个条件可以写在同一行用逗号分隔,这比switch里重复case要简洁。default分支在match里不是必须的,但如果没有default且主体值未被任何分支命中,就会报错,因此处理外部输入时建议保留default。
match与switch的核心差异
很多从旧版本迁移的开发者容易把match当成switch的语法糖,其实二者在比较方式和执行模型上差别明显。switch使用松散比较(==),会进行类型转换,例如0和"abc"在switch里可能意外命中某个case;match始终使用严格比较,彻底杜绝这类隐式转换带来的bug。
另一个关键区别是返回值。switch是一个语句,要在每个case里手动赋值并使用break;match是表达式,直接产出值。下面的表格列出了主要不同点,方便在代码评审时快速对照。
| 对比维度 | switch | match |
|---|---|---|
| 比较方式 | 松散比较(==) | 严格比较(===) |
| 是否返回值 | 否,仅为语句 | 是,表达式返回值 |
| 穿透行为 | 默认穿透,需break | 无穿透,自动阻断 |
| 未命中处理 | 静默跳过 | 抛UnhandledMatchError |
| 多条件写法 | 重复case | 逗号分隔 |
从表中可以看出,match在安全性上更强,但代价是必须确保所有路径都有结果。如果原有switch依赖宽松比较处理字符串数字混合的场景,直接换成match会导致逻辑改变,需要提前做类型归一化。
实战示例:状态映射与路由分发
在业务代码中,match非常适合做枚举式状态转义。比如订单状态存储为整型,前端需要展示文案与操作权限,用match可以一行搞定映射并且保证未定义状态被捕获。
<?php
function getOrderInfo(int $status): array {
return match ($status) {
0 => ['text' => '待支付', 'canCancel' => true],
1 => ['text' => '已支付', 'canCancel' => false],
2 => ['text' => '已发货', 'canCancel' => false],
3 => ['text' => '已完成', 'canCancel' => false],
default => throw new InvalidArgumentException('非法状态'),
};
}
?>
第二个例子是简易路由。根据请求路径返回不同处理函数名,由于match返回字符串,可直接用于后续调用。这里展示了多值合并分支的便利:多个路径共用同一处理逻辑时无需复制代码。
<?php
$path = $_SERVER['PATH_INFO'] ?? '/';
$handler = match ($path) {
'/', '/index' => 'showIndex',
'/list', '/items' => 'showList',
'/detail' => 'showDetail',
default => 'show404',
};
// 假设已定义对应函数
$handler();
?>
使用match时应当避免在其中写过多副作用代码,保持分支纯净只做返回,能让单元测试更容易覆盖。如果某个分支逻辑复杂,建议抽成独立函数再在箭头后调用。
常见误区与迁移建议
一个典型误区是认为match的default等同于switch里最后一个case,实际上default在match中也是严格比较失败后的兜底,并且如果主体值为null,而你没有写null分支也没有default,同样会抛出异常。迁移旧switch时,先列出所有case的值类型,统一类型后再替换。
另外,match不能替代需要贯穿执行的复杂流程控制。如果你的switch故意利用穿透来执行多段共享代码,那么match并不合适,应改用ifelse或重构为函数组合。总体而言,新项目优先使用match处理值映射,老项目在做类型安全改造时再逐步引入,能显著降低出错概率。