3.1. Chain Of Responsibilities

3.1.1. Purpose

Usado para construir una cadena de objetos que permita manejar una llamada en orden secuencial. Si un objeto no puede manejar una llamada, delega la llamada al siguiente en la cadena, etc.

3.1.2. Examples

  • un logging framework, donde cada elemento de la cadena decide de forma autónoma qué hacer con un mensaje de log

  • un filtro de Spam

  • Cachear: el primer objeto es una instancia de, por ejemplo, una interfaz Memcached,si es que » falla » delega la llamada a la interfaz de la base de datos

3.1.3. Diagrama UML

Alt ChainOfResponsibility UML Diagram

3.1.4. Código

Puedes encontrar el código en GitHub

Handler.php

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

Responsible/FastStorage.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
 6
 7use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
 8use Psr\Http\Message\RequestInterface;
 9
10class HttpInMemoryCacheHandler extends Handler
11{
12    public function __construct(private array $data, ?Handler $successor = null)
13    {
14        parent::__construct($successor);
15    }
16
17    protected function processing(RequestInterface $request): ?string
18    {
19        $key = sprintf(
20            '%s?%s',
21            $request->getUri()->getPath(),
22            $request->getUri()->getQuery()
23        );
24
25        if ($request->getMethod() == 'GET' && isset($this->data[$key])) {
26            return $this->data[$key];
27        }
28
29        return null;
30    }
31}

Responsible/SlowStorage.php

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

3.1.5. Test

Tests/ChainTest.php

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