Merged branch master into translate-template

This commit is contained in:
Axel Pardemann
2016-09-23 11:44:18 -05:00
288 changed files with 9359 additions and 9024 deletions

View File

@@ -2,22 +2,25 @@
namespace DesignPatterns\Structural\Adapter;
/**
* Book is a concrete and standard paper book.
*/
class Book implements PaperBookInterface
class Book implements BookInterface
{
/**
* {@inheritdoc}
* @var int
*/
private $page;
public function open()
{
$this->page = 1;
}
/**
* {@inheritdoc}
*/
public function turnPage()
{
$this->page++;
}
public function getPage(): int
{
return $this->page;
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace DesignPatterns\Structural\Adapter;
interface BookInterface
{
public function turnPage();
public function open();
public function getPage(): int;
}

View File

@@ -3,12 +3,10 @@
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.
* This is the adapter here. Notice it implements BookInterface,
* therefore you don't have to change the code of the client which is using a Book
*/
class EBookAdapter implements PaperBookInterface
class EBookAdapter implements BookInterface
{
/**
* @var EBookInterface
@@ -16,13 +14,11 @@ class EBookAdapter implements PaperBookInterface
protected $eBook;
/**
* Notice the constructor, it "wraps" an electronic book.
*
* @param EBookInterface $ebook
* @param EBookInterface $eBook
*/
public function __construct(EBookInterface $ebook)
public function __construct(EBookInterface $eBook)
{
$this->eBook = $ebook;
$this->eBook = $eBook;
}
/**
@@ -30,14 +26,22 @@ class EBookAdapter implements PaperBookInterface
*/
public function open()
{
$this->eBook->pressStart();
$this->eBook->unlock();
}
/**
* turns pages.
*/
public function turnPage()
{
$this->eBook->pressNext();
}
/**
* notice the adapted behavior here: EBookInterface::getPage() will return two integers, but BookInterface
* supports only a current page getter, so we adapt the behavior here
*
* @return int
*/
public function getPage(): int
{
return $this->eBook->getPage()[0];
}
}

View File

@@ -2,22 +2,16 @@
namespace DesignPatterns\Structural\Adapter;
/**
* EBookInterface is a contract for an electronic book.
*/
interface EBookInterface
{
/**
* go to next page.
*
* @return mixed
*/
public function unlock();
public function pressNext();
/**
* start the book.
* returns current page and total number of pages, like [10, 100] is page 10 of 100
*
* @return mixed
* @return int[]
*/
public function pressStart();
public function getPage(): array;
}

View File

@@ -3,21 +3,37 @@
namespace DesignPatterns\Structural\Adapter;
/**
* Kindle is a concrete electronic book.
* this is the adapted class. In production code, this could be a class from another package, some vendor code.
* Notice that it uses another naming scheme and the implementation does something similar but in another way
*/
class Kindle implements EBookInterface
{
/**
* {@inheritdoc}
* @var int
*/
private $page = 1;
/**
* @var int
*/
private $totalPages = 100;
public function pressNext()
{
$this->page++;
}
public function unlock()
{
}
/**
* {@inheritdoc}
* returns current page and total number of pages, like [10, 100] is page 10 of 100
*
* @return int[]
*/
public function pressStart()
public function getPage(): array
{
return [$this->page, $this->totalPages];
}
}

View File

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

View File

@@ -28,7 +28,7 @@ Code
You can also find these code on `GitHub`_
PaperBookInterface.php
BookInterface.php
.. literalinclude:: PaperBookInterface.php
:language: php

View File

@@ -5,37 +5,26 @@ namespace DesignPatterns\Structural\Adapter\Tests;
use DesignPatterns\Structural\Adapter\Book;
use DesignPatterns\Structural\Adapter\EBookAdapter;
use DesignPatterns\Structural\Adapter\Kindle;
use DesignPatterns\Structural\Adapter\PaperBookInterface;
/**
* 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()
public function testCanTurnPageOnBook()
{
return array(
array(new Book()),
// we build a "wrapped" electronic book in the adapter
array(new EBookAdapter(new Kindle())),
);
$book = new Book();
$book->open();
$book->turnPage();
$this->assertEquals(2, $book->getPage());
}
/**
* 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)
public function testCanTurnPageOnKindleLikeInANormalBook()
{
$this->assertTrue(method_exists($book, 'open'));
$this->assertTrue(method_exists($book, 'turnPage'));
$kindle = new Kindle();
$book = new EBookAdapter($kindle);
$book->open();
$book->turnPage();
$this->assertEquals(2, $book->getPage());
}
}

View File

@@ -1,11 +0,0 @@
<?php
namespace DesignPatterns\Structural\Bridge;
class Assemble implements Workshop
{
public function work()
{
echo 'Assembled';
}
}

View File

@@ -1,21 +0,0 @@
<?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()
{
echo 'Car ';
$this->workShop1->work();
$this->workShop2->work();
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace DesignPatterns\Structural\Bridge;
interface FormatterInterface
{
public function format(string $text);
}

View File

@@ -0,0 +1,11 @@
<?php
namespace DesignPatterns\Structural\Bridge;
class HelloWorldService extends Service
{
public function get()
{
return $this->implementation->format('Hello World');
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace DesignPatterns\Structural\Bridge;
class HtmlFormatter implements FormatterInterface
{
public function format(string $text)
{
return sprintf('<p>%s</p>', $text);
}
}

View File

@@ -1,21 +0,0 @@
<?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()
{
echo 'Motorcycle ';
$this->workShop1->work();
$this->workShop2->work();
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace DesignPatterns\Structural\Bridge;
class PlainTextFormatter implements FormatterInterface
{
public function format(string $text)
{
return $text;
}
}

View File

@@ -1,14 +0,0 @@
<?php
namespace DesignPatterns\Structural\Bridge;
/**
* Concrete Implementation.
*/
class Produce implements Workshop
{
public function work()
{
echo 'Produced ';
}
}

View File

@@ -25,39 +25,33 @@ Code
You can also find these code on `GitHub`_
Workshop.php
FormatterInterface.php
.. literalinclude:: Workshop.php
.. literalinclude:: FormatterInterface.php
:language: php
:linenos:
Assemble.php
PlainTextFormatter.php
.. literalinclude:: Assemble.php
.. literalinclude:: PlainTextFormatter.php
:language: php
:linenos:
Produce.php
HtmlFormatter.php
.. literalinclude:: Produce.php
.. literalinclude:: HtmlFormatter.php
:language: php
:linenos:
Vehicle.php
Service.php
.. literalinclude:: Vehicle.php
.. literalinclude:: Service.php
:language: php
:linenos:
Motorcycle.php
HelloWorldService.php
.. literalinclude:: Motorcycle.php
:language: php
:linenos:
Car.php
.. literalinclude:: Car.php
.. literalinclude:: HelloWorldService.php
:language: php
:linenos:

View File

@@ -0,0 +1,29 @@
<?php
namespace DesignPatterns\Structural\Bridge;
abstract class Service
{
/**
* @var FormatterInterface
*/
protected $implementation;
/**
* @param FormatterInterface $printer
*/
public function __construct(FormatterInterface $printer)
{
$this->implementation = $printer;
}
/**
* @param FormatterInterface $printer
*/
public function setImplementation(FormatterInterface $printer)
{
$this->implementation = $printer;
}
abstract public function get();
}

View File

@@ -2,24 +2,19 @@
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;
use DesignPatterns\Structural\Bridge\HelloWorldService;
use DesignPatterns\Structural\Bridge\HtmlFormatter;
use DesignPatterns\Structural\Bridge\PlainTextFormatter;
class BridgeTest extends \PHPUnit_Framework_TestCase
{
public function testCar()
public function testCanPrintUsingThePlainTextPrinter()
{
$vehicle = new Car(new Produce(), new Assemble());
$this->expectOutputString('Car Produced Assembled');
$vehicle->manufacture();
}
$service = new HelloWorldService(new PlainTextFormatter());
$this->assertEquals('Hello World', $service->get());
public function testMotorcycle()
{
$vehicle = new Motorcycle(new Produce(), new Assemble());
$this->expectOutputString('Motorcycle Produced Assembled');
$vehicle->manufacture();
// now change the implemenation and use the HtmlFormatter instead
$service->setImplementation(new HtmlFormatter());
$this->assertEquals('<p>Hello World</p>', $service->get());
}
}

View File

@@ -1,20 +0,0 @@
<?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;
}
abstract public function manufacture();
}

