Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

1107 lines (980 sloc) 31.169 kb
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Stores the Twig configuration.
*
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Environment
{
const VERSION = '1.8.1';
protected $charset;
protected $loader;
protected $debug;
protected $autoReload;
protected $cache;
protected $lexer;
protected $parser;
protected $compiler;
protected $baseTemplateClass;
protected $extensions;
protected $parsers;
protected $visitors;
protected $filters;
protected $tests;
protected $functions;
protected $globals;
protected $runtimeInitialized;
protected $loadedTemplates;
protected $strictVariables;
protected $unaryOperators;
protected $binaryOperators;
protected $templateClassPrefix = '__TwigTemplate_';
protected $functionCallbacks;
protected $filterCallbacks;
protected $staging;
/**
* Constructor.
*
* Available options:
*
* * debug: When set to `true`, the generated templates have a __toString()
* method that you can use to display the generated nodes (default to
* false).
*
* * charset: The charset used by the templates (default to utf-8).
*
* * 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).
*
* * auto_reload: Whether to reload the template is the original source changed.
* If you don't provide the auto_reload option, it will be
* determined automatically base on the debug value.
*
* * strict_variables: Whether to ignore invalid variables in templates
* (default to false).
*
* * autoescape: Whether to enable auto-escaping (default to html):
* * 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"
*
* * 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 array $options An array of options
*/
public function __construct(Twig_LoaderInterface $loader = null, $options = array())
{
if (null !== $loader) {
$this->setLoader($loader);
}
$options = array_merge(array(
'debug' => false,
'charset' => 'UTF-8',
'base_template_class' => 'Twig_Template',
'strict_variables' => false,
'autoescape' => 'html',
'cache' => false,
'auto_reload' => null,
'optimizations' => -1,
), $options);
$this->debug = (bool) $options['debug'];
$this->charset = $options['charset'];
$this->baseTemplateClass = $options['base_template_class'];
$this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
$this->extensions = array(
'core' => new Twig_Extension_Core(),
'escaper' => new Twig_Extension_Escaper($options['autoescape']),
'optimizer' => new Twig_Extension_Optimizer($options['optimizations']),
);
$this->strictVariables = (bool) $options['strict_variables'];
$this->runtimeInitialized = false;
$this->setCache($options['cache']);
$this->functionCallbacks = array();
$this->filterCallbacks = array();
$this->staging = array(
'functions' => array(),
'filters' => array(),
'tests' => array(),
'token_parsers' => array(),
'visitors' => array(),
'globals' => array(),
);
}
/**
* Gets the base template class for compiled templates.
*
* @return string The base template class name
*/
public function getBaseTemplateClass()
{
return $this->baseTemplateClass;
}
/**
* Sets the base template class for compiled templates.
*
* @param string $class The base template class name
*/
public function setBaseTemplateClass($class)
{
$this->baseTemplateClass = $class;
}
/**
* Enables debugging mode.
*/
public function enableDebug()
{
$this->debug = true;
}
/**
* Disables debugging mode.
*/
public function disableDebug()
{
$this->debug = false;
}
/**
* Checks if debug mode is enabled.
*
* @return Boolean true if debug mode is enabled, false otherwise
*/
public function isDebug()
{
return $this->debug;
}
/**
* Enables the auto_reload option.
*/
public function enableAutoReload()
{
$this->autoReload = true;
}
/**
* Disables the auto_reload option.
*/
public function disableAutoReload()
{
$this->autoReload = false;
}
/**
* Checks if the auto_reload option is enabled.
*
* @return Boolean true if auto_reload is enabled, false otherwise
*/
public function isAutoReload()
{
return $this->autoReload;
}
/**
* Enables the strict_variables option.
*/
public function enableStrictVariables()
{
$this->strictVariables = true;
}
/**
* Disables the strict_variables option.
*/
public function disableStrictVariables()
{
$this->strictVariables = false;
}
/**
* Checks if the strict_variables option is enabled.
*
* @return Boolean true if strict_variables is enabled, false otherwise
*/
public function isStrictVariables()
{
return $this->strictVariables;
}
/**
* Gets the cache directory or false if cache is disabled.
*
* @return string|false
*/
public function getCache()
{
return $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
*/
public function setCache($cache)
{
$this->cache = $cache ? $cache : false;
}
/**
* Gets the cache filename for a given template.
*
* @param string $name The template name
*
* @return string The cache file name
*/
public function getCacheFilename($name)
{
if (false === $this->cache) {
return false;
}
$class = substr($this->getTemplateClass($name), strlen($this->templateClassPrefix));
return $this->getCache().'/'.substr($class, 0, 2).'/'.substr($class, 2, 2).'/'.substr($class, 4).'.php';
}
/**
* 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
*
* @return string The template class name
*/
public function getTemplateClass($name, $index = null)
{
return $this->templateClassPrefix.md5($this->loader->getCacheKey($name)).(null === $index ? '' : '_'.$index);
}
/**
* Gets the template class prefix.
*
* @return string The template class prefix
*/
public function getTemplateClassPrefix()
{
return $this->templateClassPrefix;
}
/**
* Renders a template.
*
* @param string $name The template name
* @param array $context An array of parameters to pass to the template
*
* @return string The rendered template
*/
public function render($name, array $context = array())
{
return $this->loadTemplate($name)->render($context);
}
/**
* Displays a template.
*
* @param string $name The template name
* @param array $context An array of parameters to pass to the template
*/
public function display($name, array $context = array())
{
$this->loadTemplate($name)->display($context);
}
/**
* Loads a template by name.
*
* @param string $name The template name
* @param integer $index The index if it is an embedded template
*
* @return Twig_TemplateInterface A template instance representing the given template name
*/
public function loadTemplate($name, $index = null)
{
$cls = $this->getTemplateClass($name, $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->loader->getSource($name), $name));
} else {
if (!is_file($cache) || ($this->isAutoReload() && !$this->isTemplateFresh($name, filemtime($cache)))) {
$this->writeCacheFile($cache, $this->compileSource($this->loader->getSource($name), $name));
}
require_once $cache;
}
}
if (!$this->runtimeInitialized) {
$this->initRuntime();
}
return $this->loadedTemplates[$cls] = new $cls($this);
}
/**
* Returns true if the template is still fresh.
*
* Besides checking the loader for freshness information,
* 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
*
* @return Boolean 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;
}
}
return $this->loader->isFresh($name, $time);
}
public function resolveTemplate($names)
{
if (!is_array($names)) {
$names = array($names);
}
foreach ($names as $name) {
if ($name instanceof Twig_Template) {
return $name;
}
try {
return $this->loadTemplate($name);
} catch (Twig_Error_Loader $e) {
}
}
if (1 === count($names)) {
throw $e;
}
throw new Twig_Error_Loader(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names)));
}
/**
* Clears the internal template cache.
*/
public function clearTemplateCache()
{
$this->loadedTemplates = array();
}
/**
* Clears the template cache files on the filesystem.
*/
public function clearCacheFiles()
{
if (false === $this->cache) {
return;
}
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
if ($file->isFile()) {
@unlink($file->getPathname());
}
}
}
/**
* Gets the Lexer instance.
*
* @return Twig_LexerInterface A Twig_LexerInterface instance
*/
public function getLexer()
{
if (null === $this->lexer) {
$this->lexer = new Twig_Lexer($this);
}
return $this->lexer;
}
/**
* Sets the Lexer instance.
*
* @param Twig_LexerInterface A Twig_LexerInterface instance
*/
public function setLexer(Twig_LexerInterface $lexer)
{
$this->lexer = $lexer;
}
/**
* Tokenizes a source code.
*
* @param string $source The template source code
* @param string $name The template name
*
* @return Twig_TokenStream A Twig_TokenStream instance
*/
public function tokenize($source, $name = null)
{
return $this->getLexer()->tokenize($source, $name);
}
/**
* Gets the Parser instance.
*
* @return Twig_ParserInterface A Twig_ParserInterface instance
*/
public function getParser()
{
if (null === $this->parser) {
$this->parser = new Twig_Parser($this);
}
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.
*
* @param Twig_TokenStream $tokens A Twig_TokenStream instance
*
* @return Twig_Node_Module A Node tree
*/
public function parse(Twig_TokenStream $tokens)
{
return $this->getParser()->parse($tokens);
}
/**
* Gets the Compiler instance.
*
* @return Twig_CompilerInterface A Twig_CompilerInterface instance
*/
public function getCompiler()
{
if (null === $this->compiler) {
$this->compiler = new Twig_Compiler($this);
}
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
*
* @return string The compiled PHP source code
*/
public function compile(Twig_NodeInterface $node)
{
return $this->getCompiler()->compile($node)->getSource();
}
/**
* Compiles a template source code.
*
* @param string $source The template source code
* @param string $name The template name
*
* @return string The compiled PHP source code
*/
public function compileSource($source, $name = null)
{
try {
return $this->compile($this->parse($this->tokenize($source, $name)));
} catch (Twig_Error $e) {
$e->setTemplateFile($name);
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);
}
}
/**
* Sets the Loader instance.
*
* @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance
*/
public function setLoader(Twig_LoaderInterface $loader)
{
$this->loader = $loader;
}
/**
* Gets the Loader instance.
*
* @return Twig_LoaderInterface A Twig_LoaderInterface instance
*/
public function getLoader()
{
return $this->loader;
}
/**
* Sets the default template charset.
*
* @param string $charset The default charset
*/
public function setCharset($charset)
{
$this->charset = $charset;
}
/**
* Gets the default template charset.
*
* @return string The default charset
*/
public function getCharset()
{
return $this->charset;
}
/**
* Initializes the runtime environment.
*/
public function initRuntime()
{
$this->runtimeInitialized = true;
foreach ($this->getExtensions() as $extension) {
$extension->initRuntime($this);
}
}
/**
* Returns true if the given extension is registered.
*
* @param string $name The extension name
*
* @return Boolean Whether the extension is registered or not
*/
public function hasExtension($name)
{
return isset($this->extensions[$name]);
}
/**
* Gets an extension by name.
*
* @param string $name The extension name
*
* @return Twig_ExtensionInterface A Twig_ExtensionInterface instance
*/
public function getExtension($name)
{
if (!isset($this->extensions[$name])) {
throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $name));
}
return $this->extensions[$name];
}
/**
* Registers an extension.
*
* @param Twig_ExtensionInterface $extension A Twig_ExtensionInterface instance
*/
public function addExtension(Twig_ExtensionInterface $extension)
{
$this->extensions[$extension->getName()] = $extension;
$this->parsers = null;
$this->visitors = null;
$this->filters = null;
$this->tests = null;
$this->functions = null;
$this->globals = null;
}
/**
* Removes an extension by name.
*
* @param string $name The extension name
*/
public function removeExtension($name)
{
unset($this->extensions[$name]);
$this->parsers = null;
$this->visitors = null;
$this->filters = null;
$this->tests = null;
$this->functions = null;
$this->globals = null;
}
/**
* Registers an array of extensions.
*
* @param array $extensions An array of extensions
*/
public function setExtensions(array $extensions)
{
foreach ($extensions as $extension) {
$this->addExtension($extension);
}
}
/**
* Returns all registered extensions.
*
* @return array An array of extensions
*/
public function getExtensions()
{
return $this->extensions;
}
/**
* Registers a Token Parser.
*
* @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance
*/
public function addTokenParser(Twig_TokenParserInterface $parser)
{
$this->staging['token_parsers'][] = $parser;
$this->parsers = null;
}
/**
* Gets the registered Token Parsers.
*
* @return Twig_TokenParserBrokerInterface A broker containing token parsers
*/
public function getTokenParsers()
{
if (null === $this->parsers) {
$this->parsers = new Twig_TokenParserBroker();
if (isset($this->staging['token_parsers'])) {
foreach ($this->staging['token_parsers'] as $parser) {
$this->parsers->addTokenParser($parser);
}
}
foreach ($this->getExtensions() as $extension) {
$parsers = $extension->getTokenParsers();
foreach($parsers as $parser) {
if ($parser instanceof Twig_TokenParserInterface) {
$this->parsers->addTokenParser($parser);
} elseif ($parser instanceof Twig_TokenParserBrokerInterface) {
$this->parsers->addTokenParserBroker($parser);
} else {
throw new Twig_Error_Runtime('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances');
}
}
}
}
return $this->parsers;
}
/**
* Gets registered tags.
*
* Be warned that this method cannot return tags defined by Twig_TokenParserBrokerInterface classes.
*
* @return Twig_TokenParserInterface[] An array of Twig_TokenParserInterface instances
*/
public function getTags()
{
$tags = array();
foreach ($this->getTokenParsers()->getParsers() as $parser) {
if ($parser instanceof Twig_TokenParserInterface) {
$tags[$parser->getTag()] = $parser;
}
}
return $tags;
}
/**
* Registers a Node Visitor.
*
* @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance
*/
public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
{
$this->staging['visitors'][] = $visitor;
$this->visitors = null;
}
/**
* Gets the registered Node Visitors.
*
* @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances
*/
public function getNodeVisitors()
{
if (null === $this->visitors) {
foreach ($this->getExtensions() as $extension) {
foreach ($extension->getNodeVisitors() as $visitor) {
$this->addNodeVisitor($visitor);
}
}
$this->visitors = $this->staging['visitors'];
}
return $this->visitors;
}
/**
* Registers a Filter.
*
* @param string $name The filter name
* @param Twig_FilterInterface $filter A Twig_FilterInterface instance
*/
public function addFilter($name, Twig_FilterInterface $filter)
{
$this->staging['filters'][$name] = $filter;
$this->filters = null;
}
/**
* Get a filter by name.
*
* Subclasses may override this method and load filters differently;
* so no list of filters is available.
*
* @param string $name The filter name
*
* @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exists
*/
public function getFilter($name)
{
if (null === $this->filters) {
$this->getFilters();
}
if (isset($this->filters[$name])) {
return $this->filters[$name];
}
foreach ($this->filters as $pattern => $filter) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count) {
if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$filter->setArguments($matches);
return $filter;
}
}
}
foreach ($this->filterCallbacks as $callback) {
if (false !== $filter = call_user_func($callback, $name)) {
return $filter;
}
}
return false;
}
public function registerUndefinedFilterCallback($callable)
{
$this->filterCallbacks[] = $callable;
}
/**
* Gets the registered Filters.
*
* Be warned that this method cannot return filters defined with registerUndefinedFunctionCallback.
*
* @return Twig_FilterInterface[] An array of Twig_FilterInterface instances
*
* @see registerUndefinedFilterCallback
*/
public function getFilters()
{
if (null === $this->filters) {
foreach ($this->getExtensions() as $extension) {
foreach ($extension->getFilters() as $name => $filter) {
$this->addFilter($name, $filter);
}
}
$this->filters = $this->staging['filters'];
}
return $this->filters;
}
/**
* Registers a Test.
*
* @param string $name The test name
* @param Twig_TestInterface $test A Twig_TestInterface instance
*/
public function addTest($name, Twig_TestInterface $test)
{
$this->staging['tests'][$name] = $test;
$this->tests = null;
}
/**
* Gets the registered Tests.
*
* @return Twig_TestInterface[] An array of Twig_TestInterface instances
*/
public function getTests()
{
if (null === $this->tests) {
foreach ($this->getExtensions() as $extension) {
foreach ($extension->getTests() as $name => $test) {
$this->addTest($name, $test);
}
}
$this->tests = $this->staging['tests'];
}
return $this->tests;
}
/**
* Registers a Function.
*
* @param string $name The function name
* @param Twig_FunctionInterface $function A Twig_FunctionInterface instance
*/
public function addFunction($name, Twig_FunctionInterface $function)
{
$this->staging['functions'][$name] = $function;
$this->functions = null;
}
/**
* Get a function by name.
*
* Subclasses may override this method and load functions differently;
* so no list of functions is available.
*
* @param string $name function name
*
* @return Twig_Function|false A Twig_Function instance or false if the function does not exists
*/
public function getFunction($name)
{
if (null === $this->functions) {
$this->getFunctions();
}
if (isset($this->functions[$name])) {
return $this->functions[$name];
}
foreach ($this->functions as $pattern => $function) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count) {
if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$function->setArguments($matches);
return $function;
}
}
}
foreach ($this->functionCallbacks as $callback) {
if (false !== $function = call_user_func($callback, $name)) {
return $function;
}
}
return false;
}
public function registerUndefinedFunctionCallback($callable)
{
$this->functionCallbacks[] = $callable;
}
/**
* Gets registered functions.
*
* Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback.
*
* @return Twig_FunctionInterface[] An array of Twig_FunctionInterface instances
*
* @see registerUndefinedFunctionCallback
*/
public function getFunctions()
{
if (null === $this->functions) {
foreach ($this->getExtensions() as $extension) {
foreach ($extension->getFunctions() as $name => $function) {
$this->addFunction($name, $function);
}
}
$this->functions = $this->staging['functions'];
}
return $this->functions;
}
/**
* Registers a Global.
*
* @param string $name The global name
* @param mixed $value The global value
*/
public function addGlobal($name, $value)
{
$this->staging['globals'][$name] = $value;
$this->globals = null;
}
/**
* Gets the registered Globals.
*
* @return array An array of globals
*/
public function getGlobals()
{
if (null === $this->globals) {
$this->globals = isset($this->staging['globals']) ? $this->staging['globals'] : array();
foreach ($this->getExtensions() as $extension) {
$this->globals = array_merge($this->globals, $extension->getGlobals());
}
}
return $this->globals;
}
/**
* Merges a context with the defined globals.
*
* @param array $context An array representing the context
*
* @return array The context merged with the globals
*/
public function mergeGlobals(array $context)
{
// we don't use array_merge as the context being generally
// bigger than globals, this code is faster.
foreach ($this->getGlobals() as $key => $value) {
if (!array_key_exists($key, $context)) {
$context[$key] = $value;
}
}
return $context;
}
/**
* Gets the registered unary Operators.
*
* @return array An array of unary operators
*/
public function getUnaryOperators()
{
if (null === $this->unaryOperators) {
$this->initOperators();
}
return $this->unaryOperators;
}
/**
* Gets the registered binary Operators.
*
* @return array An array of binary operators
*/
public function getBinaryOperators()
{
if (null === $this->binaryOperators) {
$this->initOperators();
}
return $this->binaryOperators;
}
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);
return array_keys($alternatives);
}
protected function initOperators()
{
$this->unaryOperators = array();
$this->binaryOperators = array();
foreach ($this->getExtensions() as $extension) {
$operators = $extension->getOperators();
if (!$operators) {
continue;
}
if (2 !== count($operators)) {
throw new InvalidArgumentException(sprintf('"%s::getOperators()" does not return a valid operators array.', get_class($extension)));
}
$this->unaryOperators = array_merge($this->unaryOperators, $operators[0]);
$this->binaryOperators = array_merge($this->binaryOperators, $operators[1]);
}
}
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, 0644);
return;
}
}
throw new Twig_Error_Runtime(sprintf('Failed to write cache file "%s".', $file));
}
}
Jump to Line
Something went wrong with that request. Please try again.