Skip to content

Commit b210a7d

Browse files
committed
Merge branch '6.4' into 7.1
* 6.4: [BeanstalkMessenger] Round delay to an integer to avoid deprecation warning [PropertyInfo] Fix interface handling in `PhpStanTypeHelper` [HttpClient] Test POST to GET redirects [HttpKernel] Denormalize request data using the csv format when using "#[MapQueryString]" or "#[MapRequestPayload]" (except for content data) fix: preserve and nowrap in profiler code highlighting
2 parents 08f9e58 + c78a9ec commit b210a7d

File tree

10 files changed

+249
-7
lines changed

10 files changed

+249
-7
lines changed

src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/open.css.twig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#source .source-content ol li {
4141
margin: 0 0 2px 0;
4242
padding-left: 5px;
43+
white-space: preserve nowrap;
4344
}
4445
#source .source-content ol li::marker {
4546
color: var(--color-muted);

src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,4 +678,26 @@ public function testHeadRequestWithClosureBody()
678678
$this->assertIsArray($vars);
679679
$this->assertSame('HEAD', $vars['REQUEST_METHOD']);
680680
}
681+
682+
/**
683+
* @testWith [301]
684+
* [302]
685+
* [303]
686+
*/
687+
public function testPostToGetRedirect(int $status)
688+
{
689+
$p = TestHttpServer::start(8067);
690+
691+
try {
692+
$client = $this->getHttpClient(__FUNCTION__);
693+
694+
$response = $client->request('POST', 'http://localhost:8057/custom?status=' . $status . '&headers[]=Location%3A%20%2F');
695+
$body = $response->toArray();
696+
} finally {
697+
$p->stop();
698+
}
699+
700+
$this->assertSame('GET', $body['REQUEST_METHOD']);
701+
$this->assertSame('/', $body['REQUEST_URI']);
702+
}
681703
}

src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,9 @@
4646
class RequestPayloadValueResolver implements ValueResolverInterface, EventSubscriberInterface
4747
{
4848
/**
49-
* @see \Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT
5049
* @see DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS
5150
*/
5251
private const CONTEXT_DENORMALIZE = [
53-
'disable_type_enforcement' => true,
5452
'collect_denormalization_errors' => true,
5553
];
5654

@@ -189,7 +187,7 @@ private function mapQueryString(Request $request, ArgumentMetadata $argument, Ma
189187
return null;
190188
}
191189

192-
return $this->serializer->denormalize($data, $argument->getType(), null, $attribute->serializationContext + self::CONTEXT_DENORMALIZE + ['filter_bool' => true]);
190+
return $this->serializer->denormalize($data, $argument->getType(), 'csv', $attribute->serializationContext + self::CONTEXT_DENORMALIZE + ['filter_bool' => true]);
193191
}
194192

195193
private function mapRequestPayload(Request $request, ArgumentMetadata $argument, MapRequestPayload $attribute): object|array|null
@@ -209,7 +207,7 @@ private function mapRequestPayload(Request $request, ArgumentMetadata $argument,
209207
}
210208

211209
if ($data = $request->request->all()) {
212-
return $this->serializer->denormalize($data, $type, null, $attribute->serializationContext + self::CONTEXT_DENORMALIZE + ('form' === $format ? ['filter_bool' => true] : []));
210+
return $this->serializer->denormalize($data, $type, 'csv', $attribute->serializationContext + self::CONTEXT_DENORMALIZE + ('form' === $format ? ['filter_bool' => true] : []));
213211
}
214212

215213
if ('' === $data = $request->getContent()) {

src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Symfony\Component\HttpKernel\Exception\HttpException;
2323
use Symfony\Component\HttpKernel\Exception\NearMissValueResolverException;
2424
use Symfony\Component\HttpKernel\HttpKernelInterface;
25+
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
2526
use Symfony\Component\Serializer\Encoder\JsonEncoder;
2627
use Symfony\Component\Serializer\Encoder\XmlEncoder;
2728
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
@@ -395,6 +396,38 @@ public function testQueryStringValidationPassed()
395396
$this->assertEquals([$payload], $event->getArguments());
396397
}
397398

