Zend Framework 2 – les modules. Les modules dans Zend Framework 2

Zend Framework 2 – les modules dans ZF2

(article inspiré du webinar de Evan Coury )

un module est un composant regroupant une ou des fonctionnalités de l’application (aspect logique/fonctionnel/design/technique/etc…), il peut regrouper un ensemble de tàches ou simplement modifier un comportement d’un autre module ou de l’application elle-même. il pourra être réutilisables dans d’autres applications (voir d’autres framework comme PPI meta framework ). un module peut aussi contenir toute une application (ou une partie qui sera complétée par d’autres modules).

Dans Zend framework 1, les modules, étaient couplés au système MCV de l’application. ils n’étaient pas performants (bootstraping), non distribuable (sans un effort de re-paramètrage de la nouvelle application), pas de système de packaging (composer, pyrus), etc ….

Dans ZF2, les modules sont beaucoup plus légers et rapides, autosuffisant, portables et ré-utilisables. Ils sont distribuables de plusieurs manières (packagist.org/composer, pyrus, modules.zendframework.com), on peut les organiser dans plusieurs dossiers de modules, et les utiliser en tant que packages phar/tar/zip, …

Qu’est ce qu’on peut mettre dans un module ?

Ce que l’on veut !!! une application ( wiki, blog, gestion de calendrier, etc…), des plugins ( gestion de droits, module de paiement, historique d’opérations …), librairies externes ( intégration de twig, intégration de Doctrine2 ),  themes ( templates, css, images )

L’architecture de ZF2, rend possible qu’un module puisse compléter un autre module, ou remplacer certaines de ces fonctionnalités. ainsi, par exemple, si on utilise l’authentification dans notre application via le module ZfcUser qui gère les sessions et utilise une base de donnée pour la persistance des login/password, si nous voulons utiliser l’authentification via ou une base NoSQL (ou un réseau social donné …) au lieu de mysql, on pourra créer un deuxième module qui modifie le comportement du premier. on pourra, par exemple, créer en plus un module qui modifie le formulaire  de login ainsi que son design pour l’afficher dans un widget dans un endroit voulu du gabari (layout) du site. etc…

ainsi, la création d’une application, consistera, généralement, à créer un skeleton, puis y ajouter des modules (que l’on développe ou que l’on récupère d’un autre projet ou de modules.zendframework.com) selon les besoin de l’application.

structure de l’application :

il n’y a pas de structure prédéfinie dans ZF2 (contrairement à ZF1). on est libre de définir la structure qui nous convient. l’application  skeleton utilise par défaut cette structure (recommandée par l’équipe ZF):

les modules sont, par défaut, placés dans modules/ et vendor/ . on place les modules téléchargé (développés par des tiers) dans vendor et ceux que l’on développe dans modules (ou autre dossier que l’on a choisi de déclarer dans application.config.php). On est pas sensé modifier les modules se trouvants dans vendor. Si besoin, on crée un module de même nom dans “module/” , ou un de nom différent qui le complètera dans modules et qui sera chargé après le module modifié . N’oubliez pas qu’un module dans vendor  est susceptible d’être amélioré par la communauté de développeurs (améliorations, correction de bug, etc…) et donc que vous pouvez le mettre à jour grâce à ‘composer’ ou ‘pyrus’ (donc au besoin, copiez le dans module, ZF2 cherche les modules dans le dossier ‘module/’ en premier, s’il ne le trouve pas le cherche dans ‘vendor/’ )

le fichier application.config.php et le fichier de base que va utiliser l’application pour charger les modules (voir plus loin).

un dossier data est présent car un module n’est pas sensé écrire dans son dossier. un module peu avoir besoin de mettre en cache certaines données, ou écrire dans une log etc… . donc on fera en sorte que nos module écrivent toujour à l’extérieur de leur dossier, et,  par défaut, on utilisera le dossier data pour çà (le serveur web doit avoir les droits d’écriture dedans).

