Zend Framework 2 – le gestionnaire de service ServiceManager – introduction

ZF2 – Le gestionnaire de service – (ou ServiceManager)

Le but du ServiceManager est de fournir à notre application des objets configurés quand on en a besoin. ceci nous permet d’obtenir des objet à un haut niveau sans se soucier de certain de leurs détails techniques (instanciation, paramétrage, dépendances, etc…). en ingénierie logiciel, on parlera de mécanisme d’inversion de contrôle, car on laisse le soin de fournir ce dont on a besoin au framework.

voici comment on demanderai au framework de nous fournir un objet (qui s’appelle ‘objet_metier’ dans la config de mon exemple )

N.B. dans ZF2 on rencontrera souvent les applelations ServiceManager ainsi que ServiceLocator . il s’agit de la même chose car le ServiceManager implémente ce design pattern, ( dans ZF2 l’un au l’autre veulent dire la même chose, aujourd’hui),

Ainsi, par exemple dans un controller, si on demande un modèle au ServiceManager, celui-ci s’occupe de l’instancier, si il ne l’a pas déjà fait auparavant, en lui fournissant les dépendances et paramètres nécessaires (adapter DB, user, password, etc… ), sinon il nous renvoie l’objet existant.

Dans ZF2 il existe un autre mécanisme d’inversion de contrôle + injection de dépendances puissant (Zend\Di). Celui-ci est plus difficile à appréhender et consomme plus de ressources car il utilise par défaut l’introspection pour scanner les classes (mécanisme de reflection)  afin de deviner leurs dépendances (en fait on peut utiliser Di ou SM selon ce qui nous plait, et Di peut être optimisé pour compiler le code et le mettre en cache, ce qui peut le rendre très performant). il est aussi possible d’utiliser les deux de manière à ce que le ServiceManager puisse demander à Di de lui fournir un objet qu’il ne connait pas.

Le SM est simple d’utilisation et efficace pour fournir les objets qu’on lui demande. il faudra, auparavant, le configurer dans nos modules (dans getServiceConfig() , ou sous la clé ‘service_manager’ dans ‘config/module.config.php’) en lui renseignant la config des classes qu’il va pouvoir instancier. ainsi, dans chaque module que l’on va créer, on prendra le soin de renseigner la config des classes dans ce module que l’on veut rendre accessibles dans l’application via le ServiceManager.

Enfin, l’utilisation du ServiceManager répond à des bonnes pratique de génie logiciel.  d’un coté, On instancie les objets quand on en a besoin, tout en évitant de créer plusieurs fois le même objet à différents endroits de l’application. D’un autre coté, le code est plus facilement testables car sans dépendances codées en dur. ce qui nous permet de fournir des dépendances différentes pendant les tests.

enfin, les classes que l’on paramètre via le ServiceManager sont appelés (dans le jargon) des services. on verra qu’il y a plusieurs manière de les configurer selon les besoins

principes :

le ServiceManager implemente l’interface ‘ServiceLocatorInterface‘ . Le ServiceLocator permet de récupérer des Objets/Services . Deux méthodes nous permettent d’interagir avec le SL (ou SM) :

  • has($nom)  
  • get($nom) 

Le premier servant à interroger le SM s’il a un service qui porte le nom $nom,  le deuxième à obtenir ce service.

le ServiceManager ajoute d’autres méthodes pour l’instanciation : ( dans l’exemple, $sm est le  ServiceManager ) :

  • $sm->setInvokableClass($name, $invokable);
  • $sm->setFactory($name, $factory);
  • $sm->setService($name, $service);
  • $sm->setAlias($alias, $nameOrAlias);
  • $sm->addAbstractFactory($factory);
  • $sm->addInitializer($initializer);
  • $sm->setShared($name, $isShared);

Comme le ServiceManager est créé par l’application à son démarrage, on va ajouter la configuration de nos différents Objets/Services dans la configuration des modules où on les a créés. ils seront ainsi disponibles très tôt dans la vie de l’application

deux endroits sont appropriés pour çà :

ou

