"Fossies" - the Fresh Open Source Software Archive

Member "grav/vendor/twig/twig/src/Environment.php" (1 Sep 2020, 52960 Bytes) of package /linux/www/grav-v1.6.27.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Environment.php" see the Fossies "Dox" file reference documentation.

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