Merge pull request #14 from Trismegiste/master

Major improvement
This commit is contained in:
Dominik Liebler 2013-06-30 15:30:01 +02:00
commit 21b904b7d5
97 changed files with 2629 additions and 237 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
# common
.idea
/nbproject

7
.travis.yml Normal file
View File

@ -0,0 +1,7 @@
language: php
php:
- 5.4
branches:
only:
- master

View File

@ -1,42 +1,43 @@
<?php
namespace DesignPatterns;
namespace DesignPatterns\AbstractFactory;
/**
* Abstract Factory pattern
*
* Purpose:
* to create series of related or dependant objects without specifying their concrete classes,
* usually the created classes all implement the same interface
*
* Examples:
* - A Factory to create media in a CMS: classes would be text, audio, video, picture
* - SQL Factory (types are all strings with SQL, but they vary in detail (tables, fields, etc.))
* - Zend Framework: Zend_Form::createElement() creates form elements, but you could also call new T
* TextElement() instead
* - an abstract factory to create various exceptions (e.g. Doctrine2 uses this method)
*
* usually the created classes all implement the same interface. The client of the abstract
* factory does not care about how these objects are created, he just knows they goes together.
*
* 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 is two ways
* of rendering : HTML or JSON.
*
* Therefore 4 concretes classes, but the client just need to know this contract
* to build a correct http response (for a html page or for an ajax request)
*/
abstract class AbstractFactory
{
/**
* @static
* Creates a text component
*
* @param string $content
* @return AbstractFactory\Text
* @return Text
*/
public static function createText($content)
{
return new AbstractFactory\Text($content);
}
abstract public function createText($content);
/**
* @static
* Createss a picture component
*
* @param string $path
* @param string $name
* @return AbstractFactory\Picture
* @return Picture
*/
public static function createPicture($path, $name = '')
{
return new AbstractFactory\Picture($path, $name);
}
abstract public function createPicture($path, $name = '');
}

View File

@ -0,0 +1,23 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\AbstractFactory\Html;
use DesignPatterns\AbstractFactory\Picture as BasePicture;
/**
* Picture is a concrete image for HTML rendering
*/
class Picture extends BasePicture
{
// some crude rendering from HTML output
public function render()
{
return sprintf('<img src="%s" title="$s"/>', $this->_path, $this->_name);
}
}

View File

@ -0,0 +1,22 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\AbstractFactory\Html;
use DesignPatterns\AbstractFactory\Text as BaseText;
/**
* Text is a concrete text for HTML rendering
*/
class Text extends BaseText
{
public function render()
{
return "<div>" . htmlspecialchars($this->_text) . '</div>';
}
}

View File

@ -0,0 +1,25 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\AbstractFactory;
/**
* HtmlFactory is a concrete factory for HTML component
*/
class HtmlFactory extends AbstractFactory
{
public function createPicture($path, $name = '')
{
return new Html\Picture($path, $name);
}
public function createText($content)
{
return new Html\Text($content);
}
}

View File

@ -0,0 +1,23 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\AbstractFactory\Json;
use DesignPatterns\AbstractFactory\Picture as BasePicture;
/**
* Picture is a concrete image for JSON rendering
*/
class Picture extends BasePicture
{
// some crude rendering from JSON output
public function render()
{
return json_encode(array('title' => $this->_name, 'path' => $this->_path));
}
}

View File

@ -0,0 +1,22 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\AbstractFactory\Json;
use DesignPatterns\AbstractFactory\Text as BaseText;
/**
* Text is a text component with a JSON rendering
*/
class Text extends BaseText
{
public function render()
{
return json_encode(array('content' => $this->_text));
}
}

View File

@ -0,0 +1,26 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\AbstractFactory;
/**
* JsonFactory is a factory for creating a family of JSON component
* (example for ajax)
*/
class JsonFactory extends AbstractFactory
{
public function createPicture($path, $name = '')
{
return new Json\Picture($path, $name);
}
public function createText($content)
{
return new Json\Text($content);
}
}

View File

@ -2,7 +2,12 @@
namespace DesignPatterns\AbstractFactory;
/**
* This contract is not part of the pattern, in general case, each component
* are not related
*/
interface Media
{
function render();
}

View File

