10000 [ErrorHandler] Improve fileLinkFormat handling · symfony/symfony@3cc9728 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3cc9728

Browse files
nlemoinenicolas-grekas
authored andcommitted
[ErrorHandler] Improve fileLinkFormat handling
- Avoid repeating file link format guessing (logic is already in FileLinkFormatter class) - Always set a fileLinkFormat to a FileLinkFormatter object to handle path mappings properly
1 parent f13a4b1 commit 3cc9728

File tree

26 files changed

+241
-129
lines changed

26 files changed

+241
-129
lines changed

UPGRADE-6.4.md

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ HttpKernel
109109
* [BC break] Add native return types to `TraceableEventDispatcher` and to `MergeExtensionConfigurationPass`
110110
* Deprecate `Kernel::stripComments()`
111111
* Deprecate `UriSigner`, use `UriSigner` from the HttpFoundation component instead
112+
* Deprecate `FileLinkFormatter`, use `FileLinkFormatter` from the ErrorHandler component instead
112113

113114
Messenger
114115
---------

src/Symfony/Bridge/Twig/Command/DebugCommand.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
use Symfony\Component\Console\Input\InputOption;
2323
use Symfony\Component\Console\Output\OutputInterface;
2424
use Symfony\Component\Console\Style\SymfonyStyle;
25+
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
2526
use Symfony\Component\Finder\Finder;
26-
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
2727
use Twig\Environment;
2828
use Twig\Loader\ChainLoader;
2929
use Twig\Loader\FilesystemLoader;

src/Symfony/Bridge/Twig/Extension/CodeExtension.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Symfony\Bridge\Twig\Extension;
1313

14-
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
14+
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
1515
use Twig\Extension\AbstractExtension;
1616
use Twig\TwigFilter;
1717

src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Bridge\Twig\Extension\CodeExtension;
16-
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
16+
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
1717

