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

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