Twig — le moteur de templates Symfony
🔒 Sécurisé
Échappe automatiquement toutes les variables — protection XSS par défaut
⚡ Performant
Compile en PHP pur — exécution aussi rapide que du PHP natif
🧩 Extensible
Filtres, fonctions, tags personnalisés — Symfony ajoute path(), asset(), etc.
{# Commentaire Twig — non affiché dans le HTML #}
{# 3 types de blocs #}
{{ variable }} {# affiche une variable (échappée) #}
{% tag %} {# logique : if, for, block, extends… #}
{# commentaire #} {# ignoré à la compilation #}
Syntaxe essentielle
{# Conditions #}
{% if user.isAdmin %}
<span>Admin</span>
{% elseif user.isModerator %}
<span>Modo</span>
{% else %}
<span>Membre</span>
{% endif %}
{# Opérateur ternaire #}
{{ user.name ?? 'Anonyme' }}
{{ isActive ? 'Actif' : 'Inactif' }}
{# Boucles #}
{% for article in articles %}
<li>{{ loop.index }}. {{ article.title }}</li>
{% else %}
<p>Aucun article.</p>
{% endfor %}
{# loop.index, loop.index0, loop.first, loop.last, loop.length #}
{# Définir une variable #}
{% set total = items|length %}
{% set name = 'Symfony' %}
{# Concatenation #}
{{ 'Bonjour ' ~ user.name ~ ' !' }}
Variables & tests
{# Accès aux propriétés (getters, propriétés publiques, __get) #}
{{ user.name }} {# appelle getName() ou $user->name #}
{{ user['email'] }} {# accès tableau #}
{{ article.tags.0 }} {# premier élément du tableau tags #}
{# Tests is #}
{% if articles is empty %}
{% if user is defined %}
{% if value is null %}
{% if number is odd %}
{% if number is divisible by(3) %}
{% if 'admin' in user.roles %}
{# Tests personnalisés Symfony #}
{% if form.vars.valid %}
{% if app.user %} {# utilisateur connecté #}
Variable globale app
{{ app.user }} {# utilisateur connecté (ou null) #}
{{ app.request }} {# objet Request Symfony #}
{{ app.session }} {# Session #}
{{ app.environment }} {# 'dev', 'prod' #}
{{ app.debug }} {# true en dev #}
{{ app.flashes }} {# flash messages #}
{{ app.token }} {# token de sécurité #}
Filtres & fonctions
{# Filtres courants #}
{{ name|upper }} {# MAJUSCULES #}
{{ name|lower }} {# minuscules #}
{{ name|capitalize }} {# Première lettre #}
{{ text|slice(0, 100) ~ '…' }} {# extrait #}
{{ price|number_format(2, ',', ' ') }} {# 1 234,56 #}
{{ date|date('d/m/Y H:i') }} {# 20/05/2026 10:30 #}
{{ html|raw }} {# désactive l'échappement ! #}
{{ items|length }} {# nombre d'éléments #}
{{ items|first }} {# premier élément #}
{{ items|last }} {# dernier élément #}
{{ items|sort }} {# tri #}
{{ items|join(', ') }} {# 'a, b, c' #}
{{ object|json_encode }} {# sérialisation JSON #}
{# Fonctions Twig #}
{{ range(1, 5) }} {# [1,2,3,4,5] #}
{{ max(1, 5, 3) }} {# 5 #}
{{ random(10) }} {# 0..10 #}
{# Fonctions Symfony #}
{{ path('blog_show', {slug: article.slug}) }} {# URL interne #}
{{ url('blog_show', {slug: article.slug}) }} {# URL absolue #}
{{ asset('images/logo.png') }} {# asset public #}
{{ csrf_token('delete-article') }} {# token CSRF #}
{{ is_granted('ROLE_ADMIN') }} {# droits #}
Héritage de templates
L'héritage Twig est le concept le plus puissant : un template enfant étend un parent et remplit ses blocs.
{# templates/base.html.twig — layout principal #}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Mon Site{% endblock %}</title>
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
{% endblock %}
</head>
<body>
{% block navbar %}
<nav>Navigation</nav>
{% endblock %}
<main>
{% block content %}{% endblock %}
</main>
{% block javascripts %}
<script src="{{ asset('js/app.js') }}"></script>
{% endblock %}
</body>
</html>
{# templates/blog/show.html.twig — template enfant #}
{% extends 'base.html.twig' %}
{% block title %}{{ article.title }} | Mon Blog{% endblock %}
{% block content %}
<article>
<h1>{{ article.title }}</h1>
<p>{{ article.body }}</p>
<p>{{ parent() }}</p> {# inclut le contenu du bloc parent #}
</article>
{% endblock %}
{% block javascripts %}
{{ parent() }} {# hérite du JS parent PUIS ajoute le nôtre #}
<script src="{{ asset('js/comments.js') }}"></script>
{% endblock %}
include & embed
{# include — simple inclusion, partage le contexte #}
{% include 'partials/_navbar.html.twig' %}
{% include 'partials/_card.html.twig' with { article: article } only %}
{# embed — inclusion avec override de blocs #}
{% embed 'partials/_card.html.twig' %}
{% block card_title %}Mon titre custom{% endblock %}
{% endembed %}
{# render — appel d'un controller depuis Twig (ESI / fragments) #}
{{ render(controller('App\\Controller\\WidgetController::sidebar')) }}
{{ render(path('widget_sidebar')) }}
Macros
{# templates/macros/forms.html.twig #}
{% macro input(name, value, type = 'text', placeholder = '') %}
<input
type="{{ type }}"
name="{{ name }}"
value="{{ value }}"
placeholder="{{ placeholder }}"
class="form-control"
>
{% endmacro %}
{% macro alert(message, type = 'info') %}
<div class="alert alert-{{ type }}">{{ message }}</div>
{% endmacro %}
{# Utilisation dans un autre template #}
{% from 'macros/forms.html.twig' import input, alert %}
{{ input('email', '', 'email', 'Votre email') }}
{{ alert('Enregistrement réussi', 'success') }}
Créer une extension Twig
<?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('markdown', [$this, 'parseMarkdown'], ['is_safe' => ['html']]),
];
}
public function getFunctions(): array
{
return [
new TwigFunction('github_link', [$this, 'buildGithubLink']),
];
}
public function formatPrice(float $number, string $currency = '€'): string
{
return number_format($number, 2, ',', ' ') . ' ' . $currency;
}
}
{# Utilisation #}
{{ product.price|price }} {# 1 234,56 € #}
{{ product.price|price('USD') }} {# 1 234,56 USD #}