introduction to di(c)
DESCRIPTION
Quick introduction to Dependency Injection (Container).TRANSCRIPT
$speaker = new Speaker;
$speaker->name = "Radosław Benkel";$speaker->twitter = "@singlespl";$speaker->blog = "http://blog.rbenkel.me";
$speaker->givePresentation();
About me
What?
DependencyInjectionContainer
What?
Dependency
What?
Dependency
class TwitterAPIClient{ protected $httpClient; public function __construct() { $this->httpClient = new SomeCurlWrapper(); } /* ... */}
$client = new TwitterApiClient;
Dependency
class TwitterAPIClient{ protected $httpClient; public function __construct() { $this->httpClient = new SomeCurlWrapper(); } /* ... */}
$client = new TwitterApiClient;
This����������� ������������������ is����������� ������������������ your����������� ������������������ dependency.
“What’s����������� ������������������ wrong����������� ������������������ with����������� ������������������ that”?
Dependency
Try testing it...
Dependency
...or change client implementation
Dependency
Dependency
So let’s use Injection
Dependency
Injection
What?
Injection
class TwitterAPIClient{ protected $httpClient; public function __construct($httpClient) { $this->httpClient = $httpClient; } /* ... */}
$client = new TwitterApiClient(new SomeCurlWrapper);
And����������� ������������������ here����������� ������������������ you����������� ������������������ inject����������� ������������������ dependency
So...
Injection
Injection
public function __construct(){ $this->httpClient = new SomeCurlWrapper();}
public function __construct($httpClient){ $this->httpClient = $httpClient;}
VS
it’s just like...
Injection
Injection
VS
Injection
VS
Try����������� ������������������ replacing����������� ������������������ battery,����������� ������������������ and����������� ������������������ you����������� ������������������ will����������� ������������������ now����������� ������������������ what����������� ������������������ I’m����������� ������������������ talking����������� ������������������ about.����������� ������������������
Injection types:
Injection
Injection types:
Injection
• constructor injection
Injection types:
Injection
• constructor injection• setter injection
Injection types:
Injection
• constructor injection• setter injection• interface injection
Constructor injection
Injection
class TwitterAPIClient{ protected $httpClient; public function __construct($httpClient) { $this->httpClient = $httpClient; } /* ... */}$client = new TwitterApiClient(new SomeCurlWrapper);
Setter injection
Injection
class TwitterAPIClient{ protected $httpClient; public function __construct() {} public function setHttpClient($httpClient) { $this->httpClient = $httpClient; } /* ... */}$client = new TwitterApiClient;$client->setHttpClient(new SomeCurlWrapper);
Interface injection
Injection
interface HttpClientInterface { public function setHttpClient($httpClient);}
class TwitterAPIClient implements HttpClientInterface { protected $httpClient; public function __construct() {} public function setHttpClient($httpClient) { $this->httpClient = $httpClient; } /* ... */}
$client = new TwitterApiClient;$client->setHttpClient(new SomeCurlWrapper);
So far so good...
Injection
...until you don’t have to do something like
that:
Injection
Injection$mapper = new UserMapperEncrypted( new UserMapperCached( new UserMapperDB( new PDO( 'mysql:host=127.0.0.1', 'user', 'password' ) ), new RedisCacheAdapter( '127.0.0.1:6379' ) ), 'YourSuperSecretPass');
$mapper->save(new User('John', 'Doe'));
Injection
Injection
“How����������� ������������������ to����������� ������������������ solve����������� ������������������ that”?
Just use...
Injection
Container
What?
Container
require_once "container_prod.php";
$mapper = $container->get('mapper.user');
/* mapper is UserMapperEncrypted, which uses UserMapperCached, which uses UserMapperDB, which uses PDO. */$mapper->save(new User('John', 'Doe'));
Container
require_once "container_dev.php";
$mapper = $container->get('mapper.user');
/* mapper is UserMapperDB, with different PDO configuration. */$mapper->save(new User('John', 'Doe'));
Container
require_once "container_prod.php";$mapper = $container->get('mapper.user');$mapper->save(new User('John', 'Doe'));
require_once "container_dev.php";$mapper = $container->get('mapper.user');$mapper->save(new User('John', 'Doe'));
Find����������� ������������������ the����������� ������������������ difference
Container
require_once "container_prod.php";$mapper = $container->get('mapper.user');$mapper->save(new User('John', 'Doe'));
require_once "container_dev.php";$mapper = $container->get('mapper.user');$mapper->save(new User('John', 'Doe'));
Container
require_once "container_prod.php";$mapper = $container->get('mapper.user');$mapper->save(new User('John', 'Doe'));
require_once "container_dev.php";$mapper = $container->get('mapper.user');$mapper->save(new User('John', 'Doe'));
Find����������� ������������������ the����������� ������������������ differenceConfigures����������� ������������������ services����������� ������������������ in����������� ������������������ different����������� ������������������ way,����������� ������������������ but����������� ������������������ for����������� ������������������ you,����������� ������������������ API����������� ������������������ for����������� ������������������ mapper����������� ������������������ is����������� ������������������ the����������� ������������������ same.
Container
require_once "container_prod.php";$mapper = $container->get('mapper.user');$mapper->save(new User('John', 'Doe'));
require_once "container_dev.php";$mapper = $container->get('mapper.user');$mapper->save(new User('John', 'Doe'));
Find����������� ������������������ the����������� ������������������ difference*����������� ������������������ Assuming,����������� ������������������ that����������� ������������������ all����������� ������������������ mappers����������� ������������������ share����������� ������������������ the����������� ������������������ same����������� ������������������ interface,����������� ������������������ which����������� ������������������ they����������� ������������������ rather����������� ������������������ should.
So, DIC it’s something like:
Container
Container
What it does:
Container
• injects object dependencies• creates objects on demand• objects could be shared• (and sometimes other stuff, like tagging, XML/
JSON/YAML config etc. )
So. Lets write simple one.
Container
Containerclass Container{ protected $items = array();
public function get($key) { $item = $this->items[$key]; return is_callable($item) ? $item($this) : $item; }
public function set($key, $value, $shared = false) { if ($shared === true && is_callable($value)) { $this->items[$key] = function($c) use ($value) { static $obj; if (!$obj) { $obj = $value($c); } return $obj; }; } else { $this->items[$key] = $value; } }}
Containerclass Container{ protected $items = array();
public function get($key) { $item = $this->items[$key]; return is_callable($item) ? $item($this) : $item; }
public function set($key, $value, $shared = false) { if ($shared === true && is_callable($value)) { $this->items[$key] = function($c) use ($value) { static $obj; if (!$obj) { $obj = $value($c); } return $obj; }; } else { $this->items[$key] = $value; } }}
*����������� ������������������ Inspired����������� ������������������ by����������� ������������������ Pimple
Lets use that.
Injection
Replacing this:
Injection
Injection$mapper = new UserMapperEncrypted( new UserMapperCached( new UserMapperDB( new PDO( 'mysql:host=127.0.0.1', 'user', 'password' ) ), new RedisCacheAdapter( '127.0.0.1:6379' ) ), 'YourSuperSecretPass');
$mapper->save(new User('John', 'Doe'));
with this:
Injection
Injection
require_once "container_prod.php";
$mapper = $container->get('mapper.user');
/* mapper is UserMapperEncrypted, which uses UserMapperCached (using Redis for cache), which uses UserMapperDB, which uses PDO. */$mapper->save(new User('John', 'Doe'));
Because everything is configured in container...
Injection
Injection//container_prod.php$c = new Container();$c->set('pdo.dsn', 'mysql:host=127.0.0.1');$c->set('pdo.user', 'user');$c->set('pdo.pass', 'password');$c->set('redis.host', '127.0.0.1:6379');$c->set('mcrypt.key', 'YourSuperSecretPass');$c->set('pdo', function(Container $c) { return new PDO( $c->get('pdo.dsn'), $c->get('pdo.user'), $c->get('pdo.pass'), );}, true);$c->set('cache.adapter', function(Container $c) { return new RedisCacheAdapter($c->get('redis.host'));});
$c->set('mapper.user', function(Container $c) { return new UserMapperEncrypted(
new UserMapperCached( new UserMapperDB($c->get('pdo')), $c->get('cache.adapter')
), $c->get('mcrypt.key')
);});
...you can change e.g cache adapter...
Injection
Injection//container_prod.php$c = new Container();$c->set('pdo.dsn', 'mysql:host=127.0.0.1');$c->set('pdo.user', 'user');$c->set('pdo.pass', 'password');$c->set('mcrypt.key', 'YourSuperSecretPass');$c->set('pdo', function(Container $c) { return new PDO( $c->get('pdo.dsn'), $c->get('pdo.user'), $c->get('pdo.pass'), );}, true);$c->set('cache.adapter', function(Container $c) { return new ApcCacheAdapter();});
$c->set('mapper.user', function(Container $c) { return new UserMapperEncrypted( new UserMapperCached( new UserMapperDB($c->get('pdo')), $c->get('cache.adapter') ), $c->get('mcrypt.key') );});
Injection//container_prod.php$c = new Container();$c->set('pdo.dsn', 'mysql:host=127.0.0.1');$c->set('pdo.user', 'user');$c->set('pdo.pass', 'password');$c->set('mcrypt.key', 'YourSuperSecretPass');$c->set('pdo', function(Container $c) { return new PDO( $c->get('pdo.dsn'), $c->get('pdo.user'), $c->get('pdo.pass'), );}, true);$c->set('cache.adapter', function(Container $c) { return new ApcCacheAdapter();});
$c->set('mapper.user', function(Container $c) { return new UserMapperEncrypted( new UserMapperCached( new UserMapperDB($c->get('pdo')), $c->get('cache.adapter') ), $c->get('mcrypt.key') );});
...and your your code hasn’t changed at all.
Injection
Injection
require_once "container_prod.php";
$mapper = $container->get('mapper.user');
/* mapper is UserMapperEncrypted, which uses UserMapperCached (using Apc for cache), which uses UserMapperDB, which uses PDO. */$mapper->save(new User('John', 'Doe'));
But probably, you should use another
DIC:
Container
• AuraDIhttp://auraphp.github.com/Aura.Di/
• Pimplehttp://pimple.sensiolabs.org/
• Symfony % Dependency Injection Componenthttp://symfony.com/doc/current/components/dependency_injection/introduction.html
• ZF% Dependency Injectionhttp://framework.zend.com/wiki/display/ZFDEV%/Zend+DI+QuickStart
• Twitteehttp://twittee.org/
Container
• AuraDIhttp://auraphp.github.com/Aura.Di/
• Pimplehttp://pimple.sensiolabs.org/
• Symfony % Dependency Injection Componenthttp://symfony.com/doc/current/components/dependency_injection/introduction.html
• ZF% Dependency Injectionhttp://framework.zend.com/wiki/display/ZFDEV%/Zend+DI+QuickStart
• Twitteehttp://twittee.org/
Container
PHP����������� ������������������ 5.4����������� ������������������ only
• AuraDIhttp://auraphp.github.com/Aura.Di/
• Pimplehttp://pimple.sensiolabs.org/
• Symfony % Dependency Injection Componenthttp://symfony.com/doc/current/components/dependency_injection/introduction.html
• ZF% Dependency Injectionhttp://framework.zend.com/wiki/display/ZFDEV%/Zend+DI+QuickStart
• Twitteehttp://twittee.org/
Container
Small,����������� ������������������ basic
• AuraDIhttp://auraphp.github.com/Aura.Di/
• Pimplehttp://pimple.sensiolabs.org/
• Symfony % Dependency Injection Componenthttp://symfony.com/doc/current/components/dependency_injection/introduction.html
• ZF% Dependency Injectionhttp://framework.zend.com/wiki/display/ZFDEV%/Zend+DI+QuickStart
• Twitteehttp://twittee.org/
Container
Lot’s����������� ������������������ of����������� ������������������ features
• AuraDIhttp://auraphp.github.com/Aura.Di/
• Pimplehttp://pimple.sensiolabs.org/
• Symfony % Dependency Injection Componenthttp://symfony.com/doc/current/components/dependency_injection/introduction.html
• ZF% Dependency Injectionhttp://framework.zend.com/wiki/display/ZFDEV%/Zend+DI+QuickStart
• Twitteehttp://twittee.org/
Container
Fits����������� ������������������ into����������� ������������������ tweet!
Thank you!