Design Pattern: Chain of Responsibility Pattern in PHP

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 an 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 execution, so that the child step classes can define their own execution process.
Create classes for each step and extend the base step class. Define the method for the 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 processingn";

        // 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 processingn";

        // 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 processingn";

        // 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:

Output will be as below-

Execute all operation for Step1 processing

Execute all operation for Step2 processing

Execute all operation for Step3 processing

Examples

Check the following examples-

Example #1: Caching Data

Here we are implementing 3 caching methods – Redis, Disk cache, and CDN. Based on certain conditions 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 the constructor.
Define getter methods for the fields.
<?php
// Data.php

namespace BigBoxCodeDesignPatternChainOfResponsibilityCache;


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 BigBoxCodeDesignPatternChainOfResponsibilityCache;


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 the data type is “CSS” or “JAVASCRIPT”, if the type matches then save the data in CDN. If it does not match then send it to the next step.
<?php
// CdnCacheHandler.php

namespace BigBoxCodeDesignPatternChainOfResponsibilityCache;


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 CDNn";
        } 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 the length of data is less than 1024. If less then save in Redis. Otherwise, send it to the next step.
<?php
// RedisCacheHandler.php

namespace BigBoxCodeDesignPatternChainOfResponsibilityCache;


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 Redisn";
        } 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 BigBoxCodeDesignPatternChainOfResponsibilityCache;


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 Diskn";
        } 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 BigBoxCodeDesignPatternChainOfResponsibilityCacheCdnCacheHandler;
use BigBoxCodeDesignPatternChainOfResponsibilityCacheData;
use BigBoxCodeDesignPatternChainOfResponsibilityCacheDATA_TYPE;
use BigBoxCodeDesignPatternChainOfResponsibilityCacheDiskCacheHandler;
use BigBoxCodeDesignPatternChainOfResponsibilityCacheRedisCacheHandler;


$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 BigBoxCodeDesignPatternChainOfResponsibilityInterview;


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 the steps for a phone interview and then call the next step(if the next step is available).
<?php
// PhoneInterview.php

namespace BigBoxCodeDesignPatternChainOfResponsibilityInterview;


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 questionsn";

        // 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 BigBoxCodeDesignPatternChainOfResponsibilityInterview;


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 questionsn";

        // Execute the next interview set while creating new struct
        if ($this->nextInterview) {
            $this->nextInterview->execute();
        }
    }

}

HR Interview

Create file “HrInterview.php”.
Define class “HrInterview”, and extend “Interview”.
In the execute method implementation, define steps for the HR interview and then call the next step(if the next step is available).
<?php
// HrInterview.php

namespace BigBoxCodeDesignPatternChainOfResponsibilityInterview;

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 questionsn";

        // Execute the next interview set while creating new struct
        if ($this->nextInterview) {
            $this->nextInterview->execute();
        }
    }

}

CEO Interview

Create file “CeoInterview.php”.
Create class “CeoInterview”, and extend “Interview” for this class.
In the execute method implementation, define steps for the CEO interview and then call the next step(if the next step is available).
<?php
// CeoInterview.php

namespace BigBoxCodeDesignPatternChainOfResponsibilityInterview;


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 questionsn";

        // 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 BigBoxCodeDesignPatternChainOfResponsibilityInterviewCeoInterview;
use BigBoxCodeDesignPatternChainOfResponsibilityInterviewHrInterview;
use BigBoxCodeDesignPatternChainOfResponsibilityInterviewPhoneInterview;
use BigBoxCodeDesignPatternChainOfResponsibilityInterviewTechnicalInterview;

$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.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.