Twig

灵活、快速且安全的
PHP 模板引擎

a Symfony Product
文档 扩展 Twig
您正在阅读 Twig 3.x 的文档。切换到 Twig 1.x, 2.x 的文档。

扩展 Twig

Twig 可以通过多种方式扩展;您可以添加额外的标签、过滤器、测试、运算符、全局变量和函数。您甚至可以使用节点访问器扩展解析器本身。

注意

本章的第一节描述了如何扩展 Twig。如果您想在不同的项目中重用您的更改,或者如果您想与他人分享,那么您应该创建一个扩展,如以下部分所述。

小心

当在不创建扩展的情况下扩展 Twig 时,当 PHP 代码更新时,Twig 将无法重新编译您的模板。要实时查看您的更改,请禁用模板缓存或将您的代码打包到扩展中(请参阅本章的下一节)。

在扩展 Twig 之前,您必须了解所有不同可能的扩展点之间的差异以及何时使用它们。

首先,请记住 Twig 有两个主要的语言结构

  • {{ }}:用于打印表达式求值的结果;
  • {% %}:用于执行语句。

为了理解为什么 Twig 公开了如此多的扩展点,让我们看看如何实现一个 Lorem ipsum 生成器(它需要知道要生成的单词数量)。

您可以使用 lipsum 标签

1
{% lipsum 40 %}

