🏆
Blog CMS — Symfony 7 + API Platform + JWT
Un CMS Blog complet et production-ready : API REST authentifiée par JWT, interface d'administration, upload d'images, notifications async et déploiement Docker.
Symfony 7.2
Doctrine 3
Twig 3
API Platform 3
LexikJWT
Docker
Spécifications fonctionnelles
🌐 Front public
- Page d'accueil — articles récents
- Listing paginé + filtres par tag/catégorie
- Page article avec commentaires
- Recherche full-text
🔐 Auth & profil
- Inscription / connexion / déconnexion
- Profil utilisateur éditable
- JWT pour l'API REST
- Remember me 7 jours
✏️ Admin & édition
- Dashboard admin (statistiques)
- CRUD articles + upload image
- Gestion catégories et tags
- Modération des commentaires
📡 API REST
- API Platform 3 — CRUD complet
- Filtres, pagination, sérialisation par groupes
- Auth JWT stateless
- Swagger UI documentée
Stack technique
{
"php": "^8.3",
"symfony/framework-bundle": "7.2.*",
"doctrine/orm": "^3.0",
"twig/extra-bundle": "*",
"api-platform/core": "^3.3",
"lexik/jwt-authentication-bundle": "^3.0",
"symfony/messenger": "^7.2",
"symfony/mailer": "^7.2",
"symfony/maker-bundle": "*",
"phpunit/phpunit": "^11.0",
"symfony/browser-kit": "*",
"symfony/css-selector": "*"
}
Schéma de base de données
-- users
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(180) UNIQUE NOT NULL,
roles JSON NOT NULL,
password VARCHAR(255) NOT NULL,
name VARCHAR(100) NOT NULL,
bio TEXT,
avatar VARCHAR(255),
created_at TIMESTAMP NOT NULL
);
-- categories
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
slug VARCHAR(110) UNIQUE NOT NULL,
color VARCHAR(7) DEFAULT '#0A6EB4'
);
-- articles
CREATE TABLE articles (
id SERIAL PRIMARY KEY,
author_id INT REFERENCES users(id),
category_id INT REFERENCES categories(id),
title VARCHAR(255) NOT NULL,
slug VARCHAR(270) UNIQUE NOT NULL,
excerpt VARCHAR(500),
body TEXT NOT NULL,
cover_image VARCHAR(255),
published BOOLEAN DEFAULT false,
published_at TIMESTAMP,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP
);
-- tags + pivot
CREATE TABLE tags (id SERIAL PRIMARY KEY, name VARCHAR(50) UNIQUE NOT NULL, slug VARCHAR(60) UNIQUE NOT NULL);
CREATE TABLE article_tags (article_id INT REFERENCES articles(id), tag_id INT REFERENCES tags(id), PRIMARY KEY(article_id, tag_id));
-- comments
CREATE TABLE comments (
id SERIAL PRIMARY KEY,
article_id INT REFERENCES articles(id) ON DELETE CASCADE,
author_id INT REFERENCES users(id),
body TEXT NOT NULL,
approved BOOLEAN DEFAULT false,
created_at TIMESTAMP NOT NULL
);
API Endpoints
# Authentification
POST /api/login → JWT token
POST /api/register → créer un compte
GET /api/me → profil courant
# Articles
GET /api/articles → liste (paginée, filtrable)
POST /api/articles → créer [ROLE_USER]
GET /api/articles/{id} → lire
PATCH /api/articles/{id} → modifier [auteur]
DELETE /api/articles/{id} → supprimer [auteur/admin]
POST /api/articles/{id}/publish → publier [ROLE_EDITOR]
# Catégories & Tags
GET /api/categories → liste
GET /api/tags → liste
# Commentaires
GET /api/articles/{id}/comments → liste
POST /api/articles/{id}/comments → créer [ROLE_USER]
DELETE /api/comments/{id} → supprimer [auteur/admin]
Structure du projet
blog-cms-symfony/
├── src/
│ ├── Controller/
│ │ ├── BlogController.php
│ │ ├── Admin/ArticleController.php
│ │ └── SecurityController.php
│ ├── Entity/
│ │ ├── User.php, Article.php, Category.php, Tag.php, Comment.php
│ ├── Repository/
│ │ └── ArticleRepository.php (search, paginate, findPublished…)
│ ├── Form/
│ │ └── ArticleType.php, CommentType.php, RegistrationType.php
│ ├── Security/
│ │ └── Voter/ArticleVoter.php
│ ├── Service/
│ │ └── ArticleService.php (publish, archive)
│ ├── Event/
│ │ └── ArticlePublishedEvent.php
│ ├── EventListener/
│ │ └── SendNotificationListener.php
│ ├── Message/
│ │ └── SendEmailMessage.php
│ ├── MessageHandler/
│ │ └── SendEmailMessageHandler.php
│ ├── State/
│ │ └── ArticleProcessor.php (slug auto)
│ └── Twig/
│ └── AppExtension.php (excerpt, timeago)
├── templates/
│ ├── base.html.twig
│ ├── blog/index.html.twig, show.html.twig
│ ├── admin/article/index.html.twig, form.html.twig
│ └── security/login.html.twig, register.html.twig
├── tests/
│ ├── Controller/BlogControllerTest.php
│ └── Api/ArticleApiTest.php
├── docker/
│ ├── nginx/default.conf
│ └── php/opcache.ini
├── Dockerfile
├── docker-compose.yml
└── .github/workflows/ci.yml
Docker Setup
# Démarrer l'environnement complet
docker compose up -d
# Installer les dépendances
docker compose exec php composer install
# Migrations + fixtures
docker compose exec php php bin/console doctrine:migrations:migrate --no-interaction
docker compose exec php php bin/console doctrine:fixtures:load --no-interaction
# Générer les clés JWT
docker compose exec php php bin/console lexik:jwt:generate-keypair
# Accéder à l'application
# Web: http://localhost:8080
# API: http://localhost:8080/api
# Swagger: http://localhost:8080/api/docs
# Worker Messenger
docker compose exec php php bin/console messenger:consume async -vv
Étapes de réalisation
1
Initialiser le projet Symfony 7
composer create-project, .env, BDD PostgreSQL
2
Entités + migrations
User, Article, Category, Tag, Comment — make:entity + make:migration
3
Authentification
make:user, make:security:form-login, inscription, JWT
4
Interface blog publique
Routes, controllers, templates Twig, pagination
5
Admin CRUD
ArticleType, upload image, Voters, zone admin protégée
6
API Platform
#[ApiResource], groupes, filtres, StateProcessor slug
7
Messenger + Events
SendEmailMessage async, ArticlePublishedEvent, commande cleanup
8
Docker + CI/CD
Dockerfile multi-stage, docker-compose, GitHub Actions
🎓 Valider la formation
🏆 QCM Final Symfony →