recipes.rst 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. Recipes
  2. =======
  3. Making a Layout conditional
  4. ---------------------------
  5. Working with Ajax means that the same content is sometimes displayed as is,
  6. and sometimes decorated with a layout. As Twig layout template names can be
  7. any valid expression, you can pass a variable that evaluates to ``true`` when
  8. the request is made via Ajax and choose the layout accordingly:
  9. .. code-block:: jinja
  10. {% extends request.ajax ? "base_ajax.html" : "base.html" %}
  11. {% block content %}
  12. This is the content to be displayed.
  13. {% endblock %}
  14. Making an Include dynamic
  15. -------------------------
  16. When including a template, its name does not need to be a string. For
  17. instance, the name can depend on the value of a variable:
  18. .. code-block:: jinja
  19. {% include var ~ '_foo.html' %}
  20. If ``var`` evaluates to ``index``, the ``index_foo.html`` template will be
  21. rendered.
  22. As a matter of fact, the template name can be any valid expression, such as
  23. the following:
  24. .. code-block:: jinja
  25. {% include var|default('index') ~ '_foo.html' %}
  26. Overriding a Template that also extends itself
  27. ----------------------------------------------
  28. A template can be customized in two different ways:
  29. * *Inheritance*: A template *extends* a parent template and overrides some
  30. blocks;
  31. * *Replacement*: If you use the filesystem loader, Twig loads the first
  32. template it finds in a list of configured directories; a template found in a
  33. directory *replaces* another one from a directory further in the list.
  34. But how do you combine both: *replace* a template that also extends itself
  35. (aka a template in a directory further in the list)?
  36. Let's say that your templates are loaded from both ``.../templates/mysite``
  37. and ``.../templates/default`` in this order. The ``page.twig`` template,
  38. stored in ``.../templates/default`` reads as follows:
  39. .. code-block:: jinja
  40. {# page.twig #}
  41. {% extends "layout.twig" %}
  42. {% block content %}
  43. {% endblock %}
  44. You can replace this template by putting a file with the same name in
  45. ``.../templates/mysite``. And if you want to extend the original template, you
  46. might be tempted to write the following:
  47. .. code-block:: jinja
  48. {# page.twig in .../templates/mysite #}
  49. {% extends "page.twig" %} {# from .../templates/default #}
  50. Of course, this will not work as Twig will always load the template from
  51. ``.../templates/mysite``.
  52. It turns out it is possible to get this to work, by adding a directory right
  53. at the end of your template directories, which is the parent of all of the
  54. other directories: ``.../templates`` in our case. This has the effect of
  55. making every template file within our system uniquely addressable. Most of the
  56. time you will use the "normal" paths, but in the special case of wanting to
  57. extend a template with an overriding version of itself we can reference its
  58. parent's full, unambiguous template path in the extends tag:
  59. .. code-block:: jinja
  60. {# page.twig in .../templates/mysite #}
  61. {% extends "default/page.twig" %} {# from .../templates #}
  62. .. note::
  63. This recipe was inspired by the following Django wiki page:
  64. http://code.djangoproject.com/wiki/ExtendingTemplates
  65. Customizing the Syntax
  66. ----------------------
  67. Twig allows some syntax customization for the block delimiters. It's not
  68. recommended to use this feature as templates will be tied with your custom
  69. syntax. But for specific projects, it can make sense to change the defaults.
  70. To change the block delimiters, you need to create your own lexer object::
  71. $twig = new Twig_Environment();
  72. $lexer = new Twig_Lexer($twig, array(
  73. 'tag_comment' => array('{#', '#}'),
  74. 'tag_block' => array('{%', '%}'),
  75. 'tag_variable' => array('{{', '}}'),
  76. ));
  77. $twig->setLexer($lexer);
  78. Here are some configuration example that simulates some other template engines
  79. syntax::
  80. // Ruby erb syntax
  81. $lexer = new Twig_Lexer($twig, array(
  82. 'tag_comment' => array('<%#', '%>'),
  83. 'tag_block' => array('<%', '%>'),
  84. 'tag_variable' => array('<%=', '%>'),
  85. ));
  86. // SGML Comment Syntax
  87. $lexer = new Twig_Lexer($twig, array(
  88. 'tag_comment' => array('<!--#', '-->'),
  89. 'tag_block' => array('<!--', '-->'),
  90. 'tag_variable' => array('${', '}'),
  91. ));
  92. // Smarty like
  93. $lexer = new Twig_Lexer($twig, array(
  94. 'tag_comment' => array('{*', '*}'),
  95. 'tag_block' => array('{', '}'),
  96. 'tag_variable' => array('{$', '}'),
  97. ));
  98. Using dynamic Object Properties
  99. -------------------------------
  100. When Twig encounters a variable like ``article.title``, it tries to find a
  101. ``title`` public property in the ``article`` object.
  102. It also works if the property does not exist but is rather defined dynamically
  103. thanks to the magic ``__get()`` method; you just need to also implement the
  104. ``__isset()`` magic method like shown in the following snippet of code::
  105. class Article
  106. {
  107. public function __get($name)
  108. {
  109. if ('title' == $name) {
  110. return 'The title';
  111. }
  112. // throw some kind of error
  113. }
  114. public function __isset($name)
  115. {
  116. if ('title' == $name) {
  117. return true;
  118. }
  119. return false;
  120. }
  121. }
  122. Accessing the parent Context in Nested Loops
  123. --------------------------------------------
  124. Sometimes, when using nested loops, you need to access the parent context. The
  125. parent context is always accessible via the ``loop.parent`` variable. For
  126. instance, if you have the following template data::
  127. $data = array(
  128. 'topics' => array(
  129. 'topic1' => array('Message 1 of topic 1', 'Message 2 of topic 1'),
  130. 'topic2' => array('Message 1 of topic 2', 'Message 2 of topic 2'),
  131. ),
  132. );
  133. And the following template to display all messages in all topics:
  134. .. code-block:: jinja
  135. {% for topic, messages in topics %}
  136. * {{ loop.index }}: {{ topic }}
  137. {% for message in messages %}
  138. - {{ loop.parent.loop.index }}.{{ loop.index }}: {{ message }}
  139. {% endfor %}
  140. {% endfor %}
  141. The output will be similar to:
  142. .. code-block:: text
  143. * 1: topic1
  144. - 1.1: The message 1 of topic 1
  145. - 1.2: The message 2 of topic 1
  146. * 2: topic2
  147. - 2.1: The message 1 of topic 2
  148. - 2.2: The message 2 of topic 2
  149. In the inner loop, the ``loop.parent`` variable is used to access the outer
  150. context. So, the index of the current ``topic`` defined in the outer for loop
  151. is accessible via the ``loop.parent.loop.index`` variable.
  152. Defining undefined Functions and Filters on the Fly
  153. ---------------------------------------------------
  154. When a function (or a filter) is not defined, Twig defaults to throw a
  155. ``Twig_Error_Syntax`` exception. However, it can also call a `callback`_ (any
  156. valid PHP callable) which should return a function (or a filter).
  157. For filters, register callbacks with ``registerUndefinedFilterCallback()``.
  158. For functions, use ``registerUndefinedFunctionCallback()``::
  159. // auto-register all native PHP functions as Twig functions
  160. // don't try this at home as it's not secure at all!
  161. $twig->registerUndefinedFunctionCallback(function ($name) {
  162. if (function_exists($name)) {
  163. return new Twig_Function_Function($name);
  164. }
  165. return false;
  166. });
  167. If the callable is not able to return a valid function (or filter), it must
  168. return ``false``.
  169. If you register more than one callback, Twig will call them in turn until one
  170. does not return ``false``.
  171. .. tip::
  172. As the resolution of functions and filters is done during compilation,
  173. there is no overhead when registering these callbacks.
  174. Validating the Template Syntax
  175. ------------------------------
  176. When template code is providing by a third-party (through a web interface for
  177. instance), it might be interesting to validate the template syntax before
  178. saving it. If the template code is stored in a `$template` variable, here is
  179. how you can do it::
  180. try {
  181. $twig->parse($twig->tokenize($template));
  182. // the $template is valid
  183. } catch (Twig_Error_Syntax $e) {
  184. // $template contains one or more syntax errors
  185. }
  186. If you iterate over a set of files, you can pass the filename to the
  187. ``tokenize()`` method to get the filename in the exception message::
  188. foreach ($files as $file) {
  189. try {
  190. $twig->parse($twig->tokenize($template, $file));
  191. // the $template is valid
  192. } catch (Twig_Error_Syntax $e) {
  193. // $template contains one or more syntax errors
  194. }
  195. }
  196. .. note::
  197. This method won't catch any sandbox policy violations because the policy
  198. is enforced during template rendering (as Twig needs the context for some
  199. checks like allowed methods on objects).
  200. Refreshing modified Templates when APC is enabled and apc.stat = 0
  201. ------------------------------------------------------------------
  202. When using APC with ``apc.stat`` set to ``0`` and Twig cache enabled, clearing
  203. the template cache won't update the APC cache. To get around this, one can
  204. extend ``Twig_Environment`` and force the update of the APC cache when Twig
  205. rewrites the cache::
  206. class Twig_Environment_APC extends Twig_Environment
  207. {
  208. protected function writeCacheFile($file, $content)
  209. {
  210. parent::writeCacheFile($file, $content);
  211. // Compile cached file into bytecode cache
  212. apc_compile_file($file);
  213. }
  214. }
  215. Reusing a stateful Node Visitor
  216. -------------------------------
  217. When attaching a visitor to a ``Twig_Environment`` instance, Twig uses it to
  218. visit *all* templates it compiles. If you need to keep some state information
  219. around, you probably want to reset it when visiting a new template.
  220. This can be easily achieved with the following code::
  221. protected $someTemplateState = array();
  222. public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
  223. {
  224. if ($node instanceof Twig_Node_Module) {
  225. // reset the state as we are entering a new template
  226. $this->someTemplateState = array();
  227. }
  228. // ...
  229. return $node;
  230. }
  231. Using the Template name to set the default Escaping Strategy
  232. ------------------------------------------------------------
  233. .. versionadded:: 1.8
  234. This recipe requires Twig 1.8 or later.
  235. The ``autoescape`` option determines the default escaping strategy to use when
  236. no escaping is applied on a variable. When Twig is used to mostly generate
  237. HTML files, you can set it to ``html`` and explicitly change it to ``js`` when
  238. you have some dynamic JavaScript files thanks to the ``autoescape`` tag:
  239. .. code-block:: jinja
  240. {% autoescape 'js' %}
  241. ... some JS ...
  242. {% endautoescape %}
  243. But if you have many HTML and JS files, and if your template names follow some
  244. conventions, you can instead determine the default escaping strategy to use
  245. based on the template name. Let's say that your template names always ends
  246. with ``.html`` for HTML files, ``.js`` for JavaScript ones, and ``.css`` for
  247. stylesheets, here is how you can configure Twig::
  248. class TwigEscapingGuesser
  249. {
  250. function guess($filename)
  251. {
  252. // get the format
  253. $format = substr($filename, strrpos($filename, '.') + 1);
  254. switch ($format) {
  255. case 'js':
  256. return 'js';
  257. case 'css':
  258. return 'css';
  259. case 'html':
  260. default:
  261. return 'html';
  262. }
  263. }
  264. }
  265. $loader = new Twig_Loader_Filesystem('/path/to/templates');
  266. $twig = new Twig_Environment($loader, array(
  267. 'autoescape' => array(new TwigEscapingGuesser(), 'guess'),
  268. ));
  269. This dynamic strategy does not incur any overhead at runtime as auto-escaping
  270. is done at compilation time.
  271. .. _callback: http://www.php.net/manual/en/function.is-callable.php