Browse code

made the strategy used to guess the real template file name and line number in exception messages much faster and more accurate (refs #647)

Fabien Potencier authored on 17/03/2012 17:12:19
Showing 9 changed files
... ...
@@ -1,6 +1,6 @@
1 1
 * 1.7.0 (2012-XX-XX)
2 2
 
3
- * n/a
3
+ * made the strategy used to guess the real template file name and line number in exception messages much faster and more accurate
4 4
 
5 5
 * 1.6.2 (2012-03-18)
6 6
 
... ...
@@ -22,6 +22,7 @@ class Twig_Compiler implements Twig_CompilerInterface
22 22
     protected $source;
23 23
     protected $indentation;
24 24
     protected $env;
25
+    protected $debugInfo;
25 26
 
26 27
     /**
27 28
      * Constructor.
... ...
@@ -31,6 +32,7 @@ class Twig_Compiler implements Twig_CompilerInterface
31 32
     public function __construct(Twig_Environment $env)
32 33
     {
33 34
         $this->env = $env;
35
+        $this->debugInfo = array();
34 36
     }
35 37
 
36 38
     /**
... ...
@@ -178,6 +180,8 @@ class Twig_Compiler implements Twig_CompilerInterface
178 180
     public function addDebugInfo(Twig_NodeInterface $node)
179 181
     {
180 182
         if ($node->getLine() != $this->lastLine) {
183
+            $this->debugInfo[substr_count($this->source, "\n")] = $node->getLine();
184
+
181 185
             $this->lastLine = $node->getLine();
182 186
             $this->write("// line {$node->getLine()}\n");
183 187
         }
... ...
@@ -185,6 +189,11 @@ class Twig_Compiler implements Twig_CompilerInterface
185 189
         return $this;
186 190
     }
187 191
 
192
+    public function getDebugInfo()
193
+    {
194
+        return $this->debugInfo;
195
+    }
196
+
188 197
     /**
189 198
      * Indents the generated code.
190 199
      *
... ...
@@ -33,7 +33,15 @@ class Twig_Error extends Exception
33 33
     public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null)
34 34
     {
35 35
         if (-1 === $lineno || null === $filename) {
36
-            list($lineno, $filename) = $this->findTemplateInfo(null !== $previous ? $previous : $this, $lineno, $filename);
36
+            if ($trace = $this->getTemplateTrace()) {
37
+                if (-1 === $lineno) {
38
+                    $lineno = $this->guessTemplateLine($trace);
39
+                }
40
+
41
+                if (null === $filename) {
42
+                    $filename = $trace['object']->getTemplateName();
43
+                }
44
+            }
37 45
         }
38 46
 
39 47
         $this->lineno = $lineno;
... ...
@@ -144,52 +152,23 @@ class Twig_Error extends Exception
144 152
         }
145 153
     }
146 154
 
147
-    protected function findTemplateInfo(Exception $e, $currentLine, $currentFile)
155
+    protected function getTemplateTrace()
148 156
     {
149
-        if (!function_exists('token_get_all')) {
150
-            return array($currentLine, $currentFile);
151
-        }
152
-
153
-        $traces = $e->getTrace();
154
-        foreach ($traces as $i => $trace) {
155
-            if (!isset($trace['class']) || 'Twig_Template' === $trace['class']) {
156
-                continue;
157
-            }
158
-
159
-            $r = new ReflectionClass($trace['class']);
160
-            if (!$r->implementsInterface('Twig_TemplateInterface')) {
161
-                continue;
162
-            }
163
-
164
-            if (!is_file($r->getFilename())) {
165
-                // probably an eval()'d code
166
-                return array($currentLine, $currentFile);
157
+        foreach (debug_backtrace() as $trace) {
158
+            if (isset($trace['object']) && $trace['object'] instanceof Twig_Template) {
159
+                return $trace;
167 160
             }
161
+        }
162
+    }
168 163
 
169
-            if (0 === $i) {
170
-                $line = $e->getLine();
171
-            } else {
172
-                $line = isset($traces[$i - 1]['line']) ? $traces[$i - 1]['line'] : -log(0);
173
-            }
174
-
175
-            $tokens = token_get_all(file_get_contents($r->getFilename()));
176
-            $templateline = -1;
177
-            $template = null;
178
-            foreach ($tokens as $token) {
179
-                if (isset($token[2]) && $token[2] >= $line) {
180
-                    return array($templateline, $template);
181
-                }
182
-
183
-                if (T_COMMENT === $token[0] && null === $template && preg_match('#/\* +(.+) +\*/#', $token[1], $match)) {
184
-                    $template = $match[1];
185
-                } elseif (T_COMMENT === $token[0] && preg_match('#^//\s*line (\d+)\s*$#', $token[1], $match)) {
186
-                    $templateline = $match[1];
187
-                }
164
+    protected function guessTemplateLine($trace)
165
+    {
166
+        foreach ($trace['object']->getDebugInfo() as $codeLine => $templateLine) {
167
+            if ($codeLine <= $trace['line']) {
168
+                return $templateLine;
188 169
             }
189
-
190
-            return array($currentLine, $template);
191 170
         }
192 171
 
193
-        return array($currentLine, $currentFile);
172
+        return -1;
194 173
     }
195 174
 }
... ...
@@ -57,6 +57,8 @@ class Twig_Node_Module extends Twig_Node
57 57
 
58 58
         $this->compileIsTraitable($compiler);
59 59
 
60
+        $this->compileDebugInfo($compiler);
61
+
60 62
         $this->compileClassFooter($compiler);
61 63
     }
62 64
 
