├── .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 | }
--------------------------------------------------------------------------------