Slide 1

Slide 1 text

Introduction to design patterns with PHP

Slide 2

Slide 2 text

Who are we? Hugo Hamon Julien Pauli

Slide 3

Slide 3 text

Object Oriented Programming in PHP...

Slide 4

Slide 4 text

Overview of OOP in PHP Objects and classes Abstract and final classes Interfaces Abstract & final Magic methods Garbage collector Object cloning Single inheritance SPL and more… Encapsulation Overloading (a bit...) Classes autoloading Type hinting Reflection Namespaces Traits

Slide 5

Slide 5 text

Object Oriented Design Best Practices

Slide 6

Slide 6 text

SOLID Principle Single Responsibility Principle Open Close Principle Liskov Substitution Principle Interface Segregation Principle Dependency Injection Principle

Slide 7

Slide 7 text

Single Responsability (SRP) « A class should have one, and only one, reason to change. » Keep your classes small and focused on small responsabilities.

Slide 8

Slide 8 text

class CsvDataImporter { public function import($file) { $records = array(); if (false !== $handle = fopen($file, 'r')) { while ($record = fgetcsv($handle)) { $records[] = $record; } } fclose($handle); try { $this->db->beginTransaction(); foreach ($records as $record) { $stmt = $this->db->prepare('INSERT INTO ...'); $stmt->execute($record); } $this->db->commit(); } catch (PDOException $e) { $this->db->rollback(); throw $e; } } }

Slide 9

Slide 9 text

class DataImporter { private $loader; private $table; function __construct(FileLoader $loader, TableGateway $table) { $this->loader = $loader; $this->table = $table; } public function import($file) { $records = $this->loader->load($file); return $this->table->insert($records); } }

Slide 10

Slide 10 text

Open Close (OCP) Software entities should be open to extension and close to modifications. « You should be able to extend a classe’s behavior, without modifying it. »

Slide 11

Slide 11 text

Open Close $loader = new CsvFileLoader(); $loader = new XmlFileLoader(); $loader = new JsonFileLoader(); $importer = new DataImporter($loader, ...); $importer->import('foo.csv|xml|json'); Using a different file loader is now very easy without changing the importer class.

Slide 12

Slide 12 text

Liskov Substitution A dependency of type T should be replaced by another subtype S of T without breaking the code. « Derived classes must be substitutable for their base classes. »

Slide 13

Slide 13 text