1818
class CodeExtensionTest extends TestCase
1919
{

src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
use Symfony\Component\Console\Output\OutputInterface;
2222
use Symfony\Component\Console\Style\SymfonyStyle;
2323
use Symfony\Component\DependencyInjection\Attribute\Target;
24-
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
24+
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
2525

2626
/**
2727
* A console command for autowiring information.

src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
use Symfony\Component\Console\Input\InputOption;
2323
use Symfony\Component\Console\Output\OutputInterface;
2424
use Symfony\Component\Console\Style\SymfonyStyle;
25-
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
25+
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
2626
use Symfony\Component\Routing\RouteCollection;
2727
use Symfony\Component\Routing\RouterInterface;
2828

src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
use Symfony\Component\DependencyInjection\Definition;
2727
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
2828
use Symfony\Component\DependencyInjection\Reference;
29+
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
2930
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
30-
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
3131
use Symfony\Component\Routing\Route;
3232
use Symfony\Component\Routing\RouteCollection;
3333

src/Symfony/Bundle/FrameworkBundle/Console/Helper/DescriptorHelper.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
use Symfony\Bundle\FrameworkBundle\Console\Descriptor\TextDescriptor;
1717
use Symfony\Bundle\FrameworkBundle\Console\Descriptor\XmlDescriptor;
1818
use Symfony\Component\Console\Helper\DescriptorHelper as BaseDescriptorHelper;
19-
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
19+
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
2020

2121
/**
2222
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>

src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

14+
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
1415
use Symfony\Component\HttpKernel\Debug\ErrorHandlerConfigurator;
15-
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
1616
use Symfony\Component\HttpKernel\EventListener\DebugHandlersListener;
1717

1818
return static function (ContainerConfigurator $container) {

src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/TextDescriptorTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
namespace Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor;
1313

1414
use Symfony\Bundle\FrameworkBundle\Console\Descriptor\TextDescriptor;
15-
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
15+
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
1616
use Symfony\Component\Routing\Route;
1717

1818
class TextDescriptorTest extends AbstractDescriptorTestCase

src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use Symfony\Bundle\WebProfilerBundle\Csp\ContentSecurityPolicyHandler;
1818
use Symfony\Bundle\WebProfilerBundle\Csp\NonceGenerator;
1919
use Symfony\Bundle\WebProfilerBundle\Twig\WebProfilerExtension;
20-
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
20+
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
2121
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
2222

2323
return static function (ContainerConfigurator $container) {

src/Symfony/Bundle/WebProfilerBundle/composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"php": ">=8.1",
2020
"symfony/config": "^5.4|^6.0|^7.0",
2121
"symfony/framework-bundle": "^5.4|^6.0|^7.0",
22-
"symfony/http-kernel": "^6.3|^7.0",
22+
"symfony/http-kernel": "^6.4|^7.0",
2323
"symfony/routing": "^5.4|^6.0|^7.0",
2424
"symfony/twig-bundle": "^5.4|^6.0|^7.0",
2525
"twig/twig": "^2.13|^3.0.4"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\ErrorHandler\ErrorRenderer;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpFoundation\RequestStack;
16+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
17+
18+
/**
19+
* Formats debug file links.
20+
*
21+
* @author Jérémy Romey <jeremy@free-agent.fr>
22+
*
23+
* @final
24+
*/
25+
class FileLinkFormatter
26+
{
27+
private array|false $fileLinkFormat;
28+
private ?RequestStack $requestStack = null;
29+
private ?string $baseDir = null;
30+
private \Closure|string|null $urlFormat;
31+
32+
/**
33+
* @param string|\Closure $urlFormat the URL format, or a closure that returns it on-demand
34+
*/
35+
public function __construct(string|array $fileLinkFormat = null, RequestStack $requestStack = null, string $baseDir = null, string|\Closure $urlFormat = null)
36+
{
37+
$fileLinkFormat ??= $_ENV['SYMFONY_IDE'] ?? $_SERVER['SYMFONY_IDE'] ?? '';
38+
39+
if (!\is_array($f = $fileLinkFormat)) {
40+
$f = (ErrorRendererInterface::IDE_LINK_FORMATS[$f] ?? $f) ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l';
41+
$i = strpos($f, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f);
42+
$fileLinkFormat = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, \PREG_SPLIT_DELIM_CAPTURE);
43+
}
44+
45+
$this->fileLinkFormat = $fileLinkFormat;
46+
$this->requestStack = $requestStack;
47+
$this->baseDir = $baseDir;
48+
$this->urlFormat = $urlFormat;
49+
}
50+
51+
/**
52+
* @return string|false
53+
*/
54+
public function format(string $file, int $line): string|bool
55+
{
56+
if ($fmt = $this->getFileLinkFormat()) {
57+
for ($i = 1; isset($fmt[$i]); ++$i) {
58+
if (str_starts_with($file, $k = $fmt[$i++])) {
59+
$file = substr_replace($file, $fmt[$i], 0, \strlen($k));
60+
break;
61+
}
62+
}
63+
64+
return strtr($fmt[0], ['%f' => $file, '%l' => $line]);
65+
}
66+
67+
return false;
68+
}
69+
70+
/**
71+
* @internal
72+
*/
73+
public function __sleep(): array
74+
{
75+
$this->fileLinkFormat = $this->getFileLinkFormat();
76+
77+
return ['fileLinkFormat'];
78+
}
79+
80+
/**
81+
* @internal
82+
*/
83+
public static function generateUrlFormat(UrlGeneratorInterface $router, string $routeName, string $queryString): ?string
84+
{
85+
try {
86+
return $router->generate($routeName).$queryString;
87+
} catch (\Throwable) {
88+
return null;
89+
}
90+
}
91+
92+
private function getFileLinkFormat(): array|false
93+
{
94+
if ($this->fileLinkFormat) {
95+
return $this->fileLinkFormat;
96+
}
97+
98+
if ($this->requestStack && $this->baseDir && $this->urlFormat) {
99+
$request = $this->requestStack->getMainRequest();
100+
101+
if ($request instanceof Request && (!$this->urlFormat instanceof \Closure || $this->urlFormat = ($this->urlFormat)())) {
102+
return [
103+
$request->getSchemeAndHttpHost().$this->urlFormat,
104+
$this->baseDir.\DIRECTORY_SEPARATOR, '',
105+
];
106+
}
107+
}
108+
109+
return false;
110+
}
111+
}
112+
113+
if (!class_exists(\Symfony\Component\HttpKernel\Debug\FileLinkFormatter::class, false)) {
114+
class_alias(FileLinkFormatter::class, \Symfony\Component\HttpKernel\Debug\FileLinkFormatter::class);
115+
}

src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php

+4-11
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
namespace Symfony\Component\ErrorHandler\ErrorRenderer;
1313

1414
use Psr\Log\LoggerInterface;
15+
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
1516
use Symfony\Component\ErrorHandler\Exception\FlattenException;
1617
use Symfony\Component\HttpFoundation\RequestStack;
1718
use Symfony\Component\HttpFoundation\Response;
18-
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
1919
use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator;
2020
use Symfony\Component\VarDumper\Cloner\Data;
2121
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
@@ -37,7 +37,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
3737