View File

@@ -1,11 +0,0 @@
<?php
namespace DesignPatterns\Structural\Bridge;
/**
* Implementer.
*/
interface Workshop
{
public function work();
}

View File

@@ -1,50 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<Diagram>
<ID>PHP</ID>
<OriginalElement>\DesignPatterns\Structural\Bridge\Vehicle</OriginalElement>
<nodes>
<node x="53.5" y="272.0">\DesignPatterns\Structural\Bridge\Workshop</node>
<node x="290.0" y="159.0">\DesignPatterns\Structural\Bridge\Car</node>
<node x="107.0" y="369.0">\DesignPatterns\Structural\Bridge\Produce</node>
<node x="0.0" y="159.0">\DesignPatterns\Structural\Bridge\Motorcycle</node>
<node x="0.0" y="369.0">\DesignPatterns\Structural\Bridge\Assemble</node>
<node x="187.5" y="0.0">\DesignPatterns\Structural\Bridge\Vehicle</node>
</nodes>
<notes />
<edges>
<edge source="\DesignPatterns\Structural\Bridge\Car" target="\DesignPatterns\Structural\Bridge\Vehicle">
<point x="0.0" y="-34.0" />
<point x="425.0" y="134.0" />
<point x="326.25" y="134.0" />
<point x="46.25" y="54.5" />
</edge>
<edge source="\DesignPatterns\Structural\Bridge\Assemble" target="\DesignPatterns\Structural\Bridge\Workshop">
<point x="0.0" y="-23.5" />
<point x="43.5" y="344.0" />
<point x="75.25" y="344.0" />
<point x="-21.75" y="23.5" />
</edge>
<edge source="\DesignPatterns\Structural\Bridge\Motorcycle" target="\DesignPatterns\Structural\Bridge\Vehicle">
<point x="0.0" y="-34.0" />
<point x="135.0" y="134.0" />
<point x="233.75" y="134.0" />
<point x="-46.25" y="54.5" />
</edge>
<edge source="\DesignPatterns\Structural\Bridge\Produce" target="\DesignPatterns\Structural\Bridge\Workshop">
<point x="0.0" y="-23.5" />
<point x="150.5" y="344.0" />
<point x="118.75" y="344.0" />
<point x="21.75" y="23.5" />
</edge>
</edges>
<settings layout="Hierarchic Group" zoom="0.8853211009174312" x="280.0" y="208.0" />
<SelectedNodes />
<Categories>
<Category>Fields</Category>
<Category>Constants</Category>
<Category>Constructors</Category>
<Category>Methods</Category>
</Categories>
<VISIBILITY>private</VISIBILITY>
</Diagram>
<?xml version="1.0" encoding="UTF-8"?>
<Diagram>
<ID>PHP</ID>
<OriginalElement>\DesignPatterns\Structural\Bridge\HtmlFormatter</OriginalElement>
<nodes>
<node x="-111.0" y="-111.0">\DesignPatterns\Structural\Bridge\PlainTextFormatter</node>
<node x="-194.0" y="-212.0">\DesignPatterns\Structural\Bridge\FormatterInterface</node>
<node x="88.0" y="-239.0">\DesignPatterns\Structural\Bridge\Service</node>
<node x="121.0" y="-93.0">\DesignPatterns\Structural\Bridge\HelloWorldService</node>
<node x="-278.0" y="-111.0">\DesignPatterns\Structural\Bridge\HtmlFormatter</node>
</nodes>
<notes />
<edges>
<edge source="\DesignPatterns\Structural\Bridge\HelloWorldService" target="\DesignPatterns\Structural\Bridge\Service">
<point x="0.0" y="-25.5" />
<point x="0.0" y="48.0" />
</edge>
<edge source="\DesignPatterns\Structural\Bridge\PlainTextFormatter" target="\DesignPatterns\Structural\Bridge\FormatterInterface">
<point x="0.0" y="-25.5" />
<point x="-35.5" y="-136.0" />
<point x="-83.0" y="-136.0" />
<point x="37.0" y="25.5" />
</edge>
<edge source="\DesignPatterns\Structural\Bridge\HtmlFormatter" target="\DesignPatterns\Structural\Bridge\FormatterInterface">
<point x="0.0" y="-25.5" />
<point x="-204.5" y="-136.0" />
<point x="-157.0" y="-136.0" />
<point x="-37.0" y="25.5" />
</edge>
</edges>
<settings layout="Hierarchic Group" zoom="1.0" x="147.5" y="130.5" />
<SelectedNodes />
<Categories>
<Category>Fields</Category>
<Category>Constants</Category>
<Category>Methods</Category>
</Categories>
<VISIBILITY>private</VISIBILITY>
</Diagram>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 40 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

@@ -6,12 +6,12 @@ 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
class Form implements RenderableInterface
{
/**
* @var array|FormElement[]
* @var RenderableInterface[]
*/
protected $elements;
private $elements;
/**
* runs through all elements and calls render() on them, then returns the complete representation
@@ -19,25 +19,25 @@ class Form extends FormElement
*
* 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)
public function render(): string
{
$formCode = '';
$formCode = '<form>';
foreach ($this->elements as $element) {
$formCode .= $element->render($indent + 1).PHP_EOL;
$formCode .= $element->render();
}
$formCode .= '</form>';
return $formCode;
}
/**
* @param FormElement $element
* @param RenderableInterface $element
*/
public function addElement(FormElement $element)
public function addElement(RenderableInterface $element)
{
$this->elements[] = $element;
}

View File

@@ -1,18 +0,0 @@
<?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);
}

