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.