mirror of
https://github.com/DesignPatternsPHP/DesignPatternsPHP.git
synced 2025-08-07 23:46:52 +02:00
start a restructure
This commit is contained in:
78
Behavioral/ChainOfResponsibilities/Handler.php
Normal file
78
Behavioral/ChainOfResponsibilities/Handler.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\ChainOfResponsibilities;
|
||||
|
||||
/**
|
||||
* Handler is a generic handler in the chain of responsibilities
|
||||
*
|
||||
* Yes you could have a lighter CoR with 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.
|
||||
*/
|
||||
abstract class Handler
|
||||
{
|
||||
/**
|
||||
* @var Handler
|
||||
*/
|
||||
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 it 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)
|
||||
{
|
||||
if (is_null($this->successor)) {
|
||||
$this->successor = $handler;
|
||||
} else {
|
||||
$this->successor->append($handler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the request.
|
||||
*
|
||||
* This approach by using a template method pattern ensures you that
|
||||
* each subclass will not forget to call the successor. Beside, the returned
|
||||
* boolean value indicates you if the request have been processed or not.
|
||||
*
|
||||
* @param Request $req
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
final public function handle(Request $req)
|
||||
{
|
||||
$req->forDebugOnly = get_called_class();
|
||||
$processed = $this->processing($req);
|
||||
if (!$processed) {
|
||||
// the request has not been processed by this handler => see the next
|
||||
if (!is_null($this->successor)) {
|
||||
$processed = $this->successor->handle($req);
|
||||
}
|
||||
}
|
||||
|
||||
return $processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Each concrete handler has to implement the processing of the request
|
||||
*
|
||||
* @param Request $req
|
||||
*
|
||||
* @return bool true if the request has been processed
|
||||
*/
|
||||
abstract protected function processing(Request $req);
|
||||
}
|
12
Behavioral/ChainOfResponsibilities/README.md
Normal file
12
Behavioral/ChainOfResponsibilities/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Chain Of Responsibilities
|
||||
|
||||
## Purpose:
|
||||
|
||||
To build a chain of objects to handle a call. If one object cannot handle a call, it delegates the call to the next in the chain and so forth.
|
||||
|
||||
## Examples:
|
||||
|
||||
* logging framework, where each chain element decides autonomously what to do with a log message
|
||||
* a Spam filter
|
||||
* Caching: first object is an instance of e.g. a Memcached Interface, if that "misses" it delegates the call to the database interface
|
||||
* Yii Framework: CFilterChain is a chain of controller action filters. the executing point is passed from one filter to the next along the chain, and only if all filters say "yes", the action can be invoked at last.
|
19
Behavioral/ChainOfResponsibilities/Request.php
Normal file
19
Behavioral/ChainOfResponsibilities/Request.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\ChainOfResponsibilities;
|
||||
|
||||
/**
|
||||
* Request is a request which goes through the chain of responsibilities.
|
||||
*
|
||||
* About the request : Sometimes, you don't need an object, just an integer or
|
||||
* an array. But in this case of a full example, I've made a class to illustrate
|
||||
* this important idea in the CoR (Chain of Responsibilities). In real world,
|
||||
* I recommend to always use a class, even a \stdClass if you want, it proves
|
||||
* to be more adaptive because a single handler doesn't know much about the
|
||||
* outside world and it is more difficult if, one day, you want to add some
|
||||
* criterion in a decision process.
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
// getter and setter but I don't want to generate too much noise in handlers
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
|
||||
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Request;
|
||||
|
||||
/**
|
||||
* Class FastStorage
|
||||
*/
|
||||
class FastStorage extends Handler
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct($data = array())
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
protected function processing(Request $req)
|
||||
{
|
||||
if ('get' === $req->verb) {
|
||||
if (array_key_exists($req->key, $this->data)) {
|
||||
// the handler IS responsible and then processes the request
|
||||
$req->response = $this->data[$req->key];
|
||||
// instead of returning true, I could return the value but it proves
|
||||
// to be a bad idea. What if the value IS "false" ?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
|
||||
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Request;
|
||||
|
||||
/**
|
||||
* This is mostly the same code as FastStorage but in fact, it may greatly differs
|
||||
*
|
||||
* One important fact about CoR : each item in the chain MUST NOT assume its position
|
||||
* in the chain. A CoR is not responsible if the request is not handled UNLESS
|
||||
* you make an "ExceptionHandler" which throws execption if the request goes there.
|
||||
*
|
||||
* To be really extendable, each handler doesn't know if there is something after him.
|
||||
*
|
||||
*/
|
||||
class SlowStorage extends Handler
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct($data = array())
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
protected function processing(Request $req)
|
||||
{
|
||||
if ('get' === $req->verb) {
|
||||
if (array_key_exists($req->key, $this->data)) {
|
||||
$req->response = $this->data[$req->key];
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
78
Behavioral/ChainOfResponsibilities/Tests/ChainTest.php
Normal file
78
Behavioral/ChainOfResponsibilities/Tests/ChainTest.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Tests\ChainOfResponsibilities;
|
||||
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Request;
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\FastStorage;
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowStorage;
|
||||
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
|
||||
|
||||
/**
|
||||
* ChainTest tests the CoR
|
||||
*/
|
||||
class ChainTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
||||
/**
|
||||
* @var FastStorage
|
||||
*/
|
||||
protected $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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider makeRequest
|
||||
*/
|
||||
public function testFastStorage($request)
|
||||
{
|
||||
$request->key = 'bar';
|
||||
$ret = $this->chain->handle($request);
|
||||
|
||||
$this->assertTrue($ret);
|
||||
$this->assertObjectHasAttribute('response', $request);
|
||||
$this->assertEquals('baz', $request->response);
|
||||
// despite both handle owns the 'bar' key, the FastStorage is responding first
|
||||
$this->assertEquals('DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\FastStorage', $request->forDebugOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider makeRequest
|
||||
*/
|
||||
public function testSlowStorage($request)
|
||||
{
|
||||
$request->key = 'foo';
|
||||
$ret = $this->chain->handle($request);
|
||||
|
||||
$this->assertTrue($ret);
|
||||
$this->assertObjectHasAttribute('response', $request);
|
||||
$this->assertEquals('bar', $request->response);
|
||||
// FastStorage has no 'foo' key, the SlowStorage is responding
|
||||
$this->assertEquals('DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowStorage', $request->forDebugOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider makeRequest
|
||||
*/
|
||||
public function testFailure($request)
|
||||
{
|
||||
$request->key = 'kurukuku';
|
||||
$ret = $this->chain->handle($request);
|
||||
|
||||
$this->assertFalse($ret);
|
||||
// the last responsible :
|
||||
$this->assertEquals('DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowStorage', $request->forDebugOnly);
|
||||
}
|
||||
}
|
15
Behavioral/Command/CommandInterface.php
Normal file
15
Behavioral/Command/CommandInterface.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Command;
|
||||
|
||||
/**
|
||||
* class CommandInterface
|
||||
*/
|
||||
interface CommandInterface
|
||||
{
|
||||
/**
|
||||
* this is the most important method in the Command pattern,
|
||||
* The Receiver goes in the constructor.
|
||||
*/
|
||||
public function execute();
|
||||
}
|
36
Behavioral/Command/HelloCommand.php
Normal file
36
Behavioral/Command/HelloCommand.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Command;
|
||||
|
||||
/**
|
||||
* This concrete command calls "print" on the Receiver, but an external
|
||||
* invoker just know he can call "execute"
|
||||
*/
|
||||
class HelloCommand implements CommandInterface
|
||||
{
|
||||
/**
|
||||
* @var Receiver
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* Each concrete command is builded with different receivers.
|
||||
* Can be one, many, none or even other Command in parameters
|
||||
*
|
||||
* @param Receiver $console
|
||||
*/
|
||||
public function __construct(Receiver $console)
|
||||
{
|
||||
$this->output = $console;
|
||||
}
|
||||
|
||||
/**
|
||||
* execute and output "Hello World"
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
// sometimes, there is no receiver and this is the command which
|
||||
// does all the work
|
||||
$this->output->write('Hello World');
|
||||
}
|
||||
}
|
36
Behavioral/Command/Invoker.php
Normal file
36
Behavioral/Command/Invoker.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Command;
|
||||
|
||||
/**
|
||||
* Invoker is using the command given to it.
|
||||
* Example : an Application in SF2
|
||||
*/
|
||||
class Invoker
|
||||
{
|
||||
/**
|
||||
* @var CommandInterface
|
||||
*/
|
||||
protected $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...
|
||||
*
|
||||
* @param CommandInterface $cmd
|
||||
*/
|
||||
public function setCommand(CommandInterface $cmd)
|
||||
{
|
||||
$this->command = $cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* executes 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();
|
||||
}
|
||||
}
|
17
Behavioral/Command/README.md
Normal file
17
Behavioral/Command/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Command
|
||||
|
||||
## Purpose
|
||||
|
||||
To encapsulate invocation and decoupling.
|
||||
|
||||
We have an Invoker and a Receiver. This pattern uses a "Command" to delegate the method call against the Receiver and presents the same method "execute".
|
||||
Therefore, the Invoker just knows to call "execute" to process the Command of the client. The Receiver is decoupled from the Invoker.
|
||||
|
||||
The second aspect of this pattern is the undo(), which undoes the method execute().
|
||||
Command can also be aggregated to combine more complex commands with minimum copy-paste and relying on composition over inheritance.
|
||||
|
||||
## Examples
|
||||
|
||||
* A text editor : all events are Command which can be undone, stacked and saved.
|
||||
* Symfony2: SF2 Commands that can be run from the CLI are built with just the Command pattern in mind
|
||||
* big CLI tools use subcommands to distribute various tasks and pack them in "modules", each of these can be implemented with the Command pattern (e.g. vagrant)
|
17
Behavioral/Command/Receiver.php
Normal file
17
Behavioral/Command/Receiver.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Command;
|
||||
|
||||
/**
|
||||
* Receiver is specific service with its own contract and can be only concrete
|
||||
*/
|
||||
class Receiver
|
||||
{
|
||||
/**
|
||||
* @param string $str
|
||||
*/
|
||||
public function write($str)
|
||||
{
|
||||
echo $str;
|
||||
}
|
||||
}
|
31
Behavioral/Command/Test/CommandTest.php
Normal file
31
Behavioral/Command/Test/CommandTest.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Tests\Command;
|
||||
|
||||
use DesignPatterns\Command\Invoker;
|
||||
use DesignPatterns\Command\Receiver;
|
||||
use DesignPatterns\Command\HelloCommand;
|
||||
|
||||
/**
|
||||
* CommandTest has the role of the Client in the Command Pattern
|
||||
*/
|
||||
class CommandTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
||||
protected $invoker;
|
||||
protected $receiver;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
// this is the context of the application
|
||||
$this->invoker = new Invoker();
|
||||
$this->receiver = new Receiver();
|
||||
}
|
||||
|
||||
public function testInvocation()
|
||||
{
|
||||
$this->invoker->setCommand(new HelloCommand($this->receiver));
|
||||
$this->expectOutputString('Hello World');
|
||||
$this->invoker->run();
|
||||
}
|
||||
}
|
39
Behavioral/Iterator/File.php
Normal file
39
Behavioral/Iterator/File.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Iterator;
|
||||
|
||||
/**
|
||||
* class File
|
||||
*
|
||||
* THIS EXAMPLE ALSO APPLIES THE COMPOSITE PATTERN
|
||||
*/
|
||||
class File
|
||||
{
|
||||
/**
|
||||
* @var RowSet
|
||||
*/
|
||||
protected $rowSet;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $pathName;
|
||||
|
||||
/**
|
||||
* @param string $pathName
|
||||
*/
|
||||
public function __construct($pathName)
|
||||
{
|
||||
$this->rowSet = new Rowset($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* processes the rowSet
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
// this is the place to show how using an iterator, with foreach
|
||||
// See the CardGame.php file
|
||||
$this->rowSet->process();
|
||||
}
|
||||
}
|
13
Behavioral/Iterator/README.md
Normal file
13
Behavioral/Iterator/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Iterator
|
||||
|
||||
## Purpose
|
||||
|
||||
To make an object iterable and to make it appear like a collection of objects.
|
||||
|
||||
## Examples
|
||||
|
||||
* to process a file line by line by just running over all lines (which have an object representation) for a file (which of course is an object, too)
|
||||
|
||||
## Note
|
||||
|
||||
Standard PHP Library (SPL) defines an interface Iterator which is best suited for this! Often you would want to implement the Countable interface too, to allow `count($object)` on your iterable object
|
27
Behavioral/Iterator/Row.php
Normal file
27
Behavioral/Iterator/Row.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Iterator;
|
||||
|
||||
/**
|
||||
* Class Row
|
||||
*/
|
||||
class Row
|
||||
{
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
// do some fancy things here ...
|
||||
}
|
||||
}
|
100
Behavioral/Iterator/RowSet.php
Normal file
100
Behavioral/Iterator/RowSet.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Iterator;
|
||||
|
||||
/**
|
||||
* Class RowSet
|
||||
*/
|
||||
class RowSet implements \Iterator
|
||||
{
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $currentRow;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $file;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $lineNumber;
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
*/
|
||||
public function __construct($file)
|
||||
{
|
||||
$this->file = $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* composite pattern: run through all rows and process them
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
// this actually calls rewind(), { next(), valid(), key() and current() :}
|
||||
/**
|
||||
* THE key feature of the Iterator Pattern is to provide a *public contract*
|
||||
* to iterate on a collection without knowing how items are handled inside
|
||||
* the collection. It is not just an easy way to use "foreach"
|
||||
*
|
||||
* One cannot see the point of iterator pattern if you iterate on $this.
|
||||
* This example is unclear and mixed with some Composite pattern ideas.
|
||||
*/
|
||||
foreach ($this as $line => $row) {
|
||||
$row->process();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
// seek to first line from $this->file
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
// read the next line from $this->file
|
||||
if (!$eof) {
|
||||
$data = ''; // get the line
|
||||
$this->currentRow = new Row($data);
|
||||
} else {
|
||||
$this->currentRow = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return $this->currentRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return null !== $this->currentRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
// you would want to increment this in next() or whatsoever
|
||||
return $this->lineNumber;
|
||||
}
|
||||
}
|
32
Behavioral/Mediator/Colleague.php
Normal file
32
Behavioral/Mediator/Colleague.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Mediator;
|
||||
|
||||
/**
|
||||
* Colleague is an abstract colleague who works together but he only knows
|
||||
* the Mediator, not other colleague.
|
||||
*/
|
||||
abstract class Colleague
|
||||
{
|
||||
/**
|
||||
* this ensures no change in subclasses
|
||||
*
|
||||
* @var MediatorInterface
|
||||
*/
|
||||
private $mediator;
|
||||
|
||||
// for subclasses
|
||||
protected function getMediator()
|
||||
{
|
||||
return $this->mediator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param MediatorInterface $medium
|
||||
*/
|
||||
public function __construct(MediatorInterface $medium)
|
||||
{
|
||||
// in this way, we are sure the concrete colleague knows the mediator
|
||||
$this->mediator = $medium;
|
||||
}
|
||||
}
|
59
Behavioral/Mediator/Mediator.php
Normal file
59
Behavioral/Mediator/Mediator.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Mediator;
|
||||
|
||||
use DesignPatterns\Mediator\Subsystem;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
|
||||
// you could have an array
|
||||
protected $server;
|
||||
protected $database;
|
||||
protected $client;
|
||||
|
||||
/**
|
||||
* @param Subsystem\Database $db
|
||||
* @param Subsystem\Client $cl
|
||||
* @param Subsystem\Server $srv
|
||||
*/
|
||||
public function setColleague(Subsystem\Database $db, Subsystem\Client $cl, Subsystem\Server $srv)
|
||||
{
|
||||
$this->database = $db;
|
||||
$this->server = $srv;
|
||||
$this->client = $cl;
|
||||
}
|
||||
|
||||
/**
|
||||
* make request
|
||||
*/
|
||||
public function makeRequest()
|
||||
{
|
||||
$this->server->process();
|
||||
}
|
||||
|
||||
/**
|
||||
* query db
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function queryDb()
|
||||
{
|
||||
return $this->database->getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* send response
|
||||
*
|
||||
* @param string $content
|
||||
*/
|
||||
public function sendResponse($content)
|
||||
{
|
||||
$this->client->output($content);
|
||||
}
|
||||
}
|
27
Behavioral/Mediator/MediatorInterface.php
Normal file
27
Behavioral/Mediator/MediatorInterface.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Mediator;
|
||||
|
||||
/**
|
||||
* MediatorInterface is a contract for the Mediator
|
||||
* This interface is not mandatory but it is better for LSP concerns
|
||||
*/
|
||||
interface MediatorInterface
|
||||
{
|
||||
/**
|
||||
* sends the response
|
||||
*
|
||||
* @param string $content
|
||||
*/
|
||||
public function sendResponse($content);
|
||||
|
||||
/**
|
||||
* makes a request
|
||||
*/
|
||||
public function makeRequest();
|
||||
|
||||
/**
|
||||
* queries the DB
|
||||
*/
|
||||
public function queryDb();
|
||||
}
|
11
Behavioral/Mediator/README.md
Normal file
11
Behavioral/Mediator/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Mediator
|
||||
|
||||
## Purpose
|
||||
|
||||
This pattern provides an easy to decouple many components working together.
|
||||
It is a good alternative over Observer IF you have a "central intelligence",
|
||||
like a controller (but not in the sense of the MVC).
|
||||
|
||||
All components (called Colleague) are only coupled to the MediatorInterface and
|
||||
it is a good thing because in OOP, one good friend is better than many. This
|
||||
is the key-feature of this pattern.
|
29
Behavioral/Mediator/Subsystem/Client.php
Normal file
29
Behavioral/Mediator/Subsystem/Client.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Mediator\Subsystem;
|
||||
|
||||
use DesignPatterns\Mediator\Colleague;
|
||||
|
||||
/**
|
||||
* Client is a client that make request et get response
|
||||
*/
|
||||
class Client extends Colleague
|
||||
{
|
||||
/**
|
||||
* request
|
||||
*/
|
||||
public function request()
|
||||
{
|
||||
$this->getMediator()->makeRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* output content
|
||||
*
|
||||
* @param string $content
|
||||
*/
|
||||
public function output($content)
|
||||
{
|
||||
echo $content;
|
||||
}
|
||||
}
|
19
Behavioral/Mediator/Subsystem/Database.php
Normal file
19
Behavioral/Mediator/Subsystem/Database.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Mediator\Subsystem;
|
||||
|
||||
use DesignPatterns\Mediator\Colleague;
|
||||
|
||||
/**
|
||||
* Database is a database service
|
||||
*/
|
||||
class Database extends Colleague
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return "World";
|
||||
}
|
||||
}
|
20
Behavioral/Mediator/Subsystem/Server.php
Normal file
20
Behavioral/Mediator/Subsystem/Server.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Mediator\Subsystem;
|
||||
|
||||
use DesignPatterns\Mediator\Colleague;
|
||||
|
||||
/**
|
||||
* Server serves responses
|
||||
*/
|
||||
class Server extends Colleague
|
||||
{
|
||||
/**
|
||||
* process on server
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
$data = $this->getMediator()->queryDb();
|
||||
$this->getMediator()->sendResponse("Hello $data");
|
||||
}
|
||||
}
|
34
Behavioral/Mediator/Test/MediatorTest.php
Normal file
34
Behavioral/Mediator/Test/MediatorTest.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Tests\Mediator;
|
||||
|
||||
use DesignPatterns\Mediator\Mediator;
|
||||
use DesignPatterns\Mediator\Subsystem\Database;
|
||||
use DesignPatterns\Mediator\Subsystem\Client;
|
||||
use DesignPatterns\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 :
|
||||
$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.
|
||||
}
|
||||
}
|
18
Behavioral/NullObject/LoggerInterface.php
Normal file
18
Behavioral/NullObject/LoggerInterface.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\NullObject;
|
||||
|
||||
/**
|
||||
* LoggerInterface is a contract for logging something
|
||||
*
|
||||
* Key feature: NullLogger MUST inherit from this interface like any other Loggers
|
||||
*/
|
||||
interface LoggerInterface
|
||||
{
|
||||
/**
|
||||
* @param string $str
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function log($str);
|
||||
}
|
21
Behavioral/NullObject/NullLogger.php
Normal file
21
Behavioral/NullObject/NullLogger.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\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)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
}
|
17
Behavioral/NullObject/PrintLogger.php
Normal file
17
Behavioral/NullObject/PrintLogger.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\NullObject;
|
||||
|
||||
/**
|
||||
* PrintLogger is a logger that prints the log entry to standard output
|
||||
*/
|
||||
class PrintLogger implements LoggerInterface
|
||||
{
|
||||
/**
|
||||
* @param string $str
|
||||
*/
|
||||
public function log($str)
|
||||
{
|
||||
echo $str;
|
||||
}
|
||||
}
|
20
Behavioral/NullObject/README.md
Normal file
20
Behavioral/NullObject/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Null Object
|
||||
|
||||
## Purpose
|
||||
|
||||
NullOutput is a example of NullObject pattern. It is not formally a Design Pattern by the GoF but it's a schema which appears frequently enough to
|
||||
be a pattern. Furthermore it is a really good pattern in my opinion:
|
||||
|
||||
* the code in the client is simple
|
||||
* it reduces the chance of null pointer exception
|
||||
* less "if" => less test cases
|
||||
|
||||
Every time you have a method which returns an object or null, you should return an object or a `NullObject`. With NullObject, you don't need
|
||||
a statement like `if (!is_null($obj)) { $obj->callSomething(); }` anymore.
|
||||
|
||||
## Examples
|
||||
|
||||
* Symfony2: null logger of profiler
|
||||
* Symfony2: null output in Symfony/Console
|
||||
* null handler in a Chain of Responsibilities pattern
|
||||
* null command in a Command pattern
|
34
Behavioral/NullObject/Service.php
Normal file
34
Behavioral/NullObject/Service.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\NullObject;
|
||||
|
||||
/**
|
||||
* Service is dummy service that uses a logger
|
||||
*/
|
||||
class Service
|
||||
{
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* we inject the logger in ctor and it is mandatory
|
||||
*
|
||||
* @param LoggerInterface $log
|
||||
*/
|
||||
public function __construct(LoggerInterface $log)
|
||||
{
|
||||
$this->logger = $log;
|
||||
}
|
||||
|
||||
/**
|
||||
* do something ...
|
||||
*/
|
||||
public function doSomething()
|
||||
{
|
||||
// no more check "if (!is_null($this->logger))..." with the NullObject pattern
|
||||
$this->logger->log('We are in ' . __METHOD__);
|
||||
// something to do...
|
||||
}
|
||||
}
|
30
Behavioral/NullObject/Test/LoggerTest.php
Normal file
30
Behavioral/NullObject/Test/LoggerTest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Tests\NullObject;
|
||||
|
||||
use DesignPatterns\NullObject\NullLogger;
|
||||
use DesignPatterns\NullObject\Service;
|
||||
use DesignPatterns\NullObject\PrintLogger;
|
||||
|
||||
/**
|
||||
* 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
|
||||
$service->doSomething();
|
||||
}
|
||||
|
||||
public function testStandardLogger()
|
||||
{
|
||||
$service = new Service(new PrintLogger());
|
||||
$this->expectOutputString('We are in DesignPatterns\NullObject\Service::doSomething');
|
||||
$service->doSomething();
|
||||
}
|
||||
}
|
14
Behavioral/Observer/README.md
Normal file
14
Behavioral/Observer/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Observer
|
||||
|
||||
## Purpose
|
||||
|
||||
To implement a publish/subscribe behaviour to an object, whenever a "Subject" object changes it's state, the attached
|
||||
"Observers" will be notified. It is used to shorten the amount of coupled objects and uses loose coupling instead.
|
||||
|
||||
## Examples
|
||||
|
||||
* a message queue system is observed to show the progress of a job in a GUI
|
||||
|
||||
## Note
|
||||
|
||||
PHP already defines two interfaces that can help to implement this pattern: SplObserver and SplSubject.
|
62
Behavioral/Observer/Test/ObserverTest.php
Normal file
62
Behavioral/Observer/Test/ObserverTest.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Tests\Observer;
|
||||
|
||||
use DesignPatterns\Observer\UserObserver;
|
||||
use DesignPatterns\Observer\User;
|
||||
|
||||
/**
|
||||
* ObserverTest tests the Observer pattern
|
||||
*/
|
||||
class ObserverTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
||||
protected $observer;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->observer = new UserObserver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the notification
|
||||
*/
|
||||
public function testNotify()
|
||||
{
|
||||
$this->expectOutputString('DesignPatterns\Observer\User has been updated');
|
||||
$subject = new User();
|
||||
|
||||
$subject->attach($this->observer);
|
||||
$subject->property = 123;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the subscribing
|
||||
*/
|
||||
public function testAttachDetach()
|
||||
{
|
||||
$subject = new User();
|
||||
$this->assertAttributeEmpty('observers', $subject);
|
||||
$subject->attach($this->observer);
|
||||
$this->assertAttributeNotEmpty('observers', $subject);
|
||||
$subject->detach($this->observer);
|
||||
$this->assertAttributeEmpty('observers', $subject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
84
Behavioral/Observer/User.php
Normal file
84
Behavioral/Observer/User.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Observer;
|
||||
|
||||
/**
|
||||
* Observer pattern : The observed object (the subject)
|
||||
*
|
||||
* The subject maintains a list of Observers and sends notifications.
|
||||
*
|
||||
*/
|
||||
class User implements \SplSubject
|
||||
{
|
||||
/**
|
||||
* user data
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* observers
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $observers = array();
|
||||
|
||||
/**
|
||||
* attach a new observer
|
||||
*
|
||||
* @param \SplObserver $observer
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function attach(\SplObserver $observer)
|
||||
{
|
||||
$this->observers[] = $observer;
|
||||
}
|
||||
|
||||
/**
|
||||
* detach an observer
|
||||
*
|
||||
* @param \SplObserver $observer
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function detach(\SplObserver $observer)
|
||||
{
|
||||
$index = array_search($observer, $this->observers);
|
||||
|
||||
if (false !== $index) {
|
||||
unset($this->observers[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* notify observers
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function notify()
|
||||
{
|
||||
/** @var SplObserver $observer */
|
||||
foreach ($this->observers as $observer) {
|
||||
$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();
|
||||
}
|
||||
}
|
20
Behavioral/Observer/UserObserver.php
Normal file
20
Behavioral/Observer/UserObserver.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\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() )
|
||||
*
|
||||
* @param \SplSubject $subject
|
||||
*/
|
||||
public function update(\SplSubject $subject)
|
||||
{
|
||||
echo get_class($subject) . ' has been updated';
|
||||
}
|
||||
}
|
51
Behavioral/Specification/AbstractSpecification.php
Normal file
51
Behavioral/Specification/AbstractSpecification.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
namespace DesignPatterns\Specification;
|
||||
|
||||
/**
|
||||
* An abstract specification allows the creation of wrapped specifications
|
||||
*/
|
||||
abstract class AbstractSpecification implements SpecificationInterface
|
||||
{
|
||||
/**
|
||||
* Checks if given item meets all criteria
|
||||
*
|
||||
* @param Item $item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function isSatisfiedBy(Item $item);
|
||||
|
||||
/**
|
||||
* Creates a new logical AND specification
|
||||
*
|
||||
* @param SpecificationInterface $spec
|
||||
*
|
||||
* @return SpecificationInterface
|
||||
*/
|
||||
public function plus(SpecificationInterface $spec)
|
||||
{
|
||||
return new Plus($this, $spec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new logical OR composite specification
|
||||
*
|
||||
* @param SpecificationInterface $spec
|
||||
*
|
||||
* @return SpecificationInterface
|
||||
*/
|
||||
public function either(SpecificationInterface $spec)
|
||||
{
|
||||
return new Either($this, $spec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new logical NOT specification
|
||||
*
|
||||
* @return SpecificationInterface
|
||||
*/
|
||||
public function not()
|
||||
{
|
||||
return new Not($this);
|
||||
}
|
||||
}
|
37
Behavioral/Specification/Either.php
Normal file
37
Behavioral/Specification/Either.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace DesignPatterns\Specification;
|
||||
|
||||
/**
|
||||
* A logical OR specification
|
||||
*/
|
||||
class Either extends AbstractSpecification
|
||||
{
|
||||
|
||||
protected $left;
|
||||
protected $right;
|
||||
|
||||
/**
|
||||
* A composite wrapper of two specifications
|
||||
*
|
||||
* @param SpecificationInterface $left
|
||||
* @param SpecificationInterface $right
|
||||
|
||||
*/
|
||||
public function __construct(SpecificationInterface $left, SpecificationInterface $right)
|
||||
{
|
||||
$this->left = $left;
|
||||
$this->right = $right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the evaluation of both wrapped specifications as a logical OR
|
||||
*
|
||||
* @param Item $item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSatisfiedBy(Item $item)
|
||||
{
|
||||
return $this->left->isSatisfiedBy($item) || $this->right->isSatisfiedBy($item);
|
||||
}
|
||||
}
|
31
Behavioral/Specification/Item.php
Normal file
31
Behavioral/Specification/Item.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
namespace DesignPatterns\Specification;
|
||||
|
||||
/**
|
||||
* An trivial item
|
||||
*/
|
||||
class Item
|
||||
{
|
||||
protected $price;
|
||||
|
||||
/**
|
||||
* An item must have a price
|
||||
*
|
||||
* @param int $price
|
||||
|
||||
*/
|
||||
public function __construct($price)
|
||||
{
|
||||
$this->price = $price;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the items price
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPrice()
|
||||
{
|
||||
return $this->price;
|
||||
}
|
||||
}
|
34
Behavioral/Specification/Not.php
Normal file
34
Behavioral/Specification/Not.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace DesignPatterns\Specification;
|
||||
|
||||
/**
|
||||
* A logical Not specification
|
||||
*/
|
||||
class Not extends AbstractSpecification
|
||||
{
|
||||
|
||||
protected $spec;
|
||||
|
||||
/**
|
||||
* Creates a new specification wrapping another
|
||||
*
|
||||
* @param SpecificationInterface $spec
|
||||
|
||||
*/
|
||||
public function __construct(SpecificationInterface $spec)
|
||||
{
|
||||
$this->spec = $spec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the negated result of the wrapped specification
|
||||
*
|
||||
* @param Item $item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSatisfiedBy(Item $item)
|
||||
{
|
||||
return !$this->spec->isSatisfiedBy($item);
|
||||
}
|
||||
}
|
37
Behavioral/Specification/Plus.php
Normal file
37
Behavioral/Specification/Plus.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace DesignPatterns\Specification;
|
||||
|
||||
/**
|
||||
* A logical AND specification
|
||||
*/
|
||||
class Plus extends AbstractSpecification
|
||||
{
|
||||
|
||||
protected $left;
|
||||
protected $right;
|
||||
|
||||
/**
|
||||
* Creation of a locical AND of two specifications
|
||||
*
|
||||
* @param SpecificationInterface $left
|
||||
* @param SpecificationInterface $right
|
||||
|
||||
*/
|
||||
public function __construct(SpecificationInterface $left, SpecificationInterface $right)
|
||||
{
|
||||
$this->left = $left;
|
||||
$this->right = $right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the composite AND of specifications passes
|
||||
*
|
||||
* @param Item $item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSatisfiedBy(Item $item)
|
||||
{
|
||||
return $this->left->isSatisfiedBy($item) && $this->right->isSatisfiedBy($item);
|
||||
}
|
||||
}
|
50
Behavioral/Specification/PriceSpecification.php
Normal file
50
Behavioral/Specification/PriceSpecification.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
namespace DesignPatterns\Specification;
|
||||
|
||||
/**
|
||||
* A specification to check an Item is priced between min and max
|
||||
*/
|
||||
class PriceSpecification extends AbstractSpecification
|
||||
{
|
||||
protected $maxPrice;
|
||||
protected $minPrice;
|
||||
|
||||
/**
|
||||
* Sets the optional maximum price
|
||||
*
|
||||
* @param int $maxPrice
|
||||
*/
|
||||
public function setMaxPrice($maxPrice)
|
||||
{
|
||||
$this->maxPrice = $maxPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional minimum price
|
||||
*
|
||||
* @param int $minPrice
|
||||
*/
|
||||
public function setMinPrice($minPrice)
|
||||
{
|
||||
$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) {
|
||||
return false;
|
||||
}
|
||||
if ( !empty($this->minPrice) && $item->getPrice() < $this->minPrice) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
7
Behavioral/Specification/README.md
Normal file
7
Behavioral/Specification/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Specification
|
||||
|
||||
## Purpose
|
||||
|
||||
Builds a clear specification of business rules, where objects can be checked against. The composite specification class has
|
||||
one method called `isSatisfiedBy` that returns either true or false depending on whether the given object satisfies the specification.
|
||||
|
38
Behavioral/Specification/SpecificationInterface.php
Normal file
38
Behavioral/Specification/SpecificationInterface.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace DesignPatterns\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();
|
||||
}
|
103
Behavioral/Specification/Test/SpecificationTest.php
Normal file
103
Behavioral/Specification/Test/SpecificationTest.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Tests\Specification;
|
||||
|
||||
use DesignPatterns\Specification\PriceSpecification;
|
||||
use DesignPatterns\Specification\Item;
|
||||
|
||||
/**
|
||||
* SpecificationTest tests the specification pattern
|
||||
*/
|
||||
class SpecificationTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testSimpleSpecification()
|
||||
{
|
||||
$item = new Item(100);
|
||||
$spec = new PriceSpecification();
|
||||
|
||||
$this->assertTrue($spec->isSatisfiedBy($item));
|
||||
|
||||
$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));
|
||||
}
|
||||
|
||||
public function testNotSpecification()
|
||||
{
|
||||
$item = new Item(100);
|
||||
$spec = new PriceSpecification();
|
||||
$not = $spec->not();
|
||||
|
||||
$this->assertFalse($not->isSatisfiedBy($item));
|
||||
|
||||
$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));
|
||||
}
|
||||
|
||||
public function testPlusSpecification()
|
||||
{
|
||||
$spec1 = new PriceSpecification();
|
||||
$spec2 = new PriceSpecification();
|
||||
$plus = $spec1->plus($spec2);
|
||||
|
||||
$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));
|
||||
}
|
||||
}
|
49
Behavioral/State/CreateOrder.php
Normal file
49
Behavioral/State/CreateOrder.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Status;
|
||||
|
||||
/**
|
||||
* Class CreateOrder
|
||||
*/
|
||||
class CreateOrder implements OrderInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $order;
|
||||
|
||||
/**
|
||||
* @param array $order
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(array $order)
|
||||
{
|
||||
if (empty($order)) {
|
||||
throw new \Exception('Order can not be empty!');
|
||||
}
|
||||
$this->order = $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function shipOrder()
|
||||
{
|
||||
$this->order['status'] = 'shipping';
|
||||
$this->order['updatedTime'] = time();
|
||||
|
||||
// Setting the new order status into database;
|
||||
return $this->updateOrder($order);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed|void
|
||||
* @throws \Exception
|
||||
*/
|
||||
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!');
|
||||
}
|
||||
}
|
37
Behavioral/State/OrderController.php
Normal file
37
Behavioral/State/OrderController.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Status;
|
||||
|
||||
/**
|
||||
* Class OrderController
|
||||
*/
|
||||
class OrderController
|
||||
{
|
||||
/**
|
||||
* @param int $id
|
||||
*/
|
||||
public function shipAction($id)
|
||||
{
|
||||
$order = OrderFactory::getOrder($id);
|
||||
try {
|
||||
$order->shipOrder();
|
||||
} catch (Exception $e) {
|
||||
//handle error!
|
||||
}
|
||||
// response to browser
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*/
|
||||
public function completeAction($id)
|
||||
{
|
||||
$order = OrderFactory::getOrder($id);
|
||||
try {
|
||||
$order->completeOrder();
|
||||
} catch (Exception $e) {
|
||||
//handle error!
|
||||
}
|
||||
// response to browser
|
||||
}
|
||||
}
|
35
Behavioral/State/OrderFactory.php
Normal file
35
Behavioral/State/OrderFactory.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Status;
|
||||
|
||||
/**
|
||||
* Class OrderFactory
|
||||
*/
|
||||
class OrderFactory
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
throw Exception('Can not instance the OrderFactory class!');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*
|
||||
* @return CreateOrder|ShippingOrder
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getOrder($id)
|
||||
{
|
||||
$order = 'Get Order From Database';
|
||||
|
||||
switch ($order['status']) {
|
||||
case 'created':
|
||||
return new CreateOrder($order);
|
||||
case 'shipping':
|
||||
return new ShippingOrder($order);
|
||||
default:
|
||||
throw new \Exception('Order status error!');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
19
Behavioral/State/OrderInterface.php
Normal file
19
Behavioral/State/OrderInterface.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Status;
|
||||
|
||||
/**
|
||||
* Class OrderInterface
|
||||
*/
|
||||
interface OrderInterface
|
||||
{
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function shipOrder();
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function completeOrder();
|
||||
}
|
5
Behavioral/State/README.md
Normal file
5
Behavioral/State/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# State
|
||||
|
||||
## Purpose
|
||||
|
||||
Encapsulate varying behavior for the same routine based on an object's state. This can be a cleaner way for an object to change its behavior at runtime without resorting to large monolithic conditional statements.
|
49
Behavioral/State/ShippingOrder.php
Normal file
49
Behavioral/State/ShippingOrder.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Status;
|
||||
|
||||
/**
|
||||
* Class ShippingOrder
|
||||
*/
|
||||
class ShippingOrder implements OrderInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $order;
|
||||
|
||||
/**
|
||||
* @param array $order
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(array $order)
|
||||
{
|
||||
if (empty($order)) {
|
||||
throw new \Exception('Order can not be empty!');
|
||||
}
|
||||
$this->order = $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed|void
|
||||
* @throws \Exception
|
||||
*/
|
||||
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();
|
||||
|
||||
// Setting the new order status into database;
|
||||
return $this->updateOrder($order);
|
||||
}
|
||||
}
|
17
Behavioral/Strategy/ComparatorInterface.php
Normal file
17
Behavioral/Strategy/ComparatorInterface.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Strategy;
|
||||
|
||||
/**
|
||||
* Class ComparatorInterface
|
||||
*/
|
||||
interface ComparatorInterface
|
||||
{
|
||||
/**
|
||||
* @param mixed $a
|
||||
* @param mixed $b
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function compare($a, $b);
|
||||
}
|
24
Behavioral/Strategy/DateComparator.php
Normal file
24
Behavioral/Strategy/DateComparator.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Strategy;
|
||||
|
||||
/**
|
||||
* Class DateComparator
|
||||
*/
|
||||
class DateComparator implements ComparatorInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function compare($a, $b)
|
||||
{
|
||||
$aDate = strtotime($a['date']);
|
||||
$bDate = strtotime($b['date']);
|
||||
|
||||
if ($aDate == $bDate) {
|
||||
return 0;
|
||||
} else {
|
||||
return $aDate < $bDate ? -1 : 1;
|
||||
}
|
||||
}
|
||||
}
|
21
Behavioral/Strategy/IdComparator.php
Normal file
21
Behavioral/Strategy/IdComparator.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Strategy;
|
||||
|
||||
/**
|
||||
* Class IdComparator
|
||||
*/
|
||||
class IdComparator implements ComparatorInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function compare($a, $b)
|
||||
{
|
||||
if ($a['id'] == $b['id']) {
|
||||
return 0;
|
||||
} else {
|
||||
return $a['id'] < $b['id'] ? -1 : 1;
|
||||
}
|
||||
}
|
||||
}
|
52
Behavioral/Strategy/ObjectCollection.php
Normal file
52
Behavioral/Strategy/ObjectCollection.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Strategy;
|
||||
|
||||
/**
|
||||
* Class ObjectCollection
|
||||
*/
|
||||
class ObjectCollection
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $elements;
|
||||
|
||||
/**
|
||||
* @var ComparatorInterface
|
||||
*/
|
||||
private $comparator;
|
||||
|
||||
/**
|
||||
* @param array $elements
|
||||
*/
|
||||
public function __construct(array $elements = array())
|
||||
{
|
||||
$this->elements = $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function sort()
|
||||
{
|
||||
if (!$this->comparator){
|
||||
throw new \LogicException("Comparator is not set");
|
||||
}
|
||||
|
||||
$callback = array($this->comparator, 'compare');
|
||||
uasort($this->elements, $callback);
|
||||
|
||||
return $this->elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ComparatorInterface $comparator
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setComparator(ComparatorInterface $comparator)
|
||||
{
|
||||
$this->comparator = $comparator;
|
||||
}
|
||||
}
|
16
Behavioral/Strategy/README.md
Normal file
16
Behavioral/Strategy/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Strategy
|
||||
|
||||
## Terminology:
|
||||
|
||||
* Context
|
||||
* Strategy
|
||||
* Concrete Strategy
|
||||
|
||||
## Purpose
|
||||
|
||||
To separate strategies and to enable fast switching between them. Also this pattern is a good alternative to inheritance (instead of having an abstract class that is extended).
|
||||
|
||||
## Examples
|
||||
|
||||
* sorting a list of objects, one strategy by date, the other by id
|
||||
* simplify unit testing: e.g. switching between file and in-memory storage
|
21
Behavioral/Strategy/index.php
Normal file
21
Behavioral/Strategy/index.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Strategy;
|
||||
|
||||
$elements = array(
|
||||
array(
|
||||
'id' => 2,
|
||||
'date' => '2011-01-01',
|
||||
),
|
||||
array(
|
||||
'id' => 1,
|
||||
'date' => '2011-02-01'
|
||||
)
|
||||
);
|
||||
|
||||
$collection = new ObjectCollection($elements);
|
||||
$collection->setComparator(new IdComparator());
|
||||
$collection->sort();
|
||||
|
||||
$collection->setComparator(new DateComparator());
|
||||
$collection->sort();
|
17
Behavioral/TemplateMethod/BeachJourney.php
Normal file
17
Behavioral/TemplateMethod/BeachJourney.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\TemplateMethod;
|
||||
|
||||
/**
|
||||
* BeachJourney is vacation at the beach
|
||||
*/
|
||||
class BeachJourney extends Journey
|
||||
{
|
||||
/**
|
||||
* prints what to do to enjoy your vacation
|
||||
*/
|
||||
protected function enjoyVacation()
|
||||
{
|
||||
echo "Swimming and sun-bathing\n";
|
||||
}
|
||||
}
|
17
Behavioral/TemplateMethod/CityJourney.php
Normal file
17
Behavioral/TemplateMethod/CityJourney.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\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()
|
||||
{
|
||||
echo "Eat, drink, take photos and sleep\n";
|
||||
}
|
||||
}
|
60
Behavioral/TemplateMethod/Journey.php
Normal file
60
Behavioral/TemplateMethod/Journey.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\TemplateMethod;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
abstract class Journey
|
||||
{
|
||||
/**
|
||||
* This is the public service provided by this class and its subclasses.
|
||||
* Notice it is final to "freeze" the global behavior of algorithm.
|
||||
* If you want to override this contract, make an interface with only takeATrip()
|
||||
* and subclass it.
|
||||
*/
|
||||
final public function takeATrip()
|
||||
{
|
||||
$this->buyAFlight();
|
||||
$this->takePlane();
|
||||
$this->enjoyVacation();
|
||||
$this->buyGift();
|
||||
$this->takePlane();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method must be implemented, this is the key-feature of this pattern
|
||||
*/
|
||||
abstract protected function enjoyVacation();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected function buyGift()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* this method will be unknown by subclasses (better)
|
||||
*/
|
||||
private function buyAFlight()
|
||||
{
|
||||
echo "Buying a flight\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* sbclasses 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 !
|
||||
}
|
14
Behavioral/TemplateMethod/README.md
Normal file
14
Behavioral/TemplateMethod/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Template Method
|
||||
|
||||
## Purpose
|
||||
|
||||
Template Method is a behavioral design pattern.
|
||||
|
||||
Perhaps you have encountered it many times already. The idea is to let subclasses of this abstract template "finish" the behavior of an algorithm.
|
||||
|
||||
A.k.a the "Hollywood principle": "Don't call us, we call you." This class is not called by subclasses but the inverse.
|
||||
How? With abstraction of course.
|
||||
|
||||
In other words, this is a skeleton of algorithm, well-suited for framework libraries. The user has just to implement one method and the superclass do the job.
|
||||
|
||||
It is an easy way to decouple concrete classes and reduce copy-paste, that's why you'll find it everywhere.
|
45
Behavioral/TemplateMethod/Test/JourneyTest.php
Normal file
45
Behavioral/TemplateMethod/Test/JourneyTest.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Tests\TemplateMethod;
|
||||
|
||||
use DesignPatterns\TemplateMethod;
|
||||
|
||||
/**
|
||||
* JourneyTest tests all journeys
|
||||
*/
|
||||
class JourneyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
||||
public function testBeach()
|
||||
{
|
||||
$journey = new TemplateMethod\BeachJourney();
|
||||
$this->expectOutputRegex('#sun-bathing#');
|
||||
$journey->takeATrip();
|
||||
}
|
||||
|
||||
public function testCity()
|
||||
{
|
||||
$journey = new TemplateMethod\CityJourney();
|
||||
$this->expectOutputRegex('#drink#');
|
||||
$journey->takeATrip();
|
||||
}
|
||||
|
||||
/**
|
||||
* How to test an abstract template method with PHPUnit
|
||||
*/
|
||||
public function testLasVegas()
|
||||
{
|
||||
$journey = $this->getMockForAbstractClass('DesignPatterns\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";
|
||||
}
|
||||
|
||||
}
|
30
Behavioral/Visitor/Group.php
Normal file
30
Behavioral/Visitor/Group.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Visitor;
|
||||
|
||||
/**
|
||||
* An example of a Visitor: Group
|
||||
*/
|
||||
class Group extends Role
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct($name)
|
||||
{
|
||||
$this->name = (string) $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return "Group: " . $this->name;
|
||||
}
|
||||
}
|
8
Behavioral/Visitor/README.md
Normal file
8
Behavioral/Visitor/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Visitor
|
||||
|
||||
## Purpose
|
||||
|
||||
The Visitor Pattern lets you outsource operations on objects to other objects. The main reason to do this is to keep a separation of concerns.
|
||||
But classes have to define a contract to allow visitors (the `Role::accept` method in the example).
|
||||
|
||||
The contract is an abstract class but you can have also a clean interface. In that case, each Visitor has to choose itself which method to invoke on the visitor.
|
33
Behavioral/Visitor/Role.php
Normal file
33
Behavioral/Visitor/Role.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Visitor;
|
||||
|
||||
/**
|
||||
* class Role
|
||||
*/
|
||||
abstract class 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\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);
|
||||
}
|
||||
}
|
27
Behavioral/Visitor/RolePrintVisitor.php
Normal file
27
Behavioral/Visitor/RolePrintVisitor.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Visitor;
|
||||
|
||||
/**
|
||||
* Visitor Pattern
|
||||
*
|
||||
* An implementation of a concrete Visitor
|
||||
*/
|
||||
class RolePrintVisitor implements RoleVisitorInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function visitGroup(Group $role)
|
||||
{
|
||||
echo "Role: " . $role->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function visitUser(User $role)
|
||||
{
|
||||
echo "Role: " . $role->getName();
|
||||
}
|
||||
}
|
31
Behavioral/Visitor/RoleVisitorInterface.php
Normal file
31
Behavioral/Visitor/RoleVisitorInterface.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\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.
|
||||
*/
|
||||
interface RoleVisitorInterface
|
||||
{
|
||||
/**
|
||||
* Visit a User object
|
||||
*
|
||||
* @param \DesignPatterns\Visitor\User $role
|
||||
*/
|
||||
public function visitUser(User $role);
|
||||
|
||||
/**
|
||||
* Visit a Group object
|
||||
*
|
||||
* @param \DesignPatterns\Visitor\Group $role
|
||||
*/
|
||||
public function visitGroup(Group $role);
|
||||
}
|
47
Behavioral/Visitor/Test/VisitorTest.php
Normal file
47
Behavioral/Visitor/Test/VisitorTest.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Tests\Visitor;
|
||||
|
||||
use DesignPatterns\Visitor;
|
||||
|
||||
/**
|
||||
* VisitorTest tests the visitor pattern
|
||||
*/
|
||||
class VisitorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
||||
protected $visitor;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->visitor = new Visitor\RolePrintVisitor();
|
||||
}
|
||||
|
||||
public function getRole()
|
||||
{
|
||||
return array(
|
||||
array(new Visitor\User("Dominik"), 'Role: User Dominik'),
|
||||
array(new Visitor\Group("Administrators"), 'Role: Group: Administrators')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getRole
|
||||
*/
|
||||
public function testVisitSomeRole(Visitor\Role $role, $expect)
|
||||
{
|
||||
$this->expectOutputString($expect);
|
||||
$role->accept($this->visitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \InvalidArgumentException
|
||||
* @expectedExceptionMessage Mock
|
||||
*/
|
||||
public function testUnknownObject()
|
||||
{
|
||||
$mock = $this->getMockForAbstractClass('DesignPatterns\Visitor\Role');
|
||||
$mock->accept($this->visitor);
|
||||
}
|
||||
|
||||
}
|
32
Behavioral/Visitor/User.php
Normal file
32
Behavioral/Visitor/User.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Visitor;
|
||||
|
||||
/**
|
||||
* Visitor Pattern
|
||||
*
|
||||
* One example for a visitee. Each visitee has to extends Role
|
||||
*/
|
||||
class User extends Role
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct($name)
|
||||
{
|
||||
$this->name = (string) $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return "User " . $this->name;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user