Site web professionnel

À propos de cette démo

Du MVC scolaire à l'architecture web professionnelle

Du MVC scolaire à l'architecture web professionnelle
💡 Professionnel signifie : compréhensible, testable, extensible, structure sécurisée

Développement d'un mini-framework modulaire comme base de projet à long terme

Objectif

Ce projet sert de référence architecturale pour le développement d'applications web évolutives. Il montre la transition progressive d'une structure MVC scolaire vers une architecture web professionnelle et modulaire.

Le résultat final n'est pas un projet unique, mais un mini-framework léger qui :

  • Est compréhensible, testable, extensible et structure sécurisée.
  • Sert de base stable pour n'importe quel projet web
  • Évite les refontes complètes ultérieures

«Construire le bon châssis une fois – puis échanger seulement les modules.»

Point de départ

Plusieurs projets de démonstration déjà réalisés servent de base (notamment MVC, Sessions, Login, CSS modulaire).

Ces projets sont fonctionnellement bien construits, mais reposent fortement sur des fonctions et sont principalement adaptés aux applications plus petites. Avec l'augmentation de la taille du projet, ils atteignent leurs limites en termes de structure, maintenabilité et extensibilité.

Pour contrôler cette transition, j'ai créé 2 projets, on pourrait dire en 2 phases :

  • Phase 1: Amélioration d'un projet MVC scolaire classique
  • Phase 2: Professionnalisation de mon projet de démo de connexion et passage au niveau supérieur.

Étapes

👉 Chaque étape fonctionne indépendamment
👉 Pas de moments de "tout recommencer"

ÉTAPEFOCUSOBJECTIFPHASE
1 MVC Scolaire Compréhension (point de départ)1
2 Structure de dossiers & ServiceSéparer les responsabilités & Vue d'ensemble 1
3Allégement de l'index & Flux de requêtesBootstrap & Routeur1
4 Configuration .htaccessSécurité d'accès (base) 1
5 Ajustements pour la Phase 2Modularité & Maintenabilité 2
6 CSS ModulaireMaintenabilité & Évolutivité 2
7 Classes & Navigation Structure & Testabilité2
8 Mini-FrameworkBase à long terme2

Le tableau montre la transition structurée des concepts simples vers l'architecture professionnelle.

Structure

Objectif: Séparer les responsabilités, sécurité et clarté, sans refonte

Principe: Seul public/ est accessible publiquement – tout le reste est protégé.

10_Professional_web/
 ├─ app/
 │  ├─ config/
 │  ├─ control/
 │  ├─ helper/
 │  ├─ model/
 │  ├─ router/
 │  ├─ security/
 │  ├─ service/
 │  ├─ view/
 │  └─ bootstrap.php
 │  └─ router.php
 │  └─ session.php
 ├─ doc/...
 ├─ public/          ← seul public
 │  ├─ css/
 │  └─ img/
 │  ├─ js/
 │  ├─ index.php     ← Point d'entrée unique
 ├─ sql/
 ├─ storage/
 │  ├─ logs/
 │  └─ upload/
 └─ README.md

Allégement de l'index

Problème

Dans le MVC scolaire, index.php assume trop de tâches :

  • Routeur
  • Contrôleur
  • Porte de sécurité
  • Orchestrateur de flux de travail

👉 Point unique de chaos

Objectif

index.php devient

  • Point de départ
  • aucune logique

Solution

  • Externaliser le routeur
  • Logique Bootstrap centrale
  • Préparation pour l'autochargement
  • Pipeline de requêtes claire

Pipeline de requêtes

Requêtepublic/index.phpBootstrapRouteurContrôleurServiceVue / Réponse

Bootstrap

Initialise :

  • Config
  • Session
  • Helper
  • Contrôleur & Service
  • Modèle & Vue

Routeur

Tâche :

  • URL → Contrôleur / Action
  • Extraire les paramètres
  • Traiter les erreurs proprement

Mettre en œuvre

  • Créer : app/bootstrap.php
  • Déplacer les chemins d'importation de l'index vers le nouveau bootstrap
  • Déplacer le switch($page) dans le contrôleur dans une fonction intégrée.
  • Déplacer le contenu du contrôleur dans le dossier Service et le répartir sur les nouveaux fichiers :
App/services/
 │  ├─ AuthService.php
 │  ├─ UserService.php
 │  ├─ ProfileService.php
 │  ├─ SessionService.php
 │  └─ UploadService.php
  • Créer : app/router.php
  • Déplacer la vérification de langue et de page de session.php vers router.php

