Aller au contenu
Nibiru docsv0.9.2

Modules

Construire des modules Nibiru — le deuxième M dans MMVC. Caractéristiques, plug-ins, interfaces, paramètres, observateurs.

Stable Reading time ~ 5 min Edit on GitHub

Un module est une unité de domaine autonome. Il possède sa propre configuration, ses plugins (services), ses traits (méthodes réutilisables), ses interfaces et éventuellement une tranche MVC de son propre. Les modules sont la façon dont Nibiru évite le problème du fat-controller sans un conteneur de services.

Anatomie botanique d'un module Nibiru, dessiné comme les pétales en couches d'un lotus — pétales extérieurs, pétales moyens, pétales intérieurs, couche de sépales, réceptacle.
Un module, par couche.
application/module/<name>/
├── <name>.php # main class (implements IModule, optionally SplSubject)
├── interfaces/ # contracts for plugins / external consumers
│ └── <name>.php
├── plugins/ # stateless services usable from controllers
│ ├── <thing>.php
│ └── <other>.php
├── settings/ # auto-discovered .ini files
│ ├── <name>.ini
│ └── <name>.production.ini
└── traits/ # reusable method groups
└── <name>.php

Le Registry parcourt application/module/ au démarrage, découvre chaque fichier settings/*.ini du module, analyse la section correspondant au nom du module (en majuscules) et le met en cache pour une recherche via Registry::getInstance()->loadModuleConfigByName('users').

<?php
namespace Nibiru\Module\Users;
use Nibiru\Module as ModuleAdapter;
use Nibiru\Interfaces\IModule;
use Nibiru\Registry;
class Users extends ModuleAdapter implements IModule, \SplSubject
{
use Traits\Users;
const CONFIG_MODULE_NAME = 'users';
protected static \stdClass $usersRegistry;
protected \SplObjectStorage $observers;
public function __construct() {
$this->setUsersRegistry();
$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); }
}
protected function setUsersRegistry(): void {
self::$usersRegistry = Registry::getInstance()
->loadModuleConfigByName(self::CONFIG_MODULE_NAME);
}
}

L’interface IModule est délibérément un marqueur — le comportement réel se trouve dans vos traits et vos plugins.

Un plugin est une classe que les contrôleurs peuvent instancier pour accéder aux fonctionnalités du module :

application/module/users/plugins/user.php
<?php
namespace Nibiru\Module\Users\Plugin;
use Nibiru\Module\Users\Users;
use Nibiru\Pdo;
class User extends Users
{
public function isAuthorized(): bool {
return isset($_SESSION['auth']['user_id']);
}
public function checkForStandardUser(): bool {
return $this->isAuthorized()
&& ($_SESSION['auth']['role'] ?? '') === 'standard';
}
}

Dans un contrôleur :

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

Les plugins héritent de la classe du module, donc ils partagent accès au registre, aux paramètres et à la machine observateur.

Un trait contient des corps de méthodes réutilisables que la classe du module souhaite. Modèle courant : usines de formulaires.

application/module/users/traits/users.php
<?php
namespace Nibiru\Module\Users\Traits;
use Nibiru\Form;
trait Users
{
public function loginForm(): string {
Form::create();
Form::addOpenDiv(['class' => 'form-group']);
Form::addInputTypeText([
'class' => 'form-control', 'name' => 'login',
'placeholder' => 'Username',
]);
Form::addCloseDiv();
Form::addOpenDiv(['class' => 'form-group']);
Form::addInputTypePassword([
'class' => 'form-control', 'name' => 'password',
'placeholder' => 'Password',
]);
Form::addCloseDiv();
return Form::addForm([
'method' => 'POST',
'action' => '/login',
'name' => 'loginForm',
]);
}
}

La classe principale Users l’intègre via use Traits\Users;.

Chaque module peut contenir ses propres fichiers INI. Le Registre analyse chaque fichier *.ini dans le dossier settings/ et recherche une section nommée d’après le module (en majuscules) :

; application/module/users/settings/users.ini
[USERS]
session.lifetime = 7200
password.min.length = 12
allowed.roles[] = "admin"
allowed.roles[] = "editor"
allowed.roles[] = "standard"

Lisez-le à partir d’ailleurs :

$cfg = \Nibiru\Registry::getInstance()->loadModuleConfigByName('users');
$cfg->session_lifetime; / 7200
$cfg->password_min_length; / 12
$cfg->allowed_roles; / [admin, editor, standard]

Superpositions d’environnement : un fichier nommé users.production.ini est préféré par rapport à users.ini lorsque APPLICATION_ENV=production.

Les modules implémentant SplSubject peuvent diffuser des événements aux observateurs attachés sans être couplés à eux. À partir de l’exemple, le module analytics informe tout suivi attaché à chaque affichage de page :

// in a controller
$analytics = new \Nibiru\Module\Analytics\Analytics();
$analytics->attach(new \Nibiru\Module\Analytics\Plugin\Matomo());
$analytics->attach(new \Nibiru\Module\Analytics\Plugin\Plausible());
$analytics->trackPageView(); / internally calls notify()

Chaque observateur reçoit l’instance d’analytics via sa méthode update($subject) et extrait les données d’événement dont il a besoin.

Depuis les applications de démonstration :

  • auth (TPMS) — gestion des sessions, connexion par code QR, accès basé sur les rôles. Implémente SplSubject afin que les événements de connexion/déconnexion puissent être diffusés aux modules d’enregistrement et de conformité.
  • cms (prod.maschinen-stockert.de) — magasin de contenu cléé par chemin du contrôleur + langue. Permet aux non-développeurs de mettre à jour le contenu du site.
  • graph_mail (TPMS) — enveloppe de l’API Microsoft Graph pour les e-mails transactionnels.
  • pdfgenerator (prod.maschinen-stockert.de) — génère des catalogues de machines à partir de modèles pilotés par la base de données.
  • machineryscout — gestion de l’index Elasticsearch avec des traits pour l’indexation, la requête et le re-indexage.
  • assetmanager — pipeline CSS/JS centralisé, utilisé pour basculer les thèmes selon la langue.
  • analytics — suivi basé sur les observateurs diffusant (Matomo, etc.).

Enregistrement du module : comment le framework trouve votre module

Section intitulée « Enregistrement du module : comment le framework trouve votre module »

Un dossier de module sur le disque est nécessaire mais non suffisant. Le framework doit également savoir le charger — c’est fait avec trois tableaux de positions dans votre configuration [AUTOLOADER] :

; application/settings/config/settings.development.ini
[AUTOLOADER]
iface.pos[] = "users" ; load application/module/users/interfaces/
iface.pos[] = "billing" ; load application/module/billing/interfaces/
trait.pos[] = "users" ; load application/module/users/traits/
trait.pos[] = "billing"
class.pos[] = "users" ; load application/module/users/users.php
class.pos[] = "billing"
class.plugin.pos[] = "" ; reserved

Les noms sont des noms de dossiers en minuscules, exactement tels qu’ils apparaissent sous application/module/. Le chargeur automatique du framework (Auto::loader()) (appelé par le Dispatcher) parcourt chaque module dans l’ordre, en chargeant ses fichiers.

Les classes de plugin se trouvent dans l’espace de noms pluriel Plugins:

application/module/billing/plugins/invoice.php
namespace Nibiru\Module\Billing\Plugins; / plural
class Invoice extends \Nibiru\Module\Billing\Billing { /* ... */ }

