📘

Formulaires

Codes rĂ©pĂ©titifs, validation difficile, HTML illisible, beaucoup d’erreurs possibles


Le paquet Form permet d’automatiser la mise en place d’un formulaire et d’en faciliter la validation (les donnĂ©es soumises et validĂ©es sont insĂ©rĂ©es en base de donnĂ©es automatiquement). L’affichage est automatisĂ©.

Ce paquet est couplé avec le paquet Validator qui permet de personnaliser les rÚgles de validation.

📌
Pour aller plus loin : http://www.cril.univ-artois.fr/~hemery/enseignement/An18-19/php_symfony/td-tp/td_tp_3.html

Créer un formulaire


Un dossier Form est créé dans src. Le formulaire est une classe PHP qui hérite de Form.

Son nom correspond Ă  celui de l’entitĂ© concernĂ©e, on peut y ajouter le mot-clĂ© “Form” et ilse termine toujours par “Type”.

Il contient la méthode buildForm() avec une variable $builder qui liste les champs du formulaire ou input. Il devine un type par défaut.

// MovieFormType
<?php

namespace App\Form;

use App\Entity\Movie;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class MovieFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('title')
            ->add('releaseYear')
            ->add('country')
            ->add('wasSeen')
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Movie::class,
        ]);
    }
}

Tout est personnalisable.

Les types de champs sont nombreux, dont Text, TextArea, Email, Password, Integer, Search, Tel, Choice, Entity, Date, File. On les modifie en indiquant le type suivi de ::class (on fait appel à la classe en général, sans passer par une instance).

Des options peuvent ĂȘtre ajoutĂ©es dans un tableau, comme label, attr et required.

class MovieFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('title', TextType::class, [
                "label" => "Titre ",
                "required" => true
            ])
            ->add('releaseYear', IntegerType::class, ["label" => "Année de sortie "])
            ->add('country', TextType::class, ["label" => "Pays "])
            ->add('wasSeen', CheckboxType::class, ["label" => "Déjà vu ?"])
        ;
    }
â„č
C’est ici qu’on dĂ©finit les contraintes par rapport Ă  l’utilisateur, comme les champs obligatoires qui se transforme en required dans le fichier HTML.
⚠
Ce ne sont en aucun cas des contraintes de validation des donnĂ©es, simplement des indications pour aider l’utilisateur Ă  remplir correctement le formulaire.

Afficher un formulaire


Dans la classe du formulaire (-Type), on définit les champs.

Dans le contrĂŽleur correspondant,

#[Route('/create', name: '_create')]
    public function create(): Response
    {
        $movie = new Movie();
        $movieForm = $this->createForm(MovieFormType::class, $movie);
        return $this->render('movie/create.html.twig',
            compact('movieForm')
        );
    }

Dans le twig, il est possible d’afficher le formulaire de façon plus ou moins dĂ©composĂ©e

{{ form(movieForm) }}
{{ form_start(movieForm) }}
{{ form_widget(movieForm) }}
<button>OK</button>
{{ form_end(movieForm) }}
{{ form_start(movieForm) }}
{{ form_widget(movieForm.champ1) }}
{{ form_widget(movieForm.champ2) }}
{{ form_widget(movieForm.champ3) }}
<button>OK</button>
{{ form_end(movieForm) }}
{{ form_start(movieForm) }}
<div class="champ">
	{{ form_label(movieForm.champ1) }}
	{{ form_widget(movieForm.champ1) }}
	{{ form_errors(movieForm.champ1) }}
</div>
(...)
<button>OK</button>
{{ form_end(movieForm) }}
{{ form_start(movieForm) }}
<div class="champ">
<label for="{{ field_name(movieForm.champ1) }}">Champs 1</label>
	<input type="text"
			id="{{ field_name(movieForm.champ1) }}"
			name="{{ field_name(movieForm.champ1) }}"
			value="{{ field_value(movieForm.champ1) }}"
			placeholder="{{ field_label(movieForm.champ1) }}"
			class="form-control"
	>
	
	{% for error in field_errors(movieForm.champ1) %}
		<div class="error">{{ error}}</div>
	{% endfor%}
</div>
(...)
<button>OK</button>
{{ form_end(movieForm) }}

Pour le bouton de soumission du formulaire, il existe deux possibilités :

