mirror of
https://github.com/DesignPatternsPHP/DesignPatternsPHP.git
synced 2025-06-06 22:14:59 +02:00
commit
21b904b7d5
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
# common
|
||||
.idea
|
||||
/nbproject
|
7
.travis.yml
Normal file
7
.travis.yml
Normal file
@ -0,0 +1,7 @@
|
||||
language: php
|
||||
php:
|
||||
- 5.4
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
@ -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 = '');
|
||||
}
|
||||
|
23
AbstractFactory/Html/Picture.php
Normal file
23
AbstractFactory/Html/Picture.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
22
AbstractFactory/Html/Text.php
Normal file
22
AbstractFactory/Html/Text.php
Normal 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>';
|
||||
}
|
||||
|
||||
}
|
25
AbstractFactory/HtmlFactory.php
Normal file
25
AbstractFactory/HtmlFactory.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
23
AbstractFactory/Json/Picture.php
Normal file
23
AbstractFactory/Json/Picture.php
Normal 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));
|
||||
}
|
||||
|
||||
}
|
22
AbstractFactory/Json/Text.php
Normal file
22
AbstractFactory/Json/Text.php
Normal 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));
|
||||
}
|
||||
|
||||
}
|
26
AbstractFactory/JsonFactory.php
Normal file
26
AbstractFactory/JsonFactory.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace DesignPatterns\AbstractFactory;
|
||||
|
||||
class Picture implements Media
|
||||
abstract class Picture implements Media
|
||||
{
|
||||
protected $_path;
|
||||
protected $_name;
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace DesignPatterns\AbstractFactory;
|
||||
|
||||
class Text implements Media
|
||||
abstract class Text implements Media
|
||||
{
|
||||
/**
|
||||
*
|
||||
|
@ -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
25
Adapter/Book.php
Normal 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()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
41
Adapter/ElecBookAdapter.php
Normal file
41
Adapter/ElecBookAdapter.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
18
Adapter/ElecBookInterface.php
Normal file
18
Adapter/ElecBookInterface.php
Normal 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
27
Adapter/Kindle.php
Normal 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()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
18
Adapter/PaperBookInterface.php
Normal file
18
Adapter/PaperBookInterface.php
Normal 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
43
Builder/BikeBuilder.php
Normal 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
33
Builder/Builder.php
Normal 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
46
Builder/CarBuilder.php
Normal 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
32
Builder/Director.php
Normal 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
15
Builder/Parts/Bike.php
Normal 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
15
Builder/Parts/Car.php
Normal 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
8
Builder/Parts/Door.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Builder\Parts;
|
||||
|
||||
class Door
|
||||
{
|
||||
|
||||
}
|
8
Builder/Parts/Engine.php
Normal file
8
Builder/Parts/Engine.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Builder\Parts;
|
||||
|
||||
class Engine
|
||||
{
|
||||
|
||||
}
|
22
Builder/Parts/Vehicle.php
Normal file
22
Builder/Parts/Vehicle.php
Normal 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
8
Builder/Parts/Wheel.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Builder\Parts;
|
||||
|
||||
class Wheel
|
||||
{
|
||||
|
||||
}
|
@ -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);
|
||||
|
72
ChainOfResponsibilities/Handler.php
Normal file
72
ChainOfResponsibilities/Handler.php
Normal 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);
|
||||
}
|
23
ChainOfResponsibilities/Request.php
Normal file
23
ChainOfResponsibilities/Request.php
Normal 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
|
||||
}
|
37
ChainOfResponsibilities/Responsible/FastStorage.php
Normal file
37
ChainOfResponsibilities/Responsible/FastStorage.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
44
ChainOfResponsibilities/Responsible/SlowStorage.php
Normal file
44
ChainOfResponsibilities/Responsible/SlowStorage.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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
30
Command/HelloCommand.php
Normal 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
34
Command/Invoker.php
Normal 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
20
Command/Receiver.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
8
Composite/FormElement.php
Normal file
8
Composite/FormElement.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Composite;
|
||||
|
||||
abstract class FormElement
|
||||
{
|
||||
abstract public function render($indent = 0);
|
||||
}
|
11
Composite/InputElement.php
Normal file
11
Composite/InputElement.php
Normal 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
11
Composite/TextElement.php
Normal 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';
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
14
Decorator/RenderInJson.php
Normal file
14
Decorator/RenderInJson.php
Normal 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
20
Decorator/RenderInXml.php
Normal 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
9
Decorator/Renderer.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Decorator;
|
||||
|
||||
interface Renderer
|
||||
{
|
||||
|
||||
public function renderData();
|
||||
}
|
20
Decorator/Webservice.php
Normal file
20
Decorator/Webservice.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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
20
FactoryMethod/Bicycle.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* DesignPatternPHP
|
||||
*/
|
||||
|
||||
namespace DesignPatterns\FactoryMethod;
|
||||
|
||||
/**
|
||||
* Bicycle is a bicycle
|
||||
*/
|
||||
class Bicycle implements Vehicle
|
||||
{
|
||||
|
||||
public function setColor($rgb)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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
20
FactoryMethod/Ferrari.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* DesignPatternPHP
|
||||
*/
|
||||
|
||||
namespace DesignPatterns\FactoryMethod;
|
||||
|
||||
/**
|
||||
* Ferrari is a italian car
|
||||
*/
|
||||
class Ferrari implements Vehicle
|
||||
{
|
||||
|
||||
public function setColor($rgb)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
39
FactoryMethod/GermanFactory.php
Normal file
39
FactoryMethod/GermanFactory.php
Normal 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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
35
FactoryMethod/ItalianFactory.php
Normal file
35
FactoryMethod/ItalianFactory.php
Normal 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
25
FactoryMethod/Porsche.php
Normal 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
16
FactoryMethod/Vehicle.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* DesignPatternPHP
|
||||
*/
|
||||
|
||||
namespace DesignPatterns\FactoryMethod;
|
||||
|
||||
/**
|
||||
* Vehicle is a contract for a vehicle
|
||||
*/
|
||||
interface Vehicle
|
||||
{
|
||||
|
||||
function setColor($rgb);
|
||||
}
|
@ -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
80
Iterator/CardGame.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@ -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
31
Mediator/Colleague.php
Normal 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
54
Mediator/Mediator.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
21
Mediator/MediatorInterface.php
Normal file
21
Mediator/MediatorInterface.php
Normal 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();
|
||||
}
|
27
Mediator/Subsystem/Client.php
Normal file
27
Mediator/Subsystem/Client.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
22
Mediator/Subsystem/Database.php
Normal file
22
Mediator/Subsystem/Database.php
Normal 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";
|
||||
}
|
||||
|
||||
}
|
23
Mediator/Subsystem/Server.php
Normal file
23
Mediator/Subsystem/Server.php
Normal 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");
|
||||
}
|
||||
|
||||
}
|
18
NullObject/LoggerInterface.php
Normal file
18
NullObject/LoggerInterface.php
Normal 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
41
NullObject/NullLogger.php
Normal 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
|
||||
}
|
||||
|
||||
}
|
20
NullObject/PrintLogger.php
Normal file
20
NullObject/PrintLogger.php
Normal 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
30
NullObject/Service.php
Normal 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...
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
# design patterns in PHP #
|
||||
|
||||
[](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
20
SimpleFactory/Bicycle.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* DesignPatternPHP
|
||||
*/
|
||||
|
||||
namespace DesignPatterns\SimpleFactory;
|
||||
|
||||
/**
|
||||
* Bicycle is a bicycle
|
||||
*/
|
||||
class Bicycle implements Vehicle
|
||||
{
|
||||
|
||||
public function driveTo($destination)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
52
SimpleFactory/ConcreteFactory.php
Normal file
52
SimpleFactory/ConcreteFactory.php
Normal 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
20
SimpleFactory/Scooter.php
Normal 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
16
SimpleFactory/Vehicle.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* DesignPatternPHP
|
||||
*/
|
||||
|
||||
namespace DesignPatterns\SimpleFactory;
|
||||
|
||||
/**
|
||||
* Vehicle is a contract for a vehicle
|
||||
*/
|
||||
interface Vehicle
|
||||
{
|
||||
|
||||
function driveTo($destination);
|
||||
}
|
8
StaticFactory/FormatNumber.php
Normal file
8
StaticFactory/FormatNumber.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\StaticFactory;
|
||||
|
||||
class FormatNumber implements Formatter
|
||||
{
|
||||
|
||||
}
|
8
StaticFactory/FormatString.php
Normal file
8
StaticFactory/FormatString.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\StaticFactory;
|
||||
|
||||
class FormatString implements Formatter
|
||||
{
|
||||
|
||||
}
|
8
StaticFactory/Formatter.php
Normal file
8
StaticFactory/Formatter.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\StaticFactory;
|
||||
|
||||
interface Formatter
|
||||
{
|
||||
|
||||
}
|
39
StaticFactory/StaticFactory.php
Normal file
39
StaticFactory/StaticFactory.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
20
TemplateMethod/BeachJourney.php
Normal file
20
TemplateMethod/BeachJourney.php
Normal 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";
|
||||
}
|
||||
|
||||
}
|
20
TemplateMethod/CityJouney.php
Normal file
20
TemplateMethod/CityJouney.php
Normal 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";
|
||||
}
|
||||
|
||||
}
|
76
TemplateMethod/Journey.php
Normal file
76
TemplateMethod/Journey.php
Normal 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 !
|
||||
}
|
50
Tests/AbstractFactory/AbstractFactoryTest.php
Normal file
50
Tests/AbstractFactory/AbstractFactoryTest.php
Normal 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
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
}
|
42
Tests/Adapter/AdapterTest.php
Normal file
42
Tests/Adapter/AdapterTest.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
46
Tests/Builder/DirectorTest.php
Normal file
46
Tests/Builder/DirectorTest.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
78
Tests/ChainOfResponsibilities/ChainTest.php
Normal file
78
Tests/ChainOfResponsibilities/ChainTest.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
36
Tests/Command/CommandTest.php
Normal file
36
Tests/Command/CommandTest.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
39
Tests/Composite/FormTest.php
Normal file
39
Tests/Composite/FormTest.php
Normal 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'));
|
||||
}
|
||||
|
||||
}
|
68
Tests/Decorator/DecoratorTest.php
Normal file
68
Tests/Decorator/DecoratorTest.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
50
Tests/Facade/FacadeTest.php
Normal file
50
Tests/Facade/FacadeTest.php
Normal 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());
|
||||
}
|
||||
|
||||
}
|
55
Tests/FactoryMethod/FactoryMethodTest.php
Normal file
55
Tests/FactoryMethod/FactoryMethodTest.php
Normal 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');
|
||||
}
|
||||
|
||||
}
|
28
Tests/FluentInterface/FluentInterfaceTest.php
Normal file
28
Tests/FluentInterface/FluentInterfaceTest.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
68
Tests/Iterator/IteratorTest.php
Normal file
68
Tests/Iterator/IteratorTest.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
39
Tests/Mediator/MediatorTest.php
Normal file
39
Tests/Mediator/MediatorTest.php
Normal 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.
|
||||
}
|
||||
|
||||
}
|
35
Tests/NullObject/LoggerTest.php
Normal file
35
Tests/NullObject/LoggerTest.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
49
Tests/SimpleFactory/SimpleFactoryTest.php
Normal file
49
Tests/SimpleFactory/SimpleFactoryTest.php
Normal 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');
|
||||
}
|
||||
|
||||
}
|
31
Tests/StaticFactory/StaticFactoryTest.php
Normal file
31
Tests/StaticFactory/StaticFactoryTest.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
49
Tests/TemplateMethod/JourneyTest.php
Normal file
49
Tests/TemplateMethod/JourneyTest.php
Normal 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
8
Tests/bootstrap.php
Normal 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
10
phpunit.xml
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user