Slim 4 默认 errorHandler输出HTML错误页且Content-Type为text/html,破坏API的JSON一致性;它仅捕获未被try-catch拦截的异常,不处理HttpNotFoundException等Slim自定义异常,且不自动写日志,需手动集成。

Slim 4 的错误处理中间件必须显式注册并重写默认处理器,否则未捕获异常会返回 HTML 堆栈页,破坏 API 一致性。
为什么 Slim 默认 errorHandler 不适合 API 场景
Slim 内置的 errorHandler 默认输出带 HTML 样式的错误页面(含堆栈跟踪),且 Content-Type 是 text/html。这对前端调用 JSON 接口是灾难性的:前端解析失败、监控误报、日志混杂 HTML 片段。
关键点在于:errorHandler 是 PSR-15 中间件链末端的兜底逻辑,它不参与中间件 add() 链,而是通过 addErrorMiddleware() 单独配置。
- 它只捕获未被 try-catch 拦截的异常,不处理
HttpNotFoundException(404)或HttpUnauthorizedException(401)等 Slim 自定义异常 - 它的返回响应对象必须是 PSR-7
ResponseInterface,不能直接 echo 或 print - 它默认不写日志,需手动集成 Monolog 或
file_put_contents()
如何注册自定义 errorHandler 和 notFoundHandler
必须在创建 App 实例后、run() 前完成配置,且顺序不能颠倒 —— 先配 notFoundHandler,再配 errorHandler,否则 404 可能被 error 处理器吞掉。
示例配置:
$app = new \Slim\App();
// 1. 重写 404 处理器
$app->getContainer()->get('notFoundHandler') = function ($c) {
return function ($request, $response) {
return $response->withStatus(404)->withHeader('Content-Type', 'application/json')
->withJson(['error' => 'Not Found'], 404);
};
};
// 2. 重写全局异常处理器
$app->getContainer()->get('errorHandler') = function ($c) {
return function ($request, $response, $exception) {
// 记录错误(建议用 Monolog)
error_log('[ERROR] ' . $exception->getMessage() . ' in ' . $exception->getFile() . ':' . $exception->getLine());
$body = [
'error' => 'Internal Server Error',
'message' => $_ENV['APP_DEBUG'] ? $exception->getMessage() : null,
'code' => $exception->getCode() ?: 500
];
return $response->withStatus(500)->withHeader('Content-Type', 'application/json')
->withJson($body, 500);
};
};
-
withJson()是 Slim 的扩展方法,依赖Response对象已注入 JSON 编码器;若未启用,需手动json_encode()+getBody()->write() - 不要在 handler 里 throw 新异常,会导致无限递归崩溃
-
$_ENV['APP_DEBUG']控制是否暴露敏感信息,生产环境务必关闭
中间件内 try-catch 与全局 errorHandler 的分工
中间件中的 try-catch 应该处理**可预期、可恢复**的业务异常,比如数据库连接失败、第三方 API 超时;而全局 errorHandler 只兜底**不可预期、不可恢复**的致命错误,比如内存溢出、类未找到、语法错误。
常见错误模式:
- 在中间件里 catch 了
Exception却没 re-throw,导致后续中间件和路由不执行 → 改用更细粒度异常类型,如PDOException - 在路由回调里直接
throw new \Exception(),期望被全局 handler 捕获 → 可以,但应优先使用 Slim 提供的Http*Exception类(如HttpBadRequestException),它们自带状态码和标准化结构 - 自定义异常类没继承
\Exception,导致全局 handler 完全忽略 → 所有抛出对象必须是\Throwable子类
日志写入时机和位置容易被忽略
错误日志不能只写在 errorHandler 里 —— 404、401 等 HTTP 异常不会进这里;也不能只写在中间件 try-catch 里 —— 那些根本没走到中间件的致命错误就漏掉了。
真正健壮的做法是三层日志:
- 中间件层:记录业务级异常(如认证失败、参数校验不通过),用
$request->getUri()+$request->getMethod()补充上下文 - 全局
errorHandler:记录未捕获异常,包含$exception->getTraceAsString()(仅开发环境) - PHP 级别:设置
set_exception_handler()作为最后防线,捕获连 Slim 都没机会介入的错误(极少见,但存在)
所有日志路径必须是绝对路径,避免因工作目录变化导致写入失败;推荐用 __DIR__ . '/logs/error.log' 而非相对路径。

