Accès sécurisé avec .htaccess

Architecture à deux fichiers

Pour le contrôle d'accès, une architecture à deux fichiers avec .htaccess est utilisée. Le fichier dans le répertoire racine contrôle l'accès externe et dirige les demandes spécifiquement vers le dossier public. Le .htaccess dans le répertoire public prend en charge le routage d'application.

Cette séparation assure un concept de sécurité multicouche, des responsabilités claires et une meilleure maintenabilité.

Ajustements pour la Phase 2

La Phase 2 sert à préparer l'architecture existante de manière ciblée à la croissance, à la complexité croissante et à la gestion propre des ressources.

Répartition de la configuration

app/config/
├── config.php # Configuration principale
├── app/ # Configuration d'application
│    ├── form.config.php # Configurations de formulaire
│    └── app.config.php # Paramètres de l'app
└── domain/ # Configs spécifiques au domaine
│    ├── page.config.php # Configuration des pages
│    └── user.config.php # Config gestion des utilisateurs
└── security/ # Espace réservé pour la sécurité
  • 🎯 Chargement ciblé : Seulement les configurations nécessaires chargées
  • 📁 Meilleure organisation : Regroupement thématique
  • 🧩 Modularité : Intégration facile dans les services

CSS Modulaire

Dans l'étape suivante, le CSS modulaire a été intégré au projet. Un projet de démonstration existant a servi de base, pouvant être repris avec peu d'effort et inséré dans le répertoire app/css/. La structure existante a ensuite été légèrement adaptée et optimisée pour s'intégrer proprement dans le reste de l'architecture.

Pour intégrer professionnellement le CSS modulaire dans l'application, les styles ne sont pas livrés directement depuis la zone App. Au lieu de cela, le CSS est généré dynamiquement dans le répertoire public/css/. Ainsi, il peut être inclus dans l'en-tête comme une feuille de style classique :

<link rel="stylesheet" href="css/style.php?page=<?php echo htmlspecialchars($_SESSION[S_PAGE]); ?>">

Le fichier style.php sert de point d'entrée contrôlé. Il intègre les modules CSS internes, empêche l'accès direct aux fichiers internes et garantit que le CSS est correctement livré comme feuille de style. De plus, la mise en cache indésirable est empêchée, de sorte que les modifications prennent effet immédiatement :

// Définir une constante de sécurité pour empêcher l'accès direct aux fichiers include
define("SECURE_INCLUDE", true);

// Inclure le fichier CSS principal
require_once __DIR__ . "/../../app/css/main.css.php";

// Déclarer le fichier comme CSS et empêcher le cache
header("Content-Type: text/css; charset=UTF-8");
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Pragma: no-cache");
header("X-Content-Type-Options: nosniff");

// Déterminer la page via paramètre GET
$page = $_GET["page"] ?? "index";
if (!in_array($page, P_LIST, true)) {
    $page = "index";
}

echo generateCSS(renderCSS($page));

Cette solution combine les avantages du CSS modulaire avec une séparation claire entre la structure interne et la livraison publique et s'intègre parfaitement dans l'architecture de projet existante.

Classes

Les classes sont utilisées dans le projet là où elles apportent une valeur ajoutée claire. Cela inclut les données partagées avec leur logique, les dépendances plus complexes ainsi que les domaines où la testabilité et l'extensibilité sont importantes. Toutes les fonctions ne sont pas nécessairement converties en classe.

Les vues restent de simples templates, et les helpers simples peuvent délibérément rester des fonctions.

Session

La logique de session a été entièrement externalisée dans sa propre classe. Pour cela, la classe cSession a été créée, qui prend en charge le démarrage et l'initialisation de base de la session de manière centrale :

