Votre premier pas dans le framework PHP professionnel
Installation • Structure • Routing • Contrôleurs • Twig • Console
Formation DWWM - 2026
Comprendre avant de coder
Symfony est un framework PHP professionnel pour créer des applications web robustes et évolutives.
PHP pur (sans framework) :
C'est comme cuisiner chez soi. Tu as tous les ingrédients (fonctions PHP), mais tu dois TOUT faire toi-même : préparer, cuire, dresser l'assiette, faire la vaisselle. C'est long et fatiguant.
Symfony (framework) :
C'est comme travailler dans un restaurant étoilé bien organisé. Tu as une cuisine équipée, des ustensiles de qualité, des recettes éprouvées, et une équipe qui gère les tâches répétitives. Tu te concentres sur l'essentiel : créer de bons plats !
En résumé : Symfony te fournit des outils, des structures et des bonnes pratiques pour que tu ne réinventes pas la roue. Tu codes plus vite, mieux et de manière plus sûre.
| Avantage | Explication |
|---|---|
| 🏗️ Structure claire | MVC (Modèle-Vue-Contrôleur) : code organisé et maintenable |
| ⚡ Gain de temps | Composants prêts à l'emploi (routing, formulaires, sécurité...) |
| 🔒 Sécurité | Protection contre XSS, CSRF, injections SQL par défaut |
| 📚 Documentation | Documentation complète et communauté active |
| 🧩 Réutilisable | Bundles et composants réutilisables |
| 💼 Professionnel | Utilisé par de grandes entreprises (BlaBlaCar, Spotify...) |
<?php
// Route manuelle
if ($_GET['page'] == 'home') {
// Code home
}
// Requête BDD manuelle
$conn = mysqli_connect(...);
$result = mysqli_query($conn,
"SELECT * FROM users");
// Template manuel
echo "<h1>Titre</h1>";
// Sécurité manuelle
$input = htmlspecialchars($_POST['data']);
?>
😓 Beaucoup de code répétitif
<?php
// Route automatique
#[Route('/home', name: 'home')]
// ORM Doctrine
$users = $userRepository->findAll();
// Template Twig
return $this->render('home.html.twig');
// Sécurité intégrée
// CSRF, XSS, etc. gérés automatiquement
?>
😎 Code propre et sécurisé
Mettre en place votre environnement
Avant d'installer Symfony, vous avez besoin de :
| Outil | Version minimum | Pourquoi ? |
|---|---|---|
| PHP | 8.1+ | Le langage de base |
| Composer | 2.0+ | Gestionnaire de dépendances PHP |
| Symfony CLI | Dernière | Outil en ligne de commande (optionnel mais recommandé) |
# Vérifier PHP
php -v
# Vérifier Composer
composer -V
# Vérifier Symfony CLI
symfony -V
Composer est le gestionnaire de paquets de PHP (comme npm pour JavaScript).
Imagine que tu veux lire 50 livres pour un projet. Au lieu d'aller les chercher toi-même dans toute la bibliothèque, tu demandes au bibliothécaire (Composer) : "J'ai besoin de ces livres". Il va les chercher pour toi, les organise, et s'assure que tu as les bonnes versions !
# Télécharger depuis https://getcomposer.org/
# Installer Composer-Setup.exe
# Redémarrer le terminal
# Via le script officiel
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php composer-setup.php
sudo mv composer.phar /usr/local/bin/composer
Il existe 2 façons de créer un projet Symfony :
# Application web complète
composer create-project \
symfony/skeleton mon-projet
cd mon-projet
# Plus rapide et moderne
symfony new mon-projet
cd mon-projet
Symfony inclut un serveur web de développement intégré.
# Démarrer le serveur
symfony serve
# Le serveur démarre sur https://127.0.0.1:8000
# Ou http://localhost:8000
# Démarrer avec PHP built-in server
php -S localhost:8000 -t public/
✅ Ça marche ! Si tu vois la page d'accueil Symfony dans ton navigateur, ton installation est réussie !
Comprendre l'organisation des fichiers
mon-projet/
├── bin/ # Scripts exécutables
│ └── console # Console Symfony (commandes)
├── config/ # 📋 Configuration de l'app
│ ├── packages/ # Config des bundles
│ ├── routes.yaml # Définition des routes
│ └── services.yaml # Services et dépendances
├── public/ # 🌐 Racine web publique
│ └── index.php # Point d'entrée de l'app
├── src/ # 💻 TON CODE ICI !
│ ├── Controller/ # Contrôleurs
│ ├── Entity/ # Entités (modèles)
│ └── Repository/ # Accès base de données
├── templates/ # 🎨 Vues Twig
├── var/ # Fichiers temporaires
│ ├── cache/ # Cache
│ └── log/ # Logs
├── vendor/ # 📦 Dépendances (ne pas modifier)
├── .env # Variables d'environnement
└── composer.json # Dépendances du projet
C'est ici que VOUS allez travailler !
Analogie : C'est votre bureau personnel. Tout ce que vous créez va ici.
Fichiers de configuration de l'application.
Analogie : Les réglages de votre voiture (vitesse max, type d'essence, etc.)
Seul dossier accessible depuis Internet. Contient CSS, JS, images et index.php.
Analogie : La vitrine de votre magasin. C'est ce que les clients voient.
Vos fichiers Twig (HTML avec des super-pouvoirs).
Analogie : Les moules à gâteaux. Ils définissent la forme de vos pages.
TOUTES les requêtes passent par ce fichier unique !
Dans une grande entreprise, quand tu appelles, tu tombes d'abord sur le standard. Le standardiste (index.php) écoute ta demande, puis te redirige vers la bonne personne (contrôleur).
Tu n'appelles JAMAIS directement un employé. Toujours le standard d'abord !
C'est le Front Controller Pattern : Un seul point d'entrée pour toute l'application = plus facile à sécuriser et à gérer !
Relier les URLs aux contrôleurs
Le routing associe une URL à un contrôleur (fonction PHP).
Imagine que ton application est une ville :
Sans routing = conducteur perdu. Avec routing = destination atteinte !
Depuis Symfony 6, on utilise les attributs PHP pour définir les routes.
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class HomeController extends AbstractController
{
#[Route('/', name: 'home')]
public function index(): Response
{
return new Response('Bienvenue sur mon site !');
}
}
On peut capturer des valeurs dynamiques dans l'URL !
<?php
#[Route('/user/{id}', name: 'user_show')]
public function show(int $id): Response
{
return new Response("Affichage de l'utilisateur n°" . $id);
}
// URL : /user/123
// Affiche : Affichage de l'utilisateur n°123
#[Route('/blog/{category}/{slug}', name: 'blog_post')]
public function post(string $category, string $slug): Response
{
return new Response(
"Catégorie: $category, Article: $slug"
);
}
// URL : /blog/tech/symfony-tutorial
// Affiche : Catégorie: tech, Article: symfony-tutorial
On peut forcer un paramètre à respecter un format !
#[Route('/user/{id}', name: 'user_show', requirements: ['id' => '\d+'])]
public function show(int $id): Response
{
// $id sera TOUJOURS un nombre
}
// ✅ /user/123 → Fonctionne
// ❌ /user/abc → Erreur 404
| Contrainte | Regex | Exemple |
|---|---|---|
| Nombres uniquement | \d+ |
123, 456 |
| Lettres uniquement | [a-zA-Z]+ |
hello, World |
| Slug (URL friendly) | [a-z0-9-]+ |
mon-article-123 |
| Format date | \d{4}-\d{2}-\d{2} |
2026-01-28 |
Le cerveau de votre application
Un contrôleur est une classe PHP qui contient la logique métier de votre application.
Dans un orchestre :
Le chef lit la partition, dit aux musiciens quoi jouer et quand, puis coordonne le tout pour créer la symphonie (réponse HTTP).
php bin/console make:controller HomeController
Cette commande crée automatiquement :
src/Controller/HomeController.phptemplates/home/index.html.twig<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class HomeController extends AbstractController
{
#[Route('/home', name: 'app_home')]
public function index(): Response
{
return $this->render('home/index.html.twig', [
'controller_name' => 'HomeController',
]);
}
}
return new Response('Hello World!');
return $this->render('home/index.html.twig', [
'titre' => 'Ma page d\'accueil',
'date' => new DateTime()
]);
return $this->json([
'message' => 'Success',
'data' => ['id' => 123, 'name' => 'Marie']
]);
// Redirection vers une URL
return $this->redirectToRoute('home');
// Redirection vers une URL externe
return $this->redirect('https://google.com');
return $this->file('/path/to/document.pdf');
Symfony injecte automatiquement l'objet Request !
use Symfony\Component\HttpFoundation\Request;
#[Route('/search', name: 'search')]
public function search(Request $request): Response
{
// URL: /search?q=symfony
$query = $request->query->get('q');
return new Response("Recherche pour: " . $query);
}
#[Route('/login', name: 'login', methods: ['POST'])]
public function login(Request $request): Response
{
$email = $request->request->get('email');
$password = $request->request->get('password');
// Traiter le login...
}
$userAgent = $request->headers->get('User-Agent');
$acceptLanguage = $request->headers->get('Accept-Language');
Générer du HTML dynamique
Twig est le moteur de templates de Symfony. Il permet de générer du HTML de manière élégante et sécurisée.
Imagine que tu veux peindre 100 fois le même logo sur des T-shirts :
Sans pochoir (PHP pur) : Tu dessines à la main chaque fois. Long, fatiguant, et jamais identique.
Avec pochoir (Twig) : Tu crées un pochoir une fois, puis tu l'appliques partout. Rapide, propre, et toujours parfait !
<h1><?php echo htmlspecialchars($titre); ?></h1>
<?php foreach($users as $user): ?>
<p><?php echo $user['name']; ?></p>
<?php endforeach; ?>
😓 Moche et verbeux
<h1>{{ titre }}</h1>
{% for user in users %}
<p>{{ user.name }}</p>
{% endfor %}
😎 Propre et lisible
| Syntaxe | Utilisation | Exemple |
|---|---|---|
{{ ... }} |
Afficher une variable | {{ nom }} |
{% ... %} |
Instructions (if, for...) | {% if age > 18 %} |
{# ... #} |
Commentaire | {# Ceci est un commentaire #} |
{# Afficher une variable #}
<h1>Bonjour {{ prenom }} !</h1>
{# Condition #}
{% if age >= 18 %}
<p>Vous êtes majeur</p>
{% else %}
<p>Vous êtes mineur</p>
{% endif %}
{# Boucle #}
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
Dans le contrôleur, on passe un tableau de variables :
#[Route('/blog', name: 'blog')]
public function blog(): Response
{
$articles = [
['titre' => 'Article 1', 'auteur' => 'Marie'],
['titre' => 'Article 2', 'auteur' => 'Paul'],
['titre' => 'Article 3', 'auteur' => 'Sophie']
];
return $this->render('blog/index.html.twig', [
'articles' => $articles,
'siteName' => 'Mon Blog',
'year' => 2025
]);
}
<h1>{{ siteName }}</h1>
{% for article in articles %}
<article>
<h2>{{ article.titre }}</h2>
<p>Par {{ article.auteur }}</p>
</article>
{% endfor %}
<footer>
<p>© {{ year }} {{ siteName }}</p>
</footer>
Créer un layout de base et le réutiliser !
Tu as un moule à gaufres (layout de base) avec des trous (blocks). Pour chaque gaufre (page), tu remplis les trous avec ta pâte (contenu spécifique). Le moule reste le même, mais chaque gaufre est unique !
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Mon Site{% endblock %}</title>
</head>
<body>
<header>
<h1>Mon Super Site</h1>
<nav>...</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2025 Mon Site</p>
</footer>
</body>
</html>
{% extends 'base.html.twig' %}
{% block title %}Mon Blog{% endblock %}
{% block content %}
<h2>Bienvenue sur mon blog !</h2>
{% for article in articles %}
<article>{{ article.titre }}</article>
{% endfor %}
{% endblock %}
Les filtres transforment les variables avant affichage.
| Filtre | Utilisation | Exemple |
|---|---|---|
upper |
Majuscules | {{ nom|upper }} → MARIE |
lower |
Minuscules | {{ nom|lower }} → marie |
length |
Longueur | {{ texte|length }} → 15 |
date |
Formater date | {{ date|date('d/m/Y') }} |
slice |
Extraire partie | {{ texte|slice(0, 10) }} |
default |
Valeur par défaut | {{ nom|default('Anonyme') }} |
{{ description|striptags|slice(0, 100)|upper }}
Étapes :
1. striptags → retire les balises HTML
2. slice(0, 100) → garde 100 premiers caractères
3. upper → convertit en majuscules
Votre outil en ligne de commande
La console est un outil en ligne de commande pour gérer votre application.
Un mécanicien a une boîte à outils avec des clés, tournevis, pinces... Chaque outil fait une tâche précise. La console Symfony, c'est pareil ! Des commandes pour chaque besoin : créer un contrôleur, vider le cache, lancer les tests, etc.
# Format général
php bin/console nom:commande [options] [arguments]
# Exemple
php bin/console make:controller HomeController
| Commande | Action |
|---|---|
php bin/console list |
Liste toutes les commandes disponibles |
php bin/console about |
Informations sur l'application |
php bin/console cache:clear |
Vider le cache |
php bin/console debug:router |
Lister toutes les routes |
php bin/console make:controller |
Créer un contrôleur |
php bin/console make:entity |
Créer une entité |
php bin/console doctrine:migrations:migrate |
Exécuter les migrations BDD |
php bin/console help make:controller
# Affiche :
# - Description de la commande
# - Options disponibles
# - Exemples d'utilisation
Cette commande liste toutes les routes de votre application.
php bin/console debug:router
Name Method Path Controller
-----------------------------------------------------
home ANY / App\Controller\HomeController::index
blog GET /blog App\Controller\BlogController::index
blog_show GET /blog/{id} App\Controller\BlogController::show
contact ANY /contact App\Controller\ContactController::index
php bin/console debug:router blog
# Affiche uniquement la route "blog" avec tous ses détails
Mettre en pratique
php bin/console make:controller HomeController
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class HomeController extends AbstractController
{
#[Route('/', name: 'home')]
public function index(): Response
{
return $this->render('home/index.html.twig', [
'siteName' => 'Mon Super Site',
'year' => date('Y')
]);
}
}
{% extends 'base.html.twig' %}
{% block title %}Accueil - {{ siteName }}{% endblock %}
{% block body %}
<h1>Bienvenue sur {{ siteName }} !</h1>
<p>Ceci est ma première page Symfony.</p>
<footer>
<p>© {{ year }} {{ siteName }}</p>
</footer>
{% endblock %}
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
#[Route('/blog', name: 'blog_index')]
public function index(): Response
{
// En vrai, ces données viendraient de la base de données
$articles = [
[
'id' => 1,
'titre' => 'Découvrir Symfony',
'auteur' => 'Marie Dupont',
'date' => new \DateTime('2025-01-15'),
'resume' => 'Introduction au framework Symfony...'
],
[
'id' => 2,
'titre' => 'Les bases de Twig',
'auteur' => 'Paul Martin',
'date' => new \DateTime('2025-01-20'),
'resume' => 'Apprendre à utiliser Twig...'
],
[
'id' => 3,
'titre' => 'Doctrine ORM',
'auteur' => 'Sophie Bernard',
'date' => new \DateTime('2025-01-25'),
'resume' => 'Gérer vos bases de données...'
]
];
return $this->render('blog/index.html.twig', [
'articles' => $articles
]);
}
#[Route('/blog/{id}', name: 'blog_show', requirements: ['id' => '\d+'])]
public function show(int $id): Response
{
// En vrai, on récupérerait l'article depuis la BDD
return $this->render('blog/show.html.twig', [
'articleId' => $id
]);
}
}
{% extends 'base.html.twig' %}
{% block title %}Blog{% endblock %}
{% block body %}
<h1>Articles du Blog</h1>
<div class="articles">
{% for article in articles %}
<article>
<h2>
<a href="{{ path('blog_show', {id: article.id}) }}">
{{ article.titre }}
</a>
</h2>
<p class="meta">
Par {{ article.auteur }}
le {{ article.date|date('d/m/Y') }}
</p>
<p>{{ article.resume }}</p>
</article>
{% else %}
<p>Aucun article pour le moment.</p>
{% endfor %}
</div>
{% endblock %}
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ContactController extends AbstractController
{
#[Route('/contact', name: 'contact')]
public function index(Request $request): Response
{
$message = null;
// Si le formulaire a été soumis
if ($request->isMethod('POST')) {
$nom = $request->request->get('nom');
$email = $request->request->get('email');
$sujet = $request->request->get('sujet');
$texte = $request->request->get('message');
// En vrai, on enverrait un email ici
$message = "Merci $nom, votre message a été envoyé !";
}
return $this->render('contact/index.html.twig', [
'message' => $message
]);
}
}
{% extends 'base.html.twig' %}
{% block title %}Contact{% endblock %}
{% block body %}
<h1>Contactez-nous</h1>
{% if message %}
<div class="alert success">{{ message }}</div>
{% endif %}
<form method="POST">
<div>
<label>Nom :</label>
<input type="text" name="nom" required>
</div>
<div>
<label>Email :</label>
<input type="email" name="email" required>
</div>
<div>
<label>Sujet :</label>
<input type="text" name="sujet" required>
</div>
<div>
<label>Message :</label>
<textarea name="message" required></textarea>
</div>
<button type="submit">Envoyer</button>
</form>
{% endblock %}
Récapitulons le parcours complet d'une requête HTTP dans Symfony :
| Concept | Définition | Commande/Syntaxe |
|---|---|---|
| Symfony | Framework PHP professionnel | composer create-project symfony/skeleton |
| Routing | Associer URL → Contrôleur | #[Route('/path', name: 'route_name')] |
| Contrôleur | Logique de l'application | php bin/console make:controller |
| Twig | Moteur de templates | {{ variable }}, {% for %} |
| Response | Ce qui est renvoyé au client | return new Response() |
| Console | Outil en ligne de commande | php bin/console list |
Règle d'or : Tu travailles dans src/, templates/ et config/. Le reste, tu n'y touches pas (sauf public/ pour tes assets) !
| Action | Commande |
|---|---|
| Créer un projet | symfony new mon-projet |
| Démarrer le serveur | symfony serve |
| Créer un contrôleur | php bin/console make:controller NomController |
| Lister les routes | php bin/console debug:router |
| Vider le cache | php bin/console cache:clear |
| Lister les commandes | php bin/console list |
name: 'route_name'php bin/console cache:clear résout souvent les problèmesMaintenant que tu connais les bases, voici ce qu'il faut apprendre ensuite :
| Étape | Sujet | Pourquoi ? |
|---|---|---|
| 1 | Doctrine ORM | Gérer la base de données |
| 2 | Formulaires Symfony | Créer et valider des formulaires |
| 3 | Sécurité | Authentification et autorisations |
| 4 | Services | Injection de dépendances |
| 5 | API REST | Créer des APIs modernes |
🌐 https://symfony.com/doc
La doc officielle est EXCELLENTE. Utilise-la !
🎬 https://symfonycasts.com
Tutoriels vidéo de qualité (certains gratuits)
💬 Symfony Slack
Communauté active et bienveillante
💡 Astuce : La documentation Symfony est régulièrement mise à jour et très bien écrite. En cas de doute, c'est ta meilleure amie !
Cause : Tu as oublié l'attribut #[Route] ou mal écrit l'URL
Solution : Vérifie avec php bin/console debug:router
Cause : Le fichier Twig n'existe pas ou mal orthographié
Solution : Vérifie le chemin dans templates/
Cause : Erreur PHP cachée par le cache
Solution : php bin/console cache:clear et regarde les logs dans var/log/
Cause : Mauvais namespace ou classe mal orthographiée
Solution : Vérifie le namespace et le nom de la classe
Pour bien assimiler, voici des exercices à faire :
Crée un site avec :
Utilise un layout de base et Twig pour toutes les pages.
Crée un blog avec :
Crée une calculatrice avec :
Tu sais maintenant :
✅ Installer et configurer Symfony
✅ Comprendre la structure d'un projet
✅ Créer des routes et des contrôleurs
✅ Utiliser Twig pour les templates
✅ Utiliser la console Symfony
Continue à pratiquer ! 💪
Formation DWWM - 2026