> For the complete documentation index, see [llms.txt](https://twig.shujuwajue.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://twig.shujuwajue.com/twig-2x/twig-fang-fa.md).

# 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('{[', ']}'),
)));
```
