mirror of
https://github.com/twigphp/Twig.git
synced 2026-08-30 12:06:56 +00:00
Merge branch '3.x' into 4.x
* 3.x: Fix Error when the trace has Twig file/line information instead of the original PHP info Sync Error file and line
This commit is contained in:
+14
-5
@@ -41,6 +41,8 @@ class Error extends \Exception
|
||||
private int $lineno;
|
||||
private string $rawMessage;
|
||||
private ?Source $source;
|
||||
private string $phpFile;
|
||||
private int $phpLine;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -55,6 +57,8 @@ class Error extends \Exception
|
||||
{
|
||||
parent::__construct('', 0, $previous);
|
||||
|
||||
$this->phpFile = $this->getFile();
|
||||
$this->phpLine = $this->getLine();
|
||||
$this->lineno = $lineno;
|
||||
$this->source = $source;
|
||||
$this->rawMessage = $message;
|
||||
@@ -106,11 +110,14 @@ class Error extends \Exception
|
||||
|
||||
private function updateRepr(): void
|
||||
{
|
||||
if ($this->lineno > 0) {
|
||||
$this->line = $this->lineno;
|
||||
}
|
||||
if ($this->source && $this->source->getPath()) {
|
||||
// we only update the file and the line together
|
||||
$this->file = $this->source->getPath();
|
||||
if ($this->lineno > 0) {
|
||||
$this->line = $this->lineno;
|
||||
} else {
|
||||
$this->line = -1;
|
||||
}
|
||||
}
|
||||
|
||||
$this->message = $this->rawMessage;
|
||||
@@ -133,6 +140,7 @@ class Error extends \Exception
|
||||
{
|
||||
// $this->source is never null here (see guess() usage in Template)
|
||||
|
||||
$this->lineno = 0;
|
||||
$template = null;
|
||||
$templateClass = null;
|
||||
$backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT);
|
||||
@@ -143,6 +151,8 @@ class Error extends \Exception
|
||||
if ($this->source->getName() === $trace['object']->getTemplateName() && !$isEmbedContainer) {
|
||||
$template = $trace['object'];
|
||||
$templateClass = $trace['object']::class;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,8 +167,7 @@ class Error extends \Exception
|
||||
|
||||
while ($e = array_pop($exceptions)) {
|
||||
$traces = $e->getTrace();
|
||||
array_unshift($traces, ['file' => $e->getFile(), 'line' => $e->getLine()]);
|
||||
|
||||
array_unshift($traces, ['file' => $e instanceof Error ? $e->phpFile : $e->getFile(), 'line' => $e instanceof Error ? $e->phpLine : $e->getLine()]);
|
||||
while ($trace = array_shift($traces)) {
|
||||
if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) {
|
||||
continue;
|
||||
|
||||
@@ -13,12 +13,19 @@ namespace Twig\Tests;
|
||||
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Environment;
|
||||
use Twig\Error\Error;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Loader\ArrayLoader;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
use Twig\Loader\LoaderInterface;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Source;
|
||||
use Twig\Token;
|
||||
use Twig\TokenParser\AbstractTokenParser;
|
||||
|
||||
class ErrorTest extends TestCase
|
||||
{
|
||||
@@ -233,6 +240,134 @@ EOHTML,
|
||||
}
|
||||
}
|
||||
|
||||
public function testTwigExceptionUpdateFileAndLineTogether()
|
||||
{
|
||||
$twig = new Environment(new ArrayLoader([
|
||||
'index' => "\n\n\n\n{{ foo() }}",
|
||||
]), ['debug' => true, 'cache' => false]);
|
||||
|
||||
try {
|
||||
$twig->load('index')->render([]);
|
||||
} catch (SyntaxError $e) {
|
||||
$this->assertSame('Unknown "foo" function in "index" at line 5.', $e->getMessage());
|
||||
$this->assertSame(5, $e->getTemplateLine());
|
||||
// as we are using an ArrayLoader, we don't have a file, so the line should not be the template line,
|
||||
// but the line of the error in the Parser.php file
|
||||
$this->assertStringContainsString('Parser.php', $e->getFile());
|
||||
$this->assertNotSame(5, $e->getLine());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getErrorWithoutLineAndContextData
|
||||
*/
|
||||
public function testErrorWithoutLineAndContext(LoaderInterface $loader, bool $debug, bool $addDebugInfo, bool $exceptionWithLineAndContext, int $errorLine)
|
||||
{
|
||||
$twig = new Environment($loader, ['debug' => $debug, 'cache' => false]);
|
||||
$twig->removeCache('no_line_and_context_exception.twig');
|
||||
$twig->removeCache('no_line_and_context_exception_include_line_5.twig');
|
||||
$twig->removeCache('no_line_and_context_exception_include_line_1.twig');
|
||||
$twig->addTokenParser(new class($addDebugInfo, $exceptionWithLineAndContext) extends AbstractTokenParser {
|
||||
public function __construct(private bool $addDebugInfo, private bool $exceptionWithLineAndContext)
|
||||
{
|
||||
}
|
||||
|
||||
public function parse(Token $token)
|
||||
{
|
||||
$stream = $this->parser->getStream();
|
||||
$lineno = $stream->getCurrent()->getLine();
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
return new #[YieldReady]class($lineno, $this->addDebugInfo, $this->exceptionWithLineAndContext) extends Node
|
||||
{
|
||||
public function __construct(int $lineno, private bool $addDebugInfo, private bool $exceptionWithLineAndContext)
|
||||
{
|
||||
parent::__construct([], [], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
if ($this->addDebugInfo) {
|
||||
$compiler->addDebugInfo($this);
|
||||
}
|
||||
if ($this->exceptionWithLineAndContext) {
|
||||
$compiler
|
||||
->write('throw new \Twig\Error\RuntimeError("Runtime error.", ')
|
||||
->repr($this->lineno)->raw(", \$this->getSourceContext()")
|
||||
->raw(");\n")
|
||||
;
|
||||
} else {
|
||||
$compiler->write('throw new \Twig\Error\RuntimeError("Runtime error.");');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public function getTag()
|
||||
{
|
||||
return 'foo';
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
$twig->render('no_line_and_context_exception.twig', ['line' => $errorLine]);
|
||||
$this->fail();
|
||||
} catch (RuntimeError $e) {
|
||||
if (1 === $errorLine && !$addDebugInfo && !$exceptionWithLineAndContext) {
|
||||
// When the template only has the custom node that throws the error, we cannot find the line of the error
|
||||
// as we have no debug info and no line and context in the exception
|
||||
$this->assertSame(\sprintf('Runtime error in "no_line_and_context_exception_include_line_%d.twig".', $errorLine), $e->getMessage());
|
||||
$this->assertSame(0, $e->getTemplateLine());
|
||||
} else {
|
||||
// When the template has some space before the custom node, the associated TextNode outputs some debug info at line 1
|
||||
// that's why the line is 1 when we have no debug info and no line and context in the exception
|
||||
$line = $addDebugInfo || $exceptionWithLineAndContext ? $errorLine : 1;
|
||||
$this->assertSame(\sprintf('Runtime error in "no_line_and_context_exception_include_line_%d.twig" at line %d.', $errorLine, $line), $e->getMessage());
|
||||
$this->assertSame($line, $e->getTemplateLine());
|
||||
}
|
||||
|
||||
$line = $addDebugInfo || $exceptionWithLineAndContext ? $errorLine : 1;
|
||||
if ($loader instanceof FilesystemLoader) {
|
||||
$this->assertStringContainsString(\sprintf('errors/no_line_and_context_exception_include_line_%d.twig', $errorLine), $e->getFile());
|
||||
$line = $addDebugInfo || $exceptionWithLineAndContext ? $errorLine : (1 === $errorLine ? -1 : 1);
|
||||
$this->assertSame($line, $e->getLine());
|
||||
} else {
|
||||
$this->assertStringContainsString('Environment.php', $e->getFile());
|
||||
$this->assertNotSame($line, $e->getLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function getErrorWithoutLineAndContextData(): iterable
|
||||
{
|
||||
$fileLoaders = [
|
||||
new ArrayLoader([
|
||||
'no_line_and_context_exception.twig' => "\n\n{{ include('no_line_and_context_exception_include_line_' ~ line ~ '.twig') }}",
|
||||
'no_line_and_context_exception_include_line_5.twig' => "\n\n\n\n{% foo %}",
|
||||
'no_line_and_context_exception_include_line_1.twig' => '{% foo %}',
|
||||
]),
|
||||
new FilesystemLoader(__DIR__.'/Fixtures/errors'),
|
||||
];
|
||||
|
||||
foreach ($fileLoaders as $loader) {
|
||||
foreach ([false, true] as $exceptionWithLineAndContext) {
|
||||
foreach ([false, true] as $addDebugInfo) {
|
||||
foreach ([false, true] as $debug) {
|
||||
foreach ([5, 1] as $line) {
|
||||
$name = ($loader instanceof FilesystemLoader ? 'filesystem' : 'array')
|
||||
.($debug ? '_with_debug' : '_without_debug')
|
||||
.($addDebugInfo ? '_with_debug_info' : '_without_debug_info')
|
||||
.($exceptionWithLineAndContext ? '_with_context' : '_without_context')
|
||||
.('_line_'.$line)
|
||||
;
|
||||
yield $name => [$loader, $debug, $addDebugInfo, $exceptionWithLineAndContext, $line];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function getErroredTemplates()
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
{{ include('no_line_and_context_exception_include_line_' ~ line ~ '.twig') }}
|
||||
@@ -0,0 +1 @@
|
||||
{% foo %}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
{% foo %}
|
||||
Reference in New Issue
Block a user