Formulaire Symfony

Un formulaire Symfony sur toutes vos pages

Voici un petit tuto pour expliquer comment ajouter un même formulaire Symfony sur toutes vos pages de votre projet.

J’ai récemment démarrer un nouveau projet Symfony. Celui-ci diffère des autres projets que j’ai pu développer jusque là avec ce framework, puisqu’il s’agit d’un site Web, avec des fonctionnalité assez spécifiques évidemment, sinon l’usage de ce framework dans le cadre d’un simple site ne serait pas justifié. J’utilises habituellement ce framework pour des projets orientés « back-office ».

Mais en fait sur ce projet j’ai été confronté à un problème que je n’avais pas encore rencontré jusque là : Comment ajouter simplement un même formulaire sur toutes les pages de mon projet symfony ?

En gros je voulais simplement inclure un formulaire d’inscription à la newsletter dans le footer de mon fameux site. Et je voudrais pourquoi pas ré-utiliser ce même formulaire ailleurs (page de contenu, landing etc….).

Formulaire Symfony, un composant dédié

Symfony offre la possibilité d’utiliser un composant « Form » qui est vraiment très cool et super pratique pour la gestion des formulaires.

Pour ceux qui ne connaissent pas le framework, petit guide d’utilisation :

1. On créé le formType

Le mieux quand on veut créer un formulaire réutilisable ailleurs c’est de créer une classe « Form » associée à ce formulaire, le plus simple est le plus rapide est de lancer cette commande au niveau de votre projet :

php bin/console make:form NewsletterType

Indiquez l’entité liée au formulaire et validez, moi il n’y pas d’entité pour les newsletter donc j’ai laissé vide.

Cette commande vous génère donc un fichier de cette forme  :

<?php
#src/Form/NewsletterType.php
namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class NewsletterType extends AbstractType
{
 public function buildForm(FormBuilderInterface $builder, array $options)
 {
 $builder
 ->add('field_name')
 ;
 }

public function configureOptions(OptionsResolver $resolver)
 {
 $resolver->setDefaults([
 // Configure your form options here
 ]);
 }
}

Modifiez le pour qu’il ressemble à ça :

<?php

namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;

class NewsletterType extends AbstractType
{
 public function buildForm(FormBuilderInterface $builder, array $options)
 {
 $builder
 ->add('mail', EmailType::class, array('label' => false, 'attr' => array('placeholder' => 'Adresse Email', 'class' => 'validate-required validate-email')))
 ->add('save', SubmitType::class, ['label' => 'Envoyer', 'attr' => array('class' => 'btn btn--primary type--uppercase')])
 ;
 }

public function configureOptions(OptionsResolver $resolver)
 {
 $resolver->setDefaults([
 // Configure your form options here
 ]);
 }
}

Si vous avez besoin de gérer d’autres types de champ dans votre formulaire, je vous renvois à la documentation officielle.

2. On déclare le formulaire dans le controller

Ensuite dans le controller où vous avez besoin de ce formulaire, vous le déclarer de cette manière au niveau de la méthode voulue.

<?php

namespace App\Controller;

use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

use App\Form\NewsletterType;

class PageController extends Controller
{
 /**
 * @Route("/", name="home")
 */
 public function index()
 {
 $newslsetter = null;
 $form = $this->createForm(NewsletterType::class);
 
 return $this->render('page/index.html.twig', [
 'form' => $form->createView(),
 ]);
 }
}

Vous voyez dans cet exemple, que nous envoyons le formulaire en paramètre au template.

3. Affichage du formulaire

L’affichage du formulaire dans votre template twig est assez simple, moi je fais ça :

{{ form_start(form) }}
{{ form_widget(form.mail) }}
{{ form_widget(form.submit) }}
{{ form_end(form) }}

Mais vous pouvez faire encore plus simple :

{{ form(form) }}

Si le résultat ne correspond pas à vos attentes, encore une fois direction la doc symfony sur l’affichage des formulaires.

4. Traitement du formulaire symfony

Enfin pour traiter le formulaire, retournez dans votre controller et modifiez le de cette manière :

<?php

namespace App\Controller;

use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

use App\Form\NewsletterType;

