The 7 deadly sins of
Dependency Injection

Stephan Hochdörfer // 21.02.2014

About me

  • Stephan Hochdörfer
  • Head of IT, bitExpert AG (Mannheim, Germany)
  • S.Hochdoerfer@bitExpert.de
  • @shochdoerfer

  • #PHP, #DevOps, #Automation

T-Shirt anyone?

Think first!



The 7 deadly sins


  • Lust
  • Greed
  • Gluttony
  • Sloth
  • Wrath
  • Envy
  • Pride

Lust

(c) TchaikovskyCF

A simple controller...

class MyController
{
    public function myAction()
    {
        return new Response('Ok!');
    }
}

...evolving over time

class MyController
{
    protected $service;

    public function __construct(MyService $service)
    {
        $this->service = $service;
    }

    public function myAction()
    {
        $this->service->doSomething();

        return new Response('Ok!');
    }
}

...evolving over time

class MyController
{
    protected $service;
    protected $logger;

    public function __construct(MyService $service, MyLogger $logger)
    {
        $this->service = $service;
        $this->logger = $logger;
    }

    public function myAction()
    {
        $this->logger->debug('myAction running');
        $this->service->doSomething();

        return new Response('Ok!');
    }
}

Can a ServiceLocator help?

class MyController
{
    protected $service;
    protected $logger;

    public function __construct(Container $container)
    {
        $this->service = $container->get('service');
        $this->logger = $container->get('logger');
    }

    public function myAction()
    {
        $this->logger->debug('myAction running');
        $this->service->doSomething();

        return new Response('Ok!');
    }
}

...or some *aware magic?

class MyController implements ContainerAware
{
    protected $service;
    protected $logger;

    public function setContainer(Container $container)
    {
        $this->service = $container->get('service');
        $this->logger = $container->get('logger');
    }

    public function myAction()
    {
        $this->logger->debug('myAction running');
        $this->service->doSomething();

        return new Response('Ok!');
    }
}

Avoid "ContainerAware"




Establish boundaries

Greed

(c) TchaikovskyCF

Greedy controllers...

class UserController
{
    protected $auth;
    protected $user;

    public function __construct(AuthService $auth, UserService $user)
    {
        $this->auth = $auth;
        $this->user = $user;
    }

    public function authAction(Request $r)
    {
        return $this->auth->login($r->get('user'), $r->get('pass'));
    }

    public function listUserAction()
    {
        return $this->user->readAll();
    }
}

...should be restructured

class AuthController
{
    // [...]

    public function authAction(Request $r)
    {
        return $this->auth->login($r->get('user'), $r->get('pass'));
    }
}

class UserController
{
    // [...]

    public function listUserAction()
    {
        return $this->user->readAll();
    }
}

Architecture is tool agnostic

ServiceLocator will not fix it

Depend on what you need...

Depend on what you need...

class LoginController
{
    protected $auth;

    public function __construct(AuthService $auth)
    {
        $this->auth = $auth;
    }

    public function authAction(Request $r)
    {
        return $this->auth->login($r->get('user'), $r->get('pass'));
    }
}

Dependency Inversion



« High level modules should not depend upon
low level modules. Both should depend upon
abstractions.

Abstractions should not depend upon details.
Details should depend upon abstractions. »
- Robert C. Martin

Depend on abstractions

class LoginController
{
    protected $auth;

    public function __construct(AuthInterface $auth)
    {
        $this->auth = $auth;
    }

    public function authAction(Request $r)
    {
        return $this->auth->login($r->get('user'), $r->get('pass'));
    }
}

Gluttony

(c) TchaikovskyCF

Too many implementations

No common standard!



I have a dream

Are we there yet?

Sloth

(c) TchaikovskyCF

(Public) Property injection

class UserController
{
    /**
     * @var Logger
     * @Inject
     */
    public $logger;

    public function authAction(Request $r)
    {
        if (null !== $this->logger) {
            $this->logger->debug('Auth action running...');
        }
    }
}

(Protected) Property injection

class UserController
{
    /**
     * @var Logger
     * @Inject
     */
    protected $logger;

    public function authAction(Request $r)
    {
        if (null !== $this->logger) {
            $this->logger->debug('Auth action running...');
        }
    }
}







« NEIN! NEIN! NEIN! » - David Zülke

Constructor injection

class UserController
{
    protected $auth;

    public function __construct(AuthInterface $auth)
    {
        $this->auth = $auth;
    }
}

Setter injection

class UserController
{
    protected $logger;

    public function setLogger(Logger $logger)
    {
        $this->logger = $logger;
    }

    public function authAction(Request $r)
    {
        if (null !== $this->logger) {
            $this->logger->debug('Auth action running...');
        }
    }
}

Setter injection

class UserController
{
    protected $logger;

    public function setLogger(Logger $logger)
    {
        $this->logger = $logger;
    }

    public function authAction(Request $r)
    {
        if ($this->logger instanceof Logger) {
            $this->logger->debug('Auth action running...');
        }
    }
}

Null checks everywhere!

Circular dependency hell

class UserService
{
    protected $auth;

    public function __construct(AuthInterface $auth)
    {
        $this->auth = $auth;
    }
}

class SimpleAuthService implements AuthInterface
{
    protected $userService;

    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }
}

Circular dependency hell

