Description
Symfony version(s) affected
6.3.1
Description
When there is a type missmatch on an objet with the attribute MapQueryString
or MapRequestPayload
an empty instance is created.
For instance if I use MapQueryString
expecting a name (string) and age (int) but the user send a "John" and "foo", I end up with an empty object.
How to reproduce
- Create a simple DTO (I deliberately omitted NotBlank)
final readonly class PayloadDTO
{
public function __construct(
#[Assert\Length(min: 3)]
public string $name,
#[Assert\Positive]
public int $age
)
{
}
}
- Map this DTO in a controller
class HomeController extends AbstractController
{
#[Route('/', name: 'home')]
public function index(
#[MapQueryString] PayloadDTO $payload,
): Response
{
dd($payload); // I expect the dto to be valid (with an age / name property)
}
}
- Try to access the page using an "age" that is not a number.
http://localhost:8000/?name=John&age=foo
, you'll end up with an emptyPayloadDTO
(which will cause bugs further down the application).
Possible Solution
I tracked down the issue to the AbstractNormalizer
that ends up instantiating the object without using the constructor (using the reflectionClass) since there is a not_normalizable_value_exceptions
in the context. https://github.com/symfony/symfony/blob/6.4/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php#L424-L428
A solution would be to check the context for "collect_denormalization_errors" before skipping the exception or to collect the exception when it happens.
try {
return $reflectionClass->newInstanceArgs($params);
} catch (\TypeError $e) {
if (!isset($context['not_normalizable_value_exceptions'])) {
throw $e;
}
$context['not_normalizable_value_exceptions'][] = NotNormalizableValueException::createForUnexpectedDataType(/* Find which parameters to give */);
return $reflectionClass->newInstanceWithoutConstructor();
}