match 表达式
match 是 PHP 8.0 引入的新表达式,用于基于值的一致性进行分支计算。它与 switch 语句类似,但解决了 switch 的几个关键问题:使用严格比较(===)、不会发生穿透(fallthrough)、能返回值。在现代 PHP 开发中,match 已成为替代 switch 的首选方案。
基础概念
match 表达式的核心特点:
| 特性 | switch | match |
|---|---|---|
| 引入版本 | PHP 所有版本 | PHP 8.0+ |
| 比较方式 | 松散比较 == | 严格比较 === |
| 返回值 | 无(语句) | 有(表达式) |
| 穿透行为 | 会穿透到下一个 case | 无穿透 |
| default | 可选 | 未匹配时抛出异常 |
| 类型 | 语句 | 表达式 |
版本要求
match 表达式从 PHP 8.0.0 起可用。在 PHP 7.x 及以下版本中不可使用。
语法结构
基本 match 语法
<?php
declare(strict_types=1);
$food = 'cake';
$returnValue = match ($food) {
'apple' => 'This food is an apple',
'bar' => 'This food is a bar',
'cake' => 'This food is a cake',
};
echo $returnValue . "\n";
// 输出:This food is a cake多条件匹配
多个条件可以用逗号分隔,共享同一个返回值(逻辑 OR)。
<?php
declare(strict_types=1);
$status = 200;
$result = match ($status) {
200, 201, 204 => 'success',
301, 302 => 'redirect',
400, 404 => 'client_error',
500, 502, 503 => 'server_error',
default => 'unknown',
};
echo $result . "\n";
// 输出:successdefault 分支
当没有其他条件匹配时,执行 default 分支。注意:match 表达式必须彻底列举所有情况,如果没有 default 且值不匹配任何条件,会抛出 UnhandledMatchError。
<?php
declare(strict_types=1);
$role = 'guest';
$permission = match ($role) {
'admin' => 'full_access',
'editor' => 'write_access',
'user' => 'read_access',
default => 'no_access',
};
echo "权限级别:{$permission}\n";
// 输出:权限级别:no_access详细说明
严格比较 ===
match 使用严格比较(===),这意味着类型和值都必须匹配。这是与 switch 最关键的区别之一。
<?php
declare(strict_types=1);
$value = '1';
// match:严格比较,'1' !== 1,不匹配
$result = match ($value) {
1 => '整数 1',
true => '布尔 true',
'1' => '字符串 1',
};
echo $result . "\n";
// 输出:字符串 1
// switch:松散比较,'1' == 1,会匹配 case 1
switch ($value) {
case 1:
echo "switch: 匹配了整数 1(松散比较)\n";
break;
case '1':
echo "switch: 匹配了字符串 1\n";
break;
}
// 输出:switch: 匹配了整数 1(松散比较)无穿透(No Fallthrough)
match 不存在 switch 的穿透问题。每个分支只执行自己的返回表达式,不会意外执行后续分支。
<?php
declare(strict_types=1);
$status = 200;
// match:无穿透,每个分支独立
$message = match ($status) {
200 => 'OK',
201 => 'Created',
204 => 'No Content',
default => 'Unknown',
};
echo $message . "\n";
// 输出:OK —— 只执行了 200 对应的分支
// 对比 switch:如果没有 break 会穿透
switch ($status) {
case 200:
echo "200: OK\n";
// 忘记 break 会继续执行下面的 case
case 201:
echo "201: Created\n";
break;
}
// 如果漏掉 break,两个 echo 都会执行未处理匹配异常
如果 match 表达式没有 default 分支,且传入的值不匹配任何条件,PHP 会抛出 UnhandledMatchError 异常。
<?php
declare(strict_types=1);
$condition = 5;
try {
$result = match ($condition) {
1, 2 => 'foo',
3, 4 => 'bar',
// 没有 default,5 不匹配任何条件
};
} catch (\UnhandledMatchError $e) {
echo "捕获异常:{$e->getMessage()}\n";
}
// 输出:捕获异常:Unhandled match value of type intmatch(true) 条件分支
将 true 作为主体表达式,可以处理范围判断等非一致性匹配的场景。
<?php
declare(strict_types=1);
$age = 65;
$category = match (true) {
$age < 2 => '婴儿',
$age < 13 => '儿童',
$age < 18 => '青少年',
$age < 60 => '成年人',
$age >= 65 => '老年',
default => '未知',
};
echo "年龄分类:{$category}\n";
// 输出:年龄分类:老年使用 throw 表达式处理异常(PHP 8.0+)
在 match 分支中可以直接使用 throw 作为表达式,这在 PHP 8.0+ 中是合法的,因为 throw 已经成为表达式。
<?php
declare(strict_types=1);
function getDaysInMonth(string $month, int $year): int
{
return match (strtolower(substr($month, 0, 3))) {
'apr', 'jun', 'sep', 'nov' => 30,
'jan', 'mar', 'may', 'jul', 'aug', 'oct', 'dec' => 31,
'feb' => (($year % 4 === 0 && $year % 100 !== 0) || $year % 400 === 0) ? 29 : 28,
default => throw new \InvalidArgumentException("无效的月份:{$month}"),
};
}
echo "2月天数:" . getDaysInMonth('February', 2024) . "\n"; // 29(闰年)
echo "4月天数:" . getDaysInMonth('April', 2024) . "\n"; // 30
echo "12月天数:" . getDaysInMonth('December', 2024) . "\n"; // 31实战示例
HTTP 状态码处理
<?php
declare(strict_types=1);
function handleHttpResponse(int $statusCode): string
{
return match (true) {
$statusCode >= 200 && $statusCode < 300 => "成功 ({$statusCode})",
$statusCode >= 300 && $statusCode < 400 => "重定向 ({$statusCode})",
$statusCode >= 400 && $statusCode < 500 => "客户端错误 ({$statusCode})",
$statusCode >= 500 => "服务器错误 ({$statusCode})",
default => "未知状态码 ({$statusCode})",
};
}
echo handleHttpResponse(200) . "\n"; // 成功 (200)
echo handleHttpResponse(301) . "\n"; // 重定向 (301)
echo handleHttpResponse(404) . "\n"; // 客户端错误 (404)
echo handleHttpResponse(500) . "\n"; // 服务器错误 (500)FizzBuzz 经典问题
使用 match 的简洁方式实现 FizzBuzz。
<?php
declare(strict_types=1);
function fizzBuzz(int $num): string
{
return match (0) {
$num % 15 => 'FizzBuzz',
$num % 3 => 'Fizz',
$num % 5 => 'Buzz',
default => (string) $num,
};
}
for ($i = 1; $i <= 15; $i++) {
echo fizzBuzz($i) . " ";
}
// 输出:1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz配置映射
<?php
declare(strict_types=1);
function getDatabaseConfig(string $driver): array
{
return match ($driver) {
'mysql' => [
'host' => 'localhost',
'port' => 3306,
'charset' => 'utf8mb4',
],
'pgsql' => [
'host' => 'localhost',
'port' => 5432,
'charset' => 'utf8',
],
'sqlite' => [
'path' => __DIR__ . '/database.sqlite',
],
default => throw new \RuntimeException("不支持的数据库驱动:{$driver}"),
};
}
$config = getDatabaseConfig('mysql');
print_r($config);match 与 switch 的选择
| 场景 | 推荐 | 原因 |
|---|---|---|
| 简单值匹配 | match | 严格比较,返回值 |
| 范围判断 | match(true) | 表达式更简洁 |
| 需要执行多条语句 | switch | match 分支只支持表达式 |
| 需要穿透逻辑 | switch | match 不支持穿透 |
| PHP 7.x 兼容 | switch | match 需要 PHP 8.0+ |
| 在模板中 | switch | 替代语法支持更好 |
注意事项
match 是表达式:
match必须用在需要值的地方(如赋值、返回、参数等),或者作为独立语句使用(末尾加分号)。不能用 break:
match没有穿透,因此不需要也不支持break。分支只能是表达式:
match的箭头右侧必须是表达式,不能是语句块。如果需要执行复杂逻辑,可以调用函数。条件求值顺序:
match从上到下依次检查,找到第一个匹配项后立即返回,后续条件不会被求值(短路特性)。default 不能与普通条件用逗号组合:
case $a, default => ...是无效语法。
最佳实践
优先使用 match:PHP 8.0+ 项目中,凡是值匹配的场景优先使用
match替代switch。使用 default 或穷举:要么提供
default分支,要么确保所有可能的值都被列举。利用 throw 表达式:在
default中使用throw来处理不合法的输入,使代码更简洁。保持分支简洁:如果分支逻辑复杂,提取为独立函数,保持
match表达式的可读性。注意条件顺序:当使用
match(true)做范围判断时,注意条件的顺序(与if/elseif类似)。
下一节
条件语句就学习到这里。接下来进入循环语句,首先了解最基础的 while 循环。
进阶用法
调试与测试技巧
<?php
declare(strict_types=1);
// 单元测试辅助函数
function createTestResource(): mixed
{
return match (true) {
default => new stdClass(),
};
}
// 调试输出函数
function debugOutput(mixed , string = ''): void
{
= ? ": " : '';
.= print_r(, true);
fwrite(STDERR, . "\n");
}
// 性能基准测试
function benchmark(callable , int = 1000): float
{
= hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
$fn();
}
return (hrtime(true) - $start) / 1e9;
}日志记录实践
<?php
declare(strict_types=1);
/**
* 简易日志记录器
*/
class SimpleLogger
{
private string $logFile;
private string $level = 'INFO';
public function __construct(string $logFile)
{
$this->logFile = $logFile;
}
public function info(string $message, array $context = []): void
{
$this->log('INFO', $message, $context);
}
public function warning(string $message, array $context = []): void
{
$this->log('WARNING', $message, $context);
}
public function error(string $message, array $context = []): void
{
$this->log('ERROR', $message, $context);
}
private function log(string $level, string $message, array $context): void
{
$timestamp = date('Y-m-d H:i:s');
$contextStr = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '';
$line = "[{$timestamp}] [{$level}] {$message}{$contextStr}\n";
file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
}
}配置与环境检测
<?php
declare(strict_types=1);
// 环境检测工具
class EnvironmentChecker
{
public static function checkRequirements(array $requirements): array
{
$results = [];
foreach ($requirements as $name => $check) {
$results[$name] = is_callable($check) ? $check() : false;
}
return $results;
}
public static function getSystemInfo(): array
{
return [
'php_version' => PHP_VERSION,
'os' => PHP_OS,
'sapi' => PHP_SAPI,
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'loaded_extensions' => get_loaded_extensions(),
];
}
}常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 连接超时 | 网络问题/配置错误 | 检查配置,增加超时时间 |
| 权限不足 | 文件/目录权限 | 使用 chmod/chown 修正 |
| 性能下降 | 索引缺失/数据量大 | 添加索引,优化查询 |
| 数据不一致 | 并发冲突/事务残留 | 使用锁机制和事务 |
| 内存溢出 | 大数据集/未释放资源 | 增大内存限制,分批处理 |
故障排除步骤
- 检查错误日志和异常信息
- 确认配置和环境是否正确
- 使用调试工具逐步排查
- 参考官方文档查找已知问题
版本兼容性说明
| 功能 | 最低版本 | 说明 |
|---|---|---|
| 基础功能 | PHP 8.1 | 本文档基准版本 |
| 只读属性 | PHP 8.1 | public readonly 修饰符 |
| 枚举类型 | PHP 8.1 | enum 类型和 match 表达式 |
| Fiber | PHP 8.1 | 协程/轻量级并发 |
| 命名参数 | PHP 8.0 | foo(arg_name: value) |
| 联合类型 | PHP 8.0 | `int |
| Null 安全运算符 | PHP 8.0 | $obj?->method() |
| 析构器 promotion | PHP 8.0 | __construct(public $x) |
<?php
declare(strict_types=1);
// 版本兼容性检测
function ensureVersion(string $minVersion): void
{
if (version_compare(PHP_VERSION, $minVersion, '<')) {
throw new RuntimeException(
sprintf('需要 PHP %s+, 当前版本: %s', $minVersion, PHP_VERSION)
);
}
}
ensureVersion('8.1.0');