Jethro Hazelhurst Jethro Hazelhurst - 4 months ago 7
PHP Question

What is a Dependency Injection Container?

I am trying to understand what the role of a Dependency Injection Container is because it strikes me as fundamental to maintainable code.

As I understand it, a DIC is just as the title suggests, a container where all your dependencies are collected together, so instead of seeing new Foo/Bar all over the application, all the new instances are generated inside of the container and then passed through each other to where they are needed (e.g. model(database(config())).

To get to grips with it I have tried to make a VERY SIMPLE dependency injection container as I think it might look, this is the result.

In my index.php front controller I am instantiating a

new App\Core\Container;


The Container.php file looks like this...

<?php

namespace App\Core;

use App\Config;

class Container
{
public $config;
public $router;
public $database;

public $model;
public $view;
public $controller;

public function __construct()
{
$this->config = new Config;
$this->router = new Router;
$this->database = new Database($this->config);
}

public function add()
{
// add dependencies from the outside?
}

public function getInstance(/* string */)
{
// return an instance for use somewhere?
}

public function newModel($model)
{
$model = $this->getModelNamespace() . $model;
$this->model = new $model($this->database);
return $this->model;
}

private function getModelNamespace()
{
$namespace = 'App\Models\\';
if (array_key_exists('namespace', $this->params = [])) {
$namespace .= $this->params['namespace'] . '\\';
}
return $namespace;
}

public function newView($params)
{
$this->view = new View($this->model, $params);
return $this->view;
}

public function newController($controller)
{
$controller = $this->getControllerNamespace() . $controller;
$this->controller = new $controller;
return $this->controller;
}

private function getControllerNamespace()
{
$namespace = 'App\Controllers\\';
if (array_key_exists('namespace', $this->params = [])) {
$namespace .= $this->params['namespace'] . '\\';
}
return $namespace;
}
}


Questions

Could my implementation above, although very simple, be classed as a basic dependency injector? Is a Dependency Injection Container generally comprised of one class?

Answer

Could this be classified as a dependency injection container?

No, this does not look like a dependency injection container. A dependency injection container is meant to reduce the work that instantiation requires by determining, creating, and injecting all dependencies. Rather what you have there appears to be a combination of a factory and a service locator.

Factories abstract the creation of objects. This is essentially what your Container class is doing. By calling designated methods (i.e., newModel), your container takes on the responsibility of locating the exact object to be instantiated and constructing an instance of that object.

The reason I would call this a "poor" factory is that it's beginning to look like it might be used to locate services. Service locators work by hiding an object's dependencies: instead of being dependent on GenericService, an object might depend on a service locator. Given the service locator, it can request an instance of GenericService. I see similar behavior beginning to take hold in your add() and getInstance() methods. Service locators are generally considered anti-patterns because they abstract dependencies therefore making code impossible to test!

What is a dependency injection container?

I said it above, but again: a dependency injection container is meant to reduce the work that instantiation requires by determining, creating, and injecting all dependencies. A DIC will look at all dependencies of a class, and all dependencies those dependencies may have, and so on... In this sense, the container is responsible for hierarchically instantiating all dependencies.

The Container class you provide relies on very strict definitions of pre-defined classes. For example, classes in your model layer are appear to only be dependent on a database connection. (Similar statements can be said about classes in your controller & view layer).

How does a dependency injection container find dependencies?

A dependency injection container will detect dependencies. Typically this happens through 1 of 3 mechanisms: autowiring, annotations, and definitions. PHP-DI docs provide a good idea of what all three of these entail here. In short, though: autowiring detects dependencies by reflecting on a class, annotations are used to write in dependencies using comments above a class, and definitions are used to hard-code dependencies. Personally, I prefer autowiring because it's clean & simple.

Can I create a simple dependency injection container or no?

Yes, you can. Start with the idea that your injector should be able to instantiate any object (service, view, controller, etc...). It needs to look at the relevant object and hierarchically instantiate all dependencies (hint: possibly through some method of recursion).