class PageController extends Controller
{
 /**
 * @Route("/", name="home")
 */
 public function index(Request $request)
 {
 $form = $this->createForm(NewsletterType::class);

 $form->handleRequest($request);
 
 if ($form->isSubmitted() && $form->isValid()) {
 
 if (filter_var($form->getData()['mail'], FILTER_VALIDATE_EMAIL)) {
 //Saisissez ici votre code pour enregistrer le mail saisi.
 //...
 }
 
 return $this->render('page/index.html.twig', [
 'form' => $form->createView(),
 ]);
 }
}

Et c’est tout ? Il est où le problème ?

Le problème c’est que si je veux afficher mon formulaire dans chaque page de mon site, je vais devoir le déclarer dans chacun de mes controllers appelés à afficher des pages Web, c’est à dire que ce code :

//Appel de la classe
use App\Form\NewsletterType;

...

//Déclaration
$form = $this->createForm(NewsletterType::class);

...

//Envoi au template
return $this->render('page/index.html.twig', [ 'form' => $form->createView(), ]);

Devra être répété X fois. Quand on aime la factorisation ça fait mal au coeur :'(.

Le secret réside dans le service

Encore un truc cool de Symfony c’est le service container, je ne vais pas faire une explication ici car ce n’est pas vraiment le sujet, mais allez sur la doc pour vous cultiver un peu.

Mais pour résumé les choses, cela va rendre notre formulaire symfony encore un peu plus portable.

1. On créé le service

Je n’ai pas trouvé de commande pour cela, mais vous allez créer un fichier « FormNewsletter.php » dans le répertoire « src/Service » :

<?php

namespace App\Service;

use App\Form\NewsletterType;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Form\FormFactoryInterface;

class FormNewsletter
{
 
 private $form; 
 
 private $router;
 
 private $formFactory;
 
 public function __construct(UrlGeneratorInterface $router, FormFactoryInterface $formFactory) {
 
 $this->router = $router;
 
 $this->formFactory = $formFactory;
 
 $this->form = $this->formFactory->create(
 NewsletterType::class,
 NULL, 
 array(
 'attr' => 
 array(
 'action' => $this->router->generate('add-newsletter')
 )
 )
 );
 }
 
 public function getForm() {
 return $this->form;
 }
}

Vous voyez que dans le constructeur, on passe le service de routing, ça permet de générer l’URL qui va traiter le formulaire.

Au niveau de la configuration, il n’y a rien à faire pour déclarer le service.

2. On configure twig

Si vous  avez bien compris la problèmatique, c’est le coeur du débat. Le formulaire doit être passé à votre template pour pouvoir y être affiché. Donc on va configurer une variable globale au niveau du fichier conf de twig.

Ouvrez le fichier « config/packages/twig.yaml » et ajouter les lignes en gras :

twig:
 paths: ['%kernel.project_dir%/templates']
 debug: '%kernel.debug%'
 strict_variables: '%kernel.debug%'
 globals:
 form_newsletter: '@App\Service\FormNewsletter'

3. Affichage du formulaire symfony

La variable « form_newsletter » est maintenant accessible à tout vos templates twig, l’affichage va être la même chose que vue précedemment. Il faudra simplement au préalable appelé la fonction getForm de notre service et la fonction createView du formulaire, ceci est résumé dans cette ligne de code :

{% set formNews = form_newsletter.getForm().createView() %}

Enfin, pour l’affichage nous avons ceci :

{{ form_start(formNews) }}
{{ form_widget(formNews.mail) }}
{{ form_widget(formNews.save) }}
{{ form_end(formNews) }}

4. Traitement du formulaire symfony

Je n’ai donc qu’un seul controller qui gère le traitement du formulaire et il ressemble à cela :

<?php

namespace App\Controller;

use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use App\Service\FormNewsletter;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class NewsletterController extends Controller
{
 /**
 * @Route("/newsletter/add", name="add-newsletter")
 * @Method({"POST"})
 */
 public function add(FormNewsletter $formNewsletter, Request $request)
 {
 $form = $formNewsletter->getForm();
 
 $form->handleRequest($request);
 
 $var_return = 0;
 
 if ($form->isSubmitted() && $form->isValid()) {
 // traitement du formulaire
 //....
 }
 
 return new Response($var_return); // Simple réponse car mon formulaire est exécute en Ajax
 }
}

On passe donc le service « FormNewsletter » dans le controller pour pouvoir le traiter dans le controller. Pour tout les autres controllers, vous pouvez oublié toutes références à ce service et au formulaire newsletter.

Voilà pour ce tuto, j’espère qu’il sera compréhensible, si vous avez une autre technique pour faire la même chose qui soit un peu moins fastidieuse, je suis preneur, les commentaires sont là pour ça.