Twig 方法

原文:Recipes 翻译:小虾米(QQ:509129)

Recipes

显示的通知

弃用特性生成弃用通知(通过调用trigger_error()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类)。要捕获所有通知,请注册一个类似下面的错误处理程序:

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的变量,并据此选择布局:

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

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

做一个动态包含(Making an Include dynamic)

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

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

如果var的值是index,则是index_foo。将呈现html模板。

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

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

重写一个扩展自身的模板(Overriding a Template that also extends itself)

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

  • 继承:模板扩展了父模板,覆盖了一些块;

  • 替换:如果您使用文件系统加载程序,Twig将加载在已配置目录列表中发现的第一个模板;在目录中发现的模板将从列表中进一步的目录中替代另一个模板。

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

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

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

{% block content %}
{% endblock %}

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

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

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

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

{# 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对象:

$twig = new Twig_Environment(...);

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

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

// 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()魔术方法:

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)

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

$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'),
    ),
);

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

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

输出:

* 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():

// 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变量中,那么您可以这样做:

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()方法以获取异常消息中的文件名:

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使字节码缓存失效:

$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会使用它来访问它编译的所有模板。如果您需要保留一些状态信息,您可能希望在访问新模板时重新设置它。

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

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数据库:

$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。

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

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();
    }
}

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

$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中看到的,我们以完全相同的方式引用模板,就像我们使用常规的文件系统加载程序那样。这是能够混合和匹配来自数据库、文件系统或任何其他加载器的模板的关键:模板名称应该是一个逻辑名称,而不是来自文件系统的路径:

$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扩展)加载存储在字符串中的模板:

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

从PHP中,还可以通过Twig_Environment加载存储在字符串中的模板:createTemplate():

$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,使用插值提供服务来更改插值标记,例如在模块的初始化时间:

angular.module('myApp', []).config(function($interpolateProvider) {
    $interpolateProvider.startSymbol('{[').endSymbol(']}');
});
    • 对于Twig,通过tag_variable Lexer选项改变分隔符:

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

Last updated