@ -2,7 +2,7 @@
namespace DesignPatterns\AbstractFactory;
class Picture implements Media
abstract class Picture implements Media
{
protected $_path;
protected $_name;

View File

@ -2,7 +2,7 @@
namespace DesignPatterns\AbstractFactory;
class Text implements Media
abstract class Text implements Media
{
/**
*

View File

@ -1,37 +0,0 @@
<?php
namespace DesignPatterns;
/**
* adapter pattern
*
* Purpose:
* to link two systems, that have different interfaces. An adapter defines interfaces that are equal for all linked
* systems and wrap functionality
*
* Examples:
* - different databases have the same interface to communicate although the underlying systems act differently
*
* this is a VERY basic example which won't work at all!
*/
interface DatabaseAdapter
{
public function getTables();
}
class MySQL implements DatabaseAdapter
{
public function getTables()
{
return $this->select('SHOW TABLES');
}
}
class SQLite implements DatabaseAdapter
{
public function getTables()
{
return system('sqlite --list-tables');
}
}

25
Adapter/Book.php Normal file
View File

@ -0,0 +1,25 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Adapter;
/**
* Book is a concrete and standard paper book
*/
class Book implements PaperBookInterface
{
public function open()
{
}
public function turnPage()
{
}
}

View File

@ -0,0 +1,41 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Adapter;
/**
* ElecBookAdapter is an adapter to fit an e-book like a paper book
*
* This is the adapter here. Notice it implemennts PaperBookInterface,
* therefore you don't have to change the code of the client which using paper book.
*/
class ElecBookAdapter implements PaperBookInterface
{
protected $eBook;
/**
* Notice the constructor, it "wraps" an electronic book
*/
public function __construct(ElecBookInterface $ebook)
{
$this->eBook = $ebook;
}
/**
* This cass makes the proper translation from one interface to another
*/
public function open()
{
$this->eBook->pressStart();
}
public function turnPage()
{
$this->eBook->pressNext();
}
}

View File

@ -0,0 +1,18 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Adapter;
/**
* ElecBookInterface is a contract for an electronic book
*/
interface ElecBookInterface
{
function pressNext();
function pressStart();
}

27
Adapter/Kindle.php Normal file
View File

@ -0,0 +1,27 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Adapter;
/**
* Kindle is a concrete electronic book
*
* @author flo
*/
class Kindle implements ElecBookInterface
{
public function pressNext()
{
}
public function pressStart()
{
}
}

View File

@ -0,0 +1,18 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Adapter;
/**
* PaperBookInterface is a contract for a book
*/
interface PaperBookInterface
{
function turnPage();
function open();
}

43
Builder/BikeBuilder.php Normal file
View File

@ -0,0 +1,43 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Builder;
/**
* BikeBuilder builds bike
*/
class BikeBuilder implements Builder
{
protected $bike;
public function addDoors()
{
}
public function addEngine()
{
$this->bike->setPart('engine', new Parts\Engine());
}
public function addWheel()
{
$this->bike->setPart('forwardWheel', new Parts\Wheel());
$this->bike->setPart('rearWheel', new Parts\Wheel());
}
public function createVehicle()
{
$this->bike = new Parts\Bike();
}
public function getVehicle()
{
return $this->bike;
}
}

33
Builder/Builder.php Normal file
View File

@ -0,0 +1,33 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Builder;
/**
* Builder is an interface that build parts of a complex object.
*
* Sometime, if the builder has a better knowledge of what it builds, this
* interface could be an abstract class with default methods (aka adapter)
*
* If you have a complex inheritance tree for vehicles, it is logical to have
* a complex inheritance tree for builders too.
*
* Note: Builders have often a fluent interface, see the mock builder of
* PHPUnit for example.
*/
interface Builder
{
function createVehicle();
function addWheel();
function addEngine();
function addDoors();
function getVehicle();
}

46
Builder/CarBuilder.php Normal file
View File

@ -0,0 +1,46 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Builder;
/**
* CarBuilder builds car
*/
class CarBuilder implements Builder
{
protected $car;
public function addDoors()
{
$this->car->setPart('rightdoor', new Parts\Door());
$this->car->setPart('leftDoor', new Parts\Door());
}
public function addEngine()
{
$this->car->setPart('engine', new Parts\Engine());
}
public function addWheel()
{
$this->car->setPart('wheelLF', new Parts\Wheel());
$this->car->setPart('wheelRF', new Parts\Wheel());
$this->car->setPart('wheelLR', new Parts\Wheel());
$this->car->setPart('wheelRR', new Parts\Wheel());
}
public function createVehicle()
{
$this->car = new Parts\Car();
}
public function getVehicle()
{
return $this->car;
}
}

32
Builder/Director.php Normal file
View File

@ -0,0 +1,32 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Builder;
/**
* Director is part of the builder pattern. It knows the interface of the builder
* and build a complex object with the help of the builder.
* If you make it abstract, this becomes a "meta-factory-method" very powerfull.
*
* You can also inject many builders instead of one to build more complex objects
*/
class Director
{
/**
* The director don't know 'bout concrete part
*/
public function build(Builder $builder)
{
$builder->createVehicle();
$builder->addDoors();
$builder->addEngine();
$builder->addWheel();
return $builder->getVehicle();
}
}

15
Builder/Parts/Bike.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Builder\Parts;
/**
* Bike is a bike
*/
class Bike extends Vehicle
{
}

15
Builder/Parts/Car.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Builder\Parts;
/**
* Car is a car
*/
class Car extends Vehicle
{
}

8
Builder/Parts/Door.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace DesignPatterns\Builder\Parts;
class Door
{
}

8
Builder/Parts/Engine.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace DesignPatterns\Builder\Parts;
class Engine
{
}

22
Builder/Parts/Vehicle.php Normal file
View File

@ -0,0 +1,22 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Builder\Parts;
/**
* Vehicle is a contract for a vehicle
*/
abstract class Vehicle
{
protected $data;
public function setPart($key, $value)
{
$this->data[$key] = $value;
}
}

8
Builder/Parts/Wheel.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace DesignPatterns\Builder\Parts;
class Wheel
{
}

View File

@ -10,6 +10,8 @@ namespace DesignPatterns;
* in the chain and so forth
*
* Examples:
* - logging framework
* - spam filter
* - Caching: first object is an instance of e.g. a Memcached Interface, if that "misses" it delegates the call to the
* Database Interface
* - Yii Framework: CFilterChain is a chain of controller action filters. the executing point is passed from one filter
@ -17,6 +19,8 @@ namespace DesignPatterns;
*
*/
// the idea is good but in general, the Handler component in this pattern
// is an abstract class which makes much more of the work
interface KeyValueStorage
{
public function get($key);

View File

@ -0,0 +1,72 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\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
{
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 contructor 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 contructor, 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.
*/
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.
*/
final public function handle(Request $req)
{
$req->forDebugOnly = get_called_class();
$processed = $this->processing($req);
if (!$processed) {
// the request has not been processed by this handler => see the next
if (!is_null($this->successor)) {
$processed = $this->successor->handle($req);
}
}
return $processed;
}
/**
* Each concrete handler has to implement the processing of the request
*
* @return bool true if the request has been processed
*/
abstract protected function processing(Request $req);
}

View File

@ -0,0 +1,23 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\ChainOfResponsibilities;
/**
* Request is a request which goes throught 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 recommand to always use a class, even a \stdClass if you want, it proves
* to be more adaptative because a single handler doesn't know much about the
* outside world and it is more difficult if, one day, you want add some
* criterion in a decision process.
*/
class Request
{
// getter and setter but I don't want to generate to much noise in handlers
}

View File

@ -0,0 +1,37 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\ChainOfResponsibilities\Responsible;
use DesignPatterns\ChainOfResponsibilities\Handler;
use DesignPatterns\ChainOfResponsibilities\Request;
class FastStorage extends Handler
{
protected $_data = array();
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;
}
}

View File

@ -0,0 +1,44 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\ChainOfResponsibilities\Responsible;
use DesignPatterns\ChainOfResponsibilities\Handler;
use DesignPatterns\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
{
protected $_data = array();
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;
}
}

View File

@ -1,40 +1,34 @@
<?php
namespace DesignPatterns;
namespace DesignPatterns\Command;
/**
* Command pattern
*
* Purpose:
* to build a simple interface for commands that can all be run by just executing a single method they all have in
* common, often called 'run' or 'execute'
* Purpose: To encapsulate invocation and decoupling
*
* We have an Invoker and a Receiver. This pattern use a "Command" to delegate the
* method call against the Receiver and presents the same method "execute".
* Therefore, the Invoker just know to call "execute" to process the Command of
* the client. The Receiver is decoupled from the Invoker
*
* The second aspect of ths pattern is the undo(), which undoes the method execute()
* Command can also be agregated 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)
*
*/
interface CommandInterface
interface Command
{
/**
* this is the most important method in the Command pattern,
* all config options and parameters should go into the constructor
*
* @return mixed
* The Receiver goes in the constructor.
*/
public function execute();
}
class EchoCommand implements CommandInterface
{
public function __construct($what)
{
$this->what = (string)$what;
}
public function execute()
{
echo $this->what;
}
}

30
Command/HelloCommand.php Normal file
View File

@ -0,0 +1,30 @@
<?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 Command
{
protected $output;
/**
* Each concrete command is builded with different receivers.
* Can be one, many, none or even other Command in parameters
*/
public function __construct(Receiver $console)
{
$this->output = $console;
}
public function execute()
{
// sometimes, there is no receiver and this is the command which
// does all the work
$this->output->write('Hello World');
}
}

34
Command/Invoker.php Normal file
View File

@ -0,0 +1,34 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Command;
/**
* Invoker is using the command given to it.
* Example : an Application in SF2
*/
class Invoker
{
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...
*/
public function setCommand(Command $cmd)
{
$this->command = $cmd;
}
public function run()
{
// here is a key feature of the invoker
// the invoker is the same whatever is the command
$this->command->execute();
}
}

20
Command/Receiver.php Normal file
View File

@ -0,0 +1,20 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Command;
/**
* Receiver is specific service with its own contract and can be only concrete
*/
class Receiver
{
public function write($str)
{
echo $str;
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace DesignPatterns;
namespace DesignPatterns\Composite;
/**
* composite pattern
@ -13,8 +13,10 @@ namespace DesignPatterns;
* subsequently runs trough all its child elements and calls render() on them
* - Zend_Config: a tree of configuration options, each one is a Zend_Config object
*
* The composite node MUST extend the component contract. This is mandatory for building
* a tree of component.
*/
class Form
class Form extends FormElement
{
protected $_elements;
@ -26,11 +28,11 @@ class Form
*
* @return string
*/
public function render()
public function render($indent = 0)
{
$formCode = '';
foreach ($this->_elements as $element) {
$formCode .= $element->render();
$formCode .= $element->render($indent + 1) . PHP_EOL;
}
return $formCode;
@ -41,29 +43,3 @@ class Form
$this->_elements[] = $element;
}
}
abstract class FormElement
{
abstract public function render();
}
class TextElement extends FormElement
{
public function render()
{
return 'this is a text element';
}
}
class InputElement extends FormElement
{
public function render()
{
return '<input type="text" />';
}
}
$form = new Form();
$form->addElement(new TextElement());
$form->addElement(new InputElement());
echo $form->render();

View File

@ -0,0 +1,8 @@
<?php
namespace DesignPatterns\Composite;
abstract class FormElement
{
abstract public function render($indent = 0);
}

View File

@ -0,0 +1,11 @@
<?php
namespace DesignPatterns\Composite;
class InputElement extends FormElement
{
public function render($indent = 0)
{
return str_repeat(' ', $indent) . '<input type="text" />';
}
}

11
Composite/TextElement.php Normal file
View File

@ -0,0 +1,11 @@
<?php
namespace DesignPatterns\Composite;
class TextElement extends FormElement
{
public function render($indent = 0)
{
return str_repeat(' ', $indent) . 'this is a text element';
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace DesignPatterns;
namespace DesignPatterns\Decorator;
/**
* Decorator pattern
@ -14,61 +14,26 @@ namespace DesignPatterns;
* course)
*
*/
interface Renderer
/**
* the Deoorator MUST implement the Renderer contract, this is the key-feature
* of this design pattern. If not, this is no longer a Decorator but just a dumb
* wrapper.
*/
abstract class Decorator implements Renderer
{
public function renderData();
}
class Webservice implements Renderer
{
protected $_data;
public function __construct($data)
{
$this->_data = $data;
}
public function renderData()
{
return $this->_data;
}
}
abstract class Decorator
{
protected $_wrapped;
public function __construct($wrappable)
/**
* You must type-hint the wrapped component :
* It ensures you can call renderData() in the subclasses !
*
* @param Renderer $wrappable
*/
public function __construct(Renderer $wrappable)
{
$this->_wrapped = $wrappable;
}
}
class RenderInJson extends Decorator implements Renderer
{
public function renderData()
{
$output = $this->_wrapped->renderData();
return json_encode($output);
}
}
class RenderInXml extends Decorator implements Renderer
{
public function renderData()
{
$output = $this->_wrapped->renderData();
// do some fancy conversion to xml from array ...
return simplexml_load_string($output);
}
}
// Create a normal service
$service = new Webservice(array('foo' => 'bar'));
// Wrap service with a JSON decorator for renderers
$service = new RenderInJson($service);
// Our Renderer will now output JSON instead of an array
echo $service->renderData();

View File

@ -0,0 +1,14 @@
<?php
namespace DesignPatterns\Decorator;
class RenderInJson extends Decorator
{
public function renderData()
{
$output = $this->_wrapped->renderData();
return json_encode($output);
}
}

20
Decorator/RenderInXml.php Normal file
View File

@ -0,0 +1,20 @@
<?php
namespace DesignPatterns\Decorator;
class RenderInXml extends Decorator
{
public function renderData()
{
$output = $this->_wrapped->renderData();
// do some fany conversion to xml from array ...
$doc = new \DOMDocument();
foreach ($output as $key => $val) {
$doc->appendChild($doc->createElement('foo', 'bar'));
}
return $doc->saveXML();
}
}

9
Decorator/Renderer.php Normal file
View File

@ -0,0 +1,9 @@
<?php
namespace DesignPatterns\Decorator;
interface Renderer
{
public function renderData();
}

20
Decorator/Webservice.php Normal file
View File

@ -0,0 +1,20 @@
<?php
namespace DesignPatterns\Decorator;
class Webservice implements Renderer
{
protected $_data;
public function __construct($data)
{
$this->_data = $data;
}
public function renderData()
{
return $this->_data;
}
}

View File

@ -1,49 +1,58 @@
<?php
namespace DesignPatterns;
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Facade;
/**
* facade pattern
*
* Purpose:
* like a real facade, to hide complexity behind the wall
*
* Examples:
* - Database Abstraction Layers
* - Doctrine2: EntityManager is the facade that one sees from the outside, but in there is much more going on, Unit of
* Work, etc.
* The primary goal of a Facade Pattern is not to avoid you to read the manual of
* a complex API. It's only a side-effect.
*
* The first goal is to reduce coupling and follow the Law of Demeter.
*
* A Facade is meant to decouple a client and a sub-system by embedding
* many (but sometimes just one) interface, and of course to reduce complexity.
*
* 1. A facade does not forbid you the access to the sub-system
* 2. You can (you should) have multiple facades for one sub-system
*
* That's why a good facade has no "new" in it. If there are multiple creations
* for each method, it is not a Facade, it's a Builder or a
* [Abstract|Static|Simple] Factory [Method].
*
* The best facade has no new and a constructor with interface-type-hinted parameters.
* If you need creation of new instances, use Factory as argument.
*
*/
class Facade
{
private $_text;
private $_queryBuilder;
public function __construct($text)
protected $opsys;
protected $bios;
/**
* This is the perfect time to use a dependency injection container
* to creaate an instance of this class
*/
public function __construct(BiosInterface $bios, OsInterface $os)
{
$text .= ', called ' . __METHOD__ . ' and thought that would be fairly easy, but ...';
$this->_text = $text;
$this->_initQueryBuilder($text);
$this->bios = $bios;
$this->opsys = $os;
}
protected function _initQueryBuilder($sql)
public function turnOn()
{
$query = new QueryBuilder();
$query->setSql($sql);
$this->_queryBuilder = $query;
$this->bios->execute();
$this->bios->waitForKeyPress();
$this->bios->launch($this->opsys);
}
}
class QueryBuilder
{
protected $_sql;
public function setSql($sql)
public function turnOff()
{
$this->_sql = $sql;
$this->opsys->halt();
$this->bios->powerDown();
}
}
// this is just a simple call, but behind the facade, there's much more going on
$foo = new Facade('very simple');
}

20
FactoryMethod/Bicycle.php Normal file
View File

@ -0,0 +1,20 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\FactoryMethod;
/**
* Bicycle is a bicycle
*/
class Bicycle implements Vehicle
{
public function setColor($rgb)
{
}
}

View File

@ -1,49 +1,54 @@
<?php
namespace DesignPatterns;
/*
* DesignPatternPHP
*/
namespace DesignPatterns\FactoryMethod;
/**
* Factory Method pattern
*
* Purpose:
* similar to the AbstractFactory, this pattern is used to create series of related or dependant objects.
* The difference between this and the abstract factory pattern is that the factory method pattern uses just one static
* method to create all types of objects it can create. It is usually named "factory" or "build".
*
* Examples:
* - Zend Framework: Zend_Cache_Backend or _Frontend use a factory method create cache backends or frontends
* FactoryMethod is a factory method. The good point over the SimpleFactory
* is you can subclass it to implement different way to create vehicle for
* each country (see subclasses)
*
* For simple case, this abstract class could be just an interface
*
* This pattern is a "real" Design Pattern because it achieves the
* "Dependency Inversion Principle" a.k.a the "D" in S.O.L.I.D principles.
*
* It means the FactoryMethod class depends on abstractions not concrete classes.
* This is the real trick compared to SImpleFactory or StaticFactory.
*/
class FactoryMethod
abstract class FactoryMethod
{
const CHEAP = 1;
const FAST = 2;
/**
* the parametrized function to get create an instance
*
* @static
* @return Formatter
* 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 Vehicle a new vehicle
*/
public static function factory($type)
abstract protected function createVehicle($type);
/**
* Creates a new vehicle
*
* @param int $type
*
* @return Vehicle a new vehicle
*/
public function create($type)
{
$className = 'Format' . ucfirst($type);
if ( ! class_exists($className)) {
throw new Exception('Missing format class.');
}
$obj = $this->createVehicle($type);
$obj->setColor("#f00");
return new $className();
return $obj;
}
}
interface Formatter
{
}
class FormatString implements Formatter
{
}
class FormatNumber implements Formatter
{
}
}

20
FactoryMethod/Ferrari.php Normal file
View File

@ -0,0 +1,20 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\FactoryMethod;
/**
* Ferrari is a italian car
*/
class Ferrari implements Vehicle
{
public function setColor($rgb)
{
}
}

View File

@ -0,0 +1,39 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\FactoryMethod;
/**
* GermanFactory is vehicle factory in Germany
*/
class GermanFactory extends FactoryMethod
{
/**
* @inheritdoc
*/
protected function createVehicle($type)
{
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();
return $obj;
break;
default :
throw new \InvalidArgumentException("$type is not a valid vehicle");
}
}
}

View File

@ -0,0 +1,35 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\FactoryMethod;
/**
* ItalianFactory is vehicle factory in Italy
*/
class ItalianFactory extends FactoryMethod
{
/**
* @inheritdoc
*/
protected function createVehicle($type)
{
switch ($type) {
case parent::CHEAP :
return new Bicycle();
break;
case parent::FAST :
return new Ferrari();
break;
default :
throw new \InvalidArgumentException("$type is not a valid vehicle");
}
}
}

25
FactoryMethod/Porsche.php Normal file
View File

@ -0,0 +1,25 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\FactoryMethod;
/**
* Porsche is a german car
*/
class Porsche implements Vehicle
{
public function setColor($rgb)
{
}
public function addTuningAMG()
{
}
}

16
FactoryMethod/Vehicle.php Normal file
View File

@ -0,0 +1,16 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\FactoryMethod;
/**
* Vehicle is a contract for a vehicle
*/
interface Vehicle
{
function setColor($rgb);
}

View File

@ -1,6 +1,6 @@
<?php
namespace DesignPatterns;
namespace DesignPatterns\FluentInterface;
/**
* fluent interface pattern
@ -52,9 +52,15 @@ class SQL
$this->_where[] = $condition;
return $this;
}
/**
* Gets the query, just an example of building a query,
* no check on consistency
*/
public function getQuery()
{
return 'SELECT ' . implode(',', $this->_fields)
. ' FROM ' . implode(',', $this->_from)
. ' WHERE ' . implode(' AND ', $this->_where);
}
}
$instance = new SQL();
$instance->select(array('foo', 'bar'))
->from('foobar', 'f')
->where('f.bar = ?');

80
Iterator/CardGame.php Normal file
View File

@ -0,0 +1,80 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Iterator;
/**
* Iterator provides a standard way to iterate over a collection without knowing
* how it is implemented. All you need to know is : you can traverse it
* with current, valid, next, rewind and key.
*
* That's the key feature of this pattern :
* The underlaying machinery could be an array, a matrix, a file, a cursor
* from database, a webservice with a cache, you don't care anymore.
*
* Note: This design pattern changes from one language to another. It depends
* mostly how loop statements handle collections (see Java before and after 1.5)
*
* In this simple example, I try to demonstrate how I manage a "linear" iterator
* on a card game but in fact, the underlaying storage is handled by two combined
* arrays.
*
* If tomorrow you decide to read cards from a database, the client
* (see the PHPUnit test) will remain unchanged. That's the beauty of it.
*/
class CardGame implements \Iterator
{
protected $color = array('D', 'S', 'C', 'H');
protected $number = array(7, 8, 9, 10, 'J', 'Q', 'K', 'A');
/**
* Return the current value
*/
public function current()
{
return current($this->number) . ' of ' . current($this->color);
}
/**
* Return the current key
*/
public function key()
{
return current($this->color) . current($this->number);
}
/**
* Go to the next item in the collection
*/
public function next()
{
if (false === next($this->number)) {
if (false !== next($this->color)) {
reset($this->number);
}
}
}
/**
* Go to the first item in the collection
*/
public function rewind()
{
reset($this->color);
reset($this->number);
}
/**
* Is the current position a valid item (true)
* or do we reach the end (false) ?
*/
public function valid()
{
return current($this->number) || current($this->color);
}
}

View File

@ -32,6 +32,8 @@ class File
public function process()
{
// this is the place to show how using an iterator, with foreach
// See the CardGame.php file
$this->_rowset->process();
}
}
@ -55,6 +57,14 @@ class Rowset implements Iterator
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();
}

