多重捕获
概述
PHP 7.1 引入了多重 catch 特性,允许在一个 catch 块中捕获多种异常类型,使用 | 分隔。这减少了重复代码,使异常处理更加简洁。
多重捕获适用于多种异常需要相同处理逻辑的场景,但仍保持对不同异常类型的区分。
版本说明
- PHP 7.1 引入多重 catch 语法
catch (A | B $e) - PHP 8.0+ 中多重 catch 可与
TypeError、ValueError等新异常类型配合使用
基础概念
传统方式 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 中使用有继承关系的类型。
最佳实践
- 将相关的异常组合:业务异常、基础设施异常分别组合
- 仍保留最具体的 catch:对需要特殊处理的异常使用独立的 catch
- 使用 instanceof 区分类型:在多重 catch 内部使用
get_class()或instanceof - 与 HTTP 状态码对齐:将相同状态码的异常组合在一个 catch 中
- 兜底使用 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 修正 |
| 性能下降 | 索引缺失/数据量大 | 添加索引,优化查询 |
| 数据不一致 | 并发冲突/事务残留 | 使用锁机制和事务 |
| 内存溢出 | 大数据集/未释放资源 | 增大内存限制,分批处理 |
故障排除步骤
- 检查错误日志和异常信息
- 确认配置和环境是否正确
- 使用调试工具逐步排查
- 参考官方文档查找已知问题
版本兼容性说明
| 功能 | 最低版本 | 说明 |
|---|---|---|
| 基础功能 | 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
<?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');