3.1. Łańcuch zobowiązań (Chain Of Responsibilities)¶
3.1.1. Przeznaczenie¶
Zbudowanie łańcucha obiektów przetwarzających żądanie w sposób sekwencyjny. Jeżeli jeden obiekt nie może obsłużyć żądania, przekazuje je do następnego w łańcuchu.
3.1.2. Przykłady¶
- Framework do zapisywania logów, gdzie każdy obiekt z łańcucha odpowiedzialności decyduje co zrobić z danym wpisem.
- filtr antyspamowy
- Zapis w pamięci podręcznej (caching): pierwszy obiekt jest na przykład instancją implementującą interfejs Memcached - jeżeli nie może zapisać w pamięci podręcznej żądania, wtedy przekazuje je do obiektu implementującego interfejs zapisu w bazie danych.
3.1.3. Diagram UML¶

3.1.4. Kod¶
Ten kod znajdziesz również na GitHub.
Handler.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\ChainOfResponsibilities;
use Psr\Http\Message\RequestInterface;
abstract class Handler
{
public function __construct(private ?Handler $successor = null)
{
}
/**
* This approach by using a template method pattern ensures you that
* each subclass will not forget to call the successor
*/
final public function handle(RequestInterface $request): ?string
{
$processed = $this->processing($request);
if ($processed === null && $this->successor !== null) {
// the request has not been processed by this handler => see the next
$processed = $this->successor->handle($request);
}
return $processed;
}
abstract protected function processing(RequestInterface $request): ?string;
}
|
Responsible/FastStorage.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
use Psr\Http\Message\RequestInterface;
class HttpInMemoryCacheHandler extends Handler
{
public function __construct(private array $data, ?Handler $successor = null)
{
parent::__construct($successor);
}
protected function processing(RequestInterface $request): ?string
{
$key = sprintf(
'%s?%s',
$request->getUri()->getPath(),
$request->getUri()->getQuery()
);
if ($request->getMethod() == 'GET' && isset($this->data[$key])) {
return $this->data[$key];
}
return null;
}
}
|
Responsible/SlowStorage.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
use Psr\Http\Message\RequestInterface;
class SlowDatabaseHandler extends Handler
{
protected function processing(RequestInterface $request): ?string
{
// this is a mockup, in production code you would ask a slow (compared to in-memory) DB for the results
return 'Hello World!';
}
}
|
3.1.5. Testy¶
Tests/ChainTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | <?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Tests;
use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\HttpInMemoryCacheHandler;
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowDatabaseHandler;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
class ChainTest extends TestCase
{
private Handler $chain;
protected function setUp(): void
{
$this->chain = new HttpInMemoryCacheHandler(
['/foo/bar?index=1' => 'Hello In Memory!'],
new SlowDatabaseHandler()
);
}
public function testCanRequestKeyInFastStorage()
{
$uri = $this->createMock(UriInterface::class);
$uri->method('getPath')->willReturn('/foo/bar');
$uri->method('getQuery')->willReturn('index=1');
$request = $this->createMock(RequestInterface::class);
$request->method('getMethod')
->willReturn('GET');
$request->method('getUri')->willReturn($uri);
$this->assertSame('Hello In Memory!', $this->chain->handle($request));
}
public function testCanRequestKeyInSlowStorage()
{
$uri = $this->createMock(UriInterface::class);
$uri->method('getPath')->willReturn('/foo/baz');
$uri->method('getQuery')->willReturn('');
$request = $this->createMock(RequestInterface::class);
$request->method('getMethod')
->willReturn('GET');
$request->method('getUri')->willReturn($uri);
$this->assertSame('Hello World!', $this->chain->handle($request));
}
}
|