Ne correspondez pas à l’espace de noms et vous obtiendrez des manquements d’autoload. Le squelette CLI (./nibiru -m billing) génère l’espace de noms correct pour vous.

Vous ne vous inscrivez pas les fichiers INI de votre module — le Registry les découvre automatiquement en parcourant application/module/<name>/settings/*.ini après que [AUTOLOADER] a chargé la classe du module. Chaque section [<MODULE>] (en majuscules) d’un INI devient disponible comme :

$cfg = \Nibiru\Registry::getInstance()->loadModuleConfigByName('billing');
$cfg->invoice_prefix; / [BILLING] invoice.prefix property

Le Registre préfère <module>.<env>.ini (par exemple billing.production.ini) lorsque APPLICATION_ENV correspond.

Si votre module a des tables de base de données, déposez les fichiers SQL dans application/settings/config/database/, numérotés après la plage existante. La convention utilisée par le module IA :

200-ai_rag_collection.sql
201-ai_rag_chunk.sql
202-ai_conversation.sql
203-ai_message.sql

Exécutez avec ./nibiru -mi local. Le framework génère automatiquement des modèles dans application/model/<table>.php lorsque [GENERATOR] database = true est défini, prêts à l’utilisation via \Nibiru\Pdo::fetchAll(…).

Fenêtre de terminal
./nibiru -m billing

Échafaudages :

application/module/billing/
├── billing.php
├── interfaces/billing.php
├── plugins/
├── settings/billing.ini
└── traits/

Ajoutez l’option -g (./nibiru -m billing -g) lorsque vous souhaitez que les accroches de journalisation Graylog soient pré-connectées dans le squelette.

Ne promouvez pas chaque contrôleur en un module. Le seuil est : avez-vous au moins un trait, une extension et une clé INI ? Si oui, il mérite son propre dossier de module. Sinon, un fichier de traits partagés ou un petit assistant dans application/lib/ suffit amplement.