디자인패턴PHP¶
이 문서는 일반적으로 디자인패턴(Design Pattern)이라고 알려진 내용을 다루며, PHP에서 어떻게 구현해야하는지 샘플 코드를 담고 있습니다. 모든 패턴은 조금이나마 예제를 다루고 있습니다. (그리고 그 예제의 대부분은 사용하기에 친근한 Zend Framework, Symfony2, Doctrine2에서 가져왔습니다.)
디자인 패턴에서의 문제는 종종 사람들은 그것들을 알지만 정작 언제 적용해야하는지 모른다는 것입니다.
디자인패턴¶
이 문서에서는 디자인패턴을 크게 3가지의 다른 카테고리로 분류하였습니다. 위키피디아(Wikipedia)에 설명되어있는 디자인 패턴은 **모든 페이지의 제목**을 클릭하시면 읽을 수 있습니다.
생성 패턴 (Creational)¶
소프트웨어 공학에서 창조적 디자인 패턴이란, 객체 생성 메카니즘을 다루고 상황에 적절한 객체를 만드는 것입니다. 객체에 디자인 문제나 또는 복잡한 디자인을 추가하는 것이 객체 생성의 기본 형태입니다. 창조적 디자인 패턴으로 객체 생성을 어떻게든 제어해서 이 문제를 해결해야 합니다.
추상 팩토리 <https://ko.wikipedia.org/wiki/%EC%B6%94%EC%83%81_%ED%8C%A9%ED%86%A0%EB%A6%AC_%ED%8C%A8%ED%84%B4)>`_ (Abstract Factory)¶
사용 목적¶
관계에 대한 집합 혹은 그들의 자신의 객체에 대한 서술 없이 의존된 객체를 생성하기 위해 사용합니다. 일반적으로 생성된 클래스들은 모두 같은 인터페이스를 구현해 만듭니다. 추상 팩토리의 사용자(Client)는 어떻게 객체가 만들어지는지 신경쓰지 않고, 단지 어떻게 동작되는지만 알면 됩니다.
UML 다이어그램¶

코드¶
코드는 또한 GitHub 에서 볼 수 있습니다.
AbstractFactory.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | <?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* class AbstractFactory
*
* Sometimes also known as "Kit" in a GUI libraries.
*
* This design pattern implements the Dependency Inversion Principle since
* it is the concrete subclass which creates concrete components.
*
* In this case, the abstract factory is a contract for creating some components
* for the web. There are two components : Text and Picture. There 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
{
/**
* Creates a text component
*
* @param string $content
*
* @return Text
*/
abstract public function createText($content);
/**
* Creates a picture component
*
* @param string $path
* @param string $name
*
* @return Picture
*/
abstract public function createPicture($path, $name = '');
}
|
JsonFactory.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | <?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* Class JsonFactory
*
* JsonFactory is a factory for creating a family of JSON component
* (example for ajax)
*/
class JsonFactory extends AbstractFactory
{
/**
* Creates a picture component
*
* @param string $path
* @param string $name
*
* @return Json\Picture|Picture
*/
public function createPicture($path, $name = '')
{
return new Json\Picture($path, $name);
}
/**
* Creates a text component
*
* @param string $content
*
* @return Json\Text|Text
*/
public function createText($content)
{
return new Json\Text($content);
}
}
|
HtmlFactory.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* Class HtmlFactory
*
* HtmlFactory is a concrete factory for HTML component
*/
class HtmlFactory extends AbstractFactory
{
/**
* Creates a picture component
*
* @param string $path
* @param string $name
*
* @return Html\Picture|Picture
*/
public function createPicture($path, $name = '')
{
return new Html\Picture($path, $name);
}
/**
* Creates a text component
*
* @param string $content
*
* @return Html\Text|Text
*/
public function createText($content)
{
return new Html\Text($content);
}
}
|
MediaInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* Interface MediaInterface
*
* This contract is not part of the pattern, in general case, each component
* are not related
*/
interface MediaInterface
{
/**
* some crude rendering from JSON or html output (depended on concrete class)
*
* @return string
*/
public function render();
}
|
Picture.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* Class Picture
*/
abstract class Picture implements MediaInterface
{
/**
* @var string
*/
protected $path;
/**
* @var string
*/
protected $name;
/**
* @param string $path
* @param string $name
*/
public function __construct($path, $name = '')
{
$this->name = (string) $name;
$this->path = (string) $path;
}
}
|
Text.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* Class Text
*/
abstract class Text implements MediaInterface
{
/**
* @var string
*/
protected $text;
/**
* @param string $text
*/
public function __construct($text)
{
$this->text = (string) $text;
}
}
|
Json/Picture.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?php
namespace DesignPatterns\Creational\AbstractFactory\Json;
use DesignPatterns\Creational\AbstractFactory\Picture as BasePicture;
/**
* Class Picture
*
* Picture is a concrete image for JSON rendering
*/
class Picture extends BasePicture
{
/**
* some crude rendering from JSON output
*
* @return string
*/
public function render()
{
return json_encode(array('title' => $this->name, 'path' => $this->path));
}
}
|
Json/Text.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?php
namespace DesignPatterns\Creational\AbstractFactory\Json;
use DesignPatterns\Creational\AbstractFactory\Text as BaseText;
/**
* Class Text
*
* Text is a text component with a JSON rendering
*/
class Text extends BaseText
{
/**
* some crude rendering from JSON output
*
* @return string
*/
public function render()
{
return json_encode(array('content' => $this->text));
}
}
|
Html/Picture.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?php
namespace DesignPatterns\Creational\AbstractFactory\Html;
use DesignPatterns\Creational\AbstractFactory\Picture as BasePicture;
/**
* Class Picture
*
* Picture is a concrete image for HTML rendering
*/
class Picture extends BasePicture
{
/**
* some crude rendering from HTML output
*
* @return string
*/
public function render()
{
return sprintf('<img src="%s" title="%s"/>', $this->path, $this->name);
}
}
|
Html/Text.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?php
namespace DesignPatterns\Creational\AbstractFactory\Html;
use DesignPatterns\Creational\AbstractFactory\Text as BaseText;
/**
* Class Text
*
* Text is a concrete text for HTML rendering
*/
class Text extends BaseText
{
/**
* some crude rendering from HTML output
*
* @return string
*/
public function render()
{
return '<div>' . htmlspecialchars($this->text) . '</div>';
}
}
|
테스트¶
Tests/AbstractFactoryTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <?php
namespace DesignPatterns\Creational\AbstractFactory\Tests;
use DesignPatterns\Creational\AbstractFactory\AbstractFactory;
use DesignPatterns\Creational\AbstractFactory\HtmlFactory;
use DesignPatterns\Creational\AbstractFactory\JsonFactory;
/**
* AbstractFactoryTest tests concrete factories
*/
class AbstractFactoryTest extends \PHPUnit_Framework_TestCase
{
public function getFactories()
{
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\Creational\AbstractFactory\MediaInterface', $article);
/* this is the time to look at the Builder pattern. This pattern
* helps you to create complex object like that article above with
* a given Abstract Factory
*/
}
}
|
빌더 (Builder)¶
사용 목적¶
빌더는 복잡한 객체의 일부를 생성하는 인터페이스 입니다.
때때로, 빌더는 그것에 대해 더 명확한 정보(Knowledge)를 가질 때, 해당 인터페이스는 기본 매서드(일명 Adapter)를 가진 추상클래스가 됩니다.
만약 객체들에 복잡한 상속 트리가 있는 경우에, 빌더 또한 복잡한 상속 트리를 가져야 마땅합니다.
Note: 빌더들은 보통 유연한 인터페이스를 가지는데, PHPUnit의 mock builder가 그 예 입니다.
예시¶
- PHPUnit: Mock Builder
UML 다이어그램¶

코드¶
코드는 또한 GitHub 에서 볼 수 있습니다.
Director.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <?php
namespace DesignPatterns\Creational\Builder;
/**
* Director is part of the builder pattern. It knows the interface of the builder
* and builds a complex object with the help of the builder.
*
* You can also inject many builders instead of one to build more complex objects
*/
class Director
{
/**
* The director don't know about concrete part
*
* @param BuilderInterface $builder
*
* @return Parts\Vehicle
*/
public function build(BuilderInterface $builder)
{
$builder->createVehicle();
$builder->addDoors();
$builder->addEngine();
$builder->addWheel();
return $builder->getVehicle();
}
}
|
BuilderInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <?php
namespace DesignPatterns\Creational\Builder;
/**
*
*/
interface BuilderInterface
{
/**
* @return mixed
*/
public function createVehicle();
/**
* @return mixed
*/
public function addWheel();
/**
* @return mixed
*/
public function addEngine();
/**
* @return mixed
*/
public function addDoors();
/**
* @return mixed
*/
public function getVehicle();
}
|
BikeBuilder.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | <?php
namespace DesignPatterns\Creational\Builder;
/**
* BikeBuilder builds bike
*/
class BikeBuilder implements BuilderInterface
{
/**
* @var Parts\Bike
*/
protected $bike;
/**
* {@inheritdoc}
*/
public function addDoors()
{
}
/**
* {@inheritdoc}
*/
public function addEngine()
{
$this->bike->setPart('engine', new Parts\Engine());
}
/**
* {@inheritdoc}
*/
public function addWheel()
{
$this->bike->setPart('forwardWheel', new Parts\Wheel());
$this->bike->setPart('rearWheel', new Parts\Wheel());
}
/**
* {@inheritdoc}
*/
public function createVehicle()
{
$this->bike = new Parts\Bike();
}
/**
* {@inheritdoc}
*/
public function getVehicle()
{
return $this->bike;
}
}
|
CarBuilder.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | <?php
namespace DesignPatterns\Creational\Builder;
/**
* CarBuilder builds car
*/
class CarBuilder implements BuilderInterface
{
/**
* @var Parts\Car
*/
protected $car;
/**
* @return void
*/
public function addDoors()
{
$this->car->setPart('rightdoor', new Parts\Door());
$this->car->setPart('leftDoor', new Parts\Door());
}
/**
* @return void
*/
public function addEngine()
{
$this->car->setPart('engine', new Parts\Engine());
}
/**
* @return void
*/
public function addWheel()
{
$this->car->setPart('wheelLF', new Parts\Wheel());
$this->car->setPart('wheelRF', new Parts\Wheel());
$this->car->setPart('wheelLR', new Parts\Wheel());
$this->car->setPart('wheelRR', new Parts\Wheel());
}
/**
* @return void
*/
public function createVehicle()
{
$this->car = new Parts\Car();
}
/**
* @return Parts\Car
*/
public function getVehicle()
{
return $this->car;
}
}
|
Parts/Vehicle.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?php
namespace DesignPatterns\Creational\Builder\Parts;
/**
* VehicleInterface is a contract for a vehicle
*/
abstract class Vehicle
{
/**
* @var array
*/
protected $data;
/**
* @param string $key
* @param mixed $value
*/
public function setPart($key, $value)
{
$this->data[$key] = $value;
}
}
|
Parts/Bike.php
1 2 3 4 5 6 7 8 9 10 | <?php
namespace DesignPatterns\Creational\Builder\Parts;
/**
* Bike is a bike
*/
class Bike extends Vehicle
{
}
|
Parts/Car.php
1 2 3 4 5 6 7 8 9 10 | <?php
namespace DesignPatterns\Creational\Builder\Parts;
/**
* Car is a car
*/
class Car extends Vehicle
{
}
|
Parts/Engine.php
1 2 3 4 5 6 7 8 9 10 | <?php
namespace DesignPatterns\Creational\Builder\Parts;
/**
* Class Engine
*/
class Engine
{
}
|
Parts/Wheel.php
1 2 3 4 5 6 7 8 9 10 | <?php
namespace DesignPatterns\Creational\Builder\Parts;
/**
* Class Wheel
*/
class Wheel
{
}
|
Parts/Door.php
1 2 3 4 5 6 7 8 9 10 | <?php
namespace DesignPatterns\Creational\Builder\Parts;
/**
* Class Door
*/
class Door
{
}
|
테스트¶
Tests/DirectorTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | <?php
namespace DesignPatterns\Creational\Builder\Tests;
use DesignPatterns\Creational\Builder\Director;
use DesignPatterns\Creational\Builder\CarBuilder;
use DesignPatterns\Creational\Builder\BikeBuilder;
use DesignPatterns\Creational\Builder\BuilderInterface;
/**
* 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 concrete builder.
*
* @dataProvider getBuilder
*/
public function testBuild(BuilderInterface $builder)
{
$newVehicle = $this->director->build($builder);
$this->assertInstanceOf('DesignPatterns\Creational\Builder\Parts\Vehicle', $newVehicle);
}
}
|
팩토리 메서드 (Factory Method)¶
사용 목적¶
단순팩토리(SimpleFactory)의 좋은점은 서브클래스가 객체를 여러 방법으로 구현할 수 있다는 것 입니다.
간단한 예로는, 이 추상 클래스는 인터페이스가 될 수 있다는 것입니다.
이 패턴은 “real” 디자인 패턴 입니다. 왜냐하면 S.O.L.I.D 원리에서 “D”에 해당하는, 일명 “역 의존성 원리(Dependency Inversion Principle)”를 달성해야 하기 때문입니다.
이 말은 FactoryMethod(코드에 언급된)클래스는 구체적인 클래스가 아닌, 추상화에 따른다는 것입니다. 이것은 단순팩토리나 정적팩토리에 비하면 진짜 기술입니다.
UML 다이어그램¶

코드¶
코드는 또한 GitHub 에서 볼 수 있습니다.
FactoryMethod.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <?php
namespace DesignPatterns\Creational\FactoryMethod;
/**
* class FactoryMethod
*/
abstract class FactoryMethod
{
const CHEAP = 1;
const FAST = 2;
/**
* The children of the class must implement this method
*
* Sometimes this method can be public to get "raw" object
*
* @param string $type a generic type
*
* @return VehicleInterface a new vehicle
*/
abstract protected function createVehicle($type);
/**
* Creates a new vehicle
*
* @param int $type
*
* @return VehicleInterface a new vehicle
*/
public function create($type)
{
$obj = $this->createVehicle($type);
$obj->setColor("#f00");
return $obj;
}
}
|
ItalianFactory.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <?php
namespace DesignPatterns\Creational\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");
}
}
}
|
GermanFactory.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <?php
namespace DesignPatterns\Creational\FactoryMethod;
/**
* GermanFactory is a 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");
}
}
}
|
VehicleInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <?php
namespace DesignPatterns\Creational\FactoryMethod;
/**
* VehicleInterface is a contract for a vehicle
*/
interface VehicleInterface
{
/**
* sets the color of the vehicle
*
* @param string $rgb
*/
public function setColor($rgb);
}
|
Porsche.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <?php
namespace DesignPatterns\Creational\FactoryMethod;
/**
* Porsche is a german car
*/
class Porsche implements VehicleInterface
{
/**
* @var string
*/
protected $color;
/**
* @param string $rgb
*/
public function setColor($rgb)
{
$this->color = $rgb;
}
/**
* although tuning by AMG is only offered for Mercedes Cars,
* this is a valid coding example ...
*/
public function addTuningAMG()
{
}
}
|
Bicycle.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <?php
namespace DesignPatterns\Creational\FactoryMethod;
/**
* Bicycle is a bicycle
*/
class Bicycle implements VehicleInterface
{
/**
* @var string
*/
protected $color;
/**
* sets the color of the bicycle
*
* @param string $rgb
*/
public function setColor($rgb)
{
$this->color = $rgb;
}
}
|
Ferrari.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <?php
namespace DesignPatterns\Creational\FactoryMethod;
/**
* Ferrari is a italian car
*/
class Ferrari implements VehicleInterface
{
/**
* @var string
*/
protected $color;
/**
* @param string $rgb
*/
public function setColor($rgb)
{
$this->color = $rgb;
}
}
|
테스트¶
Tests/FactoryMethodTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | <?php
namespace DesignPatterns\Creational\FactoryMethod\Tests;
use DesignPatterns\Creational\FactoryMethod\FactoryMethod;
use DesignPatterns\Creational\FactoryMethod\GermanFactory;
use DesignPatterns\Creational\FactoryMethod\ItalianFactory;
/**
* FactoryMethodTest tests the factory method pattern
*/
class FactoryMethodTest extends \PHPUnit_Framework_TestCase
{
protected $type = array(
FactoryMethod::CHEAP,
FactoryMethod::FAST
);
public function getShop()
{
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\Creational\FactoryMethod\VehicleInterface', $vehicle);
}
}
/**
* @dataProvider getShop
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage spaceship is not a valid vehicle
*/
public function testUnknownType(FactoryMethod $shop)
{
$shop->create('spaceship');
}
}
|
멀티턴 (Multiton)¶
안티패턴이 될 수 있기 때문에 사용에 고려해 보아야 합니다! 더 나은 테스트가능성(Testablity)과 유지가능성(Maintainability)을 위해서 의존성 주입(Dependency Injection)을 사용하세요!
사용 목적¶
사용중인 이름있는 인스턴스만을 리스트로 갖고 싶을 때 사용합니다. 싱글턴과 유사하지만 싱글턴은 1개의 인스턴스를, 멀티턴은 n개의 인스턴스를 갖는 다는 점이 다릅니다.
예시¶
2개의 데이터베이스 커넥터. 이를테면 하나는 MySQL, 또다른 하나는 SQLite.
여러개의 로거(Logger). (하나는 디버그 메시지용, 또 하나는 에러처리용)
UML 다이어그램¶