les différent type de services sont les suivant :

  • invokables : Objets/Service simples, qui vont être instanciés par le SM par un simple appel de new ObjectName.
    • ServiceName‘ => ‘ModuleName\Service\ClassName’,
  • factories : Objets/Services qui nécessitent plus qu’un appel à new :
    • Un objet ou instance de classe implémentant ‘FactoryInterface’ ( contient une méthode createService()  qui renvoie l’objet configuré avec ses dépendances)
      • User‘     => ‘ModuleName\Service\UserFactory’,
    • Un callback , (qui renvoie l’objet demandé avec ses dépendances ):
      • UserProfile‘ => function ($serviceManager) {
            $userProfile = new ModuleName\Document\UserProfile();    //le SM récupère une dépendance pour l’injecter dans l’objet qu’il retourne
            $userProfile->setCollection($serviceManager->get(‘MongoUsersProfile’));
            return $userProfile;
        },
  • alias : alias d’un objet
    • alias d’un nom de classe entièrement qualifié : 
      • ‘ModuleName\Entity\User’ => ‘User‘,
    • alias d’un nom de service connu : 
      • ‘AdminUser’ => ‘User‘,
    • alias d’un alias :
      • ‘SuperUser’ => ‘AdminUser‘,
  • services : objets instanciés :
    • UserCollection‘ => new ModuleName\Service\AnyClass(),
  • abstract_factories : classe ou instance implémentant ‘AbstractFactoryInterface’, un callback PHP . si le SM ne trouve pas de fabrique (factory) pour un objet, il interroge une “fabrique-abstraite” si elle peut instancier le service, si oui il lui demande de l’instancier :
    • ‘ModuleName\Service\DefaultMongoFactory’,
  • shared : par défaut les services sont partagé, c.a.d. que si il ont été instanciés auparavant, l’instance éxistante est retournée. si on veut qu’un service soit instancié à chaque appel de get, on le spécifie ici ( en principe, on indiquer seulement les objets non partagés ):
    • ‘WeatherForm’ => false,

Pratique :

1 – invocable :

prenons le cas d’une classe que l’on veut fournir via le SM :

un service déclarée ‘invocable‘ sera simplement instanciée par le SM via l’opérateur ‘new‘. voici comment on le configure :

dans notre code on récupère l’objet comme ceci :

2 – factory :

Quand une classes nécessite pus de traitements au moment de l’instanciation, on utilisera une fabrique (qui nous retourne un objet configuré).

pour ce faire on peut soit, utiliser une classe qui implémente l’interface ‘Zend\ServiceManager\FactoryInterface‘ et donc qui contient une méthode createService($sm) qui va retourner l’objet créé, soit utiliser une fonction callback (qui retourne aussi l’objet configuré) :

si on utilise une classe fabrique il est conseillé de l’ajouter dans le même dossier que la classe qu’elle doit créer et on ajoutera Factory au nom de la clase (c’est l’approche de l’équipe ZF2):

par exemple si on veut créer une classe ‘SimpleService’ via une classe factory, on va appeler la classe de fabrication ‘SimpleServiceFactory’. La méthode ‘createService()’ de cette classe va retourner une classe ‘SimpleService’ configurée et pourra l’instancier avec l’opérateur new et lui injecter des dépendances via un/des setter(s) (ex: setOptions, setParams, etc… ), ou lui passer des dépendances via le constructeur, ou les deux. l’important c’est de retourner un objet configuré, et les dépendances injectées.

Important : les fabriques reçoivent une instance du ServiceManager en paramètre, ce qui permet de demander un autre objet au SM, que l’on peut fournir à notre classe (via son constructeur, ou via un setter)

configuration, on utilise la clé ‘factories‘ :

on peut utiliser un callback à la place d’une classe factory :

n’oubliez pas que la méthode ‘createService()’ ainsi que le callback reçoivent le ServiceManager en paramètre, ce qui nous permet de demander via ce SM passé en paramètre un autre service dont dépend notre objet, pour lui injecter (comme dit précédement via le constructeur ou/et des setters).

bonne utilisation des Fabriques

