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
Was this helpful?