par défaut, le module Application (du skeleton) contient l’application, et il est possible de mettre toute l’application dedans, mais ce n’est pas recommandé.

le dossier public contiendra les assets de l’application (css, js, images, etc…). Il est possible d’utiliser des modules (AssetManager) qui permettent de gérer les assets par modules (par exemple dans un dossier public dans chaque module). la configuration par défaut permet

Structure d’un module

Concrètement, un module n’a besoin que d’une classe module, avec un namespace identique au nom du dossier où elle se trouve mais la plupart du temps on a besoin d’un module plus riche avec une structure MVC.

voyons comme exemple le module application de l’application skeleton, qui contient une application de base :

Cette structure est recomandée par l’équipe zend et respecte le standard PSR0.

[Le standard PSR0 a pour but de standardiser l’autoloading des Classess php (pour éviter d’avoir plusieurs mécanismes d’autoloading dans chaque framework) . On utilise l’espace de nommage à l’image de l’arborescence avec quelques règles de bon sens : 1classe->1fichier php ( ZF1 respecte ces principes mais avec un nommage utilisant des préfixes séparé avec des underscores ‘_’ au lieu d’utiliser les namespaces) ]

Chaque module ajouté peut utiliser une structure qui diffère de celle proposée par l’équipe zend. il est important de respecter quelques règles (en plus des standards PSR0) : un fichier module.php est nécéssaire à la racine de chaque module. ce fichier module.php contient une classe qui s’appelle Module, le namespace du module est déclaré dans ce fichier module.php. c’est grâce à çà que l’application fait la différence entre Application\Module et Blog\Module, pour appeler les charger au démarrage.

concernant la partie MVC, les noms des contrôleurs sont en UpperCamelCase (commençant avec une majuscule). les actions (dans les contrôleurs) sont en camelCase et les vues sont en minuscule (avec des tirets comme séparateurs si plus d’un mot compose le nom de l’action) :

comme autre exemple, si on crée un module de blog avec un contrôleur (nommé Blog) contenant des actions (addAction, deleteAction, editAction, listAction, searchAnyPostAction et indexAction), voici à quoi pourrait ressembler sa structure:

Le coeur d’un module :

le minimum :

Il est possible de n’utiliser qu’une seul classe pour tout un module et donc de n’avoir seulement que le fichier module.php présent à sa racine  (par exemple pour modifier le comportement de l’application/ ajouter une route/etc…). il est possible d’incorporer une structure MVC complète, par exemple, pour développer une application (dans ce cas on indiquera au module comment chercher ses contrôleurs, les vues utilisée par ces contrôleurs, les modèles , etc… ), il est possible qu’un module ajoute simplement une aide de vue/d’action ou autre plugin, ou met à disposition un formulaire etc…

Comment l’application charge les modules :

à la réception d’une requête(qui sera traitée par public/index.php),  l’application configure l’autoloading, initie le service manager, un event manager (qui sera à disposition du module manager) et le module manager (qui s’occupe de charger les modules)

le fichier application.config.php est un bon point départ pour comprendre le chargement des modules (nous verons le ModuleManager plus loin)

La section modules contient la liste des modules que l’application va charger. L’ordre des modules est important si un module dépend d’un autre module, ou si un module en modifie un autre.(premier dans la liste -> premier chargé)

la section ‘config_glob_paths’ dans ‘module_listener_options’ sert à charger les fichiers de configuration contenant du paramètrage de l’application utilisée par les modules (connection de base de données, log utilisé par l’application, etc…).

la section ‘module_paths’ dans ‘module_listener_options’ sert à indiquer les chemins où sont placés les modules. il est possible d’expliciter le chemin d’un module (par exemple pour tester rapidement une version différente du module, ou en imposant un module à la place d’un autre, ZF2 ne se souciera, dans ce cas, pas du nom du chemin). enfin, si un même module se trouve dans les différents dossiers de modules, ZF2 va se contenter de charger le premier module trouvé (dans notre cas il cherche dans ‘module/’ puis dans ‘vendor/’). donc si on a besoin de personnaliser un module (par exemple si on utilise l’application ecomerce speck développée en ZF2), il suffira de copier le module voulu dans le dossier ‘module/’ pour le modifier.

