PSR-15 MiddlewareInterface and RequestHandlerInterface
Concept
PSR-15 defines two interfaces for HTTP server middleware: MiddlewareInterface and RequestHandlerInterface. PSR-15 middleware is compatible across any compliant framework.
RequestHandlerInterface:
interface RequestHandlerInterface {
public function handle(ServerRequestInterface $request): ResponseInterface;
}A request handler takes a request and returns a response. The controller/action is the terminal handler. The pipeline-wrapped middleware chain is also a handler.
MiddlewareInterface:
interface MiddlewareInterface {
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $next
): ResponseInterface;
}A middleware takes a request and the next handler. It can call $next->handle($request) to continue the pipeline, or return a response without calling next (short-circuit).
Key difference from the functional approach: The $next parameter is an object (RequestHandlerInterface) not a raw callable. This allows type hints and IDE support.
Adapter pattern: Wrapping each middleware in a RequestHandlerInterface so the chain can be composed. The pipeline composes middleware + handler into a single RequestHandlerInterface.
PSR-7 vs custom Request: PSR-15 uses ServerRequestInterface (PSR-7). If your framework uses a custom Request class, you can implement the interfaces while passing your custom class, or create an adapter layer.
Code Example
<?php
namespace Framework\Http\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
// PSR-15 compliant middleware
class AuthMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
{
$token = $request->getHeaderLine('Authorization');
if (!$token) {
return new \Nyholm\Psr7\Response(401, [], 'Unauthorized');
}
$user = $this->resolveUser($token);
$request = $request->withAttribute('user', $user);
return $next->handle($request); // continue to next middleware/handler
}
private function resolveUser(string $token): ?array { /* ... */ return null; }
}
class LoggingMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
{
$start = microtime(true);
$response = $next->handle($request);
$elapsed = round((microtime(true) - $start) * 1000, 2);
// Add timing header on the way back
return $response->withHeader('X-Response-Time', $elapsed . 'ms');
}
}
// Terminal handler — wraps the route action
class RouteHandler implements RequestHandlerInterface
{
public function __construct(private readonly \Closure $action) {}
public function handle(ServerRequestInterface $request): ResponseInterface
{
return ($this->action)($request);
}
}
// Middleware-to-handler adapter (for pipeline composition)
class MiddlewareHandler implements RequestHandlerInterface
{
public function __construct(
private readonly MiddlewareInterface $middleware,
private readonly RequestHandlerInterface $next,
) {}
public function handle(ServerRequestInterface $request): ResponseInterface
{
return $this->middleware->process($request, $this->next);
}
}