Dependency Inversion and Dependency Injection in PHP

34
Dependency Inversion and Dependency Injection in PHP Michael Toppa University of Pennsylvania Perelman School of Medicine Information Services August 11, 2011

description

 

Transcript of Dependency Inversion and Dependency Injection in PHP

Page 1: Dependency Inversion and Dependency Injection in PHP

Dependency Inversionand

Dependency Injectionin PHP

Michael ToppaUniversity of Pennsylvania

Perelman School of MedicineInformation Services

August 11, 2011

Page 2: Dependency Inversion and Dependency Injection in PHP

Dependency injectionis a design patternfor implementing

dependency inversion

Page 3: Dependency Inversion and Dependency Injection in PHP

Dependency inversion*is a design principle

*AKA The Hollywood Principle:“Don't call us, we'll call you”

Page 4: Dependency Inversion and Dependency Injection in PHP

The SOLID Principles

● Single Responsibility (SRP)

● Open-Closed (OCP)

● Liskov Substitution (LSP)

● Interface Segregation (ISP)

● Dependency Inversion (DIP)

Page 5: Dependency Inversion and Dependency Injection in PHP

The SRP is about objects that do one thing

The DIP is about how to wire them togetherto create working, flexible software

Page 6: Dependency Inversion and Dependency Injection in PHP

Formal Definition of the DIP

● High level modules should not depend on low-level modules. Both should depend on abstractions.

● Abstractions should not depend on details. Details should depend on abstractions.

This definition and the following example are from Bob Martin's book “Agile Software Development”

Page 7: Dependency Inversion and Dependency Injection in PHP

Naïve model of a button and lamp

Button

+ poll()

Lamp

+ turnOn()+ turnOff()

class Button {private $lamp;

public function __construct(Lamp $lamp) {$this->lamp = $lamp;

}

public function poll() {if (/* some condition */) {

$this->lamp->turnOn();}

}}

Page 8: Dependency Inversion and Dependency Injection in PHP

This solution violates the DIP

● Button depends directly on Lamp● Changes to Lamp may require changes to Button

● Button is not reusable● It can't control, for example, a Motor

● The high level abstraction is missing● “the truths that do not vary when the details are

changed”● “To detect an on/off gesture from a user and relay

that gesture to a target object”

Page 9: Dependency Inversion and Dependency Injection in PHP

From LosTechies.com

Page 10: Dependency Inversion and Dependency Injection in PHP

Dependency Inversion Applied

Button

+ poll()

<<interface>>SwitchableDevice

+ turnOn()+ turnOff()

Lamp

This is the Abstract Server pattern

Page 11: Dependency Inversion and Dependency Injection in PHP