31
Mediator/Colleague.php Normal file
View File

@ -0,0 +1,31 @@
<?php
/*
* DesignPatternPHP
*/
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
private $mediator;
// for subclasses
protected function getMediator()
{
return $this->mediator;
}
public function __construct(MediatorInterface $medium)
{
// in this way, we are sure the concrete colleague knows the mediator
$this->mediator = $medium;
}
}

54
Mediator/Mediator.php Normal file
View File

@ -0,0 +1,54 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Mediator;
use DesignPatterns\Mediator\Subsystem;
/**
* Mediator is the concrete Mediator for this design pattern.
*
* 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.
*
* In this example, I have made a "Hello World" with the Mediator Pattern, have fun (^_^)
*/
class Mediator implements MediatorInterface
{
// you could have an array
protected $server;
protected $database;
protected $client;
public function setColleague(Subsystem\Database $db, Subsystem\Client $cl, Subsystem\Server $srv)
{
$this->database = $db;
$this->server = $srv;
$this->client = $cl;
}
public function makeRequest()
{
$this->server->process();
}
public function queryDb()
{
return $this->database->getData();
}
public function sendResponse($content)
{
$this->client->output($content);
}
}

View File

@ -0,0 +1,21 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Mediator;
/**
* MediatorInterface is a contract for the Mediator
* This interface is not mandatory but it is better for LSP concerns
*/
interface MediatorInterface
{
function sendResponse($content);
function makeRequest();
function queryDb();
}

