Módulos
Construyendo módulos de Nibiru — el segundo M en MMVC. Traits, plugins, interfaces, configuraciones, observadores.
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
Sección titulada «Anatomía»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>.phpEl 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').
Un módulo mínimo
Sección titulada «Un módulo mínimo»<?phpnamespace 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.
Plugins: servicios sin estado
Sección titulada «Plugins: servicios sin estado»Un plugin es una clase que los controladores pueden instanciar para acceder a la funcionalidad del módulo:
<?phpnamespace 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.
Características: métodos reutilizables
Sección titulada «Características: métodos reutilizables»Un trait lleva cuerpos de métodos reutilizables que la clase del módulo quiere. Patrón común: fábricas de formularios.
<?phpnamespace 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;.
Configuración del módulo (INI)
Sección titulada «Configuración del módulo (INI)»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 = 7200password.min.length = 12allowed.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.
El patrón observador
Sección titulada «El patrón observador»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.
Módulos de producción reales
Sección titulada «Módulos de producción reales»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. ImplementaSplSubjectpara 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.phpclass.pos[] = "billing"class.plugin.pos[] = "" ; reservedLos 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:
namespace Nibiru\Module\Billing\Plugins; / ← pluralclass 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.
Descubrimiento de configuraciones
Sección titulada «Descubrimiento de configuraciones»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 → propertyEl Registro prefiere <módulo>.<entorno>.ini (por ejemplo, facturación.producción.ini) cuando APPLICATION_ENV coincide.
Migraciones
Sección titulada «Migraciones»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.sql201-ai_rag_chunk.sql202-ai_conversation.sql203-ai_message.sqlEjecute 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(…).
Generando un módulo
Sección titulada «Generando un módulo»./nibiru -m billingEsqueletos:
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.
Cuando no hacer un módulo
Sección titulada «Cuando no hacer un módulo»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.