8000 feature #50734 [ErrorHandler] Improve fileLinkFormat handling (nlemoine) · symfony/symfony@d30597d · GitHub
[go: up one dir, main page]

Skip to content

Commit d30597d

Browse files
committed
feature #50734 [ErrorHandler] Improve fileLinkFormat handling (nlemoine)
This PR was merged into the 6.4 branch. Discussion ---------- [ErrorHandler] Improve fileLinkFormat handling | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | no | New feature? | yes | Deprecations? | yes | Tickets | Fix #50619 | License | MIT | Doc PR | - 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 Commits ------- 510b77b [ErrorHandler] Improve fileLinkFormat handling
2 parents 48133f8 + 510b77b commit d30597d

File tree

26 files changed

+242
-138
lines changed

26 files changed

+242
-138
lines changed

UPGRADE-6.4.md

+1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ HttpKernel
152152
* [BC break] Add native return types to `TraceableEventDispatcher` and to `MergeExtensionConfigurationPass`
153153
* Deprecate `Kernel::stripComments()`
154154
* Deprecate `UriSigner`, use `UriSigner` from the HttpFoundation component instead
155+
* Deprecate `FileLinkFormatter`, use `FileLinkFormatter` from the ErrorHandler component instead
155156

156157
Messenger
157158
---------

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
"require": {
1919
"php": ">=8.1",
2020
"symfony/config": "^5.4|^6.0|^7.0",
21-
"symfony/framework-bundle": "^5.4|^6.0|^7.0",
22-
"symfony/http-kernel": "^6.3|^7.0",
21+
"symfony/framework-bundle": "^6.2|^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-19
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use Symfony\Component\ErrorHandler\Exception\FlattenException;
1616
use Symfony\Component\HttpFoundation\RequestStack;
1717
use Symfony\Component\HttpFoundation\Response;
18-
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
1918
use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator;
2019
use Symfony\Component\VarDumper\Cloner\Data;
2120
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
@@ -37,7 +36,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
3736

3837
private bool|\Closure $debug;
3938
private string $charset;
40-
private string|array|FileLinkFormatter|false $fileLinkFormat;
39+
private FileLinkFormatter $fileLinkFormat;
4140
private ?string $projectDir;
4241
private string|\Closure $outputBuffer;
4342
private ?LoggerInterface $logger;
@@ -52,10 +51,7 @@ public function __construct(bool|callable $debug = false, string $charset = null
5251
{
5352
$this->debug = \is_bool($debug) ? $debug : $debug(...);
5453
$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);
54+
$this->fileLinkFormat = $fileLinkFormat instanceof FileLinkFormatter ? $fileLinkFormat : new FileLinkFormatter($fileLinkFormat);
5955
$this->projectDir = $projectDir;
6056
$this->outputBuffer = \is_string($outputBuffer) ? $outputBuffer : $outputBuffer(...);
6157
$this->logger = $logger;
@@ -210,15 +206,6 @@ private function getFileRelative(string $file): ?string
210206
return null;
211207
}
212208

213-
private function getFileLink(string $file, int $line): string|false
214-
{
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;
220-
}
221-
222209
/**
223210
* Formats a file path.
224211
*
@@ -242,11 +229,9 @@ private function formatFile(string $file, int $line, string $text = null): strin
242229
$text .= ' at line '.$line;
243230
}
244231

245-
if (false !== $link = $this->getFileLink($file, $line)) {
246-
return sprintf('<a href="%s" title="Click to open this file" class="file_link">%s</a>', $this->escape($link), $text);
247-
}
232+
$link = $this->fileLinkFormat->format($file, $line);
248233

249-
return $text;
234+
return sprintf('<a href="%s" title="Click to open this file" class="file_link">%s</a>', $this->escape($link), $text);
250235
}
251236

252237
/**

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
{
@@ -80,4 +80,44 @@ public function testSerialize()
8080
{
8181
$this->assertInstanceOf(FileLinkFormatter::class, unserialize(serialize(new FileLinkFormatter())));
8282
}
83+
84+
/**
85+
* @dataProvider providePathMappings
86+
*/
87+
public function testIdeFileLinkFormatWithPathMappingParameters($mappings)
88+
{
89+
$params = array_reduce($mappings, function ($c, $m) {
90+
return "$c&".implode('>', $m);
91+
}, '');
92+
$sut = new FileLinkFormatter("vscode://file/%f:%l$params");
93+
foreach ($mappings as $mapping) {
94+
$fileGuest = $mapping['guest'].'file.php';
95+
$fileHost = $mapping['host'].'file.php';
96+
$this->assertSame("vscode://file/$fileHost:3", $sut->format($fileGuest, 3));
97+
}
98+
}
99+
100+
public static 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
@@ -54,4 +54,47 @@ public static function getRenderData(): iterable
5454
$expectedNonDebug,
5555
];
5656
}
57+
58+
/**
59+
* @dataProvider provideFileLinkFormats
60+
*/
61+
public function testFileLinkFormat(\ErrorException $exception, string $fileLinkFormat, bool $withSymfonyIde, string $expected)
62+
{
63+
if ($withSymfonyIde) {
64+
$_ENV['SYMFONY_IDE'] = $fileLinkFormat;
65+
}
66+
$errorRenderer = new HtmlErrorRenderer(true, null, $withSymfonyIde ? null : $fileLinkFormat);
67+
68+
$this->assertStringContainsString($expected, $errorRenderer->render($exception)->getAsString());
69+
}
70+
71+
public static function provideFileLinkFormats(): iterable
72+
{
73+
$exception = new \ErrorException('Notice', 0, \E_USER_NOTICE);
74+
75+
yield 'file link format set as known IDE with SYMFONY_IDE' => [
76+
$exception,
77+
'vscode',
78+
true,
79+
'href="vscode://file/'.__DIR__,
80+
];
81+
yield 'file link format set as a raw format with SYMFONY_IDE' => [
82+
$exception,
83+
'phpstorm://open?file=%f&line=%l',
84+
true,
85+
'href="phpstorm://open?file='.__DIR__,
86+
];
87+
yield 'file link format set as known IDE without SYMFONY_IDE' => [
88+
$exception,
89+
'vscode',
90+
false,
91+
'href="vscode://file/'.__DIR__,
92+
];
93+
yield 'file link format set as a raw format without SYMFONY_IDE' => [
94+
$exception,
95+
'phpstorm://open?file=%f&line=%l',
96+
false,
97+
'href="phpstorm://open?file='.__DIR__,
98+
];
99+
}
57100
}

0 commit comments

Comments
 (0)
0