Dependency Injection for Wordpress

Preview:

DESCRIPTION

 

Citation preview

Dependency Injection forWordPress Plugin Development

Mike ToppaWordCamp Nashville

April 21, 2012#wcn12

@mtoppawww.toppa.com

@mtoppawww.toppa.com

Mike Toppa

● Director of Development, WebDevStudios● 17 years of experience in web development,

project management, and team management● Universities: Georgetown, Stanford, Penn● Dot coms: E*Trade, Ask Jeeves● Start-ups: Finexa, Kai's Candy Co● WordPress development for non-profits

Code is Poetry

@mtoppawww.toppa.com

@mtoppawww.toppa.com

We want code to be...

● Easy to understand● Easy to change● Reusable (DRY)● Bug free

“O.P.C” http://abstrusegoose.com/432

So why is it usually like this?

It happens because we are always in a hurry,and requirements are always changing.

@mtoppawww.toppa.com

But coding fast and dirtyonly makes us slower in the long run.

@mtoppawww.toppa.com

“We like to think we spend our time power typing,but we actually spend most of our time

staring into the abyss.”

- Douglas Crockfordprincipal discoverer of JSON,

Creator of JSLint

@mtoppawww.toppa.com

The ratio of time spent reading code versus writing is well over 10 to 1.

Therefore, making code easy to readmakes it easier to write.

- Bob MartinParaphrased from his book Clean Code

@mtoppawww.toppa.com

@mtoppawww.toppa.com

Clean code saves time, saves $$

@mtoppawww.toppa.com

Clean code techniques

● Using meaningful names● Don't repeat yourself (DRY)● Object oriented principles and patterns● Unit testing, test driven development (TDD)● The “boy scout rule”● Vertical slicing● ...and many more

@mtoppawww.toppa.com

Clean code techniques

● Using meaningful names● Don't repeat yourself (DRY)● Object oriented principles and patterns● Unit testing, test driven development (TDD)● The “boy scout rule”● Vertical slicing● ...and many more

@mtoppawww.toppa.com

The SOLID Principles

● Single Responsibility (SRP)● Open-Closed (OCP)● Liskov Substitution (LSP)● Interface Segregation (ISP)● Dependency Inversion (DIP)

@mtoppawww.toppa.com

The SOLID Principles

● Single Responsibility (SRP)● Open-Closed (OCP)● Liskov Substitution (LSP)● Interface Segregation (ISP)● Dependency Inversion (DIP)

@mtoppawww.toppa.com

Shashin

My plugin for displaying albums, photos, and videos from Picasa, Twitpic, and YouTube

(and others coming soon)

@mtoppawww.toppa.com

I used the new version as a test case for applyingclean code principles to WordPress plugins

From LosTechies.com

@mtoppawww.toppa.com

My Shashin plugin consists of 44 classes, each of which has a meaningful name, follows the SRP,

and “does one thing”

PicasaPhotoDisplayerSettingsMenu

YouTubeSynchronizerUninstall

etc.

@mtoppawww.toppa.com

class Admin_ShashinInstall { // ... public function run() { $this->createAlbumTable(); $this->verifyAlbumTable(); $this->createPhotoTable(); $this->verifyPhotoTable(); $this->updateSettings(); return true; }

// ...}

Shashin example

@mtoppawww.toppa.com

Class Autoloading

● The problem:● PHP lacks an equivalent statement to “import” or

“use” for classes● Having to hardcode a bunch of file paths is extra

work, and makes code harder to change

● The solution:● PHP 5.1.2 and spl_autoload_register()● PHP Standards Working Group: PSR-0

@mtoppawww.toppa.com

PSR-0: Official Description

● Each "_" character in the CLASS NAME is converted to a DIRECTORY_SEPARATOR

● The fully-qualified namespace and class is suffixed with ".php" when loading from the file system

● Alphabetic characters... may be of any combination of lower case and upper case

● ...And other rules about namespaces● Source: https://github.com/php-fig/fig-

standards/blob/master/accepted/PSR-0.md

@mtoppawww.toppa.com

Admin_ShashinInstall

is translated to

Admin/ShashinInstall.php

PSR-0 Example

@mtoppawww.toppa.com

// this is at the top of the main file for my Shashin plugin

require_once dirname(__FILE__) . '/../toppa-plugin-libraries-for-wordpress/ToppaAutoLoaderWp.php'; $shashinAutoLoader = new ToppaAutoLoaderWp('/shashin');

// that's it! I can now call “new” on any class under the shashin folder// and its class file will be automatically loaded

Toppa Plugin Libraries Autoloader

@mtoppawww.toppa.com

Shashin's capabilities are shaped by how its objects are wired together, through

implementation of theDependency Inversion Principle

From LosTechies.com

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();}

}}

