TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
Dependency Injection for Wordpress
1. Dependency Injection for
WordPress Plugin Development
Mike Toppa
WordCamp Nashville
April 21, 2012
#wcn12
www.toppa.com @mtoppa
2. 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
www.toppa.com @mtoppa
4. We want code to be...
● Easy to understand
● Easy to change
● Reusable (DRY)
● Bug free
www.toppa.com @mtoppa
5. So why is it usually like this?
“O.P.C” http://abstrusegoose.com/432
6. It happens because we are always in a hurry,
and requirements are always changing.
www.toppa.com @mtoppa
7. But coding fast and dirty
only makes us slower in the long run.
www.toppa.com @mtoppa
8. “We like to think we spend our time power typing,
but we actually spend most of our time
staring into the abyss.”
- Douglas Crockford
principal discoverer of JSON,
Creator of JSLint
www.toppa.com @mtoppa
9. The ratio of time spent reading code versus
writing is well over 10 to 1.
Therefore, making code easy to read
makes it easier to write.
- Bob Martin
Paraphrased from his book Clean Code
www.toppa.com @mtoppa
11. 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
www.toppa.com @mtoppa
12. 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
www.toppa.com @mtoppa
22. My Shashin plugin consists of 44 classes, each of
which has a meaningful name, follows the SRP,
and “does one thing”
PicasaPhotoDisplayer
SettingsMenu
YouTubeSynchronizer
Uninstall
etc.
www.toppa.com @mtoppa
23. Shashin example
class Admin_ShashinInstall {
// ...
public function run() {
$this->createAlbumTable();
$this->verifyAlbumTable();
$this->createPhotoTable();
$this->verifyPhotoTable();
$this->updateSettings();
return true;
}
// ...
}
www.toppa.com @mtoppa
24. 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
www.toppa.com @mtoppa
25. 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
www.toppa.com @mtoppa
26. PSR-0 Example
Admin_ShashinInstall
is translated to
Admin/ShashinInstall.php
www.toppa.com @mtoppa
27. Toppa Plugin Libraries Autoloader
// 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
www.toppa.com @mtoppa
28. Shashin's capabilities are shaped by how its
objects are wired together, through
implementation of the
Dependency Inversion Principle
www.toppa.com @mtoppa
30. Naïve model of a button and lamp
Lamp
Button
+ turnOn()
+ poll()
+ 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”
31. Dependency inversion applied
<<interface>>
Button SwitchableDevice
+ poll() + turnOn()
+ turnOff()
Lamp
This is the Abstract Server pattern
32. 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();
}
}
}
33. 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."
34. Composition
(“Has a...”)
vs.
Inheritance
(“Is a...”)
www.toppa.com @mtoppa
35. The SRP is about objects that do one thing
The DIP is about how to wire them together
to create working, flexible software
www.toppa.com @mtoppa
36. Never, ever do this
class Button {
private $lamp;
public function __construct() {
$this->lamp = new Lamp();
}
//...
}
www.toppa.com @mtoppa
37. Instead, you want to
inject the object dependency
Dependency injection!
www.toppa.com @mtoppa
38. Dependency injection is one of many
design patterns
for implementing the
Dependency inversion principle
www.toppa.com @mtoppa
43. Dependency chains
If class A depends on class B,
and class B depends on class C,
class A should be blissfully unaware of class C
www.toppa.com @mtoppa
44. To do this without going insane,
you need an injection container
www.toppa.com @mtoppa
45. Example from Shashin
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;
}
www.toppa.com @mtoppa
46. Example continued
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;
}
www.toppa.com @mtoppa
48. What to do when you need
a new object inside a loop
One solution is cloning
www.toppa.com @mtoppa
49. Shashin Example
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();
}
www.toppa.com @mtoppa
50. 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
www.toppa.com @mtoppa
51. Shashin Example
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();
// ...
}
www.toppa.com @mtoppa
52. 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
www.toppa.com @mtoppa
54. Use dependency injection to instantiate
only the objects you need,
when you need them
www.toppa.com @mtoppa
55. Make immutable objects into
properties of the container,
so you only need to instantiate them once
www.toppa.com @mtoppa
56. PHP 5 has improved memory
management
“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
www.toppa.com @mtoppa
This means different things to different people Smashing Magazine article compared them in terms of: Structure – short line lengths, indentions Purpose – limericks for humor, not using HTML tables for layout Meaning and Efficiency – how intent is conveyed, how bloat is avoided Perception – perceived as the master's craft “apex of the written word” - custom wooden staircases parallel But while we can appreciate poetry with hidden meaning and indirection, that's not what we want in our code
Easy to understand Intention revealing: not just what, but also why Easy to change Adaptable to emerging needs: not fragile, not rigid Reusable (DRY) If we copy and paste, we are creating duplicate code we have to maintain Bug free Broken code costs time, money, and reputation
Tight deadlines Too much work Changing requirements Too many useless meetings Other people's messy code Our messy code Just make it work and get on to the next thing!
And launching buggy code can cost money and reputation
The exact shape of these lines is hotly debated May projects have ultimately failed because of the steep cost curve of messy code
Design patterns you may know or heard of, are ways of expressing these design principles
To explain dependency injection, I need to explain the DIP, and to explain it, I first need to explain the SRP
I'm going to show SRP and DIP examples of code from Shashin
Clean code books and articles all use other languages as examples – I wanted to try applying them to PHP and WordPress
Shashin supports multiple viewers
There's a lot going on here. A lot of different data sources and display possibilities to support. How do you keep the code from becoming a tangled mess?
Do one thing, do it well, do it only For methods, this typically means changing the value of only one variable If you are passing more than 2 or 3 arguments into a method, you are probably doing more than one thing For classes, it means having a single conceptual responsibility You want high cohesiveness in a class This is the opposite of how most WordPress plugins are written
The whole class is about 150 lines The run method calls the other methods (this is a simple use of the command pattern) It reads like a table of contents Breaking the functionality down into methods that do one thing makes the code easier to read and to unit test Installation is especially useful to break down into small methods that can have automated tests, as plugin activation is especially difficult to debug
spl_autoloader_register allows you to register a function that will be called when PHP encounters a “new” call, for finding and including the file containing the class. You can register more than one function.
The problem is, this conflicts with WordPress naming conventions
It's common to see code that hard-wires together all the parts, when those connections could be made more flexible and extensible
This solution violates the DIP Button depends directly on Lamp Button is not reusable It can't control, for example, a Motor
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
Not shown here is the interface
Author: Steve Freeman and Nat Pryce
There was one thing that was ok in the naïve example – we were at least passing in the lamp object Instantiating it directly in the class makes your code impossible to unit test
Factory pattern, adapter patter, service locator pattern are others that implement the DIP
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 More about this in an upcoming slide...
We want loose coupling, not a rigid chain of dependencies that is hard to change
I am making the objects properties of the container, because they happen to be immutable objects, so they are reusable You can see I'm using constructor injection I'll explain in a minute why I called it a “clonable” photo
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
This was an interesting problem for me with Shashin. It supports multiple media sources (Picasa, etc), multiple viewers (Fancybox, etc), and we could be displaying a photo or an album cover, each of which has different behaviors
Regardless of the type, the layout rules for the thumbnails is always the same So here I've injected the container itself getDataObjectDisplayer() uses the passed in arguments to determine which subtype of DataObjectDisplayer to return
The complexity of having 44 classes in Shashin is justified by its need for flexibility You can support a new viewer or a new photo service simply by creating a new subclass. This is much more maintainable and extensible than a single huge file with deeply nested conditionals and unclear dependencies But if I knew I would never need that kind of flexibility, there would be no justification for creating these layers of abstraction – they would just be needless complexity