
本文介绍如何通过自定义异常类与全局异常处理器,替代重复创建 response 对象的冗余逻辑,实现 rest api 中 http 错误响应的集中、可维护、符合 psr-15 风格的错误处理机制。
本文介绍如何通过自定义异常类与全局异常处理器,替代重复创建 response 对象的冗余逻辑,实现 rest api 中 http 错误响应的集中、可维护、符合 psr-15 风格的错误处理机制。
在构建 PHP REST API 时,频繁手动实例化 Response 类并逐个调用 setHttpStatusCode()、addMessage() 等方法(如问题中所示),不仅代码冗长、易出错,更严重违背了 单一职责原则 和 异常即控制流 的 OOP 设计理念。真正高效的错误处理应将「错误发生」与「错误呈现」解耦——前者由业务逻辑触发,后者由统一入口接管。
✅ 推荐方案:自定义异常 + 全局异常处理器
1. 定义语义化异常类
为不同 HTTP 状态码建立清晰的异常映射,增强可读性与可扩展性:
// src/Exception/HttpException.php
class HttpException extends RuntimeException
{
private int $httpCode;
public function __construct(string $message, int $httpCode = 500, ?Throwable $previous = null)
{
parent::__construct($message, $httpCode, $previous);
$this->httpCode = $httpCode;
}
public function getHttpCode(): int
{
return $this->httpCode;
}
}
// 可选:派生常用状态码异常(提升语义)
class BadRequestException extends HttpException
{
public function __construct(string $message = 'Bad Request')
{
parent::__construct($message, 400);
}
}
class NotFoundException extends HttpException
{
public function __construct(string $message = 'Not Found')
{
parent::__construct($message, 404);
}
}
2. 重构业务逻辑:用 throw 替代 new Response()
所有校验和边界条件直接抛出异常,彻底移除重复的响应构造代码:
// 在路由或控制器中
if (!array_key_exists('newsid', $_GET)) {
throw new BadRequestException('Haber id\'si belirtilmedi.');
}
$news_id = $_GET['newsid'];
if ($news_id === '' || !is_numeric($news_id)) {
throw new BadRequestException('Haber id\'si boş olamaz veya sayı olmalı!');
}
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
try {
$stmt = $readDB->prepare("SELECT id,title,details,poster_link FROM tb_news WHERE id = :id");
$stmt->bindParam(':id', $news_id, PDO::PARAM_INT);
$stmt->execute();
if ($stmt->rowCount() === 0) {
throw new NotFoundException('Haber Bulunamadı');
}
// 正常流程:返回成功响应
$response = new Response();
$response->setHttpStatusCode(200);
$response->setSuccess(true);
$response->setData($stmt->fetch(PDO::FETCH_ASSOC));
$response->send();
exit;
} catch (PDOException $e) {
// 数据库层异常自动转为 500,无需手动捕获
throw new HttpException('Veritabanı hatası', 500, $e);
}
}
3. 注册全局异常处理器(关键一步)
在应用启动入口(如 index.php 或框架引导文件)中注册统一处理器:
// index.php 或 bootstrap.php
set_exception_handler(function (Throwable $e): void {
$response = new Response();
if ($e instanceof HttpException) {
$response->setHttpStatusCode($e->getHttpCode());
$response->addMessage($e->getMessage());
} else {
// 生产环境应隐藏敏感信息,仅记录日志
error_log('[FATAL] Unhandled exception: ' . $e->__toString());
$response->setHttpStatusCode(500);
$response->addMessage('Sunucu hatası');
}
$response->setSuccess(false);
$response->send();
exit;
});
⚠️ 注意事项与最佳实践
-
绝不暴露内部错误细节:
PDOException等底层异常必须被拦截,生产环境禁止返回 SQL 或堆栈信息; -
HTTP 状态码 ≠ 异常代码:
Exception::getCode()默认为 0,务必通过自定义属性(如getHttpCode())传递状态码,避免歧义; -
保持
Response类无状态:确保Response实例可复用、无副作用,避免在异常处理器中意外修改已有响应; -
配合 PSR-7/PSR-15:若使用现代框架(如 Slim、Laravel),优先采用其原生异常中间件,而非自行实现
set_exception_handler; -
单元测试友好:抛出异常比直接
exit更易被 PHPUnit 捕获验证,例如expectException(HttpException)。
通过该模式,你将获得:✅ 业务代码专注逻辑而非响应组装;✅ 错误响应格式与状态码完全集中管控;✅ 新增错误类型只需新增异常类,零侵入现有逻辑;✅ 符合 PHP 社区主流实践,便于团队协作与维护。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











