
前言先纠正一个称呼match不是函数而是表达式expression。PHP 8.0 引入它时用的是一套新语法结构而不是新增函数所以它没有函数签名、不能当回调传、也不能被function_exists(match)检测到。日常叫match 函数无伤大雅但理解这一点很关键正因为它是表达式它才能直接出现在return、赋值、参数位置——而这恰恰是switch语句statement做不到的事。版号方面没有问题match确确实实是PHP 8.0引入的和命名参数、构造器属性提升同批进入。该选哪个取决于你在意的是哪件事。switch的坑集中在两处忘了break导致贯穿fallthrough以及松散比较带来的意外匹配。而match恰好把这两件事都改成了硬约束不贯穿、用严格比较、不写default且没匹配上就直接抛错。代价是它也更啰嗦——每个分支必须给出一个表达式不能写多条语句。本文用可运行的代码把两者的差异摊开给出选择清单并说明为什么哪个更快通常不是一个值得纠结的问题。示例需要 PHP 8.0 及以上涉及枚举enum的部分需要 PHP 8.1。一、语义差异一览维度switchmatch引入版本语言早期就有PHP 8.0语法类别语句statement表达式expression比较方式松散比较严格比较是否贯穿会除非写break不会天然隔离未匹配时静默什么都不做抛UnhandledMatchError未写default时分支体可写任意多条语句只能是单个表达式能否直接返回需在每个分支写return可整体作为返回值多值合并case 1: case 2:靠贯穿1, 2 ...显式列出类型转换会做类型戏法不做类型不符即不匹配这张表里最该盯住的是第四、五行用switch时忘了处理某种取值是一个静默 bug用match时它是一个立刻抛出的错误。在业务分支这种漏一个值就是线上事故的场景里这个差别比语法糖重要得多。二、match 的几种正确姿势match的每个分支arm由条件列表表达式构成多个条件用逗号分隔。下面这个例子把常见用法串在一起可以直接运行?php // match_basic.php —— 需要 PHP 8.0 及以上 declare(strict_types1); /** 常量分支命中即返回不需要 break */ function httpStatusText(int $code): string { return match ($code) { 200, 201, 204 成功, 301, 302 重定向, 400 请求有误, 401, 403 未授权, 404 资源不存在, 500, 502, 503 服务器错误, default 未知状态, }; } echo httpStatusText(404), PHP_EOL; // 资源不存在 echo httpStatusText(999), PHP_EOL; // 未知状态 /** 严格比较类型不符不会命中这就是 UnhandledMatchError 的来源 */ try { // 1 是字符串不会匹配到 int 1也没有 default于是抛错 $v match (1) { 1 int 1, 1 string 1, }; echo $v, PHP_EOL; } catch (UnhandledMatchError $e) { echo 未匹配: , $e-getMessage(), PHP_EOL; }match(true)是把match当更好的 if/elseif用的惯用法把条件表达式当作被比较的值让它与true做严格比较从而支持区间判断。?php // 需要 PHP 8.0 及以上 declare(strict_types1); function grade(int $score): string { // 注意条件按书写顺序求值第一个命中的分支胜出 return match (true) { $score 90 A, $score 80 B, $score 60 C, default D, }; } echo grade(95), grade(83), grade(60), grade(30), PHP_EOL; // ABCD // 在分支里直接抛异常比 if 链更紧凑 function requirePositive(int $n): int { return match (true) { $n 0 $n, default throw new InvalidArgumentException(必须是正数), }; } echo requirePositive(5), PHP_EOL;还有两个容易被忽略的好性质只求值命中的那个分支未命中的分支表达式不会被计算所以分支里可以放心写有副作用的调用以及被比较的表达式只求值一次不像if链里把同一个函数调用写好几遍。配合枚举enumPHP 8.1 引入时match尤其顺手因为枚举天然是穷尽的?php // 需要 PHP 8.1 及以上enum 是 PHP 8.1 的特性match 本身是 8.0 declare(strict_types1); enum OrderState: string { case Pending pending; case Paid paid; case Shipped shipped; case Cancelled cancelled; } function canCancel(OrderState $state): bool { // 枚举的 case 是单例 比较是可靠的 return match ($state) { OrderState::Pending, OrderState::Paid true, OrderState::Shipped, OrderState::Cancelled false, }; } echo canCancel(OrderState::Paid) ? 可取消\n : 不可取消\n;三、switch 仍然更好的场景不要因为match新就一律替换下面这几种情况switch依旧合适甚至只能用它第一分支体需要多条语句。match的分支只能是一个表达式想写三行赋值加一行日志就只能拼成函数或者用switch。当分支逻辑本身较长时switch读起来更直白。第二需要贯穿fallthrough做累积处理。有些人确实依赖贯穿来实现逐级叠加例如按权限级别逐步追加能力。这类写法在match里无法表达可以用数组代替但语义会变。第三需要用switch(true)之外的其他技巧或者配合continue 2跳出外层循环。match是表达式不存在break/continue的概念。注意一个小坑在switch里写continue的语义等同于breakPHP 7.3 起会产生警告提醒你这一点想跳出外层循环必须写continue 2。第四老版本兼容。项目还在 PHP 7.4 上跑的只能继续用switch。?php // switch 的合理用法贯穿合并 多条语句分支 declare(strict_types1); $role admin; $permissions []; switch ($role) { case super: $permissions[] manage_users; // 故意不 break继续往下叠加 // no break case admin: $permissions[] edit_content; // no break case editor: $permissions[] view_dashboard; break; default: $permissions[] view_public; } print_r($permissions);四、性能该怎么看match 比 switch 快是网上流传很广的说法但多数场景下这不构成选型理由。原因在于当分支都是常量且类型一致时PHP 解释器会为它们生成跳转表jump tableswitch和match都能享受到这一点因此查找复杂度是一致的差异主要出现在分支条件本身就带运算的场景而那种写法无论用哪个都绕不开求值成本。与其记住一个别人环境里的数字不如在你自己的机器上量一次。下面这个脚本用hrtime()做高精度计时两个函数体除了选择结构外完全一致?php // bench.php —— 需要 PHP 8.0 及以上结果与机器相关请以自己跑出来的为准 declare(strict_types1); function usingSwitch(int $x): string { switch ($x) { case 0: return a; case 1: return b; case 2: return c; case 3: return d; default: return e; } } function usingMatch(int $x): string { return match ($x) { 0 a, 1 b, 2 c, 3 d, default e, }; } function bench(callable $fn, int $rounds 1_000_000): float { $start hrtime(true); $sum 0; for ($i 0; $i $rounds; $i) { $sum strlen($fn($i % 5)); } $elapsed hrtime(true) - $start; // 把 $sum 用掉避免被优化掉同时保证结果被真正计算 return $elapsed / 1e6 $sum * 0.0; } printf(switch: %.2f ms\n, bench(usingSwitch(...))); printf(match : %.2f ms\n, bench(usingMatch(...)));这里用了 PHP 8.1 的一等可调用语法usingSwitch(...)如果只在 PHP 8.0 上跑把调用处换成字符串usingSwitch即可。跑出来的数字只代表你自己的环境不要把它当成通用结论——真正值得为match付出的理由始终是语义不贯穿、严格比较、强制穷尽。常见坑点❌ 用match时忘记写default运行时遇到未覆盖的取值直接抛UnhandledMatchError它是Error的子类catch (Exception)抓不到✅ 要么补齐所有分支要么显式写default throw ...声明这里本不该出现❌ 以为match也会做类型转换写出match ($_GET[n]) { 1 ... }而$_GET里的值永远是字符串✅match用比较字符串1匹配不到整数1先用(int)显式转换再匹配❌ 在match的分支里写多行语句例如想$a 1; log(); return ...✅ 分支只能是单个表达式需要多条语句就用switch或把逻辑抽成一个方法再在分支里调用❌ 在match分支里写break✅match不需要也不允许break它是表达式没有贯穿概念写了是语法错误❌ 继续用switch做枚举取值判断忘了某个新加的取值分支静默落空✅ 换成match并去掉default让漏了分支在测试阶段就抛错暴露出来❌ 依赖switch的松散比较写出switch ($status) { case 0: ... }而$status可能是字符串pending✅ 领会 PHP 8.0 起的比较语义变化非数字字符串与数字比较不再互相转换显式规范类型或改用match❌ 在switch里用continue想跳出外层循环结果只是像break一样结束了 switch✅ 跳出外层循环要写continue 2PHP 7.3 起对continue直接写在 switch 里会给出警告提示❌ 为了性能把可读性更好的match换成switch却没有在真实环境测量过✅ 常量分支两者都会走跳转表优化性能差异通常不是决策依据用上面那段脚本在自己环境量过再谈总结判断条件推荐理由分支是常量映射希望漏项立刻暴露match严格比较 未覆盖即抛UnhandledMatchError需要把结果直接返回或赋值match它是表达式可整体作为返回值分支是多条语句switchmatch分支只能是单个表达式需要贯穿合并多个caseswitchmatch无贯穿需用逗号显式列出需要跳出外层循环switchmatch里没有break/continue项目仍在 PHP 7.4switchmatch是 PHP 8.0 才有的语法做枚举的穷尽判断match配合enumPHP 8.1语义最贴切结论其实很清楚默认选match因为它把两类经典事故忘记 break 的贯穿、松散比较的意外命中变成了语法或运行时层面的硬约束只有当你确实需要多条语句的分支体、贯穿逻辑、循环控制或者跑在 PHP 7.4 上时才继续用switch。至于性能两者在常量分支上都会被翻译成跳转表不值得为它牺牲语义上的安全。