#使用技巧
##顯示棄用通知
> Twig 1.21新增的。
被棄用的特性會產生棄用通知(通過調用PHP函數``trigger_error()`` )。默認地,棄用通知是沉默的,不會顯示或記錄。
要便捷地刪除模版中的所有已被棄用的特性用法,需要編寫并運行以下腳本:
require_once __DIR__.'/vendor/autoload.php';
$twig = create_your_twig_env();
$deprecations = new Twig_Util_DeprecationCollector($twig);
print_r($deprecations->collectDir(__DIR__.'/templates'));
其中的 ``collectDir()`` 方法編譯目錄中找到的所有模版,捕獲并返回棄用通知。
提示:
> 如果你的模版并不存放在文件系統中,使用搭配了迭代器``Iterator``的``collect()``方法。迭代器必須返回以模板名為鍵,模版內容為值的結果(由``Twig_Util_TemplateDirIterator``完成處理)。
然而,這些代碼并不能找到所有的棄用(比如使用棄用了的一些 Twig類)。若要捕獲所有棄用通知,需要像下面這頁注冊一個自定義的錯誤處理器:
$deprecations = array();
set_error_handler(function ($type, $msg) use (&$deprecations) {
if (E_USER_DEPRECATED === $type) {
$deprecations[] = $msg;
}
});
// run your application
print_r($deprecations);
注意,大多數棄用通知是在**編譯**過程中觸發的,所以它們不會在模版已緩存時生成。
提示:
> 如果你想要在PHP單元測試中管理棄用通知,查閱[symfony/phpunit-bridge](http://https://github.com/symfony/phpunit-bridge)包,它極大地優化了流程。
##制作布局條件句
使用Ajax意味著相同的內容有時是以本身的樣子顯示,有時是被布局裝飾的。由于Twig布局模板的名稱可以是任意有效的表達式,當通過Ajax傳遞請求時,可以傳遞一個被解析為``true``的變量,然后選擇相應的布局:
{% extends request.ajax ? "base_ajax.html" : "base.html" %}
{% block content %}
此處的內容將被顯示
{% endblock %}
##制作動態Include
在引入模板時,它的名稱不必是字符串。舉個例子,被引入的模板名可以依賴變量的值:
{% include var ~ '_foo.html' %}
如果 ``var`` 解析為 ``index``,``index_foo.html``模板將被渲染。
事實上,模板名稱可以是任意有效的表達式,就像下面這樣:
{% include var|default('index') ~ '_foo.html' %}
##Overriding a Template that also extends itself
可以通過兩種方式來自定義模板:
* *繼承*: *擴展*父模板來創建目標,并覆寫一些代碼塊。
* *替換*: 如果你用到了文件系統加載器,Twig會加載目錄列表中找到的第一個模板;在一個目錄中找到的目標會*替換*后續目錄中的其他模板。
但如何將兩者結合呢?*replace* a template that also extends itself
(aka a template in a directory further in the list)?
Let's say that your templates are loaded from both ``.../templates/mysite``
and ``.../templates/default`` in this order. The ``page.twig`` template,
stored in ``.../templates/default`` reads as follows:
{# page.twig #}
{% extends "layout.twig" %}
{% block content %}
{% endblock %}
You can replace this template by putting a file with the same name in
``.../templates/mysite``. And if you want to extend the original template, you
might be tempted to write the following:
{# page.twig in .../templates/mysite #}
{% extends "page.twig" %} {# from .../templates/default #}
Of course, this will not work as Twig will always load the template from
``.../templates/mysite``.
It turns out it is possible to get this to work, by adding a directory right
at the end of your template directories, which is the parent of all of the
other directories: ``.../templates`` in our case. This has the effect of
making every template file within our system uniquely addressable. Most of the
time you will use the "normal" paths, but in the special case of wanting to
extend a template with an overriding version of itself we can reference its
parent's full, unambiguous template path in the extends tag:
{# page.twig in .../templates/mysite #}
{% extends "default/page.twig" %} {# from .../templates #}
.. note::
This recipe was inspired by the following Django wiki page:
http://code.djangoproject.com/wiki/ExtendingTemplates
##自定義語法
Twig 允許對代碼塊分隔符進行一些自定義。It's not recommended to use this feature as templates will be tied with your custom syntax. But for specific projects, it can make sense to change the defaults.
要改變代碼塊分隔符,你必須創建你自己的詞法分析程序對象:
$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('{$', '}'),
));
##使用動態對象屬性
當Twig遇到一個像``article.title``這樣的變量,它會嘗試在``article``對象中尋找
``title``公共屬性。
如果使用了``__get()``魔術方法進行動態定義,即使該公共屬性并不存在,Twig依然會進行上面所述的工作。你只需要像下面這段代碼所示,再實現一下 ``__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;
}
}
##在嵌套的循環中訪問父級上下文(parent Context)
有時,當我們在使用嵌套的循環時,需要訪問父級上下文。父級上下文可以使用``loop.parent``變量訪問。舉個例子,如果你有下面這樣的模板數據:
$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'),
),
);
然后,下面這個模板將在所有話題(topic)中顯示所有信息:
{% 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``用于訪問外部上線(context)。所以當前在外部循環中定義的``topic``的索引可以通過``loop.parent.loop.index``訪問。
##即時定義尚未定義的函數和過濾器
如果函數或過濾器未被定義,Twig默認地會拋出一個 ``Twig_Error_Syntax``異常。然而,還可以調用能返回函數或過濾器的`callback`(任意有效的PHP callable)。
對于過濾器,使用``registerUndefinedFilterCallback()``來注冊調用(callback)。
對于函數,則使用 ``registerUndefinedFunctionCallback()``:
// 自動將所有原生PHP函數注冊為Twig函數
// 不要輕易嘗試,這樣做非常不安全!
$twig->registerUndefinedFunctionCallback(function ($name) {
if (function_exists($name)) {
return new Twig_SimpleFunction($name, $name);
}
return false;
});
如果 callable 不能返回有效的函數或過濾器,它必須返回``false``.
如果你注冊了超過一個 callback,Twig將輪流調用它們直到不再返回``false``.
提示
> 由于函數和過濾器的解析在編譯期間就已完成,所以注冊這些回調(callback)時并不會由額外的開銷。
##驗證模板語法
當模板代碼是由第三方(比如通過web接口)提供的,那么就需要再保存它之前進行模板語法驗證。如果模板代碼是存放在`$template`變量中的,你可以這樣處理:
try {
$twig->parse($twig->tokenize($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($template, $file));
// the $template is valid
} catch (Twig_Error_Syntax $e) {
// $template contains one or more syntax errors
}
}
注意:
> 這個方法不會捕獲任何沙盒策略的違規,因為沙河策略是在模板渲染過程中執行的(因為Twig需要對context進行一些檢查,比如已被允許的對象方法)。
##在OPcache或APC啟用的情況下,刷新已修改的模板
在使用``opcache.validate_timestamps`` 設為 ``0``的OPcache,或``apc.stat``設為``0``的APC,并且Twig 緩存被啟用的情況下,清除模板緩存并不會更新緩存。
為了解決這個問題,需要強行讓Twig將字節碼緩存無效化:
$twig = new Twig_Environment($loader, array(
'cache' => new Twig_Cache_Filesystem('/some/cache/path', Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION),
// ...
));
注意:
> 在 Twig 1.22 之前,需要擴展 ``Twig_Environment``:
> class OpCacheAwareTwigEnvironment extends Twig_Environment
> {
> protected function writeCacheFile($file, $content)
> {
> parent::writeCacheFile($file, $content);
> // Compile cached file into bytecode cache
> if (function_exists('opcache_invalidate')) {
> opcache_invalidate($file, true);
> } elseif (function_exists('apc_compile_file')) {
> apc_compile_file($file);
> }
> }
> }
##Reusing a stateful Node Visitor
When attaching a visitor to a ``Twig_Environment`` instance, Twig uses it to
visit *all* templates it compiles. If you need to keep some state information
around, you probably want to reset it when visiting a new template.
This can be easily achieved with the following code::
protected $someTemplateState = array();
public function enterNode(Twig_NodeInterface $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;
}
##用數據庫來存放目標
如果你在開發一款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)");
我們創建了一個簡單的 ``templates`` 表,它寄存了兩個模板:``base.twig`` 和 ``index.twig``。
現在,定義一個能使用這個數據庫的加載器:
class DatabaseTwigLoader implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
{
protected $dbh;
public function __construct(PDO $dbh)
{
$this->dbh = $dbh;
}
public function getSource($name)
{
if (false === $source = $this->getValue('source', $name)) {
throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
}
return $source;
}
// Twig_ExistsLoaderInterface as of Twig 1.11
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'));
~~~
##使用不同的模板來源
這是前面一條使用技巧的延續。即使你已經將模板存儲在數據庫中,你可能還希望能將原始的基礎模板存放在文件系統中。要想從不同的來源加載模板,你需要使用 ``Twig_Loader_Chain`` 加載器。
As you can see in the previous recipe, we reference the template in the exact same way as we would have done it with a regular filesystem loader. This is the key to be able to mix and match templates coming from the database, the filesystem, or any other loader for that matter: the template name should be a logical name, and not the path from the filesystem::
$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'));
Now that the ``base.twig`` templates is defined in an array loader, you can
remove it from the database, and everything else will still work as before.
##從字符串中加載模版
對于模板,你可以使用``template_from_string``函數輕松加載儲存在字符串中的模版(需要``Twig_Extension_StringLoader``擴展的支持,在Twig1.1以上可用):
{{ include(template_from_string("Hello {{ name }}")) }}
對于PHP,同樣可以通過``Twig_Environment::createTemplate()``來加載存儲再字符串中的模板(Twig 1.18以上可用):
$template = $twig->createTemplate('hello {{ name }}');
echo $template->render(array('name' => 'Fabien'));
注意:
> 不要使用 ``Twig_Loader_String`` 加載器,它有嚴重的局限性。
- 首頁
- 目錄
- 介紹
- 安裝
- 面向模板設計師
- 面向開發者
- 擴展 Twig
- Twig的內部構建
- 棄用的特性
- 使用技巧
- 代碼規范
- 標簽 tags
- autoescape
- block
- do
- embed
- extends
- filter
- flush
- for
- from
- if
- import
- include
- macro
- sandbox
- set
- spaceless
- use
- verbatim
- 過濾器
- abs
- batch
- capitalize
- convert_encoding
- date
- date_modify
- default
- escape
- first
- format
- join
- json_encode
- keys
- last
- length
- lower
- merge
- nl2br
- number_format
- raw
- replace
- reverse
- round
- slice
- sort
- split
- striptags
- title
- trim
- upper
- url_encode
- 函數
- attribute
- block
- constant
- cycle
- date
- dump
- include
- max
- min
- parent
- random
- range
- source
- template_from_string
- 測試
- constant
- defined
- divisibleby
- empty
- even
- iterable
- null
- odd
- sameas