ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
#使用技巧 ##显示弃用通知 > 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`` 加载器,它有严重的局限性。