코드¶
코드는 또한 GitHub 에서 볼 수 있습니다.
Multiton.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | <?php
namespace DesignPatterns\Creational\Multiton;
/**
* class Multiton
*/
class Multiton
{
/**
*
* the first instance
*/
const INSTANCE_1 = '1';
/**
*
* the second instance
*/
const INSTANCE_2 = '2';
/**
* holds the named instances
*
* @var array
*/
private static $instances = array();
/**
* should not be called from outside: private!
*
*/
private function __construct()
{
}
/**
* gets the instance with the given name, e.g. Multiton::INSTANCE_1
* uses lazy initialization
*
* @param string $instanceName
*
* @return Multiton
*/
public static function getInstance($instanceName)
{
if (!array_key_exists($instanceName, self::$instances)) {
self::$instances[$instanceName] = new self();
}
return self::$instances[$instanceName];
}
/**
* prevent instance from being cloned
*
* @return void
*/
private function __clone()
{
}
/**
* prevent instance from being unserialized
*
* @return void
*/
private function __wakeup()
{
}
}
|
테스트¶
풀 (Pool)¶
**오브젝트 풀 패턴**은 소프트웨어 생성 패턴(Creational Patterns) 중 하나입니다. 객체들을 하나하나 할당(Allocating)하고 해제(Destroying)하기 보다는, 풀(Pool)을 이용하여 사용할 준비가 된 초기화된 객체들을 사용합니다. 풀의 사용자(Client)는 풀에서 객체를 요청하고, 반환된 객체를 이용해서 작업을 수행합니다. 그리고 사용자가 작업을 끝내면 그것을 해제(Destroy)하기 보다는 풀에 팩토리 객체의 특정 타입으로 된 객체를 반환합니다.
객체 풀링(Pooling)은 클래스를 초기화 하는 비용이 크고, 클래스의 인스턴스화 비율이 크고, 특정 시점에 사용중인 인스턴스의 숫자가 적을 때 성능향상을 제공합니다. 풀된 객체는 새로운 객체의 생성(특히 네트워크를 통해)이 다양한 시간(오래 걸리든, 짧게 걸리든)을 갖더라도, 예측가능한 시간을 얻을 수 있습니다.
그러나, 이러한 장점들은 시간상에서 비용이 큰 객체, 이를테면 데이터베이스 연결이나, 소켓 연결, 스레드나 폰트 혹은 비트맵과 같은 큰 그래픽 객체에 대해서는 사실입니다만, 특정 상황, 즉, 단순한 객체 풀링(외부 자원은 보유하지만, 전용 메모리를 차지하는)은 아마도 효과적이지 않을 수 있고 오히려 성능을 감소시킬 수 있습니다.
UML 다이어그램¶

코드¶
코드는 또한 GitHub 에서 볼 수 있습니다.
Pool.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?php
namespace DesignPatterns\Creational\Pool;
class Pool
{
private $instances = array();
private $class;
public function __construct($class)
{
$this->class = $class;
}
public function get()
{
if (count($this->instances) > 0) {
return array_pop($this->instances);
}
return new $this->class();
}
public function dispose($instance)
{
$this->instances[] = $instance;
}
}
|
Processor.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <?php
namespace DesignPatterns\Creational\Pool;
class Processor
{
private $pool;
private $processing = 0;
private $maxProcesses = 3;
private $waitingQueue = [];
public function __construct(Pool $pool)
{
$this->pool = $pool;
}
public function process($image)
{
if ($this->processing++ < $this->maxProcesses) {
$this->createWorker($image);
} else {
$this->pushToWaitingQueue($image);
}
}
private function createWorker($image)
{
$worker = $this->pool->get();
$worker->run($image, array($this, 'processDone'));
}
public function processDone($worker)
{
$this->processing--;
$this->pool->dispose($worker);
if (count($this->waitingQueue) > 0) {
$this->createWorker($this->popFromWaitingQueue());
}
}
private function pushToWaitingQueue($image)
{
$this->waitingQueue[] = $image;
}
private function popFromWaitingQueue()
{
return array_pop($this->waitingQueue);
}
}
|
Worker.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <?php
namespace DesignPatterns\Creational\Pool;
class Worker
{
public function __construct()
{
// let's say that constuctor does really expensive work...
// for example creates "thread"
}
public function run($image, array $callback)
{
// do something with $image...
// and when it's done, execute callback
call_user_func($callback, $this);
}
}
|
테스트¶
Tests/PoolTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <?php
namespace DesignPatterns\Creational\Pool\Tests;
use DesignPatterns\Creational\Pool\Pool;
class PoolTest extends \PHPUnit_Framework_TestCase
{
public function testPool()
{
$pool = new Pool('DesignPatterns\Creational\Pool\Tests\TestWorker');
$worker = $pool->get();
$this->assertEquals(1, $worker->id);
$worker->id = 5;
$pool->dispose($worker);
$this->assertEquals(5, $pool->get()->id);
$this->assertEquals(1, $pool->get()->id);
}
}
|
Tests/TestWorker.php
1 2 3 4 5 6 7 8 | <?php
namespace DesignPatterns\Creational\Pool\Tests;
class TestWorker
{
public $id = 1;
}
|
프로토타입 (Prototype)¶
사용 목적¶
일반적으로 사용하는 객체 생성(new Foo())의 비용을 피하기 위해서 사용합니다. 그리고 그 대신에 프로토타입을 생성하고 그것을 복제(clone)합니다.
예시¶
많은 양의 데이터. (이를테면, ORM을 통해 데이터베이스에서 한번에 1,000,000줄을 생성할 때.)
UML 다이어그램¶

코드¶
코드는 또한 GitHub 에서 볼 수 있습니다.
index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?php
namespace DesignPatterns\Creational\Prototype;
$fooPrototype = new FooBookPrototype();
$barPrototype = new BarBookPrototype();
// now lets say we need 10,000 books of foo and 5,000 of bar ...
for ($i = 0; $i < 10000; $i++) {
$book = clone $fooPrototype;
$book->setTitle('Foo Book No ' . $i);
}
for ($i = 0; $i < 5000; $i++) {
$book = clone $barPrototype;
$book->setTitle('Bar Book No ' . $i);
}
|
BookPrototype.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | <?php
namespace DesignPatterns\Creational\Prototype;
/**
* class BookPrototype
*/
abstract class BookPrototype
{
/**
* @var string
*/
protected $title;
/**
* @var string
*/
protected $category;
/**
* @abstract
* @return void
*/
abstract public function __clone();
/**
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* @param string $title
*/
public function setTitle($title)
{
$this->title = $title;
}
}
|
BarBookPrototype.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?php
namespace DesignPatterns\Creational\Prototype;
/**
* Class BarBookPrototype
*/
class BarBookPrototype extends BookPrototype
{
/**
* @var string
*/
protected $category = 'Bar';
/**
* empty clone
*/
public function __clone()
{
}
}
|
FooBookPrototype.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?php
namespace DesignPatterns\Creational\Prototype;
/**
* Class FooBookPrototype
*/
class FooBookPrototype extends BookPrototype
{
protected $category = 'Foo';
/**
* empty clone
*/
public function __clone()
{
}
}
|
테스트¶
단순팩토리 (Simple Factory)¶
사용 목적¶
ConcreteFactory(코드에 언급된)는 단순팩토리입니다.
단순팩토리패턴은 정적이지 않기 때문에 정적팩토리패턴(Static Factory)과는 다릅니다. 참고: 정적(static) => 전역(global) => 좋지않음(evil)!
그러므로, 서로 다른 패러매터를 가진 여러개의 팩토리를 가질 수 있고, 이것의 상속된 클래스를 만들 수 있으며, 이것의 목업(mock-up)을 생성할 수 있습니다.
UML 다이어그램¶

코드¶
코드는 또한 GitHub 에서 볼 수 있습니다.
ConcreteFactory.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <?php
namespace DesignPatterns\Creational\SimpleFactory;
/**
* class ConcreteFactory
*/
class ConcreteFactory
{
/**
* @var array
*/
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 VehicleInterface a new instance of VehicleInterface
* @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();
}
}
|
VehicleInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <?php
namespace DesignPatterns\Creational\SimpleFactory;
/**
* VehicleInterface is a contract for a vehicle
*/
interface VehicleInterface
{
/**
* @param mixed $destination
*
* @return mixed
*/
public function driveTo($destination);
}
|
Bicycle.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?php
namespace DesignPatterns\Creational\SimpleFactory;
/**
* Bicycle is a bicycle
*/
class Bicycle implements VehicleInterface
{
/**
* @param mixed $destination
*
* @return mixed|void
*/
public function driveTo($destination)
{
}
}
|
Scooter.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <?php
namespace DesignPatterns\Creational\SimpleFactory;
/**
* Scooter is a Scooter
*/
class Scooter implements VehicleInterface
{
/**
* @param mixed $destination
*/
public function driveTo($destination)
{
}
}
|
테스트¶
Tests/SimpleFactoryTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <?php
namespace DesignPatterns\Creational\SimpleFactory\Tests;
use DesignPatterns\Creational\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\Creational\SimpleFactory\VehicleInterface', $obj);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testBadType()
{
$this->factory->createVehicle('car');
}
}
|
싱글턴 (Singleton)¶
안티패턴이 될 수 있기 때문에 사용에 고려해 보아야 합니다! 더 나은 테스트가능성(Testablity)과 유지가능성(Maintainability)을 위해서 의존성 주입(Dependency Injection)을 사용하세요!
사용 목적¶
어플리케이션에서 오직 하나의 객체 인스턴스를 가지고 모든 이벤트를 처리합니다.
예시¶
DB 커넥터
Logger (여러 목적을 위해 많은 로그파일 있는 경우, multiton 패턴일 수 있습니다.)
어플리케이션의 락파일 (파일시스템은 하나만 존재합니다 ...)
UML 다이어그램¶

코드¶
코드는 또한 GitHub 에서 볼 수 있습니다.
Singleton.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | <?php
namespace DesignPatterns\Creational\Singleton;
/**
* class Singleton
*/
class Singleton
{
/**
* @var Singleton reference to singleton instance
*/
private static $instance;
/**
* gets the instance via lazy initialization (created on first usage)
*
* @return self
*/
public static function getInstance()
{
if (null === static::$instance) {
static::$instance = new static;
}
return static::$instance;
}
/**
* is not allowed to call from outside: private!
*
*/
private function __construct()
{
}
/**
* prevent the instance from being cloned
*
* @return void
*/
private function __clone()
{
}
/**
* prevent from being unserialized
*
* @return void
*/
private function __wakeup()
{
}
}
|
테스트¶
Tests/SingletonTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?php
namespace DesignPatterns\Creational\Singleton\Tests;
use DesignPatterns\Creational\Singleton\Singleton;
/**
* SingletonTest tests the singleton pattern
*/
class SingletonTest extends \PHPUnit_Framework_TestCase
{
public function testUniqueness()
{
$firstCall = Singleton::getInstance();
$this->assertInstanceOf('DesignPatterns\Creational\Singleton\Singleton', $firstCall);
$secondCall = Singleton::getInstance();
$this->assertSame($firstCall, $secondCall);
}
public function testNoConstructor()
{
$obj = Singleton::getInstance();
$refl = new \ReflectionObject($obj);
$meth = $refl->getMethod('__construct');
$this->assertTrue($meth->isPrivate());
}
}
|
정적 팩토리 (Static Factory)¶
사용 목적¶
이 패턴은 추상 팩토리와 유사하고, 종속 객체들이나 연관된 집합을 만드는데 사용합니다. 이 패턴과 추상 팩토리 패턴 사이의 차이점은 정적 팩토리 패턴은 만들 수 있는 모든 타입의 객체를 만드는 오직 단 하나의 정적인 메서드를 사용한다는 것입니다. 이 패턴은 보통 factory
또는 ``build``라고 합니다.
UML 다이어그램¶

코드¶
코드는 또한 GitHub 에서 볼 수 있습니다.
StaticFactory.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <?php
namespace DesignPatterns\Creational\StaticFactory;
/**
* Note1: Remember, static => global => evil
* Note2: Cannot be subclassed or mock-upped or have multiple different instances
*/
class StaticFactory
{
/**
* the parametrized function to get create an instance
*
* @param string $type
*
* @static
*
* @throws \InvalidArgumentException
* @return FormatterInterface
*/
public static function factory($type)
{
$className = __NAMESPACE__ . '\Format' . ucfirst($type);
if (!class_exists($className)) {
throw new \InvalidArgumentException('Missing format class.');
}
return new $className();
}
}
|
FormatterInterface.php
1 2 3 4 5 6 7 8 9 10 | <?php
namespace DesignPatterns\Creational\StaticFactory;
/**
* Class FormatterInterface
*/
interface FormatterInterface
{
}
|
FormatString.php
1 2 3 4 5 6 7 8 9 10 | <?php
namespace DesignPatterns\Creational\StaticFactory;
/**
* Class FormatString
*/
class FormatString implements FormatterInterface
{
}
|
FormatNumber.php
1 2 3 4 5 6 7 8 9 10 | <?php
namespace DesignPatterns\Creational\StaticFactory;
/**
* Class FormatNumber
*/
class FormatNumber implements FormatterInterface
{
}
|
테스트¶
Tests/StaticFactoryTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | <?php
namespace DesignPatterns\Creational\StaticFactory\Tests;
use DesignPatterns\Creational\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\Creational\StaticFactory\FormatterInterface', $obj);
}
/**
* @expectedException InvalidArgumentException
*/
public function testException()
{
StaticFactory::factory("");
}
}
|
Structural¶
In Software Engineering, Structural Design Patterns are Design Patterns that ease the design by identifying a simple way to realize relationships between entities.
Adapter / Wrapper¶
Purpose¶
To translate one interface for a class into a compatible interface. An adapter allows classes to work together that normally could not because of incompatible interfaces by providing it’s interface to clients while using the original interface.
Examples¶
- DB Client libraries adapter
- using multiple different webservices and adapters normalize data so that the outcome is the same for all
UML Diagram¶