这可以工作,但是使用标签 lipsum 不是一个好主意,至少有三个主要原因

  • lipsum 不是语言结构;
  • 标签输出一些内容;
  • 该标签不够灵活,因为您无法在表达式中使用它

    1
    {{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}

事实上,您很少需要创建标签;这是个好消息,因为标签是最复杂的扩展点。

现在,让我们使用 lipsum 过滤器

1
{{ 40|lipsum }}

同样,它可以工作。但是过滤器应该将传递的值转换为其他内容。在这里,我们使用该值来指示要生成的单词数(因此,40 是过滤器的参数,而不是我们要转换的值)。

接下来,让我们使用 lipsum 函数

1
{{ lipsum(40) }}

我们开始了。对于这个特定的示例,创建函数是要使用的扩展点。您可以在接受表达式的任何地方使用它

1
2
3
{{ 'some text' ~ lipsum(40) ~ 'some more text' }}

{% set lipsum = lipsum(40) %}

最后,您还可以使用一个 全局 对象,该对象具有能够生成 lorem ipsum 文本的方法

1
{{ text.lipsum(40) }}

根据经验,对于常用功能使用函数,对于其他所有内容使用全局对象。

当您想要扩展 Twig 时,请记住以下几点

什么? 实现难度? 频率? 何时?
简单 频繁 内容生成
全局变量 简单 频繁 辅助对象
函数 简单 频繁 内容生成
过滤器 简单 频繁 值转换
标签 复杂 罕见 DSL 语言结构
测试 简单 罕见 布尔决策
运算符 简单 罕见 值转换

全局变量

全局变量在所有模板和宏中都可用。使用 addGlobal() 将全局变量添加到 Twig 环境中

1
2
$twig = new \Twig\Environment($loader);
$twig->addGlobal('text', new Text());

然后,您可以在模板中的任何位置使用 text 变量

1
{{ text.lipsum(40) }}

过滤器

创建过滤器包括将名称与 PHP 可调用对象关联

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// an anonymous function
$filter = new \Twig\TwigFilter('rot13', function ($string) {
    return str_rot13($string);
});

// or a simple PHP function
$filter = new \Twig\TwigFilter('rot13', 'str_rot13');

// or a class static method
$filter = new \Twig\TwigFilter('rot13', ['SomeClass', 'rot13Filter']);
$filter = new \Twig\TwigFilter('rot13', 'SomeClass::rot13Filter');

// or a class method
$filter = new \Twig\TwigFilter('rot13', [$this, 'rot13Filter']);
// the one below needs a runtime implementation (see below for more information)
$filter = new \Twig\TwigFilter('rot13', ['SomeClass', 'rot13Filter']);

\Twig\TwigFilter 构造函数的第一个参数是您将在模板中使用的过滤器的名称,第二个参数是要与之关联的 PHP 可调用对象。

然后,将过滤器添加到 Twig 环境中

1
2
$twig = new \Twig\Environment($loader);
$twig->addFilter($filter);

以下是如何在模板中使用它

1
2
3
{{ 'Twig'|rot13 }}

{# will output Gjvt #}

当被 Twig 调用时,PHP 可调用对象接收过滤器左侧(管道符 | 之前)作为第一个参数,并将传递给过滤器的额外参数(在括号 () 内)作为额外参数。

例如,以下代码

1
2
{{ 'TWIG'|lower }}
{{ now|date('d/m/Y') }}

被编译成类似以下内容

1
2
<?php echo strtolower('TWIG') ?>
<?php echo twig_date_format_filter($now, 'd/m/Y') ?>

\Twig\TwigFilter 类将选项数组作为其最后一个参数

1
$filter = new \Twig\TwigFilter('rot13', 'str_rot13', $options);

字符集感知过滤器

如果您想在过滤器中访问默认字符集,请将 needs_charset 选项设置为 true;Twig 将把默认字符集作为第一个参数传递给过滤器调用

1
2
3
$filter = new \Twig\TwigFilter('rot13', function (string $charset, $string) {
    return str_rot13($string);
}, ['needs_charset' => true]);

环境感知过滤器

如果您想在过滤器中访问当前环境实例,请将 needs_environment 选项设置为 true;Twig 将把当前环境作为第一个参数传递给过滤器调用

1
2
3
4
5
6
$filter = new \Twig\TwigFilter('rot13', function (\Twig\Environment $env, $string) {
    // get the current charset for instance
    $charset = $env->getCharset();

    return str_rot13($string);
}, ['needs_environment' => true]);

上下文感知过滤器

如果您想在过滤器中访问当前上下文,请将 needs_context 选项设置为 true;Twig 将把当前上下文作为第一个参数传递给过滤器调用(如果 needs_environment 也设置为 true,则为第二个参数)

1
2
3
4
5
6
7
$filter = new \Twig\TwigFilter('rot13', function ($context, $string) {
    // ...
}, ['needs_context' => true]);

$filter = new \Twig\TwigFilter('rot13', function (\Twig\Environment $env, $context, $string) {
    // ...
}, ['needs_context' => true, 'needs_environment' => true]);

自动转义

如果启用了自动转义,则过滤器的输出在打印之前可能会被转义。如果您的过滤器充当转义器(或显式输出 HTML 或 JavaScript 代码),您将希望打印原始输出。在这种情况下,设置 is_safe 选项

1
$filter = new \Twig\TwigFilter('nl2br', 'nl2br', ['is_safe' => ['html']]);

某些过滤器可能需要在已经转义或安全的输入上工作,例如,当将(安全)HTML 标签添加到原始不安全的输出时。在这种情况下,设置 pre_escape 选项以在输入数据通过过滤器运行之前对其进行转义

1
$filter = new \Twig\TwigFilter('somefilter', 'somefilter', ['pre_escape' => 'html', 'is_safe' => ['html']]);

可变参数过滤器

当过滤器应接受任意数量的参数时,将 is_variadic 选项设置为 true;Twig 将把额外的参数作为数组作为最后一个参数传递给过滤器调用

1
2
3
$filter = new \Twig\TwigFilter('thumbnail', function ($file, array $options = []) {
    // ...
}, ['is_variadic' => true]);

请注意,传递给可变参数过滤器的命名参数无法检查有效性,因为它们将自动进入选项数组。

动态过滤器

包含特殊 * 字符的过滤器名称是动态过滤器,* 部分将匹配任何字符串

1
2
3
$filter = new \Twig\TwigFilter('*_path', function ($name, $arguments) {
    // ...
});

以下过滤器与上面定义的动态过滤器匹配

  • product_path
  • category_path

动态过滤器可以定义多个动态部分

1
2
3
$filter = new \Twig\TwigFilter('*_path_*', function ($name, $suffix, $arguments) {
    // ...
});

过滤器在正常过滤器参数之前接收所有动态部分值,但在环境和上下文之后。例如,调用 'Paris'|a_path_b() 将导致以下参数传递给过滤器:('a', 'b', 'Paris')

已弃用的过滤器

3.15

deprecation_info 选项在 Twig 3.15 中添加。

您可以通过设置 deprecation_info 选项将过滤器标记为已弃用

1
2
3
$filter = new \Twig\TwigFilter('obsolete', function () {
    // ...
}, ['deprecation_info' => new DeprecatedCallableInfo('twig/twig', '3.11', 'new_one')]);

DeprecatedCallableInfo 构造函数接受以下参数

  • 定义过滤器的 Composer 包名称;
  • 过滤器被弃用的版本。

可选地,您还可以提供有关替代方案的以下参数

  • 包含替代过滤器的包名称;
  • 替换已弃用过滤器的替代过滤器名称;
  • 添加替代过滤器的包版本。

当过滤器被弃用时,Twig 在编译使用它的模板时会发出弃用通知。有关更多信息,请参阅配方

注意

在 Twig 3.15 之前,您可以通过将 deprecated 选项设置为 true 来将过滤器标记为已弃用。您还可以提供一个替代过滤器来替换已弃用的过滤器(如果这有意义)

1
2
3
$filter = new \Twig\TwigFilter('obsolete', function () {
    // ...
}, ['deprecated' => true, 'alternative' => 'new_one']);

3.11

deprecating_package 选项在 Twig 3.11 中添加。

您还可以设置 deprecating_package 选项来指定弃用过滤器的包,并且可以将 deprecated 设置为过滤器被弃用的包版本

1
2
3
$filter = new \Twig\TwigFilter('obsolete', function () {
    // ...
}, ['deprecated' => '1.1', 'deprecating_package' => 'twig/some-package']);

函数

函数的定义方式与过滤器完全相同,但您需要创建 \Twig\TwigFunction 的实例

1
2
3
4
5
$twig = new \Twig\Environment($loader);
$function = new \Twig\TwigFunction('function_name', function () {
    // ...
});
$twig->addFunction($function);

函数支持与过滤器相同的功能,但 pre_escapepreserves_safety 选项除外。

测试

测试的定义方式与过滤器和函数完全相同,但您需要创建 \Twig\TwigTest 的实例

1
2
3
4
5
$twig = new \Twig\Environment($loader);
$test = new \Twig\TwigTest('test_name', function () {
    // ...
});
$twig->addTest($test);

测试允许您为评估布尔条件创建自定义的特定于应用程序的逻辑。作为一个简单的示例,让我们创建一个 Twig 测试,检查对象是否为“红色”

1
2
3
4
5
6
7
8
9
10
11
$twig = new \Twig\Environment($loader);
$test = new \Twig\TwigTest('red', function ($value) {
    if (isset($value->color) && $value->color == 'red') {
        return true;
    }
    if (isset($value->paint) && $value->paint == 'red') {
        return true;
    }
    return false;
});
$twig->addTest($test);

测试函数必须始终返回 true/false

创建测试时,您可以使用 node_class 选项来提供自定义测试编译。如果您的测试可以编译为 PHP 原语,这将非常有用。Twig 内置的许多测试都使用了这一点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace App;

use Twig\Environment;
use Twig\Node\Expression\TestExpression;
use Twig\TwigTest;

$twig = new Environment($loader);
$test = new TwigTest(
    'odd',
    null,
    ['node_class' => OddTestExpression::class]);
$twig->addTest($test);

class OddTestExpression extends TestExpression
{
    public function compile(\Twig\Compiler $compiler)
    {
        $compiler
            ->raw('(')
            ->subcompile($this->getNode('node'))
            ->raw(' % 2 != 0')
            ->raw(')')
        ;
    }
}

上面的示例展示了如何创建使用节点类的测试。节点类可以访问一个名为 node 的子节点。此子节点包含正在测试的值。当在代码中使用 odd 过滤器时,例如

1
{% if my_value is odd %}

node 子节点将包含 my_value 的表达式。基于节点的测试还可以访问 arguments 节点。此节点将包含已提供给您的测试的各种其他参数。

如果您想将可变数量的位置或命名参数传递给测试,请将 is_variadic 选项设置为 true。测试支持动态名称(请参阅动态过滤器的语法)。

标签

像 Twig 这样的模板引擎最令人兴奋的功能之一是定义新的语言结构的可能性。这也是最复杂的功能,因为您需要了解 Twig 内部的工作原理。

但在大多数情况下,不需要标签

  • 如果您的标签生成一些输出,请改用函数
  • 如果您的标签修改了一些内容并返回它,请改用过滤器

    例如,如果您想创建一个将 Markdown 格式的文本转换为 HTML 的标签,请创建一个 markdown 过滤器

    1
    {{ '**markdown** text'|markdown }}

    如果您想对大量文本使用此过滤器,请使用 apply 标签将其包装起来

    1
    2
    3
    4
    5
    6
    {% apply markdown %}
    Title
    =====
    
    Much better than creating a tag as you can **compose** filters.
    {% endapply %}
  • 如果您的标签不输出任何内容,但仅因副作用而存在,请创建一个返回空值的函数,并通过 do 标签调用它。

    例如,如果您想创建一个记录文本的标签,请创建一个 log 函数,并通过 do 标签调用它

    1
    {% do log('Log some things') %}

如果您仍然想为新的语言结构创建标签,那太好了!

让我们创建一个 set 标签,允许从模板内部定义简单变量。该标签可以如下使用

1
2
3
4
5
{% set name = "value" %}

{{ name }}

{# should output value #}

注意

set 标签是 Core 扩展的一部分,因此始终可用。内置版本功能更强大,默认情况下支持多个赋值。

定义新标签需要三个步骤

  • 定义 Token 解析器类(负责解析模板代码);
  • 定义节点类(负责将解析后的代码转换为 PHP);
  • 注册标签。

注册新标签

通过调用 \Twig\Environment 实例上的 addTokenParser 方法添加标签

1
2
$twig = new \Twig\Environment($loader);
$twig->addTokenParser(new CustomSetTokenParser());

定义 Token 解析器

现在,让我们看看这个类的实际代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class CustomSetTokenParser extends \Twig\TokenParser\AbstractTokenParser
{
    public function parse(\Twig\Token $token)
    {
        $parser = $this->parser;
        $stream = $parser->getStream();

        $name = $stream->expect(\Twig\Token::NAME_TYPE)->getValue();
        $stream->expect(\Twig\Token::OPERATOR_TYPE, '=');
        $value = $parser->getExpressionParser()->parseExpression();
        $stream->expect(\Twig\Token::BLOCK_END_TYPE);

        return new CustomSetNode($name, $value, $token->getLine());
    }

    public function getTag()
    {
        return 'set';
    }
}

getTag() 方法必须返回我们要解析的标签,这里是 set

每当解析器遇到 set 标签时,都会调用 parse() 方法。它应该返回一个 \Twig\Node\Node 实例,该实例表示节点(下一节将解释创建的 CustomSetNode 调用)。

由于可以从令牌流 ($this->parser->getStream()) 调用一系列方法,因此解析过程得到了简化

  • getCurrent():获取流中的当前令牌。
  • next():移动到流中的下一个令牌,但返回旧令牌
  • test($type), test($value)test($type, $value):确定当前令牌是否属于特定类型或值(或两者)。该值可以是几个可能值的数组。
  • expect($type[, $value[, $message]]):如果当前令牌不是给定的类型/值,则会抛出语法错误。否则,如果类型和值正确,则返回令牌,并且流移动到下一个令牌。
  • look():查看下一个令牌而不消耗它。

解析表达式是通过调用 parseExpression() 完成的,就像我们对 set 标签所做的那样。

提示

阅读现有的 TokenParser 类是学习解析过程的所有细枝末节的最佳方法。

定义节点

CustomSetNode 类本身非常短

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class CustomSetNode extends \Twig\Node\Node
{
    public function __construct($name, \Twig\Node\Expression\AbstractExpression $value, $line)
    {
        parent::__construct(['value' => $value], ['name' => $name], $line);
    }

    public function compile(\Twig\Compiler $compiler)
    {
        $compiler
            ->addDebugInfo($this)
            ->write('$context[\''.$this->getAttribute('name').'\'] = ')
            ->subcompile($this->getNode('value'))
            ->raw(";\n")
        ;
    }
}

编译器实现了流畅的接口,并提供了帮助开发人员生成美观且可读的 PHP 代码的方法

  • subcompile():编译节点。
  • raw():按原样写入给定的字符串。
  • write():通过在每行开头添加缩进来写入给定的字符串。
  • string():写入带引号的字符串。
  • repr():写入给定值的 PHP 表示形式(有关用法示例,请参阅 \Twig\Node\ForNode)。
  • addDebugInfo():将与当前节点相关的原始模板文件的行添加为注释。
  • indent():缩进生成的代码(有关用法示例,请参阅 \Twig\Node\BlockNode)。
  • outdent():取消缩进生成的代码(有关用法示例,请参阅 \Twig\Node\BlockNode)。

创建扩展

编写扩展的主要动机是将常用代码移动到可重用的类中,例如添加对国际化的支持。扩展可以定义标签、过滤器、测试、运算符、函数和节点访问器。

大多数时候,为您的项目创建一个单独的扩展来托管您想要添加到 Twig 的所有特定标签和过滤器非常有用。

提示

当将您的代码打包到扩展中时,Twig 足够智能,可以在您对其进行更改时重新编译您的模板(当 auto_reload 启用时)。

扩展是实现以下接口的类

1
2
3
4
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
interface \Twig\Extension\ExtensionInterface
{
    /**
     * Returns the token parser instances to add to the existing list.
     *
     * @return \Twig\TokenParser\TokenParserInterface[]
     */
    public function getTokenParsers();

    /**
     * Returns the node visitor instances to add to the existing list.
     *
     * @return \Twig\NodeVisitor\NodeVisitorInterface[]
     */
    public function getNodeVisitors();

    /**
     * Returns a list of filters to add to the existing list.
     *
     * @return \Twig\TwigFilter[]
     */
    public function getFilters();

    /**
     * Returns a list of tests to add to the existing list.
     *
     * @return \Twig\TwigTest[]
     */
    public function getTests();

    /**
     * Returns a list of functions to add to the existing list.
     *
     * @return \Twig\TwigFunction[]
     */
    public function getFunctions();

    /**
     * Returns a list of operators to add to the existing list.
     *
     * @return array<array> First array of unary operators, second array of binary operators
     */
    public function getOperators();
}

为了保持您的扩展类干净简洁,请从内置的 \Twig\Extension\AbstractExtension 类继承,而不是实现接口,因为它为所有方法提供了空实现

1
2
3
class CustomTwigExtension extends \Twig\Extension\AbstractExtension
{
}

此扩展目前没有任何作用。我们将在接下来的部分中对其进行自定义。

您可以将扩展保存在文件系统上的任何位置,因为所有扩展都必须显式注册才能在您的模板中使用。

您可以使用主 Environment 对象上的 addExtension() 方法注册扩展

1
2
$twig = new \Twig\Environment($loader);
$twig->addExtension(new CustomTwigExtension());

提示

Twig 核心扩展是如何扩展工作的绝佳示例。

全局变量

全局变量可以通过 getGlobals() 方法在扩展中注册

1
2
3
4
5
6
7
8
9
10
11
class CustomTwigExtension extends \Twig\Extension\AbstractExtension implements \Twig\Extension\GlobalsInterface
{
    public function getGlobals(): array
    {
        return [
            'text' => new Text(),
        ];
    }

    // ...
}

小心

全局变量从扩展中获取一次,然后在 Twig 环境的生命周期内缓存。这意味着全局变量不应用于存储在 Twig 环境生命周期内可能更改的值。例如,如果您正在使用像 RoadRunner 或 FrankenPHP 这样的应用程序服务器,则不应存储与当前上下文相关的值(例如 HTTP 请求)。如果您这样做,请不要忘记通过调用 Environment::resetGlobals() 在请求之间重置缓存。

函数

函数可以通过 getFunctions() 方法在扩展中注册

1
2
3
4
5
6
7
8
9
10
11
class CustomTwigExtension extends \Twig\Extension\AbstractExtension
{
    public function getFunctions()
    {
        return [
            new \Twig\TwigFunction('lipsum', 'generate_lipsum'),
        ];
    }

    // ...
}

过滤器

要向扩展添加过滤器,您需要覆盖 getFilters() 方法。此方法必须返回要添加到 Twig 环境的过滤器数组

1
2
3
4
5
6
7
8
9
10
11
class CustomTwigExtension extends \Twig\Extension\AbstractExtension
{
    public function getFilters()
    {
        return [
            new \Twig\TwigFilter('rot13', 'str_rot13'),
        ];
    }

    // ...
}

标签

在扩展中添加标签可以通过覆盖 getTokenParsers() 方法来完成。此方法必须返回要添加到 Twig 环境的标签数组

1
2
3
4
5
6
7
8
9
class CustomTwigExtension extends \Twig\Extension\AbstractExtension
{
    public function getTokenParsers()
    {
        return [new CustomSetTokenParser()];
    }

    // ...
}

在上面的代码中,我们添加了一个由 CustomSetTokenParser 类定义的单个新标签。CustomSetTokenParser 类负责解析标签并将其编译为 PHP。

运算符

getOperators() 方法允许您添加新的运算符。要实现新的运算符,请查看 Twig\Extension\CoreExtension 提供的默认运算符。

测试

getTests() 方法允许您添加新的测试函数

1
2
3
4
5
6
7
8
9
10
11
class CustomTwigExtension extends \Twig\Extension\AbstractExtension
{
    public function getTests()
    {
        return [
            new \Twig\TwigTest('even', 'twig_test_even'),
        ];
    }

    // ...
}

定义与运行时

Twig 过滤器、函数和测试运行时实现可以定义为任何有效的 PHP 可调用对象

  • 函数/静态方法:易于实现且快速(所有 Twig 核心扩展都使用);但运行时很难依赖外部对象;
  • 闭包:易于实现;
  • 对象方法:更灵活,如果您的运行时代码依赖于外部对象,则需要使用对象方法。

使用方法的最简单方法是在扩展本身上定义它们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class CustomTwigExtension extends \Twig\Extension\AbstractExtension
{
    private $rot13Provider;

    public function __construct($rot13Provider)
    {
        $this->rot13Provider = $rot13Provider;
    }

    public function getFunctions()
    {
        return [
            new \Twig\TwigFunction('rot13', [$this, 'rot13']),
        ];
    }

    public function rot13($value)
    {
        return $this->rot13Provider->rot13($value);
    }
}

这非常方便,但不建议这样做,因为它使模板编译依赖于运行时依赖项,即使它们不是必需的(例如,考虑依赖于连接到数据库引擎的依赖项)。

您可以通过在环境上注册 \Twig\RuntimeLoader\RuntimeLoaderInterface 实例来将扩展定义与其运行时实现分离,该实例知道如何实例化此类运行时类(运行时类必须是可自动加载的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class RuntimeLoader implements \Twig\RuntimeLoader\RuntimeLoaderInterface
{
    public function load($class)
    {
        // implement the logic to create an instance of $class
        // and inject its dependencies
        // most of the time, it means using your dependency injection container
        if ('CustomTwigRuntime' === $class) {
            return new $class(new Rot13Provider());
        } else {
            // ...
        }
    }
}

$twig->addRuntimeLoader(new RuntimeLoader());

注意

Twig 附带一个 PSR-11 兼容的运行时加载器 (\Twig\RuntimeLoader\ContainerRuntimeLoader)。

现在可以将运行时逻辑移动到新的 CustomTwigRuntime 类,并在扩展中直接使用它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class CustomTwigRuntime
{
    private $rot13Provider;

    public function __construct($rot13Provider)
    {
        $this->rot13Provider = $rot13Provider;
    }

    public function rot13($value)
    {
        return $this->rot13Provider->rot13($value);
    }
}

class CustomTwigExtension extends \Twig\Extension\AbstractExtension
{
    public function getFunctions()
    {
        return [
            new \Twig\TwigFunction('rot13', ['CustomTwigRuntime', 'rot13']),
            // or
            new \Twig\TwigFunction('rot13', 'CustomTwigRuntime::rot13'),
        ];
    }
}

注意

扩展类应实现 Twig\Extension\LastModifiedExtensionInterface 接口,以便在运行时类被修改时使模板缓存无效。AbstractExtension 类实现了此接口,如果运行时类的名称与扩展类相同,但以 Runtime 而不是 Extension 结尾,则跟踪运行时类。

测试扩展

功能测试

您可以通过在测试目录中创建以下文件结构来为扩展创建功能测试

1
2
3
4
5
6
7
8
9
10
11
Fixtures/
    filters/
        lower.test
        upper.test
    functions/
        date.test
        format.test
    tags/
        for.test
        if.test
IntegrationTest.php

IntegrationTest.php 文件应如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace Project\Tests;

use Twig\Test\IntegrationTestCase;

class IntegrationTest extends IntegrationTestCase
{
    public function getExtensions()
    {
        return [
            new CustomTwigExtension1(),
            new CustomTwigExtension2(),
        ];
    }

    public function getFixturesDir()
    {
        return __DIR__.'/Fixtures/';
    }
}

可以在 Twig 仓库 tests/Twig/Fixtures 目录中找到 Fixture 示例。

节点测试

测试节点访问器可能很复杂,因此请从 \Twig\Test\NodeTestCase 扩展您的测试用例。示例可以在 Twig 仓库 tests/Twig/Node 目录中找到。