Twig 内部结构
Twig 非常易于扩展,您可以对其进行 hack。请记住,在 hack 核心之前,您应该尝试创建一个扩展,因为大多数特性和增强功能都可以通过扩展来处理。本章对于想要了解 Twig 底层工作原理的人也很有用。
Twig 是如何工作的?
Twig 模板的渲染可以概括为四个关键步骤
加载模板:如果模板已编译,则加载它并转到评估步骤,否则
- 首先,词法分析器将模板源代码标记化为小块,以便于处理;
- 然后,语法分析器将 token 流转换为有意义的节点树(抽象语法树);
- 最后,编译器将 AST 转换为 PHP 代码。
- 评估模板:这意味着调用已编译模板的
display()
方法并传递上下文。
词法分析器
词法分析器将模板源代码标记化为 token 流(每个 token 都是 \Twig\Token
的实例,而流是 \Twig\TokenStream
的实例)。默认词法分析器识别 15 种不同的 token 类型
\Twig\Token::BLOCK_START_TYPE
,\Twig\Token::BLOCK_END_TYPE
:块的分隔符 ({% %}
)\Twig\Token::VAR_START_TYPE
,\Twig\Token::VAR_END_TYPE
:变量的分隔符 ({{ }}
)\Twig\Token::TEXT_TYPE
:表达式之外的文本;\Twig\Token::NAME_TYPE
:表达式中的名称;\Twig\Token::NUMBER_TYPE
:表达式中的数字;\Twig\Token::STRING_TYPE
:表达式中的字符串;\Twig\Token::OPERATOR_TYPE
:运算符;\Twig\Token::ARROW_TYPE
:箭头函数运算符 (=>
);\Twig\Token::SPREAD_TYPE
:展开运算符 (...
);\Twig\Token::PUNCTUATION_TYPE
:标点符号;\Twig\Token::INTERPOLATION_START_TYPE
,\Twig\Token::INTERPOLATION_END_TYPE
:字符串插值的分隔符;\Twig\Token::EOF_TYPE
:模板结束。
您可以通过调用环境的 tokenize()
方法手动将源代码转换为 token 流
1
$stream = $twig->tokenize(new \Twig\Source($source, $identifier));
由于该流具有 __toString()
方法,您可以通过回显对象来获得它的文本表示
1
echo $stream."\n";
这是 Hello {{ name }}
模板的输出
1 2 3 4 5
TEXT_TYPE(Hello )
VAR_START_TYPE()
NAME_TYPE(name)
VAR_END_TYPE()
EOF_TYPE()
注意
可以通过调用 setLexer()
方法来更改默认词法分析器 (\Twig\Lexer
)
1
$twig->setLexer($lexer);
语法分析器
语法分析器将 token 流转换为 AST(抽象语法树),或节点树(\Twig\Node\ModuleNode
的实例)。核心扩展定义了基本节点,例如:for
、if
,... 以及表达式节点。
您可以通过调用环境的 parse()
方法手动将 token 流转换为节点树
1
$nodes = $twig->parse($stream);
回显节点对象会为您提供树的良好表示
1
echo $nodes."\n";
这是 Hello {{ name }}
模板的输出
1 2 3 4 5 6
\Twig\Node\ModuleNode(
\Twig\Node\TextNode(Hello )
\Twig\Node\PrintNode(
\Twig\Node\Expression\NameExpression(name)
)
)
注意
可以通过调用 setParser()
方法来更改默认语法分析器 (\Twig\TokenParser\AbstractTokenParser
)
1
$twig->setParser($parser);
编译器
最后一步由编译器完成。它以节点树作为输入,并生成可用于模板运行时执行的 PHP 代码。
您可以使用环境的 compile()
方法手动将节点树编译为 PHP 代码
1
$php = $twig->compile($nodes);
为 Hello {{ name }}
模板生成的模板内容如下(实际输出可能因您使用的 Twig 版本而异)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/* Hello {{ name }} */
class __TwigTemplate_1121b6f109fe93ebe8c6e22e3712bceb extends Template
{
protected function doDisplay(array $context, array $blocks = []): iterable
{
$macros = $this->macros;
// line 1
yield "Hello ";
// line 2
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["name"]) || array_key_exists("name", $context) ? $context["name"] : (function () { throw new RuntimeError('Variable "name" does not exist.', 2, $this->source); })()), "html", null, true);
return; yield '';
}
// some more code
}
注意
可以通过调用 setCompiler()
方法来更改默认编译器 (\Twig\Compiler
)
1
$twig->setCompiler($compiler);