Moon Kyong & Gabriel Maybrun
Demand Media, 2015-02-11
<?php
class FooClass
{
public function __construct() {
$this->logger = new Logger;
}
public function doFoo() {
$this->logger->log("I did some foo");
}
}The old way was `new`
<?php
class Logger
{
public function __construct() {
$this->dataStore = new MySqlLoggerDataStore();
}
public function log($message) {
$this->dataStore->log($message);
}
}Creating objects with the new operator.
What's wrong with it?!
<?php
class aClass
{
private $logger;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
public function doSomething()
{
$this->logger->log("constructor! constructor! constructor!");
}
}
// Client Code
new aClass(new Logger(new MySqlLogger));Dependency Injection
Let the clients pass all the dependencies by hand.
Terms
Dependency Injection
http://martinfowler.com/articles/injection.html
"Inversion of Control is too generic a term, and thus people find it confusing..."
What's Wrong with It?
Pros
Factory
<?php
class LoggerFactory
{
public function createMySqlLogger()
{
return new Logger(new MySqlLogDataStore);
}
}<?php
class aClass
{
public function doSomething()
{
$logger = (new LoggerFactory())->makeMySqlLogger();
$logger->log("factory! factory! factory!");
}
}
Creating an object and its dependencies through a 3rd party object that knows about dependencies
What's Wrong with It?
Service Locator
class ServiceLocator
{
private $services;
public function register($name, $callable)
{
$this->services[$name] = $callable;
}
public function get($name)
{
return $this->services[$name]();
}
}
$serviceLocator = new ServiceLocator();
$serviceLocator->register('logger', function () {
return new Logger(new MySqlLogDataStore());
});
$serviceLocator->register('UserModel', function() {
return new Model('users', new MySqlDBConnection('username', 'password');
});In bootstrap process
One single object to rule them all
Terms
Service Locator
Service Location with Service Locator
class aClass
{
private $serviceLocator;
public function __constructor(ServiceLocator $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
public function doSomething()
{
$logger = $this->serviceLocator->get('Logger');
$logger->log('locate! locate!');
}
}
class anotherClass
{
private $serviceLocator;
public function __constructor(ServiceLocator $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
public function save()
{
$user = $this->serviceLocator->get('UserModel');
$user->name = 'name';
$user->save();
}
}
Pros/Cons
SL::register('Users', function() {
return new UserRepository(
SL::get('DatabaseConnection') // SQL
);
});You bind a service somewhere in the application
Someone else bind a service somewhere in the application after your binding
SL::register('Users', function() {
return new UserSearch(
SL::get('SearchBackend') // Solr
);
});class aClass
{
public function doSomething()
{
// What am I getting?
$users = SL::get('Users');
}
}
Terms
Inversion of Control
http://martinfowler.com/bliki/InversionOfControl.html
Terms
Dependency Inversion Principle
Terms
IoC Container
abstract class LoggerBackend {
abstract public function write($message);
}
class RedisLogger extends LoggerBackend {
public function __construct(Redis $redis) {
$this->redis = $redis;
}
public function write($message) {
$this->redis->zadd('logs', time(), $message);
}
}
class Logger {
public function __construct(LoggerBackendInterface $connector) {
$this->connector = $connector;
}
public function log($level, $message) {
$this->connector->write("$level: $message");
}
}
class Application {
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
public function doSomeFoo() {
$this->logger->log('INFO', 'Someone called doSomeFoo');
}
}
$container = new Container;
$container->bind('LoggerBackendInterface', 'RedisLogger');
$container->bind('LoggerInterface', 'Logger');
$app = $container->make('Application');
$app->doSomeFoo();Design Principles
Do I Inject Everything?
Objects without any side effects don't have to be injected.
Value Objects
class EmailAddress
{
private $emailAddress;
public function __construct($emailAddress)
{
$this->assertEmailAddress($emailAddress);
$this->emailAddress = $emailAddress;
}
private function assertEmailAddress($emailAddress)
{
if (!filter_var($emailAddress, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException(
"{$emailAddress} is in an invalid email format."
);
}
}
public function __toString()
{
return $this->emailAddress;
}
}
Do I Inject Everything?
Objects without any side effects don't have to be injected.
class aClass
{
private $repository;
public function __construct(UserRepository $repository)
{
$this->repository = $repository;
}
public function createUser($email)
{
$user = new User(new Email($this->email));
$this->repository->persist($user);
}
}
Using Value Object
List of PHP IoC Containers
PHP-DI - http://php-di.org/
Aura.Di - http://auraphp.com/packages/Aura.Di/
Zend\Di - http://framework.zend.com/manual/current/en/modules/zend.di.introduction.html
Symfony\DedendencyInjection - http://symfony.com/doc/current/components/dependency_injection/introduction.html