🎨 Twig - Moteur de Templates

Générer du HTML de façon élégante

Variables • Filtres • Structures • Héritage • Fonctions

Formation DWWM - 2026

Au programme 📋

# 🎭 Partie 1

Qu'est-ce que Twig ?

Le moteur de templates de Symfony

C'est quoi Twig ? 🤔

Twig est un moteur de templates moderne, rapide et sécurisé pour PHP.

Analogie : Le pochoir de peinture 🎨

Imagine que tu veux peindre 100 fois le même logo :

Twig = le pochoir pour générer du HTML !

PHP pur vs Twig 🆚

❌ PHP pur (verbeux et mélangé)

<!DOCTYPE html>
<html>
<head>
    <title><?php echo $title; ?></title>
</head>
<body>
    <h1><?php echo htmlspecialchars($title); ?></h1>
    
    <?php if (!empty($users)): ?>
        <ul>
        <?php foreach ($users as $user): ?>
            <li>
                <?php echo htmlspecialchars($user['name']); ?>
            </li>
        <?php endforeach; ?>
        </ul>
    <?php else: ?>
        <p>Aucun utilisateur</p>
    <?php endif; ?>
</body>
</html>

✅ Twig (propre et lisible)

<!DOCTYPE html>
<html>
<head>
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{ title }}</h1>
    
    {% if users is not empty %}
        <ul>
        {% for user in users %}
            <li>{{ user.name }}</li>
        {% endfor %}
        </ul>
    {% else %}
        <p>Aucun utilisateur</p>
    {% endif %}
</body>
</html>

✅ Avantages de Twig :

Comment fonctionne Twig ? ⚙️

  1. Vous écrivez un template Twig
    Fichier .twig avec syntaxe Twig
  2. Twig compile le template
    Transforme le Twig en code PHP optimisé
  3. PHP exécute le code compilé
    Le cache est utilisé pour les requêtes suivantes
  4. HTML généré et envoyé au navigateur
    Le résultat final est du HTML pur
template.html.twig
↓ (compilation)
cache/template.php
↓ (exécution)
HTML final

💡 Performance : La compilation se fait UNE SEULE fois. Les requêtes suivantes utilisent le cache PHP, ce qui rend Twig très rapide !

# 📝 Partie 2

Syntaxe de base

Les 3 délimiteurs Twig

Les 3 types de délimiteurs 🔤

Twig utilise 3 types de délimiteurs pour différentes actions :