View File

@@ -2,20 +2,10 @@
namespace DesignPatterns\Structural\Composite;
/**
* Class InputElement.
*/
class InputElement extends FormElement
class InputElement implements RenderableInterface
{
/**
* renders the input element HTML.
*
* @param int $indent
*
* @return mixed|string
*/
public function render($indent = 0)
public function render(): string
{
return str_repeat(' ', $indent).'<input type="text" />';
return '<input type="text" />';
}
}

View File

@@ -28,9 +28,9 @@ Code
You can also find these code on `GitHub`_
FormElement.php
RenderableInterface.php
.. literalinclude:: FormElement.php
.. literalinclude:: RenderableInterface.php
:language: php
:linenos:

View File

@@ -0,0 +1,8 @@
<?php
namespace DesignPatterns\Structural\Composite;
interface RenderableInterface
{
public function render(): string;
}

View File

@@ -4,32 +4,21 @@ 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\TextElement('Email:'));
$form->addElement(new Composite\InputElement());
$embed = new Composite\Form();
$embed->addElement(new Composite\TextElement());
$embed->addElement(new Composite\TextElement('Password:'));
$embed->addElement(new Composite\InputElement());
$form->addElement($embed); // here we have a embedded form (like SF2 does)
$form->addElement($embed);
$this->assertRegExp('#^\s{4}#m', $form->render());
}
/**
* The point of this pattern, a Composite must inherit from the node
* if you want to build trees.
*/
public function testFormImplementsFormEelement()
{
$className = 'DesignPatterns\Structural\Composite\Form';
$abstractName = 'DesignPatterns\Structural\Composite\FormElement';
$this->assertTrue(is_subclass_of($className, $abstractName));
$this->assertEquals(
'<form>Email:<input type="text" /><form>Password:<input type="text" /></form></form>',
$form->render()
);
}
}

View File

@@ -2,20 +2,20 @@
namespace DesignPatterns\Structural\Composite;
/**
* Class TextElement.
*/
class TextElement extends FormElement
class TextElement implements RenderableInterface
{
/**
* renders the text element.
*
* @param int $indent
*
* @return mixed|string
* @var string
*/
public function render($indent = 0)
private $text;
public function __construct(string $text)
{
return str_repeat(' ', $indent).'this is a text element';
$this->text = $text;
}
public function render(): string
{
return $this->text;
}
}

View File

@@ -1,42 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<Diagram>
<ID>PHP</ID>
<OriginalElement>\DesignPatterns\Structural\Composite\InputElement</OriginalElement>
<nodes>
<node x="321.0" y="117.5">\DesignPatterns\Structural\Composite\TextElement</node>
<node x="175.0" y="0.0">\DesignPatterns\Structural\Composite\FormElement</node>
<node x="175.0" y="117.5">\DesignPatterns\Structural\Composite\InputElement</node>
<node x="0.0" y="97.0">\DesignPatterns\Structural\Composite\Form</node>
</nodes>
<notes />
<edges>
<edge source="\DesignPatterns\Structural\Composite\InputElement" target="\DesignPatterns\Structural\Composite\FormElement">
<point x="0.0" y="-23.5" />
<point x="0.0" y="23.5" />
</edge>
<edge source="\DesignPatterns\Structural\Composite\Form" target="\DesignPatterns\Structural\Composite\FormElement">
<point x="0.0" y="-44.0" />
<point x="77.5" y="72.0" />
<point x="196.0" y="72.0" />
<point x="-42.0" y="23.5" />
</edge>
<edge source="\DesignPatterns\Structural\Composite\TextElement" target="\DesignPatterns\Structural\Composite\FormElement">
<point x="0.0" y="-23.5" />
<point x="384.0" y="72.0" />
<point x="280.0" y="72.0" />
<point x="42.0" y="23.5" />
</edge>
</edges>
<settings layout="Hierarchic Group" zoom="1.0" x="223.5" y="92.5" />
<SelectedNodes>
<node>\DesignPatterns\Structural\Composite\InputElement</node>
</SelectedNodes>
<Categories>
<Category>Fields</Category>
<Category>Constants</Category>
<Category>Constructors</Category>
<Category>Methods</Category>
</Categories>
<VISIBILITY>private</VISIBILITY>
</Diagram>
<?xml version="1.0" encoding="UTF-8"?>
<Diagram>
<ID>PHP</ID>
<OriginalElement>\DesignPatterns\Structural\Composite\Form</OriginalElement>
<nodes>
<node x="171.0" y="0.0">\DesignPatterns\Structural\Composite\RenderableInterface</node>
<node x="189.0" y="123.5">\DesignPatterns\Structural\Composite\InputElement</node>
<node x="329.0" y="112.0">\DesignPatterns\Structural\Composite\TextElement</node>
<node x="0.0" y="101.0">\DesignPatterns\Structural\Composite\Form</node>
</nodes>
<notes />
<edges>
<edge source="\DesignPatterns\Structural\Composite\Form" target="\DesignPatterns\Structural\Composite\RenderableInterface">
<point x="0.0" y="-48.0" />
<point x="84.5" y="76.0" />
<point x="197.0" y="76.0" />
<point x="-52.0" y="25.5" />
</edge>
<edge source="\DesignPatterns\Structural\Composite\TextElement" target="\DesignPatterns\Structural\Composite\RenderableInterface">
<point x="0.0" y="-37.0" />
<point x="387.5" y="76.0" />
<point x="301.0" y="76.0" />
<point x="52.0" y="25.5" />
</edge>
<edge source="\DesignPatterns\Structural\Composite\InputElement" target="\DesignPatterns\Structural\Composite\RenderableInterface">
<point x="0.0" y="-25.5" />
<point x="0.0" y="25.5" />
</edge>
</edges>
<settings layout="Hierarchic Group" zoom="1.0" x="164.5" y="54.0" />
<SelectedNodes />
<Categories>
<Category>Methods</Category>
<Category>Constants</Category>
<Category>Fields</Category>
</Categories>
<VISIBILITY>private</VISIBILITY>
</Diagram>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 34 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

@@ -47,6 +47,12 @@ UserMapper.php
:language: php
:linenos:
StorageAdapter.php
.. literalinclude:: StorageAdapter.php
:language: php
:linenos:
Test
----

View File

@@ -0,0 +1,30 @@
<?php
namespace DesignPatterns\Structural\DataMapper;
class StorageAdapter
{
/**
* @var array
*/
private $data = [];
public function __construct(array $data)
{
$this->data = $data;
}
/**
* @param int $id
*
* @return array|null
*/
public function find(int $id)
{
if (isset($this->data[$id])) {
return $this->data[$id];
}
return null;
}
}

View File