View File

@ -0,0 +1,27 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Mediator\Subsystem;
use DesignPatterns\Mediator\Colleague;
/**
* Client is a client that make request et get response
*/
class Client extends Colleague
{
public function request()
{
$this->getMediator()->makeRequest();
}
public function output($content)
{
echo $content;
}
}

View File

@ -0,0 +1,22 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Mediator\Subsystem;
use DesignPatterns\Mediator\Colleague;
/**
* Database is a database service
*/
class Database extends Colleague
{
public function getData()
{
return "World";
}
}

View File

@ -0,0 +1,23 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Mediator\Subsystem;
use DesignPatterns\Mediator\Colleague;
/**
* Server serves responses
*/
class Server extends Colleague
{
public function process()
{
$data = $this->getMediator()->queryDb();
$this->getMediator()->sendResponse("Hello $data");
}
}

View File

@ -0,0 +1,18 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\NullObject;
/**
* LoggerInterface is a contract for logging something
*
* Key-feaature : NullLogger MUST inherit from this interface like any other Loggers
*/
interface LoggerInterface
{
public function log($str);
}

41
NullObject/NullLogger.php Normal file
View File

@ -0,0 +1,41 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\NullObject;
/**
* NullOutput is a example of NullObject pattern. It is not formely a Design
* Pattern by the GoF but it's a schema which appears frequently enough to
* be a pattern. Futhermore 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
*
* The purpose : 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
* statement like "if (!is_null($obj)) { $obj->callSomething(); }" anymore.
*
* In this case, this a logger which does nothing. Other examples :
* - null logger of symfony profiler
* - null output in symfony/console
* - null handler in a Chain of Responsiblities pattern
* - null command in a Command pattern
*
* 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
{
public function log($str)
{
// do nothing
}
}

View File

@ -0,0 +1,20 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\NullObject;
/**
* PrintLogger is a logger that prints the log entry to standard output
*/
class PrintLogger implements LoggerInterface
{
public function log($str)
{
echo $str;
}
}

