BehavioralPHPverifiedVerified
Chain of Responsibility Pattern in PHP
Passes a request along a chain of handlers, where each handler decides to process it or pass it to the next handler in the chain.
How to Implement the Chain of Responsibility Pattern in PHP
1Step 1: Define the request and abstract handler
class Request
{
public function __construct(
public readonly string $type,
public readonly mixed $data,
) {}
}
abstract class Handler
{
private ?Handler $next = null;
public function setNext(Handler $handler): Handler
{
$this->next = $handler;
return $handler;
}
public function handle(Request $request): ?string
{
if ($this->next !== null) {
return $this->next->handle($request);
}
return null;
}
}2Step 2: Implement concrete handlers
class AuthHandler extends Handler
{
public function handle(Request $request): ?string
{
if ($request->type === 'auth') {
return "AuthHandler processed: {$request->data}";
}
return parent::handle($request);
}
}
class LoggingHandler extends Handler
{
public function handle(Request $request): ?string
{
echo "Logging request: {$request->type}\n";
return parent::handle($request);
}
}
class DefaultHandler extends Handler
{
public function handle(Request $request): ?string
{
return "DefaultHandler: no specific handler for {$request->type}";
}
}3Step 3: Chain handlers together
$auth = new AuthHandler();
$logging = new LoggingHandler();
$default = new DefaultHandler();
$auth->setNext($logging)->setNext($default);
echo $auth->handle(new Request('auth', 'token123')); // AuthHandler processed
echo $auth->handle(new Request('other', 'data')); // DefaultHandler<?php
declare(strict_types=1);
// [step] Define the middleware pipeline interfaces
final class HttpRequest
{
/** @param array<string, string> $headers */
public function __construct(
public readonly string $method,
public readonly string $path,
public readonly array $headers = [],
public readonly ?string $body = null,
/** @var array<string, mixed> */
public array $attributes = [],
) {}
public function withAttribute(string $key, mixed $value): self
{
$clone = clone $this;
$clone->attributes[$key] = $value;
return $clone;
}
}
final readonly class HttpResponse
{
public function __construct(
public int $status,
public string $body,
/** @var array<string, string> */
public array $headers = [],
) {}
}
interface MiddlewareInterface
{
public function process(HttpRequest $request, RequestHandlerInterface $next): HttpResponse;
}
interface RequestHandlerInterface
{
public function handle(HttpRequest $request): HttpResponse;
}
interface LoggerInterface
{
public function info(string $message, array $context = []): void;
public function warning(string $message, array $context = []): void;
}
// [step] Implement middleware pipeline dispatcher
final class MiddlewarePipeline implements RequestHandlerInterface
{
/** @var MiddlewareInterface[] */
private array $middleware = [];
private int $index = 0;
public function __construct(
private readonly RequestHandlerInterface $fallback,
) {}
public function pipe(MiddlewareInterface $middleware): self
{
$this->middleware[] = $middleware;
return $this;
}
public function handle(HttpRequest $request): HttpResponse
{
if ($this->index >= count($this->middleware)) {
return $this->fallback->handle($request);
}
$current = $this->middleware[$this->index];
$this->index++;
return $current->process($request, $this);
}
}
// [step] Implement concrete middleware handlers
final class AuthenticationMiddleware implements MiddlewareInterface
{
public function __construct(private readonly LoggerInterface $logger) {}
public function process(HttpRequest $request, RequestHandlerInterface $next): HttpResponse
{
$token = $request->headers['Authorization'] ?? null;
if ($token === null) {
return new HttpResponse(401, '{"error": "Unauthorized"}');
}
// Validate token
$userId = $this->validateToken($token);
if ($userId === null) {
return new HttpResponse(403, '{"error": "Forbidden"}');
}
$this->logger->info("Authenticated user", ['userId' => $userId]);
$request = $request->withAttribute('userId', $userId);
return $next->handle($request);
}
private function validateToken(string $token): ?string
{
return str_starts_with($token, 'Bearer ') ? 'user_123' : null;
}
}
final class RateLimitMiddleware implements MiddlewareInterface
{
/** @var array<string, array{count: int, resetAt: int}> */
private array $buckets = [];
public function __construct(
private readonly int $maxRequests = 100,
private readonly int $windowSeconds = 60,
) {}
public function process(HttpRequest $request, RequestHandlerInterface $next): HttpResponse
{
$key = $request->headers['X-Forwarded-For'] ?? 'unknown';
$now = time();
if (!isset($this->buckets[$key]) || $this->buckets[$key]['resetAt'] < $now) {
$this->buckets[$key] = ['count' => 0, 'resetAt' => $now + $this->windowSeconds];
}
$this->buckets[$key]['count']++;
if ($this->buckets[$key]['count'] > $this->maxRequests) {
return new HttpResponse(429, '{"error": "Rate limit exceeded"}', [
'Retry-After' => (string) ($this->buckets[$key]['resetAt'] - $now),
]);
}
return $next->handle($request);
}
}
final class LoggingMiddleware implements MiddlewareInterface
{
public function __construct(private readonly LoggerInterface $logger) {}
public function process(HttpRequest $request, RequestHandlerInterface $next): HttpResponse
{
$start = hrtime(true);
$this->logger->info("Incoming request", [
'method' => $request->method,
'path' => $request->path,
]);
$response = $next->handle($request);
$durationMs = (hrtime(true) - $start) / 1e6;
$this->logger->info("Response sent", [
'status' => $response->status,
'durationMs' => round($durationMs, 2),
]);
return $response;
}
}Chain of Responsibility Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Chain of Responsibility Pattern in the Real World
“Like a customer support escalation: your call starts with a front-line agent. If they can’t resolve it, they transfer you to a specialist. If the specialist can’t help, it goes to a manager. Each level either handles it or passes it up the chain.”