用 JsonResponse 返回 JSON 响应,自动设置 Content-Type、UTF-8 编码和空值安全;Doctrine 实体需转数组或 DTO 避免循环引用;API 异常通过 kernel.exception 监听器统一转 JSON;CORS 推荐使用 nelmio/cors-bundle 精确配置。

怎么让 Symfony 控制器返回标准 JSON 响应
直接用 JsonResponse,别手写 json_encode() + Response。它自动设好 Content-Type: application/json,还处理 UTF-8 编码和空值安全(比如 null 会转成 null 而不是报错)。
常见错误:用 Response 包裹 json_encode(),结果没设 header,前端收不到 JSON;或者手动 echo,触发了 headers already sent。
- 用
use Symfony\Component\HttpFoundation\JsonResponse; - 返回时写
return new JsonResponse($data);,$data可以是数组、对象(需实现JsonSerializable)、标量 - 如果要加状态码,传第二个参数:
new JsonResponse($data, 400) - 避免在
$data里塞资源句柄、闭包、未序列化的 Doctrine 实体——会抛SerializationException
怎么序列化 Doctrine 实体为 JSON
别直接把 Entity 对象扔进 JsonResponse。Doctrine 实体带循环引用(比如 User → Post → User),默认会爆栈或报错。
最轻量的解法是用 toArray() 方法(如果你在实体里写了)或 DTO 手动投影。不推荐全局开 symfony/serializer,除非你真需要深度嵌套、组(groups)、缓存等能力。
- 简单场景:在控制器里调
$user->toArray(),确保方法只返回基础字段 - 避免用
get_object_vars($entity)——它会暴露私有属性和代理对象内部字段 - 如果用了
Serializer,必须配ObjectNormalizer+JsonEncoder,且禁用CircularReferenceHandler或设最大深度,否则开发环境不报错、生产环境偶发 500 - 注意 N+1:
$posts = $user->getPosts()是 Proxy,但JsonResponse触发加载时才查库——可能一次响应拉出几十个查询
怎么统一处理 API 错误并返回 JSON
Symfony 默认的异常页面是 HTML,API 必须拦截并转成 JSON。靠内核事件监听 kernel.exception 最稳,别在每个控制器里 try-catch。
关键点:只对 Request::isXmlHttpRequest() 或 Accept: application/json 的请求做 JSON 转换,否则会破坏普通页面跳转。
- 写一个
ApiExceptionListener,监听kernel.exception - 检查
$request->headers->get('Accept')是否含application/json,不是就跳过 - 对
ValidationException、AccessDeniedException等返回对应状态码和结构化消息,比如['error' => 'validation_failed', 'details' => [...]] - 别吞掉原始异常信息——开发环境要能看堆栈,用
%kernel.debug%配置开关是否暴露error.message
怎么让 API 支持 CORS(跨域)
前端发请求被拦,八成是 CORS。Symfony 官方推荐用 nelmio/cors-bundle,自己手设 header 极易漏掉预检(OPTIONS)或 Vary 头。
配置里最容易错的是 allow_origin:写死 * 不行(带 credentials 时禁止),本地开发用 ['http://localhost:3000'],生产按域名列表配。
- 装包:
composer require nelmio/cors-bundle - 配
nelmio_cors:下的defaults:,重点设allow_origin、allow_credentials、allow_headers - 如果 API 路由在
/api/下,加paths: '^/api/.'精确匹配,避免影响登录页等非 API 路由 - 测试时用
curl -I -H "Origin: http://localhost:3000" http://your.app/api/users看响应头有没有Access-Control-Allow-Origin
复杂点在于,当路由带 locale 前缀(如 /en/api/users)或用了子域名,paths 正则和 allow_origin 必须同步更新,否则 OPTIONS 请求 404 或 header 不生效——这个细节上线前常被漏测。