class cSession {
    public static function start(): void {

        session_start();

        self::initDefaults();
        self::initCsrfToken();
        self::sanitizeSession();
    }
    // et ces fonctions créées :
    initDefaults
    initCsrfToken
    sanitizeSession
    requireAccess
    isGuest
    getRole
    getUserId
    increaseLoginAttempts
    isRateLimited

La fonction check_user_access a été retirée de la classe Session et déplacée vers AuthService. La vérification des rôles au début de la session a également été externalisée vers la zone d'authentification, regroupée dans la fonction auth_init et appelée de manière centrale dans le Bootstrap.

En outre, un AdminService a été introduit. La vérification de l'utilisateur pouvant être modifié a été retirée de la session et encapsulée dans sa propre fonction set_edit_user_id, qui est également exécutée au démarrage dans le Bootstrap.

Dans le cadre de la restructuration, les appels de fonctions ont été uniformisés, par exemple de handleSessionTimer() à cSession::increaseLoginAttempts() ainsi que de isSessionLimited() à isRateLimited(). Enfin, les fichiers obsolètes ont été supprimés, concrètement session.php dans le répertoire app ainsi que session_service.php dans la zone Service.

Routeur

Le routeur a également été externalisé dans sa propre classe. La classe cRouter prend en charge l'initialisation centrale du routage et s'occupe de la reconnaissance de la langue et de la page :

class cRouter {
    public static function init(): void {
        self::resolveLanguage();
        self::resolvePage();
    }
}

La logique précédente de reconnaissance de la langue et de la page a été retirée du contexte Session et transférée dans deux fonctions clairement séparées au sein du routeur. Ainsi, la responsabilité du routage incombe entièrement au routeur, tandis que la classe Session se concentre sur ses tâches propres.

Base de données

Pour la connexion à la base de données, une classe générale a été créée, qui regroupe les tâches récurrentes et simplifie l'interaction avec la base de données. La classe cDatabase encapsule l'établissement de la connexion ainsi que les opérations de requête et d'exécution fréquemment nécessaires :

class cDatabase {
    private static ?PDO $dbh = null;
    public static function getConnection(): PDO {…}
    public static function close(): void {…}
    public static function fetchAll(…): array {…    }
    public static function fetchOne(…): array|false {…}
    public static function execute(…): bool {…}
}

Grâce à cette abstraction centrale, les accès à la base de données sont mis en œuvre de manière uniforme et les fonctions existantes sont simplifiées, car la logique récurrente n'est plus implémentée qu'une seule fois. Des approches de cohérence supplémentaires améliorent la maintenabilité, réduisent les sources d'erreurs et forment une base stable pour les extensions.

Navigation

Problème

Navigation différente selon :

  • Page
  • Rôle utilisateur
  • Statut de connexion

Solution

  • Pour chaque page, les rôles autorisés sont définis
  • La visibilité des pages est contrôlée par des règles centrales
  • Une fonction de filtrage remplace les if dispersés

Mise en œuvre

Tout d'abord, un fichier Security-Config central a été créé. Celui-ci définit une table de constantes avec les règles de sécurité et les autorisations d'accès pour chaque page. Les vérifications précédentes, spécifiques à chaque page, dans le contrôleur ont été supprimées et remplacées par une vérification unique et centrale au début du traitement de la page. Ainsi, la logique devient plus claire et les vérifications redondantes disparaissent.

Ensuite, un fichier de routes indépendant et appelable a été introduit. Ce fichier renvoie un tableau qui remplace la logique switch précédente du contrôleur de routes. Les pages sont définies comme clés et renvoient chacune à des fonctions anonymes contenant les appels handle* et renderView* précédents. Après l'adaptation, le switch original a pu être complètement supprimé et le nouveau routage a été chargé, testé et exécuté avec succès :

// Charger les routes (Route → Callable)
$routes = require CONTROL_DIR . 'routes.php';

// Fallback si la route n'existe pas
if (!isset($routes[$page]) || !is_callable($routes[$page])) {
    errorLog(__FILE__, __LINE__, __FUNCTION__, "Unknown route: {$page}");
    reload_page(P_HOME);
}

// Exécuter la route (Action du contrôleur)
$routes[$page]($labels);

En outre, une fonction a été implémentée qui, sur la base des règles de sécurité définies, détermine automatiquement une liste de pages pouvant établir un lien vers une page spécifique. Cette liste sert de base à la navigation. La logique de navigation existante a ensuite été révisée, simplifiée et utilise désormais exclusivement la liste de navigation générée dynamiquement.

Mini-Framework

En combinant les concepts précédemment introduits, un mini-framework léger émerge du projet MVC original.
Bootstrap, Routeur, règles de sécurité centrales, couche Service, configuration modulaire ainsi que des responsabilités clairement séparées forment ensemble une architecture stable.

Ce mini-framework ne remplace pas complètement les grands frameworks, mais sert de base contrôlée et compréhensible pour vos propres projets web.
Il permet des extensions propres, évite les ruptures structurelles ultérieures et permet de nouvelles applications sur une base éprouvée.

👉 Le projet ne se termine pas avec une application unique, mais avec une architecture réutilisable.