Merge pull request #14 from Trismegiste/master

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

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
# common # common
.idea .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 <?php
namespace DesignPatterns; namespace DesignPatterns\AbstractFactory;
/** /**
* Abstract Factory pattern * Abstract Factory pattern
* *
* Purpose: * Purpose:
* to create series of related or dependant objects without specifying their concrete classes, * to create series of related or dependant objects without specifying their concrete classes,
* usually the created classes all implement the same interface * 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.
* Examples: *
* - A Factory to create media in a CMS: classes would be text, audio, video, picture * Sometimes also known as "Kit" in a GUI libraries.
* - 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 * This design pattern implements the Dependency Inversion Principle since
* TextElement() instead * it is the concrete subclass which creates concrete components.
* - an abstract factory to create various exceptions (e.g. Doctrine2 uses this method) *
* * 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 abstract class AbstractFactory
{ {
/** /**
* @static * Creates a text component
*
* @param string $content * @param string $content
* @return AbstractFactory\Text * @return Text
*/ */
public static function createText($content) abstract public function createText($content);
{
return new AbstractFactory\Text($content);
}
/** /**
* @static * Createss a picture component
*
* @param string $path * @param string $path
* @param string $name * @param string $name
* @return AbstractFactory\Picture * @return Picture
*/ */
public static function createPicture($path, $name = '') abstract public function createPicture($path, $name = '');
{
return new AbstractFactory\Picture($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; namespace DesignPatterns\AbstractFactory;
/**
* This contract is not part of the pattern, in general case, each component
* are not related
*/
interface Media interface Media
{ {
function render();
} }

View File

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

View File

@@ -2,7 +2,7 @@
namespace DesignPatterns\AbstractFactory; 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 * in the chain and so forth
* *
* Examples: * 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 * - Caching: first object is an instance of e.g. a Memcached Interface, if that "misses" it delegates the call to the
* Database Interface * Database Interface
* - Yii Framework: CFilterChain is a chain of controller action filters. the executing point is passed from one filter * - 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 interface KeyValueStorage
{ {
public function get($key); 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 <?php
namespace DesignPatterns; namespace DesignPatterns\Command;
/** /**
* Command pattern * Command pattern
* *
* Purpose: * Purpose: To encapsulate invocation and decoupling
* 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' * 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: * 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 * - 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 * - 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) * can be implemented with the Command pattern (e.g. vagrant)
* *
*/ */
interface CommandInterface interface Command
{ {
/** /**
* this is the most important method in the Command pattern, * this is the most important method in the Command pattern,
* all config options and parameters should go into the constructor * The Receiver goes in the constructor.
*
* @return mixed
*/ */
public function execute(); 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 <?php
namespace DesignPatterns; namespace DesignPatterns\Composite;
/** /**
* composite pattern * composite pattern
@@ -13,8 +13,10 @@ namespace DesignPatterns;
* subsequently runs trough all its child elements and calls render() on them * 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 * - 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; protected $_elements;
@@ -26,11 +28,11 @@ class Form
* *
* @return string * @return string
*/ */
public function render() public function render($indent = 0)
{ {
$formCode = ''; $formCode = '';
foreach ($this->_elements as $element) { foreach ($this->_elements as $element) {
$formCode .= $element->render(); $formCode .= $element->render($indent + 1) . PHP_EOL;
} }
return $formCode; return $formCode;
@@ -41,29 +43,3 @@ class Form
$this->_elements[] = $element; $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 <?php
namespace DesignPatterns; namespace DesignPatterns\Decorator;
/** /**
* Decorator pattern * Decorator pattern
@@ -14,61 +14,26 @@ namespace DesignPatterns;
* course) * 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; 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; $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 <?php
namespace DesignPatterns; /*
* DesignPatternPHP
*/
namespace DesignPatterns\Facade;
/** /**
* facade pattern * 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.
* Purpose: *
* like a real facade, to hide complexity behind the wall * The first goal is to reduce coupling and follow the Law of Demeter.
* *
* Examples: * A Facade is meant to decouple a client and a sub-system by embedding
* - Database Abstraction Layers * many (but sometimes just one) interface, and of course to reduce complexity.
* - Doctrine2: EntityManager is the facade that one sees from the outside, but in there is much more going on, Unit of *
* Work, etc. * 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 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->bios = $bios;
$this->_text = $text; $this->opsys = $os;
$this->_initQueryBuilder($text);
} }
protected function _initQueryBuilder($sql) public function turnOn()
{ {
$query = new QueryBuilder(); $this->bios->execute();
$query->setSql($sql); $this->bios->waitForKeyPress();
$this->_queryBuilder = $query; $this->bios->launch($this->opsys);
} }
}
class QueryBuilder public function turnOff()
{
protected $_sql;
public function setSql($sql)
{ {
$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 <?php
namespace DesignPatterns; /*
* DesignPatternPHP
*/
namespace DesignPatterns\FactoryMethod;
/** /**
* Factory Method pattern * FactoryMethod is a factory method. The good point over the SimpleFactory
* * is you can subclass it to implement different way to create vehicle for
* Purpose: * each country (see subclasses)
* 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
* *
* 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 * The children of the class must implement this method
* *
* @static * Sometimes this method can be public to get "raw" object
* @return Formatter *
* @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); $obj = $this->createVehicle($type);
if ( ! class_exists($className)) { $obj->setColor("#f00");
throw new Exception('Missing format class.');
}
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 <?php
namespace DesignPatterns; namespace DesignPatterns\FluentInterface;
/** /**
* fluent interface pattern * fluent interface pattern
@@ -52,9 +52,15 @@ class SQL
$this->_where[] = $condition; $this->_where[] = $condition;
return $this; 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() public function process()
{ {
// this is the place to show how using an iterator, with foreach
// See the CardGame.php file
$this->_rowset->process(); $this->_rowset->process();
} }
} }
@@ -55,6 +57,14 @@ class Rowset implements Iterator
public function process() public function process()
{ {
// this actually calls rewind(), { next(), valid(), key() and current() :} // 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) { foreach ($this as $line => $row) {
$row->process(); $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 # # 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 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). 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. 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>