Zum Inhalt springen

Dependency Injection und Middleware

Dieses Kapitel beschreibt Dependency Injection (DI) und Middleware im DotApp PHP Framework 2.0. Controller, Middleware und Routen gehören in ein Modul. Rufen Sie einen Controller mit "Module:Controller@method!" auf (das abschließende ! schaltet DI für diese Methode aus). Hängen Sie Modul-Middleware mit ->before("#Module:AuthGate@check!") an.

1. Dependency Injection und Middleware in DotApp

Der DI-Container liegt auf der DotApp-Instanz (bind, singleton, resolve). Middleware ist entweder ein before/after-Hook einer Route, eine benannte Pipeline über Middleware::register oder eine Modulklasse unter app/modules/{Module}/Middleware/.

1.1. Was ist Dependency Injection und Middleware?

Dependency Injection (DI) fügt registrierte Dienste in Callbacks ein, statt sie von Hand zu erzeugen. In DotApp ist der Container die Klasse DotApp.

Middleware läuft vor oder nach dem Route-Handler. Hängen Sie eine Modul-Middleware-Klasse mit ->before("#Module:Class@method!") an. Benannte Pipelines nutzen Middleware::register plus Middleware::use()->group().

1.2. Der DI-Container

Drei Methoden am Kernel, den Sie als initialize($dotApp) erhalten:

1.2.1. bind(string $key, callable $resolver)

Registriert eine Factory. Jeder Aufruf von resolve erzeugt eine neue Instanz.


$dotApp->bind('logger', function () {
    return new Logger();
});
$logger = $dotApp->resolve('logger');
$logger2 = $dotApp->resolve('logger'); // different instance
        
1.2.2. singleton(string $key, callable $resolver)

Wird einmal erzeugt und anschließend wiederverwendet. Das Framework registriert DotApp::class bereits als Singleton.


$dotApp->singleton('cache', function () {
    return new CacheService();
});
$cache1 = $dotApp->resolve('cache');
$cache2 = $dotApp->resolve('cache'); // same instance
        
1.2.3. resolve(string $key)

Gibt den gebundenen Wert zurück. Löst eine Ausnahme aus, wenn der Schlüssel unbekannt ist.


$cache = $dotApp->resolve('cache');
        
1.2.4. Wie DI in der Praxis verwendet wird

Registrieren Sie Dienste in der initialize($dotApp) eines Moduls. Controller-Methoden, die von Routen verwendet werden, sind public static und nehmen $request entgegen. Das abschließende ! an der Callable-Zeichenkette schaltet DI für diese Methode aus, sodass der Handler nur $request erhält.


public function initialize($dotApp) {
    $dotApp->singleton('cache', function () {
        return new CacheService();
    });
}
        

1.3. Controller aufrufen

Das alles gehört in app/modules/{Module}/module.init.php innerhalb von initialize($dotApp).

1.3.1. Controller-Zeichenkette des Moduls

Syntax: "Module:Controller@method!". Live-Demo Users: Users:Login@page!.


Router::get('/documentation/examples/run/users/login', "Users:Login@page!", Router::STATIC_ROUTE);
        
1.3.2. Abschließendes !

Das ! deaktiviert DI für diese Methode. Verwenden Sie es bei fast jedem Route-Handler.


Router::post('/documentation/examples/run/users/login', "Users:Login@save!", Router::STATIC_ROUTE);
        
1.3.3. Closures

Das erste Argument ist $request.


Router::get('/status', function ($request) {
    return "Current method: " . $request->getMethod();
}, Router::STATIC_ROUTE);
        
1.3.4. DotApp::call

Rufen Sie eine Controller- oder Middleware-Zeichenkette eines Moduls aus PHP auf:


DotApp::call("Users:Login@page!", $request);
        

1.4. Middleware

1.4.1. Modul-Middleware an einer Route

Legen Sie die Klasse in app/modules/Users/Middleware/AuthGate.php ab und hängen Sie sie mit before an. Der Route-Handler bleibt die Controller-Methode.


Router::get('/documentation/examples/run/users/app', "Users:Login@app!", Router::STATIC_ROUTE)
    ->before("#Users:AuthGate@check!");
        

namespace Dotsystems\App\Modules\Users\Middleware;

use Dotsystems\App\Parts\Auth;
use Dotsystems\App\Parts\Config;
use Dotsystems\App\Parts\Response;

class AuthGate extends \Dotsystems\App\Parts\ModuleMiddleware
{
    public static function check($request, array $rights = [])
    {
        if (!Auth::isLogged()) {
            $p = rtrim((string) Config::module('Users', 'prefix'), '/');
            return Response::redirect($p . '/login', 302);
        }
        if (!empty($rights) && !Auth::can($rights)) {
            return new Response(403, 'Forbidden');
        }
    }
}
        
1.4.2. Benannte Middleware-Pipeline

Registrieren Sie mit Middleware::register und umschließen Sie Routen anschließend mit Middleware::use()->group(). Der Callback erhält ($request, $next) und muss $next($request) aufrufen.


use Dotsystems\App\Parts\Middleware;
use Dotsystems\App\Parts\Response;

Middleware::register('is_admin', function ($request, $next) {
    if (!\Dotsystems\App\Parts\Auth::can('Users.admin')) {
        return new Response(403, 'Forbidden');
    }
    return $next($request);
});

Middleware::use('is_admin')->group(function () {
    Router::get('/admin/users', 'Users:Login@app!');
});
        
1.4.3. Globaler before-Hook

Eine Closure, die als Router::before verwendet wird, muss $request entgegennehmen. Die Rückgabe einer Response bricht die Route ab.


Router::before(['POST'], ['/documentation/examples/run/users/*'], function ($request) {
    // e.g. extra CRC or logging
});
        
1.4.4. middleware() in der Routenkette

In einer Routenkette ist Router::middleware() ein Alias von before(). Benannte Pipelines werden mit Middleware::register registriert und mit Middleware::use()->group() angewendet. Das zweite Argument von Router::get (und den übrigen Verb-Methoden) ist der Route-Handler: eine Controller-Zeichenkette oder ein Callable.

1.5. Praktische Beispiele

1. Singleton-Registrierung in initialize($dotApp):


$dotApp->singleton('cache', function () {
    return new CacheService();
});
$cache1 = $dotApp->resolve('cache');
$cache2 = $dotApp->resolve('cache');
echo ($cache1 === $cache2) ? "Same instance" : "Different instances";
        

2. Geschützte Seite:


Router::get('/documentation/examples/run/users/app', "Users:Login@app!", Router::STATIC_ROUTE)
    ->before("#Users:AuthGate@check!");
        

3. Bind plus eine Closure, die den Request liest:


$dotApp->bind('logger', function () {
    return new Logger();
});
Router::get('/log', function ($request) use ($dotApp) {
    $logger = $dotApp->resolve('logger');
    return "Method: " . $request->getMethod();
}, Router::STATIC_ROUTE);
        

1.6. Hinweise

  • Singleton vs. bind: Singleton für gemeinsam genutzte Dienste, bind für jedes Mal eine neue Instanz.
  • Controller: public static, erstes Argument $request, Routen-Zeichenkette 'Module:Ctrl@method!'.
  • Middleware: Modulklasse + ->before('#Module:Class@method!') oder Middleware::register + use()->group().
  • Benannte Pipelines: mit Middleware::register registrieren und Routen anschließend in Middleware::use('name')->group() einschließen. In einer Routenkette ist ->middleware() ein Alias von ->before(), kein Registrierer.