Skip to content

match 表达式

match 是 PHP 8.0 引入的新表达式,用于基于值的一致性进行分支计算。它与 switch 语句类似,但解决了 switch 的几个关键问题:使用严格比较(===)、不会发生穿透(fallthrough)、能返回值。在现代 PHP 开发中,match 已成为替代 switch 的首选方案。

前置知识

基础概念

match 表达式的核心特点:

特性switchmatch
引入版本PHP 所有版本PHP 8.0+
比较方式松散比较 ==严格比较 ===
返回值无(语句)有(表达式)
穿透行为会穿透到下一个 case无穿透
default可选未匹配时抛出异常
类型语句表达式

版本要求

match 表达式从 PHP 8.0.0 起可用。在 PHP 7.x 及以下版本中不可使用。

语法结构

基本 match 语法

php
<?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
<?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";
// 输出:success

default 分支

当没有其他条件匹配时,执行 default 分支。注意:match 表达式必须彻底列举所有情况,如果没有 default 且值不匹配任何条件,会抛出 UnhandledMatchError

php
<?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
<?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
<?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
<?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 int

match(true) 条件分支

true 作为主体表达式,可以处理范围判断等非一致性匹配的场景。

php
<?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
<?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
<?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
<?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
<?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)表达式更简洁
需要执行多条语句switchmatch 分支只支持表达式
需要穿透逻辑switchmatch 不支持穿透
PHP 7.x 兼容switchmatch 需要 PHP 8.0+
在模板中switch替代语法支持更好

注意事项

  1. match 是表达式match 必须用在需要值的地方(如赋值、返回、参数等),或者作为独立语句使用(末尾加分号)。

  2. 不能用 breakmatch 没有穿透,因此不需要也不支持 break

  3. 分支只能是表达式match 的箭头右侧必须是表达式,不能是语句块。如果需要执行复杂逻辑,可以调用函数。

  4. 条件求值顺序match 从上到下依次检查,找到第一个匹配项后立即返回,后续条件不会被求值(短路特性)。

  5. default 不能与普通条件用逗号组合case $a, default => ... 是无效语法。

最佳实践

  1. 优先使用 match:PHP 8.0+ 项目中,凡是值匹配的场景优先使用 match 替代 switch

  2. 使用 default 或穷举:要么提供 default 分支,要么确保所有可能的值都被列举。

  3. 利用 throw 表达式:在 default 中使用 throw 来处理不合法的输入,使代码更简洁。

  4. 保持分支简洁:如果分支逻辑复杂,提取为独立函数,保持 match 表达式的可读性。

  5. 注意条件顺序:当使用 match(true) 做范围判断时,注意条件的顺序(与 if/elseif 类似)。

下一节

条件语句就学习到这里。接下来进入循环语句,首先了解最基础的 while 循环

进阶用法

调试与测试技巧

php
<?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
<?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
<?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 修正
性能下降索引缺失/数据量大添加索引,优化查询
数据不一致并发冲突/事务残留使用锁机制和事务
内存溢出大数据集/未释放资源增大内存限制,分批处理

故障排除步骤

  1. 检查错误日志和异常信息
  2. 确认配置和环境是否正确
  3. 使用调试工具逐步排查
  4. 参考官方文档查找已知问题

版本兼容性说明

功能最低版本说明
基础功能PHP 8.1本文档基准版本
只读属性PHP 8.1public readonly 修饰符
枚举类型PHP 8.1enum 类型和 match 表达式
FiberPHP 8.1协程/轻量级并发
命名参数PHP 8.0foo(arg_name: value)
联合类型PHP 8.0`int
Null 安全运算符PHP 8.0$obj?->method()
析构器 promotionPHP 8.0__construct(public $x)
php
<?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');

参考链接