Skip to content

Commit be6c1c7

Browse files
committed
Merge branch '6.4' into 7.2
* 6.4: [Config] Do not generate unreachable configuration paths [WebProfilerBundle] Fix missing indent on non php files opended in the profiler
2 parents 55e6706 + ae16f88 commit be6c1c7

File tree

2 files changed

+168
-27
lines changed

2 files changed

+168
-27
lines changed

Profiler/CodeExtension.php

Lines changed: 73 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -119,39 +119,85 @@ public function formatArgsAsText(array $args): string
119119
*/
120120
public function fileExcerpt(string $file, int $line, int $srcContext = 3): ?string
121121
{
122-
if (is_file($file) && is_readable($file)) {
123-
// highlight_file could throw warnings
124-
// see https://bugs.php.net/25725
125-
$code = @highlight_file($file, true);
126-
if (\PHP_VERSION_ID >= 80300) {
127-
// remove main pre/code tags
128-
$code = preg_replace('#^<pre.*?>\s*<code.*?>(.*)</code>\s*</pre>#s', '\\1', $code);
129-
// split multiline span tags
130-
$code = preg_replace_callback('#<span ([^>]++)>((?:[^<\\n]*+\\n)++[^<]*+)</span>#', function ($m) {
131-
return "<span $m[1]>".str_replace("\n", "</span>\n<span $m[1]>", $m[2]).'</span>';
132-
}, $code);
133-
$content = explode("\n", $code);
134-
} else {
135-
// remove main code/span tags
136-
$code = preg_replace('#^<code.*?>\s*<span.*?>(.*)</span>\s*</code>#s', '\\1', $code);
137-
// split multiline spans
138-
$code = preg_replace_callback('#<span ([^>]++)>((?:[^<]*+<br \/>)++[^<]*+)</span>#', fn ($m) => "<span $m[1]>".str_replace('<br />', "</span><br /><span $m[1]>", $m[2]).'</span>', $code);
139-
$content = explode('<br />', $code);
140-
}
122+
if (!is_file($file) || !is_readable($file)) {
123+
return null;
124+
}
125+
126+
$contents = file_get_contents($file);
127+
128+
if (!str_contains($contents, '<?php') && !str_contains($contents, '<?=')) {
129+
$lines = explode("\n", $contents);
141130

142-
$lines = [];
143131
if (0 > $srcContext) {
144-
$srcContext = \count($content);
132+
$srcContext = \count($lines);
145133
}
146134

147-
for ($i = max($line - $srcContext, 1), $max = min($line + $srcContext, \count($content)); $i <= $max; ++$i) {
148-
$lines[] = '<li'.($i == $line ? ' class="selected"' : '').'><a class="anchor" id="line'.$i.'"></a><code>'.self::fixCodeMarkup($content[$i - 1]).'</code></li>';
149-
}
135+
return $this->formatFileExcerpt(
136+
$this->extractExcerptLines($lines, $line, $srcContext),
137+
$line,
138+
$srcContext
139+
);
140+
}
150141

151-
return '<ol start="'.max($line - $srcContext, 1).'">'.implode("\n", $lines).'</ol>';
142+
// highlight_string could throw warnings
143+
// see https://bugs.php.net/25725
144+
$code = @highlight_string($contents, true);
145+
146+
if (\PHP_VERSION_ID >= 80300) {
147+
// remove main pre/code tags
148+
$code = preg_replace('#^<pre.*?>\s*<code.*?>(.*)</code>\s*</pre>#s', '\\1', $code);
149+
// split multiline span tags
150+
$code = preg_replace_callback(
151+
'#<span ([^>]++)>((?:[^<\\n]*+\\n)++[^<]*+)</span>#',
152+
static fn (array $m): string => "<span $m[1]>".str_replace("\n", "</span>\n<span $m[1]>", $m[2]).'</span>',
153+
$code
154+
);
155+
$lines = explode("\n", $code);
156+
} else {
157+
// remove main code/span tags
158+
$code = preg_replace('#^<code.*?>\s*<span.*?>(.*)</span>\s*</code>#s', '\\1', $code);
159+
// split multiline spans
160+
$code = preg_replace_callback(
161+
'#<span ([^>]++)>((?:[^<]*+<br \/>)++[^<]*+)</span>#',
162+
static fn (array $m): string => "<span $m[1]>".str_replace('<br />', "</span><br /><span $m[1]>", $m[2]).'</span>',
163+
$code
164+
);
165+
$lines = explode('<br />', $code);
152166
}
153167

154-
return null;
168+
if (0 > $srcContext) {
169+
$srcContext = \count($lines);
170+
}
171+
172+
return $this->formatFileExcerpt(
173+
array_map(
174+
self::fixCodeMarkup(...),
175+
$this->extractExcerptLines($lines, $line, $srcContext),
176+
),
177+
$line,
178+
$srcContext
179+
);
180+
}
181+
182+
private function extractExcerptLines(array $lines, int $selectedLine, int $srcContext): array
183+
{
184+
return \array_slice(
185+
$lines,
186+
max($selectedLine - $srcContext, 0),
187+
min($srcContext * 2 + 1, \count($lines) - $selectedLine + $srcContext),
188+
true
189+
);
190+
}
191+
192+
private function formatFileExcerpt(array $lines, int $selectedLine, int $srcContext): string
193+
{
194+
$start = max($selectedLine - $srcContext, 1);
195+
196+
return "<ol start=\"{$start}\">".implode("\n", array_map(
197+
static fn (string $line, int $num): string => '<li'.(++$num === $selectedLine ? ' class="selected"' : '')."><a class=\"anchor\" id=\"line{$num}\"></a><code>{$line}</code></li>",
198+
$lines,
199+
array_keys($lines),
200+
)).'</ol>';
155201
}
156202

157203
/**
@@ -241,7 +287,7 @@ protected static function fixCodeMarkup(string $line): string
241287
// missing </span> tag at the end of line
242288
$opening = strpos($line, '<span');
243289
$closing = strpos($line, '</span>');
244-
if (false !== $opening && (false === $closing || $closing > $opening)) {
290+
if (false !== $opening && (false === $closing || $closing < $opening)) {
245291
$line .= '</span>';
246292
}
247293

Tests/Profiler/CodeExtensionTest.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,101 @@ public function testFormatFileIntegration()
129129
$this->assertEquals($expected, $this->render($template));
130130
}
131131

132+
/**
133+
* @dataProvider fileExcerptIntegrationProvider
134+
*/
135+
public function testFileExcerptIntegration(string $expected, array $data)
136+
{
137+
$template = <<<'TWIG'
138+
{{ file_path|file_excerpt(line, src_context) }}
139+
TWIG;
140+
$html = $this->render($template, $data);
141+
142+
// highlight_file function output changed sing PHP 8.3
143+
// see https://github.com/php/php-src/blob/e2667f17bc24e3cd200bb3eda457f566f1f77f8f/UPGRADING#L239-L242
144+
if (\PHP_VERSION_ID < 80300) {
145+
$html = str_replace('&nbsp;', ' ', $html);
146+
}
147+
148+
$html = html_entity_decode($html);
149+
150+
$this->assertEquals($expected, $html);
151+
}
152+
153+
public static function fileExcerptIntegrationProvider()
154+
{
155+
$fixturesPath = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Fixtures';
156+
157+
yield 'php file' => [
158+
'expected' => <<<'HTML'
159+
<ol start="1"><li><a class="anchor" id="line1"></a><code><span style="color: #0000BB"><?php</span></code></li>
160+
<li><a class="anchor" id="line2"></a><code><span style="color: #0000BB"></span></code></li>
161+
<li><a class="anchor" id="line3"></a><code><span style="color: #0000BB"></span><span style="color: #007700">echo </span><span style="color: #DD0000">'Hello'</span><span style="color: #007700">;</span></code></li>
162+
<li><a class="anchor" id="line4"></a><code><span style="color: #007700">echo </span><span style="color: #DD0000">'World!'</span><span style="color: #007700">;</span></code></li>
163+
<li><a class="anchor" id="line5"></a><code><span style="color: #007700"></span></code></li></ol>
164+
HTML,
165+
'data' => [
166+
'file_path' => $fixturesPath.\DIRECTORY_SEPARATOR.'hello_world.php',
167+
'line' => 0,
168+
'src_context' => 3,
169+
],
170+
];
171+
172+
yield 'php file with selected line and no source context' => [
173+
'expected' => <<<'HTML'
174+
<ol start="1"><li class="selected"><a class="anchor" id="line1"></a><code><span style="color: #0000BB"><?php</span></code></li>
175+
<li><a class="anchor" id="line2"></a><code><span style="color: #0000BB"></span></code></li>
176+
<li><a class="anchor" id="line3"></a><code><span style="color: #0000BB"></span><span style="color: #007700">echo </span><span style="color: #DD0000">'Hello'</span><span style="color: #007700">;</span></code></li>
177+
<li><a class="anchor" id="line4"></a><code><span style="color: #007700">echo </span><span style="color: #DD0000">'World!'</span><span style="color: #007700">;</span></code></li>
178+
<li><a class="anchor" id="line5"></a><code><span style="color: #007700"></span></code></li></ol>
179+
HTML,
180+
'data' => [
181+
'file_path' => $fixturesPath.\DIRECTORY_SEPARATOR.'hello_world.php',
182+
'line' => 1,
183+
'src_context' => -1,
184+
],
185+
];
186+
187+
yield 'php file excerpt with selected line and custom source context' => [
188+
'expected' => <<<'HTML'
189+
<ol start="2"><li class="selected"><a class="anchor" id="line3"></a><code><span style="color: #0000BB"></span><span style="color: #007700">echo </span><span style="color: #DD0000">'Hello'</span><span style="color: #007700">;</span></code></li>
190+
<li><a class="anchor" id="line4"></a><code><span style="color: #007700">echo </span><span style="color: #DD0000">'World!'</span><span style="color: #007700">;</span></code></li>
191+
<li><a class="anchor" id="line5"></a><code><span style="color: #007700"></span></code></li></ol>
192+
HTML,
193+
'data' => [
194+
'file_path' => $fixturesPath.\DIRECTORY_SEPARATOR.'hello_world.php',
195+
'line' => 3,
196+
'src_context' => 1,
197+
],
198+
];
199+
200+
yield 'php file excerpt with out of bound selected line' => [
201+
'expected' => <<<'HTML'
202+
<ol start="99"></ol>
203+
HTML,
204+
'data' => [
205+
'file_path' => $fixturesPath.\DIRECTORY_SEPARATOR.'hello_world.php',
206+
'line' => 100,
207+
'src_context' => 1,
208+
],
209+
];
210+
211+
yield 'json file' => [
212+
'expected' => <<<'HTML'
213+
<ol start="1"><li><a class="anchor" id="line1"></a><code>[</code></li>
214+
<li><a class="anchor" id="line2"></a><code> "Hello",</code></li>
215+
<li><a class="anchor" id="line3"></a><code> "World!"</code></li>
216+
<li><a class="anchor" id="line4"></a><code>]</code></li>
217+
<li><a class="anchor" id="line5"></a><code></code></li></ol>
218+
HTML,
219+
'data' => [
220+
'file_path' => $fixturesPath.\DIRECTORY_SEPARATOR.'hello_world.json',
221+
'line' => 0,
222+
'src_context' => 3,
223+
],
224+
];
225+
}
226+
132227
public function testFormatFileFromTextIntegration()
133228
{
134229
$template = <<<'TWIG'

0 commit comments

Comments
 (0)