Framework PHP + Project hôte
Documentation globale v1.0.0 (janv. 2026)
Résumé de l’architecture, l’arborescence, la config et le fonctionnement global du framework natora-core et du projet hôte.
Vue d’ensemble Arborescences Configuration Cycle requête Routing & i18n Views (Smarty) JSON & validations Sécurité Modules Pages CMS Mails Logs Dev/Deploy Rôles & permissions Admin (roles/permissions)
1) Vue d’ensemble
Objectif
natora-core fournit le socle commun : Kernel, Container, Router, Request/Response, i18n, Smarty, sécurité, mailer, logging.
Project contient les routes/pages/features spécifiques au site, surcharge certains services (ex: mailer SMTP), et ajoute ses templates et traductions.
Principes de design (v1.0.0)
- Contrats simples
validations JSON uniques, comportements prédictibles. - Locale canonique
URL préfixée /fr, /en, etc (canonicalisation dans le Router). - Sécurité centralisée
CSP (nonce), headers, CSRF, throttling. - Surcharge propre
le projet remplace/étend via le Container, sans “fork” du core.
Composants majeurs
| Composant | Rôle | Notes |
|---|---|---|
| Kernel | Boot + pipeline middleware + dispatch route + réponse | Applique headers sécurité, gère mode prod/dev |
| Container | DI : services & factories | Core + Project registrars, surcharges possibles côté Project |
| Router | Routes + canonicalisation locale | Les contrôleurs retournent des paths neutres (/profile), le Router applique la locale |
| I18n | Résolution langue + dictionnaires | Pages : common.php + {page}.php. Mails : mails.php |
| View (Smarty 5) | Rendu templates pages & emails | Globals garantis : APP, CURRENT_YEAR, dictionnaire i18n (TXT_*) |
| Security | Auth, CSRF, remember-me, throttling, anti-spam | Remember-me selector/validator, throttles login + pwreset, contact spam guard |
| Envoi activation / reset / contact | PHPMailer, templates HTML+TXT | |
| Logging | PSR-3 + access log | Unifié : logger.core et logger.project avec préfixes |
2) Arborescences (core + project)
2.1. natora-core (package Composer)
vendor/ natora/ natora-core/ src/ Kernel.php Container/... Routing/... Http/... View/... I18n/... Security/... Mail/... Database/... Modules/... config/ app.php paths.php security.php logging.php mail.php form.php view.php i18n/ fr/ common.php auth.php profile.php admin.php mails.php en/ ... storage/ templates/ layouts/ pages/ mails/
2.2. Project (app hôte)
project/ public/ index.php assets/ css/ js/ img/ app/ Controllers/ ... Mail/ PhpMailerUserMailer.php (surcharge core possible) Services/ ... config/ app.php paths.php security.php logging.php mail.php form.php view.php (optionnel) *.dev.php / *.prod.php / *.local.php i18n/ fr/ common.php admin.php mails.php en/ ... storage/ templates/ layouts/ pages/ mails/ logs/ app.log vendor/ autoload.php ...
Surcharge (principe) : le project peut remplacer un service core via le Container (ex : user_mailer, provider permissions DB, extensions profile, etc.).
3) Configuration
La config est chargée au boot (core + project). Les clés sont accessibles via le service config (ex : $cfg->get('mail.driver')).
3.1. Fichiers de config (références)
| Fichier | Contenu | Exemples de clés |
|---|---|---|
| config/app.php | Nom app, env, debug, base_url, timezone | app.name, app.env, app.debug |
| config/paths.php | Chemins FS + URLs publiques | paths.storage, paths.templates, paths.assets_base_url |
| config/security.php | CSP, headers, HSTS, cookies session | security.csp.mode, security.csp.directives |
| config/logging.php | Driver log + fichier + niveaux | logging.driver, logging.file |
| config/mail.php | Mail driver, SMTP, from/reply-to | mail.driver, mail.from.email, mail.reply_to.email |
| config/form.php | Limites input + règles UX | form.contact.subject_max, form.auth.password_min |
| config/view.php | Smarty dirs/cache + mapping pages | view.templates_dir, view.compile_dir |
3.2. Convention “core + project”
- Le core fournit des valeurs par défaut raisonnables.
- Le project peut surcharger via ses fichiers config (mêmes clés, priorité project).
- Les environnements (dev/prod) peuvent être gérés par variantes *.dev.php/*.prod.php ou une couche d’override (selon ton loader).
Globals View (toujours assignés) : APP, AUTH (pages), dictionnaire i18n (TXT_*).
4) Cycle global d’une requête
HTTP Request ↓ public/index.php (front controller) - charge autoload - charge config core + project - instancie Kernel + Container - boot (logger, i18n, view, security) ↓ Kernel::handle(Request) - request_id - middleware access log (début) - middleware i18n (résout locale) - middlewares route (csrf/auth/perm...) - router dispatch → controller ↓ Controller - lit Request (GET/POST/JSON) - appelle services (repos, auth, mailer, etc.) - retourne Response (HTML ou JSON) ↓ Kernel::send(Response) - headers sécurité (+ CSP si activée) - access log (fin) ↓ HTTP Response
Point clé : la canonicalisation locale est dans le Router (pas dans les contrôleurs). Les contrôleurs utilisent des chemins neutres (/login, /profile), le Router ajoute la locale.
5) Routing & i18n
5.1. Locale canonique dans l’URL
- URLs attendues : /fr, /fr/profile, /en/login…
- Si la locale est absente ou invalide : redirection vers la locale par défaut (ex: /fr).
- La langue active est ensuite disponible via $i18n->lang().
5.2. Dictionnaires i18n
Pages
Chargement systématique :
i18n/{lang}/common.php + i18n/{lang}/{page}.php
“page” est défini explicitement par le contrôleur (recommandé) ou par convention.
Mails
Chargement dédié :
i18n/{lang}/mails.php
Évite d’importer des trads “page” inutiles dans les emails.
5.3. URL generation
- Les contrôleurs renvoient des chemins neutres (/profile).
- Le Router applique la locale (et sait produire des URLs absolues si nécessaire pour les mails).
6) Views (Smarty 5)
6.1. Organisation templates
storage/templates/ layouts/ base.tpl pages/ home.tpl auth/login.tpl profile/view.tpl admin/pages_list.tpl admin/pages_edit.tpl ... mails/ layout.html.tpl layout.txt.tpl activation.html.tpl activation.txt.tpl password_reset.html.tpl password_reset.txt.tpl contact.html.tpl contact.txt.tpl
6.2. Variables globales (à garantir)
| Variable | Type | Usage |
|---|---|---|
| APP | array | {$APP.name}, {$APP.env}, etc. |
| CURRENT_YEAR | int | Footer : {$CURRENT_YEAR} (si tu le calcules côté PHP) |
| AUTH | array|null | Utilisateur courant (pages) |
| TXT_* | string | Traductions injectées dans Smarty (ex : {$TXT_LOGIN}) |
7) JSON & Validation
Objectif
Validation centralisée : InputValidator (codes techniques par champ) + DefaultFormErrors (mapping scope → TXT_*).
L’API renvoie uniquement des TXT_... pour que le front décide de l’affichage (toasts, inline, etc.).
Contrat JSON (référence)
{ "ok": false, "errors": { "email": "TXT_ERR_EMAIL_INVALID", "password": "TXT_ERR_PASSWORD_TOO_SHORT" } }
Erreurs validation renvoyées en HTTP 200 (contrat homogène).
7.1. InputValidator
InputValidator est fluide et enregistre la première erreur par champ. Il ne dépend ni d’i18n ni HTTP.
Codes d’erreur (validator-only)
| Code | Signification | Exemple |
|---|---|---|
| EMPTY | Requis vide | required('email', $email) |
| TOO_SHORT | Trop court | minLen('password', $pw, 10) |
| TOO_LONG | Trop long | maxLen('username', $u, 30) |
| INVALID | Format invalide | pattern('slug', $slug, '/.../') |
| ALREADY_USED | Unicité (métier) | error('email', ALREADY_USED) |
| MISMATCH | Non identique | same('password_confirm', $a, $b) |
| EMAIL_INVALID | Email invalide | email('email', $email) |
Règles importantes
- First error wins : si un champ a déjà une erreur, les suivantes sont ignorées.
- required() gère le vide ; minLen()/maxLen() n’ajoutent rien sur une chaîne vide (évite double erreur).
- InputValidator ne renvoie pas de TXT_ : uniquement des codes techniques.
7.2. DefaultFormErrors (mapping UX)
DefaultFormErrors transforme les codes techniques en clés i18n (TXT_...) selon un scope.
Scopes (exemples)
- auth.register, auth.login, auth.forgot, auth.reset
- profile.email_change, profile.password_change, etc.
- contact.send
8) Sécurité
8.1. CSP + nonce
- CSP configurée via config/security.php (enforce / report-only / off).
- Le Kernel applique la CSP et les autres headers (XFO, XCTO, Referrer-Policy, etc.).
- Éviter les handlers inline (ex:) → JS externe.
8.2. CSRF
- Token CSRF injecté dans les formulaires.
- Middleware CSRF sur routes POST.
8.3. Auth + remember-me
- Session sécurisée + cookie httpOnly/secure/sameSite.
- Remember-me sécurisé (selector + validator), révocation au logout.
8.4. Throttling & anti-spam
- LoginThrottleService (login + pwreset)
- ContactSpamGuard : honeypot, timing gate, throttle, liens, profanity
- UX : honeypot → répondre OK silencieusement
9) Modules
Objectif
Activer/désactiver des blocs fonctionnels (auth, admin, analytics…) de manière déclarative, sans toucher au Kernel.
Chaque module peut enregistrer services, routes, et dépendances.
Note (alignée avec ton fix)
- En admin “Modules”, ne pas utiliser de fallback “defaultEnabled()” en vue : afficher le vrai état (fallback neutre).
- Typique : enabled = $ms->enabled($slug, false) dans la vue admin.
- core peut être verrouillé (toujours ON) si tu l’assumes.
9.1. Déclaration (config)
// config/modules.php return [ 'core' => true, 'i18n' => true, 'auth' => true, 'admin' => true, 'profile' => true, 'analytics' => false, ];
9.2. Structure d’un module
src/Modules/Pages/ PagesModule.php services.php routes.php
Rappel : si services.php ou routes.php existe, il doit retourner un callable, sinon boot fail-fast.
10) Pages CMS
Objectif
Publier des pages “contenu” (multi-lang) sans créer un contrôleur par page, avec une UI admin.
Le CMS est DB-first : tables cms_pages + cms_page_translations.
Conventions clés
- Slug : unique par page (ex: about)
- Traductions : 1 ligne par langue (title/content/publish)
- Publish par langue : is_published + published_at sur la traduction
- Param admin : utiliser page_lang (pas lang, réservé au router)
10.1. Modèle DB (résumé)
| Table | Rôle | Champs clés |
|---|---|---|
| cms_pages | Page logique | id, slug, (meta éventuelles) |
| cms_page_translations | Contenu par langue | page_id, lang, title, content, is_published, published_at, updated_at |
10.2. Admin (gestion)
- /admin/pages : liste, recherche, quick-nav par page/lang.
- /admin/pages/{slug} : édition du contenu ; changement de langue via dropdown.
- Publish toggle par traduction (langue), via page_lang.
- Auto-slug à la création (dérivé du titre).
10.3. Permissions (admin)
| Permission | Effet |
|---|---|
| admin.pages.view | Accès liste/édition |
| admin.pages.add | Créer une page |
| admin.pages.edit | Modifier title/content |
| admin.pages.publish | Publier/dépublier une traduction |
| admin.pages.delete | Supprimer une page |
| admin.pages.sync | Optionnel : sync/cron selon implémentation |
UX : désactiver “Publier/Supprimer” si la page ou la traduction n’existe pas encore (création au save).
11) Mails
11.1. Flux supportés
- Activation compte : envoi + resend (non-enumeration + throttle)
- Password reset : génération lien + envoi
- Contact : envoi au support / admin
11.2. i18n mails
i18n/{lang}/mails.php
11.3. Templates mails
storage/templates/mails/ layout.html.tpl layout.txt.tpl activation.html.tpl activation.txt.tpl password_reset.html.tpl password_reset.txt.tpl ...
11.4. PHPMailer
- From verrouillé (alignement SPF/DKIM) pour éviter les rejets.
- Reply-To optionnel pour permettre réponses vers l’adresse utilisateur.
12) Logging
12.1. Loggers unifiés
- logger.core : messages framework (préfixe [core])
- logger.project : messages projet (préfixe [project])
12.2. Access log middleware
[core] method=GET path=/fr/profile status=200 ms=12 rid=abc123
13) Dev / Deploy
13.1. Front controller
- Le serveur web pointe sur project/public (DocumentRoot).
- Les assets sont servis depuis public/assets (pas via PHP).
13.2. Réécriture (Apache / Nginx)
# Principe - servir fichiers/dirs existants - sinon réécrire vers /index.php
13.3. Environnements
- dev : debug activé, logs verbeux, mailer log/sandbox.
- prod : debug off, logs dans storage/logs, HSTS seulement sur HTTPS.
14) Rôles & permissions
Objectif
Contrôle d’accès basé sur rôles (hiérarchie) + permissions (clés type admin.users.view).
Règle de priorité
- deny utilisateur > grant utilisateur > rôle
- Permission inconnue ou désactivée (PER_IS_ENABLED=0) → refus par défaut
14.1. i18n des labels de permissions
Le label est dérivé de PER_KEY (pas stocké en TXT_* en DB).
PER_KEY = "admin.users.view" Label key = "TXT_PERM_ADMIN_USERS_VIEW"
15) Admin — gestion des rôles & permissions
15.1. Dictionnaire i18n admin
Les pages /admin chargent un dictionnaire dédié : i18n/{lang}/admin.php.
15.2. Pages
| Page | But | Actions |
|---|---|---|
| /admin/permissions | Catalogue permissions | search, create, toggle enabled, update |
| /admin/roles | Catalogue rôles | list, create |
| /admin/roles/{id} | Édition rôle | update role, assign permissions, delete (si permis) |
Annexes
A) Nommage & conventions
- Indentation code : 2 espaces.
- Tables DB : minuscule. Champs/PK/FK/index : MAJ avec préfixes.
- Templates : layout via {extends}, pas de display() direct côté web.
B) Points d’extension côté Project
- Surcharger user_mailer (SMTP), provider permissions DB, services spécifiques.
- Ajouter routes/pages, assets, templates.
- Conserver le contrat JSON de validation et la locale canonique.
© 2026 natora.net — Framework PHP + Project (doc interne)