diff --git a/Behavioral/ChainOfResponsibilities/Handler.php b/Behavioral/ChainOfResponsibilities/Handler.php index 679734e..fe4e18b 100644 --- a/Behavioral/ChainOfResponsibilities/Handler.php +++ b/Behavioral/ChainOfResponsibilities/Handler.php @@ -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); } diff --git a/Behavioral/ChainOfResponsibilities/README.rst b/Behavioral/ChainOfResponsibilities/README.rst index b3f47b9..a3e3b66 100644 --- a/Behavioral/ChainOfResponsibilities/README.rst +++ b/Behavioral/ChainOfResponsibilities/README.rst @@ -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 diff --git a/Behavioral/ChainOfResponsibilities/Request.php b/Behavioral/ChainOfResponsibilities/Request.php deleted file mode 100644 index 68bf24e..0000000 --- a/Behavioral/ChainOfResponsibilities/Request.php +++ /dev/null @@ -1,19 +0,0 @@ -data = $data; - } - - protected function processing(Request $req) - { - if ('get' === $req->verb) { - if (array_key_exists($req->key, $this->data)) { - // the handler IS responsible and then processes the request - $req->response = $this->data[$req->key]; - // instead of returning true, I could return the value but it proves - // to be a bad idea. What if the value IS "false" ? - return true; - } - } - - return false; - } -} diff --git a/Behavioral/ChainOfResponsibilities/Responsible/HttpInMemoryCacheHandler.php b/Behavioral/ChainOfResponsibilities/Responsible/HttpInMemoryCacheHandler.php new file mode 100644 index 0000000..9391ceb --- /dev/null +++ b/Behavioral/ChainOfResponsibilities/Responsible/HttpInMemoryCacheHandler.php @@ -0,0 +1,45 @@ +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; + } +} diff --git a/Behavioral/ChainOfResponsibilities/Responsible/SlowDatabaseHandler.php b/Behavioral/ChainOfResponsibilities/Responsible/SlowDatabaseHandler.php new file mode 100644 index 0000000..975ed42 --- /dev/null +++ b/Behavioral/ChainOfResponsibilities/Responsible/SlowDatabaseHandler.php @@ -0,0 +1,21 @@ +data = $data; - } - - protected function processing(Request $req) - { - if ('get' === $req->verb) { - if (array_key_exists($req->key, $this->data)) { - $req->response = $this->data[$req->key]; - - return true; - } - } - - return false; - } -} diff --git a/Behavioral/ChainOfResponsibilities/Tests/ChainTest.php b/Behavioral/ChainOfResponsibilities/Tests/ChainTest.php index 62d1172..296a0c3 100644 --- a/Behavioral/ChainOfResponsibilities/Tests/ChainTest.php +++ b/Behavioral/ChainOfResponsibilities/Tests/ChainTest.php @@ -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)); } } diff --git a/Behavioral/Command/AddMessageDateCommand.php b/Behavioral/Command/AddMessageDateCommand.php index 11bb9af..fcaeb4f 100644 --- a/Behavioral/Command/AddMessageDateCommand.php +++ b/Behavioral/Command/AddMessageDateCommand.php @@ -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. diff --git a/Behavioral/Command/CommandInterface.php b/Behavioral/Command/CommandInterface.php index cd9d9c6..265e862 100644 --- a/Behavioral/Command/CommandInterface.php +++ b/Behavioral/Command/CommandInterface.php @@ -2,9 +2,6 @@ namespace DesignPatterns\Behavioral\Command; -/** - * class CommandInterface. - */ interface CommandInterface { /** diff --git a/Behavioral/Command/HelloCommand.php b/Behavioral/Command/HelloCommand.php index 94d4723..1dbfaf5 100644 --- a/Behavioral/Command/HelloCommand.php +++ b/Behavioral/Command/HelloCommand.php @@ -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'); } } diff --git a/Behavioral/Command/Invoker.php b/Behavioral/Command/Invoker.php index 7942adb..c6e7f93 100644 --- a/Behavioral/Command/Invoker.php +++ b/Behavioral/Command/Invoker.php @@ -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(); } } diff --git a/Behavioral/Command/Receiver.php b/Behavioral/Command/Receiver.php index 956ce74..7ca6343 100644 --- a/Behavioral/Command/Receiver.php +++ b/Behavioral/Command/Receiver.php @@ -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() { diff --git a/Behavioral/Command/Tests/CommandTest.php b/Behavioral/Command/Tests/CommandTest.php index 3852495..cd3ea03 100644 --- a/Behavioral/Command/Tests/CommandTest.php +++ b/Behavioral/Command/Tests/CommandTest.php @@ -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'); } } diff --git a/Behavioral/Command/Tests/UndoableCommandTest.php b/Behavioral/Command/Tests/UndoableCommandTest.php index 5302a7b..8d10259 100644 --- a/Behavioral/Command/Tests/UndoableCommandTest.php +++ b/Behavioral/Command/Tests/UndoableCommandTest.php @@ -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"); } } diff --git a/Behavioral/Command/UndoableCommandInterface.php b/Behavioral/Command/UndoableCommandInterface.php index f9234ab..52b02e9 100644 --- a/Behavioral/Command/UndoableCommandInterface.php +++ b/Behavioral/Command/UndoableCommandInterface.php @@ -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(); } diff --git a/Behavioral/Mediator/Colleague.php b/Behavioral/Mediator/Colleague.php index c74dee5..3a83742 100644 --- a/Behavioral/Mediator/Colleague.php +++ b/Behavioral/Mediator/Colleague.php @@ -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; } } diff --git a/Behavioral/Mediator/Mediator.php b/Behavioral/Mediator/Mediator.php index 98a7890..08e1b71 100644 --- a/Behavioral/Mediator/Mediator.php +++ b/Behavioral/Mediator/Mediator.php @@ -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) diff --git a/Behavioral/Mediator/MediatorInterface.php b/Behavioral/Mediator/MediatorInterface.php index dbdd489..5ec717a 100644 --- a/Behavioral/Mediator/MediatorInterface.php +++ b/Behavioral/Mediator/MediatorInterface.php @@ -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(); } diff --git a/Behavioral/Mediator/Subsystem/Client.php b/Behavioral/Mediator/Subsystem/Client.php index f7a21c9..e7897a4 100644 --- a/Behavioral/Mediator/Subsystem/Client.php +++ b/Behavioral/Mediator/Subsystem/Client.php @@ -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; } diff --git a/Behavioral/Mediator/Subsystem/Database.php b/Behavioral/Mediator/Subsystem/Database.php index 69ad6cf..9255f22 100644 --- a/Behavioral/Mediator/Subsystem/Database.php +++ b/Behavioral/Mediator/Subsystem/Database.php @@ -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'; } diff --git a/Behavioral/Mediator/Subsystem/Server.php b/Behavioral/Mediator/Subsystem/Server.php index 1602bcb..c05be5e 100644 --- a/Behavioral/Mediator/Subsystem/Server.php +++ b/Behavioral/Mediator/Subsystem/Server.php @@ -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)); } } diff --git a/Behavioral/Mediator/Tests/MediatorTest.php b/Behavioral/Mediator/Tests/MediatorTest.php index 2bce947..2cd40a6 100644 --- a/Behavioral/Mediator/Tests/MediatorTest.php +++ b/Behavioral/Mediator/Tests/MediatorTest.php @@ -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(); } } diff --git a/Behavioral/Memento/Caretaker.php b/Behavioral/Memento/Caretaker.php deleted file mode 100644 index d80454a..0000000 --- a/Behavioral/Memento/Caretaker.php +++ /dev/null @@ -1,49 +0,0 @@ -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(); - } -} diff --git a/Behavioral/Memento/Memento.php b/Behavioral/Memento/Memento.php index 4dd2fc8..f75fcc9 100644 --- a/Behavioral/Memento/Memento.php +++ b/Behavioral/Memento/Memento.php @@ -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() { diff --git a/Behavioral/Memento/Originator.php b/Behavioral/Memento/Originator.php deleted file mode 100644 index 3acb0c1..0000000 --- a/Behavioral/Memento/Originator.php +++ /dev/null @@ -1,38 +0,0 @@ -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(); - } -} diff --git a/Behavioral/Memento/README.rst b/Behavioral/Memento/README.rst index 911e30e..32ec703 100644 --- a/Behavioral/Memento/README.rst +++ b/Behavioral/Memento/README.rst @@ -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: diff --git a/Behavioral/Memento/State.php b/Behavioral/Memento/State.php new file mode 100644 index 0000000..6cb5dd1 --- /dev/null +++ b/Behavioral/Memento/State.php @@ -0,0 +1,48 @@ +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; + } +} diff --git a/Behavioral/Memento/Tests/MementoTest.php b/Behavioral/Memento/Tests/MementoTest.php index 722dbfa..d4a44f3 100644 --- a/Behavioral/Memento/Tests/MementoTest.php +++ b/Behavioral/Memento/Tests/MementoTest.php @@ -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()); } } diff --git a/Behavioral/Memento/Ticket.php b/Behavioral/Memento/Ticket.php new file mode 100644 index 0000000..13f75e7 --- /dev/null +++ b/Behavioral/Memento/Ticket.php @@ -0,0 +1,49 @@ +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; + } +} diff --git a/Behavioral/Memento/uml/Memento.uml b/Behavioral/Memento/uml/Memento.uml new file mode 100644 index 0000000..194187a --- /dev/null +++ b/Behavioral/Memento/uml/Memento.uml @@ -0,0 +1,21 @@ + + + PHP + \DesignPatterns\Behavioral\Memento\Memento + + \DesignPatterns\Behavioral\Memento\State + \DesignPatterns\Behavioral\Memento\Memento + \DesignPatterns\Behavioral\Memento\Ticket + + + + + + + Fields + Constants + Methods + + private + + diff --git a/Behavioral/Memento/uml/Momento.uml b/Behavioral/Memento/uml/Momento.uml deleted file mode 100644 index c72667e..0000000 --- a/Behavioral/Memento/uml/Momento.uml +++ /dev/null @@ -1,22 +0,0 @@ - - - PHP - \DesignPatterns\Behavioral\Memento\Caretaker - - \DesignPatterns\Behavioral\Memento\Caretaker - \DesignPatterns\Behavioral\Memento\Originator - \DesignPatterns\Behavioral\Memento\Memento - - - - - - - Fields - Constants - Constructors - Methods - - private - - diff --git a/Behavioral/Memento/uml/uml.png b/Behavioral/Memento/uml/uml.png index 0fde074..417b293 100644 Binary files a/Behavioral/Memento/uml/uml.png and b/Behavioral/Memento/uml/uml.png differ diff --git a/Behavioral/Memento/uml/uml.svg b/Behavioral/Memento/uml/uml.svg index 2cc47a8..aeb665a 100644 --- a/Behavioral/Memento/uml/uml.svg +++ b/Behavioral/Memento/uml/uml.svg @@ -1,310 +1,952 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - state - - - - - - - - - - - - setState(state) - - - - - - - - - saveToMemento() - - - - - - - - - restoreFromMemento(memento) - - - - - - - - - - - - - Originator - - - Originator - - - - - - - - - - - - - - - - - - run() - - - - - - - - - - - - - Caretaker - - - Caretaker - - - - - - - - - - - - - - - - - - - state - - - - - - - - - - - - __construct(stateToSave) - - - - - - - - - - - - getState() - - - - - - - - - - - - - Memento - - - Memento - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + validStates + + + + + + + + + + + + + + + + state + + + + + + + + + + + + + + + + + + + STATE_CREATED + + + + + + + + + + + + + + + + STATE_OPENED + + + + + + + + + + + + + + + + STATE_ASSIGNED + + + + + + + + + + + + + + + + STATE_CLOSED + + + + + + + + + + + + + + + + ensureIsValidState(state) + + + + + + + + + + + + + __toString() + + + + + + + + + + + + + State + + + State + + + + + + + + + + + + + + + + + + + + + + + + + validStates + + + + + + + + + + + + + + + + state + + + + + + + + + + + + + + + + + + + STATE_CREATED + + + + + + + + + + + + + + + + STATE_OPENED + + + + + + + + + + + + + + + + STATE_ASSIGNED + + + + + + + + + + + + + + + + STATE_CLOSED + + + + + + + + + + + + + + + + + + + + + + ensureIsValidState(state) + + + + + + + + + + + + + + + + __toString() + + + + + + + + + + + + + State + + + State + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + state + + + + + + + + + + + + + + + + getState() + + + + + + + + + + + + + Memento + + + Memento + + + + + + + + + + + + + + + + + + + + + + state + + + + + + + + + + + + + + + + + + + getState() + + + + + + + + + + + + + Memento + + + Memento + + + + + + + + + + + + + + + + + + + + + + + + + currentState + + + + + + + + + + + + + + + + open() + + + + + + + + + + + + + assign() + + + + + + + + + + + + + close() + + + + + + + + + + + + + saveToMemento() + + + + + + + + + + + + + restoreFromMemento(memento) + + + + + + + + + + + + + getState() + + + + + + + + + + + + + Ticket + + + Ticket + + + + + + + + + + + + + + + + + + + + + + currentState + + + + + + + + + + + + + + + + + + + open() + + + + + + + + + + + + + + + + assign() + + + + + + + + + + + + + + + + close() + + + + + + + + + + + + + + + + saveToMemento() + + + + + + + + + + + + + + + + restoreFromMemento(memento) + + + + + + + + + + + + + + + + getState() + + + + + + + + + + + + + Ticket + + + Ticket + + + diff --git a/Behavioral/NullObject/LoggerInterface.php b/Behavioral/NullObject/LoggerInterface.php index 99a28c7..7c1ff7d 100644 --- a/Behavioral/NullObject/LoggerInterface.php +++ b/Behavioral/NullObject/LoggerInterface.php @@ -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); } diff --git a/Behavioral/NullObject/NullLogger.php b/Behavioral/NullObject/NullLogger.php index a4bf469..afddbd6 100644 --- a/Behavioral/NullObject/NullLogger.php +++ b/Behavioral/NullObject/NullLogger.php @@ -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 } diff --git a/Behavioral/NullObject/PrintLogger.php b/Behavioral/NullObject/PrintLogger.php index 371c1ab..6c1d4ba 100644 --- a/Behavioral/NullObject/PrintLogger.php +++ b/Behavioral/NullObject/PrintLogger.php @@ -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; } diff --git a/Behavioral/NullObject/Service.php b/Behavioral/NullObject/Service.php index 56a3847..81baf8f 100644 --- a/Behavioral/NullObject/Service.php +++ b/Behavioral/NullObject/Service.php @@ -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... } } diff --git a/Behavioral/NullObject/Tests/LoggerTest.php b/Behavioral/NullObject/Tests/LoggerTest.php index 034b577..0e35aa5 100644 --- a/Behavioral/NullObject/Tests/LoggerTest.php +++ b/Behavioral/NullObject/Tests/LoggerTest.php @@ -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(); } diff --git a/Behavioral/Observer/Tests/ObserverTest.php b/Behavioral/Observer/Tests/ObserverTest.php index d9dacec..c263997 100644 --- a/Behavioral/Observer/Tests/ObserverTest.php +++ b/Behavioral/Observer/Tests/ObserverTest.php @@ -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()); } } diff --git a/Behavioral/Observer/User.php b/Behavioral/Observer/User.php index 0d2a817..6fd58d4 100644 --- a/Behavioral/Observer/User.php +++ b/Behavioral/Observer/User.php @@ -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(); - } } diff --git a/Behavioral/Observer/UserObserver.php b/Behavioral/Observer/UserObserver.php index f2673ba..243e740 100644 --- a/Behavioral/Observer/UserObserver.php +++ b/Behavioral/Observer/UserObserver.php @@ -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; } } diff --git a/Behavioral/Observer/uml/Observer.uml b/Behavioral/Observer/uml/Observer.uml index 0e5ddef..b74d770 100644 --- a/Behavioral/Observer/uml/Observer.uml +++ b/Behavioral/Observer/uml/Observer.uml @@ -1,27 +1,26 @@ - - - PHP - \DesignPatterns\Behavioral\Observer\User - - \DesignPatterns\Behavioral\Observer\UserObserver - \DesignPatterns\Behavioral\Observer\User - \SplSubject - - - - - - - - - - - - Fields - Constants - Constructors - Methods - - private - - + + + PHP + \DesignPatterns\Behavioral\Observer\UserObserver + + \DesignPatterns\Behavioral\Observer\UserObserver + \SplObserver + \DesignPatterns\Behavioral\Observer\User + + + + + + + + + + + + Fields + Constants + Methods + + private + + diff --git a/Behavioral/Observer/uml/uml.png b/Behavioral/Observer/uml/uml.png index 0300bd4..3eff464 100644 Binary files a/Behavioral/Observer/uml/uml.png and b/Behavioral/Observer/uml/uml.png differ diff --git a/Behavioral/Observer/uml/uml.svg b/Behavioral/Observer/uml/uml.svg index 09c79c1..44f3c60 100644 --- a/Behavioral/Observer/uml/uml.svg +++ b/Behavioral/Observer/uml/uml.svg @@ -1,310 +1,649 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - data - - - - - - - - - - observers - - - - - - - - - - - - attach(observer) - - - - - - - - - detach(observer) - - - - - - - - - notify() - - - - - - - - - __set(name, value) - - - - - - - - - - - - - User - - - User - - - - - - - - - - - - - - - - - - update(subject) - - - - - - - - - - - - - UserObserver - - - UserObserver - - - - - - - - - - - - - - - - - - attach(observer) - - - - - - - - - detach(observer) - - - - - - - - - notify() - - - - - - - - - - - - - SplSubject - - - SplSubject - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + changedUsers + + + + + + + + + + + + + + + + update(subject) + + + + + + + + + + + + + getChangedUsers() + + + + + + + + + + + + + UserObserver + + + UserObserver + + + + + + + + + + + + + + + + + + + + + + changedUsers + + + + + + + + + + + + + + + + + + + update(subject) + + + + + + + + + + + + + + + + getChangedUsers() + + + + + + + + + + + + + UserObserver + + + UserObserver + + + + + + + + + + + + + + + + + + + + + + update(subject) + + + + + + + + + + + + + SplObserver + + + SplObserver + + + + + + + + + + + + + + + + + + + + + + update(subject) + + + + + + + + + + + + + SplObserver + + + SplObserver + + + + + + + + + + + + + + + + + + + + + + + + + email + + + + + + + + + + + + + + + + observers + + + + + + + + + + + + + + + + attach(observer) + + + + + + + + + + + + + detach(observer) + + + + + + + + + + + + + changeEmail(email) + + + + + + + + + + + + + notify() + + + + + + + + + + + + + User + + + User + + + + + + + + + + + + + + + + + + + + + + email + + + + + + + + + + + + + + + + observers + + + + + + + + + + + + + + + + + + + attach(observer) + + + + + + + + + + + + + + + + detach(observer) + + + + + + + + + + + + + + + + changeEmail(email) + + + + + + + + + + + + + + + + notify() + + + + + + + + + + + + + User + + + User + + + + + + + + + diff --git a/Behavioral/Specification/AbstractSpecification.php b/Behavioral/Specification/AbstractSpecification.php deleted file mode 100644 index 66d61b8..0000000 --- a/Behavioral/Specification/AbstractSpecification.php +++ /dev/null @@ -1,52 +0,0 @@ -specifications = $specifications; + } + + public function isSatisfiedBy(Item $item): bool + { + $satisfied = []; + + foreach ($this->specifications as $specification) { + $satisfied[] = $specification->isSatisfiedBy($item); + } + + return !in_array(false, $satisfied); + } +} diff --git a/Behavioral/Specification/Either.php b/Behavioral/Specification/Either.php deleted file mode 100644 index a30372a..0000000 --- a/Behavioral/Specification/Either.php +++ /dev/null @@ -1,36 +0,0 @@ -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); - } -} diff --git a/Behavioral/Specification/Item.php b/Behavioral/Specification/Item.php index 8d639e0..a167f0a 100644 --- a/Behavioral/Specification/Item.php +++ b/Behavioral/Specification/Item.php @@ -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; } diff --git a/Behavioral/Specification/Not.php b/Behavioral/Specification/Not.php deleted file mode 100644 index e99a6cc..0000000 --- a/Behavioral/Specification/Not.php +++ /dev/null @@ -1,33 +0,0 @@ -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); - } -} diff --git a/Behavioral/Specification/NotSpecification.php b/Behavioral/Specification/NotSpecification.php new file mode 100644 index 0000000..db47b51 --- /dev/null +++ b/Behavioral/Specification/NotSpecification.php @@ -0,0 +1,21 @@ +specification = $specification; + } + + public function isSatisfiedBy(Item $item): bool + { + return !$this->specification->isSatisfiedBy($item); + } +} diff --git a/Behavioral/Specification/OrSpecification.php b/Behavioral/Specification/OrSpecification.php new file mode 100644 index 0000000..62a4e08 --- /dev/null +++ b/Behavioral/Specification/OrSpecification.php @@ -0,0 +1,30 @@ +specifications = $specifications; + } + + public function isSatisfiedBy(Item $item): bool + { + $satisfied = []; + + foreach ($this->specifications as $specification) { + $satisfied[] = $specification->isSatisfiedBy($item); + } + + return in_array(true, $satisfied); + } +} diff --git a/Behavioral/Specification/Plus.php b/Behavioral/Specification/Plus.php deleted file mode 100644 index 26bd585..0000000 --- a/Behavioral/Specification/Plus.php +++ /dev/null @@ -1,36 +0,0 @@ -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); - } -} diff --git a/Behavioral/Specification/PriceSpecification.php b/Behavioral/Specification/PriceSpecification.php index 2019ad2..3b54586 100644 --- a/Behavioral/Specification/PriceSpecification.php +++ b/Behavioral/Specification/PriceSpecification.php @@ -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; } diff --git a/Behavioral/Specification/README.rst b/Behavioral/Specification/README.rst index b6957e0..84d6913 100644 --- a/Behavioral/Specification/README.rst +++ b/Behavioral/Specification/README.rst @@ -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: diff --git a/Behavioral/Specification/SpecificationInterface.php b/Behavioral/Specification/SpecificationInterface.php index 796af9f..7387700 100644 --- a/Behavioral/Specification/SpecificationInterface.php +++ b/Behavioral/Specification/SpecificationInterface.php @@ -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; } diff --git a/Behavioral/Specification/Tests/SpecificationTest.php b/Behavioral/Specification/Tests/SpecificationTest.php index 5abc82a..d7676a4 100644 --- a/Behavioral/Specification/Tests/SpecificationTest.php +++ b/Behavioral/Specification/Tests/SpecificationTest.php @@ -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))); } } diff --git a/Behavioral/State/CreateOrder.php b/Behavioral/State/CreateOrder.php index 7ac48d4..4a35ac3 100644 --- a/Behavioral/State/CreateOrder.php +++ b/Behavioral/State/CreateOrder.php @@ -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']; } } diff --git a/Behavioral/State/OrderInterface.php b/Behavioral/State/Order.php similarity index 77% rename from Behavioral/State/OrderInterface.php rename to Behavioral/State/Order.php index 6a97e69..ea85159 100644 --- a/Behavioral/State/OrderInterface.php +++ b/Behavioral/State/Order.php @@ -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; } diff --git a/Behavioral/State/OrderController.php b/Behavioral/State/OrderController.php deleted file mode 100644 index 93ac542..0000000 --- a/Behavioral/State/OrderController.php +++ /dev/null @@ -1,37 +0,0 @@ -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 - } -} diff --git a/Behavioral/State/OrderFactory.php b/Behavioral/State/OrderFactory.php deleted file mode 100644 index 205d505..0000000 --- a/Behavioral/State/OrderFactory.php +++ /dev/null @@ -1,36 +0,0 @@ - ['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; + } + } +} diff --git a/Behavioral/State/README.rst b/Behavioral/State/README.rst index 3722d92..0aa3b9f 100644 --- a/Behavioral/State/README.rst +++ b/Behavioral/State/README.rst @@ -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: diff --git a/Behavioral/State/ShippingOrder.php b/Behavioral/State/ShippingOrder.php index 943d714..41198b2 100644 --- a/Behavioral/State/ShippingOrder.php +++ b/Behavioral/State/ShippingOrder.php @@ -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']; } } diff --git a/Behavioral/State/Tests/StateTest.php b/Behavioral/State/Tests/StateTest.php new file mode 100644 index 0000000..637ad9a --- /dev/null +++ b/Behavioral/State/Tests/StateTest.php @@ -0,0 +1,33 @@ +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(); + } +} diff --git a/Behavioral/Strategy/ComparatorInterface.php b/Behavioral/Strategy/ComparatorInterface.php index f472fd9..4c5dcb2 100644 --- a/Behavioral/Strategy/ComparatorInterface.php +++ b/Behavioral/Strategy/ComparatorInterface.php @@ -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; } diff --git a/Behavioral/Strategy/DateComparator.php b/Behavioral/Strategy/DateComparator.php index d355dc9..3f59667 100644 --- a/Behavioral/Strategy/DateComparator.php +++ b/Behavioral/Strategy/DateComparator.php @@ -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; } } diff --git a/Behavioral/Strategy/IdComparator.php b/Behavioral/Strategy/IdComparator.php index f829195..80c43cc 100644 --- a/Behavioral/Strategy/IdComparator.php +++ b/Behavioral/Strategy/IdComparator.php @@ -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']; } } diff --git a/Behavioral/Strategy/ObjectCollection.php b/Behavioral/Strategy/ObjectCollection.php index b5c1bd2..ca8165b 100644 --- a/Behavioral/Strategy/ObjectCollection.php +++ b/Behavioral/Strategy/ObjectCollection.php @@ -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) { diff --git a/Behavioral/Strategy/Tests/StrategyTest.php b/Behavioral/Strategy/Tests/StrategyTest.php index 1911ba4..033a4a9 100644 --- a/Behavioral/Strategy/Tests/StrategyTest.php +++ b/Behavioral/Strategy/Tests/StrategyTest.php @@ -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) { diff --git a/Behavioral/TemplateMethod/BeachJourney.php b/Behavioral/TemplateMethod/BeachJourney.php index d19845c..5007054 100644 --- a/Behavioral/TemplateMethod/BeachJourney.php +++ b/Behavioral/TemplateMethod/BeachJourney.php @@ -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"; } } diff --git a/Behavioral/TemplateMethod/CityJourney.php b/Behavioral/TemplateMethod/CityJourney.php index 3f36b61..f1f9335 100644 --- a/Behavioral/TemplateMethod/CityJourney.php +++ b/Behavioral/TemplateMethod/CityJourney.php @@ -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"; } } diff --git a/Behavioral/TemplateMethod/Journey.php b/Behavioral/TemplateMethod/Journey.php index c7a6809..0a1407c 100644 --- a/Behavioral/TemplateMethod/Journey.php +++ b/Behavioral/TemplateMethod/Journey.php @@ -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 ! } diff --git a/Behavioral/TemplateMethod/Tests/JourneyTest.php b/Behavioral/TemplateMethod/Tests/JourneyTest.php index 82acef3..26c1763 100644 --- a/Behavioral/TemplateMethod/Tests/JourneyTest.php +++ b/Behavioral/TemplateMethod/Tests/JourneyTest.php @@ -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() + ); } } diff --git a/Behavioral/TemplateMethod/uml/TemplateMethod.uml b/Behavioral/TemplateMethod/uml/TemplateMethod.uml index 063b022..29d6c04 100644 --- a/Behavioral/TemplateMethod/uml/TemplateMethod.uml +++ b/Behavioral/TemplateMethod/uml/TemplateMethod.uml @@ -1,35 +1,35 @@ - - - PHP - \DesignPatterns\Behavioral\TemplateMethod\BeachJourney - - \DesignPatterns\Behavioral\TemplateMethod\CityJourney - \DesignPatterns\Behavioral\TemplateMethod\Journey - \DesignPatterns\Behavioral\TemplateMethod\BeachJourney - - - - - - - - - - - - - - - - - - - - Fields - Constants - Constructors - Methods - - private - - + + + PHP + \DesignPatterns\Behavioral\TemplateMethod\BeachJourney + + \DesignPatterns\Behavioral\TemplateMethod\BeachJourney + \DesignPatterns\Behavioral\TemplateMethod\Journey + \DesignPatterns\Behavioral\TemplateMethod\CityJourney + + + + + + + + + + + + + + + + + + + + Fields + Constants + Constructors + Methods + + private + + diff --git a/Behavioral/TemplateMethod/uml/uml.png b/Behavioral/TemplateMethod/uml/uml.png index c557e8f..d8e9f69 100644 Binary files a/Behavioral/TemplateMethod/uml/uml.png and b/Behavioral/TemplateMethod/uml/uml.png differ diff --git a/Behavioral/TemplateMethod/uml/uml.svg b/Behavioral/TemplateMethod/uml/uml.svg index d06adcb..6f7f555 100644 --- a/Behavioral/TemplateMethod/uml/uml.svg +++ b/Behavioral/TemplateMethod/uml/uml.svg @@ -1,256 +1,636 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - enjoyVacation() - - - - - - - - - - - - - CityJourney - - - CityJourney - - - - - - - - - - - - - - - - - - takeATrip() - - - - - - - - - enjoyVacation() - - - - - - - - - buyGift() - - - - - - - - - buyAFlight() - - - - - - - - - takePlane() - - - - - - - - - - - - - Journey - - - Journey - - - - - - - - - - - - - - - - - - enjoyVacation() - - - - - - - - - - - - - BeachJourney - - - BeachJourney - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + enjoyVacation() + + + + + + + + + + + + + BeachJourney + + + BeachJourney + + + + + + + + + + + + + + + + + + + + + + enjoyVacation() + + + + + + + + + + + + + BeachJourney + + + BeachJourney + + + + + + + + + + + + + + + + + + + + + + + + + thingsToDo + + + + + + + + + + + + + + + + takeATrip() + + + + + + + + + + + + + enjoyVacation() + + + + + + + + + + + + + buyGift() + + + + + + + + + + + + + buyAFlight() + + + + + + + + + + + + + takePlane() + + + + + + + + + + + + + getThingsToDo() + + + + + + + + + + + + + Journey + + + Journey + + + + + + + + + + + + + + + + + + + + + + thingsToDo + + + + + + + + + + + + + + + + + + + + + + takeATrip() + + + + + + + + + + + + + + + + enjoyVacation() + + + + + + + + + + + + + + + + buyGift() + + + + + + + + + + + + + + + + buyAFlight() + + + + + + + + + + + + + + + + takePlane() + + + + + + + + + + + + + + + + getThingsToDo() + + + + + + + + + + + + + Journey + + + Journey + + + + + + + + + + + + + + + + + + + + + + enjoyVacation() + + + + + + + + + + + + + buyGift() + + + + + + + + + + + + + CityJourney + + + CityJourney + + + + + + + + + + + + + + + + + + + + + + enjoyVacation() + + + + + + + + + + + + + + + + buyGift() + + + + + + + + + + + + + CityJourney + + + CityJourney + + + + + + + + + + + diff --git a/Behavioral/Visitor/Group.php b/Behavioral/Visitor/Group.php index b2c9d90..e4a9a65 100644 --- a/Behavioral/Visitor/Group.php +++ b/Behavioral/Visitor/Group.php @@ -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); } } diff --git a/Behavioral/Visitor/README.rst b/Behavioral/Visitor/README.rst index ba256b9..7484356 100644 --- a/Behavioral/Visitor/README.rst +++ b/Behavioral/Visitor/README.rst @@ -31,9 +31,9 @@ RoleVisitorInterface.php :language: php :linenos: -RolePrintVisitor.php +RoleVisitor.php -.. literalinclude:: RolePrintVisitor.php +.. literalinclude:: RoleVisitor.php :language: php :linenos: diff --git a/Behavioral/Visitor/Role.php b/Behavioral/Visitor/Role.php index 011a0fb..1a55c74 100644 --- a/Behavioral/Visitor/Role.php +++ b/Behavioral/Visitor/Role.php @@ -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); } diff --git a/Behavioral/Visitor/RolePrintVisitor.php b/Behavioral/Visitor/RolePrintVisitor.php deleted file mode 100644 index 49777cf..0000000 --- a/Behavioral/Visitor/RolePrintVisitor.php +++ /dev/null @@ -1,27 +0,0 @@ -getName(); - } - - /** - * {@inheritdoc} - */ - public function visitUser(User $role) - { - echo 'Role: '.$role->getName(); - } -} diff --git a/Behavioral/Visitor/RoleVisitor.php b/Behavioral/Visitor/RoleVisitor.php new file mode 100644 index 0000000..fc368b2 --- /dev/null +++ b/Behavioral/Visitor/RoleVisitor.php @@ -0,0 +1,29 @@ +visited[] = $role; + } + + public function visitUser(User $role) + { + $this->visited[] = $role; + } + + /** + * @return Role[] + */ + public function getVisited(): array + { + return $this->visited; + } +} diff --git a/Behavioral/Visitor/RoleVisitorInterface.php b/Behavioral/Visitor/RoleVisitorInterface.php index e8ba0ec..b2aff5e 100644 --- a/Behavioral/Visitor/RoleVisitorInterface.php +++ b/Behavioral/Visitor/RoleVisitorInterface.php @@ -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); } diff --git a/Behavioral/Visitor/Tests/VisitorTest.php b/Behavioral/Visitor/Tests/VisitorTest.php index f3b5bc7..ce6480b 100644 --- a/Behavioral/Visitor/Tests/VisitorTest.php +++ b/Behavioral/Visitor/Tests/VisitorTest.php @@ -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]); } } diff --git a/Behavioral/Visitor/User.php b/Behavioral/Visitor/User.php index 5a95fbe..f3d9ad3 100644 --- a/Behavioral/Visitor/User.php +++ b/Behavioral/Visitor/User.php @@ -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); } } diff --git a/Creational/AbstractFactory/AbstractFactory.php b/Creational/AbstractFactory/AbstractFactory.php index fd69448..85bd878 100644 --- a/Creational/AbstractFactory/AbstractFactory.php +++ b/Creational/AbstractFactory/AbstractFactory.php @@ -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; } diff --git a/Creational/AbstractFactory/Html/Picture.php b/Creational/AbstractFactory/Html/Picture.php deleted file mode 100644 index 3abacb4..0000000 --- a/Creational/AbstractFactory/Html/Picture.php +++ /dev/null @@ -1,23 +0,0 @@ -', $this->path, $this->name); - } -} diff --git a/Creational/AbstractFactory/Html/Text.php b/Creational/AbstractFactory/Html/Text.php deleted file mode 100644 index 8c33da6..0000000 --- a/Creational/AbstractFactory/Html/Text.php +++ /dev/null @@ -1,23 +0,0 @@ -'.htmlspecialchars($this->text).''; - } -} diff --git a/Creational/AbstractFactory/HtmlFactory.php b/Creational/AbstractFactory/HtmlFactory.php index 5c22859..ba44216 100644 --- a/Creational/AbstractFactory/HtmlFactory.php +++ b/Creational/AbstractFactory/HtmlFactory.php @@ -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); } } diff --git a/Creational/AbstractFactory/HtmlText.php b/Creational/AbstractFactory/HtmlText.php new file mode 100644 index 0000000..a3764da --- /dev/null +++ b/Creational/AbstractFactory/HtmlText.php @@ -0,0 +1,8 @@ + $this->name, 'path' => $this->path)); - } -} diff --git a/Creational/AbstractFactory/Json/Text.php b/Creational/AbstractFactory/Json/Text.php deleted file mode 100644 index 4c51785..0000000 --- a/Creational/AbstractFactory/Json/Text.php +++ /dev/null @@ -1,23 +0,0 @@ - $this->text)); - } -} diff --git a/Creational/AbstractFactory/JsonFactory.php b/Creational/AbstractFactory/JsonFactory.php index 63a9979..a767f7e 100644 --- a/Creational/AbstractFactory/JsonFactory.php +++ b/Creational/AbstractFactory/JsonFactory.php @@ -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); } } diff --git a/Creational/AbstractFactory/JsonText.php b/Creational/AbstractFactory/JsonText.php new file mode 100644 index 0000000..a0386a0 --- /dev/null +++ b/Creational/AbstractFactory/JsonText.php @@ -0,0 +1,8 @@ +name = (string) $name; - $this->path = (string) $path; - } -} diff --git a/Creational/AbstractFactory/README.rst b/Creational/AbstractFactory/README.rst index 69980eb..5251b6c 100644 --- a/Creational/AbstractFactory/README.rst +++ b/Creational/AbstractFactory/README.rst @@ -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: diff --git a/Creational/AbstractFactory/Tests/AbstractFactoryTest.php b/Creational/AbstractFactory/Tests/AbstractFactoryTest.php index 97f4417..1bdbd1c 100644 --- a/Creational/AbstractFactory/Tests/AbstractFactoryTest.php +++ b/Creational/AbstractFactory/Tests/AbstractFactoryTest.php @@ -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); } } diff --git a/Creational/AbstractFactory/Text.php b/Creational/AbstractFactory/Text.php index 30984f3..60846bb 100644 --- a/Creational/AbstractFactory/Text.php +++ b/Creational/AbstractFactory/Text.php @@ -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; } } diff --git a/Creational/AbstractFactory/uml/AbstractFactory.uml b/Creational/AbstractFactory/uml/AbstractFactory.uml index a44e64c..224cc06 100644 --- a/Creational/AbstractFactory/uml/AbstractFactory.uml +++ b/Creational/AbstractFactory/uml/AbstractFactory.uml @@ -1,38 +1,51 @@ - - - PHP - \DesignPatterns\Creational\AbstractFactory\AbstractFactory - - \DesignPatterns\Creational\AbstractFactory\Html\Text - \DesignPatterns\Creational\AbstractFactory\Html\Picture - \DesignPatterns\Creational\AbstractFactory\HtmlFactory - \DesignPatterns\Creational\AbstractFactory\JsonFactory - \DesignPatterns\Creational\AbstractFactory\AbstractFactory - \DesignPatterns\Creational\AbstractFactory\MediaInterface - - - - - - - - - - - - - - - - - - - - Fields - Constants - Constructors - Methods - - private - - + + + PHP + \DesignPatterns\Creational\AbstractFactory\AbstractFactory + + \DesignPatterns\Creational\AbstractFactory\JsonFactory + \DesignPatterns\Creational\AbstractFactory\AbstractFactory + \DesignPatterns\Creational\AbstractFactory\HtmlFactory + \DesignPatterns\Creational\AbstractFactory\JsonText + \DesignPatterns\Creational\AbstractFactory\HtmlText + \DesignPatterns\Creational\AbstractFactory\Text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \DesignPatterns\Creational\AbstractFactory\AbstractFactory + + + Methods + Constants + Fields + + private + + diff --git a/Creational/AbstractFactory/uml/uml.png b/Creational/AbstractFactory/uml/uml.png index 6de2767..68d3a8b 100644 Binary files a/Creational/AbstractFactory/uml/uml.png and b/Creational/AbstractFactory/uml/uml.png differ diff --git a/Creational/AbstractFactory/uml/uml.svg b/Creational/AbstractFactory/uml/uml.svg index 17b4b70..e7eff24 100644 --- a/Creational/AbstractFactory/uml/uml.svg +++ b/Creational/AbstractFactory/uml/uml.svg @@ -1,379 +1,560 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - createPicture(path, name) - - - - - - - - - createText(content) - - - - - - - - - - - - - HtmlFactory - - - HtmlFactory - - - - - - - - - - - - - - - - - - render() - - - - - - - - - - - - - Picture - - - Picture - - - - - - - - - - - - - - - - - - render() - - - - - - - - - - - - - Text - - - Text - - - - - - - - - - - - - - - - - - createPicture(path, name) - - - - - - - - - createText(content) - - - - - - - - - - - - - JsonFactory - - - JsonFactory - - - - - - - - - - - - - - - - - - createText(content) - - - - - - - - - createPicture(path, name) - - - - - - - - - - - - - AbstractFactory - - - AbstractFactory - - - - - - - - - - - - - - - - - - render() - - - - - - - - - - - - - MediaInterface - - - MediaInterface - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + createText(content) + + + + + + + + + + + + + JsonFactory + + + JsonFactory + + + + + + + + + + + + + + + + + + + + + + createText(content) + + + + + + + + + + + + + JsonFactory + + + JsonFactory + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + createText(content) + + + + + + + + + + + + + AbstractFactory + + + AbstractFactory + + + + + + + + + + + + + + + + + + + + + + createText(content) + + + + + + + + + + + + + AbstractFactory + + + AbstractFactory + + + + + + + + + + + + + + + + + + + + + + createText(content) + + + + + + + + + + + + + HtmlFactory + + + HtmlFactory + + + + + + + + + + + + + + + + + + + + + + createText(content) + + + + + + + + + + + + + HtmlFactory + + + HtmlFactory + + + + + + + + + + + + + + + + JsonText + + + JsonText + + + + + + + + + + + + + JsonText + + + JsonText + + + + + + + + + + + + + + + + HtmlText + + + HtmlText + + + + + + + + + + + + + HtmlText + + + HtmlText + + + + + + + + + + + + + + + + + + + + + + + + + text + + + + + + + + + + + + + Text + + + Text + + + + + + + + + + + + + + + + + + + + + + text + + + + + + + + + + + + + Text + + + Text + + + + + + + + + + + + + + + diff --git a/Creational/Builder/BikeBuilder.php b/Creational/Builder/BikeBuilder.php deleted file mode 100644 index f83c5db..0000000 --- a/Creational/Builder/BikeBuilder.php +++ /dev/null @@ -1,54 +0,0 @@ -bike->setPart('engine', new Parts\Engine()); - } - - /** - * {@inheritdoc} - */ - public function addWheel() - { - $this->bike->setPart('forwardWheel', new Parts\Wheel()); - $this->bike->setPart('rearWheel', new Parts\Wheel()); - } - - /** - * {@inheritdoc} - */ - public function createVehicle() - { - $this->bike = new Parts\Bike(); - } - - /** - * {@inheritdoc} - */ - public function getVehicle() - { - return $this->bike; - } -} diff --git a/Creational/Builder/BuilderInterface.php b/Creational/Builder/BuilderInterface.php index 563162f..3bcf961 100644 --- a/Creational/Builder/BuilderInterface.php +++ b/Creational/Builder/BuilderInterface.php @@ -2,30 +2,17 @@ namespace DesignPatterns\Creational\Builder; +use DesignPatterns\Creational\Builder\Parts\Vehicle; + interface BuilderInterface { - /** - * @return mixed - */ public function createVehicle(); - /** - * @return mixed - */ public function addWheel(); - /** - * @return mixed - */ public function addEngine(); - /** - * @return mixed - */ public function addDoors(); - /** - * @return mixed - */ - public function getVehicle(); + public function getVehicle(): Vehicle; } diff --git a/Creational/Builder/CarBuilder.php b/Creational/Builder/CarBuilder.php index a0693d0..fb20822 100644 --- a/Creational/Builder/CarBuilder.php +++ b/Creational/Builder/CarBuilder.php @@ -2,36 +2,27 @@ namespace DesignPatterns\Creational\Builder; -/** - * CarBuilder builds car. - */ +use DesignPatterns\Creational\Builder\Parts\Vehicle; + class CarBuilder implements BuilderInterface { /** * @var Parts\Car */ - protected $car; + private $car; - /** - * @return void - */ public function addDoors() { - $this->car->setPart('rightdoor', new Parts\Door()); + $this->car->setPart('rightDoor', new Parts\Door()); $this->car->setPart('leftDoor', new Parts\Door()); + $this->car->setPart('trunkLid', new Parts\Door()); } - /** - * @return void - */ public function addEngine() { $this->car->setPart('engine', new Parts\Engine()); } - /** - * @return void - */ public function addWheel() { $this->car->setPart('wheelLF', new Parts\Wheel()); @@ -40,18 +31,12 @@ class CarBuilder implements BuilderInterface $this->car->setPart('wheelRR', new Parts\Wheel()); } - /** - * @return void - */ public function createVehicle() { $this->car = new Parts\Car(); } - /** - * @return Parts\Car - */ - public function getVehicle() + public function getVehicle(): Vehicle { return $this->car; } diff --git a/Creational/Builder/Director.php b/Creational/Builder/Director.php index 642cd1b..9925d5a 100644 --- a/Creational/Builder/Director.php +++ b/Creational/Builder/Director.php @@ -2,22 +2,17 @@ namespace DesignPatterns\Creational\Builder; +use DesignPatterns\Creational\Builder\Parts\Vehicle; + /** * Director is part of the builder pattern. It knows the interface of the builder - * and builds a complex object with the help of the builder. + * and builds a complex object with the help of the builder * * You can also inject many builders instead of one to build more complex objects */ class Director { - /** - * The director don't know about concrete part. - * - * @param BuilderInterface $builder - * - * @return Parts\Vehicle - */ - public function build(BuilderInterface $builder) + public function build(BuilderInterface $builder): Vehicle { $builder->createVehicle(); $builder->addDoors(); diff --git a/Creational/Builder/Parts/Car.php b/Creational/Builder/Parts/Car.php index e345ea9..53eb0d4 100644 --- a/Creational/Builder/Parts/Car.php +++ b/Creational/Builder/Parts/Car.php @@ -2,9 +2,6 @@ namespace DesignPatterns\Creational\Builder\Parts; -/** - * Car is a car. - */ class Car extends Vehicle { } diff --git a/Creational/Builder/Parts/Door.php b/Creational/Builder/Parts/Door.php index fc12608..f2732fe 100644 --- a/Creational/Builder/Parts/Door.php +++ b/Creational/Builder/Parts/Door.php @@ -2,9 +2,6 @@ namespace DesignPatterns\Creational\Builder\Parts; -/** - * Class Door. - */ class Door { } diff --git a/Creational/Builder/Parts/Engine.php b/Creational/Builder/Parts/Engine.php index 5232ab3..48046b8 100644 --- a/Creational/Builder/Parts/Engine.php +++ b/Creational/Builder/Parts/Engine.php @@ -2,9 +2,6 @@ namespace DesignPatterns\Creational\Builder\Parts; -/** - * Class Engine. - */ class Engine { } diff --git a/Creational/Builder/Parts/Bike.php b/Creational/Builder/Parts/Truck.php similarity index 53% rename from Creational/Builder/Parts/Bike.php rename to Creational/Builder/Parts/Truck.php index e5adbba..23c137c 100644 --- a/Creational/Builder/Parts/Bike.php +++ b/Creational/Builder/Parts/Truck.php @@ -2,9 +2,6 @@ namespace DesignPatterns\Creational\Builder\Parts; -/** - * Bike is a bike. - */ -class Bike extends Vehicle +class Truck extends Vehicle { } diff --git a/Creational/Builder/Parts/Vehicle.php b/Creational/Builder/Parts/Vehicle.php index 18c47ba..c7e3c15 100644 --- a/Creational/Builder/Parts/Vehicle.php +++ b/Creational/Builder/Parts/Vehicle.php @@ -2,19 +2,16 @@ namespace DesignPatterns\Creational\Builder\Parts; -/** - * Vehicle class is an abstraction for a vehicle. - */ abstract class Vehicle { /** - * @var array + * @var object[] */ - protected $data; + private $data = []; /** * @param string $key - * @param mixed $value + * @param object $value */ public function setPart($key, $value) { diff --git a/Creational/Builder/Parts/Wheel.php b/Creational/Builder/Parts/Wheel.php index 0a1afbd..4e677e3 100644 --- a/Creational/Builder/Parts/Wheel.php +++ b/Creational/Builder/Parts/Wheel.php @@ -2,9 +2,6 @@ namespace DesignPatterns\Creational\Builder\Parts; -/** - * Class Wheel. - */ class Wheel { } diff --git a/Creational/Builder/README.rst b/Creational/Builder/README.rst index de8971b..68f3586 100644 --- a/Creational/Builder/README.rst +++ b/Creational/Builder/README.rst @@ -44,9 +44,9 @@ BuilderInterface.php :language: php :linenos: -BikeBuilder.php +TruckBuilder.php -.. literalinclude:: BikeBuilder.php +.. literalinclude:: TruckBuilder.php :language: php :linenos: @@ -62,9 +62,9 @@ Parts/Vehicle.php :language: php :linenos: -Parts/Bike.php +Parts/Truck.php -.. literalinclude:: Parts/Bike.php +.. literalinclude:: Parts/Truck.php :language: php :linenos: diff --git a/Creational/Builder/Tests/DirectorTest.php b/Creational/Builder/Tests/DirectorTest.php index 9f07b83..421107b 100644 --- a/Creational/Builder/Tests/DirectorTest.php +++ b/Creational/Builder/Tests/DirectorTest.php @@ -2,40 +2,25 @@ namespace DesignPatterns\Creational\Builder\Tests; -use DesignPatterns\Creational\Builder\BikeBuilder; -use DesignPatterns\Creational\Builder\BuilderInterface; +use DesignPatterns\Creational\Builder\TruckBuilder; use DesignPatterns\Creational\Builder\CarBuilder; use DesignPatterns\Creational\Builder\Director; -/** - * DirectorTest tests the builder pattern. - */ class DirectorTest extends \PHPUnit_Framework_TestCase { - protected $director; - - protected function setUp() + public function testCanBuildBike() { - $this->director = new Director(); + $bikeBuilder = new TruckBuilder(); + $newVehicle = (new Director())->build($bikeBuilder); + + $this->assertInstanceOf('DesignPatterns\Creational\Builder\Parts\Truck', $newVehicle); } - public function getBuilder() + public function testCanBuildCar() { - return array( - array(new CarBuilder()), - array(new BikeBuilder()), - ); - } + $carBuilder = new CarBuilder(); + $newVehicle = (new Director())->build($carBuilder); - /** - * Here we test the build process. Notice that the client don't know - * anything about the concrete builder. - * - * @dataProvider getBuilder - */ - public function testBuild(BuilderInterface $builder) - { - $newVehicle = $this->director->build($builder); - $this->assertInstanceOf('DesignPatterns\Creational\Builder\Parts\Vehicle', $newVehicle); + $this->assertInstanceOf('DesignPatterns\Creational\Builder\Parts\Car', $newVehicle); } } diff --git a/Creational/Builder/TruckBuilder.php b/Creational/Builder/TruckBuilder.php new file mode 100644 index 0000000..e686eac --- /dev/null +++ b/Creational/Builder/TruckBuilder.php @@ -0,0 +1,44 @@ +truck->setPart('rightDoor', new Parts\Door()); + $this->truck->setPart('leftDoor', new Parts\Door()); + } + + public function addEngine() + { + $this->truck->setPart('truckEngine', new Parts\Engine()); + } + + public function addWheel() + { + $this->truck->setPart('wheel1', new Parts\Wheel()); + $this->truck->setPart('wheel2', new Parts\Wheel()); + $this->truck->setPart('wheel3', new Parts\Wheel()); + $this->truck->setPart('wheel4', new Parts\Wheel()); + $this->truck->setPart('wheel5', new Parts\Wheel()); + $this->truck->setPart('wheel6', new Parts\Wheel()); + } + + public function createVehicle() + { + $this->truck = new Parts\Truck(); + } + + public function getVehicle(): Vehicle + { + return $this->truck; + } +} diff --git a/Creational/FactoryMethod/Bicycle.php b/Creational/FactoryMethod/Bicycle.php index fe6deee..95aacbf 100644 --- a/Creational/FactoryMethod/Bicycle.php +++ b/Creational/FactoryMethod/Bicycle.php @@ -2,22 +2,14 @@ namespace DesignPatterns\Creational\FactoryMethod; -/** - * Bicycle is a bicycle. - */ class Bicycle implements VehicleInterface { /** * @var string */ - protected $color; + private $color; - /** - * Sets the color of the bicycle. - * - * @param string $rgb - */ - public function setColor($rgb) + public function setColor(string $rgb) { $this->color = $rgb; } diff --git a/Creational/FactoryMethod/CarFerrari.php b/Creational/FactoryMethod/CarFerrari.php new file mode 100644 index 0000000..dfb9eca --- /dev/null +++ b/Creational/FactoryMethod/CarFerrari.php @@ -0,0 +1,16 @@ +color = $rgb; + } +} diff --git a/Creational/FactoryMethod/CarMercedes.php b/Creational/FactoryMethod/CarMercedes.php new file mode 100644 index 0000000..bc6e337 --- /dev/null +++ b/Creational/FactoryMethod/CarMercedes.php @@ -0,0 +1,21 @@ +color = $rgb; + } + + public function addAMGTuning() + { + // do additional tuning here + } +} diff --git a/Creational/FactoryMethod/FactoryMethod.php b/Creational/FactoryMethod/FactoryMethod.php index fa3d4a3..d999f7b 100644 --- a/Creational/FactoryMethod/FactoryMethod.php +++ b/Creational/FactoryMethod/FactoryMethod.php @@ -2,36 +2,17 @@ namespace DesignPatterns\Creational\FactoryMethod; -/** - * class FactoryMethod. - */ abstract class FactoryMethod { - const CHEAP = 1; - const FAST = 2; + const CHEAP = 'cheap'; + const FAST = 'fast'; - /** - * The children of the class must implement this method. - * - * Sometimes this method can be public to get "raw" object - * - * @param string $type a generic type - * - * @return VehicleInterface a new vehicle - */ - abstract protected function createVehicle($type); + abstract protected function createVehicle(string $type): VehicleInterface; - /** - * Creates a new vehicle. - * - * @param int $type - * - * @return VehicleInterface a new vehicle - */ - public function create($type) + public function create(string $type): VehicleInterface { $obj = $this->createVehicle($type); - $obj->setColor('#f00'); + $obj->setColor('black'); return $obj; } diff --git a/Creational/FactoryMethod/Ferrari.php b/Creational/FactoryMethod/Ferrari.php deleted file mode 100644 index 9434e3d..0000000 --- a/Creational/FactoryMethod/Ferrari.php +++ /dev/null @@ -1,22 +0,0 @@ -color = $rgb; - } -} diff --git a/Creational/FactoryMethod/GermanFactory.php b/Creational/FactoryMethod/GermanFactory.php index 1f65eb4..7abb750 100644 --- a/Creational/FactoryMethod/GermanFactory.php +++ b/Creational/FactoryMethod/GermanFactory.php @@ -2,28 +2,19 @@ namespace DesignPatterns\Creational\FactoryMethod; -/** - * GermanFactory is a vehicle factory in Germany. - */ class GermanFactory extends FactoryMethod { - /** - * {@inheritdoc} - */ - protected function createVehicle($type) + protected function createVehicle(string $type): VehicleInterface { switch ($type) { case parent::CHEAP: return new Bicycle(); - break; case parent::FAST: - $obj = new Porsche(); - // we can specialize the way we want some concrete Vehicle since - // we know the class - $obj->addTuningAMG(); + $carMercedes = new CarMercedes(); + // we can specialize the way we want some concrete Vehicle since we know the class + $carMercedes->addAMGTuning(); - return $obj; - break; + return $carMercedes; default: throw new \InvalidArgumentException("$type is not a valid vehicle"); } diff --git a/Creational/FactoryMethod/ItalianFactory.php b/Creational/FactoryMethod/ItalianFactory.php index 25eeaf1..58a54f4 100644 --- a/Creational/FactoryMethod/ItalianFactory.php +++ b/Creational/FactoryMethod/ItalianFactory.php @@ -2,23 +2,15 @@ namespace DesignPatterns\Creational\FactoryMethod; -/** - * ItalianFactory is vehicle factory in Italy. - */ class ItalianFactory extends FactoryMethod { - /** - * {@inheritdoc} - */ - protected function createVehicle($type) + protected function createVehicle(string $type): VehicleInterface { switch ($type) { case parent::CHEAP: return new Bicycle(); - break; case parent::FAST: - return new Ferrari(); - break; + return new CarFerrari(); default: throw new \InvalidArgumentException("$type is not a valid vehicle"); } diff --git a/Creational/FactoryMethod/Porsche.php b/Creational/FactoryMethod/Porsche.php deleted file mode 100644 index bdabb87..0000000 --- a/Creational/FactoryMethod/Porsche.php +++ /dev/null @@ -1,30 +0,0 @@ -color = $rgb; - } - - /** - * although tuning by AMG is only offered for Mercedes Cars, - * this is a valid coding example ... - */ - public function addTuningAMG() - { - } -} diff --git a/Creational/FactoryMethod/README.rst b/Creational/FactoryMethod/README.rst index 7e69a8d..3664631 100644 --- a/Creational/FactoryMethod/README.rst +++ b/Creational/FactoryMethod/README.rst @@ -52,23 +52,23 @@ VehicleInterface.php :language: php :linenos: -Porsche.php +CarMercedes.php -.. literalinclude:: Porsche.php +.. literalinclude:: CarMercedes.php :language: php :linenos: -Bicycle.php +CarFerrari.php + +.. literalinclude:: CarFerrari.php + :language: php + :linenos: + + Bicycle.php .. literalinclude:: Bicycle.php - :language: php - :linenos: - -Ferrari.php - -.. literalinclude:: Ferrari.php - :language: php - :linenos: +:language: php + :linenos: Test ---- diff --git a/Creational/FactoryMethod/Tests/FactoryMethodTest.php b/Creational/FactoryMethod/Tests/FactoryMethodTest.php index 6b1c4d2..263b0cb 100644 --- a/Creational/FactoryMethod/Tests/FactoryMethodTest.php +++ b/Creational/FactoryMethod/Tests/FactoryMethodTest.php @@ -6,44 +6,46 @@ use DesignPatterns\Creational\FactoryMethod\FactoryMethod; use DesignPatterns\Creational\FactoryMethod\GermanFactory; use DesignPatterns\Creational\FactoryMethod\ItalianFactory; -/** - * FactoryMethodTest tests the factory method pattern. - */ class FactoryMethodTest extends \PHPUnit_Framework_TestCase { - protected $type = array( - FactoryMethod::CHEAP, - FactoryMethod::FAST, - ); - - public function getShop() + public function testCanCreateCheapVehicleInGermany() { - return array( - array(new GermanFactory()), - array(new ItalianFactory()), - ); + $factory = new GermanFactory(); + $result = $factory->create(FactoryMethod::CHEAP); + + $this->assertInstanceOf('DesignPatterns\Creational\FactoryMethod\Bicycle', $result); + } + + public function testCanCreateFastVehicleInGermany() + { + $factory = new GermanFactory(); + $result = $factory->create(FactoryMethod::FAST); + + $this->assertInstanceOf('DesignPatterns\Creational\FactoryMethod\CarMercedes', $result); + } + + public function testCanCreateCheapVehicleInItaly() + { + $factory = new ItalianFactory(); + $result = $factory->create(FactoryMethod::CHEAP); + + $this->assertInstanceOf('DesignPatterns\Creational\FactoryMethod\Bicycle', $result); + } + + public function testCanCreateFastVehicleInItaly() + { + $factory = new ItalianFactory(); + $result = $factory->create(FactoryMethod::FAST); + + $this->assertInstanceOf('DesignPatterns\Creational\FactoryMethod\CarFerrari', $result); } /** - * @dataProvider getShop - */ - public function testCreation(FactoryMethod $shop) - { - // this test method acts as a client for the factory. We don't care - // about the factory, all we know is it can produce vehicle - foreach ($this->type as $oneType) { - $vehicle = $shop->create($oneType); - $this->assertInstanceOf('DesignPatterns\Creational\FactoryMethod\VehicleInterface', $vehicle); - } - } - - /** - * @dataProvider getShop * @expectedException \InvalidArgumentException * @expectedExceptionMessage spaceship is not a valid vehicle */ - public function testUnknownType(FactoryMethod $shop) + public function testUnknownType() { - $shop->create('spaceship'); + (new ItalianFactory())->create('spaceship'); } } diff --git a/Creational/FactoryMethod/VehicleInterface.php b/Creational/FactoryMethod/VehicleInterface.php index ccb05ee..9f5fb15 100644 --- a/Creational/FactoryMethod/VehicleInterface.php +++ b/Creational/FactoryMethod/VehicleInterface.php @@ -2,15 +2,7 @@ namespace DesignPatterns\Creational\FactoryMethod; -/** - * VehicleInterface is a contract for a vehicle. - */ interface VehicleInterface { - /** - * sets the color of the vehicle. - * - * @param string $rgb - */ - public function setColor($rgb); + public function setColor(string $rgb); } diff --git a/Creational/Multiton/Multiton.php b/Creational/Multiton/Multiton.php index 5338e62..9df179c 100644 --- a/Creational/Multiton/Multiton.php +++ b/Creational/Multiton/Multiton.php @@ -2,46 +2,26 @@ namespace DesignPatterns\Creational\Multiton; -/** - * class Multiton. - */ class Multiton { - /** - * the first instance. - */ const INSTANCE_1 = '1'; - - /** - * the second instance. - */ const INSTANCE_2 = '2'; /** - * holds the named instances. - * - * @var array + * @var Multiton[] */ - private static $instances = array(); + private static $instances = []; /** - * should not be called from outside: private! + * this is private to prevent from creating arbitrary instances */ private function __construct() { } - /** - * gets the instance with the given name, e.g. Multiton::INSTANCE_1 - * uses lazy initialization. - * - * @param string $instanceName - * - * @return Multiton - */ - public static function getInstance($instanceName) + public static function getInstance(string $instanceName): Multiton { - if (!array_key_exists($instanceName, self::$instances)) { + if (!isset(self::$instances[$instanceName])) { self::$instances[$instanceName] = new self(); } @@ -49,18 +29,14 @@ class Multiton } /** - * prevent instance from being cloned. - * - * @return void + * prevent instance from being cloned */ private function __clone() { } /** - * prevent instance from being unserialized. - * - * @return void + * prevent instance from being unserialized */ private function __wakeup() { diff --git a/Creational/Multiton/Tests/MultitonTest.php b/Creational/Multiton/Tests/MultitonTest.php new file mode 100644 index 0000000..dd0383f --- /dev/null +++ b/Creational/Multiton/Tests/MultitonTest.php @@ -0,0 +1,27 @@ +assertInstanceOf('DesignPatterns\Creational\Multiton\Multiton', $firstCall); + $this->assertSame($firstCall, $secondCall); + } + + public function testUniquenessForEveryInstance() + { + $firstCall = Multiton::getInstance(Multiton::INSTANCE_1); + $secondCall = Multiton::getInstance(Multiton::INSTANCE_2); + + $this->assertInstanceOf('DesignPatterns\Creational\Multiton\Multiton', $firstCall); + $this->assertInstanceOf('DesignPatterns\Creational\Multiton\Multiton', $secondCall); + $this->assertNotSame($firstCall, $secondCall); + } +} diff --git a/Creational/Pool/Pool.php b/Creational/Pool/Pool.php deleted file mode 100644 index 7dcc6e3..0000000 --- a/Creational/Pool/Pool.php +++ /dev/null @@ -1,28 +0,0 @@ -class = $class; - } - - public function get() - { - if (count($this->instances) > 0) { - return array_pop($this->instances); - } - - return new $this->class(); - } - - public function dispose($instance) - { - $this->instances[] = $instance; - } -} diff --git a/Creational/Pool/Processor.php b/Creational/Pool/Processor.php deleted file mode 100644 index 89179b9..0000000 --- a/Creational/Pool/Processor.php +++ /dev/null @@ -1,51 +0,0 @@ -pool = $pool; - } - - public function process($image) - { - if ($this->processing++ < $this->maxProcesses) { - $this->createWorker($image); - } else { - $this->pushToWaitingQueue($image); - } - } - - private function createWorker($image) - { - $worker = $this->pool->get(); - $worker->run($image, array($this, 'processDone')); - } - - public function processDone($worker) - { - $this->processing--; - $this->pool->dispose($worker); - - if (count($this->waitingQueue) > 0) { - $this->createWorker($this->popFromWaitingQueue()); - } - } - - private function pushToWaitingQueue($image) - { - $this->waitingQueue[] = $image; - } - - private function popFromWaitingQueue() - { - return array_pop($this->waitingQueue); - } -} diff --git a/Creational/Pool/README.rst b/Creational/Pool/README.rst index 3fac0e9..78dc910 100644 --- a/Creational/Pool/README.rst +++ b/Creational/Pool/README.rst @@ -36,21 +36,15 @@ Code You can also find these code on `GitHub`_ -Pool.php +WorkerPool.php -.. literalinclude:: Pool.php +.. literalinclude:: WorkerPool.php :language: php :linenos: -Processor.php +StringReverseWorker.php -.. literalinclude:: Processor.php - :language: php - :linenos: - -Worker.php - -.. literalinclude:: Worker.php +.. literalinclude:: StringReverseWorker.php :language: php :linenos: @@ -63,11 +57,5 @@ Tests/PoolTest.php :language: php :linenos: -Tests/TestWorker.php - -.. literalinclude:: Tests/TestWorker.php - :language: php - :linenos: - .. _`GitHub`: https://github.com/domnikl/DesignPatternsPHP/tree/master/Creational/Pool .. __: http://en.wikipedia.org/wiki/Object_pool_pattern diff --git a/Creational/Pool/StringReverseWorker.php b/Creational/Pool/StringReverseWorker.php new file mode 100644 index 0000000..92c2ce5 --- /dev/null +++ b/Creational/Pool/StringReverseWorker.php @@ -0,0 +1,21 @@ +createdAt = new \DateTime(); + } + + public function run(string $text) + { + return strrev($text); + } +} diff --git a/Creational/Pool/Tests/PoolTest.php b/Creational/Pool/Tests/PoolTest.php index 7ff546a..99bf874 100644 --- a/Creational/Pool/Tests/PoolTest.php +++ b/Creational/Pool/Tests/PoolTest.php @@ -2,21 +2,28 @@ namespace DesignPatterns\Creational\Pool\Tests; -use DesignPatterns\Creational\Pool\Pool; +use DesignPatterns\Creational\Pool\WorkerPool; class PoolTest extends \PHPUnit_Framework_TestCase { - public function testPool() + public function testCanGetNewInstancesWithGet() { - $pool = new Pool('DesignPatterns\Creational\Pool\Tests\TestWorker'); - $worker = $pool->get(); + $pool = new WorkerPool(); + $worker1 = $pool->get(); + $worker2 = $pool->get(); - $this->assertEquals(1, $worker->id); + $this->assertCount(2, $pool); + $this->assertNotSame($worker1, $worker2); + } - $worker->id = 5; - $pool->dispose($worker); + public function testCanGetSameInstanceTwiceWhenDisposingItFirst() + { + $pool = new WorkerPool(); + $worker1 = $pool->get(); + $pool->dispose($worker1); + $worker2 = $pool->get(); - $this->assertEquals(5, $pool->get()->id); - $this->assertEquals(1, $pool->get()->id); + $this->assertCount(1, $pool); + $this->assertSame($worker1, $worker2); } } diff --git a/Creational/Pool/Tests/TestWorker.php b/Creational/Pool/Tests/TestWorker.php deleted file mode 100644 index 3cfbef5..0000000 --- a/Creational/Pool/Tests/TestWorker.php +++ /dev/null @@ -1,8 +0,0 @@ -freeWorkers) == 0) { + $worker = new StringReverseWorker(); + } else { + $worker = array_pop($this->freeWorkers); + } + + $this->occupiedWorkers[spl_object_hash($worker)] = $worker; + + return $worker; + } + + public function dispose(StringReverseWorker $worker) + { + $key = spl_object_hash($worker); + + if (isset($this->occupiedWorkers[$key])) { + unset($this->occupiedWorkers[$key]); + $this->freeWorkers[$key] = $worker; + } + } + + public function count(): int + { + return count($this->occupiedWorkers) + count($this->freeWorkers); + } +} diff --git a/Creational/Prototype/BarBookPrototype.php b/Creational/Prototype/BarBookPrototype.php index 7c9b72b..5afadad 100644 --- a/Creational/Prototype/BarBookPrototype.php +++ b/Creational/Prototype/BarBookPrototype.php @@ -2,9 +2,6 @@ namespace DesignPatterns\Creational\Prototype; -/** - * Class BarBookPrototype. - */ class BarBookPrototype extends BookPrototype { /** @@ -12,9 +9,6 @@ class BarBookPrototype extends BookPrototype */ protected $category = 'Bar'; - /** - * empty clone. - */ public function __clone() { } diff --git a/Creational/Prototype/BookPrototype.php b/Creational/Prototype/BookPrototype.php index e0fafa6..2f4d55f 100644 --- a/Creational/Prototype/BookPrototype.php +++ b/Creational/Prototype/BookPrototype.php @@ -2,9 +2,6 @@ namespace DesignPatterns\Creational\Prototype; -/** - * class BookPrototype. - */ abstract class BookPrototype { /** @@ -17,24 +14,13 @@ abstract class BookPrototype */ protected $category; - /** - * @abstract - * - * @return void - */ abstract public function __clone(); - /** - * @return string - */ - public function getTitle() + public function getTitle(): string { return $this->title; } - /** - * @param string $title - */ public function setTitle($title) { $this->title = $title; diff --git a/Creational/Prototype/FooBookPrototype.php b/Creational/Prototype/FooBookPrototype.php index 95ea9e6..51550f9 100644 --- a/Creational/Prototype/FooBookPrototype.php +++ b/Creational/Prototype/FooBookPrototype.php @@ -2,16 +2,13 @@ namespace DesignPatterns\Creational\Prototype; -/** - * Class FooBookPrototype. - */ class FooBookPrototype extends BookPrototype { + /** + * @var string + */ protected $category = 'Foo'; - /** - * empty clone. - */ public function __clone() { } diff --git a/Creational/Prototype/README.rst b/Creational/Prototype/README.rst index af38f8f..28606a6 100644 --- a/Creational/Prototype/README.rst +++ b/Creational/Prototype/README.rst @@ -25,12 +25,6 @@ Code You can also find these code on `GitHub`_ -index.php - -.. literalinclude:: index.php - :language: php - :linenos: - BookPrototype.php .. literalinclude:: BookPrototype.php @@ -52,5 +46,11 @@ FooBookPrototype.php Test ---- +Tests/PrototypeTest.php + +.. literalinclude:: Tests/PrototypeTest.php + :language: php + :linenos: + .. _`GitHub`: https://github.com/domnikl/DesignPatternsPHP/tree/master/Creational/Prototype .. __: http://en.wikipedia.org/wiki/Prototype_pattern diff --git a/Creational/Prototype/Tests/PrototypeTest.php b/Creational/Prototype/Tests/PrototypeTest.php new file mode 100644 index 0000000..31d906f --- /dev/null +++ b/Creational/Prototype/Tests/PrototypeTest.php @@ -0,0 +1,27 @@ +setTitle('Foo Book No ' . $i); + $this->assertInstanceOf('DesignPatterns\Creational\Prototype\FooBookPrototype', $book); + } + + for ($i = 0; $i < 5; $i++) { + $book = clone $barPrototype; + $book->setTitle('Bar Book No ' . $i); + $this->assertInstanceOf('DesignPatterns\Creational\Prototype\BarBookPrototype', $book); + } + } +} diff --git a/Creational/Prototype/index.php b/Creational/Prototype/index.php deleted file mode 100644 index d0f6e94..0000000 --- a/Creational/Prototype/index.php +++ /dev/null @@ -1,17 +0,0 @@ -setTitle('Foo Book No '.$i); -} - -for ($i = 0; $i < 5000; $i++) { - $book = clone $barPrototype; - $book->setTitle('Bar Book No '.$i); -} diff --git a/Creational/SimpleFactory/Bicycle.php b/Creational/SimpleFactory/Bicycle.php index defa801..552b986 100644 --- a/Creational/SimpleFactory/Bicycle.php +++ b/Creational/SimpleFactory/Bicycle.php @@ -2,17 +2,9 @@ namespace DesignPatterns\Creational\SimpleFactory; -/** - * Bicycle is a bicycle. - */ -class Bicycle implements VehicleInterface +class Bicycle { - /** - * @param mixed $destination - * - * @return mixed|void - */ - public function driveTo($destination) + public function driveTo(string $destination) { } } diff --git a/Creational/SimpleFactory/README.rst b/Creational/SimpleFactory/README.rst index 8f6ce81..3905367 100644 --- a/Creational/SimpleFactory/README.rst +++ b/Creational/SimpleFactory/README.rst @@ -6,11 +6,9 @@ Purpose SimpleFactory is a simple factory pattern. -It differs from the static factory because it is NOT static and as you -know: static => global => evil! - -Therefore, you can have multiple factories, differently parametrized, -you can subclass it and you can mock-up it. +It differs from the static factory because it is not static. +Therefore, you can have multiple factories, differently parametrized, you can subclass it and you can mock it. +It always should be preferred over a static factory! UML Diagram ----------- @@ -42,12 +40,6 @@ Bicycle.php :language: php :linenos: -Scooter.php - -.. literalinclude:: Scooter.php - :language: php - :linenos: - Usage ----- diff --git a/Creational/SimpleFactory/Scooter.php b/Creational/SimpleFactory/Scooter.php deleted file mode 100644 index e1db734..0000000 --- a/Creational/SimpleFactory/Scooter.php +++ /dev/null @@ -1,16 +0,0 @@ -typeList = array( - 'bicycle' => __NAMESPACE__.'\Bicycle', - 'other' => __NAMESPACE__.'\Scooter', - ); - } - - /** - * Creates a vehicle. - * - * @param string $type a known type key - * - * @throws \InvalidArgumentException - * - * @return VehicleInterface a new instance of VehicleInterface - */ - public function createVehicle($type) - { - if (!array_key_exists($type, $this->typeList)) { - throw new \InvalidArgumentException("$type is not valid vehicle"); - } - $className = $this->typeList[$type]; - - return new $className(); + return new Bicycle(); } } diff --git a/Creational/SimpleFactory/Tests/SimpleFactoryTest.php b/Creational/SimpleFactory/Tests/SimpleFactoryTest.php index c2f5379..16899eb 100644 --- a/Creational/SimpleFactory/Tests/SimpleFactoryTest.php +++ b/Creational/SimpleFactory/Tests/SimpleFactoryTest.php @@ -4,40 +4,11 @@ namespace DesignPatterns\Creational\SimpleFactory\Tests; use DesignPatterns\Creational\SimpleFactory\SimpleFactory; -/** - * SimpleFactoryTest tests the Simple Factory pattern. - */ class SimpleFactoryTest extends \PHPUnit_Framework_TestCase { - protected $factory; - - protected function setUp() + public function testCanCreateBicycle() { - $this->factory = new SimpleFactory(); - } - - public function getType() - { - return array( - array('bicycle'), - array('other'), - ); - } - - /** - * @dataProvider getType - */ - public function testCreation($type) - { - $obj = $this->factory->createVehicle($type); - $this->assertInstanceOf('DesignPatterns\Creational\SimpleFactory\VehicleInterface', $obj); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testBadType() - { - $this->factory->createVehicle('car'); + $bicycle = (new SimpleFactory())->createBicycle(); + $this->assertInstanceOf('DesignPatterns\Creational\SimpleFactory\Bicycle', $bicycle); } } diff --git a/Creational/SimpleFactory/VehicleInterface.php b/Creational/SimpleFactory/VehicleInterface.php deleted file mode 100644 index f2c8d3f..0000000 --- a/Creational/SimpleFactory/VehicleInterface.php +++ /dev/null @@ -1,16 +0,0 @@ - - - PHP - \DesignPatterns\Creational\SimpleFactory\SimpleFactory - - \DesignPatterns\Creational\SimpleFactory\Scooter - \DesignPatterns\Creational\SimpleFactory\SimpleFactory - \DesignPatterns\Creational\SimpleFactory\VehicleInterface - \DesignPatterns\Creational\SimpleFactory\Bicycle - - - - - - - - - - - - - - - - - - - - Fields - Constants - Constructors - Methods - - private - - + + + PHP + \DesignPatterns\Creational\SimpleFactory\Bicycle + + \DesignPatterns\Creational\SimpleFactory\Bicycle + \DesignPatterns\Creational\SimpleFactory\SimpleFactory + + + + + + + Methods + Constants + Fields + + private + + diff --git a/Creational/SimpleFactory/uml/uml.png b/Creational/SimpleFactory/uml/uml.png index d22c00a..93d2838 100644 Binary files a/Creational/SimpleFactory/uml/uml.png and b/Creational/SimpleFactory/uml/uml.png differ diff --git a/Creational/SimpleFactory/uml/uml.svg b/Creational/SimpleFactory/uml/uml.svg index 7da8077..73d07c1 100644 --- a/Creational/SimpleFactory/uml/uml.svg +++ b/Creational/SimpleFactory/uml/uml.svg @@ -1,286 +1,245 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - driveTo(destination) - - - - - - - - - - - - - Scooter - - - Scooter - - - - - - - - - - - - - - - - - - - typeList - - - - - - - - - - - - __construct() - - - - - - - - - - - - createVehicle(type) - - - - - - - - - - - - - SimpleFactory - - - SimpleFactory - - - - - - - - - - - - - - - - - - driveTo(destination) - - - - - - - - - - - - - VehicleInterface - - - VehicleInterface - - - - - - - - - - - - - - - - - - driveTo(destination) - - - - - - - - - - - - - Bicycle - - - Bicycle - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + driveTo(destination) + + + + + + + + + + + + + Bicycle + + + Bicycle + + + + + + + + + + + + + + + + + + + + + + driveTo(destination) + + + + + + + + + + + + + Bicycle + + + Bicycle + + + + + + + + + + + + + + + + + + + + + + createBicycle() + + + + + + + + + + + + + SimpleFactory + + + SimpleFactory + + + + + + + + + + + + + + + + + + + + + + createBicycle() + + + + + + + + + + + + + SimpleFactory + + + SimpleFactory + + + diff --git a/Creational/Singleton/Singleton.php b/Creational/Singleton/Singleton.php index 650810e..c7cacc7 100644 --- a/Creational/Singleton/Singleton.php +++ b/Creational/Singleton/Singleton.php @@ -2,22 +2,17 @@ namespace DesignPatterns\Creational\Singleton; -/** - * class Singleton. - */ -class Singleton +final class Singleton { /** - * @var Singleton reference to singleton instance + * @var Singleton */ private static $instance; /** - * gets the instance via lazy initialization (created on first usage). - * - * @return self + * gets the instance via lazy initialization (created on first usage) */ - public static function getInstance() + public static function getInstance(): Singleton { if (null === static::$instance) { static::$instance = new static(); @@ -27,33 +22,24 @@ class Singleton } /** - * is not allowed to call from outside: private! + * is not allowed to call from outside to prevent from creating multiple instances, + * to use the singleton, you have to obtain the instance from Singleton::getInstance() instead */ private function __construct() { } /** - * prevent the instance from being cloned. - * - * @throws SingletonPatternViolationException - * - * @return void + * prevent the instance from being cloned (which would create a second instance of it) */ - final public function __clone() + private function __clone() { - throw new SingletonPatternViolationException('This is a Singleton. Clone is forbidden'); } /** - * prevent from being unserialized. - * - * @throws SingletonPatternViolationException - * - * @return void + * prevent from being unserialized (which would create a second instance of it) */ - final public function __wakeup() + private function __wakeup() { - throw new SingletonPatternViolationException('This is a Singleton. __wakeup usage is forbidden'); } } diff --git a/Creational/Singleton/SingletonPatternViolationException.php b/Creational/Singleton/SingletonPatternViolationException.php deleted file mode 100644 index b025b4a..0000000 --- a/Creational/Singleton/SingletonPatternViolationException.php +++ /dev/null @@ -1,7 +0,0 @@ -assertInstanceOf('DesignPatterns\Creational\Singleton\Singleton', $firstCall); $secondCall = Singleton::getInstance(); + + $this->assertInstanceOf('DesignPatterns\Creational\Singleton\Singleton', $firstCall); $this->assertSame($firstCall, $secondCall); } - - public function testNoConstructor() - { - $obj = Singleton::getInstance(); - - $refl = new \ReflectionObject($obj); - $meth = $refl->getMethod('__construct'); - $this->assertTrue($meth->isPrivate()); - } - - /** - * @expectedException \DesignPatterns\Creational\Singleton\SingletonPatternViolationException - * - * @return void - */ - public function testNoCloneAllowed() - { - $obj1 = Singleton::getInstance(); - $obj2 = clone $obj1; - } - - /** - * @expectedException \DesignPatterns\Creational\Singleton\SingletonPatternViolationException - * - * @return void - */ - public function testNoSerializationAllowed() - { - $obj1 = Singleton::getInstance(); - $serialized = serialize($obj1); - $obj2 = unserialize($serialized); - } } diff --git a/Creational/StaticFactory/FormatNumber.php b/Creational/StaticFactory/FormatNumber.php index e577ab2..69fd9e1 100644 --- a/Creational/StaticFactory/FormatNumber.php +++ b/Creational/StaticFactory/FormatNumber.php @@ -2,9 +2,6 @@ namespace DesignPatterns\Creational\StaticFactory; -/** - * Class FormatNumber. - */ class FormatNumber implements FormatterInterface { } diff --git a/Creational/StaticFactory/FormatString.php b/Creational/StaticFactory/FormatString.php index 5cb9e28..14c2055 100644 --- a/Creational/StaticFactory/FormatString.php +++ b/Creational/StaticFactory/FormatString.php @@ -2,9 +2,6 @@ namespace DesignPatterns\Creational\StaticFactory; -/** - * Class FormatString. - */ class FormatString implements FormatterInterface { } diff --git a/Creational/StaticFactory/FormatterInterface.php b/Creational/StaticFactory/FormatterInterface.php index 349f8b6..22ea0de 100644 --- a/Creational/StaticFactory/FormatterInterface.php +++ b/Creational/StaticFactory/FormatterInterface.php @@ -2,9 +2,6 @@ namespace DesignPatterns\Creational\StaticFactory; -/** - * Class FormatterInterface. - */ interface FormatterInterface { } diff --git a/Creational/StaticFactory/StaticFactory.php b/Creational/StaticFactory/StaticFactory.php index e4c4f4b..237ec68 100644 --- a/Creational/StaticFactory/StaticFactory.php +++ b/Creational/StaticFactory/StaticFactory.php @@ -3,30 +3,26 @@ namespace DesignPatterns\Creational\StaticFactory; /** - * Note1: Remember, static => global => evil + * Note1: Remember, static means global state which is evil because it can't be mocked for tests * Note2: Cannot be subclassed or mock-upped or have multiple different instances. */ -class StaticFactory +final class StaticFactory { /** - * the parametrized function to get create an instance. - * * @param string $type * - * @static - * - * @throws \InvalidArgumentException - * * @return FormatterInterface */ - public static function factory($type) + public static function factory(string $type): FormatterInterface { - $className = __NAMESPACE__.'\Format'.ucfirst($type); - - if (!class_exists($className)) { - throw new \InvalidArgumentException('Missing format class.'); + if ($type == 'number') { + return new FormatNumber(); } - return new $className(); + if ($type == 'string') { + return new FormatString(); + } + + throw new \InvalidArgumentException('Unknown format given'); } } diff --git a/Creational/StaticFactory/Tests/StaticFactoryTest.php b/Creational/StaticFactory/Tests/StaticFactoryTest.php index 61f65af..cd1362d 100644 --- a/Creational/StaticFactory/Tests/StaticFactoryTest.php +++ b/Creational/StaticFactory/Tests/StaticFactoryTest.php @@ -4,33 +4,29 @@ namespace DesignPatterns\Creational\StaticFactory\Tests; use DesignPatterns\Creational\StaticFactory\StaticFactory; -/** - * Tests for Static Factory pattern. - */ class StaticFactoryTest extends \PHPUnit_Framework_TestCase { - public function getTypeList() + public function testCanCreateNumberFormatter() { - return array( - array('string'), - array('number'), + $this->assertInstanceOf( + 'DesignPatterns\Creational\StaticFactory\FormatNumber', + StaticFactory::factory('number') + ); + } + + public function testCanCreateStringFormatter() + { + $this->assertInstanceOf( + 'DesignPatterns\Creational\StaticFactory\FormatString', + StaticFactory::factory('string') ); } /** - * @dataProvider getTypeList - */ - public function testCreation($type) - { - $obj = StaticFactory::factory($type); - $this->assertInstanceOf('DesignPatterns\Creational\StaticFactory\FormatterInterface', $obj); - } - - /** - * @expectedException InvalidArgumentException + * @expectedException \InvalidArgumentException */ public function testException() { - StaticFactory::factory(''); + StaticFactory::factory('object'); } } diff --git a/LICENSE b/LICENSE index 5f1ce83..97e323b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2011 Dominik Liebler +Copyright (c) 2011-2016 Dominik Liebler and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/Makefile b/Makefile index 52169e6..c3841a2 100644 --- a/Makefile +++ b/Makefile @@ -190,3 +190,20 @@ pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." + +composer.phar: + php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" + php -r "if (hash_file('SHA384', 'composer-setup.php') === 'e115a8dc7871f15d853148a7fbac7da27d6c0030b848d9b3dc09e2a0388afed865e6a3d6b3c0fad45c48e2b5fc1196ae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" + php composer-setup.php + php -r "unlink('composer-setup.php');" + +install: vendor + +vendor: composer.phar + php composer.phar install + +cs: install + ./vendor/bin/phpcs -p --standard=PSR2 --ignore=vendor . + +test: install cs + ./vendor/bin/phpunit diff --git a/More/Delegation/JuniorDeveloper.php b/More/Delegation/JuniorDeveloper.php index 0946dad..dbaa30a 100644 --- a/More/Delegation/JuniorDeveloper.php +++ b/More/Delegation/JuniorDeveloper.php @@ -2,12 +2,9 @@ namespace DesignPatterns\More\Delegation; -/** - * Class JuniorDeveloper. - */ class JuniorDeveloper { - public function writeBadCode() + public function writeBadCode(): string { return 'Some junior developer generated code...'; } diff --git a/More/Delegation/README.rst b/More/Delegation/README.rst index e89e833..9c43254 100644 --- a/More/Delegation/README.rst +++ b/More/Delegation/README.rst @@ -27,12 +27,6 @@ Code You can also find these code on `GitHub`_ -Usage.php - -.. literalinclude:: Usage.php - :language: php - :linenos: - TeamLead.php .. literalinclude:: TeamLead.php diff --git a/More/Delegation/TeamLead.php b/More/Delegation/TeamLead.php index 9b75190..67d9bdf 100644 --- a/More/Delegation/TeamLead.php +++ b/More/Delegation/TeamLead.php @@ -2,31 +2,23 @@ namespace DesignPatterns\More\Delegation; -/** - * Class TeamLead. - */ class TeamLead { - /** @var JuniorDeveloper */ - protected $slave; + /** + * @var JuniorDeveloper + */ + private $junior; /** - * Give junior developer into teamlead submission. - * * @param JuniorDeveloper $junior */ public function __construct(JuniorDeveloper $junior) { - $this->slave = $junior; + $this->junior = $junior; } - /** - * TeamLead drink coffee, junior work. - * - * @return mixed - */ - public function writeCode() + public function writeCode(): string { - return $this->slave->writeBadCode(); + return $this->junior->writeBadCode(); } } diff --git a/More/Delegation/Tests/DelegationTest.php b/More/Delegation/Tests/DelegationTest.php index 7d0a33b..8660ef7 100644 --- a/More/Delegation/Tests/DelegationTest.php +++ b/More/Delegation/Tests/DelegationTest.php @@ -4,15 +4,13 @@ namespace DesignPatterns\More\Delegation\Tests; use DesignPatterns\More\Delegation; -/** - * DelegationTest tests the delegation pattern. - */ class DelegationTest extends \PHPUnit_Framework_TestCase { public function testHowTeamLeadWriteCode() { $junior = new Delegation\JuniorDeveloper(); $teamLead = new Delegation\TeamLead($junior); + $this->assertEquals($junior->writeBadCode(), $teamLead->writeCode()); } } diff --git a/More/Delegation/Usage.php b/More/Delegation/Usage.php deleted file mode 100644 index a1d9837..0000000 --- a/More/Delegation/Usage.php +++ /dev/null @@ -1,9 +0,0 @@ -writeCode(); diff --git a/More/EAV/Attribute.php b/More/EAV/Attribute.php index 3874360..10ac638 100644 --- a/More/EAV/Attribute.php +++ b/More/EAV/Attribute.php @@ -2,15 +2,10 @@ namespace DesignPatterns\More\EAV; -use SplObjectStorage; - -/** - * Class Attribute. - */ -class Attribute implements ValueAccessInterface +class Attribute { /** - * @var SplObjectStorage + * @var \SplObjectStorage */ private $values; @@ -19,64 +14,27 @@ class Attribute implements ValueAccessInterface */ private $name; - public function __construct() + public function __construct(string $name) { - $this->values = new SplObjectStorage(); + $this->values = new \SplObjectStorage(); + $this->name = $name; + } + + public function addValue(Value $value) + { + $this->values->attach($value); } /** - * @return SplObjectStorage + * @return \SplObjectStorage */ - public function getValues() + public function getValues(): \SplObjectStorage { return $this->values; } - /** - * @param ValueInterface $value - * - * @return $this - */ - public function addValue(ValueInterface $value) - { - if (!$this->values->contains($value)) { - $this->values->attach($value); - } - - return $this; - } - - /** - * @param ValueInterface $value - * - * @return $this - */ - public function removeValue(ValueInterface $value) - { - if ($this->values->contains($value)) { - $this->values->detach($value); - } - - return $this; - } - - /** - * @return string - */ - public function getName() + public function __toString(): string { return $this->name; } - - /** - * @param string $name - * - * @return $this - */ - public function setName($name) - { - $this->name = $name; - - return $this; - } } diff --git a/More/EAV/Entity.php b/More/EAV/Entity.php index ff26589..96d6583 100644 --- a/More/EAV/Entity.php +++ b/More/EAV/Entity.php @@ -2,15 +2,10 @@ namespace DesignPatterns\More\EAV; -use SplObjectStorage; - -/** - * Class Entity. - */ -class Entity implements ValueAccessInterface +class Entity { /** - * @var SplObjectStorage + * @var \SplObjectStorage */ private $values; @@ -19,64 +14,28 @@ class Entity implements ValueAccessInterface */ private $name; - public function __construct() - { - $this->values = new SplObjectStorage(); - } - - /** - * @return SplObjectStorage - */ - public function getValues() - { - return $this->values; - } - - /** - * @param ValueInterface $value - * - * @return $this - */ - public function addValue(ValueInterface $value) - { - if (!$this->values->contains($value)) { - $this->values->attach($value); - } - - return $this; - } - - /** - * @param ValueInterface $value - * - * @return $this - */ - public function removeValue(ValueInterface $value) - { - if ($this->values->contains($value)) { - $this->values->detach($value); - } - - return $this; - } - - /** - * @return string - */ - public function getName() - { - return $this->name; - } - /** * @param string $name - * - * @return $this + * @param Value[] $values */ - public function setName($name) + public function __construct(string $name, $values) { + $this->values = new \SplObjectStorage(); $this->name = $name; - return $this; + foreach ($values as $value) { + $this->values->attach($value); + } + } + + public function __toString(): string + { + $text = [$this->name]; + + foreach ($this->values as $value) { + $text[] = (string) $value; + } + + return join(', ', $text); } } diff --git a/More/EAV/README.rst b/More/EAV/README.rst index b8a8fbe..f23c71b 100644 --- a/More/EAV/README.rst +++ b/More/EAV/README.rst @@ -11,81 +11,6 @@ where the number of attributes (properties, parameters) that can be used to describe them is potentially vast, but the number that will actually apply to a given entity is relatively modest. -Examples --------- - -Check full work example in `example.php`_ file. - -.. code-block:: php - - use DesignPatterns\More\EAV\Entity; - use DesignPatterns\More\EAV\Attribute; - use DesignPatterns\More\EAV\Value; - - // Create color attribute - $color = (new Attribute())->setName('Color'); - // Create color values - $colorSilver = (new Value($color))->setName('Silver'); - $colorGold = (new Value($color))->setName('Gold'); - $colorSpaceGrey = (new Value($color))->setName('Space Grey'); - - // Create memory attribute - $memory = (new Attribute())->setName('Memory'); - // Create memory values - $memory4Gb = (new Value($memory))->setName('4GB'); - $memory8Gb = (new Value($memory))->setName('8GB'); - $memory16Gb = (new Value($memory))->setName('16GB'); - - // Create storage attribute - $storage = (new Attribute())->setName('Storage'); - // Create storage values - $storage128Gb = (new Value($storage))->setName('128GB'); - $storage256Gb = (new Value($storage))->setName('256GB'); - $storage512Gb = (new Value($storage))->setName('512GB'); - $storage1Tb = (new Value($storage))->setName('1TB'); - - // Create entities with specific values - $mac = (new Entity()) - ->setName('MacBook') - // colors - ->addValue($colorSilver) - ->addValue($colorGold) - ->addValue($colorSpaceGrey) - // memories - ->addValue($memory8Gb) - // storages - ->addValue($storage256Gb) - ->addValue($storage512Gb) - ; - - $macAir = (new Entity()) - ->setName('MacBook Air') - // colors - ->addValue($colorSilver) - // memories - ->addValue($memory4Gb) - ->addValue($memory8Gb) - // storages - ->addValue($storage128Gb) - ->addValue($storage256Gb) - ->addValue($storage512Gb) - ; - - $macPro = (new Entity()) - ->setName('MacBook Pro') - // colors - ->addValue($colorSilver) - // memories - ->addValue($memory8Gb) - ->addValue($memory16Gb) - // storages - ->addValue($storage128Gb) - ->addValue($storage256Gb) - ->addValue($storage512Gb) - ->addValue($storage1Tb) - ; - - UML Diagram ----------- @@ -98,28 +23,32 @@ Code You can also find these code on `GitHub`_ +Entity.php + +.. literalinclude:: Entity.php + :language: php + :linenos: + +Attribute.php + +.. literalinclude:: Attribute.php + :language: php + :linenos: + +Value.php + +.. literalinclude:: Value.php + :language: php + :linenos: + Test ---- -Tests/EntityTest.php +Tests/EAVTest.php .. literalinclude:: Tests/EntityTest.php :language: php :linenos: -Tests/AttributeTest.php - -.. literalinclude:: Tests/AttributeTest.php - :language: php - :linenos: - -Tests/ValueTest.php - -.. literalinclude:: Tests/ValueTest.php - :language: php - :linenos: - - -.. _`example.php`: https://github.com/domnikl/DesignPatternsPHP/tree/master/More/EAV/example.php .. _`GitHub`: https://github.com/domnikl/DesignPatternsPHP/tree/master/More/EAV .. __: https://en.wikipedia.org/wiki/Entity–attribute–value_model diff --git a/More/EAV/Tests/AttributeTest.php b/More/EAV/Tests/AttributeTest.php deleted file mode 100644 index 4affe41..0000000 --- a/More/EAV/Tests/AttributeTest.php +++ /dev/null @@ -1,66 +0,0 @@ -assertInstanceOf('\DesignPatterns\More\EAV\Attribute', $attribute); - } - - /** - * @depends testCreationSuccess - */ - public function testSetGetName() - { - $attribute = new Attribute(); - $attribute->setName('Color'); - - $this->assertEquals('Color', $attribute->getName()); - } - - /** - * @depends testCreationSuccess - */ - public function testAddValue() - { - $attribute = new Attribute(); - $attribute->setName('Color'); - - $colorSilver = new Value($attribute); - $colorSilver->setName('Silver'); - $colorGold = new Value($attribute); - $colorGold->setName('Gold'); - - $this->assertTrue($attribute->getValues()->contains($colorSilver)); - $this->assertTrue($attribute->getValues()->contains($colorGold)); - } - - /** - * @depends testAddValue - */ - public function testRemoveValue() - { - $attribute = new Attribute(); - $attribute->setName('Color'); - - $colorSilver = new Value($attribute); - $colorSilver->setName('Silver'); - $colorGold = new Value($attribute); - $colorGold->setName('Gold'); - - $attribute->removeValue($colorSilver); - - $this->assertFalse($attribute->getValues()->contains($colorSilver)); - $this->assertTrue($attribute->getValues()->contains($colorGold)); - } -} diff --git a/More/EAV/Tests/EAVTest.php b/More/EAV/Tests/EAVTest.php new file mode 100644 index 0000000..a210098 --- /dev/null +++ b/More/EAV/Tests/EAVTest.php @@ -0,0 +1,24 @@ +assertEquals('MacBook Pro, color: silver, color: black, memory: 8GB', (string) $entity); + } +} diff --git a/More/EAV/Tests/EntityTest.php b/More/EAV/Tests/EntityTest.php deleted file mode 100644 index ecd6c40..0000000 --- a/More/EAV/Tests/EntityTest.php +++ /dev/null @@ -1,145 +0,0 @@ -setName($name); - - $this->assertEquals($name, $macBook->getName()); - } - - /** - * @dataProvider valueProvider - * - * @var string - * @var Value[] $values - */ - public function testAddValue($name, array $values) - { - $macBook = new Entity(); - $macBook->setName($name); - - foreach ($values as $value) { - $macBook->addValue($value); - $this->assertTrue($macBook->getValues()->contains($value)); - } - - $this->assertCount(count($values), $macBook->getValues()); - } - - /** - * @depends testAddValue - * @dataProvider valueProvider - * - * @var string - * @var Value[] $values - */ - public function testRemoveValue($name, array $values) - { - $macBook = new Entity(); - $macBook->setName($name); - - foreach ($values as $value) { - $macBook->addValue($value); - } - $macBook->removeValue($values[0]); - - $this->assertFalse($macBook->getValues()->contains($values[0])); - unset($values[0]); - $this->assertCount(count($values), $macBook->getValues()); - } - - /** - * @return array - */ - public function valueProvider() - { - // color attribute - $color = new Attribute(); - $color->setName('Color'); - // color values - $colorSilver = new Value($color); - $colorSilver->setName('Silver'); - $colorGold = new Value($color); - $colorGold->setName('Gold'); - $colorSpaceGrey = new Value($color); - $colorSpaceGrey->setName('Space Grey'); - - // memory attribute - $memory = new Attribute(); - $memory->setName('Memory'); - // memory values - $memory4Gb = new Value($memory); - $memory4Gb->setName('4GB'); - $memory8Gb = new Value($memory); - $memory8Gb->setName('8GB'); - $memory16Gb = new Value($memory); - $memory16Gb->setName('16GB'); - - // storage attribute - $storage = new Attribute(); - $storage->setName('Storage'); - // storage values - $storage128Gb = new Value($storage); - $storage128Gb->setName('128GB'); - $storage256Gb = new Value($storage); - $storage256Gb->setName('256GB'); - $storage512Gb = new Value($storage); - $storage512Gb->setName('512GB'); - $storage1Tb = new Value($storage); - $storage1Tb->setName('1TB'); - - return array( - array( - 'MacBook', - array( - $colorSilver, - $colorGold, - $colorSpaceGrey, - $memory8Gb, - $storage256Gb, - $storage512Gb, - ), - ), - array( - 'MacBook Air', - array( - $colorSilver, - $memory4Gb, - $memory8Gb, - $storage128Gb, - $storage256Gb, - $storage512Gb, - ), - ), - array( - 'MacBook Pro', - array( - $colorSilver, - $memory8Gb, - $memory16Gb, - $storage128Gb, - $storage256Gb, - $storage512Gb, - $storage1Tb, - ), - ), - ); - } -} diff --git a/More/EAV/Tests/ValueTest.php b/More/EAV/Tests/ValueTest.php deleted file mode 100644 index f80f070..0000000 --- a/More/EAV/Tests/ValueTest.php +++ /dev/null @@ -1,46 +0,0 @@ -setName('Color'); - - $value = new Value($attribute); - - $this->assertInstanceOf('\DesignPatterns\More\EAV\Value', $value); - } - - public function testSetGetName() - { - $attribute = new Attribute(); - $attribute->setName('Color'); - - $value = new Value($attribute); - $value->setName('Silver'); - - $this->assertEquals('Silver', $value->getName()); - } - - public function testSetGetAttribute() - { - $attribute = new Attribute(); - $attribute->setName('Color'); - - $value = new Value($attribute); - $value->setName('Silver'); - $this->assertSame($attribute, $value->getAttribute()); - - $value->setAttribute($attribute); - $this->assertSame($attribute, $value->getAttribute()); - } -} diff --git a/More/EAV/Value.php b/More/EAV/Value.php index da4370f..f316728 100644 --- a/More/EAV/Value.php +++ b/More/EAV/Value.php @@ -2,10 +2,7 @@ namespace DesignPatterns\More\EAV; -/** - * Class Value. - */ -class Value implements ValueInterface +class Value { /** * @var Attribute @@ -17,53 +14,16 @@ class Value implements ValueInterface */ private $name; - /** - * @param Attribute $attribute - */ - public function __construct(Attribute $attribute) - { - $this->setAttribute($attribute); - } - - /** - * @param Attribute $attribute - * - * @return $this - */ - public function setAttribute(Attribute $attribute) - { - $this->attribute && $this->attribute->removeValue($this); // Remove value from current attribute - $attribute->addValue($this); // Add value to new attribute - $this->attribute = $attribute; - - return $this; - } - - /** - * @return Attribute - */ - public function getAttribute() - { - return $this->attribute; - } - - /** - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * @param string $name - * - * @return $this - */ - public function setName($name) + public function __construct(Attribute $attribute, string $name) { $this->name = $name; + $this->attribute = $attribute; - return $this; + $attribute->addValue($this); + } + + public function __toString(): string + { + return sprintf('%s: %s', $this->attribute, $this->name); } } diff --git a/More/EAV/ValueAccessInterface.php b/More/EAV/ValueAccessInterface.php deleted file mode 100644 index dde67b2..0000000 --- a/More/EAV/ValueAccessInterface.php +++ /dev/null @@ -1,24 +0,0 @@ -setName('Color'); -// Create color values -$colorSilver = (new Value($color))->setName('Silver'); -$colorGold = (new Value($color))->setName('Gold'); -$colorSpaceGrey = (new Value($color))->setName('Space Grey'); - -// Create memory attribute -$memory = (new Attribute())->setName('Memory'); -// Create memory values -$memory4Gb = (new Value($memory))->setName('4GB'); -$memory8Gb = (new Value($memory))->setName('8GB'); -$memory16Gb = (new Value($memory))->setName('16GB'); - -// Create storage attribute -$storage = (new Attribute())->setName('Storage'); -// Create storage values -$storage128Gb = (new Value($storage))->setName('128GB'); -$storage256Gb = (new Value($storage))->setName('256GB'); -$storage512Gb = (new Value($storage))->setName('512GB'); -$storage1Tb = (new Value($storage))->setName('1TB'); - -// Create entities with specific values -$mac = (new Entity()) - ->setName('MacBook') - // colors - ->addValue($colorSilver) - ->addValue($colorGold) - ->addValue($colorSpaceGrey) - // memories - ->addValue($memory8Gb) - // storages - ->addValue($storage256Gb) - ->addValue($storage512Gb); - -$macAir = (new Entity()) - ->setName('MacBook Air') - // colors - ->addValue($colorSilver) - // memories - ->addValue($memory4Gb) - ->addValue($memory8Gb) - // storages - ->addValue($storage128Gb) - ->addValue($storage256Gb) - ->addValue($storage512Gb); - -$macPro = (new Entity()) - ->setName('MacBook Pro') - // colors - ->addValue($colorSilver) - // memories - ->addValue($memory8Gb) - ->addValue($memory16Gb) - // storages - ->addValue($storage128Gb) - ->addValue($storage256Gb) - ->addValue($storage512Gb) - ->addValue($storage1Tb); diff --git a/More/Repository/MemoryStorage.php b/More/Repository/MemoryStorage.php index 44276d5..f0fd69a 100644 --- a/More/Repository/MemoryStorage.php +++ b/More/Repository/MemoryStorage.php @@ -2,50 +2,43 @@ namespace DesignPatterns\More\Repository; -/** - * Class MemoryStorage. - */ -class MemoryStorage implements Storage +class MemoryStorage { - private $data; - private $lastId; - - public function __construct() - { - $this->data = array(); - $this->lastId = 0; - } + /** + * @var array + */ + private $data = []; /** - * {@inheritdoc} + * @var int */ - public function persist($data) + private $lastId = 0; + + public function persist(array $data): int { - $this->data[++$this->lastId] = $data; + $this->lastId++; + + $data['id'] = $this->lastId; + $this->data[$this->lastId] = $data; return $this->lastId; } - /** - * {@inheritdoc} - */ - public function retrieve($id) - { - return isset($this->data[$id]) ? $this->data[$id] : null; - } - - /** - * {@inheritdoc} - */ - public function delete($id) + public function retrieve(int $id): array { if (!isset($this->data[$id])) { - return false; + throw new \OutOfRangeException(sprintf('No data found for ID %d', $id)); } - $this->data[$id] = null; - unset($this->data[$id]); + return $this->data[$id]; + } - return true; + public function delete(int $id) + { + if (!isset($this->data[$id])) { + throw new \OutOfRangeException(sprintf('No data found for ID %d', $id)); + } + + unset($this->data[$id]); } } diff --git a/More/Repository/Post.php b/More/Repository/Post.php index e9990b4..44aaefa 100644 --- a/More/Repository/Post.php +++ b/More/Repository/Post.php @@ -2,15 +2,10 @@ namespace DesignPatterns\More\Repository; -/** - * Post represents entity for some post that user left on the site. - * - * Class Post - */ class Post { /** - * @var int + * @var int|null */ private $id; @@ -24,92 +19,43 @@ class Post */ private $text; - /** - * @var string - */ - private $author; + public static function fromState(array $state): Post + { + return new self( + $state['id'], + $state['title'], + $state['text'] + ); + } /** - * @var \DateTime + * @param int|null $id + * @param string $text + * @param string $title */ - private $created; + public function __construct($id, string $title, string $text) + { + $this->id = $id; + $this->text = $text; + $this->title = $title; + } - /** - * @param int $id - */ - public function setId($id) + public function setId(int $id) { $this->id = $id; } - /** - * @return int - */ - public function getId() + public function getId(): int { return $this->id; } - /** - * @param string $author - */ - public function setAuthor($author) - { - $this->author = $author; - } - - /** - * @return string - */ - public function getAuthor() - { - return $this->author; - } - - /** - * @param \DateTime $created - */ - public function setCreated($created) - { - $this->created = $created; - } - - /** - * @return \DateTime - */ - public function getCreated() - { - return $this->created; - } - - /** - * @param string $text - */ - public function setText($text) - { - $this->text = $text; - } - - /** - * @return string - */ - public function getText() + public function getText(): string { return $this->text; } - /** - * @param string $title - */ - public function setTitle($title) - { - $this->title = $title; - } - - /** - * @return string - */ - public function getTitle() + public function getTitle(): string { return $this->title; } diff --git a/More/Repository/PostRepository.php b/More/Repository/PostRepository.php index e7687f3..991a398 100644 --- a/More/Repository/PostRepository.php +++ b/More/Repository/PostRepository.php @@ -3,80 +3,44 @@ namespace DesignPatterns\More\Repository; /** - * Repository for class Post - * This class is between Entity layer(class Post) and access object layer(interface Storage). + * This class is situated between Entity layer (class Post) and access object layer (MemoryStorage). * * Repository encapsulates the set of objects persisted in a data store and the operations performed over them * providing a more object-oriented view of the persistence layer * * Repository also supports the objective of achieving a clean separation and one-way dependency * between the domain and data mapping layers - * - * Class PostRepository */ class PostRepository { + /** + * @var MemoryStorage + */ private $persistence; - public function __construct(Storage $persistence) + public function __construct(MemoryStorage $persistence) { $this->persistence = $persistence; } - /** - * Returns Post object by specified id. - * - * @param int $id - * - * @return Post|null - */ - public function getById($id) + public function findById(int $id): Post { $arrayData = $this->persistence->retrieve($id); + if (is_null($arrayData)) { - return; + throw new \InvalidArgumentException(sprintf('Post with ID %d does not exist')); } - $post = new Post(); - $post->setId($arrayData['id']); - $post->setAuthor($arrayData['author']); - $post->setCreated($arrayData['created']); - $post->setText($arrayData['text']); - $post->setTitle($arrayData['title']); - - return $post; + return Post::fromState($arrayData); } - /** - * Save post object and populate it with id. - * - * @param Post $post - * - * @return Post - */ public function save(Post $post) { - $id = $this->persistence->persist(array( - 'author' => $post->getAuthor(), - 'created' => $post->getCreated(), + $id = $this->persistence->persist([ 'text' => $post->getText(), 'title' => $post->getTitle(), - )); + ]); $post->setId($id); - - return $post; - } - - /** - * Deletes specified Post object. - * - * @param Post $post - * - * @return bool - */ - public function delete(Post $post) - { - return $this->persistence->delete($post->getId()); } } diff --git a/More/Repository/README.rst b/More/Repository/README.rst index 48ac018..d8e1c72 100644 --- a/More/Repository/README.rst +++ b/More/Repository/README.rst @@ -43,12 +43,6 @@ PostRepository.php :language: php :linenos: -Storage.php - -.. literalinclude:: Storage.php - :language: php - :linenos: - MemoryStorage.php .. literalinclude:: MemoryStorage.php @@ -58,4 +52,10 @@ MemoryStorage.php Test ---- +Tests/RepositoryTest.php + +.. literalinclude:: Tests/RepositoryTest.php + :language: php + :linenos: + .. _`GitHub`: https://github.com/domnikl/DesignPatternsPHP/tree/master/More/Repository diff --git a/More/Repository/Storage.php b/More/Repository/Storage.php deleted file mode 100644 index a730f09..0000000 --- a/More/Repository/Storage.php +++ /dev/null @@ -1,42 +0,0 @@ -save($post); + + $this->assertEquals(1, $post->getId()); + $this->assertEquals($post->getId(), $repository->findById(1)->getId()); + } +} diff --git a/More/ServiceLocator/DatabaseService.php b/More/ServiceLocator/DatabaseService.php deleted file mode 100644 index 776ba2d..0000000 --- a/More/ServiceLocator/DatabaseService.php +++ /dev/null @@ -1,7 +0,0 @@ -services = array(); - $this->instantiated = array(); - $this->shared = array(); + $this->services[$class] = $service; + $this->instantiated[$class] = $service; + $this->shared[$class] = $share; } /** - * Registers a service with specific interface. + * instead of supplying a class here, you could also store a service for an interface * - * @param string $interface - * @param string|object $service - * @param bool $share + * @param string $class + * @param array $params + * @param bool $share */ - public function add($interface, $service, $share = true) + public function addClass(string $class, array $params, bool $share = true) { - /* - * When you add a service, you should register it - * with its interface or with a string that you can use - * in the future even if you will change the service implementation. - */ - - if (is_object($service) && $share) { - $this->instantiated[$interface] = $service; - } - $this->services[$interface] = (is_object($service) ? get_class($service) : $service); - $this->shared[$interface] = $share; + $this->services[$class] = $params; + $this->shared[$class] = $share; } - /** - * Checks if a service is registered. - * - * @param string $interface - * - * @return bool - */ - public function has($interface) + public function has(string $interface): bool { return isset($this->services[$interface]) || isset($this->instantiated[$interface]); } /** - * Gets the service registered for the interface. + * @param string $class * - * @param string $interface - * - * @return mixed + * @return object */ - public function get($interface) + public function get(string $class) { - // Retrieves the instance if it exists and it is shared - if (isset($this->instantiated[$interface]) && $this->shared[$interface]) { - return $this->instantiated[$interface]; + if (isset($this->instantiated[$class]) && $this->shared[$class]) { + return $this->instantiated[$class]; } - // otherwise gets the service registered. - $service = $this->services[$interface]; + $args = $this->services[$class]; - // You should check if the service class exists and - // the class is instantiable. + switch (count($args)) { + case 0: + $object = new $class(); + break; + case 1: + $object = new $class($args[0]); + break; + case 2: + $object = new $class($args[0], $args[1]); + break; + case 3: + $object = new $class($args[0], $args[1], $args[2]); + break; + default: + throw new \OutOfRangeException('Too many arguments given'); + } - // This example is a simple implementation, but - // when you create a service, you can decide - // if $service is a factory or a class. - // By registering a factory you can create your services - // using the DependencyInjection pattern. - - // ... - - // Creates the service object - $object = new $service(); - - // and saves it if the service must be shared. - if ($this->shared[$interface]) { - $this->instantiated[$interface] = $object; + if ($this->shared[$class]) { + $this->instantiated[$class] = $object; } return $object; diff --git a/More/ServiceLocator/ServiceLocatorInterface.php b/More/ServiceLocator/ServiceLocatorInterface.php deleted file mode 100644 index 3738624..0000000 --- a/More/ServiceLocator/ServiceLocatorInterface.php +++ /dev/null @@ -1,24 +0,0 @@ -serviceLocator = new ServiceLocator(); - $this->logService = new LogService(); - $this->databaseService = new DatabaseService(); } public function testHasServices() { - $this->serviceLocator->add( - 'DesignPatterns\More\ServiceLocator\LogServiceInterface', - $this->logService - ); + $this->serviceLocator->addInstance(LogService::class, new LogService()); - $this->serviceLocator->add( - 'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface', - $this->databaseService - ); - - $this->assertTrue($this->serviceLocator->has('DesignPatterns\More\ServiceLocator\LogServiceInterface')); - $this->assertTrue($this->serviceLocator->has('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface')); - - $this->assertFalse($this->serviceLocator->has('DesignPatterns\More\ServiceLocator\FakeServiceInterface')); + $this->assertTrue($this->serviceLocator->has(LogService::class)); + $this->assertFalse($this->serviceLocator->has(self::class)); } - public function testServicesWithObject() + public function testGetWillInstantiateLogServiceIfNoInstanceHasBeenCreatedYet() { - $this->serviceLocator->add( - 'DesignPatterns\More\ServiceLocator\LogServiceInterface', - $this->logService - ); + $this->serviceLocator->addClass(LogService::class, []); + $logger = $this->serviceLocator->get(LogService::class); - $this->serviceLocator->add( - 'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface', - $this->databaseService - ); - - $this->assertSame( - $this->logService, - $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\LogServiceInterface') - ); - - $this->assertSame( - $this->databaseService, - $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface') - ); - } - - public function testServicesWithClass() - { - $this->serviceLocator->add( - 'DesignPatterns\More\ServiceLocator\LogServiceInterface', - get_class($this->logService) - ); - - $this->serviceLocator->add( - 'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface', - get_class($this->databaseService) - ); - - $this->assertNotSame( - $this->logService, - $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\LogServiceInterface') - ); - - $this->assertInstanceOf( - 'DesignPatterns\More\ServiceLocator\LogServiceInterface', - $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\LogServiceInterface') - ); - - $this->assertNotSame( - $this->databaseService, - $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface') - ); - - $this->assertInstanceOf( - 'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface', - $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface') - ); - } - - public function testServicesNotShared() - { - $this->serviceLocator->add( - 'DesignPatterns\More\ServiceLocator\LogServiceInterface', - $this->logService, - false - ); - - $this->serviceLocator->add( - 'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface', - $this->databaseService, - false - ); - - $this->assertNotSame( - $this->logService, - $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\LogServiceInterface') - ); - - $this->assertInstanceOf( - 'DesignPatterns\More\ServiceLocator\LogServiceInterface', - $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\LogServiceInterface') - ); - - $this->assertNotSame( - $this->databaseService, - $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface') - ); - - $this->assertInstanceOf( - 'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface', - $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface') - ); + $this->assertInstanceOf('DesignPatterns\More\ServiceLocator\LogService', $logger); } } diff --git a/README.md b/README.md index 9f3bef6..d21efab 100755 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ The patterns can be structured in roughly three different categories. Please cli ## Contribute -Please feel free to fork and extend existing or add your own examples and send a pull request with your changes! +If you encounter any bugs or missing translations, please feel free to fork and send a pull request with your changes. To establish a consistent code quality, please check your code using [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) against [PSR2 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) using `./vendor/bin/phpcs -p --standard=PSR2 --ignore=vendor .`. ## License diff --git a/README.rst b/README.rst index bc32f77..bec81fe 100644 --- a/README.rst +++ b/README.rst @@ -34,9 +34,9 @@ Please click on **the title of every pattern's page** for a full explanation of Contribute ---------- -Please feel free to fork and extend existing or add your own examples -and send a pull request with your changes! To establish a consistent -code quality, please check your code using +If you encounter any bugs or missing translations, please feel free +to fork and send a pull request with your changes. +To establish a consistent code quality, please check your code using `PHP CodeSniffer`_ against `PSR2 standard`_ using ``./vendor/bin/phpcs -p --standard=PSR2 --ignore=vendor .``. diff --git a/Structural/Adapter/Book.php b/Structural/Adapter/Book.php index 6458beb..05d15cc 100644 --- a/Structural/Adapter/Book.php +++ b/Structural/Adapter/Book.php @@ -2,22 +2,25 @@ namespace DesignPatterns\Structural\Adapter; -/** - * Book is a concrete and standard paper book. - */ -class Book implements PaperBookInterface +class Book implements BookInterface { /** - * {@inheritdoc} + * @var int */ + private $page; + public function open() { + $this->page = 1; } - /** - * {@inheritdoc} - */ public function turnPage() { + $this->page++; + } + + public function getPage(): int + { + return $this->page; } } diff --git a/Structural/Adapter/BookInterface.php b/Structural/Adapter/BookInterface.php new file mode 100644 index 0000000..6ebabf4 --- /dev/null +++ b/Structural/Adapter/BookInterface.php @@ -0,0 +1,12 @@ +eBook = $ebook; + $this->eBook = $eBook; } /** @@ -30,14 +26,22 @@ class EBookAdapter implements PaperBookInterface */ public function open() { - $this->eBook->pressStart(); + $this->eBook->unlock(); } - /** - * turns pages. - */ public function turnPage() { $this->eBook->pressNext(); } + + /** + * notice the adapted behavior here: EBookInterface::getPage() will return two integers, but BookInterface + * supports only a current page getter, so we adapt the behavior here + * + * @return int + */ + public function getPage(): int + { + return $this->eBook->getPage()[0]; + } } diff --git a/Structural/Adapter/EBookInterface.php b/Structural/Adapter/EBookInterface.php index bd3dc26..bae5033 100644 --- a/Structural/Adapter/EBookInterface.php +++ b/Structural/Adapter/EBookInterface.php @@ -2,22 +2,16 @@ namespace DesignPatterns\Structural\Adapter; -/** - * EBookInterface is a contract for an electronic book. - */ interface EBookInterface { - /** - * go to next page. - * - * @return mixed - */ + public function unlock(); + public function pressNext(); /** - * start the book. + * returns current page and total number of pages, like [10, 100] is page 10 of 100 * - * @return mixed + * @return int[] */ - public function pressStart(); + public function getPage(): array; } diff --git a/Structural/Adapter/Kindle.php b/Structural/Adapter/Kindle.php index 06d4489..a150767 100644 --- a/Structural/Adapter/Kindle.php +++ b/Structural/Adapter/Kindle.php @@ -3,21 +3,37 @@ namespace DesignPatterns\Structural\Adapter; /** - * Kindle is a concrete electronic book. + * this is the adapted class. In production code, this could be a class from another package, some vendor code. + * Notice that it uses another naming scheme and the implementation does something similar but in another way */ class Kindle implements EBookInterface { /** - * {@inheritdoc} + * @var int */ + private $page = 1; + + /** + * @var int + */ + private $totalPages = 100; + public function pressNext() + { + $this->page++; + } + + public function unlock() { } /** - * {@inheritdoc} + * returns current page and total number of pages, like [10, 100] is page 10 of 100 + * + * @return int[] */ - public function pressStart() + public function getPage(): array { + return [$this->page, $this->totalPages]; } } diff --git a/Structural/Adapter/PaperBookInterface.php b/Structural/Adapter/PaperBookInterface.php deleted file mode 100644 index 4b62149..0000000 --- a/Structural/Adapter/PaperBookInterface.php +++ /dev/null @@ -1,23 +0,0 @@ -open(); + $book->turnPage(); + + $this->assertEquals(2, $book->getPage()); } - /** - * This client only knows paper book but surprise, surprise, the second book - * is in fact an electronic book, but both work the same way. - * - * @param PaperBookInterface $book - * - * @dataProvider getBook - */ - public function testIAmAnOldClient(PaperBookInterface $book) + public function testCanTurnPageOnKindleLikeInANormalBook() { - $this->assertTrue(method_exists($book, 'open')); - $this->assertTrue(method_exists($book, 'turnPage')); + $kindle = new Kindle(); + $book = new EBookAdapter($kindle); + + $book->open(); + $book->turnPage(); + + $this->assertEquals(2, $book->getPage()); } } diff --git a/Structural/Bridge/Assemble.php b/Structural/Bridge/Assemble.php deleted file mode 100644 index 9b9d114..0000000 --- a/Structural/Bridge/Assemble.php +++ /dev/null @@ -1,11 +0,0 @@ -workShop1->work(); - $this->workShop2->work(); - } -} diff --git a/Structural/Bridge/FormatterInterface.php b/Structural/Bridge/FormatterInterface.php new file mode 100644 index 0000000..d59a344 --- /dev/null +++ b/Structural/Bridge/FormatterInterface.php @@ -0,0 +1,8 @@ +implementation->format('Hello World'); + } +} diff --git a/Structural/Bridge/HtmlFormatter.php b/Structural/Bridge/HtmlFormatter.php new file mode 100644 index 0000000..79e49b5 --- /dev/null +++ b/Structural/Bridge/HtmlFormatter.php @@ -0,0 +1,11 @@ +%s

', $text); + } +} diff --git a/Structural/Bridge/Motorcycle.php b/Structural/Bridge/Motorcycle.php deleted file mode 100644 index f008785..0000000 --- a/Structural/Bridge/Motorcycle.php +++ /dev/null @@ -1,21 +0,0 @@ -workShop1->work(); - $this->workShop2->work(); - } -} diff --git a/Structural/Bridge/PlainTextFormatter.php b/Structural/Bridge/PlainTextFormatter.php new file mode 100644 index 0000000..af46283 --- /dev/null +++ b/Structural/Bridge/PlainTextFormatter.php @@ -0,0 +1,11 @@ +implementation = $printer; + } + + /** + * @param FormatterInterface $printer + */ + public function setImplementation(FormatterInterface $printer) + { + $this->implementation = $printer; + } + + abstract public function get(); +} diff --git a/Structural/Bridge/Tests/BridgeTest.php b/Structural/Bridge/Tests/BridgeTest.php index 0a89a86..2f70e87 100644 --- a/Structural/Bridge/Tests/BridgeTest.php +++ b/Structural/Bridge/Tests/BridgeTest.php @@ -2,24 +2,19 @@ namespace DesignPatterns\Structural\Bridge\Tests; -use DesignPatterns\Structural\Bridge\Assemble; -use DesignPatterns\Structural\Bridge\Car; -use DesignPatterns\Structural\Bridge\Motorcycle; -use DesignPatterns\Structural\Bridge\Produce; +use DesignPatterns\Structural\Bridge\HelloWorldService; +use DesignPatterns\Structural\Bridge\HtmlFormatter; +use DesignPatterns\Structural\Bridge\PlainTextFormatter; class BridgeTest extends \PHPUnit_Framework_TestCase { - public function testCar() + public function testCanPrintUsingThePlainTextPrinter() { - $vehicle = new Car(new Produce(), new Assemble()); - $this->expectOutputString('Car Produced Assembled'); - $vehicle->manufacture(); - } + $service = new HelloWorldService(new PlainTextFormatter()); + $this->assertEquals('Hello World', $service->get()); - public function testMotorcycle() - { - $vehicle = new Motorcycle(new Produce(), new Assemble()); - $this->expectOutputString('Motorcycle Produced Assembled'); - $vehicle->manufacture(); + // now change the implemenation and use the HtmlFormatter instead + $service->setImplementation(new HtmlFormatter()); + $this->assertEquals('

Hello World

', $service->get()); } } diff --git a/Structural/Bridge/Vehicle.php b/Structural/Bridge/Vehicle.php deleted file mode 100644 index 80311e1..0000000 --- a/Structural/Bridge/Vehicle.php +++ /dev/null @@ -1,20 +0,0 @@ -workShop1 = $workShop1; - $this->workShop2 = $workShop2; - } - - abstract public function manufacture(); -} diff --git a/Structural/Bridge/Workshop.php b/Structural/Bridge/Workshop.php deleted file mode 100644 index 9cb26c5..0000000 --- a/Structural/Bridge/Workshop.php +++ /dev/null @@ -1,11 +0,0 @@ - - - PHP - \DesignPatterns\Structural\Bridge\Vehicle - - \DesignPatterns\Structural\Bridge\Workshop - \DesignPatterns\Structural\Bridge\Car - \DesignPatterns\Structural\Bridge\Produce - \DesignPatterns\Structural\Bridge\Motorcycle - \DesignPatterns\Structural\Bridge\Assemble - \DesignPatterns\Structural\Bridge\Vehicle - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Fields - Constants - Constructors - Methods - - private - - + + + PHP + \DesignPatterns\Structural\Bridge\HtmlFormatter + + \DesignPatterns\Structural\Bridge\PlainTextFormatter + \DesignPatterns\Structural\Bridge\FormatterInterface + \DesignPatterns\Structural\Bridge\Service + \DesignPatterns\Structural\Bridge\HelloWorldService + \DesignPatterns\Structural\Bridge\HtmlFormatter + + + + + + + + + + + + + + + + + + + + + + + + Fields + Constants + Methods + + private + + diff --git a/Structural/Bridge/uml/uml.png b/Structural/Bridge/uml/uml.png index df6ce3c..6de5b02 100644 Binary files a/Structural/Bridge/uml/uml.png and b/Structural/Bridge/uml/uml.png differ diff --git a/Structural/Bridge/uml/uml.svg b/Structural/Bridge/uml/uml.svg index 189a7dc..9483eab 100644 --- a/Structural/Bridge/uml/uml.svg +++ b/Structural/Bridge/uml/uml.svg @@ -1,451 +1,661 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - work() - - - - - - - - - - - - - Workshop - - - Workshop - - - - - - - - - - - - - - - - - - - __construct(workShop1, workShop2) - - - - - - - - - - - - - manufacture() - - - - - - - - - - - - - Car - - - Car - - - - - - - - - - - - - - - - - - - work() - - - - - - - - - - - - - Produce - - - Produce - - - - - - - - - - - - - - - - - - - __construct(workShop1, workShop2) - - - - - - - - - - - - - manufacture() - - - - - - - - - - - - - Motorcycle - - - Motorcycle - - - - - - - - - - - - - - - - - - - work() - - - - - - - - - - - - - Assemble - - - Assemble - - - - - - - - - - - - - - - - - - - workShop1 - - - - - - - - - - workShop2 - - - - - - - - - - - - - __construct(workShop1, workShop2) - - - - - - - - - - - - - manufacture() - - - - - - - - - - - - - Vehicle - - - Vehicle - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + format(text) + + + + + + + + + + + + + PlainTextFormatter + + + PlainTextFormatter + + + + + + + + + + + + + + + + + + + + + + format(text) + + + + + + + + + + + + + PlainTextFormatter + + + PlainTextFormatter + + + + + + + + + + + + + + + + + + + + + + format(text) + + + + + + + + + + + + + FormatterInterface + + + FormatterInterface + + + + + + + + + + + + + + + + + + + + + + format(text) + + + + + + + + + + + + + FormatterInterface + + + FormatterInterface + + + + + + + + + + + + + + + + + + + + + + + + + implementation + + + + + + + + + + + + + + + + setImplementation(printer) + + + + + + + + + + + + + get() + + + + + + + + + + + + + Service + + + Service + + + + + + + + + + + + + + + + + + + + + + implementation + + + + + + + + + + + + + + + + + + + setImplementation(printer) + + + + + + + + + + + + + + + + get() + + + + + + + + + + + + + Service + + + Service + + + + + + + + + + + + + + + + + + + + + + get() + + + + + + + + + + + + + HelloWorldService + + + HelloWorldService + + + + + + + + + + + + + + + + + + + + + + get() + + + + + + + + + + + + + HelloWorldService + + + HelloWorldService + + + + + + + + + + + + + + + + + + + + + + format(text) + + + + + + + + + + + + + HtmlFormatter + + + HtmlFormatter + + + + + + + + + + + + + + + + + + + + + + format(text) + + + + + + + + + + + + + HtmlFormatter + + + HtmlFormatter + + + + + + + + + + + + + diff --git a/Structural/Composite/Form.php b/Structural/Composite/Form.php index 42f1bc1..6db2d3f 100644 --- a/Structural/Composite/Form.php +++ b/Structural/Composite/Form.php @@ -6,12 +6,12 @@ namespace DesignPatterns\Structural\Composite; * The composite node MUST extend the component contract. This is mandatory for building * a tree of components. */ -class Form extends FormElement +class Form implements RenderableInterface { /** - * @var array|FormElement[] + * @var RenderableInterface[] */ - protected $elements; + private $elements; /** * runs through all elements and calls render() on them, then returns the complete representation @@ -19,25 +19,25 @@ class Form extends FormElement * * from the outside, one will not see this and the form will act like a single object instance * - * @param int $indent - * * @return string */ - public function render($indent = 0) + public function render(): string { - $formCode = ''; + $formCode = '
'; foreach ($this->elements as $element) { - $formCode .= $element->render($indent + 1).PHP_EOL; + $formCode .= $element->render(); } + $formCode .= '
'; + return $formCode; } /** - * @param FormElement $element + * @param RenderableInterface $element */ - public function addElement(FormElement $element) + public function addElement(RenderableInterface $element) { $this->elements[] = $element; } diff --git a/Structural/Composite/FormElement.php b/Structural/Composite/FormElement.php deleted file mode 100644 index 0055aee..0000000 --- a/Structural/Composite/FormElement.php +++ /dev/null @@ -1,18 +0,0 @@ -'; + return ''; } } diff --git a/Structural/Composite/README.rst b/Structural/Composite/README.rst index 66d8f16..1e64739 100644 --- a/Structural/Composite/README.rst +++ b/Structural/Composite/README.rst @@ -28,9 +28,9 @@ Code You can also find these code on `GitHub`_ -FormElement.php +RenderableInterface.php -.. literalinclude:: FormElement.php +.. literalinclude:: RenderableInterface.php :language: php :linenos: diff --git a/Structural/Composite/RenderableInterface.php b/Structural/Composite/RenderableInterface.php new file mode 100644 index 0000000..444f707 --- /dev/null +++ b/Structural/Composite/RenderableInterface.php @@ -0,0 +1,8 @@ +addElement(new Composite\TextElement()); + $form->addElement(new Composite\TextElement('Email:')); $form->addElement(new Composite\InputElement()); $embed = new Composite\Form(); - $embed->addElement(new Composite\TextElement()); + $embed->addElement(new Composite\TextElement('Password:')); $embed->addElement(new Composite\InputElement()); - $form->addElement($embed); // here we have a embedded form (like SF2 does) + $form->addElement($embed); - $this->assertRegExp('#^\s{4}#m', $form->render()); - } - - /** - * The point of this pattern, a Composite must inherit from the node - * if you want to build trees. - */ - public function testFormImplementsFormEelement() - { - $className = 'DesignPatterns\Structural\Composite\Form'; - $abstractName = 'DesignPatterns\Structural\Composite\FormElement'; - $this->assertTrue(is_subclass_of($className, $abstractName)); + $this->assertEquals( + '
Email:Password:
', + $form->render() + ); } } diff --git a/Structural/Composite/TextElement.php b/Structural/Composite/TextElement.php index 48b33ba..55a868b 100644 --- a/Structural/Composite/TextElement.php +++ b/Structural/Composite/TextElement.php @@ -2,20 +2,20 @@ namespace DesignPatterns\Structural\Composite; -/** - * Class TextElement. - */ -class TextElement extends FormElement +class TextElement implements RenderableInterface { /** - * renders the text element. - * - * @param int $indent - * - * @return mixed|string + * @var string */ - public function render($indent = 0) + private $text; + + public function __construct(string $text) { - return str_repeat(' ', $indent).'this is a text element'; + $this->text = $text; + } + + public function render(): string + { + return $this->text; } } diff --git a/Structural/Composite/uml/Composite.uml b/Structural/Composite/uml/Composite.uml index 326dfd3..a874b2f 100644 --- a/Structural/Composite/uml/Composite.uml +++ b/Structural/Composite/uml/Composite.uml @@ -1,42 +1,39 @@ - - - PHP - \DesignPatterns\Structural\Composite\InputElement - - \DesignPatterns\Structural\Composite\TextElement - \DesignPatterns\Structural\Composite\FormElement - \DesignPatterns\Structural\Composite\InputElement - \DesignPatterns\Structural\Composite\Form - - - - - - - - - - - - - - - - - - - - - - - \DesignPatterns\Structural\Composite\InputElement - - - Fields - Constants - Constructors - Methods - - private - - + + + PHP + \DesignPatterns\Structural\Composite\Form + + \DesignPatterns\Structural\Composite\RenderableInterface + \DesignPatterns\Structural\Composite\InputElement + \DesignPatterns\Structural\Composite\TextElement + \DesignPatterns\Structural\Composite\Form + + + + + + + + + + + + + + + + + + + + + + + + Methods + Constants + Fields + + private + + diff --git a/Structural/Composite/uml/uml.png b/Structural/Composite/uml/uml.png index 5fed73d..1d8ad76 100644 Binary files a/Structural/Composite/uml/uml.png and b/Structural/Composite/uml/uml.png differ diff --git a/Structural/Composite/uml/uml.svg b/Structural/Composite/uml/uml.svg index c587818..ac9c76e 100644 --- a/Structural/Composite/uml/uml.svg +++ b/Structural/Composite/uml/uml.svg @@ -1,284 +1,606 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - render(indent) - - - - - - - - - - - - - TextElement - - - TextElement - - - - - - - - - - - - - - - - - - - render(indent) - - - - - - - - - - - - - FormElement - - - FormElement - - - - - - - - - - - - - - - - - - - render(indent) - - - - - - - - - - - - - InputElement - - - InputElement - - - - - - - - - - - - - - - - - - - elements - - - - - - - - - - - - - render(indent) - - - - - - - - - - addElement(element) - - - - - - - - - - - - - Form - - - Form - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + render() + + + + + + + + + + + + + RenderableInterface + + + RenderableInterface + + + + + + + + + + + + + + + + + + + + + + render() + + + + + + + + + + + + + RenderableInterface + + + RenderableInterface + + + + + + + + + + + + + + + + + + + + + + render() + + + + + + + + + + + + + InputElement + + + InputElement + + + + + + + + + + + + + + + + + + + + + + render() + + + + + + + + + + + + + InputElement + + + InputElement + + + + + + + + + + + + + + + + + + + + + + + + + text + + + + + + + + + + + + + + + + render() + + + + + + + + + + + + + TextElement + + + TextElement + + + + + + + + + + + + + + + + + + + + + + text + + + + + + + + + + + + + + + + + + + render() + + + + + + + + + + + + + TextElement + + + TextElement + + + + + + + + + + + + + + + + + + + + + + + + + elements + + + + + + + + + + + + + + + + render() + + + + + + + + + + + + + addElement(element) + + + + + + + + + + + + + Form + + + Form + + + + + + + + + + + + + + + + + + + + + + elements + + + + + + + + + + + + + + + + + + + render() + + + + + + + + + + + + + + + + addElement(element) + + + + + + + + + + + + + Form + + + Form + + + + + + + + + + + + + diff --git a/Structural/DataMapper/README.rst b/Structural/DataMapper/README.rst index 5180243..70dd17a 100644 --- a/Structural/DataMapper/README.rst +++ b/Structural/DataMapper/README.rst @@ -47,6 +47,12 @@ UserMapper.php :language: php :linenos: +StorageAdapter.php + +.. literalinclude:: StorageAdapter.php + :language: php + :linenos: + Test ---- diff --git a/Structural/DataMapper/StorageAdapter.php b/Structural/DataMapper/StorageAdapter.php new file mode 100644 index 0000000..2c9caef --- /dev/null +++ b/Structural/DataMapper/StorageAdapter.php @@ -0,0 +1,30 @@ +data = $data; + } + + /** + * @param int $id + * + * @return array|null + */ + public function find(int $id) + { + if (isset($this->data[$id])) { + return $this->data[$id]; + } + + return null; + } +} diff --git a/Structural/DataMapper/Tests/DataMapperTest.php b/Structural/DataMapper/Tests/DataMapperTest.php index 551c11e..402ed3f 100644 --- a/Structural/DataMapper/Tests/DataMapperTest.php +++ b/Structural/DataMapper/Tests/DataMapperTest.php @@ -2,109 +2,29 @@ namespace DesignPatterns\Structural\DataMapper\Tests; -use DesignPatterns\Structural\DataMapper\User; +use DesignPatterns\Structural\DataMapper\StorageAdapter; use DesignPatterns\Structural\DataMapper\UserMapper; -/** - * UserMapperTest tests the datamapper pattern. - */ class DataMapperTest extends \PHPUnit_Framework_TestCase { - /** - * @var UserMapper - */ - protected $mapper; - - /** - * @var DBAL - */ - protected $dbal; - - protected function setUp() + public function testCanMapUserFromStorage() { - $this->dbal = $this->getMockBuilder('DesignPatterns\Structural\DataMapper\DBAL') - ->disableAutoload() - ->setMethods(array('insert', 'update', 'find', 'findAll')) - ->getMock(); + $storage = new StorageAdapter([1 => ['username' => 'domnikl', 'email' => 'liebler.dominik@gmail.com']]); + $mapper = new UserMapper($storage); - $this->mapper = new UserMapper($this->dbal); - } + $user = $mapper->findById(1); - public function getNewUser() - { - return array(array(new User(null, 'Odysseus', 'Odysseus@ithaca.gr'))); - } - - public function getExistingUser() - { - return array(array(new User(1, 'Odysseus', 'Odysseus@ithaca.gr'))); - } - - /** - * @dataProvider getNewUser - */ - public function testPersistNew(User $user) - { - $this->dbal->expects($this->once()) - ->method('insert'); - $this->mapper->save($user); - } - - /** - * @dataProvider getExistingUser - */ - public function testPersistExisting(User $user) - { - $this->dbal->expects($this->once()) - ->method('update'); - $this->mapper->save($user); - } - - /** - * @dataProvider getExistingUser - */ - public function testRestoreOne(User $existing) - { - $row = array( - 'userid' => 1, - 'username' => 'Odysseus', - 'email' => 'Odysseus@ithaca.gr', - ); - $rows = new \ArrayIterator(array($row)); - $this->dbal->expects($this->once()) - ->method('find') - ->with(1) - ->will($this->returnValue($rows)); - - $user = $this->mapper->findById(1); - $this->assertEquals($existing, $user); - } - - /** - * @dataProvider getExistingUser - */ - public function testRestoreMulti(User $existing) - { - $rows = array(array('userid' => 1, 'username' => 'Odysseus', 'email' => 'Odysseus@ithaca.gr')); - $this->dbal->expects($this->once()) - ->method('findAll') - ->will($this->returnValue($rows)); - - $user = $this->mapper->findAll(); - $this->assertEquals(array($existing), $user); + $this->assertInstanceOf('DesignPatterns\Structural\DataMapper\User', $user); } /** * @expectedException \InvalidArgumentException - * @expectedExceptionMessage User #404 not found */ - public function testNotFound() + public function testWillNotMapInvalidData() { - $this->dbal->expects($this->once()) - ->method('find') - ->with(404) - ->will($this->returnValue(array())); + $storage = new StorageAdapter([]); + $mapper = new UserMapper($storage); - $user = $this->mapper->findById(404); + $mapper->findById(1); } } diff --git a/Structural/DataMapper/User.php b/Structural/DataMapper/User.php index f3f7661..8a6fd04 100644 --- a/Structural/DataMapper/User.php +++ b/Structural/DataMapper/User.php @@ -2,56 +2,34 @@ namespace DesignPatterns\Structural\DataMapper; -/** - * DataMapper pattern. - * - * This is our representation of a DataBase record in the memory (Entity) - * - * Validation would also go in this object - */ class User { /** - * @var int + * @var string */ - protected $userId; + private $username; /** * @var string */ - protected $username; + private $email; - /** - * @var string - */ - protected $email; - - /** - * @param null $id - * @param null $username - * @param null $email - */ - public function __construct($id = null, $username = null, $email = null) + public static function fromState(array $state): User { - $this->setUserID($id); - $this->setUsername($username); - $this->setEmail($email); + // validate state before accessing keys! + + return new self( + $state['username'], + $state['email'] + ); } - /** - * @return int - */ - public function getUserId() + public function __construct(string $username, string $email) { - return $this->userId; - } + // validate parameters before setting them! - /** - * @param int $userId - */ - public function setUserID($userId) - { - $this->userId = $userId; + $this->username = $username; + $this->email = $email; } /** @@ -62,14 +40,6 @@ class User return $this->username; } - /** - * @param string $username - */ - public function setUsername($username) - { - $this->username = $username; - } - /** * @return string */ @@ -77,12 +47,4 @@ class User { return $this->email; } - - /** - * @param string $email - */ - public function setEmail($email) - { - $this->email = $email; - } } diff --git a/Structural/DataMapper/UserMapper.php b/Structural/DataMapper/UserMapper.php index f147063..415246a 100644 --- a/Structural/DataMapper/UserMapper.php +++ b/Structural/DataMapper/UserMapper.php @@ -2,107 +2,44 @@ namespace DesignPatterns\Structural\DataMapper; -/** - * class UserMapper. - */ class UserMapper { /** - * @var DBAL + * @var StorageAdapter */ - protected $adapter; + private $adapter; /** - * @param DBAL $dbLayer + * @param StorageAdapter $storage */ - public function __construct(DBAL $dbLayer) + public function __construct(StorageAdapter $storage) { - $this->adapter = $dbLayer; + $this->adapter = $storage; } /** - * saves a user object from memory to Database. - * - * @param User $user - * - * @return bool - */ - public function save(User $user) - { - /* $data keys should correspond to valid Table columns on the Database */ - $data = array( - 'userid' => $user->getUserId(), - 'username' => $user->getUsername(), - 'email' => $user->getEmail(), - ); - - /* if no ID specified create new user else update the one in the Database */ - if (null === ($id = $user->getUserId())) { - unset($data['userid']); - $this->adapter->insert($data); - - return true; - } else { - $this->adapter->update($data, array('userid = ?' => $id)); - - return true; - } - } - - /** - * finds a user from Database based on ID and returns a User object located - * in memory. + * finds a user from storage based on ID and returns a User object located + * in memory. Normally this kind of logic will be implemented using the Repository pattern. + * However the important part is in mapRowToUser() below, that will create a business object from the + * data fetched from storage * * @param int $id * - * @throws \InvalidArgumentException - * * @return User */ - public function findById($id) + public function findById(int $id): User { $result = $this->adapter->find($id); - if (0 == count($result)) { + if ($result === null) { throw new \InvalidArgumentException("User #$id not found"); } - $row = $result->current(); - return $this->mapObject($row); + return $this->mapRowToUser($result); } - /** - * fetches an array from Database and returns an array of User objects - * located in memory. - * - * @return array - */ - public function findAll() + private function mapRowToUser(array $row): User { - $resultSet = $this->adapter->findAll(); - $entries = array(); - - foreach ($resultSet as $row) { - $entries[] = $this->mapObject($row); - } - - return $entries; - } - - /** - * Maps a table row to an object. - * - * @param array $row - * - * @return User - */ - protected function mapObject(array $row) - { - $entry = new User(); - $entry->setUserID($row['userid']); - $entry->setUsername($row['username']); - $entry->setEmail($row['email']); - - return $entry; + return User::fromState($row); } } diff --git a/Structural/DataMapper/uml/DataMapper.uml b/Structural/DataMapper/uml/DataMapper.uml index 697e1f9..720faf8 100644 --- a/Structural/DataMapper/uml/DataMapper.uml +++ b/Structural/DataMapper/uml/DataMapper.uml @@ -1,23 +1,23 @@ - - - PHP - \DesignPatterns\Structural\DataMapper\User - - \DesignPatterns\Structural\DataMapper\User - \DesignPatterns\Structural\DataMapper\UserMapper - - - - - - \DesignPatterns\Structural\DataMapper\User - - - Fields - Constants - Constructors - Methods - - private - - + + + PHP + \DesignPatterns\Structural\DataMapper\StorageAdapter + + \DesignPatterns\Structural\DataMapper\UserMapper + \DesignPatterns\Structural\DataMapper\StorageAdapter + \DesignPatterns\Structural\DataMapper\User + + + + + + \DesignPatterns\Structural\DataMapper\StorageAdapter + + + Methods + Fields + Constants + + private + + diff --git a/Structural/DataMapper/uml/uml.png b/Structural/DataMapper/uml/uml.png index 97cd26a..d2f59ea 100644 Binary files a/Structural/DataMapper/uml/uml.png and b/Structural/DataMapper/uml/uml.png differ diff --git a/Structural/DataMapper/uml/uml.svg b/Structural/DataMapper/uml/uml.svg index d65a9c9..612769f 100644 --- a/Structural/DataMapper/uml/uml.svg +++ b/Structural/DataMapper/uml/uml.svg @@ -1,403 +1,680 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - userId - - - - - - - - - - username - - - - - - - - - - email - - - - - - - - - - - - - __construct(id, username, email) - - - - - - - - - - - - - getUserId() - - - - - - - - - - setUserID(userId) - - - - - - - - - - getUsername() - - - - - - - - - - setUsername(username) - - - - - - - - - - getEmail() - - - - - - - - - - setEmail(email) - - - - - - - - - - - - - User - - - User - - - - - - - - - - - - - - - - - - - adapter - - - - - - - - - - - - - __construct(dbLayer) - - - - - - - - - - - - - save(user) - - - - - - - - - - findById(id) - - - - - - - - - - findAll() - - - - - - - - - - mapObject(row) - - - - - - - - - - - - - UserMapper - - - UserMapper - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + adapter + + + + + + + + + + + + + + + + findById(id) + + + + + + + + + + + + + mapRowToUser(row) + + + + + + + + + + + + + UserMapper + + + UserMapper + + + + + + + + + + + + + + + + + + + + + + adapter + + + + + + + + + + + + + + + + + + + findById(id) + + + + + + + + + + + + + + + + mapRowToUser(row) + + + + + + + + + + + + + UserMapper + + + UserMapper + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + data + + + + + + + + + + + + + + + + find(id) + + + + + + + + + + + + + StorageAdapter + + + StorageAdapter + + + + + + + + + + + + + + + + + + + + + + data + + + + + + + + + + + + + + + + + + + find(id) + + + + + + + + + + + + + StorageAdapter + + + StorageAdapter + + + + + + + + + + + + + + + + + + + + + + + + + email + + + + + + + + + + + + + + + + username + + + + + + + + + + + + + + + + fromState(state) + + + + + + + + + + + + + getUsername() + + + + + + + + + + + + + getEmail() + + + + + + + + + + + + + User + + + User + + + + + + + + + + + + + + + + + + + + + + email + + + + + + + + + + + + + + + + username + + + + + + + + + + + + + + + + + + + + + + fromState(state) + + + + + + + + + + + + + + + + getUsername() + + + + + + + + + + + + + + + + getEmail() + + + + + + + + + + + + + User + + + User + + + diff --git a/Structural/Decorator/Decorator.php b/Structural/Decorator/Decorator.php deleted file mode 100644 index 0b8fde3..0000000 --- a/Structural/Decorator/Decorator.php +++ /dev/null @@ -1,31 +0,0 @@ -wrapped = $wrappable; - } -} diff --git a/Structural/Decorator/JsonRenderer.php b/Structural/Decorator/JsonRenderer.php new file mode 100644 index 0000000..9cc4066 --- /dev/null +++ b/Structural/Decorator/JsonRenderer.php @@ -0,0 +1,11 @@ +wrapped->renderData()); + } +} diff --git a/Structural/Decorator/README.rst b/Structural/Decorator/README.rst index b57a608..c854787 100644 --- a/Structural/Decorator/README.rst +++ b/Structural/Decorator/README.rst @@ -25,9 +25,9 @@ Code You can also find these code on `GitHub`_ -RendererInterface.php +RenderableInterface.php -.. literalinclude:: RendererInterface.php +.. literalinclude:: RenderableInterface.php :language: php :linenos: @@ -37,21 +37,21 @@ Webservice.php :language: php :linenos: -Decorator.php +RendererDecorator.php -.. literalinclude:: Decorator.php +.. literalinclude:: RendererDecorator.php :language: php :linenos: -RenderInXml.php +XmlRenderer.php -.. literalinclude:: RenderInXml.php +.. literalinclude:: XmlRenderer.php :language: php :linenos: -RenderInJson.php +JsonRenderer.php -.. literalinclude:: RenderInJson.php +.. literalinclude:: JsonRenderer.php :language: php :linenos: diff --git a/Structural/Decorator/RenderInJson.php b/Structural/Decorator/RenderInJson.php deleted file mode 100644 index fb9a71e..0000000 --- a/Structural/Decorator/RenderInJson.php +++ /dev/null @@ -1,19 +0,0 @@ -wrapped->renderData()); - } -} diff --git a/Structural/Decorator/RenderInXml.php b/Structural/Decorator/RenderInXml.php deleted file mode 100644 index f203d53..0000000 --- a/Structural/Decorator/RenderInXml.php +++ /dev/null @@ -1,27 +0,0 @@ -wrapped->renderData() as $key => $val) { - $doc->appendChild($doc->createElement($key, $val)); - } - - return $doc->saveXML(); - } -} diff --git a/Structural/Decorator/RenderableInterface.php b/Structural/Decorator/RenderableInterface.php new file mode 100644 index 0000000..07e11d1 --- /dev/null +++ b/Structural/Decorator/RenderableInterface.php @@ -0,0 +1,8 @@ +wrapped = $renderer; + } +} diff --git a/Structural/Decorator/RendererInterface.php b/Structural/Decorator/RendererInterface.php deleted file mode 100644 index 73152b9..0000000 --- a/Structural/Decorator/RendererInterface.php +++ /dev/null @@ -1,16 +0,0 @@ -service = new Decorator\Webservice(array('foo' => 'bar')); + $this->service = new Decorator\Webservice('foobar'); } public function testJsonDecorator() { - // Wrap service with a JSON decorator for renderers - $service = new Decorator\RenderInJson($this->service); - // Our Renderer will now output JSON instead of an array - $this->assertEquals('{"foo":"bar"}', $service->renderData()); + $service = new Decorator\JsonRenderer($this->service); + + $this->assertEquals('"foobar"', $service->renderData()); } public function testXmlDecorator() { - // Wrap service with a XML decorator for renderers - $service = new Decorator\RenderInXml($this->service); - // Our Renderer will now output XML instead of an array - $xml = 'bar'; - $this->assertXmlStringEqualsXmlString($xml, $service->renderData()); - } + $service = new Decorator\XmlRenderer($this->service); - /** - * The first key-point of this pattern :. - */ - public function testDecoratorMustImplementsRenderer() - { - $className = 'DesignPatterns\Structural\Decorator\Decorator'; - $interfaceName = 'DesignPatterns\Structural\Decorator\RendererInterface'; - $this->assertTrue(is_subclass_of($className, $interfaceName)); - } - - /** - * Second key-point of this pattern : the decorator is type-hinted. - * - * @expectedException \PHPUnit_Framework_Error - */ - public function testDecoratorTypeHinted() - { - if (version_compare(PHP_VERSION, '7', '>=')) { - throw new \PHPUnit_Framework_Error('Skip test for PHP 7', 0, __FILE__, __LINE__); - } - - $this->getMockForAbstractClass('DesignPatterns\Structural\Decorator\Decorator', array(new \stdClass())); - } - - /** - * Second key-point of this pattern : the decorator is type-hinted. - * - * @requires PHP 7 - * @expectedException TypeError - */ - public function testDecoratorTypeHintedForPhp7() - { - $this->getMockForAbstractClass('DesignPatterns\Structural\Decorator\Decorator', array(new \stdClass())); - } - - /** - * The decorator implements and wraps the same interface. - */ - public function testDecoratorOnlyAcceptRenderer() - { - $mock = $this->getMock('DesignPatterns\Structural\Decorator\RendererInterface'); - $dec = $this->getMockForAbstractClass('DesignPatterns\Structural\Decorator\Decorator', array($mock)); - $this->assertNotNull($dec); + $this->assertXmlStringEqualsXmlString('foobar', $service->renderData()); } } diff --git a/Structural/Decorator/Webservice.php b/Structural/Decorator/Webservice.php index e2bcc69..6715a22 100644 --- a/Structural/Decorator/Webservice.php +++ b/Structural/Decorator/Webservice.php @@ -2,28 +2,19 @@ namespace DesignPatterns\Structural\Decorator; -/** - * Class Webservice. - */ -class Webservice implements RendererInterface +class Webservice implements RenderableInterface { /** - * @var mixed + * @var string */ - protected $data; + private $data; - /** - * @param mixed $data - */ - public function __construct($data) + public function __construct(string $data) { $this->data = $data; } - /** - * @return string - */ - public function renderData() + public function renderData(): string { return $this->data; } diff --git a/Structural/Decorator/XmlRenderer.php b/Structural/Decorator/XmlRenderer.php new file mode 100644 index 0000000..012da47 --- /dev/null +++ b/Structural/Decorator/XmlRenderer.php @@ -0,0 +1,15 @@ +wrapped->renderData(); + $doc->appendChild($doc->createElement('content', $data)); + + return $doc->saveXML(); + } +} diff --git a/Structural/DependencyInjection/AbstractConfig.php b/Structural/DependencyInjection/AbstractConfig.php deleted file mode 100644 index f2da4dc..0000000 --- a/Structural/DependencyInjection/AbstractConfig.php +++ /dev/null @@ -1,19 +0,0 @@ -storage = $storage; - } -} diff --git a/Structural/DependencyInjection/ArrayConfig.php b/Structural/DependencyInjection/ArrayConfig.php deleted file mode 100644 index f26c089..0000000 --- a/Structural/DependencyInjection/ArrayConfig.php +++ /dev/null @@ -1,39 +0,0 @@ -storage[$key])) { - return $this->storage[$key]; - } - - return $default; - } - - /** - * Set parameter. - * - * @param string|int $key - * @param mixed $value - */ - public function set($key, $value) - { - $this->storage[$key] = $value; - } -} diff --git a/Structural/DependencyInjection/Connection.php b/Structural/DependencyInjection/Connection.php deleted file mode 100644 index d389089..0000000 --- a/Structural/DependencyInjection/Connection.php +++ /dev/null @@ -1,50 +0,0 @@ -configuration = $config; - } - - /** - * connection using the injected config. - */ - public function connect() - { - $host = $this->configuration->get('host'); - // connection to host, authentication etc... - - //if connected - $this->host = $host; - } - - /* - * Get currently connected host - * - * @return string - */ - - public function getHost() - { - return $this->host; - } -} diff --git a/Structural/DependencyInjection/DatabaseConfiguration.php b/Structural/DependencyInjection/DatabaseConfiguration.php new file mode 100644 index 0000000..51d72d7 --- /dev/null +++ b/Structural/DependencyInjection/DatabaseConfiguration.php @@ -0,0 +1,54 @@ +host = $host; + $this->port = $port; + $this->username = $username; + $this->password = $password; + } + + public function getHost(): string + { + return $this->host; + } + + public function getPort(): int + { + return $this->port; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getPassword(): string + { + return $this->password; + } +} diff --git a/Structural/DependencyInjection/DatabaseConnection.php b/Structural/DependencyInjection/DatabaseConnection.php new file mode 100644 index 0000000..b5af8ba --- /dev/null +++ b/Structural/DependencyInjection/DatabaseConnection.php @@ -0,0 +1,34 @@ +configuration = $config; + } + + public function getDsn(): string + { + // this is just for the sake of demonstration, not a real DSN + // notice that only the injected config is used here, so there is + // a real separation of concerns here + + return sprintf( + '%s:%s@%s:%d', + $this->configuration->getUsername(), + $this->configuration->getPassword(), + $this->configuration->getHost(), + $this->configuration->getPort() + ); + } +} diff --git a/Structural/DependencyInjection/Parameters.php b/Structural/DependencyInjection/Parameters.php deleted file mode 100644 index c49f4c7..0000000 --- a/Structural/DependencyInjection/Parameters.php +++ /dev/null @@ -1,26 +0,0 @@ -`__. +directly in ``DatabaseConnection``, which is not very good for testing and +extending it. Examples -------- @@ -45,27 +38,15 @@ Code You can also find these code on `GitHub`_ -AbstractConfig.php +DatabaseConfiguration.php -.. literalinclude:: AbstractConfig.php +.. literalinclude:: DatabaseConfiguration.php :language: php :linenos: -Parameters.php +DatabaseConnection.php -.. literalinclude:: Parameters.php - :language: php - :linenos: - -ArrayConfig.php - -.. literalinclude:: ArrayConfig.php - :language: php - :linenos: - -Connection.php - -.. literalinclude:: Connection.php +.. literalinclude:: DatabaseConnection.php :language: php :linenos: @@ -78,11 +59,5 @@ Tests/DependencyInjectionTest.php :language: php :linenos: -Tests/config.php - -.. literalinclude:: Tests/config.php - :language: php - :linenos: - .. _`GitHub`: https://github.com/domnikl/DesignPatternsPHP/tree/master/Structural/DependencyInjection .. __: http://en.wikipedia.org/wiki/Dependency_injection diff --git a/Structural/DependencyInjection/Tests/DependencyInjectionTest.php b/Structural/DependencyInjection/Tests/DependencyInjectionTest.php index 6df82a7..3749d57 100644 --- a/Structural/DependencyInjection/Tests/DependencyInjectionTest.php +++ b/Structural/DependencyInjection/Tests/DependencyInjectionTest.php @@ -2,24 +2,16 @@ namespace DesignPatterns\Structural\DependencyInjection\Tests; -use DesignPatterns\Structural\DependencyInjection\ArrayConfig; -use DesignPatterns\Structural\DependencyInjection\Connection; +use DesignPatterns\Structural\DependencyInjection\DatabaseConfiguration; +use DesignPatterns\Structural\DependencyInjection\DatabaseConnection; class DependencyInjectionTest extends \PHPUnit_Framework_TestCase { - protected $config; - protected $source; - - public function setUp() - { - $this->source = include 'config.php'; - $this->config = new ArrayConfig($this->source); - } - public function testDependencyInjection() { - $connection = new Connection($this->config); - $connection->connect(); - $this->assertEquals($this->source['host'], $connection->getHost()); + $config = new DatabaseConfiguration('localhost', 3306, 'domnikl', '1234'); + $connection = new DatabaseConnection($config); + + $this->assertEquals('domnikl:1234@localhost:3306', $connection->getDsn()); } } diff --git a/Structural/DependencyInjection/Tests/config.php b/Structural/DependencyInjection/Tests/config.php deleted file mode 100644 index 29d3683..0000000 --- a/Structural/DependencyInjection/Tests/config.php +++ /dev/null @@ -1,3 +0,0 @@ - 'github.com'); diff --git a/Structural/DependencyInjection/uml/DependencyInjection.uml b/Structural/DependencyInjection/uml/DependencyInjection.uml index e058b19..2434332 100644 --- a/Structural/DependencyInjection/uml/DependencyInjection.uml +++ b/Structural/DependencyInjection/uml/DependencyInjection.uml @@ -1,38 +1,20 @@ - - - PHP - \DesignPatterns\Structural\DependencyInjection\AbstractConfig - - \DesignPatterns\Structural\DependencyInjection\Connection - \DesignPatterns\Structural\DependencyInjection\ArrayConfig - \DesignPatterns\Structural\DependencyInjection\Parameters - \DesignPatterns\Structural\DependencyInjection\AbstractConfig - - - - - - - - - - - - - - - - - - - \DesignPatterns\Structural\DependencyInjection\AbstractConfig - - - Fields - Constants - Constructors - Methods - - private - - + + + PHP + \DesignPatterns\Structural\DependencyInjection\DatabaseConfiguration + + \DesignPatterns\Structural\DependencyInjection\DatabaseConnection + \DesignPatterns\Structural\DependencyInjection\DatabaseConfiguration + + + + + + + Methods + Constants + Fields + + private + + diff --git a/Structural/DependencyInjection/uml/uml.png b/Structural/DependencyInjection/uml/uml.png index 67e6ca1..992d9a1 100644 Binary files a/Structural/DependencyInjection/uml/uml.png and b/Structural/DependencyInjection/uml/uml.png differ diff --git a/Structural/DependencyInjection/uml/uml.svg b/Structural/DependencyInjection/uml/uml.svg index 78d603d..1e15816 100644 --- a/Structural/DependencyInjection/uml/uml.svg +++ b/Structural/DependencyInjection/uml/uml.svg @@ -1,425 +1,573 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - configuration - - - - - - - - - - host - - - - - - - - - - - - - __construct(config) - - - - - - - - - - - - - connect() - - - - - - - - - - getHost() - - - - - - - - - - - - - Connection - - - Connection - - - - - - - - - - - - - - - - - - - get(key, default) - - - - - - - - - - set(key, value) - - - - - - - - - - - - - ArrayConfig - - - ArrayConfig - - - - - - - - - - - - - - - - - - - get(key) - - - - - - - - - - set(key, value) - - - - - - - - - - - - - Parameters - - - Parameters - - - - - - - - - - - - - - - - - - - storage - - - - - - - - - - - - - __construct(storage) - - - - - - - - - - - - - AbstractConfig - - - AbstractConfig - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + configuration + + + + + + + + + + + + + + + + getDsn() + + + + + + + + + + + + + DatabaseConnection + + + DatabaseConnection + + + + + + + + + + + + + + + + + + + + + + configuration + + + + + + + + + + + + + + + + + + + getDsn() + + + + + + + + + + + + + DatabaseConnection + + + DatabaseConnection + + + + + + + + + + + + + + + + + + + + + + + + + password + + + + + + + + + + + + + + + + port + + + + + + + + + + + + + + + + host + + + + + + + + + + + + + + + + username + + + + + + + + + + + + + + + + getHost() + + + + + + + + + + + + + getPort() + + + + + + + + + + + + + getUsername() + + + + + + + + + + + + + getPassword() + + + + + + + + + + + + + DatabaseConfiguration + + + DatabaseConfiguration + + + + + + + + + + + + + + + + + + + + + + password + + + + + + + + + + + + + + + + port + + + + + + + + + + + + + + + + host + + + + + + + + + + + + + + + + username + + + + + + + + + + + + + + + + + + + getHost() + + + + + + + + + + + + + + + + getPort() + + + + + + + + + + + + + + + + getUsername() + + + + + + + + + + + + + + + + getPassword() + + + + + + + + + + + + + DatabaseConfiguration + + + DatabaseConfiguration + + + diff --git a/Structural/Facade/BiosInterface.php b/Structural/Facade/BiosInterface.php index 8f1aa01..ba6bdbe 100644 --- a/Structural/Facade/BiosInterface.php +++ b/Structural/Facade/BiosInterface.php @@ -2,30 +2,13 @@ namespace DesignPatterns\Structural\Facade; -/** - * Interface BiosInterface. - */ interface BiosInterface { - /** - * Execute the BIOS. - */ public function execute(); - /** - * Wait for halt. - */ public function waitForKeyPress(); - /** - * Launches the OS. - * - * @param OsInterface $os - */ public function launch(OsInterface $os); - /** - * Power down BIOS. - */ public function powerDown(); } diff --git a/Structural/Facade/Facade.php b/Structural/Facade/Facade.php index 43d1bcb..bda8b46 100644 --- a/Structural/Facade/Facade.php +++ b/Structural/Facade/Facade.php @@ -2,25 +2,19 @@ namespace DesignPatterns\Structural\Facade; -/** - * Class Facade. - */ class Facade { /** * @var OsInterface */ - protected $os; + private $os; /** * @var BiosInterface */ - protected $bios; + private $bios; /** - * This is the perfect time to use a dependency injection container - * to create an instance of this class. - * * @param BiosInterface $bios * @param OsInterface $os */ @@ -30,9 +24,6 @@ class Facade $this->os = $os; } - /** - * Turn on the system. - */ public function turnOn() { $this->bios->execute(); @@ -40,9 +31,6 @@ class Facade $this->bios->launch($this->os); } - /** - * Turn off the system. - */ public function turnOff() { $this->os->halt(); diff --git a/Structural/Facade/OsInterface.php b/Structural/Facade/OsInterface.php index d8171e4..1b3a93e 100644 --- a/Structural/Facade/OsInterface.php +++ b/Structural/Facade/OsInterface.php @@ -2,13 +2,9 @@ namespace DesignPatterns\Structural\Facade; -/** - * Interface OsInterface. - */ interface OsInterface { - /** - * Halt the OS. - */ public function halt(); + + public function getName(): string; } diff --git a/Structural/Facade/Tests/FacadeTest.php b/Structural/Facade/Tests/FacadeTest.php index 0247aaf..8c6a0a6 100644 --- a/Structural/Facade/Tests/FacadeTest.php +++ b/Structural/Facade/Tests/FacadeTest.php @@ -2,47 +2,34 @@ namespace DesignPatterns\Structural\Facade\Tests; -use DesignPatterns\Structural\Facade\Facade as Computer; +use DesignPatterns\Structural\Facade\Facade; use DesignPatterns\Structural\Facade\OsInterface; -/** - * FacadeTest shows example of facades. - */ class FacadeTest extends \PHPUnit_Framework_TestCase { - public function getComputer() + public function testComputerOn() { + /** @var OsInterface|\PHPUnit_Framework_MockObject_MockObject $os */ + $os = $this->createMock('DesignPatterns\Structural\Facade\OsInterface'); + + $os->method('getName') + ->will($this->returnValue('Linux')); + $bios = $this->getMockBuilder('DesignPatterns\Structural\Facade\BiosInterface') - ->setMethods(array('launch', 'execute', 'waitForKeyPress')) - ->disableAutoload() - ->getMock(); - $operatingSys = $this->getMockBuilder('DesignPatterns\Structural\Facade\OsInterface') - ->setMethods(array('getName')) - ->disableAutoload() - ->getMock(); + ->setMethods(['launch', 'execute', 'waitForKeyPress']) + ->disableAutoload() + ->getMock(); + $bios->expects($this->once()) - ->method('launch') - ->with($operatingSys); - $operatingSys - ->expects($this->once()) - ->method('getName') - ->will($this->returnValue('Linux')); + ->method('launch') + ->with($os); - $facade = new Computer($bios, $operatingSys); + $facade = new Facade($bios, $os); - return array(array($facade, $operatingSys)); - } - - /** - * @param Computer $facade - * @param OsInterface $os - * @dataProvider getComputer - */ - public function testComputerOn(Computer $facade, OsInterface $os) - { - // interface is simpler : + // the facade interface is simple $facade->turnOn(); - // but I can access to lower component + + // but you can also access the underlying components $this->assertEquals('Linux', $os->getName()); } } diff --git a/Structural/Facade/uml/Facade.uml b/Structural/Facade/uml/Facade.uml index d6bdd03..8835df8 100644 --- a/Structural/Facade/uml/Facade.uml +++ b/Structural/Facade/uml/Facade.uml @@ -1,24 +1,23 @@ - - - PHP - \DesignPatterns\Structural\Facade\BiosInterface - - \DesignPatterns\Structural\Facade\BiosInterface - \DesignPatterns\Structural\Facade\OsInterface - \DesignPatterns\Structural\Facade\Facade - - - - - - \DesignPatterns\Structural\Facade\BiosInterface - - - Fields - Constants - Constructors - Methods - - private - - + + + PHP + \DesignPatterns\Structural\Facade\BiosInterface + + \DesignPatterns\Structural\Facade\BiosInterface + \DesignPatterns\Structural\Facade\Facade + \DesignPatterns\Structural\Facade\OsInterface + + + + + + \DesignPatterns\Structural\Facade\BiosInterface + + + Methods + Constants + Fields + + private + + diff --git a/Structural/Facade/uml/uml.png b/Structural/Facade/uml/uml.png index da5097a..fcea5f2 100644 Binary files a/Structural/Facade/uml/uml.png and b/Structural/Facade/uml/uml.png differ diff --git a/Structural/Facade/uml/uml.svg b/Structural/Facade/uml/uml.svg index 2e7b400..a14db50 100644 --- a/Structural/Facade/uml/uml.svg +++ b/Structural/Facade/uml/uml.svg @@ -1,324 +1,653 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - execute() - - - - - - - - - - waitForKeyPress() - - - - - - - - - - launch(os) - - - - - - - - - - powerDown() - - - - - - - - - - - - - BiosInterface - - - BiosInterface - - - - - - - - - - - - - - - - - - - halt() - - - - - - - - - - - - - OsInterface - - - OsInterface - - - - - - - - - - - - - - - - - - - os - - - - - - - - - - bios - - - - - - - - - - - - - __construct(bios, os) - - - - - - - - - - - - - turnOn() - - - - - - - - - - turnOff() - - - - - - - - - - - - - Facade - - - Facade - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + execute() + + + + + + + + + + + + + waitForKeyPress() + + + + + + + + + + + + + launch(os) + + + + + + + + + + + + + powerDown() + + + + + + + + + + + + + BiosInterface + + + BiosInterface + + + + + + + + + + + + + + + + + + + + + + execute() + + + + + + + + + + + + + + + + waitForKeyPress() + + + + + + + + + + + + + + + + launch(os) + + + + + + + + + + + + + + + + powerDown() + + + + + + + + + + + + + BiosInterface + + + BiosInterface + + + + + + + + + + + + + + + + + + + + + + + + + os + + + + + + + + + + + + + + + + bios + + + + + + + + + + + + + + + + turnOn() + + + + + + + + + + + + + turnOff() + + + + + + + + + + + + + Facade + + + Facade + + + + + + + + + + + + + + + + + + + + + + os + + + + + + + + + + + + + + + + bios + + + + + + + + + + + + + + + + + + + turnOn() + + + + + + + + + + + + + + + + turnOff() + + + + + + + + + + + + + Facade + + + Facade + + + + + + + + + + + + + + + + + + + + + + halt() + + + + + + + + + + + + + getName() + + + + + + + + + + + + + OsInterface + + + OsInterface + + + + + + + + + + + + + + + + + + + + + + halt() + + + + + + + + + + + + + + + + getName() + + + + + + + + + + + + + OsInterface + + + OsInterface + + + diff --git a/Structural/FluentInterface/Sql.php b/Structural/FluentInterface/Sql.php index 58ba491..ed54992 100644 --- a/Structural/FluentInterface/Sql.php +++ b/Structural/FluentInterface/Sql.php @@ -2,79 +2,51 @@ namespace DesignPatterns\Structural\FluentInterface; -/** - * class SQL. - */ class Sql { /** * @var array */ - protected $fields = array(); + private $fields = []; /** * @var array */ - protected $from = array(); + private $from = []; /** * @var array */ - protected $where = array(); + private $where = []; - /** - * adds select fields. - * - * @param array $fields - * - * @return SQL - */ - public function select(array $fields = array()) + public function select(array $fields): Sql { $this->fields = $fields; return $this; } - /** - * adds a FROM clause. - * - * @param string $table - * @param string $alias - * - * @return SQL - */ - public function from($table, $alias) + public function from(string $table, string $alias): Sql { $this->from[] = $table.' AS '.$alias; return $this; } - /** - * adds a WHERE condition. - * - * @param string $condition - * - * @return SQL - */ - public function where($condition) + public function where(string $condition): Sql { $this->where[] = $condition; return $this; } - /** - * Gets the query, just an example of building a query, - * no check on consistency. - * - * @return string - */ - public function getQuery() + public function __toString(): string { - return 'SELECT '.implode(',', $this->fields) - .' FROM '.implode(',', $this->from) - .' WHERE '.implode(' AND ', $this->where); + return sprintf( + 'SELECT %s FROM %s WHERE %s', + join(', ', $this->fields), + join(', ', $this->from), + join(' AND ', $this->where) + ); } } diff --git a/Structural/FluentInterface/Tests/FluentInterfaceTest.php b/Structural/FluentInterface/Tests/FluentInterfaceTest.php index ae4e656..b75e2f9 100644 --- a/Structural/FluentInterface/Tests/FluentInterfaceTest.php +++ b/Structural/FluentInterface/Tests/FluentInterfaceTest.php @@ -4,19 +4,15 @@ namespace DesignPatterns\Structural\FluentInterface\Tests; use DesignPatterns\Structural\FluentInterface\Sql; -/** - * FluentInterfaceTest tests the fluent interface SQL. - */ class FluentInterfaceTest extends \PHPUnit_Framework_TestCase { public function testBuildSQL() { - $instance = new Sql(); - $query = $instance->select(array('foo', 'bar')) + $query = (new Sql()) + ->select(['foo', 'bar']) ->from('foobar', 'f') - ->where('f.bar = ?') - ->getQuery(); + ->where('f.bar = ?'); - $this->assertEquals('SELECT foo,bar FROM foobar AS f WHERE f.bar = ?', $query); + $this->assertEquals('SELECT foo, bar FROM foobar AS f WHERE f.bar = ?', (string) $query); } } diff --git a/Structural/FluentInterface/uml/FluentInterface.uml b/Structural/FluentInterface/uml/FluentInterface.uml index 8fca787..552b641 100644 --- a/Structural/FluentInterface/uml/FluentInterface.uml +++ b/Structural/FluentInterface/uml/FluentInterface.uml @@ -1,22 +1,19 @@ - - - PHP - \DesignPatterns\Structural\FluentInterface\Sql - - \DesignPatterns\Structural\FluentInterface\Sql - - - - - - \DesignPatterns\Structural\FluentInterface\Sql - - - Fields - Constants - Constructors - Methods - - private - - + + + PHP + \DesignPatterns\Structural\FluentInterface\Sql + + \DesignPatterns\Structural\FluentInterface\Sql + + + + + + + Fields + Constants + Methods + + private + + diff --git a/Structural/FluentInterface/uml/uml.png b/Structural/FluentInterface/uml/uml.png index e49aa57..2f38396 100644 Binary files a/Structural/FluentInterface/uml/uml.png and b/Structural/FluentInterface/uml/uml.png differ diff --git a/Structural/FluentInterface/uml/uml.svg b/Structural/FluentInterface/uml/uml.svg index 60a8564..9418314 100644 --- a/Structural/FluentInterface/uml/uml.svg +++ b/Structural/FluentInterface/uml/uml.svg @@ -1,191 +1,377 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - fields - - - - - - - - - - from - - - - - - - - - - where - - - - - - - - - - - - - select(fields) - - - - - - - - - - from(table, alias) - - - - - - - - - - where(condition) - - - - - - - - - - getQuery() - - - - - - - - - - - - - Sql - - - Sql - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + from + + + + + + + + + + + + + + + + where + + + + + + + + + + + + + + + + fields + + + + + + + + + + + + + + + + select(fields) + + + + + + + + + + + + + from(table, alias) + + + + + + + + + + + + + where(condition) + + + + + + + + + + + + + __toString() + + + + + + + + + + + + + Sql + + + Sql + + + + + + + + + + + + + + + + + + + + + + from + + + + + + + + + + + + + + + + where + + + + + + + + + + + + + + + + fields + + + + + + + + + + + + + + + + + + + select(fields) + + + + + + + + + + + + + + + + from(table, alias) + + + + + + + + + + + + + + + + where(condition) + + + + + + + + + + + + + + + + __toString() + + + + + + + + + + + + + Sql + + + Sql + + + diff --git a/Structural/Flyweight/CharacterFlyweight.php b/Structural/Flyweight/CharacterFlyweight.php index 02657c1..d1e7954 100644 --- a/Structural/Flyweight/CharacterFlyweight.php +++ b/Structural/Flyweight/CharacterFlyweight.php @@ -16,22 +16,16 @@ class CharacterFlyweight implements FlyweightInterface */ private $name; - /** - * @param string $name - */ - public function __construct($name) + public function __construct(string $name) { $this->name = $name; } - /** - * Clients supply the context-dependent information that the flyweight needs to draw itself - * For flyweights representing characters, extrinsic state usually contains e.g. the font. - * - * @param string $font - */ - public function draw($font) + public function render(string $font): string { - print_r("Character {$this->name} printed $font \n"); + // Clients supply the context-dependent information that the flyweight needs to draw itself + // For flyweights representing characters, extrinsic state usually contains e.g. the font. + + return sprintf('Character %s with font %s', $this->name, $font); } } diff --git a/Structural/Flyweight/FlyweightFactory.php b/Structural/Flyweight/FlyweightFactory.php index 10a0d4d..a295a22 100644 --- a/Structural/Flyweight/FlyweightFactory.php +++ b/Structural/Flyweight/FlyweightFactory.php @@ -3,38 +3,26 @@ namespace DesignPatterns\Structural\Flyweight; /** - * A factory manages shared flyweights. Clients shouldn't instaniate them directly, + * A factory manages shared flyweights. Clients should not instantiate them directly, * but let the factory take care of returning existing objects or creating new ones. */ -class FlyweightFactory +class FlyweightFactory implements \Countable { /** - * Associative store for flyweight objects. - * - * @var array + * @var CharacterFlyweight[] */ - private $pool = array(); + private $pool = []; - /** - * Magic getter. - * - * @param string $name - * - * @return Flyweight - */ - public function __get($name) + public function get(string $name): CharacterFlyweight { - if (!array_key_exists($name, $this->pool)) { + if (!isset($this->pool[$name])) { $this->pool[$name] = new CharacterFlyweight($name); } return $this->pool[$name]; } - /** - * @return int - */ - public function totalNumber() + public function count(): int { return count($this->pool); } diff --git a/Structural/Flyweight/FlyweightInterface.php b/Structural/Flyweight/FlyweightInterface.php index b12290d..032a1be 100644 --- a/Structural/Flyweight/FlyweightInterface.php +++ b/Structural/Flyweight/FlyweightInterface.php @@ -2,13 +2,7 @@ namespace DesignPatterns\Structural\Flyweight; -/** - * An interface through which flyweights can receive and act on extrinsic state. - */ interface FlyweightInterface { - /** - * @param string $extrinsicState - */ - public function draw($extrinsicState); + public function render(string $extrinsicState): string; } diff --git a/Structural/Flyweight/Tests/FlyweightTest.php b/Structural/Flyweight/Tests/FlyweightTest.php index 997c3df..5f63456 100644 --- a/Structural/Flyweight/Tests/FlyweightTest.php +++ b/Structural/Flyweight/Tests/FlyweightTest.php @@ -4,33 +4,28 @@ namespace DesignPatterns\Structural\Flyweight\Tests; use DesignPatterns\Structural\Flyweight\FlyweightFactory; -/** - * FlyweightTest demonstrates how a client would use the flyweight structure - * You don't have to change the code of your client. - */ class FlyweightTest extends \PHPUnit_Framework_TestCase { - private $characters = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', - 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', ); - private $fonts = array('Arial', 'Times New Roman', 'Verdana', 'Helvetica'); - - // This is about the number of characters in a book of average length - private $numberOfCharacters = 300000; + private $characters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', + 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']; + private $fonts = ['Arial', 'Times New Roman', 'Verdana', 'Helvetica']; public function testFlyweight() { $factory = new FlyweightFactory(); - for ($i = 0; $i < $this->numberOfCharacters; $i++) { - $char = $this->characters[array_rand($this->characters)]; - $font = $this->fonts[array_rand($this->fonts)]; - $flyweight = $factory->$char; - // External state can be passed in like this: - // $flyweight->draw($font); + foreach ($this->characters as $char) { + foreach ($this->fonts as $font) { + $flyweight = $factory->get($char); + $rendered = $flyweight->render($font); + + $this->assertEquals(sprintf('Character %s with font %s', $char, $font), $rendered); + } } // Flyweight pattern ensures that instances are shared // instead of having hundreds of thousands of individual objects - $this->assertLessThanOrEqual($factory->totalNumber(), count($this->characters)); + // there must be one instance for every char that has been reused for displaying in different fonts + $this->assertCount(count($this->characters), $factory); } } diff --git a/Structural/Flyweight/uml/Flyweight.uml b/Structural/Flyweight/uml/Flyweight.uml index 5d53b0f..61c78dd 100644 --- a/Structural/Flyweight/uml/Flyweight.uml +++ b/Structural/Flyweight/uml/Flyweight.uml @@ -1,25 +1,24 @@ PHP - \DesignPatterns\Structural\Flyweight\FlyweightFactory + \DesignPatterns\Structural\Flyweight\CharacterFlyweight - \DesignPatterns\Structural\Flyweight\CharacterFlyweight - \DesignPatterns\Structural\Flyweight\FlyweightFactory - \DesignPatterns\Structural\Flyweight\FlyweightInterface + \DesignPatterns\Structural\Flyweight\CharacterFlyweight + \DesignPatterns\Structural\Flyweight\FlyweightFactory + \DesignPatterns\Structural\Flyweight\FlyweightInterface - - + + - + Fields Constants - Constructors Methods private diff --git a/Structural/Flyweight/uml/uml.png b/Structural/Flyweight/uml/uml.png index 1209ab4..26af3e3 100644 Binary files a/Structural/Flyweight/uml/uml.png and b/Structural/Flyweight/uml/uml.png differ diff --git a/Structural/Flyweight/uml/uml.svg b/Structural/Flyweight/uml/uml.svg index b46aa2f..fc0d70b 100644 --- a/Structural/Flyweight/uml/uml.svg +++ b/Structural/Flyweight/uml/uml.svg @@ -1,466 +1,503 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + - - + + - + - - + + - - + + - - + + - - + + - - - - - - name + + - - + + - - + + - - - - - - __construct(name) + + + name - - + + - - + + - - - - - - draw(font) + + - - + + - - - + + + render(font) - - + + - - CharacterFlyweight + + + - - CharacterFlyweight + + - - + + CharacterFlyweight - - + + CharacterFlyweight - - + + - - - - - - name + + - - + + - - + + - - - - - - __construct(name) + + - - + + - - + + + name - - - - - - draw(font) + + - - + + - - - + + - - + + - - CharacterFlyweight + + - - CharacterFlyweight + + + render(font) - - + + - - + + + - - + + - - + + CharacterFlyweight - - - - - - pool + + CharacterFlyweight - - + + - - + + - - - - - - __get(name) + + - - + + - - - - - - totalNumber() + + - - + + - - - + + - - + + + pool - - FlyweightFactory + + - - FlyweightFactory + + - - + + - - + + - - + + + get(name) - - - - - - pool + + - - + + - - + + - - - - - - __get(name) + + + count() - - + + - - - - - - totalNumber() + + + - - + + - - - + + FlyweightFactory - - + + FlyweightFactory - - FlyweightFactory + + - - FlyweightFactory + + - - + + - - + + - - + + - - + + - - - - - - draw(extrinsicState) + + + pool - - + + - - - + + - - + + - - FlyweightInterface + + - - FlyweightInterface + + - - + + + get(name) - - + + - - + + - - - - - - draw(extrinsicState) + + - - + + - - - + + + count() - - + + - - FlyweightInterface + + + - - FlyweightInterface + + + + + FlyweightFactory + + + FlyweightFactory + + + + + + + + + + + + + + + + + + + + + + render(extrinsicState) + + + + + + + + + + + + + FlyweightInterface + + + FlyweightInterface + + + + + + + + + + + + + + + + + + + + + + render(extrinsicState) + + + + + + + + + + + + + FlyweightInterface + + + FlyweightInterface - + - + diff --git a/Structural/Proxy/Record.php b/Structural/Proxy/Record.php index 7ae4a3c..ea018b2 100644 --- a/Structural/Proxy/Record.php +++ b/Structural/Proxy/Record.php @@ -3,49 +3,38 @@ namespace DesignPatterns\Structural\Proxy; /** - * class Record. + * @property string username */ class Record { /** - * @var array|null + * @var string[] */ - protected $data; + private $data; /** - * @param null $data + * @param string[] $data */ - public function __construct($data = null) + public function __construct(array $data = []) { - $this->data = (array) $data; + $this->data = $data; } /** - * magic setter. - * * @param string $name - * @param mixed $value - * - * @return void + * @param string $value */ - public function __set($name, $value) + public function __set(string $name, string $value) { - $this->data[(string) $name] = $value; + $this->data[$name] = $value; } - /** - * magic getter. - * - * @param string $name - * - * @return mixed|null - */ - public function __get($name) + public function __get(string $name): string { - if (array_key_exists($name, $this->data)) { - return $this->data[(string) $name]; - } else { - return; + if (!isset($this->data[$name])) { + throw new \OutOfRangeException('Invalid name given'); } + + return $this->data[$name]; } } diff --git a/Structural/Proxy/RecordProxy.php b/Structural/Proxy/RecordProxy.php index f8c78a8..4d29ee8 100644 --- a/Structural/Proxy/RecordProxy.php +++ b/Structural/Proxy/RecordProxy.php @@ -2,25 +2,22 @@ namespace DesignPatterns\Structural\Proxy; -/** - * Class RecordProxy. - */ class RecordProxy extends Record { /** * @var bool */ - protected $isDirty = false; + private $isDirty = false; /** * @var bool */ - protected $isInitialized = false; + private $isInitialized = false; /** * @param array $data */ - public function __construct($data) + public function __construct(array $data) { parent::__construct($data); @@ -28,23 +25,25 @@ class RecordProxy extends Record // since Record will hold our business logic, we don't want to // implement this behaviour there, but instead in a new proxy class // that extends the Record class - if (null !== $data) { + if (count($data) > 0) { $this->isInitialized = true; $this->isDirty = true; } } /** - * magic setter. - * * @param string $name - * @param mixed $value - * - * @return void + * @param string $value */ - public function __set($name, $value) + public function __set(string $name, string $value) { $this->isDirty = true; + parent::__set($name, $value); } + + public function isDirty(): bool + { + return $this->isDirty; + } } diff --git a/Structural/Proxy/Tests/ProxyTest.php b/Structural/Proxy/Tests/ProxyTest.php new file mode 100644 index 0000000..efdf3f3 --- /dev/null +++ b/Structural/Proxy/Tests/ProxyTest.php @@ -0,0 +1,25 @@ +username = 'baz'; + + $this->assertTrue($recordProxy->isDirty()); + } + + public function testProxyIsInstanceOfRecord() + { + $recordProxy = new RecordProxy([]); + $recordProxy->username = 'baz'; + + $this->assertInstanceOf('DesignPatterns\Structural\Proxy\Record', $recordProxy); + } +} diff --git a/Structural/Registry/README.rst b/Structural/Registry/README.rst index 8a92604..1702c9b 100644 --- a/Structural/Registry/README.rst +++ b/Structural/Registry/README.rst @@ -6,7 +6,8 @@ Purpose To implement a central storage for objects often used throughout the application, is typically implemented using an abstract class with only -static methods (or using the Singleton pattern) +static methods (or using the Singleton pattern). Remember that this introduces +global state, which should be avoided at all times! Instead implement it using Dependency Injection! Examples -------- diff --git a/Structural/Registry/Registry.php b/Structural/Registry/Registry.php index e40d0c3..dccaf6f 100644 --- a/Structural/Registry/Registry.php +++ b/Structural/Registry/Registry.php @@ -2,46 +2,51 @@ namespace DesignPatterns\Structural\Registry; -/** - * class Registry. - */ abstract class Registry { const LOGGER = 'logger'; /** + * this introduces global state in your application which can not be mocked up for testing + * and is therefor considered an anti-pattern! Use dependency injection instead! + * * @var array */ - protected static $storedValues = array(); + private static $storedValues = []; + + /** + * @var array + */ + private static $allowedKeys = [ + self::LOGGER, + ]; /** - * sets a value. - * * @param string $key * @param mixed $value * - * @static - * * @return void */ - public static function set($key, $value) + public static function set(string $key, $value) { + if (!in_array($key, self::$allowedKeys)) { + throw new \InvalidArgumentException('Invalid key given'); + } + self::$storedValues[$key] = $value; } /** - * gets a value from the registry. - * * @param string $key * - * @static - * * @return mixed */ - public static function get($key) + public static function get(string $key) { + if (!in_array($key, self::$allowedKeys) || !isset(self::$storedValues[$key])) { + throw new \InvalidArgumentException('Invalid key given'); + } + return self::$storedValues[$key]; } - - // typically there would be methods to check if a key has already been registered and so on ... } diff --git a/Structural/Registry/Tests/RegistryTest.php b/Structural/Registry/Tests/RegistryTest.php index 9561abe..63053db 100644 --- a/Structural/Registry/Tests/RegistryTest.php +++ b/Structural/Registry/Tests/RegistryTest.php @@ -9,12 +9,33 @@ class RegistryTest extends \PHPUnit_Framework_TestCase public function testSetAndGetLogger() { $key = Registry::LOGGER; - $object = new \StdClass(); + $logger = new \stdClass(); - Registry::set($key, $object); - $actual = Registry::get($key); + Registry::set($key, $logger); + $storedLogger = Registry::get($key); - $this->assertEquals($object, $actual); - $this->assertInstanceOf('StdClass', $actual); + $this->assertSame($logger, $storedLogger); + $this->assertInstanceOf('stdClass', $storedLogger); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testThrowsExceptionWhenTryingToSetInvalidKey() + { + Registry::set('foobar', new \stdClass()); + } + + /** + * notice @runInSeparateProcess here: without it, a previous test might have set it already and + * testing would not be possible. That's why you should implement Dependency Injection where an + * injected class may easily be replaced by a mockup + * + * @runInSeparateProcess + * @expectedException \InvalidArgumentException + */ + public function testThrowsExceptionWhenTryingToGetNotSetKey() + { + Registry::get(Registry::LOGGER); } } diff --git a/ansible/roles/php/tasks/main.yml b/ansible/roles/php/tasks/main.yml index 38f2f78..b82c2f6 100755 --- a/ansible/roles/php/tasks/main.yml +++ b/ansible/roles/php/tasks/main.yml @@ -7,9 +7,9 @@ sudo: yes apt: update_cache=yes -- name: Install php5 +- name: Install php7 sudo: yes - apt: pkg=php5 state=latest + apt: pkg=php7.0 state=latest - name: Install PHP Packages sudo: yes diff --git a/ansible/vars/all.yml b/ansible/vars/all.yml index b5559e6..fe15f1e 100755 --- a/ansible/vars/all.yml +++ b/ansible/vars/all.yml @@ -9,7 +9,7 @@ vagrant_local: vm: { base_box: trusty64, hostname: design-patterns, ip: 192.168.11.2, sharedfolder: ./, enableWindows: '1', useVagrantCloud: '1', syncType: nfs } php: install: '1' - ppa: php5-5.6 - packages: [php5-cli, php5-intl, php5-mcrypt, php5-mysql, php5-curl, php5-json] + ppa: php + packages: [php7.0-cli, php7.0-intl, php7.0-mcrypt, php7.0-mysql, php7.0-curl, php7.0-json, php7.0-xml] xdebug: install: '1' diff --git a/composer.json b/composer.json index 3bac6dc..416f1cc 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,8 @@ ], "minimum-stability": "stable", "require": { - "php": ">=7.0" + "php": ">=7.0", + "psr/http-message": "^1.0" }, "require-dev": { "phpunit/phpunit": ">=5.5.4", diff --git a/composer.lock b/composer.lock index 2f6aac1..93cae18 100644 --- a/composer.lock +++ b/composer.lock @@ -4,9 +4,60 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "fc06bf31eb6cf4b21a7923e186abfb9d", - "content-hash": "c829a84cbe749d776139a3e9ebdd3094", - "packages": [], + "hash": "61109e150871360cd44739b2a6bea702", + "content-hash": "53848b7fc8e374b8e407799bb47d6917", + "packages": [ + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06 14:39:51" + } + ], "packages-dev": [ { "name": "doctrine/instantiator", @@ -558,24 +609,24 @@ }, { "name": "phpunit/phpunit", - "version": "5.5.4", + "version": "5.5.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3e6e88e56c912133de6e99b87728cca7ed70c5f5" + "reference": "a57126dc681b08289fef6ac96a48e30656f84350" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6e88e56c912133de6e99b87728cca7ed70c5f5", - "reference": "3e6e88e56c912133de6e99b87728cca7ed70c5f5", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a57126dc681b08289fef6ac96a48e30656f84350", + "reference": "a57126dc681b08289fef6ac96a48e30656f84350", "shasum": "" }, "require": { "ext-dom": "*", "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", "myclabs/deep-copy": "~1.3", "php": "^5.6 || ^7.0", "phpspec/prophecy": "^1.3.1", @@ -597,7 +648,12 @@ "conflict": { "phpdocumentor/reflection-docblock": "3.0.2" }, + "require-dev": { + "ext-pdo": "*" + }, "suggest": { + "ext-tidy": "*", + "ext-xdebug": "*", "phpunit/php-invoker": "~1.1" }, "bin": [ @@ -632,7 +688,7 @@ "testing", "xunit" ], - "time": "2016-08-26 07:11:44" + "time": "2016-09-21 14:40:13" }, { "name": "phpunit/phpunit-mock-objects", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f08bbb2..12ff946 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,5 @@ - Behavioral/*/Tests