0

I'm trying to map and validate using #[MapRequestPayload] request payload with nested request data. When I want to map request with just 1 question, everything works fine, but when I switch type of the $question property in Quiz DTO I get this validation error.

CreateQuizDto.php

class CreateQuizDto
{
    #[Assert\NotBlank()]
    #[Assert\Length(min: 1, max: 255)]
    public string $label;

    #[Assert\Type('countable')]
    #[Assert\All(new Assert\Type(CreateQuestionDto::class))]
    #[Assert\Valid]
    public array $questions;
}

CreateQuestionDto.php

class CreateQuestionDto
{
    #[Assert\NotBlank()]
    #[Assert\Length(min: 1, max: 255)]
    public string $label;
}

Controller.php

#[Route('/api', format: 'json')]
class QuizController extends AbstractController
{
    #[Route('/quiz', name: 'app_quiz', methods: ['POST'])]
    public function index(#[MapRequestPayload] CreateQuizDto $dto): JsonResponse
    {
        return $this->json([
            'dto' => $dto

        ]);
    }
}

Example request body

{
  "label": "Example Quiz",
  "questions": [
    {
      "label": "Example Question 1"
    },
    {
      "label": "Example Question 2"
    }
  ]
}

Response error

{
 "type": "https://symfony.com/errors/validation",
    "title": "Validation Failed",
    "status": 422,
    "detail": "questions[0]: This value should be of type App\\Dto\\CreateQuestionDto.\nquestions[1]: This value should be of type App\\Dto\\CreateQuestionDto.",
    "violations": [
        {
            "propertyPath": "questions[0]",
            "title": "This value should be of type App\\Dto\\CreateQuestionDto.",
            "template": "This value should be of type {{ type }}.",
            "parameters": {
                "{{ value }}": "array",
                "{{ type }}": "App\\Dto\\CreateQuestionDto"
            },
            "type": "urn:uuid:ba785a8c-82cb-4283-967c-3cf342181b40"
        },
        {
            "propertyPath": "questions[1]",
            "title": "This value should be of type App\\Dto\\CreateQuestionDto.",
            "template": "This value should be of type {{ type }}.",
            "parameters": {
                "{{ value }}": "array",
                "{{ type }}": "App\\Dto\\CreateQuestionDto"
            },
            "type": "urn:uuid:ba785a8c-82cb-4283-967c-3cf342181b40"
        }
    ],
}

1 Answer 1

0

The problem here is that Symfony doesn't know that your public array $questions; has type array<CreateQuestionDto> as PHP for now doesn't use generics. So to explicitly "set the type" in deserialization process you should use constructor:

class CreateQuizDto
{
// ...
    public function __construct(string $label, array $questions)
    {
        $this->label = $label;

        // Map the array of questions to CreateQuestionDto objects
        $this->questions = array_map(
            fn ($question) => new CreateQuestionDto($question['label']),
            $questions
        );
    }
}

And add constructor to CreateQuestionDto:

public function __construct(string $label)
{
    $this->label = $label;
}

Using constructors in DTO makes your life easier 😉

4
  • This shouldn't be necessary if they installed symfony/serializer right?. I think the issue is with the validator itself. It's using Asset\All and Asset\Type, but that's not telling $questions is an array, it's just telling that all assertions must be valid. And now it's expecting that $questions is only one question, and not an array of questions.
    – Leroy
    Commented Nov 26 at 21:59
  • Assert\All validates that every element in array satisfy conditions. It not expects only one question. Validator is set up right. Problem is in the deserialization process where standard json_decode just create array or StdClass object. And you have an array of arrays in questions. But if you map through that array creating needed DTO you will have array of these DTOs. Commented Nov 27 at 8:58
  • Thanks, I've spotted it in the documentation. But shouldn't symfony/serializer have deserialized the json into the specified Dto's? Maybe symfony/serializer isn't installed?
    – Leroy
    Commented Nov 27 at 18:38
  • Symfony/Serializer uses json_decode. If you haven’t set up any normalizers, it won’t recognize the type of elements in the questions array. In the provided code snippet Serializer doesn't know about any types. Commented Nov 27 at 19:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.