4 Parcours ExpertLeçon 1 sur 8 8 min de lecture

L'architecture d'Éclair

  1. 1. L'architecture d'Éclair
  2. 2. Développement local et tests
  3. 3. Personnaliser les gabarits du thème
  4. 4. Nginx, VPS, proxy et CDN : déployer Éclair
  5. 5. Performance d'Éclair : garder 100 sur 100
  6. 6. Migrer depuis WordPress ou Wix
  7. 7. Sécurité avancée et maintenance
  8. 8. Industrialiser Éclair en agence
Sommaire
  1. Comment une requête traverse l'architecture d'Éclair ?
  2. Le stockage à plat : la classe Store
  3. Les fichiers du dossier data/
  4. Les classes principales d'app/lib/
  5. Pourquoi le cache se vide à chaque modification ?
  6. En-têtes de sécurité et CSP
  7. Les traductions
  8. À retenir
  9. Pour aller plus loin
  10. Questions fréquentes

L'architecture d'Éclair tient en une règle : ne faire travailler PHP que lorsque c'est utile. Cette leçon suit une requête de bout en bout, du serveur web au cache statique, montre comment les données sont rangées sans base de données et quelle classe fait quoi. Tu pourras ensuite déboguer, personnaliser ou héberger Éclair en connaissant ses rouages.

Ce que tu sauras faire

Suivre une requête

Du serveur web à index.php, puis au cache ou à l'application.

Lire le dossier data/

Ce que contient chaque fichier et comment il est protégé.

Trouver la bonne classe

Savoir où agir dans app/lib/ en quelques secondes.

Comment une requête traverse l'architecture d'Éclair ?

Schéma du cycle d'une requête : réponse du cache statique ou passage par l'application Éclair
Le chemin rapide sert les pages déjà générées, tout le reste passe par App::run.

Tout part d'un constat : la plupart des visites demandent une page inchangée. L'architecture d'Éclair la renvoie donc telle quelle, sans lire un seul contenu. Voici le trajet d'une requête.

  1. Le serveur web. Sur Apache ou LiteSpeed, le .htaccess envoie à index.php toute adresse qui n'est pas un fichier existant. Il répond 404 pour app/, data/, cache/, les fichiers cachés (sauf .well-known) et tout .php autre qu'index.php, et transmet l'en-tête Authorization au connecteur MCP.
  2. Le chemin rapide d'index.php. Pour un GET ou un HEAD, index.php lit cache/runtime.php, puis cherche la page dans cache/pages/ sous le md5 du chemin. S'il la trouve, il renvoie ses en-têtes enregistrés, son ETag et X-Cache: HIT, ou un 304 Not Modified si le navigateur a déjà cette version.
  3. L'amorçage. Sinon, app/bootstrap.php vérifie la version de PHP (8.1 au moins), charge eclair-config.php s'il existe, définit ECLAIR_DATA et ECLAIR_DEBUG, enregistre le chargement automatique des classes Eclair\…, puis appelle Eclair\App::run().
  4. L'aiguillage. App::run() installe la gestion des erreurs et confie la requête au bon module (tableau ci-dessous).
  5. Le rendu. Site::handle() n'accepte que GET et HEAD, impose une seule adresse par contenu (barre finale, majuscules, index.php : redirection 301), sert les fichiers générés (robots.txt, sitemap.xml, llms.txt, flux, versions .md), puis cherche le contenu. Une page rendue avec le statut 200 est enregistrée par Cache::put().
Chemin demandéModule qui répond
/__eclair/pingtest de réécriture, preuve d'identité (Loopback)
tout chemin, avant l'installationInstaller::handle()
l'adresse de l'administration et ses sous-pagesAdmin::handle()
/_eclair/contactContact::handle(), le formulaire de contact
/_eclair/edit/<id>redirection vers l'éditeur, si connecté
/_eclair/… et /.well-known/…Mcp::handle(), muet tant que le connecteur est désactivé
tout le resteSite::handle()

La recherche, la page 404, l'aperçu de l'apparence, le mode Visuel et l'administration ne sont jamais mis en cache. Pour voir le chemin rapide à l'œuvre, interroge deux fois la même page :

# 1er appel : la page est générée par l'application, puis enregistrée
curl -sI https://exemple.fr/contact | grep -i -E 'etag|x-cache'
# ETag: "0745734655e7d523"

# 2e appel : servie par index.php depuis cache/pages/, sans charger l'application
curl -sI https://exemple.fr/contact | grep -i -E 'etag|x-cache'
# ETag: "0745734655e7d523"
# X-Cache: HIT

# Le fichier de cache porte le md5 du chemin demandé
php -r 'echo md5("/contact"), PHP_EOL;'   # 90609cfd31a32bdbd3d81843c57d356b

Le stockage à plat : la classe Store

L'architecture d'Éclair se passe de base de données : chaque donnée est un fichier data/<nom>.php qui commence par la ligne <?php exit; ?>, suivie de JSON lisible. Appelé depuis le web, il ne renvoie rien, même sur un serveur qui ignorerait les .htaccess. Voici le début d'un vrai fichier de menus :

<?php exit; ?>
{
    "main": [
        {
            "type": "content",
            "id": "af59e5ba0a",
            "label": "",
            "url": ""
        }
    ]
}

Quatre mécanismes rendent ce stockage sûr, même sur un mutualisé chargé :

Toutes les classes qui écrivent suivent ce motif, Redirects::add() par exemple :

use Eclair\Store;

