Recipes

Displaying Deprecation Notices

Deprecated features generate deprecation notices (via a call to the trigger_error() PHP function). By default, they are silenced and never displayed nor logged.

非推奨の機能は、非推奨の通知を生成します (trigger_error() PHP 関数の呼び出しを介して)。デフォルトでは、それらは無音であり、表示も記録もされません。

To remove all deprecated feature usages from your templates, write and run a script along the lines of the following:

テンプレートから非推奨の機能の使用をすべて削除するには、次の行に沿って ascript を記述して実行します。
1
2
3
4
5
6
7
require_once __DIR__.'/vendor/autoload.php';

$twig = create_your_twig_env();

$deprecations = new \Twig\Util\DeprecationCollector($twig);

print_r($deprecations->collectDir(__DIR__.'/templates'));

The collectDir() method compiles all templates found in a directory, catches deprecation notices, and return them.

collectDir() メソッドは、ディレクトリで見つかったすべてのテンプレートをコンパイルし、非推奨の通知をキャッチして返します。

Tip

ヒント

If your templates are not stored on the filesystem, use the collect() method instead. collect() takes a Traversable which must return template names as keys and template contents as values (as done by \Twig\Util\TemplateDirIterator).

テンプレートがファイルシステムに保存されていない場合は、代わりに collect() メソッドを使用してください。 collect() は Traversable を取り、テンプレート名をキーとして、テンプレートの内容を値として返す必要があります (\Twig\Util\TemplateDirIterator によって行われるように)。

However, this code won't find all deprecations (like using deprecated some Twig classes). To catch all notices, register a custom error handler like the one below:

ただし、このコードはすべての非推奨を見つけるわけではありません (非推奨の Twigclass を使用するなど)。すべての通知をキャッチするには、次のようなカスタム エラー ハンドラを登録します。
1
2
3
4
5
6
7
8
9
10
$deprecations = [];
set_error_handler(function ($type, $msg) use (&$deprecations) {
    if (E_USER_DEPRECATED === $type) {
        $deprecations[] = $msg;
    }
});

// run your application

print_r($deprecations);

Note that most deprecation notices are triggered during compilation, so they won't be generated when templates are already cached.

ほとんどの非推奨通知はコンパイル中にトリガーされるため、テンプレートが既にキャッシュされている場合は生成されないことに注意してください。

Tip

ヒント

If you want to manage the deprecation notices from your PHPUnit tests, have a look at the symfony/phpunit-bridge package, which eases the process.

PHPUnit テストからの廃止通知を管理したい場合は、プロセスを容易にする symfony/phpunit-bridge パッケージを見てください。

Making a Layout conditional

Working with Ajax means that the same content is sometimes displayed as is, and sometimes decorated with a layout. As Twig layout template names can be any valid expression, you can pass a variable that evaluates to true when the request is made via Ajax and choose the layout accordingly:

Ajax を使用すると、同じコンテンツがそのまま表示される場合もあれば、レイアウトで装飾される場合もあります。 Twig レイアウト テンプレート名は任意の有効な式にすることができるため、リクエストが Ajax 経由で行われたときに true と評価される変数を渡し、それに応じてレイアウトを選択できます。
1
2
3
4
5
{% extends request.ajax ? "base_ajax.html" : "base.html" %}

{% block content %}
    This is the content to be displayed.
{% endblock %}

Making an Include dynamic

When including a template, its name does not need to be a string. For instance, the name can depend on the value of a variable:

テンプレートを含める場合、その名前は文字列である必要はありません。たとえば、名前は変数の値に依存する場合があります。
1
{% include var ~ '_foo.html' %}

If var evaluates to index, the index_foo.html template will be rendered.

var が index と評価される場合、index_foo.html テンプレートがレンダリングされます。

As a matter of fact, the template name can be any valid expression, such as the following:

実際のところ、テンプレート名は、次のような有効な式にすることができます。
1
{% include var|default('index') ~ '_foo.html' %}

Overriding a Template that also extends itself

