From b82330941fc78b7aac5a880ab6685a6af0ea92b1 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Fri, 10 May 2013 19:12:02 +0200 Subject: [PATCH 01/61] gitignore for netbeans --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2aeba68..9402404 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ # common .idea +/nbproject \ No newline at end of file From 148bf4934d396334268d05817742090fee7334f0 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Fri, 10 May 2013 19:51:19 +0200 Subject: [PATCH 02/61] light bootstrap for unit tests --- Tests/bootstrap.php | 8 ++++++++ phpunit.xml | 10 ++++++++++ 2 files changed, 18 insertions(+) create mode 100644 Tests/bootstrap.php create mode 100644 phpunit.xml diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php new file mode 100644 index 0000000..78d586e --- /dev/null +++ b/Tests/bootstrap.php @@ -0,0 +1,8 @@ + + + + + + ./Tests + + + + From 7ca8f615d7b05efabebf05f5b3004ecd5eabb69c Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Fri, 10 May 2013 19:13:41 +0200 Subject: [PATCH 03/61] the right place --- .../FactoryMethod.php => StaticFactory/StaticFactory.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename FactoryMethod/FactoryMethod.php => StaticFactory/StaticFactory.php (100%) diff --git a/FactoryMethod/FactoryMethod.php b/StaticFactory/StaticFactory.php similarity index 100% rename from FactoryMethod/FactoryMethod.php rename to StaticFactory/StaticFactory.php From 2bea1f390ffd33ce4b23204f3c227cda4caca505 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Fri, 10 May 2013 19:50:13 +0200 Subject: [PATCH 04/61] PSR-0 for Static Factory --- StaticFactory/FormatNumber.php | 8 ++++++++ StaticFactory/FormatString.php | 8 ++++++++ StaticFactory/Formatter.php | 8 ++++++++ StaticFactory/StaticFactory.php | 30 ++++++++++-------------------- 4 files changed, 34 insertions(+), 20 deletions(-) create mode 100644 StaticFactory/FormatNumber.php create mode 100644 StaticFactory/FormatString.php create mode 100644 StaticFactory/Formatter.php diff --git a/StaticFactory/FormatNumber.php b/StaticFactory/FormatNumber.php new file mode 100644 index 0000000..f7bf851 --- /dev/null +++ b/StaticFactory/FormatNumber.php @@ -0,0 +1,8 @@ + global => evil + * Note2: Cannot be subclassed or mock-uped or have multiple different instances */ -class FactoryMethod +class StaticFactory { + /** * the parametrized function to get create an instance * @@ -24,26 +27,13 @@ class FactoryMethod */ public static function factory($type) { - $className = 'Format' . ucfirst($type); - if ( ! class_exists($className)) { - throw new Exception('Missing format class.'); + $className = __NAMESPACE__ . '\Format' . ucfirst($type); + + if (!class_exists($className)) { + throw new \InvalidArgumentException('Missing format class.'); } return new $className(); } -} - -interface Formatter -{ } - -class FormatString implements Formatter -{ - -} - -class FormatNumber implements Formatter -{ - -} From 87d4c9277dc71c4c8c563753591087e9cf24d2a8 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Fri, 10 May 2013 19:52:53 +0200 Subject: [PATCH 05/61] test for static factory --- Tests/StaticFactory/StaticFactoryTest.php | 31 +++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Tests/StaticFactory/StaticFactoryTest.php diff --git a/Tests/StaticFactory/StaticFactoryTest.php b/Tests/StaticFactory/StaticFactoryTest.php new file mode 100644 index 0000000..e906705 --- /dev/null +++ b/Tests/StaticFactory/StaticFactoryTest.php @@ -0,0 +1,31 @@ +assertInstanceOf('DesignPatterns\StaticFactory\Formatter', $obj); + } + +} From 9b7330795d209ed80a31a557b85e207ea97c7437 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Fri, 10 May 2013 20:22:43 +0200 Subject: [PATCH 06/61] (not design) pattern : simple factory --- SimpleFactory/Bicycle.php | 20 +++++++++ SimpleFactory/ConcreteFactory.php | 52 +++++++++++++++++++++++ SimpleFactory/Scooter.php | 20 +++++++++ SimpleFactory/Vehicle.php | 16 +++++++ Tests/SimpleFactory/SimpleFactoryTest.php | 51 ++++++++++++++++++++++ 5 files changed, 159 insertions(+) create mode 100644 SimpleFactory/Bicycle.php create mode 100644 SimpleFactory/ConcreteFactory.php create mode 100644 SimpleFactory/Scooter.php create mode 100644 SimpleFactory/Vehicle.php create mode 100644 Tests/SimpleFactory/SimpleFactoryTest.php diff --git a/SimpleFactory/Bicycle.php b/SimpleFactory/Bicycle.php new file mode 100644 index 0000000..39c2d32 --- /dev/null +++ b/SimpleFactory/Bicycle.php @@ -0,0 +1,20 @@ + global => evil + * + * Therefore, you can haZ multiple factories, differently parametrized, + * you can subclass it and you can mock-up it. + */ +class ConcreteFactory +{ + + protected $typeList; + + /** + * You can imagine to inject your own type list or merge with + * the default ones... + */ + public function __construct() + { + $this->typeList = array( + 'bicycle' => __NAMESPACE__ . '\Bicycle', + 'other' => __NAMESPACE__ . '\Scooter' + ); + } + + /** + * Creates a vehicle + * + * @param string $type a known type key + * @return Vehicle a new instance of Vehicle + * @throws \InvalidArgumentException + */ + public function createVehicle($type) + { + if (!array_key_exists($type, $this->typeList)) { + throw new \InvalidArgumentException("$type is not valid vehicle"); + } + $className = $this->typeList[$type]; + + return new $className(); + } + +} \ No newline at end of file diff --git a/SimpleFactory/Scooter.php b/SimpleFactory/Scooter.php new file mode 100644 index 0000000..925d7d2 --- /dev/null +++ b/SimpleFactory/Scooter.php @@ -0,0 +1,20 @@ +factory = new ConcreteFactory(); + } + + public function getType() + { + return array( + array('bicycle'), + array('other') + ); + } + + /** + * @dataProvider getType + */ + public function testCreation($type) + { + $obj = $this->factory->createVehicle($type); + $this->assertInstanceOf('DesignPatterns\SimpleFactory\Vehicle', $obj); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testBadType() + { + $this->factory->createVehicle('car'); + } + +} \ No newline at end of file From cc765bde41a46b62a4166ecb8ace521defd003e9 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Fri, 10 May 2013 21:09:55 +0200 Subject: [PATCH 07/61] the REAL factory method pattern --- FactoryMethod/Bicycle.php | 20 +++++++++ FactoryMethod/FactoryMethod.php | 45 ++++++++++++++++++++ FactoryMethod/Ferrari.php | 20 +++++++++ FactoryMethod/GermanFactory.php | 32 +++++++++++++++ FactoryMethod/ItalianFactory.php | 32 +++++++++++++++ FactoryMethod/Porsche.php | 20 +++++++++ FactoryMethod/Vehicle.php | 16 ++++++++ Tests/FactoryMethod/FactoryMethodTest.php | 50 +++++++++++++++++++++++ 8 files changed, 235 insertions(+) create mode 100644 FactoryMethod/Bicycle.php create mode 100644 FactoryMethod/FactoryMethod.php create mode 100644 FactoryMethod/Ferrari.php create mode 100644 FactoryMethod/GermanFactory.php create mode 100644 FactoryMethod/ItalianFactory.php create mode 100644 FactoryMethod/Porsche.php create mode 100644 FactoryMethod/Vehicle.php create mode 100644 Tests/FactoryMethod/FactoryMethodTest.php diff --git a/FactoryMethod/Bicycle.php b/FactoryMethod/Bicycle.php new file mode 100644 index 0000000..772dcdf --- /dev/null +++ b/FactoryMethod/Bicycle.php @@ -0,0 +1,20 @@ +createVehicle($type); + $obj->setColor("#f00"); + + return $obj; + } + +} \ No newline at end of file diff --git a/FactoryMethod/Ferrari.php b/FactoryMethod/Ferrari.php new file mode 100644 index 0000000..f57d728 --- /dev/null +++ b/FactoryMethod/Ferrari.php @@ -0,0 +1,20 @@ +type as $oneType) { + $vehicle = $shop->create($oneType); + $this->assertInstanceOf('DesignPatterns\FactoryMethod\Vehicle', $vehicle); + } + } + + /** + * @dataProvider getShop + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage spaceship is not a valid vehicle + */ + public function testUnknownType(FactoryMethod\FactoryMethod $shop) + { + $shop->create('spaceship'); + } + +} \ No newline at end of file From d4ae3bcd8f99f53670b5e945ab2ea633ce5ed386 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Fri, 10 May 2013 21:29:43 +0200 Subject: [PATCH 08/61] use constant instead of string --- FactoryMethod/FactoryMethod.php | 17 +++++++++++++---- FactoryMethod/GermanFactory.php | 13 ++++++++++--- FactoryMethod/ItalianFactory.php | 7 +++++-- FactoryMethod/Porsche.php | 5 +++++ Tests/FactoryMethod/FactoryMethodTest.php | 17 +++++++++++------ 5 files changed, 44 insertions(+), 15 deletions(-) diff --git a/FactoryMethod/FactoryMethod.php b/FactoryMethod/FactoryMethod.php index 439965b..3402bd6 100644 --- a/FactoryMethod/FactoryMethod.php +++ b/FactoryMethod/FactoryMethod.php @@ -7,15 +7,24 @@ namespace DesignPatterns\FactoryMethod; /** - * FactoryMethod is a factory method. The good point on the SimpleFactory + * FactoryMethod is a factory method. The good point over the SimpleFactory * is you can subclass it to implement different way to create vehicle for - * each country. + * each country (see subclasses) * - * For simple case, this abstracct class could be just an interface + * For simple case, this abstract class could be just an interface + * + * This pattern is a "real" Design Pattern because it achieves the + * "Dependency Inversion Principle" a.k.a the "D" in S.O.L.I.D principles. + * + * It means the FactoryMethod class depends on abstractions not concrete classes. + * This is the real trick compared to SImpleFactory or StaticFactory. */ abstract class FactoryMethod { + const CHEAP = 1; + const FAST = 2; + /** * The children of the class must implement this method * @@ -30,7 +39,7 @@ abstract class FactoryMethod /** * Creates a new vehicle * - * @param string $type + * @param int $type * * @return Vehicle a new vehicle */ diff --git a/FactoryMethod/GermanFactory.php b/FactoryMethod/GermanFactory.php index a7fb6f1..a962c76 100644 --- a/FactoryMethod/GermanFactory.php +++ b/FactoryMethod/GermanFactory.php @@ -18,12 +18,19 @@ class GermanFactory extends FactoryMethod protected function createVehicle($type) { switch ($type) { - case 'cheap' : + + case parent::CHEAP : return new Bicycle(); break; - case 'fast' : - return new Porsche(); + + case parent::FAST : + $obj = new Porsche(); + // we can specialize the way we want some concrete Vehicle since + // we know the class + $obj->addTuningAMG(); + return $obj; break; + default : throw new \InvalidArgumentException("$type is not a valid vehicle"); } diff --git a/FactoryMethod/ItalianFactory.php b/FactoryMethod/ItalianFactory.php index 873c91d..db8514c 100644 --- a/FactoryMethod/ItalianFactory.php +++ b/FactoryMethod/ItalianFactory.php @@ -18,12 +18,15 @@ class ItalianFactory extends FactoryMethod protected function createVehicle($type) { switch ($type) { - case 'cheap' : + + case parent::CHEAP : return new Bicycle(); break; - case 'fast' : + + case parent::FAST : return new Ferrari(); break; + default : throw new \InvalidArgumentException("$type is not a valid vehicle"); } diff --git a/FactoryMethod/Porsche.php b/FactoryMethod/Porsche.php index 8ecf3ec..c2e775b 100644 --- a/FactoryMethod/Porsche.php +++ b/FactoryMethod/Porsche.php @@ -17,4 +17,9 @@ class Porsche implements Vehicle } + public function addTuningAMG() + { + + } + } \ No newline at end of file diff --git a/Tests/FactoryMethod/FactoryMethodTest.php b/Tests/FactoryMethod/FactoryMethodTest.php index ec7b652..463c1c8 100644 --- a/Tests/FactoryMethod/FactoryMethodTest.php +++ b/Tests/FactoryMethod/FactoryMethodTest.php @@ -6,7 +6,9 @@ namespace DesignPatterns\Tests\FactoryMethod; -use DesignPatterns\FactoryMethod; +use DesignPatterns\FactoryMethod\FactoryMethod; +use DesignPatterns\FactoryMethod\GermanFactory; +use DesignPatterns\FactoryMethod\ItalianFactory; /** * FactoryMethodTest tests the factory method pattern @@ -14,20 +16,23 @@ use DesignPatterns\FactoryMethod; class FactoryMethodTest extends \PHPUnit_Framework_TestCase { - protected $type = array('cheap', 'fast'); + protected $type = array( + FactoryMethod::CHEAP, + FactoryMethod::FAST + ); public function getShop() { return array( - array(new FactoryMethod\GermanFactory()), - array(new FactoryMethod\ItalianFactory()) + array(new GermanFactory()), + array(new ItalianFactory()) ); } /** * @dataProvider getShop */ - public function testCreation(FactoryMethod\FactoryMethod $shop) + public function testCreation(FactoryMethod $shop) { // this test method acts as a client for the factory. We don't care // about the factory, all we know is it can produce vehicle @@ -42,7 +47,7 @@ class FactoryMethodTest extends \PHPUnit_Framework_TestCase * @expectedException \InvalidArgumentException * @expectedExceptionMessage spaceship is not a valid vehicle */ - public function testUnknownType(FactoryMethod\FactoryMethod $shop) + public function testUnknownType(FactoryMethod $shop) { $shop->create('spaceship'); } From 3a3937f339daca05b907461b98cd28099fcd7685 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Fri, 10 May 2013 21:49:22 +0200 Subject: [PATCH 09/61] typo --- StaticFactory/StaticFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StaticFactory/StaticFactory.php b/StaticFactory/StaticFactory.php index fb5a12c..aa32d43 100644 --- a/StaticFactory/StaticFactory.php +++ b/StaticFactory/StaticFactory.php @@ -7,7 +7,7 @@ namespace DesignPatterns\StaticFactory; * * Purpose: * similar to the AbstractFactory, this pattern is used to create series of related or dependant objects. - * The difference between this and the abstract factory pattern is that the factory method pattern uses just one static + * The difference between this and the abstract factory pattern is that the static factory pattern uses just one static * method to create all types of objects it can create. It is usually named "factory" or "build". * * Examples: From eebdbc1dc5dabc3664e4e80b8752d430154bb16f Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Fri, 10 May 2013 23:54:19 +0200 Subject: [PATCH 10/61] a REAL abstract factory not a helper-like full of static --- AbstractFactory/AbstractFactory.php | 45 ++++++++++--------- AbstractFactory/Html/Picture.php | 23 ++++++++++ AbstractFactory/Html/Text.php | 22 +++++++++ AbstractFactory/HtmlFactory.php | 25 +++++++++++ AbstractFactory/Json/Picture.php | 23 ++++++++++ AbstractFactory/Json/Text.php | 22 +++++++++ AbstractFactory/JsonFactory.php | 26 +++++++++++ AbstractFactory/Media.php | 5 +++ AbstractFactory/Picture.php | 2 +- AbstractFactory/Text.php | 2 +- Tests/AbstractFactory/AbstractFactoryTest.php | 45 +++++++++++++++++++ 11 files changed, 216 insertions(+), 24 deletions(-) create mode 100644 AbstractFactory/Html/Picture.php create mode 100644 AbstractFactory/Html/Text.php create mode 100644 AbstractFactory/HtmlFactory.php create mode 100644 AbstractFactory/Json/Picture.php create mode 100644 AbstractFactory/Json/Text.php create mode 100644 AbstractFactory/JsonFactory.php create mode 100644 Tests/AbstractFactory/AbstractFactoryTest.php diff --git a/AbstractFactory/AbstractFactory.php b/AbstractFactory/AbstractFactory.php index ea5253b..6db43b8 100644 --- a/AbstractFactory/AbstractFactory.php +++ b/AbstractFactory/AbstractFactory.php @@ -1,42 +1,43 @@ ', $this->_path, $this->_name); + } + +} \ No newline at end of file diff --git a/AbstractFactory/Html/Text.php b/AbstractFactory/Html/Text.php new file mode 100644 index 0000000..49be0dc --- /dev/null +++ b/AbstractFactory/Html/Text.php @@ -0,0 +1,22 @@ +" . htmlspecialchars($this->_text) . ''; + } + +} \ No newline at end of file diff --git a/AbstractFactory/HtmlFactory.php b/AbstractFactory/HtmlFactory.php new file mode 100644 index 0000000..7c9712c --- /dev/null +++ b/AbstractFactory/HtmlFactory.php @@ -0,0 +1,25 @@ + $this->_name, 'path' => $this->_path)); + } + +} \ No newline at end of file diff --git a/AbstractFactory/Json/Text.php b/AbstractFactory/Json/Text.php new file mode 100644 index 0000000..d9aa4b3 --- /dev/null +++ b/AbstractFactory/Json/Text.php @@ -0,0 +1,22 @@ + $this->_text)); + } + +} \ No newline at end of file diff --git a/AbstractFactory/JsonFactory.php b/AbstractFactory/JsonFactory.php new file mode 100644 index 0000000..e8a6059 --- /dev/null +++ b/AbstractFactory/JsonFactory.php @@ -0,0 +1,26 @@ +createText('Lorem Ipsum'), + $factory->createPicture('/image.jpg', 'caption'), + $factory->createText('footnotes') + ); + + $this->assertContainsOnly('DesignPatterns\AbstractFactory\Media', $article); + } + +} \ No newline at end of file From 153445b5d044761e211f017649168b6a95c2b6b0 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 00:28:22 +0200 Subject: [PATCH 11/61] comment and typo --- Tests/AbstractFactory/AbstractFactoryTest.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Tests/AbstractFactory/AbstractFactoryTest.php b/Tests/AbstractFactory/AbstractFactoryTest.php index 8de7914..81d0401 100644 --- a/Tests/AbstractFactory/AbstractFactoryTest.php +++ b/Tests/AbstractFactory/AbstractFactoryTest.php @@ -26,8 +26,8 @@ class AbstractFactoryTest extends \PHPUnit_Framework_TestCase /** * This is the client of factories. Note that the client does not - * care which fatory is given to him, it can create any component he - * want and render how he wants. + * care which factory is given to him, he can create any component he + * wants and render how he wants. * * @dataProvider getFactories */ @@ -40,6 +40,11 @@ class AbstractFactoryTest extends \PHPUnit_Framework_TestCase ); $this->assertContainsOnly('DesignPatterns\AbstractFactory\Media', $article); + /* this is the time to look at the Builder pattern. This pattern + * helps you to create complex object like that article above with + * a given Abstract Factory + */ + } } \ No newline at end of file From 6443fecebc99a9bfa7bc98983ffc337fb5473ff2 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 01:17:22 +0200 Subject: [PATCH 12/61] PSR-0 with tests --- Composite/{Composite.php => Form.php} | 28 +-------------------------- Composite/FormElement.php | 8 ++++++++ Composite/InputElement.php | 11 +++++++++++ Composite/TextElement.php | 11 +++++++++++ Tests/Composite/FormTest.php | 25 ++++++++++++++++++++++++ 5 files changed, 56 insertions(+), 27 deletions(-) rename Composite/{Composite.php => Form.php} (69%) create mode 100644 Composite/FormElement.php create mode 100644 Composite/InputElement.php create mode 100644 Composite/TextElement.php create mode 100644 Tests/Composite/FormTest.php diff --git a/Composite/Composite.php b/Composite/Form.php similarity index 69% rename from Composite/Composite.php rename to Composite/Form.php index 4236d44..c3a8f46 100644 --- a/Composite/Composite.php +++ b/Composite/Form.php @@ -1,6 +1,6 @@ _elements[] = $element; } } - -abstract class FormElement -{ - abstract public function render(); -} - -class TextElement extends FormElement -{ - public function render() - { - return 'this is a text element'; - } -} - -class InputElement extends FormElement -{ - public function render() - { - return ''; - } -} - -$form = new Form(); -$form->addElement(new TextElement()); -$form->addElement(new InputElement()); -echo $form->render(); \ No newline at end of file diff --git a/Composite/FormElement.php b/Composite/FormElement.php new file mode 100644 index 0000000..8c56ac7 --- /dev/null +++ b/Composite/FormElement.php @@ -0,0 +1,8 @@ +'; + } +} diff --git a/Composite/TextElement.php b/Composite/TextElement.php new file mode 100644 index 0000000..bf5d8cd --- /dev/null +++ b/Composite/TextElement.php @@ -0,0 +1,11 @@ +addElement(new Composite\TextElement()); + $form->addElement(new Composite\InputElement()); + echo $form->render(); + } + +} \ No newline at end of file From f76b8b1ab352f08515255204faddfc69b43e2bb6 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 01:22:18 +0200 Subject: [PATCH 13/61] you MUST inherit from the component contract. If not, this is no longer a composite pattern --- Composite/Form.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Composite/Form.php b/Composite/Form.php index c3a8f46..18ad54b 100644 --- a/Composite/Form.php +++ b/Composite/Form.php @@ -13,8 +13,10 @@ namespace DesignPatterns\Composite; * subsequently runs trough all its child elements and calls render() on them * - Zend_Config: a tree of configuration options, each one is a Zend_Config object * + * The composite node MUST extend the component contract. This is mandatory for building + * a tree of component. */ -class Form +class Form extends FormElement { protected $_elements; From 007a2d61711c334887b3eae65c9f0b5a087e3bff Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 01:24:40 +0200 Subject: [PATCH 14/61] changing interface to improve rendering --- Composite/Form.php | 2 +- Composite/FormElement.php | 2 +- Composite/InputElement.php | 2 +- Composite/TextElement.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Composite/Form.php b/Composite/Form.php index 18ad54b..e2ebc60 100644 --- a/Composite/Form.php +++ b/Composite/Form.php @@ -28,7 +28,7 @@ class Form extends FormElement * * @return string */ - public function render() + public function render($indent = 0) { $formCode = ''; foreach ($this->_elements as $element) { diff --git a/Composite/FormElement.php b/Composite/FormElement.php index 8c56ac7..3e3de93 100644 --- a/Composite/FormElement.php +++ b/Composite/FormElement.php @@ -4,5 +4,5 @@ namespace DesignPatterns\Composite; abstract class FormElement { - abstract public function render(); + abstract public function render($indent = 0); } diff --git a/Composite/InputElement.php b/Composite/InputElement.php index 0ed7022..d042e72 100644 --- a/Composite/InputElement.php +++ b/Composite/InputElement.php @@ -4,7 +4,7 @@ namespace DesignPatterns\Composite; class InputElement extends FormElement { - public function render() + public function render($indent = 0) { return ''; } diff --git a/Composite/TextElement.php b/Composite/TextElement.php index bf5d8cd..9e076e8 100644 --- a/Composite/TextElement.php +++ b/Composite/TextElement.php @@ -4,7 +4,7 @@ namespace DesignPatterns\Composite; class TextElement extends FormElement { - public function render() + public function render($indent = 0) { return 'this is a text element'; } From e70395c0a3d446b219b2c1b5ead7f876bc469633 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 01:36:57 +0200 Subject: [PATCH 15/61] improving the render to show the tree with indentations --- Composite/Form.php | 2 +- Composite/InputElement.php | 2 +- Composite/TextElement.php | 2 +- Tests/Composite/FormTest.php | 5 +++++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Composite/Form.php b/Composite/Form.php index e2ebc60..0cc16e1 100644 --- a/Composite/Form.php +++ b/Composite/Form.php @@ -32,7 +32,7 @@ class Form extends FormElement { $formCode = ''; foreach ($this->_elements as $element) { - $formCode .= $element->render(); + $formCode .= $element->render($indent + 1) . PHP_EOL; } return $formCode; diff --git a/Composite/InputElement.php b/Composite/InputElement.php index d042e72..786df45 100644 --- a/Composite/InputElement.php +++ b/Composite/InputElement.php @@ -6,6 +6,6 @@ class InputElement extends FormElement { public function render($indent = 0) { - return ''; + return str_repeat(' ', $indent) . ''; } } diff --git a/Composite/TextElement.php b/Composite/TextElement.php index 9e076e8..c8214ca 100644 --- a/Composite/TextElement.php +++ b/Composite/TextElement.php @@ -6,6 +6,6 @@ class TextElement extends FormElement { public function render($indent = 0) { - return 'this is a text element'; + return str_repeat(' ', $indent) . 'this is a text element'; } } diff --git a/Tests/Composite/FormTest.php b/Tests/Composite/FormTest.php index 85d56d5..2e10a8f 100644 --- a/Tests/Composite/FormTest.php +++ b/Tests/Composite/FormTest.php @@ -19,6 +19,11 @@ class FormTest extends \PHPUnit_Framework_TestCase $form = new Composite\Form(); $form->addElement(new Composite\TextElement()); $form->addElement(new Composite\InputElement()); + $embed = new Composite\Form(); + $embed->addElement(new Composite\TextElement()); + $embed->addElement(new Composite\InputElement()); + $form->addElement($embed); // here we have a tree ! + echo $form->render(); } From 42dd2383c182d5777829f5fdb170c90799c6bbd7 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 01:46:13 +0200 Subject: [PATCH 16/61] test for rendering with embedded form --- Tests/Composite/FormTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Composite/FormTest.php b/Tests/Composite/FormTest.php index 2e10a8f..1c7a428 100644 --- a/Tests/Composite/FormTest.php +++ b/Tests/Composite/FormTest.php @@ -22,9 +22,9 @@ class FormTest extends \PHPUnit_Framework_TestCase $embed = new Composite\Form(); $embed->addElement(new Composite\TextElement()); $embed->addElement(new Composite\InputElement()); - $form->addElement($embed); // here we have a tree ! - - echo $form->render(); + $form->addElement($embed); // here we have a embedded form (like SF2 does) + + $this->assertRegExp('#^\s{4}#m', $form->render()); } } \ No newline at end of file From 97096bc23fcb2506e58b38df5c1cb5bf13ddc681 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 02:06:15 +0200 Subject: [PATCH 17/61] fixing the inheritance tree for decorator --- Decorator/Decorator.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Decorator/Decorator.php b/Decorator/Decorator.php index 856b181..67c5b35 100644 --- a/Decorator/Decorator.php +++ b/Decorator/Decorator.php @@ -35,17 +35,28 @@ class Webservice implements Renderer } } -abstract class Decorator +/** + * the Deoorator MUST implement the Renderer contract, this is the key-feature + * of this design pattern. If not, this is no longer a Decorator but just a dumb + * wrapper. + */ +abstract class Decorator implements Renderer { protected $_wrapped; - public function __construct($wrappable) + /** + * You must type-hint the wrapped component : + * It ensures you can call renderData() in the subclasses ! + * + * @param Renderer $wrappable + */ + public function __construct(Renderer $wrappable) { $this->_wrapped = $wrappable; } } -class RenderInJson extends Decorator implements Renderer +class RenderInJson extends Decorator { public function renderData() { @@ -54,7 +65,7 @@ class RenderInJson extends Decorator implements Renderer } } -class RenderInXml extends Decorator implements Renderer +class RenderInXml extends Decorator { public function renderData() { From cfa901490681247b01da5913b0f3f1910916a449 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 02:07:29 +0200 Subject: [PATCH 18/61] fixing namespace --- Decorator/Decorator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Decorator/Decorator.php b/Decorator/Decorator.php index 67c5b35..727c4c9 100644 --- a/Decorator/Decorator.php +++ b/Decorator/Decorator.php @@ -1,6 +1,6 @@ Date: Sat, 11 May 2013 02:50:05 +0200 Subject: [PATCH 19/61] fix PSR-0 --- Decorator/Decorator.php | 46 -------------------------------------- Decorator/RenderInJson.php | 12 ++++++++++ Decorator/RenderInXml.php | 18 +++++++++++++++ Decorator/Renderer.php | 8 +++++++ Decorator/Webservice.php | 18 +++++++++++++++ 5 files changed, 56 insertions(+), 46 deletions(-) create mode 100644 Decorator/RenderInJson.php create mode 100644 Decorator/RenderInXml.php create mode 100644 Decorator/Renderer.php create mode 100644 Decorator/Webservice.php diff --git a/Decorator/Decorator.php b/Decorator/Decorator.php index 727c4c9..3231f09 100644 --- a/Decorator/Decorator.php +++ b/Decorator/Decorator.php @@ -15,25 +15,6 @@ namespace DesignPatterns\Decorator; * */ -interface Renderer -{ - public function renderData(); -} - -class Webservice implements Renderer -{ - protected $_data; - - public function __construct($data) - { - $this->_data = $data; - } - - public function renderData() - { - return $this->_data; - } -} /** * the Deoorator MUST implement the Renderer contract, this is the key-feature @@ -56,30 +37,3 @@ abstract class Decorator implements Renderer } } -class RenderInJson extends Decorator -{ - public function renderData() - { - $output = $this->_wrapped->renderData(); - return json_encode($output); - } -} - -class RenderInXml extends Decorator -{ - public function renderData() - { - $output = $this->_wrapped->renderData(); - // do some fany conversion to xml from array ... - return simplexml_load_string($output); - } -} - -// Create a normal service -$service = new Webservice(array('foo' => 'bar')); - -// Wrap service with a JSON decorator for renderers -$service = new RenderInJson($service); -// Our Renderer will now output JSON instead of an array - -echo $service->renderData(); diff --git a/Decorator/RenderInJson.php b/Decorator/RenderInJson.php new file mode 100644 index 0000000..57f8b06 --- /dev/null +++ b/Decorator/RenderInJson.php @@ -0,0 +1,12 @@ +_wrapped->renderData(); + return json_encode($output); + } +} diff --git a/Decorator/RenderInXml.php b/Decorator/RenderInXml.php new file mode 100644 index 0000000..6e594f9 --- /dev/null +++ b/Decorator/RenderInXml.php @@ -0,0 +1,18 @@ +_wrapped->renderData(); + // do some fany conversion to xml from array ... + $doc = new \DOMDocument(); + foreach ($output as $key => $val) { + $doc->appendChild($doc->createElement('foo', 'bar')); + } + + return $doc->saveXML(); + } +} diff --git a/Decorator/Renderer.php b/Decorator/Renderer.php new file mode 100644 index 0000000..6a01d4e --- /dev/null +++ b/Decorator/Renderer.php @@ -0,0 +1,8 @@ +_data = $data; + } + + public function renderData() + { + return $this->_data; + } +} From 185baaae735666e2cbc0dff2e78f70ad53124996 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 02:50:22 +0200 Subject: [PATCH 20/61] test decorator --- Tests/Decorator/DecoratorTest.php | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 Tests/Decorator/DecoratorTest.php diff --git a/Tests/Decorator/DecoratorTest.php b/Tests/Decorator/DecoratorTest.php new file mode 100644 index 0000000..f8a759b --- /dev/null +++ b/Tests/Decorator/DecoratorTest.php @@ -0,0 +1,40 @@ +service = new Decorator\Webservice(array('foo' => 'bar')); + } + + public function testJsonDecorator() + { + // Wrap service with a JSON decorator for renderers + $service = new Decorator\RenderInJson($this->service); + // Our Renderer will now output JSON instead of an array + $this->assertEquals('{"foo":"bar"}', $service->renderData()); + } + + public function testXmlDecorator() + { + // Wrap service with a JSON decorator for renderers + $service = new Decorator\RenderInXml($this->service); + // Our Renderer will now output XML instead of an array + $this->assertXmlStringEqualsXmlString('bar', $service->renderData()); + } + +} \ No newline at end of file From 6b77eb71e662eb240c4eabde494178bdb9562b42 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 02:51:57 +0200 Subject: [PATCH 21/61] spacing --- Decorator/Decorator.php | 8 ++++---- Decorator/RenderInJson.php | 2 ++ Decorator/RenderInXml.php | 4 +++- Decorator/Renderer.php | 3 ++- Decorator/Webservice.php | 2 ++ 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Decorator/Decorator.php b/Decorator/Decorator.php index 3231f09..d7ab5dd 100644 --- a/Decorator/Decorator.php +++ b/Decorator/Decorator.php @@ -14,17 +14,17 @@ namespace DesignPatterns\Decorator; * course) * */ - -/** +/** * the Deoorator MUST implement the Renderer contract, this is the key-feature * of this design pattern. If not, this is no longer a Decorator but just a dumb * wrapper. */ abstract class Decorator implements Renderer { + protected $_wrapped; - + /** * You must type-hint the wrapped component : * It ensures you can call renderData() in the subclasses ! @@ -35,5 +35,5 @@ abstract class Decorator implements Renderer { $this->_wrapped = $wrappable; } -} +} diff --git a/Decorator/RenderInJson.php b/Decorator/RenderInJson.php index 57f8b06..0c521bd 100644 --- a/Decorator/RenderInJson.php +++ b/Decorator/RenderInJson.php @@ -4,9 +4,11 @@ namespace DesignPatterns\Decorator; class RenderInJson extends Decorator { + public function renderData() { $output = $this->_wrapped->renderData(); return json_encode($output); } + } diff --git a/Decorator/RenderInXml.php b/Decorator/RenderInXml.php index 6e594f9..599c2f5 100644 --- a/Decorator/RenderInXml.php +++ b/Decorator/RenderInXml.php @@ -4,6 +4,7 @@ namespace DesignPatterns\Decorator; class RenderInXml extends Decorator { + public function renderData() { $output = $this->_wrapped->renderData(); @@ -12,7 +13,8 @@ class RenderInXml extends Decorator foreach ($output as $key => $val) { $doc->appendChild($doc->createElement('foo', 'bar')); } - + return $doc->saveXML(); } + } diff --git a/Decorator/Renderer.php b/Decorator/Renderer.php index 6a01d4e..af3c46b 100644 --- a/Decorator/Renderer.php +++ b/Decorator/Renderer.php @@ -1,8 +1,9 @@ _data; } + } From 661d7e80a3ad168a685dee9ce0e19d92f84df78d Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 02:56:50 +0200 Subject: [PATCH 22/61] todo test --- Tests/Composite/FormTest.php | 5 +++++ Tests/Decorator/DecoratorTest.php | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Tests/Composite/FormTest.php b/Tests/Composite/FormTest.php index 1c7a428..71a275c 100644 --- a/Tests/Composite/FormTest.php +++ b/Tests/Composite/FormTest.php @@ -27,4 +27,9 @@ class FormTest extends \PHPUnit_Framework_TestCase $this->assertRegExp('#^\s{4}#m', $form->render()); } + public function testFormImplementsFormEelement() + { + $this->markTestIncomplete(); + } + } \ No newline at end of file diff --git a/Tests/Decorator/DecoratorTest.php b/Tests/Decorator/DecoratorTest.php index f8a759b..4eee22b 100644 --- a/Tests/Decorator/DecoratorTest.php +++ b/Tests/Decorator/DecoratorTest.php @@ -37,4 +37,9 @@ class DecoratorTest extends \PHPUnit_Framework_TestCase $this->assertXmlStringEqualsXmlString('bar', $service->renderData()); } + public function testDecoratorImplementsRenderer() + { + $this->markTestIncomplete(); + } + } \ No newline at end of file From 44e03cae467954a8cdbc19b5f32bd3081d45e844 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 10:49:44 +0200 Subject: [PATCH 23/61] more test to show the key features of patterns --- Tests/Composite/FormTest.php | 6 +++++- Tests/Decorator/DecoratorTest.php | 22 ++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Tests/Composite/FormTest.php b/Tests/Composite/FormTest.php index 71a275c..a706c99 100644 --- a/Tests/Composite/FormTest.php +++ b/Tests/Composite/FormTest.php @@ -27,9 +27,13 @@ class FormTest extends \PHPUnit_Framework_TestCase $this->assertRegExp('#^\s{4}#m', $form->render()); } + /** + * The all point of this pattern, a Composite must inherit from the node + * if you wanto to builld trees + */ public function testFormImplementsFormEelement() { - $this->markTestIncomplete(); + $this->assertTrue(is_subclass_of('DesignPatterns\Composite\Form', 'DesignPatterns\Composite\FormElement')); } } \ No newline at end of file diff --git a/Tests/Decorator/DecoratorTest.php b/Tests/Decorator/DecoratorTest.php index 4eee22b..35e19a7 100644 --- a/Tests/Decorator/DecoratorTest.php +++ b/Tests/Decorator/DecoratorTest.php @@ -37,9 +37,27 @@ class DecoratorTest extends \PHPUnit_Framework_TestCase $this->assertXmlStringEqualsXmlString('bar', $service->renderData()); } - public function testDecoratorImplementsRenderer() + /** + * The first key-point of this pattern : + */ + public function testDecoratorMustImplementsRenderer() { - $this->markTestIncomplete(); + $this->assertTrue(is_subclass_of('DesignPatterns\Decorator\Decorator', 'DesignPatterns\Decorator\Renderer')); + } + + /** + * @expectedException \PHPUnit_Framework_Error + */ + public function testDecoratorTypeHinted() + { + $this->getMockForAbstractClass('DesignPatterns\Decorator\Decorator', array(new \stdClass())); + } + + public function testDecoratorOnlyAcceptRenderer() + { + $mock = $this->getMock('DesignPatterns\Decorator\Renderer'); + $dec = $this->getMockForAbstractClass('DesignPatterns\Decorator\Decorator', array($mock)); + $this->assertNotNull($dec); } } \ No newline at end of file From 94edeede4ecc56cd0fc12ba06fc24673e3e03b09 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 12:01:06 +0200 Subject: [PATCH 24/61] Comments to explain how this example misses the point of the pattern --- Iterator/Iterator.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Iterator/Iterator.php b/Iterator/Iterator.php index d27adae..753ce77 100644 --- a/Iterator/Iterator.php +++ b/Iterator/Iterator.php @@ -32,6 +32,8 @@ class File public function process() { + // this is the place to show how using an iterator, with foreach + // See the CardGame.php file $this->_rowset->process(); } } @@ -55,6 +57,14 @@ class Rowset implements Iterator public function process() { // this actually calls rewind(), { next(), valid(), key() and current() :} + /** + * THE key feature of the Iterator Pattern is to provide a *public contract* + * to iterate on a collection without knowing how items are handled inside + * the collection. It is not just an easy way to use "foreach" + * + * One cannot see the point of iterator pattern if you iterate on $this. + * This example is unclear and mixed with some Composite pattern ideas. + */ foreach ($this as $line => $row) { $row->process(); } From aa8069fc4585d0f5bed851e7eaa82bd27620425c Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 12:47:26 +0200 Subject: [PATCH 25/61] a relevant example for iterator --- Iterator/CardGame.php | 80 +++++++++++++++++++++++++++++++++ Tests/Iterator/IteratorTest.php | 56 +++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 Iterator/CardGame.php create mode 100644 Tests/Iterator/IteratorTest.php diff --git a/Iterator/CardGame.php b/Iterator/CardGame.php new file mode 100644 index 0000000..c4b3898 --- /dev/null +++ b/Iterator/CardGame.php @@ -0,0 +1,80 @@ +number) . ' of ' . current($this->color); + } + + /** + * Return the current key + */ + public function key() + { + return current($this->color) . current($this->number); + } + + /** + * Go to the next item in the collection + */ + public function next() + { + if (false === next($this->number)) { + if (false !== next($this->color)) { + reset($this->number); + } + } + } + + /** + * Go to the first item in the collection + */ + public function rewind() + { + reset($this->color); + reset($this->number); + } + + /** + * Is the current position a valid item (true) + * or do we reach the end (false) ? + */ + public function valid() + { + return current($this->number) || current($this->color); + } + +} \ No newline at end of file diff --git a/Tests/Iterator/IteratorTest.php b/Tests/Iterator/IteratorTest.php new file mode 100644 index 0000000..6b9fd12 --- /dev/null +++ b/Tests/Iterator/IteratorTest.php @@ -0,0 +1,56 @@ +game = new CardGame(); + } + + /** + * This is the client of the iterator. + * It remains unchanged even if one I decide to use MongoDB to store the + * card. + */ + public function testCardinal() + { + $counter = 0; + foreach ($this->game as $key => $card) { + $counter++; + } + + $this->assertEquals(32, $counter); + } + + /** + * Some fancy functions of PHP. + */ + public function testExampleOf_PHP_Helper() + { + // PHPUnit works on array or iterator : + $this->assertCount(32, $this->game); + // a easy way to get an array from interator : + $cards = iterator_to_array($this->game); + $this->assertEquals('A of S', $cards['SA']); + // a easy way to get an iterator from an array : + $iterator = new \ArrayIterator($cards); + $this->assertInstanceOf('\Iterator', $iterator); + } + +} \ No newline at end of file From 3aa38f2af8750c9e1e4e81e7eff84b9aa48acb94 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 16:13:43 +0200 Subject: [PATCH 26/61] more abstract example --- Facade/Computer.php | 52 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Facade/Computer.php diff --git a/Facade/Computer.php b/Facade/Computer.php new file mode 100644 index 0000000..1349779 --- /dev/null +++ b/Facade/Computer.php @@ -0,0 +1,52 @@ +bios = $bios; + $this->opsys = $os; + } + + public function turnOn() + { + $this->bios->execute(); + $this->bios->waitForKeyPress(); + $this->bios->launch($this->opsys); + } + + public function turnOff() + { + $this->opsys->halt(); + $this->bios->powerDown(); + } + +} \ No newline at end of file From 1d99a9e65ae946539b2e52b11828ed3453e48ce3 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 16:14:08 +0200 Subject: [PATCH 27/61] test with mockup --- Tests/Facade/FacadeTest.php | 51 +++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 Tests/Facade/FacadeTest.php diff --git a/Tests/Facade/FacadeTest.php b/Tests/Facade/FacadeTest.php new file mode 100644 index 0000000..85e3f95 --- /dev/null +++ b/Tests/Facade/FacadeTest.php @@ -0,0 +1,51 @@ +getMockBuilder('DesignPatterns\Facade\BiosInterface') + ->setMethods(array('launch', 'execute', 'waitForKeyPress')) + ->disableAutoload() + ->getMock(); + $operatingSys = $this->getMockBuilder('DesignPatterns\Facade\OsInterface') + ->setMethods(array('getName')) + ->disableAutoload() + ->getMock(); + $bios->expects($this->once()) + ->method('launch') + ->with($operatingSys); + $operatingSys + ->expects($this->once()) + ->method('getName') + ->will($this->returnValue('Linux')); + + $facade = new Computer($bios, $operatingSys); + return array(array($facade, $operatingSys)); + } + + /** + * @dataProvider getComputer + */ + public function testComputerOn(Computer $facade, $os) + { + // interface is simpler : + $facade->turnOn(); + // but I can access to lower component + $this->assertEquals('Linux', $os->getName()); + } + +} \ No newline at end of file From 22dbd54cbb1132756a30123e10fb359d01dc448d Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 16:20:55 +0200 Subject: [PATCH 28/61] comments --- Facade/Computer.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Facade/Computer.php b/Facade/Computer.php index 1349779..4f6c12d 100644 --- a/Facade/Computer.php +++ b/Facade/Computer.php @@ -12,17 +12,19 @@ namespace DesignPatterns\Facade; * * The first goal is to reduce coupling and folow the Law of Demeter. * - * A Facade is meant to decouple a client and a sub-system with + * A Facade is meant to decouple a client and a sub-system by embedding * many (but sometimes just one) interface, and of course to reduce complexity. * * 1. A facade does not forbid you the access to the sub-system - * 2. You can (you shoud) have multiple facades for one sub-system + * 2. You can (you should) have multiple facades for one sub-system * * That's why a good facade has no "new" in it. If there are multiple creations * for each method, it is not a Facade, it's a Builder or a - * [Abstract|Static|Simple] Factory [Method] + * [Abstract|Static|Simple] Factory [Method]. * - * The best facade has no new and a constructor with interface-type-hinted parameters + * The best facade has no new and a constructor with interface-type-hinted parameters. + * If you need creation of new instances, use Factory as argument. + * */ class Computer { @@ -30,6 +32,10 @@ class Computer protected $opsys; protected $bios; + /** + * This is the perfect time to use a dependency injection container + * to creaate an instance of this class + */ public function __construct(BiosInterface $bios, OsInterface $os) { $this->bios = $bios; From 6f94fb5a78ab86d786a11558f6b7cf18cf9eb947 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 16:40:55 +0200 Subject: [PATCH 29/61] removing complex example --- Facade/Facade.php | 49 ----------------------------------------------- 1 file changed, 49 deletions(-) delete mode 100644 Facade/Facade.php diff --git a/Facade/Facade.php b/Facade/Facade.php deleted file mode 100644 index 60bfd7e..0000000 --- a/Facade/Facade.php +++ /dev/null @@ -1,49 +0,0 @@ -_text = $text; - - $this->_initQueryBuilder($text); - } - - protected function _initQueryBuilder($sql) - { - $query = new QueryBuilder(); - $query->setSql($sql); - $this->_queryBuilder = $query; - } -} - -class QueryBuilder -{ - protected $_sql; - - public function setSql($sql) - { - $this->_sql = $sql; - } -} - -// this is just a simple call, but behind the facade, there's much more going on -$foo = new Facade('very simple'); From 7dd0cb74c4f087d86709c171b9737843db3ad534 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 16:41:59 +0200 Subject: [PATCH 30/61] renaming new to old --- Facade/{Computer.php => Facade.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Facade/{Computer.php => Facade.php} (100%) diff --git a/Facade/Computer.php b/Facade/Facade.php similarity index 100% rename from Facade/Computer.php rename to Facade/Facade.php From e325cd9fe6688aa6729d3e1f83f1a2ab1e551ff8 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 16:43:14 +0200 Subject: [PATCH 31/61] changing class name --- Facade/Facade.php | 2 +- Tests/Facade/FacadeTest.php | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Facade/Facade.php b/Facade/Facade.php index 4f6c12d..612bc4f 100644 --- a/Facade/Facade.php +++ b/Facade/Facade.php @@ -26,7 +26,7 @@ namespace DesignPatterns\Facade; * If you need creation of new instances, use Factory as argument. * */ -class Computer +class Facade { protected $opsys; diff --git a/Tests/Facade/FacadeTest.php b/Tests/Facade/FacadeTest.php index 85e3f95..868ed32 100644 --- a/Tests/Facade/FacadeTest.php +++ b/Tests/Facade/FacadeTest.php @@ -6,8 +6,7 @@ namespace DesignPatterns\Tests\Facade; -use DesignPatterns\Facade\Computer; -use DesignPatterns\Facade\Installer; +use DesignPatterns\Facade\Facade as Computer; /** * FacadeTest shows example of facades From cc93827b8fabe176cebe0c2cf16a64820dbba166 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 16:57:41 +0200 Subject: [PATCH 32/61] first, some comments --- Command/Command.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Command/Command.php b/Command/Command.php index 5144a76..60e09ad 100644 --- a/Command/Command.php +++ b/Command/Command.php @@ -5,11 +5,19 @@ namespace DesignPatterns; /** * Command pattern * - * Purpose: - * to build a simple interface for commands that can all be run by just executing a single method they all have in - * common, often called 'run' or 'execute' + * Purpose: To encapsulate invocation and decoupling + * + * We have an Invoker and a Receiver. This pattern use a "Command" to delegate the + * method call against the Receiver and use the same method "execute". + * Therefore, the Invoker just know to call "execute" to process the Command of + * the client. + * + * The second aspect of ths pattern is the undo(), which undoes the method execute() + * Command can also be agregated to combine more complex commands with minimum + * copy-paste and relying on composition over inheritance. * * Examples: + * - A text editor : all events are Command which can be undone, stacked and saved. * - Symfony2: SF2 Commands that can be run from the CLI are built with just the Command pattern in mind * - big CLI tools use subcommands to distribute various tasks and pack them in "modules", each of these * can be implemented with the Command pattern (e.g. vagrant) From 6f6e86ee4884f9d77bd9510ae3a3c0056261e0b0 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 17:33:47 +0200 Subject: [PATCH 33/61] adding the missing classes to show the real purpose of the pattern --- Command/Command.php | 24 +++++------------------- Command/HelloCommand.php | 30 ++++++++++++++++++++++++++++++ Command/Invoker.php | 34 ++++++++++++++++++++++++++++++++++ Command/Receiver.php | 20 ++++++++++++++++++++ 4 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 Command/HelloCommand.php create mode 100644 Command/Invoker.php create mode 100644 Command/Receiver.php diff --git a/Command/Command.php b/Command/Command.php index 60e09ad..17d639e 100644 --- a/Command/Command.php +++ b/Command/Command.php @@ -1,6 +1,6 @@ what = (string)$what; - } - - public function execute() - { - echo $this->what; - } -} \ No newline at end of file diff --git a/Command/HelloCommand.php b/Command/HelloCommand.php new file mode 100644 index 0000000..1ba3ecb --- /dev/null +++ b/Command/HelloCommand.php @@ -0,0 +1,30 @@ +output = $console; + } + + public function execute() + { + // sometimes, there is no receiver and this is the command which + // does all the work + $this->output->write('Hello World'); + } + +} \ No newline at end of file diff --git a/Command/Invoker.php b/Command/Invoker.php new file mode 100644 index 0000000..75d7a23 --- /dev/null +++ b/Command/Invoker.php @@ -0,0 +1,34 @@ +command = $cmd; + } + + public function run() + { + // here is a key feature of the invoker + // the invoker is the same whatever is the command + $this->command->execute(); + } + +} \ No newline at end of file diff --git a/Command/Receiver.php b/Command/Receiver.php new file mode 100644 index 0000000..a9b6516 --- /dev/null +++ b/Command/Receiver.php @@ -0,0 +1,20 @@ + Date: Sat, 11 May 2013 17:34:06 +0200 Subject: [PATCH 34/61] adding tests for the command pattern --- Tests/Command/CommandTest.php | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Tests/Command/CommandTest.php diff --git a/Tests/Command/CommandTest.php b/Tests/Command/CommandTest.php new file mode 100644 index 0000000..0df7f9e --- /dev/null +++ b/Tests/Command/CommandTest.php @@ -0,0 +1,36 @@ +invoker = new Invoker(); + $this->receiver = new Receiver(); + } + + public function testInvocation() + { + $this->invoker->setCommand(new HelloCommand($this->receiver)); + $this->expectOutputString('Hello World'); + $this->invoker->run(); + } + +} \ No newline at end of file From 2830e7d1be0f69c2354d87deb57146f0ab46b4d0 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 19:33:36 +0200 Subject: [PATCH 35/61] first, the todos for fixing the pattern --- Adapter/Adapter.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Adapter/Adapter.php b/Adapter/Adapter.php index 3ecb4ed..85b9385 100644 --- a/Adapter/Adapter.php +++ b/Adapter/Adapter.php @@ -21,6 +21,10 @@ interface DatabaseAdapter class MySQL implements DatabaseAdapter { + /** + * The Adapter need to wrap the Adaptee in the constructor + * much like Decorator does. Nothing is adapted here... + */ public function getTables() { return $this->select('SHOW TABLES'); From 9278f295b009f562e185da5eef89a80508953eeb Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 20:54:21 +0200 Subject: [PATCH 36/61] more real example --- Adapter/Adapter.php | 41 ---------------------------------- Adapter/Book.php | 25 +++++++++++++++++++++ Adapter/ElecBookAdapter.php | 41 ++++++++++++++++++++++++++++++++++ Adapter/ElecBookInterface.php | 18 +++++++++++++++ Adapter/Kindle.php | 27 ++++++++++++++++++++++ Adapter/PaperBookInterface.php | 18 +++++++++++++++ 6 files changed, 129 insertions(+), 41 deletions(-) delete mode 100644 Adapter/Adapter.php create mode 100644 Adapter/Book.php create mode 100644 Adapter/ElecBookAdapter.php create mode 100644 Adapter/ElecBookInterface.php create mode 100644 Adapter/Kindle.php create mode 100644 Adapter/PaperBookInterface.php diff --git a/Adapter/Adapter.php b/Adapter/Adapter.php deleted file mode 100644 index 85b9385..0000000 --- a/Adapter/Adapter.php +++ /dev/null @@ -1,41 +0,0 @@ -select('SHOW TABLES'); - } -} - -class SQLite implements DatabaseAdapter -{ - public function getTables() - { - return system('sqlite --list-tables'); - } -} - diff --git a/Adapter/Book.php b/Adapter/Book.php new file mode 100644 index 0000000..dcd3b0f --- /dev/null +++ b/Adapter/Book.php @@ -0,0 +1,25 @@ +eBook = $ebook; + } + + /** + * This cass makes the proper translation from one interface to another + */ + public function open() + { + $this->eBook->pressStart(); + } + + public function turnPage() + { + $this->eBook->pressNext(); + } + +} \ No newline at end of file diff --git a/Adapter/ElecBookInterface.php b/Adapter/ElecBookInterface.php new file mode 100644 index 0000000..b16bd35 --- /dev/null +++ b/Adapter/ElecBookInterface.php @@ -0,0 +1,18 @@ + Date: Sat, 11 May 2013 20:56:47 +0200 Subject: [PATCH 37/61] the test is the client --- Tests/Adapter/AdapterTest.php | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Tests/Adapter/AdapterTest.php diff --git a/Tests/Adapter/AdapterTest.php b/Tests/Adapter/AdapterTest.php new file mode 100644 index 0000000..9dda0eb --- /dev/null +++ b/Tests/Adapter/AdapterTest.php @@ -0,0 +1,42 @@ +open(); + $book->turnPage(); + } + +} \ No newline at end of file From 8120aae5a7d908ea332204b2deeab7f19770810b Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 22:18:12 +0200 Subject: [PATCH 38/61] some typo and comments --- Command/Command.php | 4 ++-- Facade/Facade.php | 2 +- Iterator/CardGame.php | 2 +- Tests/Composite/FormTest.php | 2 +- Tests/Decorator/DecoratorTest.php | 5 +++++ Tests/Iterator/IteratorTest.php | 6 ++---- Tests/SimpleFactory/SimpleFactoryTest.php | 2 -- 7 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Command/Command.php b/Command/Command.php index 17d639e..005d0b6 100644 --- a/Command/Command.php +++ b/Command/Command.php @@ -8,7 +8,7 @@ namespace DesignPatterns\Command; * Purpose: To encapsulate invocation and decoupling * * We have an Invoker and a Receiver. This pattern use a "Command" to delegate the - * method call against the Receiver and use the same method "execute". + * method call against the Receiver and presents the same method "execute". * Therefore, the Invoker just know to call "execute" to process the Command of * the client. The Receiver is decoupled from the Invoker * @@ -28,7 +28,7 @@ interface Command /** * this is the most important method in the Command pattern, - * The Receiver go in the constructor. + * The Receiver goes in the constructor. */ public function execute(); } diff --git a/Facade/Facade.php b/Facade/Facade.php index 612bc4f..5dda3b0 100644 --- a/Facade/Facade.php +++ b/Facade/Facade.php @@ -10,7 +10,7 @@ namespace DesignPatterns\Facade; * The primary goal of a Facade Pattern is not to avoid you to read the manual of * a complex API. It's only a side-effect. * - * The first goal is to reduce coupling and folow the Law of Demeter. + * The first goal is to reduce coupling and follow the Law of Demeter. * * A Facade is meant to decouple a client and a sub-system by embedding * many (but sometimes just one) interface, and of course to reduce complexity. diff --git a/Iterator/CardGame.php b/Iterator/CardGame.php index c4b3898..f6dd081 100644 --- a/Iterator/CardGame.php +++ b/Iterator/CardGame.php @@ -23,7 +23,7 @@ namespace DesignPatterns\Iterator; * arrays. * * If tomorrow you decide to read cards from a database, the client - * (see the PHPUnit test) will remain unchanged. That's beauty of it. + * (see the PHPUnit test) will remain unchanged. That's the beauty of it. */ class CardGame implements \Iterator { diff --git a/Tests/Composite/FormTest.php b/Tests/Composite/FormTest.php index a706c99..41cf904 100644 --- a/Tests/Composite/FormTest.php +++ b/Tests/Composite/FormTest.php @@ -29,7 +29,7 @@ class FormTest extends \PHPUnit_Framework_TestCase /** * The all point of this pattern, a Composite must inherit from the node - * if you wanto to builld trees + * if you want to builld trees */ public function testFormImplementsFormEelement() { diff --git a/Tests/Decorator/DecoratorTest.php b/Tests/Decorator/DecoratorTest.php index 35e19a7..fce9e56 100644 --- a/Tests/Decorator/DecoratorTest.php +++ b/Tests/Decorator/DecoratorTest.php @@ -46,6 +46,8 @@ class DecoratorTest extends \PHPUnit_Framework_TestCase } /** + * Second key-point of this pattern : the decorator is type-hinted + * * @expectedException \PHPUnit_Framework_Error */ public function testDecoratorTypeHinted() @@ -53,6 +55,9 @@ class DecoratorTest extends \PHPUnit_Framework_TestCase $this->getMockForAbstractClass('DesignPatterns\Decorator\Decorator', array(new \stdClass())); } + /** + * The decorator implements and wraps the same interface + */ public function testDecoratorOnlyAcceptRenderer() { $mock = $this->getMock('DesignPatterns\Decorator\Renderer'); diff --git a/Tests/Iterator/IteratorTest.php b/Tests/Iterator/IteratorTest.php index 6b9fd12..37b9a40 100644 --- a/Tests/Iterator/IteratorTest.php +++ b/Tests/Iterator/IteratorTest.php @@ -9,9 +9,7 @@ namespace DesignPatterns\Tests\Iterator; use DesignPatterns\Iterator\CardGame; /** - * IteratorTest the CardGame iterator - * - * @author flo + * IteratorTest tests the CardGame iterator */ class IteratorTest extends \PHPUnit_Framework_TestCase { @@ -25,7 +23,7 @@ class IteratorTest extends \PHPUnit_Framework_TestCase /** * This is the client of the iterator. - * It remains unchanged even if one I decide to use MongoDB to store the + * It remains unchanged even if one day I decide to use MongoDB to store the * card. */ public function testCardinal() diff --git a/Tests/SimpleFactory/SimpleFactoryTest.php b/Tests/SimpleFactory/SimpleFactoryTest.php index 3494ca2..82db42d 100644 --- a/Tests/SimpleFactory/SimpleFactoryTest.php +++ b/Tests/SimpleFactory/SimpleFactoryTest.php @@ -10,8 +10,6 @@ use DesignPatterns\SimpleFactory\ConcreteFactory; /** * SimpleFactoryTest tests the Simple Factory pattern - * - * @author flo */ class SimpleFactoryTest extends \PHPUnit_Framework_TestCase { From 3695a151507d48f1ccf8fee2ff3da1369b61d3a2 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sat, 11 May 2013 22:37:20 +0200 Subject: [PATCH 39/61] more uses of iterator in PHP --- Tests/Iterator/IteratorTest.php | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Tests/Iterator/IteratorTest.php b/Tests/Iterator/IteratorTest.php index 37b9a40..de2e2b2 100644 --- a/Tests/Iterator/IteratorTest.php +++ b/Tests/Iterator/IteratorTest.php @@ -14,11 +14,11 @@ use DesignPatterns\Iterator\CardGame; class IteratorTest extends \PHPUnit_Framework_TestCase { - protected $game; + protected $deck; protected function setUp() { - $this->game = new CardGame(); + $this->deck = new CardGame(); } /** @@ -29,7 +29,7 @@ class IteratorTest extends \PHPUnit_Framework_TestCase public function testCardinal() { $counter = 0; - foreach ($this->game as $key => $card) { + foreach ($this->deck as $key => $card) { $counter++; } @@ -42,13 +42,27 @@ class IteratorTest extends \PHPUnit_Framework_TestCase public function testExampleOf_PHP_Helper() { // PHPUnit works on array or iterator : - $this->assertCount(32, $this->game); + $this->assertCount(32, $this->deck); // a easy way to get an array from interator : - $cards = iterator_to_array($this->game); + $cards = iterator_to_array($this->deck); $this->assertEquals('A of S', $cards['SA']); // a easy way to get an iterator from an array : $iterator = new \ArrayIterator($cards); $this->assertInstanceOf('\Iterator', $iterator); } + /** + * Iterator can be combine, chained, filter, there are many in the SPL + * and sadly they are rarely used. + */ + public function testIteratorCombining() + { + // a fancy way to add a joker to the deck : + $joker = array('JK' => 'Joker'); + $newDeck = new \AppendIterator(); + $newDeck->append($this->deck); + $newDeck->append(new \ArrayIterator($joker)); + $this->assertCount(33, $newDeck); + } + } \ No newline at end of file From 1569db132891b8423486820535ccfcdd833f36ff Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 12 May 2013 11:13:14 +0200 Subject: [PATCH 40/61] add a template method --- TemplateMethod/Journey.php | 76 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 TemplateMethod/Journey.php diff --git a/TemplateMethod/Journey.php b/TemplateMethod/Journey.php new file mode 100644 index 0000000..ce93b82 --- /dev/null +++ b/TemplateMethod/Journey.php @@ -0,0 +1,76 @@ +buyAFlight(); + $this->takePlane(); + $this->enjoyVacation(); + $this->buyGift(); + $this->takePlane(); + } + + /** + * This method must be implemented, this is the key-feature of this pattern + */ + abstract protected function enjoyVacation(); + + /** + * This method is also part of the algorithm but it is optional. + * This is an "adapter" (do not confuse with the Adapter pattern, not related) + * You can override it only if you need to. + */ + protected function buyGift() + { + + } + + // this method will be unknown by subclasses (better) + private function buyAFlight() + { + echo "Buying a flight\n"; + } + + // sbclasses will get access to this method but cannot override it and + // compromise this algorithm (warning : cause of cyclic depedencies) + final protected function takePlane() + { + echo "Taking the plane\n"; + } + + // A note regarding the keyword "final" : don't use it when you start coding : + // add it after you narrow and know exacly what change and what remain unchanged + // in this algorithm. + // [abstract] x [3 access] x [final] = 12 combinations, it can be hard ! +} \ No newline at end of file From 1ab184fca76ec476a01f71d879df426a54b96b1e Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 12 May 2013 11:13:37 +0200 Subject: [PATCH 41/61] make concrete journey --- TemplateMethod/BeachJourney.php | 20 ++++++++++++++++++++ TemplateMethod/CityJouney.php | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 TemplateMethod/BeachJourney.php create mode 100644 TemplateMethod/CityJouney.php diff --git a/TemplateMethod/BeachJourney.php b/TemplateMethod/BeachJourney.php new file mode 100644 index 0000000..cec7bab --- /dev/null +++ b/TemplateMethod/BeachJourney.php @@ -0,0 +1,20 @@ + Date: Sun, 12 May 2013 11:13:56 +0200 Subject: [PATCH 42/61] add tests for this pattern --- Tests/TemplateMethod/JourneyTest.php | 49 ++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 Tests/TemplateMethod/JourneyTest.php diff --git a/Tests/TemplateMethod/JourneyTest.php b/Tests/TemplateMethod/JourneyTest.php new file mode 100644 index 0000000..fcfc4e6 --- /dev/null +++ b/Tests/TemplateMethod/JourneyTest.php @@ -0,0 +1,49 @@ +expectOutputRegex('#sun-bathing#'); + $journey->takeATrip(); + } + + public function testCity() + { + $journey = new TemplateMethod\CityJouney(); + $this->expectOutputRegex('#drink#'); + $journey->takeATrip(); + } + + /** + * How to test an abstract template method with PHPUnit + */ + public function testLasVegas() + { + $journey = $this->getMockForAbstractClass('DesignPatterns\TemplateMethod\Journey'); + $journey->expects($this->once()) + ->method('enjoyVacation') + ->will($this->returnCallback(array($this, 'mockUpVacation'))); + $this->expectOutputRegex('#Las Vegas#'); + $journey->takeATrip(); + } + + public function mockUpVacation() + { + echo "Fear and loathing in Las Vegas\n"; + } + +} \ No newline at end of file From 4f46d28e290cf25f58b53bfb63ab1cc3a3d845f4 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 12 May 2013 12:30:42 +0200 Subject: [PATCH 43/61] a builder --- Builder/BikeBuilder.php | 43 ++++++++++++++++++++++++++++++++++++ Builder/Builder.php | 33 ++++++++++++++++++++++++++++ Builder/CarBuilder.php | 46 +++++++++++++++++++++++++++++++++++++++ Builder/Director.php | 32 +++++++++++++++++++++++++++ Builder/Parts/Bike.php | 15 +++++++++++++ Builder/Parts/Car.php | 15 +++++++++++++ Builder/Parts/Door.php | 8 +++++++ Builder/Parts/Engine.php | 8 +++++++ Builder/Parts/Vehicle.php | 22 +++++++++++++++++++ Builder/Parts/Wheel.php | 8 +++++++ 10 files changed, 230 insertions(+) create mode 100644 Builder/BikeBuilder.php create mode 100644 Builder/Builder.php create mode 100644 Builder/CarBuilder.php create mode 100644 Builder/Director.php create mode 100644 Builder/Parts/Bike.php create mode 100644 Builder/Parts/Car.php create mode 100644 Builder/Parts/Door.php create mode 100644 Builder/Parts/Engine.php create mode 100644 Builder/Parts/Vehicle.php create mode 100644 Builder/Parts/Wheel.php diff --git a/Builder/BikeBuilder.php b/Builder/BikeBuilder.php new file mode 100644 index 0000000..bb63fdf --- /dev/null +++ b/Builder/BikeBuilder.php @@ -0,0 +1,43 @@ +bike->setPart('engine', new Parts\Engine()); + } + + public function addWheel() + { + $this->bike->setPart('forwardWheel', new Parts\Wheel()); + $this->bike->setPart('rearWheel', new Parts\Wheel()); + } + + public function createVehicle() + { + $this->bike = new Parts\Bike(); + } + + public function getVehicle() + { + return $this->bike; + } + +} \ No newline at end of file diff --git a/Builder/Builder.php b/Builder/Builder.php new file mode 100644 index 0000000..a696936 --- /dev/null +++ b/Builder/Builder.php @@ -0,0 +1,33 @@ +car->setPart('rightdoor', new Parts\Door()); + $this->car->setPart('leftDoor', new Parts\Door()); + } + + public function addEngine() + { + $this->car->setPart('engine', new Parts\Engine()); + } + + public function addWheel() + { + $this->car->setPart('wheelLF', new Parts\Wheel()); + $this->car->setPart('wheelRF', new Parts\Wheel()); + $this->car->setPart('wheelLR', new Parts\Wheel()); + $this->car->setPart('wheelRR', new Parts\Wheel()); + } + + public function createVehicle() + { + $this->car = new Parts\Car(); + } + + public function getVehicle() + { + return $this->car; + } + +} \ No newline at end of file diff --git a/Builder/Director.php b/Builder/Director.php new file mode 100644 index 0000000..7a55a0d --- /dev/null +++ b/Builder/Director.php @@ -0,0 +1,32 @@ +createVehicle(); + $builder->addDoors(); + $builder->addEngine(); + $builder->addWheel(); + + return $builder->getVehicle(); + } + +} \ No newline at end of file diff --git a/Builder/Parts/Bike.php b/Builder/Parts/Bike.php new file mode 100644 index 0000000..b10ad1f --- /dev/null +++ b/Builder/Parts/Bike.php @@ -0,0 +1,15 @@ +data[$key] = $value; + } + +} \ No newline at end of file diff --git a/Builder/Parts/Wheel.php b/Builder/Parts/Wheel.php new file mode 100644 index 0000000..d2b8950 --- /dev/null +++ b/Builder/Parts/Wheel.php @@ -0,0 +1,8 @@ + Date: Sun, 12 May 2013 12:31:03 +0200 Subject: [PATCH 44/61] a unit test for the builder pattern --- Tests/Builder/DirectorTest.php | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Tests/Builder/DirectorTest.php diff --git a/Tests/Builder/DirectorTest.php b/Tests/Builder/DirectorTest.php new file mode 100644 index 0000000..83e2234 --- /dev/null +++ b/Tests/Builder/DirectorTest.php @@ -0,0 +1,46 @@ +director = new Director(); + } + + public function getBuilder() + { + return array( + array(new CarBuilder()), + array(new BikeBuilder()) + ); + } + + /** + * Here we test the build process. Notice that the client don't know + * anything about the contrete builder. + * + * @dataProvider getBuilder + */ + public function testBuild(\DesignPatterns\Builder\Builder $builder) + { + $newVehicle = $this->director->build($builder); + $this->assertInstanceOf('DesignPatterns\Builder\Parts\Vehicle', $newVehicle); + } + +} \ No newline at end of file From f52ebfd74f96917bfbc4d9a9754d1d8f4cb3e32f Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 12 May 2013 13:20:11 +0200 Subject: [PATCH 45/61] adding changes to readme --- README.markdown | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 40231ac..3742786 100644 --- a/README.markdown +++ b/README.markdown @@ -5,4 +5,22 @@ small list of examples (most of them from Zend Framework or Doctrine2 as I'm mos I think the problem with patterns is that often people do know them but don't know when to apply which. -*Please feel free to fork and add your own examples!* \ No newline at end of file +*Please feel free to fork and add your own examples!* + +Changes I've made : +* Adding a config and autoloader for PHPUnit +* Fixing PSR-0 for namespace and one class per file (for tested patterns) +* Moving the original Factory Method in its right place : Static Factory (+ Tests) +* Adding the Simple Factory pattern (+ Tests) +* Adding a real Factory Method Design Pattern "by the book" (+ Tests) +* Rewriting the Abstract Factory which was not a abstract factory by the GoF definition (+ Tests) +* Fixing Composite Pattern which was not a Composite pattern (+ Tests) +* Fixing Decorator Pattern which was only a Wrapper pattern (+ Tests) +* Comments on why iterator pattern example is not relevant +* Adding a new Iterator example without external influence of other pattern (+ extended Tests) +* Adding a more visual example for Facade pattern (+ Tests) +* Fixing the Command pattern by adding missing mandatory classes (+ Tests) +* Fixing the Adapter Pattern because it was buggy and incomplete (+ Tests) +* Adding Template Method Pattern (+ Tests) +* Adding Builder Pattern (+ Tests) +* \ No newline at end of file From d2e86f1c35074457dc49bad6a7ddca3bc5a9e379 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 12 May 2013 14:25:17 +0200 Subject: [PATCH 46/61] mediator in one pass --- Mediator/Colleague.php | 31 +++++++++++++++++++ Mediator/Mediator.php | 53 +++++++++++++++++++++++++++++++++ Mediator/MediatorInterface.php | 21 +++++++++++++ Mediator/Subsystem/Client.php | 27 +++++++++++++++++ Mediator/Subsystem/Database.php | 22 ++++++++++++++ Mediator/Subsystem/Server.php | 23 ++++++++++++++ Tests/Mediator/MediatorTest.php | 36 ++++++++++++++++++++++ 7 files changed, 213 insertions(+) create mode 100644 Mediator/Colleague.php create mode 100644 Mediator/Mediator.php create mode 100644 Mediator/MediatorInterface.php create mode 100644 Mediator/Subsystem/Client.php create mode 100644 Mediator/Subsystem/Database.php create mode 100644 Mediator/Subsystem/Server.php create mode 100644 Tests/Mediator/MediatorTest.php diff --git a/Mediator/Colleague.php b/Mediator/Colleague.php new file mode 100644 index 0000000..e410e86 --- /dev/null +++ b/Mediator/Colleague.php @@ -0,0 +1,31 @@ +mediator; + } + + public function __construct(MediatorInterface $medium) + { + // in this way, we are sure the concrete colleague knows the mediator + $this->mediator = $medium; + } + +} \ No newline at end of file diff --git a/Mediator/Mediator.php b/Mediator/Mediator.php new file mode 100644 index 0000000..d6edb8c --- /dev/null +++ b/Mediator/Mediator.php @@ -0,0 +1,53 @@ +database = $db; + $this->server = $srv; + $this->client = $cl; + } + + public function makeRequest() + { + $this->server->process(); + } + + public function queryDb() + { + return $this->database->getData(); + } + + public function sendResponse($content) + { + $this->client->output($content); + } + +} \ No newline at end of file diff --git a/Mediator/MediatorInterface.php b/Mediator/MediatorInterface.php new file mode 100644 index 0000000..019ad4e --- /dev/null +++ b/Mediator/MediatorInterface.php @@ -0,0 +1,21 @@ +getMediator()->makeRequest(); + } + + public function output($content) + { + echo $content; + } + +} \ No newline at end of file diff --git a/Mediator/Subsystem/Database.php b/Mediator/Subsystem/Database.php new file mode 100644 index 0000000..dd7bb01 --- /dev/null +++ b/Mediator/Subsystem/Database.php @@ -0,0 +1,22 @@ +getMediator()->queryDb(); + $this->getMediator()->sendResponse("Hello $data"); + } + +} \ No newline at end of file diff --git a/Tests/Mediator/MediatorTest.php b/Tests/Mediator/MediatorTest.php new file mode 100644 index 0000000..b673985 --- /dev/null +++ b/Tests/Mediator/MediatorTest.php @@ -0,0 +1,36 @@ +client = new Client($media); + $media->setColleague(new Database($media), $this->client, new Server($media)); + } + + public function testOutputHelloWorld() + { + // testing if Hello World is output : + $this->expectOutputString('Hello World'); + $this->client->request(); + } + +} \ No newline at end of file From a3648c449e29eaa98f1dac5dfc27e7f56b5d3a86 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 12 May 2013 14:32:16 +0200 Subject: [PATCH 47/61] adding comments --- Mediator/Mediator.php | 5 +++-- README.markdown | 2 +- Tests/Mediator/MediatorTest.php | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Mediator/Mediator.php b/Mediator/Mediator.php index d6edb8c..98b34a1 100644 --- a/Mediator/Mediator.php +++ b/Mediator/Mediator.php @@ -15,8 +15,9 @@ use DesignPatterns\Mediator\Subsystem; * It is a good alternative over Observer IF you have a "central intelligence", * like a controller (but not in the sense of the MVC). * - * All objects (called Colleague) are coupled only to the MediatorInterface and - * it is a good thing because in OOP, one good friend is better than many unreliable. + * All components (called Colleague) are only coupled to the MediatorInterface and + * it is a good thing because in OOP, one good friend is better than many. This + * is the key-feature of this pattern. * * In this example, I have made a "Hello World" with the Mediator Pattern, have fun (^_^) */ diff --git a/README.markdown b/README.markdown index 3742786..168fc4a 100644 --- a/README.markdown +++ b/README.markdown @@ -23,4 +23,4 @@ Changes I've made : * Fixing the Adapter Pattern because it was buggy and incomplete (+ Tests) * Adding Template Method Pattern (+ Tests) * Adding Builder Pattern (+ Tests) -* \ No newline at end of file +* Adding Mediator Pattern (+ Tests) \ No newline at end of file diff --git a/Tests/Mediator/MediatorTest.php b/Tests/Mediator/MediatorTest.php index b673985..f4f689d 100644 --- a/Tests/Mediator/MediatorTest.php +++ b/Tests/Mediator/MediatorTest.php @@ -30,7 +30,10 @@ class MediatorTest extends \PHPUnit_Framework_TestCase { // testing if Hello World is output : $this->expectOutputString('Hello World'); + // as you see, the 3 components Client, Server and Database are totally decoupled $this->client->request(); + // Anyway, it remains complexity in the Mediator that's why the pattern + // Observer is preferable in mnay situations. } } \ No newline at end of file From fc781c891a0da3e8976e94b13fd6b9e56ab56fb3 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 12 May 2013 20:09:13 +0200 Subject: [PATCH 48/61] comments to explain where the problem is --- ChainOfResponsibilities/ChainOfResponsibilities.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChainOfResponsibilities/ChainOfResponsibilities.php b/ChainOfResponsibilities/ChainOfResponsibilities.php index 82ba06e..5b2686d 100644 --- a/ChainOfResponsibilities/ChainOfResponsibilities.php +++ b/ChainOfResponsibilities/ChainOfResponsibilities.php @@ -10,6 +10,8 @@ namespace DesignPatterns; * in the chain and so forth * * Examples: + * - logging framework + * - spam filter * - Caching: first object is an instance of e.g. a Memcached Interface, if that "misses" it delegates the call to the * Database Interface * - Yii Framework: CFilterChain is a chain of controller action filters. the executing point is passed from one filter @@ -17,6 +19,8 @@ namespace DesignPatterns; * */ +// the idea is good but in general, the Handler component in this pattern +// is an abstract class which makes much more of the work interface KeyValueStorage { public function get($key); From 5ef810f3d0e9bcd34d5bb5e28bd0bc852da0efed Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 12 May 2013 20:52:29 +0200 Subject: [PATCH 49/61] add the generic handler and the request classes --- ChainOfResponsibilities/Handler.php | 58 +++++++++++++++++++++ ChainOfResponsibilities/Request.php | 23 ++++++++ Tests/ChainOfResponsibilities/ChainTest.php | 22 ++++++++ 3 files changed, 103 insertions(+) create mode 100644 ChainOfResponsibilities/Handler.php create mode 100644 ChainOfResponsibilities/Request.php create mode 100644 Tests/ChainOfResponsibilities/ChainTest.php diff --git a/ChainOfResponsibilities/Handler.php b/ChainOfResponsibilities/Handler.php new file mode 100644 index 0000000..43d08e3 --- /dev/null +++ b/ChainOfResponsibilities/Handler.php @@ -0,0 +1,58 @@ +successor)) { + $this->successor = $handler; + } else { + $this->successor->append($handler); + } + } + + /** + * Handle the request. + * + * This approach by using a template method pattern ensures you that + * each subclass will not forget to call the successor. Beside, the returned + * boolean value indicates you if the request have been processed or not. + */ + final public function handle(Request $req) + { + $processed = $this->processing($req); + if (!$processed) { + if (!is_null($this->successor)) { + $processed = $this->successor->handle($req); + } + } + + return $processed; + } + + abstract protected function processing(Request $req); +} \ No newline at end of file diff --git a/ChainOfResponsibilities/Request.php b/ChainOfResponsibilities/Request.php new file mode 100644 index 0000000..d1cddfc --- /dev/null +++ b/ChainOfResponsibilities/Request.php @@ -0,0 +1,23 @@ + Date: Sun, 12 May 2013 22:08:01 +0200 Subject: [PATCH 50/61] handler and request --- ChainOfResponsibilities/Handler.php | 21 +++++++++++++++++---- ChainOfResponsibilities/Request.php | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/ChainOfResponsibilities/Handler.php b/ChainOfResponsibilities/Handler.php index 43d08e3..7511221 100644 --- a/ChainOfResponsibilities/Handler.php +++ b/ChainOfResponsibilities/Handler.php @@ -8,11 +8,16 @@ namespace DesignPatterns\ChainOfResponsibilities; /** * Handler is a generic handler in the chain of responsibilities + * + * Yes you could have a lighter CoR with simpler handler but if you want your CoR to + * be extendable and decoupled, it's a better idea to do things like that in real + * situations. Usually, a CoR is meant to be changed everytime and evolves, that's + * why we slice the workflow in little bits of code. */ -abstract class Handler implements KeyValueStorage +abstract class Handler { - protected $successor = null; + private $successor = null; /** * Append a responsibility to the end of chain @@ -23,8 +28,10 @@ abstract class Handler implements KeyValueStorage * bad idea because you have to remove the type-hint of the parameter because * the last handler has a null successor. * - * And if you override the contructor, your Handler can no longer have a - * successor. One solution is to provide a NullObject (see pattern) + * And if you override the contructor, that Handler can no longer have a + * successor. One solution is to provide a NullObject (see pattern). + * It is more preferable to keep the constructor "free" to inject services + * you need with the DiC of symfony2 for example. */ final public function append(Handler $handler) { @@ -46,6 +53,7 @@ abstract class Handler implements KeyValueStorage { $processed = $this->processing($req); if (!$processed) { + // the request has not been processed by this handler => see the next if (!is_null($this->successor)) { $processed = $this->successor->handle($req); } @@ -54,5 +62,10 @@ abstract class Handler implements KeyValueStorage return $processed; } + /** + * Each concrete handler has to implement the processing of the request + * + * @return bool true if the request has been processed + */ abstract protected function processing(Request $req); } \ No newline at end of file diff --git a/ChainOfResponsibilities/Request.php b/ChainOfResponsibilities/Request.php index d1cddfc..6083c57 100644 --- a/ChainOfResponsibilities/Request.php +++ b/ChainOfResponsibilities/Request.php @@ -19,5 +19,5 @@ namespace DesignPatterns\ChainOfResponsibilities; */ class Request { - + // getter and setter but I don't want to generate to much noise in handlers } \ No newline at end of file From b581aef8f2d616b6ce6ec3cd5ef2a59e1b91340b Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 12 May 2013 22:08:35 +0200 Subject: [PATCH 51/61] adding concrete handler --- .../Responsible/FastStorage.php | 37 ++++++++++++++++ .../Responsible/SlowStorage.php | 44 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 ChainOfResponsibilities/Responsible/FastStorage.php create mode 100644 ChainOfResponsibilities/Responsible/SlowStorage.php diff --git a/ChainOfResponsibilities/Responsible/FastStorage.php b/ChainOfResponsibilities/Responsible/FastStorage.php new file mode 100644 index 0000000..0011ea0 --- /dev/null +++ b/ChainOfResponsibilities/Responsible/FastStorage.php @@ -0,0 +1,37 @@ +_data = $data; + } + + protected function processing(Request $req) + { + if ('get' === $req->verb) { + if (array_key_exists($req->key, $this->_data)) { + // the handler IS responsible and then processes the request + $req->response = $this->_data[$req->key]; + // instead of returning true, I could return the value but it proves + // to be a bad idea. What if the value IS "false" ? + return true; + } + } + + return false; + } + +} diff --git a/ChainOfResponsibilities/Responsible/SlowStorage.php b/ChainOfResponsibilities/Responsible/SlowStorage.php new file mode 100644 index 0000000..79635bf --- /dev/null +++ b/ChainOfResponsibilities/Responsible/SlowStorage.php @@ -0,0 +1,44 @@ +_data = $data; + } + + protected function processing(Request $req) + { + if ('get' === $req->verb) { + if (array_key_exists($req->key, $this->_data)) { + $req->response = $this->_data[$req->key]; + return true; + } + } + + return false; + } + +} From 71a43e2e93e79096255f9ad336c39f120c194a39 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 12 May 2013 22:08:56 +0200 Subject: [PATCH 52/61] tests for CoR --- Tests/ChainOfResponsibilities/ChainTest.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Tests/ChainOfResponsibilities/ChainTest.php b/Tests/ChainOfResponsibilities/ChainTest.php index cfe8c2f..6a9fa99 100644 --- a/Tests/ChainOfResponsibilities/ChainTest.php +++ b/Tests/ChainOfResponsibilities/ChainTest.php @@ -7,6 +7,7 @@ namespace DesignPatterns\Tests\ChainOfResponsibilities; use DesignPatterns\ChainOfResponsibilities\Request; +use DesignPatterns\ChainOfResponsibilities\Responsible; /** * ChainTest tests the CoR @@ -14,9 +15,22 @@ use DesignPatterns\ChainOfResponsibilities\Request; class ChainTest extends \PHPUnit_Framework_TestCase { + protected $chain; + + protected function setUp() + { + $this->chain = new Responsible\FastStorage(array('bar' => 'baz')); + $this->chain->append(new Responsible\SlowStorage(array('bar' => 'baz', 'foo' => 'bar'))); + } + public function testProcess() { - + $request = new Request(); + $request->verb = 'get'; + $request->key = 'bar'; + + $ret = $this->chain->handle($request); + print_r($request); } } \ No newline at end of file From f92be4efec45bc59a9ff9dd9ab8ea86ab07b6e76 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Sun, 12 May 2013 22:20:12 +0200 Subject: [PATCH 53/61] full tests --- ChainOfResponsibilities/Handler.php | 1 + Tests/ChainOfResponsibilities/ChainTest.php | 48 +++++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/ChainOfResponsibilities/Handler.php b/ChainOfResponsibilities/Handler.php index 7511221..907e6a9 100644 --- a/ChainOfResponsibilities/Handler.php +++ b/ChainOfResponsibilities/Handler.php @@ -51,6 +51,7 @@ abstract class Handler */ final public function handle(Request $req) { + $req->forDebugOnly = get_called_class(); $processed = $this->processing($req); if (!$processed) { // the request has not been processed by this handler => see the next diff --git a/Tests/ChainOfResponsibilities/ChainTest.php b/Tests/ChainOfResponsibilities/ChainTest.php index 6a9fa99..337559b 100644 --- a/Tests/ChainOfResponsibilities/ChainTest.php +++ b/Tests/ChainOfResponsibilities/ChainTest.php @@ -23,14 +23,56 @@ class ChainTest extends \PHPUnit_Framework_TestCase $this->chain->append(new Responsible\SlowStorage(array('bar' => 'baz', 'foo' => 'bar'))); } - public function testProcess() + public function makeRequest() { $request = new Request(); $request->verb = 'get'; - $request->key = 'bar'; + return array( + array($request) + ); + } + /** + * @dataProvider makeRequest + */ + public function testFastStorage($request) + { + $request->key = 'bar'; $ret = $this->chain->handle($request); - print_r($request); + + $this->assertTrue($ret); + $this->assertObjectHasAttribute('response', $request); + $this->assertEquals('baz', $request->response); + // despite both handle owns the 'bar' key, the FastStorage is responding first + $this->assertEquals('DesignPatterns\ChainOfResponsibilities\Responsible\FastStorage', $request->forDebugOnly); + } + + /** + * @dataProvider makeRequest + */ + public function testSlowStorage($request) + { + $request->key = 'foo'; + $ret = $this->chain->handle($request); + + $this->assertTrue($ret); + $this->assertObjectHasAttribute('response', $request); + $this->assertEquals('bar', $request->response); + // FastStorage has no 'foo' key, the SlowStorage is responding + $this->assertEquals('DesignPatterns\ChainOfResponsibilities\Responsible\SlowStorage', $request->forDebugOnly); + } + + /** + * @dataProvider makeRequest + */ + public function testFailure($request) + { + $request->key = 'kurukuku'; + $ret = $this->chain->handle($request); + + $this->assertFalse($ret); + // the last rsponsible : + $this->assertEquals('DesignPatterns\ChainOfResponsibilities\Responsible\SlowStorage', $request->forDebugOnly); } } \ No newline at end of file From f040f8043d28d97d841b5ad0f11b47c617e88227 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 13 May 2013 20:13:19 +0200 Subject: [PATCH 54/61] PSR-0 for namespace and filename + tests --- .../{FluentInterface.php => SQL.php} | 18 ++++++++---- Tests/FluentInterface/FluentInterfaceTest.php | 28 +++++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) rename FluentInterface/{FluentInterface.php => SQL.php} (73%) create mode 100644 Tests/FluentInterface/FluentInterfaceTest.php diff --git a/FluentInterface/FluentInterface.php b/FluentInterface/SQL.php similarity index 73% rename from FluentInterface/FluentInterface.php rename to FluentInterface/SQL.php index c7a7e7a..84034da 100644 --- a/FluentInterface/FluentInterface.php +++ b/FluentInterface/SQL.php @@ -1,6 +1,6 @@ _where[] = $condition; return $this; } + + /** + * Gets the query, just an example of building a query, + * no check on consistency + */ + public function getQuery() + { + return 'SELECT ' . implode(',', $this->_fields) + . ' FROM ' . implode(',', $this->_from) + . ' WHERE ' . implode(' AND ', $this->_where); + } } - -$instance = new SQL(); -$instance->select(array('foo', 'bar')) - ->from('foobar', 'f') - ->where('f.bar = ?'); \ No newline at end of file diff --git a/Tests/FluentInterface/FluentInterfaceTest.php b/Tests/FluentInterface/FluentInterfaceTest.php new file mode 100644 index 0000000..7dec8d8 --- /dev/null +++ b/Tests/FluentInterface/FluentInterfaceTest.php @@ -0,0 +1,28 @@ +select(array('foo', 'bar')) + ->from('foobar', 'f') + ->where('f.bar = ?') + ->getQuery(); + + $this->assertEquals('SELECT foo,bar FROM foobar AS f WHERE f.bar = ?', $query); + } + +} \ No newline at end of file From ceeab58eb9df78820a12588f1bef635693bc3cb9 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 13 May 2013 21:49:33 +0200 Subject: [PATCH 55/61] add a simple example with a null logger --- NullObject/LoggerInterface.php | 16 ++++++++++++++ NullObject/NullLogger.php | 38 ++++++++++++++++++++++++++++++++++ NullObject/PrintLogger.php | 20 ++++++++++++++++++ NullObject/Service.php | 30 +++++++++++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 NullObject/LoggerInterface.php create mode 100644 NullObject/NullLogger.php create mode 100644 NullObject/PrintLogger.php create mode 100644 NullObject/Service.php diff --git a/NullObject/LoggerInterface.php b/NullObject/LoggerInterface.php new file mode 100644 index 0000000..2c6ed97 --- /dev/null +++ b/NullObject/LoggerInterface.php @@ -0,0 +1,16 @@ + less test cases + * + * The purpose : every time you have a method which returns an object or null, + * you should return an object or a "NullObject". With NullObject, you don't need + * statement like "if (!is_null($obj)) { $obj->callSomething(); }" anymore. + * + * In this case, this a logger which does nothing. Other examples : + * - null logger of symfony profiler + * - null output in symfony/console + * - null handler in a Chain of Responsiblities pattern + * - null command in a Command pattern + * + * Performance concerns : ok there is a call for nothing but we spare an "if is_null" + * I didn't run a benchmark but I think it's equivalent. + */ +class NullLogger implements LoggerInterface +{ + + public function log($str) + { + + } + +} \ No newline at end of file diff --git a/NullObject/PrintLogger.php b/NullObject/PrintLogger.php new file mode 100644 index 0000000..4640ca4 --- /dev/null +++ b/NullObject/PrintLogger.php @@ -0,0 +1,20 @@ +logger = $log; + } + + public function doSomething() + { + // no more check "if (!is_null($this->logger))..." with the NullObject pattern + $this->logger->log('We are in ' . __METHOD__); + // something to do... + } + +} \ No newline at end of file From b9d335bb43e64bae5e05155ebcbffee8a2d40038 Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 13 May 2013 21:49:51 +0200 Subject: [PATCH 56/61] add test for null object --- Tests/NullObject/LoggerTest.php | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Tests/NullObject/LoggerTest.php diff --git a/Tests/NullObject/LoggerTest.php b/Tests/NullObject/LoggerTest.php new file mode 100644 index 0000000..36edb6b --- /dev/null +++ b/Tests/NullObject/LoggerTest.php @@ -0,0 +1,35 @@ +expectOutputString(null); // no output + $service->doSomething(); + } + + public function testStandardLogger() + { + $service = new Service(new PrintLogger()); + $this->expectOutputString('We are in DesignPatterns\NullObject\Service::doSomething'); + $service->doSomething(); + } + +} \ No newline at end of file From f157729ea42c0f3188b53b16ef7499ebf038c45a Mon Sep 17 00:00:00 2001 From: Trismegiste Date: Mon, 13 May 2013 21:57:52 +0200 Subject: [PATCH 57/61] add comments about null logger --- NullObject/LoggerInterface.php | 2 ++ NullObject/NullLogger.php | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/NullObject/LoggerInterface.php b/NullObject/LoggerInterface.php index 2c6ed97..2cb19d0 100644 --- a/NullObject/LoggerInterface.php +++ b/NullObject/LoggerInterface.php @@ -8,6 +8,8 @@ namespace DesignPatterns\NullObject; /** * LoggerInterface is a contract for logging something + * + * Key-feaature : NullLogger MUST inherit from this interface like any other Loggers */ interface LoggerInterface { diff --git a/NullObject/NullLogger.php b/NullObject/NullLogger.php index 58a67e0..761f0d9 100644 --- a/NullObject/NullLogger.php +++ b/NullObject/NullLogger.php @@ -26,13 +26,16 @@ namespace DesignPatterns\NullObject; * * Performance concerns : ok there is a call for nothing but we spare an "if is_null" * I didn't run a benchmark but I think it's equivalent. + * + * Key feature : of course this logger MUST implement the same interface (or abstract) + * like the other loggers. */ class NullLogger implements LoggerInterface { public function log($str) { - + // do nothing } } \ No newline at end of file From 7ddaa0abe59424f6667499932a4dfa3708a70470 Mon Sep 17 00:00:00 2001 From: Florent Date: Tue, 14 May 2013 09:26:35 +0200 Subject: [PATCH 58/61] update readme --- README.markdown | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 168fc4a..2c98e30 100644 --- a/README.markdown +++ b/README.markdown @@ -23,4 +23,7 @@ Changes I've made : * Fixing the Adapter Pattern because it was buggy and incomplete (+ Tests) * Adding Template Method Pattern (+ Tests) * Adding Builder Pattern (+ Tests) -* Adding Mediator Pattern (+ Tests) \ No newline at end of file +* Adding Mediator Pattern (+ Tests) +* Adding a full example of Chain of Responsibilities (+Tests) +* PSR-0 for FluentInterface & Tests +* Null Object Pattern (+ Tests) \ No newline at end of file From 1feafaf8baa0d7e4a42cd1d010d6974cb1750a48 Mon Sep 17 00:00:00 2001 From: Florent Date: Thu, 16 May 2013 09:06:24 +0200 Subject: [PATCH 59/61] adding travis --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..36bda4c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: php +php: + - 5.4 From f356077d85b8cde78a6b4f394623c52415ef4530 Mon Sep 17 00:00:00 2001 From: Florent Date: Thu, 16 May 2013 09:12:28 +0200 Subject: [PATCH 60/61] build branch travis --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 36bda4c..563ae95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,7 @@ language: php php: - 5.4 + +branches: + only: + - travis From 4c83011c1bf1a8b5207b67a1e72b1b628981043f Mon Sep 17 00:00:00 2001 From: Florent Date: Thu, 16 May 2013 09:19:02 +0200 Subject: [PATCH 61/61] adding image status of travis --- .travis.yml | 2 +- README.markdown | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 563ae95..b74069f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,4 @@ php: branches: only: - - travis + - master diff --git a/README.markdown b/README.markdown index 2c98e30..9d7910d 100644 --- a/README.markdown +++ b/README.markdown @@ -1,5 +1,7 @@ # design patterns in PHP # +[![Build Status](https://travis-ci.org/Trismegiste/DesignPatternsPHP.png?branch=master)](https://travis-ci.org/Trismegiste/DesignPatternsPHP) + This is a collection of known design patterns and some sample code how to implement them in PHP. Every pattern has a small list of examples (most of them from Zend Framework or Doctrine2 as I'm most familiar with this software).