Délimiteur Usage Exemple
{{ }} Afficher une variable {{ nom }}
{% %} Exécuter une instruction (if, for...) {% if age > 18 %}
{# #} Commenter (non affiché) {# TODO: améliorer #}

Exemples complets

{# Ceci est un commentaire, invisible dans le HTML final #}

{% set message = "Bonjour" %}  {# Instruction : définir variable #}

<h1>{{ message }}</h1>  {# Affichage : "Bonjour" #}

{% if user.isAdmin %}  {# Instruction : condition #}
    <p>Panneau d'administration</p>
{% endif %}

Analogie des 3 délimiteurs 🎭

Les 3 types de phrases dans une pièce de théâtre 🎬

{{ }} = Dialogue parlé

Ce que le public entend, ce qui s'affiche à l'écran.

<p>{{ personnage.nom }} dit : "{{ personnage.replique }}"</p>

{% %} = Didascalie (indication scénique)

Instructions pour les acteurs, non visibles du public.

{% if personnage.estEnColere %}
    <span style="color: red;">{{ personnage.replique }}</span>
{% endif %}

{# #} = Note du metteur en scène

Commentaires personnels, jamais affichés.

{# TODO: ajouter effet sonore ici #}
# 🔤 Partie 3

Variables et Affichage

Manipuler et afficher des données

Afficher des variables simples 📺

Utilisez {{ }} pour afficher n'importe quelle variable :

Variables scalaires

{# Dans le contrôleur #}
return $this->render('page.html.twig', [
    'nom' => 'Marie',
    'age' => 25,
    'actif' => true,
    'note' => 15.5
]);

{# Dans le template #}
<p>Nom : {{ nom }}</p>          {# Marie #}
<p>Âge : {{ age }}</p>           {# 25 #}
<p>Actif : {{ actif }}</p>      {# 1 (true) #}
<p>Note : {{ note }}</p>        {# 15.5 #}

💡 Échappement automatique : Twig échappe automatiquement les caractères HTML dangereux pour éviter les failles XSS.

{{ "<script>alert('XSS')</script>" }}
{# Affiche : &lt;script&gt;alert('XSS')&lt;/script&gt; #}
{# Le code JavaScript n'est PAS exécuté ! #}

Accéder aux tableaux 📊

Notation avec point ou crochets

{# Dans le contrôleur #}
return $this->render('page.html.twig', [
    'user' => [
        'nom' => 'Dupont',
        'prenom' => 'Marie',
        'age' => 25
    ]
]);

{# Dans le template - 2 syntaxes possibles #}
{{ user.nom }}      {# Dupont #}
{{ user['nom'] }}   {# Dupont #}

{{ user.prenom }}   {# Marie #}
{{ user['prenom'] }} {# Marie #}

Notation point (recommandée)

{{ user.nom }}
{{ article.titre }}
{{ produit.prix }}

✅ Plus lisible

Notation crochets

{{ user['nom'] }}
{{ article['titre'] }}
{{ produit['prix'] }}

✅ Utile pour clés dynamiques

Accéder aux propriétés d'objets 🎯

Twig peut appeler automatiquement les getters !

Classe PHP

class User
{
    private string $nom;
    private string $email;
    
    public function getNom(): string
    {
        return $this->nom;
    }
    
    public function getEmail(): string
    {
        return $this->email;
    }
    
    public function isAdmin(): bool
    {
        return $this->admin;
    }
}

Dans le template Twig

{{ user.nom }}     {# Appelle automatiquement getNom() #}
{{ user.email }}   {# Appelle automatiquement getEmail() #}
{{ user.admin }}   {# Appelle automatiquement isAdmin() #}

✅ Magie de Twig : Quand vous faites {{ user.nom }}, Twig essaie dans cet ordre :

  1. Propriété publique $user->nom
  2. Méthode getter $user->getNom()
  3. Méthode is/has $user->isNom() ou $user->hasNom()

Définir des variables dans Twig 📦

Utilisez {% set %} pour créer ou modifier des variables :

Variables simples

{% set nom = "Marie" %}
{% set age = 25 %}
{% set actif = true %}

<p>{{ nom }} a {{ age }} ans</p>

Concaténation

{% set prenom = "Marie" %}
{% set nom = "Dupont" %}
{% set nomComplet = prenom ~ " " ~ nom %}

<p>{{ nomComplet }}</p>  {# Marie Dupont #}

Calculs

{% set prix = 100 %}
{% set tva = prix * 0.20 %}
{% set total = prix + tva %}

<p>Total TTC : {{ total }} €</p>  {# 120 € #}

Blocs de capture

{% set contenu %}
    <p>Ceci est un long</p>
    <p>bloc de HTML</p>
{% endset %}

{{ contenu }}  {# Affiche tout le bloc #}
# 🔧 Partie 4

Filtres et Transformations

Modifier l'affichage des données

C'est quoi un filtre ? 🔧

Un filtre transforme une variable avant de l'afficher.

Analogie : Les filtres Instagram 📸

Quand tu prends une photo :

La photo originale ne change pas, seul l'affichage est modifié !

Syntaxe

{{ variable|filtre }}

{{ variable|filtre1|filtre2|filtre3 }}  {# Chaînage #}

{{ variable|filtre(argument1, argument2) }}  {# Avec arguments #}

Filtres de manipulation de texte 📝

Filtre Description Exemple Résultat
upper Tout en MAJUSCULES {{ "bonjour"|upper }} BONJOUR
lower Tout en minuscules {{ "BONJOUR"|lower }} bonjour
capitalize Première lettre en majuscule {{ "bonjour"|capitalize }} Bonjour
title Chaque mot avec majuscule {{ "bonjour monde"|title }} Bonjour Monde
trim Supprimer espaces début/fin {{ " test "|trim }} test
length Nombre de caractères {{ "Bonjour"|length }} 7
reverse Inverser le texte {{ "Bonjour"|reverse }} ruojnoB

Filtres slice et replace ✂️

slice - Extraire une partie du texte

{% set texte = "Bonjour le monde" %}

{{ texte|slice(0, 7) }}     {# Bonjour #}
{{ texte|slice(8) }}        {# le monde #}
{{ texte|slice(-5) }}       {# monde (5 derniers) #}

replace - Remplacer du texte

{% set phrase = "Bonjour Marie" %}

{{ phrase|replace({'Marie': 'Paul'}) }}
{# Bonjour Paul #}

{{ phrase|replace({'Bonjour': 'Salut', 'Marie': 'tout le monde'}) }}
{# Salut tout le monde #}

Cas pratique : Créer un extrait

{% set article = "Ceci est un très long article avec beaucoup de contenu..." %}

<p>{{ article|slice(0, 100) }}...</p>
{# Affiche les 100 premiers caractères + "..." #}

Filtres de formatage 🎨

date - Formater une date

{% set maintenant = "now" %}

{{ maintenant|date("d/m/Y") }}           {# 28/01/2025 #}
{{ maintenant|date("d F Y") }}           {# 28 janvier 2025 #}
{{ maintenant|date("H:i:s") }}           {# 14:30:45 #}
{{ maintenant|date("d/m/Y à H:i") }}     {# 28/01/2025 à 14:30 #}

format - Formater un texte (sprintf)

{{ "Bonjour %s !"|format("Marie") }}
{# Bonjour Marie ! #}

{{ "Il y a %d utilisateurs"|format(42) }}
{# Il y a 42 utilisateurs #}

{{ "%s a %d ans"|format("Marie", 25) }}
{# Marie a 25 ans #}

number_format - Formater un nombre

{{ 1234.5678|number_format }}
{# 1,235 (arrondi par défaut) #}

{{ 1234.5678|number_format(2) }}
{# 1,234.57 (2 décimales) #}

{{ 1234.5678|number_format(2, ',', ' ') }}
{# 1 234,57 (séparateurs français) #}

Filtres sur les tableaux 📊

sort - Trier

{% set nombres = [5, 2, 8, 1, 9] %}
{{ nombres|sort|join(', ') }}
{# 1, 2, 5, 8, 9 #}

reverse - Inverser l'ordre

{% set noms = ["Alice", "Bob", "Charlie"] %}
{{ noms|reverse|join(', ') }}
{# Charlie, Bob, Alice #}

join - Concaténer avec séparateur

{% set fruits = ["pomme", "poire", "banane"] %}

{{ fruits|join(', ') }}
{# pomme, poire, banane #}

{{ fruits|join(' - ') }}
{# pomme - poire - banane #}

{{ fruits|join(' et ') }}
{# pomme et poire et banane #}

first et last - Premier et dernier élément

{% set noms = ["Alice", "Bob", "Charlie"] %}

{{ noms|first }}  {# Alice #}
{{ noms|last }}   {# Charlie #}

Filtres de sécurité 🔒

escape (ou e) - Échapper le HTML

{% set code = "<script>alert('XSS')</script>" %}

{{ code }}           {# Auto-échappé par défaut #}
{{ code|e }}         {# Échappé manuellement #}
{{ code|escape }}    {# Même chose #}

{# Affiche : &lt;script&gt;alert('XSS')&lt;/script&gt; #}

raw - Ne PAS échapper (DANGER !)

{% set html = "<strong>Important</strong>" %}

{{ html }}       {# &lt;strong&gt;Important&lt;/strong&gt; #}
{{ html|raw }}   {# <strong>Important</strong> (HTML rendu) #}

⚠️ DANGER : N'utilisez |raw que sur du contenu de confiance ! Jamais sur des données utilisateur, sinon faille XSS garantie !

{# ❌ DANGEREUX - Ne JAMAIS faire #}
{{ commentaire_utilisateur|raw }}

{# ✅ SÛR - Échappement par défaut #}
{{ commentaire_utilisateur }}

Filtres avec arguments 🎛️

default - Valeur par défaut si vide

{{ nom|default('Anonyme') }}
{# Si nom est vide, affiche "Anonyme" #}

{{ article.titre|default('Sans titre') }}

{{ user.email|default('email@example.com') }}

round - Arrondir un nombre

{{ 3.14159|round }}        {# 3 #}
{{ 3.14159|round(2) }}     {# 3.14 #}
{{ 3.14159|round(3) }}     {# 3.142 #}

{# Modes d'arrondi #}
{{ 3.5|round(0, 'floor') }}   {# 3 (arrondi inférieur) #}
{{ 3.5|round(0, 'ceil') }}    {# 4 (arrondi supérieur) #}

abs - Valeur absolue

{{ -42|abs }}    {# 42 #}
{{ 42|abs }}     {# 42 #}

Chaînage de filtres 🔗

On peut appliquer plusieurs filtres à la suite avec le pipe | :

Exemple simple

{{ "  bonjour le monde  "|trim|upper }}
{# BONJOUR LE MONDE #}

{# Étapes :
   1. "  bonjour le monde  " (original)
   2. "bonjour le monde" (après trim)
   3. "BONJOUR LE MONDE" (après upper)
#}

Exemple complexe

{% set texte = "   ceci est un LONG texte avec DES majuscules   " %}

{{ texte|trim|lower|capitalize|slice(0, 20) }}...
{# Ceci est un long tex... #}

{# Étapes :
   1. "   ceci est un LONG texte avec DES majuscules   "
   2. "ceci est un LONG texte avec DES majuscules" (trim)
   3. "ceci est un long texte avec des majuscules" (lower)
   4. "Ceci est un long texte avec des majuscules" (capitalize)
   5. "Ceci est un long tex" (slice 0-20)
   6. "Ceci est un long tex..." (ajout ...)
#}

💡 Ordre important : Les filtres s'appliquent de gauche à droite. L'ordre peut changer complètement le résultat !

# 🔀 Partie 5

Structures de Contrôle

Conditions et boucles

Conditions : if / elseif / else 🔀

Structure de base

{% if condition %}
    {# Code si vrai #}
{% endif %}

if / else

{% if user.isAdmin %}
    <p>Vous êtes administrateur</p>
{% else %}
    <p>Vous êtes utilisateur normal</p>
{% endif %}

if / elseif / else

{% if age < 18 %}
    <p>Mineur</p>
{% elseif age < 65 %}
    <p>Adulte</p>
{% else %}
    <p>Senior</p>
{% endif %}

Conditions multiples

{% if user.isAdmin and user.isActive %}
    <p>Admin actif</p>
{% endif %}

{% if age > 18 or hasPermission %}
    <p>Accès autorisé</p>
{% endif %}

{% if not user.isBanned %}
    <p>Bienvenue</p>
{% endif %}

Opérateurs de comparaison ⚖️

Opérateur Description Exemple
== Égal à {% if age == 18 %}
!= Différent de {% if nom != 'Admin' %}
< Inférieur à {% if age < 18 %}
> Supérieur à {% if prix > 100 %}
<= Inférieur ou égal {% if note <= 10 %}
>= Supérieur ou égal {% if stock >= 5 %}
and ET logique {% if age > 18 and hasLicense %}
or OU logique {% if admin or moderator %}
not NON logique {% if not banned %}

Tests Twig (is) 🧪

Les tests permettent de vérifier le type ou l'état d'une variable :

Test Description Exemple
is defined Variable existe {% if user is defined %}
is null Variable est null {% if user is null %}
is empty Variable vide {% if tableau is empty %}
is even Nombre pair {% if nombre is even %}
is odd Nombre impair {% if nombre is odd %}
is iterable Peut être parcouru {% if data is iterable %}

Exemples pratiques

{% if users is not empty %}
    <ul>
    {% for user in users %}
        <li>{{ user.name }}</li>
    {% endfor %}
    </ul>
{% endif %}

{% if numero is even %}
    <p>{{ numero }} est pair</p>
{% else %}
    <p>{{ numero }} est impair</p>
{% endif %}

Boucle for 🔄

Syntaxe de base

{% for item in collection %}
    {# Code répété pour chaque item #}
{% endfor %}

Parcourir un tableau simple

{% set fruits = ['pomme', 'poire', 'banane'] %}

<ul>
{% for fruit in fruits %}
    <li>{{ fruit }}</li>
{% endfor %}
</ul>

{# Résultat :
   <li>pomme</li>
   <li>poire</li>
   <li>banane</li>
#}

Parcourir un tableau associatif

{% set user = {
    'nom': 'Dupont',
    'prenom': 'Marie',
    'age': 25
} %}

<dl>
{% for cle, valeur in user %}
    <dt>{{ cle }}</dt>
    <dd>{{ valeur }}</dd>
{% endfor %}
</dl>

for...else (tableau vide) 📭

Le bloc else s'exécute si le tableau est vide :

<h2>Liste des utilisateurs</h2>

<ul>
{% for user in users %}
    <li>{{ user.name }}</li>
{% else %}
    <li>Aucun utilisateur trouvé</li>
{% endfor %}
</ul>

Si users = ['Alice', 'Bob']

<ul>
    <li>Alice</li>
    <li>Bob</li>
</ul>

Si users = []

<ul>
    <li>Aucun utilisateur trouvé</li>
</ul>

Variable spéciale loop 🔁

Dans une boucle for, la variable loop donne des infos sur l'itération :

Variable Description
loop.index Numéro actuel (commence à 1)
loop.index0 Numéro actuel (commence à 0)
loop.first true si premier élément
loop.last true si dernier élément
loop.length Nombre total d'éléments
loop.parent Contexte parent (boucles imbriquées)

Exemples pratiques

<ul>
{% for user in users %}
    <li class="{% if loop.first %}first{% endif %} {% if loop.last %}last{% endif %}">
        {{ loop.index }}. {{ user.name }}
    </li>
{% endfor %}
</ul>

{# Résultat :
   <li class="first">1. Alice</li>
   <li>2. Bob</li>
   <li class="last">3. Charlie</li>
#}
# 🏗️ Partie 6

Héritage de Templates

La fonctionnalité la plus puissante de Twig

C'est quoi l'héritage de templates ? 🧬

Analogie : Le moule à gaufres 🧇

Imagine un moule à gaufres :

Le moule reste le même (header, footer...), seul le contenu des trous change !

Template de base (parent) 🏛️

Le template de base contient la structure commune de toutes les pages.

templates/base.html.twig

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}Mon Site{% endblock %}</title>
    
    {% block stylesheets %}
        <link rel="stylesheet" href="/css/style.css">
    {% endblock %}
</head>
<body>
    <header>
        <nav>
            <a href="/">Accueil</a>
            <a href="/blog">Blog</a>
            <a href="/contact">Contact</a>
        </nav>
    </header>

    <main>
        {% block body %}
            {# Contenu par défaut si aucun bloc body défini #}
        {% endblock %}
    </main>

    <footer>
        <p>© {{ 'now'|date('Y') }} Mon Site</p>
    </footer>

    {% block javascripts %}
        <script src="/js/app.js"></script>
    {% endblock %}
</body>
</html>

💡 Points clés :

Template enfant 👶

Le template enfant étend le parent et remplit ses blocs.

templates/blog/index.html.twig

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

{% block title %}Mon Blog - {{ parent() }}{% endblock %}

{% block body %}
    <h1>Liste des articles</h1>
    
    <div class="articles">
        {% for article in articles %}
            <article>
                <h2>{{ article.titre }}</h2>
                <p>{{ article.extrait }}</p>
                <a href="{{ path('blog_show', {id: article.id}) }}">
                    Lire la suite
                </a>
            </article>
        {% endfor %}
    </div>
{% endblock %}

{% block javascripts %}
    {{ parent() }}  {# Garde le JS de base.html.twig #}
    <script src="/js/blog.js"></script>  {# Ajoute du JS spécifique #}
{% endblock %}

✅ Résultat : Le HTML final aura :

extends et parent() 🔗

{% extends %} - Hériter d'un template

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

{# DOIT être la PREMIÈRE ligne du fichier #}

{{ parent() }} - Conserver le contenu parent

{# Dans base.html.twig #}
{% block javascripts %}
    <script src="/js/app.js"></script>
{% endblock %}

{# Dans enfant.html.twig #}
{% block javascripts %}
    {{ parent() }}  {# ← Garde <script src="/js/app.js"> #}
    <script src="/js/custom.js"></script>  {# ← Ajoute ce script #}
{% endblock %}

{# Résultat final :
   <script src="/js/app.js"></script>
   <script src="/js/custom.js"></script>
#}

Sans parent()

{% block javascripts %}
    <script src="/js/custom.js"></script>
{% endblock %}

❌ Remplace TOUT le contenu du parent

Avec parent()

{% block javascripts %}
    {{ parent() }}
    <script src="/js/custom.js"></script>
{% endblock %}

✅ Conserve + ajoute

Héritage multi-niveaux 🏗️🏗️🏗️

On peut créer une hiérarchie de templates :

base.html.twig (structure globale)
↓ extends
admin_base.html.twig (layout admin)
↓ extends
admin_users.html.twig (page utilisateurs)

base.html.twig

<html>
<body>
    <header>Header commun</header>
    {% block content %}{% endblock %}
    <footer>Footer commun</footer>
</body>
</html>

admin_base.html.twig

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

{% block content %}
    <div class="admin-layout">
        <aside>Menu admin</aside>
        <main>{% block admin_content %}{% endblock %}</main>
    </div>
{% endblock %}

admin_users.html.twig

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

{% block admin_content %}
    <h1>Gestion des utilisateurs</h1>
    {# ... #}
{% endblock %}
# 📦 Partie 7

Inclusion et Réutilisation

Découper pour mieux régner

include() - Inclure un template 📥

include() insère le contenu d'un autre template.

Syntaxe

{{ include('chemin/template.html.twig') }}

{# Avec variables #}
{{ include('partials/card.html.twig', {
    titre: 'Mon titre',
    contenu: 'Mon contenu'
}) }}

Exemple : Header réutilisable

templates/partials/header.html.twig

<header>
    <h1>{{ siteName }}</h1>
    <nav>
        <a href="/">Accueil</a>
        <a href="/blog">Blog</a>
    </nav>
</header>

templates/page.html.twig

{{ include('partials/header.html.twig', {
    siteName: 'Mon Super Site'
}) }}

<main>
    {# Contenu de la page #}
</main>

include vs extends 🆚

Critère extends include
Usage Hériter d'une structure Insérer un fragment
Nombre UN SEUL extends par fichier Autant d'includes que voulu
Position DOIT être en première ligne N'importe où
Cas d'usage Layout global (header/footer) Composants réutilisables (card, button...)

extends - Structure globale

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

{% block body %}
    <h1>Ma page</h1>
{% endblock %}

Pour hériter du layout complet

include - Fragment réutilisable

<div class="products">
{% for product in products %}
    {{ include('partials/product_card.html.twig', {
        product: product
    }) }}
{% endfor %}
</div>

Pour insérer des composants

# ⚙️ Partie 8

Fonctions Twig

Outils intégrés pour vos templates

Fonctions de routing 🛣️

path() - URL relative

{{ path('home') }}
{# / #}

{{ path('blog_show', {id: 123}) }}
{# /blog/123 #}

<a href="{{ path('contact') }}">Contact</a>

url() - URL absolue

{{ url('home') }}
{# http://localhost:8000/ #}

{{ url('blog_show', {id: 123}) }}
{# http://localhost:8000/blog/123 #}

{# Pour emails, flux RSS... #}
<meta property="og:url" content="{{ url('article_show', {id: article.id}) }}">

Fonctions d'assets 📦

asset() - Lien vers fichier statique

<link rel="stylesheet" href="{{ asset('css/style.css') }}">
{# /css/style.css #}

<script src="{{ asset('js/app.js') }}"></script>
{# /js/app.js #}

<img src="{{ asset('images/logo.png') }}" alt="Logo">
{# /images/logo.png #}

💡 Pourquoi utiliser asset() ?

Fonctions utilitaires 🛠️

dump() - Déboguer une variable

{{ dump(user) }}
{# Affiche toute la structure de user #}

{{ dump(user, article, comments) }}
{# Affiche plusieurs variables #}

random() - Générer de l'aléatoire

{{ random(100) }}
{# Nombre aléatoire entre 0 et 100 #}

{{ random(['rouge', 'vert', 'bleu']) }}
{# Un élément aléatoire du tableau #}

{{ random() }}
{# Nombre aléatoire entre 0 et 2^31 - 1 #}

range() - Créer une séquence

{% for i in range(1, 10) %}
    {{ i }} {# 1 2 3 4 5 6 7 8 9 10 #}
{% endfor %}

{% for i in range(0, 10, 2) %}
    {{ i }} {# 0 2 4 6 8 10 #}
{% endfor %}

max() et min() - Maximum et minimum

{{ max(1, 3, 2) }}      {# 3 #}
{{ min(1, 3, 2) }}      {# 1 #}

{{ max([10, 5, 20]) }}  {# 20 #}
# 🎪 Partie 9

Macros

Créer ses propres fonctions

C'est quoi une macro ? 🎪

Une macro est comme une fonction réutilisable en Twig.

Analogie : Le tampon encreur 🖋️

Imagine un tampon avec ton logo :

Tu crées le tampon une fois, puis tu l'utilises partout !

Définir une macro 📝

Syntaxe

{% macro nom(parametre1, parametre2) %}
    {# Code HTML à générer #}
{% endmacro %}

Exemple : Bouton réutilisable

templates/macros/forms.html.twig

{% macro button(text, type, class) %}
    <button type="{{ type|default('button') }}" 
            class="btn {{ class|default('btn-primary') }}">
        {{ text }}
    </button>
{% endmacro %}

{% macro input(name, label, type) %}
    <div class="form-group">
        <label for="{{ name }}">{{ label }}</label>
        <input type="{{ type|default('text') }}" 
               id="{{ name }}" 
               name="{{ name }}" 
               class="form-control">
    </div>
{% endmacro %}

Utiliser une macro 🎯

Importer les macros

{% import 'macros/forms.html.twig' as forms %}

Appeler les macros

{% import 'macros/forms.html.twig' as forms %}

<form>
    {{ forms.input('email', 'Adresse email', 'email') }}
    {{ forms.input('password', 'Mot de passe', 'password') }}
    
    {{ forms.button('Se connecter', 'submit', 'btn-success') }}
    {{ forms.button('Annuler', 'button', 'btn-secondary') }}
</form>

Résultat HTML

<form>
    <div class="form-group">
        <label for="email">Adresse email</label>
        <input type="email" id="email" name="email" class="form-control">
    </div>
    <div class="form-group">
        <label for="password">Mot de passe</label>
        <input type="password" id="password" name="password" class="form-control">
    </div>
    
    <button type="submit" class="btn btn-success">Se connecter</button>
    <button type="button" class="btn btn-secondary">Annuler</button>
</form>
# 🎓 Récapitulatif

Ce qu'on a appris

Syntaxe Twig - Aide-mémoire 📋

Élément Syntaxe Exemple
Afficher {{ }} {{ user.name }}
Instruction {% %} {% if age > 18 %}
Commentaire {# #} {# TODO #}
Filtre |filtre {{ nom|upper }}
Concaténation ~ {{ prenom ~ " " ~ nom }}
Test is {% if user is null %}
Extends {% extends %} {% extends 'base.html.twig' %}
Block {% block %} {% block title %}...{% endblock %}
Include {{ include() }} {{ include('header.html.twig') }}
Macro {% macro %} {% macro button(text) %}...{% endmacro %}

Top 15 des filtres Twig 🏆

Filtre Usage
|upper MAJUSCULES
|lower minuscules
|capitalize Première lettre en majuscule
|title Chaque Mot En Majuscule
|trim Supprimer espaces
|length Nombre d'éléments/caractères
|date Formater une date
|slice Extraire une portion
|join Concaténer avec séparateur
|default Valeur par défaut si vide
|replace Remplacer du texte
|round Arrondir un nombre
|sort Trier un tableau
|raw Ne pas échapper (DANGER)
|escape (e) Échapper le HTML

Structures de contrôle résumées 🔀

Condition if

{% if condition %}
    ...
{% elseif autre_condition %}
    ...
{% else %}
    ...
{% endif %}

Boucle for

{% for item in collection %}
    {{ item }}
{% else %}
    Tableau vide
{% endfor %}

Variable loop

loop.index    {# 1, 2, 3... #}
loop.index0   {# 0, 1, 2... #}
loop.first    {# true si premier #}
loop.last     {# true si dernier #}
loop.length   {# nombre total #}

Tests courants

{% if variable is defined %}
{% if variable is null %}
{% if tableau is empty %}
{% if nombre is even %}
{% if nombre is odd %}

Héritage de templates résumé 🏗️

base.html.twig (parent)
Structure commune + {% block %}
↓
page.html.twig (enfant)
{% extends 'base.html.twig' %}
Remplit les blocs
↓
HTML final
Structure du parent + contenu de l'enfant

Commandes essentielles

Commande Usage
{% extends 'base.html.twig' %} Hériter d'un template (1ère ligne)
{% block nom %}...{% endblock %} Définir un bloc remplaçable
{{ parent() }} Conserver le contenu du parent
{{ include('partial.html.twig') }} Inclure un fragment

Bonnes pratiques Twig 💡

  1. Séparer logique et présentation
    La logique métier reste dans le contrôleur, Twig affiche seulement
  2. Utiliser l'héritage de templates
    Un seul layout de base, évite la duplication
  3. Découper en petits templates
    Créer des partials pour les composants réutilisables
  4. Nommer clairement les blocs
    {% block content %} plutôt que {% block b1 %}
  5. Utiliser les macros pour le code répétitif
    Boutons, formulaires, cards...
  6. Ne JAMAIS utiliser |raw sans raison
    Sécurité XSS ! Échappement automatique = votre ami
  7. Commenter le code complexe
    {# Explication de la logique #}

Erreurs courantes à éviter ⚠️

❌ Erreur 1 : extends pas en première ligne

{# ❌ MAUVAIS #}
{# Commentaire #}
{% extends 'base.html.twig' %}

{# ✅ BON #}
{% extends 'base.html.twig' %}
{# Commentaire #}

❌ Erreur 2 : Oublier endblock

{# ❌ MAUVAIS #}
{% block content %}
    <h1>Titre</h1>
{# Oublié {% endblock %} #}

{# ✅ BON #}
{% block content %}
    <h1>Titre</h1>
{% endblock %}

❌ Erreur 3 : Utiliser |raw sans raison

{# ❌ DANGEREUX - Faille XSS #}
{{ user_comment|raw }}

{# ✅ SÛR - Échappement automatique #}
{{ user_comment }}

❌ Erreur 4 : Trop de logique dans Twig

{# ❌ MAUVAIS - Logique complexe dans Twig #}
{% set total = 0 %}
{% for item in cart %}
    {% set total = total + (item.price * item.quantity * (1 + item.tax)) %}
{% endfor %}

{# ✅ BON - Faire le calcul dans le contrôleur #}
{{ cart.total }}

Organisation des templates 📁

templates/
├── base.html.twig              # Layout principal
├── bundles/                    # Surcharges de bundles
├── macros/                     # Macros réutilisables
│   ├── forms.html.twig
│   ├── ui.html.twig
│   └── alerts.html.twig
├── partials/                   # Fragments réutilisables
│   ├── header.html.twig
│   ├── footer.html.twig
│   ├── sidebar.html.twig
│   └── pagination.html.twig
├── blog/                       # Templates du blog
│   ├── index.html.twig
│   ├── show.html.twig
│   └── _article_card.html.twig # Partial (préfixe _)
├── user/                       # Templates utilisateur
│   ├── login.html.twig
│   ├── register.html.twig
│   └── profile.html.twig
└── email/                      # Templates d'emails
    ├── welcome.html.twig
    └── reset_password.html.twig

💡 Convention : Préfixer les partials par _ pour les distinguer des pages complètes (ex: _card.html.twig)

Déboguer vos templates 🐛

Afficher le contenu d'une variable

{{ dump(user) }}
{{ dump(user, article, comments) }}
{{ dump() }}  {# Toutes les variables disponibles #}

Debug dans la toolbar Symfony

En mode dev, la toolbar Symfony affiche :

Commande de debug

# Lister toutes les fonctions Twig
php bin/console debug:twig

# Lister tous les filtres Twig
php bin/console debug:twig --filter=

# Voir les détails d'un filtre
php bin/console debug:twig --filter=date

Optimiser les performances ⚡

  1. Le cache est votre ami
    Twig compile les templates en PHP, gardé en cache
  2. Éviter trop de logique
    Faire les calculs dans le contrôleur, pas dans Twig
  3. Limiter les includes imbriqués
    Trop d'includes = beaucoup de fichiers à charger
  4. Utiliser asset() pour le versioning
    Cache-busting automatique des CSS/JS
  5. Minifier en production
    Supprimer espaces et commentaires du HTML

Vider le cache Twig

php bin/console cache:clear

Ressources pour aller plus loin 📚

Documentation Twig

🌐 https://twig.symfony.com

Documentation complète de Twig

Documentation Symfony - Templates

🌐 https://symfony.com/doc/current/templates.html

Intégration de Twig dans Symfony

Twig Extensions

🌐 https://github.com/twigphp/Twig-extensions

Extensions supplémentaires pour Twig

Cheatsheet Twig

🌐 https://devhints.io/twig

Aide-mémoire rapide

Ce que vous maîtrisez maintenant ! 💪

Compétences acquises :

💡 Prochaine étape : Doctrine ORM pour gérer la base de données !

Points clés à retenir 🎯

  1. Twig = sécurité par défaut
    Échappement automatique contre XSS
  2. 3 types de délimiteurs
    {{ }} pour afficher, {% %} pour exécuter, {# #} pour commenter
  3. Les filtres transforment l'affichage
    |upper, |date, |slice... chaînables avec |
  4. L'héritage évite la duplication
    Un layout de base, tous les templates en héritent
  5. {{ parent() }} conserve le contenu parent
    Utile pour ajouter sans remplacer
  6. Include pour les fragments réutilisables
    Header, footer, cards...
  7. Macros = fonctions Twig personnalisées
    Boutons, formulaires... à réutiliser partout

🎓 Félicitations !

Vous maîtrisez Twig

Vous savez maintenant :

✅ Créer des templates propres et maintenables

✅ Réutiliser du code avec l'héritage

✅ Transformer les données avec les filtres

✅ Sécuriser vos templates contre XSS

💡 Conseil final :

Twig est puissant mais simple. Gardez vos templates lisibles : si c'est trop complexe, c'est que ça devrait être dans le contrôleur !

Prochaine étape : Doctrine ORM ! 🗄️

Formation DWWM - 2026