Skip to content

Commit ae1ceaa

Browse files
committed
Merge branch '7.2' into 7.3
* 7.2: [Serializer] Handle invalid mapping type property type [Config] Do not generate unreachable configuration paths [WebProfilerBundle] Fix missing indent on non php files opended in the profiler
2 parents e7551d5 + ac9f913 commit ae1ceaa

File tree

18 files changed

+593
-46
lines changed

18 files changed

+593
-46
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[
2+
"Hello",
3+
"World!"
4+
]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
3+
echo 'Hello';
4+
echo 'World!';

src/Symfony/Bundle/WebProfilerBundle/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

src/Symfony/Bundle/WebProfilerBundle/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'

src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,13 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n
126126
$class->addRequire($childClass);
127127
$this->classes[] = $childClass;
128128

129+
$nodeTypes = $this->getParameterTypes($node);
130+
$paramType = $this->getParamType($nodeTypes);
131+
129132
$hasNormalizationClosures = $this->hasNormalizationClosures($node);
130133
$comment = $this->getComment($node);
131-
if ($hasNormalizationClosures) {
132-
$comment = \sprintf(" * @template TValue\n * @param TValue \$value\n%s", $comment);
134+
if ($hasNormalizationClosures && 'array' !== $paramType) {
135+
$comment = \sprintf(" * @template TValue of %s\n * @param TValue \$value\n%s", $paramType, $comment);
133136
$comment .= \sprintf(' * @return %s|$this'."\n", $childClass->getFqcn());
134137
$comment .= \sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn());
135138
}
@@ -141,8 +144,7 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n
141144
$node->getName(),
142145
$this->getType($childClass->getFqcn(), $hasNormalizationClosures)
143146
);
144-
$nodeTypes = $this->getParameterTypes($node);
145-
$body = $hasNormalizationClosures ? '
147+
$body = $hasNormalizationClosures && 'array' !== $paramType ? '
146148
COMMENTpublic function NAME(PARAM_TYPE $value = []): CLASS|static
147149
{
148150
if (!\is_array($value)) {
@@ -177,7 +179,7 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n
177179
'COMMENT' => $comment,
178180
'PROPERTY' => $property->getName(),
179181
'CLASS' => $childClass->getFqcn(),
180-
'PARAM_TYPE' => \in_array('mixed', $nodeTypes, true) ? 'mixed' : implode('|', $nodeTypes),
182+
'PARAM_TYPE' => $paramType,
181183
]);
182184

183185
$this->buildNode($node, $childClass, $this->getSubNamespace($childClass));
@@ -217,10 +219,11 @@ private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuild
217219

218220
$nodeParameterTypes = $this->getParameterTypes($node);
219221
$prototypeParameterTypes = $this->getParameterTypes($prototype);
222+
$noKey = null === $key = $node->getKeyAttribute();
220223
if (!$prototype instanceof ArrayNode || ($prototype instanceof PrototypedArrayNode && $prototype->getPrototype() instanceof ScalarNode)) {
221224
$class->addUse(ParamConfigurator::class);
222225
$property = $class->addProperty($node->getName());
223-
if (null === $key = $node->getKeyAttribute()) {
226+
if ($noKey) {
224227
// This is an array of values; don't use singular name
225228
$nodeTypesWithoutArray = array_filter($nodeParameterTypes, static fn ($type) => 'array' !== $type);
226229
$body = '
@@ -241,7 +244,7 @@ public function NAME(PARAM_TYPE $value): static
241244
'PROPERTY' => $property->getName(),
242245
'PROTOTYPE_TYPE' => implode('|', $prototypeParameterTypes),
243246
'EXTRA_TYPE' => $nodeTypesWithoutArray ? '|'.implode('|', $nodeTypesWithoutArray) : '',
244-
'PARAM_TYPE' => \in_array('mixed', $nodeParameterTypes, true) ? 'mixed' : 'ParamConfigurator|'.implode('|', $nodeParameterTypes),
247+
'PARAM_TYPE' => $this->getParamType($nodeParameterTypes, true),
245248
]);
246249
} else {
247250
$body = '
@@ -258,7 +261,7 @@ public function NAME(string $VAR, TYPE $VALUE): static
258261

259262
$class->addMethod($methodName, $body, [
260263
'PROPERTY' => $property->getName(),
261-
'TYPE' => \in_array('mixed', $prototypeParameterTypes, true) ? 'mixed' : 'ParamConfigurator|'.implode('|', $prototypeParameterTypes),
264+
'TYPE' => $this->getParamType($prototypeParameterTypes, true),
262265
'VAR' => '' === $key ? 'key' : $key,
263266
'VALUE' => 'value' === $key ? 'data' : 'value',
264267
]);
@@ -279,18 +282,27 @@ public function NAME(string $VAR, TYPE $VALUE): static
279282
$this->getType($childClass->getFqcn().'[]', $hasNormalizationClosures)
280283
);
281284

