Extending Twig

Twig can be extended in many ways; you can add extra tags, filters, tests, operators, global variables, and functions. You can even extend the parser itself with node visitors.

Twig はさまざまな方法で拡張できます。タグ、フィルター、テスト、演算子、グローバル変数、および関数を追加できます。ノード ビジターを使用して parseritself を拡張することもできます。

Note

ノート

The first section of this chapter describes how to extend Twig. If you want to reuse your changes in different projects or if you want to share them with others, you should then create an extension as described in the following section.

この章の最初のセクションでは、Twig を拡張する方法について説明します。変更を別のプロジェクトで再利用する場合、または変更を他のユーザーと共有する場合は、次のセクションで説明するように拡張機能を作成する必要があります。

Caution

注意

When extending Twig without creating an extension, Twig won't be able to recompile your templates when the PHP code is updated. To see your changes in real-time, either disable template caching or package your code into an extension (see the next section of this chapter).

拡張機能を作成せずに Twig を拡張すると、PHP コードが更新されたときに、Twig はテンプレートを再コンパイルできなくなります。変更をリアルタイムで確認するには、テンプレートのキャッシュを無効にするか、コードを拡張機能にパッケージ化します (この章の次のセクションを参照してください)。

Before extending Twig, you must understand the differences between all the different possible extension points and when to use them.

Twig を拡張する前に、可能なすべての拡張ポイントの違いと、それらをいつ使用するかを理解する必要があります。

First, remember that Twig has two main language constructs:

まず、Twig には 2 つの主要な言語構造があることを思い出してください。
  • {{ }}: used to print the result of an expression evaluation;
    {{ }}: 式評価の結果を出力するために使用されます。
  • {% %}: used to execute statements.
    {% %}: ステートメントの実行に使用されます。

To understand why Twig exposes so many extension points, let's see how to implement a Lorem ipsum generator (it needs to know the number of words to generate).

Twig が非常に多くの拡張ポイントを公開する理由を理解するために、Lorem ipsum ジェネレーターを実装する方法を見てみましょう (生成する単語数を知る必要があります)。

You can use a lipsum tag:

lipsum タグを使用できます。
1
{% lipsum 40 %}

That works, but using a tag for lipsum is not a good idea for at least three main reasons:

