You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Symfony5.4中如何通过Twig渲染Ajax返回的JSON数据

推荐方案:后端返回渲染好的HTML片段(最适配Twig开发习惯,改造成本最低)

这是Symfony生态下处理这类分页加载场景的标准做法,完全规避前端拼HTML的问题,100%复用你现有的Twig能力:

  1. 把列表项的Twig代码抽成独立的公共片段
    把你原来写在列表页里的单个figure卡片的Twig代码,单独存为模板片段,比如templates/figure/_figure_cards.html.twig,原来写的path()、asset()、Voter权限判断逻辑全部保留,不需要做任何修改:
    {% 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 %}
    
    初始页面加载时,直接循环渲染第一页数据include这个片段即可,和你之前的开发逻辑完全一致。
  2. 改造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
        ]);
    }
    
  3. 简化前端逻辑,直接插入返回的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 13:03:29