Skip to Content
SymfonyPartie 7 — Twig, AssetMapper & back-office7.1 — Twig : le moteur de templates

Chapitre 7.1 — Twig : le moteur de templates

Partie 7 : Twig & back-office — Chapitre 1/5 Version de référence : Twig 3 / Symfony 7.4.

⏱️ TL;DR

  • Twig est le moteur de templates de Symfony : des fichiers .html.twig compilés en PHP puis mis en cache (rapides).
  • Trois délimiteurs : {{ ... }} affiche une valeur, {% ... %} exécute une instruction (if, for…), {# ... #} un commentaire.
  • L’échappement automatique protège du XSS : toute valeur affichée est échappée par défaut. On ne désactive (|raw) qu’à bon escient.
  • Filtres (|upper, |date, |default) transforment ; fonctions (path(), asset(), is_granted()) produisent des valeurs.
  • L’accès par point (user.email) marche sur les clés de tableau, les propriétés et les getters — de façon transparente.
  • La variable globale app donne accès à app.user, app.request, app.flashes
  • Pour un dev Next.js : Twig ≈ un JSX sans logique, avec échappement par défaut (comme React) — mais rendu côté serveur, sans état client.

🎯 Objectifs

  • Écrire des templates Twig : affichage, filtres, fonctions, conditions, boucles.
  • Comprendre et exploiter l’échappement automatique.
  • Rendre un template depuis un contrôleur en lui passant des données.
  • Utiliser la variable globale app.

1. Un template Twig, c’est quoi

Twig transforme des fichiers .html.twig (dans templates/) en HTML. Ces fichiers sont compilés en classes PHP (dans var/cache/) puis exécutés — donc rapides en prod. Un template reçoit des variables depuis le contrôleur et produit du HTML.

{# templates/booking/show.html.twig #} <h1>Réservation n°{{ booking.id }}</h1> <p>Client : {{ booking.customerEmail }}</p> <p>Statut : {{ booking.status.label }}</p>

Rendu depuis le contrôleur (Partie 4) :

#[Route('/admin/bookings/{id}', methods: ['GET'])] public function show(Booking $booking): Response { return $this->render('booking/show.html.twig', [ 'booking' => $booking, // la variable 'booking' est disponible dans le template ]); }

2. Les trois délimiteurs

DélimiteurRôleExemple
{{ ... }}Afficher une expression{{ booking.customerEmail }}
{% ... %}Instruction (logique){% if booking.isActive %}
{# ... #}Commentaire (non rendu){# note interne #}

3. L’accès aux données : le point magique

En Twig, foo.bar essaie plusieurs choses, dans l’ordre, de façon transparente :

  1. la clé de tableau foo['bar'] ;
  2. la propriété publique foo->bar ;
  3. le getter foo->getBar() ou foo->isBar() / foo->hasBar().
{{ booking.customerEmail }} {# appelle getCustomerEmail() #} {{ booking.status.label }} {# getStatus() puis, sur l'enum, label() #} {{ params.page }} {# params['page'] si params est un tableau #}

Vous n’avez donc pas à savoir si c’est une propriété, un getter ou une clé : Twig résout. Pour une méthode avec argument, on l’appelle explicitement : foo.bar(arg).

💡 Pour un dev Next.js : oubliez la distinction propriété/méthode que vous auriez en JS. booking.status.label en Twig appelle la méthode label() de l’enum sans les parenthèses. C’est plus « déclaratif » : le template décrit quoi afficher, pas comment l’obtenir.

4. L’échappement automatique (sécurité)

Par défaut, Twig échappe toute valeur affichée ({{ ... }}) pour le contexte HTML. Si booking.customerEmail contient <script>, il sera rendu comme du texte inoffensif, pas exécuté. C’est une protection anti-XSS intégrée — l’une des grandes forces de Twig.

{{ userInput }} {# échappé : <b> devient &lt;b&gt; (sûr) #} {{ trustedHtml|raw }} {# NON échappé : le HTML est rendu tel quel (danger si non maîtrisé) #}

⚠️ Piège |raw : le filtre |raw désactive l’échappement — à n’utiliser que sur du HTML dont vous maîtrisez totalement la source (jamais sur une saisie utilisateur). Un |raw sur des données utilisateur = faille XSS. Par défaut, ne mettez pas de |raw ; laissez Twig échapper.

5. Filtres et fonctions

Un filtre (|) transforme une valeur ; une fonction en produit une.

{{ name|upper }} {# MAJUSCULES #} {{ booking.start|date('d/m/Y H:i') }} {# formate une date #} {{ price|number_format(2, ',', ' ') }} {# 1 234,56 #} {{ description|default('—') }} {# valeur de repli si vide #} {{ items|length }} {# nombre d'éléments #} {{ text|slice(0, 100) }} {# tronquer #} {# fonctions #} <a href="{{ path('booking_show', { id: booking.id }) }}">Voir</a> {# génère l'URL #} <img src="{{ asset('images/logo.png') }}" alt=""> {# chemin d'asset #} {% if is_granted('ROLE_ADMIN') %}...{% endif %} {# sécurité #}

Filtres à connaître : upper/lower, date, number_format, default, length, join, trim, nl2br, escape/e, format, map/filter (sur des collections). On peut chaîner : {{ name|trim|lower }}.

6. Conditions et boucles

{% if booking.status.value == 'confirmed' %} <span class="badge badge-success">Confirmée</span> {% elseif booking.status.value == 'pending' %} <span class="badge badge-warning">En attente</span> {% else %} <span class="badge badge-muted">{{ booking.status.label }}</span> {% endif %} <ul> {% for slot in service.slots %} <li>{{ slot.start|date('d/m H:i') }} — {{ slot.seats }} places</li> {% else %} <li>Aucun créneau.</li> {# le {% else %} d'un for s'affiche si la collection est vide #} {% endfor %}

Le bloc {% else %} d’une boucle for (affiché quand la collection est vide) est une commodité très pratique. Dans une boucle, la variable loop expose loop.index (1-based), loop.index0, loop.first, loop.last, loop.length.

7. La variable globale app

Symfony injecte une variable app dans tous les templates, pont vers le contexte de la requête :

{% if app.user %} Connecté en tant que {{ app.user.email }} {% endif %} {{ app.request.locale }} {# locale courante #} {{ app.environment }} {# dev / prod #} {# messages flash (chapitre 4.5) #} {% for label, messages in app.flashes %} {% for message in messages %} <div class="flash flash-{{ label }}">{{ message }}</div> {% endfor %} {% endfor %}

app.user (l’utilisateur connecté, Partie 10), app.request, app.session, app.flashes, app.environment couvrent l’essentiel des besoins d’un template.

8. Diagnostiquer Twig

php bin/console debug:twig # liste filtres, fonctions, tests, globals disponibles php bin/console lint:twig templates/ # vérifie la syntaxe de tous les templates

lint:twig (en CI, Partie 14) attrape les erreurs de syntaxe avant le déploiement.

Depuis Symfony 5 : Twig lui-même a peu changé (c’est stable et mûr). La nouveauté marquante côté rendu, c’est l’arrivée des composants Twig (chapitre 7.2) et d’AssetMapper (chapitre 7.3), qui modernisent l’expérience full-stack sans bundler. Vos réflexes Twig de Symfony 5 restent valides.

✏️ Exercices

Exercice 1. Affichez la date de début d’une réservation au format « 09/07/2026 14:30 », avec un repli « Date inconnue » si elle est absente.

✅ Solution

{{ booking.start ? booking.start|date('d/m/Y H:i') : 'Date inconnue' }} {# ou, si start peut être null et qu'on veut un repli sur le format : #} {{ booking.start|date('d/m/Y H:i')|default('Date inconnue') }}

Le filtre date formate ; l’opérateur ternaire (ou |default) gère l’absence. (Ici start est non-null dans notre modèle, mais l’exercice illustre le repli.)

Exercice 2. Pourquoi {{ comment.body|raw }} sur un commentaire saisi par un utilisateur est-il dangereux, et que faire à la place ?

✅ Solution

|raw désactive l’échappement : si l’utilisateur a saisi <script>...</script>, il sera exécuté dans le navigateur des visiteurs → faille XSS. Il faut laisser Twig échapper (juste {{ comment.body }}). Si on veut autoriser un sous-ensemble de HTML (gras, liens), on nettoie d’abord côté serveur avec un sanitizer (ex. le composant HtmlSanitizer de Symfony) et on n’affiche en |raw que le résultat assaini.

Exercice 3. Écrivez une boucle qui liste les services d’un prestataire, avec un message « Aucun service » si la liste est vide, et une classe first sur le premier.

✅ Solution

<ul> {% for service in provider.services %} <li class="{{ loop.first ? 'first' : '' }}">{{ service.name }}</li> {% else %} <li>Aucun service.</li> {% endfor %} </ul>

Le {% else %} du for gère la collection vide ; loop.first détecte le premier élément.

🧠 Quiz de révision

1. À quoi servent {{ }}, {% %} et {# #} ?

{{ }} affiche une valeur, {% %} exécute une instruction (if/for…), {# #} est un commentaire non rendu.

2. Que fait booking.customerEmail en Twig ?

Twig tente, de façon transparente : la clé ['customerEmail'], la propriété, puis le getter getCustomerEmail() (ou isCustomerEmail()/hasCustomerEmail()).

3. Twig échappe-t-il les valeurs par défaut ?

Oui : l’échappement automatique protège du XSS. |raw le désactive (à réserver au HTML maîtrisé).

4. Comment génère-t-on une URL et accède-t-on à l’utilisateur connecté dans un template ?

URL : path('nom_route', { params }). Utilisateur : app.user (via la variable globale app).

5. Quelle commande vérifie la syntaxe des templates ?

php bin/console lint:twig templates/.


Prochain chapitre : 7.2 — Héritage, includes & composants Twig — factoriser l’UI et créer des composants réutilisables.