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