... ...
@@ -294,6 +296,17 @@ class Twig_Node_Module extends Twig_Node
294 296
             ->indent()
295 297
             ->write(sprintf("return %s;\n", $traitable ? 'true' : 'false'))
296 298
             ->outdent()
299
+            ->write("}\n\n")
300
+        ;
301
+    }
302
+
303
+    public function compileDebugInfo(Twig_Compiler $compiler)
304
+    {
305
+        $compiler
306
+            ->write("public function getDebugInfo()\n", "{\n")
307
+            ->indent()
308
+            ->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true))))
309
+            ->outdent()
297 310
             ->write("}\n")
298 311
         ;
299 312
     }
... ...
@@ -1,7 +1,5 @@
1 1
 <?php
2 2
 
3
-require_once dirname(__FILE__).'/TestCase.php';
4
-
5 3
 /*
6 4
  * This file is part of Twig.
7 5
  *
... ...
@@ -11,12 +9,12 @@ require_once dirname(__FILE__).'/TestCase.php';
11 9
  * file that was distributed with this source code.
12 10
  */
13 11
 
14
-class Twig_Tests_ErrorTest extends Twig_Tests_TestCase
12
+class Twig_Tests_ErrorTest extends PHPUnit_Framework_TestCase
15 13
 {
16 14
     public function testTwigExceptionAddsFileAndLineWhenMissing()
17 15
     {
18 16
         $loader = new Twig_Loader_Array(array('index' => "\n\n{{ foo.bar }}"));
19
-        $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => $this->getTempDir()));
17
+        $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false));
20 18
 
21 19
         $template = $twig->loadTemplate('index');
22 20
 
... ...
@@ -34,7 +32,7 @@ class Twig_Tests_ErrorTest extends Twig_Tests_TestCase
34 32
     public function testRenderWrapsExceptions()
35 33
     {
36 34
         $loader = new Twig_Loader_Array(array('index' => "\n\n\n{{ foo.bar }}"));
37
-        $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => $this->getTempDir()));
35
+        $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false));
38 36
 
39 37
         $template = $twig->loadTemplate('index');
40 38
 
... ...
@@ -48,6 +46,30 @@ class Twig_Tests_ErrorTest extends Twig_Tests_TestCase
48 46
             $this->assertEquals('index', $e->getTemplateFile());
49 47
         }
50 48
     }
49
+
50
+    public function testTwigExceptionAddsFileAndLineWhenMissingWithInheritance()
51
+    {
52
+        $loader = new Twig_Loader_Array(array(
53
+            'index' => "{% extends 'base' %}
54
+            {% block content %}
55
+                {{ foo.bar }}
56
+            {% endblock %}",
57
+            'base' => '{% block content %}{% endblock %}'
58
+        ));
59
+        $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false));
60
+
61
+        $template = $twig->loadTemplate('index');
62
+
63
+        try {
64
+            $template->render(array());
65
+
66
+            $this->fail();
67
+        } catch (Twig_Error_Runtime $e) {
68
+            $this->assertEquals('Variable "foo" does not exist in "index" at line 3', $e->getMessage());
69
+            $this->assertEquals(3, $e->getTemplateLine());
70
+            $this->assertEquals('index', $e->getTemplateFile());
71
+        }
72
+    }
51 73
 }
52 74
 
53 75
 class Twig_Tests_ErrorTest_Foo
... ...
@@ -38,7 +38,7 @@ class Twig_Tests_Extension_SandboxTest extends PHPUnit_Framework_TestCase
38 38
 
39 39
     /**
40 40
      * @expectedException        Twig_Sandbox_SecurityError
41
-     * @expectedExceptionMessage Filter "json_encode" is not allowed.
41
+     * @expectedExceptionMessage Filter "json_encode" is not allowed in "1_child".
42 42
      */
43 43
     public function testSandboxWithInheritance()
44 44
     {
... ...
@@ -79,6 +79,10 @@ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
79 79
         return "foo.twig";
80 80
     }
81 81
 
82
+    public function getDebugInfo()
83
+    {
84
+        return array ();
85
+    }
82 86
 }
83 87
 EOF
84 88
         , $twig);
... ...
@@ -115,6 +119,11 @@ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
115 119
     {
116 120
         return false;
117 121
     }
122
+
123
+    public function getDebugInfo()
124
+    {
125
+        return array ();
126
+    }
118 127
 }
119 128
 EOF
120 129
         , $twig);
... ...
@@ -153,6 +162,11 @@ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
153 162
     {
154 163
         return false;
155 164
     }
165
+
166
+    public function getDebugInfo()
167
+    {
168
+        return array ();
169
+    }
156 170
 }
157 171
 EOF
158 172
         , $twig);
... ...
@@ -86,6 +86,10 @@ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
86 86
         return "foo.twig";
87 87
     }
88 88
 
89
+    public function getDebugInfo()
90
+    {
91
+        return array ();
92
+    }
89 93
 }
90 94
 EOF
91 95
         , $twig);
... ...
@@ -134,6 +138,11 @@ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
134 138
     {
135 139
         return false;
136 140
     }
141
+
142
+    public function getDebugInfo()
143
+    {
144
+        return array ();
145
+    }
137 146
 }
138 147
 EOF
139 148
         , $twig);
... ...
@@ -211,6 +211,11 @@ class Twig_TemplateTest extends Twig_Template
211 211
     {
212 212
     }
213 213
 
214
+    public function getDebugInfo()
215
+    {
216
+        return array();
217
+    }
218
+
214 219
     protected function doGetParent(array $context)
215 220
     {
216 221
     }