285+
$paramType = $this->getParamType($noKey ? $nodeParameterTypes : $prototypeParameterTypes);
286+
282287
$comment = $this->getComment($node);
288+
<<<<<<< HEAD
283289
if ($hasNormalizationClosures) {
284290
$comment = \sprintf(" * @template TValue\n * @param TValue \$value\n%s", $comment);
285291
$comment .= \sprintf(' * @return %s|$this'."\n", $childClass->getFqcn());
286292
$comment .= \sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn());
293+
=======
294+
if ($hasNormalizationClosures && 'array' !== $paramType) {
295+
$comment = sprintf(" * @template TValue of %s\n * @param TValue \$value\n%s", $paramType, $comment);
296+
$comment .= sprintf(' * @return %s|$this'."\n", $childClass->getFqcn());
297+
$comment .= sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn());
298+
>>>>>>> 100c683018d ([Config] Do not generate unreachable configuration paths)
287299
}
288300
if ('' !== $comment) {
289301
$comment = "/**\n$comment*/\n";
290302
}
291303

292-
if (null === $key = $node->getKeyAttribute()) {
293-
$body = $hasNormalizationClosures ? '
304+
if ($noKey) {
305+
$body = $hasNormalizationClosures && 'array' !== $paramType ? '
294306
COMMENTpublic function NAME(PARAM_TYPE $value = []): CLASS|static
295307
{
296308
$this->_usedProperties[\'PROPERTY\'] = true;
@@ -312,10 +324,10 @@ public function NAME(string $VAR, TYPE $VALUE): static
312324
'COMMENT' => $comment,
313325
'PROPERTY' => $property->getName(),
314326
'CLASS' => $childClass->getFqcn(),
315-
'PARAM_TYPE' => \in_array('mixed', $nodeParameterTypes, true) ? 'mixed' : implode('|', $nodeParameterTypes),
327+
'PARAM_TYPE' => $paramType,
316328
]);
317329
} else {
318-
$body = $hasNormalizationClosures ? '
330+
$body = $hasNormalizationClosures && 'array' !== $paramType ? '
319331
COMMENTpublic function NAME(string $VAR, PARAM_TYPE $VALUE = []): CLASS|static
320332
{
321333
if (!\is_array($VALUE)) {
@@ -351,7 +363,7 @@ public function NAME(string $VAR, TYPE $VALUE): static
351363
'CLASS' => $childClass->getFqcn(),
352364
'VAR' => '' === $key ? 'key' : $key,
353365
'VALUE' => 'value' === $key ? 'data' : 'value',
354-
'PARAM_TYPE' => \in_array('mixed', $prototypeParameterTypes, true) ? 'mixed' : implode('|', $prototypeParameterTypes),
366+
'PARAM_TYPE' => $paramType,
355367
]);
356368
}
357369

@@ -596,4 +608,9 @@ private function getType(string $classType, bool $hasNormalizationClosures): str
596608
{
597609
return $classType.($hasNormalizationClosures ? '|scalar' : '');
598610
}
611+
612+
private function getParamType(array $types, bool $withParamConfigurator = false): string
613+
{
614+
return \in_array('mixed', $types, true) ? 'mixed' : ($withParamConfigurator ? 'ParamConfigurator|' : '').implode('|', $types);
615+
}
599616
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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+
use Symfony\Config\ArrayValuesConfig;
13+
14+
return static function (ArrayValuesConfig $config) {
15+
$config->transports('foo')->dsn('bar');
16+
$config->transports('bar', ['dsn' => 'foobar']);
17+
18+
$config->errorPages()->withTrace(false);
19+
};

0 commit comments

Comments
 (0)