Adding Dependency Injection to Legacy Applications
Post on 13-Jun-2015
1539 Views
Preview:
DESCRIPTION
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