# Twig for 模板设计师

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

## Twig for 模板设计师

本文档描述了模板引擎的语法和语义，对于创建Twig模板参考的用户将是最有用的。

### 简介

模板只是一个文本文件。它可以生成任何基于文本的格式(HTML、XML、CSV、乳胶等)。它没有特定的后缀，.html或 .xml 就很好。

模板包含**变量**或**表达式**，当模板被评估时，这些变量或表达式被值所替代，而**标签**则控制模板的逻辑。

下面是一个简单的模板，它演示了一些基本知识。稍后我们将进一步讨论:

```markup
<!DOCTYPE html>
<html>
    <head>
        <title>My Webpage</title>
    </head>
    <body>
        <ul id="navigation">
        {% for item in navigation %}
            <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
        {% endfor %}
        </ul>

        <h1>My Webpage</h1>
        {{ a_variable }}
    </body>
</html>
```

分隔符有两种:{ %…% }和{ {…} }。第一个用于执行语句，例如for循环，后者将表达式的结果输出到模板。

### IDEs 集成

许多 IDEs 支持对Twig的语法高亮和自动完成:

* Textmate通过[Twig bundle](https://github.com/Anomareh/PHP-Twig.tmbundle)
* Vim通过[Jinja语法插件](http://jinja.pocoo.org/docs/integration/#vim)或[vim-twig插件](https://github.com/lumiliet/vim-twig)
* Netbeans通过[Twig语法插件](http://plugins.netbeans.org/plugin/37069/php-twig)(直到7.1，本机为7.2)
* PhpStorm（本地2.1）
* Eclipse通过[Twig插件](https://github.com/pulse00/Twig-Eclipse-Plugin)
* Sublime Text 通过[Twig bundle](https://github.com/Anomareh/PHP-Twig.tmbundle)
* GtkSourceView 通过Twig语言定义 (由gedit和其他项目使用)
* Coda和SubEthaEdit通过[Twig语法模式](https://github.com/bobthecow/Twig-HTML.mode)
* Coda 2通过[其他的Twig语法模式](https://github.com/muxx/Twig-HTML.mode)
* Komodo和Komodo 编辑通过Twig高亮/语法检查模式
* _Notepad++_ 通过[Notepad++ Twig Highlighter](https://github.com/Banane9/notepadplusplus-twig)
* Emacs通过[web-mode.el](http://web-mode.org/)
* _Atom_ 通过 [PHP-twig for atom](https://github.com/reesef/php-twig)
* _Visual Studio Code_ 通过[Twig pack](https://marketplace.visualstudio.com/items?itemName=bajdzis.vscode-twig-pack)

### 变量(Variables)

应用程序将变量传递给模板中的操作模板。变量也可以访问属性或元素。变量的可视化表示依赖于提供它的应用程序。

您可以使用点(.)来访问变量的属性(PHP对象的方法或属性，或PHP数组的项)，或所谓的“下标”语法(\[]):

```markup
{{ foo.bar }}
{{ foo['bar'] }}
```

当属性包含特殊字符时(如-这将被解释为减号操作符)，则使用属性函数来访问变量属性:

```markup
{# equivalent to the non-working foo.data-foo #}
{{ attribute(foo, 'data-foo') }}
```

> 重要的是要知道，花括号不是变量的一部分，而是打印语句。当在标签内访问变量时，不要把括号放在它们周围。

如果变量或属性不存在，那么当 strict\_variables 选项设置为false时，您将收到一个null值;或者如果设置了strict\_variables，Twig将抛出一个错误(参见[环境选项](https://twig.sensiolabs.org/doc/2.x/api.html#environment-options))。

> #### 实现
>
> 为了方便 foo.bar 在 PHP 层做以下事情:
>
> * 检查foo是否为数组，bar是否是一个有效的元素;
> * 如果不是，如果foo是一个对象，检查bar是一个有效的属性;
> * 如果不是，如果foo是一个对象，那么检查bar是一个有效的方法(即使bar是构造函数)，使用\_\_construct()代替);
> * 如果不是，如果foo是对象，检查 getBar 是一个有效的方法;
> * 如果不是，如果foo是对象，检查 isBar 是一个有效的方法;
> * 如果不是，如果foo是对象，检查 hasBar 是一个有效的方法;
> * 如果没有，则返回null值。
>
> 另一方面，foo\[' bar ']只适用于PHP数组:
>
> * 检查foo是否为数组，bar是一个有效的元素
> * 如果没有，则返回null值。
>
> 如果要访问变量的动态属性，则使用[属性](https://twig.sensiolabs.org/doc/2.x/functions/attribute.html)函数。

### 全局变量(Global Variables)

以下变量总是在模板中可用:

* \_self:引用当前的模板名称;
* \_context:引用当前上下文;
* \_charset:引用当前的字符集。

### 设置变量(Setting Variables)

您可以为代码块中的变量赋值。使用set标签:

```markup
{% set foo = 'foo' %}
{% set foo = [1, 2] %}
{% set foo = {'foo': 'bar'} %}
```

### 过滤器(Filters)

变量可以通过过滤器来修改。通过管道符号(|)将过滤器与变量分隔开，在括号中可以有可选的参数。多个过滤器可以被链式。一个过滤器的输出应用于下一个过滤器。

下面的示例从名称和标题示例中删除所有HTML标记:

```markup
{{ name|striptags|title }}
```

接受参数的过滤器在参数周围有圆括号。本例将以逗号加入列表:

```markup
{{ list|join(', ') }}
```

要在一段代码中应用一个过滤器，将它包装在[过滤](https://twig.sensiolabs.org/doc/2.x/tags/filter.html)标签中:

```markup
{% filter upper %}
    This text becomes uppercase
{% endfilter %}
```

进入[过滤器](https://twig.sensiolabs.org/doc/2.x/filters/index.html)页面，了解更多关于内置过滤器的信息。

### 函数(Functions)

可以调用函数来生成内容。函数由名称调用，后面是括号(())，可能有参数。

例如，range函数返回一个包含整数算术级数的列表:

```markup
{% for i in range(0, 3) %}
    {{ i }},
{% endfor %}
```

访问[函数](https://twig.sensiolabs.org/doc/2.x/functions/index.html)页面，了解更多关于内置函数的信息。

### 命名参数(Named Arguments)

```markup
{% for i in range(low=1, high=10, step=2) %}
    {{ i }},
{% endfor %}
```

使用命名参数可以使您的模板更明确地说明您作为参数传递的值的含义:

```markup
{{ data|convert_encoding('UTF-8', 'iso-2022-jp') }}

{# versus #}

{{ data|convert_encoding(from='iso-2022-jp', to='UTF-8') }}
```

命名参数还允许您跳过一些您不想更改默认值的参数:

```markup
{# the first argument is the date format, which defaults to the global date format if null is passed #}
{{ "now"|date(null, "Europe/Paris") }}

{# or skip the format value by using a named argument for the time zone #}
{{ "now"|date(timezone="Europe/Paris") }}
```

您还可以在一个调用中使用位置和命名参数，在这种情况下，位置参数必须在命名参数之前出现:

```
{{ "now"|date('d/m/Y H:i', timezone="Europe/Paris") }}
```

> 每个函数和过滤器文档页都有一个部分，当支持时列出所有参数的名称。

### 控制结构(Control Structure)

控制结构指的是控制程序流的所有这些东西——条件(如if / else if / else)、for循环以及诸如块之类的东西。控制结构出现在{ %…% }块。

例如，要显示在名为users的变量中提供的用户列表，请使用[for](https://twig.sensiolabs.org/doc/2.x/tags/for.html)标记:

```markup
<h1>Members</h1>
<ul>
    {% for user in users %}
        <li>{{ user.username|e }}</li>
    {% endfor %}
</ul>
```

[if](https://twig.sensiolabs.org/doc/2.x/tags/if.html)标签可以用来测试表达式:

```markup
{% if users|length > 0 %}
    <ul>
        {% for user in users %}
            <li>{{ user.username|e }}</li>
        {% endfor %}
    </ul>
{% endif %}
```

到[标签](https://twig.sensiolabs.org/doc/2.x/tags/index.html)页学习更多关于内置标签的信息。

### 注解(Comments)

要在模板中引用一行，请使用注释语法{ #…# }。这对于调试或为其他模板设计人员或您自己添加信息非常有用:

```markup
{# note: disabled template because we no longer use this
    {% for user in users %}
        ...
    {% endfor %}
#}
```

### 包括其他模板(Including other Templates)

[include](https://twig.sensiolabs.org/doc/2.x/functions/include.html)函数用于包含模板，并将该模板的呈现内容返回当前的模板:

```markup
{{ include('sidebar.html') }}
```

默认情况下，包括模板可以访问与包含它们的模板相同的上下文。这意味着主模板中定义的任何变量都可以在包含的模板中使用:

```markup
{% for box in boxes %}
    {{ include('render_box.html') }}
{% endfor %}
```

包含的模板render\_box.html能够访问box变量。

模板的文件名取决于模板加载程序。例如，Twig\_Loader\_Filesystem允许您通过提供文件名来访问其他模板。您可以在子目录中使用斜线来访问模板:

```markup
{{ include('sections/articles/sidebar.html') }}
```

这种行为依赖于应用嵌入Twig。

### 模板继承(Template Inheritance)

Twig最强大的部分是模板继承。模板继承允许您构建包含站点所有常见元素的基本“骨架”模板，并定义子模板可以覆盖的块。

听起来很复杂，但是很基础。从一个例子开始就更容易理解。

我们来定义一个基模板，base.html，它定义了一个简单的html框架文档，可以用在简单的两栏页面上:

```markup
<!DOCTYPE html>
<html>
    <head>
        {% block head %}
            <link rel="stylesheet" href="style.css" />
            <title>{% block title %}{% endblock %} - My Webpage</title>
        {% endblock %}
    </head>
    <body>
        <div id="content">{% block content %}{% endblock %}</div>
        <div id="footer">
            {% block footer %}
                &copy; Copyright 2011 by <a href="http://domain.invalid/">you</a>.
            {% endblock %}
        </div>
    </body>
</html>
```

在本例中，[块](https://twig.sensiolabs.org/doc/2.x/tags/block.html)标记定义子模板可以填充的四个块。所有的块标记都是告诉模板引擎，子模板可以覆盖模板的那些部分。

子模板可能是这样的:

```markup
{% extends "base.html" %}

{% block title %}Index{% endblock %}
{% block head %}
    {{ parent() }}
    <style type="text/css">
        .important { color: #336699; }
    </style>
{% endblock %}
{% block content %}
    <h1>Index</h1>
    <p class="important">
        Welcome to my awesome homepage.
    </p>
{% endblock %}
```

[扩展](https://twig.sensiolabs.org/doc/2.x/tags/extends.html)标签是这里的关键。它告诉模板引擎这个模板“扩展”了另一个模板。当模板系统评估这个模板时，首先它定位父级。扩展标记应该是模板中的第一个标记。

请注意，由于子模板没有定义页脚块，因此可以使用来自父模板的值。

可以使用[父](https://twig.sensiolabs.org/doc/2.x/functions/parent.html)函数来呈现父块的内容。这就回馈了父块的结果:

```markup
{% block sidebar %}
    <h3>Table Of Contents</h3>
    ...
    {{ parent() }}
{% endblock %}
```

> [扩展](https://twig.sensiolabs.org/doc/2.x/tags/extends.html)标签的文档页面描述了更高级的功能，如块嵌套、范围、动态继承和条件继承。
>
> Twig在使用标签的帮助下也支持多个继承，即所谓的水平重用。这是在常规模板中几乎不需要的高级特性。

### HTML转义(HTML Escaping)

当从模板生成HTML时，总是有一个风险，变量将包括影响生成HTML的字符。有两种方法:手动转义每个变量，或者默认情况下自动转义。

Twig支持这两种，自动转义是默认启用的。

自动转义策略可以通过[autoescape](https://twig.sensiolabs.org/doc/2.x/api.html#environment-options)选项和默认的html来配置。

#### 使用手动转义

如果手动转移已启用，如果需要转义变量这是您的责任。转义什么？任何你不信任的变量。

通过escape或e过滤器将变量通过管道进行转义:

```markup
{{ user.username|e }}
```

默认情况下，escape过滤器使用html策略，但是根据转义上下文，您可能想要显式地使用其他可用策略:

```markup
{{ user.username|e('js') }}
{{ user.username|e('css') }}
{{ user.username|e('url') }}
{{ user.username|e('html_attr') }}
```

#### 使用自动转义

无论是否启用了自动转义，您都可以通过使用自动转义标记来标记一个模板的一个片段:

```markup
{% autoescape %}
    Everything will be automatically escaped in this block (using the HTML strategy)
{% endautoescape %}
```

默认情况下，自动转义使用html转义策略。如果在其他上下文中输出变量，则需要使用适当的转义策略显式地转义它们:

```markup
{% autoescape 'js' %}
    Everything will be automatically escaped in this block (using the JS strategy)
{% endautoescape %}
```

### 转义(Escaping)

有时需要或甚至有必要让Twig忽略它可能作为变量或块来处理的部分。例如，如果使用默认的语法，并且希望在模板中使用{ {作为原始字符串，而不是启动变量，则必须使用技巧。

最简单的方法是使用一个变量表达式输出变量分隔符({ {):

```markup
{{ '{{' }}
```

### 宏(Macros)

宏可以与常规编程语言中的函数相比较。它们是有用的，可以重用经常使用的HTML片段不重复自己。

宏通过[宏](https://twig.sensiolabs.org/doc/2.x/tags/macro.html)标签定义。这里有一个小的例子(后来被称为forms.html)呈现一个表单元素:

```markup
{% macro input(name, value, type, size) %}
    <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
{% endmacro %}
```

宏可以在任何模板中定义，在使用之前需要通过[导入](https://twig.sensiolabs.org/doc/2.x/tags/import.html)标签“导入”:

```markup
{% import "forms.html" as forms %}

<p>{{ forms.input('username') }}</p>
```

或者，您可以从模板从模板导入单个宏名称，通过[from](https://twig.sensiolabs.org/doc/2.x/tags/from.html)标签到当前名称空间，并可选地将它们别名为:

```markup
{% from 'forms.html' import input as input_field %}

<dl>
    <dt>Username</dt>
    <dd>{{ input_field('username') }}</dd>
    <dt>Password</dt>
    <dd>{{ input_field('password', '', 'password') }}</dd>
</dl>
```

如果在宏调用中没有提供，还可以为宏参数定义一个默认值:

```markup
{% macro input(name, value = "", type = "text", size = 20) %}
    <input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}" />
{% endmacro %}
```

如果将额外的位置参数传递给宏调用，它们将以特殊的varargs变量作为值列表结束。

### 表达式(Expressions)

Twig 允许表达式无处不在。这些工作与普通PHP非常相似，即使您不使用PHP，您也应该感到满意。

> 操作符优先级如下，优先级低的操作符首先列出:b-and, b-xor, b-or, or, and, ==, !=, <, >, >=, <=, in, matches, starts with, ends with, .., +, -, \~, \*, /, //, %, is, \*\*, |, \[], and .:

```markup
{% set greeting = 'Hello ' %}
{% set name = 'Fabien' %}

{{ greeting ~ name|lower }}   {# Hello fabien #}

{# use parenthesis to change precedence #}
{{ (greeting ~ name)|lower }} {# hello fabien #}
```

#### 文字(Literals)

最简单的表达式形式是文字。文本是PHP类型的表示形式，如字符串、数字和数组。以下文字存在:

`“Hello World”`:两个双引号和单引号之间的所有内容都是字符串。每当在模板中需要字符串时，它们都是有用的(例如，函数调用、过滤器或只是扩展或包含模板)。一个字符串可以包含一个分隔符，如果它前面是一个反斜杠(\\)，比如'It\\'s good'。如果字符串包含反斜杠(例如。 'c:\Program Files')转移它变成双倍的(例如:'c:\Program Files')。

* `42 / 42.23`:整数和浮点数是通过写下数字来创建的。如果一个点代表一个浮点数，否则一个整数。
* `[" foo "，" bar "]`:数组的定义是由一个逗号分隔的表达式序列，并用方括号(\[])包装。
* `{“foo”:“bar”}`:散列是由一个由逗号分隔的键和值的列表定义的，并用花括号({ })结束。

```markup
{# keys as string #}
{ 'foo': 'foo', 'bar': 'bar' }

{# keys as names (equivalent to the previous hash) #}
{ foo: 'foo', bar: 'bar' }

{# keys as integer #}
{ 2: 'foo', 4: 'bar' }

{# keys as expressions (the expression must be enclosed into parentheses) #}
{ (1 + 1): 'foo', (a ~ 'b'): 'bar' }
```

* `true / false`:true表示真值，false表示假值。
* `null`: null表示没有特定的值。这是当变量不存在时返回的值。none是null的别名。

数组和散列可以嵌套:

```markup
{% set foo = [1, {"foo": "bar"}] %}
```

> 使用双引号或单引号字符串对性能没有影响，但是字符串插值只在双引号的字符串中得到支持。

#### 数学运算(Math)

Twig允许你计算值。这在模板中很少有用，但是为了完整性而存在。支持以下操作符:

* +:将两个对象加在一起(操作数被编码为数字)。{ 1 + 1 }是2。
* \-:减去第一个数字的第二个数字。{ 3 - 2 }是1。
* /:把两个数字。返回值将是一个浮点数。{ 1 / 2 }是{ { 5 } }。
* %:计算整数除数的余数。{ { 11 } }是4。
* / /:将两个数字分开，并返回整数结果。{ 20 / 7 }是2，{ { - 20 / 7 } }是- 3(这只是[round](https://twig.sensiolabs.org/doc/2.x/filters/round.html)过滤器的语法糖)。
* \*:把左边的操作数乘以右边。{ 2 } }将返回4。

#### 逻辑(Logic)

您可以将多个表达式与下列操作符组合在一起:

* and:如果左和右操作数都为真，则返回true。
* or:如果左或右操作正确，则返回true。
* not:否定。
* (expr):组织一个表达式。

> Twig还支持位运算符(b-and, b-xor, 和 b-or)。
>
> 操作符是大小写敏感的。

#### 比较(Comparisons)

下面的比较运算符在任何表达式中都被支持:= =，!= <，>，> =，and < =。

您还可以检查字符串是否以另一个字符串开头或结尾:

```markup
{% if 'Fabien' starts with 'F' %}
{% endif %}

{% if 'Fabien' ends with 'n' %}
{% endif %}
```

对于复杂的字符串比较，匹配操作符允许您使用正则表达式:

```markup
{% if phone matches '/^[\\d\\.]+$/' %}
{% endif %}
```

#### 控制操作符(Containment Operator)

in运算符执行包含测试。

如果左操作数包含在右边，则返回true:

```markup
{# returns true #}

{{ 1 in [1, 2, 3] }}

{{ 'cd' in 'abcde' }}
```

> 您可以使用此筛选器对执行遍历接口的字符串、数组或对象执行包含测试。

要做一个负面的测试，请使用not in运算符:

```markup
{% if 1 not in [1, 2, 3] %}

{# is equivalent to #}
{% if not (1 in [1, 2, 3]) %}
```

#### 测试操作符(Test Operator)

is操作符执行测试。测试可以用来测试一个变量和一个通用表达式。正确的操作数是测试的名称:

```markup
{# find out if a variable is odd #}

{{ name is odd }}
```

测试也可以接受参数:

```
{% if post.status is constant('Post::PUBLISHED') %}
```

测试可以通过使用非操作符来否定:

```
{% if post.status is not constant('Post::PUBLISHED') %}

{# is equivalent to #}
{% if not (post.status is constant('Post::PUBLISHED')) %}
```

进入[测试](https://twig.sensiolabs.org/doc/2.x/tests/index.html)页面，了解更多关于内置测试的信息。

#### 其他操作符(Other Operators)

以下的操作符不适合任何其他类别:

* \|:适用于一个过滤器。
* ..:创建一个基于操作数的序列，然后在操作符之前和之后(这是[range](https://twig.sensiolabs.org/doc/2.x/functions/range.html)函数的语法糖):

```
{{ 1..5 }}

{# equivalent to #}
{{ range(1, 5) }}
```

注意，在将它与过滤器操作符结合时必须使用括号，因为操作符优先规则:

```
(1..5)|join(', ')
```

* \~:将所有操作数转换成字符串并将它们连接起来。  将返回 (假设的名字是 'John') Hello John!.
* ., \[]: 获取对象的属性。
* ?:: 三元操作符:

```
{{ foo ? 'yes' : 'no' }}
{{ foo ?: 'no' }} is the same as {{ foo ? foo : 'no' }}
{{ foo ? 'yes' }} is the same as {{ foo ? 'yes' : '' }}
```

* ??: 空合并操作符:

```
{# returns the value of foo if it is defined and not null, 'no' otherwise #}
{{ foo ?? 'no' }}
```

#### 字符串插值(String Interpolation)

字符串插值(# { expression })允许任何有效的表达式出现在双引号内。计算表达式的结果被插入到字符串中:

```markup
{{ "foo #{bar} baz" }}
{{ "foo #{1 + 2} baz" }}
```

### 空白控制(Whitespace Control)

模板标记后的第一行自动删除(比如PHP)。模板引擎不会进一步修改空格，所以每个空格(空格、制表符、换行符等)都没有改变。\
使用无空间标记删除HTML标记之间的空格:

```
{% spaceless %}
    <div>
        <strong>foo bar</strong>
    </div>
{% endspaceless %}

{# output will be <div><strong>foo bar</strong></div> #}
```

除了这个没有空间的标记之外，您还可以在每个标记级别上控制空格。通过在您的标记上使用空格控件修饰符，您可以修剪引导或跟踪空格:

```markup
{% set value = 'no spaces' %}
{#- No leading/trailing whitespace -#}
{%- if true -%}
    {{- value -}}
{%- endif -%}

{# output 'no spaces' #}
```

上面的示例展示了默认的空白控件修饰符，以及如何使用它来移除标签上的空白。微调空间将消耗标记的这一侧的所有空格。可以在标签的一侧使用空格进行修剪:

```
{% set value = 'no spaces' %}
<li>    {{- value }}    </li>

{# outputs '<li>no spaces    </li>' #}
```

### 扩展(Extensions)

Twig可以很容易地扩展。

如果您正在寻找新的标记、过滤器或函数，请查看Twig官方[扩展repository](http://github.com/twigphp/Twig-extensions)。

如果您想创建自己的，请阅读[创建扩展](https://twig.sensiolabs.org/doc/2.x/advanced.html#creating-extensions)章节。
