本文目录导读:

- 基础:try-catch-finally 结构
- 自定义异常类
- set_exception_handler 全局异常处理
- 借助 Error 类处理致命错误
- 真实项目中的最佳实践
- PHP 8 的新特性:Match 表达式与异常的配合
- 推荐的处理流程
在PHP项目中处理异常是一个非常重要的技能,它能帮助你的代码更健壮、更易维护,下面我会从基础到实践,系统地讲解PHP异常处理的几种方式。
基础:try-catch-finally 结构
这是最核心的异常处理方式。
try {
// 可能抛出异常的代码
$result = 10 / 0; // 除零错误
} catch (DivisionByZeroError $e) {
// 捕获特定类型的异常
echo "数学错误: " . $e->getMessage();
} catch (Exception $e) {
// 捕获其他所有异常(更宽泛的类型)
echo "其他异常: " . $e->getMessage();
} finally {
// 无论是否发生异常都会执行(可选)
echo "清理资源...";
}
重要提示:PHP 7.1+ 支持 finally 块,用于释放资源(关闭文件、数据库连接等)。
自定义异常类
在实际项目中,你可能会需要创建自己的异常类型,方便区分不同的错误场景。
class ValidationException extends Exception {}
class DatabaseException extends Exception {}
class UserService {
public function createUser($data) {
if (empty($data['email'])) {
throw new ValidationException('邮箱不能为空');
}
try {
// 数据库操作...
} catch (PDOException $e) {
// 将底层异常包装成业务异常
throw new DatabaseException('用户创建失败', 0, $e);
}
}
}
为什么这样做?
- 业务逻辑层不需要知道底层数据库的细节
- 上层代码可以按业务异常类型统一处理
set_exception_handler 全局异常处理
当你没有使用 try-catch 捕获异常时,PHP会调用这个全局处理器。
// 在项目入口文件(如 index.php)设置
set_exception_handler(function($exception) {
// 记录错误日志
error_log($exception->getMessage() . ' in ' . $exception->getFile() . ':' . $exception->getLine());
// 根据环境返回不同响应
if (getenv('APP_ENV') === 'production') {
http_response_code(500);
echo json_encode(['error' => '服务器内部错误']);
} else {
echo "未捕获的异常: " . $exception->getMessage();
}
});
// 故意抛出一个未捕获的异常
throw new Exception("测试全局异常处理器");
适用场景:
- API接口的统一错误响应
- 生产环境的错误屏蔽和日志记录
借助 Error 类处理致命错误
传统 Exception 无法捕获 PHP 的致命错误(如内存耗尽、类型错误等),从 PHP 7 开始,Error 类可以处理这些。
try {
// 故意触发类型错误
$obj = new stdClass();
$obj->method(); // 调用不存在的方法
} catch (Error $e) {
echo "捕获到致命错误: " . $e->getMessage();
}
注意:Error 和 Exception 都实现了 Throwable 接口,你可以统一捕获 Throwable。
try {
// 任何异常或错误
} catch (Throwable $t) {
echo "捕获所有错误/异常: " . $t->getMessage();
}
真实项目中的最佳实践
1 分层处理
Controller层 → Service层 → Repository层
- Repository层:捕获底层数据库异常,抛出自定义
DatabaseException - Service层:捕获业务逻辑异常,抛出
BusinessException或ValidationException - Controller层:使用
try-catch捕获所有异常,返回友好的HTTP响应
// Controller示例
public function createUserAction(Request $request) {
try {
$userService = new UserService();
$user = $userService->createUser($request->getBody());
return $this->json(['data' => $user], 201);
} catch (ValidationException $e) {
return $this->json(['error' => $e->getMessage()], 400);
} catch (DatabaseException $e) {
// 记录详细日志给运维,但返回通用信息给用户
Logger::error($e->getMessage(), ['trace' => $e->getTraceAsString()]);
return $this->json(['error' => '服务暂时不可用'], 500);
} catch (Throwable $t) {
Logger::critical($t->getMessage());
return $this->json(['error' => '未知错误'], 500);
}
}
2 日志记录
无论是否在开发环境,都应该记录异常日志。
try {
// 业务逻辑
} catch (Exception $e) {
// 使用 Monolog 或其他日志库
$logger->error($e->getMessage(), [
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
]);
// 重新抛出或返回错误响应
}
3 不要吞没异常
// ❌ 错误:空 catch 块会隐藏问题
try {
riskyOperation();
} catch (Exception $e) {
// 什么都不做——这样很难调试
}
// ✅ 正确:至少记录日志
try {
riskyOperation();
} catch (Exception $e) {
error_log($e->getMessage());
// 如果无法恢复,应该重新抛出或返回错误
throw $e;
}
4 使用异常链保留上下文
try {
connectDatabase();
queryUsers();
} catch (PDOException $e) {
// 把底层异常作为第三个参数传入,保留调用栈
throw new ServiceException('数据库操作失败', 500, $e);
}
这样上层可以同时看到业务错误信息和底层技术细节(在日志中)。
PHP 8 的新特性:Match 表达式与异常的配合
PHP 8 的 match 表达式在异常处理中也很有用:
function handleHttpCode(int $code): string {
return match ($code) {
400 => throw new BadRequestException(),
404 => throw new NotFoundException(),
500 => throw new InternalServerErrorException(),
default => '正常'
};
}
推荐的处理流程
- 定义自定义异常类:
ValidationException、DatabaseException、AuthException等 - 在业务层抛出特定异常,不要抛出通用
Exception - 在控制器或入口用
try-catch捕获,并决定是继续抛出还是返回HTTP响应 - 设置全局异常处理器 兜底未捕获的异常
- 始终记录日志,尤其是在生产环境
这样,你的PHP项目无论在开发还是生产环境,都能清晰、优雅地处理各种异常情况。