Merged branch master into translate-template
@@ -2,77 +2,42 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\ChainOfResponsibilities;
|
||||
|
||||
/**
|
||||
* Handler is a generic handler in the chain of responsibilities.
|
||||
*
|
||||
* Yes you could have a lighter CoR with a simpler handler but if you want your CoR
|
||||
* to be extendable and decoupled, it's a better idea to do things like that in real
|
||||
* situations. Usually, a CoR is meant to be changed everytime and evolves, that's
|
||||
* why we slice the workflow in little bits of code.
|
||||
*/
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
abstract class Handler
|
||||
{
|
||||
/**
|
||||
* @var Handler
|
||||
* @var Handler|null
|
||||
*/
|
||||
private $successor = null;
|
||||
|
||||
/**
|
||||
* Append a responsibility to the end of chain.
|
||||
*
|
||||
* A prepend method could be done with the same spirit
|
||||
*
|
||||
* You could also send the successor in the constructor but in PHP that is a
|
||||
* bad idea because you have to remove the type-hint of the parameter because
|
||||
* the last handler has a null successor.
|
||||
*
|
||||
* And if you override the constructor, that Handler can no longer have a
|
||||
* successor. One solution is to provide a NullObject (see pattern).
|
||||
* It is more preferable to keep the constructor "free" to inject services
|
||||
* you need with the DiC of symfony2 for example.
|
||||
*
|
||||
* @param Handler $handler
|
||||
*/
|
||||
final public function append(Handler $handler)
|
||||
public function __construct(Handler $handler = null)
|
||||
{
|
||||
if (is_null($this->successor)) {
|
||||
$this->successor = $handler;
|
||||
} else {
|
||||
$this->successor->append($handler);
|
||||
}
|
||||
$this->successor = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the request.
|
||||
*
|
||||
* This approach by using a template method pattern ensures you that
|
||||
* each subclass will not forget to call the successor. Besides, the returned
|
||||
* boolean value indicates you if the request have been processed or not.
|
||||
* each subclass will not forget to call the successor
|
||||
*
|
||||
* @param Request $req
|
||||
* @param RequestInterface $request
|
||||
*
|
||||
* @return bool
|
||||
* @return string|null
|
||||
*/
|
||||
final public function handle(Request $req)
|
||||
final public function handle(RequestInterface $request)
|
||||
{
|
||||
$req->forDebugOnly = get_called_class();
|
||||
$processed = $this->processing($req);
|
||||
if (!$processed) {
|
||||
$processed = $this->processing($request);
|
||||
|
||||
if ($processed === null) {
|
||||
// the request has not been processed by this handler => see the next
|
||||
if (!is_null($this->successor)) {
|
||||
$processed = $this->successor->handle($req);
|
||||
if ($this->successor !== null) {
|
||||
$processed = $this->successor->handle($request);
|
||||
}
|
||||
}
|
||||
|
||||
return $processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Each concrete handler has to implement the processing of the request.
|
||||
*
|
||||
* @param Request $req
|
||||
*
|
||||
* @return bool true if the request has been processed
|
||||
*/
|
||||
abstract protected function processing(Request $req);
|
||||
abstract protected function processing(RequestInterface $request);
|
||||
}
|
||||
|
@@ -33,12 +33,6 @@ Code
|
||||
|
||||
You can also find these code on `GitHub`_
|
||||
|
||||
Request.php
|
||||
|
||||
.. literalinclude:: Request.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
Handler.php
|
||||
|
||||
.. literalinclude:: Handler.php
|
||||
|
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\ChainOfResponsibilities;
|
||||
|
||||
/**
|
||||
* Request is a request which goes through the chain of responsibilities.
|
||||
*
|
||||
* About the request: Sometimes, you don't need an object, just an integer or
|
||||
* an array. But in this case of a full example, I've made a class to illustrate
|
||||
* this important idea in the CoR (Chain of Responsibilities). In the real world,
|
||||
* I recommend to always use a class, even a \stdClass if you want, it proves
|
||||
* to be more adaptive because a single handler doesn't know much about the
|
||||
* outside world and it is more difficult if, one day, you want to add some
|
||||
* criterion in a decision process.
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
// getter and setter but I don't want to generate too much noise in handlers
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
|
||||
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Request;
|
||||
|
||||
/**
|
||||
* Class FastStorage.
|
||||
*/
|
||||
class FastStorage extends Handler
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct($data = array())
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
|
||||
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
class HttpInMemoryCacheHandler extends Handler
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param Handler|null $successor
|
||||
*/
|
||||
public function __construct(array $data, Handler $successor = null)
|
||||
{
|
||||
parent::__construct($successor);
|
||||
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function processing(RequestInterface $request)
|
||||
{
|
||||
$key = sprintf(
|
||||
'%s?%s',
|
||||
$request->getUri()->getPath(),
|
||||
$request->getUri()->getQuery()
|
||||
);
|
||||
|
||||
if ($request->getMethod() == 'GET' && isset($this->data[$key])) {
|
||||
return $this->data[$key];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
|
||||
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
class SlowDatabaseHandler extends Handler
|
||||
{
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function processing(RequestInterface $request)
|
||||
{
|
||||
// this is a mockup, in production code you would ask a slow (compared to in-memory) DB for the results
|
||||
|
||||
return 'Hello World!';
|
||||
}
|
||||
}
|
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
|
||||
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Request;
|
||||
|
||||
/**
|
||||
* This is mostly the same code as FastStorage but in fact, it may greatly differs.
|
||||
*
|
||||
* One important fact about CoR: each item in the chain MUST NOT assume its position
|
||||
* in the chain. A CoR is not responsible if the request is not handled UNLESS
|
||||
* you make an "ExceptionHandler" which throws exception if the request goes there.
|
||||
*
|
||||
* To be really extendable, each handler doesn't know if there is something after it.
|
||||
*/
|
||||
class SlowStorage extends Handler
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct($data = array())
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
@@ -2,80 +2,50 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Tests;
|
||||
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Request;
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\FastStorage;
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowStorage;
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\HttpInMemoryCacheHandler;
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowDatabaseHandler;
|
||||
|
||||
/**
|
||||
* ChainTest tests the CoR.
|
||||
*/
|
||||
class ChainTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var FastStorage
|
||||
* @var Handler
|
||||
*/
|
||||
protected $chain;
|
||||
private $chain;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->chain = new FastStorage(array('bar' => 'baz'));
|
||||
$this->chain->append(new SlowStorage(array('bar' => 'baz', 'foo' => 'bar')));
|
||||
}
|
||||
|
||||
public function makeRequest()
|
||||
{
|
||||
$request = new Request();
|
||||
$request->verb = 'get';
|
||||
|
||||
return array(
|
||||
array($request),
|
||||
$this->chain = new HttpInMemoryCacheHandler(
|
||||
['/foo/bar?index=1' => 'Hello In Memory!'],
|
||||
new SlowDatabaseHandler()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider makeRequest
|
||||
*/
|
||||
public function testFastStorage($request)
|
||||
public function testCanRequestKeyInFastStorage()
|
||||
{
|
||||
$request->key = 'bar';
|
||||
$ret = $this->chain->handle($request);
|
||||
$uri = $this->createMock('Psr\Http\Message\UriInterface');
|
||||
$uri->method('getPath')->willReturn('/foo/bar');
|
||||
$uri->method('getQuery')->willReturn('index=1');
|
||||
|
||||
$this->assertTrue($ret);
|
||||
$this->assertObjectHasAttribute('response', $request);
|
||||
$this->assertEquals('baz', $request->response);
|
||||
// despite both handle owns the 'bar' key, the FastStorage is responding first
|
||||
$className = 'DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\FastStorage';
|
||||
$this->assertEquals($className, $request->forDebugOnly);
|
||||
$request = $this->createMock('Psr\Http\Message\RequestInterface');
|
||||
$request->method('getMethod')
|
||||
->willReturn('GET');
|
||||
$request->method('getUri')->willReturn($uri);
|
||||
|
||||
$this->assertEquals('Hello In Memory!', $this->chain->handle($request));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider makeRequest
|
||||
*/
|
||||
public function testSlowStorage($request)
|
||||
public function testCanRequestKeyInSlowStorage()
|
||||
{
|
||||
$request->key = 'foo';
|
||||
$ret = $this->chain->handle($request);
|
||||
$uri = $this->createMock('Psr\Http\Message\UriInterface');
|
||||
$uri->method('getPath')->willReturn('/foo/baz');
|
||||
$uri->method('getQuery')->willReturn('');
|
||||
|
||||
$this->assertTrue($ret);
|
||||
$this->assertObjectHasAttribute('response', $request);
|
||||
$this->assertEquals('bar', $request->response);
|
||||
// FastStorage has no 'foo' key, the SlowStorage is responding
|
||||
$className = 'DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowStorage';
|
||||
$this->assertEquals($className, $request->forDebugOnly);
|
||||
}
|
||||
$request = $this->createMock('Psr\Http\Message\RequestInterface');
|
||||
$request->method('getMethod')
|
||||
->willReturn('GET');
|
||||
$request->method('getUri')->willReturn($uri);
|
||||
|
||||
/**
|
||||
* @dataProvider makeRequest
|
||||
*/
|
||||
public function testFailure($request)
|
||||
{
|
||||
$request->key = 'kurukuku';
|
||||
$ret = $this->chain->handle($request);
|
||||
|
||||
$this->assertFalse($ret);
|
||||
// the last responsible :
|
||||
$className = 'DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowStorage';
|
||||
$this->assertEquals($className, $request->forDebugOnly);
|
||||
$this->assertEquals('Hello World!', $this->chain->handle($request));
|
||||
}
|
||||
}
|
||||
|
@@ -4,14 +4,14 @@ namespace DesignPatterns\Behavioral\Command;
|
||||
|
||||
/**
|
||||
* This concrete command tweaks receiver to add current date to messages
|
||||
* invoker just knows that it can call "execute".
|
||||
* invoker just knows that it can call "execute"
|
||||
*/
|
||||
class AddMessageDateCommand implements UndoableCommandInterface
|
||||
{
|
||||
/**
|
||||
* @var Receiver
|
||||
*/
|
||||
protected $output;
|
||||
private $output;
|
||||
|
||||
/**
|
||||
* Each concrete command is built with different receivers.
|
||||
|
@@ -2,9 +2,6 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\Command;
|
||||
|
||||
/**
|
||||
* class CommandInterface.
|
||||
*/
|
||||
interface CommandInterface
|
||||
{
|
||||
/**
|
||||
|
@@ -4,18 +4,18 @@ namespace DesignPatterns\Behavioral\Command;
|
||||
|
||||
/**
|
||||
* This concrete command calls "print" on the Receiver, but an external
|
||||
* invoker just knows that it can call "execute".
|
||||
* invoker just knows that it can call "execute"
|
||||
*/
|
||||
class HelloCommand implements CommandInterface
|
||||
{
|
||||
/**
|
||||
* @var Receiver
|
||||
*/
|
||||
protected $output;
|
||||
private $output;
|
||||
|
||||
/**
|
||||
* Each concrete command is built with different receivers.
|
||||
* There can be one, many or completely no receivers, but there can be other commands in the parameters.
|
||||
* There can be one, many or completely no receivers, but there can be other commands in the parameters
|
||||
*
|
||||
* @param Receiver $console
|
||||
*/
|
||||
@@ -29,8 +29,7 @@ class HelloCommand implements CommandInterface
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
// sometimes, there is no receiver and this is the command which
|
||||
// does all the work
|
||||
// sometimes, there is no receiver and this is the command which does all the work
|
||||
$this->output->write('Hello World');
|
||||
}
|
||||
}
|
||||
|
@@ -11,11 +11,11 @@ class Invoker
|
||||
/**
|
||||
* @var CommandInterface
|
||||
*/
|
||||
protected $command;
|
||||
private $command;
|
||||
|
||||
/**
|
||||
* In the invoker we find this kind of method for subscribing the command.
|
||||
* There can be also a stack, a list, a fixed set...
|
||||
* in the invoker we find this kind of method for subscribing the command
|
||||
* There can be also a stack, a list, a fixed set ...
|
||||
*
|
||||
* @param CommandInterface $cmd
|
||||
*/
|
||||
@@ -25,12 +25,10 @@ class Invoker
|
||||
}
|
||||
|
||||
/**
|
||||
* executes the command.
|
||||
* executes the command; the invoker is the same whatever is the command
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
// here is a key feature of the invoker
|
||||
// the invoker is the same whatever is the command
|
||||
$this->command->execute();
|
||||
}
|
||||
}
|
||||
|
@@ -7,14 +7,20 @@ namespace DesignPatterns\Behavioral\Command;
|
||||
*/
|
||||
class Receiver
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $enableDate = false;
|
||||
|
||||
private $output = array();
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $output = [];
|
||||
|
||||
/**
|
||||
* @param string $str
|
||||
*/
|
||||
public function write($str)
|
||||
public function write(string $str)
|
||||
{
|
||||
if ($this->enableDate) {
|
||||
$str .= ' ['.date('Y-m-d').']';
|
||||
@@ -23,13 +29,13 @@ class Receiver
|
||||
$this->output[] = $str;
|
||||
}
|
||||
|
||||
public function getOutput()
|
||||
public function getOutput(): string
|
||||
{
|
||||
return implode("\n", $this->output);
|
||||
return join("\n", $this->output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable receiver to display message date.
|
||||
* Enable receiver to display message date
|
||||
*/
|
||||
public function enableDate()
|
||||
{
|
||||
@@ -37,7 +43,7 @@ class Receiver
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable receiver to display message date.
|
||||
* Disable receiver to display message date
|
||||
*/
|
||||
public function disableDate()
|
||||
{
|
||||
|
@@ -6,31 +6,15 @@ use DesignPatterns\Behavioral\Command\HelloCommand;
|
||||
use DesignPatterns\Behavioral\Command\Invoker;
|
||||
use DesignPatterns\Behavioral\Command\Receiver;
|
||||
|
||||
/**
|
||||
* CommandTest has the role of the Client in the Command Pattern.
|
||||
*/
|
||||
class CommandTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var Invoker
|
||||
*/
|
||||
protected $invoker;
|
||||
|
||||
/**
|
||||
* @var Receiver
|
||||
*/
|
||||
protected $receiver;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->invoker = new Invoker();
|
||||
$this->receiver = new Receiver();
|
||||
}
|
||||
|
||||
public function testInvocation()
|
||||
{
|
||||
$this->invoker->setCommand(new HelloCommand($this->receiver));
|
||||
$this->invoker->run();
|
||||
$this->assertEquals($this->receiver->getOutput(), 'Hello World');
|
||||
$invoker = new Invoker();
|
||||
$receiver = new Receiver();
|
||||
|
||||
$invoker->setCommand(new HelloCommand($receiver));
|
||||
$invoker->run();
|
||||
$this->assertEquals($receiver->getOutput(), 'Hello World');
|
||||
}
|
||||
}
|
||||
|
@@ -6,44 +6,27 @@ use DesignPatterns\Behavioral\Command\AddMessageDateCommand;
|
||||
use DesignPatterns\Behavioral\Command\HelloCommand;
|
||||
use DesignPatterns\Behavioral\Command\Invoker;
|
||||
use DesignPatterns\Behavioral\Command\Receiver;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
/**
|
||||
* UndoableCommandTest has the role of the Client in the Command Pattern.
|
||||
*/
|
||||
class UndoableCommandTest extends PHPUnit_Framework_TestCase
|
||||
class UndoableCommandTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var Invoker
|
||||
*/
|
||||
protected $invoker;
|
||||
|
||||
/**
|
||||
* @var Receiver
|
||||
*/
|
||||
protected $receiver;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->invoker = new Invoker();
|
||||
$this->receiver = new Receiver();
|
||||
}
|
||||
|
||||
public function testInvocation()
|
||||
{
|
||||
$this->invoker->setCommand(new HelloCommand($this->receiver));
|
||||
$this->invoker->run();
|
||||
$this->assertEquals($this->receiver->getOutput(), 'Hello World');
|
||||
$invoker = new Invoker();
|
||||
$receiver = new Receiver();
|
||||
|
||||
$messageDateCommand = new AddMessageDateCommand($this->receiver);
|
||||
$invoker->setCommand(new HelloCommand($receiver));
|
||||
$invoker->run();
|
||||
$this->assertEquals($receiver->getOutput(), 'Hello World');
|
||||
|
||||
$messageDateCommand = new AddMessageDateCommand($receiver);
|
||||
$messageDateCommand->execute();
|
||||
|
||||
$this->invoker->run();
|
||||
$this->assertEquals($this->receiver->getOutput(), "Hello World\nHello World [".date('Y-m-d').']');
|
||||
$invoker->run();
|
||||
$this->assertEquals($receiver->getOutput(), "Hello World\nHello World [".date('Y-m-d').']');
|
||||
|
||||
$messageDateCommand->undo();
|
||||
|
||||
$this->invoker->run();
|
||||
$this->assertEquals($this->receiver->getOutput(), "Hello World\nHello World [".date('Y-m-d')."]\nHello World");
|
||||
$invoker->run();
|
||||
$this->assertEquals($receiver->getOutput(), "Hello World\nHello World [".date('Y-m-d')."]\nHello World");
|
||||
}
|
||||
}
|
||||
|
@@ -2,14 +2,10 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\Command;
|
||||
|
||||
/**
|
||||
* Interface UndoableCommandInterface.
|
||||
*/
|
||||
interface UndoableCommandInterface extends CommandInterface
|
||||
{
|
||||
/**
|
||||
* This method is used to undo change made by command execution
|
||||
* The Receiver goes in the constructor.
|
||||
*/
|
||||
public function undo();
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ namespace DesignPatterns\Behavioral\Mediator;
|
||||
|
||||
/**
|
||||
* Colleague is an abstract colleague who works together but he only knows
|
||||
* the Mediator, not other colleague.
|
||||
* the Mediator, not other colleagues
|
||||
*/
|
||||
abstract class Colleague
|
||||
{
|
||||
@@ -13,21 +13,13 @@ abstract class Colleague
|
||||
*
|
||||
* @var MediatorInterface
|
||||
*/
|
||||
private $mediator;
|
||||
protected $mediator;
|
||||
|
||||
/**
|
||||
* @param MediatorInterface $medium
|
||||
* @param MediatorInterface $mediator
|
||||
*/
|
||||
public function __construct(MediatorInterface $medium)
|
||||
public function setMediator(MediatorInterface $mediator)
|
||||
{
|
||||
// in this way, we are sure the concrete colleague knows the mediator
|
||||
$this->mediator = $medium;
|
||||
}
|
||||
|
||||
// for subclasses
|
||||
|
||||
protected function getMediator()
|
||||
{
|
||||
return $this->mediator;
|
||||
$this->mediator = $mediator;
|
||||
}
|
||||
}
|
||||
|
@@ -3,59 +3,54 @@
|
||||
namespace DesignPatterns\Behavioral\Mediator;
|
||||
|
||||
/**
|
||||
* Mediator is the concrete Mediator for this design pattern.
|
||||
* In this example, I have made a "Hello World" with the Mediator Pattern.
|
||||
* Mediator is the concrete Mediator for this design pattern
|
||||
*
|
||||
* In this example, I have made a "Hello World" with the Mediator Pattern
|
||||
*/
|
||||
class Mediator implements MediatorInterface
|
||||
{
|
||||
/**
|
||||
* @var Subsystem\Server
|
||||
*/
|
||||
protected $server;
|
||||
private $server;
|
||||
|
||||
/**
|
||||
* @var Subsystem\Database
|
||||
*/
|
||||
protected $database;
|
||||
private $database;
|
||||
|
||||
/**
|
||||
* @var Subsystem\Client
|
||||
*/
|
||||
protected $client;
|
||||
private $client;
|
||||
|
||||
/**
|
||||
* @param Subsystem\Database $db
|
||||
* @param Subsystem\Client $cl
|
||||
* @param Subsystem\Server $srv
|
||||
* @param Subsystem\Database $database
|
||||
* @param Subsystem\Client $client
|
||||
* @param Subsystem\Server $server
|
||||
*/
|
||||
public function setColleague(Subsystem\Database $db, Subsystem\Client $cl, Subsystem\Server $srv)
|
||||
public function __construct(Subsystem\Database $database, Subsystem\Client $client, Subsystem\Server $server)
|
||||
{
|
||||
$this->database = $db;
|
||||
$this->server = $srv;
|
||||
$this->client = $cl;
|
||||
$this->database = $database;
|
||||
$this->server = $server;
|
||||
$this->client = $client;
|
||||
|
||||
$this->database->setMediator($this);
|
||||
$this->server->setMediator($this);
|
||||
$this->client->setMediator($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* make request.
|
||||
*/
|
||||
public function makeRequest()
|
||||
{
|
||||
$this->server->process();
|
||||
}
|
||||
|
||||
/**
|
||||
* query db.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function queryDb()
|
||||
public function queryDb(): string
|
||||
{
|
||||
return $this->database->getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* send response.
|
||||
*
|
||||
* @param string $content
|
||||
*/
|
||||
public function sendResponse($content)
|
||||
|
@@ -4,7 +4,7 @@ namespace DesignPatterns\Behavioral\Mediator;
|
||||
|
||||
/**
|
||||
* MediatorInterface is a contract for the Mediator
|
||||
* This interface is not mandatory but it is better for LSP concerns.
|
||||
* This interface is not mandatory but it is better for Liskov substitution principle concerns.
|
||||
*/
|
||||
interface MediatorInterface
|
||||
{
|
||||
@@ -16,12 +16,12 @@ interface MediatorInterface
|
||||
public function sendResponse($content);
|
||||
|
||||
/**
|
||||
* makes a request.
|
||||
* makes a request
|
||||
*/
|
||||
public function makeRequest();
|
||||
|
||||
/**
|
||||
* queries the DB.
|
||||
* queries the DB
|
||||
*/
|
||||
public function queryDb();
|
||||
}
|
||||
|
@@ -5,24 +5,16 @@ namespace DesignPatterns\Behavioral\Mediator\Subsystem;
|
||||
use DesignPatterns\Behavioral\Mediator\Colleague;
|
||||
|
||||
/**
|
||||
* Client is a client that make request et get response.
|
||||
* Client is a client that makes requests and gets the response response.
|
||||
*/
|
||||
class Client extends Colleague
|
||||
{
|
||||
/**
|
||||
* request.
|
||||
*/
|
||||
public function request()
|
||||
{
|
||||
$this->getMediator()->makeRequest();
|
||||
$this->mediator->makeRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* output content.
|
||||
*
|
||||
* @param string $content
|
||||
*/
|
||||
public function output($content)
|
||||
public function output(string $content)
|
||||
{
|
||||
echo $content;
|
||||
}
|
||||
|
@@ -4,15 +4,9 @@ namespace DesignPatterns\Behavioral\Mediator\Subsystem;
|
||||
|
||||
use DesignPatterns\Behavioral\Mediator\Colleague;
|
||||
|
||||
/**
|
||||
* Database is a database service.
|
||||
*/
|
||||
class Database extends Colleague
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getData()
|
||||
public function getData(): string
|
||||
{
|
||||
return 'World';
|
||||
}
|
||||
|
@@ -4,17 +4,11 @@ namespace DesignPatterns\Behavioral\Mediator\Subsystem;
|
||||
|
||||
use DesignPatterns\Behavioral\Mediator\Colleague;
|
||||
|
||||
/**
|
||||
* Server serves responses.
|
||||
*/
|
||||
class Server extends Colleague
|
||||
{
|
||||
/**
|
||||
* process on server.
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
$data = $this->getMediator()->queryDb();
|
||||
$this->getMediator()->sendResponse("Hello $data");
|
||||
$data = $this->mediator->queryDb();
|
||||
$this->mediator->sendResponse(sprintf("Hello %s", $data));
|
||||
}
|
||||
}
|
||||
|
@@ -7,27 +7,14 @@ use DesignPatterns\Behavioral\Mediator\Subsystem\Client;
|
||||
use DesignPatterns\Behavioral\Mediator\Subsystem\Database;
|
||||
use DesignPatterns\Behavioral\Mediator\Subsystem\Server;
|
||||
|
||||
/**
|
||||
* MediatorTest tests hello world.
|
||||
*/
|
||||
class MediatorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected $client;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$media = new Mediator();
|
||||
$this->client = new Client($media);
|
||||
$media->setColleague(new Database($media), $this->client, new Server($media));
|
||||
}
|
||||
|
||||
public function testOutputHelloWorld()
|
||||
{
|
||||
// testing if Hello World is output :
|
||||
$client = new Client();
|
||||
new Mediator(new Database(), $client, new Server());
|
||||
|
||||
$this->expectOutputString('Hello World');
|
||||
// as you see, the 3 components Client, Server and Database are totally decoupled
|
||||
$this->client->request();
|
||||
// Anyway, it remains complexity in the Mediator that's why the pattern
|
||||
// Observer is preferable in mnay situations.
|
||||
$client->request();
|
||||
}
|
||||
}
|
||||
|
@@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Memento;
|
||||
|
||||
class Caretaker
|
||||
{
|
||||
protected $history = array();
|
||||
|
||||
/**
|
||||
* @return Memento
|
||||
*/
|
||||
public function getFromHistory($id)
|
||||
{
|
||||
return $this->history[$id];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Memento $state
|
||||
*/
|
||||
public function saveToHistory(Memento $state)
|
||||
{
|
||||
$this->history[] = $state;
|
||||
}
|
||||
|
||||
public function runCustomLogic()
|
||||
{
|
||||
$originator = new Originator();
|
||||
|
||||
//Setting state to State1
|
||||
$originator->setState('State1');
|
||||
//Setting state to State2
|
||||
$originator->setState('State2');
|
||||
//Saving State2 to Memento
|
||||
$this->saveToHistory($originator->getStateAsMemento());
|
||||
//Setting state to State3
|
||||
$originator->setState('State3');
|
||||
|
||||
// We can request multiple mementos, and choose which one to roll back to.
|
||||
// Saving State3 to Memento
|
||||
$this->saveToHistory($originator->getStateAsMemento());
|
||||
//Setting state to State4
|
||||
$originator->setState('State4');
|
||||
|
||||
$originator->restoreFromMemento($this->getFromHistory(1));
|
||||
//State after restoring from Memento: State3
|
||||
|
||||
return $originator->getStateAsMemento()->getState();
|
||||
}
|
||||
}
|
@@ -4,19 +4,21 @@ namespace DesignPatterns\Behavioral\Memento;
|
||||
|
||||
class Memento
|
||||
{
|
||||
/* @var mixed */
|
||||
/**
|
||||
* @var State
|
||||
*/
|
||||
private $state;
|
||||
|
||||
/**
|
||||
* @param mixed $stateToSave
|
||||
* @param State $stateToSave
|
||||
*/
|
||||
public function __construct($stateToSave)
|
||||
public function __construct(State $stateToSave)
|
||||
{
|
||||
$this->state = $stateToSave;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @return State
|
||||
*/
|
||||
public function getState()
|
||||
{
|
||||
|
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Memento;
|
||||
|
||||
class Originator
|
||||
{
|
||||
/* @var mixed */
|
||||
private $state;
|
||||
|
||||
// The class could also contain additional data that is not part of the
|
||||
// state saved in the memento..
|
||||
|
||||
/**
|
||||
* @param mixed $state
|
||||
*/
|
||||
public function setState($state)
|
||||
{
|
||||
// you must check type of state inside child of this class
|
||||
// or use type-hinting for full pattern implementation
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Memento
|
||||
*/
|
||||
public function getStateAsMemento()
|
||||
{
|
||||
// you must save a separate copy in Memento
|
||||
$state = is_object($this->state) ? clone $this->state : $this->state;
|
||||
|
||||
return new Memento($state);
|
||||
}
|
||||
|
||||
public function restoreFromMemento(Memento $memento)
|
||||
{
|
||||
$this->state = $memento->getState();
|
||||
}
|
||||
}
|
@@ -6,8 +6,8 @@ Purpose
|
||||
|
||||
It provides the ability to restore an object to it's previous state (undo
|
||||
via rollback) or to gain access to state of the object, without revealing
|
||||
it's implementation (i.e., the object is not required to have a functional
|
||||
for return the current state).
|
||||
it's implementation (i.e., the object is not required to have a function
|
||||
to return the current state).
|
||||
|
||||
The memento pattern is implemented with three objects: the Originator, a
|
||||
Caretaker and a Memento.
|
||||
@@ -66,9 +66,9 @@ Originator.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
Caretaker.php
|
||||
Ticket.php
|
||||
|
||||
.. literalinclude:: Caretaker.php
|
||||
.. literalinclude:: Ticket.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
|
48
Behavioral/Memento/State.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Memento;
|
||||
|
||||
class State
|
||||
{
|
||||
const STATE_CREATED = 'created';
|
||||
const STATE_OPENED = 'opened';
|
||||
const STATE_ASSIGNED = 'assigned';
|
||||
const STATE_CLOSED = 'closed';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $state;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private static $validStates = [
|
||||
self::STATE_CREATED,
|
||||
self::STATE_OPENED,
|
||||
self::STATE_ASSIGNED,
|
||||
self::STATE_CLOSED,
|
||||
];
|
||||
|
||||
/**
|
||||
* @param string $state
|
||||
*/
|
||||
public function __construct(string $state)
|
||||
{
|
||||
self::ensureIsValidState($state);
|
||||
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
private static function ensureIsValidState(string $state)
|
||||
{
|
||||
if (!in_array($state, self::$validStates)) {
|
||||
throw new \InvalidArgumentException('Invalid state given');
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
}
|
@@ -2,161 +2,30 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\Memento\Tests;
|
||||
|
||||
use DesignPatterns\Behavioral\Memento\Caretaker;
|
||||
use DesignPatterns\Behavioral\Memento\Memento;
|
||||
use DesignPatterns\Behavioral\Memento\Originator;
|
||||
use DesignPatterns\Behavioral\Memento\State;
|
||||
use DesignPatterns\Behavioral\Memento\Ticket;
|
||||
|
||||
/**
|
||||
* MementoTest tests the memento pattern.
|
||||
*/
|
||||
class MementoTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testUsageExample()
|
||||
public function testOpenTicketAssignAndSetBackToOpen()
|
||||
{
|
||||
$originator = new Originator();
|
||||
$caretaker = new Caretaker();
|
||||
$ticket = new Ticket();
|
||||
|
||||
$character = new \stdClass();
|
||||
// new object
|
||||
$character->name = 'Gandalf';
|
||||
// connect Originator to character object
|
||||
$originator->setState($character);
|
||||
// open the ticket
|
||||
$ticket->open();
|
||||
$openedState = $ticket->getState();
|
||||
$this->assertEquals(State::STATE_OPENED, (string) $ticket->getState());
|
||||
|
||||
// work on the object
|
||||
$character->name = 'Gandalf the Grey';
|
||||
// still change something
|
||||
$character->race = 'Maia';
|
||||
// time to save state
|
||||
$snapshot = $originator->getStateAsMemento();
|
||||
// put state to log
|
||||
$caretaker->saveToHistory($snapshot);
|
||||
$memento = $ticket->saveToMemento();
|
||||
|
||||
// change something
|
||||
$character->name = 'Sauron';
|
||||
// and again
|
||||
$character->race = 'Ainur';
|
||||
// state inside the Originator was equally changed
|
||||
$this->assertAttributeEquals($character, 'state', $originator);
|
||||
// assign the ticket
|
||||
$ticket->assign();
|
||||
$this->assertEquals(State::STATE_ASSIGNED, (string) $ticket->getState());
|
||||
|
||||
// time to save another state
|
||||
$snapshot = $originator->getStateAsMemento();
|
||||
// put state to log
|
||||
$caretaker->saveToHistory($snapshot);
|
||||
// no restore to the opened state, but verify that the state object has been cloned for the memento
|
||||
$ticket->restoreFromMemento($memento);
|
||||
|
||||
$rollback = $caretaker->getFromHistory(0);
|
||||
// return to first state
|
||||
$originator->restoreFromMemento($rollback);
|
||||
// use character from old state
|
||||
$character = $rollback->getState();
|
||||
|
||||
// yes, that what we need
|
||||
$this->assertEquals('Gandalf the Grey', $character->name);
|
||||
// make new changes
|
||||
$character->name = 'Gandalf the White';
|
||||
|
||||
// and Originator linked to actual object again
|
||||
$this->assertAttributeEquals($character, 'state', $originator);
|
||||
}
|
||||
|
||||
public function testStringState()
|
||||
{
|
||||
$originator = new Originator();
|
||||
$originator->setState('State1');
|
||||
|
||||
$this->assertAttributeEquals('State1', 'state', $originator);
|
||||
|
||||
$originator->setState('State2');
|
||||
$this->assertAttributeEquals('State2', 'state', $originator);
|
||||
|
||||
$snapshot = $originator->getStateAsMemento();
|
||||
$this->assertAttributeEquals('State2', 'state', $snapshot);
|
||||
|
||||
$originator->setState('State3');
|
||||
$this->assertAttributeEquals('State3', 'state', $originator);
|
||||
|
||||
$originator->restoreFromMemento($snapshot);
|
||||
$this->assertAttributeEquals('State2', 'state', $originator);
|
||||
}
|
||||
|
||||
public function testSnapshotIsClone()
|
||||
{
|
||||
$originator = new Originator();
|
||||
$object = new \stdClass();
|
||||
|
||||
$originator->setState($object);
|
||||
$snapshot = $originator->getStateAsMemento();
|
||||
$object->new_property = 1;
|
||||
|
||||
$this->assertAttributeEquals($object, 'state', $originator);
|
||||
$this->assertAttributeNotEquals($object, 'state', $snapshot);
|
||||
|
||||
$originator->restoreFromMemento($snapshot);
|
||||
$this->assertAttributeNotEquals($object, 'state', $originator);
|
||||
}
|
||||
|
||||
public function testCanChangeActualState()
|
||||
{
|
||||
$originator = new Originator();
|
||||
$first_state = new \stdClass();
|
||||
|
||||
$originator->setState($first_state);
|
||||
$snapshot = $originator->getStateAsMemento();
|
||||
$second_state = $snapshot->getState();
|
||||
|
||||
// still actual
|
||||
$first_state->first_property = 1;
|
||||
// just history
|
||||
$second_state->second_property = 2;
|
||||
$this->assertAttributeEquals($first_state, 'state', $originator);
|
||||
$this->assertAttributeNotEquals($second_state, 'state', $originator);
|
||||
|
||||
$originator->restoreFromMemento($snapshot);
|
||||
// now it lost state
|
||||
$first_state->first_property = 11;
|
||||
// must be actual
|
||||
$second_state->second_property = 22;
|
||||
$this->assertAttributeEquals($second_state, 'state', $originator);
|
||||
$this->assertAttributeNotEquals($first_state, 'state', $originator);
|
||||
}
|
||||
|
||||
public function testStateWithDifferentObjects()
|
||||
{
|
||||
$originator = new Originator();
|
||||
|
||||
$first = new \stdClass();
|
||||
$first->data = 'foo';
|
||||
|
||||
$originator->setState($first);
|
||||
$this->assertAttributeEquals($first, 'state', $originator);
|
||||
|
||||
$first_snapshot = $originator->getStateAsMemento();
|
||||
$this->assertAttributeEquals($first, 'state', $first_snapshot);
|
||||
|
||||
$second = new \stdClass();
|
||||
$second->data = 'bar';
|
||||
$originator->setState($second);
|
||||
$this->assertAttributeEquals($second, 'state', $originator);
|
||||
|
||||
$originator->restoreFromMemento($first_snapshot);
|
||||
$this->assertAttributeEquals($first, 'state', $originator);
|
||||
}
|
||||
|
||||
public function testCaretaker()
|
||||
{
|
||||
$caretaker = new Caretaker();
|
||||
$memento1 = new Memento('foo');
|
||||
$memento2 = new Memento('bar');
|
||||
$caretaker->saveToHistory($memento1);
|
||||
$caretaker->saveToHistory($memento2);
|
||||
$this->assertAttributeEquals(array($memento1, $memento2), 'history', $caretaker);
|
||||
$this->assertEquals($memento1, $caretaker->getFromHistory(0));
|
||||
$this->assertEquals($memento2, $caretaker->getFromHistory(1));
|
||||
}
|
||||
|
||||
public function testCaretakerCustomLogic()
|
||||
{
|
||||
$caretaker = new Caretaker();
|
||||
$result = $caretaker->runCustomLogic();
|
||||
$this->assertEquals('State3', $result);
|
||||
$this->assertEquals(State::STATE_OPENED, (string) $ticket->getState());
|
||||
$this->assertNotSame($openedState, $ticket->getState());
|
||||
}
|
||||
}
|
||||
|
49
Behavioral/Memento/Ticket.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Memento;
|
||||
|
||||
/**
|
||||
* Ticket is the "Originator" in this implementation
|
||||
*/
|
||||
class Ticket
|
||||
{
|
||||
/**
|
||||
* @var State
|
||||
*/
|
||||
private $currentState;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->currentState = new State(State::STATE_CREATED);
|
||||
}
|
||||
|
||||
public function open()
|
||||
{
|
||||
$this->currentState = new State(State::STATE_OPENED);
|
||||
}
|
||||
|
||||
public function assign()
|
||||
{
|
||||
$this->currentState = new State(State::STATE_ASSIGNED);
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
$this->currentState = new State(State::STATE_CLOSED);
|
||||
}
|
||||
|
||||
public function saveToMemento(): Memento
|
||||
{
|
||||
return new Memento(clone $this->currentState);
|
||||
}
|
||||
|
||||
public function restoreFromMemento(Memento $memento)
|
||||
{
|
||||
$this->currentState = $memento->getState();
|
||||
}
|
||||
|
||||
public function getState(): State
|
||||
{
|
||||
return $this->currentState;
|
||||
}
|
||||
}
|
21
Behavioral/Memento/uml/Memento.uml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Diagram>
|
||||
<ID>PHP</ID>
|
||||
<OriginalElement>\DesignPatterns\Behavioral\Memento\Memento</OriginalElement>
|
||||
<nodes>
|
||||
<node x="0.0" y="0.0">\DesignPatterns\Behavioral\Memento\State</node>
|
||||
<node x="313.0" y="252.0">\DesignPatterns\Behavioral\Memento\Memento</node>
|
||||
<node x="0.0" y="252.0">\DesignPatterns\Behavioral\Memento\Ticket</node>
|
||||
</nodes>
|
||||
<notes />
|
||||
<edges />
|
||||
<settings layout="Hierarchic Group" zoom="1.0" x="150.0" y="130.0" />
|
||||
<SelectedNodes />
|
||||
<Categories>
|
||||
<Category>Fields</Category>
|
||||
<Category>Constants</Category>
|
||||
<Category>Methods</Category>
|
||||
</Categories>
|
||||
<VISIBILITY>private</VISIBILITY>
|
||||
</Diagram>
|
||||
|
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Diagram>
|
||||
<ID>PHP</ID>
|
||||
<OriginalElement>\DesignPatterns\Behavioral\Memento\Caretaker</OriginalElement>
|
||||
<nodes>
|
||||
<node x="182.0" y="153.0">\DesignPatterns\Behavioral\Memento\Caretaker</node>
|
||||
<node x="0.0" y="0.0">\DesignPatterns\Behavioral\Memento\Originator</node>
|
||||
<node x="0.0" y="153.0">\DesignPatterns\Behavioral\Memento\Memento</node>
|
||||
</nodes>
|
||||
<notes />
|
||||
<edges />
|
||||
<settings layout="Hierarchic Group" zoom="1.0" x="131.0" y="121.0" />
|
||||
<SelectedNodes />
|
||||
<Categories>
|
||||
<Category>Fields</Category>
|
||||
<Category>Constants</Category>
|
||||
<Category>Constructors</Category>
|
||||
<Category>Methods</Category>
|
||||
</Categories>
|
||||
<VISIBILITY>private</VISIBILITY>
|
||||
</Diagram>
|
||||
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 164 KiB |
@@ -3,16 +3,9 @@
|
||||
namespace DesignPatterns\Behavioral\NullObject;
|
||||
|
||||
/**
|
||||
* LoggerInterface is a contract for logging something.
|
||||
*
|
||||
* Key feature: NullLogger MUST inherit from this interface like any other Loggers
|
||||
* Key feature: NullLogger must inherit from this interface like any other loggers
|
||||
*/
|
||||
interface LoggerInterface
|
||||
{
|
||||
/**
|
||||
* @param string $str
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function log($str);
|
||||
public function log(string $str);
|
||||
}
|
||||
|
@@ -2,19 +2,9 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\NullObject;
|
||||
|
||||
/**
|
||||
* Performance concerns : ok there is a call for nothing but we spare an "if is_null"
|
||||
* I didn't run a benchmark but I think it's equivalent.
|
||||
*
|
||||
* Key feature : of course this logger MUST implement the same interface (or abstract)
|
||||
* like the other loggers.
|
||||
*/
|
||||
class NullLogger implements LoggerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function log($str)
|
||||
public function log(string $str)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
@@ -2,15 +2,9 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\NullObject;
|
||||
|
||||
/**
|
||||
* PrintLogger is a logger that prints the log entry to standard output.
|
||||
*/
|
||||
class PrintLogger implements LoggerInterface
|
||||
{
|
||||
/**
|
||||
* @param string $str
|
||||
*/
|
||||
public function log($str)
|
||||
public function log(string $str)
|
||||
{
|
||||
echo $str;
|
||||
}
|
||||
|
@@ -2,24 +2,19 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\NullObject;
|
||||
|
||||
/**
|
||||
* Service is dummy service that uses a logger.
|
||||
*/
|
||||
class Service
|
||||
{
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* we inject the logger in ctor and it is mandatory.
|
||||
*
|
||||
* @param LoggerInterface $log
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function __construct(LoggerInterface $log)
|
||||
public function __construct(LoggerInterface $logger)
|
||||
{
|
||||
$this->logger = $log;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,8 +22,7 @@ class Service
|
||||
*/
|
||||
public function doSomething()
|
||||
{
|
||||
// no more check "if (!is_null($this->logger))..." with the NullObject pattern
|
||||
// notice here that you don't have to check if the logger is set with eg. is_null(), instead just use it
|
||||
$this->logger->log('We are in '.__METHOD__);
|
||||
// something to do...
|
||||
}
|
||||
}
|
||||
|
@@ -6,17 +6,12 @@ use DesignPatterns\Behavioral\NullObject\NullLogger;
|
||||
use DesignPatterns\Behavioral\NullObject\PrintLogger;
|
||||
use DesignPatterns\Behavioral\NullObject\Service;
|
||||
|
||||
/**
|
||||
* LoggerTest tests for different loggers.
|
||||
*/
|
||||
class LoggerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testNullObject()
|
||||
{
|
||||
// one can use a singleton for NullObjet : I don't think it's a good idea
|
||||
// because the purpose behind null object is to "avoid special case".
|
||||
$service = new Service(new NullLogger());
|
||||
$this->expectOutputString(null); // no output
|
||||
$this->expectOutputString(null);
|
||||
$service->doSomething();
|
||||
}
|
||||
|
||||
|
@@ -5,65 +5,16 @@ namespace DesignPatterns\Behavioral\Observer\Tests;
|
||||
use DesignPatterns\Behavioral\Observer\User;
|
||||
use DesignPatterns\Behavioral\Observer\UserObserver;
|
||||
|
||||
/**
|
||||
* ObserverTest tests the Observer pattern.
|
||||
*/
|
||||
class ObserverTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected $observer;
|
||||
|
||||
protected function setUp()
|
||||
public function testChangeInUserLeadsToUserObserverBeingNotified()
|
||||
{
|
||||
$this->observer = new UserObserver();
|
||||
}
|
||||
$observer = new UserObserver();
|
||||
|
||||
/**
|
||||
* Tests the notification.
|
||||
*/
|
||||
public function testNotify()
|
||||
{
|
||||
$this->expectOutputString('DesignPatterns\Behavioral\Observer\User has been updated');
|
||||
$subject = new User();
|
||||
$user = new User();
|
||||
$user->attach($observer);
|
||||
|
||||
$subject->attach($this->observer);
|
||||
$subject->property = 123;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the subscribing.
|
||||
*/
|
||||
public function testAttachDetach()
|
||||
{
|
||||
$subject = new User();
|
||||
$reflection = new \ReflectionProperty($subject, 'observers');
|
||||
|
||||
$reflection->setAccessible(true);
|
||||
/** @var \SplObjectStorage $observers */
|
||||
$observers = $reflection->getValue($subject);
|
||||
|
||||
$this->assertInstanceOf('SplObjectStorage', $observers);
|
||||
$this->assertFalse($observers->contains($this->observer));
|
||||
|
||||
$subject->attach($this->observer);
|
||||
$this->assertTrue($observers->contains($this->observer));
|
||||
|
||||
$subject->detach($this->observer);
|
||||
$this->assertFalse($observers->contains($this->observer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the update() invocation on a mockup.
|
||||
*/
|
||||
public function testUpdateCalling()
|
||||
{
|
||||
$subject = new User();
|
||||
$observer = $this->getMock('SplObserver');
|
||||
$subject->attach($observer);
|
||||
|
||||
$observer->expects($this->once())
|
||||
->method('update')
|
||||
->with($subject);
|
||||
|
||||
$subject->notify();
|
||||
$user->changeEmail('foo@bar.com');
|
||||
$this->assertCount(1, $observer->getChangedUsers());
|
||||
}
|
||||
}
|
||||
|
@@ -3,60 +3,42 @@
|
||||
namespace DesignPatterns\Behavioral\Observer;
|
||||
|
||||
/**
|
||||
* Observer pattern : The observed object (the subject).
|
||||
*
|
||||
* The subject maintains a list of Observers and sends notifications.
|
||||
* User implements the observed object (called Subject), it maintains a list of observers and sends notifications to
|
||||
* them in case changes are made on the User object
|
||||
*/
|
||||
class User implements \SplSubject
|
||||
{
|
||||
/**
|
||||
* user data.
|
||||
*
|
||||
* @var array
|
||||
* @var string
|
||||
*/
|
||||
protected $data = array();
|
||||
private $email;
|
||||
|
||||
/**
|
||||
* observers.
|
||||
*
|
||||
* @var \SplObjectStorage
|
||||
*/
|
||||
protected $observers;
|
||||
private $observers;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->observers = new \SplObjectStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* attach a new observer.
|
||||
*
|
||||
* @param \SplObserver $observer
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function attach(\SplObserver $observer)
|
||||
{
|
||||
$this->observers->attach($observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* detach an observer.
|
||||
*
|
||||
* @param \SplObserver $observer
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function detach(\SplObserver $observer)
|
||||
{
|
||||
$this->observers->detach($observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* notify observers.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function changeEmail(string $email)
|
||||
{
|
||||
$this->email = $email;
|
||||
$this->notify();
|
||||
}
|
||||
|
||||
public function notify()
|
||||
{
|
||||
/** @var \SplObserver $observer */
|
||||
@@ -64,21 +46,4 @@ class User implements \SplSubject
|
||||
$observer->update($this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ideally one would better write setter/getter for all valid attributes and only call notify()
|
||||
* on attributes that matter when changed.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$this->data[$name] = $value;
|
||||
|
||||
// notify the observers, that user has been updated
|
||||
$this->notify();
|
||||
}
|
||||
}
|
||||
|
@@ -2,19 +2,28 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\Observer;
|
||||
|
||||
/**
|
||||
* class UserObserver.
|
||||
*/
|
||||
class UserObserver implements \SplObserver
|
||||
{
|
||||
/**
|
||||
* This is the only method to implement as an observer.
|
||||
* It is called by the Subject (usually by SplSubject::notify() ).
|
||||
* @var User[]
|
||||
*/
|
||||
private $changedUsers = [];
|
||||
|
||||
/**
|
||||
* It is called by the Subject, usually by SplSubject::notify()
|
||||
*
|
||||
* @param \SplSubject $subject
|
||||
*/
|
||||
public function update(\SplSubject $subject)
|
||||
{
|
||||
echo get_class($subject).' has been updated';
|
||||
$this->changedUsers[] = clone $subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User[]
|
||||
*/
|
||||
public function getChangedUsers(): array
|
||||
{
|
||||
return $this->changedUsers;
|
||||
}
|
||||
}
|
||||
|
@@ -1,27 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Diagram>
|
||||
<ID>PHP</ID>
|
||||
<OriginalElement>\DesignPatterns\Behavioral\Observer\User</OriginalElement>
|
||||
<nodes>
|
||||
<node x="239.0" y="0.0">\DesignPatterns\Behavioral\Observer\UserObserver</node>
|
||||
<node x="0.0" y="137.0">\DesignPatterns\Behavioral\Observer\User</node>
|
||||
<node x="8.0" y="0.0">\SplSubject</node>
|
||||
</nodes>
|
||||
<notes />
|
||||
<edges>
|
||||
<edge source="\DesignPatterns\Behavioral\Observer\User" target="\SplSubject">
|
||||
<point x="0.0" y="-74.0" />
|
||||
<point x="0.0" y="43.5" />
|
||||
</edge>
|
||||
</edges>
|
||||
<settings layout="Hierarchic Group" zoom="1.0" x="186.0" y="142.5" />
|
||||
<SelectedNodes />
|
||||
<Categories>
|
||||
<Category>Fields</Category>
|
||||
<Category>Constants</Category>
|
||||
<Category>Constructors</Category>
|
||||
<Category>Methods</Category>
|
||||
</Categories>
|
||||
<VISIBILITY>private</VISIBILITY>
|
||||
</Diagram>
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Diagram>
|
||||
<ID>PHP</ID>
|
||||
<OriginalElement>\DesignPatterns\Behavioral\Observer\UserObserver</OriginalElement>
|
||||
<nodes>
|
||||
<node x="215.0" y="112.0">\DesignPatterns\Behavioral\Observer\UserObserver</node>
|
||||
<node x="229.5" y="7.0">\SplObserver</node>
|
||||
<node x="-29.0" y="25.0">\DesignPatterns\Behavioral\Observer\User</node>
|
||||
</nodes>
|
||||
<notes />
|
||||
<edges>
|
||||
<edge source="\DesignPatterns\Behavioral\Observer\UserObserver" target="\SplObserver">
|
||||
<point x="0.0" y="-48.0" />
|
||||
<point x="0.0" y="25.5" />
|
||||
</edge>
|
||||
</edges>
|
||||
<settings layout="Hierarchic Group" zoom="1.0" x="58.0" y="91.0" />
|
||||
<SelectedNodes />
|
||||
<Categories>
|
||||
<Category>Fields</Category>
|
||||
<Category>Constants</Category>
|
||||
<Category>Methods</Category>
|
||||
</Categories>
|
||||
<VISIBILITY>private</VISIBILITY>
|
||||
</Diagram>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 108 KiB |
@@ -1,52 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Specification;
|
||||
|
||||
/**
|
||||
* An abstract specification allows the creation of wrapped specifications.
|
||||
*/
|
||||
abstract class AbstractSpecification implements SpecificationInterface
|
||||
{
|
||||
/**
|
||||
* Checks if given item meets all criteria.
|
||||
*
|
||||
* @param Item $item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function isSatisfiedBy(Item $item);
|
||||
|
||||
/**
|
||||
* Creates a new logical AND specification.
|
||||
*
|
||||
* @param SpecificationInterface $spec
|
||||
*
|
||||
* @return SpecificationInterface
|
||||
*/
|
||||
public function plus(SpecificationInterface $spec)
|
||||
{
|
||||
return new Plus($this, $spec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new logical OR composite specification.
|
||||
*
|
||||
* @param SpecificationInterface $spec
|
||||
*
|
||||
* @return SpecificationInterface
|
||||
*/
|
||||
public function either(SpecificationInterface $spec)
|
||||
{
|
||||
return new Either($this, $spec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new logical NOT specification.
|
||||
*
|
||||
* @return SpecificationInterface
|
||||
*/
|
||||
public function not()
|
||||
{
|
||||
return new Not($this);
|
||||
}
|
||||
}
|
30
Behavioral/Specification/AndSpecification.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Specification;
|
||||
|
||||
class AndSpecification implements SpecificationInterface
|
||||
{
|
||||
/**
|
||||
* @var SpecificationInterface[]
|
||||
*/
|
||||
private $specifications;
|
||||
|
||||
/**
|
||||
* @param SpecificationInterface[] ...$specifications
|
||||
*/
|
||||
public function __construct(SpecificationInterface ...$specifications)
|
||||
{
|
||||
$this->specifications = $specifications;
|
||||
}
|
||||
|
||||
public function isSatisfiedBy(Item $item): bool
|
||||
{
|
||||
$satisfied = [];
|
||||
|
||||
foreach ($this->specifications as $specification) {
|
||||
$satisfied[] = $specification->isSatisfiedBy($item);
|
||||
}
|
||||
|
||||
return !in_array(false, $satisfied);
|
||||
}
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Specification;
|
||||
|
||||
/**
|
||||
* A logical OR specification.
|
||||
*/
|
||||
class Either extends AbstractSpecification
|
||||
{
|
||||
protected $left;
|
||||
protected $right;
|
||||
|
||||
/**
|
||||
* A composite wrapper of two specifications.
|
||||
*
|
||||
* @param SpecificationInterface $left
|
||||
* @param SpecificationInterface $right
|
||||
*/
|
||||
public function __construct(SpecificationInterface $left, SpecificationInterface $right)
|
||||
{
|
||||
$this->left = $left;
|
||||
$this->right = $right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the evaluation of both wrapped specifications as a logical OR.
|
||||
*
|
||||
* @param Item $item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSatisfiedBy(Item $item)
|
||||
{
|
||||
return $this->left->isSatisfiedBy($item) || $this->right->isSatisfiedBy($item);
|
||||
}
|
||||
}
|
@@ -2,29 +2,19 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\Specification;
|
||||
|
||||
/**
|
||||
* An trivial item.
|
||||
*/
|
||||
class Item
|
||||
{
|
||||
protected $price;
|
||||
|
||||
/**
|
||||
* An item must have a price.
|
||||
*
|
||||
* @param int $price
|
||||
* @var float
|
||||
*/
|
||||
public function __construct($price)
|
||||
private $price;
|
||||
|
||||
public function __construct(float $price)
|
||||
{
|
||||
$this->price = $price;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the items price.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPrice()
|
||||
public function getPrice(): float
|
||||
{
|
||||
return $this->price;
|
||||
}
|
||||
|
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Specification;
|
||||
|
||||
/**
|
||||
* A logical Not specification.
|
||||
*/
|
||||
class Not extends AbstractSpecification
|
||||
{
|
||||
protected $spec;
|
||||
|
||||
/**
|
||||
* Creates a new specification wrapping another.
|
||||
*
|
||||
* @param SpecificationInterface $spec
|
||||
*/
|
||||
public function __construct(SpecificationInterface $spec)
|
||||
{
|
||||
$this->spec = $spec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the negated result of the wrapped specification.
|
||||
*
|
||||
* @param Item $item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSatisfiedBy(Item $item)
|
||||
{
|
||||
return !$this->spec->isSatisfiedBy($item);
|
||||
}
|
||||
}
|
21
Behavioral/Specification/NotSpecification.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Specification;
|
||||
|
||||
class NotSpecification implements SpecificationInterface
|
||||
{
|
||||
/**
|
||||
* @var SpecificationInterface
|
||||
*/
|
||||
private $specification;
|
||||
|
||||
public function __construct(SpecificationInterface $specification)
|
||||
{
|
||||
$this->specification = $specification;
|
||||
}
|
||||
|
||||
public function isSatisfiedBy(Item $item): bool
|
||||
{
|
||||
return !$this->specification->isSatisfiedBy($item);
|
||||
}
|
||||
}
|
30
Behavioral/Specification/OrSpecification.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Specification;
|
||||
|
||||
class OrSpecification implements SpecificationInterface
|
||||
{
|
||||
/**
|
||||
* @var SpecificationInterface[]
|
||||
*/
|
||||
private $specifications;
|
||||
|
||||
/**
|
||||
* @param SpecificationInterface[] ...$specifications
|
||||
*/
|
||||
public function __construct(SpecificationInterface ...$specifications)
|
||||
{
|
||||
$this->specifications = $specifications;
|
||||
}
|
||||
|
||||
public function isSatisfiedBy(Item $item): bool
|
||||
{
|
||||
$satisfied = [];
|
||||
|
||||
foreach ($this->specifications as $specification) {
|
||||
$satisfied[] = $specification->isSatisfiedBy($item);
|
||||
}
|
||||
|
||||
return in_array(true, $satisfied);
|
||||
}
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Specification;
|
||||
|
||||
/**
|
||||
* A logical AND specification.
|
||||
*/
|
||||
class Plus extends AbstractSpecification
|
||||
{
|
||||
protected $left;
|
||||
protected $right;
|
||||
|
||||
/**
|
||||
* Creation of a logical AND of two specifications.
|
||||
*
|
||||
* @param SpecificationInterface $left
|
||||
* @param SpecificationInterface $right
|
||||
*/
|
||||
public function __construct(SpecificationInterface $left, SpecificationInterface $right)
|
||||
{
|
||||
$this->left = $left;
|
||||
$this->right = $right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the composite AND of specifications passes.
|
||||
*
|
||||
* @param Item $item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSatisfiedBy(Item $item)
|
||||
{
|
||||
return $this->left->isSatisfiedBy($item) && $this->right->isSatisfiedBy($item);
|
||||
}
|
||||
}
|
@@ -2,47 +2,35 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\Specification;
|
||||
|
||||
/**
|
||||
* A specification to check an Item is priced between min and max.
|
||||
*/
|
||||
class PriceSpecification extends AbstractSpecification
|
||||
class PriceSpecification implements SpecificationInterface
|
||||
{
|
||||
protected $maxPrice;
|
||||
protected $minPrice;
|
||||
/**
|
||||
* @var float|null
|
||||
*/
|
||||
private $maxPrice;
|
||||
|
||||
/**
|
||||
* Sets the optional maximum price.
|
||||
*
|
||||
* @param int $maxPrice
|
||||
* @var float|null
|
||||
*/
|
||||
public function setMaxPrice($maxPrice)
|
||||
private $minPrice;
|
||||
|
||||
/**
|
||||
* @param float $minPrice
|
||||
* @param float $maxPrice
|
||||
*/
|
||||
public function __construct($minPrice, $maxPrice)
|
||||
{
|
||||
$this->minPrice = $minPrice;
|
||||
$this->maxPrice = $maxPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional minimum price.
|
||||
*
|
||||
* @param int $minPrice
|
||||
*/
|
||||
public function setMinPrice($minPrice)
|
||||
public function isSatisfiedBy(Item $item): bool
|
||||
{
|
||||
$this->minPrice = $minPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if Item price falls between bounds.
|
||||
*
|
||||
* @param Item $item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSatisfiedBy(Item $item)
|
||||
{
|
||||
if (!empty($this->maxPrice) && $item->getPrice() > $this->maxPrice) {
|
||||
if ($this->maxPrice !== null && $item->getPrice() > $this->maxPrice) {
|
||||
return false;
|
||||
}
|
||||
if (!empty($this->minPrice) && $item->getPrice() < $this->minPrice) {
|
||||
|
||||
if ($this->minPrice !== null && $item->getPrice() < $this->minPrice) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -38,15 +38,9 @@ SpecificationInterface.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
AbstractSpecification.php
|
||||
OrSpecification.php
|
||||
|
||||
.. literalinclude:: AbstractSpecification.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
Either.php
|
||||
|
||||
.. literalinclude:: Either.php
|
||||
.. literalinclude:: OrSpecification.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
@@ -56,15 +50,15 @@ PriceSpecification.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
Plus.php
|
||||
AndSpecification.php
|
||||
|
||||
.. literalinclude:: Plus.php
|
||||
.. literalinclude:: AndSpecification.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
Not.php
|
||||
NotSpecification.php
|
||||
|
||||
.. literalinclude:: Not.php
|
||||
.. literalinclude:: NotSpecification.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
|
@@ -2,36 +2,7 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\Specification;
|
||||
|
||||
/**
|
||||
* An interface for a specification.
|
||||
*/
|
||||
interface SpecificationInterface
|
||||
{
|
||||
/**
|
||||
* A boolean evaluation indicating if the object meets the specification.
|
||||
*
|
||||
* @param Item $item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSatisfiedBy(Item $item);
|
||||
|
||||
/**
|
||||
* Creates a logical AND specification.
|
||||
*
|
||||
* @param SpecificationInterface $spec
|
||||
*/
|
||||
public function plus(SpecificationInterface $spec);
|
||||
|
||||
/**
|
||||
* Creates a logical OR specification.
|
||||
*
|
||||
* @param SpecificationInterface $spec
|
||||
*/
|
||||
public function either(SpecificationInterface $spec);
|
||||
|
||||
/**
|
||||
* Creates a logical not specification.
|
||||
*/
|
||||
public function not();
|
||||
public function isSatisfiedBy(Item $item): bool;
|
||||
}
|
||||
|
@@ -3,101 +3,44 @@
|
||||
namespace DesignPatterns\Behavioral\Specification\Tests;
|
||||
|
||||
use DesignPatterns\Behavioral\Specification\Item;
|
||||
use DesignPatterns\Behavioral\Specification\NotSpecification;
|
||||
use DesignPatterns\Behavioral\Specification\OrSpecification;
|
||||
use DesignPatterns\Behavioral\Specification\AndSpecification;
|
||||
use DesignPatterns\Behavioral\Specification\PriceSpecification;
|
||||
|
||||
/**
|
||||
* SpecificationTest tests the specification pattern.
|
||||
*/
|
||||
class SpecificationTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testSimpleSpecification()
|
||||
public function testCanOr()
|
||||
{
|
||||
$item = new Item(100);
|
||||
$spec = new PriceSpecification();
|
||||
$spec1 = new PriceSpecification(50, 99);
|
||||
$spec2 = new PriceSpecification(101, 200);
|
||||
|
||||
$this->assertTrue($spec->isSatisfiedBy($item));
|
||||
$orSpec = new OrSpecification($spec1, $spec2);
|
||||
|
||||
$spec->setMaxPrice(50);
|
||||
$this->assertFalse($spec->isSatisfiedBy($item));
|
||||
|
||||
$spec->setMaxPrice(150);
|
||||
$this->assertTrue($spec->isSatisfiedBy($item));
|
||||
|
||||
$spec->setMinPrice(101);
|
||||
$this->assertFalse($spec->isSatisfiedBy($item));
|
||||
|
||||
$spec->setMinPrice(100);
|
||||
$this->assertTrue($spec->isSatisfiedBy($item));
|
||||
$this->assertFalse($orSpec->isSatisfiedBy(new Item(100)));
|
||||
$this->assertTrue($orSpec->isSatisfiedBy(new Item(51)));
|
||||
$this->assertTrue($orSpec->isSatisfiedBy(new Item(150)));
|
||||
}
|
||||
|
||||
public function testNotSpecification()
|
||||
public function testCanAnd()
|
||||
{
|
||||
$item = new Item(100);
|
||||
$spec = new PriceSpecification();
|
||||
$not = $spec->not();
|
||||
$spec1 = new PriceSpecification(50, 100);
|
||||
$spec2 = new PriceSpecification(80, 200);
|
||||
|
||||
$this->assertFalse($not->isSatisfiedBy($item));
|
||||
$orSpec = new AndSpecification($spec1, $spec2);
|
||||
|
||||
$spec->setMaxPrice(50);
|
||||
$this->assertTrue($not->isSatisfiedBy($item));
|
||||
|
||||
$spec->setMaxPrice(150);
|
||||
$this->assertFalse($not->isSatisfiedBy($item));
|
||||
|
||||
$spec->setMinPrice(101);
|
||||
$this->assertTrue($not->isSatisfiedBy($item));
|
||||
|
||||
$spec->setMinPrice(100);
|
||||
$this->assertFalse($not->isSatisfiedBy($item));
|
||||
$this->assertFalse($orSpec->isSatisfiedBy(new Item(150)));
|
||||
$this->assertFalse($orSpec->isSatisfiedBy(new Item(1)));
|
||||
$this->assertFalse($orSpec->isSatisfiedBy(new Item(51)));
|
||||
$this->assertTrue($orSpec->isSatisfiedBy(new Item(100)));
|
||||
}
|
||||
|
||||
public function testPlusSpecification()
|
||||
public function testCanNot()
|
||||
{
|
||||
$spec1 = new PriceSpecification();
|
||||
$spec2 = new PriceSpecification();
|
||||
$plus = $spec1->plus($spec2);
|
||||
$spec1 = new PriceSpecification(50, 100);
|
||||
$orSpec = new NotSpecification($spec1);
|
||||
|
||||
$item = new Item(100);
|
||||
|
||||
$this->assertTrue($plus->isSatisfiedBy($item));
|
||||
|
||||
$spec1->setMaxPrice(150);
|
||||
$spec2->setMinPrice(50);
|
||||
$this->assertTrue($plus->isSatisfiedBy($item));
|
||||
|
||||
$spec1->setMaxPrice(150);
|
||||
$spec2->setMinPrice(101);
|
||||
$this->assertFalse($plus->isSatisfiedBy($item));
|
||||
|
||||
$spec1->setMaxPrice(99);
|
||||
$spec2->setMinPrice(50);
|
||||
$this->assertFalse($plus->isSatisfiedBy($item));
|
||||
}
|
||||
|
||||
public function testEitherSpecification()
|
||||
{
|
||||
$spec1 = new PriceSpecification();
|
||||
$spec2 = new PriceSpecification();
|
||||
$either = $spec1->either($spec2);
|
||||
|
||||
$item = new Item(100);
|
||||
|
||||
$this->assertTrue($either->isSatisfiedBy($item));
|
||||
|
||||
$spec1->setMaxPrice(150);
|
||||
$spec2->setMaxPrice(150);
|
||||
$this->assertTrue($either->isSatisfiedBy($item));
|
||||
|
||||
$spec1->setMaxPrice(150);
|
||||
$spec2->setMaxPrice(0);
|
||||
$this->assertTrue($either->isSatisfiedBy($item));
|
||||
|
||||
$spec1->setMaxPrice(0);
|
||||
$spec2->setMaxPrice(150);
|
||||
$this->assertTrue($either->isSatisfiedBy($item));
|
||||
|
||||
$spec1->setMaxPrice(99);
|
||||
$spec2->setMaxPrice(99);
|
||||
$this->assertFalse($either->isSatisfiedBy($item));
|
||||
$this->assertTrue($orSpec->isSatisfiedBy(new Item(150)));
|
||||
$this->assertFalse($orSpec->isSatisfiedBy(new Item(50)));
|
||||
}
|
||||
}
|
||||
|
@@ -2,49 +2,34 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\State;
|
||||
|
||||
/**
|
||||
* Class CreateOrder.
|
||||
*/
|
||||
class CreateOrder implements OrderInterface
|
||||
class CreateOrder implements Order
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $order;
|
||||
private $details;
|
||||
|
||||
/**
|
||||
* @param array $order
|
||||
*
|
||||
* @throws \Exception
|
||||
* @param array $details
|
||||
*/
|
||||
public function __construct(array $order)
|
||||
public function __construct(array $details)
|
||||
{
|
||||
if (empty($order)) {
|
||||
throw new \Exception('Order can not be empty!');
|
||||
}
|
||||
$this->order = $order;
|
||||
$this->details = $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function shipOrder()
|
||||
{
|
||||
$this->order['status'] = 'shipping';
|
||||
$this->order['updatedTime'] = time();
|
||||
|
||||
// Setting the new order status into database;
|
||||
return $this->updateOrder($this->order);
|
||||
$this->details['status'] = 'shipping';
|
||||
$this->details['updatedTime'] = time();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function completeOrder()
|
||||
{
|
||||
//Can not complete the order which status is created, throw exception;
|
||||
throw new \Exception('Can not complete the order which status is created!');
|
||||
throw new \Exception('Can not complete the order which status is created');
|
||||
}
|
||||
|
||||
public function getStatus(): string
|
||||
{
|
||||
return $this->details['status'];
|
||||
}
|
||||
}
|
||||
|
@@ -2,10 +2,7 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\State;
|
||||
|
||||
/**
|
||||
* Class OrderInterface.
|
||||
*/
|
||||
interface OrderInterface
|
||||
interface Order
|
||||
{
|
||||
/**
|
||||
* @return mixed
|
||||
@@ -16,4 +13,6 @@ interface OrderInterface
|
||||
* @return mixed
|
||||
*/
|
||||
public function completeOrder();
|
||||
|
||||
public function getStatus(): string;
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\State;
|
||||
|
||||
/**
|
||||
* Class OrderController.
|
||||
*/
|
||||
class OrderController
|
||||
{
|
||||
/**
|
||||
* @param int $id
|
||||
*/
|
||||
public function shipAction($id)
|
||||
{
|
||||
$order = OrderFactory::getOrder($id);
|
||||
try {
|
||||
$order->shipOrder();
|
||||
} catch (Exception $e) {
|
||||
//handle error!
|
||||
}
|
||||
// response to browser
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*/
|
||||
public function completeAction($id)
|
||||
{
|
||||
$order = OrderFactory::getOrder($id);
|
||||
try {
|
||||
$order->completeOrder();
|
||||
} catch (Exception $e) {
|
||||
//handle error!
|
||||
}
|
||||
// response to browser
|
||||
}
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\State;
|
||||
|
||||
/**
|
||||
* Class OrderFactory.
|
||||
*/
|
||||
class OrderFactory
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
throw new \Exception('Can not instance the OrderFactory class!');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return CreateOrder|ShippingOrder
|
||||
*/
|
||||
public static function getOrder($id)
|
||||
{
|
||||
$order = 'Get Order From Database';
|
||||
|
||||
switch ($order['status']) {
|
||||
case 'created':
|
||||
return new CreateOrder($order);
|
||||
case 'shipping':
|
||||
return new ShippingOrder($order);
|
||||
default:
|
||||
throw new \Exception('Order status error!');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
34
Behavioral/State/OrderRepository.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\State;
|
||||
|
||||
class OrderRepository
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $orders = [
|
||||
1 => ['status' => 'created'],
|
||||
2 => ['status' => 'shipping'],
|
||||
3 => ['status' => 'completed'],
|
||||
];
|
||||
|
||||
public static function findById(int $id): Order
|
||||
{
|
||||
if (!isset(self::$orders[$id])) {
|
||||
throw new \InvalidArgumentException(sprintf('Order with id %d does not exist', $id));
|
||||
}
|
||||
|
||||
$order = self::$orders[$id];
|
||||
|
||||
switch ($order['status']) {
|
||||
case 'created':
|
||||
return new CreateOrder($order);
|
||||
case 'shipping':
|
||||
return new ShippingOrder($order);
|
||||
default:
|
||||
throw new \InvalidArgumentException('Invalid order status given');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -20,21 +20,15 @@ Code
|
||||
|
||||
You can also find these code on `GitHub`_
|
||||
|
||||
OrderController.php
|
||||
OrderRepository.php
|
||||
|
||||
.. literalinclude:: OrderController.php
|
||||
.. literalinclude:: OrderRepository.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
OrderFactory.php
|
||||
Order.php
|
||||
|
||||
.. literalinclude:: OrderFactory.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
OrderInterface.php
|
||||
|
||||
.. literalinclude:: OrderInterface.php
|
||||
.. literalinclude:: Order.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
|
@@ -2,49 +2,34 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\State;
|
||||
|
||||
/**
|
||||
* Class ShippingOrder.
|
||||
*/
|
||||
class ShippingOrder implements OrderInterface
|
||||
class ShippingOrder implements Order
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $order;
|
||||
private $details;
|
||||
|
||||
/**
|
||||
* @param array $order
|
||||
*
|
||||
* @throws \Exception
|
||||
* @param array $details
|
||||
*/
|
||||
public function __construct(array $order)
|
||||
public function __construct(array $details)
|
||||
{
|
||||
if (empty($order)) {
|
||||
throw new \Exception('Order can not be empty!');
|
||||
}
|
||||
$this->order = $order;
|
||||
$this->details = $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function shipOrder()
|
||||
{
|
||||
//Can not ship the order which status is shipping, throw exception;
|
||||
throw new \Exception('Can not ship the order which status is shipping!');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function completeOrder()
|
||||
{
|
||||
$this->order['status'] = 'completed';
|
||||
$this->order['updatedTime'] = time();
|
||||
$this->details['status'] = 'completed';
|
||||
$this->details['updatedTime'] = time();
|
||||
}
|
||||
|
||||
// Setting the new order status into database;
|
||||
return $this->updateOrder($this->order);
|
||||
public function getStatus(): string
|
||||
{
|
||||
return $this->details['status'];
|
||||
}
|
||||
}
|
||||
|
33
Behavioral/State/Tests/StateTest.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\State\Tests;
|
||||
|
||||
use DesignPatterns\Behavioral\State\OrderRepository;
|
||||
|
||||
class StateTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testCanShipCreatedOrder()
|
||||
{
|
||||
$order = (new OrderRepository())->findById(1);
|
||||
$order->shipOrder();
|
||||
|
||||
$this->assertEquals('shipping', $order->getStatus());
|
||||
}
|
||||
|
||||
public function testCanCompleteShippedOrder()
|
||||
{
|
||||
$order = (new OrderRepository())->findById(2);
|
||||
$order->completeOrder();
|
||||
|
||||
$this->assertEquals('completed', $order->getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Exception
|
||||
*/
|
||||
public function testThrowsExceptionWhenTryingToCompleteCreatedOrder()
|
||||
{
|
||||
$order = (new OrderRepository())->findById(1);
|
||||
$order->completeOrder();
|
||||
}
|
||||
}
|
@@ -2,16 +2,13 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\Strategy;
|
||||
|
||||
/**
|
||||
* Class ComparatorInterface.
|
||||
*/
|
||||
interface ComparatorInterface
|
||||
{
|
||||
/**
|
||||
* @param mixed $a
|
||||
* @param mixed $b
|
||||
*
|
||||
* @return bool
|
||||
* @return int
|
||||
*/
|
||||
public function compare($a, $b);
|
||||
public function compare($a, $b): int;
|
||||
}
|
||||
|
@@ -2,23 +2,19 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\Strategy;
|
||||
|
||||
/**
|
||||
* Class DateComparator.
|
||||
*/
|
||||
class DateComparator implements ComparatorInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @param mixed $a
|
||||
* @param mixed $b
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function compare($a, $b)
|
||||
public function compare($a, $b): int
|
||||
{
|
||||
$aDate = new \DateTime($a['date']);
|
||||
$bDate = new \DateTime($b['date']);
|
||||
|
||||
if ($aDate == $bDate) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $aDate < $bDate ? -1 : 1;
|
||||
return $aDate <=> $bDate;
|
||||
}
|
||||
}
|
||||
|
@@ -2,20 +2,16 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\Strategy;
|
||||
|
||||
/**
|
||||
* Class IdComparator.
|
||||
*/
|
||||
class IdComparator implements ComparatorInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @param mixed $a
|
||||
* @param mixed $b
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function compare($a, $b)
|
||||
public function compare($a, $b): int
|
||||
{
|
||||
if ($a['id'] == $b['id']) {
|
||||
return 0;
|
||||
} else {
|
||||
return $a['id'] < $b['id'] ? -1 : 1;
|
||||
}
|
||||
return $a['id'] <=> $b['id'];
|
||||
}
|
||||
}
|
||||
|
@@ -2,9 +2,6 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\Strategy;
|
||||
|
||||
/**
|
||||
* Class ObjectCollection.
|
||||
*/
|
||||
class ObjectCollection
|
||||
{
|
||||
/**
|
||||
@@ -20,30 +17,24 @@ class ObjectCollection
|
||||
/**
|
||||
* @param array $elements
|
||||
*/
|
||||
public function __construct(array $elements = array())
|
||||
public function __construct(array $elements = [])
|
||||
{
|
||||
$this->elements = $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function sort()
|
||||
public function sort(): array
|
||||
{
|
||||
if (!$this->comparator) {
|
||||
throw new \LogicException('Comparator is not set');
|
||||
}
|
||||
|
||||
$callback = array($this->comparator, 'compare');
|
||||
uasort($this->elements, $callback);
|
||||
uasort($this->elements, [$this->comparator, 'compare']);
|
||||
|
||||
return $this->elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ComparatorInterface $comparator
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setComparator(ComparatorInterface $comparator)
|
||||
{
|
||||
|
@@ -5,43 +5,42 @@ namespace DesignPatterns\Behavioral\Strategy\Tests;
|
||||
use DesignPatterns\Behavioral\Strategy\DateComparator;
|
||||
use DesignPatterns\Behavioral\Strategy\IdComparator;
|
||||
use DesignPatterns\Behavioral\Strategy\ObjectCollection;
|
||||
use DesignPatterns\Behavioral\Strategy\Strategy;
|
||||
|
||||
/**
|
||||
* Tests for Strategy pattern.
|
||||
*/
|
||||
class StrategyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function getIdCollection()
|
||||
public function provideIntegers()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
array(array('id' => 2), array('id' => 1), array('id' => 3)),
|
||||
array('id' => 1),
|
||||
),
|
||||
array(
|
||||
array(array('id' => 3), array('id' => 2), array('id' => 1)),
|
||||
array('id' => 1),
|
||||
),
|
||||
);
|
||||
return [
|
||||
[
|
||||
[['id' => 2], ['id' => 1], ['id' => 3]],
|
||||
['id' => 1],
|
||||
],
|
||||
[
|
||||
[['id' => 3], ['id' => 2], ['id' => 1]],
|
||||
['id' => 1],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDateCollection()
|
||||
public function providateDates()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
array(array('date' => '2014-03-03'), array('date' => '2015-03-02'), array('date' => '2013-03-01')),
|
||||
array('date' => '2013-03-01'),
|
||||
),
|
||||
array(
|
||||
array(array('date' => '2014-02-03'), array('date' => '2013-02-01'), array('date' => '2015-02-02')),
|
||||
array('date' => '2013-02-01'),
|
||||
),
|
||||
);
|
||||
return [
|
||||
[
|
||||
[['date' => '2014-03-03'], ['date' => '2015-03-02'], ['date' => '2013-03-01']],
|
||||
['date' => '2013-03-01'],
|
||||
],
|
||||
[
|
||||
[['date' => '2014-02-03'], ['date' => '2013-02-01'], ['date' => '2015-02-02']],
|
||||
['date' => '2013-02-01'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getIdCollection
|
||||
* @dataProvider provideIntegers
|
||||
*
|
||||
* @param array $collection
|
||||
* @param array $expected
|
||||
*/
|
||||
public function testIdComparator($collection, $expected)
|
||||
{
|
||||
@@ -54,7 +53,10 @@ class StrategyTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getDateCollection
|
||||
* @dataProvider providateDates
|
||||
*
|
||||
* @param array $collection
|
||||
* @param array $expected
|
||||
*/
|
||||
public function testDateComparator($collection, $expected)
|
||||
{
|
||||
|
@@ -2,16 +2,10 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\TemplateMethod;
|
||||
|
||||
/**
|
||||
* BeachJourney is vacation at the beach.
|
||||
*/
|
||||
class BeachJourney extends Journey
|
||||
{
|
||||
/**
|
||||
* prints what to do to enjoy your vacation.
|
||||
*/
|
||||
protected function enjoyVacation()
|
||||
protected function enjoyVacation(): string
|
||||
{
|
||||
echo "Swimming and sun-bathing\n";
|
||||
return "Swimming and sun-bathing";
|
||||
}
|
||||
}
|
||||
|
@@ -2,16 +2,15 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\TemplateMethod;
|
||||
|
||||
/**
|
||||
* CityJourney is a journey in a city.
|
||||
*/
|
||||
class CityJourney extends Journey
|
||||
{
|
||||
/**
|
||||
* prints what to do in your journey to enjoy vacation.
|
||||
*/
|
||||
protected function enjoyVacation()
|
||||
protected function enjoyVacation(): string
|
||||
{
|
||||
echo "Eat, drink, take photos and sleep\n";
|
||||
return "Eat, drink, take photos and sleep";
|
||||
}
|
||||
|
||||
protected function buyGift(): string
|
||||
{
|
||||
return "Buy a gift";
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,11 @@ namespace DesignPatterns\Behavioral\TemplateMethod;
|
||||
|
||||
abstract class Journey
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $thingsToDo = [];
|
||||
|
||||
/**
|
||||
* This is the public service provided by this class and its subclasses.
|
||||
* Notice it is final to "freeze" the global behavior of algorithm.
|
||||
@@ -12,46 +17,49 @@ abstract class Journey
|
||||
*/
|
||||
final public function takeATrip()
|
||||
{
|
||||
$this->buyAFlight();
|
||||
$this->takePlane();
|
||||
$this->enjoyVacation();
|
||||
$this->buyGift();
|
||||
$this->takePlane();
|
||||
$this->thingsToDo[] = $this->buyAFlight();
|
||||
$this->thingsToDo[] = $this->takePlane();
|
||||
$this->thingsToDo[] = $this->enjoyVacation();
|
||||
$buyGift = $this->buyGift();
|
||||
|
||||
if ($buyGift !== null) {
|
||||
$this->thingsToDo[] = $buyGift;
|
||||
}
|
||||
|
||||
$this->thingsToDo[] = $this->takePlane();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method must be implemented, this is the key-feature of this pattern.
|
||||
*/
|
||||
abstract protected function enjoyVacation();
|
||||
abstract protected function enjoyVacation(): string;
|
||||
|
||||
/**
|
||||
* This method is also part of the algorithm but it is optional.
|
||||
* This is an "adapter" (do not confuse with the Adapter pattern, not related)
|
||||
* You can override it only if you need to.
|
||||
* You can override it only if you need to
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
protected function buyGift()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private function buyAFlight(): string
|
||||
{
|
||||
return 'Buy a flight ticket';
|
||||
}
|
||||
|
||||
private function takePlane(): string
|
||||
{
|
||||
return 'Taking the plane';
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be unknown by subclasses (better).
|
||||
* @return string[]
|
||||
*/
|
||||
private function buyAFlight()
|
||||
public function getThingsToDo(): array
|
||||
{
|
||||
echo "Buying a flight\n";
|
||||
return $this->thingsToDo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses will get access to this method but cannot override it and
|
||||
* compromise this algorithm (warning : cause of cyclic dependencies).
|
||||
*/
|
||||
final protected function takePlane()
|
||||
{
|
||||
echo "Taking the plane\n";
|
||||
}
|
||||
|
||||
// A note regarding the keyword "final" : don't use it when you start coding :
|
||||
// add it after you narrow and know exactly what change and what remain unchanged
|
||||
// in this algorithm.
|
||||
// [abstract] x [3 access] x [final] = 12 combinations, it can be hard !
|
||||
}
|
||||
|
@@ -4,40 +4,33 @@ namespace DesignPatterns\Behavioral\TemplateMethod\Tests;
|
||||
|
||||
use DesignPatterns\Behavioral\TemplateMethod;
|
||||
|
||||
/**
|
||||
* JourneyTest tests all journeys.
|
||||
*/
|
||||
class JourneyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testBeach()
|
||||
public function testCanGetOnVacationOnTheBeach()
|
||||
{
|
||||
$journey = new TemplateMethod\BeachJourney();
|
||||
$this->expectOutputRegex('#sun-bathing#');
|
||||
$journey->takeATrip();
|
||||
$beachJourney = new TemplateMethod\BeachJourney();
|
||||
$beachJourney->takeATrip();
|
||||
|
||||
$this->assertEquals(
|
||||
['Buy a flight ticket', 'Taking the plane', 'Swimming and sun-bathing', 'Taking the plane'],
|
||||
$beachJourney->getThingsToDo()
|
||||
);
|
||||
}
|
||||
|
||||
public function testCity()
|
||||
public function testCanGetOnAJourneyToACity()
|
||||
{
|
||||
$journey = new TemplateMethod\CityJourney();
|
||||
$this->expectOutputRegex('#drink#');
|
||||
$journey->takeATrip();
|
||||
}
|
||||
$beachJourney = new TemplateMethod\CityJourney();
|
||||
$beachJourney->takeATrip();
|
||||
|
||||
/**
|
||||
* How to test an abstract template method with PHPUnit.
|
||||
*/
|
||||
public function testLasVegas()
|
||||
{
|
||||
$journey = $this->getMockForAbstractClass('DesignPatterns\Behavioral\TemplateMethod\Journey');
|
||||
$journey->expects($this->once())
|
||||
->method('enjoyVacation')
|
||||
->will($this->returnCallback(array($this, 'mockUpVacation')));
|
||||
$this->expectOutputRegex('#Las Vegas#');
|
||||
$journey->takeATrip();
|
||||
}
|
||||
|
||||
public function mockUpVacation()
|
||||
{
|
||||
echo "Fear and loathing in Las Vegas\n";
|
||||
$this->assertEquals(
|
||||
[
|
||||
'Buy a flight ticket',
|
||||
'Taking the plane',
|
||||
'Eat, drink, take photos and sleep',
|
||||
'Buy a gift',
|
||||
'Taking the plane'
|
||||
],
|
||||
$beachJourney->getThingsToDo()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,35 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Diagram>
|
||||
<ID>PHP</ID>
|
||||
<OriginalElement>\DesignPatterns\Behavioral\TemplateMethod\BeachJourney</OriginalElement>
|
||||
<nodes>
|
||||
<node x="152.0" y="177.0">\DesignPatterns\Behavioral\TemplateMethod\CityJourney</node>
|
||||
<node x="47.0" y="0.0">\DesignPatterns\Behavioral\TemplateMethod\Journey</node>
|
||||
<node x="0.0" y="177.0">\DesignPatterns\Behavioral\TemplateMethod\BeachJourney</node>
|
||||
</nodes>
|
||||
<notes />
|
||||
<edges>
|
||||
<edge source="\DesignPatterns\Behavioral\TemplateMethod\CityJourney" target="\DesignPatterns\Behavioral\TemplateMethod\Journey">
|
||||
<point x="0.0" y="-23.5" />
|
||||
<point x="218.0" y="152.0" />
|
||||
<point x="189.5" y="152.0" />
|
||||
<point x="47.5" y="63.5" />
|
||||
</edge>
|
||||
<edge source="\DesignPatterns\Behavioral\TemplateMethod\BeachJourney" target="\DesignPatterns\Behavioral\TemplateMethod\Journey">
|
||||
<point x="0.0" y="-23.5" />
|
||||
<point x="66.0" y="152.0" />
|
||||
<point x="94.5" y="152.0" />
|
||||
<point x="-47.5" y="63.5" />
|
||||
</edge>
|
||||
</edges>
|
||||
<settings layout="Hierarchic Group" zoom="1.0" x="142.0" y="112.0" />
|
||||
<SelectedNodes />
|
||||
<Categories>
|
||||
<Category>Fields</Category>
|
||||
<Category>Constants</Category>
|
||||
<Category>Constructors</Category>
|
||||
<Category>Methods</Category>
|
||||
</Categories>
|
||||
<VISIBILITY>private</VISIBILITY>
|
||||
</Diagram>
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Diagram>
|
||||
<ID>PHP</ID>
|
||||
<OriginalElement>\DesignPatterns\Behavioral\TemplateMethod\BeachJourney</OriginalElement>
|
||||
<nodes>
|
||||
<node x="-18.0" y="234.0">\DesignPatterns\Behavioral\TemplateMethod\BeachJourney</node>
|
||||
<node x="13.0" y="-3.0">\DesignPatterns\Behavioral\TemplateMethod\Journey</node>
|
||||
<node x="151.0" y="234.0">\DesignPatterns\Behavioral\TemplateMethod\CityJourney</node>
|
||||
</nodes>
|
||||
<notes />
|
||||
<edges>
|
||||
<edge source="\DesignPatterns\Behavioral\TemplateMethod\BeachJourney" target="\DesignPatterns\Behavioral\TemplateMethod\Journey">
|
||||
<point x="0.0" y="-23.5" />
|
||||
<point x="66.0" y="152.0" />
|
||||
<point x="94.5" y="152.0" />
|
||||
<point x="-47.5" y="63.5" />
|
||||
</edge>
|
||||
<edge source="\DesignPatterns\Behavioral\TemplateMethod\CityJourney" target="\DesignPatterns\Behavioral\TemplateMethod\Journey">
|
||||
<point x="0.0" y="-23.5" />
|
||||
<point x="218.0" y="152.0" />
|
||||
<point x="189.5" y="152.0" />
|
||||
<point x="47.5" y="63.5" />
|
||||
</edge>
|
||||
</edges>
|
||||
<settings layout="Hierarchic Group" zoom="1.0" x="142.0" y="112.0" />
|
||||
<SelectedNodes />
|
||||
<Categories>
|
||||
<Category>Fields</Category>
|
||||
<Category>Constants</Category>
|
||||
<Category>Constructors</Category>
|
||||
<Category>Methods</Category>
|
||||
</Categories>
|
||||
<VISIBILITY>private</VISIBILITY>
|
||||
</Diagram>
|
||||
|
||||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 107 KiB |
@@ -2,29 +2,25 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\Visitor;
|
||||
|
||||
/**
|
||||
* An example of a Visitor: Group.
|
||||
*/
|
||||
class Group extends Role
|
||||
class Group implements Role
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct($name)
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = (string) $name;
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Group: '.$this->name;
|
||||
return sprintf('Group: %s', $this->name);
|
||||
}
|
||||
|
||||
public function accept(RoleVisitorInterface $visitor)
|
||||
{
|
||||
$visitor->visitGroup($this);
|
||||
}
|
||||
}
|
||||
|
@@ -31,9 +31,9 @@ RoleVisitorInterface.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
RolePrintVisitor.php
|
||||
RoleVisitor.php
|
||||
|
||||
.. literalinclude:: RolePrintVisitor.php
|
||||
.. literalinclude:: RoleVisitor.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
|
@@ -2,32 +2,7 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\Visitor;
|
||||
|
||||
/**
|
||||
* class Role.
|
||||
*/
|
||||
abstract class Role
|
||||
interface Role
|
||||
{
|
||||
/**
|
||||
* This method handles a double dispatch based on the short name of the Visitor.
|
||||
*
|
||||
* Feel free to override it if your object must call another visiting behavior
|
||||
*
|
||||
* @param \DesignPatterns\Behavioral\Visitor\RoleVisitorInterface $visitor
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function accept(RoleVisitorInterface $visitor)
|
||||
{
|
||||
// this trick to simulate double-dispatch based on type-hinting
|
||||
$klass = get_called_class();
|
||||
preg_match('#([^\\\\]+)$#', $klass, $extract);
|
||||
$visitingMethod = 'visit'.$extract[1];
|
||||
|
||||
// this ensures strong typing with visitor interface, not some visitor objects
|
||||
if (!method_exists(__NAMESPACE__.'\RoleVisitorInterface', $visitingMethod)) {
|
||||
throw new \InvalidArgumentException("The visitor you provide cannot visit a $klass instance");
|
||||
}
|
||||
|
||||
call_user_func(array($visitor, $visitingMethod), $this);
|
||||
}
|
||||
public function accept(RoleVisitorInterface $visitor);
|
||||
}
|
||||
|
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Visitor;
|
||||
|
||||
/**
|
||||
* Visitor Pattern.
|
||||
*
|
||||
* An implementation of a concrete Visitor
|
||||
*/
|
||||
class RolePrintVisitor implements RoleVisitorInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function visitGroup(Group $role)
|
||||
{
|
||||
echo 'Role: '.$role->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function visitUser(User $role)
|
||||
{
|
||||
echo 'Role: '.$role->getName();
|
||||
}
|
||||
}
|
29
Behavioral/Visitor/RoleVisitor.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Visitor;
|
||||
|
||||
class RoleVisitor implements RoleVisitorInterface
|
||||
{
|
||||
/**
|
||||
* @var Role[]
|
||||
*/
|
||||
private $visited = [];
|
||||
|
||||
public function visitGroup(Group $role)
|
||||
{
|
||||
$this->visited[] = $role;
|
||||
}
|
||||
|
||||
public function visitUser(User $role)
|
||||
{
|
||||
$this->visited[] = $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Role[]
|
||||
*/
|
||||
public function getVisited(): array
|
||||
{
|
||||
return $this->visited;
|
||||
}
|
||||
}
|
@@ -3,29 +3,12 @@
|
||||
namespace DesignPatterns\Behavioral\Visitor;
|
||||
|
||||
/**
|
||||
* Visitor Pattern.
|
||||
*
|
||||
* The contract for the visitor.
|
||||
*
|
||||
* Note 1 : in C++ or Java, with method polymorphism based on type-hint, there are many
|
||||
* methods visit() with different type for the 'role' parameter.
|
||||
*
|
||||
* Note 2 : the visitor must not choose itself which method to
|
||||
* invoke, it is the Visitee that make this decision.
|
||||
* Note: the visitor must not choose itself which method to
|
||||
* invoke, it is the Visitee that make this decision
|
||||
*/
|
||||
interface RoleVisitorInterface
|
||||
{
|
||||
/**
|
||||
* Visit a User object.
|
||||
*
|
||||
* @param \DesignPatterns\Behavioral\Visitor\User $role
|
||||
*/
|
||||
public function visitUser(User $role);
|
||||
|
||||
/**
|
||||
* Visit a Group object.
|
||||
*
|
||||
* @param \DesignPatterns\Behavioral\Visitor\Group $role
|
||||
*/
|
||||
public function visitGroup(Group $role);
|
||||
}
|
||||
|
@@ -4,42 +4,34 @@ namespace DesignPatterns\Tests\Visitor\Tests;
|
||||
|
||||
use DesignPatterns\Behavioral\Visitor;
|
||||
|
||||
/**
|
||||
* VisitorTest tests the visitor pattern.
|
||||
*/
|
||||
class VisitorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected $visitor;
|
||||
/**
|
||||
* @var Visitor\RoleVisitor
|
||||
*/
|
||||
private $visitor;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->visitor = new Visitor\RolePrintVisitor();
|
||||
$this->visitor = new Visitor\RoleVisitor();
|
||||
}
|
||||
|
||||
public function getRole()
|
||||
public function provideRoles()
|
||||
{
|
||||
return array(
|
||||
array(new Visitor\User('Dominik'), 'Role: User Dominik'),
|
||||
array(new Visitor\Group('Administrators'), 'Role: Group: Administrators'),
|
||||
);
|
||||
return [
|
||||
[new Visitor\User('Dominik')],
|
||||
[new Visitor\Group('Administrators')],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getRole
|
||||
* @dataProvider provideRoles
|
||||
*
|
||||
* @param Visitor\Role $role
|
||||
*/
|
||||
public function testVisitSomeRole(Visitor\Role $role, $expect)
|
||||
public function testVisitSomeRole(Visitor\Role $role)
|
||||
{
|
||||
$this->expectOutputString($expect);
|
||||
$role->accept($this->visitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \InvalidArgumentException
|
||||
* @expectedExceptionMessage Mock
|
||||
*/
|
||||
public function testUnknownObject()
|
||||
{
|
||||
$mock = $this->getMockForAbstractClass('DesignPatterns\Behavioral\Visitor\Role');
|
||||
$mock->accept($this->visitor);
|
||||
$this->assertSame($role, $this->visitor->getVisited()[0]);
|
||||
}
|
||||
}
|
||||
|
@@ -2,31 +2,25 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\Visitor;
|
||||
|
||||
/**
|
||||
* Visitor Pattern.
|
||||
*
|
||||
* One example for a visitee. Each visitee has to extends Role
|
||||
*/
|
||||
class User extends Role
|
||||
class User implements Role
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct($name)
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = (string) $name;
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return 'User '.$this->name;
|
||||
return sprintf('User %s', $this->name);
|
||||
}
|
||||
|
||||
public function accept(RoleVisitorInterface $visitor)
|
||||
{
|
||||
$visitor->visitUser($this);
|
||||
}
|
||||
}
|
||||
|
@@ -3,38 +3,10 @@
|
||||
namespace DesignPatterns\Creational\AbstractFactory;
|
||||
|
||||
/**
|
||||
* class AbstractFactory.
|
||||
*
|
||||
* Sometimes also known as "Kit" in a GUI libraries.
|
||||
*
|
||||
* This design pattern implements the Dependency Inversion Principle since
|
||||
* it is the concrete subclass which creates concrete components.
|
||||
*
|
||||
* In this case, the abstract factory is a contract for creating some components
|
||||
* for the web. There are two components : Text and Picture. There are two ways
|
||||
* of rendering : HTML or JSON.
|
||||
*
|
||||
* Therefore 4 concrete classes, but the client just needs to know this contract
|
||||
* to build a correct HTTP response (for a HTML page or for an AJAX request).
|
||||
* for the web. There are two ways of rendering text: HTML and JSON
|
||||
*/
|
||||
abstract class AbstractFactory
|
||||
{
|
||||
/**
|
||||
* Creates a text component.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return Text
|
||||
*/
|
||||
abstract public function createText($content);
|
||||
|
||||
/**
|
||||
* Creates a picture component.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $name
|
||||
*
|
||||
* @return Picture
|
||||
*/
|
||||
abstract public function createPicture($path, $name = '');
|
||||
abstract public function createText(string $content): Text;
|
||||
}
|
||||
|
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Creational\AbstractFactory\Html;
|
||||
|
||||
use DesignPatterns\Creational\AbstractFactory\Picture as BasePicture;
|
||||
|
||||
/**
|
||||
* Class Picture.
|
||||
*
|
||||
* Picture is a concrete image for HTML rendering
|
||||
*/
|
||||
class Picture extends BasePicture
|
||||
{
|
||||
/**
|
||||
* some crude rendering from HTML output.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return sprintf('<img src="%s" title="%s"/>', $this->path, $this->name);
|
||||
}
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Creational\AbstractFactory\Html;
|
||||
|
||||
use DesignPatterns\Creational\AbstractFactory\Text as BaseText;
|
||||
|
||||
/**
|
||||
* Class Text.
|
||||
*
|
||||
* Text is a concrete text for HTML rendering
|
||||
*/
|
||||
class Text extends BaseText
|
||||
{
|
||||
/**
|
||||
* some crude rendering from HTML output.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return '<div>'.htmlspecialchars($this->text).'</div>';
|
||||
}
|
||||
}
|
@@ -2,35 +2,10 @@
|
||||
|
||||
namespace DesignPatterns\Creational\AbstractFactory;
|
||||
|
||||
/**
|
||||
* Class HtmlFactory.
|
||||
*
|
||||
* HtmlFactory is a concrete factory for HTML component
|
||||
*/
|
||||
class HtmlFactory extends AbstractFactory
|
||||
{
|
||||
/**
|
||||
* Creates a picture component.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $name
|
||||
*
|
||||
* @return Html\Picture|Picture
|
||||
*/
|
||||
public function createPicture($path, $name = '')
|
||||
public function createText(string $content): Text
|
||||
{
|
||||
return new Html\Picture($path, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a text component.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return Html\Text|Text
|
||||
*/
|
||||
public function createText($content)
|
||||
{
|
||||
return new Html\Text($content);
|
||||
return new HtmlText($content);
|
||||
}
|
||||
}
|
||||
|
8
Creational/AbstractFactory/HtmlText.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Creational\AbstractFactory;
|
||||
|
||||
class HtmlText extends Text
|
||||
{
|
||||
// do something here
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Creational\AbstractFactory\Json;
|
||||
|
||||
use DesignPatterns\Creational\AbstractFactory\Picture as BasePicture;
|
||||
|
||||
/**
|
||||
* Class Picture.
|
||||
*
|
||||
* Picture is a concrete image for JSON rendering
|
||||
*/
|
||||
class Picture extends BasePicture
|
||||
{
|
||||
/**
|
||||
* some crude rendering from JSON output.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return json_encode(array('title' => $this->name, 'path' => $this->path));
|
||||
}
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Creational\AbstractFactory\Json;
|
||||
|
||||
use DesignPatterns\Creational\AbstractFactory\Text as BaseText;
|
||||
|
||||
/**
|
||||
* Class Text.
|
||||
*
|
||||
* Text is a text component with a JSON rendering
|
||||
*/
|
||||
class Text extends BaseText
|
||||
{
|
||||
/**
|
||||
* some crude rendering from JSON output.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return json_encode(array('content' => $this->text));
|
||||
}
|
||||
}
|
@@ -2,36 +2,10 @@
|
||||
|
||||
namespace DesignPatterns\Creational\AbstractFactory;
|
||||
|
||||
/**
|
||||
* Class JsonFactory.
|
||||
*
|
||||
* JsonFactory is a factory for creating a family of JSON component
|
||||
* (example for ajax)
|
||||
*/
|
||||
class JsonFactory extends AbstractFactory
|
||||
{
|
||||
/**
|
||||
* Creates a picture component.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $name
|
||||
*
|
||||
* @return Json\Picture|Picture
|
||||
*/
|
||||
public function createPicture($path, $name = '')
|
||||
public function createText(string $content): Text
|
||||
{
|
||||
return new Json\Picture($path, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a text component.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return Json\Text|Text
|
||||
*/
|
||||
public function createText($content)
|
||||
{
|
||||
return new Json\Text($content);
|
||||
return new JsonText($content);
|
||||
}
|
||||
}
|
||||
|
8
Creational/AbstractFactory/JsonText.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Creational\AbstractFactory;
|
||||
|
||||
class JsonText extends Text
|
||||
{
|
||||
// do something here
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Creational\AbstractFactory;
|
||||
|
||||
/**
|
||||
* Interface MediaInterface.
|
||||
*
|
||||
* This contract is not part of the pattern, in general case, each component
|
||||
* are not related
|
||||
*/
|
||||
interface MediaInterface
|
||||
{
|
||||
/**
|
||||
* some crude rendering from JSON or html output (depended on concrete class).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render();
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Creational\AbstractFactory;
|
||||
|
||||
/**
|
||||
* Class Picture.
|
||||
*/
|
||||
abstract class Picture implements MediaInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct($path, $name = '')
|
||||
{
|
||||
$this->name = (string) $name;
|
||||
$this->path = (string) $path;
|
||||
}
|
||||
}
|
@@ -39,45 +39,21 @@ HtmlFactory.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
MediaInterface.php
|
||||
|
||||
.. literalinclude:: MediaInterface.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
Picture.php
|
||||
|
||||
.. literalinclude:: Picture.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
Text.php
|
||||
|
||||
.. literalinclude:: Text.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
Json/Picture.php
|
||||
JsonText.php
|
||||
|
||||
.. literalinclude:: Json/Picture.php
|
||||
.. literalinclude:: JsonText.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
Json/Text.php
|
||||
HtmlText.php
|
||||
|
||||
.. literalinclude:: Json/Text.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
Html/Picture.php
|
||||
|
||||
.. literalinclude:: Html/Picture.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
Html/Text.php
|
||||
|
||||
.. literalinclude:: Html/Text.php
|
||||
.. literalinclude:: HtmlText.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
|
@@ -2,43 +2,24 @@
|
||||
|
||||
namespace DesignPatterns\Creational\AbstractFactory\Tests;
|
||||
|
||||
use DesignPatterns\Creational\AbstractFactory\AbstractFactory;
|
||||
use DesignPatterns\Creational\AbstractFactory\HtmlFactory;
|
||||
use DesignPatterns\Creational\AbstractFactory\JsonFactory;
|
||||
|
||||
/**
|
||||
* AbstractFactoryTest tests concrete factories.
|
||||
*/
|
||||
class AbstractFactoryTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function getFactories()
|
||||
public function testCanCreateHtmlText()
|
||||
{
|
||||
return array(
|
||||
array(new JsonFactory()),
|
||||
array(new HtmlFactory()),
|
||||
);
|
||||
$factory = new HtmlFactory();
|
||||
$text = $factory->createText('foobar');
|
||||
|
||||
$this->assertInstanceOf('DesignPatterns\Creational\AbstractFactory\HtmlText', $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the client of factories. Note that the client does not
|
||||
* care which factory is given to him, he can create any component he
|
||||
* wants and render how he wants.
|
||||
*
|
||||
* @dataProvider getFactories
|
||||
*/
|
||||
public function testComponentCreation(AbstractFactory $factory)
|
||||
public function testCanCreateJsonText()
|
||||
{
|
||||
$article = array(
|
||||
$factory->createText('Lorem Ipsum'),
|
||||
$factory->createPicture('/image.jpg', 'caption'),
|
||||
$factory->createText('footnotes'),
|
||||
);
|
||||
$factory = new JsonFactory();
|
||||
$text = $factory->createText('foobar');
|
||||
|
||||
$this->assertContainsOnly('DesignPatterns\Creational\AbstractFactory\MediaInterface', $article);
|
||||
|
||||
/* this is the time to look at the Builder pattern. This pattern
|
||||
* helps you to create complex object like that article above with
|
||||
* a given Abstract Factory
|
||||
*/
|
||||
$this->assertInstanceOf('DesignPatterns\Creational\AbstractFactory\JsonText', $text);
|
||||
}
|
||||
}
|
||||
|
@@ -2,21 +2,15 @@
|
||||
|
||||
namespace DesignPatterns\Creational\AbstractFactory;
|
||||
|
||||
/**
|
||||
* Class Text.
|
||||
*/
|
||||
abstract class Text implements MediaInterface
|
||||
abstract class Text
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $text;
|
||||
private $text;
|
||||
|
||||
/**
|
||||
* @param string $text
|
||||
*/
|
||||
public function __construct($text)
|
||||
public function __construct(string $text)
|
||||
{
|
||||
$this->text = (string) $text;
|
||||
$this->text = $text;
|
||||
}
|
||||
}
|
||||
|
@@ -1,38 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Diagram>
|
||||
<ID>PHP</ID>
|
||||
<OriginalElement>\DesignPatterns\Creational\AbstractFactory\AbstractFactory</OriginalElement>
|
||||
<nodes>
|
||||
<node x="281.0" y="228.0">\DesignPatterns\Creational\AbstractFactory\Html\Text</node>
|
||||
<node x="150.0" y="229.0">\DesignPatterns\Creational\AbstractFactory\Html\Picture</node>
|
||||
<node x="223.0" y="117.0">\DesignPatterns\Creational\AbstractFactory\HtmlFactory</node>
|
||||
<node x="0.0" y="117.0">\DesignPatterns\Creational\AbstractFactory\JsonFactory</node>
|
||||
<node x="126.0" y="0.0">\DesignPatterns\Creational\AbstractFactory\AbstractFactory</node>
|
||||
<node x="0.0" y="229.0">\DesignPatterns\Creational\AbstractFactory\MediaInterface</node>
|
||||
</nodes>
|
||||
<notes />
|
||||
<edges>
|
||||
<edge source="\DesignPatterns\Creational\AbstractFactory\HtmlFactory" target="\DesignPatterns\Creational\AbstractFactory\AbstractFactory">
|
||||
<point x="0.0" y="-33.5" />
|
||||
<point x="324.5" y="92.0" />
|
||||
<point x="256.5" y="92.0" />
|
||||
<point x="43.5" y="33.5" />
|
||||
</edge>
|
||||
<edge source="\DesignPatterns\Creational\AbstractFactory\JsonFactory" target="\DesignPatterns\Creational\AbstractFactory\AbstractFactory">
|
||||
<point x="0.0" y="-33.5" />
|
||||
<point x="101.5" y="92.0" />
|
||||
<point x="169.5" y="92.0" />
|
||||
<point x="-43.5" y="33.5" />
|
||||
</edge>
|
||||
</edges>
|
||||
<settings layout="Hierarchic Group" zoom="1.0" x="213.0" y="138.0" />
|
||||
<SelectedNodes />
|
||||
<Categories>
|
||||
<Category>Fields</Category>
|
||||
<Category>Constants</Category>
|
||||
<Category>Constructors</Category>
|
||||
<Category>Methods</Category>
|
||||
</Categories>
|
||||
<VISIBILITY>private</VISIBILITY>
|
||||
</Diagram>
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Diagram>
|
||||
<ID>PHP</ID>
|
||||
<OriginalElement>\DesignPatterns\Creational\AbstractFactory\AbstractFactory</OriginalElement>
|
||||
<nodes>
|
||||
<node x="214.0" y="101.0">\DesignPatterns\Creational\AbstractFactory\JsonFactory</node>
|
||||
<node x="107.0" y="0.0">\DesignPatterns\Creational\AbstractFactory\AbstractFactory</node>
|
||||
<node x="0.0" y="101.0">\DesignPatterns\Creational\AbstractFactory\HtmlFactory</node>
|
||||
<node x="111.0" y="298.0">\DesignPatterns\Creational\AbstractFactory\JsonText</node>
|
||||
<node x="0.0" y="298.0">\DesignPatterns\Creational\AbstractFactory\HtmlText</node>
|
||||
<node x="51.5" y="197.0">\DesignPatterns\Creational\AbstractFactory\Text</node>
|
||||
</nodes>
|
||||
<notes />
|
||||
<edges>
|
||||
<edge source="\DesignPatterns\Creational\AbstractFactory\HtmlText" target="\DesignPatterns\Creational\AbstractFactory\Text">
|
||||
<point x="0.0" y="-14.5" />
|
||||
<point x="45.5" y="273.0" />
|
||||
<point x="75.75" y="273.0" />
|
||||
<point x="-24.25" y="25.5" />
|
||||
</edge>
|
||||
<edge source="\DesignPatterns\Creational\AbstractFactory\JsonText" target="\DesignPatterns\Creational\AbstractFactory\Text">
|
||||
<point x="0.0" y="-14.5" />
|
||||
<point x="154.5" y="273.0" />
|
||||
<point x="124.25" y="273.0" />
|
||||
<point x="24.25" y="25.5" />
|
||||
</edge>
|
||||
<edge source="\DesignPatterns\Creational\AbstractFactory\JsonFactory" target="\DesignPatterns\Creational\AbstractFactory\AbstractFactory">
|
||||
<point x="0.0" y="-25.5" />
|
||||
<point x="311.0" y="76.0" />
|
||||
<point x="252.5" y="76.0" />
|
||||
<point x="48.5" y="25.5" />
|
||||
</edge>
|
||||
<edge source="\DesignPatterns\Creational\AbstractFactory\HtmlFactory" target="\DesignPatterns\Creational\AbstractFactory\AbstractFactory">
|
||||
<point x="0.0" y="-25.5" />
|
||||
<point x="97.0" y="76.0" />
|
||||
<point x="155.5" y="76.0" />
|
||||
<point x="-48.5" y="25.5" />
|
||||
</edge>
|
||||
</edges>
|
||||
<settings layout="Hierarchic Group" zoom="1.0" x="117.0" y="130.5" />
|
||||
<SelectedNodes>
|
||||
<node>\DesignPatterns\Creational\AbstractFactory\AbstractFactory</node>
|
||||
</SelectedNodes>
|
||||
<Categories>
|
||||
<Category>Methods</Category>
|
||||
<Category>Constants</Category>
|
||||
<Category>Fields</Category>
|
||||
</Categories>
|
||||
<VISIBILITY>private</VISIBILITY>
|
||||
</Diagram>
|
||||
|
||||
|