Adding Dependency Injection to Legacy Applications


Published on

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.

Published in: Technology
1 Like
  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide
  • The Hollywood principle Try to make your code ignorant of it’s context
  • It’s a way of adding abstraction.By abstraction, we mean to abstract from view or hide.
  • danielspils
  • tnarik's - flickr
  • Have you ever felt like a crash test dummyAdd automated testing
  • Strategy PatternExplain picPic – Flickr - dominicbartolini
  • Add pointers to dependencies
  • 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.
  • 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 context
  • 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.
  • A helper pattern that works well with constructor injection, when you expect people to extend the class
  • 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 type
  • 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!
  • 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.
  • Adding Dependency Injection to Legacy Applications

    1. 1. DependencyAddingInjection To Legacy Applications
    2. 2. Who Is i3logix?
    3. 3. Inversion of Control
    4. 4. Dependency Injection
    5. 5. Built-InFlexibility
    6. 6. interface Calc { public function __invoke($left, $right);}class AddCalc implements Calc { public function __invoke($left, $right) { return $left + $right; }}class SubCalc implements Calc { public function __invoke($left, $right) { return $left - $right; }}
    7. 7. class Calculator{ public function __invoke(Calc $calc, $left, $right){ return $calc($left, $right); }}$calculator = new Calculator();echo $calculator(new AddCalc(), 2, 1), "n";echo $calculator(new SubCalc(), 2, 1), "n";
    8. 8. De-Coupling
    9. 9. Have Clear Goals
    10. 10. It’sonly aTool
    11. 11. I TN EJ C HE NC IT QI UO EN S
    12. 12. 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; }}
    13. 13. Constructor Injection Vs.Setter/Property Injection
    14. 14. 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; }}
    15. 15. 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; }}
    16. 16. Init Pattern
    17. 17. 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);//... }}
    18. 18. OptionalInjection
    19. 19. 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);//... }}
    20. 20. 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);//...
    21. 21. Class Name Override
    22. 22. class StatusClassNameOverrideConstruct{ protected $clientClass; public function __construct($class = Zend_Http_Client){ $this->clientClass = $class; } public function get($system){ $client = new $this->clientClass; $r = $this->getClient()->setUri(SERVICE_URI) ->setParameterGet(compact(system)) ->request(GET); if($r->isSuccessful()){ return $r->getBody(); } return false; }}
    23. 23. class StatusClassNameOverrideSetter{ protected $clientClass = Zend_Http_Client; public function setClass($class){ $this->clientClass = $class; } public function get($system){ $client = new $this->clientClass; $r = $this->getClient()->setUri(SERVICE_URI) ->setParameterGet(compact(system)) ->request(GET); if($r->isSuccessful()){ return $r->getBody(); } return false; }}
    25. 25. class PolyBad { public function foo($bar) { if (get_class($bar) === SomeOtherClass) { $tmp = new SomeClass(); } }}
    26. 26. class PolyGood { public function foo(SomeClass $bar) { if ($baz instanceof SomeInterface) { // Do something } }}
    27. 27. class Factory{ public function create(){ return new SimpleClass(); }}
    28. 28. Dynamic Factory
    29. 29. 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); }}
    30. 30. 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; } }");}
    32. 32. Injector, Provider, Container
    33. 33. Configuration
    34. 34. <service id="bar" class="FooClass" shared="true" constructor="getInstance"> <file>%path%/foo.php</file> <argument>foo</argument> <argument type="service" id="foo" /> <argument type="collection"> <argument>true</argument> <argument>false</argument> </argument> <configurator function="configure" /> <call method="setBar" /> <call method="setBar"> <argument>foo</argument> <argument type="service" id="foo" /> <argument type="collection"> <argument>true</argument> <argument>false</argument> </argument> </call></service>
    35. 35. Interfaces
    36. 36. if($obj instanceof InjectDb){ $obj->setDb($this->serviceLocator->getDb());}if($obj instanceof InjectTool){ $obj->setTool($this->serviceLocator->getTool());}if($obj instanceof InjectClientBuilder){ $obj->setClientBuilder($this->getClientBuilder());}if($obj instanceof InjectEvent){ $obj->setEvent($this->serviceLocator->getEvent());}
    37. 37. Duck Typing
    38. 38. 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); }//...
    39. 39. Auto Wiring
    40. 40. $rParmList = $methodReflection->getParameters();foreach($rParmList as $rParm ){ $parmClass = $rParm->getClass(); if($parmClass !== null){ $bindConfig->getInjectionConfig() ->addMethodInstanceKey( $methodName, $parmClass->getName()); }else{ throw new Exception( "Not possible to configure automatically" ); }}
    41. 41. Annotations
    42. 42. /** * @Singleton */class LoggerSimple{ /** * @Inject Zend_Db_Adapter_Abstract */ public $db; /** * @Inject * @Arg Zend_Db_Adapter_Abstract */ public function __construct($db){ $this->db = $db; } /** * @Inject * @Arg Zend_Db_Adapter_Abstract */ public function setDb($db){ $this->db = $db; }}
    43. 43. /** * @ImplementedBy LoggerSimple */interface Logger{ public function log($message, $level);}
    44. 44. $rClass = new ReflectionAnnotatedClass($config->getClass());if($rClass->isInterface()){ if($rClass->hasAnnotation(ImplementedBy) === false){ throw new Exception(…); } $impClass = $rClass->getAnnotation(ImplementedBy)->value; if($impClass === null){ throw new Exception (…); } $config->setImplementationClass($impClass);}
    45. 45. Combine. Win!
    46. 46. Singleton
    47. 47. class Db{ protected function __construct(){} protected function __clone(){} public static function instance(){ static $instance = null; if($instance === null){ $instance = new static(); } return $instance; }}
    48. 48. Singleton + Registry
    49. 49. 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]; }}
    50. 50. 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; }}
    51. 51. Registry::add(db, function (){return new Db();});var_dump(Registry::get(db));
    52. 52. Service Locator
    53. 53. class ServiceLocator{ protected function __construct(){} protected function __clone(){} public static function instance(){ static $instance = null; if($instance === null){ $instance = new static(); } return $instance; } public function getDb(){ static $db = null; if($db === null){ $db = new Db(); } return $db; }}
    54. 54. Coarse-Grained Fine-Grained
    55. 55. Design by Contract
    56. 56. class Logger{}
    57. 57. abstract class LoggerAbstract{}class LoggerEcho extends LoggerAbstract{}class LoggerFile extends LoggerAbstract{}
    58. 58. interface LoggerInterface{}abstract class LoggerAbstract implements LoggerInterface{}class LoggerEcho extends LoggerAbstract{}class LoggerFile extends LoggerAbstract{}class LoggerOddBall implements LoggerInterface{}
    59. 59. class Logger{}
    60. 60. abstract class Logger{}class LoggerEcho extends Logger{}class LoggerFile extends Logger{}
    61. 61. interface Logger{}abstract class LoggerAbstract implements Logger{}class LoggerEcho extends Logger{}class LoggerFile extends Logger{}class LoggerOddBall implements Logger{}
    62. 62. Prefer Composition Over Inheritance
    64. 64. YOU NEED AGAME PLAN!
    65. 65. HANK Y OU!