3838
private bool|\Closure $debug;
3939
private string $charset;
40-
private string|array|FileLinkFormatter|false $fileLinkFormat;
40+
private FileLinkFormatter $fileLinkFormat;
4141
private ?string $projectDir;
4242
private string|\Closure $outputBuffer;
4343
private ?LoggerInterface $logger;
@@ -52,10 +52,7 @@ public function __construct(bool|callable $debug = false, string $charset = null
5252
{
5353
$this->debug = \is_bool($debug) ? $debug : $debug(...);
5454
$this->charset = $charset ?: (\ini_get('default_charset') ?: 'UTF-8');
55-
$fileLinkFormat ??= $_ENV['SYMFONY_IDE'] ?? $_SERVER['SYMFONY_IDE'] ?? null;
56-
$this->fileLinkFormat = \is_string($fileLinkFormat)
57-
? (ErrorRendererInterface::IDE_LINK_FORMATS[$fileLinkFormat] ?? $fileLinkFormat ?: false)
58-
: ($fileLinkFormat ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: false);
55+
$this->fileLinkFormat = $fileLinkFormat instanceof FileLinkFormatter ? $fileLinkFormat : new FileLinkFormatter($fileLinkFormat);
5956
$this->projectDir = $projectDir;
6057
$this->outputBuffer = \is_string($outputBuffer) ? $outputBuffer : $outputBuffer(...);
6158
$this->logger = $logger;
@@ -212,11 +209,7 @@ private function getFileRelative(string $file): ?string
212209

213210
private function getFileLink(string $file, int $line): string|false
214211
{
215-
if ($fmt = $this->fileLinkFormat) {
216-
return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line);
217-
}
218-
219-
return false;
212+
return $this->fileLinkFormat->format($file, $line);
220213
}
221214

222215
/**

src/Symfony/Component/HttpKernel/Tests/Debug/FileLinkFormatterTest.php renamed to src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/FileLinkFormatterTest.php

+42-2
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
namespace Symfony\Component\HttpKernel\Tests\Debug;
12+
namespace Symfony\Component\ErrorHandler\Tests\ErrorRenderer;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
1516
use Symfony\Component\HttpFoundation\Request;
1617
use Symfony\Component\HttpFoundation\RequestStack;
17-
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
1818

1919
class FileLinkFormatterTest extends TestCase
2020
{
@@ -76,8 +76,48 @@ public function testIdeFileLinkFormat()
7676
$this->assertSame("atom://core/open/file?filename=$file&line=3", $sut->format($file, 3));
7777
}
7878

79+
/**
80+
* @dataProvider providePathMappings
81+
*/
82+
public function testIdeFileLinkFormatWithPathMappingParameters($mappings)
83+
{
84+
$params = array_reduce($mappings, function ($c, $m) {
85+
return "$c&".implode('>', $m);
86+
}, '');
87+
$sut = new FileLinkFormatter("vscode://file/%f:%l$params");
88+
foreach ($mappings as $mapping) {
89+
$fileGuest = $mapping['guest'].'file.php';
90+
$fileHost = $mapping['host'].'file.php';
91+
$this->assertSame("vscode://file/$fileHost:3", $sut->format($fileGuest, 3));
92+
}
93+
}
94+
7995
public function testSerialize()
8096
{
8197
$this->assertInstanceOf(FileLinkFormatter::class, unserialize(serialize(new FileLinkFormatter())));
8298
}
99+
100+
public function providePathMappings()
101+
{
102+
yield 'single path mapping' => [
103+
[
104+
[
105+
'guest' => '/var/www/app/',
106+
'host' => '/user/name/project/',
107+
],
108+
],
109+
];
110+
yield 'multiple path mapping' => [
111+
[
112+
[
113+
'guest' => '/var/www/app/',
114+
'host' => '/user/name/project/',
115+
],
116+
[
117+
'guest' => '/var/www/app2/',
118+
'host' => '/user/name/project2/',
119+
],
120+
],
121+
];
122+
}
83123
}

src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/HtmlErrorRendererTest.php

+43
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,49 @@ public function testRender(\Throwable $exception, HtmlErrorRenderer $errorRender
2424
$this->assertStringMatchesFormat($expected, $errorRenderer->render($exception)->getAsString());
2525
}
2626

27+
/**
28+
* @dataProvider provideFileLinkFormats
29+
*/
30+
public function testFileLinkFormat(\ErrorException $exception, string $fileLinkFormat, bool $withSymfonyIde, string $expected)
31+
{
32+
if ($withSymfonyIde) {
33+
$_ENV['SYMFONY_IDE'] = $fileLinkFormat;
34+
}
35+
$errorRenderer = new HtmlErrorRenderer(true, null, $withSymfonyIde ? null : $fileLinkFormat);
36+
37+
$this->assertStringContainsString($expected, $errorRenderer->render($exception)->getAsString());
38+
}
39+
40+
public function provideFileLinkFormats(): iterable
41+
{
42+
$exception = new \ErrorException('Notice', 0, \E_USER_NOTICE);
43+
44+
yield 'file link format set as known IDE with SYMFONY_IDE' => [
45+
$exception,
46+
'vscode',
47+
true,
48+
'href="vscode://file/'.__DIR__,
49+
];
50+
yield 'file link format set as a raw format with SYMFONY_IDE' => [
51+
$exception,
52+
'phpstorm://open?file=%f&line=%l',
53+
true,
54+
'href="phpstorm://open?file='.__DIR__,
55+
];
56+
yield 'file link format set as known IDE without SYMFONY_IDE' => [
57+
$exception,
58+
'vscode',
59+
false,
60+
'href="vscode://file/'.__DIR__,
61+
];
62+
yield 'file link format set as a raw format without SYMFONY_IDE' => [
63+
$exception,
64+
'phpstorm://open?file=%f&line=%l',
65+
false,
66+
'href="phpstorm://open?file='.__DIR__,
67+
];
68+
}
69+
2770
public static function getRenderData(): iterable
2871
{
2972
$expectedDebug = <<<HTML

0 commit comments

Comments
 (0)
0