Chapitre 7.2 — Héritage, includes & composants Twig
Partie 7 : Twig & back-office — Chapitre 2/5 Version de référence : Twig 3 / Symfony UX Twig Component / Symfony 7.4.
⏱️ TL;DR
- Héritage : un template
base.html.twigdéfinit des blocs ({% block body %}) ; les pages font{% extends 'base.html.twig' %}et remplissent ces blocs.{{ parent() }}réutilise le contenu parent.{% include %}insère un fragment ;{% embed %}l’insère en surchargeant ses blocs.- Les composants Twig (Symfony UX) sont l’équivalent serveur des composants React : une classe (props, logique) + un template, appelés en HTML
<twig:Alert type="success">…</twig:Alert>.- Il existe aussi des composants anonymes (juste un template dans
templates/components/), sans classe.- Pour un dev Next.js : l’héritage Twig ≈ un layout Next (
layout.tsx+children), et les composants Twig ≈ vos composants React (props, réutilisables) — mais rendus côté serveur.
🎯 Objectifs
- Structurer les pages avec l’héritage de templates et les blocs.
- Réutiliser des fragments avec
include/embedet des macros. - Créer des composants Twig avec props et logique.
- Choisir entre include, macro et composant.
1. L’héritage : un squelette commun
Toute application a un gabarit commun (en-tête, navigation, pied de page). On le définit une fois dans base.html.twig, avec des blocs que les pages rempliront.
{# templates/base.html.twig #}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>{% block title %}BookLab{% endblock %}</title>
{% block stylesheets %}{% endblock %}
</head>
<body>
<header>
<a href="{{ path('home') }}">BookLab</a>
{% block nav %}{% endblock %}
</header>
<main>
{% for label, messages in app.flashes %}
{% for message in messages %}
<div class="flash flash-{{ label }}">{{ message }}</div>
{% endfor %}
{% endfor %}
{% block body %}{% endblock %}
</main>
{% block javascripts %}{% endblock %}
</body>
</html>Une page étend ce gabarit et remplit les blocs :
{# templates/booking/list.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}Réservations — {{ parent() }}{% endblock %}
{% block body %}
<h1>Réservations</h1>
<table>
{% for booking in bookings %}
<tr><td>{{ booking.customerEmail }}</td><td>{{ booking.status.label }}</td></tr>
{% endfor %}
</table>
{% endblock %}{{ parent() }} insère le contenu du bloc parent (ici, « BookLab » s’ajoute au titre). Une page ne remplit que les blocs qui l’intéressent ; les autres gardent leur contenu par défaut.
💡 Pour un dev Next.js :
base.html.twig+{% block body %}≈ unlayout.tsxavec{children}.{% extends %}correspond à « cette page s’insère dans ce layout ». La différence : Twig permet plusieurs blocs nommés (title, nav, body, scripts…), pas seulementchildren.
2. Includes et embeds
include insère un fragment, éventuellement avec des variables :
{% include 'partials/_booking_row.html.twig' with { booking: booking } %}
{# le fragment reçoit 'booking' ; 'only' isole le contexte : #}
{% include 'partials/_badge.html.twig' with { status: booking.status } only %}embed est un include surchargeable : on insère un fragment et on redéfinit certains de ses blocs — utile pour des « cartes » ou « modales » réutilisables dont on personnalise le contenu.
{% embed 'partials/_card.html.twig' %}
{% block card_title %}Réservation n°{{ booking.id }}{% endblock %}
{% block card_body %}{{ booking.customerEmail }}{% endblock %}
{% endembed %}Convention : on préfixe les fragments d’un underscore (_booking_row.html.twig) pour signaler « ce n’est pas une page ».
3. Macros : des « fonctions » de template
Une macro est une fonction réutilisable qui produit du HTML — pratique pour de petits éléments répétés (un badge, un bouton) sans créer un fichier par fragment.
{# templates/macros/ui.html.twig #}
{% macro statusBadge(status) %}
<span class="badge badge-{{ status.value }}">{{ status.label }}</span>
{% endmacro %}{% import 'macros/ui.html.twig' as ui %}
{{ ui.statusBadge(booking.status) }}Les macros sont sans état et purement présentationnelles. Dès qu’il faut de la logique (calculs, dépendances), on passe aux composants Twig.
4. Les composants Twig : le React côté serveur
C’est la fonctionnalité moderne à connaître. Un composant Twig (bundle symfony/ux-twig-component) associe une classe PHP (props typées, logique) à un template, et s’appelle en HTML comme une balise.
composer require symfony/ux-twig-component// src/Twig/Components/StatusBadge.php
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
use App\Enum\BookingStatus;
#[AsTwigComponent]
final class StatusBadge
{
public BookingStatus $status; // prop
// logique : une propriété calculée exposée au template
public function cssClass(): string
{
return 'badge badge-' . $this->status->value;
}
}{# templates/components/StatusBadge.html.twig #}
<span class="{{ this.cssClass }}">{{ status.label }}</span>On l’utilise dans n’importe quel template avec la syntaxe balise :
<twig:StatusBadge :status="booking.status" />:status="..." passe une expression (l’enum) ; status="pending" passerait une chaîne. Dans le template du composant, this est l’instance de la classe (this.cssClass), et les props sont accessibles directement (status).
💡 Pour un dev Next.js : c’est littéralement un composant React, côté serveur :
class StatusBadge { status }≈ les props,cssClass()≈ une fonction dérivée,<twig:StatusBadge :status="..." />≈<StatusBadge status={...} />. Vous retrouvez la composition par composants — sans hydratation JS (ça reste du HTML). La Partie 8 (LiveComponents) ajoutera la réactivité à ces composants.
Il existe aussi des composants anonymes : un simple template dans templates/components/ (sans classe), pour des composants purement présentationnels — encore plus léger qu’une macro, avec la syntaxe balise.
5. Include vs macro vs composant : lequel ?
| Besoin | Outil |
|---|---|
| Insérer un bout de page, contexte simple | include |
| Insérer + surcharger des blocs | embed |
| Petit élément présentationnel répété | macro ou composant anonyme |
| Élément réutilisable avec props typées + logique | composant Twig (classe) |
| Élément réactif (mise à jour sans recharger) | LiveComponent (Partie 8) |
✏️ Exercices
Exercice 1. Créez un base.html.twig minimal avec des blocs title et body, puis une page provider/show.html.twig qui l’étend et affiche le nom du prestataire.
✅ Solution
{# base.html.twig #}
<!DOCTYPE html>
<html lang="fr"><head><title>{% block title %}BookLab{% endblock %}</title></head>
<body>{% block body %}{% endblock %}</body></html>{# provider/show.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}{{ provider.name }} — {{ parent() }}{% endblock %}
{% block body %}<h1>{{ provider.name }}</h1>{% endblock %}Exercice 2. Transformez cette macro statusBadge en composant Twig avec une classe, exposant une méthode cssClass().
✅ Solution
Créer src/Twig/Components/StatusBadge.php avec #[AsTwigComponent], une prop public BookingStatus $status; et une méthode cssClass(): string. Créer templates/components/StatusBadge.html.twig avec <span class="{{ this.cssClass }}">{{ status.label }}</span>. L’utiliser via <twig:StatusBadge :status="booking.status" />. (Voir le §4 pour le code complet.) Avantage sur la macro : props typées et logique dans une classe testable.
Exercice 3. Quand choisir un include plutôt qu’un composant Twig ?
✅ Solution
include convient pour insérer un fragment de page simple, sans logique ni props typées (ex. une ligne de tableau, un en-tête réutilisé), en lui passant quelques variables. Le composant Twig (classe) s’impose dès qu’il faut des props typées, de la logique (méthodes dérivées), une réutilisation cadrée et testable — bref, un vrai « composant » plutôt qu’un simple copier-coller de balisage.
🧠 Quiz de révision
1. Comment une page réutilise-t-elle un gabarit commun ?
Avec {% extends 'base.html.twig' %} puis en remplissant les blocs ({% block body %}…{% endblock %}). {{ parent() }} réutilise le contenu du bloc parent.
2. Différence entre include et embed ?
include et embed ?include insère un fragment tel quel ; embed l’insère en permettant de surcharger ses blocs (fragment personnalisable).
3. Qu’est-ce qu’un composant Twig ?
Une classe PHP (props typées + logique) associée à un template, appelée en HTML <twig:Nom :prop="..." /> — l’équivalent serveur d’un composant React.
4. Dans le template d’un composant, à quoi correspond this ?
this ?À l’instance de la classe du composant : on accède à ses méthodes/propriétés (this.cssClass), et aux props directement (status).
5. Macro ou composant pour un élément avec logique et props typées ?
Un composant Twig (classe) : les macros sont sans état et présentationnelles ; les composants portent props typées et logique.
Prochain chapitre : 7.3 — AssetMapper — servir CSS/JS modernes sans bundler.