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

Alt ChainOfResponsibility UML Diagram

3.1.4. Code

You can also find this code on GitHub

Handler.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Behavioral\ChainOfResponsibilities;
  4.  
  5. use Psr\Http\Message\RequestInterface;
  6. use Psr\Http\Message\ResponseInterface;
  7.  
  8. abstract class Handler
  9. {
  10. /**
  11. * @var Handler|null
  12. */
  13. private $successor = null;
  14.  
  15. public function __construct(Handler $handler = null)
  16. {
  17. $this->successor = $handler;
  18. }
  19.  
  20. /**
  21. * This approach by using a template method pattern ensures you that
  22. * each subclass will not forget to call the successor
  23. *
  24. * @param RequestInterface $request
  25. *
  26. * @return string|null
  27. */
  28. final public function handle(RequestInterface $request)
  29. {
  30. $processed = $this->processing($request);
  31.  
  32. if ($processed === null) {
  33. // the request has not been processed by this handler => see the next
  34. if ($this->successor !== null) {
  35. $processed = $this->successor->handle($request);
  36. }
  37. }
  38.  
  39. return $processed;
  40. }
  41.  
  42. abstract protected function processing(RequestInterface $request);
  43. }

Responsible/FastStorage.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
  4.  
  5. use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
  6. use Psr\Http\Message\RequestInterface;
  7.  
  8. class HttpInMemoryCacheHandler extends Handler
  9. {
  10. /**
  11. * @var array
  12. */
  13. private $data;
  14.  
  15. /**
  16. * @param array $data
  17. * @param Handler|null $successor
  18. */
  19. public function __construct(array $data, Handler $successor = null)
  20. {
  21. parent::__construct($successor);
  22.  
  23. $this->data = $data;
  24. }
  25.  
  26. /**
  27. * @param RequestInterface $request
  28. *
  29. * @return string|null
  30. */
  31. protected function processing(RequestInterface $request)
  32. {
  33. $key = sprintf(
  34. '%s?%s',
  35. $request->getUri()->getPath(),
  36. $request->getUri()->getQuery()
  37. );
  38.  
  39. if ($request->getMethod() == 'GET' && isset($this->data[$key])) {
  40. return $this->data[$key];
  41. }
  42.  
  43. return null;
  44. }
  45. }

Responsible/SlowStorage.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
  4.  
  5. use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
  6. use Psr\Http\Message\RequestInterface;
  7.  
  8. class SlowDatabaseHandler extends Handler
  9. {
  10. /**
  11. * @param RequestInterface $request
  12. *
  13. * @return string|null
  14. */
  15. protected function processing(RequestInterface $request)
  16. {
  17. // this is a mockup, in production code you would ask a slow (compared to in-memory) DB for the results
  18.  
  19. return 'Hello World!';
  20. }
  21. }

3.1.5. Test

Tests/ChainTest.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Tests;
  4.  
  5. use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
  6. use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\HttpInMemoryCacheHandler;
  7. use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowDatabaseHandler;
  8. use PHPUnit\Framework\TestCase;
  9.  
  10. class ChainTest extends TestCase
  11. {
  12. /**
  13. * @var Handler
  14. */
  15. private $chain;
  16.  
  17. protected function setUp()
  18. {
  19. $this->chain = new HttpInMemoryCacheHandler(
  20. ['/foo/bar?index=1' => 'Hello In Memory!'],
  21. new SlowDatabaseHandler()
  22. );
  23. }
  24.  
  25. public function testCanRequestKeyInFastStorage()
  26. {
  27. $uri = $this->createMock('Psr\Http\Message\UriInterface');
  28. $uri->method('getPath')->willReturn('/foo/bar');
  29. $uri->method('getQuery')->willReturn('index=1');
  30.  
  31. $request = $this->createMock('Psr\Http\Message\RequestInterface');
  32. $request->method('getMethod')
  33. ->willReturn('GET');
  34. $request->method('getUri')->willReturn($uri);
  35.  
  36. $this->assertEquals('Hello In Memory!', $this->chain->handle($request));
  37. }
  38.  
  39. public function testCanRequestKeyInSlowStorage()
  40. {
  41. $uri = $this->createMock('Psr\Http\Message\UriInterface');
  42. $uri->method('getPath')->willReturn('/foo/baz');
  43. $uri->method('getQuery')->willReturn('');
  44.  
  45. $request = $this->createMock('Psr\Http\Message\RequestInterface');
  46. $request->method('getMethod')
  47. ->willReturn('GET');
  48. $request->method('getUri')->willReturn($uri);
  49.  
  50. $this->assertEquals('Hello World!', $this->chain->handle($request));
  51. }
  52. }