TP n°5 - Gabarits (templates) Twig pour créer les pages HTML
Ajout de gabarits Twig dans l’application ToDo

Table des matières

1. Introduction

Cette séance va permettre d’expérimenter l’application des concepts présentés dans le cours qui précède, à travers la mise en œuvre d’interfaces HTML pour une application Symfony avec Twig, sur la base de l’application fil-rouge ToDo.

Nous nous concentrons sur le moteur de gabarits (templates) Twig, le composant de Symfony permettant la génération des pages Web de l’application. En utilisant Twig, on peut notamment standardiser la structure des pages du site avec un jeu de gabarits permettant d’unifier le contenu de toutes les pages.

Dans cette séance, nous faisons référence à des rudiments d’HTML, en nous appuyant sur le travail effectué en autonomie dans la séquence de travail précédente.

2. Étape 1 : Ajout de gabarits Twig dans l’application ToDo

L’objectif de cette étape est d’étudier comment fonctionne la mise en place d’un gabarit standard pour différentes pages d’une même application, et de pratiquer la syntaxe du langage Twig.

Nous allons maintenant travailler sur code de l’application de gestion de tâches (ToDo) qui nous sert de fil-rouge, en réalisant la mise en œuvre de la génération de pages avec des gabarits.

2.1. Génération du HTML grâce au moteur de rendu des gabarits

Le code des classes contrôleurs de Symfony ne manipule plus de chaînes de caractères à concaténer, pour produire du HTML. Il se contente de construire une représentation minimaliste des ressources à afficher dans les pages, puis de demander au moteur de rendu Twig d’en faire un rendu HTML. Twig va injecter cette représentation dans des gabarits qui contiennent du HTML et dans lequel sont prévus des emplacements pour insérer les différents champs de cette représentation minimaliste.