@@ -2,109 +2,29 @@
namespace DesignPatterns\Structural\DataMapper\Tests;
use DesignPatterns\Structural\DataMapper\User;
use DesignPatterns\Structural\DataMapper\StorageAdapter;
use DesignPatterns\Structural\DataMapper\UserMapper;
/**
* UserMapperTest tests the datamapper pattern.
*/
class DataMapperTest extends \PHPUnit_Framework_TestCase
{
/**
* @var UserMapper
*/
protected $mapper;
/**
* @var DBAL
*/
protected $dbal;
protected function setUp()
public function testCanMapUserFromStorage()
{
$this->dbal = $this->getMockBuilder('DesignPatterns\Structural\DataMapper\DBAL')
->disableAutoload()
->setMethods(array('insert', 'update', 'find', 'findAll'))
->getMock();
$storage = new StorageAdapter([1 => ['username' => 'domnikl', 'email' => 'liebler.dominik@gmail.com']]);
$mapper = new UserMapper($storage);
$this->mapper = new UserMapper($this->dbal);
}
$user = $mapper->findById(1);
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);
$this->assertInstanceOf('DesignPatterns\Structural\DataMapper\User', $user);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage User #404 not found
*/
public function testNotFound()
public function testWillNotMapInvalidData()
{
$this->dbal->expects($this->once())
->method('find')
->with(404)
->will($this->returnValue(array()));
$storage = new StorageAdapter([]);
$mapper = new UserMapper($storage);
$user = $this->mapper->findById(404);
$mapper->findById(1);
}
}

View File

@@ -2,56 +2,34 @@
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
* @var string
*/
protected $userId;
private $username;
/**
* @var string
*/
protected $username;
private $email;
/**
* @var string
*/
protected $email;
/**
* @param null $id
* @param null $username
* @param null $email
*/
public function __construct($id = null, $username = null, $email = null)
public static function fromState(array $state): User
{
$this->setUserID($id);
$this->setUsername($username);
$this->setEmail($email);
// validate state before accessing keys!
return new self(
$state['username'],
$state['email']
);
}
/**
* @return int
*/
public function getUserId()
public function __construct(string $username, string $email)
{
return $this->userId;
}
// validate parameters before setting them!
/**
* @param int $userId
*/
public function setUserID($userId)
{
$this->userId = $userId;
$this->username = $username;
$this->email = $email;
}
/**
@@ -62,14 +40,6 @@ class User
return $this->username;
}
/**
* @param string $username
*/
public function setUsername($username)
{
$this->username = $username;
}
/**
* @return string
*/
@@ -77,12 +47,4 @@ class User
{
return $this->email;
}
/**
* @param string $email
*/
public function setEmail($email)
{
$this->email = $email;
}
}

View File

@@ -2,107 +2,44 @@
namespace DesignPatterns\Structural\DataMapper;
/**
* class UserMapper.
*/
class UserMapper
{
/**
* @var DBAL
* @var StorageAdapter
*/
protected $adapter;
private $adapter;
/**
* @param DBAL $dbLayer
* @param StorageAdapter $storage
*/
public function __construct(DBAL $dbLayer)
public function __construct(StorageAdapter $storage)
{
$this->adapter = $dbLayer;
$this->adapter = $storage;
}
/**
* saves a user object from memory to Database.
*
* @param User $user
*
* @return bool
*/
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.
* finds a user from storage based on ID and returns a User object located
* in memory. Normally this kind of logic will be implemented using the Repository pattern.
* However the important part is in mapRowToUser() below, that will create a business object from the
* data fetched from storage
*
* @param int $id
*
* @throws \InvalidArgumentException
*
* @return User
*/
public function findById($id)
public function findById(int $id): User
{
$result = $this->adapter->find($id);
if (0 == count($result)) {
if ($result === null) {
throw new \InvalidArgumentException("User #$id not found");
}
$row = $result->current();
return $this->mapObject($row);
return $this->mapRowToUser($result);
}
/**
* fetches an array from Database and returns an array of User objects
* located in memory.
*
* @return array
*/
public function findAll()
private function mapRowToUser(array $row): User
{
$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;
return User::fromState($row);
}
}

View File

@@ -1,23 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<Diagram>
<ID>PHP</ID>
<OriginalElement>\DesignPatterns\Structural\DataMapper\User</OriginalElement>
<nodes>
<node x="0.0" y="0.0">\DesignPatterns\Structural\DataMapper\User</node>
<node x="0.0" y="274.0">\DesignPatterns\Structural\DataMapper\UserMapper</node>
</nodes>
<notes />
<edges />
<settings layout="Hierarchic Group" zoom="0.871331828442438" x="141.5" y="211.5" />
<SelectedNodes>
<node>\DesignPatterns\Structural\DataMapper\User</node>
</SelectedNodes>
<Categories>
<Category>Fields</Category>
<Category>Constants</Category>
<Category>Constructors</Category>
<Category>Methods</Category>
</Categories>
<VISIBILITY>private</VISIBILITY>
</Diagram>
<?xml version="1.0" encoding="UTF-8"?>
<Diagram>
<ID>PHP</ID>
<OriginalElement>\DesignPatterns\Structural\DataMapper\StorageAdapter</OriginalElement>
<nodes>
<node x="0.0" y="185.0">\DesignPatterns\Structural\DataMapper\UserMapper</node>
<node x="204.0" y="185.0">\DesignPatterns\Structural\DataMapper\StorageAdapter</node>
<node x="0.0" y="0.0">\DesignPatterns\Structural\DataMapper\User</node>
</nodes>
<notes />
<edges />
<settings layout="Hierarchic Group" zoom="1.0" x="64.0" y="88.5" />
<SelectedNodes>
<node>\DesignPatterns\Structural\DataMapper\StorageAdapter</node>
</SelectedNodes>
<Categories>
<Category>Methods</Category>
<Category>Fields</Category>
<Category>Constants</Category>
</Categories>
<VISIBILITY>private</VISIBILITY>
</Diagram>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 112 KiB

View File

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

View File

@@ -0,0 +1,11 @@
<?php
namespace DesignPatterns\Structural\Decorator;
class JsonRenderer extends RendererDecorator
{
public function renderData(): string
{
return json_encode($this->wrapped->renderData());
}
}

View File

@@ -25,9 +25,9 @@ Code
You can also find these code on `GitHub`_
RendererInterface.php
RenderableInterface.php
.. literalinclude:: RendererInterface.php
.. literalinclude:: RenderableInterface.php
:language: php
:linenos:
@@ -37,21 +37,21 @@ Webservice.php
:language: php
:linenos:
Decorator.php
RendererDecorator.php
.. literalinclude:: Decorator.php
.. literalinclude:: RendererDecorator.php
:language: php
:linenos:
RenderInXml.php
XmlRenderer.php
.. literalinclude:: RenderInXml.php
.. literalinclude:: XmlRenderer.php
:language: php
:linenos:
RenderInJson.php
JsonRenderer.php
.. literalinclude:: RenderInJson.php
.. literalinclude:: JsonRenderer.php
:language: php
:linenos:

View File

@@ -1,19 +0,0 @@
<?php
namespace DesignPatterns\Structural\Decorator;
/**
* Class RenderInJson.
*/
class RenderInJson extends Decorator
{
/**
* render data as JSON.
*
* @return string
*/
public function renderData()
{
return json_encode($this->wrapped->renderData());
}
}

View File

@@ -1,27 +0,0 @@
<?php
namespace DesignPatterns\Structural\Decorator;
/**
* Class RenderInXml.
*/
class RenderInXml extends Decorator
{
/**
* render data as XML.
*
* @return string
*/
public function renderData()
{
// do some fancy conversion to xml from array ...
$doc = new \DOMDocument();
foreach ($this->wrapped->renderData() as $key => $val) {
$doc->appendChild($doc->createElement($key, $val));
}
return $doc->saveXML();
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace DesignPatterns\Structural\Decorator;
interface RenderableInterface
{
public function renderData(): string;
}

View File

@@ -0,0 +1,24 @@
<?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.
*/
abstract class RendererDecorator
{
/**
* @var RenderableInterface
*/
protected $wrapped;
/**
* @param RenderableInterface $renderer
*/
public function __construct(RenderableInterface $renderer)
{
$this->wrapped = $renderer;
}
}

View File

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

View File

@@ -4,77 +4,29 @@ namespace DesignPatterns\Structural\Decorator\Tests;
use DesignPatterns\Structural\Decorator;
/**
* DecoratorTest tests the decorator pattern.
*/
class DecoratorTest extends \PHPUnit_Framework_TestCase
{
protected $service;
/**
* @var Decorator\Webservice
*/
private $service;
protected function setUp()
{
$this->service = new Decorator\Webservice(array('foo' => 'bar'));
$this->service = new Decorator\Webservice('foobar');
}
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());
$service = new Decorator\JsonRenderer($this->service);
$this->assertEquals('"foobar"', $service->renderData());
}
public function testXmlDecorator()
{
// Wrap service with a XML 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());
}
$service = new Decorator\XmlRenderer($this->service);
/**
* 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()
{
if (version_compare(PHP_VERSION, '7', '>=')) {
throw new \PHPUnit_Framework_Error('Skip test for PHP 7', 0, __FILE__, __LINE__);
}
$this->getMockForAbstractClass('DesignPatterns\Structural\Decorator\Decorator', array(new \stdClass()));
}
/**
* Second key-point of this pattern : the decorator is type-hinted.
*
* @requires PHP 7
* @expectedException TypeError
*/
public function testDecoratorTypeHintedForPhp7()
{
$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);
$this->assertXmlStringEqualsXmlString('<?xml version="1.0"?><content>foobar</content>', $service->renderData());
}
}

