Programmation PHP avec Symfony/Formulaire

Principe

modifier

Le principe est d'ajouter des champs de formulaire en PHP, qui seront automatiquement convertis en code HTML correspondant.

En effet, en HTML on utilise habituellement la balise <form> pour afficher les champs à remplir par le visiteur. Puis sur validation on récupère leurs valeurs en PHP avec la superglobale $_REQUEST (ou ses composantes $_GET et $_POST). Or ce système ne fonctionne pas en $_POST dans Symfony : si on affiche un tel formulaire et qu'on le valide, $_POST est vide, et l'équivalent Symfony de $_REQUEST, $request->request[1] aussi.

Les formulaires doivent donc nécessairement être préparés en PHP.

Installation

modifier
Terminal

 

 composer require symfony/form


Les formulaires présents sont ensuite listables avec :

Terminal

 

 bin/console debug:form


Et vérifiables individuellement :

Terminal

 

 bin/console debug:form "App\Service\Form\MyForm"


Avec le composant maker, on peut créer un formulaire pour chaque entité Doctrine à modifier :

Terminal

 

 composer require symfony/maker-bundle
 bin/console make:form


Validator

modifier

Pour ajouter des contrôles sur les champs, il existe un deuxième composant Symfony[2] :

Terminal

 

 composer require symfony/validator


Contrôleur

modifier

Injection du formulaire dans un Twig

modifier
class HelloWorldType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('name', TextType::class)
            ->add('save', SubmitType::class)
        ;
    }
}

class HelloWorldController extends AbstractController
{
    #[Route('/helloWorld/{id}, requirements: ['id' => '\d*']')]
    public function indexAction(Request $request, ?HelloWorld $helloWorld = null): Response
    {
        $form = $this->createForm(HelloWorldType::class, $helloWorld);

        return $this->render('helloWorld.html.twig', [
            'form' => $form->createView(),
        ]);
    }
}

Le second paramètre de createForm() est facultatif est sert à préciser des valeurs initiales dans le formulaire qui seront injectées en Twig, mais elles peuvent aussi l'être via le fichier du formulaire dans les paramètres de chaque champ.

Traitement post-validation

modifier

Dans la même méthode du contrôleur qui injecte le formulaire, il faut prévoir le traitement post-validation. Par exemple pour mettre à jour l'entité en base :

        if (empty($myEntity)) {
            $myEntity = new MyEntity();
        }

        $form = $this->createForm(MyEntityType::class, $myEntity);
        $form->handleRequest($request); // Cette méthode remplit l'objet avec les valeurs postées dans $request pour les champs du formulaires mappés

        if ($form->isSubmitted() && $form->isValid()) {
            // Mise à jour d'un champ non mappé (ex : car absent de $myEntity)
            $email = $form->get('email')->getData();
            $this->em->persist($email);
            $this->em->flush();

            return $this->redirectToRoute('home');
        }

Fichier du formulaire

modifier

Dans SF4, l'espace de nom Symfony\Component\Form\Extension\Core\Type propose 35 types de champ, tels que :

  • Text
  • TextArea
  • Email (avec validation en option de la présence d'arrobase ou de domaine)
  • Date
  • Choice (menu déroulant)
  • Checkbox (cases à cocher et boutons radio)
  • Hidden (caché)
  • Number (génère une balise input type="number", qui empêche donc les navigateurs d'écrire des lettres dedans)
  • Submit (bouton).

TextType

modifier

Exemple[3] :

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('email', TextType::class, [
                'required' => true,
                'empty_data' => 'valeur par défaut si vide à la validation',
                'data' => 'valeur par défaut préremplie à la création',
                'constraints' => [new Assert\NotBlank()],
                'attr' => ['class' => 'ma_classe_CSS'],
        ]);
    }

Pour préremplir des valeurs dans les champs :

    $form->get('email')->setData($user->getEmail());

 

L'attribut "required" peut être interprété par les navigateurs comme un "NotBlank", mais il faut tout de même le compléter avec la contrainte sans quoi un simple retrait du "required" de la page web par la console du navigateur pourrait contourner l'obligation.

ChoiceType

modifier

Il faut injecter le tableau des choix du menu déroulant dans la clé "choices", avec en clé ce qui sera visible dans la liste et en valeur ce qui sera envoyé à la soumission[4].

Ex :

        $builder
            ->add('civility', ChoiceType::class, [
                'choices' => ['Choisir' => null, 'M.' => 'M.', 'Mme' => 'Mme'],
            ])

Dans le cas où une valeur par défaut est définie dans 'data', elle doit appartenir aux valeurs du tableau de "choices", sans quoi elle ne sera pas prise en compte.

Si une valeur absente de la liste des choix est envoyée à la soumission, on peut la faire accepter en l'ajoutant à la volée avec[5] :

            ->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
                ...
            })