Vous allez donc faire en sorte qu’il n’y ait plus de traces de HTML dans le code PHP de la classe contrôleur. À la place nous trouvons l’appel à une méthode render() qui utilise un fichier Twig et un tableau associatif avec des variables.

  1. Vous pouvez par exemple ajouter un nouveau fichier index.html.twig dans le répertoire templates/, qui contient les fichiers gabarit de Twig utilisés par la méthode render() :

    {% extends 'base.html.twig' %}
    
    {% block title %}Welcome!{% endblock %}
    
    {% block body %}
    <h1>Welcome</h1>
    
        <p>{{ welcome }}</p>
    
    {% endblock %} {# body #}
    

    La syntaxe des gabarits Twig n’est pas forcément reconnue immédiatement par votre IDE. Mais il est probable que des plugins existent : vous pouvez installer le plugin Twig pour Eclipse, via le Eclipse Marketplace. Un plugin Jinja pourrait probablement faire l’affaire également, pour d’autres IDE.

  2. Ensuite, vous pouvez modifier la méthode d’affichage de la page d’accueil de l’application pour qu’elle utilise ce gabarit :

    #[Route('/', name: 'home', methods: ['GET'])]
    public function indexAction()
    {
        return $this->render('index.html.twig',
                [ 'welcome' => "Bonne utilisation de la todo list" ]
            );
    }
    
  3. Examinez la façon avec laquelle les données sont insérées pour construire la page affichable (HTML), au travers de l’inclusion de variables comme welcome, qui va être insérée dans la cible {{ welcome }}.
  4. Modifiez ce message et rechargez la page pour voir l’affichage changer.

On va voir plus loin que Twig ne permet pas seulement d’injecter des valeurs dans des champs à l’intérieur d’un fichier HTML. Il permet aussi d’effectuer des traitements pour factoriser des motifs, ou gérer des traitements conditionnels, par exemple.

2.2. Standardisation de la structure des pages.

Observez le code source de la page HTML générée par Twig, dans le navigateur Web (Ctrl-U, ou menu bouton droit, puis « Code source de la page »).

Vous y trouvez du code HTML ressemblant à :

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Welcome!</title>
        <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>">



            </head>
    <body>
        <h1>Welcome</h1>

    <p>Bonne utilisation de la todo list</p>


<div id="sfwdtbdd3ea" class="sf-toolbar sf-display-none" role="region" aria-label="Symfony Web Debug Toolbar"></div><script nonce="385d6c2fcbeca40f9e98545f02b77976">/*<![CDATA[*/        Sfjs = (function() {        "use strict";        if ('classList' in 

Ignorez la partie du <div> en fin de page, qui correspond à l’affichage de la barre d’outils Symfony.

Comparez avec le code du gabarit templates/index.html.twig.

Comprenez-vous le principe de surcharge du contenu du fichier base.html.twig, mis en œuvre grâce à l’instruction {% extends 'base.html.twig' %} en en-tête de index.html.twig ?

Ces différents fichiers définissent des blocs, avec les balises Twig {% block ... %} et {% endblock %}, et comme ils font référence à d’autres gabarits, cela permet de mettre en place un système de surcharge de ces blocs, comme autant de motifs permettant de construire une factorisation et une réutilisation pour la structure des pages HTML.

De la même façon, comprenez-vous le rôle des blocs « title » et « body », dans templates/base.html.twig, ?

Avec ce mécanisme de surcharge de blocs, on peut définir une structure générale, puis la personnaliser, faire des cas particuliers.

On peut ainsi décliner un gabarit général en spécificités relatives à chacune des sections ou des pages particulières de l’application.

La conception des gabarits consiste donc à chercher des motifs factorisables (généralisables), ou spécialisables, et s’apparente à la conception orientée objet qu’on applique pour la programmation.

3. Étape 2 : Utilisation de la barre d’outils Symfony pour la mise au point

L’objectif de cette étape est de découvrir les fonctionnalités de la barre d’outils de Symfony.

Pendant le développement de l’application, l’environnement Symfony est réglé en mode dev. En conséquence, en bas de chaque page Web générée par Symfony se trouve une barre d’outils permettant d’accéder à de nombreux outils facilitant la mise au point.

Pour explorer ces outils commencez par cliquer sur le logo Symfony qui ouvre la page du Symfony Profiler.

  1. Consultez les routes de l’application (symfony console debug:router)
  2. Testez le lancement de l’application :

    symfony server:start
    
  3. Testez l’affichage des pages de l’application selon les différentes routes disponibles, et consultez les informations affichées dans la barre d’outils Symfony en bas de pages :
    1. Examinez le module « Request/Response » pour vérifier les en-têtes de la requête HTTP tels qu’ils sont reçus par le serveur HTTP (ou plutôt par PHP qui s’exécute derrière le serveur)
    2. Examinez la compilation des gabarits dans le module « Twig », selon que la page HTML est construite avec ou sans Twig

      Vous pouvez regarder cela en particulier pour les pages d’EasyAdmin, qui utilise Twig en interne… et vous verrez alors un exemple d’architecture de gabarits et sous-gabarits assez impressionante.

4. Étape 3 : Remplacement du code HTML par des gabarits, dans ToDo

À l’image de ce qui a été montré ci-desssus pour la page d’accueil, modifiez les différentes méthodes du contrôleur TodoController, pour utiliser les gabarits, au lieu d’une génération de chaînes de caractère HTML dans le code PHP.

4.1. Étape 3-a : Consultation d’une collection de tâches

Créez un sous-répertoire templates/todo/ et à l’intérieur, un nouveau gabarit index.html.twig, contenant :

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

{% block title %}Welcome!{% endblock %}

{% block body %}
<h1>Todo index</h1>

<ul>
  {% for todo in todos %}
  <li>{{ todo.title }}</li>
  {% endfor %} {# todos #}
</ul>

{% endblock %} {# body #}

Modifiez le code de la méthode du contrôleur TodoController pour utiliser ce gabarit :

/**
 * Lists all todo entities.
 */
#[Route('/list', name: 'todo_list', methods: ['GET'])]
public function listAction(ManagerRegistry $doctrine): Response
{
        $entityManager= $doctrine->getManager();
        $todos = $entityManager->getRepository(Todo::class)->findAll();

        // dump($todos);

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

Notez que par rapport à la version précédente, cette fois, c’est dans le gabarit Twig que se produit l’algorithme d’itération sur l’affichage de chaque élément d’une collection, avec l’instruction {%for ITEM in COLLECTION %}.

Le contrôleur passe directement en argument la variable todos à Twig, qui contient une collection d’entités Doctrine.

Twig accède alors aux attributs des classes PHP : {{ todo.title }} résulte donc en un appel à Todo:getTitle().

Essayez de modifier le code de getTitle() pour ajouter, par exemple un caractère « ! » au début du titre de chaque tâche :

public function getTitle(): ?string
{
    return '!' . $this->title;
}

On voit bien sur cet exemple que les gabarits Twig qui construisent les vues de notre application, sont bien câblés par Symfony avec les données du modèle objet de l’application réalisé avec Doctrine.

4.2. Étape 3-b : Amélioration du rendu de la liste de tâche, sous forme de tableau

Vous pouvez modifier encore le gabarit templates/todo/index.html.twig pour afficher tous les attributs des tâches dans un tableau.

Au lieu d’une liste à puces simple, vous obtiendrez un tableau du type :

<table class="table">
    <thead>
        <tr>
            <th>Id</th>
            <th>Todo</th>
            <th>Completed</th>
            <th>Created</th>
            <th>Updated</th>
        </tr>
    </thead>
    <tbody>

        <tr>
            <td>1</td>
            <td>apprendre les bases de PHP</td>
            <td>1</td>
            <td>2018-08-06 14:03:57</td>
            <td>2018-08-06 14:03:57</td>
        </tr>

        ...

    </tbody>
</table>

Modifiez le code du gabarit pour afficher ainsi les différents attributs de chaque tâche.

Notez que les dates peuvent être affichées avec une instruction Twig du type : {{ todo.created ? todo.created|date('Y-m-d H:i:s') : '' }}, qui signifie :

  • si l’attribut created est défini, alors afficher sa valeur sous forme de date avec un certain format d’affichage
  • sinon, afficher une chaîne vide

4.3. Étape 3-c : Affichage d’une tâche

L’objectif de cette étape est de comprendre le fonctionnement de la surcharge des blocs dans les gabarits Twig.

Soyez attentifs, cette étape est un peu moins triviale… méfiez-vous du copier/coller.

Modifiez maintenant l’affichage d’une seule tâche, en vous inspirant des extraits suivants.

  1. ajoutez la méthode du contrôleur qui va gérer les requêtes HTTP entrantes

    /**
     * Finds and displays a todo entity.
     */
    #[Route('/{id}', name: 'todo_show', requirements: ['id' => '\d+'], methods: ['GET'])]
    public function showAction(Todo $todo): Response
    {
        return $this->render('todo/show.html.twig',
            [ 'todo' => $todo ]
        );
    }
    
    

    vérifiez que la nouvelle route ajoutée est bien présente dans la sortie de symfony console debug:router.

  2. ajoutez un nouveau gabarit todo/show.html.twig, contenant par exemple la génération d’un tableau HTML du type :

    {# ... #} 
      {% block body %}
          <h1>Todo</h1>
    
             {% dump todo %}
    
          <table class="table">
              <tbody>
                  <tr>
                      <th>Id</th>
                      <td>{{ todo.id }}</td>
                  </tr>
                  <tr>
                      <th>Todo</th>
                      <td>{{ todo.title }}</td>
                  </tr>
                  ...
              </tbody>
          </table>
    
          <a href="{{ path('todo_index') }}">back to list</a>
    
      {% endblock %} {# body #} 
    {# ... #} 
    

    Le lien « back to list » en bas de page contient une balise <a href="">...</a> tout à fait classique, mais dont la valeur de l’attribut href (l’URL cible), est calculé avec une fonction Twig : path( ).

    La fonction path( ) prend en premier argument le nom d’une route, qui sera la cible du lien, pour accéder à une autre page de l’application.

    Vérifiez que l’affichage fonctionne bien, en corrigeant le nom de la route si nécessaire.

    Est-ce que l’affichage fonctionne bien ? Est-ce que la barre d’outils Symfony est toujours affichée ?

    Si non, utilisez les outils du développeur Web dans le navigateur pour voir si quelque chose n’est pas cassé :

    1. Consultez le code source de la page, et vérifiez qu’elle contient bien du HTML bien formé… avec tout ce qui va bien….
    2. Y a-t-il les éléments nécessaire à l’affichage de la barre d’outils Symfony dans la page ?
  3. Réparons l’affichage : il est nécessaire de remettre en place cette génération d’HTML, en faisant comme dans le gabarit todo/index.html.twig l’inclusion du gabarit de base : {% extends 'base.html.twig' %}.

    Une fois que c’est fait, la barre d’outils Symfony doit se rafficher correctement…

    Mais par contre, voyez-vous encore le contenu « utile » de votre page, qui affiche la tâche sous forme de tableau, ou n’a-t-il pas souffert entre-temps ?

    Vérifiez la façon dont est compilé le gabarit dans le module « Twig » de la barre d’outils Symfony

    Le « Rendering Call Graph » affiche vraissemblablement quelque chose comme :

    main 1.45ms/100%
    └ todo/show.html.twig
    │ └ base.html.twig
    │   └ base.html.twig::block(title)
    │   └ base.html.twig::block(stylesheets)
    │   └ base.html.twig::block(javascripts)
    │   └ base.html.twig::block(body)
    
    

    Cela signifie que le gabarit todo/show.html.twig étend bien base.html.twig, qui affiche donc les sous-gabarits title, stylesheets, javacripts et body.

    Si le « contenu utile » de la page est vide, c’est que l’on ne voit que ça dans notre HTML… normal, on n’a ici que le contenu des blocs du gabarit de base, plutôt vide !

    Comparez avec la compilation du Twig équivalente pour la page /todo/list correspondant au gabarit todo/index.html.twig, et réparez ce qui cloche pour que la surcharge de blocs Twig fonctionne comme on le souhaite.

    Bravo : vous avez compris comment fonctionne la surcharge des gabarits. Vous savez maintenant comment factoriser des blocs et les spécialiser dans certaines pages d’une application.

    Indice : si on attend un block body, mais qu’on fournit un bloc content, ça ne marche pas exactement comme il faut…

    Ou bien tout marchait dès le début, et on a rajouté ces explications pour rien (tant mieux, et désolé pour la fausse alerte).

4.4. Étape 3-d : Ajout d’un lien de navigation entre liste des tâches et affichage d’une tâche

Vous pouvez maintenant ajouter dans la consultation de la collection des tâches, un lien pointant vers la page de consultation de chacune de ses tâches.

Ajoutez dans le tableau des tâches, une dernière colonne pour contenir des liens navigables :

<thead>
  <tr>
    ...
    <th> &nbsp;</th>
  </tr>
</thead>
<tbody>
  ...
  <tr>
    ...
    <td><a href="{{ path('todo_show', {'id': todo.id}) }}">show</a></td>
  </tr>
</tbody>

Ces liens ont l’attribut href, qui doit pointer vers l’URL d’affichage correspondant à la tâche courante. Cette URL varie, pour chaque tâche à afficher, puisqu’elle contient l’identifiant de la tâche.

La fonction path( ) doit donc cette fois encore prendre en premier argument le nom de la route, mais aussi la valeur des arguments de cette route, qui rendent l’URL dynamique. Ici, la route todo_show attend un argument ’id’, comme cela est défini dans l’attribut Route('/{id}' de TodoController::showAction.

5. Étape 4 : Utilisation de dump() pour faciliter la mise au point

En cours de développement, pour faciliter le débogage (debug), vous pourrez introduire l’affichage d’un objet pour le debug en utilisant la fonction dump() dans le code PHP, par exemple à l’intérieur d’une méthode d’action contrôleur, avant l’appel à la méthode render :

$todos = $em->getRepository(Todo::class)->findAll();
dump($todos);

Mais vous pouvez aussi utiliser la fonction dump() à l’intérieur d’un gabarit Twig, dans une instruction Twig du type :

{% dump todo %}

Voyons maintenant en détail le rôle des instructions dump() que nous avions mises dans un exemple de code et un exemple de gabarit.

Dans les deux cas, la présence des informations de debug correspondantes aux appels à dump() se retrouve dans la console Symfony, derrière l’icône en forme de « cible » qui apparaît alors dans la barre d’outils du développeur Symfony.

Attention : {% dump todo %} est différent de {{ dump(todo) }} : la première est une instruction Twig (« {% »), comme un for, alors que la seconde substitue la valeur des informations de debug (code HTML renvoyé par dump(todo)) au milieu du rendu de la page (comme pour tout accès à une variable via « {{ ... }} »).

6. Étape 5 : Affichage des tâches terminées

L’objectif de cette étape est de mettre en forme les tâches terminées de façon différente des tâches à faire, grâce aux instructions Twig dans les gabarits.

Vous allez par exemple afficher les tâches terminées comme étant barrées, dans la liste des tâches. Vous pouvez utiliser la balise HTML <del> pour ce faire.

Vous exploiterez la primitive if du langage de gabarits de Twig, en vous référant à la documentation de référence de Twig.

Modifiez le gabarit d’affichage de la liste des tâches (index.html.twig) pour intégrer ce changement

Dans les données d’exemple de l’application fournies, seule la tâche « terminer le hp 2-3 » « apprendre les bases de PHP » a le statut terminé ;-).

Comme vous l’aurez maintenant compris, dans une application Symfony, une partie des algorithmes peuvent être programmés dans le code PHP, comme par exemple le filtrage des données mis en œuvre avec findByCompleted().

Mais on peut aussi faire varier seulement la présentation des données qui est effectuée dans les gabarits, en programmant certains comportements dans les gabarits Twig.

Il faudra faire un choix : appliquer des traitements dans la couche traitements, ou dans la couche présentation… tout dépend des compétences des différents développeurs (PHP vs Twig ?), mais aussi des contraintes : performances, encombrement mémoire, factorisation. À vous de décider.

7. Étape 6 : Mise en place des gabarits Twig dans votre projet

Vous pouvez maintenant mettre en pratique ce que nous venons de voir, sur les gabarits Twig, dans votre projet.

Le principe est de structurer des pages Web avec des gabarits, plutôt que de faire du HTML « à la main ».

Poursuivez la séance en déroulant les étapes du guide de réalisation du projet.

8. Fin du TP

Sauvegardez votre travail. Vous y reviendrez ultérieurement. Autant ne rien perdre et faire des sauvegardes intermédiaires.

Si vous maîtrisez déjà l’utilisation des outils comme Git, vous pouvez bien-entendu vous en servir pour réaliser cet archivage au fil de l’eau. Prenez garde à utiliser un référentiel Git privé pour éviter le plagiat à ce niveau également.

9. Évaluation

À l’issue de cette séance, vous savez :

  • construire des pages d’une application à partir de gabarits (templates) Twig
  • vous comprenez le principe de surcharge de gabarits pour rendre la structure des pages uniforme
  • vous savez utiliser des primitives du langage Twig telles que if ou for
  • vous savez tisser des liens vers d’autres pages avec path()
  • vous savez examiner des éléments de diagnostic (pour déverminage / debug) avec dump()

10. Pour aller plus loin (optionnel)

10.1. Entity Value Resolver

Observez le code de TodoController::showAction.

Comprenez-vous comment est obtenue l’instance de Todo, $todo qui est récupérée en paramètre par cette méthode ?

Vous avez bien vu que l’identifiant id est spécifié dans l’annotation définissant la route (on précise même que c’est un entier), qui correspond à l’attribut todo.id dans la génération des liens avec path() dans le gabarit de liste des tâches.

Peut-on instancier en mémoire un objet $todo chargé depuis la base de données, une fois qu’on connaît son identifiant ?

On peut utiliser le code suivant, qui utilise explicitement Doctrine, comme dans le TP précédent :

$todo = $em->getRepository(Todo::class)->find($id);

Mais comme une application Symfony finit typiquement par contenir ce genre de code presque systématiquement, les développeurs du framework ont intégré ce mécanisme dans la classe Contrôleur via le mécanisme Entity Value Resolver, de façon a rendre cela « automagique ».

Ce mécanisme, sur lequel reviendra plus tard, instancie automatiquement une classe, chargée depuis la base de données, à partir d’un identifiant fourni, lorsqu’il n’y a pas d’ambiguïté.

La documentation Symfony vous donnera plus d’infos, à partir de Automatically Fetching Objects (EntityValueResolver).

Auteur: Olivier Berger (TSP)

Created: 2023-10-02 Mon 14:49

Validate