Retour à la liste

Valider les éléments de collection polymorphiques avec les contraintes Symfony

Cover Image for Valider les éléments de collection polymorphiques avec les contraintes Symfony
Dylan Ballandras
Dylan Ballandras

Gérer la validation des collections polymorphiques d'objets peut être un véritable défi, surtout avec Symfony. Dans cet article, nous explorerons un validateur de contraintes personnalisé qui peut valider les éléments de collection polymorphiques directement à partir de la requête HTTP.

Imaginez un scénario où vous avez une requête HTTP contenant un tableau d'objets de différents types. Vous devez valider chaque objet en fonction de son type, et les règles de validation peuvent varier pour chaque type. Le processus de validation doit être aussi simple et efficace que possible.

Présentation du PolymorphicCollectionItemValidator

Voici un validateur de contraintes personnalisé Symfony qui peut gérer la validation polymorphique des collections. Il valide chaque élément de la collection en fonction d'un champ discriminant et applique les règles de validation correspondantes.

Contrainte PolymorphicCollectionItem

<?php

declare(strict_types=1);

namespace App\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

class PolymorphicCollectionItem extends Constraint
{
    public string $discriminatorField = 'type';
    /** @var array<string, Constraint> */
    public array $mapping = [];

    /** @param array{discriminatorField: string, mapping: array<string, Constraint>} $options */
    public function __construct($options = null)
    {
        parent::__construct($options);
    }

    public function getRequiredOptions(): array
    {
        return [
            'discriminatorField',
            'mapping',
        ];
    }
}

PolymorphicCollectionItemValidator

<?php

declare(strict_types=1);

namespace App\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;

class PolymorphicCollectionItemValidator extends ConstraintValidator
{
    /**
     * {@inheritdoc}
     */
    public function validate($object, Constraint $constraint): void
    {
        if (!$constraint instanceof PolymorphicCollectionItem) {
            throw new UnexpectedTypeException($constraint, PolymorphicCollectionItem::class);
        }

        if (null === $object) {
            return;
        }

        if (!\is_array($object) && !($object instanceof \Traversable && $object instanceof \ArrayAccess)) {
            throw new UnexpectedValueException($object, 'array|(Traversable&ArrayAccess)');
        }

        $discriminatorField = $constraint->discriminatorField;
        $type = $object[$discriminatorField] ?? null;

        if ($type === null) {
            return;
        }

        $constraintClassName = $constraint->mapping[$type] ?? null;
        if ($constraintClassName === null) {
            $this
                ->context
                ->buildViolation("Le champ '$discriminatorField' n'est pas valide.")
                ->atPath($discriminatorField)
                ->setParameter('{{ type }}', $type)
                ->addViolation()
            ;

            return;
        }

        $this->context->getValidator()
            ->inContext($this->context)
            ->atPath('')
            ->validate($object, $constraintClassName)
            ->getViolations()
        ;
    }
}

Utilisation

Voici un exemple d'utilisation de ce validateur de contraintes personnalisé dans votre application Symfony :

<?php

declare(strict_types=1);

namespace App\Validator;

use App\Validator\Constraints\AllowExtraFieldsCollection;
use App\Validator\Constraints\PolymorphicCollectionItem;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints;

class BulkObjectValidator
{
    public function getConstraint(): Constraint
    {
        return new Constraints\Collection([
            'fields' => [
                'objects' => [
                    new Constraints\All([
                        'constraints' => [
                            new Constraints\Collection([
                                'fields' => [
                                    'type' => [
                                        new Constraints\NotNull(),
                                        new Constraints\Choice(['choices' => ['TYPE_1', 'TYPE_2']]),
                                    ],
                                ],
                            ]),
                            new PolymorphicCollectionItem([
                                'discriminatorField' => 'type',
                                'mapping' => [
                                    'TYPE_1' => new Constraints\Collection(['fields' => ['value' => new Constraints\Choice(['choices' => ['VALUE_1', 'VALUE_2']]]),
                                    'TYPE_2' => new Constraints\Collection(['fields' => ['value' => new Constraints\Choice(['choices' => ['VALUE_3', 'VALUE_4']]]),
                                ],
                            ]),
                        ],
                    ]),
                ],
            ],
        ]);
    }
}

Vous pouvez utiliser le BulkObjectValidator dans un contrôleur Symfony pour valider les données d'une requête HTTP.

<?php

declare(strict_types=1);

namespace App\Controller;

use App\Validator\BulkObjectValidator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class BulkObjectsController
{
    public function __construct(private BulkObjectValidator $validator, private ValidatorInterface $validatorInterface)
    {
    }

    #[Route('/bulk-objects', methods: ['POST'])]
    public function bulkObjects(Request $request): Response
    {
        $data = $request->toArray();
        $constraint = $this->validator->getConstraint();
        $violations = $this->validatorInterface->validate($data, $constraint);

        if ($violations->count() > 0) {
            // Gérer les erreurs de validation
            // ...
        }

        // Traiter les données valides
        // ...

        return new Response('Succès', Response::HTTP_OK);
    }
}

En conclusion, le PolymorphicCollectionItemValidator offre un moyen simple et puissant de gérer la validation polymorphique dans les applications Symfony. Il vous permet de valider les données directement à partir de la requête HTTP et d'appliquer différentes règles de validation en fonction du type d'objet. Essayez-le et faites-moi part de vos avis et remarques sur Twitter !