The 7 deadly sins of
Dependency Injection

Stephan Hochdörfer // 25.01.2014

About me

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

  • #PHP, #DevOps, #Automation

Think first!



Buy a T-Shirt 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!');
    }
}

ServiceLocator for the win?

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!');
    }
}

Apply some "black 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!');
    }
}

Lessons learned




Lessons learned





Let your code be as portable as possible.

Greed

(c) TchaikovskyCF

Fat Controller

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();
    }
}

Lessons learned




Rethink your architecture
and avoid fat controllers!

Lessons learned




Architecture and structure
are agnostic of tools.

Lessons learned




A ServiceLocator will not fix your
problem. It will help you to hide it.

Depend on abstractions

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'));
    }
}

Depend on abstractions



« 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'));
    }
}

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());
    }
}

Gluttony

(c) TchaikovskyCF

Too many implementations

No common standard!



I have a dream

Are we there yet?

Sloth

(c) TchaikovskyCF

Constructor injection

class UserController
{
    protected $auth;

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

Lessons learned





Immutability rocks!

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...');
        }
    }
}

Lessons learned





Setter injection means
null checks everywhere!

Lessons learned





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...');
        }
    }
}

Lessons learned





Interface injection is
"forced" setter injection.

(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...');
        }
    }
}

Lessons learned





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

Wrath

(c) TchaikovskyCF

DIC vs. SL





Can you explain 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"]

Lessons learned





A mix of internal and external
configuration can make sense.

External Configuration

$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());

Lessons learned





If your Container configuration is too complex
you are using the wrong container!

Lessons learned





DI is not slow. Use a PHP based configuration!

Lessons learned





PHP based configuration means:
No calls to the Reflection API!

Pride

(c) TchaikovskyCF

Choose wisely...





Choose whatever works
for you and your use-case.

Understand DI





DI is so much more than *just*
being useful for testing.

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/10294