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 : Mise en place de la séance

2.1. TODO Préparation du répertoire de travail

Vous allez dupliquer l’arborescence du projet PHP obtenu à la fin de la séance précédente, pour travailler sur une nouvelle version.

Vous pourrez ainsi faire évoluer le code, et revenir en arrière si besoin, en comparant l’état à la fin de la séance prédente, avec l’état courant.

  1. Effectuez les opérations suivantes pour dupliquer le dossier :

    cd "$HOME/CSC4101"
    cp -r tp-02 tp-05
    cd tp-05
    cd todo-app
    
  2. Ensuite, dans ce nouveau projet, on va réinitialiser les rouages du cadriciel Symfony avec Composer :

    cd "$HOME/CSC4101/tp-05/todo-app"
    rm -fr composer.lock symfony.lock var/cache/ vendor/ .project
    symfony composer install
    

    Confirmez la génération de fichiers pour Docker (on ne s’en servira pas tout de suite, mais pourquoi ne pas les avoir… le rôle de Docker ne sera pas évoqué en cours, mais vous pouvez en parler à vos encadrants de TP).

Cette étape est nécessaire car Symfony s’appuie sur une mécanique sophistiquée de mise en cache du code de l’application (dans var/cache/), pour assurer des performances maximales. Malheureusement, si on duplique un projet, une partie de ce cache a tendance à devenir incohérente (présence de chemins d’accès « en dur », etc.).

Il est donc préférable de réinitialiser le projet radicalement : à un état le plus « propre » possible. On fait ensuite réinstaller par Composer les bibliothèques du cadriciel Symfony (installées dans vendor/), et reconfigurer certains fichiers de configuration associés.

2.2. TODO Chargement du nouveau projet dans l’IDE

Vous allez travailler sur un nouveau projet dans le workspace dans l’IDE Eclipse, importé depuis ce nouveau répertoire de travail.

  1. Pour les utilisateurs de l’IDE Eclipse, supprimez les anciennes infos du projet Eclipse :

    cd "$HOME/CSC4101/tp-05/todo-app"
    rm -fr .project .settings/
    
  2. Importez dans votre IDE cette nouvelle version du projet Symfony Todo sur laquelle vous allez maintenant travailler.

    Dans Eclipse vous pouvez importer ce nouveau projet dans le même workspace que le précédent, mais avec un nom distinctif (par exemple « todo-tp-5 »).

    Si besoin, vous pourrez comparer le code des deux projets « todo-tp-5 » et « todo-tp-2 ». Mais pour l’instant, et pour éviter de vous mélanger, nous vous conseillons de fermer le projet de la séance précédente (menu Project / Close …).

3. É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.

3.1. Principe de la génération du HTML grâce au moteur de rendu des gabarits

En utilisant des gabarits, pour produire du HTML, le code des classes contrôleurs de notre application n’aura plus besoin de manipuler des chaînes de caractères à concaténer.

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 un squelette de HTML, et dans lequel sont prévus des emplacements pour insérer les différents champs de cette représentation minimaliste.

3.2. TODO Modification de l’affichage des tâches

Vous travaillez sur le contrôleur réalisant l’affichage des tâches TodoController pour qu’il utilise les gabarits Twig.

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 on devra trouver l’appel à une méthode render() qui utilise un fichier Twig et un tableau associatif avec des variables.

