diff options
Diffstat (limited to 'vendor/twig/twig/doc/recipes.rst')
-rw-r--r-- | vendor/twig/twig/doc/recipes.rst | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/vendor/twig/twig/doc/recipes.rst b/vendor/twig/twig/doc/recipes.rst new file mode 100644 index 00000000..dfcc9205 --- /dev/null +++ b/vendor/twig/twig/doc/recipes.rst | |||
@@ -0,0 +1,475 @@ | |||
1 | Recipes | ||
2 | ======= | ||
3 | |||
4 | Making a Layout conditional | ||
5 | --------------------------- | ||
6 | |||
7 | Working with Ajax means that the same content is sometimes displayed as is, | ||
8 | and sometimes decorated with a layout. As Twig layout template names can be | ||
9 | any valid expression, you can pass a variable that evaluates to ``true`` when | ||
10 | the request is made via Ajax and choose the layout accordingly: | ||
11 | |||
12 | .. code-block:: jinja | ||
13 | |||
14 | {% extends request.ajax ? "base_ajax.html" : "base.html" %} | ||
15 | |||
16 | {% block content %} | ||
17 | This is the content to be displayed. | ||
18 | {% endblock %} | ||
19 | |||
20 | Making an Include dynamic | ||
21 | ------------------------- | ||
22 | |||
23 | When including a template, its name does not need to be a string. For | ||
24 | instance, the name can depend on the value of a variable: | ||
25 | |||
26 | .. code-block:: jinja | ||
27 | |||
28 | {% include var ~ '_foo.html' %} | ||
29 | |||
30 | If ``var`` evaluates to ``index``, the ``index_foo.html`` template will be | ||
31 | rendered. | ||
32 | |||
33 | As a matter of fact, the template name can be any valid expression, such as | ||
34 | the following: | ||
35 | |||
36 | .. code-block:: jinja | ||
37 | |||
38 | {% include var|default('index') ~ '_foo.html' %} | ||
39 | |||
40 | Overriding a Template that also extends itself | ||
41 | ---------------------------------------------- | ||
42 | |||
43 | A template can be customized in two different ways: | ||
44 | |||
45 | * *Inheritance*: A template *extends* a parent template and overrides some | ||
46 | blocks; | ||
47 | |||
48 | * *Replacement*: If you use the filesystem loader, Twig loads the first | ||
49 | template it finds in a list of configured directories; a template found in a | ||
50 | directory *replaces* another one from a directory further in the list. | ||
51 | |||
52 | But how do you combine both: *replace* a template that also extends itself | ||
53 | (aka a template in a directory further in the list)? | ||
54 | |||
55 | Let's say that your templates are loaded from both ``.../templates/mysite`` | ||
56 | and ``.../templates/default`` in this order. The ``page.twig`` template, | ||
57 | stored in ``.../templates/default`` reads as follows: | ||
58 | |||
59 | .. code-block:: jinja | ||
60 | |||
61 | {# page.twig #} | ||
62 | {% extends "layout.twig" %} | ||
63 | |||
64 | {% block content %} | ||
65 | {% endblock %} | ||
66 | |||
67 | You can replace this template by putting a file with the same name in | ||
68 | ``.../templates/mysite``. And if you want to extend the original template, you | ||
69 | might be tempted to write the following: | ||
70 | |||
71 | .. code-block:: jinja | ||
72 | |||
73 | {# page.twig in .../templates/mysite #} | ||
74 | {% extends "page.twig" %} {# from .../templates/default #} | ||
75 | |||
76 | Of course, this will not work as Twig will always load the template from | ||
77 | ``.../templates/mysite``. | ||
78 | |||
79 | It turns out it is possible to get this to work, by adding a directory right | ||
80 | at the end of your template directories, which is the parent of all of the | ||
81 | other directories: ``.../templates`` in our case. This has the effect of | ||
82 | making every template file within our system uniquely addressable. Most of the | ||
83 | time you will use the "normal" paths, but in the special case of wanting to | ||
84 | extend a template with an overriding version of itself we can reference its | ||
85 | parent's full, unambiguous template path in the extends tag: | ||
86 | |||
87 | .. code-block:: jinja | ||
88 | |||
89 | {# page.twig in .../templates/mysite #} | ||
90 | {% extends "default/page.twig" %} {# from .../templates #} | ||
91 | |||
92 | .. note:: | ||
93 | |||
94 | This recipe was inspired by the following Django wiki page: | ||
95 | http://code.djangoproject.com/wiki/ExtendingTemplates | ||
96 | |||
97 | Customizing the Syntax | ||
98 | ---------------------- | ||
99 | |||
100 | Twig allows some syntax customization for the block delimiters. It's not | ||
101 | recommended to use this feature as templates will be tied with your custom | ||
102 | syntax. But for specific projects, it can make sense to change the defaults. | ||
103 | |||
104 | To change the block delimiters, you need to create your own lexer object:: | ||
105 | |||
106 | $twig = new Twig_Environment(); | ||
107 | |||
108 | $lexer = new Twig_Lexer($twig, array( | ||
109 | 'tag_comment' => array('{#', '#}'), | ||
110 | 'tag_block' => array('{%', '%}'), | ||
111 | 'tag_variable' => array('{{', '}}'), | ||
112 | 'interpolation' => array('#{', '}'), | ||
113 | )); | ||
114 | $twig->setLexer($lexer); | ||
115 | |||
116 | Here are some configuration example that simulates some other template engines | ||
117 | syntax:: | ||
118 | |||
119 | // Ruby erb syntax | ||
120 | $lexer = new Twig_Lexer($twig, array( | ||
121 | 'tag_comment' => array('<%#', '%>'), | ||
122 | 'tag_block' => array('<%', '%>'), | ||
123 | 'tag_variable' => array('<%=', '%>'), | ||
124 | )); | ||
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 | |||
133 | // Smarty like | ||
134 | $lexer = new Twig_Lexer($twig, array( | ||
135 | 'tag_comment' => array('{*', '*}'), | ||
136 | 'tag_block' => array('{', '}'), | ||
137 | 'tag_variable' => array('{$', '}'), | ||
138 | )); | ||
139 | |||
140 | Using dynamic Object Properties | ||
141 | ------------------------------- | ||
142 | |||
143 | When Twig encounters a variable like ``article.title``, it tries to find a | ||
144 | ``title`` public property in the ``article`` object. | ||
145 | |||
146 | It also works if the property does not exist but is rather defined dynamically | ||
147 | thanks to the magic ``__get()`` method; you just need to also implement the | ||
148 | ``__isset()`` magic method like shown in the following snippet of code:: | ||
149 | |||
150 | class Article | ||
151 | { | ||
152 | public function __get($name) | ||
153 | { | ||
154 | if ('title' == $name) { | ||
155 | return 'The title'; | ||
156 | } | ||
157 | |||
158 | // throw some kind of error | ||
159 | } | ||
160 | |||
161 | public function __isset($name) | ||
162 | { | ||
163 | if ('title' == $name) { | ||
164 | return true; | ||
165 | } | ||
166 | |||
167 | return false; | ||
168 | } | ||
169 | } | ||
170 | |||
171 | Accessing the parent Context in Nested Loops | ||
172 | -------------------------------------------- | ||
173 | |||
174 | Sometimes, when using nested loops, you need to access the parent context. The | ||
175 | parent context is always accessible via the ``loop.parent`` variable. For | ||
176 | instance, if you have the following template data:: | ||
177 | |||
178 | $data = array( | ||
179 | 'topics' => array( | ||
180 | 'topic1' => array('Message 1 of topic 1', 'Message 2 of topic 1'), | ||
181 | 'topic2' => array('Message 1 of topic 2', 'Message 2 of topic 2'), | ||
182 | ), | ||
183 | ); | ||
184 | |||
185 | And the following template to display all messages in all topics: | ||
186 | |||
187 | .. code-block:: jinja | ||
188 | |||
189 | {% for topic, messages in topics %} | ||
190 | * {{ loop.index }}: {{ topic }} | ||
191 | {% for message in messages %} | ||
192 | - {{ loop.parent.loop.index }}.{{ loop.index }}: {{ message }} | ||
193 | {% endfor %} | ||
194 | {% endfor %} | ||
195 | |||
196 | The output will be similar to: | ||
197 | |||
198 | .. code-block:: text | ||
199 | |||
200 | * 1: topic1 | ||
201 | - 1.1: The message 1 of topic 1 | ||
202 | - 1.2: The message 2 of topic 1 | ||
203 | * 2: topic2 | ||
204 | - 2.1: The message 1 of topic 2 | ||
205 | - 2.2: The message 2 of topic 2 | ||
206 | |||
207 | In the inner loop, the ``loop.parent`` variable is used to access the outer | ||
208 | context. So, the index of the current ``topic`` defined in the outer for loop | ||
209 | is accessible via the ``loop.parent.loop.index`` variable. | ||
210 | |||
211 | Defining undefined Functions and Filters on the Fly | ||
212 | --------------------------------------------------- | ||
213 | |||
214 | When a function (or a filter) is not defined, Twig defaults to throw a | ||
215 | ``Twig_Error_Syntax`` exception. However, it can also call a `callback`_ (any | ||
216 | valid PHP callable) which should return a function (or a filter). | ||
217 | |||
218 | For filters, register callbacks with ``registerUndefinedFilterCallback()``. | ||
219 | For functions, use ``registerUndefinedFunctionCallback()``:: | ||
220 | |||
221 | // auto-register all native PHP functions as Twig functions | ||
222 | // don't try this at home as it's not secure at all! | ||
223 | $twig->registerUndefinedFunctionCallback(function ($name) { | ||
224 | if (function_exists($name)) { | ||
225 | return new Twig_Function_Function($name); | ||
226 | } | ||
227 | |||
228 | return false; | ||
229 | }); | ||
230 | |||
231 | If the callable is not able to return a valid function (or filter), it must | ||
232 | return ``false``. | ||
233 | |||
234 | If you register more than one callback, Twig will call them in turn until one | ||
235 | does not return ``false``. | ||
236 | |||
237 | .. tip:: | ||
238 | |||
239 | As the resolution of functions and filters is done during compilation, | ||
240 | there is no overhead when registering these callbacks. | ||
241 | |||
242 | Validating the Template Syntax | ||
243 | ------------------------------ | ||
244 | |||
245 | When template code is providing by a third-party (through a web interface for | ||
246 | instance), it might be interesting to validate the template syntax before | ||
247 | saving it. If the template code is stored in a `$template` variable, here is | ||
248 | how you can do it:: | ||
249 | |||
250 | try { | ||
251 | $twig->parse($twig->tokenize($template)); | ||
252 | |||
253 | // the $template is valid | ||
254 | } catch (Twig_Error_Syntax $e) { | ||
255 | // $template contains one or more syntax errors | ||
256 | } | ||
257 | |||
258 | If you iterate over a set of files, you can pass the filename to the | ||
259 | ``tokenize()`` method to get the filename in the exception message:: | ||
260 | |||
261 | foreach ($files as $file) { | ||
262 | try { | ||
263 | $twig->parse($twig->tokenize($template, $file)); | ||
264 | |||
265 | // the $template is valid | ||
266 | } catch (Twig_Error_Syntax $e) { | ||
267 | // $template contains one or more syntax errors | ||
268 | } | ||
269 | } | ||
270 | |||
271 | .. note:: | ||
272 | |||
273 | This method won't catch any sandbox policy violations because the policy | ||
274 | is enforced during template rendering (as Twig needs the context for some | ||
275 | checks like allowed methods on objects). | ||
276 | |||
277 | Refreshing modified Templates when APC is enabled and apc.stat = 0 | ||
278 | ------------------------------------------------------------------ | ||
279 | |||
280 | When using APC with ``apc.stat`` set to ``0`` and Twig cache enabled, clearing | ||
281 | the template cache won't update the APC cache. To get around this, one can | ||
282 | extend ``Twig_Environment`` and force the update of the APC cache when Twig | ||
283 | rewrites the cache:: | ||
284 | |||
285 | class Twig_Environment_APC extends Twig_Environment | ||
286 | { | ||
287 | protected function writeCacheFile($file, $content) | ||
288 | { | ||
289 | parent::writeCacheFile($file, $content); | ||
290 | |||
291 | // Compile cached file into bytecode cache | ||
292 | apc_compile_file($file); | ||
293 | } | ||
294 | } | ||
295 | |||
296 | Reusing a stateful Node Visitor | ||
297 | ------------------------------- | ||
298 | |||
299 | When attaching a visitor to a ``Twig_Environment`` instance, Twig uses it to | ||
300 | visit *all* templates it compiles. If you need to keep some state information | ||
301 | around, you probably want to reset it when visiting a new template. | ||
302 | |||
303 | This can be easily achieved with the following code:: | ||
304 | |||
305 | protected $someTemplateState = array(); | ||
306 | |||
307 | public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) | ||
308 | { | ||
309 | if ($node instanceof Twig_Node_Module) { | ||
310 | // reset the state as we are entering a new template | ||
311 | $this->someTemplateState = array(); | ||
312 | } | ||
313 | |||
314 | // ... | ||
315 | |||
316 | return $node; | ||
317 | } | ||
318 | |||
319 | Using the Template name to set the default Escaping Strategy | ||
320 | ------------------------------------------------------------ | ||
321 | |||
322 | .. versionadded:: 1.8 | ||
323 | This recipe requires Twig 1.8 or later. | ||
324 | |||
325 | The ``autoescape`` option determines the default escaping strategy to use when | ||
326 | no escaping is applied on a variable. When Twig is used to mostly generate | ||
327 | HTML files, you can set it to ``html`` and explicitly change it to ``js`` when | ||
328 | you have some dynamic JavaScript files thanks to the ``autoescape`` tag: | ||
329 | |||
330 | .. code-block:: jinja | ||
331 | |||
332 | {% autoescape 'js' %} | ||
333 | ... some JS ... | ||
334 | {% endautoescape %} | ||
335 | |||
336 | But if you have many HTML and JS files, and if your template names follow some | ||
337 | conventions, you can instead determine the default escaping strategy to use | ||
338 | based on the template name. Let's say that your template names always ends | ||
339 | with ``.html`` for HTML files, ``.js`` for JavaScript ones, and ``.css`` for | ||
340 | stylesheets, here is how you can configure Twig:: | ||
341 | |||
342 | class TwigEscapingGuesser | ||
343 | { | ||
344 | function guess($filename) | ||
345 | { | ||
346 | // get the format | ||
347 | $format = substr($filename, strrpos($filename, '.') + 1); | ||
348 | |||
349 | switch ($format) { | ||
350 | case 'js': | ||
351 | return 'js'; | ||
352 | case 'css': | ||
353 | return 'css'; | ||
354 | case 'html': | ||
355 | default: | ||
356 | return 'html'; | ||
357 | } | ||
358 | } | ||
359 | } | ||
360 | |||
361 | $loader = new Twig_Loader_Filesystem('/path/to/templates'); | ||
362 | $twig = new Twig_Environment($loader, array( | ||
363 | 'autoescape' => array(new TwigEscapingGuesser(), 'guess'), | ||
364 | )); | ||
365 | |||
366 | This dynamic strategy does not incur any overhead at runtime as auto-escaping | ||
367 | is done at compilation time. | ||
368 | |||
369 | Using a Database to store Templates | ||
370 | ----------------------------------- | ||
371 | |||
372 | If you are developing a CMS, templates are usually stored in a database. This | ||
373 | recipe gives you a simple PDO template loader you can use as a starting point | ||
374 | for your own. | ||
375 | |||
376 | First, let's create a temporary in-memory SQLite3 database to work with:: | ||
377 | |||
378 | $dbh = new PDO('sqlite::memory:'); | ||
379 | $dbh->exec('CREATE TABLE templates (name STRING, source STRING, last_modified INTEGER)'); | ||
380 | $base = '{% block content %}{% endblock %}'; | ||
381 | $index = ' | ||
382 | {% extends "base.twig" %} | ||
383 | {% block content %}Hello {{ name }}{% endblock %} | ||
384 | '; | ||
385 | $now = time(); | ||
386 | $dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('base.twig', '$base', $now)"); | ||
387 | $dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('index.twig', '$index', $now)"); | ||
388 | |||
389 | We have created a simple ``templates`` table that hosts two templates: | ||
390 | ``base.twig`` and ``index.twig``. | ||
391 | |||
392 | Now, let's define a loader able to use this database:: | ||
393 | |||
394 | class DatabaseTwigLoader implements Twig_LoaderInterface, Twig_ExistsLoaderInterface | ||
395 | { | ||
396 | protected $dbh; | ||
397 | |||
398 | public function __construct(PDO $dbh) | ||
399 | { | ||
400 | $this->dbh = $dbh; | ||
401 | } | ||
402 | |||
403 | public function getSource($name) | ||
404 | { | ||
405 | if (false === $source = $this->getValue('source', $name)) { | ||
406 | throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name)); | ||
407 | } | ||
408 | |||
409 | return $source; | ||
410 | } | ||
411 | |||
412 | // Twig_ExistsLoaderInterface as of Twig 1.11 | ||
413 | public function exists($name) | ||
414 | { | ||
415 | return $name === $this->getValue('name', $name); | ||
416 | } | ||
417 | |||
418 | public function getCacheKey($name) | ||
419 | { | ||
420 | return $name; | ||
421 | } | ||
422 | |||
423 | public function isFresh($name, $time) | ||
424 | { | ||
425 | if (false === $lastModified = $this->getValue('last_modified', $name)) { | ||
426 | return false; | ||
427 | } | ||
428 | |||
429 | return $lastModified <= $time; | ||
430 | } | ||
431 | |||
432 | protected function getValue($column, $name) | ||
433 | { | ||
434 | $sth = $this->dbh->prepare('SELECT '.$column.' FROM templates WHERE name = :name'); | ||
435 | $sth->execute(array(':name' => (string) $name)); | ||
436 | |||
437 | return $sth->fetchColumn(); | ||
438 | } | ||
439 | } | ||
440 | |||
441 | Finally, here is an example on how you can use it:: | ||
442 | |||
443 | $loader = new DatabaseTwigLoader($dbh); | ||
444 | $twig = new Twig_Environment($loader); | ||
445 | |||
446 | echo $twig->render('index.twig', array('name' => 'Fabien')); | ||
447 | |||
448 | Using different Template Sources | ||
449 | -------------------------------- | ||
450 | |||
451 | This recipe is the continuation of the previous one. Even if you store the | ||
452 | contributed templates in a database, you might want to keep the original/base | ||
453 | templates on the filesystem. When templates can be loaded from different | ||
454 | sources, you need to use the ``Twig_Loader_Chain`` loader. | ||
455 | |||
456 | As you can see in the previous recipe, we reference the template in the exact | ||
457 | same way as we would have done it with a regular filesystem loader. This is | ||
458 | the key to be able to mix and match templates coming from the database, the | ||
459 | filesystem, or any other loader for that matter: the template name should be a | ||
460 | logical name, and not the path from the filesystem:: | ||
461 | |||
462 | $loader1 = new DatabaseTwigLoader($dbh); | ||
463 | $loader2 = new Twig_Loader_Array(array( | ||
464 | 'base.twig' => '{% block content %}{% endblock %}', | ||
465 | )); | ||
466 | $loader = new Twig_Loader_Chain(array($loader1, $loader2)); | ||
467 | |||
468 | $twig = new Twig_Environment($loader); | ||
469 | |||
470 | echo $twig->render('index.twig', array('name' => 'Fabien')); | ||
471 | |||
472 | Now that the ``base.twig`` templates is defined in an array loader, you can | ||
473 | remove it from the database, and everything else will still work as before. | ||
474 | |||
475 | .. _callback: http://www.php.net/manual/en/function.is-callable.php | ||