A template can be customized in two different ways:

テンプレートは、次の 2 つの方法でカスタマイズできます。
  • Inheritance: A template extends a parent template and overrides some blocks;
    継承: テンプレートは親テンプレートを拡張し、一部のブロックをオーバーライドします。
  • Replacement: If you use the filesystem loader, Twig loads the first template it finds in a list of configured directories; a template found in a directory replaces another one from a directory further in the list.
    置換: ファイルシステム ローダーを使用する場合、Twig は構成されたディレクトリのリストで最初に見つけたテンプレートを読み込みます。ディレクトリで見つかったテンプレートは、リスト内のさらに先のディレクトリから別のテンプレートを置き換えます。

But how do you combine both: replace a template that also extends itself (aka a template in a directory further in the list)?

しかし、両方をどのように組み合わせるのですか? それ自体も拡張するテンプレート (つまり、リストのさらに先のディレクトリにあるテンプレート) を置き換えますか?

Let's say that your templates are loaded from both .../templates/mysite and .../templates/default in this order. The page.twig template, stored in .../templates/default reads as follows:

テンプレートが .../templates/mysite と .../templates/default の両方からこの順序で読み込まれるとします。 .../templates/default に保存されている page.twig テンプレートは次のようになります。
1
2
3
4
5
{# page.twig #}
{% extends "layout.twig" %}

{% block content %}
{% endblock %}

You can replace this template by putting a file with the same name in .../templates/mysite. And if you want to extend the original template, you might be tempted to write the following:

このテンプレートは、.../templates/mysite に同じ名前のファイルを置くことで置き換えることができます。元のテンプレートを拡張したい場合は、次のように書きたくなるかもしれません。
1
2
{# page.twig in .../templates/mysite #}
{% extends "page.twig" %} {# from .../templates/default #}

However, this will not work as Twig will always load the template from .../templates/mysite.

ただし、Twig は常に.../templates/mysite からテンプレートをロードするため、これは機能しません。

It turns out it is possible to get this to work, by adding a directory right at the end of your template directories, which is the parent of all of the other directories: .../templates in our case. This has the effect of making every template file within our system uniquely addressable. Most of the time you will use the "normal" paths, but in the special case of wanting to extend a template with an overriding version of itself we can reference its parent's full, unambiguous template path in the extends tag:

テンプレートディレクトリの最後にディレクトリを追加することで、これを機能させることができることがわかりました。これは、他のすべてのディレクトリの親です: この場合は .../templates です。これにより、システム内のすべてのテンプレート ファイルを一意にアドレス指定できるようになります。ほとんどの場合、「通常の」パスを使用しますが、それ自体のオーバーライド バージョンでテンプレートを拡張したいという特別なケースでは、 extends タグでその親の完全で明確なテンプレート パスを参照できます。
1
2
{# page.twig in .../templates/mysite #}
{% extends "default/page.twig" %} {# from .../templates #}

Note

ノート

This recipe was inspired by the following Django wiki page: https://code.djangoproject.com/wiki/ExtendingTemplates

このレシピは、次の Django wiki ページに触発されました:https://code.djangoproject.com/wiki/ExtendingTemplates

Customizing the Syntax

Twig allows some syntax customization for the block delimiters. It's not recommended to use this feature as templates will be tied with your custom syntax. But for specific projects, it can make sense to change the defaults.

Twig では、ブロック区切り文字の構文をカスタマイズできます。テンプレートが customsyntax に関連付けられるため、この機能を使用することはお勧めしません。ただし、特定のプロジェクトでは、デフォルトを変更することが理にかなっている場合があります。

To change the block delimiters, you need to create your own lexer object:

ブロック区切り文字を変更するには、独自のレクサー オブジェクトを作成する必要があります。
1
2
3
4
5
6
7
8
9
$twig = new \Twig\Environment(...);

$lexer = new \Twig\Lexer($twig, [
    'tag_comment'   => ['{#', '#}'],
    'tag_block'     => ['{%', '%}'],
    'tag_variable'  => ['{{', '}}'],
    'interpolation' => ['#{', '}'],
]);
$twig->setLexer($lexer);

Here are some configuration example that simulates some other template engines syntax:

他のテンプレート エンジンの構文をシミュレートする設定例を次に示します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Ruby erb syntax
$lexer = new \Twig\Lexer($twig, [
    'tag_comment'  => ['<%#', '%>'],
    'tag_block'    => ['<%', '%>'],
    'tag_variable' => ['<%=', '%>'],
]);

// SGML Comment Syntax
$lexer = new \Twig\Lexer($twig, [
    'tag_comment'  => ['<!--#', '-->'],
    'tag_block'    => ['<!--', '-->'],
    'tag_variable' => ['${', '}'],
]);

// Smarty like
$lexer = new \Twig\Lexer($twig, [
    'tag_comment'  => ['{*', '*}'],
    'tag_block'    => ['{', '}'],
    'tag_variable' => ['{$', '}'],
]);

Using dynamic Object Properties

When Twig encounters a variable like article.title, it tries to find a title public property in the article object.

Twig が article.title のような変数に遭遇すると、article オブジェクトで atitle パブリック プロパティを見つけようとします。

It also works if the property does not exist but is rather defined dynamically thanks to the magic __get() method; you need to also implement the __isset() magic method like shown in the following snippet of code:

プロパティが存在しない場合でも機能しますが、魔法の __get() メソッドのおかげで動的に定義されます。次のコード スニペットに示すように、__isset() マジック メソッドも実装する必要があります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Article
{
    public function __get($name)
    {
        if ('title' == $name) {
            return 'The title';
        }

        // throw some kind of error
    }

    public function __isset($name)
    {
        if ('title' == $name) {
            return true;
        }

        return false;
    }
}

Accessing the parent Context in Nested Loops

Sometimes, when using nested loops, you need to access the parent context. The parent context is always accessible via the loop.parent variable. For instance, if you have the following template data:

ネストされたループを使用する場合、親コンテキストにアクセスする必要がある場合があります。親コンテキストには、常に loop.parent 変数を介してアクセスできます。たとえば、次のテンプレート データがあるとします。
1
2
3
4
5
6
$data = [
    'topics' => [
        'topic1' => ['Message 1 of topic 1', 'Message 2 of topic 1'],
        'topic2' => ['Message 1 of topic 2', 'Message 2 of topic 2'],
    ],
];

And the following template to display all messages in all topics:

そして、すべてのトピックのすべてのメッセージを表示する次のテンプレート:
1
2
3
4
5
6
{% for topic, messages in topics %}
    * {{ loop.index }}: {{ topic }}
  {% for message in messages %}
      - {{ loop.parent.loop.index }}.{{ loop.index }}: {{ message }}
  {% endfor %}
{% endfor %}

The output will be similar to:

出力は次のようになります。
1
2
3
4
5
6
* 1: topic1
  - 1.1: The message 1 of topic 1
  - 1.2: The message 2 of topic 1
* 2: topic2
  - 2.1: The message 1 of topic 2
  - 2.2: The message 2 of topic 2

In the inner loop, the loop.parent variable is used to access the outer context. So, the index of the current topic defined in the outer for loop is accessible via the loop.parent.loop.index variable.

内側のループでは、 loop.parent 変数を使用して外側のコンテキストにアクセスします。したがって、外側の for ループで定義された現在のトピックのインデックスは、 loop.parent.loop.index 変数を介してアクセスできます。

Defining undefined Functions, Filters, and Tags on the Fly

3.2

3.2

The registerUndefinedTokenParserCallback() method was added in Twig 3.2.

registerUndefinedTokenParserCallback() メソッドは Twig3.2 で追加されました。

When a function/filter/tag is not defined, Twig defaults to throw a \Twig\Error\SyntaxError exception. However, it can also call a callback (any valid PHP callable) which should return a function/filter/tag.

関数/フィルター/タグが定義されていない場合、Twig はデフォルトで \Twig\Error\SyntaxError 例外をスローします。ただし、関数/フィルター/タグを返すコールバック (任意の有効な PHP 呼び出し可能オブジェクト) を呼び出すこともできます。

For tags, register callbacks with registerUndefinedTokenParserCallback(). For filters, register callbacks with registerUndefinedFilterCallback(). For functions, use registerUndefinedFunctionCallback():

タグの場合は、registerUndefinedTokenParserCallback() でコールバックを登録します。フィルターの場合は、registerUndefinedFilterCallback() でコールバックを登録します。関数の場合は、registerUndefinedFunctionCallback() を使用します。
1
2
3
4
5
6
7
8
9
// auto-register all native PHP functions as Twig functions
// NEVER do this in a project as it's NOT secure
$twig->registerUndefinedFunctionCallback(function ($name) {
    if (function_exists($name)) {
        return new \Twig\TwigFunction($name, $name);
    }

    return false;
});

If the callable is not able to return a valid function/filter/tag, it must return false.

callable が有効な関数/フィルター/タグを返すことができない場合は、false を返す必要があります。

If you register more than one callback, Twig will call them in turn until one does not return false.

複数のコールバックを登録すると、Twig はいずれかが false を返さなくなるまでそれらを順番に呼び出します。

Tip

ヒント

As the resolution of functions/filters/tags is done during compilation, there is no overhead when registering these callbacks.

関数/フィルター/タグの解決はコンパイル中に行われるため、これらのコールバックを登録する際のオーバーヘッドはありません。

Validating the Template Syntax

When template code is provided by a third-party (through a web interface for instance), it might be interesting to validate the template syntax before saving it. If the template code is stored in a $template variable, here is how you can do it:

テンプレート コードがサード パーティによって (たとえば Web インターフェースを介して) 提供された場合、保存する前にテンプレートの構文を検証すると興味深い場合があります。テンプレート コードが $template 変数に格納されている場合は、次のように実行できます。
1
2
3
4
5
6
7
try {
    $twig->parse($twig->tokenize(new \Twig\Source($template)));

    // the $template is valid
} catch (\Twig\Error\SyntaxError $e) {
    // $template contains one or more syntax errors
}

If you iterate over a set of files, you can pass the filename to the tokenize() method to get the filename in the exception message:

ファイルのセットを反復処理する場合、ファイル名を tokenize() メソッドに渡して、例外メッセージでファイル名を取得できます。
1
2
3
4
5
6
7
8
9
foreach ($files as $file) {
    try {
        $twig->parse($twig->tokenize(new \Twig\Source($template, $file->getFilename(), $file)));

        // the $template is valid
    } catch (\Twig\Error\SyntaxError $e) {
        // $template contains one or more syntax errors
    }
}

Note

ノート

This method won't catch any sandbox policy violations because the policy is enforced during template rendering (as Twig needs the context for some checks like allowed methods on objects).

テンプレートのレンダリング中にポリシーが適用されるため、このメソッドはサンドボックス ポリシー違反をキャッチしません (Twig は、オブジェクトの許可されたメソッドなどのいくつかのチェックにコンテキストを必要とするため)。

Refreshing modified Templates when OPcache is enabled

When using OPcache with opcache.validate_timestamps set to 0, Twig cache enabled and auto reload disabled, clearing the template cache won't update the cache.

opcache.validate_timestamps を 0 に設定して OPcache を使用している場合、Twig キャッシュが有効で自動リロードが無効になっている場合、templatecache をクリアしてもキャッシュは更新されません。

To get around this, force Twig to invalidate the bytecode cache:

これを回避するには、Twig に強制的にバイトコード キャッシュを無効にさせます。
1
2
3
4
$twig = new \Twig\Environment($loader, [
    'cache' => new \Twig\Cache\FilesystemCache('/some/cache/path', \Twig\Cache\FilesystemCache::FORCE_BYTECODE_INVALIDATION),
    // ...
]);

Reusing a stateful Node Visitor

When attaching a visitor to a \Twig\Environment instance, Twig uses it to visit all templates it compiles. If you need to keep some state information around, you probably want to reset it when visiting a new template.

ビジターを \Twig\Environment インスタンスにアタッチすると、Twig はそれを使用して、コンパイルするすべてのテンプレートにアクセスします。何らかの状態情報を保持する必要がある場合は、新しいテンプレートにアクセスするときにリセットすることをお勧めします。

This can be achieved with the following code:

これは、次のコードで実現できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
protected $someTemplateState = [];

public function enterNode(\Twig\Node\Node $node, \Twig\Environment $env)
{
    if ($node instanceof \Twig\Node\ModuleNode) {
        // reset the state as we are entering a new template
        $this->someTemplateState = [];
    }

    // ...

    return $node;
}

Using a Database to store Templates

If you are developing a CMS, templates are usually stored in a database. This recipe gives you a simple PDO template loader you can use as a starting point for your own.

CMS を開発している場合、通常、テンプレートはデータベースに保存されます。このレシピは、独自の開始点として使用できるシンプルな PDO テンプレート ローダーを提供します。

First, let's create a temporary in-memory SQLite3 database to work with:

まず、使用する一時的なメモリ内 SQLite3 データベースを作成しましょう。
1
2
3
4
5
6
7
8
9
10
$dbh = new PDO('sqlite::memory:');
$dbh->exec('CREATE TABLE templates (name STRING, source STRING, last_modified INTEGER)');
$base = '{% block content %}{% endblock %}';
$index = '
{% extends "base.twig" %}
{% block content %}Hello {{ name }}{% endblock %}
';
$now = time();
$dbh->prepare('INSERT INTO templates (name, source, last_modified) VALUES (?, ?, ?)')->execute(['base.twig', $base, $now]);
$dbh->prepare('INSERT INTO templates (name, source, last_modified) VALUES (?, ?, ?)')->execute(['index.twig', $index, $now]);

We have created a simple templates table that hosts two templates: base.twig and index.twig.

base.twig と index.twig の 2 つのテンプレートをホストする単純なテンプレート テーブルを作成しました。

Now, let's define a loader able to use this database:

それでは、このデータベースを使用できるローダーを定義しましょう。
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
45
class DatabaseTwigLoader implements \Twig\Loader\LoaderInterface
{
    protected $dbh;

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

    public function getSourceContext(string $name): Source
    {
        if (false === $source = $this->getValue('source', $name)) {
            throw new \Twig\Error\LoaderError(sprintf('Template "%s" does not exist.', $name));
        }

        return new \Twig\Source($source, $name);
    }

    public function exists(string $name)
    {
        return $name === $this->getValue('name', $name);
    }

    public function getCacheKey(string $name): string
    {
        return $name;
    }

    public function isFresh(string $name, int $time): bool
    {
        if (false === $lastModified = $this->getValue('last_modified', $name)) {
            return false;
        }

        return $lastModified <= $time;
    }

    protected function getValue($column, $name)
    {
        $sth = $this->dbh->prepare('SELECT '.$column.' FROM templates WHERE name = :name');
        $sth->execute([':name' => (string) $name]);

        return $sth->fetchColumn();
    }
}

Finally, here is an example on how you can use it:

最後に、使用方法の例を次に示します。
1
2
3
4
$loader = new DatabaseTwigLoader($dbh);
$twig = new \Twig\Environment($loader);

echo $twig->render('index.twig', ['name' => 'Fabien']);

Using different Template Sources

This recipe is the continuation of the previous one. Even if you store the contributed templates in a database, you might want to keep the original/base templates on the filesystem. When templates can be loaded from different sources, you need to use the \Twig\Loader\ChainLoader loader.

このレシピは前回の続きです。提供されたテンプレートをデータベースに保存したとしても、オリジナル/ベース テンプレートをファイル システムに保持したい場合があります。テンプレートを異なるソースからロードできる場合は、\Twig\Loader\ChainLoader ローダーを使用する必要があります。

As you can see in the previous recipe, we reference the template in the exact same way as we would have done it with a regular filesystem loader. This is the key to be able to mix and match templates coming from the database, the filesystem, or any other loader for that matter: the template name should be a logical name, and not the path from the filesystem:

前のレシピでわかるように、通常のファイルシステム ローダーで行ったのとまったく同じ方法でテンプレートを参照します。これは、データベース、ファイルシステム、またはその他のローダーからのテンプレートを組み合わせて一致させるための鍵です。テンプレート名は、ファイルシステムからのパスではなく、論理的な名前にする必要があります。
1
2
3
4
5
6
7
8
9
$loader1 = new DatabaseTwigLoader($dbh);
$loader2 = new \Twig\Loader\ArrayLoader([
    'base.twig' => '{% block content %}{% endblock %}',
]);
$loader = new \Twig\Loader\ChainLoader([$loader1, $loader2]);

$twig = new \Twig\Environment($loader);

echo $twig->render('index.twig', ['name' => 'Fabien']);

Now that the base.twig templates is defined in an array loader, you can remove it from the database, and everything else will still work as before.

base.twig テンプレートが配列ローダーで定義されたので、それをデータベースから削除できます。他のすべては以前と同じように機能します。

Loading a Template from a String

From a template, you can load a template stored in a string via the template_from_string function (via the \Twig\Extension\StringLoaderExtension extension):

テンプレートから、template_from_string 関数を介して (\Twig\Extension\StringLoaderExtension 拡張を介して) 文字列に格納されたテンプレートをロードできます。
1
{{ include(template_from_string("Hello {{ name }}")) }}

From PHP, it's also possible to load a template stored in a string via \Twig\Environment::createTemplate():

PHP から、\Twig\Environment::createTemplate() 経由で文字列に格納されたテンプレートをロードすることもできます。
1
2
$template = $twig->createTemplate('hello {{ name }}');
echo $template->render(['name' => 'Fabien']);

Using Twig and AngularJS in the same Templates

Mixing different template syntaxes in the same file is not a recommended practice as both AngularJS and Twig use the same delimiters in their syntax: {{ and }}.

AngularJS と Twig の両方が構文で同じ区切り文字を使用するため、同じファイルに異なるテンプレート構文を混在させることはお勧めできません:{{ と }}。

Still, if you want to use AngularJS and Twig in the same template, there are two ways to make it work depending on the amount of AngularJS you need to include in your templates:

それでも、同じテンプレートで AngularJS と Twig を使用したい場合、テンプレートに含める必要がある AngularJS の量に応じて、それを機能させる 2 つの方法があります。
  • Escaping the AngularJS delimiters by wrapping AngularJS sections with the {% verbatim %} tag or by escaping each delimiter via {{ '{{' }} and {{ '}}' }};
    AngularJS セクションを {% verbatim %} タグでラップするか、{{ '{{' }} および {{ '}}' }} を介して各区切り記号をエスケープすることにより、AngularJS 区切り記号をエスケープします。
  • Changing the delimiters of one of the template engines (depending on which engine you introduced last):

    いずれかのテンプレート エンジンの区切り文字を変更する (最後に導入したエンジンに応じて):
    • For AngularJS, change the interpolation tags using the interpolateProvider service, for instance at the module initialization time:

      AngularJS の場合、たとえばモジュールの初期化時に、interpolateProvider サービスを使用して補間タグを変更します。
      angular.module('myApp', []).config(function($interpolateProvider) {
      $interpolateProvider.startSymbol('{[').endSymbol(']}');
      $interpolateProvider.startSymbol('{[').endSymbol(']}');

      });

      });
    • For Twig, change the delimiters via the tag_variable Lexer option:

      Twig の場合、tag_variable Lexer オプションを使用して区切り記号を変更します。
      1
      2
      3
      $env->setLexer(new \Twig\Lexer($env, [
          'tag_variable' => ['{[', ']}'],
      ]));