🏆

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 →