Design Patterns: Chain Of Responsibility
The Chain of Responsibility pattern is a Behavioural design pattern that allows a request to be passed along a chain of handlers until one of them processes it.
Overview
The Chain of Responsibility pattern is a Behavioural design pattern that allows a request to be passed along a chain of handlers until one of them processes it. This pattern promotes loose coupling by allowing multiple handlers to process a request without the sender needing to know which one will handle it.
Laravel's middleware system is a perfect example of the Chain of Responsibility pattern. Middleware allows HTTP requests to pass through a series of handlers before reaching the controller, each one deciding whether to process the request or pass it along.
Or let’s consider an ordering system where we apply a series of middleware checks before processing an order:
Authenticate the User – Ensure the user is logged in.
Check Stock Availability – Verify if the requested product is in stock.
Process Payment – If all conditions are met, proceed with the payment.
Instead of tightly coupling a request to a single handler, the Chain of Responsibility enables dynamic selection of handlers at runtime. If a handler can't process the request, it simply passes it to the next handler in the chain.
Example
This example demonstrates the use of the Chain of Responsibility Pattern to process a sequence of validation and authorisation steps within a request handling pipeline. Each step in the chain checks certain conditions and either processes the request or passes it along to the next handler in line.
Let’s get into it, first, lets define a handler Interface
:
interface Handler
{
public function setNext(Handler $handler): Handler;
public function handle(array $request): ?string;
}
With that in place, we can create some concrete handlers which implement this interface BUT before that, let’s create an Abstract
class.
abstract class AbstractHandler implements Handler
{
private ?Handler $nextHandler = null;
public function setNext(Handler $handler): Handler
{
$this->nextHandler = $handler;
return $handler;
}
public function handle(array $request): ?string
{
if ($this->nextHandler instanceof Handler) {
return $this->nextHandler->handle($request);
}
return null;
}
}
But why? Well, yeah good question… but it removes boilerplate code throughout the concrete handlers. Without this abstraction class, we’d need to duplicate the code below throughout.
Ok, now onto the concrete handlers:
class AuthHandler extends AbstractHandler
{
public function handle(array $request): ?string
{
if (!isset($request['user'])) {
throw new AuthException('User not authenticated');
}
return parent::handle($request);
}
}
class PermissionsHandler extends AbstractHandler
{
public function handle(array $request): ?string
{
$permissions = $request['user']['roles'] ?? [];
if (!in_array('ADMIN', $permissions)) {
throw new PermissionException('User not authorised.');
}
return parent::handle($request);
}
}
class ValidationHandler extends AbstractHandler
{
public function handle(array $request): ?string
{
if (empty($request['data'])) {
throw new ValidationException('Request data is invalid.');
}
return parent::handle($request);
}
}
Ok great stuff, that’s essentially it. Now let’s start piecing this code together.
// Bootstrap the chain
$authHandler = new AuthHandler();
$permissionsHandler = new PermissionsHandler();
$validationHandler = new ValidationHandler();
$authHandler
->setNext($permissionsHandler)
->setNext($validationHandler);
// Set request
$request = [
'user' => [
'name' => 'John Doe',
'roles' => ['USER', 'ADMIN']
],
'data' => 'Some important data'
];
// Run the request through the chain.
// If request is invalid, an appropriate exception is thrown.
$authHandler->handle($request);
In summary:
Create the Interface.
Create the concrete handlers (optionally abstract boilerplate code if you wish).
Bootstrap the chain.
Trigger it.
That’s it.
Advantages
Single Responsibility Principle: Each handler performs a single responsibility, making it easier to understand, maintain, and test independently.
Flexible and Extensible: Handlers can be added, removed, or reordered easily within the chain, allowing for flexible request handling as requirements evolve.
Reduced Complexity: Distributing responsibilities among multiple classes keeps each handler lightweight and allows for complex condition processing without creating a large monolithic class.
Disadvantages
Overhead of Multiple Classes: Each step requires a new class, which may add to the number of classes in the project and increase overall file management complexity.
Potential Debugging Complexity: Debugging can become challenging with multiple handlers, as errors might be caused by dependencies or a handler further down the chain.
An Alternative
You don’t need to be waking up at 5am smashing down a broccoli smoothie, memorising design patterns whilst whipping yourself for thinking about Creme Eggs every day of the week.
Sometimes simple works, and that’s perfectly fine, here’s a quick alternative:
class RequestProcessor
{
public function process(array $request): void
{
if (!isset($request['user'])) {
throw new AuthException('User not authenticated.');
}
if (!in_array('ADMIN', $request['user']['roles'] ?? [])) {
throw new PermissionException('User not authorized.');
}
if (empty($request['data'])) {
throw new ValidationException('Invalid data provided.');
}
}
}
Simple, right? And it achieves the same thing, let’s trigger it.
$processor = new RequestProcessor();
$request = [
'user' => [
'name' => 'John Doe',
'roles' => ['USER', 'ADMIN'],
],
'data' => 'Some important data',
];
// If request is invalid, an appropriate exception is thrown.
$processor->process($request);
Advantages
Simplicity: By consolidating all validation and authorisation logic in a single class, the design is easier to follow, especially for smaller-scale applications.
Centralised Management: All request checks are in one place, making it simpler to add new conditions or modify existing ones without managing multiple handlers.
Disadvantages
Less Flexibility: The single-class approach limits the ability to rearrange or selectively apply only some validation steps, which can become limiting as the application grows.
Monolithic: Over time, adding additional checks can make the class bloated and harder to maintain, breaking the single responsibility principle.
Use what makes sense, this article IS NOT telling you, you should be using it, it’s telling you how to use it. The decision should be based upon the business problem you’re trying to solve. Being armed with this knowledge helps you make a decision that’s a little more informed.
Want to see the Chain Of Responsibility Pattern in action? Check out this simple PHP example: Github