hyperf全局exceptionhandler不处理404,因其由dispatchermiddleware直接返回响应而不抛异常;必须在routes.php末尾添加router::get('/{any:.+}', )通配路由手动捕获并返回结构化404响应。

Hyperf 的全局 ExceptionHandler 默认不处理 404——它只捕获「已进入控制器但执行中抛出的异常」,而 404 是路由未匹配时由框架在更早阶段直接返回的响应,根本不会触发异常处理器链。
404 不走 ExceptionHandler 的真实原因
Hyperf 的 App\Exception\Handler\AppExceptionHandler::handle() 只接管 Throwable 类型的未捕获异常。而 404(即 NotFoundHttpException)在路由匹配失败时,是由 Hyperf\HttpServer\Middleware\DispatcherMiddleware 直接构造并返回 404 响应的,全程不 throw 任何异常。所以即使你写了 if ($throwable instanceof NotFoundHttpException),这个分支永远进不去。
常见错误现象:
- 访问不存在路径(如 /api/xxx)返回纯文本 Not Found 或空响应
- AppExceptionHandler::handle() 完全没被调用,日志里也无记录
- 自定义 JSON 错误格式对 404 无效
正确拦截 404 的唯一可靠方式:Router 层兜底
必须在路由注册末尾加一条「通配路由」,手动捕获所有未匹配请求,并返回你想要的结构化响应。这不是 hack,而是 Hyperf 官方推荐的模式。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
- 在
config/routes.php文件末尾添加:
Router::get('/{any:.+}', function () {
$data = json_encode(['code' => 404, 'message' => 'Resource not found'], JSON_UNESCAPED_UNICODE);
return response()->withStatus(404)
->withHeader('content-type', 'application/json; charset=utf-8')
->withBody(new SwooleStream($data));
})->name('fallback');
- 关键点:
–{any:.+}必须放在所有显式路由之后,否则会提前匹配掉合法路径
– 不要用Router::addRoute('GET', '/{any:.*}', ...),它不支持命名和中间件,优先级不可控
– 该闭包内不要依赖$request->getAttribute()等上下文,此时请求尚未进入完整生命周期
为什么不能靠 set404Override 或中间件?
Hyperf 没有类似 Laravel 的 set404Override() 或 CI4 那样的全局 404 覆盖钩子。它的 NotFoundHttpException 是硬编码在 Dispatcher 中的,无法通过配置替换。
试图用中间件拦截 404 也不可行:
– 中间件只对「已匹配到控制器」的请求生效
– 404 请求根本不会经过任何中间件栈(CoreMiddleware → DispatcherMiddleware 就结束了)
– 在 DispatcherMiddleware 里 patch 逻辑属于侵入式修改,升级时极易断裂
注意 fallback 路由的边界行为
这条兜底路由虽能捕获大部分 404,但仍有两个典型漏网场景:
– 静态资源路径(如 /static/js/app.js)若被 static_handler_locations 配置劫持,Swoole 会直接返回 404,绕过所有 PHP 路由逻辑
– HTTP 方法不匹配(如对 /user 发 POST)返回的是 405 Method Not Allowed,不是 404,需单独加 Router::post('/{any:.+}', ...) 处理
真正健壮的做法是:把 fallback 写成函数复用,再为 GET/POST/PUT/DELETE 各挂一次,避免遗漏方法维度的“伪 404”。










