PRESENTED BY
JEFF CAROUTH
@jcarouth
Dependency Injection,
Dependency Inversion,
and You
Slide 2
Slide 2 text
Past experience
developing software
shows us coupling is bad.
Slide 3
Slide 3 text
Past experience
developing software
shows us coupling is bad.
But it also suggests
coupling is unavoidable.
Slide 4
Slide 4 text
Coupling and dependencies
Slide 5
Slide 5 text
Dependencies are the other
objects, resources, or functions
any given object uses to
accomplish its responsibility.
Slide 6
Slide 6 text
Common Dependencies
Slide 7
Slide 7 text
Common Dependencies
• Infrastructure – e.g., Database, third party module
Slide 8
Slide 8 text
Common Dependencies
• Infrastructure – e.g., Database, third party module
• Utility objects – e.g., Logger
Slide 9
Slide 9 text
Common Dependencies
• Infrastructure – e.g., Database, third party module
• Utility objects – e.g., Logger
• Environment – e.g., FileSystem, system clock
Slide 10
Slide 10 text
Common Dependencies
• Infrastructure – e.g., Database, third party module
• Utility objects – e.g., Logger
• Environment – e.g., FileSystem, system clock
• Static Method calls – e.g., DataAccess::saveUser($u)
Slide 11
Slide 11 text
class OrderProcessor
{
public function __construct()
{
$this->orderRepository = new MysqlOrderRepository();
$this->logger = Logger::getInstance();
}
public function completeOrder($order)
{
$order->complete = true;
$this->logger->log("Order {$order->id} marked as complete");
$this->orderRepository->save($order);
Mailer::sendOrderCompleteEmail($order);
}
}
Slide 12
Slide 12 text
Dependency Injection
Slide 13
Slide 13 text
As the name suggests,
this literally means to
inject dependencies into
the objects that need
them.
Slide 14
Slide 14 text
DI Techniques
• Constructor Injection
• Setter or Property Injection
• “Parameter Injection”
Slide 15
Slide 15 text
DI Techniques
• Constructor Injection
• Setter or Property Injection
• “Parameter Injection”
Slide 16
Slide 16 text
Constructor Injection
Dependencies are injected upon instantiation
through the constructor.
class Alpha { }
class DependsOnAlpha
{
public function __construct(Alpha $a)
{
/*...*/
}
}
Slide 17
Slide 17 text
Constructor Injection
Makes dependencies explicit.
You cannot modify the dependencies after instantiation.
Discourages violation of the Single Responsibility
Principle.
You might end up with many constructor params.
You could have dependencies stored in the object’s state
which are not needed for all methods.
Slide 18
Slide 18 text
class Database
{
public function where($column, $value)
{
$query = "...";
return $this->connection->fetchAll($query);
}
}
class UserService
{
public function __construct(Database $db)
{
$this->db = $db;
}
public function findByEmail($email)
{
$data = $this->db->where('email', $email);
return new User($data);
}
}
Slide 19
Slide 19 text
Setter Injection
Dependencies are injected through a setter (or
using a public property if you like to be wild.)
class Beta { }
class DependsOnBeta
{
public function setBeta(Beta $b)
{
/*...*/
}
}
Slide 20
Slide 20 text
Setter Injection
Works well with optional dependencies for a class.
Works well with optional dependencies for a class.
Flexible across the object’s lifecycle.
Can leads to invalid state because setters must be called
in certain order.
Slide 21
Slide 21 text
class PaymentGateway
{
public function process(Payment $payment) { /*…*/ }
}
class OrderService
{
private $paymentGateway;
public function setPaymentGateway(PaymentGateway $gateway)
{
$this->paymentGateway = $gateway;
}
public function charge(Order $order)
{
foreach ($order->getPayments() as $payment) {
$this->paymentGateway->process($payment);
}
}
}
Slide 22
Slide 22 text
Parameter “Injection”
Dependencies are passed as parameters to
specific methods which need them.
class Gamma {}
class Delta
{
public function dependsOnGamma(Gamma $c)
{
/*...*/
}
}
Slide 23
Slide 23 text
Parameter “Injection”
Does not clutter your object with refs to collaborators
which are not needed.
Almost everything else about it.
Slide 24
Slide 24 text
class EmailService
{
public function send() { /*…*/ }
}
class Order
{
public function complete(EmailService $mailer)
{
//...
$mailer->send(new OrderCompleteMessage($this));
}
}
Slide 25
Slide 25 text
Dependency Inversion
Principle
Slide 26
Slide 26 text
High level modules
should not depend on low
level modules. Both
should depend on
abstractions.
A dependency injection
container is an object
used to manage
instantiation of other
objects.
Slide 33
Slide 33 text
The power of a DiC
comes in it’s ability to
resolve specific
dependencies and their
dependencies at runtime.
Slide 34
Slide 34 text
You do not need a DiC to
practice dependency
injection.
Slide 35
Slide 35 text
Twittee
class Container {
protected $s=array();
function __set($k, $c) { $this->s[$k]=$c; }
function __get($k) { return $this->s[$k]($this); }
}
A Dependency Injection Container in a Tweet
http://twittee.org
Slide 36
Slide 36 text
Full DI FizzBuzz
Slide 37
Slide 37 text
class FizzBuzz
{
public function __construct(Fizz $fizz, Buzz $buzz)
{
$this->fizz = $fizz;
$this->buzz = $buzz;
}
public function of($number)
{
if ($number % 3 == 0 && $number % 5 == 0) {
return "{$this->fizz}{$this->buzz}";
} else if ($number % 3 == 0) {
return "{$this->fizz}";
} else if ($number % 5 == 0) {
return "{$this->buzz}";
}
return "{$number}";
}
}
Slide 38
Slide 38 text
class FizzBuzz
{
public function __construct(Fizz $fizz, Buzz $buzz)
{
$this->fizz = $fizz;
$this->buzz = $buzz;
}
public function of($number)
{
if ($number % 3 == 0 && $number % 5 == 0) {
return "{$this->fizz}{$this->buzz}";
} else if ($number % 3 == 0) {
return "{$this->fizz}";
} else if ($number % 5 == 0) {
return "{$this->buzz}";
}
return "{$number}";
}
}
Slide 39
Slide 39 text
class Fizz
{
public function __toString()
{
return "Fizz";
}
}
class Buzz
{
public function __toString()
{
return "Buzz";
}
}
Slide 40
Slide 40 text
$fizzbuzz = new FizzBuzz(new Fizz(), new Buzz());
for ($n = 1; $n <= 20; $n++) {
print "FizzBuzz of {$n} is {$fizzbuzz->of($n)}" . PHP_EOL;
}
class Fizz { }
class Buzz { }
class FizzBuzz {
public function __construct(Fizz $fizz, Buzz $buzz) { }
public function of($number) { }
}
Slide 41
Slide 41 text
FizzBuzz of 1 is 1
FizzBuzz of 2 is 2
FizzBuzz of 3 is Fizz
FizzBuzz of 4 is 4
FizzBuzz of 5 is Buzz
FizzBuzz of 6 is Fizz
FizzBuzz of 7 is 7
FizzBuzz of 8 is 8
FizzBuzz of 9 is Fizz
FizzBuzz of 10 is Buzz
FizzBuzz of 11 is 11
FizzBuzz of 12 is Fizz
FizzBuzz of 13 is 13
FizzBuzz of 14 is 14
FizzBuzz of 15 is FizzBuzz
FizzBuzz of 16 is 16
FizzBuzz of 17 is 17
FizzBuzz of 18 is Fizz
FizzBuzz of 19 is 19
FizzBuzz of 20 is Buzz
[jcarouth@fizzlabs] $ php fizzbuzz.php
Slide 42
Slide 42 text
class Fizz { }
class Buzz { }
class FizzBuzz { }
Slide 43
Slide 43 text
class Fizz { }
class Buzz { }
class FizzBuzz { }
class Container {
protected $s=array();
function __set($k, $c) { $this->s[$k]=$c; }
function __get($k) { return $this->s[$k]($this); }
}
Slide 44
Slide 44 text
class Fizz { }
class Buzz { }
class FizzBuzz { }
class Container {
protected $s=array();
function __set($k, $c) { $this->s[$k]=$c; }
function __get($k) { return $this->s[$k]($this); }
}
$container = new Container();
$container->fizz = function() { return new Fizz(); };
$container->buzz = function() { return new Buzz(); };
Slide 45
Slide 45 text
$fizzbuzz = new FizzBuzz($container->fizz, $container->buzz);
for ($n = 1; $n <= 20; $n++) {
print "FizzBuzz of {$n} is {$fizzbuzz->of($n)}" . PHP_EOL;
}
class Fizz { }
class Buzz { }
class FizzBuzz { }
class Container {
protected $s=array();
function __set($k, $c) { $this->s[$k]=$c; }
function __get($k) { return $this->s[$k]($this); }
}
$container = new Container();
$container->fizz = function() { return new Fizz(); };
$container->buzz = function() { return new Buzz(); };
Slide 46
Slide 46 text
FizzBuzz of 1 is 1
FizzBuzz of 2 is 2
FizzBuzz of 3 is Fizz
FizzBuzz of 4 is 4
FizzBuzz of 5 is Buzz
FizzBuzz of 6 is Fizz
FizzBuzz of 7 is 7
FizzBuzz of 8 is 8
FizzBuzz of 9 is Fizz
FizzBuzz of 10 is Buzz
FizzBuzz of 11 is 11
FizzBuzz of 12 is Fizz
FizzBuzz of 13 is 13
FizzBuzz of 14 is 14
FizzBuzz of 15 is FizzBuzz
FizzBuzz of 16 is 16
FizzBuzz of 17 is 17
FizzBuzz of 18 is Fizz
FizzBuzz of 19 is 19
FizzBuzz of 20 is Buzz
[jcarouth@fizzlabs] $ php fizzbuzz.php
Slide 47
Slide 47 text
class Database
{
private $host;
public function __construct(array $config)
{
$this->host = $config['host'];
}
}
class OrderMapper
{
private $db;
public function __construct(Database $db)
{
$this->db = $db;
}
}
Slide 48
Slide 48 text
class Database {
private $host;
public function __construct(array $config) { }
}
class OrderMapper {
private $db;
public function __construct(Database $db) { }
}
$orderMapper = new OrderMapper(
new Database(['host' => ‘localhost'])
);
Slide 49
Slide 49 text
class Database {
private $host;
public function __construct(array $config) { }
}
class OrderMapper {
private $db;
public function __construct(Database $db) { }
}
$di = new Container();
Slide 50
Slide 50 text
class Database {
private $host;
public function __construct(array $config) { }
}
class OrderMapper {
private $db;
public function __construct(Database $db) { }
}
$di = new Container();
$di->dbConfig = function() { return ['host' => 'localhost']; };
Slide 51
Slide 51 text
class Database {
private $host;
public function __construct(array $config) { }
}
class OrderMapper {
private $db;
public function __construct(Database $db) { }
}
$di = new Container();
$di->dbConfig = function() { return ['host' => 'localhost']; };
$di->database = function($c) { return new Database($c->dbConfig); };
Slide 52
Slide 52 text
class Database {
private $host;
public function __construct(array $config) { }
}
class OrderMapper {
private $db;
public function __construct(Database $db) { }
}
$di = new Container();
$di->dbConfig = function() { return ['host' => 'localhost']; };
$di->database = function($c) { return new Database($c->dbConfig); };
$orderMapper = new OrderMapper($di->database);
var_dump($orderMapper);
Aura.di
A Dependency Injection Container in a
little more than a Tweet
https://github.com/auraphp/Aura.Di
Slide 58
Slide 58 text
class OrderProcessor
{
public function __construct()
{
$this->orderRepository = new MySqlOrderRepository();
$this->logger = Logger::getInstance();
}
public function completeOrder($order)
{
$order->complete = true;
$this->logger->log("Order {$order->id} marked as complete");
$this->orderRepository->save($order);
Mailer::sendOrderCompleteEmail($order);
}
}
Slide 59
Slide 59 text
namespace AwesomeCart;
interface OrderRepository
{
public function save(Order $order);
}
class InMemoryOrderRepository implements OrderRepository {}
Refactor: Extract Interface
Slide 60
Slide 60 text
namespace AwesomeCart;
interface OrderRepository
{
public function save(Order $order);
}
class InMemoryOrderRepository implements OrderRepository {}
class MySqlOrderRepository implements OrderRepository {}
Refactor: Extract Interface
Slide 61
Slide 61 text
namespace AwesomeCart;
class OrderProcessor
{
public function __construct(OrderRepository $orderRepository)
{
$this->orderRepository = $orderRepository;
$this->logger = Logger::getInstance();
}
public function completeOrder($order)
{
$order->complete = true;
$this->logger->log("Order {$order->id} marked as complete");
$this->orderRepository->save($order);
Mailer::sendOrderCompleteEmail($order);
}
}
Slide 62
Slide 62 text
namespace AwesomeCart;
class OrderProcessor
{
public function __construct(OrderRepository $orderRepository)
{
$this->orderRepository = $orderRepository;
$this->logger = Logger::getInstance();
}
public function completeOrder($order)
{
$order->complete = true;
$this->logger->log("Order {$order->id} marked as complete");
$this->orderRepository->save($order);
Mailer::sendOrderCompleteEmail($order);
}
}
Slide 63
Slide 63 text
namespace AwesomeCart;
use Aura\Di\Container;
use Aura\Di\Factory;
$di = new Container(new Factory());
Slide 64
Slide 64 text
namespace AwesomeCart;
use Aura\Di\Container;
use Aura\Di\Factory;
$di = new Container(new Factory());
$di->params['AwesomeCart\OrderProcessor']['orderRepository'] =
$di->lazyNew('AwesomeCart\InMemoryOrderRepository');
Slide 65
Slide 65 text
namespace AwesomeCart;
use Aura\Di\Container;
use Aura\Di\Factory;
$di = new Container(new Factory());
$di->params['AwesomeCart\OrderProcessor']['orderRepository'] =
$di->lazyNew('AwesomeCard\InMemoryOrderRepository');
$di->set(
'order_processor',
$di->lazyNew(‘AwesomeCart\OrderProcessor')
);
Slide 66
Slide 66 text
namespace AwesomeCart;
use Aura\Di\Container;
use Aura\Di\Factory;
$di = new Container(new Factory());
$di->params['AwesomeCart\OrderProcessor']['orderRepository'] =
$di->lazyNew('AwesomeCard\InMemoryOrderRepository');
$di->set(
'order_processor',
$di->lazyNew(‘AwesomeCart\OrderProcessor')
);
$orderProcessor = $di->get('order_processor');
Slide 67
Slide 67 text
Using a Container
In a typical application you will use the container from within
your “controllers” and use them to inject dependencies into
your “models.”
Beware Service Location
“…please note that this package is intended for use
as a dependency injection system, not as a service
locator system. If you use it as a service locator,
that's bad, and you should feel bad.”
– Aura.DI Readme
Slide 77
Slide 77 text
class Foo { }
class Bar {
protected $foo;
public function __construct(Container $c)
{
$this->foo = $c->foo;
}
}
$c = new Container();
$c->foo = function() { return new Foo(); };
$bar = new Bar($c);
Slide 78
Slide 78 text
Recap
Slide 79
Slide 79 text
Recap
• Dependencies in code are unavoidable, but that
doesn’t mean they need to be unmanageable.
Slide 80
Slide 80 text
Recap
• Dependencies in code are unavoidable, but that
doesn’t mean they need to be unmanageable.
• Inverting dependencies is a way to create more
flexible software.
Slide 81
Slide 81 text
Recap
• Dependencies in code are unavoidable, but that
doesn’t mean they need to be unmanageable.
• Inverting dependencies is a way to create more
flexible software.
• DI containers are a helpful tool for maintaining
single responsibility within objects.
Slide 82
Slide 82 text
BONUS!
Slide 83
Slide 83 text
A testing Example
This example shows why having dependencies
hidden becomes problematic when using the code.
To illustrate that point we will test a simple piece of
functionality.
Slide 84
Slide 84 text
class GuestRepository
{
public function __construct()
{
}
public function delete(Guest $guest)
{
/*…*/
}
}
Slide 85
Slide 85 text
class GuestRepository
{
public function __construct()
{
$this->db = new \PDO('...');
}
public function delete(Guest $guest)
{
$purchaseRepository = new PurchaseRepository();
$purchasesAssignedToGuest = $purchaseRepository->findByGuest($guest);
if (count($purchasesAssignedToGuest) > 0) {
throw new \Exception('Cannot delete Guest since it is assigned to purchases');
}
$deleteQuery = "DELETE FROM guest WHERE id = :id";
$statement = $this->db->prepare($deleteQuery);
$statement->execute(array('id' => $guest->id));
return $statement->rowCount() < 1;
}
}
Slide 86
Slide 86 text
class PurchaseRepository
{
public function __construct()
{
$this->db = new \PDO('...');
}
public function findByGuest($guest)
{
$query = 'SELECT * FROM purchases WHERE assigned_to = :guest_id';
$statement = $this->db->prepare($query);
$statement->execute();
$purchases = $statement->fetchAll();
$purchaseCollection = array();
foreach ($purchases $result) {
$purchase = new Purchase();
$purchase = $this->mapDatabaseResultToPurchase($purchase, $result);
$purchaseCollection[] = $purchase;
}
return $purchaseCollection;
}
}
Slide 87
Slide 87 text
$guestRepository = new GuestRepository();
$guest = new Guest();
$guest->id = 8945221;
$guestRepository->delete($guest);
Slide 88
Slide 88 text
class GuestRepositoryTest extends \PHPUnit_Framework_TestCase
{
public function testCannotDeleteGuestWhenAssignedToPurchases()
{
$sql = "INSERT INTO guests VALUES(1, 'Test User')";
$this->db->prepare($sql)->execute();
$sql = "INSERT INTO purchases VALUES(1, 1), (2, 1)";
$this->db->prepare($sql)->execute();
$guestToDelete = new Guest();
$guestToDelete->id = 1;
$repository = new GuestRepository();
$this->setExpectedException('\Exception');
$repository->delete($guestToDelete);
}
}
Slide 89
Slide 89 text
class GuestRepository
{
public function __construct()
{
$this->db = new \PDO('...');
}
public function delete(Guest $guest)
{
$purchaseRepository = new PurchaseRepository();
$purchasesAssignedToGuest = $purhaseRepository->findByGuest($guest);
if (count($purchasesAssignedToGuest) > 0) {
throw new \Exception('Cannot delete Guest since it is assigned to purchases');
}
$deleteQuery = "DELETE FROM guest WHERE id = :id";
$statement = $this->db->prepare($deleteQuery);
$statement->execute(array('id' => $guest->id));
return $statement->rowCount() < 1;
}
}
Slide 90
Slide 90 text
class GuestRepository
{
private $db;
public function __construct(\PDO $db)
{
$this->db = $db;
}
public function delete(Guest $guest) {}
}
Slide 91
Slide 91 text
class GuestRepository
{
private $db;
public function __construct(\PDO $db)
{
$this->db = $db;
}
public function delete(Guest $guest) {}
}
$guestRepository = new GuestRepository(new \PDO('...'));
$guest = new Guest();
$guest->id = 8945221;
$guestRepository->delete($guest);
Slide 92
Slide 92 text
class GuestRepository
{
public function __construct(\PDO $db)
{
$this->db = $db;
}
public function delete(Guest $guest)
{
$purchaseRepository = new PurchaseRepository();
$purchasesAssignedToGuest = $purchaseRepository->findByGuest($guest);
if (count($purchasesAssignedToGuest) > 0) {
throw new \Exception('Cannot delete Guest since it is assigned to purchases');
}
$deleteQuery = "DELETE FROM guest WHERE id = :id";
$statement = $this->db->prepare($deleteQuery);
$statement->execute(array('id' => $guest->id));
return $statement->rowCount() < 1;
}
}
Slide 93
Slide 93 text
class GuestRepository
{
private $db;
private $purchaseRepository;
public function __construct(\PDO $db, PurchaseRepository $purchaseRepository)
{
$this->db = $db;
$this->purchaseRepository = $purchaseRepository;
}
public function delete(Guest $guest)
{
$purchasesAssignedToGuest = $this->purchaseRepository->findByGuest($guest);
if (count($purchasesAssignedToGuest) > 0) {
throw new \Exception('Cannot delete Guest since it is assigned to purchases');
}
//...snip...
}
}
Slide 94
Slide 94 text
$guestRepository = new GuestRepository(new \PDO('...'), new PurchaseRepository());
$guest = new Guest();
$guest->id = 8945221;
$guestRepository->delete($guest);
Slide 95
Slide 95 text
class PurchaseRepository
{
public function __construct()
{
$this->db = new \PDO('...');
}
public function findByGuest($guest)
{
$query = 'SELECT * FROM purchases WHERE assigned_to = :guest_id';
$statement = $this->db->prepare($query);
$statement->execute();
$purchases = $statement->fetchAll();
$purchaseCollection = array();
foreach ($purchases $result) {
$purchase = new Purchase();
$purchase = $this->mapDatabaseResultToPurchase($purchase, $result);
$purchaseCollection[] = $purchase;
}
return $purchaseCollection;
}
}
Slide 96
Slide 96 text
class PurchaseRepository
{
public function __construct(\PDO $db)
{
$this->db = $db;
}
public function findByGuest($guest)
{
$query = 'SELECT * FROM purchases WHERE assigned_to = :guest_id';
$statement = $this->db->prepare($query);
$statement->execute();
$purchases = $statement->fetchAll();
$purchaseCollection = array();
foreach ($purchases $result) {
$purchase = new Purchase();
$purchase = $this->mapDatabaseResultToPurchase($purchase, $result);
$purchaseCollection[] = $purchase;
}
return $purchaseCollection;
}
}
Slide 97
Slide 97 text
$db = new \PDO('...');
$guestRepository = new GuestRepository($db, new PurchaseRepository($db));
$guest = new Guest();
$guest->id = 8945221;
$guestRepository->delete($guest);
Slide 98
Slide 98 text
class GuestRepositoryTest extends \PHPUnit_Framework_TestCase
{
public function testCannotDeleteGuestWhenAssignedToPurchases()
{
$sql = "INSERT INTO guests VALUES(1, 'Test User')";
$this->db->prepare($sql)->execute();
$purchaseRepository = $this->getMockBuilder('PurchaseRepository')
->disableOriginalConstructor()
->getMock();
$purchaseRepository->expects($this->once())
->method('')
->will(This->returnValue(array($this->getMock('Purchase'))));
$guestToDelete = new Guest();
$guestToDelete->id = 1;
$repository = new GuestRepository($this->db, $purchaseRepository);
$this->setExpectedException('\Exception');
$repository->delete($guestToDelete);
}
}