diff --git a/ChainOfResponsibilities/ChainOfResponsibilities.php b/ChainOfResponsibilities/ChainOfResponsibilities.php index 82ba06e..5b2686d 100644 --- a/ChainOfResponsibilities/ChainOfResponsibilities.php +++ b/ChainOfResponsibilities/ChainOfResponsibilities.php @@ -10,6 +10,8 @@ namespace DesignPatterns; * in the chain and so forth * * Examples: + * - logging framework + * - 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 * - Yii Framework: CFilterChain is a chain of controller action filters. the executing point is passed from one filter @@ -17,6 +19,8 @@ namespace DesignPatterns; * */ +// the idea is good but in general, the Handler component in this pattern +// is an abstract class which makes much more of the work interface KeyValueStorage { public function get($key); diff --git a/ChainOfResponsibilities/Handler.php b/ChainOfResponsibilities/Handler.php new file mode 100644 index 0000000..907e6a9 --- /dev/null +++ b/ChainOfResponsibilities/Handler.php @@ -0,0 +1,72 @@ +successor)) { + $this->successor = $handler; + } else { + $this->successor->append($handler); + } + } + + /** + * Handle the request. + * + * This approach by using a template method pattern ensures you that + * each subclass will not forget to call the successor. Beside, the returned + * boolean value indicates you if the request have been processed or not. + */ + final public function handle(Request $req) + { + $req->forDebugOnly = get_called_class(); + $processed = $this->processing($req); + if (!$processed) { + // the request has not been processed by this handler => see the next + if (!is_null($this->successor)) { + $processed = $this->successor->handle($req); + } + } + + return $processed; + } + + /** + * Each concrete handler has to implement the processing of the request + * + * @return bool true if the request has been processed + */ + abstract protected function processing(Request $req); +} \ No newline at end of file diff --git a/ChainOfResponsibilities/Request.php b/ChainOfResponsibilities/Request.php new file mode 100644 index 0000000..6083c57 --- /dev/null +++ b/ChainOfResponsibilities/Request.php @@ -0,0 +1,23 @@ +_data = $data; + } + + protected function processing(Request $req) + { + if ('get' === $req->verb) { + if (array_key_exists($req->key, $this->_data)) { + // the handler IS responsible and then processes the request + $req->response = $this->_data[$req->key]; + // instead of returning true, I could return the value but it proves + // to be a bad idea. What if the value IS "false" ? + return true; + } + } + + return false; + } + +} diff --git a/ChainOfResponsibilities/Responsible/SlowStorage.php b/ChainOfResponsibilities/Responsible/SlowStorage.php new file mode 100644 index 0000000..79635bf --- /dev/null +++ b/ChainOfResponsibilities/Responsible/SlowStorage.php @@ -0,0 +1,44 @@ +_data = $data; + } + + protected function processing(Request $req) + { + if ('get' === $req->verb) { + if (array_key_exists($req->key, $this->_data)) { + $req->response = $this->_data[$req->key]; + return true; + } + } + + return false; + } + +} diff --git a/Tests/ChainOfResponsibilities/ChainTest.php b/Tests/ChainOfResponsibilities/ChainTest.php new file mode 100644 index 0000000..337559b --- /dev/null +++ b/Tests/ChainOfResponsibilities/ChainTest.php @@ -0,0 +1,78 @@ +chain = new Responsible\FastStorage(array('bar' => 'baz')); + $this->chain->append(new Responsible\SlowStorage(array('bar' => 'baz', 'foo' => 'bar'))); + } + + public function makeRequest() + { + $request = new Request(); + $request->verb = 'get'; + return array( + array($request) + ); + } + + /** + * @dataProvider makeRequest + */ + public function testFastStorage($request) + { + $request->key = 'bar'; + $ret = $this->chain->handle($request); + + $this->assertTrue($ret); + $this->assertObjectHasAttribute('response', $request); + $this->assertEquals('baz', $request->response); + // despite both handle owns the 'bar' key, the FastStorage is responding first + $this->assertEquals('DesignPatterns\ChainOfResponsibilities\Responsible\FastStorage', $request->forDebugOnly); + } + + /** + * @dataProvider makeRequest + */ + public function testSlowStorage($request) + { + $request->key = 'foo'; + $ret = $this->chain->handle($request); + + $this->assertTrue($ret); + $this->assertObjectHasAttribute('response', $request); + $this->assertEquals('bar', $request->response); + // FastStorage has no 'foo' key, the SlowStorage is responding + $this->assertEquals('DesignPatterns\ChainOfResponsibilities\Responsible\SlowStorage', $request->forDebugOnly); + } + + /** + * @dataProvider makeRequest + */ + public function testFailure($request) + { + $request->key = 'kurukuku'; + $ret = $this->chain->handle($request); + + $this->assertFalse($ret); + // the last rsponsible : + $this->assertEquals('DesignPatterns\ChainOfResponsibilities\Responsible\SlowStorage', $request->forDebugOnly); + } + +} \ No newline at end of file