Dependency Injection

Purpose of the Dependecy Injection (DI) is to free classes from the responsibility for obtaining objects that they need for its operation (these objects are called services). To pass them these services on their instantiation instead. We'll talk over:

  • What is the principle of the Dependency Injection.
  • How to create DI containers.

What is Dependency Injection?

Dependency Injection (DI) is nothing mysterious or baffling. It can be comprehended into one selfish sentence: “Don't seek for anything, let someone else do it.” Now let's translate this into programmers' speech. We have a class Article that represents a blog post:

class Article
{
    public $id;
    public $title;
    public $content;

    function save()
    {
        // we'll save into the database
    }
}

and the usage is this:

$article = new Article;
$article->title = '10 Things You Need to Know About Losing Weight';
$article->content = 'Every year millions of people in ...';
$article->save();

Method save() saves the article into the database table articles. It's easy to implement using Nette Database, except one thing: Where is Article supposed to get the database connection, ie. an object of the class Connection?

Well, we can place it into some global variable like $GLOBALS['database'] or into some static member of a class. But haven't you heard that use of global variables is bad? It's true, global variables are evil and static members are exactly the same.

So where else will we get the database connection? DI has the answer: “Don't seek for anything, let someone else do it.” In other words, if I need a database, someone give it to me, it's not my job. Hah, it's devious, dear DI! Let's do it:

class Article
{
    public $id;
    public $title;
    public $content;
    private $database;

    function __construct(Nette\Database\Connection $database)
    {
        $this->database = $database;
    }

    function save()
    {
        $this->database->query('INSERT INTO articles', [
            'title' => $this->title,
            'content' => $this->content,
        ]);
    }
}

Use of the Article class will slightly change:

$article = new Article($database);
$article->title = ...
$article->content = ...
$article->save();

Are you asking, where this code takes the $database? DI gives a straight answer: “Let someone else do it.” Database connection will be supplied by he who called the code. And so on, and so on. Sure you say that it's not possible to eternalize the delegation of responsibility. That there must be a zero point. And you're right. There's a creator at the beginning, he doesn't delegate anything and he creates objects. We call him DI container and you can read about it in a moment.

Why are Global Variables Evil?

Good question. Class Article needs the database connection anyway. But from the first example there's not at all evident, from where and how it gets it. User of such code might be surprised, that the article really saves and he asks: “Where did it save?” With the second example using DI, the code is self-explaining.

Imagine, that you're exploring some payment gateway and you write an example:

$cc = new CreditCard('4461510140804839', 12, 2013);
$cc->pay(1000, CreditCard::DOLLARS);

You run the code, with your card number, and later you'll find, that it really withdrew the money from your account! Shocked you stare at the list and lament: “Where is my money, how could that happen, I didn't pair it with any payment gateway!” Class CreditCard did it by herself, found one in some global variable, as mysterious, as Article got the database connection. Such a thing you're not deducating from the code and you don't even know how to change the gateway to another, like testing.

Factories

You can protest, that use of DI means more writing, that for creating of an instance of Article you have to handle the database connection and so. That's true, but don't forget the last time, when “less writing” costed you $1000! No, we don't want to ease that. Objection is correct an we're gonna add one even bigger: When we find a need for Article to cache some data, in harmony with DI it will require one more argument with the cache repository. That would mean to adapt the application at many places: At least everywhere, where Article is instantiated.

class UnhappyClassWorkingWithArticle
{
    private $database;
    private $cacheStorage;

    function __construct(
        Nette\Database\Connection $database,
        Nette\Caching\IStorage $cacheStorage,
        ...
    )
    {
        $this->database = $database;
        $this->cacheStorage = $cacheStorage;
        ...
    }

    function doSomething()
    {
        $article = new Article($this->database, $this->cacheStorage, ...);
        ...
    }
}

What now? The thing has a solution: Instead of manual creating of Article object we make a factory ArticleFactory, ie. object that creates these Article objects. When Article changes the constructor, only the factory has to update, nothing more. And where to get the factory in our code? You know… let someone else do it. :-)

class ArticleFactory
{
    private $database;
    private $cacheStorage;

    function __construct(
        Nette\Database\Connection $database,
        Nette\Caching\IStorage $cacheStorage,
        ...
    )
    {
        $this->database = $database;
        $this->cacheStorage = $cacheStorage;
        ...
    }

    function create()
    {
        $article = new Article($this->database, $this->cacheStorage, ...);
    }
}


class HappyClassWorkingWithArticle
{
    private $articleFactory;

    function __construct(ArticleFactory $articleFactory)
    {
        $this->articleFactory = $articleFactory;
    }

    function doSomething()
    {
        $article = $this->articleFactory->create();
        ...
    }
}

DI Container and Services

