Top Banner
Adding Dependency Injection To Legacy Applications
77

Adding Dependency Injection to Legacy Applications

Jun 13, 2015

Download

Technology

Sam Hennessy

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.
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Adding Dependency Injection to Legacy Applications

Adding Dependency Injection To Legacy

Applications

Page 2: Adding Dependency Injection to Legacy Applications

• Sam Hennessy• Originally from the UK• Now live in Denver, CO• Software Architect at i3logix• Ex-Pro Services Consultant For Zend• Regular at Front Range PHP Users Group

Page 3: Adding Dependency Injection to Legacy Applications

Who Is i3logix?

• Involved in many different markets• Privately funded• SaaS

Always looking for good people!

Page 4: Adding Dependency Injection to Legacy Applications

Inversion of Control

Page 5: Adding Dependency Injection to Legacy Applications

Dependency Injection

Page 6: Adding Dependency Injection to Legacy Applications

Why?

Page 7: Adding Dependency Injection to Legacy Applications
Page 8: Adding Dependency Injection to Legacy Applications

Built-InFlexibility

Page 9: Adding Dependency Injection to Legacy Applications

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

Page 10: Adding Dependency Injection to Legacy Applications

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";

Page 11: Adding Dependency Injection to Legacy Applications

De-Coupling

Page 12: Adding Dependency Injection to Legacy Applications

Have Clear Goals

Page 13: Adding Dependency Injection to Legacy Applications
Page 14: Adding Dependency Injection to Legacy Applications

It’sonly

a Tool

Page 15: Adding Dependency Injection to Legacy Applications

INJECTION

TECHNIQUES

Page 16: Adding Dependency Injection to Legacy Applications

Global Namespace, Off Limits!

Page 17: Adding Dependency Injection to Legacy Applications

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

Page 18: Adding Dependency Injection to Legacy Applications

Constructor Injection

Vs. Setter/Property

Injection

Page 19: Adding Dependency Injection to Legacy Applications

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

Page 20: Adding Dependency Injection to Legacy Applications

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

Page 21: Adding Dependency Injection to Legacy Applications

Init Pattern

Page 22: Adding Dependency Injection to Legacy Applications

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');//... }}

Page 23: Adding Dependency Injection to Legacy Applications

Op

tion

al

Inje

ctio

n

Page 24: Adding Dependency Injection to Legacy Applications

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');//... }}

Page 25: Adding Dependency Injection to Legacy Applications

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');//...

Page 26: Adding Dependency Injection to Legacy Applications

Class Name Override

Page 27: Adding Dependency Injection to Legacy Applications

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

Page 28: Adding Dependency Injection to Legacy Applications

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

Page 29: Adding Dependency Injection to Legacy Applications

BUT I NEED TO CREATE OBJECTS?

Page 30: Adding Dependency Injection to Legacy Applications

class PolyBad { public function foo($bar) { if (get_class($bar) === 'SomeOtherClass') { $tmp = new SomeClass(); } }}

Page 31: Adding Dependency Injection to Legacy Applications

class PolyGood { public function foo(SomeClass $bar) { if ($baz instanceof SomeInterface) { // Do something } }}

Page 32: Adding Dependency Injection to Legacy Applications

Factory

Page 33: Adding Dependency Injection to Legacy Applications

class Factory{ public function create(){ return new SimpleClass(); }}

Page 34: Adding Dependency Injection to Legacy Applications

Dynamic Factory

Page 35: Adding Dependency Injection to Legacy Applications

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

Page 36: Adding Dependency Injection to Legacy Applications

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'; } }");}

Page 37: Adding Dependency Injection to Legacy Applications

WHAT IS INJECTING THESE OBJECTS?

Page 38: Adding Dependency Injection to Legacy Applications

Injector, Provider, Container

Page 39: Adding Dependency Injection to Legacy Applications

Configuration

Page 40: Adding Dependency Injection to Legacy Applications

<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>

Page 41: Adding Dependency Injection to Legacy Applications

Interfaces

Page 42: Adding Dependency Injection to Legacy Applications

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

Page 43: Adding Dependency Injection to Legacy Applications

Duck Typing

Page 44: Adding Dependency Injection to Legacy Applications

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); }//...

Page 45: Adding Dependency Injection to Legacy Applications

Auto Wiring

Page 46: Adding Dependency Injection to Legacy Applications

$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" ); }}

Page 47: Adding Dependency Injection to Legacy Applications

Annotations

Page 48: Adding Dependency Injection to Legacy Applications

/** * @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; }}

Page 49: Adding Dependency Injection to Legacy Applications

/** * @ImplementedBy LoggerSimple */interface Logger{ public function log($message, $level);}

Page 50: Adding Dependency Injection to Legacy Applications

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

Page 51: Adding Dependency Injection to Legacy Applications

Win!Combin

e.

Page 52: Adding Dependency Injection to Legacy Applications

BRIDGING THE GAPS

Page 53: Adding Dependency Injection to Legacy Applications

Singleton

Page 54: Adding Dependency Injection to Legacy Applications

class Db{ protected function __construct(){} protected function __clone(){}

public static function instance(){ static $instance = null; if($instance === null){ $instance = new static(); } return $instance; }}

Page 55: Adding Dependency Injection to Legacy Applications

Singleton + Registry

Page 56: Adding Dependency Injection to Legacy Applications

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]; }}

Page 57: Adding Dependency Injection to Legacy Applications

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

Page 58: Adding Dependency Injection to Legacy Applications

Registry::add('db', function (){return new Db();});var_dump(Registry::get('db'));

Page 59: Adding Dependency Injection to Legacy Applications

Service Locator

Page 60: Adding Dependency Injection to Legacy Applications

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

Page 61: Adding Dependency Injection to Legacy Applications

Coarse-Grained

Fine-Grained

Page 62: Adding Dependency Injection to Legacy Applications
Page 63: Adding Dependency Injection to Legacy Applications

Design by Contract

Page 64: Adding Dependency Injection to Legacy Applications

class Logger{}

Page 65: Adding Dependency Injection to Legacy Applications

abstract class LoggerAbstract{}

class LoggerEcho extends LoggerAbstract{}

class LoggerFile extends LoggerAbstract{}

Page 66: Adding Dependency Injection to Legacy Applications

interface LoggerInterface{}

abstract class LoggerAbstract implements LoggerInterface{}

class LoggerEcho extends LoggerAbstract{}

class LoggerFile extends LoggerAbstract{}

class LoggerOddBall implements LoggerInterface{}

Page 67: Adding Dependency Injection to Legacy Applications

class Logger{}

Page 68: Adding Dependency Injection to Legacy Applications

abstract class Logger{}

class LoggerEcho extends Logger{}

class LoggerFile extends Logger{}

Page 69: Adding Dependency Injection to Legacy Applications

interface Logger{}

abstract class LoggerAbstract implements Logger{}

class LoggerEcho extends Logger{}

class LoggerFile extends Logger{}

class LoggerOddBall implements Logger{}

Page 70: Adding Dependency Injection to Legacy Applications

Prefer Composition Over Inheritance

Page 71: Adding Dependency Injection to Legacy Applications

WHAT’S ALREADY OUT THERE?

Page 72: Adding Dependency Injection to Legacy Applications
Page 73: Adding Dependency Injection to Legacy Applications
Page 74: Adding Dependency Injection to Legacy Applications
Page 75: Adding Dependency Injection to Legacy Applications

YOU NEED A

GAME PLAN!

Page 76: Adding Dependency Injection to Legacy Applications
Page 77: Adding Dependency Injection to Legacy Applications

THANK YOU!

http://joind.in/3740