Code¶
You can also find these code on GitHub
PaperBookInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?php
namespace DesignPatterns\Structural\Adapter;
/**
* PaperBookInterface is a contract for a book
*/
interface PaperBookInterface
{
/**
* method to turn pages
*
* @return mixed
*/
public function turnPage();
/**
* method to open the book
*
* @return mixed
*/
public function open();
}
|
Book.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?php
namespace DesignPatterns\Structural\Adapter;
/**
* Book is a concrete and standard paper book
*/
class Book implements PaperBookInterface
{
/**
* {@inheritdoc}
*/
public function open()
{
}
/**
* {@inheritdoc}
*/
public function turnPage()
{
}
}
|
EBookAdapter.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | <?php
namespace DesignPatterns\Structural\Adapter;
/**
* EBookAdapter is an adapter to fit an e-book like a paper book
*
* This is the adapter here. Notice it implements PaperBookInterface,
* therefore you don't have to change the code of the client which using paper book.
*/
class EBookAdapter implements PaperBookInterface
{
/**
* @var EBookInterface
*/
protected $eBook;
/**
* Notice the constructor, it "wraps" an electronic book
*
* @param EBookInterface $ebook
*/
public function __construct(EBookInterface $ebook)
{
$this->eBook = $ebook;
}
/**
* This class makes the proper translation from one interface to another
*/
public function open()
{
$this->eBook->pressStart();
}
/**
* turns pages
*/
public function turnPage()
{
$this->eBook->pressNext();
}
}
|
EBookInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?php
namespace DesignPatterns\Structural\Adapter;
/**
* EBookInterface is a contract for an electronic book
*/
interface EBookInterface
{
/**
* go to next page
*
* @return mixed
*/
public function pressNext();
/**
* start the book
*
* @return mixed
*/
public function pressStart();
}
|
Kindle.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?php
namespace DesignPatterns\Structural\Adapter;
/**
* Kindle is a concrete electronic book
*/
class Kindle implements EBookInterface
{
/**
* {@inheritdoc}
*/
public function pressNext()
{
}
/**
* {@inheritdoc}
*/
public function pressStart()
{
}
}
|
Test¶
Tests/AdapterTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | <?php
namespace DesignPatterns\Structural\Adapter\Tests;
use DesignPatterns\Structural\Adapter\EBookAdapter;
use DesignPatterns\Structural\Adapter\Kindle;
use DesignPatterns\Structural\Adapter\PaperBookInterface;
use DesignPatterns\Structural\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
{
/**
* @return array
*/
public function getBook()
{
return array(
array(new Book()),
// we build a "wrapped" electronic book in the adapter
array(new EBookAdapter(new Kindle()))
);
}
/**
* This client only knows paper book but surprise, surprise, the second book
* is in fact an electronic book, but both work the same way
*
* @param PaperBookInterface $book
*
* @dataProvider getBook
*/
public function testIAmAnOldClient(PaperBookInterface $book)
{
$book->open();
$book->turnPage();
}
}
|
Bridge¶
Purpose¶
Decouple an abstraction from its implementation so that the two can vary independently.
Sample:¶
UML Diagram¶

Code¶
You can also find these code on GitHub
Workshop.php
1 2 3 4 5 6 7 8 9 10 11 12 | <?php
namespace DesignPatterns\Structural\Bridge;
/**
* Implementer
*/
interface Workshop
{
public function work();
}
|
Assemble.php
1 2 3 4 5 6 7 8 9 10 11 12 | <?php
namespace DesignPatterns\Structural\Bridge;
class Assemble implements Workshop
{
public function work()
{
print 'Assembled';
}
}
|
Produce.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php
namespace DesignPatterns\Structural\Bridge;
/**
* Concrete Implementation
*/
class Produce implements Workshop
{
public function work()
{
print 'Produced ';
}
}
|
Vehicle.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?php
namespace DesignPatterns\Structural\Bridge;
/**
* Abstraction
*/
abstract class Vehicle
{
protected $workShop1;
protected $workShop2;
protected function __construct(Workshop $workShop1, Workshop $workShop2)
{
$this->workShop1 = $workShop1;
$this->workShop2 = $workShop2;
}
public function manufacture()
{
}
}
|
Motorcycle.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <?php
namespace DesignPatterns\Structural\Bridge;
/**
* Refined Abstraction
*/
class Motorcycle extends Vehicle
{
public function __construct(Workshop $workShop1, Workshop $workShop2)
{
parent::__construct($workShop1, $workShop2);
}
public function manufacture()
{
print 'Motorcycle ';
$this->workShop1->work();
$this->workShop2->work();
}
}
|
Car.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <?php
namespace DesignPatterns\Structural\Bridge;
/**
* Refined Abstraction
*/
class Car extends Vehicle
{
public function __construct(Workshop $workShop1, Workshop $workShop2)
{
parent::__construct($workShop1, $workShop2);
}
public function manufacture()
{
print 'Car ';
$this->workShop1->work();
$this->workShop2->work();
}
}
|
Test¶
Tests/BridgeTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <?php
namespace DesignPatterns\Structural\Bridge\Tests;
use DesignPatterns\Structural\Bridge\Assemble;
use DesignPatterns\Structural\Bridge\Car;
use DesignPatterns\Structural\Bridge\Motorcycle;
use DesignPatterns\Structural\Bridge\Produce;
class BridgeTest extends \PHPUnit_Framework_TestCase
{
public function testCar()
{
$vehicle = new Car(new Produce(), new Assemble());
$this->expectOutputString('Car Produced Assembled');
$vehicle->manufacture();
}
public function testMotorcycle()
{
$vehicle = new Motorcycle(new Produce(), new Assemble());
$this->expectOutputString('Motorcycle Produced Assembled');
$vehicle->manufacture();
}
}
|
Composite¶
Purpose¶
To treat a group of objects the same way as a single instance of the object.
Examples¶
- a form class instance handles all its form elements like a single
instance of the form, when
render()
is called, it subsequently runs through all its child elements and callsrender()
on them Zend_Config
: a tree of configuration options, each one is aZend_Config
object itself
UML Diagram¶

Code¶
You can also find these code on GitHub
FormElement.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?php
namespace DesignPatterns\Structural\Composite;
/**
* Class FormElement
*/
abstract class FormElement
{
/**
* renders the elements' code
*
* @param int $indent
*
* @return mixed
*/
abstract public function render($indent = 0);
}
|
Form.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <?php
namespace DesignPatterns\Structural\Composite;
/**
* The composite node MUST extend the component contract. This is mandatory for building
* a tree of components.
*/
class Form extends FormElement
{
/**
* @var array|FormElement[]
*/
protected $elements;
/**
* runs through all elements and calls render() on them, then returns the complete representation
* of the form
*
* from the outside, one will not see this and the form will act like a single object instance
*
* @param int $indent
*
* @return string
*/
public function render($indent = 0)
{
$formCode = '';
foreach ($this->elements as $element) {
$formCode .= $element->render($indent + 1) . PHP_EOL;
}
return $formCode;
}
/**
* @param FormElement $element
*/
public function addElement(FormElement $element)
{
$this->elements[] = $element;
}
}
|
InputElement.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?php
namespace DesignPatterns\Structural\Composite;
/**
* Class InputElement
*/
class InputElement extends FormElement
{
/**
* renders the input element HTML
*
* @param int $indent
*
* @return mixed|string
*/
public function render($indent = 0)
{
return str_repeat(' ', $indent) . '<input type="text" />';
}
}
|
TextElement.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?php
namespace DesignPatterns\Structural\Composite;
/**
* Class TextElement
*/
class TextElement extends FormElement
{
/**
* renders the text element
*
* @param int $indent
*
* @return mixed|string
*/
public function render($indent = 0)
{
return str_repeat(' ', $indent) . 'this is a text element';
}
}
|
Test¶
Tests/CompositeTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <?php
namespace DesignPatterns\Structural\Composite\Tests;
use DesignPatterns\Structural\Composite;
/**
* FormTest tests the composite pattern on Form
*/
class CompositeTest 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()
{
$className = 'DesignPatterns\Structural\Composite\Form';
$abstractName = 'DesignPatterns\Structural\Composite\FormElement';
$this->assertTrue(is_subclass_of($className, $abstractName));
}
}
|
Data Mapper¶
Purpose¶
A Data Mapper, is a Data Access Layer that performs bidirectional transfer of data between a persistent data store (often a relational database) and an in memory data representation (the domain layer). The goal of the pattern is to keep the in memory representation and the persistent data store independent of each other and the data mapper itself. The layer is composed of one or more mappers (or Data Access Objects), performing the data transfer. Mapper implementations vary in scope. Generic mappers will handle many different domain entity types, dedicated mappers will handle one or a few.
The key point of this pattern is, unlike Active Record pattern, the data model follows Single Responsibility Principle.
Examples¶
- DB Object Relational Mapper (ORM) : Doctrine2 uses DAO named as “EntityRepository”
UML Diagram¶

Code¶
You can also find these code on GitHub
User.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | <?php
namespace DesignPatterns\Structural\DataMapper;
/**
* DataMapper pattern
*
* This is our representation of a DataBase record in the memory (Entity)
*
* Validation would also go in this object
*
*/
class User
{
/**
* @var int
*/
protected $userId;
/**
* @var string
*/
protected $username;
/**
* @var string
*/
protected $email;
/**
* @param null $id
* @param null $username
* @param null $email
*/
public function __construct($id = null, $username = null, $email = null)
{
$this->userId = $id;
$this->username = $username;
$this->email = $email;
}
/**
* @return int
*/
public function getUserId()
{
return $this->userId;
}
/**
* @param int $userId
*/
public function setUserID($userId)
{
$this->userId = $userId;
}
/**
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* @param string $username
*/
public function setUsername($username)
{
$this->username = $username;
}
/**
* @return string
*/
public function getEmail()
{
return $this->email;
}
/**
* @param string $email
*/
public function setEmail($email)
{
$this->email = $email;
}
}
|
UserMapper.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | <?php
namespace DesignPatterns\Structural\DataMapper;
/**
* class UserMapper
*/
class UserMapper
{
/**
* @var DBAL
*/
protected $adapter;
/**
* @param DBAL $dbLayer
*/
public function __construct(DBAL $dbLayer)
{
$this->adapter = $dbLayer;
}
/**
* saves a user object from memory to Database
*
* @param User $user
*
* @return boolean
*/
public function save(User $user)
{
/* $data keys should correspond to valid Table columns on the Database */
$data = array(
'userid' => $user->getUserId(),
'username' => $user->getUsername(),
'email' => $user->getEmail(),
);
/* if no ID specified create new user else update the one in the Database */
if (null === ($id = $user->getUserId())) {
unset($data['userid']);
$this->adapter->insert($data);
return true;
} else {
$this->adapter->update($data, array('userid = ?' => $id));
return true;
}
}
/**
* finds a user from Database based on ID and returns a User object located
* in memory
*
* @param int $id
*
* @throws \InvalidArgumentException
* @return User
*/
public function findById($id)
{
$result = $this->adapter->find($id);
if (0 == count($result)) {
throw new \InvalidArgumentException("User #$id not found");
}
$row = $result->current();
return $this->mapObject($row);
}
/**
* fetches an array from Database and returns an array of User objects
* located in memory
*
* @return array
*/
public function findAll()
{
$resultSet = $this->adapter->findAll();
$entries = array();
foreach ($resultSet as $row) {
$entries[] = $this->mapObject($row);
}
return $entries;
}
/**
* Maps a table row to an object
*
* @param array $row
*
* @return User
*/
protected function mapObject(array $row)
{
$entry = new User();
$entry->setUserID($row['userid']);
$entry->setUsername($row['username']);
$entry->setEmail($row['email']);
return $entry;
}
}
|
Test¶
Tests/DataMapperTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | <?php
namespace DesignPatterns\Structural\DataMapper\Tests;
use DesignPatterns\Structural\DataMapper\UserMapper;
use DesignPatterns\Structural\DataMapper\User;
/**
* UserMapperTest tests the datamapper pattern
*/
class DataMapperTest extends \PHPUnit_Framework_TestCase
{
/**
* @var UserMapper
*/
protected $mapper;
/**
* @var DBAL
*/
protected $dbal;
protected function setUp()
{
$this->dbal = $this->getMockBuilder('DesignPatterns\Structural\DataMapper\DBAL')
->disableAutoload()
->setMethods(array('insert', 'update', 'find', 'findAll'))
->getMock();
$this->mapper = new UserMapper($this->dbal);
}
public function getNewUser()
{
return array(array(new User(null, 'Odysseus', 'Odysseus@ithaca.gr')));
}
public function getExistingUser()
{
return array(array(new User(1, 'Odysseus', 'Odysseus@ithaca.gr')));
}
/**
* @dataProvider getNewUser
*/
public function testPersistNew(User $user)
{
$this->dbal->expects($this->once())
->method('insert');
$this->mapper->save($user);
}
/**
* @dataProvider getExistingUser
*/
public function testPersistExisting(User $user)
{
$this->dbal->expects($this->once())
->method('update');
$this->mapper->save($user);
}
/**
* @dataProvider getExistingUser
*/
public function testRestoreOne(User $existing)
{
$row = array(
'userid' => 1,
'username' => 'Odysseus',
'email' => 'Odysseus@ithaca.gr'
);
$rows = new \ArrayIterator(array($row));
$this->dbal->expects($this->once())
->method('find')
->with(1)
->will($this->returnValue($rows));
$user = $this->mapper->findById(1);
$this->assertEquals($existing, $user);
}
/**
* @dataProvider getExistingUser
*/
public function testRestoreMulti(User $existing)
{
$rows = array(array('userid' => 1, 'username' => 'Odysseus', 'email' => 'Odysseus@ithaca.gr'));
$this->dbal->expects($this->once())
->method('findAll')
->will($this->returnValue($rows));
$user = $this->mapper->findAll();
$this->assertEquals(array($existing), $user);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage User #404 not found
*/
public function testNotFound()
{
$this->dbal->expects($this->once())
->method('find')
->with(404)
->will($this->returnValue(array()));
$user = $this->mapper->findById(404);
}
}
|
Decorator¶
Purpose¶
To dynamically add new functionality to class instances.
Examples¶
- Zend Framework: decorators for
Zend_Form_Element
instances - Web Service Layer: Decorators JSON and XML for a REST service (in this case, only one of these should be allowed of course)
UML Diagram¶

Code¶
You can also find these code on GitHub
RendererInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <?php
namespace DesignPatterns\Structural\Decorator;
/**
* Class RendererInterface
*/
interface RendererInterface
{
/**
* render data
*
* @return mixed
*/
public function renderData();
}
|
Webservice.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <?php
namespace DesignPatterns\Structural\Decorator;
/**
* Class Webservice
*/
class Webservice implements RendererInterface
{
/**
* @var mixed
*/
protected $data;
/**
* @param mixed $data
*/
public function __construct($data)
{
$this->data = $data;
}
/**
* @return string
*/
public function renderData()
{
return $this->data;
}
}
|
Decorator.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <?php
namespace DesignPatterns\Structural\Decorator;
/**
* the Decorator MUST implement the RendererInterface contract, this is the key-feature
* of this design pattern. If not, this is no longer a Decorator but just a dumb
* wrapper.
*/
/**
* class Decorator
*/
abstract class Decorator implements RendererInterface
{
/**
* @var RendererInterface
*/
protected $wrapped;
/**
* You must type-hint the wrapped component :
* It ensures you can call renderData() in the subclasses !
*
* @param RendererInterface $wrappable
*/
public function __construct(RendererInterface $wrappable)
{
$this->wrapped = $wrappable;
}
}
|
RenderInXml.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?php
namespace DesignPatterns\Structural\Decorator;
/**
* Class RenderInXml
*/
class RenderInXml extends Decorator
{
/**
* render data as XML
*
* @return mixed|string
*/
public function renderData()
{
$output = $this->wrapped->renderData();
// do some fancy conversion to xml from array ...
$doc = new \DOMDocument();
foreach ($output as $key => $val) {
$doc->appendChild($doc->createElement($key, $val));
}
return $doc->saveXML();
}
}
|
RenderInJson.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?php
namespace DesignPatterns\Structural\Decorator;
/**
* Class RenderInJson
*/
class RenderInJson extends Decorator
{
/**
* render data as JSON
*
* @return mixed|string
*/
public function renderData()
{
$output = $this->wrapped->renderData();
return json_encode($output);
}
}
|
Test¶
Tests/DecoratorTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | <?php
namespace DesignPatterns\Structural\Decorator\Tests;
use DesignPatterns\Structural\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
$xml = '<?xml version="1.0"?><foo>bar</foo>';
$this->assertXmlStringEqualsXmlString($xml, $service->renderData());
}
/**
* The first key-point of this pattern :
*/
public function testDecoratorMustImplementsRenderer()
{
$className = 'DesignPatterns\Structural\Decorator\Decorator';
$interfaceName = 'DesignPatterns\Structural\Decorator\RendererInterface';
$this->assertTrue(is_subclass_of($className, $interfaceName));
}
/**
* Second key-point of this pattern : the decorator is type-hinted
*
* @expectedException \PHPUnit_Framework_Error
*/
public function testDecoratorTypeHinted()
{
$this->getMockForAbstractClass('DesignPatterns\Structural\Decorator\Decorator', array(new \stdClass()));
}
/**
* The decorator implements and wraps the same interface
*/
public function testDecoratorOnlyAcceptRenderer()
{
$mock = $this->getMock('DesignPatterns\Structural\Decorator\RendererInterface');
$dec = $this->getMockForAbstractClass('DesignPatterns\Structural\Decorator\Decorator', array($mock));
$this->assertNotNull($dec);
}
}
|
Dependency Injection¶
Purpose¶
To implement a loosely coupled architecture in order to get better testable, maintainable and extendable code.
Usage¶
Configuration gets injected and Connection
will get all that it
needs from $config
. Without DI, the configuration would be created
directly in Connection
, which is not very good for testing and
extending Connection
.
Notice we are following Inversion of control principle in Connection
by asking $config
to implement Parameters
interface. This
decouples our components. We don’t care where the source of information
comes from, we only care that $config
has certain methods to
retrieve that information. Read more about Inversion of control
here.
Examples¶
- The Doctrine2 ORM uses dependency injection e.g. for configuration
that is injected into a
Connection
object. For testing purposes, one can easily create a mock object of the configuration and inject that into theConnection
object - Symfony and Zend Framework 2 already have containers for DI that create objects via a configuration array and inject them where needed (i.e. in Controllers)
UML Diagram¶

