Browse code

exposed a way to access template data and methods in a portable way

Fabien Potencier authored on 12/11/2016 16:50:38
Showing 10 changed files
... ...
@@ -1,5 +1,6 @@
1 1
 * 1.28.0 (2016-XX-XX)
2 2
 
3
+ * exposed a way to access template data and methods in a portable way
3 4
  * changed context access to use the PHP 7 null coalescing operator when available
4 5
  * added the "with" tag
5 6
  * added support for a custom template on the block() function
... ...
@@ -43,10 +43,18 @@ templates from a database or other resources.
43 43
     the evaluated templates. For such a need, you can use any available PHP
44 44
     cache library.
45 45
 
46
-To load a template from this environment you just have to call the
47
-``loadTemplate()`` method which then returns a ``Twig_Template`` instance::
46
+Rendering Templates
47
+-------------------
48
+
49
+To load a template from a Twig environment, call the ``load()`` method which
50
+returns a ``Twig_TemplateWrapper`` instance::
51
+
52
+    $template = $twig->load('index.html');
53
+
54
+.. note::
48 55
 
49
-    $template = $twig->loadTemplate('index.html');
56
+    Before Twig 1.28, you should use ``loadTemplate()`` instead which returns a
57
+    ``Twig_Template`` instance.
50 58
 
51 59
 To render the template with some variables, call the ``render()`` method::
52 60
 
... ...
@@ -60,6 +68,14 @@ You can also load and render the template in one fell swoop::
60 68
 
61 69
     echo $twig->render('index.html', array('the' => 'variables', 'go' => 'here'));
62 70
 
71
+.. versionadded:: 1.28
72
+    The possibility to render blocks from the API was added in Twig 1.28.
73
+
74
+If a template defines blocks, they can be rendered individually via the
75
+``renderBlock()`` call::
76
+
77
+    echo $template->renderBlock('block_name', array('the' => 'variables', 'go' => 'here'));
78
+
63 79
 .. _environment_options:
64 80
 
65 81
 Environment Options
... ...
@@ -37,14 +37,18 @@ You can disable access to the context by setting ``with_context`` to
37 37
     {# no variables will be accessible #}
38 38
     {{ include('template.html', with_context = false) }}
39 39
 
40
-And if the expression evaluates to a ``Twig_Template`` object, Twig will use it
41
-directly::
40
+And if the expression evaluates to a ``Twig_Template`` or a
41
+``Twig_TemplateWrapper`` instance, Twig will use it directly::
42 42
 
43 43
     // {{ include(template) }}
44 44
 
45
+    // deprecated as of Twig 1.28
45 46
     $template = $twig->loadTemplate('some_template.twig');
46 47
 
47
-    $twig->loadTemplate('template.twig')->display(array('template' => $template));
48
+    // as of Twig 1.28
49
+    $template = $twig->load('some_template.twig');
50
+
51
+    $twig->display('template.twig', array('template' => $template));
48 52
 
49 53
 When you set the ``ignore_missing`` flag, Twig will return an empty string if
50 54
 the template does not exist:
... ...
@@ -153,13 +153,17 @@ Twig supports dynamic inheritance by using a variable as the base template:
153 153
 
154 154
     {% extends some_var %}
155 155
 
156
-If the variable evaluates to a ``Twig_Template`` object, Twig will use it as
157
-the parent template::
156
+If the variable evaluates to a ``Twig_Template`` or a ``Twig_TemplateWraper``
157
+instance, Twig will use it as the parent template::
158 158
 
159 159
     // {% extends layout %}
160 160
 
161
+    // deprecated as of Twig 1.28
161 162
     $layout = $twig->loadTemplate('some_layout_template.twig');
162 163
 
164
+    // as of Twig 1.28
165
+    $layout = $twig->load('some_layout_template.twig');
166
+
163 167
     $twig->display('template.twig', array('layout' => $layout));
164 168
 
165 169
 .. versionadded:: 1.2
... ...
@@ -50,14 +50,18 @@ The template name can be any valid Twig expression:
50 50
     {% include some_var %}
51 51
     {% include ajax ? 'ajax.html' : 'not_ajax.html' %}
52 52
 
53
-And if the expression evaluates to a ``Twig_Template`` object, Twig will use it
54
-directly::
53
+And if the expression evaluates to a ``Twig_Template`` or a
54
+``Twig_TemplateWrapper`` instance, Twig will use it directly::
55 55
 
56 56
     // {% include template %}
57 57
 
58
+    // deprecated as of Twig 1.28
58 59
     $template = $twig->loadTemplate('some_template.twig');
59 60
 
60
-    $twig->loadTemplate('template.twig')->display(array('template' => $template));
61
+    // as of Twig 1.28
62
+    $template = $twig->load('some_template.twig');
63
+
64
+    $twig->display('template.twig', array('template' => $template));
61 65
 
62 66
 .. versionadded:: 1.2
63 67
     The ``ignore missing`` feature has been added in Twig 1.2.
... ...
@@ -378,7 +378,30 @@ class Twig_Environment
378 378
     }
