Programmation PHP avec Symfony/Service
Principe
modifierLe principe des services Symfony est d'éviter d'instancier la plupart des classes avec des "new" dispersés dans le code, pour les déclarer une seule fois, grâce au container. Ils sont alors instanciés uniquement s'ils sont utilisés (ex : sur la page web courante), grâce au lazy loading du container[1].
Cette déclaration peut se faire en PHP, en YAML ou en XML. On baptise alors le service (il peut y en avoir plusieurs par classe), et on appelle ses arguments par leur nom de service. Exemple :
services:
app.my_namespace.my_service:
class: App\myNamespace\myServiceClass
arguments:
- '%parameter%'
- '@app.my_namespace.my_other_service'
Pas de include ou require
modifierLes classes natives de PHP doivent être introduites par leur namespace ou bien par l'espace de nom global. Ex :
use DateTime;
echo new DateTime();
ou
echo new \DateTime();
Autowiring
modifierAvant SF2.8, il était obligatoire de déclarer chaque service dans les fichiers de configuration .yml ou .yaml, en plus de leurs classes .php (qui peuvent se contredire), et de les mettre à jour à chaque changement de structure.
Depuis SF2.8, l'"autoconfigure: true" permet de déclarer automatiquement chaque service à partir de sa classe, et l'"autowiring: true" d'injecter automatiquement les arguments connus (ex : une autre classe appelée par son espace de nom et son nom), donc sans déclaration manuelle[2].
Depuis SF4, cette déclaration est par défaut sans le fichier services.yaml, mais on peut la placer dans un autre fichier qui sera importé par le premier, par exemple avec :
imports:
- { resource: services1.yaml }
- { resource: services2.yaml }
ou :
imports:
- { resource: services/* }
Cette séparation des services en plusieurs .yaml nécessite par contre d'exclure les dossiers de ces services de l'autowiring, et de reprendre la section _defaults
dans le nouveau .yaml.
Exemple d'exclusion récursive de plusieurs dossiers de même nom, avec ** :
App\:
resource: '../src/*'
exclude:
- '../src/UnDossier'
- '../src/**/Entity' # Tous les sous-dossiers "Entity"
bind
modifierPar défaut, l'autowiring ne fonctionne pas avec les classes avec des tags, ou ayant autre chose que des services dans leurs constructeurs[3]. Néanmoins pour injecter des scalaires automatiquement, il suffit que ces derniers soit déclarés aussi. Ex :
services:
_defaults:
bind:
$salt: 'ma_chaine_de_caractères'
$variableSymfony: '%kernel.project_dir%'
$variableDEnvironnement: '%env(resolve:APP_DEBUG)%'
_instanceof
modifierPour ajouter un tag ou injecter un service si on implémente une interface. Ex :
services:
_instanceof:
Psr\Log\LoggerAwareInterface:
calls:
- [ 'setLogger', [ '@logger' ] ]
Ici, toutes les classes qui implémentent LoggerAwareInterface
verront leurs méthodes setLogger(LoggerInterface $logger)
appelées automatiquement à l’instanciation.
En SF <2.8
modifierLes contrôleurs sont des services qui peuvent en appeler avec la méthode héritée de leur classe mère :
$this->get('app.my_namespace.my_service')
Pour déterminer si un service existe depuis un contrôleur :
$this->getContainer->hasDefinition('app.my_namespace.my_service')
Paramètres
modifierChaque service doit donc être déclaré avec un paramètre "class", puis peut ensuite facultativement contenir les paramètres suivants :
Nom | Rôle |
---|---|
class | Nom de la classe instanciée par le service. |
arguments | Tableau des arguments du constructeur de la classe, services ou variables. |
calls | Tableau des méthodes de la classe à lancer après l'instanciation, généralement des setters. |
factory | Instancie la classe depuis une autre classe donnée. Méthode statique de la classe qui sera renvoyée par le service[4]. |
configurator | Exécute un invocable donné après l'instanciation de la classe[5]. |
alias | Crée un autre nom pour un service, qui peut alors être modifié par d'autres paramètres de déclaration (ex : créer une version publique d'un service privé dans services_test.yaml[6]). |
parent | Nom de la superclasse. |
abstract | Booléen indiquant si la méthode est abstraite. |
public | Booléen indiquant une portée publique du service. |
shared | Booléen indiquant un singleton. |
tags | Quand on doit injecter un nombre indéterminé de services dans un autre, il est possible de le définir avec chacun des services à injecter, en y ajoutant un tag avec le nom du service qui peut les appeler. Ce tag doit néanmoins être défini dans un CompilerPass[7]. |
autowire | Booléen vrai par défaut, spécifiant si le framework doit injecter automatiquement les arguments du constructeur. |
decorates | Remplace un service par sa version décorée (mais l'ancien est toujours accessible an ajoutant le suffixe .inner au service décorateur)[8] |
Injecter des services tagués
modifierDans un constructeur :
App\Service\FactoriesHandler:
arguments:
- !tagged_iterator app.factory
Dans une autre méthode :
App\Service\FactoriesHandler:
calls:
- [ 'setFactories', [!tagged_iterator app.factory] ]
Par défaut, l'itérateur contient des clés numériques, mais on peut les personnaliser[9]. Ex :
App\Factory\FactoryOne:
tags:
- { name: 'app.factory', my_key: 'factory_one' }
App\Service\FactoriesHandler:
arguments:
- !tagged_iterator { tag: 'app.factory', key: 'my_key' }
Service abstrait
modifierUn service abstrait est un système de factorisation des injections par l'intermédiaire d'une classe abstraite. Par exemple si on veut que tous les contrôleurs héritent du service logger
(comme l'exemple _instanceof
ci-dessus), plus la méthode setLogger()
de leur classe abstraite, sans avoir à toucher à leurs constructeurs :
App\Controller\:
resource: '../src/Controller'
parent: App\Controller\AbstractEntitiesController
tags: ['controller.service_arguments']
App\Controller\AbstractEntitiesController:
abstract: true
autoconfigure: false
calls:
- [ 'setLogger', [ '@logger' ] ]
Références
modifier- ↑ https://symfony.com/doc/3.4/service_container.html
- ↑ https://symfony.com/doc/current/service_container/autowiring.html
- ↑ https://symfony.com/doc/current/service_container/autowiring.html#fixing-non-autowireable-arguments
- ↑ https://symfony.com/doc/current/service_container/factories.html
- ↑ https://symfony.com/doc/current/service_container/configurators.html
- ↑ https://symfony.com/doc/current/testing.html
- ↑ https://symfony.com/doc/current/service_container/compiler_passes.html
- ↑ https://symfony.com/doc/current/service_container/service_decoration.html
- ↑ https://symfony.com/doc/5.4/service_container/tags.html#tagged-services-with-index