Templates Twig
🌿 Qu’est-ce que Twig ?
Section intitulée « 🌿 Qu’est-ce que Twig ? »Twig est le moteur de templates utilisé par Drupal depuis la version 8. C’est un langage de templating moderne, sûr et flexible qui sépare la logique métier (PHP) de la présentation (HTML).
Pourquoi Twig dans Drupal ?
Section intitulée « Pourquoi Twig dans Drupal ? »Avant Drupal 8 : PHPTemplate (mélange PHP/HTML difficile à maintenir) Depuis Drupal 8 : Twig (séparation claire, sécurité renforcée)
Avantages de Twig
Section intitulée « Avantages de Twig »- ✅ Sécurité : Échappement automatique contre les attaques XSS
- ✅ Syntaxe claire : Facile à lire et à maintenir
- ✅ Héritage : Réutilisation et extension de templates
- ✅ Filtres puissants : Transformation de données sans PHP
- ✅ Séparation : Logique métier (PHP) vs présentation (Twig)
- ✅ Performance : Templates compilés en PHP optimisé
Twig vs PHP dans les templates
Section intitulée « Twig vs PHP dans les templates »| Aspect | PHPTemplate (ancien) | Twig (actuel) |
|---|---|---|
| Sécurité | Échappement manuel | Automatique |
| Syntaxe | <?php echo $var ?> | {{ var }} |
| Logique | PHP mélangé au HTML | Séparé en .theme |
| Maintenabilité | Difficile | Excellente |
| Courbe d’apprentissage | Connaissance PHP | Syntaxe simple |
Syntaxe de base
Section intitulée « Syntaxe de base »{# Commentaire - ignoré dans le rendu #}
{{ variable }} {# Affiche une variable #}{{ variable|filter }} {# Applique un filtre #}
{% if condition %} {# Structure de contrôle #}{% endif %}
{% for item in items %} {# Boucle #}{% endfor %}🔍 Activer le débogage Twig
Section intitulée « 🔍 Activer le débogage Twig »-
Copiez le fichier de services par défaut
Fenêtre de terminal cp sites/default/default.services.yml sites/default/services.yml -
Éditez
sites/default/services.ymlparameters:twig.config:debug: trueauto_reload: truecache: false -
Videz le cache
Fenêtre de terminal drush cr
Résultat dans le HTML source
Section intitulée « Résultat dans le HTML source »<!-- THEME DEBUG --><!-- THEME HOOK: 'node' --><!-- FILE NAME SUGGESTIONS: * node--product--full.html.twig * node--product.html.twig * node--1.html.twig * node.html.twig--><!-- BEGIN OUTPUT from 'core/themes/olivero/templates/content/node.html.twig' -->📐 Hiérarchie des templates
Section intitulée « 📐 Hiérarchie des templates »Template de base → Plus spécifique
Section intitulée « Template de base → Plus spécifique »node.html.twig # Template générique └── node--article.html.twig # Type de contenu spécifique └── node--article--teaser.html.twig # + View mode └── node--123.html.twig # Nœud spécifiquePriorité (du plus au moins spécifique)
Section intitulée « Priorité (du plus au moins spécifique) »node--{id}.html.twig→node--123.html.twignode--{type}--{view-mode}.html.twig→node--product--card.html.twignode--{type}.html.twig→node--product.html.twignode--{view-mode}.html.twig→node--teaser.html.twignode.html.twig→ Template de base
📄 Templates principaux
Section intitulée « 📄 Templates principaux »page.html.twig
Section intitulée « page.html.twig »Structure globale de la page :
{#/** * @file * Theme override for the page template. */#}<!DOCTYPE html><html{{ html_attributes }}><head> <head-placeholder token="{{ placeholder_token }}"> <title>{{ head_title|safe_join(' | ') }}</title> <css-placeholder token="{{ placeholder_token }}"> <js-placeholder token="{{ placeholder_token }}"></head><body{{ attributes }}> <a href="#main-content" class="visually-hidden focusable skip-link"> {{ 'Skip to main content'|t }} </a>
{{ page_top }} {{ page }} {{ page_bottom }}
<js-bottom-placeholder token="{{ placeholder_token }}"></body></html>Fichier page.html.twig personnalisé
Section intitulée « Fichier page.html.twig personnalisé »{#/** * @file * TailStore theme override for the page template. */#}<div class="layout-container">
<header class="site-header" role="banner"> <div class="header-inner container"> {{ page.header }}
<nav class="main-nav" role="navigation"> {{ page.primary_menu }} </nav>
<div class="header-actions"> {{ page.secondary_menu }} </div> </div> </header>
{% if page.highlighted %} <div class="highlighted"> {{ page.highlighted }} </div> {% endif %}
{% if page.breadcrumb %} <nav class="breadcrumb-nav" aria-label="Breadcrumb"> <div class="container"> {{ page.breadcrumb }} </div> </nav> {% endif %}
<main role="main" id="main-content" class="site-main"> <div class="container">
{{ page.help }}
<div class="main-content-wrapper{% if page.sidebar %} has-sidebar{% endif %}"> <div class="content-area"> {{ page.content }} </div>
{% if page.sidebar %} <aside class="sidebar" role="complementary"> {{ page.sidebar }} </aside> {% endif %} </div>
{% if page.content_below %} <div class="content-below"> {{ page.content_below }} </div> {% endif %}
</div> </main>
<footer class="site-footer" role="contentinfo"> <div class="container"> {% if page.footer_top %} <div class="footer-top"> {{ page.footer_top }} </div> {% endif %}
{% if page.footer_bottom %} <div class="footer-bottom"> {{ page.footer_bottom }} </div> {% endif %} </div> </footer>
</div>🛍️ Template node—product.html.twig
Section intitulée « 🛍️ Template node—product.html.twig »{#/** * @file * Theme override for Product full display. * * Available variables: * - node: The node entity. * - label: The node title. * - content: All node items. * - formatted_price: Prix formaté (depuis preprocess). * - in_stock: Booléen de disponibilité. * - is_on_sale: Produit en promotion. * - old_price: Ancien prix si promo. */#}
{% set classes = [ 'node', 'node--type-' ~ node.bundle|clean_class, node.isPromoted() ? 'node--promoted', node.isSticky() ? 'node--sticky', not node.isPublished() ? 'node--unpublished', view_mode ? 'node--view-mode-' ~ view_mode|clean_class, 'product', 'product--full',] %}
<article{{ attributes.addClass(classes) }}>
<div class="product__gallery"> {% if content.field_images|render %} <div class="product__main-image"> {{ content.field_images.0 }} </div>
{% if content.field_images|length > 1 %} <div class="product__thumbnails"> {% for key, image in content.field_images if key matches '/^\\d+$/' %} <button class="product__thumbnail" data-index="{{ key }}"> {{ image }} </button> {% endfor %} </div> {% endif %} {% endif %} </div>
<div class="product__details">
{# Marque #} {% if content.field_brand|render %} <div class="product__brand"> {{ content.field_brand }} </div> {% endif %}
{# Titre #} {{ title_prefix }} <h1 class="product__title">{{ label }}</h1> {{ title_suffix }}
{# Prix #} <div class="product__pricing"> {% if is_on_sale and old_price %} <span class="product__old-price">{{ old_price }}</span> <span class="product__price product__price--sale">{{ formatted_price }}</span> <span class="product__discount-badge">Promo</span> {% else %} <span class="product__price">{{ formatted_price }}</span> {% endif %} </div>
{# Disponibilité #} <div class="product__availability"> {% if in_stock %} <span class="badge badge--success">En stock</span> {% else %} <span class="badge badge--danger">Rupture de stock</span> {% endif %} </div>
{# Description courte #} {% if content.field_short_description|render %} <div class="product__short-description"> {{ content.field_short_description }} </div> {% endif %}
{# Tailles #} {% if content.field_sizes|render %} <div class="product__sizes"> <label class="product__option-label">Taille :</label> <div class="product__size-options"> {% for size in node.field_sizes %} <button class="size-btn" type="button" data-size="{{ size.entity.id }}"> {{ size.entity.label }} </button> {% endfor %} </div> </div> {% endif %}
{# Couleurs #} {% if content.field_colors|render %} <div class="product__colors"> <label class="product__option-label">Couleur :</label> <div class="product__color-options"> {% for color in node.field_colors %} <button class="color-btn" type="button" data-color="{{ color.entity.id }}" style="background-color: {{ color.entity.field_color_code.value }}" title="{{ color.entity.label }}" ></button> {% endfor %} </div> </div> {% endif %}
{# Actions #} <div class="product__actions"> <div class="product__quantity"> <label for="quantity">Quantité :</label> <input type="number" id="quantity" name="quantity" value="1" min="1" max="10"> </div>
<button type="button" class="btn btn--primary btn--lg product__add-to-cart" data-product-id="{{ node.id }}" {% if not in_stock %}disabled{% endif %} > <span class="btn__icon">🛒</span> <span class="btn__text">Ajouter au panier</span> </button>
<button type="button" class="btn btn--outline product__wishlist"> ♡ Favoris </button> </div>
{# SKU #} {% if content.field_sku|render %} <div class="product__sku"> <strong>Référence :</strong> {{ content.field_sku }} </div> {% endif %}
{# Catégorie #} {% if content.field_category|render %} <div class="product__category"> <strong>Catégorie :</strong> {{ content.field_category }} </div> {% endif %}
</div>
{# Description complète #} {% if content.field_description|render %} <div class="product__full-description"> <h2>Description</h2> {{ content.field_description }} </div> {% endif %}
</article>🃏 Template node—product—card.html.twig
Section intitulée « 🃏 Template node—product—card.html.twig »{#/** * @file * Theme override for Product card display. */#}
{% set classes = [ 'product-card', is_on_sale ? 'product-card--sale', not in_stock ? 'product-card--out-of-stock',] %}
<article{{ attributes.addClass(classes) }}>
{# Badges #} <div class="product-card__badges"> {% if is_on_sale %} <span class="badge badge--danger">-{{ discount_percent }}%</span> {% endif %} {% if node.isPromoted() %} <span class="badge badge--primary">Nouveau</span> {% endif %} </div>
{# Image #} <a href="{{ url }}" class="product-card__image-link"> {% if content.field_images.0 %} {{ content.field_images.0 }} {% else %} <img src="/themes/custom/tailstore/images/placeholder.jpg" alt="Image non disponible"> {% endif %} </a>
{# Actions rapides #} <div class="product-card__quick-actions"> <button class="quick-action quick-action--wishlist" title="Ajouter aux favoris"> ♡ </button> <button class="quick-action quick-action--view" title="Aperçu rapide"> 👁 </button> </div>
<div class="product-card__info">
{# Marque #} {% if node.field_brand.entity %} <span class="product-card__brand"> {{ node.field_brand.entity.label }} </span> {% endif %}
{# Titre #} <h3 class="product-card__title"> <a href="{{ url }}">{{ label }}</a> </h3>
{# Prix #} <div class="product-card__price"> {% if is_on_sale and old_price %} <span class="price--old">{{ old_price }}</span> <span class="price--current price--sale">{{ formatted_price }}</span> {% else %} <span class="price--current">{{ formatted_price }}</span> {% endif %} </div>
{# Couleurs disponibles #} {% if node.field_colors|length > 0 %} <div class="product-card__colors"> {% for color in node.field_colors|slice(0, 4) %} <span class="color-dot" style="background-color: {{ color.entity.field_color_code.value }}" title="{{ color.entity.label }}" ></span> {% endfor %} {% if node.field_colors|length > 4 %} <span class="color-more">+{{ node.field_colors|length - 4 }}</span> {% endif %} </div> {% endif %}
</div>
{# Bouton ajout panier #} <button class="product-card__add-to-cart btn btn--primary btn--full" data-product-id="{{ node.id }}" {% if not in_stock %}disabled{% endif %} > {% if in_stock %} Ajouter au panier {% else %} Indisponible {% endif %} </button>
</article>🔧 Filtres Twig utiles
Section intitulée « 🔧 Filtres Twig utiles »| Filtre | Description | Exemple |
|---|---|---|
|t | Traduction | {{ 'Hello'|t }} |
|raw | HTML non échappé | {{ html_content|raw }} |
|escape | Échapper le HTML | {{ user_input|escape }} |
|length | Longueur | {{ items|length }} |
|first / |last | Premier/dernier | {{ items|first }} |
|slice(0, 5) | Sous-ensemble | {{ items|slice(0, 5) }} |
|join(', ') | Concaténer | {{ tags|join(', ') }} |
|default('N/A') | Valeur par défaut | {{ value|default('N/A') }} |
|clean_class | Classe CSS valide | {{ type|clean_class }} |
|without('field_x') | Exclure un champ | {{ content|without('field_images') }} |
|render | Rendre un élément | {% if content.field_x|render %} |
|number_format | Formater un nombre | {{ price|number_format(2, ',', ' ') }} |
🔄 Fonctions Twig Drupal
Section intitulée « 🔄 Fonctions Twig Drupal »| Fonction | Description | Exemple |
|---|---|---|
url(route) | Générer une URL | {{ url('entity.node.canonical', {'node': node.id}) }} |
path(route) | Chemin sans domaine | {{ path('user.page') }} |
link(text, url) | Créer un lien | {{ link('Cliquez', url) }} |
file_url(uri) | URL d’un fichier | {{ file_url(node.field_image.entity.fileuri) }} |
attach_library() | Charger une librairie | {{ attach_library('tailstore/slider') }} |
create_attribute() | Créer des attributs | {% set attr = create_attribute() %} |
🎯 Exercices pratiques
Section intitulée « 🎯 Exercices pratiques »Exercice 1 : Créer un template page personnalisé
Section intitulée « Exercice 1 : Créer un template page personnalisé »Objectif : Surcharger le template de page pour ajouter une structure HTML personnalisée.
-
Localisez le template de base :
Fenêtre de terminal # Trouver le template page.html.twig du thème de basefind web/core/themes -name "page.html.twig" -
Copiez-le dans votre thème :
Fenêtre de terminal mkdir -p themes/custom/tailstore/templates/layoutcp web/core/themes/olivero/templates/layout/page.html.twig \themes/custom/tailstore/templates/layout/page.html.twig -
Modifiez le template pour ajouter une classe personnalisée :
<div class="layout-container tailstore-layout">{# Votre structure personnalisée #}</div> -
Videz le cache :
Fenêtre de terminal drush cr -
Inspectez le HTML (F12) : la classe
tailstore-layoutdoit apparaître
Validation : ✅ La classe personnalisée est visible dans l’inspecteur
Exercice 2 : Créer un template de produit simple
Section intitulée « Exercice 2 : Créer un template de produit simple »Objectif : Créer un template basique pour afficher un produit avec titre et prix.
-
Créez le fichier :
Fenêtre de terminal touch themes/custom/tailstore/templates/node/node--product.html.twig -
Ajoutez ce code minimal :
<article class="product"><h2>{{ label }}</h2>{% if content.field_price|render %}<div class="price">{{ content.field_price }}</div>{% endif %}{{ content|without('field_price') }}</article> -
Videz le cache et visitez une page produit
Validation : ✅ Le produit s’affiche avec le nouveau template
Exercice 3 : Utiliser les filtres Twig
Section intitulée « Exercice 3 : Utiliser les filtres Twig »Objectif : Manipuler des données avec les filtres Twig.
Tâches :
-
Limiter la description :
{# Afficher seulement les 100 premiers caractères #}{{ content.field_description|render|striptags|slice(0, 100) }}... -
Formater un prix :
{# Dans tailstore.theme, préparez formatted_price #}{# Puis dans le template : #}<span class="price">{{ formatted_price }}</span> -
Afficher une liste de catégories :
{% if node.field_categories %}<div class="categories">{% for category in node.field_categories %}<span class="badge">{{ category.entity.label }}</span>{% endfor %}</div>{% endif %}
Validation : ✅ Les filtres fonctionnent et transforment les données correctement
Exercice 4 : Ajouter des conditions
Section intitulée « Exercice 4 : Ajouter des conditions »Objectif : Afficher du contenu conditionnel selon le contexte.
{# Badge "Nouveau" si publié il y a moins de 7 jours #}{% set created_date = node.getCreatedTime() %}{% set current_date = "now"|date('U') %}{% set days_old = (current_date - created_date) / 86400 %}
{% if days_old < 7 %} <span class="badge badge--new">Nouveau</span>{% endif %}
{# Badge "Promo" si prix barré existe #}{% if content.field_old_price|render %} <span class="badge badge--sale">Promo</span>{% endif %}
{# Afficher stock #}{% if in_stock %} <span class="stock stock--available">En stock</span>{% else %} <span class="stock stock--unavailable">Rupture de stock</span>{% endif %}Validation : ✅ Les badges s’affichent selon les conditions
🧠 Bonnes pratiques essentielles
Section intitulée « 🧠 Bonnes pratiques essentielles »🔒 Sécurité Twig
Section intitulée « 🔒 Sécurité Twig »Échappement automatique
Twig échappe automatiquement toutes les variables pour prévenir les attaques XSS.
{# ✅ Sécurisé par défaut #}{{ user_input }} {# Échappe automatiquement < > " ' & #}
{# ⚠️ Dangereux - À éviter #}{{ user_input|raw }} {# Pas d'échappement, risque XSS #}
{# ✅ Si vous devez afficher du HTML, utilisez les fonctions Drupal #}{{ content.field_body }} {# Drupal gère l'échappement intelligent #}⚡ Performance
Section intitulée « ⚡ Performance »Vérifier l’existence avant le rendu
{# ❌ Mauvais - Rend même si vide, génère du HTML inutile #}{% if content.field_images %} {{ content.field_images }}{% endif %}
{# ✅ Bon - Vérifie vraiment si le champ a du contenu #}{% if content.field_images|render %} {{ content.field_images }}{% endif %}
{# ✅ Encore mieux - Vérifie en PHP (preprocessing) #}{% if has_images %} {{ content.field_images }}{% endif %}Limiter les boucles
{# ❌ Mauvais - Affiche potentiellement des milliers d'items #}{% for item in items %} {{ item }}{% endfor %}
{# ✅ Bon - Limite à 10 items #}{% for item in items|slice(0, 10) %} {{ item }}{% endfor %}♿ Accessibilité
Section intitulée « ♿ Accessibilité »Landmarks HTML5 et ARIA
{# ✅ Utiliser les balises sémantiques HTML5 #}<header role="banner"> {{ page.header }}</header>
<nav role="navigation" aria-label="{{ 'Navigation principale'|t }}"> {{ page.primary_menu }}</nav>
<main role="main" id="main-content" tabindex="-1"> <a id="main-content"></a> {{ page.content }}</main>
<aside role="complementary" aria-label="{{ 'Filtres'|t }}"> {{ page.sidebar }}</aside>
<footer role="contentinfo"> {{ page.footer }}</footer>Skip link obligatoire
<a href="#main-content" class="visually-hidden focusable skip-link"> {{ 'Aller au contenu principal'|t }}</a>Images accessibles
{# ❌ Mauvais - Pas d'attribut alt #}<img src="{{ image_url }}">
{# ✅ Bon - Alt descriptif #}<img src="{{ image_url }}" alt="{{ node.label }}">
{# ✅ Image décorative #}<img src="{{ icon_url }}" alt="" role="presentation">📋 Conventions de nommage
Section intitulée « 📋 Conventions de nommage »-
Classes CSS : Utilisez BEM ou une convention cohérente
<div class="product-card"><h3 class="product-card__title">{{ label }}</h3><div class="product-card__price">{{ price }}</div></div> -
Variables Twig : snake_case
{% set is_featured = node.field_featured.value %}{% set product_classes = ['product', 'product--' ~ view_mode] %} -
Toujours commenter :
{# Badge promo si prix barré existe #}{% if old_price %}<span class="badge badge--sale">-{{ discount }}%</span>{% endif %}
🔧 Débogage
Section intitulée « 🔧 Débogage »Afficher les variables disponibles
{# En mode debug uniquement #}{{ dump() }} {# Toutes les variables #}{{ dump(node) }} {# Variable spécifique #}{{ dump(node.field_price) }} {# Champ spécifique #}Inspecter les suggestions de templates
Avec le débogage Twig activé, inspectez le HTML source (Ctrl+U) pour voir :
- Les suggestions de noms de fichiers
- Le template actuellement utilisé
- Les variables disponibles
📝 Checklist qualité template
Section intitulée « 📝 Checklist qualité template »Avant de valider un template :
- Échappement automatique préservé (pas de
|rawinutile) - Vérifications
|renderpour les champs optionnels - Balises sémantiques HTML5 (header, nav, main, aside, footer)
- Attributs ARIA appropriés
- Classes CSS cohérentes (BEM ou convention choisie)
- Commentaires Twig pour expliquer la logique
- Pas de
dump()oublié - Responsive (mobile-first avec Tailwind)
- Images avec attribut
alt - Skip link pour navigation clavier
✅ Checklist de validation
Section intitulée « ✅ Checklist de validation »Templates créés et fonctionnels
Section intitulée « Templates créés et fonctionnels »| Template | Emplacement | Validation | Statut |
|---|---|---|---|
page.html.twig | templates/layout/ | Inspecter la structure HTML | ⬜ |
node--product.html.twig | templates/node/ | Visiter une page produit | ⬜ |
node--product--card.html.twig | templates/node/ | Voir la grille de produits | ⬜ |
block--system-branding-block.html.twig | templates/block/ | Vérifier le logo | ⬜ |
Vérifications techniques
Section intitulée « Vérifications techniques »-
Débogage Twig activé
Fenêtre de terminal # Vérifier la configgrep -A3 "twig.config" sites/default/services.ymlAttendu :
debug: true,auto_reload: true,cache: false -
Templates détectés par Drupal
Fenêtre de terminal # Vider le cachedrush cr# Vérifier les suggestions dans le HTML source (Ctrl+U)# Chercher "THEME DEBUG" dans le code source -
Pas d’erreurs dans les logs
Fenêtre de terminal drush watchdog:show --severity=Error --count=20Attendu : Aucune erreur Twig
-
Templates compilés
Fenêtre de terminal ls -la sites/default/files/php/twig/Attendu : Fichiers .php générés pour chaque template
Tests fonctionnels
Section intitulée « Tests fonctionnels »- La structure HTML de la page est personnalisée
- Les produits s’affichent avec le bon template (mode full)
- Les cartes produits utilisent le template card
- Les badges (nouveau, promo) s’affichent correctement
- Les conditions (stock, prix) fonctionnent
- Les boucles (couleurs, tailles) affichent les bonnes données
- Pas de code PHP visible dans le HTML
- Responsive fonctionne (tester mobile F12)
Commande récapitulative
Section intitulée « Commande récapitulative »# Script de validation completecho "=== Vérification templates Twig ==="
# 1. Config Twig debugif grep -q "debug: true" sites/default/services.yml; then echo "✅ Debug Twig activé"else echo "❌ Debug Twig désactivé"fi
# 2. Templates présentsecho "\n📁 Templates trouvés :"find themes/custom/tailstore/templates -name "*.twig" -type f
# 3. Cache Twigecho "\n🗂️ Templates compilés :"ls -1 sites/default/files/php/twig/*.php | wc -lecho "fichiers compilés"
# 4. Erreurs récentesecho "\n⚠️ Erreurs récentes :"drush watchdog:show --severity=Error --count=5
echo "\n✅ Vérification terminée"Temps estimé total : 2-3 heures pour créer et tester tous les templates
� Erreurs courantes et solutions
Section intitulée « � Erreurs courantes et solutions »Erreur 1 : Template non détecté
Section intitulée « Erreur 1 : Template non détecté »Symptôme : Modifications du template non visibles
Causes possibles :
- Cache non vidé
- Nom de fichier incorrect
- Mauvais emplacement
Solution :
# 1. Vider le cachedrush cr
# 2. Vérifier le nom exact suggéré# Activer debug Twig et inspecter le HTML source (Ctrl+U)# Chercher "FILE NAME SUGGESTIONS"
# 3. Vérifier l'emplacementls -la themes/custom/tailstore/templates/node/node--product.html.twigErreur 2 : Variable undefined
Section intitulée « Erreur 2 : Variable undefined »Symptôme : Warning: Undefined variable
Cause : Variable non préprocessée ou mal nommée
Solution :
{# ❌ Mauvais - Erreur si la variable n'existe pas #}{{ my_custom_var }}
{# ✅ Bon - Vérifie l'existence #}{% if my_custom_var is defined %} {{ my_custom_var }}{% endif %}
{# ✅ Ou avec valeur par défaut #}{{ my_custom_var|default('Valeur par défaut') }}Erreur 3 : Champ vide génère du HTML
Section intitulée « Erreur 3 : Champ vide génère du HTML »Symptôme : Balises vides dans le HTML (<div></div>)
Cause : Condition sur le champ sans vérifier le rendu
Solution :
{# ❌ Mauvais - Génère des balises vides #}{% if content.field_description %} <div class="description"> {{ content.field_description }} </div>{% endif %}
{# ✅ Bon - Vérifie vraiment le contenu #}{% if content.field_description|render %} <div class="description"> {{ content.field_description }} </div>{% endif %}Erreur 4 : Boucle infinie ou erreur de syntaxe
Section intitulée « Erreur 4 : Boucle infinie ou erreur de syntaxe »Symptôme : Page blanche, timeout, ou erreur Twig
Solution :
# 1. Consulter les logs Drupaldrush watchdog:show --severity=Error
# 2. Consulter les logs PHPtail -f sites/default/files/php-errors.log
# 3. Désactiver temporairement le templatemv themes/custom/tailstore/templates/node/node--product.html.twig \ themes/custom/tailstore/templates/node/node--product.html.twig.bak
drush crErreur 5 : Styles CSS non appliqués
Section intitulée « Erreur 5 : Styles CSS non appliqués »Symptôme : Classes présentes mais pas de style
Cause : Problème de chargement CSS, pas de problème Twig
Solution :
# Vérifier que la librairie CSS est chargée# F12 → Network → Filtrer CSS# Vérifier que tailstore.css est chargé (statut 200)
# Vider le cache CSSdrush cc css-jsdrush cr📚 Ressources complémentaires
Section intitulée « 📚 Ressources complémentaires »Documentation officielle
Section intitulée « Documentation officielle »- Twig Documentation - Documentation complète Twig
- Drupal Twig - Twig dans Drupal
- Twig Filters - Liste des filtres
- Twig Functions - Fonctions disponibles
Outils utiles
Section intitulée « Outils utiles »- Extension VS Code : Twig Language 2 (autocomplétion, coloration)
- Module Drupal : Devel (dump, Kint pour debug)
- Module Drupal : Twig Tweak (fonctions Twig supplémentaires)
Exemples de code
Section intitulée « Exemples de code »- Drupal Core Templates - Templates Olivero
- Twig Recipes - Recettes pratiques
🔜 Prochaine étape
Section intitulée « 🔜 Prochaine étape »Les templates sont en place ! Passons aux Assets (CSS/JS) pour styliser tout ça avec Tailwind CSS.