Architecture(s) et
application(s) Web

CSC4101 - Gestion avancée des routes et formulaires

09/10/2024

Plan de la séquence

Objectifs de cette séance (1h) :

  1. Formulaires améliorés
  2. Aller au-delà des CRUDs basiques
  3. Téléversement d'images

Vers des formulaires gérés avec une seule méthode

  • Affichage d’un formulaire HTML : méthode d’un contrôleur (insertion d’un formulaire dans un template)
  • Gestion de la soumission du formulaire : méthode du même contrôleur (par exemple la même méthode)

Passer à une seule méthode :

  • Rendre le code plus compact
  • Lisibilité ?

Rappel : Variante à 2 méthodes

Méthode gérant le GET : affichage du formulaire HTML

#[Route('/todo/new', name: 'todo_new_get', methods: ['GET'])]
public function newFormGet(): Response
{
    $todo = new Todo();
    // init valeurs par défaut
    $form = $this->createForm(TodoType::class, $todo);

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

Méthode gérant le POST : traitement des données soumises

#[Route('/todo/new', name: 'todo_new_post', methods: ['POST'])]
public function newFormPost(Request $request, TodoRepository $todoRepository): Response
{
    $todo = new Todo();
    $form = $this->createForm(TodoType::class, $todo);
    $form->handleRequest($request);

    if($form->isSubmitted() && $form->isValid()) {
        $todoRepository->add($todo, true);

        return $this->redirectToRoute('todo_index');
    }
    # else {
    return $this->render('todo/new.html.twig', [
        'todo' => $todo,
        'form' => $form->createView(),
    ]);
    # }
}

Formulaire moderne

Fusionne en une seule méthode gestion du GET et du POST

Cf. Forms dans la documentation Symfony.

Route et méthode unique

#[Route('/todo/new', name: 'todo_new', methods: ['GET', 'POST'])]
public function new(Request $request, TodoRepository $todoRepository): Response
{
    $todo = new Todo();
    $form = $this->createForm(TodoType::class, $todo);
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {
        $todoRepository->add($todo, true);

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

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

Génération des contrôleurs et formulaires

Symfony peut aussi générer des classes contenant le code des contrôleurs et les formulaires

symfony console make:crud

Exemple : nouvelle tâche

#[Route('/new', name: 'todo_new', methods: ['GET', 'POST'])]
public function new(Request $request, TodoRepository $todoRepository): Response
{
    $todo = new Todo();
    $form = $this->createForm(TodoType::class, $todo);
    $form->handleRequest($request);

    // Gestion du POST
    if ($form->isSubmitted() && $form->isValid()) {
        $todoRepository->add($todo, true);

        return $this->redirectToRoute('todo_index', [], Response::HTTP_SEE_OTHER);
    }

    // Gestion du GET ou problème validation
    return $this->renderForm('todo/new.html.twig', [
        'todo' => $todo,
        'form' => $form,
    ]);
}
  • TodoType est vue plus loin

Gabarit todo/new.html.twig :

{% extends 'base.html.twig' %}

{% block title %}New Todo{% endblock %}

{% block main %}
    <h1>Create new Todo</h1>

    {{ include('todo/_form.html.twig') }}

    <a href="{{ path('todo_list') }}">back to list</a>
{% endblock %}

Gabarit templates/todo/_form.html.twig :

{{ form_start(form) }}
    {{ form_widget(form) }}
    <button class="btn">{{ button_label|default('Save') }}</button>
{{ form_end(form) }}

(aussi inclus par todo/edit.html.twig)

Classe TodoType gestionnaire de formulaire

class TodoType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title', TextType::class,
                  ['required' => false])
            ->add('completed', ChoiceType::class,
                  ['choices' => [
                      'Maybe' => null,
                      'Yes' => true,
                      'No' => false ]
                  ])
            ->add('project');
    }
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Todo::class,
        ]);
    }
}

< / code formulaire moderne >

Chargement auto des instances

Mécanisme MapEntity des contrôleurs

  • Paramètre {id} de l’attribut Route : extrait du chemin de la requête HTTP
  • Argument de la méthode : $todo, instance de Todo
  • Chargement auto grâce au Repository de Todo (appel à TodoRepository::find() automagique)