soit l’ajouter dans la classe du formulaire : ->add('ok', ButtonType::class) ou ->add('ok', SubmitType::class)

soit l’ajouter dans le Twig qui affiche le formulaire, avec une balise <input type="submit"> ou une balise <button>

Pour pré-remplir une case, on utilise les setters dans le contrÎleur : $movie->setCountry("France");.

Pour ajouter un thùme, on l’indique dans le Twig (ex : bootstrap_5_layout.html.twig).

{% form_theme serieForm 'nomdutheme' %}

Les classes correspondantes sont ajoutées lors de la compilation en HTML. Il faut en plus rajouter bootstrap en CDN par exemple :

// dans le base.html.twig pour appliquer partout
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
📌
Pour aller plus loin : https://symfony.com/doc/current/form/form_themes.html

Traiter un formulaire


Le traitement se fait sur la mĂȘme page.

Dans le contrĂŽleur,

#[Route('/create', name: '_create')]
    public function create(
        Request $request,
        EntityManagerInterface $entityManager
    ): Response
    {
        $movie = new Movie();
        $movieForm = $this->createForm(MovieFormType::class, $movie);
        $movieForm->handleRequest($request);
        if ($movieForm->isSubmitted()) {
						// traitement, ici insertion en base
            $entityManager->persist($movie);
            $entityManager->flush();
            $this->addFlash('success', 'Film ajouté à la liste !');
            return $this->redirectToRoute('movie_list');
        }
        return $this->render('movie/create.html.twig',
            compact('movieForm')
        );
    }
â„č
Quand le formulaire est soumis, ça recharge la page (sauf si on indique une redirection avec $this->redirectToRoute('movie_list');).

Pour afficher un message sur la page suivante, on utilise des message flash qui sont stockĂ©s en session et dĂ©truits dĂšs qu’ils sont affichĂ©s.

{% for label, messages in app.flashes%}
	{% for message in messages %}
		<div class="alert-{{ label }}">
			{{ message }}
		</div>
	{% endfor%}
{% endfor%}
â„č
Pour modifier l’affichage du message flash en CSS, on utilise le sĂ©lecteur de classe .alert-lelabel :
.alert-success {
    text-align: center;
    background-color: darkseagreen;
    color: white;
    padding: 1%;
}

Pour vider les champs aprĂšs soumission,

on crĂ©e une nouvelle instante de l’entitĂ© ($serie = new Serie();) et un autre createForm() pour le lier aprĂšs le flush,

ou on redirige (sur la mĂȘme page, ou sur une page qui indique que le traitement a bien fonctionnĂ©)

BONUS

Un paquet permet de gĂ©nĂ©rer automatiquement les 4 formulaires du CRUD avec les contrĂŽleurs et les Twigs. Il gĂ©nĂšre mĂȘme les tests unitaires.

Valider les données


Les rĂšgles de validation sont des contraintes mĂ©tier permettent de s’assurer que l’utilisateur envoie des donnĂ©es telles qu’attendues.

Sous Symfony, on valide l’entitĂ© avec une configuration faite avec XML, YAML, PHP, annotations ou attributs (BP).

Il existe plus de 40 validations prĂ©dĂ©finies et il est possible d’en crĂ©er d’autres.

Exemples : NotBlank, Type, Email, Length, Url, Regex, Range, EqualTo / NotEqualTo, LessThan / GreaterThan, Date, Choice, UniqueEntity, File, Image, Callback.

Pour appliquer une contrainte :

BP : On met toujours une contrainte de longueur sur les string, pour qu’elles ne soient pas tronquĂ©es en base.

â„č
On a ainsi des contraintes “front” avec l’Assert et des contraintes “back” avec l’ORM, elles sont complĂ©mentaires.

Pour que les contraintes fonctionnent, on vĂ©rifie que le formulaire a Ă©tĂ© soumis (l’utilisateur a cliquĂ© sur le bouton) ET qu’il est valide (tous les champs respectent les contraintes) :

if ($movieForm->isSubmitted() && $movieForm->isValid())

Il est possible de créer une contrainte, avec une classe qui hérite de Constraints (un peu long).

En vrac


⚙
Outils : Suite Burp, un outil dĂ©diĂ© Ă  l’audit de sĂ©curitĂ© des plateformes web https://portswigger.net/burp https://www.vaadata.com/blog/fr/introduction-burp-outil-dedie-securite-plateformes-web/