原文:Recipes
翻译:小虾米(QQ:509129)
Recipes
显示的通知
弃用特性生成弃用通知(通过调用trigger_error()PHP函数)。默认情况下,它们是静默的,不会显示,也不会记录。
为了方便地从模板中删除所有已弃用的特性用法,请在下面的代码行中编写和运行一个脚本:
Copy 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类)。要捕获所有通知,请注册一个类似下面的错误处理程序:
Copy 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的变量,并据此选择布局:
Copy {% extends request.ajax ? "base_ajax.html" : "base.html" %}
{% block content %}
This is the content to be displayed.
{% endblock %}
做一个动态包含(Making an Include dynamic)
当包含模板时,它的名称不需要是字符串。例如,名称可以依赖于变量的值:
Copy {% include var ~ '_foo.html' %}
如果var的值是index,则是index_foo。将呈现html模板。
事实上,模板名称可以是任何有效的表达式,例如以下内容:
Copy {% include var|default('index') ~ '_foo.html' %}
重写一个扩展自身的模板(Overriding a Template that also extends itself)
模板可以以两种不同的方式定制:
替换:如果您使用文件系统加载程序,Twig将加载在已配置目录列表中发现的第一个模板;在目录中发现的模板将从列表中进一步的目录中替代另一个模板。
但是,如何将二者结合在一起:替换一个扩展自身的模板(在目录中进一步的目录中)?
让我们假设您的模板从两个都加载../templates/mysite和.../templates/default按此顺序。该页面。将模板存储在.../templates/default读取模板如下:
Copy {# page.twig #}
{% extends "layout.twig" %}
{% block content %}
{% endblock %}
您可以通过在 .../templates/mysite中放入相同名称的文件来替换这个模板。如果您想扩展原始模板,您可能会尝试编写以下内容:
Copy {# page.twig in .../templates/mysite #}
{% extends "page.twig" %} {# from .../templates/default #}
当然,这不会起作用,因为Twig总是从.../templates/mysite 加载模板。
事实证明,这是可能的工作,通过添加一个目录在您的模板目录,这是所有其他目录的父目录:.../templates在我们的例子。这使得我们的系统中的每个模板文件唯一的寻址。大多数情况下,你将使用“正常”的路径,但在特殊情况下,要扩展的模板与压倒一切的版本本身,我们可以引用其父的完整,明确的模板路径的扩展标签:
Copy {# 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对象:
Copy $twig = new Twig_Environment ( ... );
$lexer = new Twig_Lexer ($twig , array (
'tag_comment' => array ( '{#' , '#}' ) ,
'tag_block' => array ( '{%' , '%}' ) ,
'tag_variable' => array ( '{{' , '}}' ) ,
'interpolation' => array ( '#{' , '}' ) ,
));
$twig -> setLexer ( $lexer ) ;
下面是一些模拟其他模板引擎语法的配置示例:
Copy // 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()魔术方法:
Copy 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)
有时,当使用嵌套循环时,您需要访问父级上下文。父上下文始终可以通过循环访问。父类变量。例如,如果您有以下模板数据:
Copy $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' ) ,
) ,
);
下面的模板将显示所有主题中的所有消息:
Copy {% for topic, messages in topics %}
* {{ loop.index }}: {{ topic }}
{% for message in messages %}
- {{ loop.parent.loop.index }}.{{ loop.index }}: {{ message }}
{% endfor %}
{% endfor %}
输出:
Copy * 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():
Copy // 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变量中,那么您可以这样做:
Copy 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()方法以获取异常消息中的文件名:
Copy 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使字节码缓存失效:
Copy $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会使用它来访问它编译的所有模板。如果您需要保留一些状态信息,您可能希望在访问新模板时重新设置它。
这可以很容易地用以下代码实现:
Copy 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数据库:
Copy $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。
现在,让我们定义一个可以使用这个数据库的加载器:
Copy 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 () ;
}
}
最后,这里有一个关于如何使用它的例子:
Copy $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中看到的,我们以完全相同的方式引用模板,就像我们使用常规的文件系统加载程序那样。这是能够混合和匹配来自数据库、文件系统或任何其他加载器的模板的关键:模板名称应该是一个逻辑名称,而不是来自文件系统的路径:
Copy $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扩展)加载存储在字符串中的模板:
Copy {{ include(template_from_string("Hello {{ name }}")) }}
从PHP中,还可以通过Twig_Environment加载存储在字符串中的模板:createTemplate():
Copy $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,使用插值提供服务来更改插值标记,例如在模块的初始化时间:
Copy angular .module ( 'myApp' , []) .config ( function ($interpolateProvider) {
$interpolateProvider .startSymbol ( '{[' ) .endSymbol ( ']}' );
});
对于Twig,通过tag_variable Lexer选项改变分隔符:
Copy $env -> setLexer ( new Twig_Lexer ($env , array (
'tag_variable' => array ( '{[' , ']}' ) ,
)) ) ;