399+
public function testQueryStringParameterTypeMismatch()
400+
{
401+
$query = ['price' => 'not a float'];
402+
403+
$normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor());
404+
$serializer = new Serializer([$normalizer], ['json' => new JsonEncoder()]);
405+
406+
$validator = $this->createMock(ValidatorInterface::class);
407+
$validator->expects($this->never())->method('validate');
408+
409+
$resolver = new RequestPayloadValueResolver($serializer, $validator);
410+
411+
$argument = new ArgumentMetadata('invalid', RequestPayload::class, false, false, null, false, [
412+
MapQueryString::class => new MapQueryString(),
413+
]);
414+
415+
$request = Request::create('/', 'GET', $query);
416+
417+
$kernel = $this->createMock(HttpKernelInterface::class);
418+
$arguments = $resolver->resolve($request, $argument);
419+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
420+
421+
try {
422+
$resolver->onKernelControllerArguments($event);
423+
$this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class));
424+
} catch (HttpException $e) {
425+
$validationFailedException = $e->getPrevious();
426+
$this->assertInstanceOf(ValidationFailedException::class, $validationFailedException);
427+
$this->assertSame('This value should be of type float.', $validationFailedException->getViolations()[0]->getMessage());
428+
}
429+
}
430+
398431
public function testRequestInputValidationPassed()
399432
{
400433
$input = ['price' => '50'];
@@ -457,6 +490,38 @@ public function testRequestArrayDenormalization()
457490
$this->assertEquals([$payload], $event->getArguments());
458491
}
459492

493+
public function testRequestInputTypeMismatch()
494+
{
495+
$input = ['price' => 'not a float'];
496+
497+
$normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor());
498+
$serializer = new Serializer([$normalizer], ['json' => new JsonEncoder()]);
499+
500+
$validator = $this->createMock(ValidatorInterface::class);
501+
$validator->expects($this->never())->method('validate');
502+
503+
$resolver = new RequestPayloadValueResolver($serializer, $validator);
504+
505+
$argument = new ArgumentMetadata('invalid', RequestPayload::class, false, false, null, false, [
506+
MapRequestPayload::class => new MapRequestPayload(),
507+
]);
508+
509+
$request = Request::create('/', 'POST', $input);
510+
511+
$kernel = $this->createMock(HttpKernelInterface::class);
512+
$arguments = $resolver->resolve($request, $argument);
513+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
514+
515+
try {
516+
$resolver->onKernelControllerArguments($event);
517+
$this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class));
518+
} catch (HttpException $e) {
519+
$validationFailedException = $e->getPrevious();
520+
$this->assertInstanceOf(ValidationFailedException::class, $validationFailedException);
521+
$this->assertSame('This value should be of type float.', $validationFailedException->getViolations()[0]->getMessage());
522+
}
523+
}
524+
460525
public function testItThrowsOnMissingAttributeType()
461526
{
462527
$serializer = new Serializer();

src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/ConnectionTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,4 +330,25 @@ public function testSendWhenABeanstalkdExceptionOccurs()
330330

331331
$connection->send($body, $headers, $delay);
332332
}
333+
334+
public function testSendWithRoundedDelay()
335+
{
336+
$tube = 'xyz';
337+
$body = 'foo';
338+
$headers = ['test' => 'bar'];
339+
$delay = 920;
340+
$expectedDelay = 0;
341+
342+
$client = $this->createMock(PheanstalkInterface::class);
343+
$client->expects($this->once())->method('useTube')->with($tube)->willReturn($client);
344+
$client->expects($this->once())->method('put')->with(
345+
$this->anything(),
346+
$this->anything(),
347+
$expectedDelay,
348+
$this->anything(),
349+
);
350+
351+
$connection = new Connection(['tube_name' => $tube], $client);
352+
$connection->send($body, $headers, $delay);
353+
}
333354
}

