Nette DI Container
******************
.[perex]
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|neon:format]. Le conteneur que nous avons créé manuellement dans le [chapitre précédent|container] s'écrirait ainsi :
```neon
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|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 |services#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|factory] à partir d'une interface fournie. Au lieu de la classe `ArticleFactory`, il nous suffit donc de créer une interface dans l'application :
```php
interface ArticleFactory
{
function create(): Article;
}
```
Vous trouverez l'exemple complet [sur GitHub|https://github.com/nette-examples/di-example-doc].
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é) :
```shell
composer require nette/di
```
Le code suivant utilise le [Compiler |api:Nette\DI\Compiler] pour créer une instance du conteneur DI selon la configuration stockée dans le fichier `config.neon` :
```php
$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` :
```php
$compiler->addExtension('search', new Nette\DI\Extensions\SearchExtension($tempDir));
$compiler->addExtension('extensions', new Nette\DI\Extensions\ExtensionsExtension);
```
Le [Configurator |application:bootstrapping] 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 :
```php
$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` :
```php
$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 |api:Nette\DI\ContainerLoader].
```php
$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 :
```php
$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 |application:bootstrapping#Configuration du Conteneur DI].
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.