# Twig 方法

> 原文：[Recipes](https://twig.sensiolabs.org/doc/2.x/recipes.html)\
> 翻译：小虾米（QQ:509129）

## Recipes

### 显示的通知

弃用特性生成弃用通知(通过调用trigger\_error()PHP函数)。默认情况下，它们是静默的，不会显示，也不会记录。

为了方便地从模板中删除所有已弃用的特性用法，请在下面的代码行中编写和运行一个脚本:

```php
require_once __DIR__.'/vendor/autoload.php';

$twig = create_your_twig_env();

$deprecations = new Twig_Util_DeprecationCollector($twig);

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

collectDir()方法编译目录中找到的所有模板，捕获弃用通知，并返回它们。

> 如果模板不存储在文件系统中，则使用collect()方法。collect()接受一个可遍历，它必须返回模板名称作为键，模板内容作为值(如Twig\_Util\_TemplateDirIterator)。

但是，这段代码不会找到所有的弃用(比如使用不赞成的一些Twig类)。要捕获所有通知，请注册一个类似下面的错误处理程序:

```php
require_once __DIR__.'/vendor/autoload.php';

$twig = create_your_twig_env();

$deprecations = new Twig_Util_DeprecationCollector($twig);

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

注意，在编译过程中会触发大多数的弃用通知，所以当模板已经被缓存时，它们不会生成。

> 如果您想从您的PHPUnit测试中管理弃用通知，请查看一下symfony / PHPUnit - bridge包，这将极大地简化过程。

做一个布局条件(Making a Layout conditional)

使用Ajax意味着相同的内容有时会显示为is，有时还会以布局来装饰。当Twig布局模板名称可以是任何有效的表达式时，您可以通过Ajax传递一个评估为true的变量，并据此选择布局:

```markup
{% extends request.ajax ? "base_ajax.html" : "base.html" %}

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

### 做一个动态包含（Making an Include dynamic）

当包含模板时，它的名称不需要是字符串。例如，名称可以依赖于变量的值:

```markup
{% include var ~ '_foo.html' %}
```

如果var的值是index，则是index\_foo。将呈现html模板。

事实上，模板名称可以是任何有效的表达式，例如以下内容:

```markup
{% include var|default('index') ~ '_foo.html' %}
```

### 重写一个扩展自身的模板（Overriding a Template that also extends itself）

模板可以以两种不同的方式定制:

* 继承:模板扩展了父模板，覆盖了一些块;
* 替换:如果您使用文件系统加载程序，Twig将加载在已配置目录列表中发现的第一个模板;在目录中发现的模板将从列表中进一步的目录中替代另一个模板。

但是，如何将二者结合在一起:替换一个扩展自身的模板(在目录中进一步的目录中)?

让我们假设您的模板从两个都加载../templates/mysite和.../templates/default按此顺序。该页面。将模板存储在.../templates/default读取模板如下:

```markup
{# page.twig #}
{% extends "layout.twig" %}

{% block content %}
{% endblock %}
```

您可以通过在 .../templates/mysite中放入相同名称的文件来替换这个模板。如果您想扩展原始模板，您可能会尝试编写以下内容:

```markup
{# page.twig in .../templates/mysite #}
{% extends "page.twig" %} {# from .../templates/default #}
```

当然，这不会起作用，因为Twig总是从.../templates/mysite 加载模板。

事实证明，这是可能的工作，通过添加一个目录在您的模板目录，这是所有其他目录的父目录：.../templates在我们的例子。这使得我们的系统中的每个模板文件唯一的寻址。大多数情况下，你将使用“正常”的路径，但在特殊情况下，要扩展的模板与压倒一切的版本本身，我们可以引用其父的完整，明确的模板路径的扩展标签：

```markup
{# page.twig in .../templates/mysite #}
{% extends "default/page.twig" %} {# from .../templates #}
```

> 这个recipe灵感来自以下Django wiki页面:<http://code.djangoproject.com/wiki/extendingtemplatez>

### 自定义语法（Customizing the Syntax）

Twig允许对块分隔符进行一些语法定制。不推荐使用此特性作为模板将与您的自定义语法绑定。但是对于特定的项目，更改默认值是有意义的。

要更改块分隔符，您需要创建自己的lexer对象:

```php
$twig = new Twig_Environment(...);

$lexer = new Twig_Lexer($twig, array(
    'tag_comment'   => array('{#', '#}'),
    'tag_block'     => array('{%', '%}'),
    'tag_variable'  => array('{{', '}}'),
    'interpolation' => array('#{', '}'),
));
$twig->setLexer($lexer);
```

下面是一些模拟其他模板引擎语法的配置示例:

```php
// Ruby erb syntax
$lexer = new Twig_Lexer($twig, array(
    'tag_comment'  => array('<%#', '%>'),
    'tag_block'    => array('<%', '%>'),
    'tag_variable' => array('<%=', '%>'),
));

// SGML Comment Syntax
$lexer = new Twig_Lexer($twig, array(
    'tag_comment'  => array('<!--#', '-->'),
    'tag_block'    => array('<!--', '-->'),
    'tag_variable' => array('${', '}'),
));

// Smarty like
$lexer = new Twig_Lexer($twig, array(
    'tag_comment'  => array('{*', '*}'),
    'tag_block'    => array('{', '}'),
    'tag_variable' => array('{$', '}'),
));
```

### 使用动态对象属性(Using dynamic Object Properties)

当Twig遇到像 article.title 这样的变量时，它试图在article对象中找到一个title公共属性。

如果属性不存在，它也可以工作，但是由于魔术方法\_\_get()方法动态地定义了属性，您只需要执行如下代码片段所示的\_\_isset()魔术方法:

```php
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）

有时，当使用嵌套循环时，您需要访问父级上下文。父上下文始终可以通过循环访问。父类变量。例如，如果您有以下模板数据:

```php
$data = array(
    'topics' => array(
        'topic1' => array('Message 1 of topic 1', 'Message 2 of topic 1'),
        'topic2' => array('Message 1 of topic 2', 'Message 2 of topic 2'),
    ),
);
```

下面的模板将显示所有主题中的所有消息:

```markup
{% for topic, messages in topics %}
    * {{ loop.index }}: {{ topic }}
  {% for message in messages %}
      - {{ loop.parent.loop.index }}.{{ loop.index }}: {{ message }}
  {% endfor %}
{% endfor %}
```

输出:

```markup
* 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
```

在内部循环中，loop.parent变量用于访问外部上下文。因此，在外部for循环中定义的当前topic的索引是通过 loop.parent.loop.index 变量。

### 定义未定义的函数和过滤器（Defining undefined Functions and Filters on the Fly）

当没有定义函数(或过滤器)时，Twig默认会抛出Twig\_Error\_Syntax异常。但是，它也可以调用回调(任何有效的PHP调用)，它应该返回一个函数(或过滤器)。

对于筛选器，使用registerUndefinedFilterCallback()注册回调。对于函数,使用registerUndefinedFunctionCallback():

```php
// auto-register all native PHP functions as Twig functions
// don't try this at home as it's not secure at all!
$twig->registerUndefinedFunctionCallback(function ($name) {
    if (function_exists($name)) {
        return new Twig_Function($name, $name);
    }

    return false;
});
```

如果callable无法返回有效的函数(或过滤器)，则必须返回false。

如果您注册多个回调，Twig将依次调用它们，直到其中一个不返回false。

> 由于函数和过滤器的解析是在编译期间完成的，所以在注册这些回调时没有开销。

### 验证模板语法(Validating the Template Syntax)

当模板代码由第三方提供时(例如通过web接口)，在保存模板语法之前，验证模板语法可能会很有趣。如果模板代码存储在$template变量中，那么您可以这样做:

```php
try {
    $twig->parse($twig->tokenize(new Twig_Source($template)));

    // the $template is valid
} catch (Twig_Error_Syntax $e) {
    // $template contains one or more syntax errors
}
```

如果您对一组文件进行迭代，可以将文件名传递给tokenize()方法以获取异常消息中的文件名:

```php
foreach ($files as $file) {
    try {
        $twig->parse($twig->tokenize(new Twig_Source($template, $file->getFilename(), $file)));

        // the $template is valid
    } catch (Twig_Error_Syntax $e) {
        // $template contains one or more syntax errors
    }
}
```

此方法不会捕获任何沙箱策略违规，因为在模板呈现期间执行策略(当Twig需要上下文来进行一些检查，比如对象允许的方法)。

### 启用OPcache或APC时刷新修改的模板(Refreshing modified Templates when OPcache or APC is enabled)

在使用OPcache时使用opcache.validate\_timestamps设置为0或APC的apc.stat设置为0并启用Twig缓存，清除模板缓存不会更新缓存。

为了解决这个问题，强制Twig使字节码缓存失效:

```php
$twig = new Twig_Environment($loader, array(
    'cache' => new Twig_Cache_Filesystem('/some/cache/path', Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION),
    // ...
));
```

### 重用有状态的节点访问者（Reusing a stateful Node Visitor）

当将访问者附加到Twig\_Environment实例时，Twig会使用它来访问它编译的所有模板。如果您需要保留一些状态信息，您可能希望在访问新模板时重新设置它。

这可以很容易地用以下代码实现:

```php
protected $someTemplateState = array();

public function enterNode(Twig_Node $node, Twig_Environment $env)
{
    if ($node instanceof Twig_Node_Module) {
        // reset the state as we are entering a new template
        $this->someTemplateState = array();
    }

    // ...

    return $node;
}
```

### 使用数据库存储模板（Using a Database to store Templates）

如果您正在开发CMS，模板通常存储在数据库中。这个配方提供了一个简单的PDO模板加载器，您可以将它用作您自己的起点。

首先，让我们创建一个用于工作的临时内存SQLite3数据库:

```php
$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->exec("INSERT INTO templates (name, source, last_modified) VALUES ('base.twig', '$base', $now)");
$dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('index.twig', '$index', $now)");
```

我们创建了一个简单的模板表，其中包含两个模板:base.twig 和 index.twig。

现在，让我们定义一个可以使用这个数据库的加载器:

```php
class DatabaseTwigLoader implements Twig_LoaderInterface
{
    protected $dbh;

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

    public function getSourceContext($name)
    {
        if (false === $source = $this->getValue('source', $name)) {
            throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
        }

        return new Twig_Source($source, $name);
    }

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

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

    public function isFresh($name, $time)
    {
        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(array(':name' => (string) $name));

        return $sth->fetchColumn();
    }
}
```

最后，这里有一个关于如何使用它的例子:

```php
$loader = new DatabaseTwigLoader($dbh);
$twig = new Twig_Environment($loader);

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

### 使用不同的模板来源（Using different Template Sources）

这个recipe是前一个的延续。即使您将贡献的模板存储在数据库中，您可能希望保留文件系统上的原始/基本模板。当模板可以从不同的源加载时，您需要使用Twig\_Loader\_Chain加载程序。

正如您在前面的recipe中看到的，我们以完全相同的方式引用模板，就像我们使用常规的文件系统加载程序那样。这是能够混合和匹配来自数据库、文件系统或任何其他加载器的模板的关键:模板名称应该是一个逻辑名称，而不是来自文件系统的路径:

```php
$loader1 = new DatabaseTwigLoader($dbh);
$loader2 = new Twig_Loader_Array(array(
    'base.twig' => '{% block content %}{% endblock %}',
));
$loader = new Twig_Loader_Chain(array($loader1, $loader2));

$twig = new Twig_Environment($loader);

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

现在 base.twig模板定义在一个数组加载器中，您可以从数据库中删除它，其他所有的内容仍然会像以前那样工作。

### 从字符串中加载模板（Loading a Template from a String）

从模板中，您可以轻松地通过template\_from\_string函数(通过Twig\_Extension\_StringLoader扩展)加载存储在字符串中的模板:

```markup
{{ include(template_from_string("Hello {{ name }}")) }}
```

从PHP中，还可以通过Twig\_Environment加载存储在字符串中的模板:createTemplate():

```markup
$template = $twig->createTemplate('hello {{ name }}');
echo $template->render(array('name' => 'Fabien'));
```

### 在同一个模板中使用Twig和AngularJS(Using Twig and AngularJS in the same Templates)

在同一个文件中混合不同的模板语法并不是一个推荐的做法，因为AngularJS和Twig在语法中使用相同的分隔符: }。

不过，如果您想在同一个模板中使用AngularJS和Twig，有两种方法可以使它工作，这取决于您需要在模板中包含的AngularJS的数量:

* 从AngularJS分隔符中，通过将AngularJS部分与{% verbatim %}标记或通过{{ '{{' }} 和 {{ '}}' }}来分隔每个分隔符;
* 更改一个模板引擎的分隔符(取决于您最后引入的引擎)
  * 对于AngularJS，使用插值提供服务来更改插值标记，例如在模块的初始化时间:

```javascript
angular.module('myApp', []).config(function($interpolateProvider) {
    $interpolateProvider.startSymbol('{[').endSymbol(']}');
});
```

* * 对于Twig，通过tag\_variable Lexer选项改变分隔符:

```php
$env->setLexer(new Twig_Lexer($env, array(
    'tag_variable' => array('{[', ']}'),
)));
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://twig.shujuwajue.com/twig-2x/twig-fang-fa.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
