Professional Documents
Culture Documents
Ralph Schindler, Matthew Weier O'Phinney, and Enrico Zimuel ZendCon 2012
A formal way of documenting a solution to a design problem in a particular field of expertise. (http://en.wikipedia.org /wiki/Design_patterns)
Foundation patterns
Bridge
Problem: manage an abstraction with different implementations First solution: use inheritance to build different implementations Cons: the implementations are too close with the abstraction. The abstraction and implementations cannot be independently extended or composed. Better solution: use the Bridge pattern
Implementation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface DrawingAPI { public function drawCircle($x, $y, $radius); } class DrawingAPI1 implements DrawingAPI { public function drawCircle($x, $y, $radius) { printf ("API1 draw (%d, %d, %d)\n", $x, $y, $radius); }
class DrawingAPI2 implements DrawingAPI { public function drawCircle($x, $y, $radius) { printf ("API2 draw (%d, %d, %d)\n", $x, $y, $radius); }
Implementation (2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 abstract class Shape { protected $api; protected $x; protected $y; public function __construct(DrawingAPI $api) { $this->api = $api; } } class CircleShape extends Shape { protected $radius; public function __construct($x, $y, $radius, DrawingAPI $api) { parent::__construct($api); $this->x = $x; $this->y = $y; $this->radius = $radius; } public function draw() { $this->api->drawCircle($this->x, $this->y, $this->radius); } }
Usage example
1 2 3 4 5 6 7 8 9 10 11 12 $shapes = array( new CircleShape(1, 3, 7, new DrawingAPI1()), new CircleShape(5, 7, 11, new DrawingAPI2()), ); foreach ($shapes as $sh) { $sh->draw(); } // Expected output: // API1 draw (1, 3, 7) // API2 draw (5, 7, 11)
Facade
Problem: simplify the usage of a complex code Solution: use the Facade pattern, a simplified interface to a larger body of code, such as a class library. Using a facade schema we can hide all the logic of the complex code, while the mechanism in question knows nothing about the calling class.
Implementation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class CPU { public function freeze() { echo "Freeze the CPU\n"; } public function jump($address) { echo "Jump to $address\n"; } public function execute() { echo "Execute\n"; } } class Memory { public function load($address, $data) { echo "Loading address $address with data: $data\n"; } } class Disk { public function read($sector, $size) { return "data from sector $sector ($size)"; } }
Implementation (2)
1 class Computer { 2 const BOOT_ADDRESS = 0; 3 const BOOT_SECTOR = 1; const SECTOR_SIZE = 16; 4 5 protected $cpu; 6 protected $mem; protected $hd; 7 8 public function __construct(CPU $cpu, Memory $mem, Disk $hd) { $this->cpu = $cpu; 9 $this->mem = $mem; 10 $this->hd = $hd; 11 12 } public function startComputer() { 13 $this->cpu->freeze(); 14 $this->mem->load( 15 16 self::BOOT_ADDRESS, $this->hd->read(self::BOOT_SECTOR, self::SECTOR_SIZE)); 17 $this->cpu->jump(self::BOOT_ADDRESS); 18 $this->cpu->execute(); 19 20 } 21 }
Proxy
Problem 1: manage "expensive to create" objects, lazy-loading them only on first access Problem 2: provide a local object representation of remote system processes Problem 3: consuming and controlling access to another object Solution: Design a proxy class that access/extend the object, overriding one or more methods
Implementation (Prob. 1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 interface ImageInterface { public function display(); } class Image implements ImageInterface { protected $filename; public function __construct($filename) { $this->filename = $filename; $this->loadFromDisk(); } protected function loadFromDisk() { echo "Loading {$this->filename}\n"; } public function display() { echo "Display {$this->filename}\n"; } }
Implementation (Prob. 1)
1 class ProxyImage implements ImageInterface 2 { protected $id; 3 protected $image; 4 5 public function __construct($filename) { $this->filename = $filename; 6 7 } public function display() { 8 9 if (null === $this->image) { $this->image = new Image($this->filename); 10 11 } return $this->image->display(); 12 13 } 14 }
Usage example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $filename = 'test.png'; $image1 = new Image($filename); // loading necessary echo $image1->display(); // loading unnecessary $image2 = new ProxyImage($filename); // loading unnecessary echo $image2->display(); // loading necessary echo $image2->display(); // loading unnecessary // // // // // // Expected output: Loading test.png Display test.png Loading test.png Display test.png Display test.png
Implementation (Prob. 3)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class SomeObject { protected $message; public function __construct($message) { $this->message = $message; } protected function doSomething() { return $this->message; } } class Proxy extends SomeObject { protected $proxied; public function __construct(SomeObject $o) { $this->proxied = $o; } public function doSomething() { return ucwords( $this->proxied->doSomething() ); } }
Usage example
1 2 3 4 5 6 7 8 9 $o = new SomeObject('foo bar'); $p = new Proxy($o); printf( "Message from Proxy: %s\n", $p->doSomething() ); // Expected output: // Message from Proxy: Foo Bar
Iterator
Problem: manipulate/traverse a collection of objects with a standard interface Solution: use the Iterator pattern that enables to traverse a container of objects PHP: PHP supports a standard Iterator interface (and Iterators classes in the SPL ready to be used)
Implementation
1 class Fibonacci implements Iterator { 2 protected $value = 0; 3 protected $sum = 0; protected $key = 0; 4 5 public function rewind() { $this->value = 0; 6 $this->key = 0; 7 8 } public function current() { 9 return $this->value; 10 11 } public function key() { 12 return $this->key; 13 14 } 15 ...
Implementation (2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 } ... public function next() { if ($this->value === 0) { $this->value = 1; } else { $old = $this->value; $this->value += $this->sum; $this->sum = $old; } $this->key++; } public function valid() { return ($this->value < PHP_INT_MAX); }
Usage example
1 2 3 4 5 6 7 8 9 10 11 // print the Fibonacci numbers until PHP_INT_MAX foreach ($test = new Fibonacci() as $key => $value) { printf("%d) %d\n", $key, $value); } // print the first 10 Fibonacci's numbers $num = new Fibonacci(); for ($i = 0; $i < 10; $i++) { printf("%d) %d\n", $i, $num->current()); $num->next(); }
Visitor
Problem: separate an algorithm from an object structure on which it operates Solution: uses the Visitor pattern that allows one to add new virtual functions to a family of classes without modifying the classes themselves
Implementation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface Visited { public function accept(Visitor $visitor); } class VisitedArray implements Visited { protected $elements = array(); public function addElement($element){ $this->elements[]=$element; } public function getSize(){ return count($this->elements); } public function accept(Visitor $visitor){ $visitor->visit($this); } }
Implementation (2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface Visitor { public function visit(VisitedArray $elements); } class DataVisitor implements Visitor { protected $info; public function visit(VisitedArray $visitedArray){ $this->info = sprintf ("The array has %d elements", $visitedArray->getSize()); } public function getInfo(){ return $this->info; } }
Usage example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $visitedArray = new VisitedArray(); $visitedArray->addElement('Element 1'); $visitedArray->addElement('Element 2'); $visitedArray->addElement('Element 3'); $dataVisitor = new DataVisitor(); $visitedArray->accept($dataVisitor); $dataVisitor->visit($visitedArray); printf( "Info from visitor object: %s\n", $dataVisitor->getInfo() );
Decorator
Problem: add functionalities to an existing object dynamically, without extend it Solution: use the Decorator pattern to alter or decorate portions of an existing objects content or functionality without modifying the structure of the original object.
Implementation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 interface HtmlElement { public function __toString(); public function getName(); } class InputText implements HtmlElement { protected $name; public function __construct($name) { $this->name = $name; } public function getName() { return $this->name; } public function __toString() { return "<input type=\"text\" " . "id=\"{$this->name}\"" . "name=\"{$this->name}\" />\n"; } }
Implementation (2)
1 abstract class HtmlDecorator implements HtmlElement 2 { protected $element; 3 public function __construct(HtmlElement $input) { 4 $this->element = $input; 5 6 } public function getName() { 7 8 return $this->element->getName(); 9 } public function __toString() { 10 11 return $this->element->__toString(); 12 } 13 }
Implementation (3)
1 class LabelDecorator extends HtmlDecorator { 2 protected $label; 3 public function setLabel($label) { $this->label = $label; 4 5 } public function __toString() { 6 $name = $this->getName(); 7 8 return "<label for=\"{$name}\">" 9 . $this->label . "</label>\n" 10 . $this->element->__toString(); 11 } 12 }
Implementation (4)
1 class ErrorDecorator extends HtmlDecorator { 2 protected $error; 3 public function setError($message) { $this->error = $message; 4 5 } public function __toString() { 6 return $this->element->__toString() . 7 8 "<span>{$this->error}</span>\n"; 9 } 10 }
Usage example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // Add a label to the input text $input = new InputText('nickname'); $labelled = new LabelDecorator($input); $labelled->setLabel('Nickname:'); printf("%s\n", $labelled); // Add an error message to the input text $input = new InputText('nickname'); $error = new ErrorDecorator($input); $error->setError('You must enter a unique nickname'); printf("%s\n", $error); // Add a label and an error message to the input text $input = new InputText('nickname'); $labelled = new LabelDecorator($input); $labelled->setLabel('Nickname:'); $error = new ErrorDecorator($labelled); $error->setError('You must enter a unique nickname'); printf("%s\n", $error);
Fundamental Patterns
Strategy/Adapter/Command Factory Subject/Observer
Strategy
The problem
You've written code for which you need interchangeable algorithms. Based on the route, you'd handle a request differently. Based on console environment, you might need different line endings. You've got a large switch statement with many cases.
Adapter
The problem
You have domain-specific code that you want to use that doesn't follow interfaces of your toolkit/framework. You wrote an XML-RPC server, and now want to route to it. You have a data transfer object that could easily be re-purposed as a request You want to consume another object as a command, but it doesn't follow the interface you've defined.
Implementation
Step 1: Extract an interface
1 interface Handler 2 { public function handle(Request $request); 3 4 }
Implementation
Step 2: Compose a strategy
1 class Request 2 { 3 protected $handler; 4 public function setHandler(Handler $handler); public function handle() 5 6 { return $this->handler->handle($this); 7 8 } 9 }
Implementation
Step 3: Class to be adapted
1 class Paste 2 { 3 public function create( $content, $language = 'php' 4 5 ) { 6 // Return array of errors, 7 // or array representing paste 8 } 9 }
Implementation
Extension: Implement the desired interface in an extending object.
1 class PasteAdapter extends Paste implements Handler 2 { public function handle(Request $request) 3 4 { $post = $request->getPost(); 5 $content = $post->get('content', ''); 6 $lang = $post->get('lang', '') 7 8 return $this->create($content, $lang); 9 } 10 }
Implementation
Composition: Implement the desired interface, and inject the object being adapted.
1 class PasteAdapter implements Handler 2 { protected $paste; 3 4 public function setPaste(Paste $paste); public function handle(Request $request) 5 6 { $post = $request->getPost(); 7 $content = $post->get('content', ''); 8 $lang = $post->get('lang', '') 9 10 return $this->paste 11 ->create($content, $lang); 12 } 13 }
Command
The Problem
You have a lot of metadata that needs to be passed to one or more other objects. You discover you're coupling implementation details inside an object that should deal with abstractions. You're passing around query, post, header, and additional collections. You're passing around a set of common objects as individual arguments. The main context object shouldn't need to know what specific objects need to be passed to collaborators.
Implementation
Step 1: Extract a value object
1 interface Request 2 { public function 3 4 public function public function 5 6 public function 7 } getUri(); getQuery(); getPost(); getHeaders();
Implementation
Step 2: Create an interface for strategies/commands
1 interface Handler 2 { public function dispatch(Request $request); 3 4 }
Implementation
Step 3: Write handlers that delegate to other objects
1 2 3 4 5 6 7 8 9 10 11 12 class RoutePath { public function route($path; } class PathHandler implements Handler { protected $routePath; public function dispatch(Request $request) { $uri = $request->getUri(); $path = $uri->getPath(); $this->routePath->route($path); } }
Implementation
Step 4: Compose the strategies/commands
1 class RequestHandler 2 { 3 protected $handlers = array(); 4 protected $request; 5 public function addHandler(Handler $handler); 6 7 public function setRequest(Request $request); 8 public function dispatch() 9 10 { foreach ($this->handlers as $handler) { 11 $handler->dispatch($this->request); 12 13 } 14 } 15 }
Creational Patterns
Factory
The problem
You know that you need an object of a specified type, but the concrete implementation will be determined dynamically. The controller will vary based on request. How pagination occurs will vary based on persistence. Validators will vary based on element.
Implementation
Step 1: Extract the common interface
1 interface Handler 2 { public function handle(Request $request); 3 4 }
Implementation
Step 1a: Define a standard method for object creation
1 interface Handler 2 { 3 // Usually one of: 4 public function __construct($options); 5 // or: 6 public static function factory($options); 7 }
Implementation
Step 2: Define a factory that returns objects of that interface
1 interface Factory 2 { 3 /** 4 * @return Handler 5 */ 6 public function create($type); 7 }
Implementation
Step 3: Compose and consume a factory to get the concrete implementations
1 class RequestHandler 2 { protected $factory; 3 4 public function setFactory(Factory $factory); 5 public function handle(Request $request) 6 { $type = $this->request->getController(); 7 $handler = $this->factory->create($type); 8 return $handler->handle($request); 9 10 } 11 }
Related Patterns
Inversion of Control Dependency Injection Container Service Locator
Service Locator
1 $services->setFactory('foo', function ($services) { 2 // do some work, and create and return 3 // an object instance 4 return $foo; 5 }); 6 $foo = $services->get('foo');
The Problem
You have an indeterminate number of objects that need notifications of certain state changes. When a transaction happens, notify a CSR, and send a confirmation email. When a commit is made, each of the webhooks must be notified.
Implementation
Subject/Observer
1 class Subject 2 { 3 protected $observers = array(); public function addObserver(Observer $observer) 4 5 { $this->observers[] = $observer 6 7 } public function execute() 8 9 { foreach ($this->observers as $observer) { 10 $observer->notify($this); 11 12 } 13 } 14 }
Implementation
Subject/Observer (cont)
1 interface Observer 2 { public function notify(Subject $subject); 3 4 }
Implementation
SignalSlot
1 interface Signals 2 { public function connect($signal, $callable); 3 public function emit($signal, $argv = null); 4 5 }
Implementation
SignalSlot (cont)
1 class Foo 2 { 3 protected $signals; public function setSignals(Signals $signals); 4 5 public function bar($baz, $bat) 6 7 { $this->signals->emit('bar', $baz, $bat); 8 9 } 10 }
Implementation
SignalSlot (cont)
1 2 3 4 5 6 7 8 $signals = new SignalSlotManager(); $signals->connect('bar', function ($baz, $bat) { printf('%s:%s', $baz, $bat); }); $foo = new Foo(); $foo->setSignals($signals); $foo->bar('do', 'something');
Implementation
Event Handler
1 2 3 4 5 6 7 8 9 10 11 12 13 interface Event { public function public function public function public function } getName(); getTarget(); setParams(array $params); getParams();
interface Events { public function attach($name, $callback); public function trigger( $name, $target, Event $event ); }
Implementation
Event Handler (cont)
1 class Foo 2 { 3 protected $events; public function setEventHandler(Events $events); 4 5 public function bar($baz, $bat) 6 { $event = new Event(); 7 8 $event->setParams(array( 'baz' => $baz, 9 'bat' => $bat, 10 11 )); $this->events->trigger( 12 'bar', $this, $event); 13 14 } 15 }
Implementation
Event Handler (cont)
1 2 3 4 5 6 7 8 9 10 11 12 13 $events = new EventHandler(); $events->attach('bar', function (Event $e) { $params = $e->getParams(); $class = get_class($e->getTarget()); printf('[%s][%s] %s', $e->getName(), $class, json_encode($params) ); }); $foo = new Foo; $foo->setEvents($events); $foo->bar('do', 'something');
More concepts
Short circuiting If a listener returns a particular response, end early Allow a listener to halt the event loop Response aggregation and introspection Global/Static manager, or per object? Event/signal naming
Summary
We examined patterns around interchangeability of algorithms (Strategy, Adapter, Command). We examined how to dynamically build objects of a specific type based on provided criteria (Factory, Builder, IoC). We examined how to compose objects that we can notify of changes or important stages of the application workflow (Subject/Observer, SignalSlot, Event Handler).
Assignment
Build a dispatcher
Controller will be given via a query string argument. Comma-delimit multiple controllers. Only instantiate the controllers specified. Do not use the fully-qualified class names in the query string. Add a non-Controller handler that executes for every request and which logs the query string argument. Use as many of the discussed patterns as possible.
Modeling patterns
Prototype
Category: Creational Pattern, typically used by code promoting extension Problem: objects that need to generate objects as part of a normal workflow
Prototype
First Solution: let your primary object create (call new) for every new object it must create Cons: When object creation is complex or becomes custom, this workflow then requires overriding and customization of the parent class Better Solution: Use the prototype pattern
Implementation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface PrototypicalInterface { /* Nothing new required */ public function initialize($values); } class PrototypicalFoo implements PrototypicalInterface { public function __construct() {} } class PrototypicalBar implements PrototypicalInterface { public function __construct(\mysqli $mysqli) {} }
Implementation (2)
1 class PrototypeConsumer { 2 protected $p; 3 public function __construct( 4 PrototypicalInterface $p 5 ) { $this->p = $p; 6 7 } public function operation() { 8 $p = clone $this->p; 9 $p->initialize($this->values); 10 return $p; // new instance, 11 12 // based off the foo 13 } 14 }
Usage
1 $pf = new PrototypicalFoo; 2 $pc = new PrototypeConsumer(); 3 $p = $pc->operation(); // a clone $pf, specialized 4 // by the $pc during 5 // operation()
Usage
All I care is that you implement ResultSetInterface
1 $table = new TableGatgeway( 'my_table', 2 $adapter, 3 4 null, // features 5 new HydratingResultSet(new MyTableHydrator) 6 );
Mapper
First Solution: There are many: cast to stdClass, allow entity object to sort out a translation, use data source specific solution (PDO::FETCH_CLASS for example). Cons: Mixed levels of separation of concerns, repeated code (in the case of translations), etc. Better Solution: Use a Mapper object.
Implementation
1 class Artist { 2 public $name, $bio; 3 /** @var Album[] */ 4 public $albums = array(); 5 public function getName() { 6 7 return $this->name; 8 } // ... 9 10 }
Implementation
1 class ArtistMapper { 2 public function mapArrayToArtist( 3 array $data, Artist $artist = null 4 ) { 5 $artist = ($artist) ?: new Artist; $artist->firstName = $data['first_name']; 6 $artist->lastName = $data['last_name']; 7 8 $album = new Album; 9 $album->title = $data['album_1_title']; 10 $artist->albums[] = $album; 11 12 return $artist; 13 } 14 }
Implementation (2)
1 public function mapArtistToArray( 2 Artist $artist 3 ) { 4 return array( 'first_name' => $artist->firstName, 5 'last_name' => $artist->lastName 6 7 ); 8 }
Usage
1 2 3 4 5 $artistData = $dbMapper; $artistMapper = new ArtistDataMapper; $artist = $artistMapper->mapArrayToArtist( $personArray ); // returns Artist object
Repository
Category: Data Access Problem: You don't want SQL in your controller code. You want to hide the persistence implementation from the Model's API (Persistence Ignorance).
Repository
First Solution: create collection returning methods on your Data Access object. Cons: Public API of the Data Access object becomes confused and overloaded with semi-related methods Better Solution: Use the repository pattern
Implementation
1 interface TrackRepositoryInterface { 2 // @return Track[] 3 public function findAll(); public function findById($id); 4 5 public function store(Track $track); 6 7 public function remove(Track $track); 8 }
Implementation (2)
1 class DbTrackRepository 2 implements TrackRepositoryInterface { 3 public function __construct( 4 TrackDbMapper $mapper 5 ) {} 6 /** ... **/ 7 }
Usage
1 2 3 4 5 6 7 $trackRepo = new DbTrackRepository( $services->get('TrackMapper') ); $tracks = $trackRepo->findAll(); foreach ($tracks as $track) { // do something interesting }
Value Objects
By definition, identity free and immutable. Values are simply put, any scalar in PHP (for all intents and purposes). Two separate Entities can share the same reference to a Value Object.
Entity
1 class Artist { 2 public $id; // has identity! 3 public $name; // has identity! 4 public $yearFormed; 5 }
Value Object
1 /** 2 * NOT PHP's DATETIME!!!! 3 */ 4 class Date { 5 public $year; public $month; 6 7 public $day; 8 }
Value
1 $artist = new Artist; 2 3 // name is a value, a string 4 $artist->name = 'Splender';
Other Patterns
There are a number of patterns/diction not discussed, that need to be at least mentioned What is an Layered Architecture? What are Services? What is an Aggregate + Aggregate Root?
Layered Architecture
Layered Architecture
A way of dividing out software conceptually In PHP, this might happen with some usage of namespaces The type of pattern it implements implies the layer of code it belongs to
Services
An overly used term, has many different contexts Service Layer: separate abstraction layer between controllers and models Model Services: (DDD) A place where "workflows/functions that have no natural place in a value object/entity" Dependency Injection / Application Architecture: shared objects, dependencies (Service Locator)
Exercise
Let's build something with all that we've learned!
Base Application
https://github.com/ralphschindler/PatternsTutorialApp/
The Idea
I there is money in sharing playlists online. I am not sure what the business will be, but I know it centers around a playlist We need to be able to model Track, Arist and Album information We might want to be able to pull information from web services
Switch To IDE
Time to switch to IDE, lets explore code
References
E.Gamma, R.Helm, R.Johnson, J.Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, AddisonWesley Professional, 1994 Aaron Saray, Professional PHP Design Patterns, Wrox 2004 Jason E. Sweat, Guide to PHP Design Patterns, Marco Tabini & Associates 2004 Matt Zandstra , PHP Objects, Patterns and Practice, Apress (3 edition) 2010
References (2)
Matthew Weier O'Phinney, Proxies in PHP Giorgio Sironi, Pratical PHP Patterns: Decorator PHP Manual, The Iterator Interface
Resources
https://github.com/ezimuel/PHP-design-patterns https://github.com/ralphschindler/PatternsTutorialApp
Feedback
http://joind.in/6857
Thank You