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?!

  • Almost impossible to write unittest - Mocking isn't possible.
  • Hard to extend/change behaviors
<?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..."

  • External dependencies are explicitly injected into classes
  • Constructor injection
  • Setter injection

 

What's Wrong with It?

  • Cumbersome
  • Unnecessary burden to consumers
  • Domino effect on just one single change
  • Error-prone
  • Dependencies are not always just one depth. They tend to be nested into multiple levels. You sometimes end up creating more than 15 objects just to get one object you need.

Pros

  • Mocking is very easy

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 single object to rule them all

Terms

Service Locator 

  • An object which can resolve your services for you
  • Entire app now depends on the particulars of your 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

  • Suffers from all the problems of factories, except that you now have one global and singleton object that manages all your dependencies, which is a little bit more convenient for consumers.
  • Being a globally shared object, you're not guaranteed to get what you ask.
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

  • A design principle or design philosophy 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
    • MVC frameworks
    • Template Method Pattern
    • Strategy Pattern
    • Dependency Injection

 

Terms

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.

 

Terms

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.
  • Typically called exactly once from the framework and is never called or references by application code.

 

Example

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

  • Inversion of Control (Hollywood Principle - Don't call us, we'll call you)
  • Single Responsibility Principle
  • Dependency Inversion Principle
    • Program to an Interface

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

How do they work?

List of PHP IoC Containers 

deck

By dicontainerslide