In this talk, I introduce the concept of Dependency Injection as well as Pimple as an example for a Dependency Injection Container. I also talk about one thing that you absolutely do *not* want to do when using Dependency Injection Containers.
class Foo { private $db; private $memcache; public function __construct() { $this->db = new DatabaseConnection(); $this->memcache = new MemcacheClient(); } }
use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; class Bar { private $logger; public function __construct() { $this->logger = new NullLogger(); } public function setLogger(LoggerInterface $logger) { $this->logger = $logger; } }
Dependency Injection ● Use constructor injection for required dependencies – services your class needs to run ● Use setter injection for optional dependencies – services your class may use if available ● Program against interfaces, not concrete implementations!
Advantages ● Dependencies of classes are obvious, your IDE will point them out to you ● Dependencies can easily be replaced ● Easy replacement also means easier to test
// Create a new instance of Foo $mysqli = new mysqli(); $db = new DatabaseConnection($mysqli); $memcache = new MemcacheClient(); $foo = new Foo($db, $memcache);
// Create a new instance of Foo list($host, $user, $pw, $db) = getDbConfig(); $mysqli = new mysqli($host, $user, $pw, $db); $db = new DatabaseConnection($mysqli); $memcache = new MemcacheClient(); $foo = new Foo($db, $memcache);
// DIC setup for db and cache here... $dic['foo'] = function($dic) { return new Foo($dic['db'], $dic['cache']); }; // Later in your application: $foo = $dic['foo']; var_dump($foo instanceof Foo); // true
Lazy initialisation in Pimple ● Pimple::offsetGet() calls closure we saved as $dic['foo'] ● Closure needs $dic['db'] and $dic['cache'], means more calls to Pimple::offsetGet() ● Cascade closure / Pimple::offsetGet() calls until dependencies are resolved
More Pimple features // To have one global instance of a class: $dic['example'] = $dic->share(function($dic) { return new Example(); }); // To return the closure instead of executing it: $dic['closure'] = $dic->protect(function() { // Do cool stuff here });
DI Container ● Knows the services that are available in your application ● Knows how to create them using Builders ● Creates them as soon as you need them
class Controller { private $dic; public function __construct(Pimple $dic) { $this->dic = $dic; } public function doSomething() { $foo = $this->dic['foo']; // Do something here } }
$dic = new Pimple(); $controller = new Controller($dic); $controller->doSomething(); // InvalidArgumentException: // - Identifier "foo" is not defined.
Don't abuse the DIC ● Do not hide your classes' dependencies by passing the DIC (or any other Service Locator) to them ● You get to pull exactly ONE object out of the DIC yourself: The controller that needs to be executed.