Construire un CMS maison en PHP
Plutôt que d'installer WordPress ou Drupal pour un simple site CV, j'ai choisi de construire mon propre CMS en PHP 8 : un petit framework MVC maison et un back-office sur mesure. Ce site — celui que vous lisez — en est le résultat. Voici comment il est conçu.
Pourquoi un CMS maison ?
Un CMS « tout fait » apporte des centaines de fonctionnalités… dont on n'utilise que 5 %. En partant de zéro, on obtient un code léger, compris de bout en bout et sans dette technique inutile. C'est aussi un excellent terrain pour maîtriser des fondamentaux : routage, requêtes préparées, sessions, sécurité. L'objectif n'est pas de réinventer Symfony, mais d'avoir juste ce qu'il faut, parfaitement adapté au besoin.
Une architecture en couches
Le projet suit le patron MVC (Modèle — Vue — Contrôleur), organisé en trois familles : un cœur réutilisable, la logique applicative, et la présentation.
Un point d'entrée unique
Toutes les requêtes passent par public/index.php (le front controller). Un fichier .htaccess réécrit les URLs vers ce fichier, qui démarre l'application : session, routeur, gestion des erreurs.
// public/index.php
require dirname(__DIR__) . '/vendor/autoload.php';
(new App\Core\App())->run();
Le routeur
Les routes sont déclarées dans un tableau et associent méthode HTTP + chemin à un couple [Contrôleur, action]. Les segments dynamiques (comme {slug}) sont transformés en expressions régulières.
['GET', '/blog/{slug}', [BlogController::class, 'show']],
['POST', '/contact', [ContactController::class, 'send']],
Au moment de la requête, le routeur cherche la première route qui correspond, instancie le contrôleur et appelle l'action avec les paramètres extraits de l'URL.
La couche base de données
L'accès aux données repose sur PDO, avec une règle non négociable : toute valeur passe par une requête préparée. Aucune donnée utilisateur n'est concaténée dans une chaîne SQL — c'est la première barrière contre les injections.
public function all(string $sql, array $params = []): array
{
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
Par-dessus, un petit modèle de base fournit le CRUD générique (créer, lire, mettre à jour, supprimer) et chaque entité (Article, Projet, Compétence…) en hérite.
Les vues avec Twig
La présentation est gérée par Twig : les contrôleurs ne produisent jamais de HTML directement, ils transmettent des données à un gabarit. Twig échappe automatiquement les variables, ce qui protège contre les failles XSS, et permet l'héritage de gabarits (un base.twig commun, des pages qui l'étendent).
Le back-office sur mesure
C'est le cœur du « CMS » : une zone /admin protégée par authentification, d'où l'on gère tout le contenu — sections, projets, compétences, parcours, articles de blog, messages et paramètres. Chaque type de contenu dispose d'un CRUD complet, avec éditeur de texte riche et upload d'images sécurisé.
Le principe : tout ce qui est visible sur le site doit être modifiable depuis l'administration, sans toucher au code.
La sécurité, dès la conception
- Requêtes préparées partout (anti-injection SQL) ;
- Jeton CSRF vérifié sur chaque formulaire POST ;
- Mots de passe hachés avec
password_hash(bcrypt) et session régénérée à la connexion ; - Upload contrôlé : type MIME réel vérifié, extensions limitées, taille plafonnée ;
- Échappement automatique des sorties via Twig (anti-XSS).
Le modèle de données
Les tables restent simples et explicites. La relation la plus intéressante : une section peut être marquée « blog » et contenir alors plusieurs articles — exactement le mécanisme qui publie le billet que vous lisez.
En résumé
Construire un CMS maison, ce n'est pas réécrire un mastodonte : c'est assembler quelques briques bien comprises — front controller, routeur, couche données préparée, vues Twig, back-office — et garder la maîtrise totale du résultat. Le code reste petit, rapide, et fait exactement ce dont on a besoin. Pour un portfolio, c'est idéal ; et c'est aussi la meilleure façon d'apprendre ce qui se cache vraiment sous le capot des frameworks.