Slide 1

Slide 1 text

Implementing Design Patterns with PHP AFSY 2017 – Bordeaux – Hugo Hamon https://www.flickr.com/photos/64667396@N00/1668455127/sizes/o/

Slide 2

Slide 2 text

Hugo Hamon Software Architect SensioLabs Book author Conferences speaker Symfony contributor Bengal cat fan @hhamon

Slide 3

Slide 3 text

Introduction

Slide 4

Slide 4 text

What are design patterns? In software design, a design pattern is an abstract generic solution to solve a particular redundant problem.

Slide 5

Slide 5 text

Design Patterns Classification 23 « Gang of Four » Design Patterns ¤  Creational ¤  Structural ¤  Behavioral

Slide 6

Slide 6 text

Benefits of Design Patterns ¤  Communication & vocabulary ¤  Testability ¤  Maintainance ¤  Extensibility ¤  Loose coupling

Slide 7

Slide 7 text

Downsides of Design Patterns ¤  Hard to teach and learn ¤  Hard to know when to apply ¤  Require good knowledge of OOP ¤  Not always the holly grail!

Slide 8

Slide 8 text

Design Patterns encourage SOLID code SRP / Single Responsability Principle OCP / Open / Closed Principle LSP / Liskov Substitution Principle ISP / Interface Segregation Principle DIP / Dependency Inversion Principle

Slide 9

Slide 9 text

Creational Patterns Creational design patterns encapsulate and isolate the algorithms to create and initialize objects. Abstract Factory – Builder – Factory Method Lazy Initialization – Prototype – Singleton

Slide 10

Slide 10 text

Factory Method Define an interface for creating an object, but let subclasses decide which class to instantiate. The Factory method lets a class defer instantiation it uses to subclasses. - GoF

Slide 11

Slide 11 text

The Need For a Factory The Factory object encapsulates the process for creating, assembling and initializing objects. It hides this whole process from the client code that just needs to get a new made object. Having a Factory also allows to change the process for creating objects at any point in time without impacting the client code that still relies on the same interface.

Slide 12

Slide 12 text

The Static Method Approach class MediaFactory { public static function createMedia($path) { $format = strtolower(pathinfo($path, PATHINFO_EXTENSION)); if ('pdf' === $format) { return new PdfFile($path); } if (in_array($format, [ 'jpg', 'png', 'gif' ])) { return new ImageFile($path); } if ('txt' === $format) { return new TextFile($path); } throw new \UnexpectedValueException('Unexpected format '. $format); } }

Slide 13

Slide 13 text

