Skip to content

Commit 90fd820

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

File tree

4 files changed

+118
-2
lines changed

4 files changed

+118
-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

+101
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,101 @@ public function testTwigExceptionUpdateFileAndLineTogether()
253259
}
254260
}
255261

262+
/**
263+
* @dataProvider getErrorWithoutLineAndContextData
264+
*/
265+
public function testErrorWithoutLineAndContext(LoaderInterface $loader, bool $debug, bool $addDebugInfo, bool $exceptionWithLineAndContext)
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, $exceptionWithLineAndContext) extends AbstractTokenParser {
271+
public function __construct(private bool $addDebugInfo, private bool $exceptionWithLineAndContext)
272+
{
273+
}
274+
275+
public function parse(Token $token)
276+
{
277+
$stream = $this->parser->getStream();
278+
$stream->expect(Token::BLOCK_END_TYPE);
279+
$lineno = $stream->getCurrent()->getLine();
280+
281+
return new #[YieldReady]class($lineno, $this->addDebugInfo, $this->exceptionWithLineAndContext) extends Node
282+
{
283+
public function __construct(int $lineno,private bool $addDebugInfo, private bool $exceptionWithLineAndContext)
284+
{
285+
parent::__construct([], [], $lineno);
286+
}
287+
288+
public function compile(Compiler $compiler): void
289+
{
290+
if ($this->addDebugInfo) {
291+
$compiler->addDebugInfo($this);
292+
}
293+
if ($this->exceptionWithLineAndContext) {
294+
$compiler
295+
->write('throw new \Twig\Error\RuntimeError("Runtime error.", ')
296+
->repr($this->lineno)->raw(", \$this->getSourceContext()")
297+
->raw(');')
298+
;
299+
} else {
300+
$compiler->write('throw new \Twig\Error\RuntimeError("Runtime error.");');
301+
}
302+
}
303+
};
304+
}
305+
306+
public function getTag()
307+
{
308+
return 'foo';
309+
}
310+
});
311+
312+
try {
313+
$twig->render('no_line_and_context_exception.twig');
314+
$this->fail();
315+
} catch (RuntimeError $e) {
316+
$line = $addDebugInfo || $exceptionWithLineAndContext ? 5 : 1;
317+
318+
$this->assertSame(\sprintf('Runtime error in "no_line_and_context_exception_include.twig" at line %d.', $line), $e->getMessage());
319+
$this->assertSame($line, $e->getTemplateLine());
320+
321+
if ($loader instanceof FilesystemLoader) {
322+
$this->assertStringContainsString(\sprintf('errors/no_line_and_context_exception_include.twig', $line), $e->getFile());
323+
$this->assertSame($line, $e->getLine());
324+
} else {
325+
$this->assertStringContainsString('Environment.php', $e->getFile());
326+
$this->assertNotSame($line, $e->getLine());
327+
}
328+
}
329+
}
330+
331+
public static function getErrorWithoutLineAndContextData(): iterable
332+
{
333+
$fileLoaders = [
334+
new ArrayLoader([
335+
'no_line_and_context_exception.twig' => "\n\n{{ include('no_line_and_context_exception_include.twig') }}",
336+
'no_line_and_context_exception_include.twig' => "\n\n\n\n{% foo %}",
337+
]),
338+
new FilesystemLoader(__DIR__.'/Fixtures/errors'),
339+
];
340+
341+
foreach ($fileLoaders as $loader) {
342+
foreach ([false, true] as $exceptionWithLineAndContext) {
343+
foreach ([false, true] as $addDebugInfo) {
344+
foreach ([false, true] as $debug) {
345+
$name = ($loader instanceof FilesystemLoader ? 'filesystem' : 'array')
346+
.($debug ? '_with_debug' : '_without_debug')
347+
.($addDebugInfo ? '_with_debug_info' : '_without_debug_info')
348+
.($exceptionWithLineAndContext ? '_with_context' : '_without_context')
349+
;
350+
yield $name => [$loader, $debug, $addDebugInfo, $exceptionWithLineAndContext, $name];
351+
}
352+
}
353+
}
354+
}
355+
}
356+
256357
public static function getErroredTemplates()
257358
{
258359
return [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
3+
{{ include('no_line_and_context_exception_include.twig') }}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
3+
4+
{% foo %}

0 commit comments

Comments
 (0)