Dependency Injection

And Inversion of Control

study-group-05

Created by Juan Manuel Torres / @onema / onema.io

Follow Live

http://slides.com/onema/dependency-injection/live  

What is Dependency Injection? (DI)

Dependency injection menas providing objects the varaibles it needs instead of having it construct them. [1] [2]

 

It's a very useful technique for testing, since it allows dependencies to be mocked or stubbed out. [1] 

 

NOT DEPENDENCY INJECTION

Remember our "Hello World" Translation controller?

If we would have been lazy we could have done it like this:

class HelloWorldController {

    private $request;

    public function __construct() {

        $this->request =  Request::createFromGlobals();
    }

    public function translateAction() {
        $language = 
            $this->request->attributes->get('lang');
            // . . . 
       return $response;
    }
}

We are creating object dependencies in the constructor.  Hard to customize

$controller = HelloWorldController();
$response = $controller->translateAction();

Very easy to use

DEpendency Injection

class HelloWorldController {

    private $request;

    public function __construct(Request $request) {

        $this->request = $request;
    }

    public function translateAction() {
        $language = 
            $this->request->attributes->get('lang');
            // . . . 
       return $response;
    }
}

We inject the Request dependency via the constructor.  Easy to customize

$request = Request::createFromGlobals();
$controller = HelloWorldController($request);
$response = $controller->translateAction();

Not so easy to use

That is it!

we are done here...

If we want to modify the request

We could use Globals in order to make any modifications to the request.

Why is the first example not a good idea?

class HelloWorldController {

    private $request;

    public function __construct() {

        $this->request =  
            Request::createFromGlobals();
        
        global $attr;

        if (isset($attr)) {         
            $request->attributes->add($attr);
        }
    }
    // . . . 
}
$attr = ['default_language' => 'EN']
$controller = HelloWorldController();
$response = $controller->translateAction();

Bad global, BAD!

If we want to modify the request

We could modify the request by passing custom attributes array.

Why is the first example not a good idea?

class HelloWorldController {

    private $request;

    public function __construct($attr = null) 
    {
        $this->request =  
            Request::createFromGlobals();

        if (isset($attr)) {         
            $request->attributes->add($attr);
        }
    }
    // . . . 
}
$attr = ['default_language' => 'EN']
$controller = HelloWorldController($attr);
$response = $controller->translateAction();

Is OK, but not a great solution

If we want to modify the request Implementation?

We could use a global registry.

Why is the first example not a good idea?

class HelloWorldController {

    private $request;

    public function __construct() 
    {
        $this->request =  
            Registry::get('request');

    }
    // . . . 
}
Registry::set('request', new CustomRequest)
$controller = HelloWorldController();
$response = $controller->translateAction();

Now we depend on

the Registry

Advantages:

  • Decoupling: No need for us to know much about our dependecies (Maybe we could use an interface here?)
  • Dependency inversion: Configuration is natural
  • Reusability: Could use 3rd party libs (Interfaces anyone?)
  • Simple Testing: Mock Request for Testing
class HelloWorldController {

    private $request;

    public function __construct($request) 
    {
        $this->request =  $request;
    }
    // . . . 
}
$request = Request::createFromGlobals();
$request->attributes->add($parameters);
$controller = HelloWorldController($request);
$response = $controller->translateAction();

inject the dependecy to the constructor

Disadvantages:

  • Complex initialization
$request = Request::createFromGlobals();
$request->attributes->add($parameters);
$controller = HelloWorldController($request);
$response = $controller->translateAction();

inject the dependecy to the constructor

$controller = HelloWorldController();
$response = $controller->translateAction();

This is harder:

Than this:

How?:

  • Mocking or Stubbing Objects
class HelloWorldControllerTest extends PHPUnit_Framework_TestCase
{
    public function testTranslation()
    {
        // Create a Mock Object for the Request class
        $mockRequest = $this->getMock('Request');
        $mockRequest->attributes->add(['lang' => 'EN']);
        
        $controller = new HelloWorldController($mockRequest);
        $response = $controller->translateAction();
        $this->assertEquals('Hello World!', 
                            $response->getContent(), 
                            'Invalid English message.')

    }
}

Simplify Testing

A few ways to inject Dependencies

  • Constructor Injection
  • Setter Injection
  • Property Injection

Setter Injection

We use a setter instead of using the constructor

class HelloWorldController {

    private $request;

    public function __construct() 
    {
        // . . .
    }

    public function setRequest($request)
    {
        $this->request = $request;
    }
    // . . . 
}
$request = Request::createFromGlobals();
$controller = HelloWorldController();
$controller->setRequest($request);
$response = $controller->translateAction();

Parameter Injection

We set the class property directly, this requires the property to be public

class HelloWorldController {

    public $request;

    public function __construct() 
    {
        // . . .
    }

    // . . . 
}
$request = Request::createFromGlobals();
$controller = HelloWorldController();
$controller->request = $request;
$response = $controller->translateAction();

Dependency Injection Container

Service Container (or dependency injection container) is simply a PHP object that manages the instantiation of services (i.e. objects).

Remember it was hard to Initialize classes?

A proper solution to simplify the creation of complex objects is to use a Dependency Injection Container

class Container
{
    public function getRequest()
    {
        $request=Request::createFromGlobals();
        $request->attributes->add([
            'lang' => 'EN'
        ]);
        return $request;
    }

    public function getHelloController()
    {
        $request = $this->getRequest();
        return HelloWorldController($request);
    }
}
$container = new Container();
$controller = $container->getHelloController();
$response = $controller->translateAction();

Use a third party DI Container

  • Pimple
  • Twittee
  • Bullet Three
  • Symfony2 Service Container

Using Pimple

THE END

BY Juan Manuel Torres / onema.io / @onema / kinojman@gmail.com

Reference

Dependency Injection

By Juan Manuel Torres

Dependency Injection

  • 1,597