Code¶
You can also find these code on GitHub
AbstractConfig.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?php
namespace DesignPatterns\Structural\DependencyInjection;
/**
* class AbstractConfig
*/
abstract class AbstractConfig
{
/**
* @var Storage of data
*/
protected $storage;
public function __construct($storage)
{
$this->storage = $storage;
}
}
|
Parameters.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <?php
namespace DesignPatterns\Structural\DependencyInjection;
/**
* Parameters interface
*/
interface Parameters
{
/**
* Get parameter
*
* @param string|int $key
*
* @return mixed
*/
public function get($key);
/**
* Set parameter
*
* @param string|int $key
* @param mixed $value
*/
public function set($key, $value);
}
|
ArrayConfig.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <?php
namespace DesignPatterns\Structural\DependencyInjection;
/**
* class ArrayConfig
*
* uses array as data source
*/
class ArrayConfig extends AbstractConfig implements Parameters
{
/**
* Get parameter
*
* @param string|int $key
* @param null $default
* @return mixed
*/
public function get($key, $default = null)
{
if (isset($this->storage[$key])) {
return $this->storage[$key];
}
return $default;
}
/**
* Set parameter
*
* @param string|int $key
* @param mixed $value
*/
public function set($key, $value)
{
$this->storage[$key] = $value;
}
}
|
Connection.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <?php
namespace DesignPatterns\Structural\DependencyInjection;
/**
* Class Connection
*/
class Connection
{
/**
* @var Configuration
*/
protected $configuration;
/**
* @var Currently connected host
*/
protected $host;
/**
* @param Parameters $config
*/
public function __construct(Parameters $config)
{
$this->configuration = $config;
}
/**
* connection using the injected config
*/
public function connect()
{
$host = $this->configuration->get('host');
// connection to host, authentication etc...
//if connected
$this->host = $host;
}
/*
* Get currently connected host
*
* @return string
*/
public function getHost()
{
return $this->host;
}
}
|
Test¶
Tests/DependencyInjectionTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <?php
namespace DesignPatterns\Structural\DependencyInjection\Tests;
use DesignPatterns\Structural\DependencyInjection\ArrayConfig;
use DesignPatterns\Structural\DependencyInjection\Connection;
class DependencyInjectionTest extends \PHPUnit_Framework_TestCase
{
protected $config;
protected $source;
public function setUp()
{
$this->source = include 'config.php';
$this->config = new ArrayConfig($this->source);
}
public function testDependencyInjection()
{
$connection = new Connection($this->config);
$connection->connect();
$this->assertEquals($this->source['host'], $connection->getHost());
}
}
|
Tests/config.php
1 2 3 | <?php
return array('host' => 'github.com');
|
Facade¶
Purpose¶
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.
- A facade does not forbid you the access to the sub-system
- 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 a Factory as argument.
UML Diagram¶

Code¶
You can also find these code on GitHub
Facade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <?php
namespace DesignPatterns\Structural\Facade;
/**
*
*
*/
class Facade
{
/**
* @var OsInterface
*/
protected $os;
/**
* @var BiosInterface
*/
protected $bios;
/**
* This is the perfect time to use a dependency injection container
* to create an instance of this class
*
* @param BiosInterface $bios
* @param OsInterface $os
*/
public function __construct(BiosInterface $bios, OsInterface $os)
{
$this->bios = $bios;
$this->os = $os;
}
/**
* turn on the system
*/
public function turnOn()
{
$this->bios->execute();
$this->bios->waitForKeyPress();
$this->bios->launch($this->os);
}
/**
* turn off the system
*/
public function turnOff()
{
$this->os->halt();
$this->bios->powerDown();
}
}
|
OsInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?php
namespace DesignPatterns\Structural\Facade;
/**
* Class OsInterface
*/
interface OsInterface
{
/**
* halt the OS
*/
public function halt();
}
|
BiosInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <?php
namespace DesignPatterns\Structural\Facade;
/**
* Class BiosInterface
*/
interface BiosInterface
{
/**
* execute the BIOS
*/
public function execute();
/**
* wait for halt
*/
public function waitForKeyPress();
/**
* launches the OS
*
* @param OsInterface $os
*/
public function launch(OsInterface $os);
/**
* power down BIOS
*/
public function powerDown();
}
|
Test¶
Tests/FacadeTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <?php
namespace DesignPatterns\Structural\Facade\Tests;
use DesignPatterns\Structural\Facade\Facade as Computer;
use DesignPatterns\Structural\Facade\OsInterface;
/**
* FacadeTest shows example of facades
*/
class FacadeTest extends \PHPUnit_Framework_TestCase
{
public function getComputer()
{
$bios = $this->getMockBuilder('DesignPatterns\Structural\Facade\BiosInterface')
->setMethods(array('launch', 'execute', 'waitForKeyPress'))
->disableAutoload()
->getMock();
$operatingSys = $this->getMockBuilder('DesignPatterns\Structural\Facade\OsInterface')
->setMethods(array('getName'))
->disableAutoload()
->getMock();
$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, OsInterface $os)
{
// interface is simpler :
$facade->turnOn();
// but I can access to lower component
$this->assertEquals('Linux', $os->getName());
}
}
|
Fluent Interface¶
Purpose¶
To write code that is easy readable just like sentences in a natural language (like English).
Examples¶
- Doctrine2’s QueryBuilder works something like that example class below
- PHPUnit uses fluent interfaces to build mock objects
- Yii Framework: CDbCommand and CActiveRecord use this pattern, too
UML Diagram¶

Code¶
You can also find these code on GitHub
Sql.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | <?php
namespace DesignPatterns\Structural\FluentInterface;
/**
* class SQL
*/
class Sql
{
/**
* @var array
*/
protected $fields = array();
/**
* @var array
*/
protected $from = array();
/**
* @var array
*/
protected $where = array();
/**
* adds select fields
*
* @param array $fields
*
* @return SQL
*/
public function select(array $fields = array())
{
$this->fields = $fields;
return $this;
}
/**
* adds a FROM clause
*
* @param string $table
* @param string $alias
*
* @return SQL
*/
public function from($table, $alias)
{
$this->from[] = $table . ' AS ' . $alias;
return $this;
}
/**
* adds a WHERE condition
*
* @param string $condition
*
* @return SQL
*/
public function where($condition)
{
$this->where[] = $condition;
return $this;
}
/**
* Gets the query, just an example of building a query,
* no check on consistency
*
* @return string
*/
public function getQuery()
{
return 'SELECT ' . implode(',', $this->fields)
. ' FROM ' . implode(',', $this->from)
. ' WHERE ' . implode(' AND ', $this->where);
}
}
|
Test¶
Tests/FluentInterfaceTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?php
namespace DesignPatterns\Structural\FluentInterface\Tests;
use DesignPatterns\Structural\FluentInterface\Sql;
/**
* FluentInterfaceTest tests the fluent interface SQL
*/
class FluentInterfaceTest extends \PHPUnit_Framework_TestCase
{
public function testBuildSQL()
{
$instance = new Sql();
$query = $instance->select(array('foo', 'bar'))
->from('foobar', 'f')
->where('f.bar = ?')
->getQuery();
$this->assertEquals('SELECT foo,bar FROM foobar AS f WHERE f.bar = ?', $query);
}
}
|
Proxy¶
Purpose¶
To interface to anything that is expensive or impossible to duplicate.
Examples¶
- Doctrine2 uses proxies to implement framework magic (e.g. lazy initialization) in them, while the user still works with his own entity classes and will never use nor touch the proxies
UML Diagram¶

Code¶
You can also find these code on GitHub
Record.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | <?php
namespace DesignPatterns\Structural\Proxy;
/**
* class Record
*/
class Record
{
/**
* @var array|null
*/
protected $data;
/**
* @param null $data
*/
public function __construct($data = null)
{
$this->data = (array) $data;
}
/**
* magic setter
*
* @param string $name
* @param mixed $value
*
* @return void
*/
public function __set($name, $value)
{
$this->data[(string) $name] = $value;
}
/**
* magic getter
*
* @param string $name
*
* @return mixed|null
*/
public function __get($name)
{
if (array_key_exists($name, $this->data)) {
return $this->data[(string) $name];
} else {
return null;
}
}
}
|
RecordProxy.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | <?php
namespace DesignPatterns\Structural\Proxy;
/**
* Class RecordProxy
*/
class RecordProxy extends Record
{
/**
* @var bool
*/
protected $isDirty = false;
/**
* @var bool
*/
protected $isInitialized = false;
/**
* @param array $data
*/
public function __construct($data)
{
parent::__construct($data);
// when the record has data, mark it as initialized
// since Record will hold our business logic, we don't want to
// implement this behaviour there, but instead in a new proxy class
// that extends the Record class
if (null !== $data) {
$this->isInitialized = true;
$this->isDirty = true;
}
}
/**
* magic setter
*
* @param string $name
* @param mixed $value
*
* @return void
*/
public function __set($name, $value)
{
$this->isDirty = true;
parent::__set($name, $value);
}
}
|
Test¶
Registry¶
Purpose¶
To implement a central storage for objects often used throughout the application, is typically implemented using an abstract class with only static methods (or using the Singleton pattern)
Examples¶
- Zend Framework 1:
Zend_Registry
holds the application’s logger object, front controller etc. - Yii Framework:
CWebApplication
holds all the application components, such asCWebUser
,CUrlManager
, etc.
UML Diagram¶

Code¶
You can also find these code on GitHub
Registry.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | <?php
namespace DesignPatterns\Structural\Registry;
/**
* class Registry
*/
abstract class Registry
{
const LOGGER = 'logger';
/**
* @var array
*/
protected static $storedValues = array();
/**
* sets a value
*
* @param string $key
* @param mixed $value
*
* @static
* @return void
*/
public static function set($key, $value)
{
self::$storedValues[$key] = $value;
}
/**
* gets a value from the registry
*
* @param string $key
*
* @static
* @return mixed
*/
public static function get($key)
{
return self::$storedValues[$key];
}
// typically there would be methods to check if a key has already been registered and so on ...
}
|
Test¶
Tests/RegistryTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?php
namespace DesignPatterns\Structural\Registry\Tests;
use DesignPatterns\Structural\Registry\Registry;
class RegistryTest extends \PHPUnit_Framework_TestCase
{
public function testSetAndGetLogger()
{
Registry::set(Registry::LOGGER, new \StdClass());
$logger = Registry::get(Registry::LOGGER);
$this->assertInstanceOf('StdClass', $logger);
}
}
|
Behavioral¶
In software engineering, behavioral design patterns are design patterns that identify common communication patterns between objects and realize these patterns. By doing so, these patterns increase flexibility in carrying out this communication.
Chain Of Responsibilities¶
Purpose:¶
To build a chain of objects to handle a call in sequential order. If one object cannot handle a call, it delegates the call to the next in the chain and so forth.
Examples:¶
- logging framework, where each chain element decides autonomously what to do with a log message
- a Spam filter
- Caching: first object is an instance of e.g. a Memcached Interface, if that “misses” it delegates the call to the database interface
- Yii Framework: CFilterChain is a chain of controller action filters. the executing point is passed from one filter to the next along the chain, and only if all filters say “yes”, the action can be invoked at last.
UML Diagram¶

