Hyperf异常处理需实现ExceptionHandlerInterface接口并重写handle()方法,按优先级注册处理器,区分业务异常(200+JSON)与系统错误(500),补充请求上下文日志,避免在handle中抛出新异常或直接输出。

Hyperf 的异常处理机制基于 Swoole 协程和 PSR-7/PSR-15,其默认行为会将未捕获异常转为 500 响应,但实际项目中需要更精细的控制:比如区分业务异常与系统错误、统一 JSON 错误格式、记录上下文、或对特定异常跳过日志。自定义的关键在于拦截异常流并重写响应逻辑,而非简单 try-catch。
覆盖默认异常处理器
Hyperf 提供 ExceptionHandlerInterface 接口,所有自定义异常处理器需实现它。核心是重写 handle() 方法,在其中决定如何响应、是否记录、是否中断流程。
- 创建类(如
App\Exception\Handler\AppExceptionHandler),实现接口并声明public function handle(Throwable $throwable, ResponseInterface $response): ResponseInterface - 在
config/autoload/exceptions.php中注册该处理器,按优先级顺序排列;越靠前越先执行,可设多个处理器处理不同异常类型 - 注意:若某个处理器返回了响应对象,后续处理器将不再触发;若想“透传”给下一个,可不返回或抛出新异常
区分异常类型做差异化响应
不是所有异常都该返回 500。比如 App\Exception\BusinessException 可返回 200 + 错误码,而 PDOException 应返回 500 并脱敏。
- 用
instanceof判断异常类型:if ($throwable instanceof BusinessException) - 对业务异常,构造标准 JSON 响应:
return $response->withStatus(200)->withBody(new SwooleStream(json_encode(['code' => $throwable->getCode(), 'msg' => $throwable->getMessage()]))) - 对系统异常,建议统一返回
['code' => 500, 'msg' => 'Internal Server Error'],且不暴露堆栈(开发环境除外)
补充上下文与日志记录
默认异常日志只含消息和堆栈,缺少请求 ID、用户 ID、URI 等关键信息,不利于排查。
- 在
handle()中通过$this->container->get(RequestInterface::class)获取当前请求对象 - 提取
$request->getUri()->getPath()、$request->getAttribute('user_id', 'guest')、$request->getHeaderLine('x-request-id')等字段 - 使用
$this->logger->error()记录结构化日志,例如:"Uncaught exception {class} in {uri}, uid={uid}, req_id={req_id}"
避免常见陷阱
自定义异常处理看似简单,但几个细节容易导致线上问题:
- 不要在
handle()中抛出新异常(除非你明确要交由上层处理),否则可能引发嵌套崩溃 - 不要直接 echo 或 var_dump,Swoole 协程下输出不可控,响应必须通过
ResponseInterface返回 - 若使用中间件做前置校验(如权限、参数),建议抛出业务异常而非返回响应,让异常处理器统一收口,保持响应风格一致
- 开发环境可开启
display_errors并返回详细堆栈,生产环境务必关闭,仅记录日志
不复杂但容易忽略。关键是把异常当作一种可控的业务信号,而不是必须兜底的错误现场。处理得当,API 的稳定性和可维护性会明显提升。










