Dependency Injection (DI) is a fantastic technique, but what if you what to use dependency injection in your legacy application. Fear not! As someone who as done this very thing, I will show how you can successful and incrementally add DI to any application. I will present a number of recipes and solutions to common problems and give a tour of the various PHP DI projects and how they can help.
17. class Status {
public function get($system) {
$client = new Zend_Http_Client();
$r = $client->setUri(SERVICE_URI)
->setParameterGet(compact('system'))
->request('GET');
if ($r->isSuccessful()) {
return $r->getBody();
}
return false;
}
}
18. Constructor
Injection
Vs.
Setter/Property
Injection
19. class StatusSetterInject{
protected $client;
public function setClient(Zend_Http_Client $client){
$this->client = $client;
}
public function get($system){
$r = $this->client->setUri(SERVICE_URI)
->setParameterGet(compact('system'))
->request('GET');
if ($r->isSuccessful()){
return $r->getBody();
}
return false;
}
}
20. class StatusConstructInject{
protected $client;
public function __construct(Zend_Http_Client $client){
$this->client = $client;
}
public function get($system){
$r = $this->client->setUri(SERVICE_URI)
->setParameterGet(compact('system'))
->request('GET');
if($r->isSuccessful()){
return $r->getBody();
}
return false;
}
}
22. class StatusInit{
protected $client;
public function __construct(Zend_Http_Client $client){
$this->client = $client;
$this->init();
}
public function init(){
//Left empty
}
public function get($system){
$r = $this->client->setUri(SERVICE_URI)
->setParameterGet(compact('system'))
->request('GET');
//...
}
}
24. class StatusConstructInjectOptional{
protected $client;
public function __construct(
Zend_Http_Client $client = null){
if($client === null){
$client = new Zend_Http_Client();
}
$this->client = $client;
}
public function get($system){
$r = $this->client->setUri(SERVICE_URI)
->setParameterGet(compact('system'))
->request('GET');
//...
}
}
25. class StatusSetterInjectOptional{
protected $client;
public function setClient(Zend_Http_Client $client){
$this->client = $client;
}
protected function getClient(){
if ($this->client === null){
$this->client = new Zend_Http_Client();
}
return $this->client;
}
public function get($system){
$r = $this->getClient()->setUri(SERVICE_URI)
->setParameterGet(compact('system'))
->request('GET');
//...
35. abstract class FactoryAbstract{
protected abstract function getClassName();
public function create(){
$class = $this->getClassName();
$argList = func_get_args();
if (count($argList) < 1){
return new $class;
}
$rClass = new ReflectionClass($class);
return $rClass->newInstanceArgs($argList);
}
}
36. function __autoload($class){
$classPath = str_replace('_', '/', $class);
$filePath = __DIR__."/src/$classPath.php";
if (file_exists($filePath))
return require $filePath;
if (substr($class, -7) !== 'Factory')
return;
$subName = substr($class, 0, -8);
eval("class $class extends FactoryAbstract{
protected function getClassName(){
return '$subName';
}
}");
}
44. abstract class Zend_View_Abstract
implements Zend_View_Interface
//...
public function registerHelper($helper, $name){
//...
if (!$helper instanceof Zend_View_Interface){
if (!method_exists($helper, $name)){
require_once 'Zend/View/Exception.php';
$e = new Zend_View_Exception(
'View helper must …');
$e->setView($this);
throw $e;
}
}
if (method_exists($helper, 'setView')){
$helper->setView($this);
}
//...
54. class Db{
protected function __construct(){}
protected function __clone(){}
public static function instance(){
static $instance = null;
if($instance === null){
$instance = new static();
}
return $instance;
}
}
56. class Registry{
protected static $reg = array();
public static function add($key, $value){
static::$reg[$key] = $value;
}
public static function get($key){
return static::$reg[$key];
}
}
57. class Registry{
protected static $reg = array();
public static function add($key, $value){
static::$reg[$key] = $value;
}
public static function get($key){
$value = static::$reg[$key];
if(is_callable($value)){
return $value();
}
return $value;
}
}
66. interface LoggerInterface{}
abstract class LoggerAbstract implements LoggerInterface{}
class LoggerEcho extends LoggerAbstract{}
class LoggerFile extends LoggerAbstract{}
class LoggerOddBall implements LoggerInterface{}
69. interface Logger{}
abstract class LoggerAbstract implements Logger{}
class LoggerEcho extends Logger{}
class LoggerFile extends Logger{}
class LoggerOddBall implements Logger{}
Welding customHitch interfaceRope a lot of workWelder – Flickr - F0t0Synthcogdogblog – tow hitch - flickr
DI should not be your goal, especially when it comes to adding DI to you’re legacy appsMaintainable codeQuality codeFlexible codeReusable codeNVJ – Flickr
Bang for the Buck!Not everything needs to have DI or fit the goal you are going afterIf you’re goal is testability and you are using ZF1 for example, not every plugin or helper you create maybe worth the time it would take to create unit tests for.If you are adding DI to a code base where not everyone is on-board with the concept. This will help you fall foul of the perception you are wasting time.Being a purest in legacy code may take you to an early grave.http://misternewuzer.deviantart.com/art/Pop-Bang-Warpath-119523671
What I hope to give to you are some new tools to add to your tool boxWhiteforgeflickr
The Hollywood principle in practiceYou’re code should not know what it’s doing in the grand schema of things It should not be aware of it’s contexthttp://www.flickr.com/photos/duke_raoul/2262882022/sizes/l/in/photostream/
Be careful, opinions vary on this topicBike shed question,It doesn’t matter when you are adding DI to legacy, because the answer is, what ever works for you right now.http://www.flickr.com/photos/sue_langford/4525633192/sizes/l/in/photostream/
A helper pattern that works well with constructor injection, when you expect people to extend the classhttp://www.flickr.com/photos/22212359@N06/2307645669/sizes/l/in/photostream/
This can get ugly with large dependency listsThat’s also a code smell that your object is trying to do too much
You can combine this with constructor injection also
showbiz kids - flickr
Mixed string with instance
If you so an string check you could combine this is injection of an object instance
Hollywood principle againIf you use a class name where polymorphism can’t be used then you are calling out to the global namespace.Polymorphism is the ability to switch or replace an object of one type with an object of another typehttp://www.flickr.com/photos/demonbaby/3788532381/sizes/o/in/photostream/
Many types and names (factory method, builder, containers)Dirty work happens hereNeed to inject the factory into the objects that needs to create objectsRamson – Flickr
This is a little tedious for simple things
CDEGlobal – Flickr
Need to be careful once you start to combine with other techniques
Need some class name conventionCreate the real one to overrideDon’t have to use eval
So I can inject an instanceI can inject a factoryBut when and how do I do thatThis is the real hard workMichael D. Dunn – flickr
Just a sophisticated factorySo we will just be building upon what we have already cover for factoriesPinkpurse - Flickr
DavidMenting – Flickr
Could be PHP, XML, ini, etc
DAN_DAN2 – Flickr
Remember this would be inside of a factoryDoesn't scale great, but not awful
If it walks like a duck and it talks like a duck, then it’s a duck!http://www.flickr.com/photos/shinyhappyworld/5475939747/sizes/l/in/photostream/
Code from ZF1Could go further and check type hits and param counts.Could become a mess if it’s used a lot. In PHP you will need to do some kind of introspection You can cache the result of the introspection based on class nameIn reality this is the most powerful model for decupling, but with great power come great opportunity to kill yourself
Try to wire the objects up with out a central configuration and without a hand coded factory for every non-trivial object
Code from a DI project I created
Well sudoZalgon – Flickr
Will also have to check inheritance tree for things like class level annotations
Used with auto wiring
If you process the annotations by creating a config object about the class, you can then cache that config object.
It not always possible or advisable to be 100% DI, especially but not right away.
Gives you global access and a shared instanceWill spread global access all over your codeBob.Fornal– Flickr
Hard to test without adding test hooks to reset, which is evil.
You can put anything you need in the registryAnyone can put or change the data in the registry
A little better than just a singleton. Something still has to fill it with class instances first. Could use anonymous function.
ilovememphis – Flickr
Like a fixed registry with builder code baked in.Still has an issue with testing, but solutions are less evilIf you’ve ever worked with ZF1 boot-strapper and resource plugins this is much like thatIt also is a model of making the service locator pluggable
cogdogblog – Flickr
Refactoring'sKaptain Kobold – Flickr
Don't call an interface an interface
Written, verbal, call a meeting, socialize it, lunch and learns. Give it time.