Ajouter une validation conditionnelle à un formulaire Symfony

Publié le 26/03/2019 • Mis à jour le 28/03/2019

Parfois, valider unitairement chacun des champs d'un formulaire n'est pas suffisant. Dans certains cas on a besoin d'une validation conditionnelle. C'est à dire que la validation d'un champ ou d'un groupe de champs dépend de la valeur d'un autre. Voici un exemple simple permettant de valider une date de fin, mais seulement si une valeur a été saisie. On verra aussi comment soumettre manuellement des valeurs à un formulaire sans utiliser la requête http en cours (Request).


<?php

// src/Type/EventCreateType.php

namespace App\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Context\ExecutionContextInterface;

/**
 * We set a global validation on the form. Not on a specific field.
 */
class EventCreateType extends AbstractType
{
    /**
     * We remove csrf as we manually submit values to the form.
     */
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'csrf_protection' => false,
            'constraints' => [
                new Callback([$this, 'validate']),
            ],
        ]);
    }

    /**
     * Valid if the end date if not set or if it is greater than the start date.
     * If the second test, we are sure both fields are DateTime objects.
     */
    public function validate(array $data, ExecutionContextInterface $context): void
    {
        if ($data['end_date'] && $data['start_date'] > $data['end_date']) {
            $context->buildViolation('The end date must be greater than the start date.')
                ->atPath('end_date')
                ->addViolation();
        }
    }

    /**
     * Only the start date is mandatory.
     */
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->add('start_date', DateType::class, [
            'widget' => 'single_text',
            'constraints' => [new NotBlank()],
        ]);

        $builder->add('end_date', DateType::class, [
            'widget' => 'single_text',
        ]);
    }
}
En bonus, le snippet permettant d'utiliser ce code : 🎉
<?php declare(strict_types=1);

namespace App\Controller\Snippet;

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

/**
 * I am using a PHP trait in order to isolate each snippet in a file.
 * This code should be called from a Symfony controller extending AbstractController (as of Sf 4.2)
 * or Symfony\Bundle\FrameworkBundle\Controller\Controller (Sf <= 4.1)
 * Services are injected in the controller constructor.
 */
trait Snippet20Trait
{
    /**
     * Test the validation with a set of values.
     */
    public function snippet20(): void
    {
        if (!$this->formFactory instanceof FormFactoryInterface) {
            throw new \RuntimeException("Houston, we've got a problem! 💥");
        }
        $formValues = [
            [
                'start_date' => '2019-03-26', // valid
            ],
            [
                'start_date' => '2019-03-27',
                'end_date' => '2019-03-20', // NOT valid
            ],
            [
                'start_date' => '2019-03-28',
                'end_date' => '2019-03-29', // valid
            ],
        ];

        // Manually submit values to the form. Note that the form creation is in
        // the loop because a form can only be submitted once
        foreach ($formValues as $formValue) {
            $form = $this->formFactory->create(EventCreateType::class);
            $form->submit($formValue);
            if ($form->isValid()) {
                $startDate = $form->getData()['start_date'];
                echo 'Form is valid! start_date: '.$startDate->format('Y-m-d');
            } else {
                echo 'Form is not valid: '.$form->getErrors(true);
            }
            echo PHP_EOL;
        }

        // That's it! 😁
    }
}

 Exécuter le snippet  Plus sur Stackoverflow   Lire la doc