• 12 hours
  • Hard

Free online content available in this course.

course.header.alt.is_certifying

Got it!

Last updated on 8/26/24

Validez des données

Validez des données

Sébastien et vous envoyez tout votre code, y compris les formulaires, sur les serveurs de test pour qu’Amélie puisse vous faire des retours. Quelques heures plus tard, elle vous appelle, paniquée.

Je ne comprends pas, je me suis trompée de case sur le formulaire, j’ai entré le nombre de pages dans la case du numéro ISBN, et ça a fonctionné quand même ! Pourtant il n’y a rien à voir entre les deux, le numéro ISBN doit faire treize caractères, et je n’ai mis que trois chiffres ! 

C’est effectivement problématique, il va falloir nous pencher sur la question. Par exemple, essayez de remplir le formulaire de livre suivant (le titre et le synopsis sont plusieurs espaces blancs) :

Remplissage d'un formulaire de livre
Remplissage d'un formulaire de livre

Vous obtenez une erreur qui vous explique que la colonne  title  de la table  book  ne peut être égale à  null  .

Pourquoi ai-je cette erreur ?

Paradoxalement, c’est parce que ça ne posait pas de problème au formulaire d’avoir des espaces vides comme chaîne de caractères. Le numéro ISBN qui est une phrase ne lui a pas non plus posé de problème, ni le nombre de pages, ni la date d’édition. Pour le formulaire, tout s’est bien passé. Donc le controller est passé à la suite et a tenté d’enregistrer ces données en base de données. Cela confirme complètement ce qu’Amélie nous a rapporté : on peut entrer des données erronées sans problème.

Ceci est dû au fait que nous n’avons aucune validation des données dans notre application. Et comme souvent, la validation HTML n’est pas suffisante pour garantir que les données sont conformes. Il nous faut aussi une validation de notre côté.

Sébastien se frappe le front subitement.

Mais oui, j’ai complètement oublié de te parler du composant Validator et de ses contraintes de validation !

Ce composant a un but et un seul, parfaitement résumé par son nom : nous aider à valider des données. Il peut fonctionner seul de manière totalement indépendante, en mode standalone, ou bien incorporé au composant Form.

L’utilisation indépendante se fait en ajoutant un argument typé avec l’interface  Symfony\Component\Validator\Validator\ValidatorInterface  à votre controller. Vous n’avez plus ensuite qu’à appeler sa méthode  validate  sur l'objet à valider. Vous recevez une liste d’erreurs. Si elle est vide, c’est que tout va bien. Par exemple, sur une route fictive (ne l'ajoutez pas à votre projet) : 

<?php
    #[Route('/validate', name: 'app_admin_book_validate')]
    public function validate(ValidatorInterface $validator): Response
    {
        // ...
        // $book est un objet Book que nous voulons valider, peu importe sa provenance
        $errors = $validator->validate($book);
        if (0 < \count($errors)) {
            // Gérer les erreurs
        }
        // ...
    }

Pour ce qui est de l’utilisation dans le composant Form, vous vous en êtes déjà servi sans le savoir. Ouvrez par exemple votre  Admin\BookController  et regardez sa méthode  new  :

<?php
    public function new(Request $request, EntityManagerInterface $manager): Response
    {
        $book = new Book();
        $form = $this->createForm(BookType::class, $book);
        
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $manager->persist($book);
            $manager->flush();
            // …

Eh oui !  $form->isValid()  n’est rien d’autre qu’un appel au composant Validator.

Dans ce cas, pourquoi on peut toujours envoyer des données qui ne sont pas valides ?

Parce que pour l’instant, nous ne lui avons pas dit ce qu’il devait valider exactement. Pour ce faire, il va falloir appliquer des contraintes de validation sur les données que nous souhaitons valider.  

Les contraintes de validation sont des classes qui ne contiennent pas de logique, seulement un message d’erreur. Elles sont par contre toutes associées à un ConstraintValidator qui leur est spécifique. Concrètement, vous vous contenterez d’appliquer des contraintes sur vos objets. Puis, quand vous demandez au Validator de valider cet objet, il va lire les contraintes que vous y avez associées, et va appeler les ConstraintValidator correspondants.

Et comment on applique ces contraintes ?

C’est précisément ce que nous allons voir. Il y a deux façons de le faire : sur un objet directement, comme une entité par exemple, ou dans un  FormType  . Les bonnes pratiques recommandent de mettre les contraintes en priorité sur vos objets, et de ne les mettre sur les  FormType  que si vous n’avez pas le choix.

Les contraintes sont des classes qui sont quasiment toutes situées dans le namespace  Symfony\Component\Validator\Constraints  , qu’on a coutume d’aliaser en tant que  Assert  .

Nous ne la reprendrons pas ici, mais nous allons voir quelques exemples :

  • NotBlank  : la contrainte par défaut de la plupart des champs de formulaires requis, elle vérifie que le champ n’est pas  null  ,  false  , ni une chaîne de caractères vides (comme des espaces).

  • Length  : permet de vérifier qu’une chaîne de caractères contient un minimum ou un maximum de caractères, ou les deux. 

  • Email  : vérifie que la valeur respecte le schéma standard d’une adresse e-mail (  <première partie>@<hote>.<tld>  ).

  • EqualTo  /  GreaterThan  /  LowerThan  : comparaisons logiques classiques, mais qui permettent éventuellement de comparer deux propriétés entre elles.

  • Choice  : vérifie que la valeur fait partie d’une liste de choix autorisés (à fournir à la contrainte).

  • AtLeastOneOf  : prend en paramètre un array d’autres contraintes, et valide la donnée si au moins une de celles-ci est valide.

Contraignez la validation d’une entité

Commençons par voir la bonne pratique et ajoutons des contraintes de validation sur nos entités. Ouvrons notre entité Author. Les données à valider sont les suivantes :

  • L’id ne sera bien entendu pas validé. Par contre, on peut vérifier qu’un auteur est unique en base de données.

  • Le nom ne peut pas être vide, et devra faire au moins 10 caractères.

  • La date de naissance ne peut pas être vide.

  • La date de décès peut être vide, mais ne doit pas être inférieure à la date de naissance.

  • La nationalité n’a pas de validation particulière.

Cherchez dans la liste des contraintes et essayez de trouver les bonnes. Vous devriez être à peu près arrivé à ce résultat :

<?php
// …
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;

#[UniqueEntity(['name'])]
#[ORM\Entity(repositoryClass: AuthorRepository::class)]
class Author
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;
    
    #[Assert\Length(min: 10)]
    #[Assert\NotBlank()]
    #[ORM\Column(length: 255)]
    private ?string $name = null;
    
    #[Assert\NotBlank()]
    #[ORM\Column(type: Types::DATE_IMMUTABLE)]
    private ?\DateTimeImmutable $dateOfBirth = null;
    
    #[Assert\GreaterThan(propertyPath: 'dateOfBirth')]
    #[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)]
    private ?\DateTimeImmutable $dateOfDeath = null;
    
    #[ORM\Column(length: 255, nullable: true)]
    private ?string $nationality = null;
    
    #[ORM\ManyToMany(targetEntity: Book::class, mappedBy: 'authors')]
    private Collection $books;
    // …
}

