[Symfony] Utilisation du validateur UniqueEntity sans utiliser les annotations

Publié le 04/07/2019 • Mis à jour le 04/07/2019

English language detected! 🇬🇧

  We noticed that your browser is using English. Do you want to read this post in this language?

Read the english version 🇬🇧

Quand on a un formulaire relatif à une entité, il est fréquent d'avoir des contraintes d'unicité pour certains champs, par exemple sur le nom d'utilisateur ou l'email. Je ne suis pas un grand fan de l'utilisation des annotations pour la validation donc voici comment utiliser ce validateur dans un FormType relatif à une entité. L'astuce ici réside dans le fait d'ajouter la contrainte au niveau du formulaire et pas d'essayer d'assigner la contrainte au champ dont nous voulons contrôler l'unicité. Dans cet exemple nous allons créer un formulaire factice pour l'entité Article de ce site, puis nous allons vérifier que la contrainte fonctionne en essayant de soumettre un slug déjà utilisé (celui de cet article), celui-ci devant être unique pour chaque article. Comme vous pouvez le constater, il n'y a pas d'identifiant de base de donnée dans les URLs des articles ou des snippets de ce site. Le slug (en anglais) est l'identifiant principal. Celui-ci est traduit en français quand on accède au contenu dans cette langue.


<?php declare(strict_types=1);

// src/Type/ArticleType.php

namespace App\Type;

use App\Entity\Article;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;

/**
 * Fake article form for snippet30.
 */
class ArticleType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->add('slug', TextType::class, ['constraints' => [new NotBlank()]]);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'csrf_protection' => false,
            'data_class' => Article::class,
            'constraints' => [
                new UniqueEntity(['fields' => ['slug']])
            ],
        ]);
    }
}
En bonus, le snippet permettant d'utiliser ce code : 🎉
<?php declare(strict_types=1);

namespace App\Controller\Snippet;

use App\Type\ArticleType;
use Symfony\Component\Form\FormFactoryInterface;

/**
 * J'utilise un trait PHP afin d'isoler chaque snippet dans un fichier.
 * Ce code doit être apellé d'un contrôleur Symfony étendant AbstractController (depuis Symfony 4.2)
 * ou Symfony\Bundle\FrameworkBundle\Controller\Controller (Symfony <= 4.1).
 * Les services sont injectés dans le constructeur du contrôleur principal.
 *
 * @property FormFactoryInterface $formFactory
 */
trait Snippet30Trait
{
    /**
     * Test the validation with a set of values.
     */
    public function snippet30(): void
    {
        $slugs = [
            // This is the reference slug of this snippet (in english) so the validation must fail
            'on-using-the-uniqueentity-validator-without-annotation',

            // This one should be OK
            'new-unique-slug',
            'other-new-unique-slug',
        ];

        // Manually submit values to the form. Note that the form creation is in the loop because a form can only be submitted once!
        foreach ($slugs as $slug) {
            $form = $this->formFactory->create(ArticleType::class);
            $form->submit(compact('slug'));
            if ($form->isValid()) {
                echo sprintf('✅ Form is valid! slug: "%s"', $slug);
            } else {
                echo sprintf('❌ Form is not valid: Error: "%s" (slug: "%s")', trim((string) $form->getErrors(true)), $slug);
            }
            echo PHP_EOL;
        }

        // That's it! 😁
    }
}

 Exécuter le snippet   Lire la doc