それは機能しますが、lipsum のタグを使用することは、少なくとも 3 つの主な理由からお勧めできません。
  • lipsum is not a language construct;
    lipsum は言語構造ではありません。
  • The tag outputs something;
    タグは何かを出力します。
  • The tag is not flexible as you cannot use it in an expression:

    タグは式で使用できないため、柔軟ではありません。
    1
    {{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}

In fact, you rarely need to create tags; and that's good news because tags are the most complex extension point.

実際、タグを作成する必要はほとんどありません。タグは最も複雑な拡張ポイントであるため、これは朗報です。

Now, let's use a lipsum filter:

それでは、リップサム フィルターを使用してみましょう。
1
{{ 40|lipsum }}

Again, it works. But a filter should transform the passed value to something else. Here, we use the value to indicate the number of words to generate (so, 40 is an argument of the filter, not the value we want to transform).

繰り返しますが、機能します。ただし、フィルターは渡された値を別のものに変換する必要があります。ここでは、値を使用して、生成する単語の数を示します (したがって、40 はフィルターの引数であり、変換する値ではありません)。

Next, let's use a lipsum function:

次に、lipsum 関数を使用しましょう。
1
{{ lipsum(40) }}

Here we go. For this specific example, the creation of a function is the extension point to use. And you can use it anywhere an expression is accepted:

どうぞ。この特定の例では、関数の作成が使用する拡張ポイントです。また、式が受け入れられる場所ならどこでも使用できます。
1
2
3
{{ 'some text' ~ lipsum(40) ~ 'some more text' }}

{% set lipsum = lipsum(40) %}

Lastly, you can also use a global object with a method able to generate lorem ipsum text:

最後に、loremipsum テキストを生成できるメソッドでグローバル オブジェクトを使用することもできます。
1
{{ text.lipsum(40) }}

As a rule of thumb, use functions for frequently used features and global objects for everything else.

経験則として、頻繁に使用される機能には関数を使用し、その他の機能にはグローバル オブジェクトを使用します。

Keep in mind the following when you want to extend Twig:

Twig を拡張する場合は、次の点に注意してください。
What? Implementation difficulty? How often? When?
macro simple frequent Content generation
global simple frequent Helper object
function simple frequent Content generation
filter simple frequent Value transformation
tag complex rare DSL language construct
test simple rare Boolean decision
operator simple rare Values transformation

Globals

A global variable is like any other template variable, except that it's available in all templates and macros:

グローバル変数は、すべてのテンプレートとマクロで使用できることを除いて、他のテンプレート変数と同じです。
1
2
$twig = new \Twig\Environment($loader);
$twig->addGlobal('text', new Text());

You can then use the text variable anywhere in a template:

その後、テンプレート内の任意の場所で text 変数を使用できます。
1
{{ text.lipsum(40) }}

Filters

Creating a filter consists of associating a name with a PHP callable:

フィルターを作成するには、名前を 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']);

The first argument passed to the \Twig\TwigFilter constructor is the name of the filter you will use in templates and the second one is the PHP callable to associate with it.

\Twig\TwigFilter コンストラクターに渡される最初の引数は、テンプレートで使用するフィルターの名前で、2 番目の引数はそれに関連付けるための PHP 呼び出し可能オブジェクトです。

Then, add the filter to the Twig environment:

次に、フィルターを Twig 環境に追加します。
1
2
$twig = new \Twig\Environment($loader);
$twig->addFilter($filter);

And here is how to use it in a template:

そして、これをテンプレートで使用する方法は次のとおりです。
1
2
3
{{ 'Twig'|rot13 }}

{# will output Gjvt #}

When called by Twig, the PHP callable receives the left side of the filter (before the pipe |) as the first argument and the extra arguments passed to the filter (within parentheses ()) as extra arguments.

Twig によって呼び出されると、PHP callable はフィルターの左側 (パイプ | の前) を最初の引数として受け取り、フィルターに渡された追加の引数 (括弧 () 内) を追加の引数として受け取ります。

For instance, the following code:

たとえば、次のコード:
1
2
{{ 'TWIG'|lower }}
{{ now|date('d/m/Y') }}

is compiled to something like the following:

次のようなものにコンパイルされます。
1
2
<?php echo strtolower('TWIG') ?>
<?php echo twig_date_format_filter($now, 'd/m/Y') ?>

The \Twig\TwigFilter class takes an array of options as its last argument:

\Twig\TwigFilter クラスは最後の引数としてオプションの配列を取ります:
1
$filter = new \Twig\TwigFilter('rot13', 'str_rot13', $options);

Environment-aware Filters

If you want to access the current environment instance in your filter, set the needs_environment option to true; Twig will pass the current environment as the first argument to the filter call:

フィルターで現在の環境インスタンスにアクセスする場合は、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]);

Context-aware Filters

If you want to access the current context in your filter, set the needs_context option to true; Twig will pass the current context as the first argument to the filter call (or the second one if needs_environment is also set to true):

フィルターで現在のコンテキストにアクセスする場合は、needs_context オプションを true に設定します。 Twig は現在のコンテキストをフィルター呼び出しの最初の引数として渡します (または、needs_environment も true に設定されている場合は 2 番目の引数)。
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]);

Automatic Escaping

If automatic escaping is enabled, the output of the filter may be escaped before printing. If your filter acts as an escaper (or explicitly outputs HTML or JavaScript code), you will want the raw output to be printed. In such a case, set the is_safe option:

自動エスケープが有効になっている場合、フィルタの出力は印刷前にエスケープされる場合があります。フィルターがエスケーパーとして機能する (または HTML または JavaScript コードを明示的に出力する) 場合は、生の出力を印刷する必要があります。そのような場合は、is_safe オプションを設定します。
1
$filter = new \Twig\TwigFilter('nl2br', 'nl2br', ['is_safe' => ['html']]);

Some filters may need to work on input that is already escaped or safe, for example when adding (safe) HTML tags to originally unsafe output. In such a case, set the pre_escape option to escape the input data before it is run through your filter:

一部のフィルターは、(安全な) HTML タグを元々安全でない出力に追加する場合など、既にエスケープされているか安全な入力で動作する必要がある場合があります。そのような場合、 pre_escape オプションを設定して、フィルターを通過する前に入力データをエスケープします。
1
$filter = new \Twig\TwigFilter('somefilter', 'somefilter', ['pre_escape' => 'html', 'is_safe' => ['html']]);

Variadic Filters

When a filter should accept an arbitrary number of arguments, set the is_variadic option to true; Twig will pass the extra arguments as the last argument to the filter call as an array:

