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

Symfony+Twig模块式资源加载异常:CSS数组为空如何解决?

问题描述

在Symfony项目中拆分Twig模板为模块化组件,每个组件配置独立的CSS/JS文件,通过模板片段中的add_resource函数自动收集资源:

{# ui-button.twig #}
<button class="ui-button">
    {{ icon }} {{ text }}
</button>

{# 声明组件依赖资源 #}
{% do add_resource('ui-button') %}

自定义Twig扩展实现资源收集逻辑,基础模板中循环渲染资源列表:

namespace App\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

class AppExtension extends AbstractExtension {
    protected $scripts = [];
    protected $styles  = [];

    public function getFunctions() {
        return [
            new TwigFunction('get_resources', [$this, 'get_resources']),
            new TwigFunction('add_resouce', [$this, 'add_resouce']),
        ];
    }

    public get_resources ($type) {
        if ($type == 'js')
            return $this->$scripts;

        if ($type == 'css')
            return $this->$styles;
    }

    public add_resouce ($resourse) {
        $css_file = '\/bundle\/' . $resourse . '.css';
        $js_file = '\/bundle\/' . $resourse . '.js';

        if (!is_array($js_file, $this->get_resources('js')))
            $this->$scripts[] = $js_file;

        if (!is_array($css_file, $this->get_resources('css')))
            $this->$styles[] = $css_file;
    }
}

基础模板资源加载代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    {% for resource in get_resources('css') %}
        <link rel="stylesheet" href="{{ asset(resource) }}">
    {% endfor %}
</head>
<body>
    {% block body %}{% endblock body %}

    {% for resource in get_resources('js') %}
        <script src="{{ asset(resource) }}"></script>
    {% endfor %}
</body>
</html>

当前JS资源可正常收集加载,但CSS数组始终为空,已知是Twig模板渲染顺序导致的问题,尝试过embed、block等结构未解决,需实现模块化资源加载方案。


解决方案

第一步:修正Twig扩展代码错误

原扩展存在语法和逻辑错误,先修复这些问题:

namespace App\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

class AppExtension extends AbstractExtension {
    protected $scripts = [];
    protected $styles  = [];

    public function getFunctions() {
        return [
            new TwigFunction('get_resources', [$this, 'getResources']),
            new TwigFunction('add_resource', [$this, 'addResource']),
        ];
    }

    public function getResources($type) {
        if ($type === 'js') {
            return $this->scripts;
        }
        if ($type === 'css') {
            return $this->styles;
        }
        return [];
    }

    public function addResource($resource) {
        $cssFile = '/bundle/' . $resource . '.css';
        $jsFile = '/bundle/' . $resource . '.js';

        // 避免重复添加资源
        if (!in_array($jsFile, $this->getResources('js'))) {
            $this->scripts[] = $jsFile;
        }
        if (!in_array($cssFile, $this->getResources('css'))) {
            $this->styles[] = $cssFile;
        }
    }
}

修复点说明:

  • 修正方法名拼写错误(add_resouce→addResource、get_resources→getResources)
  • 修复变量引用错误($this->$scripts→$this->scripts,去掉多余的$符号)
  • 替换错误的is_array为in_array(正确检查元素是否存在于数组)
  • 移除路径中不必要的转义字符
  • 添加默认空数组返回逻辑,避免无匹配类型时报错

第二步:解决Twig渲染顺序问题

核心问题:Twig是从上到下线性渲染,基础模板的<head>部分先执行,此时add_resource还未在body块中被调用,导致CSS资源未被收集。

方案1:使用Twig延迟渲染标签(推荐,Twig 3.10+)

修改基础模板的CSS渲染部分,用{% deferred %}标签延迟渲染,直到所有模板片段执行完成:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    {% deferred %}
        {% for resource in get_resources('css') %}
            <link rel="stylesheet" href="{{ asset(resource) }}">
        {% endfor %}
    {% enddeferred %}
</head>
<body>
    {% block body %}{% endblock body %}

    {% for resource in get_resources('js') %}
        <script src="{{ asset(resource) }}"></script>
    {% endfor %}
</body>
</html>

{% deferred %}会推迟这段代码的渲染时机,确保所有模板片段的add_resource执行完毕后再渲染CSS资源。

方案2:兼容旧版Twig的请求作用域Runtime

如果使用Twig版本低于3.10,可将资源收集逻辑放到请求作用域的Runtime类中,避免单例扩展的状态污染,同时结合事件监听实现延迟收集:

  1. 创建Runtime类:
namespace App\Twig\Runtime;

use Twig\Extension\RuntimeExtensionInterface;

class AppRuntime implements RuntimeExtensionInterface {
    private $scripts = [];
    private $styles = [];

    public function getResources(string $type): array {
        return match($type) {
            'js' => $this->scripts,
            'css' => $this->styles,
            default => []
        };
    }

    public function addResource(string $resource): void {
        $cssFile = '/bundle/' . $resource . '.css';
        $jsFile = '/bundle/' . $resource . '.js';

        if (!in_array($jsFile, $this->scripts)) {
            $this->scripts[] = $jsFile;
        }
        if (!in_array($cssFile, $this->styles)) {
            $this->styles[] = $cssFile;
        }
    }
}
  1. 修改Twig扩展关联Runtime:
namespace App\Twig;

use App\Twig\Runtime\AppRuntime;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

class AppExtension extends AbstractExtension {
    public function getFunctions() {
        return [
            new TwigFunction('get_resources', [AppRuntime::class, 'getResources']),
            new TwigFunction('add_resource', [AppRuntime::class, 'addResource']),
        ];
    }

    public function getRuntimeClass() {
        return AppRuntime::class;
    }
}
  1. 在services.yaml中配置Runtime为请求作用域:
services:
    App\Twig\Runtime\AppRuntime:
        scope: request
        tags:
            - { name: twig.runtime }

之后在基础模板中使用{% block styles %}配合子模板的扩展,或者通过Symfony的KernelEvents::RESPONSE事件在模板渲染完成后注入CSS资源。

第三步:验证模板调用

确保组件模板通过{% include %}或{% embed %}在基础模板的body块中调用,例如:

{% extends 'base.html.twig' %}

{% block body %}
    {% include 'ui-button.twig' with {icon: '🔘', text: '点击按钮'} %}
{% endblock %}

内容的提问来源于stack exchange,提问作者Кепри де Лармо

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 04:30:48