Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dependency Injection - PHPUG Cologne 2013/8

Dependency Injection - PHPUG Cologne 2013/8

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.

503778aa6a31b4ecb5b37ffb62ff5dab?s=128

Tobias Gies

August 02, 2013
Tweet

Transcript

  1. Dependency Injection

  2. inject dependencies!

  3. class Foo { private $db; private $memcache; public function __construct()

    { $this->db = new DatabaseConnection(); $this->memcache = new MemcacheClient(); } }
  4. class Foo { private $db; private $memcache; public function __construct(

    DatabaseConnection $db, MemcacheClient $memcache ) { $this->db = $db; $this->memcache = $memcache; } }
  5. class Foo { private $db; private $memcache; public function __construct(

    DatabaseConnectionInterface $db, CacheClientInterface $memcache ) { $this->db = $db; $this->memcache = $memcache; } }
  6. Optional dependencies

  7. 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; } }
  8. 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!
  9. That's it!

  10. Advantages

  11. $foo = new Foo(); // fatal error $db = new

    DatabaseConnection(); $memcache = new MemcacheClient(); $foo = new Foo($db, $memcache); // works
  12. $db = new PostgreSqlConnection(); $memcache = new MemcacheClient(); $foo =

    new Foo($db, $memcache);
  13. $db = new DatabaseConnectionMock(); $memcache = new CacheClientMock(); $foo =

    new Foo($db, $memcache);
  14. 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
  15. Problems?

  16. // Create a new instance of Foo $db = new

    DatabaseConnection(); $memcache = new MemcacheClient(); $foo = new Foo($db, $memcache);
  17. // Create a new instance of Foo $mysqli = new

    mysqli(); $db = new DatabaseConnection($mysqli); $memcache = new MemcacheClient(); $foo = new Foo($db, $memcache);
  18. // 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);
  19. // Create a new instance of Foo list($host, $user, $pw,

    $db) = getDbConfig(); $mysqli = new mysqli($host, $user, $pw, $db); $db = new DatabaseConnection($mysqli); list($mcHost, $mcPort) = getMemcacheConfig(); $memcache = new MemcacheClient($mcHost, $mcPort); $foo = new Foo($db, $memcache);
  20. Problems? • Construction can turn into a major PITA.

  21. DI Container To the rescue!

  22. DIC Example: Pimple • http://pimple.sensiolabs.org • MIT License • Single-class

    solution • About 50 SLOC
  23. $dic = new Pimple(); $dic['mysqli.config'] = getDbConfig();

  24. $dic = new Pimple(); $dic['mysql.config'] = getDbConfig(); $dic['mysqli'] = new

    mysqli(/* ... */); $dic['db'] = new DatabaseConnection($dic['mysqli']);
  25. Lazy initialisation Watch closely, here's where the magic happens.

  26. None
  27. $dic = new Pimple(); $dic['mysql.config'] = getDbConfig(); $dic['mysqli'] = function($dic)

    { return new mysqli(/* ... */); }; $dic['db'] = function($dic) { return new DatabaseConnection($dic['mysqli']); };
  28. // 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
  29. 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
  30. 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 });
  31. 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
  32. Cool! So now I can do this:

  33. class Controller { private $dic; public function __construct(Pimple $dic) {

    $this->dic = $dic; } public function doSomething() { $foo = $this->dic['foo']; // Do something here } }
  34. NOPE!

  35. $dic = new Pimple(); $controller = new Controller($dic); $controller->doSomething(); //

    InvalidArgumentException: // - Identifier "foo" is not defined.
  36. Stop hiding your dependencies!

  37. class Controller { private $foo; public function __construct(Foo $foo) {

    $this->foo = $foo; } public function doSomething() { // Do something here } }
  38. $dic = new Pimple(); // DIC setup here... $name =

    determineControllerFromRequest(); $controller = $dic[$name]; echo $controller->doSomething();
  39. 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.
  40. Thank you! Any questions?