フィルタが任意の数の引数を受け入れる必要がある場合は、is_variadic オプションを true に設定します。 Twig は追加の引数を最後の引数としてフィルター呼び出しに配列として渡します。
1
2
3
$filter = new \Twig\TwigFilter('thumbnail', function ($file, array $options = []) {
    // ...
}, ['is_variadic' => true]);

Be warned that named arguments passed to a variadic filter cannot be checked for validity as they will automatically end up in the option array.

variadicfilter に渡された名前付き引数は、オプション配列で自動的に終了するため、有効性をチェックできないことに注意してください。

Dynamic Filters

A filter name containing the special * character is a dynamic filter and the * part will match any string:

特殊文字 * を含むフィルター名は動的フィルターであり、* の部分は任意の文字列と一致します。
1
2
3
$filter = new \Twig\TwigFilter('*_path', function ($name, $arguments) {
    // ...
});

The following filters are matched by the above defined dynamic filter:

次のフィルターは、上記で定義された動的フィルターに一致します。
  • product_path
    product_path
  • category_path
    カテゴリパス

A dynamic filter can define more than one dynamic parts:

動的フィルターは、複数の動的パーツを定義できます。
1
2
3
$filter = new \Twig\TwigFilter('*_path_*', function ($name, $suffix, $arguments) {
    // ...
});

The filter receives all dynamic part values before the normal filter arguments, but after the environment and the context. For instance, a call to 'foo'|a_path_b() will result in the following arguments to be passed to the filter: ('a', 'b', 'foo').

フィルターは、通常のフィルター引数の前にすべての動的部分の値を受け取りますが、環境とコンテキストの後に受け取ります。たとえば、'foo'|a_path_b() を呼び出すと、次の引数がフィルターに渡されます: ('a', 'b', 'foo')。

Deprecated Filters

You can mark a filter as being deprecated by setting the deprecated option to true. You can also give an alternative filter that replaces the deprecated one when that makes sense:

deprecated オプションを true に設定することで、フィルターを非推奨としてマークできます。理にかなっている場合は、非推奨のフィルターを置き換える代替フィルターを指定することもできます。
1
2
3
$filter = new \Twig\TwigFilter('obsolete', function () {
    // ...
}, ['deprecated' => true, 'alternative' => 'new_one']);

When a filter is deprecated, Twig emits a deprecation notice when compiling a template using it. See Recipes for more information.

フィルターが非推奨になると、Twig はそれを使用してテンプレートをコンパイルするときに非推奨通知を発行します。詳細については、レシピを参照してください。

Functions

Functions are defined in the exact same way as filters, but you need to create an instance of \Twig\TwigFunction:

関数はフィルターとまったく同じ方法で定義されますが、\Twig\TwigFunction のインスタンスを作成する必要があります。
1
2
3
4
5
$twig = new \Twig\Environment($loader);
$function = new \Twig\TwigFunction('function_name', function () {
    // ...
});
$twig->addFunction($function);

Functions support the same features as filters, except for the pre_escape and preserves_safety options.

関数は、pre_escape および preserves_safety オプションを除いて、フィルターと同じ機能をサポートします。

Tests

Tests are defined in the exact same way as filters and functions, but you need to create an instance of \Twig\TwigTest:

テストはフィルターや関数とまったく同じ方法で定義されますが、\Twig\TwigTest のインスタンスを作成する必要があります。
1
2
3
4
5
$twig = new \Twig\Environment($loader);
$test = new \Twig\TwigTest('test_name', function () {
    // ...
});
$twig->addTest($test);

Tests allow you to create custom application specific logic for evaluating boolean conditions. As a simple example, let's create a Twig test that checks if objects are 'red':

テストを使用すると、ブール条件を評価するためのカスタム アプリケーション固有のロジックを作成できます。簡単な例として、オブジェクトが「赤」かどうかをチェックする 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);

Test functions must always return true/false.

テスト関数は常に true/false を返す必要があります。

When creating tests you can use the node_class option to provide custom test compilation. This is useful if your test can be compiled into PHP primitives. This is used by many of the tests built into Twig:

テストを作成するときは、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(')')
        ;
    }
}

The above example shows how you can create tests that use a node class. The node class has access to one sub-node called node. This sub-node contains the value that is being tested. When the odd filter is used in code such as:

