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