Example from “Agile Software Development”

Dependency inversion applied

Button

+ poll()

<<interface>>SwitchableDevice

+ turnOn()+ turnOff()

Lamp

This is the Abstract Server pattern

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();}

}}

A web of collaborating objects

● The SRP and DIP together drive 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."

@mtoppawww.toppa.com

Composition(“Has a...”)

vs.

Inheritance(“Is a...”)

@mtoppawww.toppa.com

The SRP is about objects that do one thing

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

@mtoppawww.toppa.com

class Button {private $lamp;

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

}

//...}

Never, ever do this

@mtoppawww.toppa.com

Instead, you want toinject the object dependency

Dependency injection!

@mtoppawww.toppa.com

Dependency injection is one of manydesign patterns

for implementing theDependency inversion principle

@mtoppawww.toppa.com

It comes in two flavors

@mtoppawww.toppa.com

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

}

Constructor injection

@mtoppawww.toppa.com

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

}

Setter injection

@mtoppawww.toppa.com

Which to use?

It depends

@mtoppawww.toppa.com

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

class A should be blissfully unaware of class C

Dependency chains

@mtoppawww.toppa.com

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

@mtoppawww.toppa.com

class Lib_ShashinContainer { // … public function getPhotoRefData() { if (!isset($this->photoRefData)) { $this->photoRefData = new Lib_ShashinPhotoRefData(); }

return $this->photoRefData; }

public function getClonablePhoto() { if (!isset($this->clonablePhoto)) { $this->getPhotoRefData(); $this->clonablePhoto = new Lib_ShashinPhoto($this->photoRefData); }

return $this->clonablePhoto; }

Example from Shashin

@mtoppawww.toppa.com

public function getClonablePhotoCollection() { if (!isset($this->photoCollection)) { $this->getClonablePhoto(); $this->getSettings(); $this->clonablePhotoCollection = new Lib_ShashinPhotoCollection(); $this->clonablePhotoCollection->setClonableDataObject($this->clonablePhoto); $this->clonablePhotoCollection->setSettings($this->settings); }

return $this->clonablePhotoCollection; }

Example continued

@mtoppawww.toppa.com

Beyond the textbook examples

@mtoppawww.toppa.com

What to do when you needa new object inside a loop

One solution is cloning

@mtoppawww.toppa.com

class 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(); }

Shashin Example

@mtoppawww.toppa.com

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

@mtoppawww.toppa.com

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

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

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

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

// ... }

Shashin Example

@mtoppawww.toppa.com

Dependency injection: key benefits

● Makes your object dependencies adaptable to emerging needs

● With an injection container, simplifies managing dependency chains

● Supports “preferring polymorphism to conditionals”, making it easy to add new class subtypes

@mtoppawww.toppa.com

Will this proliferation of objectseat up all my memory?

No

@mtoppawww.toppa.com

Use dependency injection to instantiateonly the objects you need,

when you need them

@mtoppawww.toppa.com

Make immutable objects intoproperties of the container,

so you only need to instantiate them once

@mtoppawww.toppa.com

“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

PHP 5 has improved memory management

But don't overdo it:avoid needless complexity

Recommended