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

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