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

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