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

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