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 #}
▶ Mini-projet SF03 🧠 QCM SF03 Module 04 →