1
\$\begingroup\$

I just learned about dependency injection/dependency injection containers, and I realize that the controller in my MVC web application is hard to test.

My original controller class:

class Controller
{
    protected $db;
    private $article;
    private $nav;

    public function __construct($db) 
    {
        $this->db = $db;
        $this->article = new Article($this->db);
        $this->nav = new Nav($this->db);
    }

    public function render($template) 
    {
        var_dump($this->article->getRow());
        var_dump($this->nav->getRows());
    }

}

This is my test using DI/DIC in my MVC app:

Controller

class Controller
{
    private $container;

    public function __construct() 
    {
    }

    public function render($container,$template) 
    {
        $this->container = $container;
        var_dump($this->container->getArticle($url = 'home'));
        var_dump($this->container->getNav());
    }

}

Container: holds all objects which generate data from the database

class Container
{
    protected $db;
    private $article;
    private $nav;

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

    public function setArticle() 
    {
        if (is_null($this->article)) 
        {
            $this->article = new Article($this->db);
        }
        return $this->article;
    }

    public function setNav() 
    {
        if (is_null($this->nav)) 
        {
            $this->nav = new Nav($this->db);
        }
        return $this->nav;
    }

    public function getArticle($url) 
    {
        return $this->setArticle()->getRow($url);
    }

    public function getNav() 
    {
        return $this->setNav()->getRows();
    }
}

Models

class Article
{
    public function __construct($db) 
    {

    }

    public function getRow($url) 
    {
        return "row 1";
    }
}

class nav
{
    public function __construct($db) 
    {

    }

    public function getRows() 
    {
        return "rows nav";
    }
}

Usage

$db = 'pdo connection';
$template = 'template.phtml';
$container = new Container($db);
$controller = new Controller();
$controller->render($container,$template);

Questions:

  1. Am I doing it correctly or not?
  2. Do the improved Controller and Container above make sense to you if you were a developer going to pick up this application's development?
  3. Do I need any interfaces or traits as part of the app? If so, why?
\$\endgroup\$
8
  • \$\begingroup\$ Your controller is hard to test because it instantiates the objects it depends on instead of asking for them. \$\endgroup\$
    – Kolargol00
    Aug 1, 2014 at 12:56
  • \$\begingroup\$ @Kolargol00: you are looking at the wrong controller just like Gufran did. But thanks for the comment! I realised that my original controller is hard to test because it instantiates the objects but I have reworked on it if you read further down my post. thanks! \$\endgroup\$
    – Run
    Aug 1, 2014 at 13:02
  • \$\begingroup\$ Now I see the second Controller, duh! :/ Gufran's answer still stands IMHO. Your Controller shouldn't know about the Container. I'm not familiar with PHP dependency injection frameworks, but when using Spring (Java), objects instantiated by the DI container do not depend on it. \$\endgroup\$
    – Kolargol00
    Aug 1, 2014 at 13:08
  • \$\begingroup\$ @Kolargol00: thanks. IMHO - what does it mean? Gulfran was reviewing on my older controller. \$\endgroup\$
    – Run
    Aug 1, 2014 at 13:16
  • 1
    \$\begingroup\$ See my answer below for a code example. :) \$\endgroup\$
    – Kolargol00
    Aug 1, 2014 at 13:49

3 Answers 3

1
\$\begingroup\$

Building upon Gufran's answer, here is what the Container class using constructor injection would look like. It instantiates all objects of the application and then "wires" them together.

class Container {
  protected $db;
  private $article;
  private $nav;
  private $controller;

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

  public function getArticle() {
    if (is_null($this->article)) {
      $this->article = new Article($this->db);
    }
    return $this->article;
  }

  public function getNav() {
    if (is_null($this->nav)) {
        $this->nav = new Nav($this->db);
    }
    return $this->nav;
  }

  public function getController() {
    if (is_null($this->controller)) {
        $this->controller = new Controller(getArticle(), getNav());
    }
    return $this->controller;
  }
}
\$\endgroup\$
0
3
\$\begingroup\$

You are not even doing Dependency Injection here. In your controller constructor for example

public function __construct($db) 
{
    $this->db = $db;
    $this->article = new Article($this->db);
    $this->nav = new Nav($this->db);
}

sure you are injecting the $db but that is not a dependency for as long as it does not enforce the type of object required. In this case I can inject anything, say, a string, a number, an object of some other class, array or pretty much everything else. How is constructor going to control the behaviour of this object ?

Furthermore, you are 'newing' up the objects in constructor. Don't you think the objects you are initialising inside the constructor should instead be injected into it ? That is what IoC does.

