Search code examples
phpsymfonyvalidationannotationsfosrestbundle

Alternative to Symfony's "Collection" constraint


I have a FOSRestBundle annotation on an existing controller that works well. It takes in a collection of nested JSON objects. Like this:

 use Symfony\Component\Validator\Constraints;
 
 ...
 
 * @Rest\RequestParam(
 *     name="forumsToCreate",
 *     key="forumsToCreate",
 *     strict=true,
 *     nullable=true,
 *     description="Array of objects [{ adminEmail: '[email protected]', forumName: 'my forum name', topic: 'baking'}]
 * (email is mandatory when sending forumsToCreate)",
 *     requirements=@Constraints\Collection(
 *         fields={
 *             "adminEmail": @Constraints\Required({@Constraints\NotBlank, @Constraints\Email}),
 *             "forumName" : @Constraints\Optional({@Constraints\NotBlank, @Constraints\Length(max="255")}),
 *             "topic": @Constraints\Optional({@Constraints\NotBlank, @Constraints\Length(max="255")}),
 *         },
 *     ),
 *     map = true
 * )

The Collection constratint has been working well so far, but now I want to have a new controller take in data for just one nested forum object, rather than an array of objects. Is there a different constraint I should use?

===

Clarification: I still want to be able to have other top-level params on this controller. But I want the "forum" param to be a single nested JSON object containing email, name and topic fields, rather than a collection of forum objects.


Solution

  • If you want to apply the previous annotation to a Controller that takes only one forum object you have to put:

    map = false
    

    To understand why, just look at AbstractScalarParam (parent class of RequestParam) where you can see that $this->map === true implies that the constraint specified in the annotation will be applied to every item of the input (which is considered an array). If map is false the constraint is directly applied to the input.

    Pay attention that the constraint called Collection can create misunderstandings: its goal it's not to validate a list (or a collection) but to validate an associative array by specifying a rule for each key of the array. Look here to get some more info: https://symfony.com/doc/current/validation/raw_values.html

    If you need to apply a constraint to a list of values, you need to use the constraint called All. See the following example.

    To validate this array:

    $input = [
        'email' => '[email protected]',
        'tags' => [
            [
                'slug' => 'symfony_doc',
                'label' => 'symfony doc',
            ],
        ],
    ];
    

    You need these constraints:

    $constraint = new Assert\Collection([
        // the keys correspond to the keys in the input array
        'email' => new Assert\Email(),
        'tags' => new Assert\Optional([
            new Assert\Type('array'),
            new Assert\Count(['min' => 1]),
            new Assert\All([
                new Assert\Collection([
                    'slug' => [
                        new Assert\NotBlank(),
                        new Assert\Type(['type' => 'string']),
                    ],
                    'label' => [
                        new Assert\NotBlank(),
                    ],
                ])
            ]),
        ]),
    ]);