Slim 4 默认错误处理器不返回 JSON,因设计上将错误呈现交由开发者决定;需继承ErrorResponseGenerator重写generateResponse并替换容器中errorHandler服务,同时单独配置notFoundHandler以统一JSON响应。

Slim 4 默认错误处理器不返回 JSON?
默认情况下,Slim 4 的 ErrorHandler 在开发环境会渲染 HTML 错误页,生产环境才吐纯文本;它压根不会自动设 Content-Type: application/json,也不会按你想要的结构包装错误字段。这不是 bug,是设计使然——Slim 把「如何呈现错误」留给你自己决定。
常见错误现象:500 Internal Server Error 响应体是 HTML(本地)或空/乱码(线上),前端 fetch().json() 直接抛 SyntaxError。
- 必须显式替换默认的
ErrorHandler实例,不能只改配置 - 替换时机必须在容器注册路由前,否则中间件链已固化
- 别在
errorHandler配置项里试图 return 数组——Slim 不认这个格式
怎么写一个返回标准 JSON 错误响应的 ErrorHandler?
核心是继承 ErrorResponseGenerator 并重写 generateResponse() 方法,再用自定义类替换容器里的 errorHandler 服务。不要试图 patch 默认类,Slim 4 的错误处理器是不可变对象。
示例关键片段:
立即学习“PHP免费学习笔记(深入)”;
// src/Exception/JsonErrorResponseGenerator.php
class JsonErrorResponseGenerator extends ErrorResponseGenerator
{
protected function generateResponse(ServerRequestInterface $request, Throwable $exception, bool $displayErrorDetails): ResponseInterface
{
$response = new Response();
$response->getBody()->write(json_encode([
'success' => false,
'message' => $displayErrorDetails ? $exception->getMessage() : 'Internal server error',
'code' => $exception->getCode() ?: 500,
'timestamp' => date('c'),
], JSON_UNESCAPED_UNICODE));
return $response->withStatus($this->determineStatusCode($exception))
->withHeader('Content-Type', 'application/json; charset=utf-8');
}
}
-
$displayErrorDetails控制是否暴露敏感信息,靠Settings的displayErrorDetails键驱动 -
$this->determineStatusCode()是父类方法,会把HttpException映射为对应 HTTP 状态码 - 别漏掉
JSON_UNESCAPED_UNICODE,否则中文变 \uXXXX
如何让不同异常类型返回不同结构?
比如 NotFoundException 返回 { "success": false, "message": "Not found" },而业务异常 ValidationException 返回带 errors 字段的结构。靠在 generateResponse() 里做 instanceof 判断最直接。
实操建议:
- 用
if ($exception instanceof HttpException)拦住 4xx/5xx 标准异常 - 对自定义异常如
ValidationException,加->withStatus(422)并写入'errors' => $exception->getErrors() - 避免在错误处理器里 throw 新异常,会导致递归调用
generateResponse() - 不要在判断分支里调用
$request->getAttribute()—— 此时请求属性可能已被清理
为什么 404 路由未匹配时没走自定义 ErrorHandler?
因为 Slim 把「未找到路由」视为正常流程分支,走的是 NotFoundHandler,不是 ErrorHandler。这是两个独立服务,必须分别替换。
解决办法:注册自定义 notFoundHandler,且逻辑要和 errorHandler 保持一致(比如都返回 JSON、都设相同 header):
$app->addRoutingMiddleware();
$app->addErrorMiddleware(true, true, true); // 这行只影响 ErrorHandler,不影响 NotFoundHandler
// 单独替换 NotFoundHandler
$container->set('notFoundHandler', function (ContainerInterface $container) {
return function (ServerRequestInterface $request, ResponseInterface $response) {
$data = ['success' => false, 'message' => 'Not found', 'code' => 404];
return $response->withStatus(404)
->withHeader('Content-Type', 'application/json; charset=utf-8')
->withBody(new SwooleStream(json_encode($data, JSON_UNESCAPED_UNICODE)));
};
});
- 注意
SwooleStream是示例,实际用new Stream(fopen('php://temp', 'r+'))或Psr\Http\Message\StreamInterface实现 - 别漏掉
$app->addRoutingMiddleware(),否则notFoundHandler不生效 - 如果用了
addRoutingMiddleware()但没看到 404 响应变化,大概率是路由注册顺序错了——必须在addRoutingMiddleware()之后注册路由
notFoundHandler,运行时抛异常 → errorHandler。但这两个 handler 默认互不感知,各自独立配置,最容易忽略的就是 404 场景完全绕过你精心写的 JSON 错误处理器。










