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

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