---
title: "Module"
description: "Erstellen von Nibiru-Modulen — das zweite M in MMVC. Merkmale, Plugins, Schnittstellen, Einstellungen, Beobachter."
---

Ein **Modul** ist eine selbstständige Domänen-Einheit. Es besitzt seine Konfiguration, seine Plugins (Dienste), seine Traits (wiederverwendbare Methoden), seine Schnittstellen und optional einen eigenen MVC-Slice. Module sind die Art und Weise, wie Nibiru das *fat-controller*-Problem ohne einen Dienstcontainer vermeidet.

<figure>
  <img src="/img/modules-anatomy.png" alt="Botanische Anatomie eines Nibiru-Moduls, als geschichtete Blätter einer Lotus-Zehe gezeichnet — äußere Blätter, mittlere Blätter, innere Blätter, Kelchschicht, Empfänger." />
  <figcaption>Ein Modul, Schicht für Schicht.</figcaption>
</figure>

## Anatomie
```
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
```
Der `Registry` durchsucht `application/module/` beim Starten, entdeckt die jeweiligen Module in `settings/*.ini`, analysiert den Abschnitt, der mit dem Modulnamen (in Großbuchstaben) übereinstimmt, und speichert ihn für den Abruf über `Registry::getInstance()->loadModuleConfigByName('users')`.

## Ein minimales Modul
```php
<?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);
    }
}
```
Die `IModule` Schnittstelle ist absichtlich ein Marker – der tatsächliche Verhalten befindet sich vollständig in Ihren Traits und Plugins.

## Plugins: Zustandslose Dienste

Ein **Plugin** ist eine Klasse, die Controller instanziieren können, um auf Modulfunktionen zuzugreifen:
```php
<?php
// application/module/users/plugins/user.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';
    }
}
```
In einem Controller:
```php
$this->user = new \Nibiru\Module\Users\Plugin\User();
if (!$this->user->isAuthorized()) {
    View::forwardTo('/login');
}
```
Plugins erben von der Modulklasse und teilen daher Zugriff auf den Registrierungsmechanismus, die Einstellungen und das Beobachtergerüst.

## Merkmale: wiederverwendbare Methoden

Ein **Trait** trägt wiederverwendbare Methodenkörper, die die Modulklassen benötigen. Häufiges Muster: Form-Factories.
```php
<?php
// application/module/users/traits/users.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',
        ]);
    }
}
```
Die Hauptklasse `Users` bringt es über `use Traits\Users;` ein.

## Modul-Einstellungen (INI)

Jeder Modul kann seine eigenen INI-Dateien haben. Der Registrierungsmechanismus analysiert jede `*.ini`-Datei im Verzeichnis `settings/` und sucht nach einem Abschnitt, der den Namen des Moduls (in Großbuchstaben) trägt:
```ini
; application/module/users/settings/users.ini
[USERS]
session.lifetime    = 7200
password.min.length = 12
allowed.roles[]     = "admin"
allowed.roles[]     = "editor"
allowed.roles[]     = "standard"
```
Lesen Sie es von überall aus:
```php
$cfg = \Nibiru\Registry::getInstance()->loadModuleConfigByName('users');
$cfg->session_lifetime;     // 7200
$cfg->password_min_length;  // 12
$cfg->allowed_roles;        // [admin, editor, standard]
```
Umgebungsüberschreibungen: Eine Datei mit dem Namen `users.production.ini` wird vorzugsweise gegenüber `users.ini` verwendet, wenn `APPLICATION_ENV=production`.

## Das Beobachter-Muster

Module, die `SplSubject` implementieren, können Ereignisse an angehängte Beobachter übertragen, ohne mit ihnen gekoppelt zu sein. Aus der Präsentation meldet das Modul `analytics` jedem angehängten Tracker bei jeder Seitenansicht:
```php
// 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()
```
Jeder Beobachter erhält die Analytics-Instanz und zieht die Ereignisdaten aus, über die er sich interessiert.

## Echte Produktionsmodule

Von den Showcase-Anwendungen:

