3.1. Chain Of Responsibilities

3.1.1. Amaç

Bir isteme sıralı bir düzende cevap verecek bir nesneler zinciri oluşturmak. Eğer bir nesne bir istemi çözümleyemezse, bu istemi zincirdeki bir sonraki nesneye devreder.

3.1.2. Örnekler

  • logging framework, where each chain element decides autonomously what to 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

3.1.3. UML Diyagramı

Alt ChainOfResponsibility UML Diyagramı

3.1.4. Kod

Bu kodu Github üzerinde de bulabilirsiniz.

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}