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

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