La validation de données avec Symfony2

Après avoir vu comment créer nos formulaires dans Symfony2, nous allons maintenant voir comment valider les données postées via ces formulaires, ou comment utiliser les validateurs sf2 indépendamment des formulaires.




Définition des règles de validation dans nos entités Symfony2


La validation des champs dans Symfony2 peut se faire de diverses manières:
  • En définissant nos règles sur nos entités (En YAML, PHP, XML ou Annotations)
  • En utilisant le service de validation (validator) directement dans nos contrôleurs ou entités (par injection de dépendance)


Commençons par voir comment nous allons définir les validateurs sur les propriétés de nos entités.
Ouvrez l'entité Desk.php et ajoutez le namespace suivant que l'on va renommer en Assert:
Code php:
use Symfony\Component\Validator\Constraints as Assert;


Validation Symfony2


Toujours grâce aux annotations, nous allons définir quelles règles / contraintes les champs de notre entité Desk devront respecter.

La liste des contraintes disponibles dans Symfony2 sont dispo dans la documentation officielle.

Commençons par énumérer les contraintes que l'on souhaite apporter à Desk:
  • Le titre, le résumé et la description ne devront pas être vides: contrainte NotBlank.
  • Le titre devra faire entre 3 et 255 caractères: contrainte MinLength et MaxLength
  • Pas de limite de taille pour le résumé ni la description
  • La note devra être comprise entre 0 et 5: contrainte Min et Max
  • La date de création et date de mise à jour devront être de type DateTime: contrainte DateTime


Et maintenant ajoutons les contraintes dans les annotations des propriétés concernées:
Code php:
/**
     * @var string $title
     * @Assert\NotBlank(message="Title must not be empty")
     * @Assert\MinLength(
     *      limit=3,
     *      message="Title should have at least {{ limit }} characters."
     * )
     * @Assert\MaxLength(255)
     * @ORM\Column(name="title", type="string", length=255)
     */
    private $title;

    /**
     * @var text $summary
     * @Assert\NotBlank()
     * @ORM\Column(name="summary", type="text")
     */
    private $summary;

    /**
     * @var text $description
     * @Assert\NotBlank()
     * @ORM\Column(name="description", type="text")
     */
    private $description;

    /**
     * @var decimal $note
     * @Assert\Min(limit = "0", message = "Desk's note must be positive")
     * @Assert\Max(limit = "5", message = "The max value for the note is 5")
     * @ORM\Column(name="note", type="decimal", nullable=true)
     */
    private $note;

    /**
     * @var integer $voteCount
     *
     * @ORM\Column(name="vote_count", type="integer", nullable=true)
     */
    private $voteCount;

    /**
     * @var datetime $createdAt
     * @Assert\DateTime()
     * @ORM\Column(name="created_at", type="datetime")
     */
    private $createdAt;

    /**
     * @var datetime $updatedAt
     * @Assert\DateTime()
     * @ORM\Column(name="updated_at", type="datetime", nullable=true)
     */
    private $updatedAt;


Il est possible de définir le message d'erreur qui sera déclenché si la valeur postée est incorrecte. Si vous ne spécifiez pas le message, alors c'est la valeur par défaut de la contrainte qui est utilisée.
Si vous ne souhaitez pas mettre de message spécifique, vous pouvez utiliser les raccourcis comme:
Code php:
 @Assert\Max("5")

Qui attribuera automatiquement la valeur passée (5) à la propriété limit.


Valider les données du formulaire dans notre Contrôleur Symfony


Nos contraintes sont fixées dans notre entité Desk. Nous allons pouvoir lancer la validation lors de la soumission du formulaire.
Si vous ouvrez le DeskController.php, nous avions déjà anticipé la validation dans les actions edit et add:
Code php:
        if ('POST' == $request->getMethod()) { // Si on a posté le formulaire
            $form->bindRequest($request); // On bind les données du form
            if ($form->isValid()) { // Si le formulaire est valide
                // Les données sont valides, on peut sauvegarder et rediriger
            }
        }


Si la validation échoue, l'objet form contiendra automatiquement le libellé des erreurs déclenchées. Et il sera ensuite possible de les afficher dans notre template.
Ouvrez le template views/Desk/add.html.twig.
Nous avons déjà prévu l'affichage des erreurs pour les champs du form:
Code php:
{{ form_errors(form.title) }}

Mais nous n'avons pas encore prévu l'affichage des erreurs globales du formulaire. Ajoutons le:
Code html:
<form action="{% if desk.id %}{{ path('desk_edit', {'deskId':desk.id}) }}{% else %}{{ path('desk_add') }}{% endif %}" method="post" {{ form_enctype(form) }} novalidate="novalidate">
    <h1>{% if desk.id %}{{ 'Edit desk'|trans }}{% else %}{{ 'Add desk'|trans }}{% endif %}</h1>
    <div class='errors'>
        {{ form_errors(form) }}
    </div>


