Qu'est-ce qu'un conteneur DI ?
Un conteneur d'injection de dépendances (DIC) est une classe qui peut instancier et configurer des objets.
Cela peut vous surprendre, mais dans de nombreux cas, vous n'avez pas besoin d'un conteneur d'injection de dépendances pour profiter des avantages de l'injection de dépendances (DI en abrégé). Après tout, même dans le chapitre d'introduction, nous avons montré la DI avec des exemples concrets, et aucun conteneur n'était nécessaire.
Cependant, si vous devez gérer un grand nombre d'objets différents avec de nombreuses dépendances, un conteneur d'injection de dépendances sera vraiment utile. C'est le cas, par exemple, des applications web construites sur un framework.
Dans le chapitre précédent, nous avons présenté les classes Article
et UserController
. Les deux
ont des dépendances, à savoir la base de données et la factory ArticleFactory
. Et pour ces classes, nous allons
maintenant créer un conteneur. Bien sûr, pour un exemple aussi simple, il n'est pas logique d'avoir un conteneur. Mais nous
allons le créer pour montrer à quoi il ressemble et comment il fonctionne.
Voici un conteneur simple codé en dur pour l'exemple donné :
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'utilisation ressemblerait à ceci :
$container = new Container;
$controller = $container->createUserController();
Nous demandons simplement l'objet au conteneur et nous n'avons plus besoin de savoir comment le créer ni quelles sont ses dépendances ; le conteneur sait tout cela. Les dépendances sont injectées automatiquement par le conteneur. C'est là sa force.
Pour l'instant, le conteneur a toutes les données codées en dur. Faisons donc un pas de plus et ajoutons des paramètres pour rendre le conteneur vraiment 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' => '***',
]);
Les lecteurs attentifs ont peut-être remarqué un certain problème. Chaque fois que j'obtiens un objet
UserController
, une nouvelle instance de ArticleFactory
et de la base de données est également
créée. Ce n'est certainement pas ce que nous voulons.
Ajoutons donc une méthode getService()
qui renverra toujours les mêmes instances :
class Container
{
private array $services = [];
public function __construct(
private array $parameters,
) {
}
public function getService(string $name): object
{
if (!isset($this->services[$name])) {
// getService('Database') appellera createDatabase()
$method = 'create' . $name;
$this->services[$name] = $this->$method();
}
return $this->services[$name];
}
// ...
}
Lors du premier appel, par exemple $container->getService('Database')
, il demandera à
createDatabase()
de créer l'objet de base de données, le stockera dans le tableau $services
et le
renverra directement lors du prochain appel.
Modifions également le reste du conteneur pour utiliser getService()
:
class Container
{
// ...
public function createArticleFactory(): ArticleFactory
{
return new ArticleFactory($this->getService('Database'));
}
public function createUserController(): UserController
{
return new UserController($this->getService('ArticleFactory'));
}
}
Au fait, le terme service désigne tout objet géré par le conteneur. D'où le nom de la méthode
getService()
.
Terminé. Nous avons un conteneur DI entièrement fonctionnel ! Et nous pouvons l'utiliser :
$container = new Container([
'db.dsn' => 'mysql:',
'db.user' => 'root',
'db.password' => '***',
]);
$controller = $container->getService('UserController');
$database = $container->getService('Database');
Comme vous pouvez le voir, écrire un DIC n'est pas compliqué. Il convient de rappeler que les objets eux-mêmes ne savent pas qu'ils sont créés par un conteneur. Par conséquent, il est possible de créer ainsi n'importe quel objet en PHP sans interférer avec son code source.
La création et la maintenance manuelles de la classe du conteneur peuvent rapidement devenir un cauchemar. Dans le chapitre suivant, nous parlerons donc du Conteneur Nette DI, qui peut se générer et se mettre à jour presque tout seul.