src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/Connection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public function send(string $body, array $headers, int $delay = 0): string
124124
$job = $this->client->useTube($this->tube)->put(
125125
$message,
126126
PheanstalkInterface::DEFAULT_PRIORITY,
127-
$delay / 1000,
127+
(int) ($delay / 1000),
128128
$this->ttr
129129
);
130130
} catch (Exception $exception) {

src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
1616
use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor;
1717
use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy;
18+
use Symfony\Component\PropertyInfo\Tests\Fixtures\Clazz;
1819
use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummyWithoutDocBlock;
1920
use Symfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue;
2021
use Symfony\Component\PropertyInfo\Tests\Fixtures\DockBlockFallback;
@@ -25,6 +26,8 @@
2526
use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyUnionType;
2627
use Symfony\Component\PropertyInfo\Tests\Fixtures\IntRangeDummy;
2728
use Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy;
29+
use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyGeneric;
30+
use Symfony\Component\PropertyInfo\Tests\Fixtures\IFace;
2831
use Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy;
2932
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php80Dummy;
3033
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php80PromotedDummy;
@@ -968,7 +971,88 @@ public static function allowPrivateAccessProvider(): array
968971

969972
public function testGenericInterface()
970973
{
971-
$this->assertNull($this->extractor->getTypes(Dummy::class, 'genericInterface'));
974+
$this->assertEquals(
975+
[
976+
new Type(
977+
builtinType: Type::BUILTIN_TYPE_OBJECT,
978+
class: \BackedEnum::class,
979+
collectionValueType: new Type(
980+
builtinType: Type::BUILTIN_TYPE_STRING,
981+
)
982+
),
983+
],
984+
$this->extractor->getTypes(Dummy::class, 'genericInterface')
985+
);
986+
}
987+
988+
/**
989+
* @param list<Type> $expectedTypes
990+
* @dataProvider genericsProvider
991+
*/
992+
public function testGenericsLegacy(string $property, array $expectedTypes)
993+
{
994+
$this->assertEquals($expectedTypes, $this->extractor->getTypes(DummyGeneric::class, $property));
995+
}
996+
997+
/**
998+
* @return iterable<array{0: string, 1: list<Type>}>
999+
*/
1000+
public static function genericsProvider(): iterable
1001+
{
1002+
yield [
1003+
'basicClass',
1004+
[
1005+
new Type(
1006+
builtinType: Type::BUILTIN_TYPE_OBJECT,
1007+
class: Clazz::class,
1008+
collectionValueType: new Type(
1009+
builtinType: Type::BUILTIN_TYPE_OBJECT,
1010+
class: Dummy::class,
1011+
)
1012+
),
1013+
],
1014+
];
1015+
yield [
1016+
'nullableClass',
1017+
[
1018+
new Type(
1019+
builtinType: Type::BUILTIN_TYPE_OBJECT,
1020+
class: Clazz::class,
1021+
nullable: true,
1022+
collectionValueType: new Type(
1023+
builtinType: Type::BUILTIN_TYPE_OBJECT,
1024+
class: Dummy::class,
1025+
)
1026+
),
1027+
],
1028+
];
1029+
yield [
1030+
'basicInterface',
1031+
[
1032+
new Type(
1033+
builtinType: Type::BUILTIN_TYPE_OBJECT,
1034+
class: IFace::class,
1035+
collectionValueType: new Type(
1036+
builtinType: Type::BUILTIN_TYPE_OBJECT,
1037+
class: Dummy::class,
1038+
)
1039+
),
1040+
],
1041+
];
1042+
yield [
1043+
'nullableInterface',
1044+
[
1045+
new Type(
1046+
builtinType: Type::BUILTIN_TYPE_OBJECT,
1047+
class: IFace::class,
1048+
nullable: true,
1049+
collectionValueType: new Type(
1050+
builtinType: Type::BUILTIN_TYPE_OBJECT,
1051+
class: Dummy::class,
1052+
)
1053+
),
1054+
],
1055+
];
9721056
}
9731057
}
9741058

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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\PropertyInfo\Tests\Fixtures;
13+
14+
interface IFace {}
15+
16+
class Clazz {}
17+
18+
class DummyGeneric
19+
{
20+
21+
/**
22+
* @var Clazz<Dummy>
23+
*/
24+
public $basicClass;
25+
26+
/**
27+
* @var ?Clazz<Dummy>
28+
*/
29+
public $nullableClass;
30+
31+
/**
32+
* @var IFace<Dummy>
33+
*/
34+
public $basicInterface;
35+
36+
/**
37+
* @var ?IFace<Dummy>
38+
*/
39+
public $nullableInterface;
40+
41+
}

src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ private function extractTypes(TypeNode $node, NameScope $nameScope): array
128128
$collection = $mainType->isCollection() || \is_a($mainType->getClassName(), \Traversable::class, true) || \is_a($mainType->getClassName(), \ArrayAccess::class, true);
129129

130130
// it's safer to fall back to other extractors if the generic type is too abstract
131-
if (!$collection && !class_exists($mainType->getClassName())) {
131+
if (!$collection && !class_exists($mainType->getClassName()) && !interface_exists($mainType->getClassName(), false)) {
132132
return [];
133133
}
134134

src/Symfony/Contracts/HttpClient/Test/Fixtures/web/index.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,16 @@
199199
]);
200200

201201
exit;
202+
203+
case '/custom':
204+
if (isset($_GET['status'])) {
205+
http_response_code((int) $_GET['status']);
206+
}
207+
if (isset($_GET['headers']) && is_array($_GET['headers'])) {
208+
foreach ($_GET['headers'] as $header) {
209+
header($header);
210+
}
211+
}
202212
}
203213

204214
header('Content-Type: application/json', true);

0 commit comments

Comments
 (0)