You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

recipes.rst 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. Recipes
  2. =======
  3. .. _deprecation-notices:
  4. Displaying Deprecation Notices
  5. ------------------------------
  6. .. versionadded:: 1.21
  7. This works as of Twig 1.21.
  8. Deprecated features generate deprecation notices (via a call to the
  9. ``trigger_error()`` PHP function). By default, they are silenced and never
  10. displayed nor logged.
  11. To easily remove all deprecated feature usages from your templates, write and
  12. run a script along the lines of the following::
  13. require_once __DIR__.'/vendor/autoload.php';
  14. $twig = create_your_twig_env();
  15. $deprecations = new Twig_Util_DeprecationCollector($twig);
  16. print_r($deprecations->collectDir(__DIR__.'/templates'));
  17. The ``collectDir()`` method compiles all templates found in a directory,
  18. catches deprecation notices, and return them.
  19. .. tip::
  20. If your templates are not stored on the filesystem, use the ``collect()``
  21. method instead which takes an ``Iterator``; the iterator must return
  22. template names as keys and template contents as values (as done by
  23. ``Twig_Util_TemplateDirIterator``).
  24. However, this code won't find all deprecations (like using deprecated some Twig
  25. classes). To catch all notices, register a custom error handler like the one
  26. below::
  27. $deprecations = array();
  28. set_error_handler(function ($type, $msg) use (&$deprecations) {
  29. if (E_USER_DEPRECATED === $type) {
  30. $deprecations[] = $msg;
  31. }
  32. });
  33. // run your application
  34. print_r($deprecations);
  35. Note that most deprecation notices are triggered during **compilation**, so
  36. they won't be generated when templates are already cached.
  37. .. tip::
  38. If you want to manage the deprecation notices from your PHPUnit tests, have
  39. a look at the `symfony/phpunit-bridge
  40. <https://github.com/symfony/phpunit-bridge>`_ package, which eases the
  41. process a lot.
  42. Making a Layout conditional
  43. ---------------------------
  44. Working with Ajax means that the same content is sometimes displayed as is,
  45. and sometimes decorated with a layout. As Twig layout template names can be
  46. any valid expression, you can pass a variable that evaluates to ``true`` when
  47. the request is made via Ajax and choose the layout accordingly:
  48. .. code-block:: jinja
  49. {% extends request.ajax ? "base_ajax.html" : "base.html" %}
  50. {% block content %}
  51. This is the content to be displayed.
  52. {% endblock %}
  53. Making an Include dynamic
  54. -------------------------
  55. When including a template, its name does not need to be a string. For
  56. instance, the name can depend on the value of a variable:
  57. .. code-block:: jinja
  58. {% include var ~ '_foo.html' %}
  59. If ``var`` evaluates to ``index``, the ``index_foo.html`` template will be
  60. rendered.
  61. As a matter of fact, the template name can be any valid expression, such as
  62. the following:
  63. .. code-block:: jinja
  64. {% include var|default('index') ~ '_foo.html' %}
  65. Overriding a Template that also extends itself
  66. ----------------------------------------------
  67. A template can be customized in two different ways:
  68. * *Inheritance*: A template *extends* a parent template and overrides some
  69. blocks;
  70. * *Replacement*: If you use the filesystem loader, Twig loads the first
  71. template it finds in a list of configured directories; a template found in a
  72. directory *replaces* another one from a directory further in the list.
  73. But how do you combine both: *replace* a template that also extends itself
  74. (aka a template in a directory further in the list)?
  75. Let's say that your templates are loaded from both ``.../templates/mysite``
  76. and ``.../templates/default`` in this order. The ``page.twig`` template,
  77. stored in ``.../templates/default`` reads as follows:
  78. .. code-block:: jinja
  79. {# page.twig #}
  80. {% extends "layout.twig" %}
  81. {% block content %}
  82. {% endblock %}
  83. You can replace this template by putting a file with the same name in
  84. ``.../templates/mysite``. And if you want to extend the original template, you
  85. might be tempted to write the following:
  86. .. code-block:: jinja
  87. {# page.twig in .../templates/mysite #}
  88. {% extends "page.twig" %} {# from .../templates/default #}
  89. Of course, this will not work as Twig will always load the template from
  90. ``.../templates/mysite``.
  91. It turns out it is possible to get this to work, by adding a directory right
  92. at the end of your template directories, which is the parent of all of the
  93. other directories: ``.../templates`` in our case. This has the effect of
  94. making every template file within our system uniquely addressable. Most of the
  95. time you will use the "normal" paths, but in the special case of wanting to
  96. extend a template with an overriding version of itself we can reference its
  97. parent's full, unambiguous template path in the extends tag:
  98. .. code-block:: jinja
  99. {# page.twig in .../templates/mysite #}
  100. {% extends "default/page.twig" %} {# from .../templates #}
  101. .. note::
  102. This recipe was inspired by the following Django wiki page:
  103. http://code.djangoproject.com/wiki/ExtendingTemplates
  104. Customizing the Syntax
  105. ----------------------
  106. Twig allows some syntax customization for the block delimiters. It's not
  107. recommended to use this feature as templates will be tied with your custom
  108. syntax. But for specific projects, it can make sense to change the defaults.
  109. To change the block delimiters, you need to create your own lexer object::
  110. $twig = new Twig_Environment();
  111. $lexer = new Twig_Lexer($twig, array(
  112. 'tag_comment' => array('{#', '#}'),
  113. 'tag_block' => array('{%', '%}'),
  114. 'tag_variable' => array('{{', '}}'),
  115. 'interpolation' => array('#{', '}'),
  116. ));
  117. $twig->setLexer($lexer);
  118. Here are some configuration example that simulates some other template engines
  119. syntax::
  120. // Ruby erb syntax
  121. $lexer = new Twig_Lexer($twig, array(
  122. 'tag_comment' => array('<%#', '%>'),
  123. 'tag_block' => array('<%', '%>'),
  124. 'tag_variable' => array('<%=', '%>'),
  125. ));
  126. // SGML Comment Syntax
  127. $lexer = new Twig_Lexer($twig, array(
  128. 'tag_comment' => array('<!--#', '-->'),
  129. 'tag_block' => array('<!--', '-->'),
  130. 'tag_variable' => array('${', '}'),
  131. ));
  132. // Smarty like
  133. $lexer = new Twig_Lexer($twig, array(
  134. 'tag_comment' => array('{*', '*}'),
  135. 'tag_block' => array('{', '}'),
  136. 'tag_variable' => array('{$', '}'),
  137. ));
  138. Using dynamic Object Properties
  139. -------------------------------
  140. When Twig encounters a variable like ``article.title``, it tries to find a
  141. ``title`` public property in the ``article`` object.
  142. It also works if the property does not exist but is rather defined dynamically
  143. thanks to the magic ``__get()`` method; you just need to also implement the
  144. ``__isset()`` magic method like shown in the following snippet of code::
  145. class Article
  146. {
  147. public function __get($name)
  148. {
  149. if ('title' == $name) {
  150. return 'The title';
  151. }
  152. // throw some kind of error
  153. }
  154. public function __isset($name)
  155. {
  156. if ('title' == $name) {
  157. return true;
  158. }
  159. return false;
  160. }
  161. }
  162. Accessing the parent Context in Nested Loops
  163. --------------------------------------------
  164. Sometimes, when using nested loops, you need to access the parent context. The
  165. parent context is always accessible via the ``loop.parent`` variable. For
  166. instance, if you have the following template data::
  167. $data = array(
  168. 'topics' => array(
  169. 'topic1' => array('Message 1 of topic 1', 'Message 2 of topic 1'),
  170. 'topic2' => array('Message 1 of topic 2', 'Message 2 of topic 2'),
  171. ),
  172. );
  173. And the following template to display all messages in all topics:
  174. .. code-block:: jinja
  175. {% for topic, messages in topics %}
  176. * {{ loop.index }}: {{ topic }}
  177. {% for message in messages %}
  178. - {{ loop.parent.loop.index }}.{{ loop.index }}: {{ message }}
  179. {% endfor %}
  180. {% endfor %}
  181. The output will be similar to:
  182. .. code-block:: text
  183. * 1: topic1
  184. - 1.1: The message 1 of topic 1
  185. - 1.2: The message 2 of topic 1
  186. * 2: topic2
  187. - 2.1: The message 1 of topic 2
  188. - 2.2: The message 2 of topic 2
  189. In the inner loop, the ``loop.parent`` variable is used to access the outer
  190. context. So, the index of the current ``topic`` defined in the outer for loop
  191. is accessible via the ``loop.parent.loop.index`` variable.
  192. Defining undefined Functions and Filters on the Fly
  193. ---------------------------------------------------
  194. When a function (or a filter) is not defined, Twig defaults to throw a
  195. ``Twig_Error_Syntax`` exception. However, it can also call a `callback`_ (any
  196. valid PHP callable) which should return a function (or a filter).
  197. For filters, register callbacks with ``registerUndefinedFilterCallback()``.
  198. For functions, use ``registerUndefinedFunctionCallback()``::
  199. // auto-register all native PHP functions as Twig functions
  200. // don't try this at home as it's not secure at all!
  201. $twig->registerUndefinedFunctionCallback(function ($name) {
  202. if (function_exists($name)) {
  203. return new Twig_Function_Function($name);
  204. }
  205. return false;
  206. });
  207. If the callable is not able to return a valid function (or filter), it must
  208. return ``false``.
  209. If you register more than one callback, Twig will call them in turn until one
  210. does not return ``false``.
  211. .. tip::
  212. As the resolution of functions and filters is done during compilation,
  213. there is no overhead when registering these callbacks.
  214. Validating the Template Syntax
  215. ------------------------------
  216. When template code is provided by a third-party (through a web interface for
  217. instance), it might be interesting to validate the template syntax before
  218. saving it. If the template code is stored in a `$template` variable, here is
  219. how you can do it::
  220. try {
  221. $twig->parse($twig->tokenize($template));
  222. // the $template is valid
  223. } catch (Twig_Error_Syntax $e) {
  224. // $template contains one or more syntax errors
  225. }
  226. If you iterate over a set of files, you can pass the filename to the
  227. ``tokenize()`` method to get the filename in the exception message::
  228. foreach ($files as $file) {
  229. try {
  230. $twig->parse($twig->tokenize($template, $file));
  231. // the $template is valid
  232. } catch (Twig_Error_Syntax $e) {
  233. // $template contains one or more syntax errors
  234. }
  235. }
  236. .. note::
  237. This method won't catch any sandbox policy violations because the policy
  238. is enforced during template rendering (as Twig needs the context for some
  239. checks like allowed methods on objects).
  240. Refreshing modified Templates when OPcache or APC is enabled
  241. ------------------------------------------------------------
  242. When using OPcache with ``opcache.validate_timestamps`` set to ``0`` or APC
  243. with ``apc.stat`` set to ``0`` and Twig cache enabled, clearing the template
  244. cache won't update the cache.
  245. To get around this, force Twig to invalidate the bytecode cache::
  246. $twig = new Twig_Environment($loader, array(
  247. 'cache' => new Twig_Cache_Filesystem('/some/cache/path', Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION),
  248. // ...
  249. ));
  250. .. note::
  251. Before Twig 1.22, you should extend ``Twig_Environment`` instead::
  252. class OpCacheAwareTwigEnvironment extends Twig_Environment
  253. {
  254. protected function writeCacheFile($file, $content)
  255. {
  256. parent::writeCacheFile($file, $content);
  257. // Compile cached file into bytecode cache
  258. if (function_exists('opcache_invalidate')) {
  259. opcache_invalidate($file, true);
  260. } elseif (function_exists('apc_compile_file')) {
  261. apc_compile_file($file);
  262. }
  263. }
  264. }
  265. Reusing a stateful Node Visitor
  266. -------------------------------
  267. When attaching a visitor to a ``Twig_Environment`` instance, Twig uses it to
  268. visit *all* templates it compiles. If you need to keep some state information
  269. around, you probably want to reset it when visiting a new template.
  270. This can be easily achieved with the following code::
  271. protected $someTemplateState = array();
  272. public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
  273. {
  274. if ($node instanceof Twig_Node_Module) {
  275. // reset the state as we are entering a new template
  276. $this->someTemplateState = array();
  277. }
  278. // ...
  279. return $node;
  280. }
  281. Using a Database to store Templates
  282. -----------------------------------
  283. If you are developing a CMS, templates are usually stored in a database. This
  284. recipe gives you a simple PDO template loader you can use as a starting point
  285. for your own.
  286. First, let's create a temporary in-memory SQLite3 database to work with::
  287. $dbh = new PDO('sqlite::memory:');
  288. $dbh->exec('CREATE TABLE templates (name STRING, source STRING, last_modified INTEGER)');
  289. $base = '{% block content %}{% endblock %}';
  290. $index = '
  291. {% extends "base.twig" %}
  292. {% block content %}Hello {{ name }}{% endblock %}
  293. ';
  294. $now = time();
  295. $dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('base.twig', '$base', $now)");
  296. $dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('index.twig', '$index', $now)");
  297. We have created a simple ``templates`` table that hosts two templates:
  298. ``base.twig`` and ``index.twig``.
  299. Now, let's define a loader able to use this database::
  300. class DatabaseTwigLoader implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
  301. {
  302. protected $dbh;
  303. public function __construct(PDO $dbh)
  304. {
  305. $this->dbh = $dbh;
  306. }
  307. public function getSource($name)
  308. {
  309. if (false === $source = $this->getValue('source', $name)) {
  310. throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
  311. }
  312. return $source;
  313. }
  314. // Twig_ExistsLoaderInterface as of Twig 1.11
  315. public function exists($name)
  316. {
  317. return $name === $this->getValue('name', $name);
  318. }
  319. public function getCacheKey($name)
  320. {
  321. return $name;
  322. }
  323. public function isFresh($name, $time)
  324. {
  325. if (false === $lastModified = $this->getValue('last_modified', $name)) {
  326. return false;
  327. }
  328. return $lastModified <= $time;
  329. }
  330. protected function getValue($column, $name)
  331. {
  332. $sth = $this->dbh->prepare('SELECT '.$column.' FROM templates WHERE name = :name');
  333. $sth->execute(array(':name' => (string) $name));
  334. return $sth->fetchColumn();
  335. }
  336. }
  337. Finally, here is an example on how you can use it::
  338. $loader = new DatabaseTwigLoader($dbh);
  339. $twig = new Twig_Environment($loader);
  340. echo $twig->render('index.twig', array('name' => 'Fabien'));
  341. Using different Template Sources
  342. --------------------------------
  343. This recipe is the continuation of the previous one. Even if you store the
  344. contributed templates in a database, you might want to keep the original/base
  345. templates on the filesystem. When templates can be loaded from different
  346. sources, you need to use the ``Twig_Loader_Chain`` loader.
  347. As you can see in the previous recipe, we reference the template in the exact
  348. same way as we would have done it with a regular filesystem loader. This is
  349. the key to be able to mix and match templates coming from the database, the
  350. filesystem, or any other loader for that matter: the template name should be a
  351. logical name, and not the path from the filesystem::
  352. $loader1 = new DatabaseTwigLoader($dbh);
  353. $loader2 = new Twig_Loader_Array(array(
  354. 'base.twig' => '{% block content %}{% endblock %}',
  355. ));
  356. $loader = new Twig_Loader_Chain(array($loader1, $loader2));
  357. $twig = new Twig_Environment($loader);
  358. echo $twig->render('index.twig', array('name' => 'Fabien'));
  359. Now that the ``base.twig`` templates is defined in an array loader, you can
  360. remove it from the database, and everything else will still work as before.
  361. Loading a Template from a String
  362. --------------------------------
  363. From a template, you can easily load a template stored in a string via the
  364. ``template_from_string`` function (available as of Twig 1.11 via the
  365. ``Twig_Extension_StringLoader`` extension)::
  366. .. code-block:: jinja
  367. {{ include(template_from_string("Hello {{ name }}")) }}
  368. From PHP, it's also possible to load a template stored in a string via
  369. ``Twig_Environment::createTemplate()`` (available as of Twig 1.18)::
  370. $template = $twig->createTemplate('hello {{ name }}');
  371. echo $template->render(array('name' => 'Fabien'));
  372. .. note::
  373. Never use the ``Twig_Loader_String`` loader, which has severe limitations.
  374. .. _callback: http://www.php.net/manual/en/function.is-callable.php