Liskov Substitution class TextFileLoader implements FileLoader { public function load($file) { // ... return [ ... ]; } } class CsvFileLoader extends TextFileLoader { public function load($file) { // ... return [ ... ]; } }

Slide 14

Slide 14 text

Interface Segregation (ISP) Interface segregation split larger interfaces into smaller ones so that clients will only know about the methods they need to work with. « Make fine grained interfaces that are client specific. »

Slide 15

Slide 15 text

Interface Segregation interface FileLoader { public function load($file); public function supports($type); } interface TableGateway { public function insert(array $records); }

Slide 16

Slide 16 text

Interface Segregation class DataImporter { public function __construct(FileLoader $loader, TableGateway $table) { // ... } public function import($file) { $ext = pathinfo($file, PATHINFO_EXTENSION); if (!$this->loader->supports($ext)) { throw new UnsupportedDataFile(); } $records = $this->loader->load($file); return $this->table->insert($records); } }

Slide 17

Slide 17 text

Dependency Injection (DIP) D.I is where components are given their dependencies through their constructors, methods, or directly into fields. « Depend on abstractions, not on concretions. »

Slide 18

Slide 18 text

DI – The Wrong Way class DataImporter { private $loader; private $table; public function __construct() { $this->loader = new CsvFileLoader(); $this->table = new TableGateway(); } }

Slide 19

Slide 19 text

DI – The Right Way class DataImporter { private $loader; private $table; function __construct(FileLoader $loader, TableGateway $table) { $this->loader = $loader; $this->table = $table; } }

Slide 20

Slide 20 text

DI – Injecting dependencies $importer = new DataImporter( new CsvFileLoader(), new TableGateway('data', new PDO(…)) ); $importer->import('foo1.csv'); $importer->import('foo2.csv'); $importer->import('foo3.csv');

Slide 21

Slide 21 text

Discovering Design Patterns

Slide 22

Slide 22 text

Definition In software design, a design pattern is an abstract generic solution to solve a particular redundant problem.

Slide 23

Slide 23 text

Communication

Slide 24

Slide 24 text

Unit testability

Slide 25

Slide 25 text

Design patterns families Abstract Factory Builder Factory Method Lazy Initialization Prototype Singleton Adapter Bridge Composite Decorator Facade Flyweight Proxy Chain of Responsability Command Interpreter Iterator Mediator Memento Observer State Strategy Template Method Visitor Creational Structural Behavioral

Slide 26

Slide 26 text

Design Patterns in Practice with PHP

Slide 27

Slide 27 text

Disclaimer Patterns are not the holy grail!

Slide 28

Slide 28 text

The Context A web store wants to sell hard and digital products. Shipment fees are calculated based on the product caracteristics (volume, mass, type…) Products are shipped to customers.

Slide 29

Slide 29 text

Composite http://www.flickr.com/photos/xtinalamb/59989462

Slide 30

Slide 30 text

Composite Composite lets clients treat individual objects and compositions of objects uniformly.

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

Common implementations 2. Treating XML nodes. 1. Representing categories trees. 3. Dealing with embedded forms.

Slide 33

Slide 33 text

input[type=text] textarea input[type=text] input[type=file] Form Form

Slide 34

Slide 34 text

Concrete Example My web store must be able to sell single items or grouped items.

Slide 35

Slide 35 text

Product! getPrice()! HardProduct! getPrice()! Bundle! add(Product $product)! getPrice()! getMass()! getVolume()!

Slide 36

Slide 36 text

abstract class Product { protected $name; protected $price; public function __construct($name, Money $price) { $this->name = $name; $this->price = $price; } abstract public function getMass(); abstract public function getVolume(); // ... a bunch of getters }

Slide 37

Slide 37 text

class HardProduct extends Product { // ... private $mass; private $volume; public function setPhysics(Mass $mass, Volume $volume) { $this->mass = $mass; $this->volume = $volume; } public function getMass() { return $this->mass; } public function getVolume() { return $this->volume; } }

Slide 38

Slide 38 text

class DigitalProduct extends Product { public function getMass() { return new Mass(0); } public function getVolume() { return new Volume(0); } }

Slide 39

Slide 39 text

class Bundle extends Product { protected $products; public function __construct($name) { $this->name = $name; } public function setPrice(Money $price) { $this->price = $price; } public function add(ProductInterface $product) { $this->products[] = $product; } }

Slide 40

Slide 40 text

class Bundle extends Product { public function getMass() { $mass = new Mass(0); foreach ($this->products as $product) { $mass = $mass->add($product->getMass()); } return $mass; } public function getVolume() { $volume = new Volume(0); foreach ($this->products as $product) { $volume = $volume->add($product->getVolume()); } return $volume; } }

Slide 41

Slide 41 text

class Bundle extends Product { public function getPrice() { if ($this->price) { return $this->price; } $price = $this->products[0]->getPrice(); for ($i = 1; $i < count($this->products); $i++) { $product = $this->products[$i]; $price = $price->add($product->getPrice()); } return $price; } }

Slide 42

Slide 42 text

$b1 = new Bundle('C'); $b1->setPrice(new Money(39)); $b1->addProduct(new HardProduct('A', new Money(29))); $b1->addProduct(new DigitalProduct('B', new Money(20))); echo $b1->getPrice(); // 39 EUR $b2 = new Bundle('D'); $b2->addProduct(new HardProduct('C', new Money(12))); echo $b2->getPrice(); // 12 EUR Usage: creating products

Slide 43

Slide 43 text

$b1 = new Bundle('C'); $b1->setPrice(new Money(39)); $b1->addProduct(new HardProduct('A', new Money(29))); $b1->addProduct(new DigitalProduct('B', new Money(20))); $b2 = new Bundle('D'); $b2->addProduct(new HardProduct('C', new Money(12))); $b3 = new Bundle('E'); $b3->addProduct($b1); $b3->addProduct($b2); echo $b3->getPrice(); // 39 + 12 = 51 EUR Usage: embedding bundles

Slide 44

Slide 44 text

http://soyou.wordpress.com/tag/poupees-russes/ Decorator

Slide 45

Slide 45 text

Adding responsibilities to objects without subclassing their classes. Goal

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

Common implementations 1.  Adding some caching capability to a web service client to avoid network calls if not needed. 2.  Adding some logging features to an object in a dev environment. For instance, a database connection.

Slide 48

Slide 48 text

Concrete Example We want to offer discounts on orders without breaking existing domain model. Discount strategies may vary depending on the context.

Slide 49

Slide 49 text

OrderInterface! Order! BasicRateDiscount! getAmount()! getAmount()! order! getAmount()! OrderDecorator!

Slide 50

Slide 50 text

class Order implements OrderInterface { private $products = array(); public function addProduct(Product $product) { $this->products[] = $product; } public function getAmount() { $total = new Money(0); foreach ($this->products as $product) { $total = $total->add($product->getPrice()); } return $total; } }

Slide 51

Slide 51 text

abstract class OrderDecorator implements OrderInterface { protected $order; public function __construct(OrderInterface $order) { $this->order = $order; } public function addProduct(Product $product) { return $this->order->addProduct($product); } }

Slide 52

Slide 52 text

class BasicValueDiscount extends OrderDecorator { private $discount; function __construct(OrderInterface $order, Money $discount) { parent::__construct($order); $this->discount = $discount; } function getAmount() { $amount = $this->order->getAmount(); return $amount->subtract($this->discount); } }

Slide 53

Slide 53 text

$order = new Order(...); $order->addProduct(new DigitalProduct('A', new Money(15))); $order->addProduct(new DigitalProduct('B', new Money(10))); // Substract 5 euros to the total $order1 = new BasicValueDiscount($order, new Money(5)); $order1->getAmount(); // 20 EUR // Substract 50% of the total $order2 = new BasicRateDiscount($order, 50); $order2->getAmount(); // 12.50 EUR // Combine discounts together $order3 = new BasicRateDiscount( new BasicValueDiscount($order, new Money(5)), 50 ); $order3->getAmount(); // 10 EUR Usage

Slide 54

Slide 54 text

Other discount strategies… + Minimum purchase amount required + Minimum ordered items required + Loyal clients only + Special first order discount + …

Slide 55

Slide 55 text

Pros and cons + Easy way to extend an object capabilities + No need to change the underlying code - Object construction becomes more complex - Difficulty to test the concrete object type

Slide 56

Slide 56 text

http://redmtncom.com/uploads/chess_pieces_photo.jpg Strategy

Slide 57

Slide 57 text

The strategy pattern encapsulates algorithms of the same nature into dedicated classes to make them interchangeable. Goal

Slide 58

Slide 58 text

Common implementations 1.  Switching to another storage engine to store session data. 2.  Comparing and sorting elements of an array. 3.  Sending emails with SMTP, Sendmail or the mail() function.

Slide 59

Slide 59 text

class Swift_Mailer { private $transport; public function __construct(Swift_Transport $transport) { $this->transport = $transport; } public function send(Swift_Message $message) { // do stuff… $this->transport->send($message); } } Swiftmailer example

Slide 60

Slide 60 text

class Swift_MailTransport extends Swift_Transport { public function send(Swift_Message $message) { // use php mail() function to send a message } } class Swift_SmtpTransport extends Swift_Transport { public function send(Swift_Message $message) { // use an SMTP connection to send a message } } class Swift_NullTransport extends Swift_Transport { public function send(Swift_Message $message) { // fake email sending } }

Slide 61

Slide 61 text

Concrete example We want to calculate the order shipment fees depending on the chosen delivery strategy. The customer can choose between 3 delivery strategies.

Slide 62

Slide 62 text

Concrete example (wrong!!!) class OrderService { function priceShippingCost(OrderInterface $order, $type) { switch ($type) { case 'warehouse-pickup': return new Money(0); case 'regular': return new Money(7); case 'express': return new Money(10); } } }

Slide 63

Slide 63 text

Encapsulating algorithms interface ShippingStrategy { function calculateShippingCost(OrderInterface $order); } class WarehousePickup implements ShippingStrategy { function priceShippingCost(OrderInterface $order) { return new Money(0); } }

Slide 64

Slide 64 text

Encapsulating algorithms class RegularShipping implements ShippingStrategy { function priceShippingCost(OrderInterface $order) { if ($order->getAmount() > 100) { return new Money(0); } if (count($order->getProduct()) > 3) { return new Money(12); } return new Money(7); } }

Slide 65

Slide 65 text

Encapsulating algorithms class ExpressShipping implements ShippingStrategy { function priceShippingCost(OrderInterface $order) { if ($order->getWeight() < 5) { return new Money(6); } if ($order->getWeight() >= 5 && $order->getWeight() < 10) { return new Money(9); } return new Money(15); } }

Slide 66

Slide 66 text

class OrderService { public function priceShippingCost( OrderInterface $order, ShippingStrategy $shipping ) { // ... do stuff $fare = $shipping->priceShippingCost($order); $order->getAmount()->add($fare); // ... do stuff } } Strategy can be given as an argument of the client’s method.

Slide 67

Slide 67 text

class OrderService { private $shippingStrategy; public function __construct(ShippingStrategy $shipping) { $this->shippingStrategy = $shipping; } public function priceShippingCost(OrderInterface $order, $delivery) { // ... do stuff $fare = $this->shippingStrategy->priceShippingCost($order); $order->getAmount()->add($fare); // ... do stuff } } Or it can be injected in the constructor method of the client.

Slide 68

Slide 68 text

http://www.flickr.com/photos/jerryms/6704324441 Factory Method

Slide 69

Slide 69 text

Goal Define an interface for creating an object, but let subclasses decide which class to instantiate.

Slide 70

Slide 70 text

Factory Method lets a class defer instantiation to subclasses.

Slide 71

Slide 71 text

No content

Slide 72

Slide 72 text

Concrete example We want to encapsulate the way we create hard products, digital products and bundles. The goal is to keep instantiation processes in one single place.

Slide 73

Slide 73 text

ProductFactory + createProduct($name) + newProduct($name, $price) Product HardProduct HardProductFactory + createProduct($name)

Slide 74

Slide 74 text

abstract class ProductFactory { public function __construct(ReferenceGenerator $generator) { $this->refGenerator = $generator; } abstract protected function createProduct($name, $price, $mass = null, $volume = null); public function newProduct($name, $price, $mass = null, $volume = null) { $price = new Price($price, new Currency('EUR')); $product = $this->createProduct($name, $price, $mass, $volume); $product->setReference($this->refGenerator->generate($product)); return $product; } } Usage

Slide 75

Slide 75 text

class HardProductFactory extends ProductFactory { protected function createProduct( $name, Price $price, Mass $mass = null, Volume $volume = null ) { $product = new HardProduct($name, $price); $product->setPhysics($mass, $volume); return $product; } }

Slide 76

Slide 76 text

class DigitalProductFactory extends ProductFactory { protected function createProduct( $name, Price $price, Mass $mass = null, Volume $volume = null ) { return new DigitalProduct($name, $price); } }

Slide 77

Slide 77 text

class BundleFactory extends ProductFactory { protected function createProduct( $name, Price $price, Mass $mass = null, Volume $volume = null ) { return new Bundle($name, $price); } }

Slide 78

Slide 78 text

$generator = new ReferenceGenerator(); $factory = new HardProductFactory($generator); $product = $factory->newProduct('A', 50, 4, 10); $factory = new DigitalProductFactory($generator); $product = $factory->newProduct('B', 25); $factory = new BundleFactory($generator); $bundle = $factory->newProduct('C', 99); Usage

Slide 79

Slide 79 text

Observer

Slide 80

Slide 80 text

Goal A subject, the observable, emits a signal to a list of modules known as observers.

Slide 81

Slide 81 text

class Customer { public function pay(Order $order, Money $amount) { $order->setPaid(); if ($this->logger) { $this->logger->log('New order...'); } $mail = new Email(); $mail->recipient = $this->customer->getEmail(); $mail->subject = 'Your order!'; $mail->message = 'Thanks for ordering...'; $this->mailer->send($mail); $mail = new Email(); $mail->recipient = '[email protected]'; $mail->subject = 'New order to ship!'; $mail->message = '...'; $this->mailer->send($mail); } }

Slide 82

Slide 82 text

No content

Slide 83

Slide 83 text

interface ObserverInterface { function notify(ObservableInterface $subject); } interface ObservableInterface { function attach(ObserverInterface $observer); function notifyObservers(); }

Slide 84

Slide 84 text

class LoggerHandler implements ObserverInterface { public $logger; public function notify(ObservableInterface $subject) { $order = $subject->getLastOrder(); $reference = $order->getReference(); $this->logger->log('New order #'. $reference); } }

Slide 85

Slide 85 text

class CustomerNotifier implements ObserverInterface { public $mailer; public function notify(ObservableInterface $subject) { $mail = new Email(); $mail->recipient = $subject->getEmail(); $mail->subject = 'Your order!'; $mail->message = 'Thanks for ordering...'; $this->mailer->send($mail); } }

Slide 86

Slide 86 text

class SalesNotifier implements ObserverInterface { public $mailer; public function notify(ObservableInterface $subject) { $mail = new Email(); $mail->recipient = '[email protected]'; $mail->subject = 'New order to ship!'; $mail->message = '...'; $this->mailer->send($mail); } }

Slide 87

Slide 87 text

class Customer implements ObservableInterface { // ... private $observers; public function attach(ObserverInterface $observer) { $this->observers[] = $observer; } public function notifyObservers() { foreach ($this->observers as $observer) { $observer->notify($this); } } }

Slide 88

Slide 88 text

class Customer implements ObservableInterface { public function pay(Order $order) { $order->setPaid(); $this->notifyObservers(); } }

Slide 89

Slide 89 text

$client = new Customer(); $client->attach(new LoggerNotifier($logger)); $client->attach(new CustomerNotifier($mailer)); $client->attach(new SalesNotifier($mailer)); $order = new Order(); $client->pay($order, new Money(150));

Slide 90

Slide 90 text

Conclusion…

Slide 91

Slide 91 text

Good readings

Slide 92

Slide 92 text

No content