View File

@@ -2,28 +2,19 @@
namespace DesignPatterns\Structural\Decorator;
/**
* Class Webservice.
*/
class Webservice implements RendererInterface
class Webservice implements RenderableInterface
{
/**
* @var mixed
* @var string
*/
protected $data;
private $data;
/**
* @param mixed $data
*/
public function __construct($data)
public function __construct(string $data)
{
$this->data = $data;
}
/**
* @return string
*/
public function renderData()
public function renderData(): string
{
return $this->data;
}

View File

@@ -0,0 +1,15 @@
<?php
namespace DesignPatterns\Structural\Decorator;
class XmlRenderer extends RendererDecorator
{
public function renderData(): string
{
$doc = new \DOMDocument();
$data = $this->wrapped->renderData();
$doc->appendChild($doc->createElement('content', $data));
return $doc->saveXML();
}
}

View File

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

View File

@@ -1,39 +0,0 @@
<?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;
}
}

View File

@@ -1,50 +0,0 @@
<?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;
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace DesignPatterns\Structural\DependencyInjection;
class DatabaseConfiguration
{
/**
* @var string
*/
private $host;
/**
* @var int
*/
private $port;
/**
* @var string
*/
private $username;
/**
* @var string
*/
private $password;
public function __construct(string $host, int $port, string $username, string $password)
{
$this->host = $host;
$this->port = $port;
$this->username = $username;
$this->password = $password;
}
public function getHost(): string
{
return $this->host;
}
public function getPort(): int
{
return $this->port;
}
public function getUsername(): string
{
return $this->username;
}
public function getPassword(): string
{
return $this->password;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace DesignPatterns\Structural\DependencyInjection;
class DatabaseConnection
{
/**
* @var DatabaseConfiguration
*/
private $configuration;
/**
* @param DatabaseConfiguration $config
*/
public function __construct(DatabaseConfiguration $config)
{
$this->configuration = $config;
}
public function getDsn(): string
{
// this is just for the sake of demonstration, not a real DSN
// notice that only the injected config is used here, so there is
// a real separation of concerns here
return sprintf(
'%s:%s@%s:%d',
$this->configuration->getUsername(),
$this->configuration->getPassword(),
$this->configuration->getHost(),
$this->configuration->getPort()
);
}
}

View File

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

View File

@@ -10,17 +10,10 @@ testable, maintainable and extendable code.
Usage
-----
Configuration gets injected and ``Connection`` will get all that it
DatabaseConfiguration gets injected and ``DatabaseConnection`` 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 <http://en.wikipedia.org/wiki/Inversion_of_control>`__.
directly in ``DatabaseConnection``, which is not very good for testing and
extending it.
Examples
--------
@@ -45,27 +38,15 @@ Code
You can also find these code on `GitHub`_
AbstractConfig.php
DatabaseConfiguration.php
.. literalinclude:: AbstractConfig.php
.. literalinclude:: DatabaseConfiguration.php
:language: php
:linenos:
Parameters.php
DatabaseConnection.php
.. literalinclude:: Parameters.php
:language: php
:linenos:
ArrayConfig.php
.. literalinclude:: ArrayConfig.php
:language: php
:linenos:
Connection.php
.. literalinclude:: Connection.php
.. literalinclude:: DatabaseConnection.php
:language: php
:linenos:
@@ -78,11 +59,5 @@ Tests/DependencyInjectionTest.php
:language: php
:linenos:
Tests/config.php
.. literalinclude:: Tests/config.php
:language: php
:linenos:
.. _`GitHub`: https://github.com/domnikl/DesignPatternsPHP/tree/master/Structural/DependencyInjection
.. __: http://en.wikipedia.org/wiki/Dependency_injection

View File

@@ -2,24 +2,16 @@
namespace DesignPatterns\Structural\DependencyInjection\Tests;
use DesignPatterns\Structural\DependencyInjection\ArrayConfig;
use DesignPatterns\Structural\DependencyInjection\Connection;
use DesignPatterns\Structural\DependencyInjection\DatabaseConfiguration;
use DesignPatterns\Structural\DependencyInjection\DatabaseConnection;
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());
$config = new DatabaseConfiguration('localhost', 3306, 'domnikl', '1234');
$connection = new DatabaseConnection($config);
$this->assertEquals('domnikl:1234@localhost:3306', $connection->getDsn());
}
}

View File

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

View File

