3.1. Chain Of Responsibilities
3.1.1. Purpose
To build a chain of objects to handle a call in sequential order. If oneobject cannot handle a call, it delegates the call to the next in thechain and so forth.
3.1.2. Examples
- logging framework, where each chain element decides autonomously whatto do with a log message
- a Spam filter
- Caching: first object is an instance of e.g. a Memcached Interface,if that “misses” it delegates the call to the database interface
- Yii Framework: CFilterChain is a chain of controller action filters.the executing point is passed from one filter to the next along thechain, and only if all filters say “yes”, the action can be invokedat last.
3.1.3. UML Diagram
3.1.4. Code
You can also find this code on GitHub
Handler.php
- <?php
- namespace DesignPatterns\Behavioral\ChainOfResponsibilities;
- use Psr\Http\Message\RequestInterface;
- use Psr\Http\Message\ResponseInterface;
- abstract class Handler
- {
- /**
- * @var Handler|null
- */
- private $successor = null;
- public function __construct(Handler $handler = null)
- {
- $this->successor = $handler;
- }
- /**
- * This approach by using a template method pattern ensures you that
- * each subclass will not forget to call the successor
- *
- * @param RequestInterface $request
- *
- * @return string|null
- */
- final public function handle(RequestInterface $request)
- {
- $processed = $this->processing($request);
- if ($processed === null) {
- // the request has not been processed by this handler => see the next
- if ($this->successor !== null) {
- $processed = $this->successor->handle($request);
- }
- }
- return $processed;
- }
- abstract protected function processing(RequestInterface $request);
- }
Responsible/FastStorage.php
- <?php
- namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
- use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
- use Psr\Http\Message\RequestInterface;
- class HttpInMemoryCacheHandler extends Handler
- {
- /**
- * @var array
- */
- private $data;
- /**
- * @param array $data
- * @param Handler|null $successor
- */
- public function __construct(array $data, Handler $successor = null)
- {
- parent::__construct($successor);
- $this->data = $data;
- }
- /**
- * @param RequestInterface $request
- *
- * @return string|null
- */
- protected function processing(RequestInterface $request)
- {
- $key = sprintf(
- '%s?%s',
- $request->getUri()->getPath(),
- $request->getUri()->getQuery()
- );
- if ($request->getMethod() == 'GET' && isset($this->data[$key])) {
- return $this->data[$key];
- }
- return null;
- }
- }
Responsible/SlowStorage.php
- <?php
- namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
- use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
- use Psr\Http\Message\RequestInterface;
- class SlowDatabaseHandler extends Handler
- {
- /**
- * @param RequestInterface $request
- *
- * @return string|null
- */
- protected function processing(RequestInterface $request)
- {
- // this is a mockup, in production code you would ask a slow (compared to in-memory) DB for the results
- return 'Hello World!';
- }
- }
3.1.5. Test
Tests/ChainTest.php
- <?php
- namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Tests;
- use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
- use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\HttpInMemoryCacheHandler;
- use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowDatabaseHandler;
- use PHPUnit\Framework\TestCase;
- class ChainTest extends TestCase
- {
- /**
- * @var Handler
- */
- private $chain;
- protected function setUp()
- {
- $this->chain = new HttpInMemoryCacheHandler(
- ['/foo/bar?index=1' => 'Hello In Memory!'],
- new SlowDatabaseHandler()
- );
- }
- public function testCanRequestKeyInFastStorage()
- {
- $uri = $this->createMock('Psr\Http\Message\UriInterface');
- $uri->method('getPath')->willReturn('/foo/bar');
- $uri->method('getQuery')->willReturn('index=1');
- $request = $this->createMock('Psr\Http\Message\RequestInterface');
- $request->method('getMethod')
- ->willReturn('GET');
- $request->method('getUri')->willReturn($uri);
- $this->assertEquals('Hello In Memory!', $this->chain->handle($request));
- }
- public function testCanRequestKeyInSlowStorage()
- {
- $uri = $this->createMock('Psr\Http\Message\UriInterface');
- $uri->method('getPath')->willReturn('/foo/baz');
- $uri->method('getQuery')->willReturn('');
- $request = $this->createMock('Psr\Http\Message\RequestInterface');
- $request->method('getMethod')
- ->willReturn('GET');
- $request->method('getUri')->willReturn($uri);
- $this->assertEquals('Hello World!', $this->chain->handle($request));
- }
- }