上記の例は、ノード クラスを使用するテストを作成する方法を示しています。ノードクラスは、ノードと呼ばれる 1 つのサブノードにアクセスできます。このサブノードには、テストされている値が含まれています。次のようなコードで奇数フィルターを使用する場合:
1
{% if my_value is odd %}

The node sub-node will contain an expression of my_value. Node-based tests also have access to the arguments node. This node will contain the various other arguments that have been provided to your test.

ノードのサブノードには、my_value の式が含まれます。ノードベースのテストは、引数ノードにもアクセスできます。このノードには、テストに提供された他のさまざまな引数が含まれます。

If you want to pass a variable number of positional or named arguments to the test, set the is_variadic option to true. Tests support dynamic names (see dynamic filters for the syntax).

可変数の位置引数または名前付き引数をテストに渡したい場合は、is_variadic オプションを true に設定します。テストは動的名をサポートします (構文については動的フィルターを参照してください)。

Tags

One of the most exciting features of a template engine like Twig is the possibility to define new language constructs. This is also the most complex feature as you need to understand how Twig's internals work.

Twig のようなテンプレート エンジンの最もエキサイティングな機能の 1 つは、新しい言語構造を定義できることです。これは、Twig の内部がどのように機能するかを理解する必要があるため、最も複雑な機能でもあります。

Most of the time though, a tag is not needed:

ただし、ほとんどの場合、タグは必要ありません。
  • If your tag generates some output, use a function instead.
    タグが何らかの出力を生成する場合は、代わりに関数を使用してください。
  • If your tag modifies some content and returns it, use a filter instead.

    タグが一部のコンテンツを変更して返す場合は、代わりにフィルタを使用してください。

    For instance, if you want to create a tag that converts a Markdown formatted text to HTML, create a markdown filter instead:

    たとえば、Markdown 形式のテキストを HTML に変換するタグを作成する場合は、代わりに Markdown フィルターを作成します。
    1
    {{ '**markdown** text'|markdown }}

    If you want use this filter on large amounts of text, wrap it with the apply tag:

    このフィルターを大量のテキストで使用する場合は、apply タグで囲みます。
    1
    2
    3
    4
    5
    6
    {% apply markdown %}
    Title
    =====
    
    Much better than creating a tag as you can **compose** filters.
    {% endapply %}
  • If your tag does not output anything, but only exists because of a side effect, create a function that returns nothing and call it via the filter tag.

    タグが何も出力せず、副作用のためにのみ存在する場合は、何も返さない関数を作成し、filter タグを介して呼び出します。

    For instance, if you want to create a tag that logs text, create a log function instead and call it via the do tag:

    たとえば、テキストをログに記録するタグを作成する場合は、代わりに logfunction を作成し、do タグを介して呼び出します。
    1
    {% do log('Log some things') %}

If you still want to create a tag for a new language construct, great!

それでも新しい言語構造のタグを作成したい場合は、すばらしい!

Let's create a set tag that allows the definition of simple variables from within a template. The tag can be used like follows:

テンプレート内から単純な変数を定義できる set タグを作成しましょう。タグは次のように使用できます。
1
2
3
4
5
{% set name = "value" %}

{{ name }}

{# should output value #}

Note

ノート

The set tag is part of the Core extension and as such is always available. The built-in version is slightly more powerful and supports multiple assignments by default.

set タグは Core 拡張機能の一部であり、常に利用可能です。組み込みバージョンは、わずかに強力で、デフォルトで複数の割り当てをサポートしています。

Three steps are needed to define a new tag:

新しいタグを定義するには、次の 3 つの手順が必要です。
  • Defining a Token Parser class (responsible for parsing the template code);
    Token Parser クラスの定義 (テンプレート コードの解析を担当);
  • Defining a Node class (responsible for converting the parsed code to PHP);
    Node クラスの定義 (解析されたコードを PHP に変換する責任があります);
  • Registering the tag.
    タグの登録。

Registering a new tag

Add a tag by calling the addTokenParser method on the \Twig\Environment instance:

\Twig\Environment インスタンスで addTokenParser メソッドを呼び出してタグを追加します。
1
2
$twig = new \Twig\Environment($loader);
$twig->addTokenParser(new Project_Set_TokenParser());

Defining a Token Parser

Now, let's see the actual code of this class:

それでは、このクラスの実際のコードを見てみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Project_Set_TokenParser 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 Project_Set_Node($name, $value, $token->getLine(), $this->getTag());
    }

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

The getTag() method must return the tag we want to parse, here set.

