Powerful Dependency Injection Container
Nette DI (Dependency Injection Container) is one of the most interesting parts of the Nette Framework. It is compiled, extremely fast and easy to configure.
Let's have an application for sending newsletters. The code is maximally simplified and is available on the GitHub.
We have the object representing email:
class Mail
{
public $subject;
public $message;
}
An object which can send emails:
interface Mailer
{
function send(Mail $mail, $to);
}
Support for logging:
interface Logger
{
function log($message);
}
And finally, a class that provides sending 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, ie. each object uses only variables which we had passed into it.
Also, we can implement own Logger
or Mailer
,
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);
}
}
DI container is the supreme architect which can create individual objects (in the terminology DI called services) and assemble and configure them exactly according to our needs.
Container for our application might look 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 like this because:
- the individual services are created only on demand (lazy loading)
- doubly called
createNewsletterManager
will use the same logger and mailer instances
Let's instantiate Container
, let it create manager and we can
start spamming users with newsletters 🙂
$container = new Container;
$manager = $container->createNewsletterManager();
$manager->distribute(...);
Significant to Dependency Injection is that no class depends on the container. Thus it can be easily replaced with another one. For example with the container generated by Nette DI.
Nette DI
Nette DI is the generator of containers. We instruct it (usually) with
configuration files. This is a configuration that leads to generating nearly the
same class as the class Container
above:
services:
- FileLogger( log.txt )
- SendMailMailer
- NewsletterManager
The big advantage is the shortness of configuration.
Nette DI actually generates the PHP code of container. Therefore it is extremely fast. A developer can see the code, so he knows exactly what it is doing. He can even trace it.
The usage of Nette DI is very easy. In first, install it using Composer (cause downloading ZIP files is so outdated):
composer require nette/di
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 NewsletterManager
and we
can send e-mails:
$manager = $container->getByType('NewsletterManager');
$manager->distribute(['john@example.com', ...]);
The container will be generated only once and the code is stored in the cache
(in directory __DIR__ . '/temp'
). Therefore the loading of the
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 regenerates 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.
Sign in to submit a comment