Vues, templates et formulaires
Table of Contents
- 1. Introduction
- 2. Structure des pages Web de l'application
- 3. Consulter les circuits
- 3.1. Mise en place d'un controleur pour les circuits
- 3.2. Écriture d'un template de consultation de la liste des circuits
- 3.3. Ajout d'un template de consultation des détails d'un circuit
- 3.4. Ajout du lien de consultation des détails d'un circuit
- 3.5. Ajout d'une gestion de cas particulier en cas d'absence de circuits en base
- 4. Modification du template de base
- 5. Ajout d'un circuit
- 6. Ajout / suppression d'étapes dans un circuit
- 7. Mise ne place de tests systèmes de l'interface Web
- 8. Aller plus loin
1 Introduction
L'objectif est de construire un ensemble de pages et de formulaires pour :
- consulter les circuits
- ajouter un circuit
- ajouter une nouvelle étape à un circuit
- supprimer une étape d'un circuit
- supprimer un circuit et ses étapes
- …
2 Structure des pages Web de l'application
On va concevoir un graphe de pages à afficher pour implémenter les fonctionnalités de notre application, et les structurer dans une arborescence d'URLs.
2.1 Principe HATEOAS
Rappel d'un des principes du REST
Hypermedia As The Engine Of Application State (hay-dee-ous)
- Une application = diagramme d'états fini
- Transitions = hyperliens
2.2 Un état
Une page Web est un état de l'application
2.3 Implémentation
- Le client doit pouvoir suivre les liens
- Chaque réponse du serveur doit contenir les liens vers les prochaines requêtes
2.4 Exemple Application Web
2.5 Diagramme états Web app TWitter
- 1 transition en entrée
- 32 transitions en sortie
3 Consulter les circuits
On va travailler dans un premier temps sur des pages "publiques" de l'application pour tout ce qui concerne la consultation. Ultérieurement, on pourrait implémenter une gestion de contrôle d'accès aux pages, en consultation, pour ne consulter que des circuits ayant effectivement été "approuvés", pour différencier ce que consulte l'administrateur du site, de ce que voient les clients. Mais dans un premier temps, on ne distingue pas ce qui est visible en fonction du profil de l'utilisateur.
3.1 Mise en place d'un controleur pour les circuits
On va ajouter un controleur dans
src/AppBundle/Controller/CircuitController.php
:
namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Component\HttpFoundation\Request; use AppBundle\Entity\Circuit; use Symfony\Component\HttpFoundation\Response; class CircuitController extends Controller { /** * @Route("/circuit/") */ public function indexAction() { $circuits = $this->getDoctrine() ->getRepository('AppBundle:Circuit') ->findAll(); dump($circuits); return new Response(); } /** * @Route("/circuit/{id}", name="circuit_show", requirements={ * "id": "\d+" * }) */ public function showAction($id) { $circuit = $this->getDoctrine() ->getRepository('AppBundle:Circuit') ->find($id); dump($circuit); return new Response(); } }
Tester que ce controlleur basique fonctionne, et renvoie bien les
valeurs de "debug" via dump()
, en accédant avec le navigateur à :
http://localhost:8000/app_dev.php/circuit/
http://localhost:8000/app_dev.php/circuit/1
Rappel: on lance l'environnement de tests de Symfony avec :
php bin/console server:run
3.2 Écriture d'un template de consultation de la liste des circuits
Ajouter un template Twig pour l'affichage de la liste des circuits, destiné à remplacer le
dump($circuits);
.Créer le fichier
app/Resources/views/circuit/index.html.twig
{% extends 'base.html.twig' %} {% block body %} <h1>Nos circuits</h1> {{ dump(circuits) }} {% endblock %}
Modifier la génération de la réponse dans le controleur pour générer le HTML obtenu par l'exécution du template :
... return $this->render('circuit/index.html.twig', array( 'circuits' => $circuits)); ...
Tester que l'affichage des circuits renvoie bien le debug des objets du modèle passés à la vue
En s'inspirant des exemples de la documentation http://symfony.com/doc/current/book/templating.html, améliorer le template
index.html.twig
pour afficher une liste de circuits formatée en HTML. La liste des circuits devra ressemblera à ceci, dans un premier temps :... <div class="circuit"> <h2>1 : Andalousie</h2> <p>Départ de Grenade pour 1 j. de voyage à travers le Espagne, pour terminer à Séville.</p> </div> <div class="circuit"> <h2>2 : Vietnam</h2> <p>Départ de Hanoi pour 3j. de voyage à travers le VietNam, pour terminer à Hô Chi Minh.</p> </div> ...
3.3 Ajout d'un template de consultation des détails d'un circuit
Ajouter un template circuit/circuit_show.html.twig
affichant les
attributs d'une instance de Circuit
, qui sera affiché via
CircuitController::showAction()
. Dans un premier temps, on
n'affichera pas les détails de toutes les étapes.
L'affichage du template donnera quelque chose du style :
<h1>Détails du circuit</h1> <div class="circuit_details"> <p>Description: Andalousie</p> <p>Départ de Grenade pour j. de voyage à travers le Espagne, pour terminer à Séville.</p> <p>Etapes :</p> <table> <tr> <th>Numéro</th> <th>Ville</th> <th>Nombre jours</th> <th></th> </tr> </thead> <tr> <td>1</td> <td>Grenade</td> <td>1 j.</td> </tr> <tr> <td>2</td> <td>Cordoue</td> <td>2 j.</td> </tr> <tr> <td>3</td> <td>Séville</td> <td>1 j.</td> </tr> </table> </div>
3.4 Ajout du lien de consultation des détails d'un circuit
Modifier le template de liste des circuits pour ajouter un lien vers les détails du circuit.
Le lien s'appuiera sur l'utilisation de la route définie pour la consultation au niveau des annotations du controleur.
... <a href="{{ path('circuit_show', { id: circuit.id }) }}"> {{ circuit.description }} </a> ...
3.5 Ajout d'une gestion de cas particulier en cas d'absence de circuits en base
Essayer de consulter les détails d'un circuit inexistant en base de
données, par exemple sur http://localhost:8000/app_dev.php/circuit/5
Au lieu d'un plantage de l'application (erreur 500), on souhaiterait plutôt afficher un message d'erreur.
Modifier CircuitController::showAction()
pour générer une exception,
et vérifier qu'elle provoque bien un code de retour HTTP 404 Not
Found (cf. http://symfony.com/doc/current/book/controller.html#managing-errors-and-404-pages).
... throw $this->createNotFoundException('The circuit does not exist'); ...
On devrait ensuite ajouter le template d'erreur correspondant, si l'on
veut un message d'erreur plus explicite, via un template dans
app/Resources/TwigBundle/views/Exception/error404.html.twig
(cf. http://symfony.com/doc/current/cookbook/controller/error_pages.html), mais il
est un peu délicat de tester cela, car on doit changer
d'environnement, de dev à prod
4 Modification du template de base
L'objectif est d'ajouter un menu de navigation dans les pages de l'application.
L'objectif, pour l'instant, n'est pas d'implémenter une présentation ni très riche, ni très ergonomique : on verra plus tard on verra comment utiliser des CSS, et bootstrap.
On modifiera pour cela le fichier de template twig dont on souhaite qu'"héritent" toutes les pages de l'application.
5 Ajout d'un circuit
On va travailler sur la zone /admin/
du site, donc sur
/admin/circuit/
, dans un premier temps. L'objectif est de se limiter
pour l'instant à la partie back-office.
C'est le même contrôleur CircuitController
qui gérera les actions, même si le routage
met en oeuvre des URLs différentes, aussi bien pour le front-office
que pour le back-office :
<?php // ... class CircuitController extends Controller { /** * @Route("/circuit/") */ public function indexAction() { // ... } /** * @Route("/circuit/{id}", name="circuit_show"... */ public function showAction($id) { // ... } /** * @Route("/admin/circuit/new", name = "add_circuit") */ public function newAction(Request $request) { // ... } }
On se référera à la documentation Symfony sur les formulaires : http://symfony.com/doc/current/book/forms.html
5.1 Ajout d'une page "nouveau circuit"
Ajouter, en respectant l'arborescence des fichiers standard d'une
application Symfony, le template admin/circuit/new.html.twig
contenant le formulaire. On complétera ensuite le template pour générer une
page HTML valide.
... {{ form_start(form) }} {{ form_widget(form) }} {{ form_end(form) }} ...
5.2 Ajout de l'action "nouveau circuit"
On complètera le squelette de méthode suivant, pour gérer la dynamique d'affichage des pages à la soumission du formulaire.
$circuit = new Circuit(); $form = $this->createFormBuilder($circuit) ->add('description') ->add('save', SubmitType::class, array('label' => 'Create circuit')) ->getForm(); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($circuit); $em->flush(); return $this->redirectToRoute(...A_COMPLETER..., array( 'id' => $circuit->getId())); } return $this->render(...A_COMPLETER..., array( 'form' => $form->createView(), ));
6 Ajout / suppression d'étapes dans un circuit
De façon similaire aux opérations sur les circuits, on ajoutera un
controleur pour les étapes : EtapeController
, gérant les actions
suivantes :
indexAction($circuitId)
: listes des étapes d'un circuitnewAction($circuitId, Request $request)
: ajout d'une étape dans un circuitdeleteAction($circuitId, $etapeId)
: suppression d'une étape d'un circuit
L'ajout d'une nouvelle étape se fera en fin de circuit, en s'assurant que le numéro d'étape est incrémenté par rapport à l'étape précédente, plutôt qu'en le faisant saisir par l'utilisateur.
La suppression se fera sans confirmation préalable, même si, dans une application réelle, on préfèrerait un dialogue de confirmation.
7 Mise ne place de tests systèmes de l'interface Web
Afin de garantir que l'application fonctionne de bout en bout, on va mettre en place un test des controleur afin de vérifier que les actions effectuées via l'interface Web (requêtes HTTP) sont bien prises en compte au niveau de la base de données.
On va réutiliser le bundle LiipFunctionalTestBundle afin de tester l'action de suppression d'une étape d'un circuit en injectant des requêtes destinées à déclencher les actions des controleur.
Vous ajouterez une classe EtapeControllerTest
héritant de
WebTestCase
qui charge les fixtures de circuits et étapes, et
vérifie que la suppression d'une étape (par exemple, la deuxième) du
circuit "vietnam" décrémente bien le nombre d'étapes présentes dans la
base pour ce circuit (initialement 4 étapes).
La méthode de test se basera sur le squelette suivant :
... const CIRCUIT = 'vietnam-circuit'; const NBETAPES = 4; public function testRemoveEtapeWeb() { $em = $this->getContainer()->get('doctrine')->getManager(); $circuit = $this->fixtures->getReference(EtapeControllerTest::CIRCUIT); $etape = $em->getRepository('AppBundle:Etape')->findOneBy(array("circuit" => $circuit->getId(), "numeroEtape" => 2)); $client = static::makeClient(); $crawler = $client->request('GET', ...A_COMPLETER...); $this->assertStatusCode(302, $client); unset $circuit; $circuit = $this->fixtures->getReference(EtapeControllerTest::CIRCUIT); $nbetapes = $circuit->getEtapes()->count(); $this->assertEquals(EtapeControllerTest::NBETAPES-1, $nbetapes); } ...
8 Aller plus loin
Vous pouvez compléter l'application pour ajouter d'autres pages et formulaires pour les différentes actions.
Vous pouvez allez vérifier dans la documentation Symfony comment on peut mettre en oeuvre des mécanismes de vérification des données saisies dans les formulaires, en lien avec le Modèle de l'application.
Par exemple, ici, on n'a pas mis en oeuvre de controle de saisie sur les pays, ou les villes. On verra plus tard dans le cours comment mettre en place des widgets de saisie permettant par exemple une auto-complétion pour éviter des saisies erronées, en utilisant Javascript.