Qu'est-ce qu'un conteneur DI ?
Le 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 de l'injection de dépendances (DI en abrégé). Après tout, même dans le chapitre précédent, nous avons montré des exemples spécifiques de DI 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. Ce qui est peut-être le cas pour les applications web construites sur un framework.
Dans le chapitre précédent, nous avons présenté les classes Article
et UserController
. Toutes
deux ont quelques dépendances, à savoir la base de données et la fabrique ArticleFactory
. Et pour ces classes,
nous allons maintenant créer un conteneur. Bien sûr, pour un exemple aussi simple, cela n'a pas de sens d'avoir un conteneur.
Mais nous allons en créer un pour montrer comment il se présente et fonctionne.
Voici un conteneur simple codé en dur pour l'exemple ci-dessus :
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 n'avons plus besoin de savoir comment le créer ou quelles sont ses dépendances ; le conteneur sait tout cela. Les dépendances sont injectées automatiquement par le conteneur. C'est là toute sa puissance.
Jusqu'à présent, le conteneur a tout codé en dur. Nous passons donc à l'étape suivante et ajoutons des paramètres pour rendre le conteneur vraiment utile :
classe Container
{
fonction publique __construct(
tableau privé $paramètres,
) {
}
fonction publique createDatabase(): Connexion à la base de données Nette\Database\
{
return new Nette\Database\Connection(
$this->paramètres['db.dsn'],
$this->paramètres['db.user'],
$this->paramètres['db.password'],
);
}
// ...
}
$container = new Container([
'db.dsn' => 'mysql:',
'db.user' => 'root',
'db.password' => '***',
]);
Les lecteurs avisés ont peut-être remarqué un problème. Chaque fois que j'obtiens un objet UserController
, une
nouvelle instance ArticleFactory
et une base de données sont également créées. Nous ne voulons absolument pas
de cela.
Nous ajoutons donc une méthode getService()
qui renverra les mêmes instances encore et encore :
classe Container
{
tableau privé $services = [];
fonction publique __construct(
tableau privé $paramètres,
) {
}
public function getService(string $name): object
{
si (!isset($this->services[$name])) {
// getService('Database') appelle createDatabase()
$method = 'create' . $name;
$this->services[$name] = $this->$method();
}
return $this->services[$name];
}
// ...
}
Le premier appel à $container->getService('Database')
, par exemple, fera en sorte que
createDatabase()
crée un objet de base de données, qu'il stockera dans le tableau $services
et le
renverra directement lors de l'appel suivant.
Nous modifions également le reste du conteneur pour utiliser getService()
:
classe Container
{
// ...
Fonction publique createArticleFactory(): ArticleFactory
{
return new ArticleFactory($this->getService('Database'));
}
public function createUserController(): UserController
{
return new UserController($this->getService('ArticleFactory'));
}
}
Au passage, le terme service désigne tout objet géré par le conteneur. D'où le nom de la méthode
getService()
.
C'est fait. 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, il n'est pas difficile d'écrire un DIC. Il est à noter que les objets eux-mêmes ne savent pas qu'un conteneur est en train de les créer. Ainsi, il est possible de créer n'importe quel objet en PHP de cette façon sans affecter leur code source.
Créer et maintenir manuellement une classe conteneur peut devenir un cauchemar assez rapidement. C'est pourquoi, dans le prochain chapitre, nous parlerons de Nette DI Container, qui peut se générer et se mettre à jour presque automatiquement.