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

IoC Containers 

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