on a vu que qu’on peut déclarer une fabrique sous la clé ‘factories’ dans la méthode getServiceConfig() du module, ou sous la clé ‘factories’ dans le fichier config/module.config.php du module. Dans les deux cas on peut soit fournir une fonction callback/callable/static-method/php-function, soit fournir le nom d’une classe fabrique qui implémente FactoryInterface (fourni une méthode createService() ). en retour de ces fonctions on reçoit l’objet configuré.

Bien que le paramétrage des modules de l’application est assez performant, ce processus peut être optimisé, et une mise en cache du processus de configuration est une étape évidente pour améliorer les performances de l’application.

l’utilisation de fonctions callback ne pouvant pas être mis en cache, il est préférable de les déclarer à l’intérieur de la méthode getServiceConfig() et ne laisser dans le fichier config/module.config.php que des donnée sous forme de tableaux de clés/valeurs imbriqués. aussi il est préférable d’utiliser des classes de fabrique à la place des callback car on fourni leurs noms sous forme de chaine de caractère (on peut dans ce cas les spécifier dans config/module.config.php).

Bien entendu si vous n’utilisez pas de mise en cache ceci ne change rien pour vous (aujourd’hui). l’équipe ZF2 utilise principalement les fabriques et recommande de faire de même pour une meilleur portabilité des modules et clarté de code.

Injection automatique du ServiceManager dans les classes

il arrive souvent que l’on a besoin d’utiliser le ServiceManager dans un objet.
une solution pourrait être de l’injecter via une fabrique.

le ServiceManager permet d’injecter automatiquement une instance du SM via un ‘initializer’ sans être obligé de passer par une fabrique. il suffira d’implémenter l’interface Zend\ServiceManager\ServiceLocatorAwareInterface. l’initializer se contente d’interroger si la classe demandée au SM implemente cette interface (qui impose d’implémenter les méthodes setServiceLocator() et getServiceLocator() ).

voici un exemple simple :

Certains Objets dans ZF2 implémentent déjà cette interface. (la classe AbstractActionController par exemple).

pour information la classe AbstractController ( héritée par AbstractActionController) implémente cette inerface. il suffit donc de les déclarer en tant qu’invockable’ (sauf s’il faut ajouter d’autres dépendances (via des setters dans ce cas, car le constructeur attend un Objet Request obligatoire, et un objet Responce facultatif).

les exemples où on a besoin d’un ServiceManager dans une classe sont nombreux. je me contente de citer l’utilisation d’une classe model dans un formulaire pour, par exemple, remplir un objet select avec des données en base.

voici quelques ‘initialiser’ paramétrés par défaut dans le ServiceManager :

ServiceLocatorAwareInterface

ServiceManagerAwareInterface

// pour info ServiceManager est un alias de Zend\ServiceManager\ServiceLocatorInterface

injection d’un EventManager

EventManagerAwareInterface

Pour terminer :
le ServiceManager est une implémentation du ServiceLocator. on utilise plusieurs manières pour injecter des dépendances via le SM :

  • via le constructeur (dans une classe Fabrique ou une fonction de fabrique)
  • via un/des setters (dans une classe Fabrique ou une fonction de fabrique)
  • via un “initializer” qui sera appelé une l’objet instancié (on injecte une dépendance après un teste sur l’objet. par ex: le service Manager teste l’implémentation de certaines interfaces pour injecter un EM ou un SM)
Did you like this? Share it:

3 Responses to Zend Framework 2 – le gestionnaire de service ServiceManager – introduction

  1. oussama says:

    au premier abord, c’est incompréhensible !!!
    Merci pour ce tutoriel

  2. Cyril says:

    Bonjour,

    Très bon tutoriel, il fonctionne très bien chez moi à l’exception d’un détail.
    Lorsque j’utilise la fonction $this->getServiceLocator()->get(‘ServiceExemple’) elle me retourne à chaque fois une nouvelle instance de mon objet, n’est il pas possible d’instancier l’objet une fois et d’utiliser cette même instance par la suite dans tous le projet ?

    Merci bien

Open Close