Chain of Responsibility pattern gives responsibility of a process handling to more than one object(a chain of objects) so that the sender and receiver of the request remains decoupled. The chain of objects pushes the processing to the next step until the processing is complete.
This article demonstrates Chain of Responsibility pattern implementations in PHP. Check the following implementation details and examples.
Implementation
Follow the steps below for Chain of responsibility pattern implementation-
- Create abstract class for the base of each step of the chain of responsibility. Define a protected property of type of the same class, this will be used to store the reference to the current step. Declare an abstract class for process exectution, so that the child step classes can define their own execution process.
- Create classe for each step and extend the base step class. Define the method for execution process. At the end call the next step, if any next step available.
Here is a simple example-
<?php
// Chain of responsibility pattern in PHP
// Abstract step
abstract class Step {
public function __construct(protected ?Step $nextStep) {
}
abstract function execute(): void;
}
// Step 1
class Step1 extends Step {
public function __construct(?Step $nextStep) {
parent::__construct($nextStep);
}
public function execute(): void {
// Perform all required step for this step
echo "Execute all operation for Step1 processing\n";
// Call the next step execution if the step is defined
if ($this->nextStep) {
$this->nextStep->execute();
}
}
}
// Step 2
class Step2 extends Step {
public function __construct(?Step $nextStep) {
parent::__construct($nextStep);
}
public function execute(): void {
// Perform all required step for this step
echo "Execute all operation for Step2 processing\n";
// Call the next step execution if the step is defined
if ($this->nextStep) {
$this->nextStep->execute();
}
}
}
// Step 3
class Step3 extends Step {
public function __construct(?Step $nextStep) {
parent::__construct($nextStep);
}
public function execute(): void {
// Perform all required step for this step
echo "Execute all operation for Step3 processing\n";
// Call the next step execution if the step is defined
if ($this->nextStep) {
$this->nextStep->execute();
}
}
}
// Demo
$steps = new Step1(new Step2(new Step3(null)));
$steps->execute();
Output will be as the below-
Execute all operation for Step1 processing
Execute all operation for Step2 processing
Execute all operation for Step3 processing
Examples
Check following examples-
Example #1: Caching Data
Here we are implementing 3 caching methods – Redis, Disk cache and CDN. Based on certain condition a specific caching method will be used.
Data Struct
- Create file “Data.php”.
- Define enum “DATA_TYPE” with values – DATA, JAVASCRIPT, CSS.
- Define class Data. Define private fields – $type(of type DATA_TYPE), $data(of type string), $data(of type string). Accept and set these fields in constructor.
- Define getter methods for the fields.
<?php
// Data.php
namespace BigBoxCode\DesignPattern\ChainOfResponsibility\Cache;
enum DATA_TYPE {
case DATA;
case JAVASCRIPT;
case CSS;
}
class Data {
public function __construct(
private DATA_TYPE $type,
private string $key,
private string $data
) {
}
public function getType(): DATA_TYPE {
return $this->type;
}
public function getKey(): string {
return $this->key;
}
public function getData(): string {
return $this->data;
}
}
Cache Handler
- Create file “CacheHandler.php”.
- Create abstract class “CacheHandler”.
- Declare field “nextCacheHandler” of type of “CacheHandler”, this will store the next processing step information.
- Declare an abstract method “handleRequest”.
<?php
// CacheHandler.php
namespace BigBoxCode\DesignPattern\ChainOfResponsibility\Cache;
abstract class CacheHandler {
public function __construct(protected ?CacheHandler $nextCacheHandler) {
}
abstract function handleRequest(Data $data): void;
}
CDN Cache Handler
- Create file “CdnCacheHandler.php”.
- Create class “CdnCacheHandler”. Extend “CacheHandler” for this class.
- In “handleRequest” method check if data type is “CSS” or “JAVASCRIPT”, if the type matches then save data in CDN. If it does not match then send it to next step.
<?php
// CdnCacheHandler.php
namespace BigBoxCode\DesignPattern\ChainOfResponsibility\Cache;
class CdnCacheHandler extends CacheHandler {
public function __construct(?CacheHandler $nextCacheHandler) {
parent::__construct($nextCacheHandler);
}
public function handleRequest(Data $data): void {
if ($data->getType() == DATA_TYPE::CSS || $data->getType() == DATA_TYPE::JAVASCRIPT) {
echo "Caching file '" . $data->getKey() . "' in CDN\n";
} else if ($this->nextCacheHandler != null) {
$this->nextCacheHandler->handleRequest($data);
}
}
}
Redis Cache Handler
- Create file “RedisCacheHandler.php”.
- Create class “RedisCacheHandler” and extend “CacheHandler”.
- In the “handleRequest” method check if length of data is less thatn 1024. If less then save in Redis. Else send it to next step.
<?php
// RedisCacheHandler.php
namespace BigBoxCode\DesignPattern\ChainOfResponsibility\Cache;
class RedisCacheHandler extends CacheHandler {
public function __construct(?CacheHandler $nextCacheHandler) {
parent::__construct($nextCacheHandler);
}
public function handleRequest(Data $data): void {
if ($data->getType() == DATA_TYPE::DATA && strlen($data->getData()) <= 1024) {
echo "Caching file '" . $data->getKey() . "' in Redis\n";
} else if ($this->nextCacheHandler != null) {
$this->nextCacheHandler->handleRequest($data);
}
}
}
Disk Cache Handler
- Create file “DiskCacheHandler.php”.
- Create class “DiskCacheHandler” and extend “CacheHandler”.
- In the “handleRequest” method check if the data is of type “DATA” and the length is greater than 1024, if the type matches then save it in Disk. Send it to the next step if any step left.
<?php
// DiskCacheHandler.php
namespace BigBoxCode\DesignPattern\ChainOfResponsibility\Cache;
class DiskCacheHandler extends CacheHandler {
public function __construct(?CacheHandler $nextCacheHandler) {
parent::__construct($nextCacheHandler);
}
public function handleRequest(Data $data): void {
if ($data->getType() == DATA_TYPE::DATA && strlen($data->getData()) > 1024) {
echo "Caching file '" . $data->getKey() . "' in Disk\n";
} else if ($this->nextCacheHandler != null) {
$this->nextCacheHandler->handleRequest($data);
}
}
}
Demo
In the client we can chain the call to the caching classes-
<?php
// demo.php
require __DIR__ . '/../../vendor/autoload.php';
use BigBoxCode\DesignPattern\ChainOfResponsibility\Cache\CdnCacheHandler;
use BigBoxCode\DesignPattern\ChainOfResponsibility\Cache\Data;
use BigBoxCode\DesignPattern\ChainOfResponsibility\Cache\DATA_TYPE;
use BigBoxCode\DesignPattern\ChainOfResponsibility\Cache\DiskCacheHandler;
use BigBoxCode\DesignPattern\ChainOfResponsibility\Cache\RedisCacheHandler;
$cacheHandler = new DiskCacheHandler(new RedisCacheHandler(new CdnCacheHandler(null)));
$data1 = new Data(DATA_TYPE::DATA, "key1", "ABC320489un3429rn29urn29r82n9jfdn2");
$cacheHandler->handleRequest($data1);
$data2 = new Data(DATA_TYPE::CSS, "key2", ".some-class{border: 1px solid red; margin: 10px}");
$cacheHandler->handleRequest($data2);
Output
Following output will be generated by demo code-
Caching file 'key1' in Redis
Caching file 'key2' in CDN
Example #2: Interview
Here we are demonestring an interview process. Here we have interview with the HR, tech interview, interview with CEO etc.
Interview Interface
- Create file “Interview.php”.
- Create abstract class “Interview”.
- Declare a protected field named “nextInterview” of type “Interview”, this will be used to store reference to the next step. Accept this field in constructor and set the field value.
- Declare abstract method “execute”.
<?php
// Interview.php
namespace BigBoxCode\DesignPattern\ChainOfResponsibility\Interview;
abstract class Interview {
public function __construct(protected ?Interview $nextInterview) {
}
abstract function execute(): void;
}
Phone Interview
- Create file “PhoneInterview.php”.
- Create class “PhoneInterview” and extend “Interview” abstract class.
- In the execute method implementation, define steps for phone interview and then call the next step(if next step is available).
<?php
// PhoneInterview.php
namespace BigBoxCode\DesignPattern\ChainOfResponsibility\Interview;
class PhoneInterview extends Interview {
public function __construct(?Interview $nextInterview) {
parent::__construct($nextInterview);
}
public function execute(): void {
// Ask all questions for phone interview
// Perform any other action required for phone interview
echo "Ask phone interview questions\n";
// Execute the next interview set while creating new struct
if ($this->nextInterview) {
$this->nextInterview->execute();
}
}
}
Technical Interview
- Create file “TechnicalInterview.php”.
- Define class “TechniaclInterview”, extend “Interview” for the class.
- In the execute method implementation, define steps for Technical interview and then call the next step(if next step is available).
<?php
// TechnicalInterview.php
namespace BigBoxCode\DesignPattern\ChainOfResponsibility\Interview;
class TechnicalInterview extends Interview {
public function __construct(?Interview $nextInterview) {
parent::__construct($nextInterview);
}
public function execute(): void {
// Ask all questions for technical interview
// Perform any other action required for technical interview
echo "Ask technical interview questions\n";
// Execute the next interview set while creating new struct
if ($this->nextInterview) {
$this->nextInterview->execute();
}
}
}
HR Interview
- Create file “HrInterview.php”.
- Define class “HrInterview”, extend “Interview”.
- In the execute method implementation, define steps for HR interview and then call the next step(if next step is available).
<?php
// HrInterview.php
namespace BigBoxCode\DesignPattern\ChainOfResponsibility\Interview;
class HrInterview extends Interview {
public function __construct(?Interview $nextInterview) {
parent::__construct($nextInterview);
}
public function execute(): void {
// Ask all questions for hr interview
// Perform any other action required for hr interview
echo "Ask HR interview questions\n";
// Execute the next interview set while creating new struct
if ($this->nextInterview) {
$this->nextInterview->execute();
}
}
}
CEO Interview
- Create file “CeoInterview.php”.
- Create class “CeoInterview”, extend “Interview” for this class.
- In the execute method implementation, define steps for CEO interview and then call the next step(if next step is available).
<?php
// CeoInterview.php
namespace BigBoxCode\DesignPattern\ChainOfResponsibility\Interview;
class CeoInterview extends Interview {
public function __construct(?Interview $nextInterview) {
parent::__construct($nextInterview);
}
public function execute(): void {
// Ask all questions for ceo interview
// Perform any other action required for ceo interview
echo "Ask CEO interview questions\n";
// Execute the next interview set while creating new struct
if ($this->nextInterview) {
$this->nextInterview->execute();
}
}
}
Demo
In the client chain the calls. This way we can define the sequence of the interview processing. Finally call the “execute” method for execution.
<?php
// demo.php
require __DIR__ . '/../../vendor/autoload.php';
use BigBoxCode\DesignPattern\ChainOfResponsibility\Interview\CeoInterview;
use BigBoxCode\DesignPattern\ChainOfResponsibility\Interview\HrInterview;
use BigBoxCode\DesignPattern\ChainOfResponsibility\Interview\PhoneInterview;
use BigBoxCode\DesignPattern\ChainOfResponsibility\Interview\TechnicalInterview;
$interviews = new PhoneInterview(new TechnicalInterview(new HrInterview(new CeoInterview(null))));
$interviews->execute();
Output
Output will be as below-
Ask phone interview questions
Ask technical interview questions
Ask hr interview questions
Ask ceo interview questions
Source Code
Use the following link to get the source code:
Other Code Implementations
Use the following links to check the Chain of Responsibility pattern implementation in other programming languages.