start a restructure

This commit is contained in:
Antonio Spinelli
2014-03-21 18:03:44 -03:00
parent b0b0d4a1a4
commit e59d70a0ac
180 changed files with 21 additions and 16 deletions

View File

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

View File

@@ -0,0 +1,43 @@
<?php
namespace DesignPatterns\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();
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace DesignPatterns\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();
}

View File

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

View File

@@ -0,0 +1,23 @@
<?php
namespace DesignPatterns\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();
}

View File

@@ -0,0 +1,10 @@
# 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

View File

@@ -0,0 +1,41 @@
<?php
namespace DesignPatterns\Tests\Adapter;
use DesignPatterns\Adapter\EBookAdapter;
use DesignPatterns\Adapter\Kindle;
use DesignPatterns\Adapter\PaperBookInterface;
use DesignPatterns\Adapter\Book;
/**
* AdapterTest shows the use of an adapted e-book that behave like a book
* You don't have to change the code of your client
*/
class AdapterTest extends \PHPUnit_Framework_TestCase
{
/**
* @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 test_I_am_an_old_Client(PaperBookInterface $book)
{
$book->open();
$book->turnPage();
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace DesignPatterns\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;
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace DesignPatterns\Composite;
/**
* Class FormElement
*/
abstract class FormElement
{
/**
* renders the elements' code
*
* @param int $indent
*
* @return mixed
*/
abstract public function render($indent = 0);
}

View File

@@ -0,0 +1,21 @@
<?php
namespace DesignPatterns\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" />';
}
}

View File

@@ -0,0 +1,12 @@
# 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 trough all its child elements and calls `render()` on them
* `Zend_Config`: a tree of configuration options, each one is a `Zend_Config` object itself

View File

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

View File

@@ -0,0 +1,21 @@
<?php
namespace DesignPatterns\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';
}
}

View File

@@ -0,0 +1,18 @@
# 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"

View File

@@ -0,0 +1,105 @@
<?php
namespace DesignPatterns\Test\DataMapper;
use DesignPatterns\DataMapper\UserMapper;
use DesignPatterns\DataMapper\User;
/**
* UserMapperTest tests the datamapper pattern
*/
class UserMapperTest extends \PHPUnit_Framework_TestCase
{
/**
* @var UserMapper
*/
protected $mapper;
/**
* @var DBAL
*/
protected $dbal;
protected function setUp()
{
$this->dbal = $this->getMockBuilder('DesignPatterns\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)
{
$rows = new \ArrayIterator(array(array('userid' => 1, 'username' => 'Odysseus', 'email' => 'Odysseus@ithaca.gr')));
$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);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace DesignPatterns\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;
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace DesignPatterns\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;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace DesignPatterns\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;
}
}

View File

@@ -0,0 +1,10 @@
# 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)

View File

@@ -0,0 +1,21 @@
<?php
namespace DesignPatterns\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);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace DesignPatterns\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();
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace DesignPatterns\Decorator;
/**
* Class RendererInterface
*/
interface RendererInterface
{
/**
* render data
*
* @return mixed
*/
public function renderData();
}

View File

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

View File

@@ -0,0 +1,30 @@
<?php
namespace DesignPatterns\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;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace DesignPatterns\DependencyInjection;
/**
* class AbstractConfig
*/
abstract class AbstractConfig
{
/**
* @var Storage of data
*/
protected $storage;
public function __construct($storage)
{
$this->storage = $storage;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace DesignPatterns\DependencyInjection;
/**
* class ArrayConfig
*
* uses array as data source
*/
class ArrayConfig extends AbstractConfig implements Parameters
{
/**
* Get parameter
*
* @param string|int $key
*
* @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;
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace DesignPatterns\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;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace DesignPatterns\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);
}

View File

@@ -0,0 +1,16 @@
# 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 Configuration 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](http://en.wikipedia.org/wiki/Inversion_of_control).
## 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 the connection 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)

View File

@@ -0,0 +1,27 @@
<?php
namespace DesignPatterns\Tests\DependencyInjection;
use DesignPatterns\DependencyInjection\Parameters;
use DesignPatterns\DependencyInjection\AbstractConfig;
use DesignPatterns\DependencyInjection\ArrayConfig;
use DesignPatterns\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());
}
}

View File

@@ -0,0 +1,3 @@
<?php
return array('host' => 'github.com');

View File

@@ -0,0 +1,31 @@
<?php
namespace DesignPatterns\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();
}

View File

@@ -0,0 +1,52 @@
<?php
namespace DesignPatterns\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();
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace DesignPatterns\Facade;
/**
* Class OsInterface
*/
interface OsInterface
{
/**
* halt the OS
*/
public function halt();
}

View File

@@ -0,0 +1,17 @@
# 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.

View File

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

View File

@@ -0,0 +1,11 @@
# 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

View File

@@ -0,0 +1,80 @@
<?php
namespace DesignPatterns\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);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace DesignPatterns\Tests\FluentInterface;
use DesignPatterns\FluentInterface\SQL;
/**
* FluentInterfaceTest tests the fluent interface SQL
*/
class FluentInterfaceTest extends \PHPUnit_Framework_TestCase
{
public function testBuildSQL()
{
$instance = new SQL();
$query = $instance->select(array('foo', 'bar'))
->from('foobar', 'f')
->where('f.bar = ?')
->getQuery();
$this->assertEquals('SELECT foo,bar FROM foobar AS f WHERE f.bar = ?', $query);
}
}

View File

@@ -0,0 +1,9 @@
# 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

View File

@@ -0,0 +1,51 @@
<?php
namespace DesignPatterns\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;
}
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace DesignPatterns\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);
}
}

View File

@@ -0,0 +1,11 @@
# 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: `Zend_Registry` holds the application's logger object, front controller etc.
* Yii Framework: `CWebApplication` holds all the application components, such as `CWebUser`, `CUrlManager`, etc.

View File

@@ -0,0 +1,45 @@
<?php
namespace DesignPatterns\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 ...
}

View File

@@ -0,0 +1,9 @@
<?php
use DesignPatterns\Registry\Registry;
// while bootstrapping the application
Registry::set(Registry::LOGGER, new \StdClass());
// throughout the application
Registry::get(Registry::LOGGER)->log('foo');