@@ -1,38 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<Diagram>
<ID>PHP</ID>
<OriginalElement>\DesignPatterns\Structural\DependencyInjection\AbstractConfig</OriginalElement>
<nodes>
<node x="0.0" y="230.0">\DesignPatterns\Structural\DependencyInjection\Connection</node>
<node x="51.0" y="118.0">\DesignPatterns\Structural\DependencyInjection\ArrayConfig</node>
<node x="0.0" y="0.5">\DesignPatterns\Structural\DependencyInjection\Parameters</node>
<node x="137.0" y="0.0">\DesignPatterns\Structural\DependencyInjection\AbstractConfig</node>
</nodes>
<notes />
<edges>
<edge source="\DesignPatterns\Structural\DependencyInjection\ArrayConfig" target="\DesignPatterns\Structural\DependencyInjection\AbstractConfig">
<point x="39.5" y="-33.5" />
<point x="169.5" y="93.0" />
<point x="201.5" y="93.0" />
<point x="0.0" y="34.0" />
</edge>
<edge source="\DesignPatterns\Structural\DependencyInjection\ArrayConfig" target="\DesignPatterns\Structural\DependencyInjection\Parameters">
<point x="-39.5" y="-33.5" />
<point x="90.5" y="93.0" />
<point x="58.5" y="93.0" />
<point x="0.0" y="33.5" />
</edge>
</edges>
<settings layout="Hierarchic Group" zoom="1.0" x="133.0" y="179.5" />
<SelectedNodes>
<node>\DesignPatterns\Structural\DependencyInjection\AbstractConfig</node>
</SelectedNodes>
<Categories>
<Category>Fields</Category>
<Category>Constants</Category>
<Category>Constructors</Category>
<Category>Methods</Category>
</Categories>
<VISIBILITY>private</VISIBILITY>
</Diagram>
<?xml version="1.0" encoding="UTF-8"?>
<Diagram>
<ID>PHP</ID>
<OriginalElement>\DesignPatterns\Structural\DependencyInjection\DatabaseConfiguration</OriginalElement>
<nodes>
<node x="0.0" y="251.0">\DesignPatterns\Structural\DependencyInjection\DatabaseConnection</node>
<node x="0.0" y="0.0">\DesignPatterns\Structural\DependencyInjection\DatabaseConfiguration</node>
</nodes>
<notes />
<edges />
<settings layout="Hierarchic Group" zoom="1.0" x="88.5" y="51.5" />
<SelectedNodes />
<Categories>
<Category>Methods</Category>
<Category>Constants</Category>
<Category>Fields</Category>
</Categories>
<VISIBILITY>private</VISIBILITY>
</Diagram>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -2,30 +2,13 @@
namespace DesignPatterns\Structural\Facade;
/**
* Interface 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

@@ -2,25 +2,19 @@
namespace DesignPatterns\Structural\Facade;
/**
* Class Facade.
*/
class Facade
{
/**
* @var OsInterface
*/
protected $os;
private $os;
/**
* @var BiosInterface
*/
protected $bios;
private $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
*/
@@ -30,9 +24,6 @@ class Facade
$this->os = $os;
}
/**
* Turn on the system.
*/
public function turnOn()
{
$this->bios->execute();
@@ -40,9 +31,6 @@ class Facade
$this->bios->launch($this->os);
}
/**
* Turn off the system.
*/
public function turnOff()
{
$this->os->halt();

View File

@@ -2,13 +2,9 @@
namespace DesignPatterns\Structural\Facade;
/**
* Interface OsInterface.
*/
interface OsInterface
{
/**
* Halt the OS.
*/
public function halt();
public function getName(): string;
}

View File

@@ -2,47 +2,34 @@
namespace DesignPatterns\Structural\Facade\Tests;
use DesignPatterns\Structural\Facade\Facade as Computer;
use DesignPatterns\Structural\Facade\Facade;
use DesignPatterns\Structural\Facade\OsInterface;
/**
* FacadeTest shows example of facades.
*/
class FacadeTest extends \PHPUnit_Framework_TestCase
{
public function getComputer()
public function testComputerOn()
{
/** @var OsInterface|\PHPUnit_Framework_MockObject_MockObject $os */
$os = $this->createMock('DesignPatterns\Structural\Facade\OsInterface');
$os->method('getName')
->will($this->returnValue('Linux'));
$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();
->setMethods(['launch', 'execute', 'waitForKeyPress'])
->disableAutoload()
->getMock();
$bios->expects($this->once())
->method('launch')
->with($operatingSys);
$operatingSys
->expects($this->once())
->method('getName')
->will($this->returnValue('Linux'));
->method('launch')
->with($os);
$facade = new Computer($bios, $operatingSys);
$facade = new Facade($bios, $os);
return array(array($facade, $operatingSys));
}
/**
* @param Computer $facade
* @param OsInterface $os
* @dataProvider getComputer
*/
public function testComputerOn(Computer $facade, OsInterface $os)
{
// interface is simpler :
// the facade interface is simple
$facade->turnOn();
// but I can access to lower component
// but you can also access the underlying components
$this->assertEquals('Linux', $os->getName());
}
}

View File

@@ -1,24 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<Diagram>
<ID>PHP</ID>
<OriginalElement>\DesignPatterns\Structural\Facade\BiosInterface</OriginalElement>
<nodes>
<node x="0.0" y="174.0">\DesignPatterns\Structural\Facade\BiosInterface</node>
<node x="208.0" y="174.0">\DesignPatterns\Structural\Facade\OsInterface</node>
<node x="0.0" y="0.0">\DesignPatterns\Structural\Facade\Facade</node>
</nodes>
<notes />
<edges />
<settings layout="Hierarchic Group" zoom="1.0" x="149.0" y="140.5" />
<SelectedNodes>
<node>\DesignPatterns\Structural\Facade\BiosInterface</node>
</SelectedNodes>
<Categories>
<Category>Fields</Category>
<Category>Constants</Category>
<Category>Constructors</Category>
<Category>Methods</Category>
</Categories>
<VISIBILITY>private</VISIBILITY>
</Diagram>
<?xml version="1.0" encoding="UTF-8"?>
<Diagram>
<ID>PHP</ID>
<OriginalElement>\DesignPatterns\Structural\Facade\BiosInterface</OriginalElement>
<nodes>
<node x="0.0" y="163.0">\DesignPatterns\Structural\Facade\BiosInterface</node>
<node x="0.0" y="0.0">\DesignPatterns\Structural\Facade\Facade</node>
<node x="234.0" y="163.0">\DesignPatterns\Structural\Facade\OsInterface</node>
</nodes>
<notes />
<edges />
<settings layout="Hierarchic Group" zoom="1.0" x="179.0" y="140.0" />
<SelectedNodes>
<node>\DesignPatterns\Structural\Facade\BiosInterface</node>
</SelectedNodes>
<Categories>
<Category>Methods</Category>
<Category>Constants</Category>
<Category>Fields</Category>
</Categories>
<VISIBILITY>private</VISIBILITY>
</Diagram>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 108 KiB

View File

@@ -2,79 +2,51 @@
namespace DesignPatterns\Structural\FluentInterface;
/**
* class SQL.
*/
class Sql
{
/**
* @var array
*/
protected $fields = array();
private $fields = [];
/**
* @var array
*/
protected $from = array();
private $from = [];
/**
* @var array
*/
protected $where = array();
private $where = [];
/**
* adds select fields.
*
* @param array $fields
*
* @return SQL
*/
public function select(array $fields = array())
public function select(array $fields): Sql
{
$this->fields = $fields;
return $this;
}
/**
* adds a FROM clause.
*
* @param string $table
* @param string $alias
*
* @return SQL
*/
public function from($table, $alias)
public function from(string $table, string $alias): Sql
{
$this->from[] = $table.' AS '.$alias;
return $this;
}
/**
* adds a WHERE condition.
*
* @param string $condition
*
* @return SQL
*/
public function where($condition)
public function where(string $condition): Sql
{
$this->where[] = $condition;
return $this;
}
/**
* Gets the query, just an example of building a query,
* no check on consistency.
*
* @return string
*/
public function getQuery()
public function __toString(): string
{
return 'SELECT '.implode(',', $this->fields)
.' FROM '.implode(',', $this->from)
.' WHERE '.implode(' AND ', $this->where);
return sprintf(
'SELECT %s FROM %s WHERE %s',
join(', ', $this->fields),
join(', ', $this->from),
join(' AND ', $this->where)
);
}
}

View File

@@ -4,19 +4,15 @@ 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'))
$query = (new Sql())
->select(['foo', 'bar'])
->from('foobar', 'f')
->where('f.bar = ?')
->getQuery();
->where('f.bar = ?');
$this->assertEquals('SELECT foo,bar FROM foobar AS f WHERE f.bar = ?', $query);
$this->assertEquals('SELECT foo, bar FROM foobar AS f WHERE f.bar = ?', (string) $query);
}
}

