3.1. Chain Of Responsibilities

3.1.1. Purpose

Construire une chaîne d’objets pour traiter un appel dans un ordre séquentiel. Si un objet ne peut pas traiter un appel, il délègue l’appel au suivant dans la chaîne et ainsi de suite.

3.1.2. Examples

  • Logging Framework, dans lequel chaque élément de la chaîne décide lui-même de ce qu’il faut faire avec un message de journalisation.

  • Filtre anti-spam

  • Mise en cache : le premier objet est, par exemple, une instance d’une interface Memcached, s’il « manque », il délègue l’appel à l’interface de base de données.

3.1.3. Diagramme UML

Alt ChainOfResponsibility UML Diagram

3.1.4. Code

Vous pouvez également trouver ce code sur 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}