class UserService
{
    protected $auth;

    public function __construct(AuthInterface $auth)
    {
        $this->auth = $auth;
    }
}

class SimpleAuthService implements AuthInterface
{
    protected $userService;

    public function setUserService(UserService $userService)
    {
        $this->userService = $userService;
    }
}

Avoid circular dependencies!

Interface injection

interface LoggerAware
{
    public function setLogger(Logger $logger);
}
class UserController implements LoggerAware
{
    protected $logger;

    public function setLogger(Logger $logger)
    {
        $this->logger = $logger;
    }

    public function authAction(Request $r)
    {
        if (null !== $this->logger) {
            $this->logger->debug('Auth action running...');
        }
    }
}

Implicit wiring

Default dependencies?

class AuthService implements AuthInterface
{
    protected $provider;

    public function __construct(AuthProviderInterface $provider)
    {
        $this->provider = $provider;
    }
}

Default dependencies?

class AuthService implements AuthInterface
{
    protected $provider;

    public function __construct(AuthProviderInterface $provider)
    {
        $this->provider = $provider;
    }

    public static function getInstance()
    {
        return new AuthService(new DatabaseAuthProvider());
    }
}

Separating the concerns

class AuthService implements AuthInterface
{
    protected $provider;

    public function __construct(AuthProviderInterface $provider)
    {
        $this->provider = $provider;
    }
}
class AuthServiceFactory
{
    public static function getInstance()
    {
        return new AuthService(new DatabaseAuthProvider());
    }
}

Wrath

(c) TchaikovskyCF

DIC vs. SL





What is the difference?

ServiceLocator

interface MyServiceLocator
{
    /**
     * Returns an entry of the container by its $id.
     *
     * @param  string $id
     * @throws NotFoundException
     * @return mixed
     */
    public function get($id);

    /**
     * Returns true if the container can return an entry
     * for the given identifier. Returns false otherwise.
     *
     * @param string $id
     * @return bool
     */
    public function has($id);
}

DIC

interface MyDependencyContainer
{
    /**
     * Returns an entry of the container by its $id.
     *
     * @param  string $id
     * @throws NotFoundException
     * @return mixed
     */
    public function get($id);

    /**
     * Returns true if the container can return an entry
     * for the given identifier. Returns false otherwise.
     *
     * @param string $id
     * @return bool
     */
    public function has($id);
}

DIC vs. SL - The difference?





Push vs. Pull

Using a ServiceLocator

class MyController
{
    protected $service;
    protected $logger;

    public function __construct(MyServiceLocator $locator)
    {
        $this->service = $locator->get('service');
        $this->logger = $locator->get('logger');
    }
}

Using a DIC

class MyController
{
    protected $service;
    protected $logger;

    public function __construct(MyService $service, MyLogger $logger)
    {
        $this->service = $service;
        $this->logger = $logger;
    }
}

Envy

(c) TchaikovskyCF

Types of configuration




internal Configuration

vs.

external Configuration

Internal Configuration

class AuthService implements AuthInterface
{
    protected $provider;

    /**
     * @Inject
     */
    public function __construct(AuthProviderInterface $provider)
    {
        $this->provider = $provider;
    }
}

Internal Configuration

class AuthService implements AuthInterface
{
    protected $provider;

    /**
     * @Inject
     * @Named('RESTAuthProvider')
     */
    public function __construct(AuthProviderInterface $provider)
    {
        $this->provider = $provider;
    }
}

Internal Configuration

/**
 * @ImplementedBy(RESTAuthProvider)
 */
interface AuthProviderInterface
{
    /**
     * Tries to authenticate the user.
     *
     * @return bool
     */
    public function authenticate($username, $password);
}

External Configuration

services:
    authprovider:
        class:     RESTAuthProvider
    authservice:
        class:     AuthService
        arguments: ["@authprovider"]

External Configuration

services:
    provider_rest:
        class:     RESTAuthProvider
    provider_soap:
        class:     SoapAuthProvider
    authservice_rest:
        class:     AuthService
        arguments: ["@provider_rest"]
    authservice_soap:
        class:     AuthService
        arguments: ["@provider_soap"]

Both ways make sense...

...pick what makes sense!

$dep = new \Di\Definition\Builder\PhpClass();
$dep->setName('RESTAuthProvider');

$class = new \Di\Definition\Builder\PhpClass();
$class->setName('AuthService');

$im = new \Di\Definition\Builder\InjectionMethod();
$im->setName('__construct');
$im->addParameter('provider', 'RESTAuthProvider');
$class->addInjectionMethod($im);

$builder = new \Di\Definition\BuilderDefinition();
$builder->addClass($dep);
$builder->addClass($class);

$defList = new \Di\DefinitionList($builder);
$di = new \Di\DiContainer($defList);
new AuthService(new RESTAuthProvider());

Configuration is too complex?

DI is not slow!

Pride

(c) TchaikovskyCF

Choose wisely

Learn to love it

Famous final words





« Dependency Injection is a key element
of agile architecture » - Ward Cunningham







Thank you!







Do not forget to rate the talk:
https://joind.in/10687

Artwork credits



http://www.flickr.com/photos/andresrueda/3452940751/
http://www.flickr.com/photos/andresrueda/3455410635/
http://tchaikovskycf.deviantart.com/
http://sxc.hu