30
NullObject/Service.php Normal file
View File

@ -0,0 +1,30 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\NullObject;
/**
* Service is dummy service that uses a logger
*/
class Service
{
protected $logger;
// we inject the logger in ctor and it is mandatory
public function __construct(LoggerInterface $log)
{
$this->logger = $log;
}
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...
}
}

View File

@ -1,8 +1,11 @@
# design patterns in PHP #
[![Build Status](https://travis-ci.org/Trismegiste/DesignPatternsPHP.png?branch=master)](https://travis-ci.org/Trismegiste/DesignPatternsPHP)
This is a collection of known design patterns and some sample code how to implement them in PHP. Every pattern has a
small list of examples (most of them from Zend Framework or Doctrine2 as I'm most familiar with this software).
I think the problem with patterns is that often people do know them but don't know when to apply which.
*Please feel free to fork and add your own examples!*
*Please feel free to fork and add your own examples!*

20
SimpleFactory/Bicycle.php Normal file
View File

@ -0,0 +1,20 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\SimpleFactory;
/**
* Bicycle is a bicycle
*/
class Bicycle implements Vehicle
{
public function driveTo($destination)
{
}
}

View File

@ -0,0 +1,52 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\SimpleFactory;
/**
* ConcreteFactory 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 haZ multiple factories, differently parametrized,
* you can subclass it and you can mock-up it.
*/
class ConcreteFactory
{
protected $typeList;
/**
* You can imagine to inject your own type list or merge with
* the default ones...
*/
public function __construct()
{
$this->typeList = array(
'bicycle' => __NAMESPACE__ . '\Bicycle',
'other' => __NAMESPACE__ . '\Scooter'
);
}
/**
* Creates a vehicle
*
* @param string $type a known type key
* @return Vehicle a new instance of Vehicle
* @throws \InvalidArgumentException
*/
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();
}
}

20
SimpleFactory/Scooter.php Normal file
View File

@ -0,0 +1,20 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\SimpleFactory;
/**
* Scooter is a Scooter
*/
class Scooter implements Vehicle
{
public function driveTo($destination)
{
}
}

16
SimpleFactory/Vehicle.php Normal file
View File

@ -0,0 +1,16 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\SimpleFactory;
/**
* Vehicle is a contract for a vehicle
*/
interface Vehicle
{
function driveTo($destination);
}

View File

@ -0,0 +1,8 @@
<?php
namespace DesignPatterns\StaticFactory;
class FormatNumber implements Formatter
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace DesignPatterns\StaticFactory;
class FormatString implements Formatter
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace DesignPatterns\StaticFactory;
interface Formatter
{
}

View File

@ -0,0 +1,39 @@
<?php
namespace DesignPatterns\StaticFactory;
/**
* Static Factory pattern
*
* Purpose:
* similar to the AbstractFactory, this pattern is used to create series of related or dependant objects.
* The difference between this and the abstract factory pattern is that the static factory pattern uses just one static
* method to create all types of objects it can create. It is usually named "factory" or "build".
*
* Examples:
* - Zend Framework: Zend_Cache_Backend or _Frontend use a factory method create cache backends or frontends
*
* Note1: Remember, static => global => evil
* Note2: Cannot be subclassed or mock-uped or have multiple different instances
*/
class StaticFactory
{
/**
* the parametrized function to get create an instance
*
* @static
* @return Formatter
*/
public static function factory($type)
{
$className = __NAMESPACE__ . '\Format' . ucfirst($type);
if (!class_exists($className)) {
throw new \InvalidArgumentException('Missing format class.');
}
return new $className();
}
}

View File

@ -0,0 +1,20 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\TemplateMethod;
/**
* BeachJourney is vacation at the beach
*/
class BeachJourney extends Journey
{
protected function enjoyVacation()
{
echo "Swimming and sun-bathing\n";
}
}

View File

@ -0,0 +1,20 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\TemplateMethod;
/**
* CityJouney is a journey in a city
*/
class CityJouney extends Journey
{
protected function enjoyVacation()
{
echo "Eat, drink, take photos and sleep\n";
}
}

View File

@ -0,0 +1,76 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\TemplateMethod;
/**
* Template Method is a behavioral design pattern.
*
* Perhaps you have encoutered 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.
*/
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.
*/
public final 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 depedencies)
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 exacly what change and what remain unchanged
// in this algorithm.
// [abstract] x [3 access] x [final] = 12 combinations, it can be hard !
}

