Project

General

Profile

1
<?php
2

    
3
/*
4
 * This file is part of Twig.
5
 *
6
 * (c) 2009 Fabien Potencier
7
 * (c) 2009 Armin Ronacher
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12

    
13
/**
14
 * Default base class for compiled templates.
15
 *
16
 * @package twig
17
 * @author  Fabien Potencier <fabien@symfony.com>
18
 */
19
abstract class Twig_Template implements Twig_TemplateInterface
20
{
21
    protected static $cache = array();
22

    
23
    protected $parent;
24
    protected $parents;
25
    protected $env;
26
    protected $blocks;
27
    protected $traits;
28

    
29
    /**
30
     * Constructor.
31
     *
32
     * @param Twig_Environment $env A Twig_Environment instance
33
     */
34
    public function __construct(Twig_Environment $env)
35
    {
36
        $this->env = $env;
37
        $this->blocks = array();
38
        $this->traits = array();
39
    }
40

    
41
    /**
42
     * Returns the template name.
43
     *
44
     * @return string The template name
45
     */
46
    abstract public function getTemplateName();
47

    
48
    /**
49
     * {@inheritdoc}
50
     */
51
    public function getEnvironment()
52
    {
53
        return $this->env;
54
    }
55

    
56
    /**
57
     * Returns the parent template.
58
     *
59
     * This method is for internal use only and should never be called
60
     * directly.
61
     *
62
     * @return Twig_TemplateInterface|false The parent template or false if there is no parent
63
     */
64
    public function getParent(array $context)
65
    {
66
        if (null !== $this->parent) {
67
            return $this->parent;
68
        }
69

    
70
        $parent = $this->doGetParent($context);
71
        if (false === $parent) {
72
            return false;
73
        } elseif ($parent instanceof Twig_Template) {
74
            $name = $parent->getTemplateName();
75
            $this->parents[$name] = $parent;
76
            $parent = $name;
77
        } elseif (!isset($this->parents[$parent])) {
78
            $this->parents[$parent] = $this->env->loadTemplate($parent);
79
        }
80

    
81
        return $this->parents[$parent];
82
    }
83

    
84
    protected function doGetParent(array $context)
85
    {
86
        return false;
87
    }
88

    
89
    public function isTraitable()
90
    {
91
        return true;
92
    }
93

    
94
    /**
95
     * Displays a parent block.
96
     *
97
     * This method is for internal use only and should never be called
98
     * directly.
99
     *
100
     * @param string $name    The block name to display from the parent
101
     * @param array  $context The context
102
     * @param array  $blocks  The current set of blocks
103
     */
104
    public function displayParentBlock($name, array $context, array $blocks = array())
105
    {
106
        $name = (string) $name;
107

    
108
        if (isset($this->traits[$name])) {
109
            $this->traits[$name][0]->displayBlock($name, $context, $blocks);
110
        } elseif (false !== $parent = $this->getParent($context)) {
111
            $parent->displayBlock($name, $context, $blocks);
112
        } else {
113
            throw new Twig_Error_Runtime(sprintf('The template has no parent and no traits defining the "%s" block', $name), -1, $this->getTemplateName());
114
        }
115
    }
116

    
117
    /**
118
     * Displays a block.
119
     *
120
     * This method is for internal use only and should never be called
121
     * directly.
122
     *
123
     * @param string $name    The block name to display
124
     * @param array  $context The context
125
     * @param array  $blocks  The current set of blocks
126
     */
127
    public function displayBlock($name, array $context, array $blocks = array())
128
    {
129
        $name = (string) $name;
130

    
131
        if (isset($blocks[$name])) {
132
            $b = $blocks;
133
            unset($b[$name]);
134
            call_user_func($blocks[$name], $context, $b);
135
        } elseif (isset($this->blocks[$name])) {
136
            call_user_func($this->blocks[$name], $context, $blocks);
137
        } elseif (false !== $parent = $this->getParent($context)) {
138
            $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks));
139
        }
140
    }
141

    
142
    /**
143
     * Renders a parent block.
144
     *
145
     * This method is for internal use only and should never be called
146
     * directly.
147
     *
148
     * @param string $name    The block name to render from the parent
149
     * @param array  $context The context
150
     * @param array  $blocks  The current set of blocks
151
     *
152
     * @return string The rendered block
153
     */
154
    public function renderParentBlock($name, array $context, array $blocks = array())
