Nette DI Container

Nette DI est l'une des bibliothèques les plus intéressantes de Nette. Elle sait générer et mettre à jour automatiquement des conteneurs DI compilés, extrêmement rapides et remarquablement faciles à configurer.

La forme des services que le conteneur DI doit créer se définit habituellement à l'aide de fichiers de configuration au format NEON. Le conteneur que nous avons créé manuellement dans le chapitre précédent s'écrirait ainsi :

parameters:
	db:
		dsn: 'mysql:'
		user: root
		password: '***'

services:
	- Nette\Database\Connection(%db.dsn%, %db.user%, %db.password%)
	- ArticleFactory
	- EditController

La syntaxe est très concise.

Toutes les dépendances déclarées dans les constructeurs des classes ArticleFactory et EditController sont détectées et passées automatiquement par Nette DI grâce à ce qu'on appelle l'autowiring, il n'y a donc rien à indiquer dans le fichier de configuration. Ainsi, même si les paramètres changent, vous n'avez rien à modifier dans la configuration. Pendant le développement, Nette régénère le conteneur automatiquement. Vous pouvez vous concentrer purement sur le développement de l'application.

Si nous voulons passer les dépendances à l'aide de setters, nous utilisons pour cela la section setup.

Nette DI génère directement le code PHP du conteneur. Le résultat est donc un fichier .php que vous pouvez ouvrir et examiner. Vous voyez ainsi exactement comment le conteneur fonctionne. Vous pouvez aussi le déboguer dans votre IDE et parcourir son exécution pas à pas. Et surtout : le code PHP généré est extrêmement rapide.

Nette DI sait également générer le code d'une factory à partir d'une interface fournie. Au lieu de la classe ArticleFactory, il nous suffit donc de créer une interface dans l'application :

interface ArticleFactory
{
	function create(): Article;
}

Vous trouverez l'exemple complet sur GitHub.

Utilisation autonome

Intégrer la bibliothèque Nette DI dans une application est très facile. Nous l'installons d'abord à l'aide de Composer (parce que télécharger des fichiers zip, c'est tellement dépassé) :

composer require nette/di

Le code suivant utilise le Compiler pour créer une instance du conteneur DI selon la configuration stockée dans le fichier config.neon :

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

Le conteneur n'est généré qu'une seule fois, son code est écrit dans le cache (le répertoire __DIR__ . '/temp') et, lors des requêtes suivantes, il est seulement chargé depuis là.

Seul, le Compiler n'active dans la configuration que les sections services et parameters. Pour utiliser les autres – comme search, decorator, di ou inject – enregistrez d'abord leurs extensions. Et pour permettre l'enregistrement d'extensions depuis la section extensions de la configuration, ajoutez l'ExtensionsExtension :

$compiler->addExtension('search', new Nette\DI\Extensions\SearchExtension($tempDir));
$compiler->addExtension('extensions', new Nette\DI\Extensions\ExtensionsExtension);

Le Configurator utilisé dans les applications Nette complètes les enregistre tous automatiquement.

Si vous conservez plusieurs conteneurs différents dans le même répertoire de cache, distinguez-les par une clé passée en deuxième argument à load() ; elle fait partie du nom de la classe générée :

$class = $loader->load(
	fn($compiler) => $compiler->loadConfig(__DIR__ . '/config.neon'),
	'my-key',
);

Les méthodes getService() ou getByType() servent à créer et à récupérer les services. Voici comment nous créons l'objet EditController :

$controller = $container->getByType(EditController::class);
$controller->someMethod();

Pendant le développement, il est utile d'activer le mode de rafraîchissement automatique, où le conteneur se régénère dès qu'une classe ou un fichier de configuration est modifié. Il suffit de passer true en deuxième argument du constructeur de ContainerLoader.

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

Travailler avec le conteneur

Outre getService() et getByType(), l'objet conteneur offre plusieurs autres méthodes utiles :

  • getByType(string $type, bool $throw = true): ?object renvoie le service du type donné. Si vous passez false en deuxième argument, il renvoie null au lieu de lever une exception lorsqu'aucun service de ce type n'existe.
  • hasService(string $name): bool et isCreated(string $name): bool vous disent si un service est défini et s'il a déjà été instancié.
  • getParameters(): array renvoie tous les paramètres du conteneur, getParameter($key) en renvoie un seul.
  • createInstance(string $class, array $args = []): object crée une nouvelle instance de la classe donnée et lui passe les dépendances du constructeur par autowiring.
  • callMethod(callable $function, array $args = []): mixed appelle le callable donné et lui passe ses arguments par autowiring.
  • callInjects(object $service): void appelle toutes les méthodes inject*() de l'objet donné et leur passe les dépendances.

Le constructeur du conteneur accepte également un tableau de paramètres qui complètent ceux définis dans la configuration :

$container = new $class(['host' => 'localhost']);

Utilisation avec Nette Framework

Comme nous l'avons montré, l'utilisation de Nette DI n'est pas réservée aux applications construites avec Nette Framework ; vous pouvez l'intégrer n'importe où en seulement trois lignes de code. Cependant, si vous développez des applications à l'aide de Nette Framework, la configuration et la création du conteneur sont prises en charge par Bootstrap.

version: 3.x