Si vous tentez d'ajouter un bureau via notre formulaire et que vous ne remplissez pas les champs, vous devriez obtenir les erreurs suivantes:
Symfony2 form validation erreurs

Désactivez la validation HTML5 avec la propriété novalidate dans la balise form pour obenir la validation Symfony2.



Vous pouvez aussi tester la taille minimum & maximum des champs pour voir les erreurs déclenchées.
A savoir que les messages d'erreurs pourront être traduits via les dictionnaires de langue (on y reviendra plus tard).






Allons plus loin dans la validation Symfony2


Nous avons vu comment utiliser quelques contraintes de base qui nous servirons quotidiennement. Maintenant, voyons comment ajouter des contraintes plus avancées sur nos entités.

Contrainte UniqueEntity



La contrainte UniqueEntity est très pratique lorsque vous voulez qu'un champ soit unique dans votre BDD. Par exemple un login ou une adresse email.

Par exemple, imaginons que nous voulons rendre les titres de nos bureaux uniques (pas d'un grand intérêt).
Il faut dans un premier temps ajouter le namespace de la contrainte spéciale (Ne fonctionne pas via @Assert):
Code php:
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;


Puis modifier l'annotation de la classe et la méthode ainsi:
Code php:
/**
 * Wmd\WatchMyDeskBundle\Entity\Desk
 *
 * @ORM\Table(name="desk")
 * @ORM\Entity(repositoryClass="Wmd\WatchMyDeskBundle\Repository\DeskRepository")
 * @UniqueEntity(fields="title", message="Ce titre de bureau existe déjà...")
 */
class Desk
{
    /* ... */

    /**
     * @var string $title
     * @Assert\NotBlank(message="Title must not be empty")
     * @Assert\MinLength(
     *      limit=3,
     *      message="Title should have at least {{ limit }} characters."
     * )
     * @Assert\MaxLength(255)
     * @ORM\Column(name="title", type="string", length=255, unique=true)
     */
    private $title;


Vous pouvez tester le formulaire d'ajout de bureau en mettant 2 fois le même titre de bureau.

UniqueEntity contrainte Symfony2

Une contrainte bien pratique et facile à utiliser !


Contrainte Callback


Il s'agit d'une contrainte que l'on peut personnaliser à souhait.
Très pratique lorsque vous avez à tester des données qui doivent correspondre à votre logique.

Prenons un nouvel exemple: nous souhaitons vérifier dans nos champs titre et description il n'y'ai pas les mots réservés suivants: "poule", "poulette", "cocotte".

Nous allons alors créer une méthode dédiée à la validation dans notre entité Desk.php comme ceci:
Code php:
    public function isContentCorrect(ExecutionContext $context)
    {
        $badWords = "#poule|poulette|cocotte#i"; // FDW FTW

        // Nous testons si nos propriétés contiennent ces mots reservés
        if (preg_match($badWords, $this->getTitle())) {
            $propertyPath = $context->getPropertyPath() . '.title';
            $context->setPropertyPath($propertyPath);
            $context->addViolation('Vous utilisez un mot réservé dans le titre !', array(), null); // On renvoi l'erreur au contexte
        }
        if (preg_match($badWords, $this->getDescription())) {
            $propertyPath = $context->getPropertyPath() . '.description';
            $context->setPropertyPath($propertyPath);
            $context->addViolation('Vous utilisez un mot réservé dans la description !', array(), null);
        }
    }


Comme vous pouvez le voir sur le code ci-dessus, la méthode prend en paramètre un objet ExecutionContext, que l'on pourra utiliser pour ajouter les messages d'erreurs (violations).
Les messages seront alors automatiquement ajoutés à notre objet Form et affichés dans twig.

Il faut ensuite ajouter le namespace de l'objet:
Code php:
use Symfony\Component\Validator\ExecutionContext;

Et enfin, déclarer notre callback au niveau des annotations de l'entité:
Code php:
/**
 * Wmd\WatchMyDeskBundle\Entity\Desk
 *
 * @ORM\Table(name="desk")
 * @ORM\Entity(repositoryClass="Wmd\WatchMyDeskBundle\Repository\DeskRepository")
 * @Assert\Callback(methods={"isContentCorrect"})
 */
class Desk
{


Il ne nous reste plus qu'à tester:
<img src="/images/tutorial/47/.orig/sf2-validation-callback.jpg" alt="" />


Ça marche! Je sens que vous allez aimer ce validateur, je me trompe ?


Vous pourrez aussi utiliser les validateurs Symfony 2 en dehors des entités. Par exemple, si vous développez un script dédié à l'import de contacts via un cron.
Il se peut que vous ayez besoin de tester si les emails sont valides dans cette tâche.
Il y'a un bon exemple de l'utilisation des validateurs en standalone dans la doc officielle.






Rechercher sur la Ferme du web