$menus = Store::read('menus', []);           // lecture, avec une valeur par défaut
Store::locked(function (): void {            // lire, modifier, écrire sans course
    $list = Store::read('redirects', []);
    // … modifier $list …
    Store::write('redirects', $list);        // fichier temporaire, 0600, renommage
});

Les fichiers du dossier data/

La dernière colonne indique ce que garde la sauvegarde ZIP de la page Sécurité : secrets et données passagères en sont exclus.

Fichier ou dossierContenuSauvegarde ZIP
site.phpréglages, fusionnés avec Settings::DEFAULTSoui
users.phpcomptes, empreintes Argon2id, double authentificationoui
keys.phpsecret du site et clé IndexNow, créés à l'installationoui
index.phprésumé de chaque contenu, pour lister et router viteoui
content/<id>.phpune page, un article ou un produit, avec ses blocsoui
revisions/<id>/les 20 dernières versions de ce contenuoui
search.php, media.phptexte de recherche, registre de la médiathèqueoui
menus.php, categories.php, redirects.php, custom-css.phpmenus, catégories, redirections, CSS personnaliséoui
messages/, inbox.phpmessages du formulaire, 500 au plusoui
logs/journaux d'erreurs et de sécurité, bornés à 1 Mooui
vault.phpclés d'API chiffréesnon
ai/conversations de l'assistant, propositions, connexions MCPnon
sessions/, limits/sessions de l'administration, compteurs anti-bruteforcenon

Les mises à jour ne touchent jamais à data/, que la constante ECLAIR_DATA peut même placer hors du dossier public.

Les classes principales d'app/lib/

Toutes les classes vivent dans l'espace de noms Eclair\, un fichier par classe, sans framework ni Composer :

ClasseRôle
Appaiguillage des requêtes non servies par le cache
Req, Httplecture de la requête, réponses et redirections
Store, Settingsstockage à plat, réglages et valeurs par défaut
Content, Blockscontenus, index, révisions ; rendu des 18 blocs
Site, Themerouteur public ; gabarits et CSS en ligne
Cachepages statiques et cache/runtime.php
Seo, Analyzerdonnées structurées, sitemap, llms.txt ; scores
Security, Session, Authen-têtes, jetons anti-CSRF, sessions, connexion
Mediamédiathèque, ré-encodage et WebP
Admin et Admin…écrans et actions de l'administration
Ai, AiChat, AiTools, Mcpassistant IA, outils, connecteur MCP et OAuth
Integrity, Log, Keys, Vault, Loopbackintégrité, journaux, secrets, coffre, vérification d'adresse
I18ntraductions de l'administration

Pourquoi le cache se vide à chaque modification ?

Un titre modifié apparaît aussi dans un menu, une carte d'article, un fil d'Ariane. Plutôt que de deviner ces dépendances, l'architecture d'Éclair vide tout : Cache::purge() suit chaque enregistrement d'un contenu, des réglages, des menus, des catégories, des médias, d'un profil ou du CSS personnalisé. Il supprime les fichiers de cache/pages/ et réécrit cache/runtime.php ; chaque page se régénère à sa première visite.

<?php exit; ?>
{"on":true,"expires":0,"host":"exemple.fr","https":true}

Ce petit fichier est lu par index.php à chaque requête :

En-têtes de sécurité et CSP

Security::headers() produit trois jeux d'en-têtes : site, admin et text (sitemap, llms.txt, flux). Ils dépendent des réglages, jamais de la requête : personne ne peut empoisonner le cache avec un en-tête forgé. Ceux d'une page publique servie en HTTP, relevés sur Éclair 1.8 :

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'sha256-…'; img-src 'self' data: https:; media-src 'self' https:; frame-src https://www.youtube-nocookie.com https://player.vimeo.com; connect-src 'self'; form-action 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), browsing-topics=()
Cross-Origin-Opener-Policy: same-origin

L'empreinte sha256 est calculée par Theme::hash() sur le CSS inséré dans la page : seule cette balise <style> est autorisée, aucun script en ligne n'est permis, et le seul script public est assets/site/site.js. En HTTPS s'ajoutent upgrade-insecure-requests et, si l'option HSTS est cochée, Strict-Transport-Security. L'administration est plus stricte encore : aucun style en ligne, frame-ancestors 'none', noindex et Cache-Control: no-store, private.

Les traductions

À retenir

Pour aller plus loin

Questions fréquentes

Éclair peut-il fonctionner sans son cache statique ?

Oui : décoche Cache statique des pages dans Réglages › Avancé, ou active le mode débogage. Chaque visite génère alors la page, ce qui coûte plus de temps serveur.

Pourquoi stocker du JSON dans des fichiers .php ?

Pour que la ligne <?php exit; ?> protège chaque fichier même si le serveur ignore le .htaccess. Appelé depuis le web, il n'affiche rien, tandis qu'Éclair lit le JSON qui suit cette première ligne.

Que se passe-t-il si deux personnes enregistrent en même temps ?

Les écritures passent par Store::locked(), qui pose un verrou exclusif sur data/.lock. Les deux enregistrements s'exécutent donc l'un après l'autre, et l'ancienne version rejoint l'historique des révisions.

Peut-on placer le dossier data/ hors du dossier public ?

Oui. Déplace d'abord le dossier, puis indique son nouvel emplacement avec define('ECLAIR_DATA', '/chemin/vers/eclair-data'); dans eclair-config.php, à côté d'index.php. Le chemin rapide n'en dépend pas : il ne lit que cache/.

Toutes les leçons du parcours