Ajoutez ces contraintes puis essayez de vous rendre sur votre application, sur la routehttps://127.0.0.1:8000/admin/author/new. Remplissez le formulaire de manière erronée, comme ceci par exemple :

Remplissage erroné de formulaire
Remplissage erroné de formulaire

Soumettez le formulaire, et voyez le résultat :

Résultat suite à l'envoi d'un formulaire avec un remplissage erroné
Résultat suite à l'envoi d'un formulaire avec un remplissage erroné

Et voilà, des erreurs de validation !

Alors oui mais elles sont en anglais ces erreurs. Ça conviendra pour la médiathèque ?

Effectivement non. C’est parce que par défaut, tout Symfony est en anglais. Mais les traductions existent déjà. Ouvrez le fichier  config/packages/translations.yaml  et remplacez les deux occurrences de  en  par  fr  :

framework:
    default_locale: fr
    translator:
        default_path: '%kernel.project_dir%/translations'
        fallbacks:
            - fr
        # …

Soumettez à nouveau le formulaire avec vos mauvaises données, et voilà !

Traduction des éléments indiquant un remplissage erroné
Traduction des éléments indiquant un remplissage erroné

Votre entité est maintenant validée, et vous pouvez être un peu plus tranquille par rapport à vos formulaires.

Contraignez la validation d’un FormType

  • Vous pouvez tout à fait créer des FormTypes qui ne sont pas basés sur des entités.

  • Dans un FormType basé sur une entité, vous pouvez malgré tout rajouter des champs qui ne correspondent pas aux propriétés de votre entité, mais il faut l'indiquer à Symfony.

  • Pour cela, dans le tableau d'options à passer en troisième paramètre à la fonction  $builder->add()  , ajoutez une option  'mapped' => false  .

  • Vous pouvez ensuite y ajouter des contraintes de validation, directement dans le FormType, grâce à l'option  'constraints' => []  qui prend comme valeur un tableau de vos contraintes, instanciées avec le mot-clé  new  .

Exemple :

<?php
// …
use Symfony\Component\Validator\Constraints as Assert;
// …
class BookType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
        // …
            ->add('certification', CheckboxType::class, [
                'mapped' => false,
                'label' => "Je certifie l'exactitude des informations fournies",
                'constraints' => [
                    new Assert\IsTrue(message: "Vous devez cocher la case pour ajouter un livre."),
                ],
            ])
    // …

À vous de jouer

Contexte

Nous avons ajouté de la validation sur notre entité Author, mais il nous reste à valider les autres pour fournir à Amélie des formulaires sécurisés et fiables. Sébastien est malheureusement absent aujourd'hui, c'est donc à vous de trouver quelles contraintes appliquer sur chaque donnée en vous basant sur la documentation officielle.

Consignes

Vous allez maintenant ajouter des contraintes sur les entités Book et Editor, en respectant les points obligatoires suivants :

  • À chaque fois qu'une propriété est non nullable en base de données, la validation doit s'assurer qu'elle est remplie.

  • À chaque fois qu'une propriété représente une donnée spéciale avec un formatage codifié (comme une URL ou un numéro ISBN), ce format doit être validé grâce à une contrainte. Attention, des contraintes spécifiques existent pour beaucoup de formats spéciaux.

  • À chaque fois qu'une donnée doit être d'un type spécifique (comme un integer, par exemple), vous pouvez aussi valider ce type.

  • Pour rappel, vous n'avez pas besoin de valider les énumérations PHP qui ne peuvent de toute façon être que d'un seul type, avec des valeurs limitées.

En résumé

  • Le composant Validator permet d’appliquer des contraintes de validation sur nos entités et nos formulaires.

  • Cette validation est indépendante de la validation HTML et plus sécurisée.

  • La bonne pratique est de mettre vos contraintes sur vos entités.

  • Si c’est impossible, vous pouvez en mettre directement sur un  FormType  .

  • La méthode  $form->isValid()  permet d’appeler le Validator sur un formulaire et sur l’entité qui y est rattachée.

  • Le Validator peut aussi être utilisé seul si on ne valide pas un formulaire.

Et maintenant, voyons comment lire toutes ces données parfaitement validées que nous avons enregistrées en base de données !

Example of certificate of achievement
Example of certificate of achievement