155
    {
156
        ob_start();
157
        $this->displayParentBlock($name, $context, $blocks);
158

    
159
        return ob_get_clean();
160
    }
161

    
162
    /**
163
     * Renders a block.
164
     *
165
     * This method is for internal use only and should never be called
166
     * directly.
167
     *
168
     * @param string $name    The block name to render
169
     * @param array  $context The context
170
     * @param array  $blocks  The current set of blocks
171
     *
172
     * @return string The rendered block
173
     */
174
    public function renderBlock($name, array $context, array $blocks = array())
175
    {
176
        ob_start();
177
        $this->displayBlock($name, $context, $blocks);
178

    
179
        return ob_get_clean();
180
    }
181

    
182
    /**
183
     * Returns whether a block exists or not.
184
     *
185
     * This method is for internal use only and should never be called
186
     * directly.
187
     *
188
     * This method does only return blocks defined in the current template
189
     * or defined in "used" traits.
190
     *
191
     * It does not return blocks from parent templates as the parent
192
     * template name can be dynamic, which is only known based on the
193
     * current context.
194
     *
195
     * @param string $name The block name
196
     *
197
     * @return Boolean true if the block exists, false otherwise
198
     */
199
    public function hasBlock($name)
200
    {
201
        return isset($this->blocks[(string) $name]);
202
    }
203

    
204
    /**
205
     * Returns all block names.
206
     *
207
     * This method is for internal use only and should never be called
208
     * directly.
209
     *
210
     * @return array An array of block names
211
     *
212
     * @see hasBlock
213
     */
214
    public function getBlockNames()
215
    {
216
        return array_keys($this->blocks);
217
    }
218

    
219
    /**
220
     * Returns all blocks.
221
     *
222
     * This method is for internal use only and should never be called
223
     * directly.
224
     *
225
     * @return array An array of blocks
226
     *
227
     * @see hasBlock
228
     */
229
    public function getBlocks()
230
    {
231
        return $this->blocks;
232
    }
233

    
234
    /**
235
     * {@inheritdoc}
236
     */
237
    public function display(array $context, array $blocks = array())
238
    {
239
        $this->displayWithErrorHandling($this->env->mergeGlobals($context), $blocks);
240
    }
241

    
242
    /**
243
     * {@inheritdoc}
244
     */
245
    public function render(array $context)
246
    {
247
        $level = ob_get_level();
248
        ob_start();
249
        try {
250
            $this->display($context);
251
        } catch (Exception $e) {
252
            while (ob_get_level() > $level) {
253
                ob_end_clean();
254
            }
255

    
256
            throw $e;
257
        }
258

    
259
        return ob_get_clean();
260
    }
261

    
262
    protected function displayWithErrorHandling(array $context, array $blocks = array())
263
    {
264
        try {
265
            $this->doDisplay($context, $blocks);
266
        } catch (Twig_Error $e) {
267
            if (!$e->getTemplateFile()) {
268
                $e->setTemplateFile($this->getTemplateName());
269
            }
270

    
271
            // this is mostly useful for Twig_Error_Loader exceptions
272
            // see Twig_Error_Loader
273
            if (false === $e->getTemplateLine()) {
274
                $e->setTemplateLine(-1);
275
                $e->guess();
276
            }
277

    
278
            throw $e;
279
        } catch (Exception $e) {
280
            throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, null, $e);
281
        }
282
    }
283

    
284
    /**
285
     * Auto-generated method to display the template with the given context.
286
     *
287
     * @param array $context An array of parameters to pass to the template
288
     * @param array $blocks  An array of blocks to pass to the template
289
     */
290
    abstract protected function doDisplay(array $context, array $blocks = array());
291

    
292
    /**
293
     * Returns a variable from the context.
294
     *
295
     * This method is for internal use only and should never be called
296
     * directly.
297
     *
298
     * This method should not be overridden in a sub-class as this is an
299
     * implementation detail that has been introduced to optimize variable
300
     * access for versions of PHP before 5.4. This is not a way to override
301
     * the way to get a variable value.
302
     *
303
     * @param array   $context           The context
304
     * @param string  $item              The variable to return from the context
305
     * @param Boolean $ignoreStrictCheck Whether to ignore the strict variable check or not
306
     *
307
     * @return The content of the context variable
308
     *
309
     * @throws Twig_Error_Runtime if the variable does not exist and Twig is running in strict mode
310
     */
311
    final protected function getContext($context, $item, $ignoreStrictCheck = false)
