
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.
Créer un formulaire
- installer les deux paquets :
composer require form
- créer un formulaire :
symfony console make:form>MonFormType>MonEntiteLiee
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 ?"])
;
}required dans le fichier HTML.
Afficher un formulaire
Dans la classe du formulaire (-Type), on définit les champs.
Dans le contrĂŽleur correspondant,
- on crĂ©e une instance de lâentitĂ© associĂ©e
- on crée une instance du formulaire
- on envoie le formulaire vers le twig
#[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
- en une ligne
{{ form(movieForm) }}- en indiquant le début, le milieu, la fin (BP)
{{ form_start(movieForm) }}
{{ form_widget(movieForm) }}
<button>OK</button>
{{ form_end(movieForm) }}- en détaillant chaque champ
{{ form_start(movieForm) }}
{{ form_widget(movieForm.champ1) }}
{{ form_widget(movieForm.champ2) }}
{{ form_widget(movieForm.champ3) }}
<button>OK</button>
{{ form_end(movieForm) }}- en détaillant encore plus chaque champ
{{ 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) }}- en dĂ©taillant autant quâen HTML
{{ 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">
Traiter un formulaire
Le traitement se fait sur la mĂȘme page.
Dans le contrĂŽleur,
- on injecte
Request
- on utilise la mĂ©thode de lâinstance de formulaire
handleRequestpour gérer le traitement de la saisie
- on vérifie si le formulaire est soumis avec
isSubmitted
- si oui, on effectue le traitement souhaitĂ© (insertion ou mise Ă jour donnĂ©es, redirection, affichage dâun message Ă lâutilisateur)
#[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')
);
}$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.
- on en crée un dans le contrÎleur, généralement aprÚs la soumission et la validation des données :
$this->addFlash('success', 'Film ajouté à la liste !');
- on indique comment afficher le message flash dans le Twig avec une boucle
for, généralement dans le base.html.twig pour en simplifier la gestion
{% for label, messages in app.flashes%}
{% for message in messages %}
<div class="alert-{{ label }}">
{{ message }}
</div>
{% endfor%}
{% endfor%}.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.
- installer le paquet :
composer require security-csrf
- générer tout ça :
symfony console make:crud>MonEntite>LeController>Yes(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 :
- installer le paquet :
composer require validatorDans lâentitĂ©,
- importer la classe Constraints et lui donner lâalias Assert :
use Symfony\Component\Validator\Constraints as Assert;
- ajouter des attributs à façon avec
#[@Assert\UneContrainte(des options)]
BP : On met toujours une contrainte de longueur sur les string, pour quâelles ne soient pas tronquĂ©es en base.
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
- Attaques CSRF (Cross Site Request Forgery) :
Dans la fonction
createForm(), Symfony ajoute lâattribut CSRF token afin de pouvoir vĂ©rifier si câest bien ce projet qui a gĂ©nĂ©rĂ© le formulaire (soupe obscure interne de vĂ©rification).En effet, il est facile de copier le code source dâun formulaire, lâenregistrer en HTML et lancer une copie = une faille de sĂ©curitĂ© Ă citer.
- ConnaĂźtre au minimum le OWASP Top10 : https://owasp.org/www-project-top-ten/
- Un paquet pour hasher un mot de passe :
symfony console security:hashIl donne accĂšs Ă une mĂ©thode qui nous demande le mot de passe en clair et produit le hash correspondant, quâon peut insĂ©rer en db.
Utile notamment si on veut changer un mot de passe quâon a oubliĂ©, quand on est en dĂ©vâŠ