Skip to content

Commit 7c926c3

Browse files
committed
Fix Error when the trace has Twig file/line information instead of the original PHP info
1 parent 02118c7 commit 7c926c3

File tree

4 files changed

+103
-2
lines changed

4 files changed

+103
-2
lines changed

src/Error/Error.php

+10-2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ class Error extends \Exception
4141
private $lineno;
4242
private $rawMessage;
4343
private ?Source $source;
44+
private string $phpFile;
45+
private int $phpLine;
4446

4547
/**
4648
* Constructor.
@@ -55,6 +57,8 @@ public function __construct(string $message, int $lineno = -1, ?Source $source =
5557
{
5658
parent::__construct('', 0, $previous);
5759

60+
$this->phpFile = $this->getFile();
61+
$this->phpLine = $this->getLine();
5862
$this->lineno = $lineno;
5963
$this->source = $source;
6064
$this->rawMessage = $message;
@@ -111,6 +115,8 @@ private function updateRepr(): void
111115
$this->file = $this->source->getPath();
112116
if ($this->lineno > 0) {
113117
$this->line = $this->lineno;
118+
} else {
119+
$this->line = -1;
114120
}
115121
}
116122

@@ -134,6 +140,7 @@ private function guessTemplateInfo(): void
134140
{
135141
// $this->source is never null here (see guess() usage in Template)
136142

143+
$this->lineno = 0;
137144
$template = null;
138145
$templateClass = null;
139146
$backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT);
@@ -144,6 +151,8 @@ private function guessTemplateInfo(): void
144151
if ($this->source->getName() === $trace['object']->getTemplateName() && !$isEmbedContainer) {
145152
$template = $trace['object'];
146153
$templateClass = \get_class($trace['object']);
154+
155+
break;
147156
}
148157
}
149158
}
@@ -158,8 +167,7 @@ private function guessTemplateInfo(): void
158167

159168
while ($e = array_pop($exceptions)) {
160169
$traces = $e->getTrace();
161-
array_unshift($traces, ['file' => $e->getFile(), 'line' => $e->getLine()]);
162-
170+
array_unshift($traces, ['file' => $e instanceof Error ? $e->phpFile : $e->getFile(), 'line' => $e instanceof Error ? $e->phpLine : $e->getLine()]);
163171
while ($trace = array_shift($traces)) {
164172
if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) {
165173
continue;

tests/ErrorTest.php

+91
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,19 @@
1212
*/
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Twig\Attribute\YieldReady;
16+
use Twig\Compiler;
1517
use Twig\Environment;
1618
use Twig\Error\Error;
1719
use Twig\Error\RuntimeError;
1820
use Twig\Error\SyntaxError;
1921
use Twig\Loader\ArrayLoader;
2022
use Twig\Loader\FilesystemLoader;
23+
use Twig\Loader\LoaderInterface;
24+
use Twig\Node\Node;
2125
use Twig\Source;
26+
use Twig\Token;
27+
use Twig\TokenParser\AbstractTokenParser;
2228

2329
class ErrorTest extends TestCase
2430
{
@@ -253,6 +259,91 @@ public function testTwigExceptionUpdateFileAndLineTogether()
253259
}
254260
}
255261

262+
/**
263+
* @dataProvider getErrorWithoutLineAndContextData
264+
*/
265+
public function testErrorWithoutLineAndContext(LoaderInterface $loader, bool $debug, bool $addDebugInfo)
266+
{
267+
$twig = new Environment($loader, ['debug' => $debug, 'cache' => false]);
268+
$twig->removeCache('no_line_and_context_exception.twig');
269+
$twig->removeCache('no_line_and_context_exception_include.twig');
270+
$twig->addTokenParser(new class($addDebugInfo) extends AbstractTokenParser {
271+
public function __construct(private bool $addDebugInfo)
272+
{
273+
}
274+
275+
public function parse(Token $token)
276+
{
277+
$this->parser->getStream()->expect(Token::BLOCK_END_TYPE);
278+
279+
return new #[YieldReady]class($this->addDebugInfo) extends Node
280+
{
281+
public function __construct(private bool $addDebugInfo)
282+
{
283+
parent::__construct([], [], 2);
284+
}
285+
286+
public function compile(Compiler $compiler): void
287+
{
288+
if ($this->addDebugInfo) {
289+
$compiler->addDebugInfo($this);
290+
}
291+
$compiler->write('throw new \Twig\Error\RuntimeError("Runtime error.");');
292+
}
293+
};
294+
}
295+
296+
public function getTag()
297+
{
298+
return 'foo';
299+
}
300+
});
301+
302+
try {
303+
$twig->render('no_line_and_context_exception.twig');
304+
$this->fail();
305+
} catch (RuntimeError $e) {
306+
if ($addDebugInfo) {
307+
$this->assertSame('Runtime error in "no_line_and_context_exception_include.twig" at line 2.', $e->getMessage());
308+
$this->assertSame(2, $e->getTemplateLine());
309+
} else {
310+
$this->assertSame('Runtime error in "no_line_and_context_exception_include.twig".', $e->getMessage());
311+
$this->assertSame(0, $e->getTemplateLine());
312+
}
313+
314+
if ($loader instanceof FilesystemLoader) {
315+
$this->assertStringContainsString('errors/no_line_and_context_exception_include.twig', $e->getFile());
316+
if ($addDebugInfo) {
317+
$this->assertSame(2, $e->getLine());
318+
} else {
319+
$this->assertSame(-1, $e->getLine());
320+
}
321+
} else {
322+
$this->assertStringContainsString('Environment.php', $e->getFile());
323+
$this->assertNotSame(2, $e->getLine());
324+
}
325+
}
326+
}
327+
328+
public static function getErrorWithoutLineAndContextData(): iterable
329+
{
330+
$arrayLoader = new ArrayLoader([
331+
'no_line_and_context_exception.twig' => '{{ include("no_line_and_context_exception_include.twig") }}',
332+
'no_line_and_context_exception_include.twig' => '{% foo %}',
333+
]);
334+
335+
yield [$arrayLoader, false, false];
336+
yield [$arrayLoader, false, true];
337+
yield [$arrayLoader, true, false];
338+
yield [$arrayLoader, true, true];
339+
340+
$filesystemLoader = new FilesystemLoader(__DIR__.'/Fixtures/errors');
341+
yield [$filesystemLoader, false, false];
342+
yield [$filesystemLoader, false, true];
343+
yield [$filesystemLoader, true, false];
344+
yield [$filesystemLoader, true, true];
345+
}
346+
256347
public static function getErroredTemplates()
257348
{
258349
return [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{ include('no_line_and_context_exception_include.twig') }}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{% foo %}

0 commit comments

Comments
 (0)