View File

@ -0,0 +1,50 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Tests\AbstractFactory;
use DesignPatterns\AbstractFactory\AbstractFactory;
use DesignPatterns\AbstractFactory\HtmlFactory;
use DesignPatterns\AbstractFactory\JsonFactory;
/**
* AbstractFactoryTest tests concrete factories
*/
class AbstractFactoryTest extends \PHPUnit_Framework_TestCase
{
public function getFactories()
{
return array(
array(new JsonFactory()),
array(new HtmlFactory())
);
}
/**
* 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)
{
$article = array(
$factory->createText('Lorem Ipsum'),
$factory->createPicture('/image.jpg', 'caption'),
$factory->createText('footnotes')
);
$this->assertContainsOnly('DesignPatterns\AbstractFactory\Media', $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
*/
}
}

View File

@ -0,0 +1,42 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Tests\Adapter;
use DesignPatterns\Adapter\ElecBookAdapter;
use DesignPatterns\Adapter\Kindle;
use DesignPatterns\Adapter\PaperBookInterface;
use DesignPatterns\Adapter\Book;
/**
* AdapterTest shows the use of an adapted e-book that behave like a book
* You don't have to change the code of your client
*/
class AdapterTest extends \PHPUnit_Framework_TestCase
{
public function getBook()
{
return array(
array(new Book()),
// we build a "wrapped" electronic book in the adapter
array(new ElecBookAdapter(new Kindle()))
);
}
/**
* This client only knows paper book but surprise, surprise, the second book
* is in fact an electronic book.
*
* @dataProvider getBook
*/
public function test_I_am_an_old_Client(PaperBookInterface $book)
{
$book->open();
$book->turnPage();
}
}

