What is Dependency Injection?

This chapter introduces you to the basic programming practices you should follow when writing any application. These are the basics required to write clean, understandable, and maintainable code.

If you learn and follow these rules, Nette will be there for you every step of the way. It will handle routine tasks for you and make you as comfortable as possible so you can focus on the logic itself.

The principles we will show here are quite simple. You have nothing to worry about.

Remember Your First Program?

We have no idea what language you wrote it in, but if it was PHP, it would probably look something like this:

function addition($a, $b)
{
	return $a + $b;
}

echo addition(23, 1); // prints 24

A few trivial lines of code, but so many key concepts hidden in them. That there are variables. That code is broken down into smaller units, which are functions, for example. That we pass them input arguments and they return results. All that's missing are conditions and loops.

The fact that we pass input to a function and it returns a result is a perfectly understandable concept that is used in other fields, such as mathematics.

A function has a signature, which consists of its name, a list of parameters and their types, and finally the type of return value. As users, we are interested in the signature; we usually don't need to know anything about the internal implementation.

Now imagine that the signature of a function looks like this:

function addition($x)

An addition with one parameter? That's weird… How about this?

function addition()

That's really weird, isn't it? How do you think the function is used?

echo addition(); // what does it prints?

Looking at such code, we are confused. Not only a beginner would not understand it, even a skilled programmer would not understand such code.

Do you wonder what such a function would actually look like inside? Where would it get the adders? It would probably get them somehow on its own, like this:

function addition()
{
	$a = Input::get('a');
	$b = Input::get('b');
	return $a + $b;
}

It turns out that there are hidden bindings to other functions (or static methods) in the body of the function, and to find out where the addends actually come from, we have to dig further.

Not This Way!

The design we have just been shown is the essence of many negative features:

  • the function signature pretended that it didn't need addends, which confused us
  • we have no idea how to make the function calculate with two other numbers
  • we had to look into the code to see where it takes the addends
  • we discovered hidden bindings
  • to fully understand, we need to explore these bindings as well

And is it even the job of the addition function to procure inputs? Of course it isn't. Its responsibility is only to add.

We don't want to encounter such code, and we certainly don't want to write it. The remedy is simple: go back to basics and just use parameters:

function addition($a, $b)
{
	return $a + $b;
}

Rule #1: Let It Be Passed to You

The most important rule is: all data that functions or classes need must be passed to them.

Instead of inventing hidden mechanisms to help them somehow get to it themselves, simply pass the parameters in. You'll save the time it takes to come up with hidden way, which definitely won't improve your code.

If you follow this rule always and everywhere, you are on your way to code without hidden bindings. Towards code that is understandable not only to the author, but also to anyone who reads it afterwards. Where everything is understandable from the signatures of functions and classes and there is no need to search for hidden secrets in the implementation.

This technique is expertly called dependency injection. And the data is called dependencies. But it's a simple parameter passing, nothing more.

Please don't confuse dependency injection, which is a design pattern, with “dependency injection container”, which is a tool, something completely different. We will discuss containers later.

From Functions to Classes

And how do classes relate to this? A class is a more complex entity than a simple function, but rule #1 applies here as well. There are just more ways to pass arguments. For example, quite similar to the case of a function:

class Math
{
	public function addition($a, $b)
	{
		return $a + $b;
	}
}

$math = new Math;
echo $math->addition(23, 1); // 24

Or by using other methods, or directly by the constructor:

class Addition
{
	private $a;
	private $b;

	public function __construct($a, $b)
	{
		$this->a = $a;
		$this->b = $b;
	}

	public function calculate()
	{
		return $this->a + $this->b;
	}

}

$addition = new Addition(23, 1);
echo $addition->calculate(); // 24

Both examples are completely in compliance with dependency injection.

Real-Life Examples

In the real world, you won't write classes for adding numbers. Let's move on to real-world examples.

Let's have a class Article representing a blog article:

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

	public function save()
	{
		// save the article to the database
	}
}

and the usage will be as follows:

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

The save() method saves the article in a database table. Implementing it using Nette Database would be a piece of cake, if it weren't for one hitch: where should Article get the database connection, i.e. the Nette\Database\Connection class object ?

It seems we have a lot of options. It can take it from somewhere in a static variable. Or inherit it from a class that will provide the database connection. Or take advantage of a singleton. Or the so-called facades that are used in Laravel:

