optimized the way Twig exceptions are managed

As guessing the template name and the line number of where an error
occurred is quite expensive, this is now avoided as much as possible.
This commit is contained in:
Fabien Potencier
2012-10-17 19:29:45 +02:00
parent 244eaf53b3
commit 2506be1794
28 changed files with 186 additions and 85 deletions
+1
View File
@@ -1,5 +1,6 @@
* 1.11.0 (2012-XX-XX)
* optimized the way Twig exceptions are managed (to make them faster)
* added Twig_ExistsLoaderInterface (implementing this interface in your loader make the chain loader much faster)
* 1.10.3 (2012-10-19)
+11 -1
View File
@@ -25,6 +25,7 @@ class Twig_Compiler implements Twig_CompilerInterface
protected $debugInfo;
protected $sourceOffset;
protected $sourceLine;
protected $filename;
/**
* Constructor.
@@ -37,6 +38,11 @@ class Twig_Compiler implements Twig_CompilerInterface
$this->debugInfo = array();
}
public function getFilename()
{
return $this->filename;
}
/**
* Returns the environment instance related to this compiler.
*
@@ -73,6 +79,10 @@ class Twig_Compiler implements Twig_CompilerInterface
$this->sourceLine = 0;
$this->indentation = $indentation;
if ($node instanceof Twig_Node_Module) {
$this->filename = $node->getAttribute('filename');
}
$node->compile($this);
return $this;
@@ -246,7 +256,7 @@ class Twig_Compiler implements Twig_CompilerInterface
{
// can't outdent by more steps that the current indentation level
if ($this->indentation < $step) {
throw new Twig_Error('Unable to call outdent() as the indentation would become negative');
throw new LogicException('Unable to call outdent() as the indentation would become negative');
}
$this->indentation -= $step;
+2 -2
View File
@@ -711,7 +711,7 @@ class Twig_Environment
} 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');
throw new LogicException('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances');
}
}
}
@@ -1100,6 +1100,6 @@ class Twig_Environment
}
}
throw new Twig_Error_Runtime(sprintf('Failed to write cache file "%s".', $file));
throw new RuntimeException(sprintf('Failed to write cache file "%s".', $file));
}
}
+34 -2
View File
@@ -12,6 +12,23 @@
/**
* Twig base exception.
*
* This exception class and its children must only be used when
* an error occurs during the loading of a template, when a syntax error
* is detected in a template, or when rendering a template. Other
* errors must use regular PHP exception classes (like when the template
* cache directory is not writable for instance).
*
* To help debugging template issues, this class tracks the original template
* name and line where the error occurred.
*
* Whenever possible, you must set these information (original template name
* and line number) yourself by passing them to the constructor. If some or all
* these information are not available from where you throw the exception, then
* this class will guess them automatically (when the line number is set to -1
* and/or the filename is set to null). As this is a costly operation, this
* can be disabled by passing false for both the filename and the line number
* when creating a new instance of this class.
*
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
@@ -25,6 +42,15 @@ class Twig_Error extends Exception
/**
* Constructor.
*
* Set both the line number and the filename to false to
* disable automatic guessing of the original template name
* and line number.
*
* Set the line number to -1 to enable its automatic guessing.
* Set the filename to null to enable its automatic guessing.
*
* By default, automatic guessing is enabled.
*
* @param string $message The error message
* @param integer $lineno The template line where the error occurred
* @param string $filename The template file name where the error occurred
@@ -105,6 +131,12 @@ class Twig_Error extends Exception
$this->updateRepr();
}
public function guess()
{
$this->guessTemplateInfo();
$this->updateRepr();
}
/**
* For PHP < 5.3.0, provides access to the getPrevious() method.
*
@@ -134,7 +166,7 @@ class Twig_Error extends Exception
$dot = true;
}
if (null !== $this->filename) {
if ($this->filename) {
if (is_string($this->filename) || (is_object($this->filename) && method_exists($this->filename, '__toString'))) {
$filename = sprintf('"%s"', $this->filename);
} else {
@@ -143,7 +175,7 @@ class Twig_Error extends Exception
$this->message .= sprintf(' in %s', $filename);
}
if ($this->lineno >= 0) {
if ($this->lineno && $this->lineno >= 0) {
$this->message .= sprintf(' at line %d', $this->lineno);
}
+12
View File
@@ -12,9 +12,21 @@
/**
* Exception thrown when an error occurs during template loading.
*
* Automatic template information guessing is always turned off as
* if a template cannot be loaded, there is nothing to guess.
* However, when a template is loaded from another one, then, we need
* to find the current context and this is automatically done by
* Twig_Template::displayWithErrorHandling().
*
* This strategy makes Twig_Environment::resolveTemplate() much faster.
*
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Error_Loader extends Twig_Error
{
public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null)
{
parent::__construct($message, false, false, $previous);
}
}
+7 -7
View File
@@ -158,7 +158,7 @@ class Twig_ExpressionParser
} elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) {
$node = $this->parseHashExpression();
} else {
throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine());
throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine(), $this->parser->getFilename());
}
}
@@ -252,7 +252,7 @@ class Twig_ExpressionParser
} else {
$current = $stream->getCurrent();
throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($current->getType(), $current->getLine()), $current->getValue()), $current->getLine());
throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($current->getType(), $current->getLine()), $current->getValue()), $current->getLine(), $this->parser->getFilename());
}
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
@@ -291,11 +291,11 @@ class Twig_ExpressionParser
switch ($name) {
case 'parent':
if (!count($this->parser->getBlockStack())) {
throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line);
throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line, $this->parser->getFilename());
}
if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden', $line);
throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden', $line, $this->parser->getFilename());
}
return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line);
@@ -303,7 +303,7 @@ class Twig_ExpressionParser
return new Twig_Node_Expression_BlockReference($args->getNode(0), false, $line);
case 'attribute':
if (count($args) < 2) {
throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line);
throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line, $this->parser->getFilename());
}
return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : new Twig_Node_Expression_Array(array(), $line), Twig_TemplateInterface::ANY_CALL, $line);
@@ -351,7 +351,7 @@ class Twig_ExpressionParser
}
}
} else {
throw new Twig_Error_Syntax('Expected name or number', $lineno);
throw new Twig_Error_Syntax('Expected name or number', $lineno, $this->parser->getFilename());
}
} else {
$type = Twig_TemplateInterface::ARRAY_CALL;
@@ -446,7 +446,7 @@ class Twig_ExpressionParser
while (true) {
$token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to');
if (in_array($token->getValue(), array('true', 'false', 'none'))) {
throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine());
throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine(), $this->parser->getFilename());
}
$targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine());
+2 -2
View File
@@ -139,7 +139,7 @@ class Twig_Node implements Twig_NodeInterface
public function getAttribute($name)
{
if (!array_key_exists($name, $this->attributes)) {
throw new Twig_Error_Runtime(sprintf('Attribute "%s" does not exist for Node "%s".', $name, get_class($this)));
throw new LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, get_class($this)));
}
return $this->attributes[$name];
@@ -188,7 +188,7 @@ class Twig_Node implements Twig_NodeInterface
public function getNode($name)
{
if (!array_key_exists($name, $this->nodes)) {
throw new Twig_Error_Runtime(sprintf('Node "%s" does not exist for Node "%s".', $name, get_class($this)));
throw new LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, get_class($this)));
}
return $this->nodes[$name];
+1 -1
View File
@@ -26,7 +26,7 @@ class Twig_Node_Expression_Filter extends Twig_Node_Expression
$message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
}
throw new Twig_Error_Syntax($message, $this->getLine());
throw new Twig_Error_Syntax($message, $this->getLine(), $compiler->getFilename());
}
$this->compileFilter($compiler, $filter);
+1 -1
View File
@@ -25,7 +25,7 @@ class Twig_Node_Expression_Function extends Twig_Node_Expression
$message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
}
throw new Twig_Error_Syntax($message, $this->getLine());
throw new Twig_Error_Syntax($message, $this->getLine(), $compiler->getFilename());
}
$compiler->raw($function->compile().'(');
+1 -1
View File
@@ -25,7 +25,7 @@ class Twig_Node_Expression_Test extends Twig_Node_Expression
$message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
}
throw new Twig_Error_Syntax($message, $this->getLine());
throw new Twig_Error_Syntax($message, $this->getLine(), $compiler->getFilename());
}
$name = $this->getAttribute('name');
+1 -1
View File
@@ -35,7 +35,7 @@ class Twig_Node_Expression_Test_Defined extends Twig_Node_Expression_Test
$this->changeIgnoreStrictCheck($node);
} else {
throw new Twig_Error_Syntax('The "defined" test only works with simple variables', $this->getLine());
throw new Twig_Error_Syntax('The "defined" test only works with simple variables', $this->getLine(), $compiler->getFilename());
}
}
-13
View File
@@ -20,19 +20,6 @@ class Twig_Node_Sandbox extends Twig_Node
public function __construct(Twig_NodeInterface $body, $lineno, $tag = null)
{
parent::__construct(array('body' => $body), array(), $lineno, $tag);
// in a sandbox tag, only include tags are allowed
if (!$this->getNode('body') instanceof Twig_Node_Include) {
foreach ($this->getNode('body') as $node) {
if ($node instanceof Twig_Node_Text && ctype_space($node->getAttribute('data'))) {
continue;
}
if (!$node instanceof Twig_Node_Include) {
throw new Twig_Error_Syntax('Only "include" tags are allowed within a "sandbox" section', $node->getLine());
}
}
}
}
/**
+19 -10
View File
@@ -53,6 +53,11 @@ class Twig_Parser implements Twig_ParserInterface
return sprintf('__internal_%s', hash('sha1', uniqid(mt_rand(), true), false));
}
public function getFilename()
{
return $this->stream->getFilename();
}
/**
* Converts a token stream to a node tree.
*
@@ -100,14 +105,18 @@ class Twig_Parser implements Twig_ParserInterface
}
}
} catch (Twig_Error_Syntax $e) {
if (null === $e->getTemplateFile()) {
$e->setTemplateFile($this->stream->getFilename());
if (!$e->getTemplateFile()) {
$e->setTemplateFile($this->getFilename());
}
if (!$e->getTemplateLine()) {
$e->setTemplateLine($this->stream->getCurrent()->getLine());
}
throw $e;
}
$node = new Twig_Node_Module(new Twig_Node_Body(array($body)), $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->embeddedTemplates, $this->stream->getFilename());
$node = new Twig_Node_Module(new Twig_Node_Body(array($body)), $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->embeddedTemplates, $this->getFilename());
$traverser = new Twig_NodeTraverser($this->env, $this->visitors);
@@ -144,7 +153,7 @@ class Twig_Parser implements Twig_ParserInterface
$token = $this->getCurrentToken();
if ($token->getType() !== Twig_Token::NAME_TYPE) {
throw new Twig_Error_Syntax('A block must start with a tag name', $token->getLine(), $this->stream->getFilename());
throw new Twig_Error_Syntax('A block must start with a tag name', $token->getLine(), $this->getFilename());
}
if (null !== $test && call_user_func($test, $token)) {
@@ -167,7 +176,7 @@ class Twig_Parser implements Twig_ParserInterface
$error .= sprintf(' (expecting closing tag for the "%s" tag defined near line %s)', $test[0]->getTag(), $lineno);
}
throw new Twig_Error_Syntax($error, $token->getLine(), $this->stream->getFilename());
throw new Twig_Error_Syntax($error, $token->getLine(), $this->getFilename());
}
$message = sprintf('Unknown tag name "%s"', $token->getValue());
@@ -175,7 +184,7 @@ class Twig_Parser implements Twig_ParserInterface
$message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
}
throw new Twig_Error_Syntax($message, $token->getLine(), $this->stream->getFilename());
throw new Twig_Error_Syntax($message, $token->getLine(), $this->getFilename());
}
$this->stream->next();
@@ -187,7 +196,7 @@ class Twig_Parser implements Twig_ParserInterface
break;
default:
throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', -1, $this->stream->getFilename());
throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', 0, $this->getFilename());
}
}
@@ -259,7 +268,7 @@ class Twig_Parser implements Twig_ParserInterface
}
if (in_array($name, $this->reservedMacroNames)) {
throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine());
throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine(), $this->getFilename());
}
$this->macros[$name] = $node;
@@ -360,10 +369,10 @@ class Twig_Parser implements Twig_ParserInterface
(!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface)
) {
if (false !== strpos((string) $node, chr(0xEF).chr(0xBB).chr(0xBF))) {
throw new Twig_Error_Syntax('A template that extends another one cannot have a body but a byte order mark (BOM) has been detected; it must be removed.', $node->getLine(), $this->stream->getFilename());
throw new Twig_Error_Syntax('A template that extends another one cannot have a body but a byte order mark (BOM) has been detected; it must be removed.', $node->getLine(), $this->getFilename());
}
throw new Twig_Error_Syntax('A template that extends another one cannot have a body.', $node->getLine(), $this->stream->getFilename());
throw new Twig_Error_Syntax('A template that extends another one cannot have a body.', $node->getLine(), $this->getFilename());
}
// bypass "set" nodes as they "capture" the output
+16 -5
View File
@@ -264,6 +264,17 @@ abstract class Twig_Template implements Twig_TemplateInterface
try {
$this->doDisplay($context, $blocks);
} catch (Twig_Error $e) {
if (!$e->getTemplateFile()) {
$e->setTemplateFile($this->getTemplateName());
}
// this is mostly useful for Twig_Error_Loader exceptions
// see Twig_Error_Loader
if (false === $e->getTemplateLine()) {
$e->setTemplateLine(-1);
$e->guess();
}
throw $e;
} catch (Exception $e) {
throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, null, $e);
@@ -350,11 +361,11 @@ abstract class Twig_Template implements Twig_TemplateInterface
}
if (is_object($object)) {
throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $item, get_class($object)));
throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName());
} elseif (is_array($object)) {
throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $item, implode(', ', array_keys($object))));
throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $item, implode(', ', array_keys($object))), -1, $this->getTemplateName());
} else {
throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a "%s" variable', $item, gettype($object)));
throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a "%s" variable', $item, gettype($object)), -1, $this->getTemplateName());
}
}
}
@@ -368,7 +379,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
return null;
}
throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist', $item, is_array($object) ? 'Array' : $object));
throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist', $item, is_array($object) ? 'Array' : $object), -1, $this->getTemplateName());
}
$class = get_class($object);
@@ -411,7 +422,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
return null;
}
throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)));
throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName());
}
if ($isDefinedTest) {
+2 -2
View File
@@ -169,7 +169,7 @@ class Twig_Token
$name = 'INTERPOLATION_END_TYPE';
break;
default:
throw new Twig_Error_Syntax(sprintf('Token of type "%s" does not exist.', $type), $line);
throw new LogicException(sprintf('Token of type "%s" does not exist.', $type));
}
return $short ? $name : 'Twig_Token::'.$name;
@@ -213,7 +213,7 @@ class Twig_Token
case self::INTERPOLATION_END_TYPE:
return 'end of string interpolation';
default:
throw new Twig_Error_Syntax(sprintf('Token of type "%s" does not exist.', $type), $line);
throw new LogicException(sprintf('Token of type "%s" does not exist.', $type));
}
}
}
+8 -7
View File
@@ -39,13 +39,14 @@ class Twig_TokenParser_AutoEscape extends Twig_TokenParser
public function parse(Twig_Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
if ($this->parser->getStream()->test(Twig_Token::BLOCK_END_TYPE)) {
if ($stream->test(Twig_Token::BLOCK_END_TYPE)) {
$value = 'html';
} else {
$expr = $this->parser->getExpressionParser()->parseExpression();
if (!$expr instanceof Twig_Node_Expression_Constant) {
throw new Twig_Error_Syntax('An escaping strategy must be a string or a Boolean.', $lineno);
throw new Twig_Error_Syntax('An escaping strategy must be a string or a Boolean.', $stream->getCurrent()->getLine(), $stream->getFilename());
}
$value = $expr->getAttribute('value');
@@ -55,18 +56,18 @@ class Twig_TokenParser_AutoEscape extends Twig_TokenParser
$value = 'html';
}
if ($compat && $this->parser->getStream()->test(Twig_Token::NAME_TYPE)) {
if ($compat && $stream->test(Twig_Token::NAME_TYPE)) {
if (false === $value) {
throw new Twig_Error_Syntax('Unexpected escaping strategy as you set autoescaping to false.', $lineno);
throw new Twig_Error_Syntax('Unexpected escaping strategy as you set autoescaping to false.', $stream->getCurrent()->getLine(), $stream->getFilename());
}
$value = $this->parser->getStream()->next()->getValue();
$value = $stream->next()->getValue();
}
}
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
$stream->expect(Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
$stream->expect(Twig_Token::BLOCK_END_TYPE);
return new Twig_Node_AutoEscape($value, $body, $lineno, $this->getTag());
}
+2 -2
View File
@@ -35,7 +35,7 @@ class Twig_TokenParser_Block extends Twig_TokenParser
$stream = $this->parser->getStream();
$name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
if ($this->parser->hasBlock($name)) {
throw new Twig_Error_Syntax(sprintf("The block '$name' has already been defined line %d", $this->parser->getBlock($name)->getLine()), $lineno);
throw new Twig_Error_Syntax(sprintf("The block '$name' has already been defined line %d", $this->parser->getBlock($name)->getLine()), $stream->getCurrent()->getLine(), $stream->getFilename());
}
$this->parser->setBlock($name, $block = new Twig_Node_Block($name, new Twig_Node(array()), $lineno));
$this->parser->pushLocalScope();
@@ -49,7 +49,7 @@ class Twig_TokenParser_Block extends Twig_TokenParser
$value = $stream->next()->getValue();
if ($value != $name) {
throw new Twig_Error_Syntax(sprintf("Expected endblock for block '$name' (but %s given)", $value), $lineno);
throw new Twig_Error_Syntax(sprintf("Expected endblock for block '$name' (but %s given)", $value), $stream->getCurrent()->getLine(), $stream->getFilename());
}
}
} else {
+2 -2
View File
@@ -29,11 +29,11 @@ class Twig_TokenParser_Extends extends Twig_TokenParser
public function parse(Twig_Token $token)
{
if (!$this->parser->isMainScope()) {
throw new Twig_Error_Syntax('Cannot extend from a block', $token->getLine());
throw new Twig_Error_Syntax('Cannot extend from a block', $token->getLine(), $this->parser->getFilename());
}
if (null !== $this->parser->getParent()) {
throw new Twig_Error_Syntax('Multiple extends tags are forbidden', $token->getLine());
throw new Twig_Error_Syntax('Multiple extends tags are forbidden', $token->getLine(), $this->parser->getFilename());
}
$this->parser->setParent($this->parser->getExpressionParser()->parseExpression());
+7 -6
View File
@@ -36,22 +36,23 @@ class Twig_TokenParser_If extends Twig_TokenParser
{
$lineno = $token->getLine();
$expr = $this->parser->getExpressionParser()->parseExpression();
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
$stream = $this->parser->getStream();
$stream->expect(Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideIfFork'));
$tests = array($expr, $body);
$else = null;
$end = false;
while (!$end) {
switch ($this->parser->getStream()->next()->getValue()) {
switch ($stream->next()->getValue()) {
case 'else':
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
$stream->expect(Twig_Token::BLOCK_END_TYPE);
$else = $this->parser->subparse(array($this, 'decideIfEnd'));
break;
case 'elseif':
$expr = $this->parser->getExpressionParser()->parseExpression();
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
$stream->expect(Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideIfFork'));
$tests[] = $expr;
$tests[] = $body;
@@ -62,11 +63,11 @@ class Twig_TokenParser_If extends Twig_TokenParser
break;
default:
throw new Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d)', $lineno), -1);
throw new Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d)', $lineno), $stream->getCurrent()->getLine(), $stream->getFilename());
}
}
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
$stream->expect(Twig_Token::BLOCK_END_TYPE);
return new Twig_Node_If(new Twig_Node($tests), $else, $lineno, $this->getTag());
}
+7 -6
View File
@@ -30,22 +30,23 @@ class Twig_TokenParser_Macro extends Twig_TokenParser
public function parse(Twig_Token $token)
{
$lineno = $token->getLine();
$name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
$stream = $this->parser->getStream();
$name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
$arguments = $this->parser->getExpressionParser()->parseArguments();
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
$stream->expect(Twig_Token::BLOCK_END_TYPE);
$this->parser->pushLocalScope();
$body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE)) {
$value = $this->parser->getStream()->next()->getValue();
if ($stream->test(Twig_Token::NAME_TYPE)) {
$value = $stream->next()->getValue();
if ($value != $name) {
throw new Twig_Error_Syntax(sprintf("Expected endmacro for macro '$name' (but %s given)", $value), $lineno);
throw new Twig_Error_Syntax(sprintf("Expected endmacro for macro '$name' (but %s given)", $value), $stream->getCurrent()->getLine(), $stream->getFilename());
}
}
$this->parser->popLocalScope();
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
$stream->expect(Twig_Token::BLOCK_END_TYPE);
$this->parser->setMacro($name, new Twig_Node_Macro($name, new Twig_Node_Body(array($body)), $arguments, $lineno, $this->getTag()));
+13
View File
@@ -35,6 +35,19 @@ class Twig_TokenParser_Sandbox extends Twig_TokenParser
$body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
// in a sandbox tag, only include tags are allowed
if (!$body instanceof Twig_Node_Include) {
foreach ($body as $node) {
if ($node instanceof Twig_Node_Text && ctype_space($node->getAttribute('data'))) {
continue;
}
if (!$node instanceof Twig_Node_Include) {
throw new Twig_Error_Syntax('Only "include" tags are allowed within a "sandbox" section', $node->getLine(), $this->parser->getFilename());
}
}
}
return new Twig_Node_Sandbox($body, $token->getLine(), $this->getTag());
}
+2 -2
View File
@@ -49,13 +49,13 @@ class Twig_TokenParser_Set extends Twig_TokenParser
$stream->expect(Twig_Token::BLOCK_END_TYPE);
if (count($names) !== count($values)) {
throw new Twig_Error_Syntax("When using set, you must have the same number of variables and assignements.", $lineno);
throw new Twig_Error_Syntax("When using set, you must have the same number of variables and assignements.", $stream->getCurrent()->getLine(), $stream->getFilename());
}
} else {
$capture = true;
if (count($names) > 1) {
throw new Twig_Error_Syntax("When using set with a block, you cannot have a multi-target.", $lineno);
throw new Twig_Error_Syntax("When using set with a block, you cannot have a multi-target.", $stream->getCurrent()->getLine(), $stream->getFilename());
}
$stream->expect(Twig_Token::BLOCK_END_TYPE);
+2 -3
View File
@@ -35,13 +35,12 @@ class Twig_TokenParser_Use extends Twig_TokenParser
public function parse(Twig_Token $token)
{
$template = $this->parser->getExpressionParser()->parseExpression();
$stream = $this->parser->getStream();
if (!$template instanceof Twig_Node_Expression_Constant) {
throw new Twig_Error_Syntax('The template references in a "use" statement must be a string.', $token->getLine());
throw new Twig_Error_Syntax('The template references in a "use" statement must be a string.', $stream->getCurrent()->getLine(), $stream->getFilename());
}
$stream = $this->parser->getStream();
$targets = array();
if ($stream->test('with')) {
$stream->next();
+2 -2
View File
@@ -32,13 +32,13 @@ class Twig_TokenParserBroker implements Twig_TokenParserBrokerInterface
{
foreach ($parsers as $parser) {
if (!$parser instanceof Twig_TokenParserInterface) {
throw new Twig_Error('$parsers must a an array of Twig_TokenParserInterface');
throw new LogicException('$parsers must a an array of Twig_TokenParserInterface');
}
$this->parsers[$parser->getTag()] = $parser;
}
foreach ($brokers as $broker) {
if (!$broker instanceof Twig_TokenParserBrokerInterface) {
throw new Twig_Error('$brokers must a an array of Twig_TokenParserBrokerInterface');
throw new LogicException('$brokers must a an array of Twig_TokenParserBrokerInterface');
}
$this->brokers[] = $broker;
}
+2 -2
View File
@@ -58,7 +58,7 @@ class Twig_TokenStream
public function next()
{
if (!isset($this->tokens[++$this->current])) {
throw new Twig_Error_Syntax('Unexpected end of template', -1, $this->filename);
throw new Twig_Error_Syntax('Unexpected end of template', $this->token[$this->current - 1]->getLine(), $this->filename);
}
return $this->tokens[$this->current - 1];
@@ -97,7 +97,7 @@ class Twig_TokenStream
public function look($number = 1)
{
if (!isset($this->tokens[$this->current + $number])) {
throw new Twig_Error_Syntax('Unexpected end of template', -1, $this->filename);
throw new Twig_Error_Syntax('Unexpected end of template', $this->token[$this->current + $number - 1]->getLine(), $this->filename);
}
return $this->tokens[$this->current + $number];
@@ -0,0 +1,8 @@
--TEST--
"include" tag
--TEMPLATE--
{% include "foo.twig" %}
--DATA--
return array();
--EXCEPTION--
Twig_Error_Loader: Template "foo.twig" is not defined in "index.twig" at line 2.
@@ -0,0 +1,16 @@
--TEST--
"include" tag
--TEMPLATE--
{% extends "base.twig" %}
{% block content %}
{{ parent() }}
{% endblock %}
--TEMPLATE(base.twig)--
{% block content %}
{% include "foo.twig" %}
{% endblock %}
--DATA--
return array();
--EXCEPTION--
Twig_Error_Loader: Template "foo.twig" is not defined in "base.twig" at line 3.
+5 -5
View File
@@ -15,7 +15,7 @@ class Twig_Tests_ParserTest extends PHPUnit_Framework_TestCase
*/
public function testSetMacroThrowsExceptionOnReservedMethods()
{
$parser = new Twig_Parser(new Twig_Environment());
$parser = $this->getParser();
$parser->setMacro('display', $this->getMock('Twig_Node_Macro', array(), array(), '', null));
}
@@ -40,7 +40,7 @@ class Twig_Tests_ParserTest extends PHPUnit_Framework_TestCase
*/
public function testFilterBodyNodes($input, $expected)
{
$parser = $this->getParserForFilterBodyNodes();
$parser = $this->getParser();
$this->assertEquals($expected, $parser->filterBodyNodes($input));
}
@@ -69,7 +69,7 @@ class Twig_Tests_ParserTest extends PHPUnit_Framework_TestCase
*/
public function testFilterBodyNodesThrowsException($input)
{
$parser = $this->getParserForFilterBodyNodes();
$parser = $this->getParser();
$parser->filterBodyNodes($input);
}
@@ -88,7 +88,7 @@ class Twig_Tests_ParserTest extends PHPUnit_Framework_TestCase
*/
public function testFilterBodyNodesWithBOM()
{
$parser = $this->getParserForFilterBodyNodes();
$parser = $this->getParser();
$parser->filterBodyNodes(new Twig_Node_Text(chr(0xEF).chr(0xBB).chr(0xBF), 1));
}
@@ -135,7 +135,7 @@ EOF
));
}
protected function getParserForFilterBodyNodes()
protected function getParser()
{
$parser = new TestParser(new Twig_Environment());
$parser->setParent(new Twig_Node());