Le moteur de templates Twig
Comprendre en détail Twig, le moteur de templates utilisé par Symfony pour générer le HTML des pages.
Notions théoriques
Rappel : à quoi sert Twig
Dans la séance sur le routage et les vues, vous avez déjà utilisé Twig sans le savoir vraiment expliqué : la méthode render() d'un contrôleur affiche un fichier .html.twig.
Twig est le moteur de templates de Symfony. Un moteur de templates permet d'écrire des fichiers qui ressemblent à du HTML, mais qui peuvent aussi contenir des variables PHP et de la logique simple (conditions, boucles), sans mélanger tout le code PHP au milieu du HTML.
Séparer le HTML (dans les templates Twig) du code métier (dans les contrôleurs et les entités) rend l'application plus facile à maintenir. Un intégrateur peut modifier l'apparence d'une page sans toucher au code PHP.
La syntaxe Twig
Twig utilise deux types de balises principales :
{{ ... }}: pour afficher une valeur (une variable, le résultat d'un filtre, etc.){% ... %}: pour exécuter une instruction (condition, boucle, héritage, bloc...)
Afficher une variable :
<p>Bonjour {{ prenom }} !</p>
Une condition :
{% if joueurs|length > 0 %}
<p>Il y a des joueurs inscrits.</p>
{% else %}
<p>Aucun joueur pour le moment.</p>
{% endif %}
Une boucle :
<ul>
{% for joueur in joueurs %}
<li>{{ joueur.name }}</li>
{% endfor %}
</ul>
Dans une boucle Twig, on accède à une propriété d'objet avec la notation pointée (joueur.name), même si en PHP il s'agirait d'une méthode getName(). Twig essaie automatiquement la propriété, puis le getter correspondant.
L'héritage de templates
Sans héritage, il faudrait recopier le header, le menu et le footer dans chaque template. L'héritage de Twig évite cette duplication.
Le principe : on crée un template parent (souvent base.html.twig) qui contient la structure commune, avec des blocs vides à des endroits précis. Les templates enfants héritent du parent avec {% extends %} et remplissent uniquement les blocs qui les intéressent.
Le template parent base.html.twig :
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Mon Jeu{% endblock %}</title>
</head>
<body>
<header>Mon Jeu</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>Copyright {{ "now"|date("Y") }}</footer>
</body>
</html>
Un template enfant :
{% extends 'base.html.twig' %}
{% block content %}
<h1>Liste des joueurs</h1>
{% endblock %}
Le résultat final combine automatiquement la structure de base.html.twig avec le contenu du bloc content défini dans le template enfant. Le header et le footer sont hérités sans être réécrits.
Les filtres Twig usuels
Un filtre transforme une valeur avant de l'afficher. On l'applique avec le symbole | (pipe).
| Filtre | Rôle | Exemple |
|---|---|---|
upper | Met en majuscules | {{ name|upper }} |
lower | Met en minuscules | {{ name|lower }} |
date | Formate une date | {{ createdAt|date("d/m/Y") }} |
length | Compte les éléments | {{ joueurs|length }} |
default | Valeur de secours si vide | {{ description|default("Aucune description") }} |
Les fonctions Twig usuelles
Une fonction Twig, contrairement à un filtre, s'écrit avec des parenthèses, comme en PHP.
| Fonction | Rôle | Exemple |
|---|---|---|
path() | Génère une URL à partir du nom d'une route | {{ path('game_show', {id: 1}) }} |
asset() | Génère le chemin vers un fichier statique (CSS, JS, image) | {{ asset('css/style.css') }} |
Écrire <a href="/game/1"> fonctionne, mais si la route change de chemin dans le contrôleur,
tous les liens écrits en dur cassent. {{ path('game_show', {id: 1}) }} régénère toujours
l'URL correcte à partir du nom de la route, même si le chemin change.
Exemple de mise en application
Reprenons le projet my_game et son GameController. Nous allons créer un template parent base.html.twig, puis faire hériter la page d'affichage d'un jeu de ce template.
Le fichier templates/base.html.twig :
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Mon Jeu{% endblock %}</title>
<link rel="stylesheet" href="{{ asset('css/style.css') }}">
</head>
<body>
<header>
<a href="{{ path('home') }}">Accueil</a>
</header>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>
Le fichier templates/game/show.html.twig hérite de base.html.twig :
{% extends 'base.html.twig' %}
{% block title %}Détails du jeu{% endblock %}
{% block content %}
<h1>{{ name|upper }}</h1>
<p>{{ description|default("Pas de description pour ce jeu.") }}</p>
{% if joueurs|length > 0 %}
<ul>
{% for joueur in joueurs %}
<li>{{ joueur.name }}</li>
{% endfor %}
</ul>
{% else %}
<p>Aucun joueur inscrit sur ce jeu.</p>
{% endif %}
{% endblock %}
La page affichée combine la structure de base.html.twig (header avec le lien vers l'accueil) et le contenu spécifique défini dans le bloc content de show.html.twig.
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Votre défi pour aujourd'hui consiste à créer un template parent commun à toutes les pages du projet
my_game, et à l'utiliser pour la page d'affichage d'un joueur.
Étape 1 — Créer le template parent base.html.twig
Créez le fichier templates/base.html.twig avec un bloc title (valeur par défaut "Mon Jeu") et un bloc content vide.
Donner une valeur par défaut à un bloc comme title évite d'avoir une page sans titre
si un développeur oublie de redéfinir le bloc dans un template enfant.
Étape 2 — Faire hériter le template player/show.html.twig de base.html.twig
Le fichier templates/player/show.html.twig doit maintenant hériter de base.html.twig au lieu de contenir tout le HTML lui-même.
Étape 3 — Afficher le nom du joueur en majuscules
Dans le bloc content de player/show.html.twig, la variable name est transmise par le contrôleur. Affichez-la en majuscules à l'aide d'un filtre Twig.
Mettre un texte en majuscules pour l'affichage relève de la présentation, pas de la logique
métier. Utiliser le filtre upper dans le template plutôt que strtoupper() dans le
contrôleur garde le contrôleur concentré sur son rôle : préparer les données.
Étape 4 — Ajouter un lien vers la liste des joueurs avec path()
Dans le bloc content, ajoutez un lien qui pointe vers la route nommée player_index, en utilisant la fonction path().