getTag() メソッドは、解析したいタグを返す必要があります。ここで設定します。

The parse() method is invoked whenever the parser encounters a set tag. It should return a \Twig\Node\Node instance that represents the node (the Project_Set_Node calls creating is explained in the next section).

parse() メソッドは、パーサーが settag に遭遇するたびに呼び出されます。ノードを表す \Twig\Node\Node インスタンスを返す必要があります (Project_Set_Node 呼び出しの作成については、次のセクションで説明します)。

The parsing process is simplified thanks to a bunch of methods you can call from the token stream ($this->parser->getStream()):

トークン ストリーム ($this->parser->getStream()) から呼び出すことができる一連のメソッドのおかげで、解析プロセスが簡素化されます。
  • getCurrent(): Gets the current token in the stream.
    getCurrent(): ストリーム内の現在のトークンを取得します。
  • next(): Moves to the next token in the stream, but returns the old one.
    next(): ストリーム内の次のトークンに移動しますが、古いトークンを返します。
  • test($type), test($value) or test($type, $value): Determines whether the current token is of a particular type or value (or both). The value may be an array of several possible values.
    test($type)、test($value) または test($type, $value): 現在のトークンが特定のタイプまたは値 (またはその両方) であるかどうかを判断します。値は、複数の可能な値の配列である場合があります。
  • expect($type[, $value[, $message]]): If the current token isn't of the given type/value a syntax error is thrown. Otherwise, if the type and value are correct, the token is returned and the stream moves to the next token.
    expect($type[, $value[, $message]]): 現在のトークンが指定されたタイプ/値でない場合、構文エラーがスローされます。それ以外の場合、型と値が正しい場合、トークンが返され、ストリームは次のトークンに移動します。
  • look(): Looks at the next token without consuming it.
    look(): 消費せずに次のトークンを調べます。

Parsing expressions is done by calling the parseExpression() like we did for the set tag.

式の解析は、 set タグの場合と同様に parseExpression() を呼び出すことによって行われます。

Tip

ヒント

Reading the existing TokenParser classes is the best way to learn all the nitty-gritty details of the parsing process.

既存の TokenParser クラスを読むことは、解析プロセスのすべての核心を学ぶ最良の方法です。

Defining a Node

The Project_Set_Node class itself is quite short:

Project_Set_Node クラス自体は非常に短いです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Project_Set_Node extends \Twig\Node\Node
{
    public function __construct($name, \Twig\Node\Expression\AbstractExpression $value, $line, $tag = null)
    {
        parent::__construct(['value' => $value], ['name' => $name], $line, $tag);
    }

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

The compiler implements a fluid interface and provides methods that help the developer generate beautiful and readable PHP code:

コンパイラは流動的なインターフェイスを実装し、開発者が美しく読みやすい PHP コードを生成するのに役立つメソッドを提供します。
  • subcompile(): Compiles a node.
    subcompile(): ノードをコンパイルします。
  • raw(): Writes the given string as is.
    raw(): 指定された文字列をそのまま書き込みます。
  • write(): Writes the given string by adding indentation at the beginning of each line.
    write(): 各行の先頭にインデントを追加して、指定された文字列を書き込みます。
  • string(): Writes a quoted string.
    string(): 引用符で囲まれた文字列を書き込みます。
  • repr(): Writes a PHP representation of a given value (see \Twig\Node\ForNode for a usage example).
    repr(): 指定された値の PHP 表現を書き込みます (使用例については、\Twig\Node\ForNode を参照してください)。
  • addDebugInfo(): Adds the line of the original template file related to the current node as a comment.
    addDebugInfo(): 現在のノードに関連する元のテンプレート ファイルの行をコメントとして追加します。
  • indent(): Indents the generated code (see \Twig\Node\BlockNode for a usage example).
    indent(): 生成されたコードをインデントします (使用例については \Twig\Node\BlockNode を参照してください)。
  • outdent(): Outdents the generated code (see \Twig\Node\BlockNode for a usage example).
    outdent(): 生成されたコードをアウトデントします (使用例については \Twig\Node\BlockNode を参照してください)。

Creating an Extension

The main motivation for writing an extension is to move often used code into a reusable class like adding support for internationalization. An extension can define tags, filters, tests, operators, functions, and node visitors.

拡張機能を作成する主な動機は、国際化のサポートを追加するなど、頻繁に使用されるコードを再利用可能なクラスに移動することです。拡張機能では、タグ、フィルター、テスト、演算子、関数、およびノー​​ド ビジターを定義できます。

Most of the time, it is useful to create a single extension for your project, to host all the specific tags and filters you want to add to Twig.

ほとんどの場合、Twig に追加したいすべての特定のタグとフィルターをホストするために、プロジェクト用の単一の拡張機能を作成すると便利です。

Tip

ヒント

When packaging your code into an extension, Twig is smart enough to recompile your templates whenever you make a change to it (when auto_reload is enabled).

コードを拡張機能にパッケージ化する場合、Twig はテンプレートに変更を加えるたびに (auto_reload が有効な場合)、テンプレートを再コンパイルするほどスマートです。

An extension is a class that implements the following interface:

拡張機能は、次のインターフェイスを実装するクラスです。
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();
}

