shop.balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

Environment.php (46287B)


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