Skip to content

Commit a72def9

Browse files
Merge branch '6.4' into 7.3
* 6.4: [Serializer] Fix unknown type in denormalization errors when union type used in constructor [HttpKernel] Handle an array vary header in the http cache store for write [Console] Fix handling of `\E` in Bash completion
2 parents 8d9d7d3 + 1a60616 commit a72def9

File tree

6 files changed

+214
-12
lines changed

6 files changed

+214
-12
lines changed

src/Symfony/Component/Console/Resources/completion.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ _sf_{{ COMMAND_NAME }}() {
3737

3838
local completecmd=("$sf_cmd" "_complete" "--no-interaction" "-sbash" "-c$cword" "-a{{ VERSION }}")
3939
for w in ${words[@]}; do
40-
w=$(printf -- '%b' "$w")
40+
w="${w//\\\\/\\}"
4141
# remove quotes from typed values
4242
quote="${w:0:1}"
4343
if [ "$quote" == \' ]; then

src/Symfony/Component/HttpKernel/HttpCache/Store.php

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,9 @@ public function write(Request $request, Response $response): string
206206

207207
// read existing cache entries, remove non-varying, and add this one to the list
208208
$entries = [];
209-
$vary = $response->headers->get('vary');
209+
$vary = implode(', ', $response->headers->all('vary'));
210210
foreach ($this->getMetadata($key) as $entry) {
211-
if (!isset($entry[1]['vary'][0])) {
212-
$entry[1]['vary'] = [''];
213-
}
214-
215-
if ($entry[1]['vary'][0] != $vary || !$this->requestsMatch($vary ?? '', $entry[0], $storedEnv)) {
211+
if (!$this->requestsMatch($vary ?? '', $entry[0], $storedEnv)) {
216212
$entries[] = $entry;
217213
}
218214
}
@@ -278,7 +274,7 @@ public function invalidate(Request $request): void
278274
*/
279275
private function requestsMatch(?string $vary, array $env1, array $env2): bool
280276
{
281-
if (!$vary) {
277+
if ('' === ($vary ?? '')) {
282278
return true;
283279
}
284280

src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,127 @@ public function testLoadsBodyEval()
348348
$this->assertSame($content, $response->getContent());
349349
}
350350

351+
/**
352+
* Basic case when the second header has a different value.
353+
* Both responses should be cached
354+
*/
355+
public function testWriteWithMultipleVaryAndCachedAllResponse()
356+
{
357+
$req1 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_BAR' => 'bar']);
358+
$content = str_repeat('a', 24).'b'.str_repeat('a', 24);
359+
$res1 = new Response($content, 200, ['vary' => ['Foo', 'Bar'], 'X-Body-Eval' => 'SSI']);
360+
$this->store->write($req1, $res1);
361+
362+
$responseLook = $this->store->lookup($req1);
363+
$this->assertSame($content, $responseLook->getContent());
364+
365+
$req2 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_BAR' => 'foobar']);
366+
$content2 = str_repeat('b', 24).'a'.str_repeat('b', 24);
367+
$res2 = new Response($content2, 200, ['vary' => ['Foo', 'Bar'], 'X-Body-Eval' => 'SSI']);
368+
$this->store->write($req2, $res2);
369+
370+
$responseLook = $this->store->lookup($req2);
371+
$this->assertSame($content2, $responseLook->getContent());
372+
373+
$responseLook = $this->store->lookup($req1);
374+
$this->assertSame($content, $responseLook->getContent());
375+
}
376+
377+
/**
378+
* Basic case when the second header has the same value on both requests.
379+
* The last response should be cached
380+
*/
381+
public function testWriteWithMultipleVaryAndCachedLastResponse()
382+
{
383+
$req1 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_BAR' => 'bar']);
384+
$content = str_repeat('a', 24).'b'.str_repeat('a', 24);
385+
$res1 = new Response($content, 200, ['vary' => ['Foo', 'Bar'], 'X-Body-Eval' => 'SSI']);
386+
$this->store->write($req1, $res1);
387+
388+
$responseLook = $this->store->lookup($req1);
389+
$this->assertSame($content, $responseLook->getContent());
390+
391+
$req2 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_BAR' => 'bar']);
392+
$content2 = str_repeat('b', 24).'a'.str_repeat('b', 24);
393+
$res2 = new Response($content2, 200, ['vary' => ['Foo', 'Bar'], 'X-Body-Eval' => 'SSI']);
394+
$this->store->write($req2, $res2);
395+
396+
$responseLook = $this->store->lookup($req2);
397+
$this->assertSame($content2, $responseLook->getContent());
398+
399+
$responseLook = $this->store->lookup($req1);
400+
$this->assertSame($content2, $responseLook->getContent());
401+
}
402+
403+
/**
404+
* Case when a vary value has been removed.
405+
* Both responses should be cached
406+
*/
407+
public function testWriteWithChangingVary()
408+
{
409+
$req1 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_BAR' => 'bar']);
410+
$content = str_repeat('a', 24).'b'.str_repeat('a', 24);
411+
$res1 = new Response($content, 200, ['vary' => ['Foo', 'bar', 'foobar'], 'X-Body-Eval' => 'SSI']);
412+
$this->store->write($req1, $res1);
413+
414+
$req2 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_FOOBAR' => 'bar']);
415+
$content2 = str_repeat('b', 24).'a'.str_repeat('b', 24);
416+
$res2 = new Response($content2, 200, ['vary' => ['Foo', 'foobar'], 'X-Body-Eval' => 'SSI']);
417+
$this->store->write($req2, $res2);
418+
419+
$responseLook = $this->store->lookup($req2);
420+
$this->assertSame($content2, $responseLook->getContent());
421+
422+
$responseLook = $this->store->lookup($req1);
423+
$this->assertSame($content, $responseLook->getContent());
424+
}
425+
426+
/**
427+
* Case when a vary value has been removed and headers of the new vary list are the same.
428+
* The last response should be cached
429+
*/
430+
public function testWriteWithRemoveVaryAndAllHeadersOnTheList()
431+
{
432+
$req1 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_FOOBAR' => 'bar',]);
433+
$content = str_repeat('a', 24).'b'.str_repeat('a', 24);
434+
$res1 = new Response($content, 200, ['vary' => ['Foo', 'bar', 'foobar'], 'X-Body-Eval' => 'SSI']);
435+
$this->store->write($req1, $res1);
436+
437+
$req2 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_FOOBAR' => 'bar']);
438+
$content2 = str_repeat('b', 24).'a'.str_repeat('b', 24);
439+
$res2 = new Response($content2, 200, ['vary' => ['Foo', 'foobar'], 'X-Body-Eval' => 'SSI']);
440+
$this->store->write($req2, $res2);
441+
442+
$responseLook = $this->store->lookup($req2);
443+
$this->assertSame($content2, $responseLook->getContent());
444+
445+
$responseLook = $this->store->lookup($req1);
446+
$this->assertSame($content2, $responseLook->getContent());
447+
}
448+
449+
/**
450+
* Case when a vary value has been added and headers of the new vary list are the same.
451+
* The last response should be cached
452+
*/
453+
public function testWriteWithAddingVaryAndAllHeadersOnTheList()
454+
{
455+
$req1 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_FOOBAR' => 'bar']);
456+
$content = str_repeat('a', 24).'b'.str_repeat('a', 24);
457+
$res1 = new Response($content, 200, ['vary' => ['Foo', 'foobar'], 'X-Body-Eval' => 'SSI']);
458+
$this->store->write($req1, $res1);
459+
460+
$req2 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_BAR' => 'foobar', 'HTTP_FOOBAR' => 'bar']);
461+
$content2 = str_repeat('b', 24).'a'.str_repeat('b', 24);
462+
$res2 = new Response($content2, 200, ['vary' => ['Foo', 'bar', 'foobar'], 'X-Body-Eval' => 'SSI']);
463+
$this->store->write($req2, $res2);
464+
465+
$responseLook = $this->store->lookup($req2);
466+
$this->assertSame($content2, $responseLook->getContent());
467+
468+
$responseLook = $this->store->lookup($req1);
469+
$this->assertSame($content, $responseLook->getContent());
470+
}
471+
351472
protected function storeSimpleEntry($path = null, $headers = [])
352473
{
353474
$path ??= '/test';

src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -396,16 +396,22 @@ protected function instantiateObject(array &$data, string $class, array &$contex
396396
continue;
397397
}
398398

399-
$constructorParameterType = 'unknown';
399+
$constructorParameterTypes = [];
400400
$reflectionType = $constructorParameter->getType();
401-
if ($reflectionType instanceof \ReflectionNamedType) {
402-
$constructorParameterType = $reflectionType->getName();
401+
if ($reflectionType instanceof \ReflectionUnionType) {
402+
foreach ($reflectionType->getTypes() as $reflectionType) {
403+
$constructorParameterTypes[] = (string) $reflectionType;
404+
}
405+
} elseif ($reflectionType instanceof \ReflectionType) {
406+
$constructorParameterTypes[] = (string) $reflectionType;
407+
} else {
408+
$constructorParameterTypes[] = 'unknown';
403409
}
404410

405411
$exception = NotNormalizableValueException::createForUnexpectedDataType(
406412
\sprintf('Failed to create object because the class misses the "%s" property.', $constructorParameter->name),
407413
null,
408-
[$constructorParameterType],
414+
$constructorParameterTypes,
409415
$attributeContext['deserialization_path'] ?? null,
410416
true
411417
);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Serializer\Tests\Fixtures;
13+
14+
/**
15+
* @author Dmitrii <github.com/d-mitrofanov-v>
16+
*/
17+
class DummyWithUnion
18+
{
19+
public function __construct(
20+
public int|float $value,
21+
public string|int $value2,
22+
) {
23+
}
24+
}

src/Symfony/Component/Serializer/Tests/SerializerTest.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumConstructor;
6868
use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumProperty;
6969
use Symfony\Component\Serializer\Tests\Fixtures\DummyWithObjectOrNull;
70+
use Symfony\Component\Serializer\Tests\Fixtures\DummyWithUnion;
7071
use Symfony\Component\Serializer\Tests\Fixtures\DummyWithVariadicParameter;
7172
use Symfony\Component\Serializer\Tests\Fixtures\FalseBuiltInDummy;
7273
use Symfony\Component\Serializer\Tests\Fixtures\FooImplementationDummy;
@@ -1429,6 +1430,60 @@ public function testCollectDenormalizationErrorsWithInvalidConstructorTypes()
14291430
$this->assertSame($expected, $exceptionsAsArray);
14301431
}
14311432

1433+
public function testCollectDenormalizationErrorsWithUnionConstructorTypes()
1434+
{
1435+
$json = '{}';
1436+
1437+
$serializer = new Serializer(
1438+
[new ObjectNormalizer()],
1439+
['json' => new JsonEncoder()]
1440+
);
1441+
1442+
try {
1443+
$serializer->deserialize(
1444+
$json,
1445+
DummyWithUnion::class,
1446+
'json',
1447+
[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true]
1448+
);
1449+
1450+
$this->fail();
1451+
} catch (\Throwable $th) {
1452+
$this->assertInstanceOf(PartialDenormalizationException::class, $th);
1453+
}
1454+
1455+
$exceptionsAsArray = array_map(fn (NotNormalizableValueException $e): array => [
1456+
'currentType' => $e->getCurrentType(),
1457+
'expectedTypes' => $e->getExpectedTypes(),
1458+
'path' => $e->getPath(),
1459+
'useMessageForUser' => $e->canUseMessageForUser(),
1460+
'message' => $e->getMessage(),
1461+
], $th->getErrors());
1462+
1463+
$expected = [
1464+
[
1465+
'currentType' => 'null',
1466+
'expectedTypes' => [
1467+
'int', 'float',
1468+
],
1469+
'path' => 'value',
1470+
'useMessageForUser' => true,
1471+
'message' => 'Failed to create object because the class misses the "value" property.',
1472+
],
1473+
[
1474+
'currentType' => 'null',
1475+
'expectedTypes' => [
1476+
'string', 'int',
1477+
],
1478+
'path' => 'value2',
1479+
'useMessageForUser' => true,
1480+
'message' => 'Failed to create object because the class misses the "value2" property.',
1481+
],
1482+
];
1483+
1484+
$this->assertSame($expected, $exceptionsAsArray);
1485+
}
1486+
14321487
public function testCollectDenormalizationErrorsWithEnumConstructor()
14331488
{
14341489
$serializer = new Serializer(

0 commit comments

Comments
 (0)