Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0d25263

Browse files
committed
bug #50125 [HttpKernel] Fix handling of MapRequest* attributes (nicolas-grekas)
This PR was merged into the 6.3 branch. Discussion ---------- [HttpKernel] Fix handling of `MapRequest*` attributes | Q | A | ------------- | --- | Branch? | 6.3 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | Fix #50120 | License | MIT | Doc PR | - Commits ------- 5b0b85a [HttpKernel] Fix handling of `MapRequest*` attributes
2 parents 6042c00 + 5b0b85a commit 0d25263

File tree

6 files changed

+123
-33
lines changed

6 files changed

+123
-33
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,8 @@ public function load(array $configs, ContainerBuilder $container)
381381
);
382382

383383
$container->getDefinition('argument_resolver.request_payload')
384-
->replaceArgument(0, new Reference('.argument_resolver.request_payload.no_serializer', ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE));
384+
->replaceArgument(0, new Reference('.argument_resolver.request_payload.no_serializer', ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE))
385+
->clearTag('kernel.event_subscriber');
385386

386387
$container->removeDefinition('console.command.serializer_debug');
387388
}

src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070
service('translator')->nullOnInvalid(),
7171
])
7272
->tag('controller.targeted_value_resolver', ['name' => RequestPayloadValueResolver::class])
73+
->tag('kernel.event_subscriber')
74+
->lazy()
7375

7476
->set('argument_resolver.request_attribute', RequestAttributeValueResolver::class)
7577
->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => RequestAttributeValueResolver::class])

src/Symfony/Component/HttpKernel/Attribute/MapQueryString.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\HttpKernel\Attribute;
1313

1414
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver;
15+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
1516
use Symfony\Component\Validator\Constraints\GroupSequence;
1617

