1 / 20
🌿

Twig & Templates

Moteur de templates et vues dans Symfony 6.4

Formation DWWM - Développement Web et Web Mobile

🎨 Introduction à Twig :
Twig est le moteur de templates de Symfony qui sépare la logique PHP de la présentation HTML. Il transforme vos données PHP en belles pages web, tout en gardant votre code propre et sécurisé.

Plan du Cours

🔧 Fondamentaux

  • Introduction à Twig
  • Syntaxe de base
  • Variables et affichage
  • Structures de contrôle

🏗️ Structure

  • Héritage de templates
  • Inclusion et composition
  • Filtres et fonctions
  • Macros et réutilisabilité

⚡ Avancé

  • Formulaires avec Twig
  • Assets et optimisation
  • Variables globales
  • Internationalisation

🚀 Optimisation

  • Cache et performance
  • Extensions personnalisées
  • Débogage
  • Bonnes pratiques

Pourquoi Utiliser Twig ?

PHP Classique

<?php if ($user): ?>
    <h1><?= htmlspecialchars($user->getName()) ?></h1>
    <?php foreach ($articles as $article): ?>
        <div>
            <h2><?= htmlspecialchars($article->getTitle()) ?></h2>
            <p><?= nl2br(htmlspecialchars($article->getContent())) ?></p>
        </div>
    <?php endforeach; ?>
<?php else: ?>
    <p>Veuillez vous connecter</p>
<?php endif; ?>

Avec Twig

{% if user %}
    <h1>{{ user.name }}</h1>
    {% for article in articles %}
        <div>
            <h2>{{ article.title }}</h2>
            <p>{{ article.content|nl2br }}</p>
        </div>
    {% endfor %}
{% else %}
    <p>Veuillez vous connecter</p>
{% endif %}
✨ Avantages de Twig :
Lisibilité : Syntaxe claire sans mélange PHP/HTML
Sécurité : Échappement automatique des variables (fini les XSS !)
Performance : Templates compilés en PHP optimisé
Maintenance : Séparation claire entre logique et présentation
Fonctionnalités : Héritage, filtres, macros...

Syntaxe de Base Twig

Variables

{{ variable }}
{{ user.name }}
{{ user.getName() }}
{{ user['email'] }}

Instructions

{% if condition %}
{% for item in items %}
{% set variable = value %}
{% include 'template.html.twig' %}

Commentaires