379 379
 
380 380
     /**
381
-     * Loads a template by name.
381
+     * Loads a template.
382
+     *
383
+     * @param string|Twig_TemplateWrapper|Twig_Template $name The template name
384
+     *
385
+     * @return Twig_TemplateWrapper
386
+     */
387
+    public function load($name)
388
+    {
389
+        if ($name instanceof Twig_TemplateWrapper) {
390
+            return $name;
391
+        }
392
+
393
+        if ($name instanceof Twig_Template) {
394
+            return new Twig_TemplateWrapper($this, $name);
395
+        }
396
+
397
+        return new Twig_TemplateWrapper($this, $this->loadTemplate($name));
398
+    }
399
+
400
+    /**
401
+     * Loads a template internal representation.
402
+     *
403
+     * This method is for internal use only and should never be called
404
+     * directly.
382 405
      *
383 406
      * @param string $name  The template name
384 407
      * @param int    $index The index if it is an embedded template
... ...
@@ -387,6 +410,8 @@ class Twig_Environment
387 410
      *
388 411
      * @throws Twig_Error_Loader When the template cannot be found
389 412
      * @throws Twig_Error_Syntax When an error occurred during compilation
413
+     *
414
+     * @internal
390 415
      */
391 416
     public function loadTemplate($name, $index = null)
