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

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