PSR-14 — Event Dispatcher interface
Concept
PSR-15 defines two interfaces for HTTP server-side request handling: RequestHandlerInterface (handles a request and produces a response) and MiddlewareInterface (wraps a handler to add cross-cutting behavior).
RequestHandlerInterface: A single method handle(ServerRequestInterface $request): ResponseInterface. This is the core of any HTTP request pipeline — routes, controllers, or any object that turns a request into a response.
MiddlewareInterface: A single method process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface. Middleware can: inspect/modify the request before passing to $handler->handle($request), inspect/modify the response after $handler->handle(), short-circuit by returning a response without calling $handler, or call $handler->handle() multiple times (rare — retry logic).
Middleware pipeline: Middleware is typically stacked. Request enters the first middleware, propagates inward through the stack, the innermost handler produces a response, and the response propagates back outward through the stack in reverse order. Laravel calls this the "middleware stack" and it wraps every HTTP request.
PSR-14 (event dispatcher): EventDispatcherInterface with dispatch(object $event): object. Allows dispatching and listening to events in a PSR-standard way. Laravel's Event facade and Symfony's EventDispatcher both implement PSR-14.
Code Example
<?php
declare(strict_types=1);
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
// Authentication middleware
class AuthMiddleware implements MiddlewareInterface
{
public function __construct(private readonly TokenVerifier $verifier) {}
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
): ResponseInterface {
$token = $request->getHeaderLine('Authorization');
if (!$this->verifier->isValid($token)) {
// Short-circuit — don't call $handler
return new JsonResponse(['error' => 'Unauthorized'], 401);
}
// Attach user to request attributes, pass along
$user = $this->verifier->getUser($token);
$request = $request->withAttribute('user', $user);
return $handler->handle($request); // call next in pipeline
}
}
// Rate limiting middleware — uses response from inner handler
class RateLimitMiddleware implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
): ResponseInterface {
if ($this->isRateLimited($request)) {
return new JsonResponse(['error' => 'Too Many Requests'], 429);
}
$response = $handler->handle($request);
$this->incrementCounter($request);
// Add headers to response on the way out
return $response->withHeader('X-RateLimit-Remaining', (string) $this->getRemaining($request));
}
}
// PSR-14 event dispatcher (Laravel implementation)
use Illuminate\Contracts\Events\Dispatcher;
class OrderService
{
public function __construct(private readonly Dispatcher $events) {}
public function place(Order $order): void
{
$this->saveOrder($order);
// Dispatch PSR-14 compatible event
$this->events->dispatch(new OrderPlaced($order));
}
}
// Listener
class SendOrderConfirmation
{
public function handle(OrderPlaced $event): void
{
Mail::to($event->order->customer_email)->send(new OrderConfirmationMail($event->order));
}
}