├── .gitignore ├── README.md ├── Decorator ├── README.md ├── exercise.php └── solution.php ├── Observer ├── README.md ├── exercise.php └── solution.php ├── Iterator ├── README.md ├── exercise.php └── solution.php ├── Adapter ├── README.md ├── exercise.php └── solution.php ├── Strategy ├── README.md ├── exercise.php └── solution.php ├── Proxy ├── README.md ├── exercise.php └── solution.php ├── Mapper ├── README.md ├── exercise.php └── solution.php ├── Visitor ├── README.md ├── exercise.php └── solution.php ├── DependencyInjection ├── exercise.php ├── solution.php └── README.md ├── Prototype ├── README.md ├── solution.php └── exercise.php ├── FactoryMethod ├── exercise.php ├── README.md └── solution.php └── AbstractFactory ├── README.md ├── exercise.php └── solution.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Collection of design patterns in PHP presented during the workshop "Do you speak design patterns in PHP?" at ZendCon 2013. 2 | 3 | (C) Copyright 2013 by Josh Butts, Ralph Schindler, Enrico Zimuel 4 | -------------------------------------------------------------------------------- /Decorator/README.md: -------------------------------------------------------------------------------- 1 | Decorator 2 | ========= 3 | 4 | Definition 5 | ---------- 6 | The Decorator is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. 7 | 8 | 9 | Scenario 10 | -------- 11 | 12 | You want to add a label and manage the error output for an InpuText class that manage input text from user's form. 13 | 14 | -------------------------------------------------------------------------------- /Observer/README.md: -------------------------------------------------------------------------------- 1 | Observer 2 | ===== 3 | 4 | The observer pattern is used to promote loose coupling between objects. "Subjects" may be subscribed to by "Observers", such that hanges to the subject trigger a notification to one or more observers. Usually, the observers are passed the entire subject as context. 5 | 6 | Scenario 7 | -------- 8 | 9 | A data model may be changed from various different points in the code. You want to implement a logger that keeps track of when the model changes. -------------------------------------------------------------------------------- /Iterator/README.md: -------------------------------------------------------------------------------- 1 | Iterator 2 | ======== 3 | 4 | Definition 5 | ---------- 6 | 7 | The iterator pattern is a design pattern in which an iterator is used to traverse a container and access the container's elements. The iterator pattern decouples algorithms from containers; in some cases, algorithms are necessarily container-specific and thus cannot be decoupled. 8 | 9 | Scenario 10 | -------- 11 | 12 | We want to implement the Fibonacci series of numbers using the PHP Iterator pattern: http://php.net/manual/en/class.iterator.php 13 | -------------------------------------------------------------------------------- /Adapter/README.md: -------------------------------------------------------------------------------- 1 | Adapter 2 | ======= 3 | 4 | Adapters are used to take the public API's of classes that would not otherwise be compatible, and layer on top of them a common interface. Generally, the adapter class will contain a reference to a concrete instance of the adaptee. 5 | 6 | Examples of adapters include: Database adapters / drivers, cache adapters, and third-party API adapters. 7 | 8 | Scenario 9 | -------- 10 | 11 | Your want to accept payments online from both paypal and standard credit cards, but the APIs for this are quite different. -------------------------------------------------------------------------------- /Strategy/README.md: -------------------------------------------------------------------------------- 1 | Strategy 2 | ================ 3 | 4 | The strategy pattern is essentially a system for for pluggable logic, designed to be easily switched out giving varying implementations of the same task. 5 | 6 | This pattern is often used in sorting and filtering scenarios. It may also be used for data storage / persistance, and in a more concrete definition, adapters. 7 | 8 | Scenario 9 | -------- 10 | 11 | You have a collection of business objects that represent items for sale in a shopping cart system. You may want to allow users to narrow results in the view layer by various different criteria, depending on their tastes. -------------------------------------------------------------------------------- /Proxy/README.md: -------------------------------------------------------------------------------- 1 | Proxy 2 | ===== 3 | 4 | Definition 5 | ---------- 6 | A proxy, in its most general form, is a class functioning as an interface to something else. The proxy could interface to anything: a network connection, a large object in memory, a file, or some other resource that is expensive or impossible to duplicate. 7 | 8 | In situations where multiple copies of a complex object must exist, the proxy pattern can be adapted to incorporate the flyweight pattern in order to reduce the application's memory footprint. Typically, one instance of the complex object and multiple proxy objects are created, all of which contain a reference to the single original complex object 9 | 10 | Scenario 11 | -------- 12 | Imagine you have an Image class that loads the file on constructor. We want to create a Proxy class that loads the image only if needed, for instance only during the dispaly. 13 | -------------------------------------------------------------------------------- /Mapper/README.md: -------------------------------------------------------------------------------- 1 | Mapper 2 | ====== 3 | 4 | Mapper patterns are used to translate data from one state into another. Most often, mappers are used to convert "flat" data into an object graph, or vice-versa, as you would do when savnig and retrieving objects from a database. The key point of the mapper pattern is that the objects don't need to be aware of the interfaces of the other states that the mapper may be aware of. 5 | 6 | Mappers also promote loose coupling and separation of concerns. Many ORM systems use a datamapper pattern. Doctrine 2.x uses datamappers, whereas Doctrine 1.x used an ActiveRecord pattern, which does not allow for separation of business logic and persistence. 7 | 8 | Scenario 9 | -------- 10 | 11 | Data from a database (in associative array form) needs to be converted into model objects. At some point in the future, those objects need to be mapped back into associative array form to be written back to the database. -------------------------------------------------------------------------------- /Visitor/README.md: -------------------------------------------------------------------------------- 1 | Visitor 2 | ======= 3 | 4 | Definition 5 | ---------- 6 | 7 | The Visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying those structures. It is one way to follow the open/closed principle. 8 | 9 | In essence, the visitor allows one to add new virtual functions to a family of classes without modifying the classes themselves; instead, one creates a visitor class that implements all of the appropriate specializations of the virtual function. The visitor takes the instance reference as input, and implements the goal through double dispatch. 10 | 11 | Scenario 12 | -------- 13 | 14 | We want to implement a simple data structure that manages GET or POST input data, being it single or multiple values. We want to implement a pair of Visitor pattern to define operations on these data structures. 15 | -------------------------------------------------------------------------------- /Iterator/exercise.php: -------------------------------------------------------------------------------- 1 | value = 0; 15 | $this->key = 0; 16 | } 17 | public function current() { 18 | return $this->value; 19 | } 20 | public function key() { 21 | return $this->key; 22 | } 23 | public function next() { 24 | // @todo here the code to implement 25 | } 26 | 27 | public function valid() { 28 | return true; 29 | } 30 | } 31 | 32 | // check the first 10 Fibonacci's numbers 33 | $num = new Fibonacci(); 34 | $correct = array(0, 1, 1, 2, 3, 5, 8, 13, 21, 34); 35 | for ($i = 0; $i < 10; $i++) { 36 | printf ("%s
", $num->current() === $correct[$i] ? 'OK ' : 'ERROR'); 37 | $num->next(); 38 | } 39 | -------------------------------------------------------------------------------- /DependencyInjection/exercise.php: -------------------------------------------------------------------------------- 1 | database->query()); 16 | // } 17 | 18 | // @TODO Implement constructor injection DatabaseSetterConsumer 19 | // with same worker method above 20 | 21 | } 22 | 23 | namespace { 24 | 25 | use MyShop\Database; 26 | use MyShop\DatabaseConstructorConsumer; 27 | use MyShop\DatabaseSetterConsumer; 28 | 29 | // constructor injection 30 | $consumer = new DatabaseConstructorConsumer(new MyShop\Database); 31 | assert($consumer->doSomething() == '1, 2, 3'); 32 | 33 | // setter injection 34 | $consumer = new DatabaseSetterConsumer; 35 | $consumer->setDatabase(new MyShop\Database); 36 | assert($consumer->doSomething() == '1, 2, 3'); 37 | 38 | } -------------------------------------------------------------------------------- /Proxy/exercise.php: -------------------------------------------------------------------------------- 1 | filename = $filename; 18 | $this->loadFromDisk(); 19 | } 20 | protected function loadFromDisk() { 21 | echo "Loading {$this->filename}\n"; 22 | } 23 | public function display() { 24 | echo "Display {$this->filename}\n"; 25 | } 26 | } 27 | 28 | class ProxyImage implements ImageInterface 29 | { 30 | protected $image; 31 | 32 | // @todo here the code to implement 33 | } 34 | 35 | // Usage example 36 | 37 | $filename = 'test.png'; 38 | 39 | $image1 = new Image($filename); // loading necessary 40 | echo $image1->display(); // loading unnecessary 41 | 42 | $image2 = new ProxyImage($filename); // loading unnecessary 43 | echo $image2->display(); // loading necessary 44 | echo $image2->display(); // loading unnecessary 45 | -------------------------------------------------------------------------------- /Mapper/exercise.php: -------------------------------------------------------------------------------- 1 | "test product", 31 | "price" => 50, 32 | "manufacturer_name" => "Widgets, Inc", 33 | "manufacturer_url" => "http://widgets.io" 34 | ]; 35 | 36 | $mapper = new ProductMapper; 37 | 38 | $product = $mapper->toProduct($data); 39 | assert($product->name == "test product"); 40 | assert($product instanceof Product); 41 | assert($product->price == 50); 42 | assert($product->manufacturer instanceof Manufacturer); 43 | assert($product->manufacturer->name == "Widgets, Inc"); 44 | assert($product->manufacturer->url == "http://widgets.io"); 45 | 46 | $mappedData = $mapper->toArray($product); 47 | assert($data === $mappedData); 48 | } -------------------------------------------------------------------------------- /Iterator/solution.php: -------------------------------------------------------------------------------- 1 | value = 0; 15 | $this->key = 0; 16 | } 17 | public function current() { 18 | return $this->value; 19 | } 20 | public function key() { 21 | return $this->key; 22 | } 23 | public function next() { 24 | if ($this->value === 0) { 25 | $this->value = 1; 26 | } else { 27 | $old = $this->value; 28 | $this->value += $this->sum; 29 | $this->sum = $old; 30 | } 31 | $this->key++; 32 | } 33 | 34 | public function valid() { 35 | return true; 36 | } 37 | } 38 | 39 | 40 | // check the first 10 Fibonacci's numbers 41 | $num = new Fibonacci(); 42 | $correct = array(0, 1, 1, 2, 3, 5, 8, 13, 21, 34); 43 | for ($i = 0; $i < 10; $i++) { 44 | printf ("%s
", $num->current() === $correct[$i] ? 'OK ' : 'ERROR'); 45 | $num->next(); 46 | } 47 | -------------------------------------------------------------------------------- /Prototype/README.md: -------------------------------------------------------------------------------- 1 | Prototype 2 | ========= 3 | 4 | The prototype pattern is a creational design pattern. It is used when the type of objects to create is determined by a prototypical instance, which is cloned to produce new objects. 5 | 6 | Definition 7 | ---------- 8 | 9 | This pattern is used to: 10 | 11 | * avoid subclasses of an object creator in the client application, like the abstract factory pattern does. 12 | * avoid the inherent cost of creating a new object in the standard way (e.g., using the 'new' keyword) when it is prohibitively expensive for a given application. 13 | 14 | Prohibitively expensive could mean that the object's creation workflow is too involved or it could mean that computationally "cloning" an existing object is less expensive than using the "new" operator. This is especially useful when you want to deal with sets of objects that have a small amount of variation but might also share a set of dependencies. 15 | 16 | Scenario 17 | -------- 18 | 19 | Similar to the Factory Method, we want to delegate the creation of a particular object type to the consumer. Instead of using a factory, and because we know there will be a large set of these objects the might have wired dependencies, this particular extension point will use prototypical object creation: the prototype pattern. -------------------------------------------------------------------------------- /Proxy/solution.php: -------------------------------------------------------------------------------- 1 | filename = $filename; 18 | $this->loadFromDisk(); 19 | } 20 | protected function loadFromDisk() { 21 | echo "Loading {$this->filename}\n"; 22 | } 23 | public function display() { 24 | echo "Display {$this->filename}\n"; 25 | } 26 | } 27 | 28 | class ProxyImage implements ImageInterface 29 | { 30 | protected $image; 31 | public function __construct($filename) { 32 | $this->filename = $filename; 33 | } 34 | public function display() { 35 | if (null === $this->image) { 36 | $this->image = new Image($this->filename); 37 | } 38 | return $this->image->display(); 39 | } 40 | } 41 | 42 | // Usage example 43 | 44 | $filename = 'test.png'; 45 | 46 | $image1 = new Image($filename); // loading necessary 47 | echo $image1->display(); // loading unnecessary 48 | 49 | $image2 = new ProxyImage($filename); // loading unnecessary 50 | echo $image2->display(); // loading necessary 51 | echo $image2->display(); // loading unnecessary 52 | -------------------------------------------------------------------------------- /DependencyInjection/solution.php: -------------------------------------------------------------------------------- 1 | database = $database; 15 | } 16 | public function doSomething() { 17 | return implode(', ', $this->database->query()); 18 | } 19 | } 20 | 21 | class DatabaseSetterConsumer { 22 | protected $database; 23 | public function setDatabase(Database $database) { 24 | $this->database = $database; 25 | } 26 | public function doSomething() { 27 | return implode(', ', $this->database->query()); 28 | } 29 | } 30 | 31 | } 32 | 33 | namespace { 34 | 35 | use MyShop\Database; 36 | use MyShop\DatabaseConstructorConsumer; 37 | use MyShop\DatabaseSetterConsumer; 38 | 39 | // constructor injection 40 | $consumer = new DatabaseConstructorConsumer(new MyShop\Database); 41 | assert($consumer->doSomething() == '1, 2, 3'); 42 | 43 | // setter injection 44 | $consumer = new DatabaseSetterConsumer; 45 | $consumer->setDatabase(new MyShop\Database); 46 | assert($consumer->doSomething() == '1, 2, 3'); 47 | 48 | } -------------------------------------------------------------------------------- /DependencyInjection/README.md: -------------------------------------------------------------------------------- 1 | Dependency Injection 2 | ==================== 3 | 4 | Dependency injection is a software design pattern that allows the removal of hard-coded dependencies and makes it possible to change them, whether at run-time or compile-time. 5 | 6 | We're actually talking about Inversion of Control. 7 | 8 | Dependency Injection is the concept of introducing one or more dependent objects into a consuming object as to fulfill the consuming objects requirements. 9 | 10 | Definition 11 | ---------- 12 | 13 | In practice, inversion of control / dependency injection is a style of software construction where reusable code controls the execution of problem-specific code. It carries the strong connotation that the reusable code and the problem-specific code are developed independently, which often results in a single integrated application. Inversion of control as a design guideline serves the following purposes: 14 | 15 | * There is a decoupling of the execution of a certain task from implementation 16 | * Every module can focus on what it is designed for 17 | * Modules make no assumptions about what other systems do but rely on their contracts 18 | * Replacing modules have no side effect on other modules 19 | 20 | Hollywood Principle: "Don't call us, we'll call you". 21 | 22 | Scenario 23 | -------- 24 | 25 | You have many objects that need to talk to a database. Instead of opening multiple connections to the database, which is an object, you want to maintain a single instance and single open connection. 26 | -------------------------------------------------------------------------------- /Observer/exercise.php: -------------------------------------------------------------------------------- 1 | data[$key]; 14 | } 15 | 16 | public function __set($key, $value) 17 | { 18 | $this->data[$key] = $value; 19 | } 20 | 21 | public function attach(SplObserver $observer) 22 | { 23 | // TODO: Implement attach() method. 24 | } 25 | 26 | public function detach(SplObserver $observer) 27 | { 28 | // TODO: Implement detach() method. 29 | } 30 | 31 | public function notify() 32 | { 33 | // TODO: Implement notify() method. 34 | } 35 | 36 | 37 | } 38 | 39 | class Logger implements SplObserver 40 | { 41 | private $events = []; 42 | 43 | public function getEvents() 44 | { 45 | return $this->events; 46 | } 47 | 48 | function update(SplSubject $subject) 49 | { 50 | // TODO: Implement update() method. 51 | } 52 | 53 | 54 | } 55 | } 56 | 57 | namespace { 58 | use MyCompanyShop\Product, 59 | MyCompanyShop\Logger; 60 | 61 | $p = new Product; 62 | $l = new Logger; 63 | $p->name = 'Test Product 1'; 64 | $p->attach($l); 65 | 66 | assert(count($l->getEvents()) == 0); 67 | $p->foo = 'bar'; 68 | assert(count($l->getEvents()) == 1); 69 | 70 | } -------------------------------------------------------------------------------- /Prototype/solution.php: -------------------------------------------------------------------------------- 1 | productPrototype = $productPrototype; 8 | } 9 | public function listProducts(array $codes) { 10 | $output = []; 11 | foreach ($codes as $code) { 12 | // @TODO create an actual $product, and initialize it 13 | $output[] = $product->getShopProductCode() . ' - ' . $product->getShopDescription(); 14 | } 15 | return implode(PHP_EOL, $output); 16 | } 17 | } 18 | interface ProductInterface { 19 | public function initialize($code); 20 | public function getShopProductCode(); 21 | public function getShopDescription(); 22 | } 23 | } 24 | 25 | namespace MyCompanyShop { 26 | use ShopingCartFramework\ProductInterface; 27 | // @TODO implement MyShopProduct prototype 28 | 29 | } 30 | 31 | namespace { 32 | use ShopingCartFramework\Shop; 33 | use MyCompanyShop\MyShopProduct; 34 | 35 | $mockWebService = function($code) { 36 | static $data = [ 37 | 'BumperSticker1' => 'Cool bumper sticker', 38 | 'CoffeeTableBook5' => 'Coffee Table book', 39 | ]; 40 | return $data[$code]; 41 | }; 42 | 43 | $shop = new Shop(new MyShopProduct($mockWebService)); 44 | 45 | $productsToList = ['BumperSticker1', 'CoffeeTableBook5']; 46 | 47 | echo $shop->listProducts($productsToList); // simulation of Shopping Cart Listings Page 48 | } -------------------------------------------------------------------------------- /Observer/solution.php: -------------------------------------------------------------------------------- 1 | data[$key]; 15 | } 16 | 17 | public function __set($key, $value) 18 | { 19 | $this->data[$key] = $value; 20 | $this->notify(); 21 | } 22 | 23 | public function attach(SplObserver $observer) 24 | { 25 | $this->observers[] = $observer; 26 | } 27 | 28 | public function detach(SplObserver $observer) 29 | { 30 | $index = array_search($observer, $this->observers); 31 | if (false !== $index) { 32 | unset($this->observers[$index]); 33 | } 34 | } 35 | 36 | public function notify() 37 | { 38 | foreach ($this->observers as $observer) { 39 | $observer->update($this); 40 | } 41 | } 42 | } 43 | 44 | class Logger implements \SplObserver 45 | { 46 | private $events = []; 47 | 48 | public function update(SplSubject $subject) 49 | { 50 | $this->events[] = "subject has changed!"; 51 | } 52 | 53 | public function getEvents() 54 | { 55 | return $this->events; 56 | } 57 | } 58 | } 59 | 60 | namespace { 61 | use MyCompanyShop\Product, 62 | MyCompanyShop\Logger; 63 | 64 | $p = new Product; 65 | $l = new Logger; 66 | $p->name = 'Test Product 1'; 67 | $p->attach($l); 68 | 69 | assert(count($l->getEvents()) == 0); 70 | $p->foo = 'bar'; 71 | assert(count($l->getEvents()) == 1); 72 | 73 | } -------------------------------------------------------------------------------- /Adapter/exercise.php: -------------------------------------------------------------------------------- 1 | email = $email; 12 | $this->password = $password; 13 | } 14 | 15 | public function transfer($email, $amount) 16 | { 17 | return "Paypal Success!"; 18 | } 19 | 20 | } 21 | 22 | class CreditCard 23 | { 24 | private $number; 25 | private $expiration; 26 | 27 | public function __construct($number, $expiration) 28 | { 29 | $this->number = $number; 30 | $this->expiration = $expiration; 31 | } 32 | 33 | public function authorizeTransaction($amount) 34 | { 35 | return "Authorization code: 234da"; 36 | } 37 | } 38 | 39 | interface PaymentAdapterInterface 40 | { 41 | /** 42 | * @param $amount 43 | * @return boolean 44 | */ 45 | public function collectMoney($amount); 46 | } 47 | 48 | class CrediCardAdapter implements PaymentAdapterInterface 49 | { 50 | 51 | } 52 | 53 | class PayPalAdapter implements PaymentAdapterInterface 54 | { 55 | 56 | } 57 | } 58 | 59 | namespace { 60 | use MyCompanyShop\PayPal, 61 | MyCompanyShop\PayPalAdapter, 62 | MyCompanyShop\CrediCardAdapter, 63 | MyCompanyShop\CreditCard; 64 | 65 | $paypal = new PayPal('customer@aol.com', 'password'); 66 | $cc = new CreditCard(1234567890123456, "09/12"); 67 | 68 | $paypalAdapter = new PayPalAdapter($paypal); 69 | $ccAdapter = new CrediCardAdapter($cc); 70 | 71 | assert($paypalAdapter->collectMoney(50)); 72 | assert($ccAdapter->collectMoney(50)); 73 | 74 | } -------------------------------------------------------------------------------- /FactoryMethod/exercise.php: -------------------------------------------------------------------------------- 1 | productFactory = $productFactory; 8 | } 9 | public function listProducts(array $codes) { 10 | $output = []; 11 | foreach ($codes as $code) { 12 | $product = $this->productFactory->createProduct($code); 13 | $output[] = $product->getShopProductCode() . ' - ' . $product->getShopDescription(); 14 | } 15 | return implode(PHP_EOL, $output); 16 | } 17 | } 18 | interface ProductFactoryInterface { 19 | public function createProduct($productCode); 20 | } 21 | interface ProductInterface { 22 | public function getShopProductCode(); 23 | public function getShopDescription(); 24 | } 25 | } 26 | 27 | namespace MyCompanyShop { 28 | use ShopingCartFramework\ProductFactoryInterface; 29 | use ShopingCartFramework\ProductInterface; 30 | 31 | // @TODO implement MyShopProductFactory with internal database of: 32 | // $database = [ 33 | // 'BumperSticker1' => 'Cool bumper sticker', 34 | // 'CoffeeTableBook5' => 'Coffee Table book', 35 | // ]; 36 | 37 | 38 | 39 | // @TODO implement MyShopProduct 40 | 41 | } 42 | 43 | namespace { 44 | use ShopingCartFramework\Shop; 45 | use MyCompanyShop\MyShopProductFactory; 46 | $shop = new Shop(new MyShopProductFactory); 47 | 48 | $productsToList = ['BumperSticker1', 'CoffeeTableBook5']; 49 | 50 | $targetOutput = <<listProducts($productsToList)); 56 | } -------------------------------------------------------------------------------- /AbstractFactory/README.md: -------------------------------------------------------------------------------- 1 | Abstract Factory 2 | ================ 3 | 4 | Definition 5 | ---------- 6 | 7 | The essence of the Abstract Factory Pattern is to "Provide an interface for creating families of related or dependent objects without specifying their concrete classes". 8 | 9 | In Depth 10 | -------- 11 | 12 | This pattern is highly useful when integrating separate systems. On one side, there is a framework that is providing significant value by solving the most common aspects of a problem. On the other side is the framework consumer who wants to build on the framework to fulfill specific use cases. Since the framework can't fully imagine all possible use cases, the framework plans for this ability to extend through abstraction. One such abstraction is an Abstract Factory. 13 | 14 | In this particular pattern, the framework does not have to be burdened with how many specific instances of a particular concrete type are created, it simply abstracts the creation of those concrete types into an interface that the framework consumer can them implement. The end result is framework consumer can fulfill his specific use cases surrounding object/instance creation in his own way, while leveraging the wholesale value added by the framework. 15 | 16 | Scenario 17 | -------- 18 | 19 | From the Framework side perspective: 20 | 21 | You want to build a shopping cart framework, but you don't want to create concrete product model and shipping method objects because you want your consumers to provide both the factory for product & shipping method model objects as well as those speific product and shipping method objects. 22 | 23 | Frome the consumer side perspective: 24 | 25 | You want to be able to build your product and shipping method object model without having to extend a base model provided by a shopping cart system. Ideally, you can develop your own product object model and simply introduce a mechanism into the shopping cart framework that can then consume these product objects. -------------------------------------------------------------------------------- /Mapper/solution.php: -------------------------------------------------------------------------------- 1 | name = $data["name"]; 23 | $p->price = $data["price"]; 24 | $p->manufacturer = new Manufacturer; 25 | $p->manufacturer->name = $data["manufacturer_name"]; 26 | $p->manufacturer->url = $data["manufacturer_url"]; 27 | return $p; 28 | } 29 | 30 | public function toArray(Product $product) 31 | { 32 | $data = []; 33 | $data["price"] = $product->price; 34 | $data["name"] = $product->name; 35 | $data["manufacturer_name"] = $product->manufacturer->name; 36 | $data["manufacturer_url"] = $product->manufacturer->url; 37 | 38 | return $data; 39 | } 40 | } 41 | } 42 | 43 | namespace { 44 | 45 | use MyCompanyShop\Product; 46 | use MyCompanyShop\Manufacturer; 47 | use MyCompanyShop\ProductMapper; 48 | 49 | $data = [ 50 | "name" => "test product", 51 | "price" => 50, 52 | "manufacturer_name" => "Widgets, Inc", 53 | "manufacturer_url" => "http://widgets.io" 54 | ]; 55 | 56 | $mapper = new ProductMapper; 57 | 58 | $product = $mapper->toProduct($data); 59 | assert($product->name == "test product"); 60 | assert($product instanceof Product); 61 | assert($product->price == 50); 62 | assert($product->manufacturer instanceof Manufacturer); 63 | assert($product->manufacturer->name == "Widgets, Inc"); 64 | assert($product->manufacturer->url == "http://widgets.io"); 65 | 66 | $mappedData = $mapper->toArray($product); 67 | assert($data == $mappedData); 68 | } -------------------------------------------------------------------------------- /FactoryMethod/README.md: -------------------------------------------------------------------------------- 1 | Factory Method 2 | ============== 3 | 4 | Definition 5 | ---------- 6 | 7 | The essence of the Factory Method Pattern is to "implement the concept of factories and deals with the problem of creating objects (products) without specifying the exact class of object that will be created." 8 | 9 | In Depth 10 | -------- 11 | 12 | This pattern is highly useful in situations where creating an actual concrete object is complex/expensive or the target concrete type is unknown to the caller. 13 | 14 | when integrating separate systems. On one side, there is a framework that is providing significant value by solving the most common aspects of a problem. On the other side is the framework consumer who wants to build on the framework to fulfill specific use cases. Since the framework can't fully imagine all possible use cases, the framework plans for this ability to extend through abstraction. One such abstraction for object creation is a Factory Method. 15 | 16 | In this particular pattern, the framework does not have to be burdened with how specific instances of a particular concrete type are created, it simply abstracts the creation into an interface that the framework consumer can them implement. The end result is framework consumer can fulfill his specific use cases surrounding object/instance creation in his own way, while leveraging the wholesale value added by the framework. 17 | 18 | Scenario 19 | -------- 20 | 21 | From the Framework side perspective: 22 | 23 | You want to build a shopping cart framework, but you don't want to create concrete product model objects because you want your consumers to provide both the factory for product model objects as well as those speific product model objects. 24 | 25 | Frome the consumer side perspective: 26 | 27 | You want to be able to build your product object model without having to extend a base product model provided by a shopping cart system. Ideally, you can develop your own product object model and simply introduce a mechanism into the shopping cart framework that can then consume these product objects. -------------------------------------------------------------------------------- /Strategy/exercise.php: -------------------------------------------------------------------------------- 1 | products = $products; 16 | } 17 | 18 | /** 19 | * @param ProductFilteringStrategy $filterStrategy 20 | * @return ProductCollection 21 | */ 22 | public function filter(ProductFilteringStrategy $filterStrategy) { 23 | $filteredProducts = array(); 24 | //@TODO use the strategy to filter products that don't meet criteria 25 | return new ProductCollection($filteredProducts); 26 | } 27 | 28 | public function getProductsArray() { 29 | return $this->products; 30 | } 31 | } 32 | 33 | interface ProductFilteringStrategy { 34 | /** 35 | * @param Product $product 36 | * @return true|false 37 | */ 38 | public function filter(Product $product); 39 | } 40 | 41 | //@TODO implement a strategy for filtering products by maximum price 42 | //@TODO implement a strategy for filtering products by manufacturer 43 | 44 | } 45 | 46 | namespace { 47 | 48 | use MyCompanyShop\Product; 49 | use MyCompanyShop\ProductCollection; 50 | 51 | $p1 = new Product; 52 | $p1->listPrice = 100; 53 | $p1->sellingPrice = 50; 54 | $p1->manufacturer = 'WidgetCorp'; 55 | 56 | $p2 = new Product; 57 | $p2->listPrice = 100; 58 | $p2->manufacturer = 'Widgetron, LLC'; 59 | 60 | $collection = new ProductCollection([$p1, $p2]); 61 | 62 | $resultCollection = $collection->filter(new ManufacturerFilter('Widgetron, LLC')); 63 | 64 | assert(count($resultCollection->getProductsArray()) == 1); 65 | assert($resultCollection->getProductsArray()[0]->manufacturer == 'Widgetron, LLC'); 66 | 67 | 68 | $resultCollection = $collection->filter(new MaxPriceFilter(50)); 69 | 70 | assert(count($resultCollection->getProductsArray()) == 1); 71 | assert($resultCollection->getProductsArray()[0]->manufacturer == 'WidgetCorp'); 72 | 73 | } 74 | -------------------------------------------------------------------------------- /Decorator/exercise.php: -------------------------------------------------------------------------------- 1 | name = $name; 20 | } 21 | public function getName() { 22 | return $this->name; 23 | } 24 | public function __toString() { 25 | return "name}\" name=\"{$this->name}\" />\n"; 26 | } 27 | } 28 | 29 | abstract class HtmlDecorator implements HtmlElement 30 | { 31 | protected $element; 32 | 33 | // @todo here the code to implement 34 | } 35 | 36 | class LabelDecorator extends HtmlDecorator 37 | { 38 | protected $label; 39 | public function setLabel($label) { 40 | $this->label = $label; 41 | } 42 | public function __toString() { 43 | $name = $this->getName(); 44 | return "\n" 45 | . $this->element->__toString(); 46 | } 47 | } 48 | 49 | class ErrorDecorator extends HtmlDecorator 50 | { 51 | protected $error; 52 | public function setError($message) { 53 | $this->error = $message; 54 | } 55 | public function __toString() { 56 | return $this->element->__toString() . "{$this->error}\n"; 57 | } 58 | } 59 | 60 | $input = new InputText('nickname'); 61 | printf("InputText without decorator:
%s
", $input); 62 | 63 | $labelled = new LabelDecorator($input); 64 | $labelled->setLabel('Nickname:'); 65 | printf("InputText with LabelDecorator:
%s
", $labelled); 66 | 67 | $error = new ErrorDecorator($input); 68 | $error->setError('You must enter a unique nickname'); 69 | printf("InputText with ErrorDecorator:
%s
\n", $error); 70 | 71 | // Label + Error 72 | $error = new ErrorDecorator($labelled); 73 | $error->setError('You must enter a unique nickname'); 74 | printf("InputText with LabelDecorator and ErrorDecorator:
%s
", $error); 75 | -------------------------------------------------------------------------------- /Prototype/exercise.php: -------------------------------------------------------------------------------- 1 | productPrototype = $productPrototype; 8 | } 9 | public function listProducts(array $codes) { 10 | $output = []; 11 | foreach ($codes as $code) { 12 | $product = clone $this->productPrototype; 13 | $product->initialize($code); 14 | $output[] = $product->getShopProductCode() . ' - ' . $product->getShopDescription(); 15 | } 16 | return implode(PHP_EOL, $output); 17 | } 18 | } 19 | interface ProductInterface { 20 | public function initialize($code); 21 | public function getShopProductCode(); 22 | public function getShopDescription(); 23 | } 24 | } 25 | 26 | namespace MyCompanyShop { 27 | use ShopingCartFramework\ProductInterface; 28 | class MyShopProduct implements ProductInterface { 29 | protected $productService = array(); 30 | protected $code, $description; 31 | public function __construct(\Closure $productService) { 32 | $this->productService = $productService; 33 | } 34 | public function initialize($code) { 35 | $this->code = $code; 36 | $this->description = call_user_func($this->productService, $code); 37 | } 38 | public function getShopProductCode() { 39 | return $this->code; 40 | } 41 | public function getShopDescription() { 42 | return $this->description; 43 | } 44 | } 45 | 46 | } 47 | 48 | namespace { 49 | use ShopingCartFramework\Shop; 50 | use MyCompanyShop\MyShopProduct; 51 | 52 | $mockWebService = function($code) { 53 | static $data = [ 54 | 'BumperSticker1' => 'Cool bumper sticker', 55 | 'CoffeeTableBook5' => 'Coffee Table book', 56 | ]; 57 | return $data[$code]; 58 | }; 59 | 60 | $shop = new Shop(new MyShopProduct($mockWebService)); 61 | 62 | $productsToList = ['BumperSticker1', 'CoffeeTableBook5']; 63 | header('content-type: plain/text'); 64 | echo $shop->listProducts($productsToList); // simulation of Shopping Cart Listings Page 65 | } -------------------------------------------------------------------------------- /FactoryMethod/solution.php: -------------------------------------------------------------------------------- 1 | productFactory = $productFactory; 8 | } 9 | public function listProducts(array $codes) { 10 | $output = []; 11 | foreach ($codes as $code) { 12 | $product = $this->productFactory->createProduct($code); 13 | $output[] = $product->getShopProductCode() . ' - ' . $product->getShopDescription(); 14 | } 15 | return implode(PHP_EOL, $output); 16 | } 17 | } 18 | interface ProductFactoryInterface { 19 | public function createProduct($productCode); 20 | } 21 | interface ProductInterface { 22 | public function getShopProductCode(); 23 | public function getShopDescription(); 24 | } 25 | } 26 | 27 | namespace MyCompanyShop { 28 | use ShopingCartFramework\ProductFactoryInterface; 29 | use ShopingCartFramework\ProductInterface; 30 | class MyShopProductFactory implements ProductFactoryInterface { 31 | public function createProduct($productCode) { 32 | $database = [ 33 | 'BumperSticker1' => 'Cool bumper sticker', 34 | 'CoffeeTableBook5' => 'Coffee Table book', 35 | ]; 36 | return new MyShopProduct( 37 | $productCode, $database[$productCode] 38 | ); 39 | } 40 | } 41 | class MyShopProduct implements ProductInterface { 42 | protected $code, $description; 43 | public function __construct($code, $description) { 44 | $this->code = $code; 45 | $this->description = $description; 46 | } 47 | public function getShopProductCode() { 48 | return $this->code; 49 | } 50 | public function getShopDescription() { 51 | return $this->description; 52 | } 53 | } 54 | 55 | } 56 | 57 | namespace { 58 | use ShopingCartFramework\Shop; 59 | use MyCompanyShop\MyShopProductFactory; 60 | $shop = new Shop(new MyShopProductFactory); 61 | 62 | $productsToList = ['BumperSticker1', 'CoffeeTableBook5']; 63 | header('content-type: plain/text'); 64 | echo $shop->listProducts($productsToList); // simulation of Shopping Cart Listings Page 65 | } -------------------------------------------------------------------------------- /Decorator/solution.php: -------------------------------------------------------------------------------- 1 | name = $name; 20 | } 21 | public function getName() { 22 | return $this->name; 23 | } 24 | public function __toString() { 25 | return "name}\" name=\"{$this->name}\" />\n"; 26 | } 27 | } 28 | 29 | abstract class HtmlDecorator implements HtmlElement 30 | { 31 | protected $element; 32 | public function __construct(HtmlElement $input) { 33 | $this->element = $input; 34 | } 35 | public function getName() { 36 | return $this->element->getName(); 37 | } 38 | public function __toString() { 39 | return $this->element->__toString(); 40 | } 41 | } 42 | 43 | class LabelDecorator extends HtmlDecorator 44 | { 45 | protected $label; 46 | public function setLabel($label) { 47 | $this->label = $label; 48 | } 49 | public function __toString() { 50 | $name = $this->getName(); 51 | return "\n" 52 | . $this->element->__toString(); 53 | } 54 | } 55 | 56 | class ErrorDecorator extends HtmlDecorator 57 | { 58 | protected $error; 59 | public function setError($message) { 60 | $this->error = $message; 61 | } 62 | public function __toString() { 63 | return $this->element->__toString() . "{$this->error}\n"; 64 | } 65 | } 66 | 67 | $input = new InputText('nickname'); 68 | printf("InputText without decorator:
%s
", $input); 69 | 70 | $labelled = new LabelDecorator($input); 71 | $labelled->setLabel('Nickname:'); 72 | printf("InputText with LabelDecorator:
%s
", $labelled); 73 | 74 | $error = new ErrorDecorator($input); 75 | $error->setError('You must enter a unique nickname'); 76 | printf("InputText with ErrorDecorator:
%s
\n", $error); 77 | 78 | // Label + Error 79 | $error = new ErrorDecorator($labelled); 80 | $error->setError('You must enter a unique nickname'); 81 | printf("InputText with LabelDecorator and ErrorDecorator:
%s
", $error); 82 | -------------------------------------------------------------------------------- /Visitor/exercise.php: -------------------------------------------------------------------------------- 1 | set($value); 14 | } 15 | 16 | public function set($value) 17 | { 18 | $this->_value = $value; 19 | } 20 | 21 | public function get() 22 | { 23 | return $this->_value; 24 | } 25 | 26 | public abstract function acceptVisitor(Visitor $visitor); 27 | } 28 | 29 | /** 30 | * A ConcreteElement. Accepts a Visitor and forwards to it on its specialized method. 31 | */ 32 | class SingleInputValue extends InputValue 33 | { 34 | public function acceptVisitor(Visitor $visitor) 35 | { 36 | $visitor->visitSingleInputValue($this); 37 | } 38 | } 39 | 40 | /** 41 | * Another ConcreteElement. 42 | */ 43 | class MultipleInputValue extends InputValue 44 | { 45 | public function acceptVisitor(Visitor $visitor) 46 | { 47 | $visitor->visitMultipleInputValue($this); 48 | } 49 | } 50 | 51 | /** 52 | * The Visitor participant. Again, interface or abstract classes are equivalent. 53 | */ 54 | interface Visitor 55 | { 56 | /** 57 | * Since in PHP there is no simple mechanism for method overloading, 58 | * the visit() methods must not only have different parameters 59 | * but different names too. 60 | */ 61 | public function visitSingleInputValue(SingleInputValue $inputValue); 62 | public function visitMultipleInputValue(MultipleInputValue $inputValue); 63 | } 64 | 65 | /** 66 | * A ConcreteVisitor. 67 | * Filters all the values provided casting them to integers. 68 | */ 69 | class IntFilter implements Visitor 70 | { 71 | // @todo here the code to implement 72 | } 73 | 74 | /** 75 | * Another ConcreteVisitor. 76 | * Sorts multiple values. 77 | */ 78 | class AscendingSort implements Visitor 79 | { 80 | // @todo here the code to implement 81 | } 82 | 83 | $userId = new SingleInputValue("42"); 84 | $categories = new MultipleInputValue(array('hated' => 16, 'ordinary' => 23, 'preferred' => 15)); 85 | $userId->acceptVisitor(new IntFilter); 86 | printf("%s
", is_int($userId->get()) ? "OK" : "ERROR"); 87 | $categories->acceptVisitor(new AscendingSort); 88 | $result = array_values($categories->get()); 89 | if ($result === array(15, 16, 23)) { 90 | echo "OK"; 91 | } else { 92 | echo "ERROR"; 93 | } 94 | 95 | -------------------------------------------------------------------------------- /Adapter/solution.php: -------------------------------------------------------------------------------- 1 | email = $email; 12 | $this->password = $password; 13 | } 14 | 15 | public function transfer($email, $amount) 16 | { 17 | return "Paypal Success!"; 18 | } 19 | 20 | } 21 | 22 | class CreditCard 23 | { 24 | private $number; 25 | private $expiration; 26 | 27 | public function __construct($number, $expiration) 28 | { 29 | $this->number = $number; 30 | $this->expiration = $expiration; 31 | } 32 | 33 | public function authorizeTransaction($amount) 34 | { 35 | return "Authorization code: 234da"; 36 | } 37 | } 38 | 39 | interface PaymentAdapterInterface 40 | { 41 | /** 42 | * @param $amount 43 | * @return boolean 44 | */ 45 | public function collectMoney($amount); 46 | } 47 | 48 | class CrediCardAdapter implements PaymentAdapterInterface 49 | { 50 | private $creditCard; 51 | 52 | public function __construct($creditCard) 53 | { 54 | $this->creditCard = $creditCard; 55 | } 56 | 57 | public function collectMoney($amount) 58 | { 59 | $result = $this->creditCard->authorizeTransaction($amount); 60 | return strpos($result, 'Authorization') === 0; 61 | } 62 | } 63 | 64 | class PayPalAdapter implements PaymentAdapterInterface 65 | { 66 | private $paypal; 67 | 68 | public function __construct($paypal) 69 | { 70 | $this->paypal = $paypal; 71 | } 72 | 73 | public function collectMoney($amount) 74 | { 75 | $result = $this->paypal->transfer('payments@myshop.com', $amount); 76 | return ($result === 'Paypal Success!'); 77 | } 78 | } 79 | } 80 | 81 | namespace { 82 | use MyCompanyShop\PayPal, 83 | MyCompanyShop\PayPalAdapter, 84 | MyCompanyShop\CrediCardAdapter, 85 | MyCompanyShop\CreditCard; 86 | 87 | $paypal = new PayPal('customer@aol.com', 'password'); 88 | $cc = new CreditCard(1234567890123456, "09/12"); 89 | 90 | $paypalAdapter = new PayPalAdapter($paypal); 91 | $ccAdapter = new CrediCardAdapter($cc); 92 | 93 | assert($paypalAdapter->collectMoney(50)); 94 | assert($ccAdapter->collectMoney(50)); 95 | 96 | } -------------------------------------------------------------------------------- /AbstractFactory/exercise.php: -------------------------------------------------------------------------------- 1 | shopFactory = $shopFactory; 8 | } 9 | public function listProductsWithShippingCost(array $codes, $shippingMethodName, $miles) { 10 | $output = array(); 11 | foreach ($codes as $code) { 12 | $product = $this->shopFactory->createProduct($code); 13 | $shippingMethod = $this->shopFactory->createShippingMethod($shippingMethodName); 14 | $output[] = $product->getShopProductCode() . ' - ' 15 | . $product->getShopDescription() . ' / via: ' 16 | . $shippingMethod->getName() . ', cost: $' 17 | . $shippingMethod->getCostEstimate($miles, $product); 18 | } 19 | return implode(PHP_EOL, $output); 20 | } 21 | } 22 | interface ShopFactoryInterface { 23 | public function createProduct($productCode); 24 | public function createShippingMethod($name); 25 | } 26 | interface ProductInterface { 27 | public function getShopProductCode(); 28 | public function getShopDescription(); 29 | } 30 | interface ShippingMethodInterface { 31 | public function getName(); 32 | public function getCostEstimate($miles, ProductInterface $product); 33 | } 34 | } 35 | 36 | namespace MyCompanyShop { 37 | 38 | use ShopingCartFramework\ShopFactoryInterface; 39 | use ShopingCartFramework\ProductInterface; 40 | use ShopingCartFramework\ShippingMethodInterface; 41 | 42 | // @TODO Implement the abstract factory MyShopProductFactory 43 | 44 | // @TODO Implement the product MyShopProduct 45 | 46 | // @TODO Implement Shipping Method MyShippingMethod 47 | 48 | 49 | } 50 | 51 | namespace { 52 | use ShopingCartFramework\Shop; 53 | use MyCompanyShop\MyShopProductFactory; 54 | 55 | $productData = [ 56 | // id => [name, weight] 57 | 'BumperSticker1' => ['Bumper Sticker Item #1', 1], 58 | 'BumperSticker3' => ['Bumper Sticker Item #3', 1], 59 | 'BumperSticker5' => ['Bumper Sticker Item #5', 1], 60 | 'CoffeeTableBook6' => ['Coffee Table Book Item #6 (500 pages)', 5], 61 | 'CoffeeTableBook7' => ['Coffee Table Book Item #7 (300 pages)', 3], 62 | 'CoffeeTableBook9' => ['Coffee Table Book Item #9 (900 pages)', 9], 63 | ]; 64 | 65 | $shippingMethodData = [ 66 | // code => [miles, weight] 67 | 'UPS' => [1.4, 1.1], 68 | 'FEXED' => [2.2, 1.3], 69 | ]; 70 | 71 | $shop = new Shop(new MyShopProductFactory($productData, $shippingMethodData)); 72 | 73 | $targetOutput = <<listProductsWithShippingCost(['BumperSticker1', 'CoffeeTableBook6'], 'UPS', 10); 81 | 82 | assert($actualOutput == $targetOutput); 83 | } -------------------------------------------------------------------------------- /Visitor/solution.php: -------------------------------------------------------------------------------- 1 | set($value); 14 | } 15 | 16 | public function set($value) 17 | { 18 | $this->_value = $value; 19 | } 20 | 21 | public function get() 22 | { 23 | return $this->_value; 24 | } 25 | 26 | public abstract function acceptVisitor(Visitor $visitor); 27 | } 28 | 29 | /** 30 | * A ConcreteElement. Accepts a Visitor and forwards to it on its specialized method. 31 | */ 32 | class SingleInputValue extends InputValue 33 | { 34 | public function acceptVisitor(Visitor $visitor) 35 | { 36 | $visitor->visitSingleInputValue($this); 37 | } 38 | } 39 | 40 | /** 41 | * Another ConcreteElement. 42 | */ 43 | class MultipleInputValue extends InputValue 44 | { 45 | public function acceptVisitor(Visitor $visitor) 46 | { 47 | $visitor->visitMultipleInputValue($this); 48 | } 49 | } 50 | 51 | /** 52 | * The Visitor participant. Again, interface or abstract classes are equivalent. 53 | */ 54 | interface Visitor 55 | { 56 | /** 57 | * Since in PHP there is no simple mechanism for method overloading, 58 | * the visit() methods must not only have different parameters 59 | * but different names too. 60 | */ 61 | public function visitSingleInputValue(SingleInputValue $inputValue); 62 | public function visitMultipleInputValue(MultipleInputValue $inputValue); 63 | } 64 | 65 | /** 66 | * A ConcreteVisitor. 67 | * Filters all the values provided casting them to integers. 68 | */ 69 | class IntFilter implements Visitor 70 | { 71 | public function visitSingleInputValue(SingleInputValue $inputValue) 72 | { 73 | $inputValue->set((int) $inputValue->get()); 74 | } 75 | 76 | public function visitMultipleInputValue(MultipleInputValue $inputValue) 77 | { 78 | $newValues = array(); 79 | foreach ($inputValue->get() as $index => $value) { 80 | $newValues[$index] = (int) $value; 81 | } 82 | $inputValue->set($newValues); 83 | } 84 | } 85 | 86 | /** 87 | * Another ConcreteVisitor. 88 | * Sorts multiple values. 89 | */ 90 | class AscendingSort implements Visitor 91 | { 92 | /** 93 | * Do nothing. This part is equivalent to a Null Object, 94 | * which leverages polymorphism to achieve more concise code. 95 | */ 96 | public function visitSingleInputValue(SingleInputValue $inputValue) 97 | { 98 | } 99 | 100 | public function visitMultipleInputValue(MultipleInputValue $inputValue) 101 | { 102 | $values = $inputValue->get(); 103 | asort($values); 104 | $inputValue->set($values); 105 | } 106 | } 107 | 108 | $userId = new SingleInputValue("42"); 109 | $categories = new MultipleInputValue(array('hated' => 16, 'ordinary' => 23, 'preferred' => 15)); 110 | $userId->acceptVisitor(new IntFilter); 111 | printf("%s
", is_int($userId->get()) ? "OK" : "ERROR"); 112 | $categories->acceptVisitor(new AscendingSort); 113 | $result = array_values($categories->get()); 114 | if ($result === array(15, 16, 23)) { 115 | echo "OK"; 116 | } else { 117 | echo "ERROR"; 118 | } 119 | 120 | -------------------------------------------------------------------------------- /Strategy/solution.php: -------------------------------------------------------------------------------- 1 | products = $products; 16 | } 17 | 18 | /** 19 | * @param ProductFilteringStrategy $filterStrategy 20 | * @return ProductCollection 21 | */ 22 | public function filter(ProductFilteringStrategy $filterStrategy) { 23 | $filteredProducts = array(); 24 | foreach ($this->products as $product) { 25 | if ($filterStrategy->filter($product)) { 26 | $filteredProducts[] = $product; 27 | } 28 | } 29 | return new ProductCollection($filteredProducts); 30 | } 31 | 32 | public function getProductsArray() { 33 | return $this->products; 34 | } 35 | } 36 | 37 | interface ProductFilteringStrategy { 38 | /** 39 | * @param Product $product 40 | * @return boolean 41 | */ 42 | public function filter(Product $product); 43 | } 44 | 45 | class ManufacturerFilter implements ProductFilteringStrategy { 46 | 47 | private $manufacturerName; 48 | 49 | public function __construct($manufacturerName) { 50 | $this->manufacturerName = $manufacturerName; 51 | } 52 | 53 | public function filter(Product $product) { 54 | if ($product->manufacturer == $this->manufacturerName) { 55 | return true; 56 | } 57 | return false; 58 | } 59 | } 60 | 61 | class MaxPriceFilter implements ProductFilteringStrategy { 62 | private $maxPrice; 63 | 64 | public function __construct($maxPrice) { 65 | $this->maxPrice = $maxPrice; 66 | } 67 | 68 | public function filter(Product $product) { 69 | if ($product->listPrice && $product->sellingPrice) { 70 | return ($product->listPrice - $product->sellingPrice) <= $this->maxPrice; 71 | } else if ($product->listPrice) { 72 | return ($product->listPrice <= $this->maxPrice); 73 | } 74 | return false; 75 | } 76 | } 77 | } 78 | 79 | namespace { 80 | 81 | use MyCompanyShop\Product; 82 | use MyCompanyShop\ProductCollection; 83 | use MyCompanyShop\MaxPriceFilter; 84 | use MyCompanyShop\ManufacturerFilter; 85 | 86 | $p1 = new Product; 87 | $p1->listPrice = 100; 88 | $p1->sellingPrice = 50; 89 | $p1->manufacturer = 'WidgetCorp'; 90 | 91 | $p2 = new Product; 92 | $p2->listPrice = 100; 93 | $p2->manufacturer = 'Widgetron, LLC'; 94 | 95 | $collection = new ProductCollection([$p1, $p2]); 96 | 97 | $resultCollection = $collection->filter(new ManufacturerFilter('Widgetron, LLC')); 98 | 99 | assert(count($resultCollection->getProductsArray()) == 1); 100 | assert($resultCollection->getProductsArray()[0]->manufacturer == 'Widgetron, LLC'); 101 | 102 | 103 | $resultCollection = $collection->filter(new MaxPriceFilter(50)); 104 | 105 | assert(count($resultCollection->getProductsArray()) == 1); 106 | assert($resultCollection->getProductsArray()[0]->manufacturer == 'WidgetCorp'); 107 | 108 | } -------------------------------------------------------------------------------- /AbstractFactory/solution.php: -------------------------------------------------------------------------------- 1 | shopFactory = $shopFactory; 8 | } 9 | public function listProductsWithShippingCost(array $codes, $shippingMethodName, $miles) { 10 | $output = array(); 11 | foreach ($codes as $code) { 12 | $product = $this->shopFactory->createProduct($code); 13 | $shippingMethod = $this->shopFactory->createShippingMethod($shippingMethodName); 14 | $output[] = $product->getShopProductCode() . ' - ' 15 | . $product->getShopDescription() . ' / via: ' 16 | . $shippingMethod->getName() . ', cost: $' 17 | . $shippingMethod->getCostEstimate($miles, $product); 18 | } 19 | return implode(PHP_EOL, $output); 20 | } 21 | } 22 | interface ShopFactoryInterface { 23 | public function createProduct($productCode); 24 | public function createShippingMethod($name); 25 | } 26 | interface ProductInterface { 27 | public function getShopProductCode(); 28 | public function getShopDescription(); 29 | } 30 | interface ShippingMethodInterface { 31 | public function getName(); 32 | public function getCostEstimate($miles, ProductInterface $product); 33 | } 34 | } 35 | 36 | namespace MyCompanyShop { 37 | 38 | use ShopingCartFramework\ShopFactoryInterface; 39 | use ShopingCartFramework\ProductInterface; 40 | use ShopingCartFramework\ShippingMethodInterface; 41 | 42 | class MyShopProductFactory implements ShopFactoryInterface { 43 | protected $productData; 44 | public function __construct($productData, $shippingMethodData) { 45 | $this->productData = $productData; 46 | $this->shippingMethodData = $shippingMethodData; 47 | } 48 | public function createProduct($productCode) { 49 | return new MyShopProduct( 50 | $productCode, $this->productData[$productCode] 51 | ); 52 | } 53 | public function createShippingMethod($name) { 54 | return new MyShippingMethod($name, $this->shippingMethodData[$name]); 55 | } 56 | } 57 | 58 | class MyShopProduct implements ProductInterface { 59 | protected $code, $descriptionAndWeight; 60 | public function __construct($code, $descriptionAndWeight) { 61 | $this->code = $code; 62 | $this->descriptionAndWeight = $descriptionAndWeight; 63 | } 64 | public function getShopProductCode() { 65 | return $this->code; 66 | } 67 | public function getShopDescription() { 68 | return $this->descriptionAndWeight[0]; 69 | } 70 | public function getWeight() { 71 | return $this->descriptionAndWeight[1]; 72 | } 73 | } 74 | 75 | class MyShippingMethod implements ShippingMethodInterface { 76 | protected $name, $multipliers; 77 | public function __construct($name, $multipliers) { 78 | $this->name = $name; 79 | $this->multipliers = $multipliers; 80 | } 81 | public function getName() { 82 | return $this->name; 83 | } 84 | public function getCostEstimate($miles, ProductInterface $product) { 85 | return ($this->multipliers[0] * $miles) + ($this->multipliers[1] * $product->getWeight()); 86 | } 87 | } 88 | 89 | } 90 | 91 | namespace { 92 | use ShopingCartFramework\Shop; 93 | use MyCompanyShop\MyShopProductFactory; 94 | 95 | $productData = [ 96 | // id => [name, weight] 97 | 'BumperSticker1' => ['Bumper Sticker Item #1', 1], 98 | 'BumperSticker3' => ['Bumper Sticker Item #3', 1], 99 | 'BumperSticker5' => ['Bumper Sticker Item #5', 1], 100 | 'CoffeeTableBook6' => ['Coffee Table Book Item #6 (500 pages)', 5], 101 | 'CoffeeTableBook7' => ['Coffee Table Book Item #7 (300 pages)', 3], 102 | 'CoffeeTableBook9' => ['Coffee Table Book Item #9 (900 pages)', 9], 103 | ]; 104 | 105 | $shippingMethodData = [ 106 | // code => [miles, weight] 107 | 'UPS' => [1.4, 1.1], 108 | 'FEXED' => [2.2, 1.3], 109 | ]; 110 | 111 | $shop = new Shop(new MyShopProductFactory($productData, $shippingMethodData)); 112 | 113 | $targetOutput = <<listProductsWithShippingCost(['BumperSticker1', 'CoffeeTableBook6'], 'UPS', 10); 121 | 122 | assert($actualOutput == $targetOutput); 123 | } --------------------------------------------------------------------------------