Skip to content

多重捕获

概述

PHP 7.1 引入了多重 catch 特性,允许在一个 catch 块中捕获多种异常类型,使用 | 分隔。这减少了重复代码,使异常处理更加简洁。

多重捕获适用于多种异常需要相同处理逻辑的场景,但仍保持对不同异常类型的区分。

版本说明

  • PHP 7.1 引入多重 catch 语法 catch (A | B $e)
  • PHP 8.0+ 中多重 catch 可与 TypeErrorValueError 等新异常类型配合使用

基础概念

传统方式 vs 多重捕获

php
<?php

declare(strict_types=1);

// 传统方式:多个 catch 块处理相同逻辑
try {
    riskyOperation();
} catch (InvalidArgumentException $e) {
    handleError($e);
} catch (RuntimeException $e) {
    handleError($e);  // 重复逻辑
} catch (LogicException $e) {
    handleError($e);  // 重复逻辑
}

// PHP 7.1+ 多重捕获:一个 catch 处理多种异常
try {
    riskyOperation();
} catch (InvalidArgumentException | RuntimeException | LogicException $e) {
    handleError($e);  // 统一处理
}

语法与代码

基本 | 语法

php
<?php

declare(strict_types=1);

function processData(string $input): string
{
    if (strlen($input) < 3) {
        throw new InvalidArgumentException('输入长度不能少于3');
    }
    if (!ctype_alnum($input)) {
        throw new RuntimeException('输入只能包含字母和数字');
    }
    return strtoupper($input);
}

try {
    $result = processData('ab');
} catch (InvalidArgumentException | RuntimeException $e) {
    echo "输入验证失败: " . $e->getMessage();
} catch (Throwable $e) {
    echo "未知错误: " . $e->getMessage();
}

与 Error 类型的多重捕获

php
<?php

declare(strict_types=1);

function safeDivide(int $a, int $b): float
{
    return $a / $b;
}

try {
    $result = safeDivide(1, 0);
} catch (ArithmeticError | DivisionByZeroError $e) {
    // DivisionByZeroError 继承自 ArithmeticError
    // 这里会匹配 DivisionByZeroError
    echo "算术错误: " . $e->getMessage();
} catch (TypeError $e) {
    echo "类型错误: " . $e->getMessage();
}

使用 get_class() 区分具体类型

php
<?php

declare(strict_types=1);

function handleRequest(array $params): array
{
    try {
        $data = validate($params);
        return process($data);
    } catch (InvalidArgumentException | ValidationException | DomainException $e) {
        // 不同异常类型可能需要不同的处理
        return match (get_class($e)) {
            InvalidArgumentException::class => ['error' => 'invalid_param', 'detail' => $e->getMessage()],
            ValidationException::class => ['error' => 'validation_failed', 'detail' => $e->getMessage()],
            DomainException::class => ['error' => 'domain_error', 'detail' => $e->getMessage()],
            default => ['error' => 'unknown', 'detail' => $e->getMessage()],
        };
    }
}

function validate(array $params): array { return $params; }
function process(array $data): array { return ['success' => true]; }
class ValidationException extends RuntimeException {}

详细说明

多重 catch 与异常继承

php
<?php

declare(strict_types=1);

// 多重 catch 遵循与单 catch 相同的继承规则
// 如果异常类 A 是 B 的子类,catch (A | B) 两者都能匹配
// 但 A 会优先匹配更具体的类型

class MyException extends RuntimeException {}
class SpecificException extends MyException {}

try {
    throw new SpecificException('test');
} catch (SpecificException $e) {
    echo "SpecificException\n";  // 先匹配(更具体)
} catch (MyException | RuntimeException $e) {
    echo "MyException or RuntimeException\n";  // 不会到达
}

// 如果使用多重 catch
try {
    throw new MyException('test');
} catch (SpecificException | MyException $e) {
    echo "Specific or My\n";  // 匹配 MyException
}

与 switch/match 配合的替代方案

php
<?php

declare(strict_types=1);

// 方式 1:多个 catch + match(PHP 8.0+)
try {
    throw new DomainException('domain');
} catch (InvalidArgumentException | DomainException | RuntimeException $e) {
    $handler = match (true) {
        $e instanceof InvalidArgumentException => 'handleInvalidArg',
        $e instanceof DomainException => 'handleDomain',
        default => 'handleRuntime',
    };
    echo $handler;
}

// 方式 2:多个 catch 分别处理
try {
    throw new DomainException('domain');
} catch (InvalidArgumentException $e) {
    // 处理参数异常
} catch (DomainException $e) {
    echo "处理域异常: " . $e->getMessage();
} catch (RuntimeException $e) {
    // 处理运行时异常
}

