Définition des services
La configuration est l'endroit où nous apprenons au conteneur DI comment créer chaque service et comment le relier à ses dépendances. Nette propose pour cela une manière très claire et élégante.
La section services du fichier de configuration NEON est l'endroit où nous définissons nos propres services et
leur configuration. Regardons un exemple simple qui définit un service nommé database, représentant une instance
de la classe PDO :
services:
database: PDO('sqlite::memory:')
La configuration ci-dessus produit la méthode fabrique suivante dans le conteneur DI :
public function createServiceDatabase(): PDO
{
return new PDO('sqlite::memory:');
}
Les noms des services permettent de les référencer dans d'autres parties du fichier de configuration, sous la forme
@nomDuService. S'il n'est pas nécessaire de donner un nom au service, nous pouvons simplement utiliser un tiret
(-) :
services:
- PDO('sqlite::memory:')
Pour récupérer un service depuis le conteneur DI, nous pouvons utiliser la méthode getService() avec le nom du
service en paramètre, ou la méthode getByType() avec le type du service :
$database = $container->getService('database');
$database = $container->getByType(PDO::class);
Création de services
Habituellement, nous créons un service simplement en instanciant une classe précise. Par exemple :
services:
database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
Si nous avons besoin d'étoffer la configuration avec d'autres clés, la définition peut s'étaler sur plusieurs lignes :
services:
database:
create: PDO('sqlite::memory:')
setup: ...
La clé create a un alias factory ; les deux variantes sont couramment utilisées. Nous recommandons
cependant create.
Les arguments du constructeur ou de la méthode fabrique peuvent également être indiqués à l'aide de la clé
arguments :
services:
database:
create: PDO
arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret]
Les services ne doivent pas nécessairement être créés par simple instanciation d'une classe ; ils peuvent aussi être le résultat de l'appel de méthodes statiques ou de méthodes d'autres services :
services:
database: DatabaseFactory::create()
router: @routerFactory::create()
Notez que, par souci de simplicité, on écrit :: au lieu de ->, voir Langage d'expressions. Ces méthodes fabriques seront générées :
public function createServiceDatabase(): PDO
{
return DatabaseFactory::create();
}
public function createServiceRouter(): RouteList
{
return $this->getService('routerFactory')->create();
}
Le conteneur DI a besoin de connaître le type du service créé. Si nous créons un service à l'aide d'une méthode qui n'a pas de type de retour déclaré, nous devons indiquer ce type explicitement dans la configuration :
services:
database:
create: DatabaseFactory::create()
type: PDO
Arguments
Nous passons les arguments aux constructeurs et aux méthodes d'une manière très proche de celle de PHP lui-même :
services:
database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
Pour une meilleure lisibilité, nous pouvons énumérer les arguments sur des lignes distinctes. Dans ce cas, les virgules deviennent facultatives :
services:
database: PDO(
'mysql:host=127.0.0.1;dbname=test'
root
secret
)
Vous pouvez aussi nommer les arguments, ce qui vous dispense de vous soucier de leur ordre :
services:
database: PDO(
username: root
password: secret
dsn: 'mysql:host=127.0.0.1;dbname=test'
)
Si vous voulez omettre certains arguments et utiliser leur valeur par défaut, ou faire injecter un service par autowiring, utilisez un tiret bas (_) :
services:
foo: Foo(_, %appDir%)
Les arguments peuvent contenir des services, des paramètres et bien plus encore, voir Langage d'expressions.
Setup
Dans la section setup, nous définissons les méthodes qui doivent être appelées lors de la création du
service.
services:
database:
create: PDO(%dsn%, %user%, %password%)
setup:
- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)
En PHP, cela donnerait ceci :
public function createServiceDatabase(): PDO
{
$service = new PDO('...', '...', '...');
$service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $service;
}
Outre les appels de méthodes, il est aussi possible d'affecter des valeurs à des propriétés. L'ajout d'éléments à des tableaux est également pris en charge ; il faut alors mettre l'accès au tableau entre guillemets pour éviter tout conflit avec la syntaxe NEON :
services:
foo:
create: Foo
setup:
- $value = 123
- '$onClick[]' = [@bar, clickHandler]
Ce qui, en code PHP, donnerait ceci :
public function createServiceFoo(): Foo
{
$service = new Foo;
$service->value = 123;
$service->onClick[] = [$this->getService('bar'), 'clickHandler'];
return $service;
}
Dans le setup, vous pouvez cependant aussi appeler des méthodes statiques ou des méthodes d'autres services. Si vous avez
besoin de passer le service courant lui-même en argument, référencez-le par @self :
services:
foo:
create: Foo
setup:
- My\Helpers::initializeFoo(@self)
- @anotherService::setFoo(@self)
Notez que, par souci de simplicité, on écrit :: au lieu de ->, voir Langage d'expressions. La méthode fabrique suivante sera générée :
public function createServiceFoo(): Foo
{
$service = new Foo;
My\Helpers::initializeFoo($service);
$this->getService('anotherService')->setFoo($service);
return $service;
}
Langage d'expressions
Nette DI propose un langage d'expressions exceptionnellement riche, qui nous permet de définir presque n'importe quoi. Dans les fichiers de configuration, nous pouvons ainsi utiliser des paramètres :
# paramètre
%wwwDir%
# valeur d'un paramètre sous une clé
%mailer.user%
# paramètre à l'intérieur d'une chaîne
'%wwwDir%/images'
Nous pouvons également créer des objets, appeler des méthodes et des fonctions :
# créer un objet
DateTime()
# appeler une méthode statique
Collator::create(%locale%)
# appeler une fonction PHP
::getenv(DB_USER)
Référencer les services par leur nom ou par leur type :
# service par son nom
@database
# service par son type
@Nette\Database\Connection
Utiliser la syntaxe first-class callable :
# créer un callback, équivalent à [@user, logout]
@user::logout(...)
Utiliser des constantes :
# constante de classe
FilesystemIterator::SKIP_DOTS
# obtenir une constante globale à l'aide de la fonction PHP constant()
::constant(\PHP_VERSION)
Accéder aux propriétés publiques et aux constantes d'un service via @service::membre. C'est la première lettre
du nom qui décide s'il s'agit d'une propriété ou d'une constante : une minuscule initiale signifie une propriété publique,
une majuscule une constante :
# propriété publique d'un service (commence par une minuscule)
@settings::apiUrl
# constante de classe d'un service (commence par une majuscule)
@settings::Version
Les appels de méthodes peuvent être chaînés comme en PHP. Par souci de simplicité, on écrit :: au lieu de
-> :
DateTime()::format('Y-m-d')
# PHP: (new DateTime())->format('Y-m-d')
@http.request::getUrl()::getHost()
# PHP: $this->getService('http.request')->getUrl()->getHost()
Vous pouvez utiliser ces expressions partout : lors de la création des services, dans les Arguments, dans la section Setup ou dans les paramètres :
parameters:
ipAddress: @http.request::getRemoteAddress()
services:
database:
create: DatabaseFactory::create( @anotherService::getDsn() )
setup:
- initialize( ::getenv('DB_USER') )
Fonctions spéciales
Dans les fichiers de configuration, vous pouvez utiliser les fonctions spéciales suivantes :
not()inverse une valeurbool(),int(),float(),string()conversion sans perte vers le type indiquétyped()crée un tableau de tous les services du type indiquétagged()crée un tableau de tous les services portant le tag donné
services:
- Foo(
id: int(::getenv('ProjectId'))
productionMode: not(%debugMode%)
)
Contrairement à la conversion standard de PHP, comme (int), la conversion sans perte lève une exception pour les
valeurs non numériques.
La fonction typed() crée un tableau de tous les services du type indiqué (classe ou interface). Elle exclut les
services dont l'autowiring est désactivé. Il est aussi possible d'indiquer plusieurs types, séparés par des virgules.
services:
- BarsDependent( typed(Bar) )
Un tableau de services d'un certain type peut aussi être passé automatiquement en argument grâce à l'autowiring.
La fonction tagged() crée quant à elle un tableau de tous les services portant un tag précis. Là encore, vous
pouvez indiquer plusieurs tags séparés par des virgules.
services:
- LoggersDependent( tagged(logger) )
Autowiring
La clé autowired vous permet d'influencer le comportement de l'autowiring pour un service donné. Pour les
détails, voir le chapitre sur l'autowiring.
services:
foo:
create: Foo
autowired: false # le service foo est exclu de l'autowiring
Services lazy
Le chargement lazy est une technique qui diffère la création d'un service jusqu'à ce qu'il soit réellement nécessaire. Dans la configuration globale, vous pouvez activer la création lazy pour tous les services d'un coup. Pour chaque service, vous pouvez ensuite redéfinir ce comportement :
services:
foo:
create: Foo
lazy: false
Lorsqu'un service est défini comme lazy, nous recevons, en le demandant au conteneur DI, un objet proxy particulier. Ce proxy a l'apparence et le comportement du service réel, mais l'initialisation effective (l'appel du constructeur et des appels de setup) n'a lieu qu'au premier accès à l'une de ses méthodes ou propriétés.
Gardez à l'esprit que, le service étant créé plus tard, les erreurs de sa configuration se manifestent elles aussi plus tard. Par exemple, des identifiants de base de données incorrects ne se révéleront pas au démarrage de l'application, mais seulement à la première requête.
La création lazy atténue également les dépendances circulaires, c'est-à-dire la situation où le service A a besoin du
service B et où B a en même temps besoin de A. Sans elle, le conteneur signale l'erreur
Circular reference detected. Avec un proxy lazy, le service A ne reçoit qu'un proxy du service B, qui s'initialise
au moment où il est réellement utilisé, alors que A existe déjà. Une dépendance circulaire signale néanmoins une
conception défaillante et il vaut mieux s'en débarrasser.
Le chargement lazy nécessite PHP 8.4 ou plus récent et ne fonctionne que pour les services créés par
instanciation directe d'une classe (par exemple create: Foo), pas pour ceux créés par une méthode fabrique. Il ne
peut pas non plus être utilisé pour les classes qui, en fin de compte, étendent une classe interne de PHP. Lorsque le
chargement lazy ne peut pas s'appliquer, le drapeau lazy: true est ignoré silencieusement.
Tags
Les tags servent à ajouter des informations complémentaires aux services. Vous pouvez attribuer un ou plusieurs tags à un service :
services:
foo:
create: Foo
tags:
- cached
Les tags peuvent aussi porter des valeurs :
services:
foo:
create: Foo
tags:
logger: monolog.logger.event
Pour récupérer tous les services associés à des tags précis, vous pouvez utiliser la fonction tagged() :
services:
- LoggersDependent( tagged(logger) )
Au sein du conteneur DI, vous pouvez obtenir les noms de tous les services portant un tag précis à l'aide de la méthode
findByTag() :
$names = $container->findByTag('logger');
// $names est un tableau dont les clés sont les noms des services et les valeurs celles des tags
// par ex. ['foo' => 'monolog.logger.event', ...]
Mode inject
Le drapeau inject: true active l'injection de dépendances par des propriétés publiques portant l'attribut Inject et par les méthodes inject*().
services:
articles:
create: App\Model\Articles
inject: true
Par défaut, le mode inject n'est activé que pour les presenters.
Modification des services
Le conteneur DI contient de nombreux services ajoutés par des extensions intégrées ou écrites par l'utilisateur. Vous pouvez modifier les
définitions de ces services existants directement dans la configuration. Vous pouvez par exemple remplacer la classe du service
application.application, qui est par défaut Nette\Application\Application, par une autre :
services:
application.application:
create: MyApplication
alteration: true
Le drapeau alteration indique que nous ne faisons que modifier un service existant. Il joue aussi le rôle de
garde-fou : si le service modifié n'existe pas, la compilation échoue avec une exception.
Nous pouvons aussi compléter le setup :
services:
application.application:
create: MyApplication
alteration: true
setup:
- '$onStartup[]' = [@resource, init]
Vous n'êtes pas obligé d'identifier un service par son nom interne – vous pouvez le désigner par son type. L'exemple précédent peut aussi s'écrire ainsi :
services:
@Nette\Application\Application:
create: MyApplication
Lors de la modification d'un service, nous pouvons vouloir supprimer les arguments, les éléments de setup ou les tags
d'origine, à l'aide de la clé reset :
services:
application.application:
create: MyApplication
alteration: true
reset:
arguments: true
setup: true
tags: true
Si vous voulez supprimer un service ajouté par une extension, vous pouvez procéder ainsi :
services:
cache.journal: false