{# Ceci est un commentaire #}
{#
    Commentaire
    sur plusieurs lignes
#}
📝 Les trois syntaxes Twig :
{{ }} : Affiche une variable (équivalent à echo en PHP)
{% %} : Exécute une instruction (if, for, set...)
{# #} : Commentaire (invisible dans le HTML final)

Accès aux propriétés : user.name fonctionne avec getters, propriétés publiques ou méthodes
💡 Conventions :
• Variables en snake_case : user_name
• Templates en minuscules : blog/show.html.twig
• Pas d'espaces dans les {{ }}

Variables et Affichage

Depuis le contrôleur

// Dans le contrôleur
return $this->render('blog/article.html.twig', [
    'article' => $article,
    'user' => $this->getUser(),
    'comments' => $comments,
    'is_published' => true,
    'tags' => ['symfony', 'php', 'web']
]);

Dans le template

{# Affichage simple #}
<h1>{{ article.title }}</h1>
<p>Par {{ user.username }} le {{ article.publishedAt|date('d/m/Y') }}</p>

{# Affichage conditionnel #}
{{ is_published ? 'Publié' : 'Brouillon' }}

{# Valeur par défaut #}
{{ user.avatar|default('default-avatar.png') }}

{# Tableaux #}
{{ tags[0] }}
{{ tags|length }} tags au total
🔄 Du contrôleur au template :
Tableau associatif → Variables Twig accessibles
Objets → Propriétés et méthodes accessibles avec la notation point
Sécurité → Toutes les variables sont automatiquement échappées

Opérateur ternaire : condition ? valeur_si_vrai : valeur_si_faux
🎯 Accès flexible :
user.name peut correspondre à :
• $user['name'] (tableau)
• $user->name (propriété)
• $user->getName() (méthode)
Twig essaie dans cet ordre !

Structures de Contrôle

Conditions

{% if user %}
    Bonjour {{ user.name }}
{% elseif guest %}
    Bonjour invité
{% else %}
    Veuillez vous connecter
{% endif %}

{# Conditions complexes #}
{% if user and user.isActive %}
    Utilisateur actif
{% endif %}

Boucles

{% for article in articles %}
    <h2>{{ article.title }}</h2>
{% else %}
    <p>Aucun article</p>
{% endfor %}

{# Avec index #}
{% for key, article in articles %}
    {{ loop.index }}. {{ article.title }}
{% endfor %}

Variable loop dans les boucles

{% for item in items %}
    {% if loop.first %}<ul>{% endif %}
    
    <li class="{{ loop.index is odd ? 'odd' : 'even' }}">
        {{ loop.index }}/{{ loop.length }} : {{ item.name }}
    </li>
    
    {% if loop.last %}</ul>{% endif %}
{% endfor %}
🔄 Structures de contrôle :
if/elseif/else : Conditions comme en PHP
for...else : else s'exécute si la collection est vide
Variable loop : Informations sur la boucle courante

Opérateurs : and, or, not, ==, !=, <, >, in, is
🎯 Variable loop utile :
• loop.index : Position (commence à 1)
• loop.index0 : Position (commence à 0)
• loop.first/last : Premier/dernier élément
• loop.length : Nombre total d'éléments

Héritage de Templates

base.html.twig
Template parent
↓ extends
layout.html.twig
Layout spécifique
↓ extends
page.html.twig
Page finale

Template de base (base.html.twig)

<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}Mon Site{% endblock %}</title>
    {% block stylesheets %}
        <link rel="stylesheet" href="/css/app.css">
    {% endblock %}
</head>
<body>
    <header>{% block header %}{% endblock %}</header>
    <main>{% block body %}{% endblock %}</main>
    <footer>{% block footer %}{% endblock %}</footer>
    {% block javascripts %}{% endblock %}
</body>
</html>
🏗️ Héritage expliqué :
{% block %} : Zone remplaçable par les templates enfants
extends : Hérite d'un template parent
Hiérarchie : Base → Layout → Page spécifique

Avantage : Structure HTML commune, contenu spécialisé par page

Template Enfant

blog/article.html.twig

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

{% block title %}{{ article.title }} - {{ parent() }}{% endblock %}

{% block stylesheets %}
    {{ parent() }}
    <link rel="stylesheet" href="/css/blog.css">
{% endblock %}

{% block body %}
    <article>
        <h1>{{ article.title }}</h1>
        <div class="meta">
            Par {{ article.author.name }} le {{ article.publishedAt|date('d/m/Y') }}
        </div>
        <div class="content">
            {{ article.content|markdown|raw }}
        </div>
    </article>
{% endblock %}

{% block javascripts %}
    {{ parent() }}
    <script src="/js/blog.js"></script>
{% endblock %}
👶 Template enfant :
extends : Indique quel template parent utiliser
block : Remplace ou enrichit les blocs du parent
parent() : Récupère le contenu du bloc parent

Stratégie : parent() + nouveau contenu = enrichissement progressif
💡 Bonnes pratiques :
• Un seul extends par template
• extends en première ligne
• Utilisez parent() pour enrichir, pas remplacer
• Nommez vos blocs de façon explicite

Inclusion et Composition

Include simple

{# Inclusion d'un template #}
{% include 'partials/header.html.twig' %}

{# Avec variables #}
{% include 'partials/article_card.html.twig' with {
    'article': article,
    'show_author': true
} %}

{# Include conditionnel #}
{% include 'partials/sidebar.html.twig' ignore missing %}

Template partiel (partials/article_card.html.twig)

<div class="card">
    <h3><a href="{{ path('article_show', {id: article.id}) }}">
        {{ article.title }}
    </a></h3>
    <p>{{ article.excerpt }}</p>
    {% if show_author|default(false) %}
        <small>Par {{ article.author.name }}</small>
    {% endif %}
</div>

Embed (inclusion avec blocks)

{% embed 'partials/modal.html.twig' %}
    {% block modal_title %}Supprimer l'article{% endblock %}
    {% block modal_body %}
        Êtes-vous sûr de vouloir supprimer "{{ article.title }}" ?
    {% endblock %}
{% endembed %}
🧩 Composition de templates :
include : Insère un template complet
with : Passe des variables spécifiques
embed : Include + possibilité de redéfinir des blocs
ignore missing : Pas d'erreur si le fichier n'existe pas

Cas d'usage : Composants réutilisables (cartes, boutons, modales...)
🔄 Différences :
extends → Héritage (1 parent, plusieurs enfants)
include → Composition (réutilisation de morceaux)
embed → Include + personnalisation via blocks

Filtres Courants

Texte

{{ text|upper }}
{{ text|lower }}
{{ text|title }}
{{ text|length }}
{{ text|slice(0, 100) }}

Dates

{{ date|date('d/m/Y') }}
{{ date|date('H:i') }}
{{ date|format_datetime() }}
{{ "now"|date('Y-m-d') }}

Tableaux

{{ array|join(', ') }}
{{ array|first }}
{{ array|last }}
{{ array|reverse }}
{{ array|sort }}

Formatage

{{ price|number_format(2) }}
{{ text|nl2br }}
{{ html|striptags }}
{{ url|url_encode }}
{{ text|trim }}

Exemples pratiques

{# Chaîne multiple de filtres #}
{{ article.content|striptags|slice(0, 150)|trim ~ '...' }}

{# Valeur par défaut #}
{{ user.avatar|default('default.png') }}

{# Échappement (ou non) #}
{{ content|markdown|raw }}
{{ user_input }}  {# Échappé automatiquement #}

{# Formatage conditionnel #}
{{ articles|length > 0 ? articles|length ~ ' articles' : 'Aucun article' }}
🔧 Filtres expliqués :
Pipe | : Applique un filtre à une variable
Chaînage : Plusieurs filtres successifs (gauche vers droite)
Paramètres : Certains filtres acceptent des arguments
raw : Désactive l'échappement automatique (attention !)

Ordre important : striptags puis slice, pas l'inverse !
⚠️ Sécurité : N'utilisez |raw que sur du contenu de confiance ! Les données utilisateurs doivent rester échappées.

Fonctions Twig

URLs et routes

{# Génération d'URLs #}
{{ path('blog_show', {id: article.id}) }}
{{ url('blog_show', {id: article.id}) }}

{# Assets #}
{{ asset('images/logo.png') }}
{{ asset('css/app.css') }}

Chaînes et tableaux

{# Manipulation #}
{{ random(array) }}
{{ range(1, 10) }}
{{ cycle(['odd', 'even'], loop.index0) }}

{# Tests #}
{{ dump(variable) }}
{{ max(prices) }}
{{ min(prices) }}

Fonctions Symfony spécifiques

{# Sécurité #}
{% if is_granted('ROLE_ADMIN') %}
    <a href="{{ path('admin_panel') }}">Administration</a>
{% endif %}

{# Contrôleur et sous-requêtes #}
{{ render(controller('App\\Controller\\WidgetController::latest')) }}

{# Formulaires #}
{{ form_start(form) }}
{{ form_widget(form.title) }}
{{ form_end(form) }}

{# Traductions #}
{{ 'hello.world'|trans }}
{{ 'hello.user'|trans({'%name%': user.name}) }}
⚡ Fonctions vs Filtres :
Fonctions : Appelées directement → path(), asset(), dump()
Filtres : Appliqués à une variable → variable|filter
Symfony : Fonctions spécifiques pour routing, sécurité, forms...

render(controller()) : Exécute un contrôleur et inclut le résultat
💡 Fonctions utiles :
• dump() → Debug (visible seulement en dev)
• cycle() → Alterne entre valeurs dans une boucle
• random() → Élément aléatoire d'un tableau
• range() → Génère une séquence de nombres

Macros et Réutilisabilité

Définition de macros (macros/ui.html.twig)

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

{% macro alert(message, type='info') %}
    <div class="alert alert-{{ type }}">
        <strong>{{ type|title }} :</strong> {{ message }}
    </div>
{% endmacro %}

{% macro pagination(current_page, total_pages, route_name) %}
    <nav>
        <ul class="pagination">
            {% for page in 1..total_pages %}
                <li class="{{ page == current_page ? 'active' : '' }}">
                    <a href="{{ path(route_name, {page: page}) }}">{{ page }}</a>
                </li>
            {% endfor %}
        </ul>
    </nav>
{% endmacro %}

Utilisation des macros

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

{# Utilisation simple #}
{{ ui.button('Voir l\'article', path('blog_show', {id: article.id})) }}
{{ ui.button('Supprimer', path('blog_delete', {id: article.id}), 'danger') }}

{# Alerts #}
{{ ui.alert('Article sauvegardé avec succès', 'success') }}
{{ ui.alert('Erreur lors de la sauvegarde', 'error') }}

{# Pagination #}
{{ ui.pagination(current_page, total_pages, 'blog_index') }}
🔧 Macros = Fonctions réutilisables :
{% macro %} : Définit une fonction Twig réutilisable
import : Importe les macros d'un fichier
Paramètres : Avec valeurs par défaut possibles
Usage : Composants UI, widgets, éléments répétitifs

Avantage : Centralisation du code, cohérence visuelle, maintenance facilitée
🎯 Cas d'usage typiques :
• Boutons avec styles cohérents
• Messages d'alerte uniformes
• Pagination standardisée
• Cards/widgets répétitifs
• Formulaires complexes

Formulaires avec Twig

Rendu automatique

{# Formulaire complet automatique #}
{{ form(form) }}

{# Contrôle manuel #}
{{ form_start(form) }}
    {{ form_widget(form) }}
{{ form_end(form) }}

{# Contrôle total #}
{{ form_start(form) }}
    {{ form_row(form.title) }}
    {{ form_row(form.content) }}
    {{ form_row(form.submit) }}
{{ form_end(form) }}

Personnalisation avancée

{{ form_start(form, {'attr': {'class': 'form-horizontal'}}) }}

{# Champ personnalisé #}
<div class="form-group">
    {{ form_label(form.title, 'Titre de l\'article') }}
    {{ form_widget(form.title, {'attr': {'class': 'form-control'}}) }}
    {{ form_errors(form.title) }}
    <small>Le titre doit être accrocheur</small>
</div>

{# Champ avec conditions #}
{% if form.category is defined %}
    {{ form_row(form.category) }}
{% endif %}

{# Gestion des erreurs globales #}
{{ form_errors(form) }}

{{ form_end(form) }}
📝 Rendu de formulaires :
form() : Rendu complet automatique
form_start/end() : Balises form + champs cachés
form_row() : Label + widget + erreurs
form_widget() : Champ de saisie uniquement

Granularité : Du plus automatique au plus personnalisé
💡 Personnalisation :
• attr : Attributs HTML (class, id, placeholder...)
• label : Texte du label personnalisé
• form_errors() : Affichage des erreurs de validation
• Variables Twig disponibles dans les champs

Gestion des Assets

Assets simples

{# Dans base.html.twig #}
{% block stylesheets %}
    <link rel="stylesheet" href="{{ asset('css/app.css') }}">
    <link rel="stylesheet" href="{{ asset('css/bootstrap.min.css') }}">
{% endblock %}

{% block javascripts %}
    <script src="{{ asset('js/app.js') }}"></script>
    <script src="{{ asset('js/jquery.min.js') }}"></script>
{% endblock %}

Avec Webpack Encore

{# Installation: composer require symfony/webpack-encore-bundle #}

{% block stylesheets %}
    {{ encore_entry_link_tags('app') }}
{% endblock %}

{% block javascripts %}
    {{ encore_entry_script_tags('app') }}
{% endblock %}

{# Assets spécifiques à une page #}
{% block stylesheets %}
    {{ parent() }}
    {{ encore_entry_link_tags('admin') }}
{% endblock %}

Images et ressources

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

{# Avec versioning automatique #}
<img src="{{ asset('images/avatar.jpg', 'images') }}" alt="Avatar">

{# Chemins absolus #}
<meta property="og:image" content="{{ absolute_url(asset('images/og.jpg')) }}">

{# Vérification d'existence #}
{% if asset_exists('images/banner.jpg') %}
    <img src="{{ asset('images/banner.jpg') }}" alt="Banner">
{% endif %}
📦 Gestion des assets :
asset() : Génère le chemin vers un fichier statique
Webpack Encore : Compilation, minification, versioning automatique
encore_entry_* : Inclut les assets compilés
Versioning : Cache-busting automatique

Organisation : public/css, public/js, public/images
🚀 Avantages Webpack Encore :
• Compilation Sass/Less automatique
• Minification et optimisation
• Modules ES6/CommonJS
• Hot reload en développement
• Splitting de code automatique

Variables Globales et App

Variable app

{# Utilisateur connecté #}
{% if app.user %}
    Bonjour {{ app.user.username }}
    <a href="{{ path('app_logout') }}">Déconnexion</a>
{% else %}
    <a href="{{ path('app_login') }}">Connexion</a>
{% endif %}

{# Informations sur la requête #}
Route actuelle: {{ app.request.get('_route') }}
Paramètres: {{ dump(app.request.get('_route_params')) }}

{# Environnement #}
{% if app.environment == 'dev' %}
    <div class="debug-bar">Mode développement</div>
{% endif %}

{# Session #}
{{ app.session.get('cart_count', 0) }} articles dans le panier

Variables globales personnalisées

# config/packages/twig.yaml
twig:
    globals:
        site_name: 'Mon Super Site'
        contact_email: 'contact@monsite.com'
        social_links:
            twitter: 'https://twitter.com/monsite'
            facebook: 'https://facebook.com/monsite'
{# Utilisation dans les templates #}
<title>{{ block('title') }} - {{ site_name }}</title>

<footer>
    <p>Contact: <a href="mailto:{{ contact_email }}">{{ contact_email }}</a></p>
    <div class="social">
        {% for platform, url in social_links %}
            <a href="{{ url }}">{{ platform|title }}</a>
        {% endfor %}
    </div>
</footer>
🌐 Variables globales :
app : Variable automatique avec user, request, session, environment
Globals custom : Variables définies dans twig.yaml
Disponibilité : Accessibles dans tous les templates
Cas d'usage : Configuration site, informations partagées

app.user : null si non connecté, objet User sinon
🎯 Utilisations courantes :
• Nom du site, slogan, contact
• Liens réseaux sociaux
• Configuration générale
• Informations de l'utilisateur connecté
• Détection mobile/desktop

Internationalisation (i18n)

Configuration

# config/packages/translation.yaml
framework:
    default_locale: fr
    translator:
        default_path: '%kernel.project_dir%/translations'
        fallbacks:
            - fr

Traductions dans Twig

{# Traduction simple #}
{{ 'hello.world'|trans }}
{{ 'menu.home'|trans }}

{# Avec paramètres #}
{{ 'hello.user'|trans({'%name%': user.name}) }}
{{ 'articles.count'|trans({'%count%': articles|length}) }}

{# Domaine de traduction #}
{{ 'error.not_found'|trans({}, 'errors') }}

{# Pluralisation #}
{{ message|trans_choice(count) }}

{# Dans un bloc #}
{% trans %}
    Bienvenue sur notre site
{% endtrans %}

{# Bloc avec paramètres #}
{% trans with {'%name%': user.name} %}
    Bonjour %name%, vous avez %count% messages
{% endtrans %}

Fichiers de traduction

# translations/messages.fr.yaml
hello:
    world: Bonjour le monde
    user: Bonjour %name%

menu:
    home: Accueil
    about: À propos
    contact: Contact

articles:
    count: '{0} Aucun article|{1} 1 article|]1,Inf[ %count% articles'
🌍 Internationalisation :
trans filter : Traduit une clé de traduction
Paramètres : %name% remplacé par la valeur fournie
Domaines : Organisation des traductions par thème
Pluralisation : Formes différentes selon le nombre

Organisation : translations/messages.{locale}.yaml
💡 Bonnes pratiques i18n :
• Clés hiérarchiques : menu.home, user.profile
• Paramètres explicites : %user_name%, %count%
• Domaines séparés : errors, admin, emails
• Fallback sur locale par défaut
• Extraction automatique : php bin/console translation:update

Cache et Optimisation

Cache de templates

# Cache automatique en production
# var/cache/prod/twig/ contient les templates compilés

# Vider le cache Twig
php bin/console cache:clear
php bin/console cache:warmup

# En développement (auto-reload)
# config/packages/dev/twig.yaml
twig:
    auto_reload: true
    strict_variables: true

Cache de fragments

{# Cache d'un bloc coûteux #}
{% cache 'sidebar' %}
    {# Contenu mis en cache pendant 1 heure #}
    {% for article in expensive_query_articles %}
        {{ include('partials/article_card.html.twig') }}
    {% endfor %}
{% endcache %}

{# Cache avec clé dynamique #}
{% cache 'user_dashboard_' ~ user.id for date('+1 hour') %}
    {{ render(controller('App\\Controller\\DashboardController::widgets')) }}
{% endcache %}

Optimisations

{# Lazy loading d'includes coûteux #}
{{ include('partials/heavy_widget.html.twig', lazy=true) }}

{# ESI pour parties dynamiques #}
{{ render_esi(controller('App\\Controller\\UserController::status')) }}

{# Minification HTML en production #}
# config/packages/prod/twig.yaml
twig:
    optimizations: -1  # Toutes les optimisations
⚡ Performance Twig :
Compilation : Templates convertis en PHP optimisé
Cache automatique : Recalculé seulement si fichier modifié
Fragment cache : Cache de portions de pages
ESI : Edge Side Includes pour contenu dynamique

Environnements : Auto-reload en dev, cache persistant en prod
🚀 Optimisations recommandées :
• Cache fragments coûteux (requêtes complexes)
• Lazy loading pour includes lourds
• ESI pour parties personnalisées
• Webpack Encore pour assets
• OpCache PHP activé

Extensions Twig

Extension personnalisée

// src/Twig/AppExtension.php
namespace App\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;

class AppExtension extends AbstractExtension
{
    public function getFilters(): array
    {
        return [
            new TwigFilter('price', [$this, 'formatPrice']),
            new TwigFilter('excerpt', [$this, 'excerpt']),
        ];
    }

    public function getFunctions(): array
    {
        return [
            new TwigFunction('random_color', [$this, 'randomColor']),
        ];
    }

    public function formatPrice(float $price): string
    {
        return number_format($price, 2, ',', ' ') . ' €';
    }

    public function excerpt(string $text, int $length = 100): string
    {
        return strlen($text) > $length ? substr($text, 0, $length) . '...' : $text;
    }

    public function randomColor(): string
    {
        return sprintf('#%06X', mt_rand(0, 0xFFFFFF));
    }
}

Utilisation

{# Utilisation des filtres personnalisés #}
{{ product.price|price }}
{{ article.content|excerpt(200) }}

{# Utilisation des fonctions #}
<div style="background-color: {{ random_color() }}">
    Contenu avec couleur aléatoire
</div>
🔧 Extensions personnalisées :
AbstractExtension : Classe de base pour créer des extensions
TwigFilter : Ajoute des filtres personnalisés
TwigFunction : Ajoute des fonctions personnalisées
Auto-registration : Symfony détecte automatiquement l'extension

Cas d'usage : Logique métier spécifique, formatage, helpers
🎯 Exemples d'extensions utiles :
• Formatage monétaire, dates, nombres
• Génération de couleurs, tokens
• Manipulation de texte avancée
• Intégration APIs externes
• Helpers de développement

Débogage et Profiler

Débogage en mode dev

{# Debug des variables #}
{{ dump(variable) }}
{{ dump(user, articles) }}
{{ dump() }}  {# Toutes les variables #}

{# Dans une condition #}
{% if app.environment == 'dev' %}
    {{ dump(debug_info) }}
{% endif %}

{# Test d'existence #}
{% if variable is defined %}
    {{ variable }}
{% else %}
    Variable non définie
{% endif %}

Profiler Twig

{# Informations dans la barre de debug #}
- Nombre de templates rendus
- Temps de compilation
- Variables passées aux templates
- Héritage et includes

{# Erreurs courantes détectées #}
- Variables non définies
- Filtres inexistants  
- Blocs non fermés
- Syntaxe invalide

Commandes de débogage

# Lister tous les filtres, fonctions, tests disponibles
php bin/console debug:twig

# Analyser un template spécifique
php bin/console lint:twig templates/

# Vérifier la syntaxe
php bin/console lint:twig templates/blog/article.html.twig

# Afficher l'arbre d'héritage
php bin/console debug:twig --show-inheritance
🔍 Outils de débogage :
dump() : Affiche le contenu des variables (dev uniquement)
Profiler : Panneau dédié Twig dans la toolbar
debug:twig : Liste toutes les fonctionnalités disponibles
lint:twig : Vérifie la syntaxe des templates

Erreurs : Messages détaillés avec numéro de ligne et contexte
🐛 Debug efficace :
• dump() en début de template pour voir toutes les variables
• Vérifiez l'onglet Twig du profiler
• Utilisez is defined pour éviter les erreurs
• lint:twig avant chaque commit

Bonnes Pratiques et Structure

📁 Organisation

  • Structure claire : base → layout → pages
  • Nommage cohérent et explicite
  • Partials réutilisables avec _
  • Dossiers par fonctionnalité

🔧 Code

  • Blocs logiques et bien nommés
  • Macros pour composants
  • Filtres pour formatage
  • Commentaires explicatifs

Structure recommandée

templates/
├── base.html.twig           # Template racine
├── layout/
│   ├── admin.html.twig      # Layout admin
│   └── front.html.twig      # Layout public
├── partials/
│   ├── _header.html.twig    # En-tête
│   ├── _footer.html.twig    # Pied de page
│   └── _pagination.html.twig # Pagination
├── macros/
│   ├── ui.html.twig         # Composants UI
│   └── forms.html.twig      # Éléments formulaires
├── blog/
│   ├── index.html.twig      # Liste articles
│   ├── show.html.twig       # Article unique
│   └── _article_card.html.twig # Carte article
└── errors/
    ├── 404.html.twig        # Page 404
    └── 500.html.twig        # Erreur serveur
🏆 Principes fondamentaux :
DRY : Don't Repeat Yourself - utilisez includes et macros
Séparation : Logique dans le contrôleur, présentation dans Twig
Lisibilité : Code clair, indentation cohérente
Performance : Cache, optimisations, assets compilés

Sécurité : Échappement par défaut, |raw avec précaution
⚠️ Pièges à éviter :
• Logique complexe dans les templates
• Trop de niveaux d'héritage
• Variables non échappées (|raw)
• Templates trop volumineux
• Noms de blocs génériques
✅ Checklist qualité :
• Templates validés avec lint:twig
• Héritage optimal (max 3 niveaux)
• Macros pour code répétitif
• Cache sur fragments coûteux
• Tests sur différents navigateurs
• Documentation des composants

🎨 Félicitations !

Vous maîtrisez maintenant Twig et les templates Symfony !

Créez des vues élégantes, maintenables et performantes 🚀