Ir al contenido
Nibiru docsv0.9.2

Módulos

Construyendo módulos de Nibiru — el segundo M en MMVC. Traits, plugins, interfaces, configuraciones, observadores.

Stable Reading time ~ 5 min Edit on GitHub

Un módulo es una unidad de dominio autónoma. Proporciona su propia configuración, sus plugins (servicios), sus tratos (métodos reutilizables), sus interfaces y, opcionalmente, una porción MVC propia. Los módulos son cómo Nibiru evita el problema del controlador gordo sin un contenedor de servicios.

Anatomía botánica de un módulo Nibiru, dibujado como las pétalos en capas de una lotus — pétalos externos, pétalos medios, pétalos internos, capa de sepalo, receptáculo.
Un módulo, capa por capa.
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

El Registro recorre application/module/ al iniciar, descubre cada archivo settings/*.ini de los módulos, analiza la sección que coincide con el nombre del módulo (en mayúsculas) y lo almacena en caché para su consulta mediante 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);
}
}

La interfaz IModule está intencionalmente marcadora — el comportamiento real se encuentra en tus tratos y complementos.

Un plugin es una clase que los controladores pueden instanciar para acceder a la funcionalidad del módulo:

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';
}
}

En un controlador:

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

Los plugins heredan de la clase módulo, por lo que comparten acceso al registro, configuraciones y mecanismo de observadores.

Un trait lleva cuerpos de métodos reutilizables que la clase del módulo quiere. Patrón común: fábricas de formularios.

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 clase principal Users la incluye mediante use Traits\Users;.

Cada módulo puede llevar sus propios archivos INI. El Registro analiza cada archivo *.ini en configuraciones/ y busca una sección con el nombre del módulo (en mayúsculas):

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

Léelo desde cualquier lugar:

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

Capas de entorno: un archivo llamado users.production.ini se prefiere sobre users.ini cuando APPLICATION_ENV=production.

Módulos que implementan SplSubject pueden difundir eventos a observadores adjuntos sin acoplarse a ellos. Desde el ejemplo, el módulo analytics notifica a cualquier rastreador adjunto en cada vista de página:

// 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()

Cada observador recibe la instancia de análisis en su método update($subject) y extrae los datos del evento que le interesan.

Desde las aplicaciones de demostración:

  • auth (TPMS) — gestión de sesión, inicio de sesión con código QR, acceso basado en roles. Implementa SplSubject para que los eventos de inicio y cierre de sesión se propaguen a los módulos de registro y auditoría.
  • cms (prod.maschinen-stockert.de) — almacén de contenido claveado por ruta del controlador + idioma. Permite que no desarrolladores actualicen el texto del sitio.
  • graph_mail (TPMS) — envoltorio de la API Microsoft Graph para correos electrónicos transaccionales.
  • pdfgenerator (prod.maschinen-stockert.de) — genera catálogos de máquinas a partir de plantillas impulsadas por la base de datos.
  • machineryscout — gestión del índice de Elasticsearch con rasgos para indexación, consulta y re-indexación.
  • assetmanager — canal central CSS/JS, utilizado para intercambiar temas según el idioma.
  • analytics — rastreador impulsado por observador (Matomo, etc.).

Registro del módulo: cómo el marco encuentra su módulo

Sección titulada «Registro del módulo: cómo el marco encuentra su módulo»

Un folder de módulo en el disco es necesario pero no suficiente. El framework también tiene que saber cómo cargarlo — eso se hace con tres arreglos posicionales en tu configuración [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

Los nombres son nombres de carpetas en minúsculas, exactamente como aparecen bajo application/module/. El cargador automático del framework Auto::loader() (llamado desde el Dispatcher) recorre cada módulo en orden, requiriendo sus archivos.

Convención de espacio de nombres del plugin

Sección titulada «Convención de espacio de nombres del plugin»

Las clases de plugin residen bajo el espacio de nombres plural Plugins:

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

Desacuerda el espacio de nombres y obtendrás fallos en la carga automática. La plantilla de la CLI (./nibiru -m billing) genera el espacio de nombres correcto para ti.

No registras los archivos INI de tu módulo — el Registro los descubre automáticamente recorriendo application/module/<name>/settings/*.ini después de que [AUTOLOADER] carga la clase del módulo. Cada sección [<MODULE>] (en mayúsculas) de un INI se vuelve disponible como:

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

El Registro prefiere <módulo>.<entorno>.ini (por ejemplo, facturación.producción.ini) cuando APPLICATION_ENV coincide.

Si tu módulo tiene tablas de base de datos, coloca los archivos SQL en application/settings/config/database/ numerados después del rango existente. Convención utilizada por el módulo AI:

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

Ejecute con ./nibiru -mi local. El marco genera automáticamente modelos en application/model/<table>.php cuando se establece [GENERATOR] database = true, listos para usar a través de \Nibiru\Pdo::fetchAll(…).

Ventana de terminal
./nibiru -m billing

Esqueletos:

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

Añade el interruptor -g (./nibiru -m billing -g) cuando quieras que los ganchos de registro de Graylog estén preconfigurados en la estructura básica.

No promuevas cada controlador en un módulo. El umbral es: ¿tienes al menos una traza, un plugin y una clave INI? Si sí, merece su propia carpeta de módulo. Si no, un archivo compartido de trazas o un pequeño ayudante application/lib/ es suficiente.