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

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