Middleware resolution from the container
Concept
Middleware resolution from the container means middleware classes are registered as bindings in the IoC container and resolved (with their dependencies injected) when the pipeline processes a request. This enables middleware to have constructor dependencies.
Why container resolution matters: A CorsMiddleware might need a ConfigRepository. A ThrottleMiddleware needs a CacheInterface. Without container resolution, you'd have to instantiate all middleware manually with their dependencies.
Resolution approaches:
- String class names: Store middleware as
'App\Http\Middleware\AuthMiddleware'. Resolve via$container->make($className)at pipeline run time. - Closures: Store as
fn() => new AuthMiddleware($dependency). Call the closure to get the instance. - Pre-instantiated objects: Already resolved objects. No container needed.
Lazy resolution: Only resolve middleware when the pipeline actually runs, not at registration time. This avoids initializing middleware that won't be needed.
Route-level vs global middleware: Global middleware runs on every request. Route-level middleware runs only on matched routes. The pipeline is built differently for each. Global middleware is added by the framework. Route middleware is registered by name (e.g., 'auth' → AuthMiddleware::class) and resolved per-route.
Middleware groups: Named collections of middleware (e.g., 'web' includes session, CSRF; 'api' includes throttle). Groups are expanded when building the pipeline.
Code Example
<?php
namespace Framework\Http;
use Framework\Container\Container;
use Psr\Http\Server\MiddlewareInterface;
class MiddlewareRegistry
{
private array $global = []; // always run — class names
private array $named = []; // 'auth' => AuthMiddleware::class
private array $groups = []; // 'web' => ['session', 'csrf', 'auth']
public function __construct(private readonly Container $container) {}
public function add(string $className): void
{
$this->global[] = $className;
}
public function alias(string $alias, string $className): void
{
$this->named[$alias] = $className;
}
public function group(string $name, array $middleware): void
{
$this->groups[$name] = $middleware;
}
public function resolve(string|array $middleware): array
{
$names = (array) $middleware;
$resolved = [];
foreach ($names as $name) {
if (isset($this->groups[$name])) {
// Expand group recursively
$resolved = array_merge($resolved, $this->resolve($this->groups[$name]));
} elseif (isset($this->named[$name])) {
$resolved[] = $this->container->make($this->named[$name]);
} elseif (class_exists($name)) {
$resolved[] = $this->container->make($name);
}
}
return $resolved;
}
public function buildGlobal(): array
{
return array_map(fn($class) => $this->container->make($class), $this->global);
}
}
// Registration in bootstrap/app.php
$registry = new MiddlewareRegistry($container);
// Global middleware (every request)
$registry->add(\Framework\Http\Middleware\LoggingMiddleware::class);
$registry->add(\Framework\Http\Middleware\HandleCors::class);
// Named aliases (for route middleware)
$registry->alias('auth', \App\Http\Middleware\Authenticate::class);
$registry->alias('throttle', \App\Http\Middleware\RateLimiter::class);
$registry->alias('verified', \App\Http\Middleware\EnsureEmailVerified::class);
// Groups
$registry->group('api', ['throttle', 'auth']);
$registry->group('web', ['session', 'csrf', 'auth']);
// Building pipeline for a request
$routeMiddleware = $registry->resolve($matchedRoute->middleware);
$globalMiddleware = $registry->buildGlobal();
$allMiddleware = array_merge($globalMiddleware, $routeMiddleware);