View File

@@ -1,22 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<Diagram>
<ID>PHP</ID>
<OriginalElement>\DesignPatterns\Structural\FluentInterface\Sql</OriginalElement>
<nodes>
<node x="0.0" y="0.0">\DesignPatterns\Structural\FluentInterface\Sql</node>
</nodes>
<notes />
<edges />
<settings layout="Hierarchic Group" zoom="1.0" x="108.5" y="84.0" />
<SelectedNodes>
<node>\DesignPatterns\Structural\FluentInterface\Sql</node>
</SelectedNodes>
<Categories>
<Category>Fields</Category>
<Category>Constants</Category>
<Category>Constructors</Category>
<Category>Methods</Category>
</Categories>
<VISIBILITY>private</VISIBILITY>
</Diagram>
<?xml version="1.0" encoding="UTF-8"?>
<Diagram>
<ID>PHP</ID>
<OriginalElement>\DesignPatterns\Structural\FluentInterface\Sql</OriginalElement>
<nodes>
<node x="0.0" y="0.0">\DesignPatterns\Structural\FluentInterface\Sql</node>
</nodes>
<notes />
<edges />
<settings layout="Hierarchic Group" zoom="1.0" x="25.5" y="14.5" />
<SelectedNodes />
<Categories>
<Category>Fields</Category>
<Category>Constants</Category>
<Category>Methods</Category>
</Categories>
<VISIBILITY>private</VISIBILITY>
</Diagram>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -16,22 +16,16 @@ class CharacterFlyweight implements FlyweightInterface
*/
private $name;
/**
* @param string $name
*/
public function __construct($name)
public function __construct(string $name)
{
$this->name = $name;
}
/**
* Clients supply the context-dependent information that the flyweight needs to draw itself
* For flyweights representing characters, extrinsic state usually contains e.g. the font.
*
* @param string $font
*/
public function draw($font)
public function render(string $font): string
{
print_r("Character {$this->name} printed $font \n");
// Clients supply the context-dependent information that the flyweight needs to draw itself
// For flyweights representing characters, extrinsic state usually contains e.g. the font.
return sprintf('Character %s with font %s', $this->name, $font);
}
}

View File

