Skip to content

Commit 2b4b3be

Browse files
author
Dominik Liebler
committed
PHP7 Chain Of Responsibilities
1 parent ea8c91a commit 2b4b3be

File tree

10 files changed

+178
-226
lines changed

10 files changed

+178
-226
lines changed

Behavioral/ChainOfResponsibilities/Handler.php

+16-51
Original file line numberDiff line numberDiff line change
@@ -2,77 +2,42 @@
22

33
namespace DesignPatterns\Behavioral\ChainOfResponsibilities;
44

5-
/**
6-
* Handler is a generic handler in the chain of responsibilities.
7-
*
8-
* Yes you could have a lighter CoR with a simpler handler but if you want your CoR
9-
* to be extendable and decoupled, it's a better idea to do things like that in real
10-
* situations. Usually, a CoR is meant to be changed everytime and evolves, that's
11-
* why we slice the workflow in little bits of code.
12-
*/
5+
use Psr\Http\Message\RequestInterface;
6+
use Psr\Http\Message\ResponseInterface;
7+
138
abstract class Handler
149
{
1510
/**
16-
* @var Handler
11+
* @var Handler|null
1712
*/
1813
private $successor = null;
1914

20-
/**
21-
* Append a responsibility to the end of chain.
22-
*
23-
* A prepend method could be done with the same spirit
24-
*
25-
* You could also send the successor in the constructor but in PHP that is a
26-
* bad idea because you have to remove the type-hint of the parameter because
27-
* the last handler has a null successor.
28-
*
29-
* And if you override the constructor, that Handler can no longer have a
30-
* successor. One solution is to provide a NullObject (see pattern).
31-
* It is more preferable to keep the constructor "free" to inject services
32-
* you need with the DiC of symfony2 for example.
33-
*
34-
* @param Handler $handler
35-
*/
36-
final public function append(Handler $handler)
15+
public function __construct(Handler $handler = null)
3716
{
38-
if (is_null($this->successor)) {
39-
$this->successor = $handler;
40-
} else {
41-
$this->successor->append($handler);
42-
}
17+
$this->successor = $handler;
4318
}
4419

4520
/**
46-
* Handle the request.
47-
*
4821
* This approach by using a template method pattern ensures you that
49-
* each subclass will not forget to call the successor. Besides, the returned
50-
* boolean value indicates you if the request have been processed or not.
22+
* each subclass will not forget to call the successor
5123
*
52-
* @param Request $req
24+
* @param RequestInterface $request
5325
*
54-
* @return bool
26+
* @return string|null
5527
*/
56-
final public function handle(Request $req)
28+
final public function handle(RequestInterface $request)
5729
{
58-
$req->forDebugOnly = get_called_class();
59-
$processed = $this->processing($req);
60-
if (!$processed) {
30+
$processed = $this->processing($request);
31+
32+
if ($processed === null) {
6133
// the request has not been processed by this handler => see the next
62-
if (!is_null($this->successor)) {
63-
$processed = $this->successor->handle($req);
34+
if ($this->successor !== null) {
35+
$processed = $this->successor->handle($request);
6436
}
6537
}
6638

6739
return $processed;
6840
}
6941

70-
/**
71-
* Each concrete handler has to implement the processing of the request.
72-
*
73-
* @param Request $req
74-
*
75-
* @return bool true if the request has been processed
76-
*/
77-
abstract protected function processing(Request $req);
42+
abstract protected function processing(RequestInterface $request);
7843
}

Behavioral/ChainOfResponsibilities/README.rst

-6
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,6 @@ Code
3333

3434
You can also find these code on `GitHub`_
3535

36-
Request.php
37-
38-
.. literalinclude:: Request.php
39-
:language: php
40-
:linenos:
41-
4236
Handler.php
4337

4438
.. literalinclude:: Handler.php

Behavioral/ChainOfResponsibilities/Request.php

-19
This file was deleted.

Behavioral/ChainOfResponsibilities/Responsible/FastStorage.php

-37
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
4+
5+
use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
6+
use Psr\Http\Message\RequestInterface;
7+
8+
class HttpInMemoryCacheHandler extends Handler
9+
{
10+
/**
11+
* @var array
12+
*/
13+
private $data;
14+
15+
/**
16+
* @param array $data
17+
* @param Handler|null $successor
18+
*/
19+
public function __construct(array $data, Handler $successor = null)
20+
{
21+
parent::__construct($successor);
22+
23+
$this->data = $data;
24+
}
25+
26+
/**
27+
* @param RequestInterface $request
28+
*
29+
* @return string|null
30+
*/
31+
protected function processing(RequestInterface $request)
32+
{
33+
$key = sprintf(
34+
'%s?%s',
35+
$request->getUri()->getPath(),
36+
$request->getUri()->getQuery()
37+
);
38+
39+
if ($request->getMethod() == 'GET' && isset($this->data[$key])) {
40+
return $this->data[$key];
41+
}
42+
43+
return null;
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
4+
5+
use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
6+
use Psr\Http\Message\RequestInterface;
7+
8+
class SlowDatabaseHandler extends Handler
9+
{
10+
/**
11+
* @param RequestInterface $request
12+
*
13+
* @return string|null
14+
*/
15+
protected function processing(RequestInterface $request)
16+
{
17+
// this is a mockup, in production code you would ask a slow (compared to in-memory) DB for the results
18+
19+
return 'Hello World!';
20+
}
21+
}

Behavioral/ChainOfResponsibilities/Responsible/SlowStorage.php

-44
This file was deleted.

Behavioral/ChainOfResponsibilities/Tests/ChainTest.php

+27-57
Original file line numberDiff line numberDiff line change
@@ -2,80 +2,50 @@
22

33
namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Tests;
44

5-
use DesignPatterns\Behavioral\ChainOfResponsibilities\Request;
6-
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
7-
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\FastStorage;
8-
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowStorage;
5+
use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
6+
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\HttpInMemoryCacheHandler;
7+
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowDatabaseHandler;
98

10-
/**
11-
* ChainTest tests the CoR.
12-
*/
139
class ChainTest extends \PHPUnit_Framework_TestCase
1410
{
1511
/**
16-
* @var FastStorage
12+
* @var Handler
1713
*/
18-
protected $chain;
14+
private $chain;
1915

2016
protected function setUp()
2117
{
22-
$this->chain = new FastStorage(array('bar' => 'baz'));
23-
$this->chain->append(new SlowStorage(array('bar' => 'baz', 'foo' => 'bar')));
24-
}
25-
26-
public function makeRequest()
27-
{
28-
$request = new Request();
29-
$request->verb = 'get';
30-
31-
return array(
32-
array($request),
18+
$this->chain = new HttpInMemoryCacheHandler(
19+
['/foo/bar?index=1' => 'Hello In Memory!'],
20+
new SlowDatabaseHandler()
3321
);
3422
}
3523

36-
/**
37-
* @dataProvider makeRequest
38-
*/
39-
public function testFastStorage($request)
24+
public function testCanRequestKeyInFastStorage()
4025
{
41-
$request->key = 'bar';
42-
$ret = $this->chain->handle($request);
43-
44-
$this->assertTrue($ret);
45-
$this->assertObjectHasAttribute('response', $request);
46-
$this->assertEquals('baz', $request->response);
47-
// despite both handle owns the 'bar' key, the FastStorage is responding first
48-
$className = 'DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\FastStorage';
49-
$this->assertEquals($className, $request->forDebugOnly);
50-
}
26+
$uri = $this->createMock('Psr\Http\Message\UriInterface');
27+
$uri->method('getPath')->willReturn('/foo/bar');
28+
$uri->method('getQuery')->willReturn('index=1');
5129

52-
/**
53-
* @dataProvider makeRequest
54-
*/
55-
public function testSlowStorage($request)
56-
{
57-
$request->key = 'foo';
58-
$ret = $this->chain->handle($request);
30+
$request = $this->createMock('Psr\Http\Message\RequestInterface');
31+
$request->method('getMethod')
32+
->willReturn('GET');
33+
$request->method('getUri')->willReturn($uri);
5934

60-
$this->assertTrue($ret);
61-
$this->assertObjectHasAttribute('response', $request);
62-
$this->assertEquals('bar', $request->response);
63-
// FastStorage has no 'foo' key, the SlowStorage is responding
64-
$className = 'DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowStorage';
65-
$this->assertEquals($className, $request->forDebugOnly);
35+
$this->assertEquals('Hello In Memory!', $this->chain->handle($request));
6636
}
6737

68-
/**
69-
* @dataProvider makeRequest
70-
*/
71-
public function testFailure($request)
38+
public function testCanRequestKeyInSlowStorage()
7239
{
73-
$request->key = 'kurukuku';
74-
$ret = $this->chain->handle($request);
40+
$uri = $this->createMock('Psr\Http\Message\UriInterface');
41+
$uri->method('getPath')->willReturn('/foo/baz');
42+
$uri->method('getQuery')->willReturn('');
43+
44+
$request = $this->createMock('Psr\Http\Message\RequestInterface');
45+
$request->method('getMethod')
46+
->willReturn('GET');
47+
$request->method('getUri')->willReturn($uri);
7548

76-
$this->assertFalse($ret);
77-
// the last responsible :
78-
$className = 'DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowStorage';
79-
$this->assertEquals($className, $request->forDebugOnly);
49+
$this->assertEquals('Hello World!', $this->chain->handle($request));
8050
}
8151
}

composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
],
1010
"minimum-stability": "stable",
1111
"require": {
12-
"php": ">=7.0"
12+
"php": ">=7.0",
13+
"psr/http-message": "^1.0"
1314
},
1415
"require-dev": {
1516
"phpunit/phpunit": ">=5.5.4",

0 commit comments

Comments
 (0)