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.12';
42    public const VERSION_ID = 21412;
43    public const MAJOR_VERSION = 2;
44    public const MINOR_VERSION = 14;
45    public const RELEASE_VERSION = 12;
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(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $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(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $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        $count = \count($names);
505        foreach ($names as $name) {
506            if ($name instanceof Template) {
507                return $name;
508            }
509            if ($name instanceof TemplateWrapper) {
510                return $name;
511            }
512
513            if (1 !== $count && !$this->getLoader()->exists($name)) {
514                continue;
515            }
516
517            return $this->loadTemplate($name);
518        }
519
520        throw new LoaderError(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names)));
521    }
522
523    public function setLexer(Lexer $lexer)
524    {
525        $this->lexer = $lexer;
526    }
527
528    /**
529     * Tokenizes a source code.
530     *
531     * @return TokenStream
532     *
533     * @throws SyntaxError When the code is syntactically wrong
534     */
535    public function tokenize(Source $source)
536    {
537        if (null === $this->lexer) {
538            $this->lexer = new Lexer($this);
539        }
540
541        return $this->lexer->tokenize($source);
542    }
543
544    public function setParser(Parser $parser)
545    {
546        $this->parser = $parser;
547    }
548
549    /**
550     * Converts a token stream to a node tree.
551     *
552     * @return ModuleNode
553     *
554     * @throws SyntaxError When the token stream is syntactically or semantically wrong
555     */
556    public function parse(TokenStream $stream)
557    {
558        if (null === $this->parser) {
559            $this->parser = new Parser($this);
560        }
561
562        return $this->parser->parse($stream);
563    }
564
565    public function setCompiler(Compiler $compiler)
566    {
567        $this->compiler = $compiler;
568    }
569
570    /**
571     * Compiles a node and returns the PHP code.
572     *
573     * @return string The compiled PHP source code
574     */
575    public function compile(Node $node)
576    {
577        if (null === $this->compiler) {
578            $this->compiler = new Compiler($this);
579        }
580
581        return $this->compiler->compile($node)->getSource();
582    }
583
584    /**
585     * Compiles a template source code.
586     *
587     * @return string The compiled PHP source code
588     *
589     * @throws SyntaxError When there was an error during tokenizing, parsing or compiling
590     */
591    public function compileSource(Source $source)
592    {
593        try {
594            return $this->compile($this->parse($this->tokenize($source)));
595        } catch (Error $e) {
596            $e->setSourceContext($source);
597            throw $e;
598        } catch (\Exception $e) {
599            throw new SyntaxError(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e);
600        }
601    }
602
603    public function setLoader(LoaderInterface $loader)
604    {
605        $this->loader = $loader;
606    }
607
608    /**
609     * Gets the Loader instance.
610     *
611     * @return LoaderInterface
612     */
613    public function getLoader()
614    {
615        return $this->loader;
616    }
617
618    /**
619     * Sets the default template charset.
620     *
621     * @param string $charset The default charset
622     */
623    public function setCharset($charset)
624    {
625        if ('UTF8' === $charset = null === $charset ? null : strtoupper($charset)) {
626            // iconv on Windows requires "UTF-8" instead of "UTF8"
627            $charset = 'UTF-8';
628        }
629
630        $this->charset = $charset;
631    }
632
633    /**
634     * Gets the default template charset.
635     *
636     * @return string The default charset
637     */
638    public function getCharset()
639    {
640        return $this->charset;
641    }
642
643    /**
644     * Returns true if the given extension is registered.
645     *
646     * @param string $class The extension class name
647     *
648     * @return bool Whether the extension is registered or not
649     */
650    public function hasExtension($class)
651    {
652        return $this->extensionSet->hasExtension($class);
653    }
654
655    /**
656     * Adds a runtime loader.
657     */
658    public function addRuntimeLoader(RuntimeLoaderInterface $loader)
659    {
660        $this->runtimeLoaders[] = $loader;
661    }
662
663    /**
664     * Gets an extension by class name.
665     *
666     * @param string $class The extension class name
667     *
668     * @return ExtensionInterface
669     */
670    public function getExtension($class)
671    {
672        return $this->extensionSet->getExtension($class);
673    }
674
675    /**
676     * Returns the runtime implementation of a Twig element (filter/function/test).
677     *
678     * @param string $class A runtime class name
679     *
680     * @return object The runtime implementation
681     *
682     * @throws RuntimeError When the template cannot be found
683     */
684    public function getRuntime($class)
685    {
686        if (isset($this->runtimes[$class])) {
687            return $this->runtimes[$class];
688        }
689
690        foreach ($this->runtimeLoaders as $loader) {
691            if (null !== $runtime = $loader->load($class)) {
692                return $this->runtimes[$class] = $runtime;
693            }
694        }
695
696        throw new RuntimeError(sprintf('Unable to load the "%s" runtime.', $class));
697    }
698
699    public function addExtension(ExtensionInterface $extension)
700    {
701        $this->extensionSet->addExtension($extension);
702        $this->updateOptionsHash();
703    }
704
705    /**
706     * Registers an array of extensions.
707     *
708     * @param array $extensions An array of extensions
709     */
710    public function setExtensions(array $extensions)
711    {
712        $this->extensionSet->setExtensions($extensions);
713        $this->updateOptionsHash();
714    }
715
716    /**
717     * Returns all registered extensions.
718     *
719     * @return ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on)
720     */
721    public function getExtensions()
722    {
723        return $this->extensionSet->getExtensions();
724    }
725
726    public function addTokenParser(TokenParserInterface $parser)
727    {
728        $this->extensionSet->addTokenParser($parser);
729    }
730
731    /**
732     * Gets the registered Token Parsers.
733     *
734     * @return TokenParserInterface[]
735     *
736     * @internal
737     */
738    public function getTokenParsers()
739    {
740        return $this->extensionSet->getTokenParsers();
741    }
742
743    /**
744     * Gets registered tags.
745     *
746     * @return TokenParserInterface[]
747     *
748     * @internal
749     */
750    public function getTags()
751    {
752        $tags = [];
753        foreach ($this->getTokenParsers() as $parser) {
754            $tags[$parser->getTag()] = $parser;
755        }
756
757        return $tags;
758    }
759
760    public function addNodeVisitor(NodeVisitorInterface $visitor)
761    {
762        $this->extensionSet->addNodeVisitor($visitor);
763    }
764
765    /**
766     * Gets the registered Node Visitors.
767     *
768     * @return NodeVisitorInterface[]
769     *
770     * @internal
771     */
772    public function getNodeVisitors()
773    {
774        return $this->extensionSet->getNodeVisitors();
775    }
776
777    public function addFilter(TwigFilter $filter)
778    {
779        $this->extensionSet->addFilter($filter);
780    }
781
782    /**
783     * Get a filter by name.
784     *
785     * Subclasses may override this method and load filters differently;
786     * so no list of filters is available.
787     *
788     * @param string $name The filter name
789     *
790     * @return TwigFilter|false
791     *
792     * @internal
793     */
794    public function getFilter($name)
795    {
796        return $this->extensionSet->getFilter($name);
797    }
798
799    public function registerUndefinedFilterCallback(callable $callable)
800    {
801        $this->extensionSet->registerUndefinedFilterCallback($callable);
802    }
803
804    /**
805     * Gets the registered Filters.
806     *
807     * Be warned that this method cannot return filters defined with registerUndefinedFilterCallback.
808     *
809     * @return TwigFilter[]
810     *
811     * @see registerUndefinedFilterCallback
812     *
813     * @internal
814     */
815    public function getFilters()
816    {
817        return $this->extensionSet->getFilters();
818    }
819
820    public function addTest(TwigTest $test)
821    {
822        $this->extensionSet->addTest($test);
823    }
824
825    /**
826     * Gets the registered Tests.
827     *
828     * @return TwigTest[]
829     *
830     * @internal
831     */
832    public function getTests()
833    {
834        return $this->extensionSet->getTests();
835    }
836
837    /**
838     * Gets a test by name.
839     *
840     * @param string $name The test name
841     *
842     * @return TwigTest|false
843     *
844     * @internal
845     */
846    public function getTest($name)
847    {
848        return $this->extensionSet->getTest($name);
849    }
850
851    public function addFunction(TwigFunction $function)
852    {
853        $this->extensionSet->addFunction($function);
854    }
855
856    /**
857     * Get a function by name.
858     *
859     * Subclasses may override this method and load functions differently;
860     * so no list of functions is available.
861     *
862     * @param string $name function name
863     *
864     * @return TwigFunction|false
865     *
866     * @internal
867     */
868    public function getFunction($name)
869    {
870        return $this->extensionSet->getFunction($name);
871    }
872
873    public function registerUndefinedFunctionCallback(callable $callable)
874    {
875        $this->extensionSet->registerUndefinedFunctionCallback($callable);
876    }
877
878    /**
879     * Gets registered functions.
880     *
881     * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback.
882     *
883     * @return TwigFunction[]
884     *
885     * @see registerUndefinedFunctionCallback
886     *
887     * @internal
888     */
889    public function getFunctions()
890    {
891        return $this->extensionSet->getFunctions();
892    }
893
894    /**
895     * Registers a Global.
896     *
897     * New globals can be added before compiling or rendering a template;
898     * but after, you can only update existing globals.
899     *
900     * @param string $name  The global name
901     * @param mixed  $value The global value
902     */
903    public function addGlobal($name, $value)
904    {
905        if ($this->extensionSet->isInitialized() && !\array_key_exists($name, $this->getGlobals())) {
906            throw new \LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name));
907        }
908
909        if (null !== $this->resolvedGlobals) {
910            $this->resolvedGlobals[$name] = $value;
911        } else {
912            $this->globals[$name] = $value;
913        }
914    }
915
916    /**
917     * Gets the registered Globals.
918     *
919     * @return array An array of globals
920     *
921     * @internal
922     */
923    public function getGlobals()
924    {
925        if ($this->extensionSet->isInitialized()) {
926            if (null === $this->resolvedGlobals) {
927                $this->resolvedGlobals = array_merge($this->extensionSet->getGlobals(), $this->globals);
928            }
929
930            return $this->resolvedGlobals;
931        }
932
933        return array_merge($this->extensionSet->getGlobals(), $this->globals);
934    }
935
936    /**
937     * Merges a context with the defined globals.
938     *
939     * @param array $context An array representing the context
940     *
941     * @return array The context merged with the globals
942     */
943    public function mergeGlobals(array $context)
944    {
945        // we don't use array_merge as the context being generally
946        // bigger than globals, this code is faster.
947        foreach ($this->getGlobals() as $key => $value) {
948            if (!\array_key_exists($key, $context)) {
949                $context[$key] = $value;
950            }
951        }
952
953        return $context;
954    }
955
956    /**
957     * Gets the registered unary Operators.
958     *
959     * @return array An array of unary operators
960     *
961     * @internal
962     */
963    public function getUnaryOperators()
964    {
965        return $this->extensionSet->getUnaryOperators();
966    }
967
968    /**
969     * Gets the registered binary Operators.
970     *
971     * @return array An array of binary operators
972     *
973     * @internal
974     */
975    public function getBinaryOperators()
976    {
977        return $this->extensionSet->getBinaryOperators();
978    }
979
980    private function updateOptionsHash()
981    {
982        $this->optionsHash = implode(':', [
983            $this->extensionSet->getSignature(),
984            \PHP_MAJOR_VERSION,
985            \PHP_MINOR_VERSION,
986            self::VERSION,
987            (int) $this->debug,
988            $this->baseTemplateClass,
989            (int) $this->strictVariables,
990        ]);
991    }
992}
993
994class_alias('Twig\Environment', 'Twig_Environment');
995