Aller au contenu
Nibiru docsv0.9.2

Architectures (MMVC)

Comment les modules, contrôleurs, vues, modèles, le registre et le dispatcheur s'entourent mutuellement.

Stable Reading time ~ 3 min Edit on GitHub

Nibiru est MMVC : Modèle — Vue — Contrôleur — et une seconde M pour Module. Les trois premiers sont familiers ; la deuxième M donne à Nibiru son goût.

Illustration en coupe de la couche d'exécution Nibiru dessinée comme une lotus, avec chaque pétale étiqueté comme un composant système : Routeur, Contrôleur, Vue, Modèle, Module, Registre.
La couche d'exécution Nibiru, dessinée comme une lotus.
┌──────────────┐
│ Browser │
└──────┬───────┘
│ HTTP
┌──────────────┐
│ index.php │
└──────┬───────┘
│ require
┌──────────────┐
│ framework.php│ ◀── boots Config, Router, Engine, Smarty,
└──────┬───────┘ all 28 form types, DB drivers, Auth.
┌──────────────┐
│ Dispatcher │
└──────┬───────┘
┌───────────────┼─────────────────┐
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌───────────┐
│ Router │ │ Modules │ │ Auto │
└────────┘ └──────────┘ │ loader │
└───────────┘
┌─────────────────────────────┐
│ applicationController.php │
│ navigationAction() │
│ <_action>Action() │
│ pageAction() │
└──────────────┬──────────────┘
│ View::assign(...)
┌──────────────┐
│ Smarty │ templates/<ctrl>.tpl + shared/*
└──────────────┘

Un contrôleur est une classe qui étend Nibiru\Adapter\Controller. Le dispatcheur invoque une séquence fixe à chaque requête :

  1. navigationAction() — remplir les données des menus / fil d’Ariane.
  2. <_action>Action() — uniquement si ?_action=foo est défini.
  3. pageAction() — attribution finale des données au moment du rendu.

Après le retour des trois, Display::display() transmet les variables assignées à Smarty.

Fichiers Smarty .tpl. Le dispatcheur résout automatiquement <controller>.tpl; les actions imbriquées se trouvent sous templates/<controller>/<action>.tpl. Chaque variable transmise via View::assign(['x' => ...]) est disponible sous la forme {$x}.

Les modèles sont générés automatiquement à partir de votre schéma de base de données par Model::__construct(false) — une classe PHP par table. Ils étendent Nibiru\Adapter\<Driver>\Db et exposent des assistants CRUD. Vous pouvez éditer manuellement un modèle généré et désactiver le régénérateur avec [GENERATOR] database = false.

Un module regroupe ses propres traits, plugins, interfaces et paramètres. Le Registre détecte automatiquement chaque fichier de configuration settings/*.ini des modules et expose la configuration analysée via Registry::getInstance()->loadModuleConfigByName('users'). Les modules peuvent implémenter SplSubject pour le modèle observateur, permettant à d’autres parties du système d’attacher des observateurs et de réagir aux changements d’état.

5. Les singletons qui maintiennent l’univers ensemble

Section intitulée « 5. Les singletons qui maintiennent l’univers ensemble »
SingletonTâche
Config::getInstance()Lit settings.<env>.ini et fusionne les configurations de module.
Router::getInstance()Analyse l’URL en contrôleur/action/paramètres ; reconnaît les formes d’URL SEO.
Registry::getInstance()Découverte des modules + mise en cache des configurations.
Dispatcher::getInstance()Le chef d’orchestre. dispatch::run() est le battement cardiaque de votre application.
View::getInstance()Encapsule Smarty. View::assign() est la boîte aux lettres des variables de modèle globales.

Le simple MVC fonctionne jusqu’à ce que vos contrôleurs commencent à partager de la logique : des vérifications d’authentification, des fabriques de formulaires, des clients d’API tierces. Les réponses habituelles sont des services + un conteneur DI, mais c’est beaucoup de cérémonie pour un cadre de prototypage rapide.

La réponse de Nibiru : modules. Un module possède un domaine (utilisateurs, cms, analytiques, tpms-citations), exposant ses services via des contrôleurs de plug-ins que les instances peuvent instancier directement :

// In a controller
$user = new \Nibiru\Module\Users\Plugin\User();
if (!$user->isAuthorized()) {
View::forwardTo('/login');
}

Le module possède sa configuration, ses tables de base de données, ses modèles, ses formulaires — et peut être supprimé en tant qu’unité.

Certains modules implémentent SplSubject afin que d’autres parties de code puissent réagir aux événements sans couplage. À partir du showcase, le module analytics sur prod.maschinen-stockert.de fait exactement cela : n’importe quel contrôleur peut attach() un suivi (plugin Matomo) et le module d’analyse notify() à chaque affichage de page, sans que le contrôleur ne sache quels suivis existent.

class Analytics implements \SplSubject {
private \SplObjectStorage $observers;
public function __construct() { $this->observers = new \SplObjectStorage(); }
public function attach(\SplObserver $o): void { $this->observers->attach($o); }
public function detach(\SplObserver $o): void { $this->observers->detach($o); }
public function notify(): void {
foreach ($this->observers as $o) { $o->update($this); }
}
}

Ce qui n’est pas délibérément inclus dans le cadre

Section intitulée « Ce qui n’est pas délibérément inclus dans le cadre »
  • Aucun conteneur DI. Les singletons et les plugins suffisent.
  • Aucune ORM. Les modèles sont générés à partir du schéma ; les requêtes utilisent l’adaptateur Db ou le SQL brut via le pilote actif.
  • Aucune héritage de modèle via des astuces Twig/Blade. Smarty {include} est l’unité de composition.
  • Aucun bus d’événements. SplSubject/SplObserver sont en première classe.
  • Aucuns travaux d’arrière-plan de première classe. La CLI est votre planificateur — utilisez-le avec cron ou systemd timers.

Moins à apprendre, plus à livrer.