Matthew Weier O'Phinney (@mwop)
SunshinePHP, February 2015
class Cuppa
{
public function prepare();
}
class Cuppa
{
public function prepare()
{
$packet = new TeaBag();
}
}
class Cuppa
{
public function prepare()
{
$packet = new TeaBag();
$liquid = new HotWater();
$packet->dissolve($liquid);
}
}
class HotWater
{
private $water;
private $kettle;
public function __construct()
{
$this->water = new Water();
$this->kettle = new Kettle();
}
}
class Cuppa
{
private $drinkPacket;
private $hotLiquid;
public function prepare();
}
class Cuppa
{
public function prepare()
{
$packet = Registry::get('DrinkPacket');
}
}
class Cuppa
{
public function prepare()
{
$this->packet = Registry::get('DrinkPacket');
$this->liquid = Registry::get('HotLiquid');
$this->packet->dissolve($this->liquid);
return $this;
}
}
class HotWater implements HotLiquidInterface
{
private $water;
private $kettle;
public function __construct()
{
$this->water = Registry::get('Water');
$this->kettle = Registry::get('Kettle');
}
}
class Cuppa
{
private $drinkPacket;
private $hotLiquid;
public function __construct(Bag $bag)
{
$this->drinkPacket = $bag->get('DrinkPacket');
$this->hotLiquid = $bag->get('HotLiquid');
}
public function prepare() { /* ... */ }
}
class HotWater implements HotLiquidInterface
{
private $water;
private $kettle;
public function __construct(Bag $bag)
{
$this->water = $bag->get('Water');
$this->kettle = $bag->get('Kettle');
}
}
How does the HotWater get the container?
$bag->put('HotWater', function () use ($bag) {
return new HotWater($bag);
});
class Cuppa
{
private $drinkPacket;
private $hotLiquid;
public function __construct(
DrinkPacketInterface $drinkPacket,
HotLiquidInterface $hotLiquid,
) {
$this->drinkPacket = $drinkPacket;
$this->hotLiquid = $hotLiquid;
}
public function prepare() { /* ... */ }
}
class HotWater implements HotLiquidInterface
{
private $water;
private $kettle;
public function __construct(
Water $water,
HeatingInterface $heater
) {
$this->water = $water;
$this->kettle = $heater;
}
}
How is it all prepared?
$hotWater = new HotWater(
new Water(),
new Kettle()
);
$cuppa = new Cuppa(new TeaBag(), $hotWater);
This looks like work.
Dependency Injection is the practice
of coding classes and functions such that they
receive their dependencies as
arguments.
Do not fetch or build dependencies;
have others give them.
public function __construct(
DatabaseInterface $db,
HttpClientInterface $http,
CacheInterface $cache,
LogInterface $logger
) {
/* ... */
}
$ballOfMud = new BallOfMud(
$database,
$httpClient,
$cache,
$logger
);
public function setDatabase(DatabaseInterface $db) { }
public function setHttpClient(HttpClientInterface $http) { }
public function setCache(CacheInterface $cache) { }
public function setLogger(LogInterface $logger) { }
$ballOfMud = (new BallOfMud())
->setDatabase($database)
->setHttpClient($httpClient)
->setCache($cache)
->setLogger($logger);
interface EventManagerAwareInterface {
public function setEventManager(
EventManagerInterface $events
);
}
class Foo implements EventManagerAwareInterface {
public function setEventManager(
EventManagerInterface $events
) {
$this->events = $events;
}
}
if ($instance instanceof EventManagerAwareInterface) {
$instance->setEventManager($events);
}
$db = new Database;
$http = new HttpClient;
$cache = new Cache;
$logger = new Logger;
$awesome = new Awesome($db, $http, $cache, $Logger);
A normal web workflow:
Outside in:
class Controller
{
public function action()
{
$model = new Model();
$model->execute();
}
}
class Model
{
public function execute()
{
$db = new Db();
$db->query();
}
}
class Controller
{
public function action()
{
$model = Registry::get('Model');
$model->execute();
}
}
class Model
{
public function execute()
{
$db = Registry::get('Db');
$db->query();
}
}
class Controller implements ContainerAwareInterface {
private $container;
public function setContainer($container) { /* ... */ }
public function action() {
$model = $this->container->get('Model');
$model->execute();
}
}
class Model implements ContainerAwareInterface {
private $container;
public function setContainer($container) { /* ... */ }
public function execute() {
$db = $this->container->get('Db');
$db->query();
}
}
class Controller
{
private $model;
public function __construct(
ModelInterface $model
) {
$this->model = $model;
}
public function action()
{
$this->model->execute();
}
}
class Model
{
private $db;
public function __construct(
DatabaseInterface $db
) {
$this->db = $db;
}
public function execute()
{
$this->db->query();
}
}
We have to delegate instantiation.
Inversion of Control is the practice of
delegating instantiation
of your objects to another process.
$container->set('Controller', function ($container) {
return new Controller($container->get('ModelInterface'));
});
$container->set('ModelInterface', function ($container) {
return new Model($container->get('DatabaseInterface'));
});
$container->set('DatabaseInterface', function ($container) {
return new Database;
});
$controller = $container->get('Controller');
IoC means we delegate instantiation,
and thus dependency resolution,
to the container.
Automated, using one or more of...
$controller = $container->get('Controller');
/* And it magically identifies your dependencies
and injects them for you. And any of their
dependencies. */
Maps types to factories that can create the implementation
$container->set('Controller', function ($container) {
return new Controller($container->get('ModelInterface'));
});
// which needs:
$container->set('ModelInterface', function ($container) {
return new Model($container->get('DatabaseInterface'));
});
// which needs:
$container->set('DatabaseInterface', function ($container) {
return new Database();
});
// Which means this now works:
$controller = $container->get('Controller');
if you use...
Matthew Weier O'Phinney (@mwop)