Vous pouvez vous inspirer du code généré par make:controller pour TagController, qui utilise Twig.

  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(): Response
    {
        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.

3.3. TODO Observation de la structure des pages standardisée

  1. 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">...
    

    Ignorez la partie du <div class="sf-toolbar ..."> en fin de page, qui correspond à l’affichage de la barre d’outils Symfony.

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

    Vous devriez voir la partie utile du HTML provenant du blog body ({% block body %}...{% endblock %}).

    Mais d’où provient le reste de ce qui précède : <html>, <head>, … </head>, jusqu’à <body> ?

  2. Observez le contenu du fichier source du gabarit templates/base.html.twig

    Vous y voyez justement tous ces éléments du gabarit HTML de base, jusqu’à l’inclusion du bloc Twig body {% block body %}{% endblock %}.

    Tout cela résulte de l’instruction {% extends 'base.html.twig' %} en en-tête de index.html.twig.

    Le rendu du gabarit part du gabarit de base, « étendu » (surchargé) par les gabarits spécialisés, comme body.

    Vous devriez comprendre le principe de surcharge des blocs, comme dans l’orienté objet.

    Ces différents fichiers Twig 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.

  3. 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.

4. TODO É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 pour les gabarits.

Comme on l’a déjà vu, 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.

Testez l’affichage des pages de l’application dans lequelles les gabarits Twig sont maintenant utilisés.

Consultez les informations affichées dans la barre d’outils Symfony en bas de pages, où vous devriez trouver une nouvelle icône correspondant aux gabarits Twig.

L’outil du Profiler correspondant à la génération des gabarits Twig donne des informations sur ce qui se passe sous le capot.

Le Rendering Call Graph est particulièrement intéressant en montrant l’enchaînement des générations des différents blocs surchargés.

Pour l’affichage de /todo/ il devrait donner quelque chose du style :

main 11.73ms/100%
└ index.html.twig 11.44ms/98%
│ └ base.html.twig 10.98ms/94%
│   └ index.html.twig::block(title)
│   └ base.html.twig::block(stylesheets) 10.94ms/93%
│   └ base.html.twig::block(javascripts)
│   └ index.html.twig::block(body)
└ @WebProfiler/Profiler/toolbar_js.html.twig
  └ @WebProfiler/Profiler/toolbar.html.twig
  │ └ @WebProfiler/Profiler/cancel.html.twig::block(toolbar)
  │   └ @WebProfiler/Profiler/toolbar_item.html.twig
  └ @WebProfiler/Profiler/toolbar.css.twig

On voit l’enchaînement :

  1. index.html.twig inclut base.html.twig
  2. comme base.html.twig contient le rendu de 4 blocs : title, stylesheet, javascripts et body, on les voit apparaître successivement. Mais :
    • stylesheet, javascripts dont notés comme étant ceux de base.html.twig. Normal, ils n’ont pas été surchargés.
    • par contre, title et body proviennent de index.html.twig car ce template les a redéfinis, pour les besoins de cette page particulière de l’application.
  3. Le reste des gabarits de @WebProfiler correspond aux outils du Profiler inclus dans la page.

5. É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, on va modifier 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.

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

Procédez aux opérations suivantes :

  1. 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 #}
    

    Notez qu’on utilise {{ }} pour afficher la valeur d’une variable, alors qu’on utilise {% %} pour exécuter des instructions Twig.

  1. 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 ]
                    );
    }
    

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

    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 foreach sur l’affichage de chaque élément d’une collection, avec l’instruction {%for ITEM in COLLECTION %}.

    Twig accède alors nativement aux propriétés des classes PHP : {{ todo.title }} se traduira donc en un appel à Todo:getTitle().

  2. Pour le vérifier, 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 câblés correctement par Symfony avec les données du modèle objet de Doctrine.

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

On va modifier encore le gabarit templates/todo/index.html.twig pour afficher toutes les propriétés des tâches dans un tableau.

Au lieu d’une liste à puces simple, on veut obtenir un tableau HTML 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 templates/todo/index.html.twig autour du foreach, pour afficher ainsi les différentes propriétés de chaque tâche présente dans todos.

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

On ne vous fournit pas le squelette à copier/coller, cette fois. Il faut un peu démarrer la réflexion sur le code. Ce n’est pas très complexe, mais prenez le temps de relire attentivement les instructions.

5.3. TODO Étape 3-c : Utilisation d’un gabarit pour l’affichage d’une instance de tâche

L’objectif de cette étape est de mieux comprendre le fonctionnement de la surcharge des blocs dans les gabarits Twig, et d’introduire path().

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. la méthode du contrôleur qui va gérer les requêtes HTTP entrantes devrait ressembler à ceci :

    /**
     * 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 ]
        );
    }
    
    

    Pour l’instant on laisse un peu de côté la « magie » qui permet de récupérer l’instance de Todo grâce à doctrine (cf. « Aller plus loin » optionnel).

  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 HTML de lien <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 ?

Réparons l’affichage : il est nécessaire de remettre en place cette génération d’HTML complète, en faisant comme dans le gabarit todo/index.html.twig : il faut bien l’inclusion du gabarit de base : {% extends 'base.html.twig' %} au début du gabarit.

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

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.

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

L’objectif de cette étape est de mieux comprendre l’utilité de la fonction path().

On va 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 HTML 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.

Cette fois encore, la fonction path( ) doit prendre en premier argument le nom de la route. Mais elle doit aussi recevoir 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 de l’annotation Route('/{id}' de TodoController::showAction.

Bravo : vous connaissez presque tous les éléments fondamentaux pour programmer une interface Web en consultation.

6. É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 « {{ ... }} »).

7. TODO É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.

On souhaite par exemple afficher les tâches terminées comme étant barrées, dans la liste des tâches.

C’est l’étape finale qui est le moins guidée. Bon courage.

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é ;-).

8. Conclusion

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() au niveau du modèle des données avec Doctrine.

Mais on peut aussi programmer certains comportements dans les gabarits Twig, par exemple pour faire varier seulement la présentation des données.

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.

9. TODO É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.

Vous la continuerez en travail hors présentiel sur votre projet, d’ici la prochaine séance de TP (Notez le label « Après_TP_5 » dans la section du guide de réalisation).

10. É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()

11. Pour aller plus loin (optionnel)

11.1. Chargement « automagique » d’une instance en consultation grâce à l’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).

Author: Olivier Berger (TSP)

Date: 2024-10-04 Fri 12:52

Emacs (Org mode)