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

Leave a Comment


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