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 12/** 13 * Stores the Twig configuration. 14 * 15 * @author Fabien Potencier <fabien@symfony.com> 16 */ 17class Twig_Environment 18{ 19 const VERSION = '1.35.3'; 20 const VERSION_ID = 13503; 21 const MAJOR_VERSION = 1; 22 const MINOR_VERSION = 35; 23 const RELEASE_VERSION = 3; 24 const EXTRA_VERSION = ''; 25 26 protected $charset; 27 protected $loader; 28 protected $debug; 29 protected $autoReload; 30 protected $cache; 31 protected $lexer; 32 protected $parser; 33 protected $compiler; 34 protected $baseTemplateClass; 35 protected $extensions; 36 protected $parsers; 37 protected $visitors; 38 protected $filters; 39 protected $tests; 40 protected $functions; 41 protected $globals; 42 protected $runtimeInitialized = false; 43 protected $extensionInitialized = false; 44 protected $loadedTemplates; 45 protected $strictVariables; 46 protected $unaryOperators; 47 protected $binaryOperators; 48 protected $templateClassPrefix = '__TwigTemplate_'; 49 protected $functionCallbacks = array(); 50 protected $filterCallbacks = array(); 51 protected $staging; 52 53 private $originalCache; 54 private $bcWriteCacheFile = false; 55 private $bcGetCacheFilename = false; 56 private $lastModifiedExtension = 0; 57 private $extensionsByClass = array(); 58 private $runtimeLoaders = array(); 59 private $runtimes = array(); 60 private $optionsHash; 61 private $loading = array(); 62 63 /** 64 * Constructor. 65 * 66 * Available options: 67 * 68 * * debug: When set to true, it automatically set "auto_reload" to true as 69 * well (default to false). 70 * 71 * * charset: The charset used by the templates (default to UTF-8). 72 * 73 * * base_template_class: The base template class to use for generated 74 * templates (default to Twig_Template). 75 * 76 * * cache: An absolute path where to store the compiled templates, 77 * a Twig_Cache_Interface implementation, 78 * or false to disable compilation cache (default). 79 * 80 * * auto_reload: Whether to reload the template if the original source changed. 81 * If you don't provide the auto_reload option, it will be 82 * determined automatically based on the debug value. 83 * 84 * * strict_variables: Whether to ignore invalid variables in templates 85 * (default to false). 86 * 87 * * autoescape: Whether to enable auto-escaping (default to html): 88 * * false: disable auto-escaping 89 * * true: equivalent to html 90 * * html, js: set the autoescaping to one of the supported strategies 91 * * name: set the autoescaping strategy based on the template name extension 92 * * PHP callback: a PHP callback that returns an escaping strategy based on the template "name" 93 * 94 * * optimizations: A flag that indicates which optimizations to apply 95 * (default to -1 which means that all optimizations are enabled; 96 * set it to 0 to disable). 97 * 98 * @param Twig_LoaderInterface $loader 99 * @param array $options An array of options 100 */ 101 public function __construct(Twig_LoaderInterface $loader = null, $options = array()) 102 { 103 if (null !== $loader) { 104 $this->setLoader($loader); 105 } else { 106 @trigger_error('Not passing a Twig_LoaderInterface as the first constructor argument of Twig_Environment is deprecated since version 1.21.', E_USER_DEPRECATED); 107 } 108 109 $options = array_merge(array( 110 'debug' => false, 111 'charset' => 'UTF-8', 112 'base_template_class' => 'Twig_Template', 113 'strict_variables' => false, 114 'autoescape' => 'html', 115 'cache' => false, 116 'auto_reload' => null, 117 'optimizations' => -1, 118 ), $options); 119 120 $this->debug = (bool) $options['debug']; 121 $this->charset = strtoupper($options['charset']); 122 $this->baseTemplateClass = $options['base_template_class']; 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 127 $this->addExtension(new Twig_Extension_Core()); 128 $this->addExtension(new Twig_Extension_Escaper($options['autoescape'])); 129 $this->addExtension(new Twig_Extension_Optimizer($options['optimizations'])); 130 $this->staging = new Twig_Extension_Staging(); 131 132 // For BC 133 if (is_string($this->originalCache)) { 134 $r = new ReflectionMethod($this, 'writeCacheFile'); 135 if (__CLASS__ !== $r->getDeclaringClass()->getName()) { 136 @trigger_error('The Twig_Environment::writeCacheFile method is deprecated since version 1.22 and will be removed in Twig 2.0.', E_USER_DEPRECATED); 137 138 $this->bcWriteCacheFile = true; 139 } 140 141 $r = new ReflectionMethod($this, 'getCacheFilename'); 142 if (__CLASS__ !== $r->getDeclaringClass()->getName()) { 143 @trigger_error('The Twig_Environment::getCacheFilename method is deprecated since version 1.22 and will be removed in Twig 2.0.', E_USER_DEPRECATED); 144 145 $this->bcGetCacheFilename = true; 146 } 147 } 148 } 149 150 /** 151 * Gets the base template class for compiled templates. 152 * 153 * @return string The base template class name 154 */ 155 public function getBaseTemplateClass() 156 { 157 return $this->baseTemplateClass; 158 } 159 160 /** 161 * Sets the base template class for compiled templates. 162 * 163 * @param string $class The base template class name 164 */ 165 public function setBaseTemplateClass($class) 166 { 167 $this->baseTemplateClass = $class; 168 $this->updateOptionsHash(); 169 } 170 171 /** 172 * Enables debugging mode. 173 */ 174 public function enableDebug() 175 { 176 $this->debug = true; 177 $this->updateOptionsHash(); 178 } 179 180 /** 181 * Disables debugging mode. 182 */ 183 public function disableDebug() 184 { 185 $this->debug = false; 186 $this->updateOptionsHash(); 187 } 188 189 /** 190 * Checks if debug mode is enabled. 191 * 192 * @return bool true if debug mode is enabled, false otherwise 193 */ 194 public function isDebug() 195 { 196 return $this->debug; 197 } 198 199 /** 200 * Enables the auto_reload option. 201 */ 202 public function enableAutoReload() 203 { 204 $this->autoReload = true; 205 } 206 207 /** 208 * Disables the auto_reload option. 209 */ 210 public function disableAutoReload() 211 { 212 $this->autoReload = false; 213 } 214 215 /** 216 * Checks if the auto_reload option is enabled. 217 * 218 * @return bool true if auto_reload is enabled, false otherwise 219 */ 220 public function isAutoReload() 221 { 222 return $this->autoReload; 223 } 224 225 /** 226 * Enables the strict_variables option. 227 */ 228 public function enableStrictVariables() 229 { 230 $this->strictVariables = true; 231 $this->updateOptionsHash(); 232 } 233 234 /** 235 * Disables the strict_variables option. 236 */ 237 public function disableStrictVariables() 238 { 239 $this->strictVariables = false; 240 $this->updateOptionsHash(); 241 } 242 243 /** 244 * Checks if the strict_variables option is enabled. 245 * 246 * @return bool true if strict_variables is enabled, false otherwise 247 */ 248 public function isStrictVariables() 249 { 250 return $this->strictVariables; 251 } 252 253 /** 254 * Gets the current cache implementation. 255 * 256 * @param bool $original Whether to return the original cache option or the real cache instance 257 * 258 * @return Twig_CacheInterface|string|false A Twig_CacheInterface implementation, 259 * an absolute path to the compiled templates, 260 * or false to disable cache 261 */ 262 public function getCache($original = true) 263 { 264 return $original ? $this->originalCache : $this->cache; 265 } 266 267 /** 268 * Sets the current cache implementation. 269 * 270 * @param Twig_CacheInterface|string|false $cache A Twig_CacheInterface implementation, 271 * an absolute path to the compiled templates, 272 * or false to disable cache 273 */ 274 public function setCache($cache) 275 { 276 if (is_string($cache)) { 277 $this->originalCache = $cache; 278 $this->cache = new Twig_Cache_Filesystem($cache); 279 } elseif (false === $cache) { 280 $this->originalCache = $cache; 281 $this->cache = new Twig_Cache_Null(); 282 } elseif (null === $cache) { 283 @trigger_error('Using "null" as the cache strategy is deprecated since version 1.23 and will be removed in Twig 2.0.', E_USER_DEPRECATED); 284 $this->originalCache = false; 285 $this->cache = new Twig_Cache_Null(); 286 } elseif ($cache instanceof Twig_CacheInterface) { 287 $this->originalCache = $this->cache = $cache; 288 } else { 289 throw new LogicException(sprintf('Cache can only be a string, false, or a Twig_CacheInterface implementation.')); 290 } 291 } 292 293 /** 294 * Gets the cache filename for a given template. 295 * 296 * @param string $name The template name 297 * 298 * @return string|false The cache file name or false when caching is disabled 299 * 300 * @deprecated since 1.22 (to be removed in 2.0) 301 */ 302 public function getCacheFilename($name) 303 { 304 @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); 305 306 $key = $this->cache->generateKey($name, $this->getTemplateClass($name)); 307 308 return !$key ? false : $key; 309 } 310 311 /** 312 * Gets the template class associated with the given string. 313 * 314 * The generated template class is based on the following parameters: 315 * 316 * * The cache key for the given template; 317 * * The currently enabled extensions; 318 * * Whether the Twig C extension is available or not; 319 * * PHP version; 320 * * Twig version; 321 * * Options with what environment was created. 322 * 323 * @param string $name The name for which to calculate the template class name 324 * @param int|null $index The index if it is an embedded template 325 * 326 * @return string The template class name 327 */ 328 public function getTemplateClass($name, $index = null) 329 { 330 $key = $this->getLoader()->getCacheKey($name).$this->optionsHash; 331 332 return $this->templateClassPrefix.hash('sha256', $key).(null === $index ? '' : '_'.$index); 333 } 334 335 /** 336 * Gets the template class prefix. 337 * 338 * @return string The template class prefix 339 * 340 * @deprecated since 1.22 (to be removed in 2.0) 341 */ 342 public function getTemplateClassPrefix() 343 { 344 @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); 345 346 return $this->templateClassPrefix; 347 } 348 349 /** 350 * Renders a template. 351 * 352 * @param string $name The template name 353 * @param array $context An array of parameters to pass to the template 354 * 355 * @return string The rendered template 356 * 357 * @throws Twig_Error_Loader When the template cannot be found 358 * @throws Twig_Error_Syntax When an error occurred during compilation 359 * @throws Twig_Error_Runtime When an error occurred during rendering 360 */ 361 public function render($name, array $context = array()) 362 { 363 return $this->loadTemplate($name)->render($context); 364 } 365 366 /** 367 * Displays a template. 368 * 369 * @param string $name The template name 370 * @param array $context An array of parameters to pass to the template 371 * 372 * @throws Twig_Error_Loader When the template cannot be found 373 * @throws Twig_Error_Syntax When an error occurred during compilation 374 * @throws Twig_Error_Runtime When an error occurred during rendering 375 */ 376 public function display($name, array $context = array()) 377 { 378 $this->loadTemplate($name)->display($context); 379 } 380 381 /** 382 * Loads a template. 383 * 384 * @param string|Twig_TemplateWrapper|Twig_Template $name The template name 385 * 386 * @throws Twig_Error_Loader When the template cannot be found 387 * @throws Twig_Error_Runtime When a previously generated cache is corrupted 388 * @throws Twig_Error_Syntax When an error occurred during compilation 389 * 390 * @return Twig_TemplateWrapper 391 */ 392 public function load($name) 393 { 394 if ($name instanceof Twig_TemplateWrapper) { 395 return $name; 396 } 397 398 if ($name instanceof Twig_Template) { 399 return new Twig_TemplateWrapper($this, $name); 400 } 401 402 return new Twig_TemplateWrapper($this, $this->loadTemplate($name)); 403 } 404 405 /** 406 * Loads a template internal representation. 407 * 408 * This method is for internal use only and should never be called 409 * directly. 410 * 411 * @param string $name The template name 412 * @param int $index The index if it is an embedded template 413 * 414 * @return Twig_TemplateInterface A template instance representing the given template name 415 * 416 * @throws Twig_Error_Loader When the template cannot be found 417 * @throws Twig_Error_Runtime When a previously generated cache is corrupted 418 * @throws Twig_Error_Syntax When an error occurred during compilation 419 * 420 * @internal 421 */ 422 public function loadTemplate($name, $index = null) 423 { 424 $cls = $mainCls = $this->getTemplateClass($name); 425 if (null !== $index) { 426 $cls .= '_'.$index; 427 } 428 429 if (isset($this->loadedTemplates[$cls])) { 430 return $this->loadedTemplates[$cls]; 431 } 432 433 if (!class_exists($cls, false)) { 434 if ($this->bcGetCacheFilename) { 435 $key = $this->getCacheFilename($name); 436 } else { 437 $key = $this->cache->generateKey($name, $mainCls); 438 } 439 440 if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) { 441 $this->cache->load($key); 442 } 443 444 if (!class_exists($cls, false)) { 445 $loader = $this->getLoader(); 446 if (!$loader instanceof Twig_SourceContextLoaderInterface) { 447 $source = new Twig_Source($loader->getSource($name), $name); 448 } else { 449 $source = $loader->getSourceContext($name); 450 } 451 452 $content = $this->compileSource($source); 453 454 if ($this->bcWriteCacheFile) { 455 $this->writeCacheFile($key, $content); 456 } else { 457 $this->cache->write($key, $content); 458 $this->cache->load($key); 459 } 460 461 if (!class_exists($mainCls, false)) { 462 /* Last line of defense if either $this->bcWriteCacheFile was used, 463 * $this->cache is implemented as a no-op or we have a race condition 464 * where the cache was cleared between the above calls to write to and load from 465 * the cache. 466 */ 467 eval('?>'.$content); 468 } 469 } 470 471 if (!class_exists($cls, false)) { 472 throw new Twig_Error_Runtime(sprintf('Failed to load Twig template "%s", index "%s": cache is corrupted.', $name, $index), -1, $source); 473 } 474 } 475 476 if (!$this->runtimeInitialized) { 477 $this->initRuntime(); 478 } 479 480 if (isset($this->loading[$cls])) { 481 throw new Twig_Error_Runtime(sprintf('Circular reference detected for Twig template "%s", path: %s.', $name, implode(' -> ', array_merge($this->loading, array($name))))); 482 } 483 484 $this->loading[$cls] = $name; 485 486 try { 487 $this->loadedTemplates[$cls] = new $cls($this); 488 unset($this->loading[$cls]); 489 } catch (\Exception $e) { 490 unset($this->loading[$cls]); 491 492 throw $e; 493 } 494 495 return $this->loadedTemplates[$cls]; 496 } 497 498 /** 499 * Creates a template from source. 500 * 501 * This method should not be used as a generic way to load templates. 502 * 503 * @param string $template The template name 504 * 505 * @return Twig_Template A template instance representing the given template name 506 * 507 * @throws Twig_Error_Loader When the template cannot be found 508 * @throws Twig_Error_Syntax When an error occurred during compilation 509 */ 510 public function createTemplate($template) 511 { 512 $name = sprintf('__string_template__%s', hash('sha256', $template, false)); 513 514 $loader = new Twig_Loader_Chain(array( 515 new Twig_Loader_Array(array($name => $template)), 516 $current = $this->getLoader(), 517 )); 518 519 $this->setLoader($loader); 520 try { 521 $template = $this->loadTemplate($name); 522 } catch (Exception $e) { 523 $this->setLoader($current); 524 525 throw $e; 526 } catch (Throwable $e) { 527 $this->setLoader($current); 528 529 throw $e; 530 } 531 $this->setLoader($current); 532 533 return $template; 534 } 535 536 /** 537 * Returns true if the template is still fresh. 538 * 539 * Besides checking the loader for freshness information, 540 * this method also checks if the enabled extensions have 541 * not changed. 542 * 543 * @param string $name The template name 544 * @param int $time The last modification time of the cached template 545 * 546 * @return bool true if the template is fresh, false otherwise 547 */ 548 public function isTemplateFresh($name, $time) 549 { 550 if (0 === $this->lastModifiedExtension) { 551 foreach ($this->extensions as $extension) { 552 $r = new ReflectionObject($extension); 553 if (file_exists($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModifiedExtension) { 554 $this->lastModifiedExtension = $extensionTime; 555 } 556 } 557 } 558 559 return $this->lastModifiedExtension <= $time && $this->getLoader()->isFresh($name, $time); 560 } 561 562 /** 563 * Tries to load a template consecutively from an array. 564 * 565 * Similar to loadTemplate() but it also accepts instances of Twig_Template and 566 * Twig_TemplateWrapper, and an array of templates where each is tried to be loaded. 567 * 568 * @param string|Twig_Template|Twig_TemplateWrapper|array $names A template or an array of templates to try consecutively 569 * 570 * @return Twig_Template|Twig_TemplateWrapper 571 * 572 * @throws Twig_Error_Loader When none of the templates can be found 573 * @throws Twig_Error_Syntax When an error occurred during compilation 574 */ 575 public function resolveTemplate($names) 576 { 577 if (!is_array($names)) { 578 $names = array($names); 579 } 580 581 foreach ($names as $name) { 582 if ($name instanceof Twig_Template) { 583 return $name; 584 } 585 586 if ($name instanceof Twig_TemplateWrapper) { 587 return $name; 588 } 589 590 try { 591 return $this->loadTemplate($name); 592 } catch (Twig_Error_Loader $e) { 593 } 594 } 595 596 if (1 === count($names)) { 597 throw $e; 598 } 599 600 throw new Twig_Error_Loader(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names))); 601 } 602 603 /** 604 * Clears the internal template cache. 605 * 606 * @deprecated since 1.18.3 (to be removed in 2.0) 607 */ 608 public function clearTemplateCache() 609 { 610 @trigger_error(sprintf('The %s method is deprecated since version 1.18.3 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); 611 612 $this->loadedTemplates = array(); 613 } 614 615 /** 616 * Clears the template cache files on the filesystem. 617 * 618 * @deprecated since 1.22 (to be removed in 2.0) 619 */ 620 public function clearCacheFiles() 621 { 622 @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); 623 624 if (is_string($this->originalCache)) { 625 foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->originalCache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { 626 if ($file->isFile()) { 627 @unlink($file->getPathname()); 628 } 629 } 630 } 631 } 632 633 /** 634 * Gets the Lexer instance. 635 * 636 * @return Twig_LexerInterface 637 * 638 * @deprecated since 1.25 (to be removed in 2.0) 639 */ 640 public function getLexer() 641 { 642 @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); 643 644 if (null === $this->lexer) { 645 $this->lexer = new Twig_Lexer($this); 646 } 647 648 return $this->lexer; 649 } 650 651 public function setLexer(Twig_LexerInterface $lexer) 652 { 653 $this->lexer = $lexer; 654 } 655 656 /** 657 * Tokenizes a source code. 658 * 659 * @param string|Twig_Source $source The template source code 660 * @param string $name The template name (deprecated) 661 * 662 * @return Twig_TokenStream 663 * 664 * @throws Twig_Error_Syntax When the code is syntactically wrong 665 */ 666 public function tokenize($source, $name = null) 667 { 668 if (!$source instanceof Twig_Source) { 669 @trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED); 670 $source = new Twig_Source($source, $name); 671 } 672 673 if (null === $this->lexer) { 674 $this->lexer = new Twig_Lexer($this); 675 } 676 677 return $this->lexer->tokenize($source); 678 } 679 680 /** 681 * Gets the Parser instance. 682 * 683 * @return Twig_ParserInterface 684 * 685 * @deprecated since 1.25 (to be removed in 2.0) 686 */ 687 public function getParser() 688 { 689 @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); 690 691 if (null === $this->parser) { 692 $this->parser = new Twig_Parser($this); 693 } 694 695 return $this->parser; 696 } 697 698 public function setParser(Twig_ParserInterface $parser) 699 { 700 $this->parser = $parser; 701 } 702 703 /** 704 * Converts a token stream to a node tree. 705 * 706 * @return Twig_Node_Module 707 * 708 * @throws Twig_Error_Syntax When the token stream is syntactically or semantically wrong 709 */ 710 public function parse(Twig_TokenStream $stream) 711 { 712 if (null === $this->parser) { 713 $this->parser = new Twig_Parser($this); 714 } 715 716 return $this->parser->parse($stream); 717 } 718 719 /** 720 * Gets the Compiler instance. 721 * 722 * @return Twig_CompilerInterface 723 * 724 * @deprecated since 1.25 (to be removed in 2.0) 725 */ 726 public function getCompiler() 727 { 728 @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); 729 730 if (null === $this->compiler) { 731 $this->compiler = new Twig_Compiler($this); 732 } 733 734 return $this->compiler; 735 } 736 737 public function setCompiler(Twig_CompilerInterface $compiler) 738 { 739 $this->compiler = $compiler; 740 } 741 742 /** 743 * Compiles a node and returns the PHP code. 744 * 745 * @return string The compiled PHP source code 746 */ 747 public function compile(Twig_NodeInterface $node) 748 { 749 if (null === $this->compiler) { 750 $this->compiler = new Twig_Compiler($this); 751 } 752 753 return $this->compiler->compile($node)->getSource(); 754 } 755 756 /** 757 * Compiles a template source code. 758 * 759 * @param string|Twig_Source $source The template source code 760 * @param string $name The template name (deprecated) 761 * 762 * @return string The compiled PHP source code 763 * 764 * @throws Twig_Error_Syntax When there was an error during tokenizing, parsing or compiling 765 */ 766 public function compileSource($source, $name = null) 767 { 768 if (!$source instanceof Twig_Source) { 769 @trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED); 770 $source = new Twig_Source($source, $name); 771 } 772 773 try { 774 return $this->compile($this->parse($this->tokenize($source))); 775 } catch (Twig_Error $e) { 776 $e->setSourceContext($source); 777 throw $e; 778 } catch (Exception $e) { 779 throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e); 780 } 781 } 782 783 public function setLoader(Twig_LoaderInterface $loader) 784 { 785 if (!$loader instanceof Twig_SourceContextLoaderInterface && 0 !== strpos(get_class($loader), 'Mock_')) { 786 @trigger_error(sprintf('Twig loader "%s" should implement Twig_SourceContextLoaderInterface since version 1.27.', get_class($loader)), E_USER_DEPRECATED); 787 } 788 789 $this->loader = $loader; 790 } 791 792 /** 793 * Gets the Loader instance. 794 * 795 * @return Twig_LoaderInterface 796 */ 797 public function getLoader() 798 { 799 if (null === $this->loader) { 800 throw new LogicException('You must set a loader first.'); 801 } 802 803 return $this->loader; 804 } 805 806 /** 807 * Sets the default template charset. 808 * 809 * @param string $charset The default charset 810 */ 811 public function setCharset($charset) 812 { 813 $this->charset = strtoupper($charset); 814 } 815 816 /** 817 * Gets the default template charset. 818 * 819 * @return string The default charset 820 */ 821 public function getCharset() 822 { 823 return $this->charset; 824 } 825 826 /** 827 * Initializes the runtime environment. 828 * 829 * @deprecated since 1.23 (to be removed in 2.0) 830 */ 831 public function initRuntime() 832 { 833 $this->runtimeInitialized = true; 834 835 foreach ($this->getExtensions() as $name => $extension) { 836 if (!$extension instanceof Twig_Extension_InitRuntimeInterface) { 837 $m = new ReflectionMethod($extension, 'initRuntime'); 838 839 if ('Twig_Extension' !== $m->getDeclaringClass()->getName()) { 840 @trigger_error(sprintf('Defining the initRuntime() method in the "%s" extension is deprecated since version 1.23. Use the `needs_environment` option to get the Twig_Environment instance in filters, functions, or tests; or explicitly implement Twig_Extension_InitRuntimeInterface if needed (not recommended).', $name), E_USER_DEPRECATED); 841 } 842 } 843 844 $extension->initRuntime($this); 845 } 846 } 847 848 /** 849 * Returns true if the given extension is registered. 850 * 851 * @param string $class The extension class name 852 * 853 * @return bool Whether the extension is registered or not 854 */ 855 public function hasExtension($class) 856 { 857 $class = ltrim($class, '\\'); 858 if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) { 859 // For BC/FC with namespaced aliases 860 $class = new ReflectionClass($class); 861 $class = $class->name; 862 } 863 864 if (isset($this->extensions[$class])) { 865 if ($class !== get_class($this->extensions[$class])) { 866 @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); 867 } 868 869 return true; 870 } 871 872 return isset($this->extensionsByClass[$class]); 873 } 874 875 /** 876 * Adds a runtime loader. 877 */ 878 public function addRuntimeLoader(Twig_RuntimeLoaderInterface $loader) 879 { 880 $this->runtimeLoaders[] = $loader; 881 } 882 883 /** 884 * Gets an extension by class name. 885 * 886 * @param string $class The extension class name 887 * 888 * @return Twig_ExtensionInterface 889 */ 890 public function getExtension($class) 891 { 892 $class = ltrim($class, '\\'); 893 if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) { 894 // For BC/FC with namespaced aliases 895 $class = new ReflectionClass($class); 896 $class = $class->name; 897 } 898 899 if (isset($this->extensions[$class])) { 900 if ($class !== get_class($this->extensions[$class])) { 901 @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); 902 } 903 904 return $this->extensions[$class]; 905 } 906 907 if (!isset($this->extensionsByClass[$class])) { 908 throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $class)); 909 } 910 911 return $this->extensionsByClass[$class]; 912 } 913 914 /** 915 * Returns the runtime implementation of a Twig element (filter/function/test). 916 * 917 * @param string $class A runtime class name 918 * 919 * @return object The runtime implementation 920 * 921 * @throws Twig_Error_Runtime When the template cannot be found 922 */ 923 public function getRuntime($class) 924 { 925 if (isset($this->runtimes[$class])) { 926 return $this->runtimes[$class]; 927 } 928 929 foreach ($this->runtimeLoaders as $loader) { 930 if (null !== $runtime = $loader->load($class)) { 931 return $this->runtimes[$class] = $runtime; 932 } 933 } 934 935 throw new Twig_Error_Runtime(sprintf('Unable to load the "%s" runtime.', $class)); 936 } 937 938 public function addExtension(Twig_ExtensionInterface $extension) 939 { 940 if ($this->extensionInitialized) { 941 throw new LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $extension->getName())); 942 } 943 944 $class = get_class($extension); 945 if ($class !== $extension->getName()) { 946 if (isset($this->extensions[$extension->getName()])) { 947 unset($this->extensions[$extension->getName()], $this->extensionsByClass[$class]); 948 @trigger_error(sprintf('The possibility to register the same extension twice ("%s") is deprecated since version 1.23 and will be removed in Twig 2.0. Use proper PHP inheritance instead.', $extension->getName()), E_USER_DEPRECATED); 949 } 950 } 951 952 $this->lastModifiedExtension = 0; 953 $this->extensionsByClass[$class] = $extension; 954 $this->extensions[$extension->getName()] = $extension; 955 $this->updateOptionsHash(); 956 } 957 958 /** 959 * Removes an extension by name. 960 * 961 * This method is deprecated and you should not use it. 962 * 963 * @param string $name The extension name 964 * 965 * @deprecated since 1.12 (to be removed in 2.0) 966 */ 967 public function removeExtension($name) 968 { 969 @trigger_error(sprintf('The %s method is deprecated since version 1.12 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); 970 971 if ($this->extensionInitialized) { 972 throw new LogicException(sprintf('Unable to remove extension "%s" as extensions have already been initialized.', $name)); 973 } 974 975 $class = ltrim($name, '\\'); 976 if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) { 977 // For BC/FC with namespaced aliases 978 $class = new ReflectionClass($class); 979 $class = $class->name; 980 } 981 982 if (isset($this->extensions[$class])) { 983 if ($class !== get_class($this->extensions[$class])) { 984 @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); 985 } 986 987 unset($this->extensions[$class]); 988 } 989 990 unset($this->extensions[$class]); 991 $this->updateOptionsHash(); 992 } 993 994 /** 995 * Registers an array of extensions. 996 * 997 * @param array $extensions An array of extensions 998 */ 999 public function setExtensions(array $extensions) 1000 { 1001 foreach ($extensions as $extension) { 1002 $this->addExtension($extension); 1003 } 1004 } 1005 1006 /** 1007 * Returns all registered extensions. 1008 * 1009 * @return Twig_ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on) 1010 */ 1011 public function getExtensions() 1012 { 1013 return $this->extensions; 1014 } 1015 1016 public function addTokenParser(Twig_TokenParserInterface $parser) 1017 { 1018 if ($this->extensionInitialized) { 1019 throw new LogicException('Unable to add a token parser as extensions have already been initialized.'); 1020 } 1021 1022 $this->staging->addTokenParser($parser); 1023 } 1024 1025 /** 1026 * Gets the registered Token Parsers. 1027 * 1028 * @return Twig_TokenParserBrokerInterface 1029 * 1030 * @internal 1031 */ 1032 public function getTokenParsers() 1033 { 1034 if (!$this->extensionInitialized) { 1035 $this->initExtensions(); 1036 } 1037 1038 return $this->parsers; 1039 } 1040 1041 /** 1042 * Gets registered tags. 1043 * 1044 * Be warned that this method cannot return tags defined by Twig_TokenParserBrokerInterface classes. 1045 * 1046 * @return Twig_TokenParserInterface[] 1047 * 1048 * @internal 1049 */ 1050 public function getTags() 1051 { 1052 $tags = array(); 1053 foreach ($this->getTokenParsers()->getParsers() as $parser) { 1054 if ($parser instanceof Twig_TokenParserInterface) { 1055 $tags[$parser->getTag()] = $parser; 1056 } 1057 } 1058 1059 return $tags; 1060 } 1061 1062 public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) 1063 { 1064 if ($this->extensionInitialized) { 1065 throw new LogicException('Unable to add a node visitor as extensions have already been initialized.'); 1066 } 1067 1068 $this->staging->addNodeVisitor($visitor); 1069 } 1070 1071 /** 1072 * Gets the registered Node Visitors. 1073 * 1074 * @return Twig_NodeVisitorInterface[] 1075 * 1076 * @internal 1077 */ 1078 public function getNodeVisitors() 1079 { 1080 if (!$this->extensionInitialized) { 1081 $this->initExtensions(); 1082 } 1083 1084 return $this->visitors; 1085 } 1086 1087 /** 1088 * Registers a Filter. 1089 * 1090 * @param string|Twig_SimpleFilter $name The filter name or a Twig_SimpleFilter instance 1091 * @param Twig_FilterInterface|Twig_SimpleFilter $filter 1092 */ 1093 public function addFilter($name, $filter = null) 1094 { 1095 if (!$name instanceof Twig_SimpleFilter && !($filter instanceof Twig_SimpleFilter || $filter instanceof Twig_FilterInterface)) { 1096 throw new LogicException('A filter must be an instance of Twig_FilterInterface or Twig_SimpleFilter.'); 1097 } 1098 1099 if ($name instanceof Twig_SimpleFilter) { 1100 $filter = $name; 1101 $name = $filter->getName(); 1102 } else { 1103 @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleFilter" instead when defining filter "%s".', __METHOD__, $name), E_USER_DEPRECATED); 1104 } 1105 1106 if ($this->extensionInitialized) { 1107 throw new LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $name)); 1108 } 1109 1110 $this->staging->addFilter($name, $filter); 1111 } 1112 1113 /** 1114 * Get a filter by name. 1115 * 1116 * Subclasses may override this method and load filters differently; 1117 * so no list of filters is available. 1118 * 1119 * @param string $name The filter name 1120 * 1121 * @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exist 1122 * 1123 * @internal 1124 */ 1125 public function getFilter($name) 1126 { 1127 if (!$this->extensionInitialized) { 1128 $this->initExtensions(); 1129 } 1130 1131 if (isset($this->filters[$name])) { 1132 return $this->filters[$name]; 1133 } 1134 1135 foreach ($this->filters as $pattern => $filter) { 1136 $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); 1137 1138 if ($count) { 1139 if (preg_match('#^'.$pattern.'$#', $name, $matches)) { 1140 array_shift($matches); 1141 $filter->setArguments($matches); 1142 1143 return $filter; 1144 } 1145 } 1146 } 1147 1148 foreach ($this->filterCallbacks as $callback) { 1149 if (false !== $filter = call_user_func($callback, $name)) { 1150 return $filter; 1151 } 1152 } 1153 1154 return false; 1155 } 1156 1157 public function registerUndefinedFilterCallback($callable) 1158 { 1159 $this->filterCallbacks[] = $callable; 1160 } 1161 1162 /** 1163 * Gets the registered Filters. 1164 * 1165 * Be warned that this method cannot return filters defined with registerUndefinedFilterCallback. 1166 * 1167 * @return Twig_FilterInterface[] 1168 * 1169 * @see registerUndefinedFilterCallback 1170 * 1171 * @internal 1172 */ 1173 public function getFilters() 1174 { 1175 if (!$this->extensionInitialized) { 1176 $this->initExtensions(); 1177 } 1178 1179 return $this->filters; 1180 } 1181 1182 /** 1183 * Registers a Test. 1184 * 1185 * @param string|Twig_SimpleTest $name The test name or a Twig_SimpleTest instance 1186 * @param Twig_TestInterface|Twig_SimpleTest $test A Twig_TestInterface instance or a Twig_SimpleTest instance 1187 */ 1188 public function addTest($name, $test = null) 1189 { 1190 if (!$name instanceof Twig_SimpleTest && !($test instanceof Twig_SimpleTest || $test instanceof Twig_TestInterface)) { 1191 throw new LogicException('A test must be an instance of Twig_TestInterface or Twig_SimpleTest.'); 1192 } 1193 1194 if ($name instanceof Twig_SimpleTest) { 1195 $test = $name; 1196 $name = $test->getName(); 1197 } else { 1198 @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleTest" instead when defining test "%s".', __METHOD__, $name), E_USER_DEPRECATED); 1199 } 1200 1201 if ($this->extensionInitialized) { 1202 throw new LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $name)); 1203 } 1204 1205 $this->staging->addTest($name, $test); 1206 } 1207 1208 /** 1209 * Gets the registered Tests. 1210 * 1211 * @return Twig_TestInterface[] 1212 * 1213 * @internal 1214 */ 1215 public function getTests() 1216 { 1217 if (!$this->extensionInitialized) { 1218 $this->initExtensions(); 1219 } 1220 1221 return $this->tests; 1222 } 1223 1224 /** 1225 * Gets a test by name. 1226 * 1227 * @param string $name The test name 1228 * 1229 * @return Twig_Test|false A Twig_Test instance or false if the test does not exist 1230 * 1231 * @internal 1232 */ 1233 public function getTest($name) 1234 { 1235 if (!$this->extensionInitialized) { 1236 $this->initExtensions(); 1237 } 1238 1239 if (isset($this->tests[$name])) { 1240 return $this->tests[$name]; 1241 } 1242 1243 return false; 1244 } 1245 1246 /** 1247 * Registers a Function. 1248 * 1249 * @param string|Twig_SimpleFunction $name The function name or a Twig_SimpleFunction instance 1250 * @param Twig_FunctionInterface|Twig_SimpleFunction $function 1251 */ 1252 public function addFunction($name, $function = null) 1253 { 1254 if (!$name instanceof Twig_SimpleFunction && !($function instanceof Twig_SimpleFunction || $function instanceof Twig_FunctionInterface)) { 1255 throw new LogicException('A function must be an instance of Twig_FunctionInterface or Twig_SimpleFunction.'); 1256 } 1257 1258 if ($name instanceof Twig_SimpleFunction) { 1259 $function = $name; 1260 $name = $function->getName(); 1261 } else { 1262 @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleFunction" instead when defining function "%s".', __METHOD__, $name), E_USER_DEPRECATED); 1263 } 1264 1265 if ($this->extensionInitialized) { 1266 throw new LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $name)); 1267 } 1268 1269 $this->staging->addFunction($name, $function); 1270 } 1271 1272 /** 1273 * Get a function by name. 1274 * 1275 * Subclasses may override this method and load functions differently; 1276 * so no list of functions is available. 1277 * 1278 * @param string $name function name 1279 * 1280 * @return Twig_Function|false A Twig_Function instance or false if the function does not exist 1281 * 1282 * @internal 1283 */ 1284 public function getFunction($name) 1285 { 1286 if (!$this->extensionInitialized) { 1287 $this->initExtensions(); 1288 } 1289 1290 if (isset($this->functions[$name])) { 1291 return $this->functions[$name]; 1292 } 1293 1294 foreach ($this->functions as $pattern => $function) { 1295 $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); 1296 1297 if ($count) { 1298 if (preg_match('#^'.$pattern.'$#', $name, $matches)) { 1299 array_shift($matches); 1300 $function->setArguments($matches); 1301 1302 return $function; 1303 } 1304 } 1305 } 1306 1307 foreach ($this->functionCallbacks as $callback) { 1308 if (false !== $function = call_user_func($callback, $name)) { 1309 return $function; 1310 } 1311 } 1312 1313 return false; 1314 } 1315 1316 public function registerUndefinedFunctionCallback($callable) 1317 { 1318 $this->functionCallbacks[] = $callable; 1319 } 1320 1321 /** 1322 * Gets registered functions. 1323 * 1324 * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback. 1325 * 1326 * @return Twig_FunctionInterface[] 1327 * 1328 * @see registerUndefinedFunctionCallback 1329 * 1330 * @internal 1331 */ 1332 public function getFunctions() 1333 { 1334 if (!$this->extensionInitialized) { 1335 $this->initExtensions(); 1336 } 1337 1338 return $this->functions; 1339 } 1340 1341 /** 1342 * Registers a Global. 1343 * 1344 * New globals can be added before compiling or rendering a template; 1345 * but after, you can only update existing globals. 1346 * 1347 * @param string $name The global name 1348 * @param mixed $value The global value 1349 */ 1350 public function addGlobal($name, $value) 1351 { 1352 if ($this->extensionInitialized || $this->runtimeInitialized) { 1353 if (null === $this->globals) { 1354 $this->globals = $this->initGlobals(); 1355 } 1356 1357 if (!array_key_exists($name, $this->globals)) { 1358 // The deprecation notice must be turned into the following exception in Twig 2.0 1359 @trigger_error(sprintf('Registering global variable "%s" at runtime or when the extensions have already been initialized is deprecated since version 1.21.', $name), E_USER_DEPRECATED); 1360 //throw new LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name)); 1361 } 1362 } 1363 1364 if ($this->extensionInitialized || $this->runtimeInitialized) { 1365 // update the value 1366 $this->globals[$name] = $value; 1367 } else { 1368 $this->staging->addGlobal($name, $value); 1369 } 1370 } 1371 1372 /** 1373 * Gets the registered Globals. 1374 * 1375 * @return array An array of globals 1376 * 1377 * @internal 1378 */ 1379 public function getGlobals() 1380 { 1381 if (!$this->runtimeInitialized && !$this->extensionInitialized) { 1382 return $this->initGlobals(); 1383 } 1384 1385 if (null === $this->globals) { 1386 $this->globals = $this->initGlobals(); 1387 } 1388 1389 return $this->globals; 1390 } 1391 1392 /** 1393 * Merges a context with the defined globals. 1394 * 1395 * @param array $context An array representing the context 1396 * 1397 * @return array The context merged with the globals 1398 */ 1399 public function mergeGlobals(array $context) 1400 { 1401 // we don't use array_merge as the context being generally 1402 // bigger than globals, this code is faster. 1403 foreach ($this->getGlobals() as $key => $value) { 1404 if (!array_key_exists($key, $context)) { 1405 $context[$key] = $value; 1406 } 1407 } 1408 1409 return $context; 1410 } 1411 1412 /** 1413 * Gets the registered unary Operators. 1414 * 1415 * @return array An array of unary operators 1416 * 1417 * @internal 1418 */ 1419 public function getUnaryOperators() 1420 { 1421 if (!$this->extensionInitialized) { 1422 $this->initExtensions(); 1423 } 1424 1425 return $this->unaryOperators; 1426 } 1427 1428 /** 1429 * Gets the registered binary Operators. 1430 * 1431 * @return array An array of binary operators 1432 * 1433 * @internal 1434 */ 1435 public function getBinaryOperators() 1436 { 1437 if (!$this->extensionInitialized) { 1438 $this->initExtensions(); 1439 } 1440 1441 return $this->binaryOperators; 1442 } 1443 1444 /** 1445 * @deprecated since 1.23 (to be removed in 2.0) 1446 */ 1447 public function computeAlternatives($name, $items) 1448 { 1449 @trigger_error(sprintf('The %s method is deprecated since version 1.23 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); 1450 1451 return Twig_Error_Syntax::computeAlternatives($name, $items); 1452 } 1453 1454 /** 1455 * @internal 1456 */ 1457 protected function initGlobals() 1458 { 1459 $globals = array(); 1460 foreach ($this->extensions as $name => $extension) { 1461 if (!$extension instanceof Twig_Extension_GlobalsInterface) { 1462 $m = new ReflectionMethod($extension, 'getGlobals'); 1463 1464 if ('Twig_Extension' !== $m->getDeclaringClass()->getName()) { 1465 @trigger_error(sprintf('Defining the getGlobals() method in the "%s" extension without explicitly implementing Twig_Extension_GlobalsInterface is deprecated since version 1.23.', $name), E_USER_DEPRECATED); 1466 } 1467 } 1468 1469 $extGlob = $extension->getGlobals(); 1470 if (!is_array($extGlob)) { 1471 throw new UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', get_class($extension))); 1472 } 1473 1474 $globals[] = $extGlob; 1475 } 1476 1477 $globals[] = $this->staging->getGlobals(); 1478 1479 return call_user_func_array('array_merge', $globals); 1480 } 1481 1482 /** 1483 * @internal 1484 */ 1485 protected function initExtensions() 1486 { 1487 if ($this->extensionInitialized) { 1488 return; 1489 } 1490 1491 $this->parsers = new Twig_TokenParserBroker(array(), array(), false); 1492 $this->filters = array(); 1493 $this->functions = array(); 1494 $this->tests = array(); 1495 $this->visitors = array(); 1496 $this->unaryOperators = array(); 1497 $this->binaryOperators = array(); 1498 1499 foreach ($this->extensions as $extension) { 1500 $this->initExtension($extension); 1501 } 1502 $this->initExtension($this->staging); 1503 // Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception 1504 $this->extensionInitialized = true; 1505 } 1506 1507 /** 1508 * @internal 1509 */ 1510 protected function initExtension(Twig_ExtensionInterface $extension) 1511 { 1512 // filters 1513 foreach ($extension->getFilters() as $name => $filter) { 1514 if ($filter instanceof Twig_SimpleFilter) { 1515 $name = $filter->getName(); 1516 } else { 1517 @trigger_error(sprintf('Using an instance of "%s" for filter "%s" is deprecated since version 1.21. Use Twig_SimpleFilter instead.', get_class($filter), $name), E_USER_DEPRECATED); 1518 } 1519 1520 $this->filters[$name] = $filter; 1521 } 1522 1523 // functions 1524 foreach ($extension->getFunctions() as $name => $function) { 1525 if ($function instanceof Twig_SimpleFunction) { 1526 $name = $function->getName(); 1527 } else { 1528 @trigger_error(sprintf('Using an instance of "%s" for function "%s" is deprecated since version 1.21. Use Twig_SimpleFunction instead.', get_class($function), $name), E_USER_DEPRECATED); 1529 } 1530 1531 $this->functions[$name] = $function; 1532 } 1533 1534 // tests 1535 foreach ($extension->getTests() as $name => $test) { 1536 if ($test instanceof Twig_SimpleTest) { 1537 $name = $test->getName(); 1538 } else { 1539 @trigger_error(sprintf('Using an instance of "%s" for test "%s" is deprecated since version 1.21. Use Twig_SimpleTest instead.', get_class($test), $name), E_USER_DEPRECATED); 1540 } 1541 1542 $this->tests[$name] = $test; 1543 } 1544 1545 // token parsers 1546 foreach ($extension->getTokenParsers() as $parser) { 1547 if ($parser instanceof Twig_TokenParserInterface) { 1548 $this->parsers->addTokenParser($parser); 1549 } elseif ($parser instanceof Twig_TokenParserBrokerInterface) { 1550 @trigger_error('Registering a Twig_TokenParserBrokerInterface instance is deprecated since version 1.21.', E_USER_DEPRECATED); 1551 1552 $this->parsers->addTokenParserBroker($parser); 1553 } else { 1554 throw new LogicException('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances.'); 1555 } 1556 } 1557 1558 // node visitors 1559 foreach ($extension->getNodeVisitors() as $visitor) { 1560 $this->visitors[] = $visitor; 1561 } 1562 1563 // operators 1564 if ($operators = $extension->getOperators()) { 1565 if (!is_array($operators)) { 1566 throw new InvalidArgumentException(sprintf('"%s::getOperators()" must return an array with operators, got "%s".', get_class($extension), is_object($operators) ? get_class($operators) : gettype($operators).(is_resource($operators) ? '' : '#'.$operators))); 1567 } 1568 1569 if (2 !== count($operators)) { 1570 throw new InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', get_class($extension), count($operators))); 1571 } 1572 1573 $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]); 1574 $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]); 1575 } 1576 } 1577 1578 /** 1579 * @deprecated since 1.22 (to be removed in 2.0) 1580 */ 1581 protected function writeCacheFile($file, $content) 1582 { 1583 $this->cache->write($file, $content); 1584 } 1585 1586 private function updateOptionsHash() 1587 { 1588 $hashParts = array_merge( 1589 array_keys($this->extensions), 1590 array( 1591 (int) function_exists('twig_template_get_attributes'), 1592 PHP_MAJOR_VERSION, 1593 PHP_MINOR_VERSION, 1594 self::VERSION, 1595 (int) $this->debug, 1596 $this->baseTemplateClass, 1597 (int) $this->strictVariables, 1598 ) 1599 ); 1600 $this->optionsHash = implode(':', $hashParts); 1601 } 1602} 1603 1604class_alias('Twig_Environment', 'Twig\Environment', false); 1605