To keep your extension class clean and lean, inherit from the built-in \Twig\Extension\AbstractExtension class instead of implementing the interface as it provides empty implementations for all methods:

拡張クラスをクリーンで無駄のない状態に保つには、すべてのメソッドに空の実装を提供するインターフェイスを実装する代わりに、ビルトイン\Twig\Extension\AbstractExtension クラスから継承します。
1
2
3
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
}

This extension does nothing for now. We will customize it in the next sections.

この拡張機能は今のところ何もしません。次のセクションでカスタマイズします。

You can save your extension anywhere on the filesystem, as all extensions must be registered explicitly to be available in your templates.

テンプレートで使用できるようにするには、すべての拡張機能を明示的に登録する必要があるため、ファイルシステムのどこにでも拡張機能を保存できます。

You can register an extension by using the addExtension() method on your main Environment object:

メインの Environment オブジェクトで addExtension() メソッドを使用して、拡張機能を登録できます。
1
2
$twig = new \Twig\Environment($loader);
$twig->addExtension(new Project_Twig_Extension());

Tip

ヒント

The Twig core extensions are great examples of how extensions work.

Twig コア拡張機能は、拡張機能がどのように機能するかを示す好例です。

Globals

Global variables can be registered in an extension via the getGlobals() method:

グローバル変数は、getGlobals() メソッドを介して拡張機能に登録できます。
1
2
3
4
5
6
7
8
9
10
11
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension implements \Twig\Extension\GlobalsInterface
{
    public function getGlobals(): array
    {
        return [
            'text' => new Text(),
        ];
    }

    // ...
}

Functions

Functions can be registered in an extension via the getFunctions() method:

関数は、getFunctions() メソッドを介して拡張機能に登録できます。
1
2
3
4
5
6
7
8
9
10
11
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getFunctions()
    {
        return [
            new \Twig\TwigFunction('lipsum', 'generate_lipsum'),
        ];
    }

    // ...
}

Filters

To add a filter to an extension, you need to override the getFilters() method. This method must return an array of filters to add to the Twig environment:

拡張機能にフィルターを追加するには、getFilters() メソッドをオーバーライドする必要があります。このメソッドは、Twigenvironment に追加するフィルターの配列を返す必要があります。
1
2
3
4
5
6
7
8
9
10
11
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getFilters()
    {
        return [
            new \Twig\TwigFilter('rot13', 'str_rot13'),
        ];
    }

    // ...
}

Tags

Adding a tag in an extension can be done by overriding the getTokenParsers() method. This method must return an array of tags to add to the Twig environment:

拡張機能にタグを追加するには、getTokenParsers() メソッドをオーバーライドします。このメソッドは、Twig 環境に追加するタグの配列を返す必要があります。
1
2
3
4
5
6
7
8
9
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getTokenParsers()
    {
        return [new Project_Set_TokenParser()];
    }

    // ...
}

In the above code, we have added a single new tag, defined by the Project_Set_TokenParser class. The Project_Set_TokenParser class is responsible for parsing the tag and compiling it to PHP.

上記のコードでは、Project_Set_TokenParser クラスによって定義された単一の新しいタグを追加しました。 Project_Set_TokenParser クラスは、タグの解析と PHP へのコンパイルを担当します。

Operators

The getOperators() methods lets you add new operators. Here is how to add the !, ||, and && operators:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getOperators()
    {
        return [
            [
                '!' => ['precedence' => 50, 'class' => \Twig\Node\Expression\Unary\NotUnary::class],
            ],
            [
                '||' => ['precedence' => 10, 'class' => \Twig\Node\Expression\Binary\OrBinary::class, 'associativity' => \Twig\ExpressionParser::OPERATOR_LEFT],
                '&&' => ['precedence' => 15, 'class' => \Twig\Node\Expression\Binary\AndBinary::class, 'associativity' => \Twig\ExpressionParser::OPERATOR_LEFT],
            ],
        ];
    }

    // ...
}

