Updated Drupal to 8.6. This goes with the following updates because it's possible...
[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.35.4';
20     const VERSION_ID = 13504;
21     const MAJOR_VERSION = 1;
22     const MINOR_VERSION = 35;
23     const RELEASE_VERSION = 4;
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
1604 class_alias('Twig_Environment', 'Twig\Environment', false);