3.1. Chain Of Responsibilities

3.1.1. Purpose

Um eine Kette von Objekten zu bauen, die einen Aufruf in sequentieller Folge abzuarbeiten. Wenn ein Objekt den Aufruf nicht bearbeiten kann, delegiert es die Anfrage weiter an das nächste Objekt und so weiter. So lange, bis die Anfrage von einem Objekt erfolgreich abgearbeitet werden kann.

3.1.2. Examples

  • Logging Framework, in dem jedes Element der Kette automatisch entscheidet, was es mit einer Logmessage tun möchte

  • ein Spamfilter

  • Caching: das erste Objekt ist Beispielsweise eine Memcache-Instanz. Wenn es einen „Miss“ hat, dann wird an das nächste Element weitergeleitet, das eine Datenbank-Abfrage unternimmt

3.1.3. UML Diagramm

Alt ChainOfResponsibility UML Diagram

3.1.4. Code

Du kannst den Code auch auf GitHub einsehen

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. Теst

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}