Dependency Injection 101

Stephan Hochdörfer // 04.03.2017

About me


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

  • #PHP, #DevOps, #Automation
  • #phpugffm, #phpugmrn, #unKonf

Code used to look like this

<?php

class Mage_Newsletter_ManageController
    extends Mage_Core_Controller_Front_Action
{
    public function preDispatch()
    {
        parent::preDispatch();
        if(!Mage::getSingleton('customer/session')->authenticate($this)){
            $this->setFlag('', 'no-dispatch', true);
        }
    }

    // [...]
}

Tightly coupled code

Let's run this code...

<?php

$controller = new Mage_Newsletter_ManageController();
$controller->preDispatch();
[27-Feb-2017 13:05:13 Europe/Berlin] PHP Fatal error:  Uncaught Error:
Class 'Mage' not found in ...

No component re-use

What about testing?

<?php

class Mage_Newsletter_ManageController
    extends Mage_Core_Controller_Front_Action
{
    public function preDispatch()
    {
        parent::preDispatch();
        if(!Mage::getSingleton('customer/session')->authenticate($this)){
            $this->setFlag('', 'no-dispatch', true);
        }
    }

    // [...]
}

No isolation, not testable!

Testing (The Laravel way)

<?php

Cache::get('key');
Cache::shouldReceive('get')
        ->once()
        ->with('key')
        ->andReturn('value');

What is customer/session?

Mage::getSingleton('customer/session')

S.O.L.I.D.



« High level modules should not depend upon
low level modules. Both should depend upon
abstractions.» - Robert C. Martin


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

Inversion of Control





« [...] is a design principle in which custom-written portions
of a computer program receive the flow of control from a
generic framework. » - Wikipedia

Inversion of Control

<?php

class Mage_Newsletter_ManageController
    extends Mage_Core_Controller_Front_Action
{
    private $session;

    public function __construct(Mage_Customer_Model_Session $session)
    {
        $this->session = $session;
    }

    public function preDispatch()
    {
        parent::preDispatch();
        if(!$this->session->authenticate($this)){
            $this->setFlag('', 'no-dispatch', true);
        }
    }
    // [...]
}

Let's run this code...

<?php

