Dependency Injection

This chapter introduces you to the basic programming practices that underpin the entire Nette framework and that you should follow when writing your own applications. These are the basics needed to write clean, understandable, and maintainable code.

If you learn and follow these rules, the framework 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 were PHP, it would probably look something like this:

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

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

A few trivial lines of code, yet so many key concepts are hidden in them. We see 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(float $x): float

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

function addition(): float

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.

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

function addition(): float
{
	$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 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(float $a, float $b): float
{
	return $a + $b;
}

Rule #1: Use Parameters

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

If we break this rule, it will be impossible to make the code understandable, clean and sustainable.

If we follow it, we're on our way to code without hidden constraints. Towards code that is understandable not only to the author, but 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 of passing arguments is technically called dependency injection.

(Don't confuse dependency injection with a “dependency injection container”; it is something radically different, and we'll cover containers in next chapter.)

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(float $a, float $b): float
	{
		return $a + $b;
	}
}

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

Or by using other methods, or the constructor directly:

class Addition
{
	private float $a;
	private float $b;

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

	public function calculate(): float
	{
		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 to add numbers. Let's move on to real-life 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(): void
	{
		// 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();

Method save() will store 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 does Article get the database connection, i.e. the class object Nette\Database\Connection?

It seems we have plenty of options. It can take it from some static variable. Or inherit from the class that will provide the database connection. Or take advantage of a so-called 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(): void
	{
		DB::insert(
			'INSERT INTO articles (title, content) VALUES (?, ?)',
			[$this->title, $this->content],
		);
	}
}

Great, we've solved the problem.

Or have we?

Recall rule #1: use parameters: we have to pass all the data the class needs to them. 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, crisp or test? 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): void
	{
		$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 Nette\Database\Connection $db;

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

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

If you are going to write a class that requires a database, for example, don't figure out where to get it from, but let it be 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: use parameters?

We didn't.

The class obtains the key information, the directory containing the log file, from a constant.

See an 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 string $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(): void
	{
		$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;
		}
	}
}

However, the new Logger, which no longer uses the LOG_DIR constant, requires the path to the file 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: use parameters: pass all the data the class needs to it.

So we pass the path to the log to the constructor, which we then use when creating the Logger object? No. Because the path is not the data that the NewsletterDistributor class needs; that's what Logger needs. The class needs the logger itself. And we're going to pass that:

class NewsletterDistributor
{
	private Logger $logger;

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

	public function distribute(): void
	{
		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 class NewsletterDistributor that logging is part of its functionality. And you have the option to replace the logger with another one.

While in the whole application we can be happy with a single instance of the logger and pass it wherever something is logged, it is different in the case of the Article class. We will want to create multiple instances of it. How to deal with the database dependency in the constructor? As an example, let's take a controller that is supposed to save an article to the database after submitting a form:

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

A possible solution is suggested: pass the database object to the UserController by the constructor and use $article = new Article($this->db).

As in the previous case, this is not the correct practice. The database is not a UserController dependency, but an Article dependency. Moreover, the moment the constructor of the Article class is somehow changed (a new parameter is added), we will have to modify the code in all the places where instances are created.

The solution is factories.

Rule #2: Use Factories

By removing the hidden bindings and passing all data as arguments, we get more configurable and flexible classes. Therefore, we still need something to create and configure those more flexible classes. We'll call it a factory.

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 class that creates and configures objects. The factory that produces Article will be called ArticleFactory and its use in the controller will be as follows:

class UserController extends Controller
{
	private ArticleFactory $articleFactory;

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

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

A factory implementation might look like this:

class ArticleFactory
{
	private Nette\Database\Connection $db;

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

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

At this point, when the signature of the class constructor Article changes, the only part of the code that needs to react to this is the factory ArticleFactory. Any other code that works with Article objects, such as UserController, is unaffected.

Summary

At the beginning of this chapter, we promised to demonstrate a simple principle of how to design applications. Although the principle itself is simple (give the classes the data they need), what follows from it requires more thought. Feel free to read this chapter several times.

Programmers who have thrown out old habits and started using dependency injection consistently consider this a pivotal moment in their professional lives. It opened up a world of clear and sustainable applications.

Now we will see what Dependency Injection Container is.