Description
Symfony version(s) affected
7.1.1
Description
When using MapRequestPayload
on a parameter with a default value, behavior with an empty request body is inconsistent and depends on the Content-Type
header. There are different possibilities:
- With no
Content-Type
,UnsupportedMediaTypeHttpException
is thrown. - With a
Content-Type
that isn't understood by theRequest
class (egimage/jpeg
),UnsupportedMediaTypeHttpException
is thrown. - With a
Content-Type
that is understood by theRequest
class, the default parameter value is used. This happens regardless of whether the content is serialized data (egapplication/json
) or not (egtext/plain
).
This distinction doesn't make sense to me because it's based on the arbitrary formats that Request
understands, not whether or not the content is actually serialized data.
I think behavior should be consistent in all of these cases, whether it means throwing an exception or using the default parameter value. I would personally prefer using the default, but I think you could make the case either way. However throwing an exception in all cases would probably be a BC break.
How to reproduce
I've written these unit tests which fit within RequestPayloadValueResolverTest
and demonstrate the inconsistency:
public static function provideEmptyRequestBody(): iterable
{
// The first two throw Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException
yield 'no content-type' => ['contentType' => ''];
yield 'jpeg content-type' => ['contentType' => 'image/jpeg'];
// The rest are fine and pass the test.
yield 'json content-type' => ['contentType' => 'application/json'];
yield 'form content-type' => ['contentType' => 'application/x-www-form-urlencoded'];
yield 'text content-type' => ['contentType' => 'text/plain'];
}
/**
* @dataProvider provideEmptyRequestBody
*/
public function testEmptyRequestBodyDefaultParameter(?string $contentType): void
{
$payload = new RequestPayload(50);
$serializer = new Serializer([new ObjectNormalizer()]);
$validator = $this->createMock(ValidatorInterface::class);
$resolver = new RequestPayloadValueResolver($serializer, $validator);
$argument = new ArgumentMetadata('empty', RequestPayload::class, false, true, $payload, false, [
MapRequestPayload::class => new MapRequestPayload(),
]);
$request = Request::create('/', 'POST', server: ['CONTENT_TYPE' => $contentType]);
$kernel = $this->createMock(HttpKernelInterface::class);
$arguments = $resolver->resolve($request, $argument);
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
$resolver->onKernelControllerArguments($event);
$this->assertEquals([$payload], $event->getArguments());
}
Possible Solution
Always use the default parameter value when the request body is empty.
Additional Context
No response