The solution to first problem is Type Hinting. What you do here is you define the type of argument and only defined type can be passed to the constructor as argument. For more flexibility (which you'd more likely want in case of database connection) you can define an interface and inject an implementation of it into the constructor. So for example you define an interface DatabaseConnection like this:

interface DatabaseConnection {
    public function prepareQuery($queryString);
    public function addQueryBindings(array $queryBindings);
    public function run();
}

Now you can create multiple implementations of this interface and have IoC inject them into constructor. So now you can have MySQLDatabaseConnection, PostgreDatabaseConnection, MSSQLDatabaseConnection, SQLiteDatabaseConnection each one implementing DatabaseConnection and then you type hint the constructor of controller to accept DatabaseConnection

public function __construct(DatabaseConnection $db) 
{
    $this->db = $db;
    $this->article = new Article($this->db);
    $this->nav = new Nav($this->db);
}

This constructor will allow any object which implements the DatabaseConnection interface and throw error if you try to inject anything else that is not a descendent of this interface.

Now that type hinting and interface is out of the way, I hope you can already see this but let me point it out.
The objects you are initialising inside controller constructor can be injected by IoC, this will also eliminate the need to have DatabaseConnection injected into a controller (which looks a terrible idea to me). So here is the controller constructor freshen up

public function __construct(Article $article, Nav $nav) 
{
    $this->article = $article;
    $this->nav = $nav;
}

and you'll also need to change the constructor of both Article and Nav class to type hint the DatabaseConnection so that IoC can do its things

class Article
{
    public function __construct(DatabaseConnection $db) 
    {
        //...
    }
}

and

class Nav
{
    public function __construct(DatabaseConnection $db) 
    {
        //...
    }
}

The container, as you call it, is also not an IoC container (Inversion Of Control), It is not doing dependency injection just holding pre-initialised objects into itself. If you want Dependency Injection you need an IoC Container for that.
I dont think I can explain the architecture of an IoC container in a post here but with IoC in place you'll be able to initialise objects auto-magically with proper dependency already injected into it. So for example when you initialise our new controller from IoC container you will have an object of Controller with Article and Nav injected into it and both of them will have DatabaseConnection injected into them.

There are many IoC containers available on Github, some standalone and some as a part of a framework.

\$\endgroup\$
2
  • \$\begingroup\$ thanks for the answer. I think you reviewing on my older version of controller. please read my post again carefully. thanks. \$\endgroup\$
    – Run
    Aug 1, 2014 at 9:35
  • 1
    \$\begingroup\$ @lauthiamkok saw your edit, the answer still stands. \$\endgroup\$
    – Gufran
    Aug 1, 2014 at 15:54
3
\$\begingroup\$

DI Container

Dependancy Injection Container is a name for something different than you have here. It's holding construction parameters and object instances (composed and single) not it's methods and itself is not injected - it only fetches required objects for injection. See the difference in examples:

class SomeObject
{
    // constructor tells me that if I want to create/use/test this object
    // I need another one that is implementing iNavClass interface
    public function __construct(iNavClass $nav) {
        $this->nav = $nav;
    }
    ...
}
// I've programmed my container earlier that it returns an apprioprate instance of
// iNavClass implementation and it's not limited to concerete class
// I could choose from many classes that use required interface (at runtime)
new SomeObject($this->container->getObject('NavClassImplementation'));

"Injected container" is Service Locator and is considered an anti-pattern

class SomeObject
{
    // constructor tells me that if I want to create/use/test this object
    // I need other object(s), but can't tell what are they.
    // Sometimes (not here) I need deep code inspection to find out
    // how and what kind of objects are used.
    // If I want to test this class I need to mock ServiceLocator
    // along with object it should return.
    public function __construct(ServiceLocator $locator) {
        $this->nav = $locator->getObject('NavClassImplementation');
    }
    ...
}
// Can't tell what this object would really need
new SomeObject($this->locator);

I'll explain rest in short (for now) - maybe will expand later (or maybe someone else will):

  • Controll is going general-to-specific, but object instantiation (with some exceptions) the other way - that's why you use dependency injection. You can encapsulate creation with Factory Method pattern, Abstract Factory pattern or more dynamically with DI Container (this movie shows how it works).
  • Your controller-envolved "Container" is slowly becoming Model layer. Create ModelFactory class that instantiates (in this order): $db,
    $article($db), $nav($db) (I'd go for CommonPageElements here - nav
    only is too small for a class imo), $articlePage($article, $nav)
    and have a method that returns last element - that was your "container". Invoke that method and inject result into new Controller.
  • The "View" and communication between these three are not resolved yet and there's still much more to learn (through practice & theory).
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.