Symfony custom form validation

Cette méthode est intéressante pour affiner votre contrôle d’un champ de formulaire. Par exemple nous allons ajouter un contrôle d’unicité de numéro de téléphone. Nous allons créer depuis le terminal un validateur

Création du validateur

php bin/console make:validator

Un prompt vous demande le nom de la classe validateur

 The name of the validator class (e.g. EnabledValidator):
 > UniquePhoneValidator                                 

 created: src/Validator/UniquePhoneValidator.php
 created: src/Validator/UniquePhone.php

On doit avoir deux classes, UniquePhone qui va servir pour créer l’annotation et UniquePhoneValidator qui contient le code qui va faire le contrôle d’unicité proprement dit.

Ensuite l’entité sur laquelle cette vérification va se faire, imaginons qu’on ait une entité Contrat, importons la classe créée qui se trouve dans la répertoire validator

Préparation de la classe UniquePhone

Pour pouvoir utiliser les annotation plus tard, nous devons importer une classe

<?php

namespace App\Validator;

use Doctrine\Common\Annotations\Annotation\Target;//ajouté
use Symfony\Component\Validator\Constraint;

/**
 * @Annotation
 * @Target({"PROPERTY","METHOD","ANNOTATION"}) // ajouté
 */
class UniquePhone extends Constraint
{
    /*
     * Any public properties become valid options for the annotation.
     * Then, use these in your validator class.
     */
    public $message = 'Le numéro de téléphone {{ value }} existe déjà.'; // modifié
}

Préparation de la classe UniquePhoneValidator

<?php

namespace App\Validator;

use App\Repository\MatriceRepository;//ajouté
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class UniquePhoneValidator extends ConstraintValidator
{
    private $matriceRepository;v // on a besoin du repository pour faire le check
    public function __construct(MatriceRepository $matriceRepository)
    {
        $this->matriceRepository = $matriceRepository;
    }
    public function validate($value, Constraint $constraint)
    {
        /* @var $constraint \App\Validator\UniquePhone */

        $existingPhone = $this->matriceRepository->findOneBy(['clientTelFixe' => $value]); // ajouté

        if(!$existingPhone){ // ajouté
            return;
        }
        // TODO: implement the validation here
        $this->context->buildViolation($constraint->message)
            ->setParameter('{{ value }}', $value)
            ->addViolation();
    }
}
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use App\Validator\UniquePhone;

Ensuite plus loin nous avons la propriété ( le champ) qui sera concernée par cette vérification.

    /**
     * @var string
     * @UniquePhone()
     * @ORM\Column(name="CLIENT_TEL_FIXE", type="string", length=50, nullable=false)
     */
    private $clientTelFixe;

    /**
     * @var string|null
     * @UniquePhone()
     * @ORM\Column(name="CLIENT_TEL_MOBILE", type="string", length=50, nullable=true)
     */
    private $clientTelMobile;

Remarquez que tout se fait par annotation, c’est Doctrine qui se charge de faire marcher ces annotations. Donc il suffit d’ajouter l’annotation@uniquePhone() aux deux propriétés de l’entité pour activer la vérification.

Contrainte sur une classe entière

Pour placer l’annotation sur une classe et non sur une méthode, vous devez modifier la classe de contraint UniquePhone

<?php

namespace App\Validator;

use Doctrine\Common\Annotations\Annotation\Target;//ajouté
use Symfony\Component\Validator\Constraint;

/**
 * @Annotation
 * @Target({"PROPERTY","METHOD","ANNOTATION","CLASS"}) // modifié
 */
class UniquePhone extends Constraint
{
    /*
     * Any public properties become valid options for the annotation.
     * Then, use these in your validator class.
     */
    public $message = 'Le numéro de téléphone {{ value }} existe déjà.'; // modifié

    public function getTargets()
    {
        return self::CLASS_CONSTRAINT;  // ajouté
    }

}

On a modifié l’annotation Target pour ajouter CLASS, et on a ajouté la méhode getTargets() pour que le validator puisse retourner la classe. Et au dessus de l’entité, enlever les annotations au dessus des propriétés de l’entité pour mettre au dessus de la class.

* MyEntity 
* @UniquePhone()
....
class MyEntity
{
...

Modifier la classe UniquePhoneValidator, cette étape est très importante sinon le messagene s’affichera pas. $value représente désormais la classe, et plus le numéro de téléphone (évidemment),

class UniquePhoneValidator extends ConstraintValidator
{
    private $matriceRepository;

    public function __construct(MatriceRepository $matriceRepository)
    {
        $this->matriceRepository = $matriceRepository;
    }

    public function validate($value, Constraint $constraint)
    {
        /* @var $constraint \App\Validator\UniquePhone */

        // at matrice creation we do the check,
        if (is_null($value->getIdMatrice())) {
            $existingPhone = $this->matriceRepository->findOneBy(['clientTelFixe' => $value->getClientTelFixe()]);
        } else {
            // at edition we do not do the check
            return ;
        }

        if (!$existingPhone) {
            return;
        }


        // TODO: implement the validation here
        $this->context->buildViolation($constraint->message)
            ->atPath('clientTelFixe') // la propriété concernée par la contrainte, avec cette ligne le message d'erreur s'affichera
            ->setParameter('{{ value }}', $value->getClientTelFixe())
            ->addViolation();
    }
}

Note sur la vérification d’unicité

Cela fait beaucoup de travail pour une simple vérification d’unicité, je vous suggère de garder à l’idée qu’une vérification clientside peut être utile et plus simple, couplé à une requête Ajax.

Vous aimerez aussi...