Code¶
You can also find these code on GitHub
Request.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?php
namespace DesignPatterns\Behavioral\ChainOfResponsibilities;
/**
* Request is a request which goes through the chain of responsibilities.
*
* About the request: Sometimes, you don't need an object, just an integer or
* an array. But in this case of a full example, I've made a class to illustrate
* this important idea in the CoR (Chain of Responsibilities). In the real world,
* I recommend to always use a class, even a \stdClass if you want, it proves
* to be more adaptive because a single handler doesn't know much about the
* outside world and it is more difficult if, one day, you want to add some
* criterion in a decision process.
*/
class Request
{
// getter and setter but I don't want to generate too much noise in handlers
}
|
Handler.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | <?php
namespace DesignPatterns\Behavioral\ChainOfResponsibilities;
/**
* Handler is a generic handler in the chain of responsibilities
*
* Yes you could have a lighter CoR with a simpler handler but if you want your CoR
* to be extendable and decoupled, it's a better idea to do things like that in real
* situations. Usually, a CoR is meant to be changed everytime and evolves, that's
* why we slice the workflow in little bits of code.
*/
abstract class Handler
{
/**
* @var Handler
*/
private $successor = null;
/**
* Append a responsibility to the end of chain
*
* A prepend method could be done with the same spirit
*
* You could also send the successor in the constructor but in PHP that is a
* bad idea because you have to remove the type-hint of the parameter because
* the last handler has a null successor.
*
* And if you override the constructor, that Handler can no longer have a
* successor. One solution is to provide a NullObject (see pattern).
* It is more preferable to keep the constructor "free" to inject services
* you need with the DiC of symfony2 for example.
*
* @param Handler $handler
*/
final public function append(Handler $handler)
{
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. Besides, the returned
* boolean value indicates you if the request have been processed or not.
*
* @param Request $req
*
* @return bool
*/
final public function handle(Request $req)
{
$req->forDebugOnly = get_called_class();
$processed = $this->processing($req);
if (!$processed) {
// the request has not been processed by this handler => see the next
if (!is_null($this->successor)) {
$processed = $this->successor->handle($req);
}
}
return $processed;
}
/**
* Each concrete handler has to implement the processing of the request
*
* @param Request $req
*
* @return bool true if the request has been processed
*/
abstract protected function processing(Request $req);
}
|
Responsible/SlowStorage.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | <?php
namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
use DesignPatterns\Behavioral\ChainOfResponsibilities\Request;
/**
* This is mostly the same code as FastStorage but in fact, it may greatly differs
*
* One important fact about CoR: each item in the chain MUST NOT assume its position
* in the chain. A CoR is not responsible if the request is not handled UNLESS
* you make an "ExceptionHandler" which throws execption if the request goes there.
*
* To be really extendable, each handler doesn't know if there is something after it.
*
*/
class SlowStorage extends Handler
{
/**
* @var array
*/
protected $data = array();
/**
* @param array $data
*/
public function __construct($data = array())
{
$this->data = $data;
}
protected function processing(Request $req)
{
if ('get' === $req->verb) {
if (array_key_exists($req->key, $this->data)) {
$req->response = $this->data[$req->key];
return true;
}
}
return false;
}
}
|
Responsible/FastStorage.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | <?php
namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
use DesignPatterns\Behavioral\ChainOfResponsibilities\Request;
/**
* Class FastStorage
*/
class FastStorage extends Handler
{
/**
* @var array
*/
protected $data = array();
/**
* @param array $data
*/
public function __construct($data = array())
{
$this->data = $data;
}
protected function processing(Request $req)
{
if ('get' === $req->verb) {
if (array_key_exists($req->key, $this->data)) {
// the handler IS responsible and then processes the request
$req->response = $this->data[$req->key];
// instead of returning true, I could return the value but it proves
// to be a bad idea. What if the value IS "false" ?
return true;
}
}
return false;
}
}
|
Test¶
Tests/ChainTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | <?php
namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Tests;
use DesignPatterns\Behavioral\ChainOfResponsibilities\Request;
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\FastStorage;
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowStorage;
use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
/**
* ChainTest tests the CoR
*/
class ChainTest extends \PHPUnit_Framework_TestCase
{
/**
* @var FastStorage
*/
protected $chain;
protected function setUp()
{
$this->chain = new FastStorage(array('bar' => 'baz'));
$this->chain->append(new SlowStorage(array('bar' => 'baz', 'foo' => 'bar')));
}
public function makeRequest()
{
$request = new Request();
$request->verb = 'get';
return array(
array($request)
);
}
/**
* @dataProvider makeRequest
*/
public function testFastStorage($request)
{
$request->key = 'bar';
$ret = $this->chain->handle($request);
$this->assertTrue($ret);
$this->assertObjectHasAttribute('response', $request);
$this->assertEquals('baz', $request->response);
// despite both handle owns the 'bar' key, the FastStorage is responding first
$className = 'DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\FastStorage';
$this->assertEquals($className, $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
$className = 'DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowStorage';
$this->assertEquals($className, $request->forDebugOnly);
}
/**
* @dataProvider makeRequest
*/
public function testFailure($request)
{
$request->key = 'kurukuku';
$ret = $this->chain->handle($request);
$this->assertFalse($ret);
// the last responsible :
$className = 'DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowStorage';
$this->assertEquals($className, $request->forDebugOnly);
}
}
|
Command¶
Purpose¶
To encapsulate invocation and decoupling.
We have an Invoker and a Receiver. This pattern uses a “Command” to delegate the method call against the Receiver and presents the same method “execute”. Therefore, the Invoker just knows to call “execute” to process the Command of the client. The Receiver is decoupled from the Invoker.
The second aspect of this pattern is the undo(), which undoes the method execute(). Command can also be aggregated to combine more complex commands with minimum copy-paste and relying on composition over inheritance.
Examples¶
- A text editor : all events are Command which can be undone, stacked and saved.
- Symfony2: SF2 Commands that can be run from the CLI are built with just the Command pattern in mind
- big CLI tools use subcommands to distribute various tasks and pack them in “modules”, each of these can be implemented with the Command pattern (e.g. vagrant)
UML Diagram¶

Code¶
You can also find these code on GitHub
CommandInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php
namespace DesignPatterns\Behavioral\Command;
/**
* class CommandInterface
*/
interface CommandInterface
{
/**
* this is the most important method in the Command pattern,
* The Receiver goes in the constructor.
*/
public function execute();
}
|
HelloCommand.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <?php
namespace DesignPatterns\Behavioral\Command;
/**
* This concrete command calls "print" on the Receiver, but an external
* invoker just know he can call "execute"
*/
class HelloCommand implements CommandInterface
{
/**
* @var Receiver
*/
protected $output;
/**
* Each concrete command is builded with different receivers.
* Can be one, many, none or even other Command in parameters
*
* @param Receiver $console
*/
public function __construct(Receiver $console)
{
$this->output = $console;
}
/**
* execute and output "Hello World"
*/
public function execute()
{
// sometimes, there is no receiver and this is the command which
// does all the work
$this->output->write('Hello World');
}
}
|
Receiver.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?php
namespace DesignPatterns\Behavioral\Command;
/**
* Receiver is specific service with its own contract and can be only concrete
*/
class Receiver
{
/**
* @param string $str
*/
public function write($str)
{
echo $str;
}
}
|
Invoker.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <?php
namespace DesignPatterns\Behavioral\Command;
/**
* Invoker is using the command given to it.
* Example : an Application in SF2
*/
class Invoker
{
/**
* @var CommandInterface
*/
protected $command;
/**
* In the invoker we find this kind of method for subscribing the command.
* There can be also a stack, a list, a fixed set...
*
* @param CommandInterface $cmd
*/
public function setCommand(CommandInterface $cmd)
{
$this->command = $cmd;
}
/**
* executes the command
*/
public function run()
{
// here is a key feature of the invoker
// the invoker is the same whatever is the command
$this->command->execute();
}
}
|
Test¶
Tests/CommandTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <?php
namespace DesignPatterns\Behavioral\Command\Tests;
use DesignPatterns\Behavioral\Command\Invoker;
use DesignPatterns\Behavioral\Command\Receiver;
use DesignPatterns\Behavioral\Command\HelloCommand;
/**
* CommandTest has the role of the Client in the Command Pattern
*/
class CommandTest extends \PHPUnit_Framework_TestCase
{
/**
* @var Invoker
*/
protected $invoker;
/**
* @var Receiver
*/
protected $receiver;
protected function setUp()
{
$this->invoker = new Invoker();
$this->receiver = new Receiver();
}
public function testInvocation()
{
$this->invoker->setCommand(new HelloCommand($this->receiver));
$this->expectOutputString('Hello World');
$this->invoker->run();
}
}
|
Iterator¶
Purpose¶
To make an object iterable and to make it appear like a collection of objects.
Examples¶
- to process a file line by line by just running over all lines (which have an object representation) for a file (which of course is an object, too)
Note¶
Standard PHP Library (SPL) defines an interface Iterator which is best
suited for this! Often you would want to implement the Countable
interface too, to allow count($object)
on your iterable object
UML Diagram¶

Code¶
You can also find these code on GitHub
Book.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <?php
namespace DesignPatterns\Behavioral\Iterator;
class Book
{
private $author;
private $title;
public function __construct($title, $author)
{
$this->author = $author;
$this->title = $title;
}
public function getAuthor()
{
return $this->author;
}
public function getTitle()
{
return $this->title;
}
public function getAuthorAndTitle()
{
return $this->getTitle() . ' by ' . $this->getAuthor();
}
}
|
BookList.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | <?php
namespace DesignPatterns\Behavioral\Iterator;
class BookList implements \Countable
{
private $books;
public function getBook($bookNumberToGet)
{
if ((int)$bookNumberToGet <= $this->count()) {
return $this->books[$bookNumberToGet];
}
return null;
}
public function addBook(Book $book)
{
$this->books[] = $book;
return $this->count();
}
public function removeBook(Book $bookToRemove)
{
foreach ($this->books as $key => $book) {
/** @var Book $book */
if ($book->getAuthorAndTitle() === $bookToRemove->getAuthorAndTitle()) {
unset($this->books[$key]);
}
}
return $this->count();
}
public function count()
{
return count($this->books);
}
}
|
BookListIterator.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | <?php
namespace DesignPatterns\Behavioral\Iterator;
class BookListIterator implements \Iterator
{
/**
* @var BookList
*/
protected $bookList;
/**
* @var int
*/
protected $currentBook = 0;
public function __construct(BookList $bookList)
{
$this->bookList = $bookList;
}
/**
* Return the current book
* @link http://php.net/manual/en/iterator.current.php
* @return Book Can return any type.
*/
public function current()
{
return $this->bookList->getBook($this->currentBook);
}
/**
* (PHP 5 >= 5.0.0)<br/>
* Move forward to next element
* @link http://php.net/manual/en/iterator.next.php
* @return void Any returned value is ignored.
*/
public function next()
{
$this->currentBook++;
}
/**
* (PHP 5 >= 5.0.0)<br/>
* Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
* @return mixed scalar on success, or null on failure.
*/
public function key()
{
return $this->currentBook;
}
/**
* (PHP 5 >= 5.0.0)<br/>
* Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
* @return boolean The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
*/
public function valid()
{
return $this->currentBook < $this->bookList->count();
}
/**
* (PHP 5 >= 5.0.0)<br/>
* Rewind the Iterator to the first element
* @link http://php.net/manual/en/iterator.rewind.php
* @return void Any returned value is ignored.
*/
public function rewind()
{
$this->currentBook = 0;
}
}
|
BookListReverseIterator.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?php
namespace DesignPatterns\Behavioral\Iterator;
class BookListReverseIterator extends BookListIterator
{
public function __construct(BookList $bookList)
{
$this->bookList = $bookList;
$this->currentBook = $this->bookList->count() - 1;
}
public function next()
{
$this->currentBook--;
}
public function valid()
{
return 0 <= $this->currentBook;
}
}
|
Test¶
Tests/IteratorTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | <?php
namespace DesignPatterns\Behavioral\Iterator\Tests;
use DesignPatterns\Behavioral\Iterator\Book;
use DesignPatterns\Behavioral\Iterator\BookList;
use DesignPatterns\Behavioral\Iterator\BookListIterator;
use DesignPatterns\Behavioral\Iterator\BookListReverseIterator;
class IteratorTest extends \PHPUnit_Framework_TestCase
{
/**
* @var BookList
*/
protected $bookList;
protected function setUp()
{
$this->bookList = new BookList();
$this->bookList->addBook(new Book('Learning PHP Design Patterns', 'William Sanders'));
$this->bookList->addBook(new Book('Professional Php Design Patterns', 'Aaron Saray'));
$this->bookList->addBook(new Book('Clean Code', 'Robert C. Martin'));
}
public function expectedAuthors()
{
return array(
array(
array(
'Learning PHP Design Patterns by William Sanders',
'Professional Php Design Patterns by Aaron Saray',
'Clean Code by Robert C. Martin'
)
),
);
}
/**
* @dataProvider expectedAuthors
*/
public function testUseAIteratorAndValidateAuthors($expected)
{
$iterator = new BookListIterator($this->bookList);
while ($iterator->valid()) {
$expectedBook = array_shift($expected);
$this->assertEquals($expectedBook, $iterator->current()->getAuthorAndTitle());
$iterator->next();
}
}
/**
* @dataProvider expectedAuthors
*/
public function testUseAReverseIteratorAndValidateAuthors($expected)
{
$iterator = new BookListReverseIterator($this->bookList);
while ($iterator->valid()) {
$expectedBook = array_pop($expected);
$this->assertEquals($expectedBook, $iterator->current()->getAuthorAndTitle());
$iterator->next();
}
}
/**
* Test BookList Remove
*/
public function testBookRemove()
{
$this->bookList->removeBook($this->bookList->getBook(0));
$this->assertEquals($this->bookList->count(), 2);
}
}
|
Mediator¶
Purpose¶
This pattern provides an easy to decouple many components working together. It is a good alternative over Observer IF you have a “central intelligence”, like a controller (but not in the sense of the MVC).
All components (called Colleague) are only coupled to the MediatorInterface and it is a good thing because in OOP, one good friend is better than many. This is the key-feature of this pattern.
UML Diagram¶

Code¶
You can also find these code on GitHub
MediatorInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <?php
namespace DesignPatterns\Behavioral\Mediator;
/**
* MediatorInterface is a contract for the Mediator
* This interface is not mandatory but it is better for LSP concerns
*/
interface MediatorInterface
{
/**
* sends the response
*
* @param string $content
*/
public function sendResponse($content);
/**
* makes a request
*/
public function makeRequest();
/**
* queries the DB
*/
public function queryDb();
}
|
Mediator.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | <?php
namespace DesignPatterns\Behavioral\Mediator;
use DesignPatterns\Behavioral\Mediator\Subsystem;
/**
* Mediator is the concrete Mediator for this design pattern.
* In this example, I have made a "Hello World" with the Mediator Pattern.
*/
class Mediator implements MediatorInterface
{
/**
* @var Subsystem\Server
*/
protected $server;
/**
* @var Subsystem\Database
*/
protected $database;
/**
* @var Subsystem\Client
*/
protected $client;
/**
* @param Subsystem\Database $db
* @param Subsystem\Client $cl
* @param Subsystem\Server $srv
*/
public function setColleague(Subsystem\Database $db, Subsystem\Client $cl, Subsystem\Server $srv)
{
$this->database = $db;
$this->server = $srv;
$this->client = $cl;
}
/**
* make request
*/
public function makeRequest()
{
$this->server->process();
}
/**
* query db
* @return mixed
*/
public function queryDb()
{
return $this->database->getData();
}
/**
* send response
*
* @param string $content
*/
public function sendResponse($content)
{
$this->client->output($content);
}
}
|
Colleague.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <?php
namespace DesignPatterns\Behavioral\Mediator;
/**
* Colleague is an abstract colleague who works together but he only knows
* the Mediator, not other colleague.
*/
abstract class Colleague
{
/**
* this ensures no change in subclasses
*
* @var MediatorInterface
*/
private $mediator;
/**
* @param MediatorInterface $medium
*/
public function __construct(MediatorInterface $medium)
{
// in this way, we are sure the concrete colleague knows the mediator
$this->mediator = $medium;
}
// for subclasses
protected function getMediator()
{
return $this->mediator;
}
}
|
Subsystem/Client.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?php
namespace DesignPatterns\Behavioral\Mediator\Subsystem;
use DesignPatterns\Behavioral\Mediator\Colleague;
/**
* Client is a client that make request et get response
*/
class Client extends Colleague
{
/**
* request
*/
public function request()
{
$this->getMediator()->makeRequest();
}
/**
* output content
*
* @param string $content
*/
public function output($content)
{
echo $content;
}
}
|
Subsystem/Database.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?php
namespace DesignPatterns\Behavioral\Mediator\Subsystem;
use DesignPatterns\Behavioral\Mediator\Colleague;
/**
* Database is a database service
*/
class Database extends Colleague
{
/**
* @return string
*/
public function getData()
{
return "World";
}
}
|
Subsystem/Server.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <?php
namespace DesignPatterns\Behavioral\Mediator\Subsystem;
use DesignPatterns\Behavioral\Mediator\Colleague;
/**
* Server serves responses
*/
class Server extends Colleague
{
/**
* process on server
*/
public function process()
{
$data = $this->getMediator()->queryDb();
$this->getMediator()->sendResponse("Hello $data");
}
}
|
Test¶
Tests/MediatorTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <?php
namespace DesignPatterns\Tests\Mediator\Tests;
use DesignPatterns\Behavioral\Mediator\Mediator;
use DesignPatterns\Behavioral\Mediator\Subsystem\Database;
use DesignPatterns\Behavioral\Mediator\Subsystem\Client;
use DesignPatterns\Behavioral\Mediator\Subsystem\Server;
/**
* MediatorTest tests hello world
*/
class MediatorTest extends \PHPUnit_Framework_TestCase
{
protected $client;
protected function setUp()
{
$media = new Mediator();
$this->client = new Client($media);
$media->setColleague(new Database($media), $this->client, new Server($media));
}
public function testOutputHelloWorld()
{
// testing if Hello World is output :
$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.
}
}
|
Memento¶
Purpose¶
Provide the ability to restore an object to its previous state (undo via rollback).
The memento pattern is implemented with three objects: the originator, a caretaker and a memento. The originator is some object that has an internal state. The caretaker is going to do something to the originator, but wants to be able to undo the change. The caretaker first asks the originator for a memento object. Then it does whatever operation (or sequence of operations) it was going to do. To roll back to the state before the operations, it returns the memento object to the originator. The memento object itself is an opaque object (one which the caretaker cannot, or should not, change). When using this pattern, care should be taken if the originator may change other objects or resources - the memento pattern operates on a single object.
Examples¶
- The seed of a pseudorandom number generator
- The state in a finite state machine
UML Diagram¶

Code¶
You can also find these code on GitHub
Memento.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <?php
namespace DesignPatterns\Behavioral\Memento;
class Memento
{
/* @var mixed */
private $state;
/**
* @param mixed $stateToSave
*/
public function __construct($stateToSave)
{
$this->state = $stateToSave;
}
/**
* @return mixed
*/
public function getState()
{
return $this->state;
}
}
|
Originator.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <?php
namespace DesignPatterns\Behavioral\Memento;
class Originator
{
/* @var mixed */
private $state;
// The class could also contain additional data that is not part of the
// state saved in the memento..
/**
* @param mixed $state
*/
public function setState($state)
{
$this->state = $state;
}
/**
* @return Memento
*/
public function saveToMemento()
{
$state = is_object($this->state) ? clone $this->state : $this->state;
return new Memento($state);
}
public function restoreFromMemento(Memento $memento)
{
$this->state = $memento->getState();
}
}
|
Caretaker.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <?php
namespace DesignPatterns\Behavioral\Memento;
class Caretaker
{
public static function run()
{
/* @var $savedStates Memento[] */
$savedStates = array();
$originator = new Originator();
//Setting state to State1
$originator->setState("State1");
//Setting state to State2
$originator->setState("State2");
//Saving State2 to Memento
$savedStates[] = $originator->saveToMemento();
//Setting state to State3
$originator->setState("State3");
// We can request multiple mementos, and choose which one to roll back to.
// Saving State3 to Memento
$savedStates[] = $originator->saveToMemento();
//Setting state to State4
$originator->setState("State4");
$originator->restoreFromMemento($savedStates[1]);
//State after restoring from Memento: State3
}
}
|
Test¶
Tests/MementoTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | <?php
namespace DesignPatterns\Behavioral\Memento\Tests;
use DesignPatterns\Behavioral\Memento\Originator;
/**
* MementoTest tests the memento pattern
*/
class MementoTest extends \PHPUnit_Framework_TestCase
{
public function testStringState()
{
$originator = new Originator();
$originator->setState("State1");
$this->assertAttributeEquals("State1", "state", $originator);
$originator->setState("State2");
$this->assertAttributeEquals("State2", "state", $originator);
$savedState = $originator->saveToMemento();
$this->assertAttributeEquals("State2", "state", $savedState);
$originator->setState("State3");
$this->assertAttributeEquals("State3", "state", $originator);
$originator->restoreFromMemento($savedState);
$this->assertAttributeEquals("State2", "state", $originator);
}
public function testObjectState()
{
$originator = new Originator();
$foo = new \stdClass();
$foo->data = "foo";
$originator->setState($foo);
$this->assertAttributeEquals($foo, "state", $originator);
$savedState = $originator->saveToMemento();
$this->assertAttributeEquals($foo, "state", $savedState);
$bar = new \stdClass();
$bar->data = "bar";
$originator->setState($bar);
$this->assertAttributeEquals($bar, "state", $originator);
$originator->restoreFromMemento($savedState);
$this->assertAttributeEquals($foo, "state", $originator);
$foo->data = null;
$this->assertAttributeNotEquals($foo, "state", $savedState);
$this->assertAttributeNotEquals($foo, "state", $originator);
}
}
|
Null Object¶
Purpose¶
NullObject is not a GoF design pattern but a schema which appears frequently enough to be considered a pattern. It has the following benefits:
- Client code is simplified
- Reduces the chance of null pointer exceptions
- Fewer conditionals require less test cases
Methods that return an object or null should instead return an object or
NullObject
. NullObject
s simplify boilerplate code such as
if (!is_null($obj)) { $obj->callSomething(); }
to just
$obj->callSomething();
by eliminating the conditional check in
client code.
Examples¶
- Symfony2: null logger of profiler
- Symfony2: null output in Symfony/Console
- null handler in a Chain of Responsibilities pattern
- null command in a Command pattern
UML Diagram¶

Code¶
You can also find these code on GitHub
Service.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <?php
namespace DesignPatterns\Behavioral\NullObject;
/**
* Service is dummy service that uses a logger
*/
class Service
{
/**
* @var LoggerInterface
*/
protected $logger;
/**
* we inject the logger in ctor and it is mandatory
*
* @param LoggerInterface $log
*/
public function __construct(LoggerInterface $log)
{
$this->logger = $log;
}
/**
* do something ...
*/
public function doSomething()
{
// no more check "if (!is_null($this->logger))..." with the NullObject pattern
$this->logger->log('We are in ' . __METHOD__);
// something to do...
}
}
|
LoggerInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?php
namespace DesignPatterns\Behavioral\NullObject;
/**
* LoggerInterface is a contract for logging something
*
* Key feature: NullLogger MUST inherit from this interface like any other Loggers
*/
interface LoggerInterface
{
/**
* @param string $str
*
* @return mixed
*/
public function log($str);
}
|
PrintLogger.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?php
namespace DesignPatterns\Behavioral\NullObject;
/**
* PrintLogger is a logger that prints the log entry to standard output
*/
class PrintLogger implements LoggerInterface
{
/**
* @param string $str
*/
public function log($str)
{
echo $str;
}
}
|
NullLogger.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?php
namespace DesignPatterns\Behavioral\NullObject;
/**
* Performance concerns : ok there is a call for nothing but we spare an "if is_null"
* I didn't run a benchmark but I think it's equivalent.
*
* Key feature : of course this logger MUST implement the same interface (or abstract)
* like the other loggers.
*/
class NullLogger implements LoggerInterface
{
/**
* {@inheritdoc}
*/
public function log($str)
{
// do nothing
}
}
|
Test¶
Tests/LoggerTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <?php
namespace DesignPatterns\Behavioral\NullObject\Tests;
use DesignPatterns\Behavioral\NullObject\NullLogger;
use DesignPatterns\Behavioral\NullObject\Service;
use DesignPatterns\Behavioral\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\Behavioral\NullObject\Service::doSomething');
$service->doSomething();
}
}
|
Observer¶
Purpose¶
To implement a publish/subscribe behaviour to an object, whenever a “Subject” object changes it’s state, the attached “Observers” will be notified. It is used to shorten the amount of coupled objects and uses loose coupling instead.
Examples¶
- a message queue system is observed to show the progress of a job in a GUI
Note¶
PHP already defines two interfaces that can help to implement this pattern: SplObserver and SplSubject.
UML Diagram¶

