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