Nette Documentation Preview

syntax
Cos'è un container DI?
**********************
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Cos'è un container DI?

Un container dependency injection (DIC) è una classe che può istanziare e configurare oggetti.

Potrebbe sorprenderti, ma in molti casi non hai bisogno di un container dependency injection per sfruttare i vantaggi della dependency injection (abbreviato DI). Dopotutto, anche nel capitolo introduttivo abbiamo mostrato DI con esempi concreti e non era necessario alcun container.

Tuttavia, se devi gestire un gran numero di oggetti diversi con molte dipendenze, un container dependency injection sarà davvero utile. Questo è il caso, ad esempio, delle applicazioni web costruite su un framework.

Nel capitolo precedente, abbiamo introdotto le classi Article e UserController. Entrambe hanno alcune dipendenze, ovvero il database e la factory ArticleFactory. E per queste classi creeremo ora un container. Ovviamente, per un esempio così semplice, non ha senso avere un container. Ma lo creeremo per mostrare come appare e funziona.

Ecco un semplice container hardcoded per l'esempio fornito:

class Container
{
	public function createDatabase(): Nette\Database\Connection
	{
		return new Nette\Database\Connection('mysql:', 'root', '***');
	}

	public function createArticleFactory(): ArticleFactory
	{
		return new ArticleFactory($this->createDatabase());
	}

	public function createUserController(): UserController
	{
		return new UserController($this->createArticleFactory());
	}
}

L'utilizzo sarebbe il seguente:

$container = new Container;
$controller = $container->createUserController();

Chiediamo semplicemente l'oggetto al container e non dobbiamo più sapere nulla su come crearlo e quali dipendenze ha; il container sa tutto questo. Le dipendenze vengono iniettate automaticamente dal container. In questo sta la sua forza.

Per ora, il container ha tutti i dati scritti in modo fisso. Faremo quindi il passo successivo e aggiungeremo parametri per rendere il container veramente utile:

class Container
{
	public function __construct(
		private array $parameters,
	) {
	}

	public function createDatabase(): Nette\Database\Connection
	{
		return new Nette\Database\Connection(
			$this->parameters['db.dsn'],
			$this->parameters['db.user'],
			$this->parameters['db.password'],
		);
	}

	// ...
}

$container = new Container([
	'db.dsn' => 'mysql:',
	'db.user' => 'root',
	'db.password' => '***',
]);

I lettori attenti potrebbero aver notato un certo problema. Ogni volta che ottengo un oggetto UserController, viene creata anche una nuova istanza di ArticleFactory e del database. Questo decisamente non lo vogliamo.

Aggiungeremo quindi un metodo getService(), che restituirà sempre le stesse istanze:

class Container
{
	private array $services = [];

	public function __construct(
		private array $parameters,
	) {
	}

	public function getService(string $name): object
	{
		if (!isset($this->services[$name])) {
			// getService('Database') chiamerà createDatabase()
			$method = 'create' . $name;
			$this->services[$name] = $this->$method();
		}
		return $this->services[$name];
	}

	// ...
}

Alla prima chiamata, ad esempio $container->getService('Database'), farà creare l'oggetto database da createDatabase(), lo salverà nell'array $services e alla chiamata successiva lo restituirà direttamente.

Modificheremo anche il resto del container per utilizzare getService():

class Container
{
	// ...

	public function createArticleFactory(): ArticleFactory
	{
		return new ArticleFactory($this->getService('Database'));
	}

	public function createUserController(): UserController
	{
		return new UserController($this->getService('ArticleFactory'));
	}
}

A proposito, il termine servizio si riferisce a qualsiasi oggetto gestito dal container. Ecco perché anche il nome del metodo getService().

Fatto. Abbiamo un container DI completamente funzionante! E possiamo usarlo:

$container = new Container([
	'db.dsn' => 'mysql:',
	'db.user' => 'root',
	'db.password' => '***',
]);

$controller = $container->getService('UserController');
$database = $container->getService('Database');

Come vedi, scrivere un DIC non è complicato. Vale la pena ricordare che gli oggetti stessi non sanno di essere creati da un container. Di conseguenza, è possibile creare in questo modo qualsiasi oggetto in PHP senza intervenire sul suo codice sorgente.

La creazione e la manutenzione manuale della classe del container possono diventare rapidamente un incubo. Nel prossimo capitolo, parleremo quindi del Container Nette DI, che può generarsi e aggiornarsi quasi da solo.