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

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