在框架中的典型应用

php
<?php

declare(strict_types=1);

// API 控制器中的多重 catch
class ApiController
{
    public function create(array $data): array
    {
        try {
            $this->validate($data);
            $entity = $this->service->create($data);
            return ['success' => true, 'data' => $entity];
        } catch (ValidationException $e) {
            return ['success' => false, 'code' => 422, 'errors' => $e->errors()];
        } catch (EntityNotFoundException | DuplicateEntityException $e) {
            return ['success' => false, 'code' => $e->getCode(), 'message' => $e->getMessage()];
        } catch (InfrastructureException $e) {
            error_log($e->__toString());
            return ['success' => false, 'code' => 500, 'message' => '系统错误'];
        }
    }

    private function validate(array $data): void {}
}

class ValidationException extends RuntimeException
{
    public function errors(): array { return []; }
}

class EntityNotFoundException extends RuntimeException {}
class DuplicateEntityException extends RuntimeException {}
class InfrastructureException extends RuntimeException {}

实战示例

统一的 HTTP 异常处理

php
<?php

declare(strict_types=1);

class HttpExceptionHandler
{
    public function handle(callable $action): array
    {
        try {
            return ['success' => true, 'data' => $action()];
        } catch (NotFoundException $e) {
            return $this->errorResponse(404, $e->getMessage());
        } catch (ValidationException $e) {
            return $this->errorResponse(422, $e->getMessage(), $e->errors());
        } catch (AuthenticationException | AuthorizationException $e) {
            return $this->errorResponse(401, $e->getMessage());
        } catch (RateLimitException | ServiceUnavailableException $e) {
            return $this->errorResponse(429, $e->getMessage());
        } catch (Throwable $e) {
            error_log($e->__toString());
            return $this->errorResponse(500, 'Internal Server Error');
        }
    }

    private function errorResponse(
        int $code,
        string $message,
        ?array $details = null
    ): array {
        return [
            'success' => false,
            'code'    => $code,
            'message' => $message,
            'details' => $details,
        ];
    }
}

class NotFoundException extends RuntimeException {}
class ValidationException extends RuntimeException
{
    public function errors(): array { return []; }
}
class AuthenticationException extends RuntimeException {}
class AuthorizationException extends RuntimeException {}
class RateLimitException extends RuntimeException {}
class ServiceUnavailableException extends RuntimeException {}

// 使用
$handler = new HttpExceptionHandler();
$response = $handler->handle(function () {
    throw new NotFoundException('资源不存在');
});

注意事项

多重 catch 的顺序不影响匹配

php
<?php

declare(strict_types=1);

// 多重 catch 中的类型顺序不影响匹配
// PHP 按异常的实际类型进行匹配,不是按声明顺序

try {
    throw new DomainException('test');
} catch (RuntimeException | InvalidArgumentException | DomainException $e) {
    echo "匹配到 DomainException: " . get_class($e);
}

// 但多个 catch 块的顺序很重要
try {
    throw new DomainException('test');
} catch (DomainException $e) {
    echo "Domain\n";           // 先匹配
} catch (RuntimeException $e) {
    echo "Runtime\n";          // 不会到达
}

类型必须不同

php
<?php

declare(strict_types=1);

// 多重 catch 中的类型不能重复
// catch (RuntimeException | RuntimeException $e)  // 错误!重复

// 也不能有父子关系(虽然语法允许,但子类永远匹配不到)
// catch (RuntimeException | LogicException $e)
// 注意:RuntimeException 不是 LogicException 的子类,所以合法

多重 catch 中的父子类

虽然语法允许在 | 中使用有继承关系的类型(如 RuntimeException | Exception),但子类会被父类覆盖,永远不会被独立匹配。应避免在同一个 catch 中使用有继承关系的类型。

最佳实践

  1. 将相关的异常组合:业务异常、基础设施异常分别组合
  2. 仍保留最具体的 catch:对需要特殊处理的异常使用独立的 catch
  3. 使用 instanceof 区分类型:在多重 catch 内部使用 get_class()instanceof
  4. 与 HTTP 状态码对齐:将相同状态码的异常组合在一个 catch 中
  5. 兜底使用 Throwable:最后一个 catch 使用 Throwable 确保不遗漏
php
<?php

declare(strict_types=1);

// 推荐结构
try {
    $result = operation();
} catch (ValidationException $e) {
    // 特定处理:验证失败
} catch (NotFoundException | ConflictException $e) {
    // 相关异常:资源问题
} catch (InfrastructureException $e) {
    // 基础设施异常
} catch (Throwable $e) {
    // 兜底
}

进阶用法

调试与测试技巧

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');

参考链接