Skip to content

Commit 657709d

Browse files
committed
[HttpKernel] Don't validate partially denormalized object
1 parent cc1b41e commit 657709d

File tree

2 files changed

+54
-8
lines changed

2 files changed

+54
-8
lines changed

Controller/ArgumentResolver/RequestPayloadValueResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
119119
$payload = $e->getData();
120120
}
121121

122-
if (null !== $payload) {
122+
if (null !== $payload && !\count($violations)) {
123123
$violations->addAll($this->validator->validate($payload, null, $argument->validationGroups ?? null));
124124
}
125125

Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
2828
use Symfony\Component\Serializer\Serializer;
2929
use Symfony\Component\Validator\Constraints as Assert;
30-
use Symfony\Component\Validator\ConstraintViolation;
3130
use Symfony\Component\Validator\ConstraintViolationList;
3231
use Symfony\Component\Validator\Exception\ValidationFailedException;
3332
use Symfony\Component\Validator\Validator\ValidatorInterface;
@@ -226,14 +225,11 @@ public function testWithoutValidatorAndCouldNotDenormalize()
226225
public function testValidationNotPassed()
227226
{
228227
$content = '{"price": 50, "title": ["not a string"]}';
229-
$payload = new RequestPayload(50);
230228
$serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]);
231229

232230
$validator = $this->createMock(ValidatorInterface::class);
233-
$validator->expects($this->once())
234-
->method('validate')
235-
->with($payload)
236-
->willReturn(new ConstraintViolationList([new ConstraintViolation('Test', null, [], '', null, '')]));
231+
$validator->expects($this->never())
232+
->method('validate');
237233

238234
$resolver = new RequestPayloadValueResolver($serializer, $validator);
239235

@@ -253,7 +249,36 @@ public function testValidationNotPassed()
253249
$validationFailedException = $e->getPrevious();
254250
$this->assertInstanceOf(ValidationFailedException::class, $validationFailedException);
255251
$this->assertSame('This value should be of type unknown.', $validationFailedException->getViolations()[0]->getMessage());
256-
$this->assertSame('Test', $validationFailedException->getViolations()[1]->getMessage());
252+
}
253+
}
254+
255+
public function testValidationNotPerformedWhenPartialDenormalizationReturnsViolation()
256+
{
257+
$content = '{"password": "abc"}';
258+
$serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]);
259+
260+
$validator = $this->createMock(ValidatorInterface::class);
261+
$validator->expects($this->never())
262+
->method('validate');
263+
264+
$resolver = new RequestPayloadValueResolver($serializer, $validator);
265+
266+
$argument = new ArgumentMetadata('invalid', User::class, false, false, null, false, [
267+
MapRequestPayload::class => new MapRequestPayload(),
268+
]);
269+
$request = Request::create('/', 'POST', server: ['CONTENT_TYPE' => 'application/json'], content: $content);
270+
271+
$kernel = $this->createMock(HttpKernelInterface::class);
272+
$arguments = $resolver->resolve($request, $argument);
273+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
274+
275+
try {
276+
$resolver->onKernelControllerArguments($event);
277+
$this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class));
278+
} catch (HttpException $e) {
279+
$validationFailedException = $e->getPrevious();
280+
$this->assertInstanceOf(ValidationFailedException::class, $validationFailedException);
281+
$this->assertSame('This value should be of type unknown.', $validationFailedException->getViolations()[0]->getMessage());
257282
}
258283
}
259284

@@ -612,3 +637,24 @@ public function __construct(public readonly float $price)
612637
{
613638
}
614639
}
640+
641+
class User
642+
{
643+
public function __construct(
644+
#[Assert\NotBlank, Assert\Email]
645+
private string $email,
646+
#[Assert\NotBlank]
647+
private string $password,
648+
) {
649+
}
650+
651+
public function getEmail(): string
652+
{
653+
return $this->email;
654+
}
655+
656+
public function getPassword(): string
657+
{
658+
return $this->password;
659+
}
660+
}

0 commit comments

Comments
 (0)