diff options
| author | Benjamin Southall <applemansigloo@gmail.com> | 2019-02-26 10:11:12 +1000 |
|---|---|---|
| committer | Benjamin Southall <applemansigloo@gmail.com> | 2019-02-26 10:11:12 +1000 |
| commit | f79395851b044689567e0e6c9504acbcaa742782 (patch) | |
| tree | ee539aee9ffa90e8accd6f130b30a8ebed7a232e /inc/lib/Twig/Environment.php | |
| parent | 28f32baf0125ca5085266e73dc5071e5814d8e3b (diff) | |
Twig update to latest Twig 1.x legacy as per vichan
Diffstat (limited to 'inc/lib/Twig/Environment.php')
| -rw-r--r-- | inc/lib/Twig/Environment.php | 802 |
1 files changed, 587 insertions, 215 deletions
diff --git a/inc/lib/Twig/Environment.php b/inc/lib/Twig/Environment.php index 09ea4a25..b15f2aff 100644 --- a/inc/lib/Twig/Environment.php +++ b/inc/lib/Twig/Environment.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,7 +16,12 @@ */ class Twig_Environment { - const VERSION = '1.14.0-DEV'; + const VERSION = '1.35.4-DEV'; + const VERSION_ID = 13504; + const MAJOR_VERSION = 1; + const MINOR_VERSION = 35; + const RELEASE_VERSION = 4; + const EXTRA_VERSION = 'DEV'; protected $charset; protected $loader; @@ -34,17 +39,26 @@ class Twig_Environment protected $tests; protected $functions; protected $globals; - protected $runtimeInitialized; - protected $extensionInitialized; + protected $runtimeInitialized = false; + protected $extensionInitialized = false; protected $loadedTemplates; protected $strictVariables; protected $unaryOperators; protected $binaryOperators; protected $templateClassPrefix = '__TwigTemplate_'; - protected $functionCallbacks; - protected $filterCallbacks; + protected $functionCallbacks = array(); + protected $filterCallbacks = array(); protected $staging; - protected $templateClasses; + + private $originalCache; + private $bcWriteCacheFile = false; + private $bcGetCacheFilename = false; + private $lastModifiedExtension = 0; + private $extensionsByClass = array(); + private $runtimeLoaders = array(); + private $runtimes = array(); + private $optionsHash; + private $loading = array(); /** * Constructor. @@ -59,12 +73,13 @@ class Twig_Environment * * base_template_class: The base template class to use for generated * templates (default to Twig_Template). * - * * cache: An absolute path where to store the compiled templates, or - * false to disable compilation cache (default). + * * cache: An absolute path where to store the compiled templates, + * a Twig_Cache_Interface implementation, + * or false to disable compilation cache (default). * - * * auto_reload: Whether to reload the template is the original source changed. + * * auto_reload: Whether to reload the template if the original source changed. * If you don't provide the auto_reload option, it will be - * determined automatically base on the debug value. + * determined automatically based on the debug value. * * * strict_variables: Whether to ignore invalid variables in templates * (default to false). @@ -73,48 +88,63 @@ class Twig_Environment * * false: disable auto-escaping * * true: equivalent to html * * html, js: set the autoescaping to one of the supported strategies - * * PHP callback: a PHP callback that returns an escaping strategy based on the template "filename" + * * name: set the autoescaping strategy based on the template name extension + * * PHP callback: a PHP callback that returns an escaping strategy based on the template "name" * * * optimizations: A flag that indicates which optimizations to apply * (default to -1 which means that all optimizations are enabled; * set it to 0 to disable). * - * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance + * @param Twig_LoaderInterface $loader * @param array $options An array of options */ public function __construct(Twig_LoaderInterface $loader = null, $options = array()) { if (null !== $loader) { $this->setLoader($loader); + } else { + @trigger_error('Not passing a Twig_LoaderInterface as the first constructor argument of Twig_Environment is deprecated since version 1.21.', E_USER_DEPRECATED); } $options = array_merge(array( - 'debug' => false, - 'charset' => 'UTF-8', + 'debug' => false, + 'charset' => 'UTF-8', 'base_template_class' => 'Twig_Template', - 'strict_variables' => false, - 'autoescape' => 'html', - 'cache' => false, - 'auto_reload' => null, - 'optimizations' => -1, + 'strict_variables' => false, + 'autoescape' => 'html', + 'cache' => false, + 'auto_reload' => null, + 'optimizations' => -1, ), $options); - $this->debug = (bool) $options['debug']; - $this->charset = strtoupper($options['charset']); - $this->baseTemplateClass = $options['base_template_class']; - $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload']; - $this->strictVariables = (bool) $options['strict_variables']; - $this->runtimeInitialized = false; + $this->debug = (bool) $options['debug']; + $this->charset = strtoupper($options['charset']); + $this->baseTemplateClass = $options['base_template_class']; + $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload']; + $this->strictVariables = (bool) $options['strict_variables']; $this->setCache($options['cache']); - $this->functionCallbacks = array(); - $this->filterCallbacks = array(); - $this->templateClasses = array(); $this->addExtension(new Twig_Extension_Core()); $this->addExtension(new Twig_Extension_Escaper($options['autoescape'])); $this->addExtension(new Twig_Extension_Optimizer($options['optimizations'])); - $this->extensionInitialized = false; $this->staging = new Twig_Extension_Staging(); + + // For BC + if (is_string($this->originalCache)) { + $r = new ReflectionMethod($this, 'writeCacheFile'); + if (__CLASS__ !== $r->getDeclaringClass()->getName()) { + @trigger_error('The Twig_Environment::writeCacheFile method is deprecated since version 1.22 and will be removed in Twig 2.0.', E_USER_DEPRECATED); + + $this->bcWriteCacheFile = true; + } + + $r = new ReflectionMethod($this, 'getCacheFilename'); + if (__CLASS__ !== $r->getDeclaringClass()->getName()) { + @trigger_error('The Twig_Environment::getCacheFilename method is deprecated since version 1.22 and will be removed in Twig 2.0.', E_USER_DEPRECATED); + + $this->bcGetCacheFilename = true; + } + } } /** @@ -135,6 +165,7 @@ class Twig_Environment public function setBaseTemplateClass($class) { $this->baseTemplateClass = $class; + $this->updateOptionsHash(); } /** @@ -143,6 +174,7 @@ class Twig_Environment public function enableDebug() { $this->debug = true; + $this->updateOptionsHash(); } /** @@ -151,12 +183,13 @@ class Twig_Environment public function disableDebug() { $this->debug = false; + $this->updateOptionsHash(); } /** * Checks if debug mode is enabled. * - * @return Boolean true if debug mode is enabled, false otherwise + * @return bool true if debug mode is enabled, false otherwise */ public function isDebug() { @@ -182,7 +215,7 @@ class Twig_Environment /** * Checks if the auto_reload option is enabled. * - * @return Boolean true if auto_reload is enabled, false otherwise + * @return bool true if auto_reload is enabled, false otherwise */ public function isAutoReload() { @@ -195,6 +228,7 @@ class Twig_Environment public function enableStrictVariables() { $this->strictVariables = true; + $this->updateOptionsHash(); } /** @@ -203,12 +237,13 @@ class Twig_Environment public function disableStrictVariables() { $this->strictVariables = false; + $this->updateOptionsHash(); } /** * Checks if the strict_variables option is enabled. * - * @return Boolean true if strict_variables is enabled, false otherwise + * @return bool true if strict_variables is enabled, false otherwise */ public function isStrictVariables() { @@ -216,24 +251,43 @@ class Twig_Environment } /** - * Gets the cache directory or false if cache is disabled. + * Gets the current cache implementation. + * + * @param bool $original Whether to return the original cache option or the real cache instance * - * @return string|false + * @return Twig_CacheInterface|string|false A Twig_CacheInterface implementation, + * an absolute path to the compiled templates, + * or false to disable cache */ - public function getCache() + public function getCache($original = true) { - return $this->cache; + return $original ? $this->originalCache : $this->cache; } - /** - * Sets the cache directory or false if cache is disabled. - * - * @param string|false $cache The absolute path to the compiled templates, - * or false to disable cache - */ + /** + * Sets the current cache implementation. + * + * @param Twig_CacheInterface|string|false $cache A Twig_CacheInterface implementation, + * an absolute path to the compiled templates, + * or false to disable cache + */ public function setCache($cache) { - $this->cache = $cache ? $cache : false; + if (is_string($cache)) { + $this->originalCache = $cache; + $this->cache = new Twig_Cache_Filesystem($cache); + } elseif (false === $cache) { + $this->originalCache = $cache; + $this->cache = new Twig_Cache_Null(); + } elseif (null === $cache) { + @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); + $this->originalCache = false; + $this->cache = new Twig_Cache_Null(); + } elseif ($cache instanceof Twig_CacheInterface) { + $this->originalCache = $this->cache = $cache; + } else { + throw new LogicException(sprintf('Cache can only be a string, false, or a Twig_CacheInterface implementation.')); + } } /** @@ -241,45 +295,54 @@ class Twig_Environment * * @param string $name The template name * - * @return string The cache file name + * @return string|false The cache file name or false when caching is disabled + * + * @deprecated since 1.22 (to be removed in 2.0) */ public function getCacheFilename($name) { - if (false === $this->cache) { - return false; - } + @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); - $class = substr($this->getTemplateClass($name), strlen($this->templateClassPrefix)); + $key = $this->cache->generateKey($name, $this->getTemplateClass($name)); - return $this->getCache().'/'.substr($class, 0, 2).'/'.substr($class, 2, 2).'/'.substr($class, 4).'.php'; + return !$key ? false : $key; } /** * Gets the template class associated with the given string. * - * @param string $name The name for which to calculate the template class name - * @param integer $index The index if it is an embedded template + * The generated template class is based on the following parameters: + * + * * The cache key for the given template; + * * The currently enabled extensions; + * * Whether the Twig C extension is available or not; + * * PHP version; + * * Twig version; + * * Options with what environment was created. + * + * @param string $name The name for which to calculate the template class name + * @param int|null $index The index if it is an embedded template * * @return string The template class name */ public function getTemplateClass($name, $index = null) { - $suffix = null === $index ? '' : '_'.$index; - $cls = $name.$suffix; - if (isset($this->templateClasses[$cls])) { - return $this->templateClasses[$cls]; - } + $key = $this->getLoader()->getCacheKey($name).$this->optionsHash; - return $this->templateClasses[$cls] = $this->templateClassPrefix.hash('sha256', $this->getLoader()->getCacheKey($name)).$suffix; + return $this->templateClassPrefix.hash('sha256', $key).(null === $index ? '' : '_'.$index); } /** * Gets the template class prefix. * * @return string The template class prefix + * + * @deprecated since 1.22 (to be removed in 2.0) */ public function getTemplateClassPrefix() { + @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + return $this->templateClassPrefix; } @@ -290,6 +353,10 @@ class Twig_Environment * @param array $context An array of parameters to pass to the template * * @return string The rendered template + * + * @throws Twig_Error_Loader When the template cannot be found + * @throws Twig_Error_Syntax When an error occurred during compilation + * @throws Twig_Error_Runtime When an error occurred during rendering */ public function render($name, array $context = array()) { @@ -301,6 +368,10 @@ class Twig_Environment * * @param string $name The template name * @param array $context An array of parameters to pass to the template + * + * @throws Twig_Error_Loader When the template cannot be found + * @throws Twig_Error_Syntax When an error occurred during compilation + * @throws Twig_Error_Runtime When an error occurred during rendering */ public function display($name, array $context = array()) { @@ -308,30 +379,97 @@ class Twig_Environment } /** - * Loads a template by name. + * Loads a template. * - * @param string $name The template name - * @param integer $index The index if it is an embedded template + * @param string|Twig_TemplateWrapper|Twig_Template $name The template name + * + * @throws Twig_Error_Loader When the template cannot be found + * @throws Twig_Error_Runtime When a previously generated cache is corrupted + * @throws Twig_Error_Syntax When an error occurred during compilation + * + * @return Twig_TemplateWrapper + */ + public function load($name) + { + if ($name instanceof Twig_TemplateWrapper) { + return $name; + } + + if ($name instanceof Twig_Template) { + return new Twig_TemplateWrapper($this, $name); + } + + return new Twig_TemplateWrapper($this, $this->loadTemplate($name)); + } + + /** + * Loads a template internal representation. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The template name + * @param int $index The index if it is an embedded template * * @return Twig_TemplateInterface A template instance representing the given template name + * + * @throws Twig_Error_Loader When the template cannot be found + * @throws Twig_Error_Runtime When a previously generated cache is corrupted + * @throws Twig_Error_Syntax When an error occurred during compilation + * + * @internal */ public function loadTemplate($name, $index = null) { - $cls = $this->getTemplateClass($name, $index); + $cls = $mainCls = $this->getTemplateClass($name); + if (null !== $index) { + $cls .= '_'.$index; + } if (isset($this->loadedTemplates[$cls])) { return $this->loadedTemplates[$cls]; } if (!class_exists($cls, false)) { - if (false === $cache = $this->getCacheFilename($name)) { - eval('?>'.$this->compileSource($this->getLoader()->getSource($name), $name)); + if ($this->bcGetCacheFilename) { + $key = $this->getCacheFilename($name); } else { - if (!is_file($cache) || ($this->isAutoReload() && !$this->isTemplateFresh($name, filemtime($cache)))) { - $this->writeCacheFile($cache, $this->compileSource($this->getLoader()->getSource($name), $name)); + $key = $this->cache->generateKey($name, $mainCls); + } + + if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) { + $this->cache->load($key); + } + + if (!class_exists($cls, false)) { + $loader = $this->getLoader(); + if (!$loader instanceof Twig_SourceContextLoaderInterface) { + $source = new Twig_Source($loader->getSource($name), $name); + } else { + $source = $loader->getSourceContext($name); + } + + $content = $this->compileSource($source); + + if ($this->bcWriteCacheFile) { + $this->writeCacheFile($key, $content); + } else { + $this->cache->write($key, $content); + $this->cache->load($key); + } + + if (!class_exists($mainCls, false)) { + /* Last line of defense if either $this->bcWriteCacheFile was used, + * $this->cache is implemented as a no-op or we have a race condition + * where the cache was cleared between the above calls to write to and load from + * the cache. + */ + eval('?>'.$content); } + } - require_once $cache; + if (!class_exists($cls, false)) { + throw new Twig_Error_Runtime(sprintf('Failed to load Twig template "%s", index "%s": cache is corrupted.', $name, $index), -1, $source); } } @@ -339,7 +477,60 @@ class Twig_Environment $this->initRuntime(); } - return $this->loadedTemplates[$cls] = new $cls($this); + if (isset($this->loading[$cls])) { + throw new Twig_Error_Runtime(sprintf('Circular reference detected for Twig template "%s", path: %s.', $name, implode(' -> ', array_merge($this->loading, array($name))))); + } + + $this->loading[$cls] = $name; + + try { + $this->loadedTemplates[$cls] = new $cls($this); + unset($this->loading[$cls]); + } catch (\Exception $e) { + unset($this->loading[$cls]); + + throw $e; + } + + return $this->loadedTemplates[$cls]; + } + + /** + * Creates a template from source. + * + * This method should not be used as a generic way to load templates. + * + * @param string $template The template name + * + * @return Twig_Template A template instance representing the given template name + * + * @throws Twig_Error_Loader When the template cannot be found + * @throws Twig_Error_Syntax When an error occurred during compilation + */ + public function createTemplate($template) + { + $name = sprintf('__string_template__%s', hash('sha256', $template, false)); + + $loader = new Twig_Loader_Chain(array( + new Twig_Loader_Array(array($name => $template)), + $current = $this->getLoader(), + )); + + $this->setLoader($loader); + try { + $template = $this->loadTemplate($name); + } catch (Exception $e) { + $this->setLoader($current); + + throw $e; + } catch (Throwable $e) { + $this->setLoader($current); + + throw $e; + } + $this->setLoader($current); + + return $template; } /** @@ -349,23 +540,38 @@ class Twig_Environment * this method also checks if the enabled extensions have * not changed. * - * @param string $name The template name - * @param timestamp $time The last modification time of the cached template + * @param string $name The template name + * @param int $time The last modification time of the cached template * - * @return Boolean true if the template is fresh, false otherwise + * @return bool true if the template is fresh, false otherwise */ public function isTemplateFresh($name, $time) { - foreach ($this->extensions as $extension) { - $r = new ReflectionObject($extension); - if (filemtime($r->getFileName()) > $time) { - return false; + if (0 === $this->lastModifiedExtension) { + foreach ($this->extensions as $extension) { + $r = new ReflectionObject($extension); + if (file_exists($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModifiedExtension) { + $this->lastModifiedExtension = $extensionTime; + } } } - return $this->getLoader()->isFresh($name, $time); + return $this->lastModifiedExtension <= $time && $this->getLoader()->isFresh($name, $time); } + /** + * Tries to load a template consecutively from an array. + * + * Similar to loadTemplate() but it also accepts instances of Twig_Template and + * Twig_TemplateWrapper, and an array of templates where each is tried to be loaded. + * + * @param string|Twig_Template|Twig_TemplateWrapper|array $names A template or an array of templates to try consecutively + * + * @return Twig_Template|Twig_TemplateWrapper + * + * @throws Twig_Error_Loader When none of the templates can be found + * @throws Twig_Error_Syntax When an error occurred during compilation + */ public function resolveTemplate($names) { if (!is_array($names)) { @@ -377,6 +583,10 @@ class Twig_Environment return $name; } + if ($name instanceof Twig_TemplateWrapper) { + return $name; + } + try { return $this->loadTemplate($name); } catch (Twig_Error_Loader $e) { @@ -392,24 +602,30 @@ class Twig_Environment /** * Clears the internal template cache. + * + * @deprecated since 1.18.3 (to be removed in 2.0) */ public function clearTemplateCache() { + @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); + $this->loadedTemplates = array(); } /** * Clears the template cache files on the filesystem. + * + * @deprecated since 1.22 (to be removed in 2.0) */ public function clearCacheFiles() { - if (false === $this->cache) { - return; - } + @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); - foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { - if ($file->isFile()) { - @unlink($file->getPathname()); + if (is_string($this->originalCache)) { + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->originalCache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if ($file->isFile()) { + @unlink($file->getPathname()); + } } } } @@ -417,10 +633,14 @@ class Twig_Environment /** * Gets the Lexer instance. * - * @return Twig_LexerInterface A Twig_LexerInterface instance + * @return Twig_LexerInterface + * + * @deprecated since 1.25 (to be removed in 2.0) */ public function getLexer() { + @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); + if (null === $this->lexer) { $this->lexer = new Twig_Lexer($this); } @@ -428,11 +648,6 @@ class Twig_Environment return $this->lexer; } - /** - * Sets the Lexer instance. - * - * @param Twig_LexerInterface A Twig_LexerInterface instance - */ public function setLexer(Twig_LexerInterface $lexer) { $this->lexer = $lexer; @@ -441,23 +656,38 @@ class Twig_Environment /** * Tokenizes a source code. * - * @param string $source The template source code - * @param string $name The template name + * @param string|Twig_Source $source The template source code + * @param string $name The template name (deprecated) + * + * @return Twig_TokenStream * - * @return Twig_TokenStream A Twig_TokenStream instance + * @throws Twig_Error_Syntax When the code is syntactically wrong */ public function tokenize($source, $name = null) { - return $this->getLexer()->tokenize($source, $name); + if (!$source instanceof Twig_Source) { + @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); + $source = new Twig_Source($source, $name); + } + + if (null === $this->lexer) { + $this->lexer = new Twig_Lexer($this); + } + + return $this->lexer->tokenize($source); } /** * Gets the Parser instance. * - * @return Twig_ParserInterface A Twig_ParserInterface instance + * @return Twig_ParserInterface + * + * @deprecated since 1.25 (to be removed in 2.0) */ public function getParser() { + @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); + if (null === $this->parser) { $this->parser = new Twig_Parser($this); } @@ -465,35 +695,38 @@ class Twig_Environment return $this->parser; } - /** - * Sets the Parser instance. - * - * @param Twig_ParserInterface A Twig_ParserInterface instance - */ public function setParser(Twig_ParserInterface $parser) { $this->parser = $parser; } /** - * Parses a token stream. + * Converts a token stream to a node tree. * - * @param Twig_TokenStream $tokens A Twig_TokenStream instance + * @return Twig_Node_Module * - * @return Twig_Node_Module A Node tree + * @throws Twig_Error_Syntax When the token stream is syntactically or semantically wrong */ - public function parse(Twig_TokenStream $tokens) + public function parse(Twig_TokenStream $stream) { - return $this->getParser()->parse($tokens); + if (null === $this->parser) { + $this->parser = new Twig_Parser($this); + } + + return $this->parser->parse($stream); } /** * Gets the Compiler instance. * - * @return Twig_CompilerInterface A Twig_CompilerInterface instance + * @return Twig_CompilerInterface + * + * @deprecated since 1.25 (to be removed in 2.0) */ public function getCompiler() { + @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); + if (null === $this->compiler) { $this->compiler = new Twig_Compiler($this); } @@ -501,62 +734,65 @@ class Twig_Environment return $this->compiler; } - /** - * Sets the Compiler instance. - * - * @param Twig_CompilerInterface $compiler A Twig_CompilerInterface instance - */ public function setCompiler(Twig_CompilerInterface $compiler) { $this->compiler = $compiler; } /** - * Compiles a Node. - * - * @param Twig_NodeInterface $node A Twig_NodeInterface instance + * Compiles a node and returns the PHP code. * * @return string The compiled PHP source code */ public function compile(Twig_NodeInterface $node) { - return $this->getCompiler()->compile($node)->getSource(); + if (null === $this->compiler) { + $this->compiler = new Twig_Compiler($this); + } + + return $this->compiler->compile($node)->getSource(); } /** * Compiles a template source code. * - * @param string $source The template source code - * @param string $name The template name + * @param string|Twig_Source $source The template source code + * @param string $name The template name (deprecated) * * @return string The compiled PHP source code + * + * @throws Twig_Error_Syntax When there was an error during tokenizing, parsing or compiling */ public function compileSource($source, $name = null) { + if (!$source instanceof Twig_Source) { + @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); + $source = new Twig_Source($source, $name); + } + try { - return $this->compile($this->parse($this->tokenize($source, $name))); + return $this->compile($this->parse($this->tokenize($source))); } catch (Twig_Error $e) { - $e->setTemplateFile($name); + $e->setSourceContext($source); throw $e; } catch (Exception $e) { - throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e); + throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e); } } - /** - * Sets the Loader instance. - * - * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance - */ public function setLoader(Twig_LoaderInterface $loader) { + if (!$loader instanceof Twig_SourceContextLoaderInterface && 0 !== strpos(get_class($loader), 'Mock_')) { + @trigger_error(sprintf('Twig loader "%s" should implement Twig_SourceContextLoaderInterface since version 1.27.', get_class($loader)), E_USER_DEPRECATED); + } + $this->loader = $loader; } /** * Gets the Loader instance. * - * @return Twig_LoaderInterface A Twig_LoaderInterface instance + * @return Twig_LoaderInterface */ public function getLoader() { @@ -589,12 +825,22 @@ class Twig_Environment /** * Initializes the runtime environment. + * + * @deprecated since 1.23 (to be removed in 2.0) */ public function initRuntime() { $this->runtimeInitialized = true; - foreach ($this->getExtensions() as $extension) { + foreach ($this->getExtensions() as $name => $extension) { + if (!$extension instanceof Twig_Extension_InitRuntimeInterface) { + $m = new ReflectionMethod($extension, 'initRuntime'); + + if ('Twig_Extension' !== $m->getDeclaringClass()->getName()) { + @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); + } + } + $extension->initRuntime($this); } } @@ -602,43 +848,111 @@ class Twig_Environment /** * Returns true if the given extension is registered. * - * @param string $name The extension name + * @param string $class The extension class name * - * @return Boolean Whether the extension is registered or not + * @return bool Whether the extension is registered or not + */ + public function hasExtension($class) + { + $class = ltrim($class, '\\'); + if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) { + // For BC/FC with namespaced aliases + $class = new ReflectionClass($class); + $class = $class->name; + } + + if (isset($this->extensions[$class])) { + if ($class !== get_class($this->extensions[$class])) { + @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); + } + + return true; + } + + return isset($this->extensionsByClass[$class]); + } + + /** + * Adds a runtime loader. */ - public function hasExtension($name) + public function addRuntimeLoader(Twig_RuntimeLoaderInterface $loader) { - return isset($this->extensions[$name]); + $this->runtimeLoaders[] = $loader; } /** - * Gets an extension by name. + * Gets an extension by class name. * - * @param string $name The extension name + * @param string $class The extension class name * - * @return Twig_ExtensionInterface A Twig_ExtensionInterface instance + * @return Twig_ExtensionInterface */ - public function getExtension($name) + public function getExtension($class) { - if (!isset($this->extensions[$name])) { - throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $name)); + $class = ltrim($class, '\\'); + if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) { + // For BC/FC with namespaced aliases + $class = new ReflectionClass($class); + $class = $class->name; + } + + if (isset($this->extensions[$class])) { + if ($class !== get_class($this->extensions[$class])) { + @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); + } + + return $this->extensions[$class]; + } + + if (!isset($this->extensionsByClass[$class])) { + throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $class)); } - return $this->extensions[$name]; + return $this->extensionsByClass[$class]; } /** - * Registers an extension. + * Returns the runtime implementation of a Twig element (filter/function/test). + * + * @param string $class A runtime class name * - * @param Twig_ExtensionInterface $extension A Twig_ExtensionInterface instance + * @return object The runtime implementation + * + * @throws Twig_Error_Runtime When the template cannot be found */ + public function getRuntime($class) + { + if (isset($this->runtimes[$class])) { + return $this->runtimes[$class]; + } + + foreach ($this->runtimeLoaders as $loader) { + if (null !== $runtime = $loader->load($class)) { + return $this->runtimes[$class] = $runtime; + } + } + + throw new Twig_Error_Runtime(sprintf('Unable to load the "%s" runtime.', $class)); + } + public function addExtension(Twig_ExtensionInterface $extension) { if ($this->extensionInitialized) { throw new LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $extension->getName())); } + $class = get_class($extension); + if ($class !== $extension->getName()) { + if (isset($this->extensions[$extension->getName()])) { + unset($this->extensions[$extension->getName()], $this->extensionsByClass[$class]); + @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); + } + } + + $this->lastModifiedExtension = 0; + $this->extensionsByClass[$class] = $extension; $this->extensions[$extension->getName()] = $extension; + $this->updateOptionsHash(); } /** @@ -652,11 +966,29 @@ class Twig_Environment */ public function removeExtension($name) { + @trigger_error(sprintf('The %s method is deprecated since version 1.12 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + if ($this->extensionInitialized) { throw new LogicException(sprintf('Unable to remove extension "%s" as extensions have already been initialized.', $name)); } - unset($this->extensions[$name]); + $class = ltrim($name, '\\'); + if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) { + // For BC/FC with namespaced aliases + $class = new ReflectionClass($class); + $class = $class->name; + } + + if (isset($this->extensions[$class])) { + if ($class !== get_class($this->extensions[$class])) { + @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); + } + + unset($this->extensions[$class]); + } + + unset($this->extensions[$class]); + $this->updateOptionsHash(); } /** @@ -674,18 +1006,13 @@ class Twig_Environment /** * Returns all registered extensions. * - * @return array An array of extensions + * @return Twig_ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on) */ public function getExtensions() { return $this->extensions; } - /** - * Registers a Token Parser. - * - * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance - */ public function addTokenParser(Twig_TokenParserInterface $parser) { if ($this->extensionInitialized) { @@ -698,7 +1025,9 @@ class Twig_Environment /** * Gets the registered Token Parsers. * - * @return Twig_TokenParserBrokerInterface A broker containing token parsers + * @return Twig_TokenParserBrokerInterface + * + * @internal */ public function getTokenParsers() { @@ -714,7 +1043,9 @@ class Twig_Environment * * Be warned that this method cannot return tags defined by Twig_TokenParserBrokerInterface classes. * - * @return Twig_TokenParserInterface[] An array of Twig_TokenParserInterface instances + * @return Twig_TokenParserInterface[] + * + * @internal */ public function getTags() { @@ -728,11 +1059,6 @@ class Twig_Environment return $tags; } - /** - * Registers a Node Visitor. - * - * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance - */ public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) { if ($this->extensionInitialized) { @@ -745,7 +1071,9 @@ class Twig_Environment /** * Gets the registered Node Visitors. * - * @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances + * @return Twig_NodeVisitorInterface[] + * + * @internal */ public function getNodeVisitors() { @@ -760,23 +1088,25 @@ class Twig_Environment * Registers a Filter. * * @param string|Twig_SimpleFilter $name The filter name or a Twig_SimpleFilter instance - * @param Twig_FilterInterface|Twig_SimpleFilter $filter A Twig_FilterInterface instance or a Twig_SimpleFilter instance + * @param Twig_FilterInterface|Twig_SimpleFilter $filter */ public function addFilter($name, $filter = null) { if (!$name instanceof Twig_SimpleFilter && !($filter instanceof Twig_SimpleFilter || $filter instanceof Twig_FilterInterface)) { - throw new LogicException('A filter must be an instance of Twig_FilterInterface or Twig_SimpleFilter'); + throw new LogicException('A filter must be an instance of Twig_FilterInterface or Twig_SimpleFilter.'); } if ($name instanceof Twig_SimpleFilter) { $filter = $name; $name = $filter->getName(); + } else { + @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); } - + if ($this->extensionInitialized) { throw new LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $name)); } - + $this->staging->addFilter($name, $filter); } @@ -789,6 +1119,8 @@ class Twig_Environment * @param string $name The filter name * * @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exist + * + * @internal */ public function getFilter($name) { @@ -830,11 +1162,13 @@ class Twig_Environment /** * Gets the registered Filters. * - * Be warned that this method cannot return filters defined with registerUndefinedFunctionCallback. + * Be warned that this method cannot return filters defined with registerUndefinedFilterCallback. * - * @return Twig_FilterInterface[] An array of Twig_FilterInterface instances + * @return Twig_FilterInterface[] * * @see registerUndefinedFilterCallback + * + * @internal */ public function getFilters() { @@ -854,14 +1188,16 @@ class Twig_Environment public function addTest($name, $test = null) { if (!$name instanceof Twig_SimpleTest && !($test instanceof Twig_SimpleTest || $test instanceof Twig_TestInterface)) { - throw new LogicException('A test must be an instance of Twig_TestInterface or Twig_SimpleTest'); + throw new LogicException('A test must be an instance of Twig_TestInterface or Twig_SimpleTest.'); } if ($name instanceof Twig_SimpleTest) { $test = $name; $name = $test->getName(); + } else { + @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); } - + if ($this->extensionInitialized) { throw new LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $name)); } @@ -872,7 +1208,9 @@ class Twig_Environment /** * Gets the registered Tests. * - * @return Twig_TestInterface[] An array of Twig_TestInterface instances + * @return Twig_TestInterface[] + * + * @internal */ public function getTests() { @@ -889,6 +1227,8 @@ class Twig_Environment * @param string $name The test name * * @return Twig_Test|false A Twig_Test instance or false if the test does not exist + * + * @internal */ public function getTest($name) { @@ -907,23 +1247,25 @@ class Twig_Environment * Registers a Function. * * @param string|Twig_SimpleFunction $name The function name or a Twig_SimpleFunction instance - * @param Twig_FunctionInterface|Twig_SimpleFunction $function A Twig_FunctionInterface instance or a Twig_SimpleFunction instance + * @param Twig_FunctionInterface|Twig_SimpleFunction $function */ public function addFunction($name, $function = null) { if (!$name instanceof Twig_SimpleFunction && !($function instanceof Twig_SimpleFunction || $function instanceof Twig_FunctionInterface)) { - throw new LogicException('A function must be an instance of Twig_FunctionInterface or Twig_SimpleFunction'); + throw new LogicException('A function must be an instance of Twig_FunctionInterface or Twig_SimpleFunction.'); } if ($name instanceof Twig_SimpleFunction) { $function = $name; $name = $function->getName(); + } else { + @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); } - + if ($this->extensionInitialized) { throw new LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $name)); } - + $this->staging->addFunction($name, $function); } @@ -936,6 +1278,8 @@ class Twig_Environment * @param string $name function name * * @return Twig_Function|false A Twig_Function instance or false if the function does not exist + * + * @internal */ public function getFunction($name) { @@ -979,9 +1323,11 @@ class Twig_Environment * * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback. * - * @return Twig_FunctionInterface[] An array of Twig_FunctionInterface instances + * @return Twig_FunctionInterface[] * * @see registerUndefinedFunctionCallback + * + * @internal */ public function getFunctions() { @@ -1008,11 +1354,11 @@ class Twig_Environment $this->globals = $this->initGlobals(); } - /* This condition must be uncommented in Twig 2.0 if (!array_key_exists($name, $this->globals)) { - throw new LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name)); + // The deprecation notice must be turned into the following exception in Twig 2.0 + @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); + //throw new LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name)); } - */ } if ($this->extensionInitialized || $this->runtimeInitialized) { @@ -1027,6 +1373,8 @@ class Twig_Environment * Gets the registered Globals. * * @return array An array of globals + * + * @internal */ public function getGlobals() { @@ -1065,6 +1413,8 @@ class Twig_Environment * Gets the registered unary Operators. * * @return array An array of unary operators + * + * @internal */ public function getUnaryOperators() { @@ -1079,6 +1429,8 @@ class Twig_Environment * Gets the registered binary Operators. * * @return array An array of binary operators + * + * @internal */ public function getBinaryOperators() { @@ -1089,24 +1441,31 @@ class Twig_Environment return $this->binaryOperators; } + /** + * @deprecated since 1.23 (to be removed in 2.0) + */ public function computeAlternatives($name, $items) { - $alternatives = array(); - foreach ($items as $item) { - $lev = levenshtein($name, $item); - if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { - $alternatives[$item] = $lev; - } - } - asort($alternatives); + @trigger_error(sprintf('The %s method is deprecated since version 1.23 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); - return array_keys($alternatives); + return Twig_Error_Syntax::computeAlternatives($name, $items); } + /** + * @internal + */ protected function initGlobals() { $globals = array(); - foreach ($this->extensions as $extension) { + foreach ($this->extensions as $name => $extension) { + if (!$extension instanceof Twig_Extension_GlobalsInterface) { + $m = new ReflectionMethod($extension, 'getGlobals'); + + if ('Twig_Extension' !== $m->getDeclaringClass()->getName()) { + @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); + } + } + $extGlob = $extension->getGlobals(); if (!is_array($extGlob)) { throw new UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', get_class($extension))); @@ -1120,14 +1479,16 @@ class Twig_Environment return call_user_func_array('array_merge', $globals); } + /** + * @internal + */ protected function initExtensions() { if ($this->extensionInitialized) { return; } - $this->extensionInitialized = true; - $this->parsers = new Twig_TokenParserBroker(); + $this->parsers = new Twig_TokenParserBroker(array(), array(), false); $this->filters = array(); $this->functions = array(); $this->tests = array(); @@ -1139,17 +1500,21 @@ class Twig_Environment $this->initExtension($extension); } $this->initExtension($this->staging); + // Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception + $this->extensionInitialized = true; } + /** + * @internal + */ protected function initExtension(Twig_ExtensionInterface $extension) { // filters foreach ($extension->getFilters() as $name => $filter) { - if ($name instanceof Twig_SimpleFilter) { - $filter = $name; - $name = $filter->getName(); - } elseif ($filter instanceof Twig_SimpleFilter) { + if ($filter instanceof Twig_SimpleFilter) { $name = $filter->getName(); + } else { + @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); } $this->filters[$name] = $filter; @@ -1157,11 +1522,10 @@ class Twig_Environment // functions foreach ($extension->getFunctions() as $name => $function) { - if ($name instanceof Twig_SimpleFunction) { - $function = $name; - $name = $function->getName(); - } elseif ($function instanceof Twig_SimpleFunction) { + if ($function instanceof Twig_SimpleFunction) { $name = $function->getName(); + } else { + @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); } $this->functions[$name] = $function; @@ -1169,11 +1533,10 @@ class Twig_Environment // tests foreach ($extension->getTests() as $name => $test) { - if ($name instanceof Twig_SimpleTest) { - $test = $name; - $name = $test->getName(); - } elseif ($test instanceof Twig_SimpleTest) { + if ($test instanceof Twig_SimpleTest) { $name = $test->getName(); + } else { + @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); } $this->tests[$name] = $test; @@ -1184,9 +1547,11 @@ class Twig_Environment if ($parser instanceof Twig_TokenParserInterface) { $this->parsers->addTokenParser($parser); } elseif ($parser instanceof Twig_TokenParserBrokerInterface) { + @trigger_error('Registering a Twig_TokenParserBrokerInterface instance is deprecated since version 1.21.', E_USER_DEPRECATED); + $this->parsers->addTokenParserBroker($parser); } else { - throw new LogicException('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances'); + throw new LogicException('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances.'); } } @@ -1197,8 +1562,12 @@ class Twig_Environment // operators if ($operators = $extension->getOperators()) { + if (!is_array($operators)) { + 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))); + } + if (2 !== count($operators)) { - throw new InvalidArgumentException(sprintf('"%s::getOperators()" does not return a valid operators array.', get_class($extension))); + throw new InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', get_class($extension), count($operators))); } $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]); @@ -1206,27 +1575,30 @@ class Twig_Environment } } + /** + * @deprecated since 1.22 (to be removed in 2.0) + */ protected function writeCacheFile($file, $content) { - $dir = dirname($file); - if (!is_dir($dir)) { - if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) { - throw new RuntimeException(sprintf("Unable to create the cache directory (%s).", $dir)); - } - } elseif (!is_writable($dir)) { - throw new RuntimeException(sprintf("Unable to write in the cache directory (%s).", $dir)); - } - - $tmpFile = tempnam(dirname($file), basename($file)); - if (false !== @file_put_contents($tmpFile, $content)) { - // rename does not work on Win32 before 5.2.6 - if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) { - @chmod($file, 0666 & ~umask()); - - return; - } - } + $this->cache->write($file, $content); + } - throw new RuntimeException(sprintf('Failed to write cache file "%s".', $file)); + private function updateOptionsHash() + { + $hashParts = array_merge( + array_keys($this->extensions), + array( + (int) function_exists('twig_template_get_attributes'), + PHP_MAJOR_VERSION, + PHP_MINOR_VERSION, + self::VERSION, + (int) $this->debug, + $this->baseTemplateClass, + (int) $this->strictVariables, + ) + ); + $this->optionsHash = implode(':', $hashParts); } } + +class_alias('Twig_Environment', 'Twig\Environment', false); |