392 417
     {
... ...
@@ -17,7 +17,13 @@ if (PHP_VERSION_ID >= 50600) {
17 17
 /**
18 18
  * Default base class for compiled templates.
19 19
  *
20
+ * This class is an implementation detail of how template compilation currently
21
+ * works, which might change. It should never be used directly. Use $twig->load()
22
+ * instead, which returns an instance of Twig_TemplateWrapper.
23
+ *
20 24
  * @author Fabien Potencier <fabien@symfony.com>
25
+ *
26
+ * @internal
21 27
  */
22 28
 abstract class Twig_Template implements Twig_TemplateInterface
23 29
 {
... ...
@@ -307,18 +313,34 @@ abstract class Twig_Template implements Twig_TemplateInterface
307 313
     }
308 314
 
309 315
     /**
310
-     * Returns all block names.
316
+     * Returns all block names in the current context of the template.
311 317
      *
312
-     * This method is for internal use only and should never be called
313
-     * directly.
318
+     * This method checks blocks defined in the current template
319
+     * or defined in "used" traits or defined in parent templates.
320
+     *
321
+     * @param string $name    The block name
322
+     * @param array  $context The context
323
+     * @param array  $blocks  The current set of blocks
314 324
      *
315 325
      * @return array An array of block names
316 326
      *
317 327
      * @internal
318 328
      */
319
-    public function getBlockNames()
329
+    public function getBlockNames(array $context = null, array $blocks = array())
320 330
     {
321
-        return array_keys($this->blocks);
331
+        if (null === $context) {
332
+            @trigger_error('The '.__METHOD__.' method is internal and should never be called; calling it directly is deprecated since version 1.28 and won\'t be possible anymore in 2.0.', E_USER_DEPRECATED);
333
+
334
+            return array_keys($this->blocks);
335
+        }
336
+
337
+        $names = array_merge(array_keys($blocks), array_keys($this->blocks));
338
+
339
+        if (false !== $parent = $this->getParent($context)) {
340
+            $names = array_merge($names, $parent->getBlockNames($context));
341
+        }
342
+
343
+        return array_unique($names);
322 344
     }
323 345
 
324 346
     protected function loadTemplate($template, $templateName = null, $line = null, $index = null)
... ...
@@ -332,6 +354,10 @@ abstract class Twig_Template implements Twig_TemplateInterface
332 354
                 return $template;
333 355
             }
334 356
 
357
+            if ($template instanceof Twig_TemplateWrapper) {
358
+                return $template;
359
+            }
360
+
335 361
             return $this->env->loadTemplate($template, $index);
336 362
         } catch (Twig_Error $e) {
337 363
             if (!$e->getTemplateName()) {
... ...
@@ -654,9 +680,10 @@ abstract class Twig_Template implements Twig_TemplateInterface
654 680
             throw $e;
655 681
         }
656 682
 
657
-        // useful when calling a template method from a template
658
-        // this is not supported but unfortunately heavily used in the Symfony profiler
683
+        // @deprecated in 1.28
659 684
         if ($object instanceof Twig_TemplateInterface) {
685
+            @trigger_error('Using the dot notation on an instance of '.__CLASS.' is deprecated since version 1.28 and won\'t be supported anymore in 2.0.', E_USER_DEPRECATED);
686
+
660 687
             return $ret === '' ? '' : new Twig_Markup($ret, $this->env->getCharset());
661 688
         }
662 689
 
663 690
new file mode 100644
... ...
@@ -0,0 +1,134 @@
1
+<?php
2
+
3
+/*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2016 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+/**
13
+ * Exposes a template to userland.
14
+ *
15
+ * @author Fabien Potencier <fabien@symfony.com>
16
+ */
17
+final class Twig_TemplateWrapper
18
+{
19
+    private $env;
20
+    private $template;
21
+
22
+    /**
23
+     * This method is for internal use only and should never be called
24
+     * directly (use Twig_Environment::load() instead).
25
+     *
26
+     * @internal
27
+     */
28
+    public function __construct(Twig_Environment $env, Twig_Template $template)
29
+    {
30
+        $this->env = $env;
31
+        $this->template = $template;
32
+    }
33
+
34
+    /**
35
+     * Renders the template.
36
+     *
37
+     * @param array $context An array of parameters to pass to the template
38
+     *
39
+     * @return string The rendered template
40
+     */
41
+    public function render($context = array())
42
+    {
43
+        return $this->template->render($context);
44
+    }
45
+
46
+    /**
47
+     * Displays the template.
48
+     *
49
+     * @param array $context An array of parameters to pass to the template
50
+     */
51
+    public function display($context = array())
52
+    {
53
+        return $this->template->display($context);
54
+    }
55
+
56
+    /**
57
+     * Checks if a block is defined.
58
+     *
59
+     * @param string $name    The block name
60
+     * @param array  $context An array of parameters to pass to the template
61
+     *
62
+     * @return bool
63
+     */
64
+    public function hasBlock($name, $context = array())
65
+    {
66
+        return $this->template->hasBlock($name, $context);
67
+    }
68
+
69
+    /**
70
+     * Returns defined block names in the template.
71
+     *
72
+     * @param array $context An array of parameters to pass to the template
73
+     *
74
+     * @return string[] An array of defined template block names
75
+     */
76
+    public function getBlockNames($context = array())
77
+    {
78
+        return $this->template->getBlockNames($context);
79
+    }
80
+
81
+    /**
82
+     * Renders a template block.
83
+     *
84
+     * @param string $name    The block name to render
85
+     * @param array  $context An array of parameters to pass to the template
86
+     *
87
+     * @return string The rendered block
88
+     */
89
+    public function renderBlock($name, $context = array())
90
+    {
91
+        ob_start();
92
+        $this->displayBlock($name, $context);
93
+
94
+        return ob_get_clean();
95
+    }
96
+
97
+    /**
98
+     * Displays a template block.
99
+     *
100
+     * @param string $name    The block name to render
101
+     * @param array  $context An array of parameters to pass to the template
102
+     */
103
+    public function displayBlock($name, $context = array())
104
+    {
105
+        $context = $this->env->mergeGlobals($context);
106
+        $level = ob_get_level();
107
+        ob_start();
108
+        try {
109
+            $this->template->displayBlock($name, $context);
110
+        } catch (Exception $e) {
111
+            while (ob_get_level() > $level) {
112
+                ob_end_clean();
113
+            }
114
+
115
+            throw $e;
116
+        } catch (Throwable $e) {
117
+            while (ob_get_level() > $level) {
118
+                ob_end_clean();
119
+            }
120
+
121
+            throw $e;
122
+        }
123
+
124
+        return ob_get_clean();
125
+    }
126
+
127
+    /**
128
+     * @return Twig_Source
129
+     */
130
+    public function getSourceContext()
131
+    {
132
+        return $this->template->getSourceContext();
133
+    }
134
+}
... ...
@@ -130,6 +130,7 @@ class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
130 130
 
131 131
     /**
132 132
      * @dataProvider getGetAttributeWithTemplateAsObject
133
+     * @group legacy
133 134
      */
134 135
     public function testGetAttributeWithTemplateAsObject($useExt)
135 136
     {
136 137
new file mode 100644
... ...
@@ -0,0 +1,38 @@
1
+<?php
2
+
3
+/*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+class Twig_Tests_TemplateWrapperTest extends PHPUnit_Framework_TestCase
12
+{
13
+    public function testHasGetBlocks()
14
+    {
15
+        $twig = new Twig_Environment(new Twig_Loader_Array(array(
16
+            'index' => '{% block foo %}{% endblock %}',
17
+            'index_with_use' => '{% use "imported" %}{% block foo %}{% endblock %}',
18
+            'index_with_extends' => '{% extends "extended" %}{% block foo %}{% endblock %}',
19
+            'imported' => '{% block imported %}{% endblock %}',
20
+            'extended' => '{% block extended %}{% endblock %}',
21
+        )));
22
+
23
+        $wrapper = new Twig_TemplateWrapper($twig, $twig->loadTemplate('index'));
24
+        $this->assertTrue($wrapper->hasBlock('foo'));
25
+        $this->assertFalse($wrapper->hasBlock('bar'));
26
+        $this->assertEquals(array('foo'), $wrapper->getBlockNames());
27
+
28
+        $wrapper = new Twig_TemplateWrapper($twig, $twig->loadTemplate('index_with_use'));
29
+        $this->assertTrue($wrapper->hasBlock('foo'));
30
+        $this->assertTrue($wrapper->hasBlock('imported'));
31
+        $this->assertEquals(array('imported', 'foo'), $wrapper->getBlockNames());
32
+
33
+        $wrapper = new Twig_TemplateWrapper($twig, $twig->loadTemplate('index_with_extends'));
34
+        $this->assertTrue($wrapper->hasBlock('foo'));
35
+        $this->assertTrue($wrapper->hasBlock('extended'));
36
+        $this->assertEquals(array('foo', 'extended'), $wrapper->getBlockNames());
37
+    }
38
+}