Slide 1

Slide 1 text

Dependency Injection

Slide 2

Slide 2 text

inject dependencies!

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

class Foo { private $db; private $memcache; public function __construct( DatabaseConnectionInterface $db, CacheClientInterface $memcache ) { $this->db = $db; $this->memcache = $memcache; } }

Slide 6

Slide 6 text

Optional dependencies

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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!

Slide 9

Slide 9 text

That's it!

Slide 10

Slide 10 text

Advantages

Slide 11

Slide 11 text

$foo = new Foo(); // fatal error $db = new DatabaseConnection(); $memcache = new MemcacheClient(); $foo = new Foo($db, $memcache); // works

Slide 12

Slide 12 text

$db = new PostgreSqlConnection(); $memcache = new MemcacheClient(); $foo = new Foo($db, $memcache);

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Problems?

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Problems? ● Construction can turn into a major PITA.

Slide 21

Slide 21 text

DI Container To the rescue!

Slide 22

Slide 22 text

DIC Example: Pimple ● http://pimple.sensiolabs.org ● MIT License ● Single-class solution ● About 50 SLOC

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

$dic = new Pimple(); $dic['mysql.config'] = getDbConfig(); $dic['mysqli'] = new mysqli(/* ... */); $dic['db'] = new DatabaseConnection($dic['mysqli']);

Slide 25

Slide 25 text

Lazy initialisation Watch closely, here's where the magic happens.

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

$dic = new Pimple(); $dic['mysql.config'] = getDbConfig(); $dic['mysqli'] = function($dic) { return new mysqli(/* ... */); }; $dic['db'] = function($dic) { return new DatabaseConnection($dic['mysqli']); };

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Cool! So now I can do this:

Slide 33

Slide 33 text

class Controller { private $dic; public function __construct(Pimple $dic) { $this->dic = $dic; } public function doSomething() { $foo = $this->dic['foo']; // Do something here } }

Slide 34

Slide 34 text

NOPE!

Slide 35

Slide 35 text

$dic = new Pimple(); $controller = new Controller($dic); $controller->doSomething(); // InvalidArgumentException: // - Identifier "foo" is not defined.

Slide 36

Slide 36 text

Stop hiding your dependencies!

Slide 37

Slide 37 text

class Controller { private $foo; public function __construct(Foo $foo) { $this->foo = $foo; } public function doSomething() { // Do something here } }

Slide 38

Slide 38 text

$dic = new Pimple(); // DIC setup here... $name = determineControllerFromRequest(); $controller = $dic[$name]; echo $controller->doSomething();

Slide 39

Slide 39 text

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.

Slide 40

Slide 40 text

Thank you! Any questions?