Symfony默认HTML错误页不适用于API,必须通过kernel.exception事件监听并按Accept头判断是否返回JSON响应,否则会向前端App返回HTML页面;不能用控制器try-catch,否则破坏HTTP语义异常转换、丢失响应头与校验细节。
直接上结论:Symfony 默认的 HTML 错误页对 API 完全不可用,必须拦截
事件,只对
请求做 JSON 化处理,否则会把登录页或 404 页面塞给前端 App。
为什么不能在控制器里 try-catch 每个异常
手动捕获会绕过 Symfony 内核的异常处理链,导致三类问题:
不再自动转成 403,
不再带标准
头
无法复用
统一字段结构(比如始终带
和
)
ValidationException 等校验异常丢失
细节,只能返回笼统的 “Bad Request”
如何注册一个只处理 JSON 请求的异常监听器
核心是检查请求头,不是所有异常都该转 JSON —— 比如管理员访问
触发 403,不该返回 JSON 而该跳登录页。
监听
事件,在服务配置中绑定
在监听器中调用
,用
判断是否应 JSON 化
对
提取
,生成带
和
的嵌套数组
对
或
直接映射为 403/404 状态码,不抛新异常
如何让异常响应和正常
保持字段一致
关键点是复用同一个序列化上下文,避免两套逻辑维护不同结构。不要手拼数组,而是统一走
。
定义一个序列化组,比如
,并在错误数据对象上用
标记字段
监听器中构造错误对象(非数组),例如
调用
输出
这样
类就能控制哪些字段出现、是否驼峰、是否默认值,和实体序列化完全一致
最容易被忽略的是 Accept 头解析逻辑 ——
返回原始字符串,需用
才能正确处理
这类带质量因子的值。漏掉这点,某些 Postman 或 curl 请求就会意外返回 HTML 错误页。
kernel.exceptionAccept: application/jsonAccessDeniedExceptionNotFoundHttpExceptionX-Content-Type-OptionsSerializererror.codeerror.messageviolations/adminkernel.exceptionApiExceptionListener$request->getAcceptableContentTypes()in_array('application/json', ...)ValidationFailedException$e->getViolations()property_pathmessageAccessDeniedExceptionNotFoundHttpExceptionpublic function onKernelException(ExceptionEvent $event): void
{
$request = $event->getRequest();
if (!in_array('application/json', $request->getAcceptableContentTypes(), true)) {
return;
}
$exception = $event->getThrowable();
$response = match (true) {
$exception instanceof ValidationFailedException => $this->buildValidationResponse($exception),
$exception instanceof AccessDeniedException => new JsonResponse(['error' => 'forbidden'], 403),
default => new JsonResponse(['error' => 'internal_error'], 500),
};
$event->setResponse($response);
}$this->json()SerializerInterfaceerror:api#[Groups(['error:api'])]new ApiErrorResponse($code, $message, $details)$this->serializer->serialize($error, 'json', ['groups' => 'error:api'])ApiErrorResponse$request->headers->get('Accept')getAcceptableContentTypes()application/json, text/plain;q=0.5