Adding Dependency Injection to Legacy Applications

Post on 13-Jun-2015

1539 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

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.

Transcript

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

Who Is i3logix?

• Involved in many different markets• Privately funded• SaaS

Always looking for good people!

Inversion of Control

Dependency Injection

Why?

Built-InFlexibility

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

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

De-Coupling

Have Clear Goals

It’sonly

a Tool

INJECTION

TECHNIQUES

Global Namespace, Off Limits!

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

Constructor Injection

Vs. Setter/Property

Injection

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

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

Init Pattern

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

Op

tion

al

Inje

ctio

n

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

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

Class Name Override

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

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

BUT I NEED TO CREATE OBJECTS?

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

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

Factory

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

Dynamic Factory

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

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

WHAT IS INJECTING THESE OBJECTS?

Injector, Provider, Container

Configuration

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

Interfaces

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

Duck Typing

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

Auto Wiring

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

Annotations

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

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

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

Win!Combin

e.

BRIDGING THE GAPS

Singleton

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

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

Singleton + Registry

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

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

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

Service Locator

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

Coarse-Grained

Fine-Grained

Design by Contract

class Logger{}

abstract class LoggerAbstract{}

class LoggerEcho extends LoggerAbstract{}

class LoggerFile extends LoggerAbstract{}

interface LoggerInterface{}

abstract class LoggerAbstract implements LoggerInterface{}

class LoggerEcho extends LoggerAbstract{}

class LoggerFile extends LoggerAbstract{}

class LoggerOddBall implements LoggerInterface{}

class Logger{}

abstract class Logger{}

class LoggerEcho extends Logger{}

class LoggerFile extends Logger{}

interface Logger{}

abstract class LoggerAbstract implements Logger{}

class LoggerEcho extends Logger{}

class LoggerFile extends Logger{}

class LoggerOddBall implements Logger{}

Prefer Composition Over Inheritance

WHAT’S ALREADY OUT THERE?

YOU NEED A

GAME PLAN!

THANK YOU!

http://joind.in/3740

top related