$controller = new Mage_Newsletter_ManageController();
$controller->preDispatch();
[27-Feb-2017 13:05:13 Uncaught TypeError: Argument 1 passed to
Mage_Newsletter_ManageController::__construct() must be an instance of
Mage_Customer_Model_Session, none given [...]

Dependency injection?

Dependency Injection types




  • Property injection
  • Setter injection
  • Interface injection
  • Constructor injection

Property injection

<?php

class Mage_Newsletter_ManageController
    extends Mage_Core_Controller_Front_Action
{
    public $session;

    // [...]
}

Property injection

<?php

class Mage_Newsletter_ManageController
    extends Mage_Core_Controller_Front_Action
{
    /** @Inject **/
    private $session;

    // [...]
}

Property injection





« Repeat after me: field injection
is a dumb idea... *sigh* » - @olivergierke

Setter injection

<?php

class Mage_Newsletter_ManageController
    extends Mage_Core_Controller_Front_Action
{
    private $session;

    public function setSession(Mage_Customer_Model_Session $session)
    {
        $this->session = $session;
    }

    // [...]
}

Setter injection

<?php

class Mage_Newsletter_ManageController
    extends Mage_Core_Controller_Front_Action
{
    private $session;

    /** @Inject **/
    public function setSession(Mage_Customer_Model_Session $session)
    {
        $this->session = $session;
    }

    // [...]
}

..but optional dependencies?





« The concept of optional dependencies is a lie! » - Me

Interface injection

<?php

interface SessionAware
{
    public function setSession(Mage_Customer_Model_Session $session);
}
            
class Mage_Newsletter_ManageController
    extends Mage_Core_Controller_Front_Action
    implements SessionAware
{
    private $session;

    public function setSession(Mage_Customer_Model_Session $session)
    {
        $this->session = $session;
    }

    // [...]
}

Constructor injection

<?php

class Mage_Newsletter_ManageController
    extends Mage_Core_Controller_Front_Action
{
    private $session;

    public function __construct(Mage_Customer_Model_Session $session)
    {
        $this->session = $session;
    }

    // [...]
}

DI Container configuration?

Internal configuration

<?php

class Mage_Newsletter_ManageController
    extends Mage_Core_Controller_Front_Action
{
    private $session;

    /** @Inject **/
    public function setSession(Mage_Customer_Model_Session $session)
    {
        $this->session = $session;
    }

    // [...]
}

Internal configuration

<?php

class Mage_Newsletter_ManageController
    extends Mage_Core_Controller_Front_Action
{
    private $session;

    /** 
     * @Inject
     * @Named('MyCustomerSessionModel')
     */
    public function setSession(Mage_Customer_Model_Session $session)
    {
        $this->session = $session;
    }

    // [...]
}

Internal configuration

<?php

/**
 * @ImplementedBy('MyCustomerSessionModel')
 */
interface SessionAware
{
    public function setSession(Mage_Customer_Model_Session $session);
}

External configuration





  • YAML
  • XML
  • PHP
  • ...

YAML configuration

services:
    session_model:
        class:     Mage_Customer_Model_Session
    newsletter_manager:
        class:     Mage_Newsletter_ManageController
        calls:
            - [setSession, ['session_model']]
            

XML configuration

<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services 
    http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="session_model"
                class="Mage_Customer_Model_Session">
        </service>

        <service id="newsletter_manager"
            class="Mage_Newsletter_ManageController">
            <call method="setSession">
                <argument type="service" id="session_model" />
            </call>
        </service>
    </services>
</container>

XML configuration





« @rdohms says XML is best for DI config. » - @benmarks

PHP configuration

<?php

use Symfony\Component\DependencyInjection\Reference;

// ...
$container
    ->register('session_model', 'Mage_Customer_Model_Session')

$container
    ->register('newsletter_manager', 'Mage_Newsletter_ManageController')
    ->addMethodCall('setSession', array(new Reference('session_model')));

Why not this?

<?php

$controller = new Mage_Newsletter_ManageController();
$controller->setSession(new Mage_Customer_Model_Session());

Depend on what you need...

Depend on what you need...

<?php

class Mage_Newsletter_ManageController
    extends Mage_Core_Controller_Front_Action
{
    public function __construct(
        Mage_Customer_Model_Session $session,
        Mage_Customer_Model_Address $address,
        Mage_Customer_Helper_Address $addressHelper,
        Mage_Customer_Block_Account_Navigation $nav,
        Some_Other_Random_Block $block1,
        Some_Other_Random_Block2 $block2,
        Some_Other_Random_Block3 $block3,
        Some_Other_Random_Block4 $block4,
        Some_Other_Random_Block5 $block5,
    ) {
         // [...]   
    }
}

Depend on what you need...





« IoC is a good idea. Like all good ideas,
it should be used with moderation. »
- @unclebobmartin

ContainerAware is a lie!

<?php

interface ContainerAware
{
    public function setContainer(ContainerInterface $container);
}
            
class Mage_Newsletter_ManageController
    extends Mage_Core_Controller_Front_Action
    implements ContainerAware
{
    private $session;

    public function setContainer(ContainerInterface $container);
    {
        $this->session = $container->get('customer/session');
    }

    // [...]
}

ContainerAware is a lie!





« ContainerAware is the new Singleton. #php » - @tobySen

But I need lazy-loading...

To new or not to new...

<?php

class Mage_Newsletter_ManageController
    extends Mage_Core_Controller_Front_Action
{
    public function __construct(
        Zend_Pdf $pdf
    ) {
         // [...]   
    }
}

To new or not to new...

<?php

class Mage_Newsletter_ManageController
    extends Mage_Core_Controller_Front_Action
{
    public function __construct() {
         $pdf = new Zend_Pdf();
         // [...]   
    }
}

Architecture is tool agnostic

Famous final words





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

Thank you! Questions?


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

  • #PHP, #DevOps, #Automation
  • #phpugffm, #phpugmrn, #unKonf