use Illuminate\Support\Facades\DB;

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

	public function save()
	{
		DB::insert(
			'INSERT INTO articles (title, content) VALUES (?, ?)',
			[$this->title, $this->content],
		);
	}
}

Great, we've solved the problem.

Or have we?

Let's recall rule #1: Let It Be Passed to You: all the dependencies the class needs must be passed to it. Because if we don't, and we break the rule, we've started down the path to dirty code full of hidden bindings, incomprehensibility, and the result will be an application that's a pain to maintain and develop.

The user of class Article has no idea where method save() stores the article. In a database table? In which one, production or development? And how can this be changed?

The user has to look at how the method save() is implemented to find the use of the method DB::insert(). So he has to search further to find out how this method procures a database connection. And hidden bindings can form quite a long chain.

Hidden bindings, Laravel facades, or static variables are never present in clean, well-designed code. In clean and well-designed code, arguments are passed:

class Article
{
	public function save(Nette\Database\Connection $db)
	{
		$db->query('INSERT INTO articles', [
			'title' => $this->title,
			'content' => $this->content,
		]);
	}
}

Even more practical, as we'll see next, is to use a constructor:

class Article
{
	private $db;

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

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

If you're an experienced programmer, you might be thinking that Article shouldn't have a save() method at all, it should be a pure data component, and a separate repository should take care of storage. This makes sense. But that would take us well beyond the topic, which is dependency injection, and trying to give simple examples.

If you're going to write a class that requires a database to operate, for example, don't figure out where to get it from, but have it passed to you. Perhaps as a parameter to a constructor or other method. Declare dependencies. Expose them in the API of your class. You'll get understandable and predictable code.

How about this class that logs error messages:

class Logger
{
	public function log(string $message)
	{
		$file = LOG_DIR . '/log.txt';
		file_put_contents($file, $message . "\n", FILE_APPEND);
	}
}

What do you think, did we follow rule #1: Let It Be Passed to You?

We didn't.

The key information, the log file directory, is obtained by the class from the constant.

See the example usage:

$logger = new Logger;
$logger->log('The temperature is 23 °C');
$logger->log('The temperature is 10 °C');

Without knowing the implementation, could you answer the question where the messages are written? Would it suggest to you that the existence of the LOG_DIR constant is necessary for it to work? And would you be able to create a second instance that writes to a different location? Certainly not.

Let's fix the class:

class Logger
{
	private $file;

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

	public function log(string $message)
	{
		file_put_contents($this->file, $message . "\n", FILE_APPEND);
	}
}

The class is now much clearer, more configurable and therefore more useful.

$logger = new Logger('/path/to/log.txt');
$logger->log('The temperature is 15 °C');

But I Don’t Care!

“When I create an Article object and call save(), I don't want to deal with the database, I just want it to be saved to the one I have set in the configuration. ”

“When I use Logger, I just want the message to be written, and I don't want to deal with where. Let the global settings be used. ”

These are correct comments.

As an example, let's take a class that sends out newsletters and logs how that went:

class NewsletterDistributor
{
	public function distribute()
	{
		$logger = new Logger(/* ... */);
		try {
			$this->sendEmails();
			$logger->log('Emails have been sent out');

		} catch (Exception $e) {
			$logger->log('An error occurred during the sending');
			throw $e;
		}
	}
}

The enhanced Logger, which no longer uses the constant LOG_DIR, requires a file path in the constructor. How to solve this? The NewsletterDistributor class doesn't care where the messages are written, it just wants to write them.

The solution is again rule #1: Let It Be Passed to You: pass all the data the class needs to it.

So we pass the path to the log to the constructor, which we then use to create the Logger object ?

class NewsletterDistributor
{
	public function __construct($file)
	{
		$this->file = $file; // ⛔ NOT THIS WAY!
	}

