Formation DWWM - Développement Web et Web Mobile
<?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; ?>
{% 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 %}
{{ variable }}
{{ user.name }}
{{ user.getName() }}
{{ user['email'] }}
{% if condition %}
{% for item in items %}
{% set variable = value %}
{% include 'template.html.twig' %}
{# Ceci est un commentaire #}
{#
Commentaire
sur plusieurs lignes
#}
// 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']
]);
{# 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
{% if user %}
Bonjour {{ user.name }}
{% elseif guest %}
Bonjour invité
{% else %}
Veuillez vous connecter
{% endif %}
{# Conditions complexes #}
{% if user and user.isActive %}
Utilisateur actif
{% endif %}
{% 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 %}
{% 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 %}
<!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>
{% 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 %}
{# 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 %}
<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 '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 %}
{{ text|upper }}
{{ text|lower }}
{{ text|title }}
{{ text|length }}
{{ text|slice(0, 100) }}
{{ date|date('d/m/Y') }}
{{ date|date('H:i') }}
{{ date|format_datetime() }}
{{ "now"|date('Y-m-d') }}
{{ array|join(', ') }}
{{ array|first }}
{{ array|last }}
{{ array|reverse }}
{{ array|sort }}
{{ price|number_format(2) }}
{{ text|nl2br }}
{{ html|striptags }}
{{ url|url_encode }}
{{ text|trim }}
{# 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' }}
{# 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') }}
{# Manipulation #}
{{ random(array) }}
{{ range(1, 10) }}
{{ cycle(['odd', 'even'], loop.index0) }}
{# Tests #}
{{ dump(variable) }}
{{ max(prices) }}
{{ min(prices) }}
{# 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}) }}
{% 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 %}
{% 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') }}
{# 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) }}
{{ 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) }}
{# 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 %}
{# 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 #}
<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 %}
{# 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
# 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>
# config/packages/translation.yaml
framework:
default_locale: fr
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
- fr
{# 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 %}
# 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'
# 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 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 %}
{# 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
// 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 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>
{# 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 %}
{# 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
# 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
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
Vous maîtrisez maintenant Twig et les templates Symfony !
Créez des vues élégantes, maintenables et performantes 🚀