EntityType

modifier

De plus, en installant Doctrine, il est possible d'ajouter un type de champ "entité" directement relié avec un champ de base de données[6].

Ex :

 $builder->add('company', EntityType::class, ['class' => Company::class]);

 

En SF4, il n'y avait pas encore les types CheckboxType ou RadioType : il fallait jouer sur deux paramètres de EntityType ainsi :

Élément Expanded Multiple
Sélecteur false false
Sélecteur multiple false true
Boutons radio true false
Cases à cocher true true

Exemple :

    $builder->add('gender', EntityType::class, ['expanded' => true, 'multiple' => false]);


Pour lui donner une valeur par défaut, il faut lui injecter un objet :

$builder->add('company', EntityType::class, [
    'class' => Company::class,
    'choice_label' => 'name',
    'data' => $company,
]);

Sous-formulaire

modifier

Utiliser le nom du sous-formulaire comme type :

$builder->add('company', MySubformType::class, [
    'label' => false,
]);

Validation

modifier

Validation depuis les entités

modifier

Le validateur de formulaire d'entité peut utiliser les annotations des entités. Ex :

use Symfony\Component\Validator\Constraints as Assert;
...
#[Assert\Type('string')]
#[Assert\NotBlank]
#[Assert\Length(
        min: 1,
        max: 255,
)]

En PHP < 8 :

use Symfony\Component\Validator\Constraints as Assert;
...
    /**
     * @Assert\Type("string")
     * @Assert\NotBlank
     * @Assert\Length(
     *      min = 2,
     *      max = 50
     * )
     */

Plusieurs types de données sont déjà définis, comme l'email ou l'URL[7]. Ex :

@Assert\Email()

Validation depuis les formulaires

modifier

Sinon il permet aussi des contrôles plus personnalisés dans les types (qui étendent Symfony\Component\Form\AbstractType). Ex :

'constraints' => [
    new Assert\NotBlank(),
    new GreaterThanOrEqual(2),
    new Assert\Callback([ProductChecker::class, 'check']),
],

Validation avec un service

modifier

Pour valider une entité depuis le service validateur[8] :

use Symfony\Component\Validator\Validator\ValidatorInterface;
...
$validator->validate(
       $entity,
       $entityConstraint
   );

NB : le second paramètre est optionnel.

 

Bien que l'on voit des services correspondant aux contraintes du validateur, on ne peut pas les injecter comme les autres services mais uniquement les utiliser via le validateur général.

Exemple pour valider un email :

php bin/console debug:container |grep -i validator |grep -i email
  validator.email Symfony\Component\Validator\Constraints\EmailValidator
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Validator\ValidatorInterface;
...
        $this->validator->validate(
            'mon_email@example.com',
            new Email()
        );

Appel du formulaire Symfony dans la vue

modifier

Les fonctions Twig permettant d'ajouter les éléments du formulaire sont :

  • form_start
  • form_errors
  • form_row
  • form_widget
  • form_label

Pour afficher tout le formulaire, dans l'ordre où les champs ont été définis en PHP :

    {{ form_start(form) }}
    {{ form_end(form) }}

Pour n'afficher qu'un seul champ :

{{ form_widget(form.choosen_credit_card) }}

Les mêmes attributs qu'en PHP peuvent être définis en paramètre. Ex :

{{ form_widget(form.name, {'attr': {'class': 'address', 'placeholder': 'Entrer une adresse'} }) }}
{{ form_label(form.name, null, {'label_attr': {'class': 'address'}}) }}

Exemple complet :

{{ form_start(form) }}
    {{ form_errors(form) }}

    {{ form_label(form.name, 'Label du champ "name" écrasé ici') }}
    {{ form_row(form.name) }}
    {{ form_widget(form.message, {'attr': {'placeholder': 'Remplacez ce texte par votre message'} }) }}

    {{ form_rest(form) }}

    {{ form_row(form.submit, { 'label': 'Submit me' }) }}
{{ form_end(form) }}

Références

modifier