class Lamp implements SwitchableDevice {public function turnOn() {

// code}

public function turnOff() {// code

}}

class Button {private $switchableDevice;

public function __construct(SwitchableDevice $switchableDevice) {$this->switchableDevice = $switchableDevice;

}

public function poll() {if (/* some condition */) {

$this->switchableDevice->turnOn();}

}}

Page 12: Dependency Inversion and Dependency Injection in PHP

What it means

● Neither Button nor Lamp “own” the interface● Buttons can now control any device that

implements SwitchableDevice● Lamps and other SwitchableDevices can now

be controlled by any object that accepts a SwitchableDevice

Page 13: Dependency Inversion and Dependency Injection in PHP

Patterns that implement the DIP

● Abstract Server● Constructor injection● Setter injection● Interface injection● Factory pattern● Adapter pattern● Service locator pattern● Contextualized lookup (push)

Page 14: Dependency Inversion and Dependency Injection in PHP

Never do this

class MySqlDb { public function __construct($username, $password, $host) { // .. snip .. } public function executeSql($sql) { // .. snip .. }}

class BookReader { private $_db; public function __construct() { $this->_db = new MySqlDb(DB_USER, DB_PASS, DB_HOST); } public function getChapters() { return $this->_db->executeSql('SELECT name FROM chapter'); }}

Example from Crafty documentationhttp://phpcrafty.sourceforge.net/documentation.php

Page 15: Dependency Inversion and Dependency Injection in PHP

In addition to other DIP violations,you cannot write unit tests for that code

Page 16: Dependency Inversion and Dependency Injection in PHP

Constructor injection solutioninterface Db { public function executeSql($sql);}

class MySqlDb implements Db { public function __construct($username, $password, $host) { // .. snip .. } public function executeSql($sql) { // .. snip .. }}

class BookReader { private $_db; public function __construct(Db $db) { $this->_db = $db; } public function getChapters() { return $this->_db->executeSql('SELECT name FROM chapter'); }}

Page 17: Dependency Inversion and Dependency Injection in PHP

Setter injection solutionclass BookReader { private $_db; public function __construct() { } public function setDb(Db $db) { $this->_db = $db; }

public function getChapters() { return $this->_db->executeSql('SELECT name FROM chapter'); }}

Page 18: Dependency Inversion and Dependency Injection in PHP

Which to use?

● Constructor injection gives you a valid object, with all its dependencies, upon construction

● But constructor injection becomes hard to read and use when there are more than a few objects to inject● This is especially true when subclassing

● More about this in an upcoming slide...

Page 19: Dependency Inversion and Dependency Injection in PHP

If class A depends on class B,and class B depends on class C,

class A should be blissfully unaware of class C

Page 20: Dependency Inversion and Dependency Injection in PHP

This supports loose coupling

and

lets you do dependency injection “just in time”

Page 21: Dependency Inversion and Dependency Injection in PHP

To do this without going insane,you need an injection container

Page 22: Dependency Inversion and Dependency Injection in PHP

Example from Shashinclass Lib_ShashinContainer { // ... public function __construct($autoLoader) { $this->autoLoader = $autoLoader; }

public function getDatabaseFacade() { if (!$this->dbFacade) { $this->dbFacade = new ToppaDatabaseFacadeWp($this->autoLoader); }

return $this->dbFacade; }

public function getClonablePhoto() { if (!$this->clonablePhoto) { $this->getDatabaseFacade(); $this->clonablePhoto = new Lib_ShashinPhoto($this->dbFacade); }

return $this->clonablePhoto; }}

I am making the objects properties of the container, because they happen to be

immutable objects, so they are reusable

Page 23: Dependency Inversion and Dependency Injection in PHP

Container Benefits

● Loose coupling - objects don't have to worry about the dependencies of the objects they use

● Facilitates portability - specific implementations or subtypes are centralized in the container

● Dependencies are clearly articulated in one place

● Simple design

Page 24: Dependency Inversion and Dependency Injection in PHP

Constructor vs setter injection:my personal preference

● Start with constructor injection● As your design evolves, switch to setter

injection once there are more than 2 objects to inject

● If you rely on an injection container, you don't have to worry about forgetting to call a required setter

Page 25: Dependency Inversion and Dependency Injection in PHP

Injection containers for PHP

● It's not hard to roll your own● There are also many available for PHP

● Bucket● PicoContainer● Crafty● Pimple● Symfony comes with one

Page 26: Dependency Inversion and Dependency Injection in PHP

Beyond the textbook examples

Page 27: Dependency Inversion and Dependency Injection in PHP

What to do when you needa new object inside a loop

One solution is cloning

Page 28: Dependency Inversion and Dependency Injection in PHP

Example from Shashinclass Admin_ShashinSynchronizerPicasa extends Admin_ShashinSynchronizer {

// …

public function syncAlbumPhotos(array $decodedAlbumData) { // …

foreach ($decodedAlbumData['feed']['entry'] as $entry) { $photoData = $this->extractFieldsFromDecodedData($entry, $photoRefData, 'picasa'); // ... $photo = clone $this->clonablePhoto; $photo->set($photoData); $photo->flush(); }

// ... }

// ...}

https://github.com/toppa/Shashin/

Page 29: Dependency Inversion and Dependency Injection in PHP

What if you need a new object inside a loop,but can't know the subtype you'll need

ahead of time?

Let the injection container figure it out

Page 30: Dependency Inversion and Dependency Injection in PHP

Example from Shashin

class Public_ShashinLayoutManager { // ... public function setTableBody() { // …

for ($i = 0; $i < count($this->collection); $i++) { // ...

$dataObjectDisplayer = $this->container->getDataObjectDisplayer( $this->shortcode, $this->collection[$i], $this->thumbnailCollection[$i] );

$this->tableBody .= $dataObjectDisplayer->run();

// ... } // ... } // ...}

getDataObjectDisplayer() uses the passed inarguments to determine which subtype of

DataObjectDisplayer to return

Page 31: Dependency Inversion and Dependency Injection in PHP

What makes an injection containerdifferent from the factory pattern?

Page 32: Dependency Inversion and Dependency Injection in PHP

Good question!

● An injection container can be used to generate more than one class of objects● A factory generates objects of a single class (or set

of class subtypes)

● An injection container consists of methods that create and return objects – it's a simple design● A full factory pattern implementation can be

complex, and hard to test*

● They're not mutually exclusive – you can use a container to create and inject a factory!

See http://blog.astrumfutura.com/2009/03/the-case-for-dependency-injection-part-1/

Page 33: Dependency Inversion and Dependency Injection in PHP

Will this proliferation of objects eat up all the server memory?

● No● “In PHP 5, the infrastructure of the object model

was rewritten to work with object handles. Unless you explicitly clone an object by using the clone keyword you will never create behind the scene duplicates of your objects. In PHP 5, there is neither a need to pass objects by reference nor assigning them by reference.”

● From http://devzone.zend.com/article/1714

Page 34: Dependency Inversion and Dependency Injection in PHP

A web of collaborating objects

● Dependency injection is all about a “composition” approach to OO design

● From Growing Object Oriented Software, Guided by Tests: "An object oriented system is a web of collaborating objects... The behavior of the system is an emergent property of the composition of the objects - the choice of objects and how they are connected... Thinking of a system in terms of its dynamic communication structure is a significant mental shift from the static classification that most of us learn when being introduced to objects."