Tests

The getTests() method lets you add new test functions:

getTests() メソッドを使用すると、新しいテスト関数を追加できます。
1
2
3
4
5
6
7
8
9
10
11
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getTests()
    {
        return [
            new \Twig\TwigTest('even', 'twig_test_even'),
        ];
    }

    // ...
}

Definition vs Runtime

Twig filters, functions, and tests runtime implementations can be defined as any valid PHP callable:

Twig のフィルター、関数、およびテストのランタイム実装は、任意の有効な PHP 呼び出し可能オブジェクトとして定義できます。
  • functions/static methods: Simple to implement and fast (used by all Twig core extensions); but it is hard for the runtime to depend on external objects;
    関数/静的メソッド: 実装が簡単で高速です (すべての Twigcore 拡張機能で使用されます)。しかし、ランタイムが外部オブジェクトに依存するのは困難です。
  • closures: Simple to implement;
    クロージャー: 実装が簡単。
  • object methods: More flexible and required if your runtime code depends on external objects.
    オブジェクト メソッド: ランタイム コードが外部オブジェクトに依存している場合は、より柔軟で必要です。

The simplest way to use methods is to define them on the extension itself:

メソッドを使用する最も簡単な方法は、拡張機能自体でメソッドを定義することです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Project_Twig_Extension 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);
    }
}

This is very convenient but not recommended as it makes template compilation depend on runtime dependencies even if they are not needed (think for instance as a dependency that connects to a database engine).

これは非常に便利ですが、テンプレートのコンパイルが実行時の依存関係に依存するようになるため (データベース エンジンに接続する依存関係などを考えてください)、必要がない場合でも推奨されません。

You can decouple the extension definitions from their runtime implementations by registering a \Twig\RuntimeLoader\RuntimeLoaderInterface instance on the environment that knows how to instantiate such runtime classes (runtime classes must be autoload-able):

そのようなランタイム クラスをインスタンス化する方法を知っている環境に \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 ('Project_Twig_RuntimeExtension' === $class) {
            return new $class(new Rot13Provider());
        } else {
            // ...
        }
    }
}

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

Note

ノート

Twig comes with a PSR-11 compatible runtime loader (\Twig\RuntimeLoader\ContainerRuntimeLoader).

Twig には、PSR-11 互換のランタイム ローダー (\Twig\RuntimeLoader\ContainerRuntimeLoader) が付属しています。

It is now possible to move the runtime logic to a new Project_Twig_RuntimeExtension class and use it directly in the extension:

ランタイム ロジックを newProject_Twig_RuntimeExtension クラスに移動し、拡張機能で直接使用できるようになりました。
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 Project_Twig_RuntimeExtension
{
    private $rot13Provider;

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

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

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

Testing an Extension

Functional Tests

You can create functional tests for extensions by creating the following file structure in your test directory:

テスト ディレクトリに次のファイル構造を作成することで、拡張機能の機能テストを作成できます。
1
2
3
4
5
6
7
8
9
10
11
Fixtures/
    filters/
        foo.test
        bar.test
    functions/
        foo.test
        bar.test
    tags/
        foo.test
        bar.test
IntegrationTest.php

The IntegrationTest.php file should look like this:

IntegrationTest.php ファイルは次のようになります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use Twig\Test\IntegrationTestCase;

class Project_Tests_IntegrationTest extends IntegrationTestCase
{
    public function getExtensions()
    {
        return [
            new Project_Twig_Extension1(),
            new Project_Twig_Extension2(),
        ];
    }

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

Fixtures examples can be found within the Twig repository tests/Twig/Fixtures directory.

フィクスチャの例は、Twig リポジトリtests/Twig/Fixtures ディレクトリにあります。

Node Tests

Testing the node visitors can be complex, so extend your test cases from \Twig\Test\NodeTestCase. Examples can be found in the Twig repository tests/Twig/Node directory.

ノード ビジターのテストは複雑になる可能性があるため、\Twig\Test\NodeTestCase からテスト ケースを拡張します。例は、Twig リポジトリのtests/Twig/Node ディレクトリにあります。