{% include %}适合嵌入结构固定、无逻辑的UI片段,变量默认全局可见但加only后仅限显式传递;支持三元路径、ignore missing容错及多路径回退;宏则适用于参数化、函数式HTML组件,作用域隔离且需显式传参。

用 {% include %} 嵌入简单 UI 片段最直接
复用页脚、卡片、按钮这类结构固定、无复杂逻辑的模板,{% include %} 是首选。它不改变继承链,只是把另一个文件内容“粘贴”进来。
常见错误是误以为子模板能自动访问所有父模板变量——其实默认可以,但一旦加了 only 就只认你明确传的那些:
-
{% include 'footer.html.twig' %}→ 子模板能读user、title等当前作用域所有变量 -
{% include 'card.html.twig' with {'product': item} only %}→card.html.twig里只能用product,user会报Variable "user" does not exist - 路径支持三元表达式:
{% include type == 'admin' ? 'admin/menu.html.twig' : 'public/menu.html.twig' %}
动态路径和容错必须显式写出来
线上环境模板可能临时缺失,或需按环境/角色加载不同版本,靠“猜路径”会直接报错。
两个关键修饰符不能省:
-
ignore missing:静默跳过不存在的模板,避免Twig\Error\LoaderError{% include 'promo-banner.html.twig' ignore missing %} - 多路径回退:Twig 按顺序检查,用第一个存在的
{% include ['@theme/v2/header.html.twig', 'header/default.html.twig'] %}
注意:路径数组里不能混用带命名空间(@theme)和相对路径,否则 Twig 可能找不到第二个候选项。
别在 include 模板里写业务判断
片段越薄越好。如果 _search_form.html.twig 里开始判断 app.environment == 'prod' 或调用 service('stats'),说明职责已经溢出。
正确做法是把判断提到控制器或 Twig 全局服务里,只传布尔值或预处理数据:
- ❌ 错误:在
_search_form.html.twig中写{% if app.user.hasRole('ADMIN') %}... - ✅ 正确:控制器传
'show_advanced' => $this->isGranted('ROLE_ADMIN'),模板只做{% if show_advanced %}... - 性能提示:大量
{% include %}嵌套(如 include → include → include)会拖慢渲染,开启 Twig 缓存后影响减弱,但逻辑分层混乱的问题仍在
宏({% macro %})适合带参数、可复用的 HTML 组件
当片段需要像函数一样接收多个参数、返回结构化 HTML(比如带图标、尺寸、禁用态的按钮),优先用宏而不是 include。
宏定义通常放在单独文件(如 macros.html.twig),再用 {% import %} 引入:
{% macro button(text, type='default', disabled=false) %}
<button class="btn btn-{{ type }}" {% if disabled %}disabled{% endif %}>
{{ text }}
</button>
{% endmacro %}
使用时:
{% import 'macros.html.twig' as html %}{{ html.button('Save', 'primary', true) }}
宏不共享变量作用域,天然隔离;但也不能访问 app 或其他全局对象,除非显式传入——这点和 include 的变量传递机制完全不同。
真正容易被忽略的是:宏一旦定义,就变成纯函数式调用,没法像 include 那样复用父模板的整个上下文。选哪个,取决于你到底要“嵌入一段现成 HTML”,还是“调用一个可配置的 UI 构建器”。


















