Conceptual Example
This example illustrates the structure of the Chain of Responsibility design pattern and focuses on the following questions:
What classes does it consist of?
What roles do these classes play?
In what way the elements of the pattern are related?
After learning about the pattern’s structure it’ll be easier for you to grasp the following example, based on a real-world PHP use case.
index.php: Conceptual example
<?php
namespace RefactoringGuru\ChainOfResponsibility\Conceptual;
/**
* The Handler interface declares a method for building the chain of handlers.
* It also declares a method for executing a request.
*/
interface Handler
{
public function setNext(Handler $handler): Handler;
public function handle(string $request): ?string;
}
/**
* The default chaining behavior can be implemented inside a base handler class.
*/
abstract class AbstractHandler implements Handler
{
/**
* @var Handler
*/
private $nextHandler;
public function setNext(Handler $handler): Handler
{
$this->nextHandler = $handler;
// Returning a handler from here will let us link handlers in a
// convenient way like this:
// $monkey->setNext($squirrel)->setNext($dog);
return $handler;
}
public function handle(string $request): ?string
{
if ($this->nextHandler) {
return $this->nextHandler->handle($request);
}
return null;
}
}
/**
* All Concrete Handlers either handle a request or pass it to the next handler
* in the chain.
*/
class MonkeyHandler extends AbstractHandler
{
public function handle(string $request): ?string
{
if ($request === "Banana") {
return "Monkey: I'll eat the " . $request . ".\n";
} else {
return parent::handle($request);
}
}
}
class SquirrelHandler extends AbstractHandler
{
public function handle(string $request): ?string
{
if ($request === "Nut") {
return "Squirrel: I'll eat the " . $request . ".\n";
} else {
return parent::handle($request);
}
}
}
class DogHandler extends AbstractHandler
{
public function handle(string $request): ?string
{
if ($request === "MeatBall") {
return "Dog: I'll eat the " . $request . ".\n";
} else {
return parent::handle($request);
}
}
}
/**
* The client code is usually suited to work with a single handler. In most
* cases, it is not even aware that the handler is part of a chain.
*/
function clientCode(Handler $handler)
{
foreach (["Nut", "Banana", "Cup of coffee"] as $food) {
echo "Client: Who wants a " . $food . "?\n";
$result = $handler->handle($food);
if ($result) {
echo " " . $result;
} else {
echo " " . $food . " was left untouched.\n";
}
}
}
/**
* The other part of the client code constructs the actual chain.
*/
$monkey = new MonkeyHandler();
$squirrel = new SquirrelHandler();
$dog = new DogHandler();
$monkey->setNext($squirrel)->setNext($dog);
/**
* The client should be able to send a request to any handler, not just the
* first one in the chain.
*/
echo "Chain: Monkey > Squirrel > Dog\n\n";
clientCode($monkey);
echo "\n";
echo "Subchain: Squirrel > Dog\n\n";
clientCode($squirrel);
Output.txt: Execution result
Chain: Monkey > Squirrel > Dog
Client: Who wants a Nut?
Squirrel: I'll eat the Nut.
Client: Who wants a Banana?
Monkey: I'll eat the Banana.
Client: Who wants a Cup of coffee?
Cup of coffee was left untouched.
Subchain: Squirrel > Dog
Client: Who wants a Nut?
Squirrel: I'll eat the Nut.
Client: Who wants a Banana?
Banana was left untouched.
Client: Who wants a Cup of coffee?
Cup of coffee was left untouched.
Real World Example
The most widely known use of the Chain of Responsibility (CoR) pattern in the PHP world is found in HTTP request middleware. These are implemented by the most popular PHP frameworks and even got standardized as part of PSR-15.
It works like this: an HTTP request must pass through a stack of middleware objects in order to be handled by the app. Each middleware can either reject the further processing of the request or pass it to the next middleware. Once the request successfully passes all middleware, the primary handler of the app can finally handle it.
You might have noticed that this approach is kind of inverse to the original intent of the pattern. Indeed, in the typical implementation, a request is only passed along a chain if a current handler Can’t process it, while a middleware passes the request further down the chain when it thinks that the app CAN handle the request. Nevertheless, since middleware objects are chained, the whole concept is still considered an example of the CoR pattern.
index.php: Real world example
<?php
namespace RefactoringGuru\ChainOfResponsibility\RealWorld;
/**
* The classic CoR pattern declares a single role for objects that make up a
* chain, which is a Handler. In our example, let's differentiate between
* middleware and a final application's handler, which is executed when a
* request gets through all the middleware objects.
*
* The base Middleware class declares an interface for linking middleware
* objects into a chain.
*/
abstract class Middleware
{
/**
* @var Middleware
*/
private $next;
/**
* This method can be used to build a chain of middleware objects.
*/
public function linkWith(Middleware $next): Middleware
{
$this->next = $next;
return $next;
}
/**
* Subclasses must override this method to provide their own checks. A
* subclass can fall back to the parent implementation if it can't process a
* request.
*/
public function check(string $email, string $password): bool
{
if (!$this->next) {
return true;
}
return $this->next->check($email, $password);
}
}
/**
* This Concrete Middleware checks whether a user with given credentials exists.
*/
class UserExistsMiddleware extends Middleware
{
private $server;
public function __construct(Server $server)
{
$this->server = $server;
}
public function check(string $email, string $password): bool
{
if (!$this->server->hasEmail($email)) {
echo "UserExistsMiddleware: This email is not registered!\n";
return false;
}
if (!$this->server->isValidPassword($email, $password)) {
echo "UserExistsMiddleware: Wrong password!\n";
return false;
}
return parent::check($email, $password);
}
}
/**
* This Concrete Middleware checks whether a user associated with the request
* has sufficient permissions.
*/
class RoleCheckMiddleware extends Middleware
{
public function check(string $email, string $password): bool
{
if ($email === "admin@example.com") {
echo "RoleCheckMiddleware: Hello, admin!\n";
return true;
}
echo "RoleCheckMiddleware: Hello, user!\n";
return parent::check($email, $password);
}
}
/**
* This Concrete Middleware checks whether there are too many failed login
* requests.
*/
class ThrottlingMiddleware extends Middleware
{
private $requestPerMinute;
private $request;
private $currentTime;
public function __construct(int $requestPerMinute)
{
$this->requestPerMinute = $requestPerMinute;
$this->currentTime = time();
}
/**
* Please, note that the parent::check call can be inserted both at the
* beginning of this method and at the end.
*
* This gives much more flexibility than a simple loop over all middleware
* objects. For instance, a middleware can change the order of checks by
* running its check after all the others.
*/
public function check(string $email, string $password): bool
{
if (time() > $this->currentTime + 60) {
$this->request = 0;
$this->currentTime = time();
}
$this->request++;
if ($this->request > $this->requestPerMinute) {
echo "ThrottlingMiddleware: Request limit exceeded!\n";
die();
}
return parent::check($email, $password);
}
}
/**
* This is an application's class that acts as a real handler. The Server class
* uses the CoR pattern to execute a set of various authentication middleware
* before launching some business logic associated with a request.
*/
class Server
{
private $users = [];
/**
* @var Middleware
*/
private $middleware;
/**
* The client can configure the server with a chain of middleware objects.
*/
public function setMiddleware(Middleware $middleware): void
{
$this->middleware = $middleware;
}
/**
* The server gets the email and password from the client and sends the
* authorization request to the middleware.
*/
public function logIn(string $email, string $password): bool
{
if ($this->middleware->check($email, $password)) {
echo "Server: Authorization has been successful!\n";
// Do something useful for authorized users.
return true;
}
return false;
}
public function register(string $email, string $password): void
{
$this->users[$email] = $password;
}
public function hasEmail(string $email): bool
{
return isset($this->users[$email]);
}
public function isValidPassword(string $email, string $password): bool
{
return $this->users[$email] === $password;
}
}
/**
* The client code.
*/
$server = new Server();
$server->register("admin@example.com", "admin_pass");
$server->register("user@example.com", "user_pass");
// All middleware are chained. The client can build various configurations of
// chains depending on its needs.
$middleware = new ThrottlingMiddleware(2);
$middleware
->linkWith(new UserExistsMiddleware($server))
->linkWith(new RoleCheckMiddleware());
// The server gets a chain from the client code.
$server->setMiddleware($middleware);
// ...
do {
echo "\nEnter your email:\n";
$email = readline();
echo "Enter your password:\n";
$password = readline();
$success = $server->logIn($email, $password);
} while (!$success);
Output.txt: Execution result
Enter your email:
asd
Enter your password:
123
UserExistsMiddleware: This email is not registered!
Enter your email:
admin@example.com
Enter your password:
wrong
UserExistsMiddleware: Wrong password!
Enter your email:
admin@example.com
Enter your password:
letmein
ThrottlingMiddleware: Request limit exceeded!
Enter your email:
admin@example.com
Enter your password:
admin_pass
RoleCheckMiddleware: Hello, admin!
Server: Authorization has been successful!