View File

@ -0,0 +1,46 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Tests\Builder;
use DesignPatterns\Builder\Director;
use DesignPatterns\Builder\CarBuilder;
use DesignPatterns\Builder\BikeBuilder;
/**
* DirectorTest tests the builder pattern
*/
class DirectorTest extends \PHPUnit_Framework_TestCase
{
protected $director;
protected function setUp()
{
$this->director = new Director();
}
public function getBuilder()
{
return array(
array(new CarBuilder()),
array(new BikeBuilder())
);
}
/**
* Here we test the build process. Notice that the client don't know
* anything about the contrete builder.
*
* @dataProvider getBuilder
*/
public function testBuild(\DesignPatterns\Builder\Builder $builder)
{
$newVehicle = $this->director->build($builder);
$this->assertInstanceOf('DesignPatterns\Builder\Parts\Vehicle', $newVehicle);
}
}

View File

@ -0,0 +1,78 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Tests\ChainOfResponsibilities;
use DesignPatterns\ChainOfResponsibilities\Request;
use DesignPatterns\ChainOfResponsibilities\Responsible;
/**
* ChainTest tests the CoR
*/
class ChainTest extends \PHPUnit_Framework_TestCase
{
protected $chain;
protected function setUp()
{
$this->chain = new Responsible\FastStorage(array('bar' => 'baz'));
$this->chain->append(new Responsible\SlowStorage(array('bar' => 'baz', 'foo' => 'bar')));
}
public function makeRequest()
{
$request = new Request();
$request->verb = 'get';
return array(
array($request)
);
}
/**
* @dataProvider makeRequest
*/
public function testFastStorage($request)
{
$request->key = 'bar';
$ret = $this->chain->handle($request);
$this->assertTrue($ret);
$this->assertObjectHasAttribute('response', $request);
$this->assertEquals('baz', $request->response);
// despite both handle owns the 'bar' key, the FastStorage is responding first
$this->assertEquals('DesignPatterns\ChainOfResponsibilities\Responsible\FastStorage', $request->forDebugOnly);
}
/**
* @dataProvider makeRequest
*/
public function testSlowStorage($request)
{
$request->key = 'foo';
$ret = $this->chain->handle($request);
$this->assertTrue($ret);
$this->assertObjectHasAttribute('response', $request);
$this->assertEquals('bar', $request->response);
// FastStorage has no 'foo' key, the SlowStorage is responding
$this->assertEquals('DesignPatterns\ChainOfResponsibilities\Responsible\SlowStorage', $request->forDebugOnly);
}
/**
* @dataProvider makeRequest
*/
public function testFailure($request)
{
$request->key = 'kurukuku';
$ret = $this->chain->handle($request);
$this->assertFalse($ret);
// the last rsponsible :
$this->assertEquals('DesignPatterns\ChainOfResponsibilities\Responsible\SlowStorage', $request->forDebugOnly);
}
}

View File

@ -0,0 +1,36 @@
<?php
/*
* DesignPatternPHP
*/
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();
}
}

View File

@ -0,0 +1,39 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Test\Composite;
use DesignPatterns\Composite;
/**
* FormTest tests the composite pattern on Form
*/
class FormTest extends \PHPUnit_Framework_TestCase
{
public function testRender()
{
$form = new Composite\Form();
$form->addElement(new Composite\TextElement());
$form->addElement(new Composite\InputElement());
$embed = new Composite\Form();
$embed->addElement(new Composite\TextElement());
$embed->addElement(new Composite\InputElement());
$form->addElement($embed); // here we have a embedded form (like SF2 does)
$this->assertRegExp('#^\s{4}#m', $form->render());
}
/**
* The all point of this pattern, a Composite must inherit from the node
* if you want to builld trees
*/
public function testFormImplementsFormEelement()
{
$this->assertTrue(is_subclass_of('DesignPatterns\Composite\Form', 'DesignPatterns\Composite\FormElement'));
}
}

View File

@ -0,0 +1,68 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Tests\Decorator;
use DesignPatterns\Decorator;
/**
* DecoratorTest tests the decorator pattern
*/
class DecoratorTest extends \PHPUnit_Framework_TestCase
{
protected $service;
protected function setUp()
{
$this->service = new Decorator\Webservice(array('foo' => 'bar'));
}
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());
}
public function testXmlDecorator()
{
// Wrap service with a JSON decorator for renderers
$service = new Decorator\RenderInXml($this->service);
// Our Renderer will now output XML instead of an array
$this->assertXmlStringEqualsXmlString('<?xml version="1.0"?><foo>bar</foo>', $service->renderData());
}
/**
* The first key-point of this pattern :
*/
public function testDecoratorMustImplementsRenderer()
{
$this->assertTrue(is_subclass_of('DesignPatterns\Decorator\Decorator', 'DesignPatterns\Decorator\Renderer'));
}
/**
* Second key-point of this pattern : the decorator is type-hinted
*
* @expectedException \PHPUnit_Framework_Error
*/
public function testDecoratorTypeHinted()
{
$this->getMockForAbstractClass('DesignPatterns\Decorator\Decorator', array(new \stdClass()));
}
/**
* The decorator implements and wraps the same interface
*/
public function testDecoratorOnlyAcceptRenderer()
{
$mock = $this->getMock('DesignPatterns\Decorator\Renderer');
$dec = $this->getMockForAbstractClass('DesignPatterns\Decorator\Decorator', array($mock));
$this->assertNotNull($dec);
}
}