By the term “DI container” we mean the initial factory that creates all the objects required to run the application. These objects are called services. What are the services? Ordinary objects, such as the instance of Nette\Database\Connection. Only in relation to DI containers we call them services.

Example could be a container, that creates an ArticleFactory object, but also the required database connection:

class Container
{
    function createDatabase()
    {
        return new Nette\Database\Connection('mysql:', 'root', '***');
    }

    function createArticleFactory()
    {
        return new ArticleFactory($this->createDatabase());
    }
}

Usage would look as follows:

$container = new Container;
$database = $container->createDatabase();

Advantage is obvious, we don't need to care, how exactly is the object instantiated, that's a job for the factory. Anyway, the solution has still two drawbacks. First, there's entry data wired into the code, so we're gonna detach them into a variable:

class Container
{
    private $parameters;

    function __construct(array $parameters)
    {
        $this->parameters = $parameters;
    }

    function createDatabase()
    {
        return new Nette\Database\Connection(
            $this->parameters['dsn'],
            $this->parameters['user'],
            $this->parameters['password']
        );
    }

    function createArticleFactory()
    {
        return new ArticleFactory($this->createDatabase());
    }
}

$container = new Container([
    'dsn' => 'mysql:',
    'user' => 'root',
    'password' => '***',
]);
$database = $container->createDatabase();

A more serious drawback is that when we call createArticleFactory() several times, a new database connection is created each time. This needs to be avoided. We'll add method getService that will keep once created services for next usage:

class Container
{
    private $parameters;

    private $services = [];

    function __construct(array $parameters)
    {
        $this->parameters = $parameters;
    }

    function createDatabase()
    {
        return new Nette\Database\Connection(
            $this->parameters['dsn'],
            $this->parameters['user'],
            $this->parameters['password']
        );
    }

    function createArticleFactory()
    {
        return new ArticleFactory($this->getService('Database'));
    }

    function getService($name)
    {
        if (!isset($this->services[$name])) {
            // getService('Database') will call createDatabase()
            $method = 'create' . $name;
            $this->services[$name] = $this->$method();
        }
        return $this->services[$name];
    }
}

And we have a fully functional DI container. As you can see, it's no complicated to write it. . It's notable that the service itself does not know that it is creating by a container, so it is possible to create any object in PHP without affecting its own source code.

Nette\DI\Container

Class Nette\DI\Container is a flexible implementation of the universal DI container. We can create custom containers either statically, ie. inheriting this class, or let them automatically generate, which is a very elegant and powerful solution.

Names of factory methods follow an uniform convention, they consist of the prefix createService + name of the service starting with first letter upper-cased. Note that the container has already defined the array $parameters for user parameters and the getService() method.

class MyContainer extends Nette\DI\Container
{
    function createServiceDatabase()
    {
        return new Nette\Database\Connection(
            $this->parameters['dsn'],
            $this->parameters['user'],
            $this->parameters['password']
        );
    }

    function createServiceArticleFactory()
    {
        return new ArticleFactory($this->getService('database'));
    }
}

Now we create an instance of the container and pass parameters that are written to $this->parameters:

$container = new MyContainer([
    'dsn' => 'mysql:',
    'user' => 'root',
    'password' => '***',
]);

We get the service by calling the getService method:

// the first letter of the service name is lower
$database = $container->getService('database');

Generated Container

But the Nette DI library is primarily a generator of containers. That's its strongest side. We instruct it (usually) with configuration files.

This is configuration that leads to generate nearly the same class as the class MyContainer above:

parameters:
    dsn: 'mysql:'
    user: root
    password: '***'

services:
    database: Nette\Database\Connection( %dsn%, %user%, %password% )
    articleFactory: ArticleFactory

A big advantage is the shortness of configuration. In addition, we can add more and more dependecies to individual classes without having to modify the configuration thanks to autowiring.

Nette DI actually generates PHP code of container. Therefore it is extremely fast. Developer can see the code, so he knows exactly what it is doing. He can even trace it.

The container may have tens of thousands of lines for large applications, and it would not be possible to keep something like this manually.

Save the (above) configuration to the file config.neon and let's create a container:

$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp');
$class = $loader->load(function ($compiler) {
    $compiler->loadConfig(__DIR__ . '/config.neon');
});
$container = new $class;

and then use container to create object Nette\Database\Connection:

$database = $container->getService('database');
$database->query('...');

Or, instead of the name of the service, we will use its type:

$database = $container->getByType(Nette\Database\Connection::class);

The container will be generated only once and the code is stored in cache (in directory __DIR__ . '/temp'). Therefore the loading of configuration file is placed in the closure in $loader->load(), so it is called only once.

During development it is useful to activate auto-refresh mode which automatically regenerate the container when any class or configuration file is changed. Just in the constructor ContainerLoader append true as the second argument.

As you can see, using Nette DI is not limited to applications written in Nette, you can use it anywhere.