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
 * @author Fabien Potencier <fabien@symfony.com>
17
 */
18
abstract class Twig_Template implements Twig_TemplateInterface
19
{
20
    protected static $cache = array();
21

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
158
        return ob_get_clean();
159
    }
160

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

    
178
        return ob_get_clean();
179
    }
180

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

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

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

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

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

    
255
            throw $e;
256
        }
257

    
258
        return ob_get_clean();
259
    }
260

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

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

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

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

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

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

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

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

    
343
            if ((is_array($object) && array_key_exists($arrayItem, $object))
344
                || ($object instanceof ArrayAccess && isset($object[$arrayItem]))
345
            ) {
346
                if ($isDefinedTest) {
347
                    return true;
348
                }
349

    
350
                return $object[$arrayItem];
351
            }
352

    
353
            if (Twig_TemplateInterface::ARRAY_CALL === $type || !is_object($object)) {
354
                if ($isDefinedTest) {
355
                    return false;
356
                }
357

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

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

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

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

    
383
            throw new Twig_Error_Runtime(sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName());
384
        }
385

    
386
        $class = get_class($object);
387

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

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

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

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

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

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

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

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

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

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

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

    
445
        return $ret;
446
    }
447

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