Création d’une contrainte de validation personnalisée sous symfony 2

symfony_logo

On utilise quasiment à chaque fois lors la création de nos entités ou de nos formulaires  des contraintes déjà fournies par le framework, voici une liste non exhaustive :

  • NotBlank : permet de garantir un champ rempli
  • Email : permet la validité d’une adresse email
  • Length : Taille requise
  • ……

Pour voir la liste complète des contraintes déjà existante je vous invite à aller dans le dossier :

/vendor/symfony/validator/constraints

Il nous arrive tous de vouloir mettre une contrainte sur un champ de formulaire ou sur une entité qui malheureusement n’existe pas car le framework ne peut pas tout prévoir ou tout imaginer.

Nous allons donc créer notre propre contrainte personnalisée afin de répondre à ce manque.

Création de la classe contrainte

Dans notre exemple, nous voudrions garantir à la création d’un nouveau produit que la référence choisie n’existe pas dans notre base de données.

On va tout d’abord créer dans notre bundle un dossier Validator contenant un autre dossier Constraints.

On va créer dans le dossier nouvellement créé une classe ReferenceExists.

Voici la définition de cette classe :

namespace votreBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraints;

/**
* @Annotation
*/ 
class ReferenceExists extends Constraint
{
    public message = “Votre reference ‘%string%’ existe déjà, veuillez en choisir une autre”;
    
    public function validatedBy(){
  	return ‘reference_exists ;
   }
}

Expliquons ce que nous venons de définir :

On a besoin d’utiliser le component Validator\Constraints pour pouvoir étendre notre classe à celle de Constraint.

@Annotation : obligatoire pour pouvoir utiliser cette contrainte en annotation

public $message qui affichera comme son nom l’indique un message à l’utilisateur.

public function validatedBy() qui est obligatoire dans notre cas, qui retourne un alias correspondant au futur service à créer. Vous comprendrez pourquoi un peu plus bas.

Cette fonction n’est pas obligatoire si nous n’avons pas besoin de faire appel à un service. En effet il s’agit d’une méthode surchargée.

Dans la classe parente (Constraint), cette fonction retourne get_class($this).'Validator'

Cette classe que l’on vient de créer va de paire avec une autre classe comme définie dans la fonction ValidatedBy() qui retourne nomDeLaClasseValidator.

Vous avez sans doute remarqué dans les contraintes déjà existantes, chaque classe va de paire avec une autre terminée par Validator. (NotBlank avec NotBlankValidator….)

 

Voici la définition de cette deuxième classe :

namespace votreBundle\Validator\Constraints ;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

/**
* @Annotation
*/ 
Class ReferenceExistsValidator extends ConstraintValidator
{
  private $em;
  
  public function __construct($em)
  {
  	$this->em = $em;
  }
  
  public function validate($value, Constraint $constraint){
        $reference = $this->em->getRepository('votreBundle:Produit')->findByReference($value);
  
        if ($reference)
        {
        	$this->context->buildViolation($constraint->message)
                              ->setParameter('%string%', $value)
                              ->addViolation();
        }
    }
}

Notez la fonction validate qui est obligatoire, elle permet de vérifier la validité de la contrainte, dans le cas contraire on demande d’afficher le message précédemment défini.

$value : valeur qui concerne la validation

Constraint $constraint : contient tout ce que nous avons déclaré en tant que public dans notre classe ReferenceExists.

Dans notre cas, nous avons besoin de l’entité manager comme dépendance afin d’interroger la base de donnée si la référence existe déjà.

Voilà de ce qui est des classes à définir.

 

Déclaration dans un service

Comme nous avons pu le voir, notre classe validator a besoin de l’entity manager.

Voici la déclaration dans notre service.yml :

services:
    validator.unique.reference_exists:
    class: votreBundle\Validator\Constraints\ReferenceExistsValidator
    arguments: [@doctrine.orm.entity_manager]
    tags:
        - { name: validator.constraint_validator, alias: reference_exists }

En argument nous envoyons bien sûr le nom du service de l’entité manager.

On retrouve en alias le nom défini plus haut dans notre méthode validatedBy()

Nous pouvons enfin utiliser notre contrainte.

 

Utilisation de cette contrainte

Dans une entité :

Voici une partie de la définition de notre entité produit :

namespace votreBundle\Entity;

use votreBundle\Validator\Constraints as CustomAssert;

class Produit
{
  //…
 /**
 * @CustomAssert\ReferenceExists()
 */
 private reference;

 //….
}

Notez que l’utilisation d’une contrainte personnalisée est la même que celle pour les contraintes fournies par Symfony 2.

Dans un formulaire :

Voici une partie de la définition du formulaire :

namespace votreBundle\Form\Type ;

use votreBundle\Validator\Constraints\ReferenceExists;

class ProduitType extends AbstractType
{
//…..
  public function buildForm(FormBuilderInterface $builder, array $options)
  {
     //………
     $builder->add($this->getReference($builder))
     //………
  }

  protected function getReference($builder)
  {
    $reference= $builder->create('reference', TextType::class, array(
            'label' => 'Référence',
            'constraints' => array(
                new NotBlank(array(
                    'message' => 'Veuillez remplir votre champ'
                        )),
                new ReferenceExists()
            )
        ));

        return $reference;
  }
//……
}

Il ne vous reste plus qu'à tester 🙂

Voilà vous savez désormais créer votre propre contrainte de validations sous Symfony 2.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.