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
12/**
13 * Stores the Twig configuration.
14 *
15 * @author Fabien Potencier <fabien@symfony.com>
16 */
17class Twig_Environment
18{
19    const VERSION = '1.35.3';
20    const VERSION_ID = 13503;
21    const MAJOR_VERSION = 1;
22    const MINOR_VERSION = 35;
23    const RELEASE_VERSION = 3;
24    const EXTRA_VERSION = '';
25
26    protected $charset;
27    protected $loader;
28    protected $debug;
29    protected $autoReload;
30    protected $cache;
31    protected $lexer;
32    protected $parser;
33    protected $compiler;
34    protected $baseTemplateClass;
35    protected $extensions;
36    protected $parsers;
37    protected $visitors;
38    protected $filters;
39    protected $tests;
40    protected $functions;
41    protected $globals;
42    protected $runtimeInitialized = false;
43    protected $extensionInitialized = false;
44    protected $loadedTemplates;
45    protected $strictVariables;
46    protected $unaryOperators;
47    protected $binaryOperators;
48    protected $templateClassPrefix = '__TwigTemplate_';
49    protected $functionCallbacks = array();
50    protected $filterCallbacks = array();
51    protected $staging;
52
53    private $originalCache;
54    private $bcWriteCacheFile = false;
55    private $bcGetCacheFilename = false;
56    private $lastModifiedExtension = 0;
57    private $extensionsByClass = array();
58    private $runtimeLoaders = array();
59    private $runtimes = array();
60    private $optionsHash;
61    private $loading = array();
62
63    /**
64     * Constructor.
65     *
66     * Available options:
67     *
68     *  * debug: When set to true, it automatically set "auto_reload" to true as
69     *           well (default to false).
70     *
71     *  * charset: The charset used by the templates (default to UTF-8).
72     *
73     *  * base_template_class: The base template class to use for generated
74     *                         templates (default to Twig_Template).
75     *
76     *  * cache: An absolute path where to store the compiled templates,
77     *           a Twig_Cache_Interface implementation,
78     *           or false to disable compilation cache (default).
79     *
80     *  * auto_reload: Whether to reload the template if the original source changed.
81     *                 If you don't provide the auto_reload option, it will be
82     *                 determined automatically based on the debug value.
83     *
84     *  * strict_variables: Whether to ignore invalid variables in templates
85     *                      (default to false).
86     *
87     *  * autoescape: Whether to enable auto-escaping (default to html):
88     *                  * false: disable auto-escaping
89     *                  * true: equivalent to html
90     *                  * html, js: set the autoescaping to one of the supported strategies
91     *                  * name: set the autoescaping strategy based on the template name extension
92     *                  * PHP callback: a PHP callback that returns an escaping strategy based on the template "name"
93     *
94     *  * optimizations: A flag that indicates which optimizations to apply
95     *                   (default to -1 which means that all optimizations are enabled;
96     *                   set it to 0 to disable).
97     *
98     * @param Twig_LoaderInterface $loader
99     * @param array                $options An array of options
100     */
101    public function __construct(Twig_LoaderInterface $loader = null, $options = array())
102    {
103        if (null !== $loader) {
104            $this->setLoader($loader);
105        } else {
106            @trigger_error('Not passing a Twig_LoaderInterface as the first constructor argument of Twig_Environment is deprecated since version 1.21.', E_USER_DEPRECATED);
107        }
108
109        $options = array_merge(array(
110            'debug' => false,
111            'charset' => 'UTF-8',
112            'base_template_class' => 'Twig_Template',
113            'strict_variables' => false,
114            'autoescape' => 'html',
115            'cache' => false,
116            'auto_reload' => null,
117            'optimizations' => -1,
118        ), $options);
119
120        $this->debug = (bool) $options['debug'];
121        $this->charset = strtoupper($options['charset']);
122        $this->baseTemplateClass = $options['base_template_class'];
123        $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
124        $this->strictVariables = (bool) $options['strict_variables'];
125        $this->setCache($options['cache']);
126
127        $this->addExtension(new Twig_Extension_Core());
128        $this->addExtension(new Twig_Extension_Escaper($options['autoescape']));
129        $this->addExtension(new Twig_Extension_Optimizer($options['optimizations']));
130        $this->staging = new Twig_Extension_Staging();
131
132        // For BC
133        if (is_string($this->originalCache)) {
134            $r = new ReflectionMethod($this, 'writeCacheFile');
135            if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
136                @trigger_error('The Twig_Environment::writeCacheFile method is deprecated since version 1.22 and will be removed in Twig 2.0.', E_USER_DEPRECATED);
137
138                $this->bcWriteCacheFile = true;
139            }
140
141            $r = new ReflectionMethod($this, 'getCacheFilename');
142            if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
143                @trigger_error('The Twig_Environment::getCacheFilename method is deprecated since version 1.22 and will be removed in Twig 2.0.', E_USER_DEPRECATED);
144
145                $this->bcGetCacheFilename = true;
146            }
147        }
148    }
149
150    /**
151     * Gets the base template class for compiled templates.
152     *
153     * @return string The base template class name
154     */
155    public function getBaseTemplateClass()
156    {
157        return $this->baseTemplateClass;
158    }
159
160    /**
161     * Sets the base template class for compiled templates.
162     *
163     * @param string $class The base template class name
164     */
165    public function setBaseTemplateClass($class)
166    {
167        $this->baseTemplateClass = $class;
168        $this->updateOptionsHash();
169    }
170
171    /**
172     * Enables debugging mode.
173     */
174    public function enableDebug()
175    {
176        $this->debug = true;
177        $this->updateOptionsHash();
178    }
179
180    /**
181     * Disables debugging mode.
182     */
183    public function disableDebug()
184    {
185        $this->debug = false;
186        $this->updateOptionsHash();
187    }
188
189    /**
190     * Checks if debug mode is enabled.
191     *
192     * @return bool true if debug mode is enabled, false otherwise
193     */
194    public function isDebug()
195    {
196        return $this->debug;
197    }
198
199    /**
200     * Enables the auto_reload option.
201     */
202    public function enableAutoReload()
203    {
204        $this->autoReload = true;
205    }
206
207    /**
208     * Disables the auto_reload option.
209     */
210    public function disableAutoReload()
211    {
212        $this->autoReload = false;
213    }
214
215    /**
216     * Checks if the auto_reload option is enabled.
217     *
218     * @return bool true if auto_reload is enabled, false otherwise
219     */
220    public function isAutoReload()
221    {
222        return $this->autoReload;
223    }
224
225    /**
226     * Enables the strict_variables option.
227     */
228    public function enableStrictVariables()
229    {
230        $this->strictVariables = true;
231        $this->updateOptionsHash();
232    }
233
234    /**
235     * Disables the strict_variables option.
236     */
237    public function disableStrictVariables()
238    {
239        $this->strictVariables = false;
240        $this->updateOptionsHash();
241    }
242
243    /**
244     * Checks if the strict_variables option is enabled.
245     *
246     * @return bool true if strict_variables is enabled, false otherwise
247     */
248    public function isStrictVariables()
249    {
250        return $this->strictVariables;
251    }
252
253    /**
254     * Gets the current cache implementation.
255     *
256     * @param bool $original Whether to return the original cache option or the real cache instance
257     *
258     * @return Twig_CacheInterface|string|false A Twig_CacheInterface implementation,
259     *                                          an absolute path to the compiled templates,
260     *                                          or false to disable cache
261     */
262    public function getCache($original = true)
263    {
264        return $original ? $this->originalCache : $this->cache;
265    }
266
267    /**
268     * Sets the current cache implementation.
269     *
270     * @param Twig_CacheInterface|string|false $cache A Twig_CacheInterface implementation,
271     *                                                an absolute path to the compiled templates,
272     *                                                or false to disable cache
273     */
274    public function setCache($cache)
275    {
276        if (is_string($cache)) {
277            $this->originalCache = $cache;
278            $this->cache = new Twig_Cache_Filesystem($cache);
279        } elseif (false === $cache) {
280            $this->originalCache = $cache;
281            $this->cache = new Twig_Cache_Null();
282        } elseif (null === $cache) {
283            @trigger_error('Using "null" as the cache strategy is deprecated since version 1.23 and will be removed in Twig 2.0.', E_USER_DEPRECATED);
284            $this->originalCache = false;
285            $this->cache = new Twig_Cache_Null();
286        } elseif ($cache instanceof Twig_CacheInterface) {
287            $this->originalCache = $this->cache = $cache;
288        } else {
289            throw new LogicException(sprintf('Cache can only be a string, false, or a Twig_CacheInterface implementation.'));
290        }
291    }
292
293    /**
294     * Gets the cache filename for a given template.
295     *
296     * @param string $name The template name
297     *
298     * @return string|false The cache file name or false when caching is disabled
299     *
300     * @deprecated since 1.22 (to be removed in 2.0)
301     */
302    public function getCacheFilename($name)
303    {
304        @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED);
305
306        $key = $this->cache->generateKey($name, $this->getTemplateClass($name));
307
308        return !$key ? false : $key;
309    }
310
311    /**
312     * Gets the template class associated with the given string.
313     *
314     * The generated template class is based on the following parameters:
315     *
316     *  * The cache key for the given template;
317     *  * The currently enabled extensions;
318     *  * Whether the Twig C extension is available or not;
319     *  * PHP version;
320     *  * Twig version;
321     *  * Options with what environment was created.
322     *
323     * @param string   $name  The name for which to calculate the template class name
324     * @param int|null $index The index if it is an embedded template
325     *
326     * @return string The template class name
327     */
328    public function getTemplateClass($name, $index = null)
329    {
330        $key = $this->getLoader()->getCacheKey($name).$this->optionsHash;
331
332        return $this->templateClassPrefix.hash('sha256', $key).(null === $index ? '' : '_'.$index);
333    }
334
335    /**
336     * Gets the template class prefix.
337     *
338     * @return string The template class prefix
339     *
340     * @deprecated since 1.22 (to be removed in 2.0)
341     */
342    public function getTemplateClassPrefix()
343    {
344        @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED);
345
346        return $this->templateClassPrefix;
347    }
348
349    /**
350     * Renders a template.
351     *
352     * @param string $name    The template name
353     * @param array  $context An array of parameters to pass to the template
354     *
355     * @return string The rendered template
356     *
357     * @throws Twig_Error_Loader  When the template cannot be found
358     * @throws Twig_Error_Syntax  When an error occurred during compilation
359     * @throws Twig_Error_Runtime When an error occurred during rendering
360     */
361    public function render($name, array $context = array())
362    {
363        return $this->loadTemplate($name)->render($context);
364    }
365
366    /**
367     * Displays a template.
368     *
369     * @param string $name    The template name
370     * @param array  $context An array of parameters to pass to the template
371     *
372     * @throws Twig_Error_Loader  When the template cannot be found
373     * @throws Twig_Error_Syntax  When an error occurred during compilation
374     * @throws Twig_Error_Runtime When an error occurred during rendering
375     */
376    public function display($name, array $context = array())
377    {
378        $this->loadTemplate($name)->display($context);
379    }
380
381    /**
382     * Loads a template.
383     *
384     * @param string|Twig_TemplateWrapper|Twig_Template $name The template name
385     *
386     * @throws Twig_Error_Loader  When the template cannot be found
387     * @throws Twig_Error_Runtime When a previously generated cache is corrupted
388     * @throws Twig_Error_Syntax  When an error occurred during compilation
389     *
390     * @return Twig_TemplateWrapper
391     */
392    public function load($name)
393    {
394        if ($name instanceof Twig_TemplateWrapper) {
395            return $name;
396        }
397
398        if ($name instanceof Twig_Template) {
399            return new Twig_TemplateWrapper($this, $name);
400        }
401
402        return new Twig_TemplateWrapper($this, $this->loadTemplate($name));
403    }
404
405    /**
406     * Loads a template internal representation.
407     *
408     * This method is for internal use only and should never be called
409     * directly.
410     *
411     * @param string $name  The template name
412     * @param int    $index The index if it is an embedded template
413     *
414     * @return Twig_TemplateInterface A template instance representing the given template name
415     *
416     * @throws Twig_Error_Loader  When the template cannot be found
417     * @throws Twig_Error_Runtime When a previously generated cache is corrupted
418     * @throws Twig_Error_Syntax  When an error occurred during compilation
419     *
420     * @internal
421     */
422    public function loadTemplate($name, $index = null)
423    {
424        $cls = $mainCls = $this->getTemplateClass($name);
425        if (null !== $index) {
426            $cls .= '_'.$index;
427        }
428
429        if (isset($this->loadedTemplates[$cls])) {
430            return $this->loadedTemplates[$cls];
431        }
432
433        if (!class_exists($cls, false)) {
434            if ($this->bcGetCacheFilename) {
435                $key = $this->getCacheFilename($name);
436            } else {
437                $key = $this->cache->generateKey($name, $mainCls);
438            }
439
440            if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) {
441                $this->cache->load($key);
442            }
443
444            if (!class_exists($cls, false)) {
445                $loader = $this->getLoader();
446                if (!$loader instanceof Twig_SourceContextLoaderInterface) {
447                    $source = new Twig_Source($loader->getSource($name), $name);
448                } else {
449                    $source = $loader->getSourceContext($name);
450                }
451
452                $content = $this->compileSource($source);
453
454                if ($this->bcWriteCacheFile) {
455                    $this->writeCacheFile($key, $content);
456                } else {
457                    $this->cache->write($key, $content);
458                    $this->cache->load($key);
459                }
460
461                if (!class_exists($mainCls, false)) {
462                    /* Last line of defense if either $this->bcWriteCacheFile was used,
463                     * $this->cache is implemented as a no-op or we have a race condition
464                     * where the cache was cleared between the above calls to write to and load from
465                     * the cache.
466                     */
467                    eval('?>'.$content);
468                }
469            }
470
471            if (!class_exists($cls, false)) {
472                throw new Twig_Error_Runtime(sprintf('Failed to load Twig template "%s", index "%s": cache is corrupted.', $name, $index), -1, $source);
473            }
474        }
475
476        if (!$this->runtimeInitialized) {
477            $this->initRuntime();
478        }
479
480        if (isset($this->loading[$cls])) {
481            throw new Twig_Error_Runtime(sprintf('Circular reference detected for Twig template "%s", path: %s.', $name, implode(' -> ', array_merge($this->loading, array($name)))));
482        }
483
484        $this->loading[$cls] = $name;
485
486        try {
487            $this->loadedTemplates[$cls] = new $cls($this);
488            unset($this->loading[$cls]);
489        } catch (\Exception $e) {
490            unset($this->loading[$cls]);
491
492            throw $e;
493        }
494
495        return $this->loadedTemplates[$cls];
496    }
497
498    /**
499     * Creates a template from source.
500     *
501     * This method should not be used as a generic way to load templates.
502     *
503     * @param string $template The template name
504     *
505     * @return Twig_Template A template instance representing the given template name
506     *
507     * @throws Twig_Error_Loader When the template cannot be found
508     * @throws Twig_Error_Syntax When an error occurred during compilation
509     */
510    public function createTemplate($template)
511    {
512        $name = sprintf('__string_template__%s', hash('sha256', $template, false));
513
514        $loader = new Twig_Loader_Chain(array(
515            new Twig_Loader_Array(array($name => $template)),
516            $current = $this->getLoader(),
517        ));
518
519        $this->setLoader($loader);
520        try {
521            $template = $this->loadTemplate($name);
522        } catch (Exception $e) {
523            $this->setLoader($current);
524
525            throw $e;
526        } catch (Throwable $e) {
527            $this->setLoader($current);
528
529            throw $e;
530        }
531        $this->setLoader($current);
532
533        return $template;
534    }
535
536    /**
537     * Returns true if the template is still fresh.
538     *
539     * Besides checking the loader for freshness information,
540     * this method also checks if the enabled extensions have
541     * not changed.
542     *
543     * @param string $name The template name
544     * @param int    $time The last modification time of the cached template
545     *
546     * @return bool true if the template is fresh, false otherwise
547     */
548    public function isTemplateFresh($name, $time)
549    {
550        if (0 === $this->lastModifiedExtension) {
551            foreach ($this->extensions as $extension) {
552                $r = new ReflectionObject($extension);
553                if (file_exists($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModifiedExtension) {
554                    $this->lastModifiedExtension = $extensionTime;
555                }
556            }
557        }
558
559        return $this->lastModifiedExtension <= $time && $this->getLoader()->isFresh($name, $time);
560    }
561
562    /**
563     * Tries to load a template consecutively from an array.
564     *
565     * Similar to loadTemplate() but it also accepts instances of Twig_Template and
566     * Twig_TemplateWrapper, and an array of templates where each is tried to be loaded.
567     *
568     * @param string|Twig_Template|Twig_TemplateWrapper|array $names A template or an array of templates to try consecutively
569     *
570     * @return Twig_Template|Twig_TemplateWrapper
571     *
572     * @throws Twig_Error_Loader When none of the templates can be found
573     * @throws Twig_Error_Syntax When an error occurred during compilation
574     */
575    public function resolveTemplate($names)
576    {
577        if (!is_array($names)) {
578            $names = array($names);
579        }
580
581        foreach ($names as $name) {
582            if ($name instanceof Twig_Template) {
583                return $name;
584            }
585
586            if ($name instanceof Twig_TemplateWrapper) {
587                return $name;
588            }
589
590            try {
591                return $this->loadTemplate($name);
592            } catch (Twig_Error_Loader $e) {
593            }
594        }
595
596        if (1 === count($names)) {
597            throw $e;
598        }
599
600        throw new Twig_Error_Loader(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names)));
601    }
602
603    /**
604     * Clears the internal template cache.
605     *
606     * @deprecated since 1.18.3 (to be removed in 2.0)
607     */
608    public function clearTemplateCache()
609    {
610        @trigger_error(sprintf('The %s method is deprecated since version 1.18.3 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED);
611
612        $this->loadedTemplates = array();
613    }
614
615    /**
616     * Clears the template cache files on the filesystem.
617     *
618     * @deprecated since 1.22 (to be removed in 2.0)
619     */
620    public function clearCacheFiles()
621    {
622        @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED);
623
624        if (is_string($this->originalCache)) {
625            foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->originalCache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
626                if ($file->isFile()) {
627                    @unlink($file->getPathname());
628                }
629            }
630        }
631    }
632
633    /**
634     * Gets the Lexer instance.
635     *
636     * @return Twig_LexerInterface
637     *
638     * @deprecated since 1.25 (to be removed in 2.0)
639     */
640    public function getLexer()
641    {
642        @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED);
643
644        if (null === $this->lexer) {
645            $this->lexer = new Twig_Lexer($this);
646        }
647
648        return $this->lexer;
649    }
650
651    public function setLexer(Twig_LexerInterface $lexer)
652    {
653        $this->lexer = $lexer;
654    }
655
656    /**
657     * Tokenizes a source code.
658     *
659     * @param string|Twig_Source $source The template source code
660     * @param string             $name   The template name (deprecated)
661     *
662     * @return Twig_TokenStream
663     *
664     * @throws Twig_Error_Syntax When the code is syntactically wrong
665     */
666    public function tokenize($source, $name = null)
667    {
668        if (!$source instanceof Twig_Source) {
669            @trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED);
670            $source = new Twig_Source($source, $name);
671        }
672
673        if (null === $this->lexer) {
674            $this->lexer = new Twig_Lexer($this);
675        }
676
677        return $this->lexer->tokenize($source);
678    }
679
680    /**
681     * Gets the Parser instance.
682     *
683     * @return Twig_ParserInterface
684     *
685     * @deprecated since 1.25 (to be removed in 2.0)
686     */
687    public function getParser()
688    {
689        @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED);
690
691        if (null === $this->parser) {
692            $this->parser = new Twig_Parser($this);
693        }
694
695        return $this->parser;
696    }
697
698    public function setParser(Twig_ParserInterface $parser)
699    {
700        $this->parser = $parser;
701    }
702
703    /**
704     * Converts a token stream to a node tree.
705     *
706     * @return Twig_Node_Module
707     *
708     * @throws Twig_Error_Syntax When the token stream is syntactically or semantically wrong
709     */
710    public function parse(Twig_TokenStream $stream)
711    {
712        if (null === $this->parser) {
713            $this->parser = new Twig_Parser($this);
714        }
715
716        return $this->parser->parse($stream);
717    }
718
719    /**
720     * Gets the Compiler instance.
721     *
722     * @return Twig_CompilerInterface
723     *
724     * @deprecated since 1.25 (to be removed in 2.0)
725     */
726    public function getCompiler()
727    {
728        @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED);
729
730        if (null === $this->compiler) {
731            $this->compiler = new Twig_Compiler($this);
732        }
733
734        return $this->compiler;
735    }
736
737    public function setCompiler(Twig_CompilerInterface $compiler)
738    {
739        $this->compiler = $compiler;
740    }
741
742    /**
743     * Compiles a node and returns the PHP code.
744     *
745     * @return string The compiled PHP source code
746     */
747    public function compile(Twig_NodeInterface $node)
748    {
749        if (null === $this->compiler) {
750            $this->compiler = new Twig_Compiler($this);
751        }
752
753        return $this->compiler->compile($node)->getSource();
754    }
755
756    /**
757     * Compiles a template source code.
758     *
759     * @param string|Twig_Source $source The template source code
760     * @param string             $name   The template name (deprecated)
761     *
762     * @return string The compiled PHP source code
763     *
764     * @throws Twig_Error_Syntax When there was an error during tokenizing, parsing or compiling
765     */
766    public function compileSource($source, $name = null)
767    {
768        if (!$source instanceof Twig_Source) {
769            @trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED);
770            $source = new Twig_Source($source, $name);
771        }
772
773        try {
774            return $this->compile($this->parse($this->tokenize($source)));
775        } catch (Twig_Error $e) {
776            $e->setSourceContext($source);
777            throw $e;
778        } catch (Exception $e) {
779            throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e);
780        }
781    }
782
783    public function setLoader(Twig_LoaderInterface $loader)
784    {
785        if (!$loader instanceof Twig_SourceContextLoaderInterface && 0 !== strpos(get_class($loader), 'Mock_')) {
786            @trigger_error(sprintf('Twig loader "%s" should implement Twig_SourceContextLoaderInterface since version 1.27.', get_class($loader)), E_USER_DEPRECATED);
787        }
788
789        $this->loader = $loader;
790    }
791
792    /**
793     * Gets the Loader instance.
794     *
795     * @return Twig_LoaderInterface
796     */
797    public function getLoader()
798    {
799        if (null === $this->loader) {
800            throw new LogicException('You must set a loader first.');
801        }
802
803        return $this->loader;
804    }
805
806    /**
807     * Sets the default template charset.
808     *
809     * @param string $charset The default charset
810     */
811    public function setCharset($charset)
812    {
813        $this->charset = strtoupper($charset);
814    }
815
816    /**
817     * Gets the default template charset.
818     *
819     * @return string The default charset
820     */
821    public function getCharset()
822    {
823        return $this->charset;
824    }
825
826    /**
827     * Initializes the runtime environment.
828     *
829     * @deprecated since 1.23 (to be removed in 2.0)
830     */
831    public function initRuntime()
832    {
833        $this->runtimeInitialized = true;
834
835        foreach ($this->getExtensions() as $name => $extension) {
836            if (!$extension instanceof Twig_Extension_InitRuntimeInterface) {
837                $m = new ReflectionMethod($extension, 'initRuntime');
838
839                if ('Twig_Extension' !== $m->getDeclaringClass()->getName()) {
840                    @trigger_error(sprintf('Defining the initRuntime() method in the "%s" extension is deprecated since version 1.23. Use the `needs_environment` option to get the Twig_Environment instance in filters, functions, or tests; or explicitly implement Twig_Extension_InitRuntimeInterface if needed (not recommended).', $name), E_USER_DEPRECATED);
841                }
842            }
843
844            $extension->initRuntime($this);
845        }
846    }
847
848    /**
849     * Returns true if the given extension is registered.
850     *
851     * @param string $class The extension class name
852     *
853     * @return bool Whether the extension is registered or not
854     */
855    public function hasExtension($class)
856    {
857        $class = ltrim($class, '\\');
858        if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) {
859            // For BC/FC with namespaced aliases
860            $class = new ReflectionClass($class);
861            $class = $class->name;
862        }
863
864        if (isset($this->extensions[$class])) {
865            if ($class !== get_class($this->extensions[$class])) {
866                @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED);
867            }
868
869            return true;
870        }
871
872        return isset($this->extensionsByClass[$class]);
873    }
874
875    /**
876     * Adds a runtime loader.
877     */
878    public function addRuntimeLoader(Twig_RuntimeLoaderInterface $loader)
879    {
880        $this->runtimeLoaders[] = $loader;
881    }
882
883    /**
884     * Gets an extension by class name.
885     *
886     * @param string $class The extension class name
887     *
888     * @return Twig_ExtensionInterface
889     */
890    public function getExtension($class)
891    {
892        $class = ltrim($class, '\\');
893        if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) {
894            // For BC/FC with namespaced aliases
895            $class = new ReflectionClass($class);
896            $class = $class->name;
897        }
898
899        if (isset($this->extensions[$class])) {
900            if ($class !== get_class($this->extensions[$class])) {
901                @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED);
902            }
903
904            return $this->extensions[$class];
905        }
906
907        if (!isset($this->extensionsByClass[$class])) {
908            throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $class));
909        }
910
911        return $this->extensionsByClass[$class];
912    }
913
914    /**
915     * Returns the runtime implementation of a Twig element (filter/function/test).
916     *
917     * @param string $class A runtime class name
918     *
919     * @return object The runtime implementation
920     *
921     * @throws Twig_Error_Runtime When the template cannot be found
922     */
923    public function getRuntime($class)
924    {
925        if (isset($this->runtimes[$class])) {
926            return $this->runtimes[$class];
927        }
928
929        foreach ($this->runtimeLoaders as $loader) {
930            if (null !== $runtime = $loader->load($class)) {
931                return $this->runtimes[$class] = $runtime;
932            }
933        }
934
935        throw new Twig_Error_Runtime(sprintf('Unable to load the "%s" runtime.', $class));
936    }
937
938    public function addExtension(Twig_ExtensionInterface $extension)
939    {
940        if ($this->extensionInitialized) {
941            throw new LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $extension->getName()));
942        }
943
944        $class = get_class($extension);
945        if ($class !== $extension->getName()) {
946            if (isset($this->extensions[$extension->getName()])) {
947                unset($this->extensions[$extension->getName()], $this->extensionsByClass[$class]);
948                @trigger_error(sprintf('The possibility to register the same extension twice ("%s") is deprecated since version 1.23 and will be removed in Twig 2.0. Use proper PHP inheritance instead.', $extension->getName()), E_USER_DEPRECATED);
949            }
950        }
951
952        $this->lastModifiedExtension = 0;
953        $this->extensionsByClass[$class] = $extension;
954        $this->extensions[$extension->getName()] = $extension;
955        $this->updateOptionsHash();
956    }
957
958    /**
959     * Removes an extension by name.
960     *
961     * This method is deprecated and you should not use it.
962     *
963     * @param string $name The extension name
964     *
965     * @deprecated since 1.12 (to be removed in 2.0)
966     */
967    public function removeExtension($name)
968    {
969        @trigger_error(sprintf('The %s method is deprecated since version 1.12 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED);
970
971        if ($this->extensionInitialized) {
972            throw new LogicException(sprintf('Unable to remove extension "%s" as extensions have already been initialized.', $name));
973        }
974
975        $class = ltrim($name, '\\');
976        if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) {
977            // For BC/FC with namespaced aliases
978            $class = new ReflectionClass($class);
979            $class = $class->name;
980        }
981
982        if (isset($this->extensions[$class])) {
983            if ($class !== get_class($this->extensions[$class])) {
984                @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED);
985            }
986
987            unset($this->extensions[$class]);
988        }
989
990        unset($this->extensions[$class]);
991        $this->updateOptionsHash();
992    }
993
994    /**
995     * Registers an array of extensions.
996     *
997     * @param array $extensions An array of extensions
998     */
999    public function setExtensions(array $extensions)
1000    {
1001        foreach ($extensions as $extension) {
1002            $this->addExtension($extension);
1003        }
1004    }
1005
1006    /**
1007     * Returns all registered extensions.
1008     *
1009     * @return Twig_ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on)
1010     */
1011    public function getExtensions()
1012    {
1013        return $this->extensions;
1014    }
1015
1016    public function addTokenParser(Twig_TokenParserInterface $parser)
1017    {
1018        if ($this->extensionInitialized) {
1019            throw new LogicException('Unable to add a token parser as extensions have already been initialized.');
1020        }
1021
1022        $this->staging->addTokenParser($parser);
1023    }
1024
1025    /**
1026     * Gets the registered Token Parsers.
1027     *
1028     * @return Twig_TokenParserBrokerInterface
1029     *
1030     * @internal
1031     */
1032    public function getTokenParsers()
1033    {
1034        if (!$this->extensionInitialized) {
1035            $this->initExtensions();
1036        }
1037
1038        return $this->parsers;
1039    }
1040
1041    /**
1042     * Gets registered tags.
1043     *
1044     * Be warned that this method cannot return tags defined by Twig_TokenParserBrokerInterface classes.
1045     *
1046     * @return Twig_TokenParserInterface[]
1047     *
1048     * @internal
1049     */
1050    public function getTags()
1051    {
1052        $tags = array();
1053        foreach ($this->getTokenParsers()->getParsers() as $parser) {
1054            if ($parser instanceof Twig_TokenParserInterface) {
1055                $tags[$parser->getTag()] = $parser;
1056            }
1057        }
1058
1059        return $tags;
1060    }
1061
1062    public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
1063    {
1064        if ($this->extensionInitialized) {
1065            throw new LogicException('Unable to add a node visitor as extensions have already been initialized.');
1066        }
1067
1068        $this->staging->addNodeVisitor($visitor);
1069    }
1070
1071    /**
1072     * Gets the registered Node Visitors.
1073     *
1074     * @return Twig_NodeVisitorInterface[]
1075     *
1076     * @internal
1077     */
1078    public function getNodeVisitors()
1079    {
1080        if (!$this->extensionInitialized) {
1081            $this->initExtensions();
1082        }
1083
1084        return $this->visitors;
1085    }
1086
1087    /**
1088     * Registers a Filter.
1089     *
1090     * @param string|Twig_SimpleFilter               $name   The filter name or a Twig_SimpleFilter instance
1091     * @param Twig_FilterInterface|Twig_SimpleFilter $filter
1092     */
1093    public function addFilter($name, $filter = null)
1094    {
1095        if (!$name instanceof Twig_SimpleFilter && !($filter instanceof Twig_SimpleFilter || $filter instanceof Twig_FilterInterface)) {
1096            throw new LogicException('A filter must be an instance of Twig_FilterInterface or Twig_SimpleFilter.');
1097        }
1098
1099        if ($name instanceof Twig_SimpleFilter) {
1100            $filter = $name;
1101            $name = $filter->getName();
1102        } else {
1103            @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleFilter" instead when defining filter "%s".', __METHOD__, $name), E_USER_DEPRECATED);
1104        }
1105
1106        if ($this->extensionInitialized) {
1107            throw new LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $name));
1108        }
1109
1110        $this->staging->addFilter($name, $filter);
1111    }
1112
1113    /**
1114     * Get a filter by name.
1115     *
1116     * Subclasses may override this method and load filters differently;
1117     * so no list of filters is available.
1118     *
1119     * @param string $name The filter name
1120     *
1121     * @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exist
1122     *
1123     * @internal
1124     */
1125    public function getFilter($name)
1126    {
1127        if (!$this->extensionInitialized) {
1128            $this->initExtensions();
1129        }
1130
1131        if (isset($this->filters[$name])) {
1132            return $this->filters[$name];
1133        }
1134
1135        foreach ($this->filters as $pattern => $filter) {
1136            $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
1137
1138            if ($count) {
1139                if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
1140                    array_shift($matches);
1141                    $filter->setArguments($matches);
1142
1143                    return $filter;
1144                }
1145            }
1146        }
1147
1148        foreach ($this->filterCallbacks as $callback) {
1149            if (false !== $filter = call_user_func($callback, $name)) {
1150                return $filter;
1151            }
1152        }
1153
1154        return false;
1155    }
1156
1157    public function registerUndefinedFilterCallback($callable)
1158    {
1159        $this->filterCallbacks[] = $callable;
1160    }
1161
1162    /**
1163     * Gets the registered Filters.
1164     *
1165     * Be warned that this method cannot return filters defined with registerUndefinedFilterCallback.
1166     *
1167     * @return Twig_FilterInterface[]
1168     *
1169     * @see registerUndefinedFilterCallback
1170     *
1171     * @internal
1172     */
1173    public function getFilters()
1174    {
1175        if (!$this->extensionInitialized) {
1176            $this->initExtensions();
1177        }
1178
1179        return $this->filters;
1180    }
1181
1182    /**
1183     * Registers a Test.
1184     *
1185     * @param string|Twig_SimpleTest             $name The test name or a Twig_SimpleTest instance
1186     * @param Twig_TestInterface|Twig_SimpleTest $test A Twig_TestInterface instance or a Twig_SimpleTest instance
1187     */
1188    public function addTest($name, $test = null)
1189    {
1190        if (!$name instanceof Twig_SimpleTest && !($test instanceof Twig_SimpleTest || $test instanceof Twig_TestInterface)) {
1191            throw new LogicException('A test must be an instance of Twig_TestInterface or Twig_SimpleTest.');
1192        }
1193
1194        if ($name instanceof Twig_SimpleTest) {
1195            $test = $name;
1196            $name = $test->getName();
1197        } else {
1198            @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleTest" instead when defining test "%s".', __METHOD__, $name), E_USER_DEPRECATED);
1199        }
1200
1201        if ($this->extensionInitialized) {
1202            throw new LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $name));
1203        }
1204
1205        $this->staging->addTest($name, $test);
1206    }
1207
1208    /**
1209     * Gets the registered Tests.
1210     *
1211     * @return Twig_TestInterface[]
1212     *
1213     * @internal
1214     */
1215    public function getTests()
1216    {
1217        if (!$this->extensionInitialized) {
1218            $this->initExtensions();
1219        }
1220
1221        return $this->tests;
1222    }
1223
1224    /**
1225     * Gets a test by name.
1226     *
1227     * @param string $name The test name
1228     *
1229     * @return Twig_Test|false A Twig_Test instance or false if the test does not exist
1230     *
1231     * @internal
1232     */
1233    public function getTest($name)
1234    {
1235        if (!$this->extensionInitialized) {
1236            $this->initExtensions();
1237        }
1238
1239        if (isset($this->tests[$name])) {
1240            return $this->tests[$name];
1241        }
1242
1243        return false;
1244    }
1245
1246    /**
1247     * Registers a Function.
1248     *
1249     * @param string|Twig_SimpleFunction                 $name     The function name or a Twig_SimpleFunction instance
1250     * @param Twig_FunctionInterface|Twig_SimpleFunction $function
1251     */
1252    public function addFunction($name, $function = null)
1253    {
1254        if (!$name instanceof Twig_SimpleFunction && !($function instanceof Twig_SimpleFunction || $function instanceof Twig_FunctionInterface)) {
1255            throw new LogicException('A function must be an instance of Twig_FunctionInterface or Twig_SimpleFunction.');
1256        }
1257
1258        if ($name instanceof Twig_SimpleFunction) {
1259            $function = $name;
1260            $name = $function->getName();
1261        } else {
1262            @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleFunction" instead when defining function "%s".', __METHOD__, $name), E_USER_DEPRECATED);
1263        }
1264
1265        if ($this->extensionInitialized) {
1266            throw new LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $name));
1267        }
1268
1269        $this->staging->addFunction($name, $function);
1270    }
1271
1272    /**
1273     * Get a function by name.
1274     *
1275     * Subclasses may override this method and load functions differently;
1276     * so no list of functions is available.
1277     *
1278     * @param string $name function name
1279     *
1280     * @return Twig_Function|false A Twig_Function instance or false if the function does not exist
1281     *
1282     * @internal
1283     */
1284    public function getFunction($name)
1285    {
1286        if (!$this->extensionInitialized) {
1287            $this->initExtensions();
1288        }
1289
1290        if (isset($this->functions[$name])) {
1291            return $this->functions[$name];
1292        }
1293
1294        foreach ($this->functions as $pattern => $function) {
1295            $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
1296
1297            if ($count) {
1298                if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
1299                    array_shift($matches);
1300                    $function->setArguments($matches);
1301
1302                    return $function;
1303                }
1304            }
1305        }
1306
1307        foreach ($this->functionCallbacks as $callback) {
1308            if (false !== $function = call_user_func($callback, $name)) {
1309                return $function;
1310            }
1311        }
1312
1313        return false;
1314    }
1315
1316    public function registerUndefinedFunctionCallback($callable)
1317    {
1318        $this->functionCallbacks[] = $callable;
1319    }
1320
1321    /**
1322     * Gets registered functions.
1323     *
1324     * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback.
1325     *
1326     * @return Twig_FunctionInterface[]
1327     *
1328     * @see registerUndefinedFunctionCallback
1329     *
1330     * @internal
1331     */
1332    public function getFunctions()
1333    {
1334        if (!$this->extensionInitialized) {
1335            $this->initExtensions();
1336        }
1337
1338        return $this->functions;
1339    }
1340
1341    /**
1342     * Registers a Global.
1343     *
1344     * New globals can be added before compiling or rendering a template;
1345     * but after, you can only update existing globals.
1346     *
1347     * @param string $name  The global name
1348     * @param mixed  $value The global value
1349     */
1350    public function addGlobal($name, $value)
1351    {
1352        if ($this->extensionInitialized || $this->runtimeInitialized) {
1353            if (null === $this->globals) {
1354                $this->globals = $this->initGlobals();
1355            }
1356
1357            if (!array_key_exists($name, $this->globals)) {
1358                // The deprecation notice must be turned into the following exception in Twig 2.0
1359                @trigger_error(sprintf('Registering global variable "%s" at runtime or when the extensions have already been initialized is deprecated since version 1.21.', $name), E_USER_DEPRECATED);
1360                //throw new LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name));
1361            }
1362        }
1363
1364        if ($this->extensionInitialized || $this->runtimeInitialized) {
1365            // update the value
1366            $this->globals[$name] = $value;
1367        } else {
1368            $this->staging->addGlobal($name, $value);
1369        }
1370    }
1371
1372    /**
1373     * Gets the registered Globals.
1374     *
1375     * @return array An array of globals
1376     *
1377     * @internal
1378     */
1379    public function getGlobals()
1380    {
1381        if (!$this->runtimeInitialized && !$this->extensionInitialized) {
1382            return $this->initGlobals();
1383        }
1384
1385        if (null === $this->globals) {
1386            $this->globals = $this->initGlobals();
1387        }
1388
1389        return $this->globals;
1390    }
1391
1392    /**
1393     * Merges a context with the defined globals.
1394     *
1395     * @param array $context An array representing the context
1396     *
1397     * @return array The context merged with the globals
1398     */
1399    public function mergeGlobals(array $context)
1400    {
1401        // we don't use array_merge as the context being generally
1402        // bigger than globals, this code is faster.
1403        foreach ($this->getGlobals() as $key => $value) {
1404            if (!array_key_exists($key, $context)) {
1405                $context[$key] = $value;
1406            }
1407        }
1408
1409        return $context;
1410    }
1411
1412    /**
1413     * Gets the registered unary Operators.
1414     *
1415     * @return array An array of unary operators
1416     *
1417     * @internal
1418     */
1419    public function getUnaryOperators()
1420    {
1421        if (!$this->extensionInitialized) {
1422            $this->initExtensions();
1423        }
1424
1425        return $this->unaryOperators;
1426    }
1427
1428    /**
1429     * Gets the registered binary Operators.
1430     *
1431     * @return array An array of binary operators
1432     *
1433     * @internal
1434     */
1435    public function getBinaryOperators()
1436    {
1437        if (!$this->extensionInitialized) {
1438            $this->initExtensions();
1439        }
1440
1441        return $this->binaryOperators;
1442    }
1443
1444    /**
1445     * @deprecated since 1.23 (to be removed in 2.0)
1446     */
1447    public function computeAlternatives($name, $items)
1448    {
1449        @trigger_error(sprintf('The %s method is deprecated since version 1.23 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED);
1450
1451        return Twig_Error_Syntax::computeAlternatives($name, $items);
1452    }
1453
1454    /**
1455     * @internal
1456     */
1457    protected function initGlobals()
1458    {
1459        $globals = array();
1460        foreach ($this->extensions as $name => $extension) {
1461            if (!$extension instanceof Twig_Extension_GlobalsInterface) {
1462                $m = new ReflectionMethod($extension, 'getGlobals');
1463
1464                if ('Twig_Extension' !== $m->getDeclaringClass()->getName()) {
1465                    @trigger_error(sprintf('Defining the getGlobals() method in the "%s" extension without explicitly implementing Twig_Extension_GlobalsInterface is deprecated since version 1.23.', $name), E_USER_DEPRECATED);
1466                }
1467            }
1468
1469            $extGlob = $extension->getGlobals();
1470            if (!is_array($extGlob)) {
1471                throw new UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', get_class($extension)));
1472            }
1473
1474            $globals[] = $extGlob;
1475        }
1476
1477        $globals[] = $this->staging->getGlobals();
1478
1479        return call_user_func_array('array_merge', $globals);
1480    }
1481
1482    /**
1483     * @internal
1484     */
1485    protected function initExtensions()
1486    {
1487        if ($this->extensionInitialized) {
1488            return;
1489        }
1490
1491        $this->parsers = new Twig_TokenParserBroker(array(), array(), false);
1492        $this->filters = array();
1493        $this->functions = array();
1494        $this->tests = array();
1495        $this->visitors = array();
1496        $this->unaryOperators = array();
1497        $this->binaryOperators = array();
1498
1499        foreach ($this->extensions as $extension) {
1500            $this->initExtension($extension);
1501        }
1502        $this->initExtension($this->staging);
1503        // Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception
1504        $this->extensionInitialized = true;
1505    }
1506
1507    /**
1508     * @internal
1509     */
1510    protected function initExtension(Twig_ExtensionInterface $extension)
1511    {
1512        // filters
1513        foreach ($extension->getFilters() as $name => $filter) {
1514            if ($filter instanceof Twig_SimpleFilter) {
1515                $name = $filter->getName();
1516            } else {
1517                @trigger_error(sprintf('Using an instance of "%s" for filter "%s" is deprecated since version 1.21. Use Twig_SimpleFilter instead.', get_class($filter), $name), E_USER_DEPRECATED);
1518            }
1519
1520            $this->filters[$name] = $filter;
1521        }
1522
1523        // functions
1524        foreach ($extension->getFunctions() as $name => $function) {
1525            if ($function instanceof Twig_SimpleFunction) {
1526                $name = $function->getName();
1527            } else {
1528                @trigger_error(sprintf('Using an instance of "%s" for function "%s" is deprecated since version 1.21. Use Twig_SimpleFunction instead.', get_class($function), $name), E_USER_DEPRECATED);
1529            }
1530
1531            $this->functions[$name] = $function;
1532        }
1533
1534        // tests
1535        foreach ($extension->getTests() as $name => $test) {
1536            if ($test instanceof Twig_SimpleTest) {
1537                $name = $test->getName();
1538            } else {
1539                @trigger_error(sprintf('Using an instance of "%s" for test "%s" is deprecated since version 1.21. Use Twig_SimpleTest instead.', get_class($test), $name), E_USER_DEPRECATED);
1540            }
1541
1542            $this->tests[$name] = $test;
1543        }
1544
1545        // token parsers
1546        foreach ($extension->getTokenParsers() as $parser) {
1547            if ($parser instanceof Twig_TokenParserInterface) {
1548                $this->parsers->addTokenParser($parser);
1549            } elseif ($parser instanceof Twig_TokenParserBrokerInterface) {
1550                @trigger_error('Registering a Twig_TokenParserBrokerInterface instance is deprecated since version 1.21.', E_USER_DEPRECATED);
1551
1552                $this->parsers->addTokenParserBroker($parser);
1553            } else {
1554                throw new LogicException('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances.');
1555            }
1556        }
1557
1558        // node visitors
1559        foreach ($extension->getNodeVisitors() as $visitor) {
1560            $this->visitors[] = $visitor;
1561        }
1562
1563        // operators
1564        if ($operators = $extension->getOperators()) {
1565            if (!is_array($operators)) {
1566                throw new InvalidArgumentException(sprintf('"%s::getOperators()" must return an array with operators, got "%s".', get_class($extension), is_object($operators) ? get_class($operators) : gettype($operators).(is_resource($operators) ? '' : '#'.$operators)));
1567            }
1568
1569            if (2 !== count($operators)) {
1570                throw new InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', get_class($extension), count($operators)));
1571            }
1572
1573            $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]);
1574            $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]);
1575        }
1576    }
1577
1578    /**
1579     * @deprecated since 1.22 (to be removed in 2.0)
1580     */
1581    protected function writeCacheFile($file, $content)
1582    {
1583        $this->cache->write($file, $content);
1584    }
1585
1586    private function updateOptionsHash()
1587    {
1588        $hashParts = array_merge(
1589            array_keys($this->extensions),
1590            array(
1591                (int) function_exists('twig_template_get_attributes'),
1592                PHP_MAJOR_VERSION,
1593                PHP_MINOR_VERSION,
1594                self::VERSION,
1595                (int) $this->debug,
1596                $this->baseTemplateClass,
1597                (int) $this->strictVariables,
1598            )
1599        );
1600        $this->optionsHash = implode(':', $hashParts);
1601    }
1602}
1603
1604class_alias('Twig_Environment', 'Twig\Environment', false);
1605