1718
/**
@@ -22,6 +23,8 @@
2223
#[\Attribute(\Attribute::TARGET_PARAMETER)]
2324
class MapQueryString extends ValueResolver
2425
{
26+
public ArgumentMetadata $metadata;
27+
2528
public function __construct(
2629
public readonly array $serializationContext = [],
2730
public readonly string|GroupSequence|array|null $validationGroups = null,

src/Symfony/Component/HttpKernel/Attribute/MapRequestPayload.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\HttpKernel\Attribute;
1313

1414
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver;
15+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
1516
use Symfony\Component\Validator\Constraints\GroupSequence;
1617

1718
/**
@@ -22,6 +23,8 @@
2223
#[\Attribute(\Attribute::TARGET_PARAMETER)]
2324
class MapRequestPayload extends ValueResolver
2425
{
26+
public ArgumentMetadata $metadata;
27+
2528
public function __construct(
2629
public readonly array|string|null $acceptFormat = null,
2730
public readonly array $serializationContext = [],

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

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@
1111

1212
namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver;
1313

14+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1415
use Symfony\Component\HttpFoundation\Request;
1516
use Symfony\Component\HttpFoundation\Response;
1617
use Symfony\Component\HttpKernel\Attribute\MapQueryString;
1718
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
1819
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
1920
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
21+
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
2022
use Symfony\Component\HttpKernel\Exception\HttpException;
23+
use Symfony\Component\HttpKernel\KernelEvents;
2124
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
2225
use Symfony\Component\Serializer\Exception\PartialDenormalizationException;
2326
use Symfony\Component\Serializer\Exception\UnsupportedFormatException;
@@ -31,8 +34,10 @@
3134

3235
/**
3336
* @author Konstantin Myakshin <[email protected]>
37+
*
38+
* @final
3439
*/
35-
final class RequestPayloadValueResolver implements ValueResolverInterface
40+
class RequestPayloadValueResolver implements ValueResolverInterface, EventSubscriberInterface
3641
{
3742
/**
3843
* @see \Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT
@@ -59,24 +64,43 @@ public function __construct(
5964

6065
public function resolve(Request $request, ArgumentMetadata $argument): iterable
6166
{
62-
$payloadMappers = [
63-
MapQueryString::class => ['mapQueryString', Response::HTTP_NOT_FOUND],
64-
MapRequestPayload::class => ['mapRequestPayload', Response::HTTP_UNPROCESSABLE_ENTITY],
65-
];
67+
$attribute = $argument->getAttributesOfType(MapQueryString::class, ArgumentMetadata::IS_INSTANCEOF)[0]
68+
?? $argument->getAttributesOfType(MapRequestPayload::class, ArgumentMetadata::IS_INSTANCEOF)[0]
69+
?? null;
70+
71+
if (!$attribute) {
72+
return [];
73+
}
74+
75+
$attribute->metadata = $argument;
76+
77+
return [$attribute];
78+
}
6679

67-
foreach ($payloadMappers as $mappingAttribute => [$payloadMapper, $validationFailedCode]) {
68-
if (!$attributes = $argument->getAttributesOfType($mappingAttribute, ArgumentMetadata::IS_INSTANCEOF)) {
80+
public function onKernelControllerArguments(ControllerArgumentsEvent $event): void
81+
{
82+
$arguments = $event->getArguments();
83+
84+
foreach ($arguments as $i => $argument) {
85+
if ($argument instanceof MapQueryString) {
86+
$payloadMapper = 'mapQueryString';
87+
$validationFailedCode = Response::HTTP_NOT_FOUND;
88+
} elseif ($argument instanceof MapRequestPayload) {
89+
$payloadMapper = 'mapRequestPayload';
90+
$validationFailedCode = Response::HTTP_UNPROCESSABLE_ENTITY;
91+
} else {
6992
continue;
7093
}
94+
$request = $event->getRequest();
7195

72-
if (!$type = $argument->getType()) {
73-
throw new \LogicException(sprintf('Could not resolve the "$%s" controller argument: argument should be typed.', $argument->getName()));
96+
if (!$type = $argument->metadata->getType()) {
97+
throw new \LogicException(sprintf('Could not resolve the "$%s" controller argument: argument should be typed.', $argument->metadata->getName()));
7498
}
7599

76100
if ($this->validator) {
77101
$violations = new ConstraintViolationList();
78102
try {
79-
$payload = $this->$payloadMapper($request, $type, $attributes[0]);
103+
$payload = $this->$payloadMapper($request, $type, $argument);
80104
} catch (PartialDenormalizationException $e) {
81105
$trans = $this->translator ? $this->translator->trans(...) : fn ($m, $p) => strtr($m, $p);
82106
foreach ($e->getErrors() as $error) {
@@ -92,26 +116,35 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable
92116
}
93117

94118
if (null !== $payload) {
95-
$violations->addAll($this->validator->validate($payload, null, $attributes[0]->validationGroups ?? null));
119+
$violations->addAll($this->validator->validate($payload, null, $argument->validationGroups ?? null));
96120
}
97121

98122
if (\count($violations)) {
99123
throw new HttpException($validationFailedCode, implode("\n", array_map(static fn ($e) => $e->getMessage(), iterator_to_array($violations))), new ValidationFailedException($payload, $violations));
100124
}
101125
} else {
102126
try {
103-
$payload = $this->$payloadMapper($request, $type, $attributes[0]);
127+
$payload = $this->$payloadMapper($request, $type, $argument);
104128
} catch (PartialDenormalizationException $e) {
105129
throw new HttpException($validationFailedCode, implode("\n", array_map(static fn ($e) => $e->getMessage(), $e->getErrors())), $e);
106130
}
107131
}
108132

109-
if (null !== $payload || $argument->isNullable()) {
110-
return [$payload];
133+
if (null === $payload && !$argument->metadata->isNullable()) {
134+
throw new HttpException($validationFailedCode);
111135
}
136+
137+
$arguments[$i] = $payload;
112138
}
113139

114-
return [];
140+
$event->setArguments($arguments);
141+
}
142+
143+
public static function getSubscribedEvents(): array
144+
{
145+
return [
146+
KernelEvents::CONTROLLER_ARGUMENTS => 'onKernelControllerArguments',
147+
];
115148
}
116149

117150
private function mapQueryString(Request $request, string $type, MapQueryString $attribute): ?object

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

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
use Symfony\Component\HttpKernel\Attribute\ValueResolver;
1919
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver;
2020
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
21+
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
2122
use Symfony\Component\HttpKernel\Exception\HttpException;
23+
use Symfony\Component\HttpKernel\HttpKernelInterface;
2224
use Symfony\Component\Serializer\Encoder\JsonEncoder;
2325
use Symfony\Component\Serializer\Encoder\XmlEncoder;
2426
use Symfony\Component\Serializer\Exception\PartialDenormalizationException;
@@ -45,10 +47,14 @@ public function testNotTypedArgument()
4547
]);
4648
$request = Request::create('/', 'POST', server: ['HTTP_CONTENT_TYPE' => 'application/json']);
4749

50+
$kernel = $this->createMock(HttpKernelInterface::class);
51+
$arguments = $resolver->resolve($request, $argument);
52+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
53+
4854
$this->expectException(\LogicException::class);
4955
$this->expectExceptionMessage('Could not resolve the "$notTyped" controller argument: argument should be typed.');
5056

51-
$resolver->resolve($request, $argument);
57+
$resolver->onKernelControllerArguments($event);
5258
}
5359

5460
public function testWithoutValidatorAndCouldNotDenormalize()
@@ -63,8 +69,12 @@ public function testWithoutValidatorAndCouldNotDenormalize()
6369
]);
6470
$request = Request::create('/', 'POST', server: ['CONTENT_TYPE' => 'application/json'], content: $content);
6571

72+
$kernel = $this->createMock(HttpKernelInterface::class);
73+
$arguments = $resolver->resolve($request, $argument);
74+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
75+
6676
try {
67-
$resolver->resolve($request, $argument);
77+
$resolver->onKernelControllerArguments($event);
6878
$this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class));
6979
} catch (HttpException $e) {
7080
$this->assertInstanceOf(PartialDenormalizationException::class, $e->getPrevious());
@@ -90,8 +100,12 @@ public function testValidationNotPassed()
90100
]);
91101
$request = Request::create('/', 'POST', server: ['CONTENT_TYPE' => 'application/json'], content: $content);
92102

103+
$kernel = $this->createMock(HttpKernelInterface::class);
104+
$arguments = $resolver->resolve($request, $argument);
105+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
106+
93107
try {
94-
$resolver->resolve($request, $argument);
108+
$resolver->onKernelControllerArguments($event);
95109
$this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class));
96110
} catch (HttpException $e) {
97111
$validationFailedException = $e->getPrevious();
@@ -112,9 +126,12 @@ public function testUnsupportedMedia()
112126
]);
113127
$request = Request::create('/', 'POST', server: ['CONTENT_TYPE' => 'foo/bar'], content: 'foo-bar');
114128

115-
try {
116-
$resolver->resolve($request, $argument);
129+
$kernel = $this->createMock(HttpKernelInterface::class);
130+
$arguments = $resolver->resolve($request, $argument);
131+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
117132

133+
try {
134+
$resolver->onKernelControllerArguments($event);
118135
$this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class));
119136
} catch (HttpException $e) {
120137
$this->assertSame(415, $e->getStatusCode());
@@ -139,7 +156,13 @@ public function testRequestContentValidationPassed()
139156
]);
140157
$request = Request::create('/', 'POST', server: ['CONTENT_TYPE' => 'application/json'], content: $content);
141158

142-
$this->assertEquals($payload, $resolver->resolve($request, $argument)[0]);
159+
$kernel = $this->createMock(HttpKernelInterface::class);
160+
$arguments = $resolver->resolve($request, $argument);
161+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
162+
163+
$resolver->onKernelControllerArguments($event);
164+
165+
$this->assertEquals([$payload], $event->getArguments());
143166
}
144167

145168
public function testQueryStringValidationPassed()
@@ -161,7 +184,13 @@ public function testQueryStringValidationPassed()
161184
]);
162185
$request = Request::create('/', 'GET', $query);
163186

164-
$this->assertEquals($payload, $resolver->resolve($request, $argument)[0]);
187+
$kernel = $this->createMock(HttpKernelInterface::class);
188+
$arguments = $resolver->resolve($request, $argument);
189+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
190+
191+
$resolver->onKernelControllerArguments($event);
192+
193+
$this->assertEquals([$payload], $event->getArguments());
165194
}
166195

167196
public function testRequestInputValidationPassed()
@@ -183,7 +212,13 @@ public function testRequestInputValidationPassed()
183212
]);
184213
$request = Request::create('/', 'POST', $input);
185214

186-
$this->assertEquals($payload, $resolver->resolve($request, $argument)[0]);
215+
$kernel = $this->createMock(HttpKernelInterface::class);
216+
$arguments = $resolver->resolve($request, $argument);
217+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
218+
219+
$resolver->onKernelControllerArguments($event);
220+
221+
$this->assertEquals([$payload], $event->getArguments());
187222
}
188223

189224
/**
@@ -202,10 +237,13 @@ public function testAcceptFormatPassed(mixed $acceptFormat, string $contentType,
202237
MapRequestPayload::class => new MapRequestPayload(acceptFormat: $acceptFormat),
203238
]);
204239

205-
$resolved = $resolver->resolve($request, $argument);
240+
$kernel = $this->createMock(HttpKernelInterface::class);
241+
$arguments = $resolver->resolve($request, $argument);
242+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
243+
244+
$resolver->onKernelControllerArguments($event);
206245

207-
$this->assertCount(1, $resolved);
208-
$this->assertEquals(new RequestPayload(50), $resolved[0]);
246+
$this->assertEquals([new RequestPayload(50)], $event->getArguments());
209247
}
210248

211249
public static function provideMatchedFormatContext(): iterable
@@ -262,9 +300,12 @@ public function testAcceptFormatNotPassed(mixed $acceptFormat, string $contentTy
262300
MapRequestPayload::class => new MapRequestPayload(acceptFormat: $acceptFormat),
263301
]);
264302

265-
try {
266-
$resolver->resolve($request, $argument);
303+
$kernel = $this->createMock(HttpKernelInterface::class);
304+
$arguments = $resolver->resolve($request, $argument);
305+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
267306

307+
try {
308+
$resolver->onKernelControllerArguments($event);
268309
$this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class));
269310
} catch (HttpException $e) {
270311
$this->assertSame(415, $e->getStatusCode());
@@ -330,10 +371,13 @@ public function testValidationGroupsPassed(string $method, ValueResolver $attrib
330371
$attribute::class => $attribute,
331372
]);
332373

333-
$resolved = $resolver->resolve($request, $argument);
374+
$kernel = $this->createMock(HttpKernelInterface::class);
375+
$arguments = $resolver->resolve($request, $argument);
376+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
334377

335-
$this->assertCount(1, $resolved);
336-
$this->assertEquals($payload, $resolved[0]);
378+
$resolver->onKernelControllerArguments($event);
379+
380+
$this->assertEquals([$payload], $event->getArguments());
337381
}
338382

339383
/**
@@ -352,8 +396,12 @@ public function testValidationGroupsNotPassed(string $method, ValueResolver $att
352396
]);
353397
$request = Request::create('/', $method, $input);
354398

399+
$kernel = $this->createMock(HttpKernelInterface::class);
400+
$arguments = $resolver->resolve($request, $argument);
401+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
402+
355403
try {
356-
$resolver->resolve($request, $argument);
404+
$resolver->onKernelControllerArguments($event);
357405
$this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class));
358406
} catch (HttpException $e) {
359407
$validationFailedException = $e->getPrevious();

0 commit comments

Comments
 (0)