Skip to content

Commit 791c5f3

Browse files
[HttpKernel] Fix handling of MapRequest* attributes
1 parent 6c28196 commit 791c5f3

File tree

12 files changed

+154
-55
lines changed

12 files changed

+154
-55
lines changed

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

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

372372
$container->getDefinition('argument_resolver.request_payload')
373-
->replaceArgument(0, new Reference('.argument_resolver.request_payload.no_serializer', ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE));
373+
->replaceArgument(0, new Reference('.argument_resolver.request_payload.no_serializer', ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE))
374+
->clearTag('kernel.event_subscriber');
374375

375376
$container->removeDefinition('console.command.serializer_debug');
376377
}

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/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public function process(ContainerBuilder $container)
5656
}
5757

5858
if ($debug) {
59-
$voterServices[] = new Reference($debugVoterServiceId = 'debug.security.voter.'.$voterServiceId);
59+
$voterServices[] = new Reference($debugVoterServiceId = '.debug.security.voter.'.$voterServiceId);
6060

6161
$container
6262
->register($debugVoterServiceId, TraceableVoter::class)

src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,11 @@ public function testThatVotersAreTraceableInDebugMode()
9191
$compilerPass = new AddSecurityVotersPass();
9292
$compilerPass->process($container);
9393

94-
$def1 = $container->getDefinition('debug.security.voter.voter1');
94+
$def1 = $container->getDefinition('.debug.security.voter.voter1');
9595
$this->assertNull($def1->getDecoratedService(), 'voter1: should not be decorated');
9696
$this->assertEquals(new Reference('voter1'), $def1->getArgument(0), 'voter1: wrong argument');
9797

98-
$def2 = $container->getDefinition('debug.security.voter.voter2');
98+
$def2 = $container->getDefinition('.debug.security.voter.voter2');
9999
$this->assertNull($def2->getDecoratedService(), 'voter2: should not be decorated');
100100
$this->assertEquals(new Reference('voter2'), $def2->getArgument(0), 'voter2: wrong argument');
101101

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 <molodchick@gmail.com>
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/DependencyInjection/ControllerArgumentValueResolverPass.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,20 @@ public function process(ContainerBuilder $container)
5656
$resolvers = array_values($resolvers);
5757

5858
if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class) && $container->has('debug.stopwatch')) {
59-
foreach ($resolvers + $namedResolvers as $resolverReference) {
59+
$debugResolvers = [];
60+
foreach ($resolvers as $resolverReference) {
6061
$id = (string) $resolverReference;
61-
$container->register("debug.$id", TraceableValueResolver::class)
62-
->setDecoratedService($id)
63-
->setArguments([new Reference("debug.$id.inner"), new Reference('debug.stopwatch')]);
62+
$debugResolvers[$id] = $container->register('.debug.value_resolver.'.$id, TraceableValueResolver::class)
63+
->setArguments([new Reference($id), new Reference('debug.stopwatch')])
64+
->addTag('controller.argument_value_resolver');
65+
$container->getDefinition($id)->clearTag('controller.argument_value_resolver');
66+
}
67+
foreach ($namedResolvers as $resolverReference) {
68+
$id = (string) $resolverReference;
69+
$debugResolvers[$id] ??= $container->register('.debug.value_resolver.'.$id, TraceableValueResolver::class)
70+
->setArguments([new Reference($id), new Reference('debug.stopwatch')]);
71+
$debugResolvers[$id]->addTag('controller.targeted_value_resolver');
72+
$container->getDefinition($id)->clearTag('controller.targeted_value_resolver');
6473
}
6574
}
6675

0 commit comments

Comments
 (0)