The Static Method in Action class DocumentRepository { private $filesystem; private $imageManipulator; // ... public function move($path, $target) { $file = MediaFactory::createMedia($path); if ($file instanceof ImageFile) { $file = $this->imageManipulator->resize($file, 120, 120); } $this->filesystem->move($file->getRealPath(), $target); } }

Slide 14

Slide 14 text

Pros & Cons of Static Factories Tight coupling between the factory and the client,   Client code is more difficult to unit test, Unable to switch to another factory, Difficulty to rely on dependencies in the factory. Very simple, Very pragmatic, Easy to understand.

Slide 15

Slide 15 text

The Simple Factory Approach class MediaFactory implements MediaFactoryInterface { public function createMedia($path) { $format = strtolower(pathinfo($path, PATHINFO_EXTENSION)); if ('pdf' === $format) { return new PdfFile($path); } if (in_array($format, [ 'jpg', 'png', 'gif' ])) { return new ImageFile($path); } if ('txt' === $format) { return new TextFile($path); } throw new \UnexpectedValueException('Unexpected format '. $format); } }

Slide 16

Slide 16 text

The Simple Method in Action class DocumentRepository { private $mediaFactory; // ... public function __construct(MediaFactoryInterface $mediaFactory, ...) { $this->mediaFactory = $mediaFactory; // ... } public function move($path, $target) { $file = $this->mediaFactory->createMedia($path); // ... } }

Slide 17

Slide 17 text

Pros & Cons of Simple Factories It’s still not a real factory method object.   The factory produces multiple kinds of objects.   Simple & pragmatic approach. Loose coupling between factory and client code. Leverages the dependency injection principle. Easy to unit test the client code. Easy to replace the actual factory by another one.

Slide 18

Slide 18 text

The Real Factory Method Design Pattern

Slide 19

Slide 19 text

Implementing Real Factory Method The goal is to build a flexible system able to analyze and extract the metadata of any kind of media files like pictures, video clips or music clips. Depending on the file type to analyze, a concrete factory object must be chosen. It will be responsible for creating and initializing a concrete type metadata class. Every concrete metadata object must inherit the attributes of an abstract parent Metadata class.

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

Concrete Factories in Action – 1/2 $factory = new ImageMetadataFactory(new ImageAnalyzer()); /** @var ImageMetadata $metadata */ $metadata = $factory->loadMetadata('/path/to/image.png'); echo 'Path: ', $metadata->getRealPath() ,"\n"; echo 'Date: ', $metadata->getCreatedAt() ,"\n"; echo 'Size: ', $metadata->getSize() ,"\n"; echo 'Width: ', $metadata->getWidth() ," px\n"; echo 'Height: ', $metadata->getHeight() ," px\n"; echo 'Orientation: ', $metadata->getOrientation() ,"\n";

Slide 22

Slide 22 text

Concrete Factories in Action – 2/2 $factory = new MovieMetadataFactory(new VideoAnalyzer()); /** @var MovieMetadata $metadata */ $metadata = $factory->loadMetadata('/path/to/movie.mp4'); echo 'Path: ', $metadata->getRealPath() ,"\n"; echo 'Date: ', $metadata->getCreatedAt() ,"\n"; echo 'Size: ', $metadata->getSize() ,"\n"; echo 'Resolution X: ', $metadata->getXResolution() ,' px ', "\n"; echo 'Resolution Y: ', $metadata->getYResolution() ,' px ', "\n"; echo 'Duration: ', $metadata->getDuration() ," seconds\n"; echo 'Frame rate: ', $metadata->getFrameRate() ," fps\n"; echo 'Frames: ', $metadata->getFrameCount() ," frames\n";

Slide 23

Slide 23 text

Designing the MediaMetadata Class? abstract class MediaMetadata { private $realPath; private $size; private $createdAt; public function initialize(\SplFileInfo $file) { $this->size = $file->getSize(); $this->realPath = $file->getRealPath(); $this->createdAt = $file->getMTime(); } // … plus one getter for each private property }

Slide 24

Slide 24 text

Designing the MediaMetadataFactory Class? abstract class MediaMetadataFactory implements MediaMetadataFactoryInterface { private $analyzer; public function __construct(MediaAnalyzerInterface $analyzer) { $this->analyzer = $analyzer; } abstract protected function createMetadata(\SplFileInfo $file); protected function analyze(\SplFileInfo $file) { return $this->analyzer->analyze($file); } }

Slide 25

Slide 25 text

Designing the MediaMetadataFactory Class? abstract class MediaMetadataFactory implements MediaMetadataFactoryInterface { // ... /** @return MediaMetada */ public function loadMetadata($path) { if (!is_readable($path)) { throw new MediaNotFoundException(sprintf('%s not readable.', $path)); } $file = new \SplFileInfo($path); $metadata = $this->createMetadata($file); $metadata->initialize($file); return $metadata; } }

Slide 26

Slide 26 text

Analyzing Image Files Each ImageMetadata instance has a real path, a size, a creation date but also a set of dimensions (width & height) and an orientation (portrait, landscape or square). The ImageMetadataFactory class is responsible for producing ImageMetadata objects. To analyze a picture, the factory uses an ImageAnalyzer instance.

Slide 27

Slide 27 text

The Concrete ImageMetadata Class class ImageMetadata extends MediaMetadata { const SQUARE = 'square'; const PORTRAIT = 'portrait'; const LANDSCAPE = 'landscape'; private $width; private $height; public function __construct($width, $height) { $this->width = (int) $width; $this->height = (int) $height; } // … plus getter methods }

Slide 28

Slide 28 text

Reading Image Information with an Analyzer class ImageAnalyzer implements MediaAnalyzerInterface { public function analyze(\SplFileInfo $file) { $path = $file->getRealPath(); if (!$path || !$metadata = @getimagesize($path)) { throw new AnalysisFailedException(sprintf( 'Unable to extract image metadata for path %s.', $path )); } return [ 'width' => $metadata[0], 'height' => $metadata[1] ]; } }

Slide 29

Slide 29 text

Implementing the ImageMetadataFactory Class class ImageMetadataFactory extends MediaMetadataFactory { /** * Creates the specific ImageMetadata object. * * @param \SplFileInfo $file * @return ImageMetadata */ protected function createMetadata(\SplFileInfo $file) { $infos = $this->analyze($file); return new ImageMetadata($infos['width'], $infos['height']); } }

Slide 30

Slide 30 text

Using the ImageMetadataFactory Object $factory = new ImageMetadataFactory(new ImageAnalyzer()); $metadata = $factory->loadMetadata('/path/to/image.png'); echo 'Path: ', $metadata->getRealPath() ,"\n"; echo 'Date: ', $metadata->getCreatedAt() ,"\n"; echo 'Size: ', $metadata->getSize() ,"\n"; echo 'Width: ', $metadata->getWidth() ," px\n"; echo 'Height: ', $metadata->getHeight() ," px\n"; echo 'Orientation: ', $metadata->getOrientation() ,"\n";

Slide 31

Slide 31 text

Pros & Cons of Factories   Harder to implement, Involves lots of classes and interfaces,   Client still doesn’t know the exact type of products. Managing new file types means creating new factories, Easy to swap a factory with another one, Each factory only produces one concrete type of object, Objects instanciations are centralized, Provides lots of flexibility to the code.

Slide 32

Slide 32 text

Structural Patterns Structural patterns organize classes and help separating the implementations from their interfaces. Adapter – Bridge – Composite – Decorator Facade – Flyweight – Proxy

Slide 33

Slide 33 text

Composite Processing single objects and collections of objects uniformly. Every single object and collections share the same unified interface. Composite design pattern enables to combine objects and perform recursive operations very easily.

Slide 34

Slide 34 text

The Need for Composite Objects   File explorer Organization chart Family tree   Content management (nested pages)   XML file parsing Nested web forms   Navigation bar (items including subitems)   …

Slide 35

Slide 35 text

The Composite Design Pattern

Slide 36

Slide 36 text

Composite Design Pattern Usage $leaf = new ConcreteLeaf(); $leaf->operation(); $simpleComposite = new ConcreteComposite(); $simpleComposite->add(new ConcreteLeaf()); $simpleComposite->add(new ConcreteLeaf()); $simpleComposite->operation(); $superComposite = new ConcreteComposite(); $superComposite->add(new ConcreteLeaf()); $superComposite->add(new ConcreteLeaf()); $superComposite->add($simpleComposite); $simpleComposite->operation();

Slide 37

Slide 37 text

Implementing the Composite Pattern The goal is to build a flexible ecommerce system able to sell single articles (physical or digital) and combos of articles for a cheaper price. Each article must have a unit price and a mass. The mass of a combo is the sum of the masses of all articles of the combo. The unit price of the combo can be a fixed price or the sum of all unit prices of the articles in the combo.

Slide 38

Slide 38 text

UML Diagram Representation

Slide 39

Slide 39 text

Designing the Base Product Class abstract class Product { protected $name; protected $price; protected $mass; public function __construct($name, Money $price, Mass $mass) { $this->name = $name; $this->price = $price; $this->mass = $mass; } // + getter methods for the attributes… }

Slide 40

Slide 40 text

Designing the Concrete Product Classes class HardProduct extends Product { } class DigitalProduct extends Product { public function __construct($name, Money $price) { parent::__construct($name, $price, new Mass(0)); } }

Slide 41

Slide 41 text

Using the Concrete Product Classes $paperBook = new HardProduct( 'PHP Design Patterns', Money::fromString('EUR 49.00'), Mass::fromString('960.00 g') ); $digitalBook = new DigitalProduct( 'PHP Design Patterns', Money::fromString('EUR 22.00') );

Slide 42

Slide 42 text

Designing the Combo Class class Combo extends Product { private $products; function __construct($name, array $products, Money $price = null) { $this->setProducts($products); $price = $price ?: $this->getTotalPrice(); parent::__construct($name, $price, $this->getTotalMass()); } // ... private function setProducts(array $products) { $this->products = $products; } }

Slide 43

Slide 43 text

Calculate the Total Price of the Combo class Combo extends Product { // ... private function getTotalPrice() { $total = $this->getPriceAt(0); for ($i = 1; $i < count($this->products); $i++) { $total = $total->add($this->getPriceAt($i)); } return $total; } private function getPriceAt($index) { return $this->products[$index]->getPrice(); } }

Slide 44

Slide 44 text

Calculate the Total Mass of the Combo class Combo extends Product { // ... private function getTotalMass() { $total = $this->getMassAt(0); for ($i = 1; $i < count($this->products); $i++) { $total = $total->add($this->getMassAt($i)); } return $total; } private function getMassAt($index) { return $this->products[$index]->getMass(); } }

Slide 45

Slide 45 text

Creating a Combo with a Fixed Price $products = [ new HardProduct('Digital Camera', new EUR(78900), new Mass(855)), new HardProduct('Camera Bag', new EUR(3900), new Mass(220)), new HardProduct('Memory Card 128 Gb', new EUR(7900), new Mass(42)), ]; $combo = new Combo('Digital Camera & Bag', $products, new EUR(83900)); echo 'Name: ', $combo->getName() ,"\n"; echo 'Mass: ', $combo->getMass()->getValue() ," g\n"; echo 'Price: ', $combo->getPrice()->getConvertedAmount() ," €\n"; Name: Digital Camera & Bag Mass: 1117 g Price: 839.00 €

Slide 46

Slide 46 text

Creating a Combo with a Dynamic Price $products = [ new HardProduct('Digital Camera', new EUR(78900), new Mass(855)), new HardProduct('Camera Bag', new EUR(3900), new Mass(220)), new HardProduct('Memory Card 128 Gb', new EUR(7900), new Mass(42)), ]; $combo = new Combo('Digital Camera & Bag', $products); echo 'Name: ', $combo->getName() ,"\n"; echo 'Mass: ', $combo->getMass()->getValue() ,"\n"; echo 'Price: ', $combo->getPrice()->getConvertedAmount() ,"\n"; Name: Digital Camera & Bag Mass: 1117 g Price: 907.00 €

Slide 47

Slide 47 text

Creating a Supper Dupper Combo $products = [ new HardProduct('Digital Camera', new EUR(78900), new Mass(855)), new HardProduct('Camera Bag', new EUR(3900), new Mass(220)), new HardProduct('Memory Card 128 Gb', new EUR(7900), new Mass(42)), ]; $combo = new Combo('Digital Camera Combo Pack + Tripod', [ new HardProduct('Lightweight Tripod', new EUR(2690), new Mass(570)), new Combo('Digital Camera & Bag', $products, new EUR(83900)), ]); echo 'Name: ', $combo->getName() ,"\n"; echo 'Weight: ', $combo->getWeight()->getValue() ,"\n"; echo 'Price: ', $combo->getPrice()->getConvertedAmount() ,"\n"; Name: Digital Camera Combo Pack + Tripod Mass: 1687 g Price: 865.90 €

Slide 48

Slide 48 text

Decorator The Decorator design pattern allows to dynamically add new responsabilities to an object without changing its class. This design pattern encourages objects composition over class inheritance.

Slide 49

Slide 49 text

The Decorator Design Pattern

Slide 50

Slide 50 text

The Decorator Design Pattern Usage $instance = new OtherConcreteDecorator( new SomeConcreteDecorator( new ConcreteDecorator( new DecoratedComponent() ) ) ); $instance->operation();

Slide 51

Slide 51 text

Implementing the Decorator Pattern The goal is to build a flexible coupons system in order to discount the total price of an order upon certain conditions. Each discount coupon can be either a rate discount (-15%) or a discount value (-10€). Each coupon can also have zero or multiple restrictions to constraint their eligibility. The system must allow to create coupons with any complex combination for restrictions.

Slide 52

Slide 52 text

Coupon Restrictions Ideas Valid for a specific period of time,   Minimum total amount required,   Minimum quantity of ordered items required, Valid for a specific geographical area,   Customer must own the loyalty membership card,   Coupon is valid for Premium / VIP customers only, Valid for the customer’s very first order, Some products are not eligible for discounts, Valid only on some specific products areas (luxury, food…),   etc.

Slide 53

Slide 53 text

UML Diagram

Slide 54

Slide 54 text

Designing the ValueCoupon Class class ValueCoupon implements CouponInterface { private $code; private $discount; public function __construct($code, Money $discount) { $this->code = $code; $this->discount = $discount; } public function getCode() { return $this->code; } public function applyDiscount(Order $order) { return $order->addDiscount($this->discount); } }

Slide 55

Slide 55 text

Designing the RateCoupon Class class RateCoupon implements CouponInterface { private $code; private $rate; public function __construct($code, $rate) { $this->code = $code; $this->rate = $rate; } public function getCode() { return $this->code; } public function applyDiscount(Order $order) { $amount = $order->getTotalAmount(); return $order->addDiscount($amount->multiply($this->rate)); } }

Slide 56

Slide 56 text

Designing the Abstract Decorator Class abstract class CouponDecorator implements CouponInterface { protected $coupon; public function __construct(CouponInterface $coupon) { $this->coupon = $coupon; } public function getCode() { return $this->coupon->getCode(); } protected function createCouponException($message, \Exception $previous = null) { return new CouponException($message, 0, $previous); } }

Slide 57

Slide 57 text

The LimitedLifetimeCoupon Class class LimitedLifetimeCoupon extends CouponDecorator { private $startAt; private $expiresAt; public function __construct(CouponInterface $coupon, $startAt, $expiresAt) { if (!$startAt instanceof \DateTime) { $startAt = new \DateTime($startAt); } if (!$expiresAt instanceof \DateTime) { $expiresAt = new \DateTime($expiresAt); } if ($startAt > $expiresAt) { throw new \InvalidArgumentException('...'); } parent::__construct($coupon); $this->startAt = $startAt; $this->expiresAt = $expiresAt; } }

Slide 58

Slide 58 text

class LimitedLifetimeCoupon extends CouponDecorator { public function applyDiscount(Order $order) { $now = new \DateTime('now'); if ($this->startAt > $now) { throw $this->createCouponException(sprintf( 'Coupon is usable from %s.', $this->startAt->format('Y-m-d H:i:s') )); } if ($now > $this->expiresAt) { throw $this->createCouponException(sprintf( 'Coupon was valid until %s.', $this->expiresAt->format('Y-m-d H:i:s') )); } return $this->coupon->applyDiscount($order); } }

Slide 59

Slide 59 text

Using the LimitedLifetimeCoupon Class // Coupon offers a discount value of 20 €. $value = Money::fromString('EUR 20.00'); $coupon = new LimitedLifetimeCoupon( new ValueCoupon('3s2h7pd65s', $value), '2016-09-01', '2016-09-30' ); // Coupon offers 25% off. $coupon = new LimitedLifetimeCoupon( new RateCoupon('76cqa6qr19', .25), '2016-09-01', '2016-09-30' );

Slide 60

Slide 60 text

The MinimumPurchaseAmountCoupon Class class MinimumPurchaseAmountCoupon extends CouponDecorator { private $minimumAmount; function __construct(CouponInterface $coupon, Money $minAmount) { parent::__construct($coupon); $this->minimumAmount = $minAmount; } public function applyDiscount(Order $order) { $amount = $order->getTotalAmount(); if ($amount->lessThan($this->minimumAmount)) { throw $this->createCouponException(...); } return $this->coupon->applyDiscount($order); } }

Slide 61

Slide 61 text

Using the MinimumPurchaseAmountCoupon Class $minAmount = Money::fromString('EUR 300'); // Get 20 € off if total amount is greater than 300 € $discount = Money::fromString('EUR 20'); $coupon = new MinimumPurchaseAmountCoupon( new ValueCoupon('3s2h7pd65s', $discount), $minAmount ); // Get 25% off if total amount is greater than 300 € $coupon = new MinimumPurchaseAmountCoupon( new RateCoupon('76cqa6qr19', .25), $minAmount );

Slide 62

Slide 62 text

Combining Multiple Restrictions Together $totalAmount = Money::fromString('EUR 200.00'); $minAmount = Money::fromString('EUR 170.00'); $discount = Money::fromString('EUR 20'); // Create the special coupon $coupon = new ValueCoupon('3s2h7pd65s', $discount); $coupon = new LimitedLifetimeCoupon($coupon, 'now', '+60 days'); $coupon = new MinimumPurchaseAmountCoupon($coupon, $minAmount); $coupon = new CustomerFirstOrderCoupon($coupon); // Create the order instance $customer = new Customer('[email protected]'); $order = new Order($customer, $totalAmount); // Apply discount coupon on the order $discountedAmount = $coupon->applyDiscount($order);

Slide 63

Slide 63 text

Pros & Cons of Decorators   The decorated object class remains unchanged, Leverage the SRP and OCP principles, Allow to support any complex decoration combinations, Ability to choose in which order decorations are applied.   The variable doesn’t always contain the « real » object,   All method calls must be forwarded,   Not suitable for interfaces with lots of public methods, Doesn’t work for methods that returns the current instance.

Slide 64

Slide 64 text

Behavioral Patterns Behavioral patterns simplify the communication between objects in order to increase flexibility and decoupling. Chain of Responsability – Command – Interpreter Iterator – Mediator – Memento – Observer – State Strategy – Template Method – Visitor

Slide 65

Slide 65 text

Visitor The Visitor design pattern represents an operation to be performed on elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

Slide 66

Slide 66 text

UML Diagram

Slide 67

Slide 67 text

The Main API class ConcreteElement implement ElementInterface { public function accept(VisitorInterface $visitor) { $visitor->visit($this); } } class ConcreteVisitor implement VisitorInterface { public function visit(ElementInterface $element) { // do something with the element } }

Slide 68

Slide 68 text

Visitor Design Pattern Usage $element1 = new ConcreteElement(); $element1->accept(new ConcreteVisitorA()); $element1->accept(new ConcreteVisitorB()); $element1->accept(new ConcreteVisitorC()); $element2 = new OtherConcreteElement(); $element2->accept(new ConcreteVisitorD()); $element2->accept(new ConcreteVisitorC()); The accept() method of each concrete element receives a concrete instance of the Visitor type and invokes the its visit() method on it. The concrete element injects itself into the visit() method list of arguments.

Slide 69

Slide 69 text

Implementing the Visitor Pattern Consider a ShoppingCart object which contains a list of ShoppingCartItem instances. Each of these instances holds a reference to a Product reference object and the ordered quantity. During checkout, we want to perform several operations on the shopping cart like computing the total price, sorting all products per categories or even issueing the receipt.

Slide 70

Slide 70 text

Designing a Shopping Cart System $cart = new ShoppingCart(); $cart->addItem(0.65, new Tomatoes('Vegetables', new EUR(189))); $cart->addItem(1.38, new Salmon('Seafood', new EUR(2250))); $cart->addItem(2, new ToothpasteTube('Wellness', new EUR(115))); $cart->addItem(0.89, new Apples('Vegetables', new EUR(128))); $cart->addItem(3, new Shampoo('Wellness', new EUR(123))); $cart->addItem(0.47, new Tuna('Seafood', new EUR(1980))); $cart->addItem(0.26, new GoatCheese('Creamery', new EUR(1723))); Each product reference object has a category and a price. The price can be either a unit price (for a shampoo for instance) or a price per kilogram (for vegetables or seafood).

Slide 71

Slide 71 text

Performing Operations on a Shopping Cart class ShoppingCart { /** @var ShoppingCartItems[] */ private $items; public function getTotalPrice() { ... } public function sortByCategories() { ... } public function issueReceipt(PDFGenerator $gen) { ... } } Implementing all these operations in the ShoppingCart class will bloat it at some point. Also, adding new operations requires to change the class body and will increase responsabilities and coupling with other dependencies.

Slide 72

Slide 72 text

Make the Shopping Cart Visitable class ShoppingCart { // ... function accept(ShoppingCartVisitorInterface $visitor) { $visitor->visit($this); } } The first step is to make the ShoppingCart instance accept a ShoppingCartVisitorInterface implementation. The latter will then visit the shopping cart object that injects itself into the visit() method.

Slide 73

Slide 73 text

Computing the Total Price of the Cart class TotalPriceCartVisitor implements ShoppingCartVisitorInterface { private $total; public function __construct() { $this->total = Money::fromString('EUR 0'); } public function visit(ShoppingCart $cart) { foreach ($cart->getItems() as $item) { $this->total = $this->total->add($item->getTotalPrice()); } } public function getTotalPrice() { return $this->total; } }

Slide 74

Slide 74 text

Computing the Total Price of the Cart $cart = new ShoppingCart(); // ... $pricerVisitor = new TotalPriceCartVisitor(); $cart->accept($pricerVisitor); $totalPrice = $pricerVisitor->getTotalPrice(); Computing the total price of the whole shopping cart is as simple as passing the visitor to the shopping cart instance.

Slide 75

Slide 75 text

Sorting the Cart Items by Categories class SortCartItemsVisitor implements ShoppingCartVisitorInterface { private $sortedList; public function visit(ShoppingCart $cart) { foreach ($cart->getItems() as $item) { $this->sortedList[$item->getCategory()][] = $item; } ksort($this->sortedList); } public function getSortedCartItems() { return $this->sortedList; } }

Slide 76

Slide 76 text

Sorting the Cart Items by Categories $cart = new ShoppingCart(); // ... $sortVisitor = new SortCartItemsVisitor(); $cart->accept($sortVisitor); print_r($sortVisitor->getSortedCartItems()); Sorting the whole list of shopping cart items by product references categories is as simple as injecting the visitor into the ShoppingCart instance.

Slide 77

Slide 77 text

Pros & Cons of Visitor Design Patterns Keep data objects and their operations separated from each other,   Encourage developers to follow the SRP from SOLID,   Encourage developers to follow the OCP from SOLID,   Encourage losely coupled classes, Easy to extend the operations list by creating new visitors. Visitors must be mutable, Visitors may have to be updated if visitee changes, Potential edge cases if visitors change the state of the visitee, Cannot use polymorphism in visitors with PHP.

Slide 78

Slide 78 text

Merci AFSY 2017 – Bordeaux – Hugo Hamon https://www.flickr.com/photos/64667396@N00/1668455127/sizes/o/