Better Code with IoC
How We Manage Dependencies
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?
- Unit testing is difficult
- Mocking objects is nearly impossible
- Hard to extend/change behavior
- Logger needs to know how to create a new kind of DataStore
- Impossible to work in parallel as a team
- Need concrete class before you can write code or tests
Dependency Injection (DI)
http://martinfowler.com/articles/injection.html
"Inversion of Control is too generic a term, and thus people find it confusing..."
- External dependencies are explicitly injected into classes
- Constructor injection
- Setter injection
- Interface injection
<?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.
What's Wrong with It?
- Cumbersome
- Unnecessary burden to consumers
- Deeply nested dependencies are difficult to maintain
- Domino effect on just one single change
- Error-prone
Pros
- Mocking is very easy
- Class dependencies are very clear
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?
- Unnecessary coupling on factory objects all over your codebase.
- Even factories depend on factories in many cases.
- Still not easy to test
- Mocking is possible, but cumbersome and hacky
- Still has domino effect on dependency changes
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 object to rule them all
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
- Basically a variation on factories
- A little more convenient for your consumers
- Being a globally shared object, you're not guaranteed to get what you ask
- Difficult to track dependency bugs
- Entire app is tightly coupled to the ServiceLocator implementation
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');
}
}
Inversion of Control
http://martinfowler.com/bliki/InversionOfControl.html
- A design principle in which control of the program is handed to your code by a broader framework.
- "Don't call us, we'll call you."
- Most frameworks are a form of IoC
-
Examples
- Event-driven code
- Node.js-style Callbacks
- MVC frameworks
- Template Method Pattern
- Strategy Pattern
- Dependency Injection
Dependency Inversion Principle
- A design principle in which high level modules do not depend on low level modules, but instead both depend on abstractions, and that these abstractions do not depend on implementation details - they are concerned with what is done, not how it is done.
-
Examples
- High level modules depend on interfaces, low level modules implement these interfaces.
- Abstract components into separate services or libraries, which depend on eachother through common interfaces.
Traditional Model
Using Dependency Inversion
IoC Container
- An IoC Framework which employs Dependency Injection
- A container object that exists above the classes that get created, and automatically injects dependencies through Dependency Injection.
- Called exactly once from the framework at the composition root, and is never referenced by application code.
Example
interface IDataStream {}
interface IEmailSender {}
interface IConfigurationReader {}
interface ILogger {}
interface IEmailCredentialsProvider {}
interface IEmailSettingsProvider {}
class SomeDataStream implements IDataStream {}
class EmailSender implements IEmailSender {
public function __construct(
IEmailCredentialsProvider $credentials,
IEmailSettingsProvider $settings
) {
// Do stuff
}
}
class EmailCredentialsProvider implements IEmailCredentialsProvider {}
class EmailSettingsProvider implements IEmailSettingsProvider {}
class ConfigurationReader implements ConfigurationReader {}
class FileSystemLogger implements ILogger {}
class NotificationEngine {
public function __construct(
IDataStream $stream,
IEmailSender $emailer,
IConfigurationReader $config,
ILogger $logger
) {
// Do stuff
}
}
Example
class NotificationEngine {
public function __construct(
IDataStream $stream,
IEmailSender $emailer,
IConfigurationReader $config,
ILogger $logger
) {
// Do stuff
}
}
// Framework Code
$container = new Container;
// Run service providers
$container->bind('ILogger', 'FileSystemLogger');
$container->bind('IDataStream', 'SomeDataStream');
$container->bind('IConfigurationReader', 'ConfigurationReader');
$container->bind('ILogger', 'FileSystemLogger');
$container->bind('IEmailCredentialsProvider', 'EmailCredentialsProvider');
$container->bind('IEmailSettingsProvider', 'EmailSettingsProvider');
// Composition Root, creates Dependency Graph
$container->make('NotificationEngine');
Example
Composition Root
Where does it happen?
"■ A console application is an executable (.exe) with a Main method.
■ An ASP.NET web application is a library (.dll) with an Application_Start event
handler in its Global.asax.
■ A WPF application is an executable (.exe) with an App.xaml file." (Dependency Injection in .NET, 2011, p.77)
The Composition Root is the single place in your application where you use an IoC container to compose a dependency graph of a class by directly talking to the container, decoupling all the other objects in your application from the container.
A possible Composition Root in an MVC framework would be a route, request class, or controller
IoC Containers Enable Clean Code
- Inversion of Control/Hollywood Principle
- Single Responsibility Principle
- Dependency Inversion Principle
- Program to an Interface
- No framework dependency
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
Sample Implementation
- Simple IoC Container Example: https://gist.github.com/moon0326/6421728c45a87a622ed4
- PHP-DI in Kohana: https://github.com/moon0326/phpdi-kohana-sample
IoC Containers
-
PHP
- 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)
-
.NET
- Ninject (http://www.ninject.org)
- Unity (https://unity.codeplex.com)
-
Swift/Objective C
- Typhoon (http://typhoonframework.org)
What's Next? S.O.L.I.D.
There ain't no such thing as a free lunch.
Writing code in a S.O.L.I.D. way creates classes with a single responsibility, high testability and reusability in an abstracted form through interfaces. It sounds good, but it also means that we create extra classes than writing code without S.O.L.I.D. in mind and we now have to manage those extra classes. That's when an IoC container shines, taking the "D" in S.O.L.I.D. IoC containers handle abstractions and dependencies we create when writing a good S.O.L.I.D. codebase.
S: Single responsibility principle
O: Open/closed principle
L: Liskov substitution principle
I: Interface segregation principle
D: Dependency inversion principle
Better Code with IoC
By dicontainerslide
Better Code with IoC
- 602