View File

@ -0,0 +1,50 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Tests\Facade;
use DesignPatterns\Facade\Facade as Computer;
/**
* FacadeTest shows example of facades
*/
class FacadeTest extends \PHPUnit_Framework_TestCase
{
public function getComputer()
{
$bios = $this->getMockBuilder('DesignPatterns\Facade\BiosInterface')
->setMethods(array('launch', 'execute', 'waitForKeyPress'))
->disableAutoload()
->getMock();
$operatingSys = $this->getMockBuilder('DesignPatterns\Facade\OsInterface')
->setMethods(array('getName'))
->disableAutoload()
->getMock();
$bios->expects($this->once())
->method('launch')
->with($operatingSys);
$operatingSys
->expects($this->once())
->method('getName')
->will($this->returnValue('Linux'));
$facade = new Computer($bios, $operatingSys);
return array(array($facade, $operatingSys));
}
/**
* @dataProvider getComputer
*/
public function testComputerOn(Computer $facade, $os)
{
// interface is simpler :
$facade->turnOn();
// but I can access to lower component
$this->assertEquals('Linux', $os->getName());
}
}

View File

@ -0,0 +1,55 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Tests\FactoryMethod;
use DesignPatterns\FactoryMethod\FactoryMethod;
use DesignPatterns\FactoryMethod\GermanFactory;
use DesignPatterns\FactoryMethod\ItalianFactory;
/**
* FactoryMethodTest tests the factory method pattern
*/
class FactoryMethodTest extends \PHPUnit_Framework_TestCase
{
protected $type = array(
FactoryMethod::CHEAP,
FactoryMethod::FAST
);
public function getShop()
{
return array(
array(new GermanFactory()),
array(new ItalianFactory())
);
}
/**
* @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\FactoryMethod\Vehicle', $vehicle);
}
}
/**
* @dataProvider getShop
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage spaceship is not a valid vehicle
*/
public function testUnknownType(FactoryMethod $shop)
{
$shop->create('spaceship');
}
}

View File

@ -0,0 +1,28 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Tests\FluentInterface;
use DesignPatterns\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'))
->from('foobar', 'f')
->where('f.bar = ?')
->getQuery();
$this->assertEquals('SELECT foo,bar FROM foobar AS f WHERE f.bar = ?', $query);
}
}

View File

@ -0,0 +1,68 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Tests\Iterator;
use DesignPatterns\Iterator\CardGame;
/**
* IteratorTest tests the CardGame iterator
*/
class IteratorTest extends \PHPUnit_Framework_TestCase
{
protected $deck;
protected function setUp()
{
$this->deck = new CardGame();
}
/**
* This is the client of the iterator.
* It remains unchanged even if one day I decide to use MongoDB to store the
* card.
*/
public function testCardinal()
{
$counter = 0;
foreach ($this->deck as $key => $card) {
$counter++;
}
$this->assertEquals(32, $counter);
}
/**
* Some fancy functions of PHP.
*/
public function testExampleOf_PHP_Helper()
{
// PHPUnit works on array or iterator :
$this->assertCount(32, $this->deck);
// a easy way to get an array from interator :
$cards = iterator_to_array($this->deck);
$this->assertEquals('A of S', $cards['SA']);
// a easy way to get an iterator from an array :
$iterator = new \ArrayIterator($cards);
$this->assertInstanceOf('\Iterator', $iterator);
}
/**
* Iterator can be combine, chained, filter, there are many in the SPL
* and sadly they are rarely used.
*/
public function testIteratorCombining()
{
// a fancy way to add a joker to the deck :
$joker = array('JK' => 'Joker');
$newDeck = new \AppendIterator();
$newDeck->append($this->deck);
$newDeck->append(new \ArrayIterator($joker));
$this->assertCount(33, $newDeck);
}
}

View File

@ -0,0 +1,39 @@
<?php
/*
* DesignPatternPHP
*/
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.
}
}

View File

@ -0,0 +1,35 @@
<?php
/*
* DesignPatternPHP
*/
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();
}
}

View File

@ -0,0 +1,49 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Tests\SimpleFactory;
use DesignPatterns\SimpleFactory\ConcreteFactory;
/**
* SimpleFactoryTest tests the Simple Factory pattern
*/
class SimpleFactoryTest extends \PHPUnit_Framework_TestCase
{
protected $factory;
protected function setUp()
{
$this->factory = new ConcreteFactory();
}
public function getType()
{
return array(
array('bicycle'),
array('other')
);
}
/**
* @dataProvider getType
*/
public function testCreation($type)
{
$obj = $this->factory->createVehicle($type);
$this->assertInstanceOf('DesignPatterns\SimpleFactory\Vehicle', $obj);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testBadType()
{
$this->factory->createVehicle('car');
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace DesignPatterns\Tests\StaticFactory;
use DesignPatterns\StaticFactory\StaticFactory;
/**
* Tests for Static Factory pattern
*
*/
class StaticFactoryTest extends \PHPUnit_Framework_TestCase
{
public function getTypeList()
{
return array(
array('string'),
array('number')
);
}
/**
* @dataProvider getTypeList
*/
public function testCreation($type)
{
$obj = StaticFactory::factory($type);
$this->assertInstanceOf('DesignPatterns\StaticFactory\Formatter', $obj);
}
}

View File

@ -0,0 +1,49 @@
<?php
/*
* DesignPatternPHP
*/
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\CityJouney();
$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";
}
}

8
Tests/bootstrap.php Normal file
View File

@ -0,0 +1,8 @@
<?php
spl_autoload_register(function ($class) {
if (preg_match('#^DesignPatterns\\\\(.+)$#', $class, $ret)) {
$relPath = str_replace('\\', DIRECTORY_SEPARATOR, $ret[1]);
require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . $relPath . '.php';
}
});

10
phpunit.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="Tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="Design Patterns tests">
<directory>./Tests</directory>
</testsuite>
</testsuites>
</phpunit>