312
    {
313
        if (!array_key_exists($item, $context)) {
314
            if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
315
                return null;
316
            }
317

    
318
            throw new Twig_Error_Runtime(sprintf('Variable "%s" does not exist', $item), -1, $this->getTemplateName());
319
        }
320

    
321
        return $context[$item];
322
    }
323

    
324
    /**
325
     * Returns the attribute value for a given array/object.
326
     *
327
     * @param mixed   $object            The object or array from where to get the item
328
     * @param mixed   $item              The item to get from the array or object
329
     * @param array   $arguments         An array of arguments to pass if the item is an object method
330
     * @param string  $type              The type of attribute (@see Twig_TemplateInterface)
331
     * @param Boolean $isDefinedTest     Whether this is only a defined check
332
     * @param Boolean $ignoreStrictCheck Whether to ignore the strict attribute check or not
333
     *
334
     * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true
335
     *
336
     * @throws Twig_Error_Runtime if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false
337
     */
338
    protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_TemplateInterface::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false)
339
    {
340
        $item = ctype_digit((string) $item) ? (int) $item : (string) $item;
341

    
342
        // array
343
        if (Twig_TemplateInterface::METHOD_CALL !== $type) {
344
            if ((is_array($object) && array_key_exists($item, $object))
345
                || ($object instanceof ArrayAccess && isset($object[$item]))
346
            ) {
347
                if ($isDefinedTest) {
348
                    return true;
349
                }
350

    
351
                return $object[$item];
352
            }
353

    
354
            if (Twig_TemplateInterface::ARRAY_CALL === $type) {
355
                if ($isDefinedTest) {
356
                    return false;
357
                }
358

    
359
                if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
360
                    return null;
361
                }
362

    
363
                if (is_object($object)) {
364
                    throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName());
365
                } elseif (is_array($object)) {
366
                    throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $item, implode(', ', array_keys($object))), -1, $this->getTemplateName());
367
                } else {
368
                    throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a "%s" variable', $item, gettype($object)), -1, $this->getTemplateName());
369
                }
370
            }
371
        }
372

    
373
        if (!is_object($object)) {
374
            if ($isDefinedTest) {
375
                return false;
376
            }
377

    
378
            if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
379
                return null;
380
            }
381

    
382
            throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist', $item, is_array($object) ? 'Array' : $object), -1, $this->getTemplateName());
383
        }
384

    
385
        $class = get_class($object);
386

    
387
        // object property
388
        if (Twig_TemplateInterface::METHOD_CALL !== $type) {
389
            if (isset($object->$item) || array_key_exists($item, $object)) {
390
                if ($isDefinedTest) {
391
                    return true;
392
                }
393

    
394
                if ($this->env->hasExtension('sandbox')) {
395
                    $this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item);
396
                }
397

    
398
                return $object->$item;
399
            }
400
        }
401

    
402
        // object method
403
        if (!isset(self::$cache[$class]['methods'])) {
404
            self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object)));
405
        }
406

    
407
        $lcItem = strtolower($item);
408
        if (isset(self::$cache[$class]['methods'][$lcItem])) {
409
            $method = $item;
410
        } elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) {
411
            $method = 'get'.$item;
412
        } elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) {
413
            $method = 'is'.$item;
414
        } elseif (isset(self::$cache[$class]['methods']['__call'])) {
415
            $method = $item;
416
        } else {
417
            if ($isDefinedTest) {
418
                return false;
419
            }
420

    
421
            if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
422
                return null;
423
            }
424

    
425
            throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName());
426
        }
427

    
428
        if ($isDefinedTest) {
429
            return true;
430
        }
431

    
432
        if ($this->env->hasExtension('sandbox')) {
433
            $this->env->getExtension('sandbox')->checkMethodAllowed($object, $method);
434
        }
435

    
436
        $ret = call_user_func_array(array($object, $method), $arguments);
437

    
438
        // useful when calling a template method from a template
439
        // this is not supported but unfortunately heavily used in the Symfony profiler
440
        if ($object instanceof Twig_TemplateInterface) {
441
            return $ret === '' ? '' : new Twig_Markup($ret, $this->env->getCharset());
442
        }
443

    
444
        return $ret;
445
    }
446

    
447
    /**
448
     * This method is only useful when testing Twig. Do not use it.
449
     */
450
    public static function clearCache()
451
    {
452
        self::$cache = array();
453
    }
454
}
(26-26/34)