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

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