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!')oderMiddleware::register+use()->group(). - Benannte Pipelines: mit
Middleware::registerregistrieren und Routen anschließend inMiddleware::use('name')->group()einschließen. In einer Routenkette ist->middleware()ein Alias von->before(), kein Registrierer.