Le gestionnaire de modules :

Zend\ModuleManager s’occupe du chargement des modules dans ZF2, il est aussi utilisé dans d’autres frameworks comme PPI Framework. ( il est possible d’utiliser les module PPI dans ZF2 et vice-versa).

Le gestionnaire de modules se base sur le gestionnaire d’évènements de ZF2 pour charger les modules
( le gestionnaire de modules va déclencher des évènements suplémentaires pendant le chargement de chaque module ) :

  • loadModules va enclencher le chargement de tous les modules (pour chaque module il enclenchera les évènements suivants) :
    • loadModule.resolve : le listener s’occupent de le trouver le module et l’instencie (new ModuleName\Module()  )
    • loadModule                (le listener fusionne la config du module avec celle existante, met à jour l’autoloading et initialise le module via ces méthodes )
  • loadModules.post  une foi  tous les modules chargés (on pourra au besoin écouter celui-ci. Si on veut charger des modules supplémentaires selon des critères. Par exemple, si on veut charger des modules du back-office si le user est authentifié en tant qu’administrateur)

Il n’est pas nécessaire de savoir dans les détails tout ce que fait le ModuleManager. le processus est assez simple à résumer : (le module manager va charger tous les modules en fusionnant leurs config, executer les méthodes qu’il compose à certains moments de la vie de l’application (selon certains évènements) et rendre la main à l’application (routage, dispatch, renvoi du résultat …).

le ModuleManager en plus détaillé :

ZF2 via sont gestionnaire d’évènement, s’occupe de ces quelques tâches à notre place:

  1. Par défaut ZF2 attache le Zend\Loader\ModuleAutoloader à l’évènement loadModules : pour que l’autoloader sache trouver les modules (en d’autres termes : dès que l’évènement loadModules est déclenché par ZF2, le ModuleAutoloader est appelé) 
  2. Ensuite, l’évènement loadModule.resolve est déclenché dans le context de chaque module, ce qui vas créer une instance de la classe “Module” de chaque module.
  3. Dès que le module est instancié (via loadModule.resolve), loadModule est déclenché dans le contexte du module, ce qui va executer les méthodes suivantes du module concernées  (car les listeners qui les exécutent écoutent l’évènement loadModule) :
    • AutoloaderListener : execute la méthode getAutoloaderConfig() si elle est présente dans le module. ceci permettra de renvoyer le résultat à Zend\Loader\AutoloaderFactory . (ainsi chaque module peut informer  l’autoloader de l’application de ces classes à autoloader en cas de besoin). ce listener est notifié avant les listener suivants.
    • ConfigListener : execute getConfig(), si présente, ce qui va permettre de fusionner son résultat avec la configuration de l’application
    • InitListener : execute init(), en lui passant le ModuleManager en paramètre.(pour des raisons de perfs, on y met en général des actions légères, comme l’ajout d’écouteurs pour des évènements, car éxécuté pour chaque module et pour chaque requète. on peut aussi demander, ici, de charger un module, dont on dépend, en priorité )
    • OnBootstrapListener : attache la méthode onBootstrap() , si présente à l’évènement ‘bootstrap’ de l’application pour qu’elle soit appelée pendant le bootstrap. (Il ne faut pas en abuser car elle est exécutée pour chaque requête, comme init(), on y met, généralement, des tâches légères comme des écouteurs d’évènements)
    • LocatorRegistrationListener : si notre module implémente Zend\ModuleManager\Feature\LocatorRegisteredInterface, sont instance sera injéctés dans le ServiceManager en tant que service portant le nom du module (il n’y pas de méthode aujourd’hui, il suffi d’implémenter cette interface, c’est tout).
    • ServiceListener : execute getServiceConfig(), si présente et fusionne sont résultat à la config du gestionnaire de service (ServiceManager). Ce listener gère aussi plusieurs gestionnaires de plugin selon la présence de leurs clés ou des méthodes qui les concernent (mais on s’éloigne du sujet.). pour info :
      Plugin Manager Config Key Interface Module Method
      Zend\ServiceManager\ServiceManager service_manager ServiceProviderInterface getServiceConfig
      Zend\View\HelperPluginManager view_helpers ViewHelperProviderInterface getViewHelperConfig
      Zend\Mvc\Controller\ControllerManager controllers ControllerProviderInterface getControllerConfig
      Zend\Mvc\Controller\PluginManager controller_plugins ControllerPluginProviderInterface getControllerPluginConfig

      pour chaque plugin, les clés suivantes sont utilisées par leur gestionnaire de service : (services,invokables,factories,abstract_factories,initializers). il est très utile de savoir que dans les gestionnaires de plugins, les factories, les abstract_factories et les initializers reçoivent le ModuleManager en paramètre, ce qui permet de récupérer simplement le ServiceManager ( $param->getServiceLocator(); ), ce qui est puissant comme technique !!!

  4. L’évènement loadModules.post permet d’être informé que tous les modules sont chargés. il permet d’enclencher des actions une foi tous les modules chargés. c’est à ce moment que les config des modules sont concaténés  (les clés  de config de chaque module peuvent mettre à jour les clés similaires dans d’autres modules qui sont chargé en premier). en suite les fichier de configuration présents dans ‘config/autolad/’ sont chargés sur le même principe et leur config est concaténée à celle des modules ( les fichiers *.local.php sont chargé après les fichiers global.php.).

rappelez-vous donc que les fichiers dans ‘config/autoload/’ sont chargé après les modules, ce qui veut dire qu’ils peuvent modifier des paramètres définis dans les config des modules.

Quelques bonnes pratique concernant les modules :

  • garder les méthodes init() et onBootstrap() le plus léger possible
  • s’interdire les écriture à l’intérieur des modules
  • un module doit servire à une seule tâche, et bien la faire
  • utiliser l’identifiant de marque comme préfixe du nom de module,(ceci évite d’avoir des modules dans vendor qui portent les mêmes noms que ceux que l’on développe ( exemple ZfcUser et non pas User )

Mise en package d’un module :

un module peut être dans un package que l’on dépose dans un dossier de modules. pour ce faire on peut se contenter d’en faire une archive tar ou zip :

on a le choix entre différents formats de packages supportés :  .phar .tar .tar.gz .tar.bz2 .zip phar.gz phar.bz2 phar.tar phar.tar.gz phar.tar.bz2 phar.zip

pour des raison de prefs, il vaut mieux éviter de compresser le package. la compresser d’un package n’est pas vraiment d’un grande utilité, car ça va être décompressé à chaque requête, sauf si on utilise une mise en cache en mémoire comme APC.

pour partager/réutiliser des modules : composer/packagist.org, pyrus, git/github, http, copie manuelle, modules.zendframework.com

les modules portent le même nom que leur namespace, et doivent donc respecter les même règles de nommage que les namespaces (pas de tiret, ni de point, ni de chiffre en début)

mise en cache de la configuration des modules

Comme vu plus haut, la configuration des modules est fusionnée (les derniers éléments de config écrasant les précédents), puis les éléments de config dans config/autoload/*.php sont fusionnés avec cette config (les fichiers *local.php écrasent les fichier *global.php).

ce processus de fusion des configs est récurent (à chaque requète), et tous ce processus génère en fin de compte une config que l’on peut stocker dans un unique tableau dans un fichier, ce qu’il est logique de faire quand on passe en production.


Bonne lecture
Did you like this? Share it:

One Response to Zend Framework 2 – les modules

Open Close