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 @@ <?php -namespace DesignPatterns; +namespace DesignPatterns\AbstractFactory; /** * Abstract Factory pattern * * Purpose: * to create series of related or dependant objects without specifying their concrete classes, - * usually the created classes all implement the same interface - * - * Examples: - * - A Factory to create media in a CMS: classes would be text, audio, video, picture - * - SQL Factory (types are all strings with SQL, but they vary in detail (tables, fields, etc.)) - * - Zend Framework: Zend_Form::createElement() creates form elements, but you could also call new T - * TextElement() instead - * - an abstract factory to create various exceptions (e.g. Doctrine2 uses this method) - * + * usually the created classes all implement the same interface. The client of the abstract + * factory does not care about how these objects are created, he just knows they goes together. + * + * Sometimes also known as "Kit" in a GUI libraries. + * + * This design pattern implements the Dependency Inversion Principle since + * it is the concrete subclass which creates concrete components. + * + * In this case, the abstract factory is a contract for creating some components + * for the web. There are two components : Text and Picture. There is two ways + * of rendering : HTML or JSON. + * + * Therefore 4 concretes classes, but the client just need to know this contract + * to build a correct http response (for a html page or for an ajax request) */ abstract class AbstractFactory { /** - * @static + * Creates a text component + * * @param string $content - * @return AbstractFactory\Text + * @return Text */ - public static function createText($content) - { - return new AbstractFactory\Text($content); - } + abstract public function createText($content); /** - * @static + * Createss a picture component + * * @param string $path * @param string $name - * @return AbstractFactory\Picture + * @return Picture */ - public static function createPicture($path, $name = '') - { - return new AbstractFactory\Picture($path, $name); - } + abstract public function createPicture($path, $name = ''); } diff --git a/AbstractFactory/Html/Picture.php b/AbstractFactory/Html/Picture.php new file mode 100644 index 0000000..6efc2be --- /dev/null +++ b/AbstractFactory/Html/Picture.php @@ -0,0 +1,23 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\AbstractFactory\Html; + +use DesignPatterns\AbstractFactory\Picture as BasePicture; + +/** + * Picture is a concrete image for HTML rendering + */ +class Picture extends BasePicture +{ + + // some crude rendering from HTML output + public function render() + { + return sprintf('<img src="%s" title="$s"/>', $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 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\AbstractFactory\Html; + +use DesignPatterns\AbstractFactory\Text as BaseText; + +/** + * Text is a concrete text for HTML rendering + */ +class Text extends BaseText +{ + + public function render() + { + return "<div>" . htmlspecialchars($this->_text) . '</div>'; + } + +} \ 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 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\AbstractFactory; + +/** + * HtmlFactory is a concrete factory for HTML component + */ +class HtmlFactory extends AbstractFactory +{ + + public function createPicture($path, $name = '') + { + return new Html\Picture($path, $name); + } + + public function createText($content) + { + return new Html\Text($content); + } + +} \ No newline at end of file diff --git a/AbstractFactory/Json/Picture.php b/AbstractFactory/Json/Picture.php new file mode 100644 index 0000000..f70e150 --- /dev/null +++ b/AbstractFactory/Json/Picture.php @@ -0,0 +1,23 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\AbstractFactory\Json; + +use DesignPatterns\AbstractFactory\Picture as BasePicture; + +/** + * Picture is a concrete image for JSON rendering + */ +class Picture extends BasePicture +{ + + // some crude rendering from JSON output + public function render() + { + return json_encode(array('title' => $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 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\AbstractFactory\Json; + +use DesignPatterns\AbstractFactory\Text as BaseText; + +/** + * Text is a text component with a JSON rendering + */ +class Text extends BaseText +{ + + public function render() + { + return json_encode(array('content' => $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 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\AbstractFactory; + +/** + * JsonFactory is a factory for creating a family of JSON component + * (example for ajax) + */ +class JsonFactory extends AbstractFactory +{ + + public function createPicture($path, $name = '') + { + return new Json\Picture($path, $name); + } + + public function createText($content) + { + return new Json\Text($content); + } + +} \ No newline at end of file diff --git a/AbstractFactory/Media.php b/AbstractFactory/Media.php index c616221..888f521 100644 --- a/AbstractFactory/Media.php +++ b/AbstractFactory/Media.php @@ -2,7 +2,12 @@ namespace DesignPatterns\AbstractFactory; +/** + * This contract is not part of the pattern, in general case, each component + * are not related + */ interface Media { + function render(); } diff --git a/AbstractFactory/Picture.php b/AbstractFactory/Picture.php index a5e2fbc..50e538b 100644 --- a/AbstractFactory/Picture.php +++ b/AbstractFactory/Picture.php @@ -2,7 +2,7 @@ namespace DesignPatterns\AbstractFactory; -class Picture implements Media +abstract class Picture implements Media { protected $_path; protected $_name; diff --git a/AbstractFactory/Text.php b/AbstractFactory/Text.php index b8070ea..dd810e8 100644 --- a/AbstractFactory/Text.php +++ b/AbstractFactory/Text.php @@ -2,7 +2,7 @@ namespace DesignPatterns\AbstractFactory; -class Text implements Media +abstract class Text implements Media { /** * 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 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\FactoryMethod; + +/** + * Bicycle is a bicycle + */ +class Bicycle implements Vehicle +{ + + public function setColor($rgb) + { + + } + +} \ No newline at end of file diff --git a/FactoryMethod/FactoryMethod.php b/FactoryMethod/FactoryMethod.php index 6b3da15..3402bd6 100644 --- a/FactoryMethod/FactoryMethod.php +++ b/FactoryMethod/FactoryMethod.php @@ -1,49 +1,54 @@ <?php -namespace DesignPatterns; +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\FactoryMethod; /** - * Factory Method pattern - * - * 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 - * method to create all types of objects it can create. It is usually named "factory" or "build". - * - * Examples: - * - Zend Framework: Zend_Cache_Backend or _Frontend use a factory method create cache backends or frontends + * 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 (see subclasses) * + * 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. */ -class FactoryMethod +abstract class FactoryMethod { + + const CHEAP = 1; + const FAST = 2; + /** - * the parametrized function to get create an instance - * - * @static - * @return Formatter + * The children of the class must implement this method + * + * Sometimes this method can be public to get "raw" object + * + * @param string $type a generic type + * + * @return Vehicle a new vehicle */ - public static function factory($type) + abstract protected function createVehicle($type); + + /** + * Creates a new vehicle + * + * @param int $type + * + * @return Vehicle a new vehicle + */ + public function create($type) { - $className = 'Format' . ucfirst($type); - if ( ! class_exists($className)) { - throw new Exception('Missing format class.'); - } + $obj = $this->createVehicle($type); + $obj->setColor("#f00"); - return new $className(); + return $obj; } -} -interface Formatter -{ - -} - -class FormatString implements Formatter -{ - -} - -class FormatNumber implements Formatter -{ - -} +} \ 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 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\FactoryMethod; + +/** + * Ferrari is a italian car + */ +class Ferrari implements Vehicle +{ + + public function setColor($rgb) + { + + } + +} \ No newline at end of file diff --git a/FactoryMethod/GermanFactory.php b/FactoryMethod/GermanFactory.php new file mode 100644 index 0000000..a962c76 --- /dev/null +++ b/FactoryMethod/GermanFactory.php @@ -0,0 +1,39 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\FactoryMethod; + +/** + * GermanFactory is vehicle factory in Germany + */ +class GermanFactory extends FactoryMethod +{ + + /** + * @inheritdoc + */ + protected function createVehicle($type) + { + switch ($type) { + + case parent::CHEAP : + return new Bicycle(); + break; + + case parent::FAST : + $obj = new Porsche(); + // we can specialize the way we want some concrete Vehicle since + // we know the class + $obj->addTuningAMG(); + return $obj; + break; + + default : + throw new \InvalidArgumentException("$type is not a valid vehicle"); + } + } + +} \ No newline at end of file diff --git a/FactoryMethod/ItalianFactory.php b/FactoryMethod/ItalianFactory.php new file mode 100644 index 0000000..db8514c --- /dev/null +++ b/FactoryMethod/ItalianFactory.php @@ -0,0 +1,35 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\FactoryMethod; + +/** + * ItalianFactory is vehicle factory in Italy + */ +class ItalianFactory extends FactoryMethod +{ + + /** + * @inheritdoc + */ + protected function createVehicle($type) + { + switch ($type) { + + case parent::CHEAP : + return new Bicycle(); + break; + + case parent::FAST : + return new Ferrari(); + break; + + default : + throw new \InvalidArgumentException("$type is not a valid vehicle"); + } + } + +} \ No newline at end of file diff --git a/FactoryMethod/Porsche.php b/FactoryMethod/Porsche.php new file mode 100644 index 0000000..c2e775b --- /dev/null +++ b/FactoryMethod/Porsche.php @@ -0,0 +1,25 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\FactoryMethod; + +/** + * Porsche is a german car + */ +class Porsche implements Vehicle +{ + + public function setColor($rgb) + { + + } + + public function addTuningAMG() + { + + } + +} \ No newline at end of file diff --git a/FactoryMethod/Vehicle.php b/FactoryMethod/Vehicle.php new file mode 100644 index 0000000..180a349 --- /dev/null +++ b/FactoryMethod/Vehicle.php @@ -0,0 +1,16 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\FactoryMethod; + +/** + * Vehicle is a contract for a vehicle + */ +interface Vehicle +{ + + function setColor($rgb); +} \ No newline at end of file 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 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\SimpleFactory; + +/** + * Bicycle is a bicycle + */ +class Bicycle implements Vehicle +{ + + public function driveTo($destination) + { + + } + +} \ No newline at end of file diff --git a/SimpleFactory/ConcreteFactory.php b/SimpleFactory/ConcreteFactory.php new file mode 100644 index 0000000..01ba1ad --- /dev/null +++ b/SimpleFactory/ConcreteFactory.php @@ -0,0 +1,52 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\SimpleFactory; + +/** + * ConcreteFactory is a simple factory pattern. + * + * It differs from the static factory because it is NOT static and as you + * know : static => 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 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\SimpleFactory; + +/** + * Scooter is a Scooter + */ +class Scooter implements Vehicle +{ + + public function driveTo($destination) + { + + } + +} \ No newline at end of file diff --git a/SimpleFactory/Vehicle.php b/SimpleFactory/Vehicle.php new file mode 100644 index 0000000..87b0ef3 --- /dev/null +++ b/SimpleFactory/Vehicle.php @@ -0,0 +1,16 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\SimpleFactory; + +/** + * Vehicle is a contract for a vehicle + */ +interface Vehicle +{ + + function driveTo($destination); +} \ No newline at end of file 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 @@ +<?php + +namespace DesignPatterns\StaticFactory; + +class FormatNumber implements Formatter +{ + +} diff --git a/StaticFactory/FormatString.php b/StaticFactory/FormatString.php new file mode 100644 index 0000000..9f961e7 --- /dev/null +++ b/StaticFactory/FormatString.php @@ -0,0 +1,8 @@ +<?php + +namespace DesignPatterns\StaticFactory; + +class FormatString implements Formatter +{ + +} diff --git a/StaticFactory/Formatter.php b/StaticFactory/Formatter.php new file mode 100644 index 0000000..54b0ba0 --- /dev/null +++ b/StaticFactory/Formatter.php @@ -0,0 +1,8 @@ +<?php + +namespace DesignPatterns\StaticFactory; + +interface Formatter +{ + +} diff --git a/StaticFactory/StaticFactory.php b/StaticFactory/StaticFactory.php new file mode 100644 index 0000000..aa32d43 --- /dev/null +++ b/StaticFactory/StaticFactory.php @@ -0,0 +1,39 @@ +<?php + +namespace DesignPatterns\StaticFactory; + +/** + * Static Factory pattern + * + * 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 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: + * - Zend Framework: Zend_Cache_Backend or _Frontend use a factory method create cache backends or frontends + * + * Note1: Remember, static => global => evil + * Note2: Cannot be subclassed or mock-uped or have multiple different instances + */ +class StaticFactory +{ + + /** + * the parametrized function to get create an instance + * + * @static + * @return Formatter + */ + public static function factory($type) + { + $className = __NAMESPACE__ . '\Format' . ucfirst($type); + + if (!class_exists($className)) { + throw new \InvalidArgumentException('Missing format class.'); + } + + return new $className(); + } + +} diff --git a/Tests/AbstractFactory/AbstractFactoryTest.php b/Tests/AbstractFactory/AbstractFactoryTest.php new file mode 100644 index 0000000..81d0401 --- /dev/null +++ b/Tests/AbstractFactory/AbstractFactoryTest.php @@ -0,0 +1,50 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\Tests\AbstractFactory; + +use DesignPatterns\AbstractFactory\AbstractFactory; +use DesignPatterns\AbstractFactory\HtmlFactory; +use DesignPatterns\AbstractFactory\JsonFactory; + +/** + * AbstractFactoryTest tests concrete factories + */ +class AbstractFactoryTest extends \PHPUnit_Framework_TestCase +{ + + public function getFactories() + { + return array( + array(new JsonFactory()), + array(new HtmlFactory()) + ); + } + + /** + * This is the client of factories. Note that the client does not + * care which factory is given to him, he can create any component he + * wants and render how he wants. + * + * @dataProvider getFactories + */ + public function testComponentCreation(AbstractFactory $factory) + { + $article = array( + $factory->createText('Lorem Ipsum'), + $factory->createPicture('/image.jpg', 'caption'), + $factory->createText('footnotes') + ); + + $this->assertContainsOnly('DesignPatterns\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 diff --git a/Tests/FactoryMethod/FactoryMethodTest.php b/Tests/FactoryMethod/FactoryMethodTest.php new file mode 100644 index 0000000..463c1c8 --- /dev/null +++ b/Tests/FactoryMethod/FactoryMethodTest.php @@ -0,0 +1,55 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\Tests\FactoryMethod; + +use DesignPatterns\FactoryMethod\FactoryMethod; +use DesignPatterns\FactoryMethod\GermanFactory; +use DesignPatterns\FactoryMethod\ItalianFactory; + +/** + * FactoryMethodTest tests the factory method pattern + */ +class FactoryMethodTest extends \PHPUnit_Framework_TestCase +{ + + protected $type = array( + FactoryMethod::CHEAP, + FactoryMethod::FAST + ); + + public function getShop() + { + return array( + array(new GermanFactory()), + array(new ItalianFactory()) + ); + } + + /** + * @dataProvider getShop + */ + public function testCreation(FactoryMethod $shop) + { + // this test method acts as a client for the factory. We don't care + // about the factory, all we know is it can produce vehicle + foreach ($this->type as $oneType) { + $vehicle = $shop->create($oneType); + $this->assertInstanceOf('DesignPatterns\FactoryMethod\Vehicle', $vehicle); + } + } + + /** + * @dataProvider getShop + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage spaceship is not a valid vehicle + */ + public function testUnknownType(FactoryMethod $shop) + { + $shop->create('spaceship'); + } + +} \ No newline at end of file diff --git a/Tests/SimpleFactory/SimpleFactoryTest.php b/Tests/SimpleFactory/SimpleFactoryTest.php new file mode 100644 index 0000000..3494ca2 --- /dev/null +++ b/Tests/SimpleFactory/SimpleFactoryTest.php @@ -0,0 +1,51 @@ +<?php + +/* + * DesignPatternPHP + */ + +namespace DesignPatterns\Tests\SimpleFactory; + +use DesignPatterns\SimpleFactory\ConcreteFactory; + +/** + * SimpleFactoryTest tests the Simple Factory pattern + * + * @author flo + */ +class SimpleFactoryTest extends \PHPUnit_Framework_TestCase +{ + + protected $factory; + + protected function setUp() + { + $this->factory = new ConcreteFactory(); + } + + public function getType() + { + return array( + array('bicycle'), + array('other') + ); + } + + /** + * @dataProvider getType + */ + public function testCreation($type) + { + $obj = $this->factory->createVehicle($type); + $this->assertInstanceOf('DesignPatterns\SimpleFactory\Vehicle', $obj); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testBadType() + { + $this->factory->createVehicle('car'); + } + +} \ No newline at end of file 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 @@ +<?php + +namespace DesignPatterns\Tests\StaticFactory; + +use DesignPatterns\StaticFactory\StaticFactory; + +/** + * Tests for Static Factory pattern + * + */ +class StaticFactoryTest extends \PHPUnit_Framework_TestCase +{ + + public function getTypeList() + { + return array( + array('string'), + array('number') + ); + } + + /** + * @dataProvider getTypeList + */ + public function testCreation($type) + { + $obj = StaticFactory::factory($type); + $this->assertInstanceOf('DesignPatterns\StaticFactory\Formatter', $obj); + } + +}