- **`auth`** (TPMS) — Sitzungsverwaltung, QR-Code-Anmeldung, rollenbasierte Zugriffskontrolle. Implementiert `SplSubject`, sodass Anmelde-/Abmeldeereignisse an Protokollierungs- und Überwachungsmodule weitergegeben werden können.
- **`cms`** (prod.maschinen-stockert.de) — Inhaltsverwaltung, indiziert durch *Controller-Pfad* + Sprache. Ermöglicht es Nicht-Entwicklern, die Seitenkopien zu aktualisieren.
- **`graph_mail`** (TPMS) — Microsoft Graph API-Wrapper für transaktionale E-Mails.
- **`pdfgenerator`** (prod.maschinen-stockert.de) — Generiert Maschinenkataloge aus DB-gesteuerten Vorlagen.
- **`machineryscout`** — Elasticsearch-Indexverwaltung mit Merkmalen für Indizierung, Abfrage und erneute Indizierung.
- **`assetmanager`** — zentrales CSS/JS-Pipeline, verwendet zum Wechseln von Themen je nach Sprache.
- **`analytics`** — beobachtungsgetriebene Tracker-Ausbreitung (Matomo usw.).

## Modulregistrierung: Wie das Framework Ihr Modul findet

Ein Modulordner auf der Festplatte ist erforderlich, aber nicht ausreichend. Das Framework muss auch wissen, **wie es geladen wird** – das geschieht mit drei Positionierungsarrays in Ihrer `[AUTOLOADER]`-Konfiguration:
```ini
; 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
```
Die Namen sind **kleinbuchstabeigeordnete Ordnername**, genau wie sie unter `application/module/` erscheinen. Der Frameworks `Auto::loader()` (aufgerufen vom [Dispatcher](/en/core/dispatcher/)) durchläuft jede Modul in der Reihenfolge und erfordert seine Dateien.

### Konvention für den Plugin-Namespace

Pluginklassen liegen im **Plural**-Namespace `Plugins`:
```php
// application/module/billing/plugins/invoice.php
namespace Nibiru\Module\Billing\Plugins;     // ← plural
class Invoice extends \Nibiru\Module\Billing\Billing { /* ... */ }
```
Namespace und Klassenname nicht übereinstimmen und es treten Autoloader-Fehlschläge auf. Der CLI-Scaffold-Befehl (`./nibiru -m billing`) generiert den richtigen Namespace für Sie.

### Einstellungensermittlung

Sie **registrieren** Ihre Modul-INI-Dateien nicht — der [Registry](/de/core/registry/) entdeckt sie automatisch, indem er `application/module/<name>/settings/*.ini` durchläuft, nachdem `[AUTOLOADER]` die Modulklassen lädt. Jede Sektion `[<MODULE>]` (in Großbuchstaben) in einer INI wird zur Verfügung gestellt als:
```php
$cfg = \Nibiru\Registry::getInstance()->loadModuleConfigByName('billing');
$cfg->invoice_prefix;            // [BILLING] invoice.prefix → property
```
Der Registrierungsmodul bevorzugt `<Modul>.<Umgebung>.ini` (z.B. `billing.production.ini`), wenn `APPLICATION_ENV` übereinstimmt.

### Migrationen

Wenn Ihr Modul Datenbanktabellen hat, fügen Sie SQL-Dateien in `application/settings/config/database/` ein, die nummeriert sind und sich auf den bestehenden Bereich beziehen. Konvention des AI-Moduls:
```
200-ai_rag_collection.sql
201-ai_rag_chunk.sql
202-ai_conversation.sql
203-ai_message.sql
```
Führen Sie mit `./nibiru -mi local` aus. Das Framework generiert automatisch Modelle in `application/model/<table>.php`, wenn `[GENERATOR] database = true` gesetzt ist, bereit zur Verwendung über `\Nibiru\Pdo::fetchAll(…)`.

## Ein Modul generieren
```bash
./nibiru -m billing
```
Skelette:
```
application/module/billing/
├── billing.php
├── interfaces/billing.php
├── plugins/
├── settings/billing.ini
└── traits/
```
Fügen Sie den Schalter `-g` (`./nibiru -m billing -g`) hinzu, wenn Sie Graylog-Protokollierungshooks vordefiniert in die Struktur einfügen möchten.

## Wenn ein Modul **nicht** erstellt werden sollte

Verwenden Sie nicht jede Controller-Klasse als Modul. Der Schwellenwert ist: *haben Sie mindestens ein Trait, eine Erweiterung und einen INI-Schlüssel?* Wenn ja, verdient es sein eigenes Modulverzeichnis. Wenn nein, reicht ein freigegebener Trait-Datei oder eine kleine Hilfsdatei in `application/lib/`.
