diff --git a/CHANGELOG b/CHANGELOG index a26f887ae..cdd48c62b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ * 1.29.0 (2016-XX-XX) + * made Twig_Error report real source path when possible * added support for {{ _self }} to provide an upgrade path from 1.x to 2.0 (replaces {{ _self.templateName }}) * deprecated silent display of undefined blocks * deprecated support for mbstring.func_overload != 0 diff --git a/lib/Twig/Environment.php b/lib/Twig/Environment.php index 0544de1e2..461ba050c 100644 --- a/lib/Twig/Environment.php +++ b/lib/Twig/Environment.php @@ -741,10 +741,10 @@ class Twig_Environment try { return $this->compile($this->parse($this->tokenize($source))); } catch (Twig_Error $e) { - $e->setTemplateName($source->getName()); + $e->setSourceContext($source); throw $e; } catch (Exception $e) { - throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source->getName(), $e); + throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e); } } diff --git a/lib/Twig/Error.php b/lib/Twig/Error.php index cf373e69b..28db05cb7 100644 --- a/lib/Twig/Error.php +++ b/lib/Twig/Error.php @@ -39,6 +39,9 @@ class Twig_Error extends Exception protected $rawMessage; protected $previous; + private $sourcePath; + private $sourceCode; + /** * Constructor. * @@ -51,13 +54,23 @@ class Twig_Error extends Exception * * By default, automatic guessing is enabled. * - * @param string $message The error message - * @param int $lineno The template line where the error occurred - * @param string $name The template logical name where the error occurred - * @param Exception $previous The previous exception + * @param string $message The error message + * @param int $lineno The template line where the error occurred + * @param Twig_Source|string|null $source The source context where the error occurred + * @param Exception $previous The previous exception */ - public function __construct($message, $lineno = -1, $name = null, Exception $previous = null) + public function __construct($message, $lineno = -1, $source = null, Exception $previous = null) { + if (null === $source) { + $name = null; + } elseif (!$source instanceof Twig_Source) { + // for compat with the Twig C ext., passing the template name as string is accepted + $name = $source; + } else { + $name = $source->getName(); + $this->sourceCode = $source->getCode(); + $this->sourcePath = $source->getPath(); + } if (PHP_VERSION_ID < 50300) { $this->previous = $previous; parent::__construct(''); @@ -68,7 +81,7 @@ class Twig_Error extends Exception $this->lineno = $lineno; $this->filename = $name; - if (-1 === $lineno || null === $name) { + if (-1 === $lineno || null === $name || null === $this->sourcePath) { $this->guessTemplateInfo(); } @@ -92,11 +105,11 @@ class Twig_Error extends Exception * * @return string The name * - * @deprecated since 1.27 (to be removed in 2.0). Use getTemplateName() instead. + * @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead. */ public function getTemplateFile() { - @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use getTemplateName() instead.', __METHOD__), E_USER_DEPRECATED); + @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); return $this->filename; } @@ -106,11 +119,11 @@ class Twig_Error extends Exception * * @param string $name The name * - * @deprecated since 1.27 (to be removed in 2.0). Use setTemplateName() instead. + * @deprecated since 1.27 (to be removed in 2.0). Use setSourceContext() instead. */ public function setTemplateFile($name) { - @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use setTemplateName() instead.', __METHOD__), E_USER_DEPRECATED); + @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use setSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); $this->filename = $name; @@ -121,9 +134,13 @@ class Twig_Error extends Exception * Gets the logical name where the error occurred. * * @return string The name + * + * @deprecated since 1.29 (to be removed in 2.0). Use getSourceContext() instead. */ public function getTemplateName() { + @trigger_error(sprintf('The "%s" method is deprecated since version 1.29 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + return $this->filename; } @@ -131,10 +148,15 @@ class Twig_Error extends Exception * Sets the logical name where the error occurred. * * @param string $name The name + * + * @deprecated since 1.29 (to be removed in 2.0). Use setSourceContext() instead. */ public function setTemplateName($name) { + @trigger_error(sprintf('The "%s" method is deprecated since version 1.29 and will be removed in 2.0. Use setSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + $this->filename = $name; + $this->sourceCode = $this->sourcePath = null; $this->updateRepr(); } @@ -161,6 +183,32 @@ class Twig_Error extends Exception $this->updateRepr(); } + /** + * Gets the source context of the Twig template where the error occurred. + * + * @return Twig_Source|null + */ + public function getSourceContext() + { + return $this->filename ? new Twig_Source($this->sourceCode, $this->filename, $this->sourcePath) : null; + } + + /** + * Sets the source context of the Twig template where the error occurred. + */ + public function setSourceContext(Twig_Source $source = null) + { + if (null === $source) { + $this->sourceCode = $this->filename = $this->sourcePath = null; + } else { + $this->sourceCode = $source->getCode(); + $this->filename = $source->getName(); + $this->sourcePath = $source->getPath(); + } + + $this->updateRepr(); + } + public function guess() { $this->guessTemplateInfo(); @@ -199,6 +247,13 @@ class Twig_Error extends Exception { $this->message = $this->rawMessage; + if ($this->sourcePath && $this->lineno > 0) { + $this->file = $this->sourcePath; + $this->line = $this->lineno; + + return; + } + $dot = false; if ('.' === substr($this->message, -1)) { $this->message = substr($this->message, 0, -1); @@ -263,6 +318,13 @@ class Twig_Error extends Exception $this->filename = $template->getTemplateName(); } + // update template path if any + if (null !== $template && null === $this->sourcePath) { + $src = $template->getSourceContext(); + $this->sourceCode = $src->getCode(); + $this->sourcePath = $src->getPath(); + } + if (null === $template || $this->lineno > -1) { return; } diff --git a/lib/Twig/Error/Loader.php b/lib/Twig/Error/Loader.php index 68efb574a..4010ba38d 100644 --- a/lib/Twig/Error/Loader.php +++ b/lib/Twig/Error/Loader.php @@ -24,8 +24,18 @@ */ class Twig_Error_Loader extends Twig_Error { - public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null) + private $guess = false; + + public function __construct($message, $lineno = -1, $source = null, Exception $previous = null) { - parent::__construct($message, false, false, $previous); + parent::__construct($message, false, null, $previous); + $this->guess = true; + } + + protected function guessTemplateInfo() + { + if ($this->guess) { + parent::guessTemplateInfo(); + } } } diff --git a/lib/Twig/ExpressionParser.php b/lib/Twig/ExpressionParser.php index de2e56a3b..a683fb35c 100644 --- a/lib/Twig/ExpressionParser.php +++ b/lib/Twig/ExpressionParser.php @@ -189,7 +189,7 @@ class Twig_ExpressionParser $negClass = 'Twig_Node_Expression_Unary_Neg'; $posClass = 'Twig_Node_Expression_Unary_Pos'; if (!(in_array($ref->getName(), array($negClass, $posClass)) || $ref->isSubclassOf($negClass) || $ref->isSubclassOf($posClass))) { - throw new Twig_Error_Syntax(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()->getName()); + throw new Twig_Error_Syntax(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); } $this->parser->getStream()->next(); @@ -205,7 +205,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->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()->getName()); + throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s".', Twig_Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); } } @@ -296,7 +296,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->getValue()), $current->getLine(), $stream->getSourceContext()->getName()); + 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->getValue()), $current->getLine(), $stream->getSourceContext()); } $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)'); @@ -335,25 +335,25 @@ class Twig_ExpressionParser case 'parent': $this->parseArguments(); if (!count($this->parser->getBlockStack())) { - throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext()->getName()); + throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext()); } 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, $this->parser->getStream()->getSourceContext()->getName()); + throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext()); } return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line); case 'block': $args = $this->parseArguments(); if (count($args) < 1) { - throw new Twig_Error_Syntax('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext()->getName()); + throw new Twig_Error_Syntax('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext()); } return new Twig_Node_Expression_BlockReference($args->getNode(0), count($args) > 1 ? $args->getNode(1) : null, $line); case 'attribute': $args = $this->parseArguments(); if (count($args) < 2) { - throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext()->getName()); + throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext()); } return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : null, Twig_Template::ANY_CALL, $line); @@ -402,18 +402,18 @@ class Twig_ExpressionParser } } } else { - throw new Twig_Error_Syntax('Expected name or number.', $lineno, $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax('Expected name or number.', $lineno, $stream->getSourceContext()); } if ($node instanceof Twig_Node_Expression_Name && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) { if (!$arg instanceof Twig_Node_Expression_Constant) { - throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext()); } $name = $arg->getAttribute('value'); if ($this->parser->isReservedMacroName($name)) { - throw new Twig_Error_Syntax(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext()); } $node = new Twig_Node_Expression_MethodCall($node, 'get'.$name, $arguments, $lineno); @@ -523,7 +523,7 @@ class Twig_ExpressionParser $name = null; if ($namedArguments && $token = $stream->nextIf(Twig_Token::OPERATOR_TYPE, '=')) { if (!$value instanceof Twig_Node_Expression_Name) { - throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given.', get_class($value)), $token->getLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given.', get_class($value)), $token->getLine(), $stream->getSourceContext()); } $name = $value->getAttribute('name'); @@ -531,7 +531,7 @@ class Twig_ExpressionParser $value = $this->parsePrimaryExpression(); if (!$this->checkConstantExpression($value)) { - throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getSourceContext()); } } else { $value = $this->parseExpression(); @@ -565,7 +565,7 @@ class Twig_ExpressionParser $token = $stream->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to'); $value = $token->getValue(); if (in_array(strtolower($value), array('true', 'false', 'none', 'null'))) { - throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext()); } $targets[] = new Twig_Node_Expression_AssignName($value, $token->getLine()); @@ -629,7 +629,7 @@ class Twig_ExpressionParser } } - $e = new Twig_Error_Syntax(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext()->getName()); + $e = new Twig_Error_Syntax(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext()); $e->addSuggestions($name, array_keys($this->env->getTests())); throw $e; @@ -646,7 +646,8 @@ class Twig_ExpressionParser if ($test->getAlternative()) { $message .= sprintf('. Use "%s" instead', $test->getAlternative()); } - $message .= sprintf(' in %s at line %d.', $stream->getSourceContext()->getName(), $stream->getCurrent()->getLine()); + $src = $stream->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $stream->getCurrent()->getLine()); @trigger_error($message, E_USER_DEPRECATED); } @@ -661,7 +662,7 @@ class Twig_ExpressionParser protected function getFunctionNodeClass($name, $line) { if (false === $function = $this->env->getFunction($name)) { - $e = new Twig_Error_Syntax(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()->getName()); + $e = new Twig_Error_Syntax(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()); $e->addSuggestions($name, array_keys($this->env->getFunctions())); throw $e; @@ -675,7 +676,8 @@ class Twig_ExpressionParser if ($function->getAlternative()) { $message .= sprintf('. Use "%s" instead', $function->getAlternative()); } - $message .= sprintf(' in %s at line %d.', $this->parser->getStream()->getSourceContext()->getName(), $line); + $src = $this->parser->getStream()->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $line); @trigger_error($message, E_USER_DEPRECATED); } @@ -690,7 +692,7 @@ class Twig_ExpressionParser protected function getFilterNodeClass($name, $line) { if (false === $filter = $this->env->getFilter($name)) { - $e = new Twig_Error_Syntax(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()->getName()); + $e = new Twig_Error_Syntax(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()); $e->addSuggestions($name, array_keys($this->env->getFilters())); throw $e; @@ -704,7 +706,8 @@ class Twig_ExpressionParser if ($filter->getAlternative()) { $message .= sprintf('. Use "%s" instead', $filter->getAlternative()); } - $message .= sprintf(' in %s at line %d.', $this->parser->getStream()->getSourceContext()->getName(), $line); + $src = $this->parser->getStream()->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $line); @trigger_error($message, E_USER_DEPRECATED); } diff --git a/lib/Twig/Lexer.php b/lib/Twig/Lexer.php index 97b4a5c68..295379aee 100644 --- a/lib/Twig/Lexer.php +++ b/lib/Twig/Lexer.php @@ -34,6 +34,8 @@ class Twig_Lexer implements Twig_LexerInterface protected $positions; protected $currentVarBlockLine; + private $source; + const STATE_DATA = 0; const STATE_BLOCK = 1; const STATE_VAR = 2; @@ -80,9 +82,9 @@ class Twig_Lexer implements Twig_LexerInterface { if (!$code instanceof Twig_Source) { @trigger_error(sprintf('Passing a string as the $code argument of %s() is deprecated since version 1.27 and will be removed in 2.0. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED); - $source = new Twig_Source($code, $name); + $this->source = new Twig_Source($code, $name); } else { - $source = $code; + $this->source = $code; } if (((int) ini_get('mbstring.func_overload')) & 2) { @@ -96,8 +98,8 @@ class Twig_Lexer implements Twig_LexerInterface $mbEncoding = null; } - $this->code = str_replace(array("\r\n", "\r"), "\n", $source->getCode()); - $this->filename = $source->getName(); + $this->code = str_replace(array("\r\n", "\r"), "\n", $this->source->getCode()); + $this->filename = $this->source->getName(); $this->cursor = 0; $this->lineno = 1; $this->end = strlen($this->code); @@ -141,14 +143,14 @@ class Twig_Lexer implements Twig_LexerInterface if (!empty($this->brackets)) { list($expect, $lineno) = array_pop($this->brackets); - throw new Twig_Error_Syntax(sprintf('Unclosed "%s".', $expect), $lineno, $this->filename); + throw new Twig_Error_Syntax(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); } if ($mbEncoding) { mb_internal_encoding($mbEncoding); } - return new Twig_TokenStream($this->tokens, $source); + return new Twig_TokenStream($this->tokens, $this->source); } protected function lexData() @@ -236,7 +238,7 @@ class Twig_Lexer implements Twig_LexerInterface $this->moveCursor($match[0]); if ($this->cursor >= $this->end) { - throw new Twig_Error_Syntax(sprintf('Unclosed "%s".', $this->state === self::STATE_BLOCK ? 'block' : 'variable'), $this->currentVarBlockLine, $this->filename); + throw new Twig_Error_Syntax(sprintf('Unclosed "%s".', $this->state === self::STATE_BLOCK ? 'block' : 'variable'), $this->currentVarBlockLine, $this->source); } } @@ -268,12 +270,12 @@ class Twig_Lexer implements Twig_LexerInterface // closing bracket elseif (false !== strpos(')]}', $this->code[$this->cursor])) { if (empty($this->brackets)) { - throw new Twig_Error_Syntax(sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->filename); + throw new Twig_Error_Syntax(sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); } list($expect, $lineno) = array_pop($this->brackets); if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) { - throw new Twig_Error_Syntax(sprintf('Unclosed "%s".', $expect), $lineno, $this->filename); + throw new Twig_Error_Syntax(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); } } @@ -293,7 +295,7 @@ class Twig_Lexer implements Twig_LexerInterface } // unlexable else { - throw new Twig_Error_Syntax(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->filename); + throw new Twig_Error_Syntax(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); } } @@ -304,7 +306,7 @@ class Twig_Lexer implements Twig_LexerInterface } if (!preg_match(str_replace('%s', $tag, $this->regexes['lex_raw_data']), $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { - throw new Twig_Error_Syntax(sprintf('Unexpected end of file: Unclosed "%s" block.', $tag), $this->lineno, $this->filename); + throw new Twig_Error_Syntax(sprintf('Unexpected end of file: Unclosed "%s" block.', $tag), $this->lineno, $this->source); } $text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor); @@ -320,7 +322,7 @@ class Twig_Lexer implements Twig_LexerInterface protected function lexComment() { if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { - throw new Twig_Error_Syntax('Unclosed comment.', $this->lineno, $this->filename); + throw new Twig_Error_Syntax('Unclosed comment.', $this->lineno, $this->source); } $this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]); @@ -339,7 +341,7 @@ class Twig_Lexer implements Twig_LexerInterface } elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, null, $this->cursor)) { list($expect, $lineno) = array_pop($this->brackets); if ($this->code[$this->cursor] != '"') { - throw new Twig_Error_Syntax(sprintf('Unclosed "%s".', $expect), $lineno, $this->filename); + throw new Twig_Error_Syntax(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); } $this->popState(); diff --git a/lib/Twig/Node/CheckSecurity.php b/lib/Twig/Node/CheckSecurity.php index 52db7d2bf..cdaaf2ead 100644 --- a/lib/Twig/Node/CheckSecurity.php +++ b/lib/Twig/Node/CheckSecurity.php @@ -56,7 +56,7 @@ class Twig_Node_CheckSecurity extends Twig_Node ->outdent() ->write("} catch (Twig_Sandbox_SecurityError \$e) {\n") ->indent() - ->write("\$e->setTemplateName(\$this->getTemplateName());\n\n") + ->write("\$e->setSourceContext(\$this->getSourceContext());\n\n") ->write("if (\$e instanceof Twig_Sandbox_SecurityNotAllowedTagError && isset(\$tags[\$e->getTagName()])) {\n") ->indent() ->write("\$e->setTemplateLine(\$tags[\$e->getTagName()]);\n") diff --git a/lib/Twig/Parser.php b/lib/Twig/Parser.php index d4e456077..53ae38f8d 100644 --- a/lib/Twig/Parser.php +++ b/lib/Twig/Parser.php @@ -109,8 +109,8 @@ class Twig_Parser implements Twig_ParserInterface $body = new Twig_Node(); } } catch (Twig_Error_Syntax $e) { - if (!$e->getTemplateName()) { - $e->setTemplateName($this->stream->getSourceContext()->getName()); + if (!$e->getSourceContext()) { + $e->setSourceContext($this->stream->getSourceContext()); } if (!$e->getTemplateLine()) { @@ -157,7 +157,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->getSourceContext()->getName()); + throw new Twig_Error_Syntax('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext()); } if (null !== $test && call_user_func($test, $token)) { @@ -175,13 +175,13 @@ class Twig_Parser implements Twig_ParserInterface $subparser = $this->handlers->getTokenParser($token->getValue()); if (null === $subparser) { if (null !== $test) { - $e = new Twig_Error_Syntax(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()->getName()); + $e = new Twig_Error_Syntax(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); if (is_array($test) && isset($test[0]) && $test[0] instanceof Twig_TokenParserInterface) { $e->appendMessage(sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $test[0]->getTag(), $lineno)); } } else { - $e = new Twig_Error_Syntax(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()->getName()); + $e = new Twig_Error_Syntax(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); $e->addSuggestions($token->getValue(), array_keys($this->env->getTags())); } @@ -197,7 +197,7 @@ class Twig_Parser implements Twig_ParserInterface break; default: - throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', 0, $this->stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', $this->getCurrentToken()->getLine(), $this->stream->getSourceContext()); } } @@ -271,7 +271,7 @@ class Twig_Parser implements Twig_ParserInterface public function setMacro($name, Twig_Node_Macro $node) { if ($this->isReservedMacroName($name)) { - throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword.', $name), $node->getTemplateLine(), $this->stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword.', $name), $node->getTemplateLine(), $this->stream->getSourceContext()); } $this->macros[$name] = $node; @@ -383,10 +383,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 start with a byte order mark (BOM); it must be removed.', $node->getTemplateLine(), $this->stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax('A template that extends another one cannot start with a byte order mark (BOM); it must be removed.', $node->getTemplateLine(), $this->stream->getSourceContext()); } - throw new Twig_Error_Syntax('A template that extends another one cannot include contents outside Twig blocks. Did you forget to put the contents inside a {% block %} tag?', $node->getTemplateLine(), $this->stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax('A template that extends another one cannot include contents outside Twig blocks. Did you forget to put the contents inside a {% block %} tag?', $node->getTemplateLine(), $this->stream->getSourceContext()); } // bypass "set" nodes as they "capture" the output diff --git a/lib/Twig/Template.php b/lib/Twig/Template.php index 90404cd3b..c09ab7d73 100644 --- a/lib/Twig/Template.php +++ b/lib/Twig/Template.php @@ -133,7 +133,7 @@ abstract class Twig_Template implements Twig_TemplateInterface $this->parents[$parent] = $this->loadTemplate($parent); } } catch (Twig_Error_Loader $e) { - $e->setTemplateName(null); + $e->setSourceContext(null); $e->guess(); throw $e; @@ -173,7 +173,7 @@ abstract class Twig_Template implements Twig_TemplateInterface } elseif (false !== $parent = $this->getParent($context)) { $parent->displayBlock($name, $context, $blocks, false); } else { - throw new Twig_Error_Runtime(sprintf('The template has no parent and no traits defining the "%s" block.', $name), -1, $this->getTemplateName()); + throw new Twig_Error_Runtime(sprintf('The template has no parent and no traits defining the "%s" block.', $name), -1, $this->getSourceContext()); } } @@ -214,8 +214,8 @@ abstract class Twig_Template implements Twig_TemplateInterface try { $template->$block($context, $blocks); } catch (Twig_Error $e) { - if (!$e->getTemplateName()) { - $e->setTemplateName($template->getTemplateName()); + if (!$e->getSourceContext()) { + $e->setSourceContext($template->getSourceContext()); } // this is mostly useful for Twig_Error_Loader exceptions @@ -227,7 +227,7 @@ abstract class Twig_Template implements Twig_TemplateInterface 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, $template->getTemplateName(), $e); + throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e); } } elseif (false !== $parent = $this->getParent($context)) { $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks), false); @@ -365,8 +365,8 @@ abstract class Twig_Template implements Twig_TemplateInterface return $this->env->loadTemplate($template, $index); } catch (Twig_Error $e) { - if (!$e->getTemplateName()) { - $e->setTemplateName($templateName ? $templateName : $this->getTemplateName()); + if (!$e->getSourceContext()) { + $e->setSourceContext($templateName ? new Twig_Source('', $templateName) : $this->getSourceContext()); } if ($e->getTemplateLine()) { @@ -437,8 +437,8 @@ abstract class Twig_Template implements Twig_TemplateInterface try { $this->doDisplay($context, $blocks); } catch (Twig_Error $e) { - if (!$e->getTemplateName()) { - $e->setTemplateName($this->getTemplateName()); + if (!$e->getSourceContext()) { + $e->setSourceContext($this->getSourceContext()); } // this is mostly useful for Twig_Error_Loader exceptions @@ -450,7 +450,7 @@ abstract class Twig_Template implements Twig_TemplateInterface 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, $this->getTemplateName(), $e); + throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e); } } @@ -490,7 +490,7 @@ abstract class Twig_Template implements Twig_TemplateInterface return; } - throw new Twig_Error_Runtime(sprintf('Variable "%s" does not exist.', $item), -1, $this->getTemplateName()); + throw new Twig_Error_Runtime(sprintf('Variable "%s" does not exist.', $item), -1, $this->getSourceContext()); } return $context[$item]; @@ -559,7 +559,7 @@ abstract class Twig_Template implements Twig_TemplateInterface $message = sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").', $item, gettype($object), $object); } - throw new Twig_Error_Runtime($message, -1, $this->getTemplateName()); + throw new Twig_Error_Runtime($message, -1, $this->getSourceContext()); } } @@ -578,7 +578,7 @@ abstract class Twig_Template implements Twig_TemplateInterface $message = sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, gettype($object), $object); } - throw new Twig_Error_Runtime($message, -1, $this->getTemplateName()); + throw new Twig_Error_Runtime($message, -1, $this->getSourceContext()); } // object property @@ -660,7 +660,7 @@ abstract class Twig_Template implements Twig_TemplateInterface return; } - throw new Twig_Error_Runtime(sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), -1, $this->getTemplateName()); + throw new Twig_Error_Runtime(sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), -1, $this->getSourceContext()); } if ($isDefinedTest) { diff --git a/lib/Twig/TokenParser/AutoEscape.php b/lib/Twig/TokenParser/AutoEscape.php index 044fad70a..b8c590d9e 100644 --- a/lib/Twig/TokenParser/AutoEscape.php +++ b/lib/Twig/TokenParser/AutoEscape.php @@ -39,7 +39,7 @@ class Twig_TokenParser_AutoEscape extends Twig_TokenParser } 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 bool.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax('An escaping strategy must be a string or a bool.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); } $value = $expr->getAttribute('value'); @@ -53,7 +53,7 @@ class Twig_TokenParser_AutoEscape extends Twig_TokenParser @trigger_error('Using the autoescape tag with "true" or "false" before the strategy name is deprecated since version 1.21.', E_USER_DEPRECATED); if (false === $value) { - throw new Twig_Error_Syntax('Unexpected escaping strategy as you set autoescaping to false.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax('Unexpected escaping strategy as you set autoescaping to false.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); } $value = $stream->next()->getValue(); diff --git a/lib/Twig/TokenParser/Block.php b/lib/Twig/TokenParser/Block.php index a2df92893..bae9a4460 100644 --- a/lib/Twig/TokenParser/Block.php +++ b/lib/Twig/TokenParser/Block.php @@ -28,7 +28,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 '%s' has already been defined line %d.", $name, $this->parser->getBlock($name)->getTemplateLine()), $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax(sprintf("The block '%s' has already been defined line %d.", $name, $this->parser->getBlock($name)->getTemplateLine()), $stream->getCurrent()->getLine(), $stream->getSourceContext()); } $this->parser->setBlock($name, $block = new Twig_Node_Block($name, new Twig_Node(array()), $lineno)); $this->parser->pushLocalScope(); @@ -40,7 +40,7 @@ class Twig_TokenParser_Block extends Twig_TokenParser $value = $token->getValue(); if ($value != $name) { - throw new Twig_Error_Syntax(sprintf('Expected endblock for block "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax(sprintf('Expected endblock for block "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); } } } else { diff --git a/lib/Twig/TokenParser/Extends.php b/lib/Twig/TokenParser/Extends.php index 2918cf5a3..940ea1bbb 100644 --- a/lib/Twig/TokenParser/Extends.php +++ b/lib/Twig/TokenParser/Extends.php @@ -24,11 +24,11 @@ class Twig_TokenParser_Extends extends Twig_TokenParser $stream = $this->parser->getStream(); if (!$this->parser->isMainScope()) { - throw new Twig_Error_Syntax('Cannot extend from a block.', $token->getLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax('Cannot extend from a block.', $token->getLine(), $stream->getSourceContext()); } if (null !== $this->parser->getParent()) { - throw new Twig_Error_Syntax('Multiple extends tags are forbidden.', $token->getLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax('Multiple extends tags are forbidden.', $token->getLine(), $stream->getSourceContext()); } $this->parser->setParent($this->parser->getExpressionParser()->parseExpression()); diff --git a/lib/Twig/TokenParser/For.php b/lib/Twig/TokenParser/For.php index 90553a161..3ce9401d5 100644 --- a/lib/Twig/TokenParser/For.php +++ b/lib/Twig/TokenParser/For.php @@ -79,7 +79,7 @@ class Twig_TokenParser_For extends Twig_TokenParser protected function checkLoopUsageCondition(Twig_TokenStream $stream, Twig_NodeInterface $node) { if ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name && 'loop' == $node->getNode('node')->getAttribute('name')) { - throw new Twig_Error_Syntax('The "loop" variable cannot be used in a looping condition.', $node->getTemplateLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax('The "loop" variable cannot be used in a looping condition.', $node->getTemplateLine(), $stream->getSourceContext()); } foreach ($node as $n) { @@ -98,7 +98,7 @@ class Twig_TokenParser_For extends Twig_TokenParser if ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name && 'loop' == $node->getNode('node')->getAttribute('name')) { $attribute = $node->getNode('attribute'); if ($attribute instanceof Twig_Node_Expression_Constant && in_array($attribute->getAttribute('value'), array('length', 'revindex0', 'revindex', 'last'))) { - throw new Twig_Error_Syntax(sprintf('The "loop.%s" variable is not defined when looping with a condition.', $attribute->getAttribute('value')), $node->getTemplateLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax(sprintf('The "loop.%s" variable is not defined when looping with a condition.', $attribute->getAttribute('value')), $node->getTemplateLine(), $stream->getSourceContext()); } } diff --git a/lib/Twig/TokenParser/From.php b/lib/Twig/TokenParser/From.php index 77a6eedb3..a8f53b321 100644 --- a/lib/Twig/TokenParser/From.php +++ b/lib/Twig/TokenParser/From.php @@ -46,7 +46,7 @@ class Twig_TokenParser_From extends Twig_TokenParser foreach ($targets as $name => $alias) { if ($this->parser->isReservedMacroName($name)) { - throw new Twig_Error_Syntax(sprintf('"%s" cannot be an imported macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax(sprintf('"%s" cannot be an imported macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext()); } $this->parser->addImportedSymbol('function', $alias, 'get'.$name, $node->getNode('var')); diff --git a/lib/Twig/TokenParser/If.php b/lib/Twig/TokenParser/If.php index 8d8328ac9..479fc5698 100644 --- a/lib/Twig/TokenParser/If.php +++ b/lib/Twig/TokenParser/If.php @@ -56,7 +56,7 @@ 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), $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); + 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->getSourceContext()); } } diff --git a/lib/Twig/TokenParser/Macro.php b/lib/Twig/TokenParser/Macro.php index bc975ff37..1b2d3989a 100644 --- a/lib/Twig/TokenParser/Macro.php +++ b/lib/Twig/TokenParser/Macro.php @@ -35,7 +35,7 @@ class Twig_TokenParser_Macro extends Twig_TokenParser $value = $token->getValue(); if ($value != $name) { - throw new Twig_Error_Syntax(sprintf('Expected endmacro for macro "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax(sprintf('Expected endmacro for macro "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); } } $this->parser->popLocalScope(); diff --git a/lib/Twig/TokenParser/Sandbox.php b/lib/Twig/TokenParser/Sandbox.php index 4809d16a4..c50a17ffd 100644 --- a/lib/Twig/TokenParser/Sandbox.php +++ b/lib/Twig/TokenParser/Sandbox.php @@ -37,7 +37,7 @@ class Twig_TokenParser_Sandbox extends Twig_TokenParser } if (!$node instanceof Twig_Node_Include) { - throw new Twig_Error_Syntax('Only "include" tags are allowed within a "sandbox" section.', $node->getTemplateLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax('Only "include" tags are allowed within a "sandbox" section.', $node->getTemplateLine(), $stream->getSourceContext()); } } } diff --git a/lib/Twig/TokenParser/Set.php b/lib/Twig/TokenParser/Set.php index b6e966e4c..819371705 100644 --- a/lib/Twig/TokenParser/Set.php +++ b/lib/Twig/TokenParser/Set.php @@ -41,13 +41,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 assignments.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax('When using set, you must have the same number of variables and assignments.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); } } else { $capture = true; if (count($names) > 1) { - throw new Twig_Error_Syntax('When using set with a block, you cannot have a multi-target.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax('When using set with a block, you cannot have a multi-target.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); } $stream->expect(Twig_Token::BLOCK_END_TYPE); diff --git a/lib/Twig/TokenParser/Use.php b/lib/Twig/TokenParser/Use.php index 3129e8303..3c4c4b927 100644 --- a/lib/Twig/TokenParser/Use.php +++ b/lib/Twig/TokenParser/Use.php @@ -31,7 +31,7 @@ class Twig_TokenParser_Use extends Twig_TokenParser $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.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); + throw new Twig_Error_Syntax('The template references in a "use" statement must be a string.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); } $targets = array(); diff --git a/lib/Twig/TokenStream.php b/lib/Twig/TokenStream.php index 1b6a7b8bc..3835414a1 100644 --- a/lib/Twig/TokenStream.php +++ b/lib/Twig/TokenStream.php @@ -63,7 +63,7 @@ class Twig_TokenStream public function next() { if (!isset($this->tokens[++$this->current])) { - throw new Twig_Error_Syntax('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->source->getName()); + throw new Twig_Error_Syntax('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->source); } return $this->tokens[$this->current - 1]; @@ -96,7 +96,7 @@ class Twig_TokenStream Twig_Token::typeToEnglish($token->getType()), $token->getValue(), Twig_Token::typeToEnglish($type), $value ? sprintf(' with value "%s"', $value) : ''), $line, - $this->source->getName() + $this->source ); } $this->next(); @@ -114,7 +114,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.', $this->tokens[$this->current + $number - 1]->getLine(), $this->source->getName()); + throw new Twig_Error_Syntax('Unexpected end of template.', $this->tokens[$this->current + $number - 1]->getLine(), $this->source); } return $this->tokens[$this->current + $number]; diff --git a/test/Twig/Tests/ErrorTest.php b/test/Twig/Tests/ErrorTest.php index 8f47ba506..b0eeca2e6 100644 --- a/test/Twig/Tests/ErrorTest.php +++ b/test/Twig/Tests/ErrorTest.php @@ -14,7 +14,7 @@ class Twig_Tests_ErrorTest extends PHPUnit_Framework_TestCase public function testErrorWithObjectFilename() { $error = new Twig_Error('foo'); - $error->setTemplateName(new SplFileInfo(__FILE__)); + $error->setSourceContext(new Twig_Source('', new SplFileInfo(__FILE__))); $this->assertContains('test'.DIRECTORY_SEPARATOR.'Twig'.DIRECTORY_SEPARATOR.'Tests'.DIRECTORY_SEPARATOR.'ErrorTest.php', $error->getMessage()); } @@ -22,14 +22,25 @@ class Twig_Tests_ErrorTest extends PHPUnit_Framework_TestCase public function testErrorWithArrayFilename() { $error = new Twig_Error('foo'); - $error->setTemplateName(array('foo' => 'bar')); + $error->setSourceContext(new Twig_Source('', array('foo' => 'bar'))); $this->assertEquals('foo in {"foo":"bar"}', $error->getMessage()); } - public function testTwigExceptionAddsFileAndLineWhenMissingWithInheritanceOnDisk() + public function testTwigExceptionGuessWithMissingVarAndArrayLoader() { - $loader = new Twig_Loader_Filesystem(dirname(__FILE__).'/Fixtures/errors'); + $loader = new Twig_Loader_Array(array( + 'base.html' => '{% block content %}{% endblock %}', + 'index.html' => << true, 'debug' => true, 'cache' => false)); $template = $twig->loadTemplate('index.html'); @@ -40,9 +51,27 @@ class Twig_Tests_ErrorTest extends PHPUnit_Framework_TestCase } catch (Twig_Error_Runtime $e) { $this->assertEquals('Variable "foo" does not exist in "index.html" at line 3.', $e->getMessage()); $this->assertEquals(3, $e->getTemplateLine()); - $this->assertEquals('index.html', $e->getTemplateName()); + $this->assertEquals('index.html', $e->getSourceContext()->getName()); } + } + public function testTwigExceptionGuessWithExceptionAndArrayLoader() + { + $loader = new Twig_Loader_Array(array( + 'base.html' => '{% block content %}{% endblock %}', + 'index.html' => << true, 'debug' => true, 'cache' => false)); + + $template = $twig->loadTemplate('index.html'); try { $template->render(array('foo' => new Twig_Tests_ErrorTest_Foo())); @@ -50,7 +79,45 @@ class Twig_Tests_ErrorTest extends PHPUnit_Framework_TestCase } catch (Twig_Error_Runtime $e) { $this->assertEquals('An exception has been thrown during the rendering of a template ("Runtime error...") in "index.html" at line 3.', $e->getMessage()); $this->assertEquals(3, $e->getTemplateLine()); - $this->assertEquals('index.html', $e->getTemplateName()); + $this->assertEquals('index.html', $e->getSourceContext()->getName()); + } + } + + public function testTwigExceptionGuessWithMissingVarAndFilesystemLoader() + { + $loader = new Twig_Loader_Filesystem(dirname(__FILE__).'/Fixtures/errors'); + $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false)); + + $template = $twig->loadTemplate('index.html'); + try { + $template->render(array()); + + $this->fail(); + } catch (Twig_Error_Runtime $e) { + $this->assertEquals('Variable "foo" does not exist.', $e->getMessage()); + $this->assertEquals(3, $e->getTemplateLine()); + $this->assertEquals('index.html', $e->getSourceContext()->getName()); + $this->assertEquals(3, $e->getLine()); + $this->assertEquals(strtr(dirname(__FILE__).'/Fixtures/errors/index.html', '/', DIRECTORY_SEPARATOR), $e->getFile()); + } + } + + public function testTwigExceptionGuessWithExceptionAndFilesystemLoader() + { + $loader = new Twig_Loader_Filesystem(dirname(__FILE__).'/Fixtures/errors'); + $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false)); + + $template = $twig->loadTemplate('index.html'); + try { + $template->render(array('foo' => new Twig_Tests_ErrorTest_Foo())); + + $this->fail(); + } catch (Twig_Error_Runtime $e) { + $this->assertEquals('An exception has been thrown during the rendering of a template ("Runtime error...").', $e->getMessage()); + $this->assertEquals(3, $e->getTemplateLine()); + $this->assertEquals('index.html', $e->getSourceContext()->getName()); + $this->assertEquals(3, $e->getLine()); + $this->assertEquals(strtr(dirname(__FILE__).'/Fixtures/errors/index.html', '/', DIRECTORY_SEPARATOR), $e->getFile()); } } @@ -71,7 +138,7 @@ class Twig_Tests_ErrorTest extends PHPUnit_Framework_TestCase } catch (Twig_Error_Runtime $e) { $this->assertEquals(sprintf('Variable "foo" does not exist in "%s" at line %d.', $name, $line), $e->getMessage()); $this->assertEquals($line, $e->getTemplateLine()); - $this->assertEquals($name, $e->getTemplateName()); + $this->assertEquals($name, $e->getSourceContext()->getName()); } try { @@ -81,7 +148,7 @@ class Twig_Tests_ErrorTest extends PHPUnit_Framework_TestCase } catch (Twig_Error_Runtime $e) { $this->assertEquals(sprintf('An exception has been thrown during the rendering of a template ("Runtime error...") in "%s" at line %d.', $name, $line), $e->getMessage()); $this->assertEquals($line, $e->getTemplateLine()); - $this->assertEquals($name, $e->getTemplateName()); + $this->assertEquals($name, $e->getSourceContext()->getName()); } }