Na navigaci | Klávesové zkratky

Supercharged DI Heart for Your Applications

One of the most interesting parts of Nette, praised even by users of other frameworks, is the Dependency Injection Container (hereinafter referred to as Nette DI). See how easily you can use it anywhere, even outside of Nette. [perex]

Let's consider an application for sending newsletters. I've simplified the code of the individual classes to the core. Here's an object representing an email:

class Mail
{
	public $subject;
	public $message;
}

Someone who knows how to send it:

interface Mailer
{
	function send(Mail $mail, $to);
}

We add support for logging:

interface Logger
{
	function log($message);
}

And finally, a class that manages the distribution of newsletters:

class NewsletterManager
{
	private $mailer;
	private $logger;

	function __construct(Mailer $mailer, Logger $logger)
	{
		$this->mailer = $mailer;
		$this->logger = $logger;
	}

	function distribute(array $recipients)
	{
		$mail = new Mail;
		...
		foreach ($recipients as $recipient) {
			$this->mailer->send($mail, $recipient);
		}
		$this->logger->log(...);
	}
}

The code respects Dependency Injection, meaning each class only works with the variables that we have passed to it. We also have the option to implement Mailer and Logger in our own way, for example like this:

class SendMailMailer implements Mailer
{
	function send(Mail $mail, $to)
	{
		mail($to, $mail->subject, $mail->message);
	}
}

class FileLogger implements Logger
{
	private $file;

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

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

The DI container is the supreme architect that can create individual objects (referred to in DI terminology as services) and assemble and configure them precisely according to our needs.

A container for our application could look something like this:

class Container
{
	private $logger;
	private $mailer;

	function getLogger()
	{
		if (!$this->logger) {
			$this->logger = new FileLogger('log.txt');
		}
		return $this->logger;
	}

	function getMailer()
	{
		if (!$this->mailer) {
			$this->mailer = new SendMailMailer;
		}
		return this->mailer;
	}

	function createNewsletterManager()
	{
		return new NewsletterManager($this->getMailer(), $this->getLogger());
	}
}

The implementation looks this way so that:

  • the individual services are created only when needed (lazy)
  • a double call to createNewsletterManager always uses the same logger and mailer objects

Create an instance of Container, let it produce a manager, and you can start spamming users with newsletters:

$container = new Container;
$manager = $container->createNewsletterManager();
$manager->distribute(...);

The essence of Dependency Injection is that no class depends on the container. Therefore, we can easily replace it with another, perhaps with a container generated by Nette DI.

Nette DI

Nette DI is indeed a container generator. We instruct it (usually) using configuration files, and perhaps this configuration generates roughly the same as the Container class:

services:
	- FileLogger( log.txt )
	- SendMailMailer
	- NewsletterManager

A significant advantage is the brevity of the notation. Additionally, we can add more and more dependencies to individual classes often without needing to modify the configuration.

Nette DI generates actual PHP container code. It is therefore extremely fast, and the programmer knows exactly what it does and can even step through it.

The container might have tens of thousands of lines in the case of large applications, and maintaining something like that manually would probably not be possible.

Deploying Nette DI into our application is very easy. First, we install it using Composer (because downloading ZIPs is so outdated):

composer require nette/di

We save the above configuration in a file config.neon and use the class Nette\DI\ContainerLoader to create the container:

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

compiler->loadConfig(__DIR__ . '/config.neon');
});
$container = new $class;

and then again let it create the NewsletterManager object and we can start sending emails:

$manager = $container->getByType('NewsletterManager');
$manager->distribute(['john@example.com', ...]);

But back to ContainerLoader for a moment. The mentioned syntax is subordinate to one thing: speed. The container is generated once, its code is written to cache (directory __DIR__ . '/temp'), and for subsequent requests, it is just loaded from here. Therefore, the loading of the configuration is placed into a closure in the $loader->load() method.

During development, it is useful to activate the auto-refresh mode, where the container is automatically regenerated if any class or configuration file changes. Just mention true as the second argument in the ContainerLoader constructor.

As you can see, using Nette DI is definitely not limited to applications written in Nette, you can deploy it anywhere with just 3 lines of code. Try playing with it, the whole example is available on GitHub.

10 years ago in section PHP | blog written by David Grudl | back to top

You might be interested in


phpFashion © 2004, 2024 David Grudl | o blogu

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.