Leitfaden

Middleware

Nutze Middleware für gemeinsame HTTP-Belange, hänge sie an Routen oder Gruppen und halte Business-Logik daraus heraus.

Wofür Middleware gedacht ist

In Lemonade ist Middleware eine PSR-15-Schicht um die Verarbeitung eines HTTP-Requests. Sie eignet sich für Request-Bedingungen, Header, CSRF oder Zugriffskontrollen vor dem Controller. Halte die Grenze klar: middleware = gemeinsames HTTP-Verhalten, controller = konkreter HTTP-Use-Case und Orchestrierung, service = Anwendungs- oder Geschäftsoperationen. Ein vollständiges Formularbeispiel mit CSRF findest du unter Formulare & POST-Ablauf.

Middleware an Route oder Gruppe hängen

Nutze Route-Middleware, wenn eine einzelne Route eine zusätzliche HTTP-Regel braucht. Group-Middleware eignet sich, wenn mehrere Routen dieselbe Regel teilen. In beiden Fällen registrierst du Klassen per Name und der Runtime löst sie vor dem Controller aus dem Container auf.

example.php
<?php

use App\Controllers\ArticleController;
use App\Controllers\ProfileController;
use App\Http\Middleware\RequirePreviewHeaderMiddleware;
use App\Http\Middleware\RequireProfileHeaderMiddleware;
use Lemonade\Framework\Routing\Router;

return static function (Router $router): void {
    $router
        ->getNamed('articles.preview', '/articles/preview', ArticleController::class . '@preview')
        ->middleware(RequirePreviewHeaderMiddleware::class);

    $router
        ->group('/profile', static function (Router $router): void {
            $router->getNamed('profile.show', '', ProfileController::class . '@show');
            $router->postNamed('profile.update', '/edit', ProfileController::class . '@update');
        })
        ->middleware(RequireProfileHeaderMiddleware::class);
};

Reihenfolge und vorzeitiges Beenden

Middleware läuft in Registrierungsreihenfolge. Die Reihenfolge ist wichtig, weil eine Schicht den Request verändern, Response-Header hinzufügen oder die Pipeline früh beenden kann. return $handler->handle($request); reicht den Request weiter. Eine eigene Response beendet dagegen den aktuellen Zweig, ohne den Rest der Pipeline auszuführen.

example.php
<?php

use App\Http\Middleware\RequestIdMiddleware;
use Lemonade\Framework\Core\Context\ApplicationContextFactory;
use Lemonade\Framework\Core\KernelFactory;
use Lemonade\Framework\Http\Middleware\ErrorHandlingMiddleware;
use Lemonade\Framework\Http\Middleware\MiddlewareStack;

$context = (new ApplicationContextFactory())->fromGlobals(__DIR__);
$kernel = (new KernelFactory())->create($context);

$kernel->framework()->middleware(static function (MiddlewareStack $stack): void {
    $stack->insertAfter(ErrorHandlingMiddleware::class, RequestIdMiddleware::class);
});

$kernel->handle();

Kleine eigene Middleware schreiben

Eigene Middleware implementiert MiddlewareInterface und erhält Abhängigkeiten über Constructor Injection. Halte sie klein und auf HTTP fokussiert. Sobald sie Persistenz oder wiederverwendbare Business-Logik übernimmt, verschiebe diese Arbeit in einen Anwendungsservice.

example.php
<?php

declare(strict_types=1);

namespace App\Http\Middleware;

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

final class RequirePreviewHeaderMiddleware implements MiddlewareInterface
{
    public function __construct(
        private readonly ResponseFactoryInterface $responses,
    ) {}

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        if ($request->getHeaderLine('X-Preview-Mode') !== 'allow') {
            return $this->responses->createResponse(403);
        }

        return $handler->handle($request);
    }
}