vendor/twig/twig/src/Environment.php line 277

  1. <?php
  2. /*
  3.  * This file is part of Twig.
  4.  *
  5.  * (c) Fabien Potencier
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Twig;
  11. use Twig\Cache\CacheInterface;
  12. use Twig\Cache\FilesystemCache;
  13. use Twig\Cache\NullCache;
  14. use Twig\Error\Error;
  15. use Twig\Error\LoaderError;
  16. use Twig\Error\RuntimeError;
  17. use Twig\Error\SyntaxError;
  18. use Twig\Extension\CoreExtension;
  19. use Twig\Extension\EscaperExtension;
  20. use Twig\Extension\ExtensionInterface;
  21. use Twig\Extension\OptimizerExtension;
  22. use Twig\Loader\ArrayLoader;
  23. use Twig\Loader\ChainLoader;
  24. use Twig\Loader\LoaderInterface;
  25. use Twig\Node\ModuleNode;
  26. use Twig\Node\Node;
  27. use Twig\NodeVisitor\NodeVisitorInterface;
  28. use Twig\RuntimeLoader\RuntimeLoaderInterface;
  29. use Twig\TokenParser\TokenParserInterface;
  30. /**
  31.  * Stores the Twig configuration and renders templates.
  32.  *
  33.  * @author Fabien Potencier <fabien@symfony.com>
  34.  */
  35. class Environment
  36. {
  37.     public const VERSION '3.4.3';
  38.     public const VERSION_ID 30403;
  39.     public const MAJOR_VERSION 3;
  40.     public const MINOR_VERSION 4;
  41.     public const RELEASE_VERSION 3;
  42.     public const EXTRA_VERSION '';
  43.     private $charset;
  44.     private $loader;
  45.     private $debug;
  46.     private $autoReload;
  47.     private $cache;
  48.     private $lexer;
  49.     private $parser;
  50.     private $compiler;
  51.     private $globals = [];
  52.     private $resolvedGlobals;
  53.     private $loadedTemplates;
  54.     private $strictVariables;
  55.     private $templateClassPrefix '__TwigTemplate_';
  56.     private $originalCache;
  57.     private $extensionSet;
  58.     private $runtimeLoaders = [];
  59.     private $runtimes = [];
  60.     private $optionsHash;
  61.     /**
  62.      * Constructor.
  63.      *
  64.      * Available options:
  65.      *
  66.      *  * debug: When set to true, it automatically set "auto_reload" to true as
  67.      *           well (default to false).
  68.      *
  69.      *  * charset: The charset used by the templates (default to UTF-8).
  70.      *
  71.      *  * cache: An absolute path where to store the compiled templates,
  72.      *           a \Twig\Cache\CacheInterface implementation,
  73.      *           or false to disable compilation cache (default).
  74.      *
  75.      *  * auto_reload: Whether to reload the template if the original source changed.
  76.      *                 If you don't provide the auto_reload option, it will be
  77.      *                 determined automatically based on the debug value.
  78.      *
  79.      *  * strict_variables: Whether to ignore invalid variables in templates
  80.      *                      (default to false).
  81.      *
  82.      *  * autoescape: Whether to enable auto-escaping (default to html):
  83.      *                  * false: disable auto-escaping
  84.      *                  * html, js: set the autoescaping to one of the supported strategies
  85.      *                  * name: set the autoescaping strategy based on the template name extension
  86.      *                  * PHP callback: a PHP callback that returns an escaping strategy based on the template "name"
  87.      *
  88.      *  * optimizations: A flag that indicates which optimizations to apply
  89.      *                   (default to -1 which means that all optimizations are enabled;
  90.      *                   set it to 0 to disable).
  91.      */
  92.     public function __construct(LoaderInterface $loader$options = [])
  93.     {
  94.         $this->setLoader($loader);
  95.         $options array_merge([
  96.             'debug' => false,
  97.             'charset' => 'UTF-8',
  98.             'strict_variables' => false,
  99.             'autoescape' => 'html',
  100.             'cache' => false,
  101.             'auto_reload' => null,
  102.             'optimizations' => -1,
  103.         ], $options);
  104.         $this->debug = (bool) $options['debug'];
  105.         $this->setCharset($options['charset'] ?? 'UTF-8');
  106.         $this->autoReload null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
  107.         $this->strictVariables = (bool) $options['strict_variables'];
  108.         $this->setCache($options['cache']);
  109.         $this->extensionSet = new ExtensionSet();
  110.         $this->addExtension(new CoreExtension());
  111.         $this->addExtension(new EscaperExtension($options['autoescape']));
  112.         $this->addExtension(new OptimizerExtension($options['optimizations']));
  113.     }
  114.     /**
  115.      * Enables debugging mode.
  116.      */
  117.     public function enableDebug()
  118.     {
  119.         $this->debug true;
  120.         $this->updateOptionsHash();
  121.     }
  122.     /**
  123.      * Disables debugging mode.
  124.      */
  125.     public function disableDebug()
  126.     {
  127.         $this->debug false;
  128.         $this->updateOptionsHash();
  129.     }
  130.     /**
  131.      * Checks if debug mode is enabled.
  132.      *
  133.      * @return bool true if debug mode is enabled, false otherwise
  134.      */
  135.     public function isDebug()
  136.     {
  137.         return $this->debug;
  138.     }
  139.     /**
  140.      * Enables the auto_reload option.
  141.      */
  142.     public function enableAutoReload()
  143.     {
  144.         $this->autoReload true;
  145.     }
  146.     /**
  147.      * Disables the auto_reload option.
  148.      */
  149.     public function disableAutoReload()
  150.     {
  151.         $this->autoReload false;
  152.     }
  153.     /**
  154.      * Checks if the auto_reload option is enabled.
  155.      *
  156.      * @return bool true if auto_reload is enabled, false otherwise
  157.      */
  158.     public function isAutoReload()
  159.     {
  160.         return $this->autoReload;
  161.     }
  162.     /**
  163.      * Enables the strict_variables option.
  164.      */
  165.     public function enableStrictVariables()
  166.     {
  167.         $this->strictVariables true;
  168.         $this->updateOptionsHash();
  169.     }
  170.     /**
  171.      * Disables the strict_variables option.
  172.      */
  173.     public function disableStrictVariables()
  174.     {
  175.         $this->strictVariables false;
  176.         $this->updateOptionsHash();
  177.     }
  178.     /**
  179.      * Checks if the strict_variables option is enabled.
  180.      *
  181.      * @return bool true if strict_variables is enabled, false otherwise
  182.      */
  183.     public function isStrictVariables()
  184.     {
  185.         return $this->strictVariables;
  186.     }
  187.     /**
  188.      * Gets the current cache implementation.
  189.      *
  190.      * @param bool $original Whether to return the original cache option or the real cache instance
  191.      *
  192.      * @return CacheInterface|string|false A Twig\Cache\CacheInterface implementation,
  193.      *                                     an absolute path to the compiled templates,
  194.      *                                     or false to disable cache
  195.      */
  196.     public function getCache($original true)
  197.     {
  198.         return $original $this->originalCache $this->cache;
  199.     }
  200.     /**
  201.      * Sets the current cache implementation.
  202.      *
  203.      * @param CacheInterface|string|false $cache A Twig\Cache\CacheInterface implementation,
  204.      *                                           an absolute path to the compiled templates,
  205.      *                                           or false to disable cache
  206.      */
  207.     public function setCache($cache)
  208.     {
  209.         if (\is_string($cache)) {
  210.             $this->originalCache $cache;
  211.             $this->cache = new FilesystemCache($cache$this->autoReload FilesystemCache::FORCE_BYTECODE_INVALIDATION 0);
  212.         } elseif (false === $cache) {
  213.             $this->originalCache $cache;
  214.             $this->cache = new NullCache();
  215.         } elseif ($cache instanceof CacheInterface) {
  216.             $this->originalCache $this->cache $cache;
  217.         } else {
  218.             throw new \LogicException('Cache can only be a string, false, or a \Twig\Cache\CacheInterface implementation.');
  219.         }
  220.     }
  221.     /**
  222.      * Gets the template class associated with the given string.
  223.      *
  224.      * The generated template class is based on the following parameters:
  225.      *
  226.      *  * The cache key for the given template;
  227.      *  * The currently enabled extensions;
  228.      *  * Whether the Twig C extension is available or not;
  229.      *  * PHP version;
  230.      *  * Twig version;
  231.      *  * Options with what environment was created.
  232.      *
  233.      * @param string   $name  The name for which to calculate the template class name
  234.      * @param int|null $index The index if it is an embedded template
  235.      *
  236.      * @internal
  237.      */
  238.     public function getTemplateClass(string $nameint $index null): string
  239.     {
  240.         $key $this->getLoader()->getCacheKey($name).$this->optionsHash;
  241.         return $this->templateClassPrefix.hash(\PHP_VERSION_ID 80100 'sha256' 'xxh128'$key).(null === $index '' '___'.$index);
  242.     }
  243.     /**
  244.      * Renders a template.
  245.      *
  246.      * @param string|TemplateWrapper $name The template name
  247.      *
  248.      * @throws LoaderError  When the template cannot be found
  249.      * @throws SyntaxError  When an error occurred during compilation
  250.      * @throws RuntimeError When an error occurred during rendering
  251.      */
  252.     public function render($name, array $context = []): string
  253.     {
  254.         return $this->load($name)->render($context);
  255.     }
  256.     /**
  257.      * Displays a template.
  258.      *
  259.      * @param string|TemplateWrapper $name The template name
  260.      *
  261.      * @throws LoaderError  When the template cannot be found
  262.      * @throws SyntaxError  When an error occurred during compilation
  263.      * @throws RuntimeError When an error occurred during rendering
  264.      */
  265.     public function display($name, array $context = []): void
  266.     {
  267.         $this->load($name)->display($context);
  268.     }
  269.     /**
  270.      * Loads a template.
  271.      *
  272.      * @param string|TemplateWrapper $name The template name
  273.      *
  274.      * @throws LoaderError  When the template cannot be found
  275.      * @throws RuntimeError When a previously generated cache is corrupted
  276.      * @throws SyntaxError  When an error occurred during compilation
  277.      */
  278.     public function load($name): TemplateWrapper
  279.     {
  280.         if ($name instanceof TemplateWrapper) {
  281.             return $name;
  282.         }
  283.         return new TemplateWrapper($this$this->loadTemplate($this->getTemplateClass($name), $name));
  284.     }
  285.     /**
  286.      * Loads a template internal representation.
  287.      *
  288.      * This method is for internal use only and should never be called
  289.      * directly.
  290.      *
  291.      * @param string $name  The template name
  292.      * @param int    $index The index if it is an embedded template
  293.      *
  294.      * @throws LoaderError  When the template cannot be found
  295.      * @throws RuntimeError When a previously generated cache is corrupted
  296.      * @throws SyntaxError  When an error occurred during compilation
  297.      *
  298.      * @internal
  299.      */
  300.     public function loadTemplate(string $clsstring $nameint $index null): Template
  301.     {
  302.         $mainCls $cls;
  303.         if (null !== $index) {
  304.             $cls .= '___'.$index;
  305.         }
  306.         if (isset($this->loadedTemplates[$cls])) {
  307.             return $this->loadedTemplates[$cls];
  308.         }
  309.         if (!class_exists($clsfalse)) {
  310.             $key $this->cache->generateKey($name$mainCls);
  311.             if (!$this->isAutoReload() || $this->isTemplateFresh($name$this->cache->getTimestamp($key))) {
  312.                 $this->cache->load($key);
  313.             }
  314.             $source null;
  315.             if (!class_exists($clsfalse)) {
  316.                 $source $this->getLoader()->getSourceContext($name);
  317.                 $content $this->compileSource($source);
  318.                 $this->cache->write($key$content);
  319.                 $this->cache->load($key);
  320.                 if (!class_exists($mainClsfalse)) {
  321.                     /* Last line of defense if either $this->bcWriteCacheFile was used,
  322.                      * $this->cache is implemented as a no-op or we have a race condition
  323.                      * where the cache was cleared between the above calls to write to and load from
  324.                      * the cache.
  325.                      */
  326.                     eval('?>'.$content);
  327.                 }
  328.                 if (!class_exists($clsfalse)) {
  329.                     throw new RuntimeError(sprintf('Failed to load Twig template "%s", index "%s": cache might be corrupted.'$name$index), -1$source);
  330.                 }
  331.             }
  332.         }
  333.         $this->extensionSet->initRuntime();
  334.         return $this->loadedTemplates[$cls] = new $cls($this);
  335.     }
  336.     /**
  337.      * Creates a template from source.
  338.      *
  339.      * This method should not be used as a generic way to load templates.
  340.      *
  341.      * @param string $template The template source
  342.      * @param string $name     An optional name of the template to be used in error messages
  343.      *
  344.      * @throws LoaderError When the template cannot be found
  345.      * @throws SyntaxError When an error occurred during compilation
  346.      */
  347.     public function createTemplate(string $templatestring $name null): TemplateWrapper
  348.     {
  349.         $hash hash(\PHP_VERSION_ID 80100 'sha256' 'xxh128'$templatefalse);
  350.         if (null !== $name) {
  351.             $name sprintf('%s (string template %s)'$name$hash);
  352.         } else {
  353.             $name sprintf('__string_template__%s'$hash);
  354.         }
  355.         $loader = new ChainLoader([
  356.             new ArrayLoader([$name => $template]),
  357.             $current $this->getLoader(),
  358.         ]);
  359.         $this->setLoader($loader);
  360.         try {
  361.             return new TemplateWrapper($this$this->loadTemplate($this->getTemplateClass($name), $name));
  362.         } finally {
  363.             $this->setLoader($current);
  364.         }
  365.     }
  366.     /**
  367.      * Returns true if the template is still fresh.
  368.      *
  369.      * Besides checking the loader for freshness information,
  370.      * this method also checks if the enabled extensions have
  371.      * not changed.
  372.      *
  373.      * @param int $time The last modification time of the cached template
  374.      */
  375.     public function isTemplateFresh(string $nameint $time): bool
  376.     {
  377.         return $this->extensionSet->getLastModified() <= $time && $this->getLoader()->isFresh($name$time);
  378.     }
  379.     /**
  380.      * Tries to load a template consecutively from an array.
  381.      *
  382.      * Similar to load() but it also accepts instances of \Twig\Template and
  383.      * \Twig\TemplateWrapper, and an array of templates where each is tried to be loaded.
  384.      *
  385.      * @param string|TemplateWrapper|array $names A template or an array of templates to try consecutively
  386.      *
  387.      * @throws LoaderError When none of the templates can be found
  388.      * @throws SyntaxError When an error occurred during compilation
  389.      */
  390.     public function resolveTemplate($names): TemplateWrapper
  391.     {
  392.         if (!\is_array($names)) {
  393.             return $this->load($names);
  394.         }
  395.         $count \count($names);
  396.         foreach ($names as $name) {
  397.             if ($name instanceof Template) {
  398.                 return $name;
  399.             }
  400.             if ($name instanceof TemplateWrapper) {
  401.                 return $name;
  402.             }
  403.             if (!== $count && !$this->getLoader()->exists($name)) {
  404.                 continue;
  405.             }
  406.             return $this->load($name);
  407.         }
  408.         throw new LoaderError(sprintf('Unable to find one of the following templates: "%s".'implode('", "'$names)));
  409.     }
  410.     public function setLexer(Lexer $lexer)
  411.     {
  412.         $this->lexer $lexer;
  413.     }
  414.     /**
  415.      * @throws SyntaxError When the code is syntactically wrong
  416.      */
  417.     public function tokenize(Source $source): TokenStream
  418.     {
  419.         if (null === $this->lexer) {
  420.             $this->lexer = new Lexer($this);
  421.         }
  422.         return $this->lexer->tokenize($source);
  423.     }
  424.     public function setParser(Parser $parser)
  425.     {
  426.         $this->parser $parser;
  427.     }
  428.     /**
  429.      * Converts a token stream to a node tree.
  430.      *
  431.      * @throws SyntaxError When the token stream is syntactically or semantically wrong
  432.      */
  433.     public function parse(TokenStream $stream): ModuleNode
  434.     {
  435.         if (null === $this->parser) {
  436.             $this->parser = new Parser($this);
  437.         }
  438.         return $this->parser->parse($stream);
  439.     }
  440.     public function setCompiler(Compiler $compiler)
  441.     {
  442.         $this->compiler $compiler;
  443.     }
  444.     /**
  445.      * Compiles a node and returns the PHP code.
  446.      */
  447.     public function compile(Node $node): string
  448.     {
  449.         if (null === $this->compiler) {
  450.             $this->compiler = new Compiler($this);
  451.         }
  452.         return $this->compiler->compile($node)->getSource();
  453.     }
  454.     /**
  455.      * Compiles a template source code.
  456.      *
  457.      * @throws SyntaxError When there was an error during tokenizing, parsing or compiling
  458.      */
  459.     public function compileSource(Source $source): string
  460.     {
  461.         try {
  462.             return $this->compile($this->parse($this->tokenize($source)));
  463.         } catch (Error $e) {
  464.             $e->setSourceContext($source);
  465.             throw $e;
  466.         } catch (\Exception $e) {
  467.             throw new SyntaxError(sprintf('An exception has been thrown during the compilation of a template ("%s").'$e->getMessage()), -1$source$e);
  468.         }
  469.     }
  470.     public function setLoader(LoaderInterface $loader)
  471.     {
  472.         $this->loader $loader;
  473.     }
  474.     public function getLoader(): LoaderInterface
  475.     {
  476.         return $this->loader;
  477.     }
  478.     public function setCharset(string $charset)
  479.     {
  480.         if ('UTF8' === $charset null === $charset null strtoupper($charset)) {
  481.             // iconv on Windows requires "UTF-8" instead of "UTF8"
  482.             $charset 'UTF-8';
  483.         }
  484.         $this->charset $charset;
  485.     }
  486.     public function getCharset(): string
  487.     {
  488.         return $this->charset;
  489.     }
  490.     public function hasExtension(string $class): bool
  491.     {
  492.         return $this->extensionSet->hasExtension($class);
  493.     }
  494.     public function addRuntimeLoader(RuntimeLoaderInterface $loader)
  495.     {
  496.         $this->runtimeLoaders[] = $loader;
  497.     }
  498.     /**
  499.      * @template TExtension of ExtensionInterface
  500.      *
  501.      * @param class-string<TExtension> $class
  502.      *
  503.      * @return TExtension
  504.      */
  505.     public function getExtension(string $class): ExtensionInterface
  506.     {
  507.         return $this->extensionSet->getExtension($class);
  508.     }
  509.     /**
  510.      * Returns the runtime implementation of a Twig element (filter/function/tag/test).
  511.      *
  512.      * @template TRuntime of object
  513.      *
  514.      * @param class-string<TRuntime> $class A runtime class name
  515.      *
  516.      * @return TRuntime The runtime implementation
  517.      *
  518.      * @throws RuntimeError When the template cannot be found
  519.      */
  520.     public function getRuntime(string $class)
  521.     {
  522.         if (isset($this->runtimes[$class])) {
  523.             return $this->runtimes[$class];
  524.         }
  525.         foreach ($this->runtimeLoaders as $loader) {
  526.             if (null !== $runtime $loader->load($class)) {
  527.                 return $this->runtimes[$class] = $runtime;
  528.             }
  529.         }
  530.         throw new RuntimeError(sprintf('Unable to load the "%s" runtime.'$class));
  531.     }
  532.     public function addExtension(ExtensionInterface $extension)
  533.     {
  534.         $this->extensionSet->addExtension($extension);
  535.         $this->updateOptionsHash();
  536.     }
  537.     /**
  538.      * @param ExtensionInterface[] $extensions An array of extensions
  539.      */
  540.     public function setExtensions(array $extensions)
  541.     {
  542.         $this->extensionSet->setExtensions($extensions);
  543.         $this->updateOptionsHash();
  544.     }
  545.     /**
  546.      * @return ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on)
  547.      */
  548.     public function getExtensions(): array
  549.     {
  550.         return $this->extensionSet->getExtensions();
  551.     }
  552.     public function addTokenParser(TokenParserInterface $parser)
  553.     {
  554.         $this->extensionSet->addTokenParser($parser);
  555.     }
  556.     /**
  557.      * @return TokenParserInterface[]
  558.      *
  559.      * @internal
  560.      */
  561.     public function getTokenParsers(): array
  562.     {
  563.         return $this->extensionSet->getTokenParsers();
  564.     }
  565.     /**
  566.      * @internal
  567.      */
  568.     public function getTokenParser(string $name): ?TokenParserInterface
  569.     {
  570.         return $this->extensionSet->getTokenParser($name);
  571.     }
  572.     public function registerUndefinedTokenParserCallback(callable $callable): void
  573.     {
  574.         $this->extensionSet->registerUndefinedTokenParserCallback($callable);
  575.     }
  576.     public function addNodeVisitor(NodeVisitorInterface $visitor)
  577.     {
  578.         $this->extensionSet->addNodeVisitor($visitor);
  579.     }
  580.     /**
  581.      * @return NodeVisitorInterface[]
  582.      *
  583.      * @internal
  584.      */
  585.     public function getNodeVisitors(): array
  586.     {
  587.         return $this->extensionSet->getNodeVisitors();
  588.     }
  589.     public function addFilter(TwigFilter $filter)
  590.     {
  591.         $this->extensionSet->addFilter($filter);
  592.     }
  593.     /**
  594.      * @internal
  595.      */
  596.     public function getFilter(string $name): ?TwigFilter
  597.     {
  598.         return $this->extensionSet->getFilter($name);
  599.     }
  600.     public function registerUndefinedFilterCallback(callable $callable): void
  601.     {
  602.         $this->extensionSet->registerUndefinedFilterCallback($callable);
  603.     }
  604.     /**
  605.      * Gets the registered Filters.
  606.      *
  607.      * Be warned that this method cannot return filters defined with registerUndefinedFilterCallback.
  608.      *
  609.      * @return TwigFilter[]
  610.      *
  611.      * @see registerUndefinedFilterCallback
  612.      *
  613.      * @internal
  614.      */
  615.     public function getFilters(): array
  616.     {
  617.         return $this->extensionSet->getFilters();
  618.     }
  619.     public function addTest(TwigTest $test)
  620.     {
  621.         $this->extensionSet->addTest($test);
  622.     }
  623.     /**
  624.      * @return TwigTest[]
  625.      *
  626.      * @internal
  627.      */
  628.     public function getTests(): array
  629.     {
  630.         return $this->extensionSet->getTests();
  631.     }
  632.     /**
  633.      * @internal
  634.      */
  635.     public function getTest(string $name): ?TwigTest
  636.     {
  637.         return $this->extensionSet->getTest($name);
  638.     }
  639.     public function addFunction(TwigFunction $function)
  640.     {
  641.         $this->extensionSet->addFunction($function);
  642.     }
  643.     /**
  644.      * @internal
  645.      */
  646.     public function getFunction(string $name): ?TwigFunction
  647.     {
  648.         return $this->extensionSet->getFunction($name);
  649.     }
  650.     public function registerUndefinedFunctionCallback(callable $callable): void
  651.     {
  652.         $this->extensionSet->registerUndefinedFunctionCallback($callable);
  653.     }
  654.     /**
  655.      * Gets registered functions.
  656.      *
  657.      * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback.
  658.      *
  659.      * @return TwigFunction[]
  660.      *
  661.      * @see registerUndefinedFunctionCallback
  662.      *
  663.      * @internal
  664.      */
  665.     public function getFunctions(): array
  666.     {
  667.         return $this->extensionSet->getFunctions();
  668.     }
  669.     /**
  670.      * Registers a Global.
  671.      *
  672.      * New globals can be added before compiling or rendering a template;
  673.      * but after, you can only update existing globals.
  674.      *
  675.      * @param mixed $value The global value
  676.      */
  677.     public function addGlobal(string $name$value)
  678.     {
  679.         if ($this->extensionSet->isInitialized() && !\array_key_exists($name$this->getGlobals())) {
  680.             throw new \LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.'$name));
  681.         }
  682.         if (null !== $this->resolvedGlobals) {
  683.             $this->resolvedGlobals[$name] = $value;
  684.         } else {
  685.             $this->globals[$name] = $value;
  686.         }
  687.     }
  688.     /**
  689.      * @internal
  690.      */
  691.     public function getGlobals(): array
  692.     {
  693.         if ($this->extensionSet->isInitialized()) {
  694.             if (null === $this->resolvedGlobals) {
  695.                 $this->resolvedGlobals array_merge($this->extensionSet->getGlobals(), $this->globals);
  696.             }
  697.             return $this->resolvedGlobals;
  698.         }
  699.         return array_merge($this->extensionSet->getGlobals(), $this->globals);
  700.     }
  701.     public function mergeGlobals(array $context): array
  702.     {
  703.         // we don't use array_merge as the context being generally
  704.         // bigger than globals, this code is faster.
  705.         foreach ($this->getGlobals() as $key => $value) {
  706.             if (!\array_key_exists($key$context)) {
  707.                 $context[$key] = $value;
  708.             }
  709.         }
  710.         return $context;
  711.     }
  712.     /**
  713.      * @internal
  714.      */
  715.     public function getUnaryOperators(): array
  716.     {
  717.         return $this->extensionSet->getUnaryOperators();
  718.     }
  719.     /**
  720.      * @internal
  721.      */
  722.     public function getBinaryOperators(): array
  723.     {
  724.         return $this->extensionSet->getBinaryOperators();
  725.     }
  726.     private function updateOptionsHash(): void
  727.     {
  728.         $this->optionsHash implode(':', [
  729.             $this->extensionSet->getSignature(),
  730.             \PHP_MAJOR_VERSION,
  731.             \PHP_MINOR_VERSION,
  732.             self::VERSION,
  733.             (int) $this->debug,
  734.             (int) $this->strictVariables,
  735.         ]);
  736.     }
  737. }