class TodoController extends Controller
{
    #[Route('/todo/{id}', name: 'todo_show'),
      requirements: ['id' => '\d+'], methods: ['GET'])]
    public function showAction(Todo $todo): Response
    {
        return $this->render('todo/show.html.twig', array(
            'todo' => $todo,
        ));
    }

Plan de la séquence

Objectifs de cette séance (1h) :

  1. Formulaires améliorés
  2. Aller au-delà des CRUDs basiques
  3. Téléversement d'images

Ajout entités liées

Création d’une Todo dans le contexte d’un Project

Version basique

Version /todo/new créée via make:crud

  • Formulaire TodoType :

    class TodoType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder
                // ...
                ->add('project');
    
    

    la référence au projet de la tâche (Todo::project) est gérée comme une propriété « ordinaire » de la tâche dans le formulaire

Résultat : liste de choix parmi les projets

ajout-popup-entite-liee.png
Figure 1 : « Sélection d’une entité liée dans un popup »

Pas ce qu’on souhaite, si une tâche ne peut pas changer de projet

Formulaire d’ajout, depuis l’affichage d’un projet

  • Dans page affichage d’un projet (project_show, sur /project/{id})
  • Sous l’affichage de la liste des tâches d’un projet : bouton/lien « ajouter nouvelle tâche »
  • Chemin du formulaire d’ajout : /project/{id}/addtodo
  • id de projet (connu) !

Code final

#[Route('/project/{id}/addtodo', name: 'todo_add',
        methods: ['GET', 'POST'])]
public function addTodo(Request $request, Project $project,
                        TodoRepository $todoRepository): Response
{
        $todo = new Todo();
        $todo->setProject($project);

        $form = $this->createForm(TodoType::class, $todo);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {

            $todoRepository->add($todo, true);
            // ...

Contexte :

  • chargement automagique du projet d’après l’{id} de la route
  • tâche créée dans son projet :

    $todo->setProject($project)

Supprimer le champ project du formulaire TodoType

Éviter la modification du champ project.

Comportement optionnel (garde fonctionnement initial formulaire nouvelle tâche isolée).

$form = $this->createForm(TodoType::class, $todo,
        ['display_project' => false]);

public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('title')
            ->add('created', DateType::class, [
                  'widget' => 'single_text'])
            ->add('project', null, [
                  'disabled' => $options['display_project'] ]);
    }

</formulaires>

Plan de la séquence

Objectifs de cette séance (1h) :

  1. Formulaires améliorés
  2. Aller au-delà des CRUDs basiques
  3. Téléversement d'images

Outil pour upload d’image dans formulaire Symfony

Exemple : utilisation de VichUploaderBundle pour mettre des photos dans les Pastes

  • Formulaire upload de photo
  • Stockage dans public/
  • Affichage dans gabarit

Entité Paste

#[ORM\Entity(repositoryClass: PasteRepository::class)]
#[Vich\Uploadable]
class Paste
{
    //...
    #[ORM\Column(nullable: true)]
    private ?string $imageName = null;

    #[Vich\UploadableField(mapping: 'pastes',
                           fileNameProperty: 'imageName')]
    private $imageFile;

    //           .../...
  • imageName stocké en base (Doctrine)
  • imageFile objet « fichier téléversé » Symfony
        /**
         * @param File|\Symfony\Component\HttpFoundation\File\UploadedFile $imageFile
         */
        public function setImageFile(?File $imageFile = null): void
        {
                $this->imageFile = $imageFile;

                //...
        }

}

Formulaire Paste

class PasteType extends AbstractType
{
        public function buildForm(FormBuilderInterface $builder,
                              array $options)
        {
                $builder
                        ->add('content')
                        ->add('created')
                        ->add('content_type')
                        ->add('imageName', TextType::class,
                              ['disabled' => true])
                        ->add('imageFile', VichImageType::class,
                              [
                                'required' => false,
                                'delete_label' => 'Delete image ?'
                              ])
                ;
        }

</upload_images>

Take Away

  • Gestion optimale intégrée entre formulaires et Doctrine
  • Ne pas se limiter à make:crud
  • Gestion des données liées dans les associations
  • Bundle pour téléversement d’images simple

Copyright

  • Document propriété de ses auteurs et de Télécom SudParis (sauf exceptions explicitement mentionnées).
  • Réservé à l’utilisation pour la formation initiale à Télécom SudParis.