* 1.7.0 (2012-XX-XX)
- * n/a
+ * made the strategy used to guess the real template file name and line number in exception messages much faster and more accurate
* 1.6.2 (2012-03-18)
protected $source;
protected $indentation;
protected $env;
+ protected $debugInfo;
/**
* Constructor.
public function __construct(Twig_Environment $env)
{
$this->env = $env;
+ $this->debugInfo = array();
}
/**
public function addDebugInfo(Twig_NodeInterface $node)
{
if ($node->getLine() != $this->lastLine) {
+ $this->debugInfo[substr_count($this->source, "\n")] = $node->getLine();
+
$this->lastLine = $node->getLine();
$this->write("// line {$node->getLine()}\n");
}
return $this;
}
+ public function getDebugInfo()
+ {
+ return $this->debugInfo;
+ }
+
/**
* Indents the generated code.
*
public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null)
{
if (-1 === $lineno || null === $filename) {
- list($lineno, $filename) = $this->findTemplateInfo(null !== $previous ? $previous : $this, $lineno, $filename);
+ if ($trace = $this->getTemplateTrace()) {
+ if (-1 === $lineno) {
+ $lineno = $this->guessTemplateLine($trace);
+ }
+
+ if (null === $filename) {
+ $filename = $trace['object']->getTemplateName();
+ }
+ }
}
$this->lineno = $lineno;
}
}
- protected function findTemplateInfo(Exception $e, $currentLine, $currentFile)
+ protected function getTemplateTrace()
{
- if (!function_exists('token_get_all')) {
- return array($currentLine, $currentFile);
- }
-
- $traces = $e->getTrace();
- foreach ($traces as $i => $trace) {
- if (!isset($trace['class']) || 'Twig_Template' === $trace['class']) {
- continue;
- }
-
- $r = new ReflectionClass($trace['class']);
- if (!$r->implementsInterface('Twig_TemplateInterface')) {
- continue;
- }
-
- if (!is_file($r->getFilename())) {
- // probably an eval()'d code
- return array($currentLine, $currentFile);
+ foreach (debug_backtrace() as $trace) {
+ if (isset($trace['object']) && $trace['object'] instanceof Twig_Template) {
+ return $trace;
}
+ }
+ }
- if (0 === $i) {
- $line = $e->getLine();
- } else {
- $line = isset($traces[$i - 1]['line']) ? $traces[$i - 1]['line'] : -log(0);
- }
-
- $tokens = token_get_all(file_get_contents($r->getFilename()));
- $templateline = -1;
- $template = null;
- foreach ($tokens as $token) {
- if (isset($token[2]) && $token[2] >= $line) {
- return array($templateline, $template);
- }
-
- if (T_COMMENT === $token[0] && null === $template && preg_match('#/\* +(.+) +\*/#', $token[1], $match)) {
- $template = $match[1];
- } elseif (T_COMMENT === $token[0] && preg_match('#^//\s*line (\d+)\s*$#', $token[1], $match)) {
- $templateline = $match[1];
- }
+ protected function guessTemplateLine($trace)
+ {
+ foreach ($trace['object']->getDebugInfo() as $codeLine => $templateLine) {
+ if ($codeLine <= $trace['line']) {
+ return $templateLine;
}
-
- return array($currentLine, $template);
}
- return array($currentLine, $currentFile);
+ return -1;
}
}
$this->compileIsTraitable($compiler);
+ $this->compileDebugInfo($compiler);
+
$this->compileClassFooter($compiler);
}
->indent()
->write(sprintf("return %s;\n", $traitable ? 'true' : 'false'))
->outdent()
+ ->write("}\n\n")
+ ;
+ }
+
+ public function compileDebugInfo(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->write("public function getDebugInfo()\n", "{\n")
+ ->indent()
+ ->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true))))
+ ->outdent()
->write("}\n")
;
}
<?php
-require_once dirname(__FILE__).'/TestCase.php';
-
/*
* This file is part of Twig.
*
* file that was distributed with this source code.
*/
-class Twig_Tests_ErrorTest extends Twig_Tests_TestCase
+class Twig_Tests_ErrorTest extends PHPUnit_Framework_TestCase
{
public function testTwigExceptionAddsFileAndLineWhenMissing()
{
$loader = new Twig_Loader_Array(array('index' => "\n\n{{ foo.bar }}"));
- $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => $this->getTempDir()));
+ $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false));
$template = $twig->loadTemplate('index');
public function testRenderWrapsExceptions()
{
$loader = new Twig_Loader_Array(array('index' => "\n\n\n{{ foo.bar }}"));
- $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => $this->getTempDir()));
+ $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false));
$template = $twig->loadTemplate('index');
$this->assertEquals('index', $e->getTemplateFile());
}
}
+
+ public function testTwigExceptionAddsFileAndLineWhenMissingWithInheritance()
+ {
+ $loader = new Twig_Loader_Array(array(
+ 'index' => "{% extends 'base' %}
+ {% block content %}
+ {{ foo.bar }}
+ {% endblock %}",
+ 'base' => '{% block content %}{% endblock %}'
+ ));
+ $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false));
+
+ $template = $twig->loadTemplate('index');
+
+ try {
+ $template->render(array());
+
+ $this->fail();
+ } catch (Twig_Error_Runtime $e) {
+ $this->assertEquals('Variable "foo" does not exist in "index" at line 3', $e->getMessage());
+ $this->assertEquals(3, $e->getTemplateLine());
+ $this->assertEquals('index', $e->getTemplateFile());
+ }
+ }
}
class Twig_Tests_ErrorTest_Foo
/**
* @expectedException Twig_Sandbox_SecurityError
- * @expectedExceptionMessage Filter "json_encode" is not allowed.
+ * @expectedExceptionMessage Filter "json_encode" is not allowed in "1_child".
*/
public function testSandboxWithInheritance()
{
return "foo.twig";
}
+ public function getDebugInfo()
+ {
+ return array ();
+ }
}
EOF
, $twig);
{
return false;
}
+
+ public function getDebugInfo()
+ {
+ return array ();
+ }
}
EOF
, $twig);
{
return false;
}
+
+ public function getDebugInfo()
+ {
+ return array ();
+ }
}
EOF
, $twig);
return "foo.twig";
}
+ public function getDebugInfo()
+ {
+ return array ();
+ }
}
EOF
, $twig);
{
return false;
}
+
+ public function getDebugInfo()
+ {
+ return array ();
+ }
}
EOF
, $twig);
{
}
+ public function getDebugInfo()
+ {
+ return array();
+ }
+
protected function doGetParent(array $context)
{
}