Vous pouvez imaginer l'application comme un ensemble de services. Par exemple, vous pouvez avoir un service d'authentification responsable de la connexion des utilisateurs du site, d'un service de gestionnaire d'entités chargé d'accéder à la base de données, d'un service de gestion des événements chargé de déclencher les événements et de les transmettre aux écouteurs d'événements.
Dans Zend Framework, la classe ServiceManager
est un conteneur centralisé pour tous les services
d'application. Le gestionnaire de service est implémenté dans le composant Zend\ServiceManager
en tant que classe ServiceManager
. Le diagramme d'héritage de classe est montré dans la figure 3.5 ci-dessous :
Le gestionnaire de service est créé au démarrage de l'application (à l'intérieur de la méthode statique init()
de la classe Zend\Mvc\Application
). Les services standard disponibles via le gestionnaire de services
sont présentés dans le tableau 3.1. Cette table est incomplète car le nombre réel de services déclarés
dans le gestionnaire de services peut être beaucoup plus important.
Nom du service | Description |
---|---|
Application |
Permet de récupérer le singleton de la classe Zend\Mvc\Application . |
ApplicationConfig |
Tableau de configuration extrait du fichier application.config.php. |
Config |
Tableau de configuration extrait des fichiers module.config.php fusionné avec autoload/global.php et autoload/local.php. |
EventManager |
Permet de récupérer une nouvelle instance de la classeZend\EventManager\EventManager . Le gestionnaire d'événements permet d'envoyer (déclencher) des événements et d'attacher des écouteurs d'événements. |
SharedEventManager |
Permet de récupérer l'instance singleton de la classe Zend\EventManager\SharedEventManager .Le gestionnaire d'événements partagés permet d'écouter les événements définis par d'autres classes et composants. |
ModuleManager |
Permet de récupérer le singleton de la classe Zend\ModuleManager\ModuleManager . Le gestionnaire de module est responsable du chargement des modules d'application. |
Request |
Le singleton de la classe Zend\Http\Request . Représente la requête HTTP reçue du client. |
Response |
Le singleton de la classe Zend\Http\Response . Représente la réponse HTTP qui sera envoyée au client. |
Router |
Le singleton de Zend\Router\Http\TreeRouteStack . Effectue le routage d'URL. |
ServiceManager |
Le gestionnaire de service lui même. |
ViewManager |
Le singleton de la classe Zend\Mvc\View\Http\ViewManager . Responsable de la préparation de la vue pour le rendu de la page. |
Un service est généralement composé d'une classe PHP basique, mais pas toujours. Par exemple, lorsque ZF3 charge
les fichiers de configuration et fusionne les données dans des tableaux imbriqués, il déclare les tableaux
dans le gestionnaire de services sous la forme d'un couple de services (!): ApplicationConfig
et Config
.
Le premier est le tableau chargé à partir du fichier de configuration au niveau de l'application
application.config.php et le second est le tableau fusionné des fichiers de configuration au niveau du
module et des fichiers de configuration au niveau de l'application chargés automatiquement.
Ainsi, dans le gestionnaire de services, vous pouvez stocker tout ce que vous voulez : une classe PHP,
une variable simple ou un tableau.
À partir du tableau 3.1, vous pouvez voir que dans ZF3 presque tout peut être considéré comme un service.
Le gestionnaire de services est lui-même déclaré en tant que tel. Ainsi que la classe Application
.
Une chose importante que vous devez noter à propos des services est qu'ils sont stockés dans une seule instance (ceci est également appelé le modèle singleton). Évidemment, vous n'avez pas besoin de la deuxième instance de la classe
Application
(ce qui serait un vrai cauchemar).
Mais, il existe une exception importante à la règle ci-dessus. Ca peut être déroutant au début, mais l'
EventManager
n'est pas un singleton. Chaque fois que vous récupérez le service du gestionnaire d'événements auprès du gestionnaire de services, vous recevez un nouvel objet. Tout ça pour des raisons de performance et pour éviter d'éventuels conflits d'événements entre différents composants. Nous en discuterons plus en détail dans la section About Event Manager plus loin dans ce chapitre.
Le gestionnaire de services définit plusieurs méthodes nécessaires pour localiser et récupérer un service auprès du gestionnaire de services (voir le tableau 3.2 ci-dessous) :
Nom de la méthode | Description |
---|---|
has($name) |
Vérifie si un tel service est déclaré. |
get($name) |
Récupère l'instance d'un service déclaré. |
build($name, $options) |
Renvoie toujours une nouvelle instance du service demandé.. |
Vous pouvez tester si un service est déclaré en transmettant son nom à la méthode has()
du gestionnaire
de services. Il renvoie le booléen vrai
si le service est déclaré ou faux
si un service avec un tel nom
n'est pas déclaré.
Vous pouvez récupérer un service par son nom à l'aide de la méthode `get() du gestionnaire de services. Cette méthode prend un seul paramètre représentant le nom du service. Regardez l'exemple suivant :
<?php
// Récupère le tableau de configuration de l'application.
$appConfig = $serviceManager->get('ApplicationConfig');
// L'utilise (par exemple, en récupérant la liste des modules).
$modules = $appConfig['modules'];
Et la méthode build()
qui crée toujours une nouvelle instance du service lorsque vous l'appelez
(contrairement à get()
qui crée l'instance du service une seule fois et la renvoie sur les requêtes ultérieures).
En règle générale, vous récupérez les services du gestionnaire de services non pas dans votre code mais à l'intérieur d'une fabrique. Une fabrique est un code responsable de la création d'un objet. Lors de la création de l'objet, vous pouvez extraire les services dont il dépend du gestionnaire de services et transmettre ces services (dépendances) au constructeur de l'objet. Ceci est également appelé injection de dépendance.
Si vous avez un peu d'expérience avec Zend Framework 2, vous remarquerez peut-être que les choses sont maintenant un peu différentes. Dans ZF2, il y avait un modèle
ServiceLocator
permettant d'obtenir des dépendances du gestionnaire de service dans n'importe quelle partie de votre application (dans les contrôleurs, les services, etc.). Dans ZF3, vous devez transmettre les dépendances explicitement. C'est un peu plus galère mais ça évite les dépendances "cachées" et rend votre code plus clair et plus facile à comprendre.
Lorsque vous codez votre site web, vous devrez souvent déclarer votre propre service dans le gestionnaire
de services. L'un des moyens de déclarer un service consiste à utiliser la méthode setService()
du
gestionnaire de services. Par exemple, créons et déclarons la classe de service "convertisseur de devise",
qui sera utilisée, par exemple, sur une page de panier pour convertir la devise EUR en USD:
<?php
// Définis l'espace de noms où notre service personnalisé est situé.
namespace Application\Service;
// Définis la classe de service CurrencyConverter.
class CurrencyConverter
{
// Convertit les euros en dollars américains.
public function convertEURtoUSD($amount)
{
return $amount*1.25;
}
//...
}
Ci-dessus, dans les lignes 6-15 nous définissons un exemple de classe CurrencyConverter
(pour simplifier,
nous implémentons une seule méthode convertEURtoUSD()
qui est capable de convertir des euros en dollars
américains).
// Création d'une instance de la classe.
$service = new CurrencyConverter();
// Déclaration de l'instance dans le gestionnaire de service.
$serviceManager->setService(CurrencyConverter::class, $service);
Dans l'exemple ci-dessus, nous instancions la classe avec le nouvel opérateur et l'enregistrons avec
le gestionnaire de service en utilisant la méthode setService()
(nous supposons que la variable
$serviceManager
est une classe de type Zend\ServiceManager\ServiceManager
et qu'elle a été déclarée
ailleurs).
La méthode setService()
prend deux paramètres : le nom du service et l'instance du service.
Le nom du service doit être unique dans tous les autres services possibles.
Une fois le service stocké dans le gestionnaire de services, vous pouvez le récupérer par son nom à
n'importe quel endroit de votre application à l'aide de la méthode get()
du gestionnaire de services.
Regardez l'exemple suivant :
<?php
// Récupère le service de convertion de devises.
$service = $serviceManager->get(CurrencyConverter::class);
// Et l'utilise (convertis une somme d'argent).
$convertedAmount = $service->convertEURtoUSD(50);
Différents services peuvent utiliser différents styles de nommage. Par exemple, le même service de
convertisseur de devises peut être chargé sous différents noms :
CurrencyConverter
, currency_converter
et ainsi de suite.
Pour introduire une convention de nommage uniforme, il est recommandé de charger un service par son nom de
classe complet, comme cela :
$serviceManager->setService(CurrencyConverter::class);
Dans l'exemple ci-dessus, nous avons utilisé le mot-clé class
. Il est disponible depuis PHP 5.5 et est
utilisé pour la résolution de noms de classe. CurrencyConverter::class
est appelé avec le nom complet
de la classe comme \Application\Service\CurrencyConverter
.
Si vous essayez de déclarer un nom de service qui est déjà présent, la méthode setService()
lèvera une
exception. Mais parfois vous voulez remplacer un service avec un service du même nom.
Pour ce faire, vous pouvez utiliser la méthode setAllowOverride()
du gestionnaire de services :
<?php
// Autorise le remplacement des services
$serviceManager->setAllowOverride(true);
// Sauvegarde l'instance dans le gestionnaire de service. Il n'y aura pas d'exception
// même s'il y a un autre service avec le même nom.
$serviceManager->setService(CurrencyConverter::class, $service);
Ci-dessus, la méthode setAllowOverride()
prend un paramètre booléen unique définissant si vous
permettez de remplacer le service CurrencyConverter
si un tel nom est déjà présent ou non.
Ce qui est dommage avec la méthode setService()
, c'est que vous devez créer l'instance du service
avant d'en avoir vraiment besoin. Si vous n'utilisez jamais le service, l'instanciation du service ne
sera qu'une perte de temps et de mémoire. Pour résoudre ce problème, le gestionnaire de services vous
fournit la méthode setInvokableClass()
.
<?php
// Déclare une classe invokable
$serviceManager->setInvokableClass(CurrencyConverter::class);
Dans l'exemple ci-dessus, nous transmettons au gestionnaire de service le nom de classe complet du service
au lieu de transmettre son instance. Avec cette technique, le service sera instancié par le gestionnaire de
service uniquement lorsque quelqu'un appelle la méthode get(CurrencyConverter::class)
. Ceci est également
appelé lazy loading.
Les services dépendent souvent les uns des autres. Par exemple, le service de conversion de devises peut utiliser le service de gestionnaire d'entités pour lire les taux de change en base de données. L'inconvénient de la méthode
setInvokableClass()
est qu'elle ne permet pas de passer des paramètres (dépendances) au service lors de l'instanciation de l'objet. Pour résoudre ce problème, vous pouvez utiliser des fabriques, comme décrit ci-dessous.
Une fabrique (factory en anglais) est une classe qui ne peut faire qu'une seule chose - créer d'autres objets.
Vous déclarez une fabrique pour un service avec la méthode setFactory()
du gestionnaire de service :
La fabrique la plus simple est InvokableFactory
- elle est analogue à la méthode setInvokableClass()` de la section précédente.
<?php
use Zend\ServiceManager\Factory\InvokableFactory;
// L'équivalent à la méthode setInvokableClass () de la section précédente.
$serviceManager->setFactory(CurrencyConverter::class, InvokableFactory::class);
Après avoir enregistré la fabrique, vous pouvez récupérer le service auprès du gestionnaire de service
comme d'habitude avec la méthode get()
. Le service sera instancié uniquement lorsque vous l'extrayez
du gestionnaire de service (lazy loading).
Parfois, l'instanciation de service est plus complexe que la simple création de l'instance de service
avec l'opérateur new
(comme le fait InvokableFactory
). Vous devrez peut-être passer certains paramètres
au constructeur du service ou appeler certaines méthodes juste après sa construction. Cette logique
d'instanciation complexe peut être encapsulée dans votre propre classe de fabrique personnalisée.
La classe de fabrique implémente alors l'interface FactoryInterface
:
<?php
namespace Zend\ServiceManager\Factory;
use Interop\Container\ContainerInterface;
interface FactoryInterface
{
public function __invoke(ContainerInterface $container,
$requestedName, array $options = null);
}
Comme nous le voyons dans la définition de FactoryInterface
,
la classe factory doit fournir la méthode magique __invoke
renvoyant l'instance d'un seul service.
Le gestionnaire de service est transmis à la méthode __invoke
en tant que paramètre $container
;
il peut être utilisé lors de la construction du service pour accéder à d'autres services (pour injecter des
dépendances). Le deuxième argument ($requestedName
) est le nom du service. Le troisième argument
($options
) peut être utilisé pour transmettre certains paramètres au service et n'est utilisé que
lorsque vous demandez le service avec la méthode build()
du gestionnaire de services.
A titre d'exemple, écrivons une fabrique pour notre service de convertisseur de devises (voir le code
ci-dessous). Nous n'utilisons pas de logiques de construction complexes pour notre service
CurrencyConverter
mais pour des services plus complexes, vous devrez peut-être en utiliser un. (???)
<?php
namespace Application\Service\Factory;
use Zend\ServiceManager\Factory\FactoryInterface;
use Application\Service\CurrencyConverter;
// Factory class
class CurrencyConverterFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container,
$requestedName, array $options = null)
{
// Create an instance of the class.
$service = new CurrencyConverter();
return $service;
}
}
Techniquement, dans ZF3 vous pouvez utiliser la même classe de fabrique pour instancier plusieurs services qui ont un code d'instanciation similaire (pour cela, vous pouvez utiliser l'argument
$requestedName
passé à la méthode__invoke()
de la fabrique). Cependant, la plupart du temps, vous allez créer une fabrique différente pour chaque service.
Un cas encore plus complexe d'usage de classe fabrique est lorsque vous devez déterminer au moment de
l'exécution quels noms de service doivent être enregistrés. Pour une telle situation, vous pouvez utiliser
une fabrique abstraite. Une classe de fabrique abstraite doit implémenter l'interface
AbstractFactoryInterface
:
<?php
namespace Zend\ServiceManager\Factory;
use Interop\Container\ContainerInterface;
interface AbstractFactoryInterface extends FactoryInterface
{
public function canCreate(ContainerInterface $container, $requestedName);
}
Une fabrique abstraite a deux méthodes : canCreate()
et __invoke()
. La première est nécessaire pour tester si la fabrique peut créer le service avec un
certain nom et le second permet réellement de créer le service. Les méthodes prennent deux paramètres :
le service manager ($container
) et le nom du service ($requestedName
).
La différence une classe de fabrique basique est qu'elle ne crée généralement qu'un seul type de service mais une fabrique abstraite peut créer dynamiquement autant de types de services qu'elle le souhaite.
Vous déclarez une fabrique abstraite avec la méthode setAbstractFactory()
du gestionnaire de service.
Les fabriques abstraites sont une fonctionnalité puissante mais vous ne devriez les utiliser que lorsque c'est vraiment nécessaire car elles ont un impact négatif sur les performances. Il est préférable d'utiliser les fabriques habituelles (non abstraites).
Parfois, vous pouvez définir un alias pour un service. L'alias est comme un lien symbolique :
il fait référence à un service déjà déclaré. Pour créer un alias, utilisez la méthode setAlias()
du gestionnaire de services :
<?php
// Déclare un alias pour le service CurrencyConverter
$serviceManager->setAlias('CurConv', CurrencyConverter::class);
Une fois déclaré, vous pouvez récupérer le service par son nom ou son alias à l'aide de la méthode get()
du gestionnaire de services.
Par défaut, les services sont stockés dans le gestionnaire de services dans une seule instance. Egalement
appelé modèle de conception singleton. Par exemple, lorsque vous essayez de récupérer le service
CurrencyConverter
deux fois, vous recevrez le même objet. On appelle celà un service partagé.
Mais, dans certaines situations (rares), vous devrez créer une nouvelle instance d'un service à chaque fois
que quelqu'un le demandera au gestionnaire de services. Un bon exemple est l'EventManager
- vous obtenez
une nouvelle instance à chaque fois que vous l'appelez.
Pour définir un service comme étant non partagé, vous pouvez utiliser la méthode setShared()
du
gestionnaire de service :
$serviceManager->setShared('EventManager', false);
Sur votre site internet, vous utilisez le fichier de configuration du gestionnaire de services pour déclarer vos services (au lieu d'appeler les méthodes du gestionnaire de service comme décrit ci-dessus).
Pour déclarer automatiquement un service dans le gestionnaire de service, la clé service_manager
d'un
fichier de configuration est généralement utilisée. Vous pouvez placer cette clé dans un fichier de
configuration au niveau de l'application ou dans un fichier de configuration au niveau du module.
Si vous placez cette clé dans un fichier de configuration au niveau du module, faites attention au risque d'écrasement de nom lors de la fusion des configs. Ne déclarez pas le même nom de service dans différents modules.
Cette clé du service_manager
devrait ressembler à ça :
<?php
return [
//...
// On déclare les services sous cette clé
'service_manager' => [
'services' => [
// Ici les instances de classe service
//...
],
'invokables' => [
// Ici les instances de classe service invokable
//...
],
'factories' => [
// Ici les instances de classe fabrique
//...
],
'abstract_factories' => [
// Ici les instances de classe abstraite fabrique
//...
],
'aliases' => [
// Ici les alias des services déclarés au dessus
//...
],
'shared' => [
// Spécifiez ici quels services ne doivent pas être partagés
]
],
//...
];
Dans l'exemple ci-dessus, vous pouvez voir que la clé service_manager
peut contenir plusieurs sous-clés
pour déclarer des services de différentes manières :
services
(ligne 7) permet de déclarer des instances de classe;invokables
(ligne 11) permet de déclarer le nom complet d'un service; le service sera instancié en utilisant le lazy loading;factories
(ligne 15) permet de déclarer une fabrique, capable de créer des instances d'un seul service;abstract_factories
(ligne 19) peut être utilisé pour déclarer des fabriques abstraites, qui sont capables de déclarer plusieurs services par leur nom;aliases
(ligne 23) offre la possibilité de déclarer un alias pour un service.shared
(ligne 27) permet de spécifier quels services ne doivent pas être partagésÀ titre d'exemple, déclarons notre service CurrencyConverter
et créons un alias pour celui-ci :
<?php
use Zend\ServiceManager\Factory\InvokableFactory;
use Application\Service\CurrencyConverter;
return [
//...
// On déclare les services sous cette clé
'service_manager' => [
'factories' => [
// On déclare le service CurrencyConverter.
CurrencyConverter::class => InvokableFactory::class
],
'aliases' => [
// On déclare un alias pour le service CurrencyConverter.
'CurConv' => CurrencyConverter::class
],
],
//...
];