Symfony5.4中如何通过Twig渲染Ajax返回的JSON数据
推荐方案:后端返回渲染好的HTML片段(最适配Twig开发习惯,改造成本最低)
这是Symfony生态下处理这类分页加载场景的标准做法,完全规避前端拼HTML的问题,100%复用你现有的Twig能力:
- 把列表项的Twig代码抽成独立的公共片段
把你原来写在列表页里的单个figure卡片的Twig代码,单独存为模板片段,比如templates/figure/_figure_cards.html.twig,原来写的path()、asset()、Voter权限判断逻辑全部保留,不需要做任何修改:
初始页面加载时,直接循环渲染第一页数据include这个片段即可,和你之前的开发逻辑完全一致。{% for figure in figures %} <div class="col"> <div class="card h-100"> <a href="{{ path('app_figure_show', {id: figure.id, slug: figure.slug}) }}"> <img class="card-img-top" src="{{ asset('uploads/images/' ~ figure.firstFigureImage) }}"> </a> <div class="card-body"> <h5 class="card-title"> <a href="{{ path('app_figure_show', {id: figure.id, slug: figure.slug}) }}"> {{ figure.name }} </a> </h5> {# 原有的权限判断直接复用 #} {% if is_granted('FIGURE_EDIT', figure) %} <a href="{{ path('app_figure_edit', {'id': figure.id, 'slug': figure.slug}) }}" class="btn btn-outline-primary btn-sm" title="Modifier cette figure"> <i class="bi bi-pencil"></i> </a> {% endif %} </div> </div> </div> {% endfor %} - 改造Ajax接口,返回HTML而非裸JSON
你的加载更多接口拿到分页查询出的Figure实体集合后,不需要序列化成JSON,直接渲染上面的模板片段,把生成好的HTML字符串作为响应返回:// 加载更多接口控制器示例 public function loadMore( int $page, FigureRepository $figureRepository ): Response { $perPage = 12; // 注意初始加载已经渲染了第一页,Ajax从第二页开始取数据 $offset = ($page - 1) * $perPage; $figures = $figureRepository->findBy( ['status' => 'status.accepted'], ['createdAt' => 'DESC'], $perPage, $offset ); // 直接返回渲染好的卡片HTML return $this->render('figure/_figure_cards.html.twig', [ 'figures' => $figures ]); } - 简化前端逻辑,直接插入返回的HTML
前端不需要做任何HTML拼接,拿到接口返回的文本内容直接插入列表容器即可:let currentPage = 2; // 初始页是1,加载更多从第2页开始 const gridContainer = document.getElementById('figures-grid'); const loadMoreBtn = document.getElementById('load-more-btn'); loadMoreBtn.addEventListener('click', async () => { try { const res = await fetch(`/figure/load-more?page=${currentPage}`); if (!res.ok) throw new Error('加载失败'); const html = await res.text(); // 没有更多数据时隐藏按钮 if (!html.trim()) { loadMoreBtn.style.display = 'none'; return; } gridContainer.insertAdjacentHTML('beforeend', html); currentPage++; } catch (err) { console.error(err); } });
这个方案的优势非常明显:
- 没有重复代码,所有模板逻辑、权限判断、路由生成统一在Twig里维护,后续改卡片样式只需要改一个模板文件
- 没有前端硬编码路径的问题,不会出现路由规则改了之后要同步改JS里拼接字符串的情况
- 对JS能力要求极低,不需要引入额外的前端模板库
- 权限判断逻辑始终在服务端执行,不会出现前端篡改JS越权显示操作按钮的问题
备选方案:后端预生成JSON所需的动态字段
如果你因为多端适配等需求,必须让接口返回JSON,那就不要让前端承担路由生成、权限判断的逻辑,在后端序列化数据时就把前端需要的字段全部计算好再返回:
$list = []; foreach ($figures as $figure) { $list[] = [ 'id' => $figure->getId(), 'name' => $figure->getName(), 'showUrl' => $this->generateUrl('app_figure_show', ['id' => $figure->getId(), 'slug' => $figure->getSlug()]), 'imageUrl' => $this->generateUrl('app_asset', ['path' => 'uploads/images/' . $figure->getFirstFigureImage()]), 'canEdit' => $this->isGranted('FIGURE_EDIT', $figure), 'editUrl' => $this->isGranted('FIGURE_EDIT', $figure) ? $this->generateUrl('app_figure_edit', ['id' => $figure->getId(), 'slug' => $figure->getSlug()]) : null, // 其余需要的字段 ]; } return $this->json($list);
前端拿到JSON后只需要把字段填到对应位置即可,不需要自己拼接路径、做权限判断。但这个方案还是需要在前端维护一套HTML模板,维护成本比第一个方案高。
不推荐的做法
不要尝试在浏览器端直接运行Twig代码:Twig是服务端模板引擎,页面发送到浏览器时Twig已经完成渲染执行,JS运行阶段Twig环境根本不存在,引入Twig.js这类前端实现不仅徒增体积,还会把权限判断等敏感逻辑暴露到前端,有安全隐患。
内容的提问来源于stack exchange,提问作者Loraga
相关产品推荐
相关产品推荐

