Merged branch master into translate-template
@@ -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;
|
||||
}
|
||||
}
|
||||
|
12
Structural/Adapter/BookInterface.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Structural\Adapter;
|
||||
|
||||
interface BookInterface
|
||||
{
|
||||
public function turnPage();
|
||||
|
||||
public function open();
|
||||
|
||||
public function getPage(): int;
|
||||
}
|
@@ -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];
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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];
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
@@ -28,7 +28,7 @@ Code
|
||||
|
||||
You can also find these code on `GitHub`_
|
||||
|
||||
PaperBookInterface.php
|
||||
BookInterface.php
|
||||
|
||||
.. literalinclude:: PaperBookInterface.php
|
||||
:language: php
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Structural\Bridge;
|
||||
|
||||
class Assemble implements Workshop
|
||||
{
|
||||
public function work()
|
||||
{
|
||||
echo 'Assembled';
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
8
Structural/Bridge/FormatterInterface.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Structural\Bridge;
|
||||
|
||||
interface FormatterInterface
|
||||
{
|
||||
public function format(string $text);
|
||||
}
|
11
Structural/Bridge/HelloWorldService.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Structural\Bridge;
|
||||
|
||||
class HelloWorldService extends Service
|
||||
{
|
||||
public function get()
|
||||
{
|
||||
return $this->implementation->format('Hello World');
|
||||
}
|
||||
}
|
11
Structural/Bridge/HtmlFormatter.php
Normal 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);
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
11
Structural/Bridge/PlainTextFormatter.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Structural\Bridge;
|
||||
|
||||
class PlainTextFormatter implements FormatterInterface
|
||||
{
|
||||
public function format(string $text)
|
||||
{
|
||||
return $text;
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Structural\Bridge;
|
||||
|
||||
/**
|
||||
* Concrete Implementation.
|
||||
*/
|
||||
class Produce implements Workshop
|
||||
{
|
||||
public function work()
|
||||
{
|
||||
echo 'Produced ';
|
||||
}
|
||||
}
|
@@ -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:
|
||||
|
||||
|
29
Structural/Bridge/Service.php
Normal 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();
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Structural\Bridge;
|
||||
|
||||
/**
|
||||
* Implementer.
|
||||
*/
|
||||
interface Workshop
|
||||
{
|
||||
public function work();
|
||||
}
|
@@ -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>
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 105 KiB |
@@ -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;
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
@@ -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" />';
|
||||
}
|
||||
}
|
||||
|
@@ -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:
|
||||
|
||||
|
8
Structural/Composite/RenderableInterface.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Structural\Composite;
|
||||
|
||||
interface RenderableInterface
|
||||
{
|
||||
public function render(): string;
|
||||
}
|
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 96 KiB |
@@ -47,6 +47,12 @@ UserMapper.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
StorageAdapter.php
|
||||
|
||||
.. literalinclude:: StorageAdapter.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
Test
|
||||
----
|
||||
|
||||
|
30
Structural/DataMapper/StorageAdapter.php
Normal 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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 112 KiB |
@@ -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;
|
||||
}
|
||||
}
|
11
Structural/Decorator/JsonRenderer.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Structural\Decorator;
|
||||
|
||||
class JsonRenderer extends RendererDecorator
|
||||
{
|
||||
public function renderData(): string
|
||||
{
|
||||
return json_encode($this->wrapped->renderData());
|
||||
}
|
||||
}
|
@@ -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:
|
||||
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
8
Structural/Decorator/RenderableInterface.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Structural\Decorator;
|
||||
|
||||
interface RenderableInterface
|
||||
{
|
||||
public function renderData(): string;
|
||||
}
|
24
Structural/Decorator/RendererDecorator.php
Normal 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;
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Structural\Decorator;
|
||||
|
||||
/**
|
||||
* Class RendererInterface.
|
||||
*/
|
||||
interface RendererInterface
|
||||
{
|
||||
/**
|
||||
* render data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function renderData();
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
15
Structural/Decorator/XmlRenderer.php
Normal 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();
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
54
Structural/DependencyInjection/DatabaseConfiguration.php
Normal 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;
|
||||
}
|
||||
}
|
34
Structural/DependencyInjection/DatabaseConnection.php
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -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
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +0,0 @@
|
||||
<?php
|
||||
|
||||
return array('host' => 'github.com');
|
@@ -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>
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 100 KiB |
@@ -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();
|
||||
}
|
||||
|
@@ -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();
|
||||
|
@@ -2,13 +2,9 @@
|
||||
|
||||
namespace DesignPatterns\Structural\Facade;
|
||||
|
||||
/**
|
||||
* Interface OsInterface.
|
||||
*/
|
||||
interface OsInterface
|
||||
{
|
||||
/**
|
||||
* Halt the OS.
|
||||
*/
|
||||
public function halt();
|
||||
|
||||
public function getName(): string;
|
||||
}
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
|
||||
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 108 KiB |
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 68 KiB |
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 80 KiB |
@@ -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];
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
25
Structural/Proxy/Tests/ProxyTest.php
Normal 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);
|
||||
}
|
||||
}
|
@@ -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
|
||||
--------
|
||||
|
@@ -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 ...
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|