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
12namespace Twig;
13
14use Twig\Cache\CacheInterface;
15use Twig\Cache\FilesystemCache;
16use Twig\Cache\NullCache;
17use Twig\Error\Error;
18use Twig\Error\LoaderError;
19use Twig\Error\RuntimeError;
20use Twig\Error\SyntaxError;
21use Twig\Extension\CoreExtension;
22use Twig\Extension\EscaperExtension;
23use Twig\Extension\ExtensionInterface;
24use Twig\Extension\OptimizerExtension;
25use Twig\Loader\ArrayLoader;
26use Twig\Loader\ChainLoader;
27use Twig\Loader\LoaderInterface;
28use Twig\Node\ModuleNode;
29use Twig\Node\Node;
30use Twig\NodeVisitor\NodeVisitorInterface;
31use Twig\RuntimeLoader\RuntimeLoaderInterface;
32use Twig\TokenParser\TokenParserInterface;
33
34/**
35 * Stores the Twig configuration and renders templates.
36 *
37 * @author Fabien Potencier <fabien@symfony.com>
38 */
39class Environment
40{
41    public const VERSION = '2.14.6';
42    public const VERSION_ID = 21406;
43    public const MAJOR_VERSION = 2;
44    public const MINOR_VERSION = 14;
45    public const RELEASE_VERSION = 6;
46    public const EXTRA_VERSION = '';
47
48    private $charset;
49    private $loader;
50    private $debug;
51    private $autoReload;
52    private $cache;
53    private $lexer;
54    private $parser;
55    private $compiler;
56    private $baseTemplateClass;
57    private $globals = [];
58    private $resolvedGlobals;
59    private $loadedTemplates;
60    private $strictVariables;
61    private $templateClassPrefix = '__TwigTemplate_';
62    private $originalCache;
63    private $extensionSet;
64    private $runtimeLoaders = [];
65    private $runtimes = [];
66    private $optionsHash;
67
68    /**
69     * Constructor.
70     *
71     * Available options:
72     *
73     *  * debug: When set to true, it automatically set "auto_reload" to true as
74     *           well (default to false).
75     *
76     *  * charset: The charset used by the templates (default to UTF-8).
77     *
78     *  * base_template_class: The base template class to use for generated
79     *                         templates (default to \Twig\Template).
80     *
81     *  * cache: An absolute path where to store the compiled templates,
82     *           a \Twig\Cache\CacheInterface implementation,
83     *           or false to disable compilation cache (default).
84     *
85     *  * auto_reload: Whether to reload the template if the original source changed.
86     *                 If you don't provide the auto_reload option, it will be
87     *                 determined automatically based on the debug value.
88     *
89     *  * strict_variables: Whether to ignore invalid variables in templates
90     *                      (default to false).
91     *
92     *  * autoescape: Whether to enable auto-escaping (default to html):
93     *                  * false: disable auto-escaping
94     *                  * html, js: set the autoescaping to one of the supported strategies
95     *                  * name: set the autoescaping strategy based on the template name extension
96     *                  * PHP callback: a PHP callback that returns an escaping strategy based on the template "name"
97     *
98     *  * optimizations: A flag that indicates which optimizations to apply
99     *                   (default to -1 which means that all optimizations are enabled;
100     *                   set it to 0 to disable).
101     */
102    public function __construct(LoaderInterface $loader, $options = [])
103    {
104        $this->setLoader($loader);
105
106        $options = array_merge([
107            'debug' => false,
108            'charset' => 'UTF-8',
109            'base_template_class' => Template::class,
110            'strict_variables' => false,
111            'autoescape' => 'html',
112            'cache' => false,
113            'auto_reload' => null,
114            'optimizations' => -1,
115        ], $options);
116
117        $this->debug = (bool) $options['debug'];
118        $this->setCharset($options['charset']);
119        $this->baseTemplateClass = '\\'.ltrim($options['base_template_class'], '\\');
120        if ('\\'.Template::class !== $this->baseTemplateClass && '\Twig_Template' !== $this->baseTemplateClass) {
121            @trigger_error('The "base_template_class" option on '.__CLASS__.' is deprecated since Twig 2.7.0.', \E_USER_DEPRECATED);
122        }
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        $this->extensionSet = new ExtensionSet();
127
128        $this->addExtension(new CoreExtension());
129        $this->addExtension(new EscaperExtension($options['autoescape']));
130        $this->addExtension(new OptimizerExtension($options['optimizations']));
131    }
132
133    /**
134     * Gets the base template class for compiled templates.
135     *
136     * @return string The base template class name
137     */
138    public function getBaseTemplateClass()
139    {
140        if (1 > \func_num_args() || \func_get_arg(0)) {
141            @trigger_error('The '.__METHOD__.' is deprecated since Twig 2.7.0.', \E_USER_DEPRECATED);
142        }
143
144        return $this->baseTemplateClass;
145    }
146
147    /**
148     * Sets the base template class for compiled templates.
149     *
150     * @param string $class The base template class name
151     */
152    public function setBaseTemplateClass($class)
153    {
154        @trigger_error('The '.__METHOD__.' is deprecated since Twig 2.7.0.', \E_USER_DEPRECATED);
155
156        $this->baseTemplateClass = $class;
157        $this->updateOptionsHash();
158    }
159
160    /**
161     * Enables debugging mode.
162     */
163    public function enableDebug()
164    {
165        $this->debug = true;
166        $this->updateOptionsHash();
167    }
168
169    /**
170     * Disables debugging mode.
171     */
172    public function disableDebug()
173    {
174        $this->debug = false;
175        $this->updateOptionsHash();
176    }
177
178    /**
179     * Checks if debug mode is enabled.
180     *
181     * @return bool true if debug mode is enabled, false otherwise
182     */
183    public function isDebug()
184    {
185        return $this->debug;
186    }
187
188    /**
189     * Enables the auto_reload option.
190     */
191    public function enableAutoReload()
192    {
193        $this->autoReload = true;
194    }
195
196    /**
197     * Disables the auto_reload option.
198     */
199    public function disableAutoReload()
200    {
201        $this->autoReload = false;
202    }
203
204    /**
205     * Checks if the auto_reload option is enabled.
206     *
207     * @return bool true if auto_reload is enabled, false otherwise
208     */
209    public function isAutoReload()
210    {
211        return $this->autoReload;
212    }
213
214    /**
215     * Enables the strict_variables option.
216     */
217    public function enableStrictVariables()
218    {
219        $this->strictVariables = true;
220        $this->updateOptionsHash();
221    }
222
223    /**
224     * Disables the strict_variables option.
225     */
226    public function disableStrictVariables()
227    {
228        $this->strictVariables = false;
229        $this->updateOptionsHash();
230    }
231
232    /**
233     * Checks if the strict_variables option is enabled.
234     *
235     * @return bool true if strict_variables is enabled, false otherwise
236     */
237    public function isStrictVariables()
238    {
239        return $this->strictVariables;
240    }
241
242    /**
243     * Gets the current cache implementation.
244     *
245     * @param bool $original Whether to return the original cache option or the real cache instance
246     *
247     * @return CacheInterface|string|false A Twig\Cache\CacheInterface implementation,
248     *                                     an absolute path to the compiled templates,
249     *                                     or false to disable cache
250     */
251    public function getCache($original = true)
252    {
253        return $original ? $this->originalCache : $this->cache;
254    }
255
256    /**
257     * Sets the current cache implementation.
258     *
259     * @param CacheInterface|string|false $cache A Twig\Cache\CacheInterface implementation,
260     *                                           an absolute path to the compiled templates,
261     *                                           or false to disable cache
262     */
263    public function setCache($cache)
264    {
265        if (\is_string($cache)) {
266            $this->originalCache = $cache;
267            $this->cache = new FilesystemCache($cache);
268        } elseif (false === $cache) {
269            $this->originalCache = $cache;
270            $this->cache = new NullCache();
271        } elseif ($cache instanceof CacheInterface) {
272            $this->originalCache = $this->cache = $cache;
273        } else {
274            throw new \LogicException('Cache can only be a string, false, or a \Twig\Cache\CacheInterface implementation.');
275        }
276    }
277
278    /**
279     * Gets the template class associated with the given string.
280     *
281     * The generated template class is based on the following parameters:
282     *
283     *  * The cache key for the given template;
284     *  * The currently enabled extensions;
285     *  * Whether the Twig C extension is available or not;
286     *  * PHP version;
287     *  * Twig version;
288     *  * Options with what environment was created.
289     *
290     * @param string   $name  The name for which to calculate the template class name
291     * @param int|null $index The index if it is an embedded template
292     *
293     * @return string The template class name
294     *
295     * @internal
296     */
297    public function getTemplateClass($name, $index = null)
298    {
299        $key = $this->getLoader()->getCacheKey($name).$this->optionsHash;
300
301        return $this->templateClassPrefix.hash('sha256', $key).(null === $index ? '' : '___'.$index);
302    }
303
304    /**
305     * Renders a template.
306     *
307     * @param string|TemplateWrapper $name    The template name
308     * @param array                  $context An array of parameters to pass to the template
309     *
310     * @return string The rendered template
311     *
312     * @throws LoaderError  When the template cannot be found
313     * @throws SyntaxError  When an error occurred during compilation
314     * @throws RuntimeError When an error occurred during rendering
315     */
316    public function render($name, array $context = [])
317    {
318        return $this->load($name)->render($context);
319    }
320
321    /**
322     * Displays a template.
323     *
324     * @param string|TemplateWrapper $name    The template name
325     * @param array                  $context An array of parameters to pass to the template
326     *
327     * @throws LoaderError  When the template cannot be found
328     * @throws SyntaxError  When an error occurred during compilation
329     * @throws RuntimeError When an error occurred during rendering
330     */
331    public function display($name, array $context = [])
332    {
333        $this->load($name)->display($context);
334    }
335
336    /**
337     * Loads a template.
338     *
339     * @param string|TemplateWrapper $name The template name
340     *
341     * @throws LoaderError  When the template cannot be found
342     * @throws RuntimeError When a previously generated cache is corrupted
343     * @throws SyntaxError  When an error occurred during compilation
344     *
345     * @return TemplateWrapper
346     */
347    public function load($name)
348    {
349        if ($name instanceof TemplateWrapper) {
350            return $name;
351        }
352
353        if ($name instanceof Template) {
354            @trigger_error('Passing a \Twig\Template instance to '.__METHOD__.' is deprecated since Twig 2.7.0, use \Twig\TemplateWrapper instead.', \E_USER_DEPRECATED);
355
356            return new TemplateWrapper($this, $name);
357        }
358
359        return new TemplateWrapper($this, $this->loadTemplate($name));
360    }
361
362    /**
363     * Loads a template internal representation.
364     *
365     * This method is for internal use only and should never be called
366     * directly.
367     *
368     * @param string $name  The template name
369     * @param int    $index The index if it is an embedded template
370     *
371     * @return Template A template instance representing the given template name
372     *
373     * @throws LoaderError  When the template cannot be found
374     * @throws RuntimeError When a previously generated cache is corrupted
375     * @throws SyntaxError  When an error occurred during compilation
376     *
377     * @internal
378     */
379    public function loadTemplate($name, $index = null)
380    {
381        return $this->loadClass($this->getTemplateClass($name), $name, $index);
382    }
383
384    /**
385     * @internal
386     */
387    public function loadClass($cls, $name, $index = null)
388    {
389        $mainCls = $cls;
390        if (null !== $index) {
391            $cls .= '___'.$index;
392        }
393
394        if (isset($this->loadedTemplates[$cls])) {
395            return $this->loadedTemplates[$cls];
396        }
397
398        if (!class_exists($cls, false)) {
399            $key = $this->cache->generateKey($name, $mainCls);
400
401            if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) {
402                $this->cache->load($key);
403            }
404
405            $source = null;
406            if (!class_exists($cls, false)) {
407                $source = $this->getLoader()->getSourceContext($name);
408                $content = $this->compileSource($source);
409                $this->cache->write($key, $content);
410                $this->cache->load($key);
411
412                if (!class_exists($mainCls, false)) {
413                    /* Last line of defense if either $this->bcWriteCacheFile was used,
414                     * $this->cache is implemented as a no-op or we have a race condition
415                     * where the cache was cleared between the above calls to write to and load from
416                     * the cache.
417                     */
418                    eval('?>'.$content);
419                }
420
421                if (!class_exists($cls, false)) {
422                    throw new RuntimeError(sprintf('Failed to load Twig template "%s", index "%s": cache might be corrupted.', $name, $index), -1, $source);
423                }
424            }
425        }
426
427        // to be removed in 3.0
428        $this->extensionSet->initRuntime($this);
429
430        return $this->loadedTemplates[$cls] = new $cls($this);
431    }
432
433    /**
434     * Creates a template from source.
435     *
436     * This method should not be used as a generic way to load templates.
437     *
438     * @param string $template The template source
439     * @param string $name     An optional name of the template to be used in error messages
440     *
441     * @return TemplateWrapper A template instance representing the given template name
442     *
443     * @throws LoaderError When the template cannot be found
444     * @throws SyntaxError When an error occurred during compilation
445     */
446    public function createTemplate($template, string $name = null)
447    {
448        $hash = hash('sha256', $template, false);
449        if (null !== $name) {
450            $name = sprintf('%s (string template %s)', $name, $hash);
451        } else {
452            $name = sprintf('__string_template__%s', $hash);
453        }
454
455        $loader = new ChainLoader([
456            new ArrayLoader([$name => $template]),
457            $current = $this->getLoader(),
458        ]);
459
460        $this->setLoader($loader);
461        try {
462            return new TemplateWrapper($this, $this->loadTemplate($name));
463        } finally {
464            $this->setLoader($current);
465        }
466    }
467
468    /**
469     * Returns true if the template is still fresh.
470     *
471     * Besides checking the loader for freshness information,
472     * this method also checks if the enabled extensions have
473     * not changed.
474     *
475     * @param string $name The template name
476     * @param int    $time The last modification time of the cached template
477     *
478     * @return bool true if the template is fresh, false otherwise
479     */
480    public function isTemplateFresh($name, $time)
481    {
482        return $this->extensionSet->getLastModified() <= $time && $this->getLoader()->isFresh($name, $time);
483    }
484
485    /**
486     * Tries to load a template consecutively from an array.
487     *
488     * Similar to load() but it also accepts instances of \Twig\Template and
489     * \Twig\TemplateWrapper, and an array of templates where each is tried to be loaded.
490     *
491     * @param string|TemplateWrapper|array $names A template or an array of templates to try consecutively
492     *
493     * @return TemplateWrapper|Template
494     *
495     * @throws LoaderError When none of the templates can be found
496     * @throws SyntaxError When an error occurred during compilation
497     */
498    public function resolveTemplate($names)
499    {
500        if (!\is_array($names)) {
501            $names = [$names];
502        }
503
504        foreach ($names as $name) {
505            if ($name instanceof Template) {
506                return $name;
507            }
508            if ($name instanceof TemplateWrapper) {
509                return $name;
510            }
511
512            try {
513                return $this->loadTemplate($name);
514            } catch (LoaderError $e) {
515                if (1 === \count($names)) {
516                    throw $e;
517                }
518            }
519        }
520
521        throw new LoaderError(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names)));
522    }
523
524    public function setLexer(Lexer $lexer)
525    {
526        $this->lexer = $lexer;
527    }
528
529    /**
530     * Tokenizes a source code.
531     *
532     * @return TokenStream
533     *
534     * @throws SyntaxError When the code is syntactically wrong
535     */
536    public function tokenize(Source $source)
537    {
538        if (null === $this->lexer) {
539            $this->lexer = new Lexer($this);
540        }
541
542        return $this->lexer->tokenize($source);
543    }
544
545    public function setParser(Parser $parser)
546    {
547        $this->parser = $parser;
548    }
549
550    /**
551     * Converts a token stream to a node tree.
552     *
553     * @return ModuleNode
554     *
555     * @throws SyntaxError When the token stream is syntactically or semantically wrong
556     */
557    public function parse(TokenStream $stream)
558    {
559        if (null === $this->parser) {
560            $this->parser = new Parser($this);
561        }
562
563        return $this->parser->parse($stream);
564    }
565
566    public function setCompiler(Compiler $compiler)
567    {
568        $this->compiler = $compiler;
569    }
570
571    /**
572     * Compiles a node and returns the PHP code.
573     *
574     * @return string The compiled PHP source code
575     */
576    public function compile(Node $node)
577    {
578        if (null === $this->compiler) {
579            $this->compiler = new Compiler($this);
580        }
581
582        return $this->compiler->compile($node)->getSource();
583    }
584
585    /**
586     * Compiles a template source code.
587     *
588     * @return string The compiled PHP source code
589     *
590     * @throws SyntaxError When there was an error during tokenizing, parsing or compiling
591     */
592    public function compileSource(Source $source)
593    {
594        try {
595            return $this->compile($this->parse($this->tokenize($source)));
596        } catch (Error $e) {
597            $e->setSourceContext($source);
598            throw $e;
599        } catch (\Exception $e) {
600            throw new SyntaxError(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e);
601        }
602    }
603
604    public function setLoader(LoaderInterface $loader)
605    {
606        $this->loader = $loader;
607    }
608
609    /**
610     * Gets the Loader instance.
611     *
612     * @return LoaderInterface
613     */
614    public function getLoader()
615    {
616        return $this->loader;
617    }
618
619    /**
620     * Sets the default template charset.
621     *
622     * @param string $charset The default charset
623     */
624    public function setCharset($charset)
625    {
626        if ('UTF8' === $charset = null === $charset ? null : strtoupper($charset)) {
627            // iconv on Windows requires "UTF-8" instead of "UTF8"
628            $charset = 'UTF-8';
629        }
630
631        $this->charset = $charset;
632    }
633
634    /**
635     * Gets the default template charset.
636     *
637     * @return string The default charset
638     */
639    public function getCharset()
640    {
641        return $this->charset;
642    }
643
644    /**
645     * Returns true if the given extension is registered.
646     *
647     * @param string $class The extension class name
648     *
649     * @return bool Whether the extension is registered or not
650     */
651    public function hasExtension($class)
652    {
653        return $this->extensionSet->hasExtension($class);
654    }
655
656    /**
657     * Adds a runtime loader.
658     */
659    public function addRuntimeLoader(RuntimeLoaderInterface $loader)
660    {
661        $this->runtimeLoaders[] = $loader;
662    }
663
664    /**
665     * Gets an extension by class name.
666     *
667     * @param string $class The extension class name
668     *
669     * @return ExtensionInterface
670     */
671    public function getExtension($class)
672    {
673        return $this->extensionSet->getExtension($class);
674    }
675
676    /**
677     * Returns the runtime implementation of a Twig element (filter/function/test).
678     *
679     * @param string $class A runtime class name
680     *
681     * @return object The runtime implementation
682     *
683     * @throws RuntimeError When the template cannot be found
684     */
685    public function getRuntime($class)
686    {
687        if (isset($this->runtimes[$class])) {
688            return $this->runtimes[$class];
689        }
690
691        foreach ($this->runtimeLoaders as $loader) {
692            if (null !== $runtime = $loader->load($class)) {
693                return $this->runtimes[$class] = $runtime;
694            }
695        }
696
697        throw new RuntimeError(sprintf('Unable to load the "%s" runtime.', $class));
698    }
699
700    public function addExtension(ExtensionInterface $extension)
701    {
702        $this->extensionSet->addExtension($extension);
703        $this->updateOptionsHash();
704    }
705
706    /**
707     * Registers an array of extensions.
708     *
709     * @param array $extensions An array of extensions
710     */
711    public function setExtensions(array $extensions)
712    {
713        $this->extensionSet->setExtensions($extensions);
714        $this->updateOptionsHash();
715    }
716
717    /**
718     * Returns all registered extensions.
719     *
720     * @return ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on)
721     */
722    public function getExtensions()
723    {
724        return $this->extensionSet->getExtensions();
725    }
726
727    public function addTokenParser(TokenParserInterface $parser)
728    {
729        $this->extensionSet->addTokenParser($parser);
730    }
731
732    /**
733     * Gets the registered Token Parsers.
734     *
735     * @return TokenParserInterface[]
736     *
737     * @internal
738     */
739    public function getTokenParsers()
740    {
741        return $this->extensionSet->getTokenParsers();
742    }
743
744    /**
745     * Gets registered tags.
746     *
747     * @return TokenParserInterface[]
748     *
749     * @internal
750     */
751    public function getTags()
752    {
753        $tags = [];
754        foreach ($this->getTokenParsers() as $parser) {
755            $tags[$parser->getTag()] = $parser;
756        }
757
758        return $tags;
759    }
760
761    public function addNodeVisitor(NodeVisitorInterface $visitor)
762    {
763        $this->extensionSet->addNodeVisitor($visitor);
764    }
765
766    /**
767     * Gets the registered Node Visitors.
768     *
769     * @return NodeVisitorInterface[]
770     *
771     * @internal
772     */
773    public function getNodeVisitors()
774    {
775        return $this->extensionSet->getNodeVisitors();
776    }
777
778    public function addFilter(TwigFilter $filter)
779    {
780        $this->extensionSet->addFilter($filter);
781    }
782
783    /**
784     * Get a filter by name.
785     *
786     * Subclasses may override this method and load filters differently;
787     * so no list of filters is available.
788     *
789     * @param string $name The filter name
790     *
791     * @return TwigFilter|false
792     *
793     * @internal
794     */
795    public function getFilter($name)
796    {
797        return $this->extensionSet->getFilter($name);
798    }
799
800    public function registerUndefinedFilterCallback(callable $callable)
801    {
802        $this->extensionSet->registerUndefinedFilterCallback($callable);
803    }
804
805    /**
806     * Gets the registered Filters.
807     *
808     * Be warned that this method cannot return filters defined with registerUndefinedFilterCallback.
809     *
810     * @return TwigFilter[]
811     *
812     * @see registerUndefinedFilterCallback
813     *
814     * @internal
815     */
816    public function getFilters()
817    {
818        return $this->extensionSet->getFilters();
819    }
820
821    public function addTest(TwigTest $test)
822    {
823        $this->extensionSet->addTest($test);
824    }
825
826    /**
827     * Gets the registered Tests.
828     *
829     * @return TwigTest[]
830     *
831     * @internal
832     */
833    public function getTests()
834    {
835        return $this->extensionSet->getTests();
836    }
837
838    /**
839     * Gets a test by name.
840     *
841     * @param string $name The test name
842     *
843     * @return TwigTest|false
844     *
845     * @internal
846     */
847    public function getTest($name)
848    {
849        return $this->extensionSet->getTest($name);
850    }
851
852    public function addFunction(TwigFunction $function)
853    {
854        $this->extensionSet->addFunction($function);
855    }
856
857    /**
858     * Get a function by name.
859     *
860     * Subclasses may override this method and load functions differently;
861     * so no list of functions is available.
862     *
863     * @param string $name function name
864     *
865     * @return TwigFunction|false
866     *
867     * @internal
868     */
869    public function getFunction($name)
870    {
871        return $this->extensionSet->getFunction($name);
872    }
873
874    public function registerUndefinedFunctionCallback(callable $callable)
875    {
876        $this->extensionSet->registerUndefinedFunctionCallback($callable);
877    }
878
879    /**
880     * Gets registered functions.
881     *
882     * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback.
883     *
884     * @return TwigFunction[]
885     *
886     * @see registerUndefinedFunctionCallback
887     *
888     * @internal
889     */
890    public function getFunctions()
891    {
892        return $this->extensionSet->getFunctions();
893    }
894
895    /**
896     * Registers a Global.
897     *
898     * New globals can be added before compiling or rendering a template;
899     * but after, you can only update existing globals.
900     *
901     * @param string $name  The global name
902     * @param mixed  $value The global value
903     */
904    public function addGlobal($name, $value)
905    {
906        if ($this->extensionSet->isInitialized() && !\array_key_exists($name, $this->getGlobals())) {
907            throw new \LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name));
908        }
909
910        if (null !== $this->resolvedGlobals) {
911            $this->resolvedGlobals[$name] = $value;
912        } else {
913            $this->globals[$name] = $value;
914        }
915    }
916
917    /**
918     * Gets the registered Globals.
919     *
920     * @return array An array of globals
921     *
922     * @internal
923     */
924    public function getGlobals()
925    {
926        if ($this->extensionSet->isInitialized()) {
927            if (null === $this->resolvedGlobals) {
928                $this->resolvedGlobals = array_merge($this->extensionSet->getGlobals(), $this->globals);
929            }
930
931            return $this->resolvedGlobals;
932        }
933
934        return array_merge($this->extensionSet->getGlobals(), $this->globals);
935    }
936
937    /**
938     * Merges a context with the defined globals.
939     *
940     * @param array $context An array representing the context
941     *
942     * @return array The context merged with the globals
943     */
944    public function mergeGlobals(array $context)
945    {
946        // we don't use array_merge as the context being generally
947        // bigger than globals, this code is faster.
948        foreach ($this->getGlobals() as $key => $value) {
949            if (!\array_key_exists($key, $context)) {
950                $context[$key] = $value;
951            }
952        }
953
954        return $context;
955    }
956
957    /**
958     * Gets the registered unary Operators.
959     *
960     * @return array An array of unary operators
961     *
962     * @internal
963     */
964    public function getUnaryOperators()
965    {
966        return $this->extensionSet->getUnaryOperators();
967    }
968
969    /**
970     * Gets the registered binary Operators.
971     *
972     * @return array An array of binary operators
973     *
974     * @internal
975     */
976    public function getBinaryOperators()
977    {
978        return $this->extensionSet->getBinaryOperators();
979    }
980
981    private function updateOptionsHash()
982    {
983        $this->optionsHash = implode(':', [
984            $this->extensionSet->getSignature(),
985            \PHP_MAJOR_VERSION,
986            \PHP_MINOR_VERSION,
987            self::VERSION,
988            (int) $this->debug,
989            $this->baseTemplateClass,
990            (int) $this->strictVariables,
991        ]);
992    }
993}
994
995class_alias('Twig\Environment', 'Twig_Environment');
996