延长枝
树枝可以以多种方式延伸;您可以添加额外的标记、筛选器、测试、操作符、全局变量和函数。您甚至可以使用节点访问者扩展解析器本身。
请注意
本章的第一部分描述了如何扩展Twig。如果您希望在不同的项目中重用更改,或者希望与他人共享更改,那么您应该按照以下部分所述创建一个扩展。
谨慎
当扩展Twig而不创建扩展时,Twig将不能在PHP代码更新时重新编译模板。要实时查看更改,要么禁用模板缓存,要么将代码打包到扩展中(参见本章的下一节)。
在扩展Twig之前,您必须了解所有不同可能的扩展点之间的差异,以及何时使用它们。
首先,请记住Twig有两个主要的语言结构:
{{}}
:用于打印表达式求值的结果;{% %}
:用于执行语句。
为了理解为什么Twig公开了这么多扩展点,让我们看看如何实现一个扩展点Lorem ipsum生成器(它需要知道要生成的单词数)。
你可以使用lipsum
标签:
1
{%lipsum40%}
这是可行的,但是使用标签lipsum
不是个好主意,至少有三个主要原因:
lipsum
不是一种语言结构;- 标签输出一些东西;
标签不灵活,因为你不能在表达式中使用它:
1
{{'some text' ~ {% lipsum 40 %} ~ 'some more text'}}
事实上,您很少需要创建标记;这是个好消息,因为标记是最复杂的欧宝平台是合法的吗扩展点。
现在,我们用alipsum
过滤器:
1
{{40|lipsum}}
同样,这是有效的。但是过滤器应该将传递的值转换为其他值。在这里,我们使用值表示要生成的字数(因此,40
是过滤器的参数,而不是我们要转换的值)。
接下来,让我们使用alipsum
函数:
1
{{lipsum(40)}}
开始吧。对于这个特定的示例,函数的创建就是要使用的扩展点。你可以在任何一个表达被接受的地方使用它:
1 2 3
{{'some text' ~ lipsum(40) ~ 'some more text'}}{%集唇膏=唇膏(40)%}
最后,您还可以使用a全球对象,使用一个方法生成lorem ipsum文本:
1
{{text.lipsum(40)}}
根据经验,对于常用的特性使用函数,对于其他所有特性使用全局对象。
当你想要扩展Twig时,请记住以下几点:
什么? | 实现困难吗? | 多长时间? | 什么时候? |
---|---|---|---|
宏 | 简单的 | 频繁的 | 内容生成 |
全球 | 简单的 | 频繁的 | Helper对象 |
函数 | 简单的 | 频繁的 | 内容生成 |
过滤器 | 简单的 | 频繁的 | 值转换 |
标签 | 复杂的 | 罕见的 | DSL语言构造 |
测验 | 简单的 | 罕见的 | 布尔决定 |
操作符 | 简单的 | 罕见的 | 值转换 |
全局变量
一个全局变量就像任何其他模板变量一样,除了它在所有模板和宏中可用:
1 2
$嫩枝=新\树枝\环境($加载程序);$嫩枝->addGlobal (“文本”,新Text ());
然后可以使用文本
变量在模板中的任意位置:
1
{{text.lipsum(40)}}
过滤器
创建过滤器包括将名称与PHP可调用对象关联:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16
//匿名函数$过滤器=新\树枝\ TwigFilter (“rot13”,函数($字符串){返回函数$字符串);});//或一个简单的PHP函数$过滤器=新\树枝\ TwigFilter (“rot13”,“函数”);//或类静态方法$过滤器=新\树枝\ TwigFilter (“rot13”, (“SomeClass”,“rot13Filter”]);$过滤器=新\树枝\ TwigFilter (“rot13”,“SomeClass:: rot13Filter”);//或类方法$过滤器=新\树枝\ TwigFilter (“rot13”, ($这,“rot13Filter”]);//下面的一个需要一个运行时实现(参见下面的更多信息)$过滤器=新\树枝\ TwigFilter (“rot13”, (“SomeClass”,“rot13Filter”]);
的第一个参数传递给\树枝\ TwigFilter
构造函数是您将在模板中使用的过滤器的名称,第二个是与它关联的PHP可调用对象。
然后,将过滤器添加到Twig环境中:
1 2
$嫩枝=新\树枝\环境($加载程序);$嫩枝->addFilter ($过滤器);
下面是如何在模板中使用它:
1 2 3
{{'Twig'|rot13}}{#输出Gjvt #}
当被Twig调用时,PHP可调用对象接收过滤器的左侧(在管道之前)|
)作为第一个参数,并将额外的参数传递给过滤器(在括号内()
)作为额外参数。
例如,以下代码:
1 2
{{|“树枝”较低的}}{{现在|日期(d / m / Y)}}
编译成如下内容:
1 2
<?php回声函数“树枝”)? ><?php回声twig_date_format_filter ($现在,' d / m / Y ')? >
的\树枝\ TwigFilter
类的最后一个参数为一个选项数组:
1
$过滤器=新\树枝\ TwigFilter (“rot13”,“函数”,$选项);
Environment-aware过滤器
如果要访问筛选器中的当前环境实例,请设置needs_environment
选项真正的
;Twig将当前环境作为过滤器调用的第一个参数传递:
1 2 3 4 5 6
$过滤器=新\树枝\ TwigFilter (“rot13”,函数(\树枝\环境$env,$字符串){//获取当前字符集$字符集=$env->getCharset ();返回函数$字符串);},“needs_environment”= >真正的]);
环境敏感的过滤器
如果要访问筛选器中的当前上下文,请设置needs_context
选项真正的
;Twig将当前上下文作为第一个参数传递给过滤器调用(如果needs_environment
也设置为真正的
):
1 2 3 4 5 6 7
$过滤器=新\树枝\ TwigFilter (“rot13”,函数($上下文,$字符串){/ /……},“needs_context”= >真正的]);$过滤器=新\树枝\ TwigFilter (“rot13”,函数(\树枝\环境$env,$上下文,$字符串){/ /……},“needs_context”= >真正的,“needs_environment”= >真正的]);
自动转义
如果启用了自动转义,则可以在打印之前转义过滤器的输出。如果您的过滤器充当逃逸器(或显式输出HTML或JavaScript代码),您将希望打印原始输出。在这种情况下,设置is_safe
选择:
1
$过滤器=新\树枝\ TwigFilter (“nl2br”,“nl2br”, (“is_safe”= > [“html”]]);
有些过滤器可能需要处理已经转义或安全的输入,例如向原本不安全的输出添加(安全的)HTML标记。在这种情况下,设置pre_escape
选项,在输入数据通过过滤器运行之前转义输入数据:
1
$过滤器=新\树枝\ TwigFilter (“somefilter”,“somefilter”, (“pre_escape”= >“html”,“is_safe”= > [“html”]]);
可变的过滤器
当筛选器应接受任意数量的参数时,设置is_variadic
选项真正的
;Twig将额外的参数作为最后一个参数作为数组传递给过滤器调用:
1 2 3
$过滤器=新\树枝\ TwigFilter (“缩略图”,函数($文件数组,$选项= []){/ /……},“is_variadic”= >真正的]);
请注意命名参数传递给可变参数过滤器不能检查有效性,因为它们将自动结束在选项数组中。
动态过滤器
包含特殊属性的筛选器名称*
字符是一个动态过滤器*
Part将匹配任何字符串:
1 2 3
$过滤器=新\树枝\ TwigFilter (‘* _path‘,函数($的名字,$参数){/ /……});
下面的过滤器与上面定义的动态过滤器匹配:
product_path
category_path
一个动态过滤器可以定义多个动态部分:
1 2 3
$过滤器=新\树枝\ TwigFilter (“* _path_ *”,函数($的名字,$后缀,$参数){/ /……});
过滤器在常规过滤器参数之前,但在环境和上下文之后接收所有动态部分值。例如,调用“foo”| a_path_b ()
将导致将以下参数传递给过滤器:('a', 'b', 'foo')
.
功能
函数的定义方式与筛选器完全相同,但是需要创建\树枝\ TwigFunction
:
1 2 3 4 5
$嫩枝=新\树枝\环境($加载程序);$函数=新\树枝\ TwigFunction (“function_name”,函数(){/ /……});$嫩枝->addFunction ($函数);
函数支持与筛选器相同的特性,除了pre_escape
而且preserves_safety
选项。
测试
的定义方式与筛选器和函数完全相同,但需要创建的实例\树枝\ TwigTest
:
1 2 3 4 5
$嫩枝=新\树枝\环境($加载程序);$测验=新\树枝\ TwigTest (“test_name”,函数(){/ /……});$嫩枝->addTest ($测验);
测试允许您创建用于计算布尔条件的自定义应用程序特定逻辑。作为一个简单的例子,让我们创建一个Twig测试,检查对象是否为“红色”:
1 2 3 4 5 6 7 8 9 10 11
$嫩枝=新\树枝\环境($加载程序);$测验=新\树枝\ TwigTest (“红色”,函数($价值){如果(收取($价值->颜色)& &$价值->颜色= =“红色”) {返回真正的;}如果(收取($价值->油漆)& &$价值->油漆= =“红色”) {返回真正的;}返回假;});$嫩枝->addTest ($测验);
测试函数必须总是返回真正的
/假
.
创建测试时,可以使用node_class
选项提供自定义测试编译。如果您的测试可以被编译成PHP原语,这是很有用的。这被许多内置到Twig的测试所使用:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
名称空间应用程序;使用嫩枝\环境;使用嫩枝\节点\表达式\TestExpression;使用嫩枝\TwigTest;$嫩枝=新环境($加载程序);$测验=新TwigTest (“奇怪”,零, (“node_class”= > OddTestExpression::类);$嫩枝->addTest ($测验);类OddTestExpression扩展TestExpression{公共函数编译(\树枝\编译器$编译器){$编译器->生(“(”)->subcompile ($这->getNode (“节点”))->生(' % 2 != 0')->生(“)”);}}
上面的示例展示了如何创建使用节点类的测试。节点类可以访问一个子节点节点
.这个子节点包含正在测试的值。当奇怪的
过滤器用于如下代码:
1
{%如果My_value为奇数%}
的节点
子节点将包含的表达式my_value
.基于节点的测试还可以访问参数
节点。这个节点将包含提供给您的测试的各种其他参数。
2.6
动态测试支持在Twig 2.6中添加。
如果希望向测试传递可变数量的位置或命名参数,请设置is_variadic
选项真正的
.测试支持动态名称(请参阅动态筛选器了解语法)。
标签
像Twig这样的模板引擎最令人兴奋的特性之一是可以定义new语言结构.这也是最复杂的功能,因为您需要了解Twig的内部工作原理。
但是大多数时候,标签是不需要的:
- 如果标记生成一些输出,则使用函数代替。
如果标记修改了一些内容并返回它,则使用过滤器代替。
例如,如果您想创建一个将Markdown格式的文本转换为HTML的标记,请创建一个
减价
而不是过滤:1
{{'**markdown** text'|减价}}
如果要对大量文本使用此筛选器,请使用应用标签:
1 2 3 4 5 6
{%应用减价%}Title =====比创建一个标签好得多,因为你可以**组成**过滤器。{%endapply%}
请注意
的
应用
标签是在Twig 2.9引入的;使用过滤器
使用以前的版本标记。
如果标记没有输出任何内容,只是因为副作用而存在,则创建一个函数返回任何内容,并通过过滤器标签。
例如,如果要创建记录文本的标记,请创建
日志
函数,并通过做标签:1
{%做log(' log一些东西')%}
如果您仍然想为一个新的语言结构创建一个标记,那太好了!
我们来创建一个集
标记,该标记允许从模板中定义简单变量。标签可以像下面这样使用:
1 2 3 4 5
{%集Name = "value" %}{{name}}{#应该输出值#}
请注意
的集
标签是核心扩展的一部分,因此总是可用的。内置版本稍微强大一些,默认情况下支持多重赋值。
定义一个新标签需要三个步骤:
- 定义一个Token Parser类(负责解析模板代码);
- 定义一个Node类(负责将解析的代码转换为PHP);
- 注册标记。
注册一个新标记
方法添加标记addTokenParser
方法。\树枝\环境
实例:
1 2
$嫩枝=新\树枝\环境($加载程序);$嫩枝->addTokenParser (新Project_Set_TokenParser ());
定义令牌解析器
现在,让我们看看这个类的实际代码:
12 3 4 5 6 7 8 9 10 11 12 13 14 16 17 18 19 20
类Project_Set_TokenParser扩展\嫩枝\TokenParser\AbstractTokenParser{公共函数解析(\树枝\令牌$令牌){$解析器=$这->解析器;$流=$解析器->getStream ();$的名字=$流->期望(\树枝\令牌::NAME_TYPE)->getValue ();$流->期望(\树枝\令牌::OPERATOR_TYPE,“=”);$价值=$解析器->getExpressionParser ()->parseExpression ();$流->期望(\树枝\令牌::BLOCK_END_TYPE);返回新Project_Set_Node ($的名字,$价值,$令牌->getLine (),$这->getTag ());}公共函数getTag(){返回“设置”;}}
的getTag ()
方法必须返回要解析的标记集
.
的parse ()
方法时调用集
标签。它应该返回一个\ \树枝\节点
实例,该实例表示节点Project_Set_Node
调用创建将在下一节中解释)。
解析过程简化了,这要归功于您可以从令牌流调用的一堆方法($ this - >解析器- > getStream ()
):
getCurrent ()
:获取流中的当前令牌。next ()
:移动到流中的下一个标记,而是返回原来的.测试类型($)
,测试(美元值)
或测试(类型,价值美元)
:确定当前令牌是否为特定类型或值(或两者都是)。该值可以是几个可能值的数组。Expect ($type[, $value[, $message]])
:如果当前令牌不是给定类型/值,则抛出语法错误。否则,如果类型和值正确,则返回令牌,流移动到下一个令牌。看()
:查看下一个令牌,但不消耗它。
解析表达式是通过调用parseExpression ()
就像我们对集
标签。
提示
阅读现有的TokenParser
类是学习解析过程的所有基本细节的最佳方法。
定义节点
的Project_Set_Node
课程本身很短:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
类Project_Set_Node扩展\嫩枝\节点\节点{公共函数__construct($的名字, \树枝\节点\ \ AbstractExpression表达式$价值,$行,$标签= null){父::__construct ([“价值”= >$价值]、[“名字”= >$的名字],$行,$标签);}公共函数编译(\树枝\编译器$编译器){$编译器->addDebugInfo ($这)->写($上下文[\”.$这->getAttribute (“名字”).'\'] = ')->subcompile ($这->getNode (“价值”))->生(”;\ n”);}}
编译器实现了一个流动的接口,并提供了一些方法来帮助开发人员生成漂亮和可读的PHP代码:
subcompile ()
:编译节点。生()
:按原样写入给定的字符串。write ()
:通过在每行开头添加缩进来写入给定的字符串。字符串()
:写入带引号的字符串。repr ()
:编写给定值的PHP表示(参见\树枝\ \ ForNode节点
使用示例)。addDebugInfo ()
:将原模板文件中与当前节点相关的行添加为注释。缩进()
:缩进生成的代码(请参阅\树枝\ \ BlockNode节点
使用示例)。减少缩进()
:超出生成的代码(请参阅\树枝\ \ BlockNode节点
使用示例)。
创建扩展
编写扩展的主要动机是将经常使用的代码转移到可重用类中,例如添加对国际化的支持。扩展可以定义标记、筛选器、测试、操作符、函数和节点访问者。
大多数时候,为你的项目创建一个单独的扩展是很有用的,以承载你想要添加到Twig的所有特定的标签和过滤器。
提示
当你把你的代码打包到一个扩展中时,Twig是足够聪明的,无论何时你对它做了更改(当auto_reload
启用)。
扩展是实现以下接口的类:
12 34 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
接口\嫩枝\扩展\ExtensionInterface{/** *返回要添加到现有列表的令牌解析器实例。* *@return\树枝\ TokenParser \ TokenParserInterface [] * /公共函数getTokenParsers();/** *返回要添加到现有列表的节点访问者实例。* *@return\树枝\ NodeVisitor \ NodeVisitorInterface [] * /公共函数getNodeVisitors();/** *返回要添加到现有列表的过滤器列表。* *@return\树枝\ TwigFilter [] * /公共函数getFilters();/** *返回要添加到现有列表的测试列表。* *@return\树枝\ TwigTest [] * /公共函数getTests();/** *返回要添加到现有列表的函数列表。* *@return\树枝\ TwigFunction [] * /公共函数getFunctions();/** *返回要添加到现有列表的操作符列表。* *@returnarray第一个一元操作符数组,第二个二元操作符数组*/ 公共函数getOperators();}
为了保持扩展类的简洁,请从内置的\树枝\ \ AbstractExtension延伸
类,而不是实现接口,因为它为所有方法提供了空实现:
1 2 3
类Project_Twig_Extension扩展\嫩枝\扩展\AbstractExtension{}
这个扩展现在什么都不做。我们将在下一节中定制它。
您可以将扩展保存在文件系统的任何位置,因为必须显式地注册所有扩展才能在模板中使用。
方法可以注册扩展addExtension ()
方法。环境
对象:
1 2
$嫩枝=新\树枝\环境($加载程序);$嫩枝->addExtension (新Project_Twig_Extension ());
提示
Twig核心扩展是扩展如何工作的很好的例子。
全局变量
方法在扩展中注册全局变量getGlobals ()
方法:
1 2 3 4 5 6 7 8 9 10 11
类Project_Twig_Extension扩展\嫩枝\扩展\AbstractExtension实现了\嫩枝\扩展\GlobalsInterface{公共函数getGlobals(){返回[“文本”= >新文本()];}/ /……}
功能
方法将函数注册到扩展中getFunctions ()
方法:
1 2 3 4 5 6 7 8 9 10 11
类Project_Twig_Extension扩展\嫩枝\扩展\AbstractExtension{公共函数getFunctions(){返回[新\树枝\ TwigFunction (“lipsum”,“generate_lipsum”),);}/ /……}
过滤器
要向扩展添加筛选器,需要重写getFilters ()
方法。该方法必须返回一个添加到Twig环境的过滤器数组:
1 2 3 4 5 6 7 8 9 10 11
类Project_Twig_Extension扩展\嫩枝\扩展\AbstractExtension{公共函数getFilters(){返回[新\树枝\ TwigFilter (“rot13”,“函数”),);}/ /……}
标签
属性可以在扩展中添加标记getTokenParsers ()
方法。这个方法必须返回一个添加到Twig环境的标签数组:
1 2 3 4 5 6 7 8 9
类Project_Twig_Extension扩展\嫩枝\扩展\AbstractExtension{公共函数getTokenParsers(){返回[新Project_Set_TokenParser ()];}/ /……}
在上面的代码中,我们添加了一个新标记,该标记由Project_Set_TokenParser
类。的Project_Set_TokenParser
类负责解析标记并将其编译为PHP。
运营商
的getOperators ()
方法允许您添加新的操作符。下面是如何添加!
,||
,& &
运营商:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
类Project_Twig_Extension扩展\嫩枝\扩展\AbstractExtension{公共函数getOperators(){返回[[“!”= > [“优先”= >50,“类”= > \树枝\节点\ \一元\ NotUnary表达式::类],],[“| |”= > [“优先”= >10,“类”= > \树枝\节点\ \二进制\ OrBinary表达式::类,结合性的= > \树枝\ ExpressionParser::OPERATOR_LEFT),“& &”= > [“优先”= >15,“类”= > \树枝\节点\ \二进制\ AndBinary表达式::类,结合性的= > \树枝\ ExpressionParser::Operator_left],],];}/ /……}
测试
的getTests ()
方法允许您添加新的测试函数:
1 2 3 4 5 6 7 8 9 10 11
类Project_Twig_Extension扩展\嫩枝\扩展\AbstractExtension{公共函数getTests(){返回[新\树枝\ TwigTest (“甚至”,“twig_test_even”),);}/ /……}
定义vs运行时
Twig过滤器、函数和测试运行时实现可以定义为任何有效的PHP可调用对象:
- 函数/静态方法:简单的实现和快速(使用的所有Twig核心扩展);但是运行时很难依赖于外部对象;
- 闭包:执行简单;
- 对象方法:如果您的运行时代码依赖于外部对象,则更加灵活并且是必需的。
使用方法最简单的方法是在扩展本身上定义它们:
12 3 4 5 6 7 8 9 10 11 12 13 14 16 17 18 19 20 21
类Project_Twig_Extension扩展\嫩枝\扩展\AbstractExtension{私人$rot13Provider;公共函数__construct($rot13Provider){$这->rot13Provider =$rot13Provider;}公共函数getFunctions(){返回[新\树枝\ TwigFunction (“rot13”, ($这,“rot13”)));}公共函数rot13($价值){返回$这->rot13Provider->rot13 ($价值);}}
这非常方便,但不建议这样做,因为它使模板编译依赖于运行时依赖项,即使它们并不需要(例如,将实例视为连接到数据库引擎的依赖项)。
可以将扩展定义与其运行时实现解耦\树枝\ RuntimeLoader \ RuntimeLoaderInterface
环境中的实例,该实例知道如何实例化这样的运行时类(运行时类必须是可自动加载的):
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16
类RuntimeLoader实现了\嫩枝\RuntimeLoader\RuntimeLoaderInterface{公共函数负载($类){//实现创建$class实例的逻辑//并注入它的依赖项//大多数时候,这意味着使用你的依赖注入容器如果(“Project_Twig_RuntimeExtension”= = =$类) {返回新$类(新Rot13Provider ());}其他的{/ /……}}}$嫩枝->addRuntimeLoader (新RuntimeLoader ());
请注意
Twig附带一个PSR-11兼容的运行时加载器(\树枝\ RuntimeLoader \ ContainerRuntimeLoader
).
现在可以将运行时逻辑移动到新的Project_Twig_RuntimeExtension
类,并直接在扩展中使用它:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
类Project_Twig_RuntimeExtension{私人$rot13Provider;公共函数__construct($rot13Provider){$这->rot13Provider =$rot13Provider;}公共函数rot13($价值){返回$这->rot13Provider->rot13 ($价值);}}类Project_Twig_Extension扩展\嫩枝\扩展\AbstractExtension{公共函数getFunctions(){返回[新\树枝\ TwigFunction (“rot13”, (“Project_Twig_RuntimeExtension”,“rot13”]),/ /或新\树枝\ TwigFunction (“rot13”,“Project_Twig_RuntimeExtension:: rot13”),);}}
测试扩展
功能测试
通过在测试目录中创建以下文件结构,可以为扩展创建功能测试:
1 2 3 4 5 6 7 8 9 10 11
Fixtures/ filters/ foo。测试棒。测验函数s/ foo.test bar.test tags/ foo.test bar.test IntegrationTest.php
的IntegrationTest.php
文件应该是这样的:
12 3 4 5 6 7 8 9 10 11 12 13 14 15
类Project_Tests_IntegrationTest扩展\嫩枝\测试\IntegrationTestCase{公共函数getExtensions(){返回[新Project_Twig_Extension1 (),新Project_Twig_Extension2 ()];}公共函数getFixturesDir(){返回__DIR__.“/夹具/”;}}
fixture示例可以在Twig存储库中找到测试/理解/夹具目录中。