	public function distribute()
	{
		$logger = new Logger($this->file);

Not like that! Because the path doesn't belong to the data that the NewsletterDistributor class needs; it needs Logger. The class needs the logger itself. And that's what we'll pass on:

class NewsletterDistributor
{
	private $logger;

	public function __construct(Logger $logger)
	{
		$this->logger = $logger; // ✅
	}

	public function distribute()
	{
		try {
			$this->sendEmails();
			$this->logger->log('Emails have been sent out');

		} catch (Exception $e) {
			$this->logger->log('An error occurred during the sending');
			throw $e;
		}
	}
}

Now it is clear from the signatures of the NewsletterDistributor class that logging is part of its functionality. And the task of replacing the logger with another one, perhaps for testing purposes, is quite trivial. Moreover, if the constructor of the Logger class is changed, it will have no effect on our class.

Rule #2: Take What Is Yours

Don't be misled and don't let the parameters of your dependencies be passed to you. Pass on the dependencies directly.

This will make code using other objects completely independent of changes to their constructors. Its API will be truer. And most importantly, it will be trivial to swap those dependencies for others.

A New Member of the Family

The development team decided to create a second logger that writes to the database. So we create a class DatabaseLogger. So we have two classes, Logger and DatabaseLogger, one writes to a file, the other writes to a database … don't you think there's something strange about that name? Wouldn't it be better to rename Logger to FileLogger? Sure it would.

But let's do it smart. We'll create an interface under the original name:

interface Logger
{
	function log($message);
}

…that both loggers will implement:

class FileLogger implements Logger
// ...

class DatabaseLogger implements Logger
// ...

And this way, nothing will need to be changed in the rest of the code where the logger is used. For example, the constructor of the NewsletterDistributor class will still be happy with requiring Logger as a parameter. And it will be up to us which instance we pass to it.

This is why we never give interface names the suffix Interface or the I prefix. Otherwise, it would be impossible to develop code this nicely.

Houston, We Have a Problem

While in the whole application we can be happy with a single instance of a logger, whether file or database, and simply pass it wherever something is logged, it is quite different in the case of the Article class. In fact, we create instances of it as needed, possibly multiple times. How to deal with the database binding in its constructor?

As an example, we can use a controller that should save an article to the database after submitting a form:

class EditController extends Controller
{
	public function formSubmitted($data)
	{
		$article = new Article(/* ... */);
		$article->title = $data->title;
		$article->content = $data->content;
		$article->save();
	}
}

A possible solution is directly offered: have the database object passed by the constructor to EditController and use $article = new Article($this->db).

As in the previous case with Logger and the file path, this is not the correct approach. The database is not a dependency of EditController, but of Article. So passing the database goes against rule #2: take what is yours. When the constructor of the Article class is changed (a new parameter is added), the code in all the places where instances are created will also need to be modified. Ufff.

Houston, what are you suggesting?

Rule #3: Let the Factory Handle It

By removing the hidden bindings and passing all dependencies as arguments, we get more configurable and flexible classes. And thus we need something else to create and configure those more flexible classes. We'll call it factories.

The rule of thumb is: if a class has dependencies, leave the creation of their instances to the factory.

Factories are a smarter replacement for the new operator in the dependency injection world.

Factory

A factory is a method or class that produces and configures objects. We call Article producing class ArticleFactory and it could look like this:

class ArticleFactory
{
	/** @var Nette\Database\Connection */
	private $db;

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

	public function create(): Article
	{
		return new Article($this->db);
	}
}

Its use in the controller would be as follows:

class EditController extends Controller
{
	/** @var ArticleFactory */
	private $articleFactory;

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

	public function formSubmitted($data)
	{
		// let the factory create an object
		$article = $this->articleFactory->create();
		$article->title = $data->title;
		$article->content = $data->content;
		$article->save();
	}
}

At this point, when the signature of the Article class constructor changes, the only part of the code that needs to respond is the ArticleFactory factory itself. Any other code that works with Article objects, such as EditController, will not be affected.

You may be tapping your forehead right now wondering if we helped ourselves at all. The amount of code has grown and the whole thing is starting to look suspiciously complicated.

Don't worry, we will soon get to the Nette DI container. And it has a number of aces up its sleeve that will make building applications using dependency injection extremely simple. For example, instead of the ArticleFactory class, it will be enough to write a simple interface:

interface ArticleFactory
{
	/** @return Article */
	function create();
}

But we're getting ahead of ourselves, hang on :-)

Summary

At the beginning of this chapter, we promised to show you a way for designing clean code. Just give the classes

It may not seem so at first glance, but these three rules have far-reaching implications. They lead to a radically different view of code design. Is it worth it? Programmers who have thrown out old habits and started consistently using dependency injection consider this a pivotal moment in their professional lives. It opened up a world of clear and sustainable applications.

But what if the code doesn't consistently use dependency injection? What if it's built on static methods or singletons? Does it bring any problems? It does, and it's very significant.

version: 3.x 2.x