Hyperf异常处理需显式注册、按数组顺序匹配,AppExceptionHandler须置于HttpExceptionHandler后以防误吞404等状态码;HTTP status需手动设置,业务code应放JSON body;stopPropagation()必须调用以终结异常传递;上下文日志需从容器取请求信息并结构化记录。

Hyperf 的异常处理不是“开了就行”,而是必须显式注册、按优先级匹配、且一旦漏配就可能让 Worker 进程崩溃退出——这不是警告,是真实发生的生产事故。
为什么 AppExceptionHandler 一定要放在 HttpExceptionHandler 后面?
因为 HttpExceptionHandler 是框架内置的兜底处理器,它会捕获所有 HttpException 及其子类(比如 NotFoundException、MethodNotAllowedHttpException),并直接返回标准 HTTP 状态码响应。如果你把自定义的 AppExceptionHandler 放在它前面,且 isValid() 返回 true,那连 404 都会被你拦截成 500,前端收不到正确的状态码。
-
config/autoload/exceptions.php中的数组顺序 = 执行顺序,靠前的先试 -
isValid()必须严格判断类型,不能写成return true;就完事(除非你真想接管全部) - 常见错误:把
FooException的 handler 放在最前,结果所有ValidationException也被它吞掉,导致表单校验失败返回空 JSON
throw new FooException('xxx', 504) 的状态码到底怎么生效?
HTTP 响应状态码不会自动从 FooException 构造函数的第二个参数映射过去。它只是个业务码,真正影响 HTTP status 的是你在 handle() 方法里调用的 $response->withStatus(500) 或类似操作。
-
FooException::getCode()返回的是业务错误码(如 504),常用于 JSON body 的code字段 - HTTP status 默认是 500,需手动设为
withStatus($throwable->getCode())——但注意:不是所有业务码都适合当 HTTP status(比如 504 是网关超时,别乱用) - 建议分离:HTTP status 固定用 200/4xx/5xx,业务 code 放 JSON body 里,避免语义混淆
为什么 stopPropagation() 必须调用?
不调用 $this->stopPropagation(),异常会继续往下一个 ExceptionHandler 传递。如果后面还有更宽泛的 handler(比如捕获 Throwable 的兜底类),你的定制逻辑就白写了。
- 每个 handler 都该明确自己是否“终结”这个异常,而不是靠顺序侥幸
- 常见坑:写了
if ($e instanceof FooException) { ... }却没加stopPropagation(),结果日志里看到两条重复报错 - 如果 handler 不处理当前异常,就不要调用
stopPropagation(),让它继续往下走
如何让异常自动记录完整上下文?
光靠 echo $e->getTraceAsString() 不够。生产环境需要请求路径、参数、IP、时间戳、文件行号——这些信息不在异常对象里,得从容器里取。
- 在
handle()方法中通过$this->container->get(RequestInterface::class)拿到当前请求 - 别用
var_dump()或print_r()记日志,用LoggerInterface写结构化日志(JSON 格式) - 注意:
RequestInterface在handle()里不一定可用(取决于 Swoole 生命周期阶段),稳妥做法是提前在中间件里把关键字段存入 Context
isValid() 更精确、谁的 stopPropagation() 更果断。写 handler 不是堆代码,是画一张异常分发地图。


















