symfony 7 彻底移除子请求支持,render() 默认使用 inlinefragmentrenderer 同步执行控制器 action,不重建容器、不触发事件循环、不启动 session;render_esi() 则通过 esi 由反向代理发起独立 http 请求,支持片段级缓存。

Symfony 7 中已彻底移除子请求(kernel.forward)支持,render() 模板函数默认走异步片段渲染,不是子请求。 这意味着你不能再依赖子请求实现“嵌套控制器执行”,所有片段逻辑必须适配无状态、轻量、可缓存的设计。
为什么 render() 不再触发子请求
Symfony 7 删除了 HttpKernelInterface::handle() 对 SubRequest 的处理路径,render() 函数底层调用的是 FragmentRendererInterface,默认使用 InlineFragmentRenderer —— 它直接实例化控制器并调用对应 action,不经过完整 HTTP 生命周期(无新 Request 对象、无事件监听器重放、无会话写入锁)。
- 子请求曾导致严重性能问题:每个子请求都重建服务容器作用域、重复解析路由、触发完整事件循环
- PHP-FPM 场景下子请求通过
fopen('http://localhost:8000/...')回环调用,极易引发端口占满或超时 -
render()现在默认跳过 session 启动,避免并发写入冲突;如需 session 数据,必须显式调用$request->getSession()->start()
render() 与 render_esi() 的行为差异
两者都输出 HTML 片段,但触发时机和缓存策略完全不同:
-
{{ render(url('cart_summary')) }}→ 走InlineFragmentRenderer,同步执行,无网络开销,但无法单独缓存 -
{{ render_esi(url('cart_summary')) }}→ 触发 ESI(Edge Side Includes)标签,由反向代理(如 Varnish、Nginx)在响应组装阶段发起独立 HTTP 请求,支持 per-fragment 缓存头(Cache-Control: public, s-maxage=300) - ESI 模式下,目标 controller 必须返回带正确缓存头的
Response,且不能依赖 session 写操作(ESI 请求是无状态的) - 本地开发没配反代时,
render_esi()会自动 fallback 到 inline 渲染,但生产环境必须验证 Nginx/Varnish 是否启用 ESI 解析
如何安全地复用控制器逻辑做片段
别再让 controller 同时承担主请求和片段职责。推荐拆分:保留 controller 做路由入口,把业务逻辑下沉到 service 层,片段渲染直接调用 service。
- 定义一个无副作用的 service:
AppServiceCartSummaryProvider,只读取购物车数据,不修改 session 或 DB - 在 controller 中注入它:
public function summary(CartSummaryProvider $provider),返回Response - 在模板中:
{{ render(controller('App\Controller\CartController::summary')) }}—— 注意命名空间双反斜杠 - 若该片段需高频更新,给 controller 方法加注解:
@Cache(smaxage="300"),配合 ESI 使用 - 禁止在片段 controller 中调用
$this->addFlash()或修改$request->getSession(),它们不会反映到主请求上下文
调试片段渲染失败的常见线索
片段空白或报错,往往不是逻辑问题,而是上下文缺失:
- 错误信息含
"Call to a member function getSession() on null"→ controller 里用了$this->getUser()但未启用 security bundle 或 token storage 为空 - 片段显示旧数据 → 检查是否误用了
render()(无缓存控制)而非render_esi(),或反代未配置 ESI 解析指令 - ESI 请求返回 404 → 主应用路由匹配正常,但 ESI 请求走的是独立 HTTP 流程,确保
url('cart_summary')生成的路径可被外部访问(如非localhost限制) - 内存爆涨 → 避免在片段逻辑中加载大型 Doctrine 关联集合,改用 DQL 投影或 DTO 查询精简字段
最易被忽略的一点:片段渲染不继承主请求的 _locale 和 _format,必须显式传参或在 route 定义中固定 defaults={"_locale": "en"},否则多语言站点会出现翻译错乱。











