twig 模板继承要求 {% extends %} 必须是子模板第一字符,父模板需有同名 {% block %},子模板中用 {{ parent() }} 复用父内容,路径基于 templates/ 目录,block 结构编译期固化。

直接写 {% extends "base.html.twig" %} 就能继承,但必须是模板第一行,且父模板里得有 {% block %} 才能被覆盖 —— 这不是语法糖,是 Twig 渲染流程强制要求的结构。
extends 必须放在模板最开头
Twig 解析器在读取模板时,一看到非空行就停止查找 {% extends %}。哪怕前面多一个空格、换行或注释,都会报错 Unexpected token "end of template" 或 Template inheritance disabled。
-
{% extends "base.html.twig" %}必须是子模板的**第一个字符**,前面不能有任何内容(包括空白符、{# comment #}、<!-- html comment -->) - 常见错误:IDE 自动插入 UTF-8 BOM、保存时带尾部空行、用 PHP 生成模板时 echo 前有空格
- 验证方法:用
hexdump -C your_template.html.twig | head -n 1看是否以25 7b 20 65 78 74 65 6e 64 73(即%{ extends的 ASCII 十六进制)开头
父模板的 block 名必须严格匹配
子模板里写的 {% block sidebar %},父模板中对应区块必须叫 sidebar,大小写、下划线、连字符都不能差 —— Twig 不做 normalize,也不报“block not found”,而是静默忽略,直接渲染父模板里的默认内容。
- 父模板中未定义的 block,子模板里写
{% block xxx %}会完全失效(不报错,也不渲染) - 子模板里漏写
{% endblock %}会导致后续所有标签解析失败,错误信息常为Unexpected end of template - 命名建议:全小写 + 下划线(如
main_content),避免用content这类可能和 Twig 内置变量冲突的名称
用 {{ parent() }} 复用父块内容而不是重复写
想在子模板里“追加”而非“替换”父块内容,必须显式调用 {{ parent() }}。它不是可选语法糖,而是唯一能拿到父级 {% block %} 原始内容的方式。
- 错误写法:
{% block head %}<link rel="stylesheet" href="extra.css">{% endblock %}→ 完全覆盖父模板的head,丢失title和基础 CSS - 正确写法:
{% block head %}{{ parent() }}<link rel="stylesheet" href="extra.css">{% endblock %} -
{{ parent() }}只能在{% block %}内部使用;在 block 外调用会返回空字符串,也不报错 - 如果父 block 是空的(
{% block footer %}{% endblock %}),{{ parent() }}也返回空,不会崩溃
Symfony 7 默认路径和自动发现规则
Symfony 7 的 TwigBundle 默认只扫描 templates/ 目录(不是 src/Resources/views/),且不支持相对路径别名 —— {% extends "base.html.twig" %} 指的是 templates/base.html.twig,不是 templates/layout/base.html.twig。
- 要继承子目录下的模板,必须写完整路径:
{% extends "layout/base.html.twig" %}(对应templates/layout/base.html.twig) - 模板路径不支持 glob 或通配符,也不能用变量:
{% extends template_name %}在 Symfony 7 中默认禁用(需手动配置twig.strict_variables: false并启用allow_relative_paths) - 开发时可用
php bin/console twig:debug查看 Twig 能识别的所有模板路径和继承关系
真正容易被忽略的是:block 的作用域是静态的,它不随数据上下文变化 —— 你不能在 {% for item in items %} 循环里动态定义新 block,也不能用 {% if condition %}{% block x %}{% endif %} 来条件化声明 block。block 结构在模板编译阶段就固化了,运行时只决定填什么内容,不决定存不存在。