Code¶
You can also find these code on GitHub
User.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | <?php
namespace DesignPatterns\Behavioral\Observer;
/**
* Observer pattern : The observed object (the subject)
*
* The subject maintains a list of Observers and sends notifications.
*
*/
class User implements \SplSubject
{
/**
* user data
*
* @var array
*/
protected $data = array();
/**
* observers
*
* @var \SplObjectStorage
*/
protected $observers;
public function __construct()
{
$this->observers = new \SplObjectStorage();
}
/**
* attach a new observer
*
* @param \SplObserver $observer
*
* @return void
*/
public function attach(\SplObserver $observer)
{
$this->observers->attach($observer);
}
/**
* detach an observer
*
* @param \SplObserver $observer
*
* @return void
*/
public function detach(\SplObserver $observer)
{
$this->observers->detach($observer);
}
/**
* notify observers
*
* @return void
*/
public function notify()
{
/** @var \SplObserver $observer */
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
/**
* Ideally one would better write setter/getter for all valid attributes and only call notify()
* on attributes that matter when changed
*
* @param string $name
* @param mixed $value
*
* @return void
*/
public function __set($name, $value)
{
$this->data[$name] = $value;
// notify the observers, that user has been updated
$this->notify();
}
}
|
UserObserver.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <?php
namespace DesignPatterns\Behavioral\Observer;
/**
* class UserObserver
*/
class UserObserver implements \SplObserver
{
/**
* This is the only method to implement as an observer.
* It is called by the Subject (usually by SplSubject::notify() )
*
* @param \SplSubject $subject
*/
public function update(\SplSubject $subject)
{
echo get_class($subject) . ' has been updated';
}
}
|
Test¶
Tests/ObserverTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | <?php
namespace DesignPatterns\Behavioral\Observer\Tests;
use DesignPatterns\Behavioral\Observer\UserObserver;
use DesignPatterns\Behavioral\Observer\User;
/**
* ObserverTest tests the Observer pattern
*/
class ObserverTest extends \PHPUnit_Framework_TestCase
{
protected $observer;
protected function setUp()
{
$this->observer = new UserObserver();
}
/**
* Tests the notification
*/
public function testNotify()
{
$this->expectOutputString('DesignPatterns\Behavioral\Observer\User has been updated');
$subject = new User();
$subject->attach($this->observer);
$subject->property = 123;
}
/**
* Tests the subscribing
*/
public function testAttachDetach()
{
$subject = new User();
$reflection = new \ReflectionProperty($subject, 'observers');
$reflection->setAccessible(true);
/** @var \SplObjectStorage $observers */
$observers = $reflection->getValue($subject);
$this->assertInstanceOf('SplObjectStorage', $observers);
$this->assertFalse($observers->contains($this->observer));
$subject->attach($this->observer);
$this->assertTrue($observers->contains($this->observer));
$subject->detach($this->observer);
$this->assertFalse($observers->contains($this->observer));
}
/**
* Tests the update() invocation on a mockup
*/
public function testUpdateCalling()
{
$subject = new User();
$observer = $this->getMock('SplObserver');
$subject->attach($observer);
$observer->expects($this->once())
->method('update')
->with($subject);
$subject->notify();
}
}
|
Specification¶
Purpose¶
Builds a clear specification of business rules, where objects can be
checked against. The composite specification class has one method called
isSatisfiedBy
that returns either true or false depending on whether
the given object satisfies the specification.
UML Diagram¶

Code¶
You can also find these code on GitHub
Item.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <?php
namespace DesignPatterns\Behavioral\Specification;
/**
* An trivial item
*/
class Item
{
protected $price;
/**
* An item must have a price
*
* @param int $price
*/
public function __construct($price)
{
$this->price = $price;
}
/**
* Get the items price
*
* @return int
*/
public function getPrice()
{
return $this->price;
}
}
|
SpecificationInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <?php
namespace DesignPatterns\Behavioral\Specification;
/**
* An interface for a specification
*/
interface SpecificationInterface
{
/**
* A boolean evaluation indicating if the object meets the specification
*
* @param Item $item
*
* @return bool
*/
public function isSatisfiedBy(Item $item);
/**
* Creates a logical AND specification
*
* @param SpecificationInterface $spec
*/
public function plus(SpecificationInterface $spec);
/**
* Creates a logical OR specification
*
* @param SpecificationInterface $spec
*/
public function either(SpecificationInterface $spec);
/**
* Creates a logical not specification
*/
public function not();
}
|
AbstractSpecification.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | <?php
namespace DesignPatterns\Behavioral\Specification;
/**
* An abstract specification allows the creation of wrapped specifications
*/
abstract class AbstractSpecification implements SpecificationInterface
{
/**
* Checks if given item meets all criteria
*
* @param Item $item
*
* @return bool
*/
abstract public function isSatisfiedBy(Item $item);
/**
* Creates a new logical AND specification
*
* @param SpecificationInterface $spec
*
* @return SpecificationInterface
*/
public function plus(SpecificationInterface $spec)
{
return new Plus($this, $spec);
}
/**
* Creates a new logical OR composite specification
*
* @param SpecificationInterface $spec
*
* @return SpecificationInterface
*/
public function either(SpecificationInterface $spec)
{
return new Either($this, $spec);
}
/**
* Creates a new logical NOT specification
*
* @return SpecificationInterface
*/
public function not()
{
return new Not($this);
}
}
|
Either.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <?php
namespace DesignPatterns\Behavioral\Specification;
/**
* A logical OR specification
*/
class Either extends AbstractSpecification
{
protected $left;
protected $right;
/**
* A composite wrapper of two specifications
*
* @param SpecificationInterface $left
* @param SpecificationInterface $right
*/
public function __construct(SpecificationInterface $left, SpecificationInterface $right)
{
$this->left = $left;
$this->right = $right;
}
/**
* Returns the evaluation of both wrapped specifications as a logical OR
*
* @param Item $item
*
* @return bool
*/
public function isSatisfiedBy(Item $item)
{
return $this->left->isSatisfiedBy($item) || $this->right->isSatisfiedBy($item);
}
}
|
PriceSpecification.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | <?php
namespace DesignPatterns\Behavioral\Specification;
/**
* A specification to check an Item is priced between min and max
*/
class PriceSpecification extends AbstractSpecification
{
protected $maxPrice;
protected $minPrice;
/**
* Sets the optional maximum price
*
* @param int $maxPrice
*/
public function setMaxPrice($maxPrice)
{
$this->maxPrice = $maxPrice;
}
/**
* Sets the optional minimum price
*
* @param int $minPrice
*/
public function setMinPrice($minPrice)
{
$this->minPrice = $minPrice;
}
/**
* Checks if Item price falls between bounds
*
* @param Item $item
*
* @return bool
*/
public function isSatisfiedBy(Item $item)
{
if (!empty($this->maxPrice) && $item->getPrice() > $this->maxPrice) {
return false;
}
if (!empty($this->minPrice) && $item->getPrice() < $this->minPrice) {
return false;
}
return true;
}
}
|
Plus.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <?php
namespace DesignPatterns\Behavioral\Specification;
/**
* A logical AND specification
*/
class Plus extends AbstractSpecification
{
protected $left;
protected $right;
/**
* Creation of a logical AND of two specifications
*
* @param SpecificationInterface $left
* @param SpecificationInterface $right
*/
public function __construct(SpecificationInterface $left, SpecificationInterface $right)
{
$this->left = $left;
$this->right = $right;
}
/**
* Checks if the composite AND of specifications passes
*
* @param Item $item
*
* @return bool
*/
public function isSatisfiedBy(Item $item)
{
return $this->left->isSatisfiedBy($item) && $this->right->isSatisfiedBy($item);
}
}
|
Not.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <?php
namespace DesignPatterns\Behavioral\Specification;
/**
* A logical Not specification
*/
class Not extends AbstractSpecification
{
protected $spec;
/**
* Creates a new specification wrapping another
*
* @param SpecificationInterface $spec
*/
public function __construct(SpecificationInterface $spec)
{
$this->spec = $spec;
}
/**
* Returns the negated result of the wrapped specification
*
* @param Item $item
*
* @return bool
*/
public function isSatisfiedBy(Item $item)
{
return !$this->spec->isSatisfiedBy($item);
}
}
|
Test¶
Tests/SpecificationTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | <?php
namespace DesignPatterns\Behavioral\Specification\Tests;
use DesignPatterns\Behavioral\Specification\PriceSpecification;
use DesignPatterns\Behavioral\Specification\Item;
/**
* SpecificationTest tests the specification pattern
*/
class SpecificationTest extends \PHPUnit_Framework_TestCase
{
public function testSimpleSpecification()
{
$item = new Item(100);
$spec = new PriceSpecification();
$this->assertTrue($spec->isSatisfiedBy($item));
$spec->setMaxPrice(50);
$this->assertFalse($spec->isSatisfiedBy($item));
$spec->setMaxPrice(150);
$this->assertTrue($spec->isSatisfiedBy($item));
$spec->setMinPrice(101);
$this->assertFalse($spec->isSatisfiedBy($item));
$spec->setMinPrice(100);
$this->assertTrue($spec->isSatisfiedBy($item));
}
public function testNotSpecification()
{
$item = new Item(100);
$spec = new PriceSpecification();
$not = $spec->not();
$this->assertFalse($not->isSatisfiedBy($item));
$spec->setMaxPrice(50);
$this->assertTrue($not->isSatisfiedBy($item));
$spec->setMaxPrice(150);
$this->assertFalse($not->isSatisfiedBy($item));
$spec->setMinPrice(101);
$this->assertTrue($not->isSatisfiedBy($item));
$spec->setMinPrice(100);
$this->assertFalse($not->isSatisfiedBy($item));
}
public function testPlusSpecification()
{
$spec1 = new PriceSpecification();
$spec2 = new PriceSpecification();
$plus = $spec1->plus($spec2);
$item = new Item(100);
$this->assertTrue($plus->isSatisfiedBy($item));
$spec1->setMaxPrice(150);
$spec2->setMinPrice(50);
$this->assertTrue($plus->isSatisfiedBy($item));
$spec1->setMaxPrice(150);
$spec2->setMinPrice(101);
$this->assertFalse($plus->isSatisfiedBy($item));
$spec1->setMaxPrice(99);
$spec2->setMinPrice(50);
$this->assertFalse($plus->isSatisfiedBy($item));
}
public function testEitherSpecification()
{
$spec1 = new PriceSpecification();
$spec2 = new PriceSpecification();
$either = $spec1->either($spec2);
$item = new Item(100);
$this->assertTrue($either->isSatisfiedBy($item));
$spec1->setMaxPrice(150);
$spec2->setMaxPrice(150);
$this->assertTrue($either->isSatisfiedBy($item));
$spec1->setMaxPrice(150);
$spec2->setMaxPrice(0);
$this->assertTrue($either->isSatisfiedBy($item));
$spec1->setMaxPrice(0);
$spec2->setMaxPrice(150);
$this->assertTrue($either->isSatisfiedBy($item));
$spec1->setMaxPrice(99);
$spec2->setMaxPrice(99);
$this->assertFalse($either->isSatisfiedBy($item));
}
}
|
State¶
Purpose¶
Encapsulate varying behavior for the same routine based on an object’s state. This can be a cleaner way for an object to change its behavior at runtime without resorting to large monolithic conditional statements.
UML Diagram¶

Code¶
You can also find these code on GitHub
OrderController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <?php
namespace DesignPatterns\Behavioral\State;
/**
* Class OrderController
*/
class OrderController
{
/**
* @param int $id
*/
public function shipAction($id)
{
$order = OrderFactory::getOrder($id);
try {
$order->shipOrder();
} catch (Exception $e) {
//handle error!
}
// response to browser
}
/**
* @param int $id
*/
public function completeAction($id)
{
$order = OrderFactory::getOrder($id);
try {
$order->completeOrder();
} catch (Exception $e) {
//handle error!
}
// response to browser
}
}
|
OrderFactory.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <?php
namespace DesignPatterns\Behavioral\State;
/**
* Class OrderFactory
*/
class OrderFactory
{
private function __construct()
{
throw new \Exception('Can not instance the OrderFactory class!');
}
/**
* @param int $id
*
* @return CreateOrder|ShippingOrder
* @throws \Exception
*/
public static function getOrder($id)
{
$order = 'Get Order From Database';
switch ($order['status']) {
case 'created':
return new CreateOrder($order);
case 'shipping':
return new ShippingOrder($order);
default:
throw new \Exception('Order status error!');
break;
}
}
}
|
OrderInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?php
namespace DesignPatterns\Behavioral\State;
/**
* Class OrderInterface
*/
interface OrderInterface
{
/**
* @return mixed
*/
public function shipOrder();
/**
* @return mixed
*/
public function completeOrder();
}
|
ShippingOrder.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <?php
namespace DesignPatterns\Behavioral\State;
/**
* Class ShippingOrder
*/
class ShippingOrder implements OrderInterface
{
/**
* @var array
*/
private $order;
/**
* @param array $order
*
* @throws \Exception
*/
public function __construct(array $order)
{
if (empty($order)) {
throw new \Exception('Order can not be empty!');
}
$this->order = $order;
}
/**
* @return mixed|void
* @throws \Exception
*/
public function shipOrder()
{
//Can not ship the order which status is shipping, throw exception;
throw new \Exception('Can not ship the order which status is shipping!');
}
/**
* @return mixed
*/
public function completeOrder()
{
$this->order['status'] = 'completed';
$this->order['updatedTime'] = time();
// Setting the new order status into database;
return $this->updateOrder($this->order);
}
}
|
CreateOrder.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <?php
namespace DesignPatterns\Behavioral\State;
/**
* Class CreateOrder
*/
class CreateOrder implements OrderInterface
{
/**
* @var array
*/
private $order;
/**
* @param array $order
*
* @throws \Exception
*/
public function __construct(array $order)
{
if (empty($order)) {
throw new \Exception('Order can not be empty!');
}
$this->order = $order;
}
/**
* @return mixed
*/
public function shipOrder()
{
$this->order['status'] = 'shipping';
$this->order['updatedTime'] = time();
// Setting the new order status into database;
return $this->updateOrder($this->order);
}
/**
* @return mixed|void
* @throws \Exception
*/
public function completeOrder()
{
//Can not complete the order which status is created, throw exception;
throw new \Exception('Can not complete the order which status is created!');
}
}
|
Test¶
Strategy¶
Terminology:¶
- Context
- Strategy
- Concrete Strategy
Purpose¶
To separate strategies and to enable fast switching between them. Also this pattern is a good alternative to inheritance (instead of having an abstract class that is extended).
Examples¶
- sorting a list of objects, one strategy by date, the other by id
- simplify unit testing: e.g. switching between file and in-memory storage
UML Diagram¶

Code¶
You can also find these code on GitHub
ObjectCollection.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <?php
namespace DesignPatterns\Behavioral\Strategy;
/**
* Class ObjectCollection
*/
class ObjectCollection
{
/**
* @var array
*/
private $elements;
/**
* @var ComparatorInterface
*/
private $comparator;
/**
* @param array $elements
*/
public function __construct(array $elements = array())
{
$this->elements = $elements;
}
/**
* @return array
*/
public function sort()
{
if (!$this->comparator) {
throw new \LogicException("Comparator is not set");
}
$callback = array($this->comparator, 'compare');
uasort($this->elements, $callback);
return $this->elements;
}
/**
* @param ComparatorInterface $comparator
*
* @return void
*/
public function setComparator(ComparatorInterface $comparator)
{
$this->comparator = $comparator;
}
}
|
ComparatorInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?php
namespace DesignPatterns\Behavioral\Strategy;
/**
* Class ComparatorInterface
*/
interface ComparatorInterface
{
/**
* @param mixed $a
* @param mixed $b
*
* @return bool
*/
public function compare($a, $b);
}
|
DateComparator.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <?php
namespace DesignPatterns\Behavioral\Strategy;
/**
* Class DateComparator
*/
class DateComparator implements ComparatorInterface
{
/**
* {@inheritdoc}
*/
public function compare($a, $b)
{
$aDate = new \DateTime($a['date']);
$bDate = new \DateTime($b['date']);
if ($aDate == $bDate) {
return 0;
} else {
return $aDate < $bDate ? -1 : 1;
}
}
}
|
IdComparator.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?php
namespace DesignPatterns\Behavioral\Strategy;
/**
* Class IdComparator
*/
class IdComparator implements ComparatorInterface
{
/**
* {@inheritdoc}
*/
public function compare($a, $b)
{
if ($a['id'] == $b['id']) {
return 0;
} else {
return $a['id'] < $b['id'] ? -1 : 1;
}
}
}
|
Test¶
Tests/StrategyTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | <?php
namespace DesignPatterns\Behavioral\Strategy\Tests;
use DesignPatterns\Behavioral\Strategy\DateComparator;
use DesignPatterns\Behavioral\Strategy\IdComparator;
use DesignPatterns\Behavioral\Strategy\ObjectCollection;
use DesignPatterns\Behavioral\Strategy\Strategy;
/**
* Tests for Strategy pattern
*/
class StrategyTest extends \PHPUnit_Framework_TestCase
{
public function getIdCollection()
{
return array(
array(
array(array('id' => 2), array('id' => 1), array('id' => 3)),
array('id' => 1)
),
array(
array(array('id' => 3), array('id' => 2), array('id' => 1)),
array('id' => 1)
),
);
}
public function getDateCollection()
{
return array(
array(
array(array('date' => '2014-03-03'), array('date' => '2015-03-02'), array('date' => '2013-03-01')),
array('date' => '2013-03-01')
),
array(
array(array('date' => '2014-02-03'), array('date' => '2013-02-01'), array('date' => '2015-02-02')),
array('date' => '2013-02-01')
),
);
}
/**
* @dataProvider getIdCollection
*/
public function testIdComparator($collection, $expected)
{
$obj = new ObjectCollection($collection);
$obj->setComparator(new IdComparator());
$elements = $obj->sort();
$firstElement = array_shift($elements);
$this->assertEquals($expected, $firstElement);
}
/**
* @dataProvider getDateCollection
*/
public function testDateComparator($collection, $expected)
{
$obj = new ObjectCollection($collection);
$obj->setComparator(new DateComparator());
$elements = $obj->sort();
$firstElement = array_shift($elements);
$this->assertEquals($expected, $firstElement);
}
}
|
Template Method¶
Purpose¶
Template Method is a behavioral design pattern.
Perhaps you have encountered it many times already. The idea is to let subclasses of this abstract template “finish” the behavior of an algorithm.
A.k.a the “Hollywood principle”: “Don’t call us, we call you.” This class is not called by subclasses but the inverse. How? With abstraction of course.
In other words, this is a skeleton of algorithm, well-suited for framework libraries. The user has just to implement one method and the superclass do the job.
It is an easy way to decouple concrete classes and reduce copy-paste, that’s why you’ll find it everywhere.
UML Diagram¶

Code¶
You can also find these code on GitHub
Journey.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | <?php
namespace DesignPatterns\Behavioral\TemplateMethod;
/**
*
*/
abstract class Journey
{
/**
* This is the public service provided by this class and its subclasses.
* Notice it is final to "freeze" the global behavior of algorithm.
* If you want to override this contract, make an interface with only takeATrip()
* and subclass it.
*/
final public function takeATrip()
{
$this->buyAFlight();
$this->takePlane();
$this->enjoyVacation();
$this->buyGift();
$this->takePlane();
}
/**
* This method must be implemented, this is the key-feature of this pattern
*/
abstract protected function enjoyVacation();
/**
* This method is also part of the algorithm but it is optional.
* This is an "adapter" (do not confuse with the Adapter pattern, not related)
* You can override it only if you need to.
*/
protected function buyGift()
{
}
/**
* This method will be unknown by subclasses (better)
*/
private function buyAFlight()
{
echo "Buying a flight\n";
}
/**
* Subclasses will get access to this method but cannot override it and
* compromise this algorithm (warning : cause of cyclic dependencies)
*/
final protected function takePlane()
{
echo "Taking the plane\n";
}
// A note regarding the keyword "final" : don't use it when you start coding :
// add it after you narrow and know exactly what change and what remain unchanged
// in this algorithm.
// [abstract] x [3 access] x [final] = 12 combinations, it can be hard !
}
|
BeachJourney.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?php
namespace DesignPatterns\Behavioral\TemplateMethod;
/**
* BeachJourney is vacation at the beach
*/
class BeachJourney extends Journey
{
/**
* prints what to do to enjoy your vacation
*/
protected function enjoyVacation()
{
echo "Swimming and sun-bathing\n";
}
}
|
CityJourney.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?php
namespace DesignPatterns\Behavioral\TemplateMethod;
/**
* CityJourney is a journey in a city
*/
class CityJourney extends Journey
{
/**
* prints what to do in your journey to enjoy vacation
*/
protected function enjoyVacation()
{
echo "Eat, drink, take photos and sleep\n";
}
}
|
Test¶
Tests/JourneyTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <?php
namespace DesignPatterns\Behavioral\TemplateMethod\Tests;
use DesignPatterns\Behavioral\TemplateMethod;
/**
* JourneyTest tests all journeys
*/
class JourneyTest extends \PHPUnit_Framework_TestCase
{
public function testBeach()
{
$journey = new TemplateMethod\BeachJourney();
$this->expectOutputRegex('#sun-bathing#');
$journey->takeATrip();
}
public function testCity()
{
$journey = new TemplateMethod\CityJourney();
$this->expectOutputRegex('#drink#');
$journey->takeATrip();
}
/**
* How to test an abstract template method with PHPUnit
*/
public function testLasVegas()
{
$journey = $this->getMockForAbstractClass('DesignPatterns\Behavioral\TemplateMethod\Journey');
$journey->expects($this->once())
->method('enjoyVacation')
->will($this->returnCallback(array($this, 'mockUpVacation')));
$this->expectOutputRegex('#Las Vegas#');
$journey->takeATrip();
}
public function mockUpVacation()
{
echo "Fear and loathing in Las Vegas\n";
}
}
|
Visitor¶
Purpose¶
The Visitor Pattern lets you outsource operations on objects to other
objects. The main reason to do this is to keep a separation of concerns.
But classes have to define a contract to allow visitors (the
Role::accept
method in the example).
The contract is an abstract class but you can have also a clean interface. In that case, each Visitor has to choose itself which method to invoke on the visitor.
UML Diagram¶

Code¶
You can also find these code on GitHub
RoleVisitorInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <?php
namespace DesignPatterns\Behavioral\Visitor;
/**
* Visitor Pattern
*
* The contract for the visitor.
*
* Note 1 : in C++ or java, with method polymorphism based on type-hint, there are many
* methods visit() with different type for the 'role' parameter.
*
* Note 2 : the visitor must not choose itself which method to
* invoke, it is the Visitee that make this decision.
*/
interface RoleVisitorInterface
{
/**
* Visit a User object
*
* @param \DesignPatterns\Behavioral\Visitor\User $role
*/
public function visitUser(User $role);
/**
* Visit a Group object
*
* @param \DesignPatterns\Behavioral\Visitor\Group $role
*/
public function visitGroup(Group $role);
}
|
RolePrintVisitor.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <?php
namespace DesignPatterns\Behavioral\Visitor;
/**
* Visitor Pattern
*
* An implementation of a concrete Visitor
*/
class RolePrintVisitor implements RoleVisitorInterface
{
/**
* {@inheritdoc}
*/
public function visitGroup(Group $role)
{
echo "Role: " . $role->getName();
}
/**
* {@inheritdoc}
*/
public function visitUser(User $role)
{
echo "Role: " . $role->getName();
}
}
|
Role.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <?php
namespace DesignPatterns\Behavioral\Visitor;
/**
* class Role
*/
abstract class Role
{
/**
* This method handles a double dispatch based on the short name of the Visitor
*
* Feel free to override it if your object must call another visiting behavior
*
* @param \DesignPatterns\Behavioral\Visitor\RoleVisitorInterface $visitor
*
* @throws \InvalidArgumentException
*/
public function accept(RoleVisitorInterface $visitor)
{
// this trick to simulate double-dispatch based on type-hinting
$klass = get_called_class();
preg_match('#([^\\\\]+)$#', $klass, $extract);
$visitingMethod = 'visit' . $extract[1];
// this ensures strong typing with visitor interface, not some visitor objects
if (!method_exists(__NAMESPACE__ . '\RoleVisitorInterface', $visitingMethod)) {
throw new \InvalidArgumentException("The visitor you provide cannot visit a $klass instance");
}
call_user_func(array($visitor, $visitingMethod), $this);
}
}
|
User.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <?php
namespace DesignPatterns\Behavioral\Visitor;
/**
* Visitor Pattern
*
* One example for a visitee. Each visitee has to extends Role
*/
class User extends Role
{
/**
* @var string
*/
protected $name;
/**
* @param string $name
*/
public function __construct($name)
{
$this->name = (string) $name;
}
/**
* @return string
*/
public function getName()
{
return "User " . $this->name;
}
}
|
Group.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <?php
namespace DesignPatterns\Behavioral\Visitor;
/**
* An example of a Visitor: Group
*/
class Group extends Role
{
/**
* @var string
*/
protected $name;
/**
* @param string $name
*/
public function __construct($name)
{
$this->name = (string) $name;
}
/**
* @return string
*/
public function getName()
{
return "Group: " . $this->name;
}
}
|
Test¶
Tests/VisitorTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <?php
namespace DesignPatterns\Tests\Visitor\Tests;
use DesignPatterns\Behavioral\Visitor;
/**
* VisitorTest tests the visitor pattern
*/
class VisitorTest extends \PHPUnit_Framework_TestCase
{
protected $visitor;
protected function setUp()
{
$this->visitor = new Visitor\RolePrintVisitor();
}
public function getRole()
{
return array(
array(new Visitor\User("Dominik"), 'Role: User Dominik'),
array(new Visitor\Group("Administrators"), 'Role: Group: Administrators')
);
}
/**
* @dataProvider getRole
*/
public function testVisitSomeRole(Visitor\Role $role, $expect)
{
$this->expectOutputString($expect);
$role->accept($this->visitor);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Mock
*/
public function testUnknownObject()
{
$mock = $this->getMockForAbstractClass('DesignPatterns\Behavioral\Visitor\Role');
$mock->accept($this->visitor);
}
}
|
More¶
Delegation¶
Purpose¶
Demonstrate the Delegator pattern, where an object, instead of performing one of its stated tasks, delegates that task to an associated helper object. In this case TeamLead professes to writeCode and Usage uses this, while TeamLead delegates writeCode to JuniorDeveloper’s writeBadCode function. This inverts the responsibility so that Usage is unknowingly executing writeBadCode.
Examples¶
Please review JuniorDeveloper.php, TeamLead.php, and then Usage.php to see it all tied together.
UML Diagram¶

Code¶
You can also find these code on GitHub
Usage.php
1 2 3 4 5 6 7 8 9 | <?php
namespace DesignPatterns\More\Delegation;
// instantiate TeamLead and appoint to assistants JuniorDeveloper
$teamLead = new TeamLead(new JuniorDeveloper());
// team lead delegate write code to junior developer
echo $teamLead->writeCode();
|
TeamLead.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <?php
namespace DesignPatterns\More\Delegation;
/**
* Class TeamLead
* @package DesignPatterns\Delegation
* The `TeamLead` class, he delegate work to `JuniorDeveloper`
*/
class TeamLead
{
/** @var JuniorDeveloper */
protected $slave;
/**
* Give junior developer into teamlead submission
* @param JuniorDeveloper $junior
*/
public function __construct(JuniorDeveloper $junior)
{
$this->slave = $junior;
}
/**
* TeamLead drink coffee, junior work
* @return mixed
*/
public function writeCode()
{
return $this->slave->writeBadCode();
}
}
|
JuniorDeveloper.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php
namespace DesignPatterns\More\Delegation;
/**
* Class JuniorDeveloper
* @package DesignPatterns\Delegation
*/
class JuniorDeveloper
{
public function writeBadCode()
{
return "Some junior developer generated code...";
}
}
|
Test¶
Tests/DelegationTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?php
namespace DesignPatterns\More\Delegation\Tests;
use DesignPatterns\More\Delegation;
/**
* DelegationTest tests the delegation pattern
*/
class DelegationTest extends \PHPUnit_Framework_TestCase
{
public function testHowTeamLeadWriteCode()
{
$junior = new Delegation\JuniorDeveloper();
$teamLead = new Delegation\TeamLead($junior);
$this->assertEquals($junior->writeBadCode(), $teamLead->writeCode());
}
}
|
Service Locator¶
Purpose¶
To implement a loosely coupled architecture in order to get better testable, maintainable and extendable code. DI pattern and Service Locator pattern are an implementation of the Inverse of Control pattern.
Usage¶
With ServiceLocator
you can register a service for a given
interface. By using the interface you can retrieve the service and use
it in the classes of the application without knowing its implementation.
You can configure and inject the Service Locator object on bootstrap.
Examples¶
- Zend Framework 2 uses Service Locator to create and share services used in the framework(i.e. EventManager, ModuleManager, all custom user services provided by modules, etc...)
UML Diagram¶

Code¶
You can also find these code on GitHub
ServiceLocatorInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <?php
namespace DesignPatterns\More\ServiceLocator;
interface ServiceLocatorInterface
{
/**
* Checks if a service is registered.
*
* @param string $interface
*
* @return bool
*/
public function has($interface);
/**
* Gets the service registered for the interface.
*
* @param string $interface
*
* @return mixed
*/
public function get($interface);
}
|
ServiceLocator.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | <?php
namespace DesignPatterns\More\ServiceLocator;
class ServiceLocator implements ServiceLocatorInterface
{
/**
* All services.
*
* @var array
*/
private $services;
/**
* The services which have an instance.
*
* @var array
*/
private $instantiated;
/**
* True if a service can be shared.
*
* @var array
*/
private $shared;
public function __construct()
{
$this->services = array();
$this->instantiated = array();
$this->shared = array();
}
/**
* Registers a service with specific interface.
*
* @param string $interface
* @param string|object $service
* @param bool $share
*/
public function add($interface, $service, $share = true)
{
/**
* When you add a service, you should register it
* with its interface or with a string that you can use
* in the future even if you will change the service implementation.
*/
if (is_object($service) && $share) {
$this->instantiated[$interface] = $service;
}
$this->services[$interface] = (is_object($service) ? get_class($service) : $service);
$this->shared[$interface] = $share;
}
/**
* Checks if a service is registered.
*
* @param string $interface
*
* @return bool
*/
public function has($interface)
{
return (isset($this->services[$interface]) || isset($this->instantiated[$interface]));
}
/**
* Gets the service registered for the interface.
*
* @param string $interface
*
* @return mixed
*/
public function get($interface)
{
// Retrieves the instance if it exists and it is shared
if (isset($this->instantiated[$interface]) && $this->shared[$interface]) {
return $this->instantiated[$interface];
}
// otherwise gets the service registered.
$service = $this->services[$interface];
// You should check if the service class exists and
// the class is instantiable.
// This example is a simple implementation, but
// when you create a service, you can decide
// if $service is a factory or a class.
// By registering a factory you can create your services
// using the DependencyInjection pattern.
// ...
// Creates the service object
$object = new $service();
// and saves it if the service must be shared.
if ($this->shared[$interface]) {
$this->instantiated[$interface] = $object;
}
return $object;
}
}
|
LogServiceInterface.php
1 2 3 4 5 6 7 | <?php
namespace DesignPatterns\More\ServiceLocator;
interface LogServiceInterface
{
}
|
LogService.php
1 2 3 4 5 6 7 | <?php
namespace DesignPatterns\More\ServiceLocator;
class LogService implements LogServiceInterface
{
}
|
DatabaseServiceInterface.php
1 2 3 4 5 6 7 | <?php
namespace DesignPatterns\More\ServiceLocator;
interface DatabaseServiceInterface
{
}
|
DatabaseService.php
1 2 3 4 5 6 7 | <?php
namespace DesignPatterns\More\ServiceLocator;
class DatabaseService implements DatabaseServiceInterface
{
}
|
Test¶
Tests/ServiceLocatorTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | <?php
namespace DesignPatterns\More\ServiceLocator\Tests;
use DesignPatterns\More\ServiceLocator\DatabaseService;
use DesignPatterns\More\ServiceLocator\LogService;
use DesignPatterns\More\ServiceLocator\ServiceLocator;
use \PHPUnit_Framework_TestCase as TestCase;
class ServiceLocatorTest extends TestCase
{
/**
* @var LogService
*/
private $logService;
/**
* @var DatabaseService
*/
private $databaseService;
/**
* @var ServiceLocator
*/
private $serviceLocator;
public function setUp()
{
$this->serviceLocator = new ServiceLocator();
$this->logService = new LogService();
$this->databaseService = new DatabaseService();
}
public function testHasServices()
{
$this->serviceLocator->add(
'DesignPatterns\More\ServiceLocator\LogServiceInterface',
$this->logService
);
$this->serviceLocator->add(
'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface',
$this->databaseService
);
$this->assertTrue($this->serviceLocator->has('DesignPatterns\More\ServiceLocator\LogServiceInterface'));
$this->assertTrue($this->serviceLocator->has('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface'));
$this->assertFalse($this->serviceLocator->has('DesignPatterns\More\ServiceLocator\FakeServiceInterface'));
}
public function testServicesWithObject()
{
$this->serviceLocator->add(
'DesignPatterns\More\ServiceLocator\LogServiceInterface',
$this->logService
);
$this->serviceLocator->add(
'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface',
$this->databaseService
);
$this->assertSame(
$this->logService,
$this->serviceLocator->get('DesignPatterns\More\ServiceLocator\LogServiceInterface')
);
$this->assertSame(
$this->databaseService,
$this->serviceLocator->get('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface')
);
}
public function testServicesWithClass()
{
$this->serviceLocator->add(
'DesignPatterns\More\ServiceLocator\LogServiceInterface',
get_class($this->logService)
);
$this->serviceLocator->add(
'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface',
get_class($this->databaseService)
);
$this->assertNotSame(
$this->logService,
$this->serviceLocator->get('DesignPatterns\More\ServiceLocator\LogServiceInterface')
);
$this->assertInstanceOf(
'DesignPatterns\More\ServiceLocator\LogServiceInterface',
$this->serviceLocator->get('DesignPatterns\More\ServiceLocator\LogServiceInterface')
);
$this->assertNotSame(
$this->databaseService,
$this->serviceLocator->get('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface')
);
$this->assertInstanceOf(
'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface',
$this->serviceLocator->get('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface')
);
}
public function testServicesNotShared()
{
$this->serviceLocator->add(
'DesignPatterns\More\ServiceLocator\LogServiceInterface',
$this->logService,
false
);
$this->serviceLocator->add(
'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface',
$this->databaseService,
false
);
$this->assertNotSame(
$this->logService,
$this->serviceLocator->get('DesignPatterns\More\ServiceLocator\LogServiceInterface')
);
$this->assertInstanceOf(
'DesignPatterns\More\ServiceLocator\LogServiceInterface',
$this->serviceLocator->get('DesignPatterns\More\ServiceLocator\LogServiceInterface')
);
$this->assertNotSame(
$this->databaseService,
$this->serviceLocator->get('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface')
);
$this->assertInstanceOf(
'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface',
$this->serviceLocator->get('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface')
);
}
}
|
Repository¶
Purpose¶
Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects. Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer. Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers.
Examples¶
- Doctrine 2 ORM: there is Repository that mediates between Entity and DBAL and contains methods to retrieve objects
- Laravel Framework
UML Diagram¶

Code¶
You can also find these code on GitHub
Post.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | <?php
namespace DesignPatterns\Repository;
/**
* Post represents entity for some post that user left on the site
*
* Class Post
* @package DesignPatterns\Repository
*/
class Post
{
/**
* @var int
*/
private $id;
/**
* @var string
*/
private $title;
/**
* @var string
*/
private $text;
/**
* @var string
*/
private $author;
/**
* @var \DateTime
*/
private $created;
/**
* @param int $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @param string $author
*/
public function setAuthor($author)
{
$this->author = $author;
}
/**
* @return string
*/
public function getAuthor()
{
return $this->author;
}
/**
* @param \DateTime $created
*/
public function setCreated($created)
{
$this->created = $created;
}
/**
* @return \DateTime
*/
public function getCreated()
{
return $this->created;
}
/**
* @param string $text
*/
public function setText($text)
{
$this->text = $text;
}
/**
* @return string
*/
public function getText()
{
return $this->text;
}
/**
* @param string $title
*/
public function setTitle($title)
{
$this->title = $title;
}
/**
* @return string
*/
public function getTitle()
{
return $this->title;
}
}
|
PostRepository.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | <?php
namespace DesignPatterns\Repository;
/**
* Repository for class Post
* This class is between Entity layer(class Post) and access object layer(interface Storage)
*
* Repository encapsulates the set of objects persisted in a data store and the operations performed over them
* providing a more object-oriented view of the persistence layer
*
* Repository also supports the objective of achieving a clean separation and one-way dependency
* between the domain and data mapping layers
*
* Class PostRepository
* @package DesignPatterns\Repository
*/
class PostRepository
{
private $persistence;
public function __construct(Storage $persistence)
{
$this->persistence = $persistence;
}
/**
* Returns Post object by specified id
*
* @param int $id
* @return Post|null
*/
public function getById($id)
{
$arrayData = $this->persistence->retrieve($id);
if (is_null($arrayData)) {
return null;
}
$post = new Post();
$post->setId($arrayData['id']);
$post->setAuthor($arrayData['author']);
$post->setCreated($arrayData['created']);
$post->setText($arrayData['text']);
$post->setTitle($arrayData['title']);
return $post;
}
/**
* Save post object and populate it with id
*
* @param Post $post
* @return Post
*/
public function save(Post $post)
{
$id = $this->persistence->persist(array(
'author' => $post->getAuthor(),
'created' => $post->getCreated(),
'text' => $post->getText(),
'title' => $post->getTitle()
));
$post->setId($id);
return $post;
}
/**
* Deletes specified Post object
*
* @param Post $post
* @return bool
*/
public function delete(Post $post)
{
return $this->persistence->delete($post->getId());
}
}
|
Storage.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | <?php
namespace DesignPatterns\Repository;
/**
* Interface Storage
*
* This interface describes methods for accessing storage.
* Concrete realization could be whatever we want - in memory, relational database, NoSQL database and etc
*
* @package DesignPatterns\Repository
*/
interface Storage
{
/**
* Method to persist data
* Returns new id for just persisted data
*
* @param array() $data
* @return int
*/
public function persist($data);
/**
* Returns data by specified id.
* If there is no such data null is returned
*
* @param int $id
* @return array|null
*/
public function retrieve($id);
/**
* Delete data specified by id
* If there is no such data - false returns, if data has been successfully deleted - true returns
*
* @param int $id
* @return bool
*/
public function delete($id);
}
|
MemoryStorage.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <?php
namespace DesignPatterns\Repository;
/**
* Class MemoryStorage
* @package DesignPatterns\Repository
*/
class MemoryStorage implements Storage
{
private $data;
private $lastId;
public function __construct()
{
$this->data = array();
$this->lastId = 0;
}
/**
* {@inheritdoc}
*/
public function persist($data)
{
$this->data[++$this->lastId] = $data;
return $this->lastId;
}
/**
* {@inheritdoc}
*/
public function retrieve($id)
{
return isset($this->data[$id]) ? $this->data[$id] : null;
}
/**
* {@inheritdoc}
*/
public function delete($id)
{
if (!isset($this->data[$id])) {
return false;
}
$this->data[$id] = null;
unset($this->data[$id]);
return true;
}
}
|
Test¶
참여하기¶
Please feel free to fork and extend existing or add your own examples
and send a pull request with your changes! To establish a consistent
code quality, please check your code using
PHP CodeSniffer against PSR2 standard
using ./vendor/bin/phpcs -p --standard=PSR2 --ignore=vendor .
.
라이센스¶
(The MIT License)
Copyright (c) 2014 Dominik Liebler and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.