@@ -3,38 +3,26 @@
namespace DesignPatterns\Structural\Flyweight;
/**
* A factory manages shared flyweights. Clients shouldn't instaniate them directly,
* A factory manages shared flyweights. Clients should not instantiate them directly,
* but let the factory take care of returning existing objects or creating new ones.
*/
class FlyweightFactory
class FlyweightFactory implements \Countable
{
/**
* Associative store for flyweight objects.
*
* @var array
* @var CharacterFlyweight[]
*/
private $pool = array();
private $pool = [];
/**
* Magic getter.
*
* @param string $name
*
* @return Flyweight
*/
public function __get($name)
public function get(string $name): CharacterFlyweight
{
if (!array_key_exists($name, $this->pool)) {
if (!isset($this->pool[$name])) {
$this->pool[$name] = new CharacterFlyweight($name);
}
return $this->pool[$name];
}
/**
* @return int
*/
public function totalNumber()
public function count(): int
{
return count($this->pool);
}

View File

@@ -2,13 +2,7 @@
namespace DesignPatterns\Structural\Flyweight;
/**
* An interface through which flyweights can receive and act on extrinsic state.
*/
interface FlyweightInterface
{
/**
* @param string $extrinsicState
*/
public function draw($extrinsicState);
public function render(string $extrinsicState): string;
}

View File

@@ -4,33 +4,28 @@ namespace DesignPatterns\Structural\Flyweight\Tests;
use DesignPatterns\Structural\Flyweight\FlyweightFactory;
/**
* FlyweightTest demonstrates how a client would use the flyweight structure
* You don't have to change the code of your client.
*/
class FlyweightTest extends \PHPUnit_Framework_TestCase
{
private $characters = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', );
private $fonts = array('Arial', 'Times New Roman', 'Verdana', 'Helvetica');
// This is about the number of characters in a book of average length
private $numberOfCharacters = 300000;
private $characters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
private $fonts = ['Arial', 'Times New Roman', 'Verdana', 'Helvetica'];
public function testFlyweight()
{
$factory = new FlyweightFactory();
for ($i = 0; $i < $this->numberOfCharacters; $i++) {
$char = $this->characters[array_rand($this->characters)];
$font = $this->fonts[array_rand($this->fonts)];
$flyweight = $factory->$char;
// External state can be passed in like this:
// $flyweight->draw($font);
foreach ($this->characters as $char) {
foreach ($this->fonts as $font) {
$flyweight = $factory->get($char);
$rendered = $flyweight->render($font);
$this->assertEquals(sprintf('Character %s with font %s', $char, $font), $rendered);
}
}
// Flyweight pattern ensures that instances are shared
// instead of having hundreds of thousands of individual objects
$this->assertLessThanOrEqual($factory->totalNumber(), count($this->characters));
// there must be one instance for every char that has been reused for displaying in different fonts
$this->assertCount(count($this->characters), $factory);
}
}

View File

@@ -1,25 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<Diagram>
<ID>PHP</ID>
<OriginalElement>\DesignPatterns\Structural\Flyweight\FlyweightFactory</OriginalElement>
<OriginalElement>\DesignPatterns\Structural\Flyweight\CharacterFlyweight</OriginalElement>
<nodes>
<node x="14.0" y="109.0">\DesignPatterns\Structural\Flyweight\CharacterFlyweight</node>
<node x="235.0" y="0.0">\DesignPatterns\Structural\Flyweight\FlyweightFactory</node>
<node x="-8.0" y="0.0">\DesignPatterns\Structural\Flyweight\FlyweightInterface</node>
<node x="19.0" y="101.0">\DesignPatterns\Structural\Flyweight\CharacterFlyweight</node>
<node x="230.0" y="94.0">\DesignPatterns\Structural\Flyweight\FlyweightFactory</node>
<node x="0.0" y="0.0">\DesignPatterns\Structural\Flyweight\FlyweightInterface</node>
</nodes>
<notes />
<edges>
<edge source="\DesignPatterns\Structural\Flyweight\CharacterFlyweight" target="\DesignPatterns\Structural\Flyweight\FlyweightInterface">
<point x="0.0" y="-56.5" />
<point x="0.0" y="29.5" />
<point x="0.0" y="-37.0" />
<point x="0.0" y="25.5" />
</edge>
</edges>
<settings layout="Hierarchic Group" zoom="1.0" x="191.0" y="111.0" />
<settings layout="Hierarchic Group" zoom="1.0" x="76.0" y="91.0" />
<SelectedNodes />
<Categories>
<Category>Fields</Category>
<Category>Constants</Category>
<Category>Constructors</Category>
<Category>Methods</Category>
</Categories>
<VISIBILITY>private</VISIBILITY>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 35 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -3,49 +3,38 @@
namespace DesignPatterns\Structural\Proxy;
/**
* class Record.
* @property string username
*/
class Record
{
/**
* @var array|null
* @var string[]
*/
protected $data;
private $data;
/**
* @param null $data
* @param string[] $data
*/
public function __construct($data = null)
public function __construct(array $data = [])
{
$this->data = (array) $data;
$this->data = $data;
}
/**
* magic setter.
*
* @param string $name
* @param mixed $value
*
* @return void
* @param string $value
*/
public function __set($name, $value)
public function __set(string $name, string $value)
{
$this->data[(string) $name] = $value;
$this->data[$name] = $value;
}
/**
* magic getter.
*
* @param string $name
*
* @return mixed|null
*/
public function __get($name)
public function __get(string $name): string
{
if (array_key_exists($name, $this->data)) {
return $this->data[(string) $name];
} else {
return;
if (!isset($this->data[$name])) {
throw new \OutOfRangeException('Invalid name given');
}
return $this->data[$name];
}
}

View File

@@ -2,25 +2,22 @@
namespace DesignPatterns\Structural\Proxy;
/**
* Class RecordProxy.
*/
class RecordProxy extends Record
{
/**
* @var bool
*/
protected $isDirty = false;
private $isDirty = false;
/**
* @var bool
*/
protected $isInitialized = false;
private $isInitialized = false;
/**
* @param array $data
*/
public function __construct($data)
public function __construct(array $data)
{
parent::__construct($data);
@@ -28,23 +25,25 @@ class RecordProxy extends Record
// 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) {
if (count($data) > 0) {
$this->isInitialized = true;
$this->isDirty = true;
}
}
/**
* magic setter.
*
* @param string $name
* @param mixed $value
*
* @return void
* @param string $value
*/
public function __set($name, $value)
public function __set(string $name, string $value)
{
$this->isDirty = true;
parent::__set($name, $value);
}
public function isDirty(): bool
{
return $this->isDirty;
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace DesignPatterns\Structural\Proxy\Tests;
use DesignPatterns\Structural\Decorator;
use DesignPatterns\Structural\Proxy\RecordProxy;
class ProxyTest extends \PHPUnit_Framework_TestCase
{
public function testWillSetDirtyFlagInProxy()
{
$recordProxy = new RecordProxy([]);
$recordProxy->username = 'baz';
$this->assertTrue($recordProxy->isDirty());
}
public function testProxyIsInstanceOfRecord()
{
$recordProxy = new RecordProxy([]);
$recordProxy->username = 'baz';
$this->assertInstanceOf('DesignPatterns\Structural\Proxy\Record', $recordProxy);
}
}

View File

@@ -6,7 +6,8 @@ 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)
static methods (or using the Singleton pattern). Remember that this introduces
global state, which should be avoided at all times! Instead implement it using Dependency Injection!
Examples
--------

View File

@@ -2,46 +2,51 @@
namespace DesignPatterns\Structural\Registry;
/**
* class Registry.
*/
abstract class Registry
{
const LOGGER = 'logger';
/**
* this introduces global state in your application which can not be mocked up for testing
* and is therefor considered an anti-pattern! Use dependency injection instead!
*
* @var array
*/
protected static $storedValues = array();
private static $storedValues = [];
/**
* @var array
*/
private static $allowedKeys = [
self::LOGGER,
];
/**
* sets a value.
*
* @param string $key
* @param mixed $value
*
* @static
*
* @return void
*/
public static function set($key, $value)
public static function set(string $key, $value)
{
if (!in_array($key, self::$allowedKeys)) {
throw new \InvalidArgumentException('Invalid key given');
}
self::$storedValues[$key] = $value;
}
/**
* gets a value from the registry.
*
* @param string $key
*
* @static
*
* @return mixed
*/
public static function get($key)
public static function get(string $key)
{
if (!in_array($key, self::$allowedKeys) || !isset(self::$storedValues[$key])) {
throw new \InvalidArgumentException('Invalid key given');
}
return self::$storedValues[$key];
}
// typically there would be methods to check if a key has already been registered and so on ...
}

View File

@@ -9,12 +9,33 @@ class RegistryTest extends \PHPUnit_Framework_TestCase
public function testSetAndGetLogger()
{
$key = Registry::LOGGER;
$object = new \StdClass();
$logger = new \stdClass();
Registry::set($key, $object);
$actual = Registry::get($key);
Registry::set($key, $logger);
$storedLogger = Registry::get($key);
$this->assertEquals($object, $actual);
$this->assertInstanceOf('StdClass', $actual);
$this->assertSame($logger, $storedLogger);
$this->assertInstanceOf('stdClass', $storedLogger);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testThrowsExceptionWhenTryingToSetInvalidKey()
{
Registry::set('foobar', new \stdClass());
}
/**
* notice @runInSeparateProcess here: without it, a previous test might have set it already and
* testing would not be possible. That's why you should implement Dependency Injection where an
* injected class may easily be replaced by a mockup
*
* @runInSeparateProcess
* @expectedException \InvalidArgumentException
*/
public function testThrowsExceptionWhenTryingToGetNotSetKey()
{
Registry::get(Registry::LOGGER);
}
}