HTTP

Middleware

Middleware contracts, pořadí zpracování, zásahy do request/response flow a pravidla kompozice.

PSR-15 runtime

Middleware runtime is PSR-15 based. Every middleware must implement Psr\Http\Server\MiddlewareInterface and either return a response or delegate to the next handler.

How the runtime executes

Framework::run() gets global middleware class names from MiddlewareStack, resolves them through MiddlewareResolver, builds MiddlewarePipeline, and uses DispatchRequestHandler as fallback request handler.

$stack = $this->container->get(MiddlewareStack::class);
$middleware = $this->container
    ->get(MiddlewareResolver::class)
    ->resolve($stack->all());

$pipeline = MiddlewarePipeline::create(
    $middleware,
    $this->container->get(DispatchRequestHandler::class),
);

Global middleware stack

HttpServiceProvider registers a default global stack. Current default order: RequestLoggingMiddleware, BenchmarkMiddleware, ErrorHandlingMiddleware, CorsMiddleware, PoweredByMiddleware, HtmlMinifyMiddleware, OptionsMiddleware.

Configuring global middleware

Application code can extend or reorder global middleware through Framework::middleware() and MiddlewareStack operations (add, prepend, insertBefore, insertAfter, remove).

$framework->middleware(function (MiddlewareStack $stack): void {
    $stack->prepend(App\Http\Middleware\AuthMiddleware::class);
    $stack->insertAfter(
        Lemonade\Framework\Http\Middleware\CorsMiddleware::class,
        App\Http\Middleware\RequestIdMiddleware::class,
    );
});

Middleware resolver

MiddlewareResolver converts middleware class definitions to container instances and enforces MiddlewareInterface. Invalid entries throw RuntimeException.

$resolved = [];
foreach ($middlewareClasses as $middlewareClass) {
    $middleware = $this->container->get($middlewareClass);
    if (!$middleware instanceof MiddlewareInterface) {
        throw new RuntimeException(sprintf(
            'Target class "%s" is not a valid middleware.',
            $middlewareClass,
        ));
    }

    $resolved[] = $middleware;
}

Route-specific middleware

After route match, DispatchRequestHandler resolves middleware classes from RouteMatch::middleware() via container, checks MiddlewareInterface, then wraps ControllerRequestHandler with route middleware pipeline.

$match = $this->router->match($request);

$handler = new ControllerRequestHandler(
    resolver: $this->resolver,
    match: $match,
);

return MiddlewarePipeline::create($middleware, $handler)
    ->handle($request);

Middleware pipeline behavior

MiddlewarePipeline is immutable and index-based. Each call to process() gets a new handler pointing to the next middleware. When the stack ends, fallback handler continues into request dispatch flow.

Example middleware

Custom middleware should be small and focused. Authentication/authorization is a common route or global concern.

<?php

declare(strict_types=1);

namespace App\Http\Middleware;

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

final class AuthMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler,
    ): ResponseInterface {
        // Perform authentication / authorization here.

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

Typed middleware definitions

Route and group middleware definitions are class-string references in routing API. Runtime resolves them from container; no dynamic middleware method lookup is used.

Runtime boundary

Global middleware wraps the whole request lifecycle first. Route middleware then wraps matched controller dispatch. Final response flows back through both pipelines in reverse order.