Skip to content

nette-examples/di-example-newsletter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Example of using Nette DI Container

The essence of Dependency Injection (DI) is to unburden classes from creating objects they rely on. Such objects are termed services. Further details can be found on the official website.

Installation

To get started:

git clone https://github.com/nette-examples/di-example-newsletter
cd di-example-newsletter
composer install

Run the Demo

Execute the demo via:

php example.php

PHP Requirements

Ensure your environment runs PHP version 8.1.

Using the Application

Consider an application designed for newsletter distribution:

The Mail class represents an email:

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

The Mailer interface defines email sending:

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

The Logger interface provides logging capabilities:

interface Logger
{
	function log(string $message): void;
}

The NewsletterManager class manages newsletter distribution:

class NewsletterManager
{
	private Mailer $mailer;
	private Logger $logger;

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

	public function distribute(array $recipients): void
	{
		$mail = new Mail;
		$mail->subject = '...';
		$mail->message = '...';

		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 have a ability to implement own Logger or Mailer, like this:

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

class FileLogger implements Logger
{
	private string $file;

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

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

DI container is the central orchestrator that can instantiate individual objects (referred to as services in DI terminology) and configure them as required.

An example container for our application:

class Container
{
	private ?Logger $logger;
	private ?Mailer $mailer;

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

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

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

The key advantage of DI is that no class is directly dependent on the container, enabling easy substitution with another container, like one generated by Nette DI.

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.

Introduction to Nette DI

Nette DI acts as a dynamic container generator, primarily utilizing configuration files to shape its behavior. The configuration shown below exemplifies a structure similar to the manually defined Container class:

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

A standout feature of Nette DI is the succinctness of its configuration syntax.

One of the pivotal aspects of Nette DI is its ability to generate the actual PHP code for the container. This ensures not just high performance but also transparency, allowing developers to delve into the generated code for clarity and debugging purposes.

Setting up and using Nette DI is intuitive:

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

Once the container is set up, it's easy to instantiate services like the NewsletterManager and perform actions, such as sending out emails:

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

For efficiency, the container is generated just once, with the resultant code cached in the specified directory (__DIR__ . '/temp'). The configuration file is thus loaded inside the closure passed to $loader->load(), ensuring it's invoked a singular time.

To enhance the development experience, Nette DI offers an auto-refresh mode. When enabled, it detects and incorporates changes from any modified class or configuration file. This feature can be activated by passing true as the second argument during ContainerLoader instantiation:

$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', autoRebuild: true);

About

Dependency Injection Container example

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages