Vous êtes sur la page 1sur 316

Au cur de Zend Framework 2

2012 Blanchon Vincent. Tous droits rservs. ISBN 978-1-4716-4809-0

Prface
Je me souviens encore de mon premier site internet. Javais dix ans et, lpoque, FrontPage tait mon meilleur alli. Peu importe quil produise du code non valide, quil soit compltement statique ou que son code soit vulnrable la moindre faille. Finalement, je mamusais bien et ctait le principal. videmment, le Web est devenu au fil des annes une industrie part entire, de plus en plus influente et aujourdhui, les dveloppeurs du monde entier le savent bien, il faut tre de plus en plus productif, crire du code toujours plus volutif, toujours plus performant, toujours plus scuris et toujours dans le mme laps de temps. cet gard, larrive des frameworks dans lunivers PHP a rvolutionn notre manire de penser et concevoir le Web. Et, surtout, il a contribu redonner une image crdible ce langage, bien trop souvent terni par les mmoires du vnrable PHP 4. sa sortie en juin 2007, la premire version stable du Zend Framework a largement contribu cet essor. Contrairement dautres frameworks, il nimposait rien, mais fournissait des briques rutilisables. Ce fut l sa grande force et lune des raisons de son succs. Depuis, de leau a coul. Le petit framework des dbuts a grossi, sest enrichi de nombreuses fonctionnalits et, douze versions majeures plus tard, il a atteint sa maturit mais, pour continuer voluer, il tait ncessaire de le repenser de zro. Cest dornavant chose faite, et cette rcriture complte du framework a, une fois de plus, t une vritable dmonstration de force de la communaut open-source, tant les chiffres donnent le tournis : plus de 200 contributeurs, plus de 1000 forks, prs de 14 000 tests unitaires crits et prs de 1800 pull requests. Le tout en un peu plus dun an. Ctait la premire fois que je participais une aventure open-source, en contribuant notamment largement au code du composant des formulaires, et je dois dire que ce fut une exprience trs formatrice. Il ny a pas de petites contributions, et que cela soit pour corriger un bogue, ajouter de la documentation ou crer une nouvelle fonctionnalit, cest grce laide de tous que notre outil de travail est ce quil est aujourdhui. ce propos, le livre que vous tenez entre les mains constitue la toute premire ressource francophone consacre ce nouveau framework, et Vincent a ralis un incroyable travail pour vous permettre non seulement de vous lancer dans l'ap-

prentissage de Zend Framework 2, mais galement pour y comprendre les rouages internes (vnements, injection de dpendances), indispensables pour tirer parti au maximum de ce framework la fois simple et complexe. Que vous soyez un actuel dveloppeur de ZF 1 ou que vous ayez dj une exprience avec ZF 2, chacun y trouvera son compte !

Michal Gallego Contributeur au Zend Framework 2 Administrateur du forum ddi la communaut franaise du Zend Framework

Table des matires


Avant-propos 11
qui sadresse ce livre ?11 Structure de louvrage12 Remerciements13

Chapitre 1 Le Zend Framework 15


Le Zend Framework 115 Le Zend Framework 215
PHP 5.3.3.......................................................................................................15 Design pattern...............................................................................................16 Les axes damlioration................................................................................17

Le squelette de lapplication17

Chapitre 2 Les autoloaders 19


Lautoloader standard19 Le ClassMapAutoloader 24 La classe ModuleAutoloader 28 Linterface SplAutoloader 28

Chapitre 3 Les vnements 31


Prsentation des vnements31 Le gestionnaire partag 33 Lvnement wildcard 39 Le gestionnaire dvnements global 40 Le type ListenerAggregate 40 Crer ses vnements personnaliss 43 Lobjet de rponse 46 Stopper la propagation dun vnement 47 Grer lordre des notifications 49 Les vnements du framework 50

Chapitre 4 Linjection de dpendances 53


Configuration manuelle du composant Di 54

Configuration automatique du composant Di 56 Configuration semi-automatique du Di 59 Le gestionnaire dinstances61

Chapitre 5 Le gestionnaire de services 65


La configuration du gestionnaire 65 Mise en uvre 67 Le ServiceManager complmentaire du Di 77
La fabrique du Zend\ServiceManager\Di...............................................77 La fabrique abstraite du Zend\ServiceManager\Di...............................79

Implmentation dans le framework 80

Chapitre 6 Les modules 83


Configuration et couteurs 83 Chargement des modules 88
Initialisation de lautoloader du module....................................................96 Initialisation du module...............................................................................97 Notification lors de linitialisation de lapplication..................................98 Configuration du gestionnaire de services................................................99 Nous remarquons que la cl service_manager est utilise pour rcuprer linstance de lobjet tendant de ServiceManager qui sera alors configur..................................................................................................... 102 Initialisation des instances partages...................................................... 102 Mise jour de la configuration des modules......................................... 102 Traitement postchargement..................................................................... 104

Rcapitulatif du processus108
Processus de chargement......................................................................... 108 Squelette de notre classe Module............................................................ 109

Chapitre 7 Les configurations 111


Accs la configuration 111 Configuration par environnement 112
Configuration par fichiers PHP............................................................... 113 Configuration par hritage de fichier...................................................... 114 Configuration par fichier INI.................................................................. 116 Configuration recommande................................................................... 117

Chapitre 8 Le router 119

Le router et la brique MVC 119 Les routes Http\Literal124 Les routes Http\Regex126 Les routes Http\Scheme127 Les routes Http\Method127 Les routes Http\Hostname128 Les routes Http\Segment129 Les routes Http\Wildcard130 Les routes Http\Part132 Lcouteur pour les modules133

Chapitre 9 Les contrleurs 137


Le contrleur AbstractActionController137
Mthodes daction par dfaut.................................................................. 143 Les interfaces du contrleur de base...................................................... 144

Le contrleur AbstractRestfulController148

Chapitre 10 Les aides daction 151


Laide daction Forward 151 Laide daction Redirect153 Laide daction Url155 Laide daction PostRedirectGet156 Cration dune aide daction157

Chapitre 11 Le gestionnaire de plugins 159


La classe de base159 Les gestionnaires du framework 161

Chapitre 12 Les vues 165


Le gestionnaire de vues165 Prparation des vues166 Prparation des vues derreurs168 Rendu de la vue173
Construction des vues.............................................................................. 173 Rendu des vues.......................................................................................... 177 Rcapitulatif du rendu.............................................................................. 191

Types de vue193

Manipulation des vues195

Chapitre 13 Les aides de vue 199


Le fil dAriane 200 Le sitemap 205 Laide Layout et ViewModel 207 Laide Partial 208 Le rendu dlment HTML210 Le rendu de JSON 211 Laide Url212 Cration dune aide de vue213

Chapitre 14 Le cur du framework 215


Initialisation des autoloaders215 Le bootstrap217 Le run221

Chapitre 15 Les composants 229


Zend\Db 229
Ladaptateur de base de donnes............................................................ 229 Labstraction SQL..................................................................................... 234 Linterface TableGateway......................................................................... 235 Linterface RowGateway........................................................................... 238

Zend\Cache241
Les adaptateurs.......................................................................................... 241 Les plugins.................................................................................................. 242 Analyse des plugins................................................................................... 243 Le composant Zend\Cache\Pattern...................................................... 246

Zend\Console 248
Utilisation basique de la console............................................................. 248 Les classes dinteractions.......................................................................... 250 Labstraction de lenvironnement............................................................ 253 Le router pour la console......................................................................... 254 La vue pour la console.............................................................................. 257

Zend\Crypt 259
Hachage sens unique.............................................................................. 259 Hachage symtrique.................................................................................. 260

Zend\Form261

Le formulaire, les fielsets et les lments............................................... 261 Les fabriques et hydrateurs...................................................................... 262 Le composant Zend\InputFilter............................................................ 267 Les aides de vue......................................................................................... 270

Zend\Mail 272 Zend\Session 275 Zend\Stdlib 278


La classe AbstractOptions....................................................................... 278 La classe ArrayUtils................................................................................... 280 La classe CallbackHandler........................................................................ 281 La classe ErrorHandler............................................................................. 284 La classe Glob............................................................................................ 285 Les hydrateurs............................................................................................ 285

Chapitre 16 Les exceptions 291 Chapitre 17 Cas dutilisation 293


Concept et fonctionnalits 293
Prsentation du projet............................................................................... 293 Les besoins du projet................................................................................ 294

Organisation et architecture 294 Personnalisation du chargement de modules 296


La restriction de chargement................................................................... 296 Limplmentation du chargement la demande................................... 296

La configuration 299 Les contrleurs de l'application301 Les tches automatises 305


Implmentation.......................................................................................... 305 Utilisation du composant ZendService.................................................. 307

Les modules du framework 308

Chapitre 18 Contribuer au framework 309


Le planning 309 La gestion des versions 311 Le bug tracker312 Corriger un bogue ou proposer une amlioration313 Rcapitulatif des tapes314

Avant-propos
qui sadresse ce livre ?
Louvrage est technique et sadresse des dveloppeurs informatiques ayant dj une exprience de la programmation en PHP, ainsi que des connaissances de base du Zend Framework, que ce soit dans sa premire ou deuxime version. Nous essaierons de faire, tout au long de louvrage, un parallle entre la premire et cette seconde version du framework afin de bien comprendre pourquoi et dans quel but les composants ont t rcrits, et ce que peuvent nous apporter les nouvelles fonctionnalits du framework. Bien que de nombreuses portions de code soient incluses et expliques dans louvrage, il est conseill de lire les diffrents chapitres avec le code source du framework sous la main pour mieux comprendre les explications donnes. Afin dviter lajout de code non pertinent, certaines portions inutiles la comprhension sont coupes et reprsentes par des crochets []. Cela peut tre le cas pour la gestion de certaines erreurs ou traitements basiques par exemple. Lors de la premire partie du livre et lexplication du cur du framework, nous prendrons comme exemple le squelette dapplication propos sur le compte github du zend framework : https://github.com/zendframework/ZendSkeletonApplication. Nous ferons peu de modifications au niveau du code du squelette dapplication, mis part pour des exemples prcis. Les premiers chapitres sont consacrs au code du Zend Framework, ce qui nous permettra den maitriser les concepts afin de pouvoir les mettre en uvre lors de la deuxime partie avec le cas dutilisation. Jai avant tout souhait crire ce livre pour tmoigner lintrt que jai pour le Zend Framework et le PHP en gnral, jespre que celui-ci rpondra vos attentes et vous permettra de mieux apprhender le fonctionnement du framework. La dcouverte et la comprhension en dtail du framework mont aussi permis de pouvoir contribuer au code du Zend Framework 2 travers limplmentation de nouvelles fonctionnalits et la correction derreurs. Jespre donner aussi lenvie dautres dveloppeurs de venir contribuer lamlioration de cet excellent framework. Vous trouverez au tout dernier chapitre les tapes de la contribution au Zend Framework.

12

Avant-propos Chapitre

Structure de louvrage
Louvrage se divise en deux grandes parties. La premire est consacre ltude du cur du Zend Framework, ce qui va nous permettre de comprendre les grands changements effectus lors de cette deuxime version. La deuxime partie prsente un cas dutilisation qui nous permet la mise en uvre dun exemple concret utilisant un grand nombre de composants. Le chapitre 1 est une introduction au Zend Framework 2, avec un petit historique sur la premire version du framework. Ce chapitre permet de se faire rapidement une ide sur les amliorations apportes au Zend Framework 2, ainsi que les conditions requises afin de lexploiter au mieux. Le chapitre 2 prsente les autoloaders existants dans le framework, avec des exemples dutilisation et lexplication de leur comportement dtaill. La maitrise du chargement de nos classes permet de sensiblement amliorer les performances de nos applications Web, souvent peu optimises sur cet aspect. Le chapitre 3 tudie le gestionnaire dvnements et limplmentation de la programmation vnementielle au sein du Zend Framework. Cette partie est lune des grandes nouveauts du framework, lutilisation des vnements permet lamlioration de la flexibilit du framework. Le chapitre 4 prsente le composant dinjection de dpendance qui permet aux dveloppeurs de trouver ici les bonnes pratiques de programmation en dcouplant chaque objet et supprimant les dpendances dans le code. Le cinquime chapitre aborde le gestionnaire de services, nouveau composant du framework, responsable de la fabrication des objets du framework. Le gestionnaire peut tre utilis en complment du composant dinjection de dpendances. Le chapitre 6 prsente une des autres nouveauts du framework: le fonctionnement des modules. Ceux-ci sont comparables des briques individuelles prtes tre intgres de projet en projet. Le chapitre 7 fait le point sur les configurations, notamment sur leur fonctionnement gnral ainsi que sur lutilisation de celles-ci pour chacun de nos modules. Le huitime chapitre nous prsente le fonctionnement du router, loptimisation de celui-ci et les diffrents types de routes existantes. Les contrleurs seront abords au chapitre 9 qui dtaille le processus de distribution au sein du contrleur, ainsi que la possibilit de jouer nimporte quelle action dun contrleur en le rendant distribuable. Le chapitre 10 nous propose de comprendre comment ont t refactorises les aides daction du framework. Plus performantes et plus simples comprendre, les aides dactions sont maintenant plus efficaces. Les gestionnaires de plugins sont prsents au chapitre 11, ce qui nous permet de voir le fonctionnement du chargement et de la gestion des instances de nos plugins. Les vues, leurs types et le fonctionnement de leurs rendus sont expliqus au

Avant-propos Chapitre

13

chapitre 12. Un gros travail de rcriture a t effectu par lquipe du Zend Framework sur le mcanisme des vues. Les aides de vue et leurs optimisations sont prsents au chapitre 13. Le cur du patron darchitecture MVC de Zend est tudi au chapitre 14 avec lexplication de nos points dentres et le processus de lancement de lapplication. Plusieurs composants importants (bases de donnes, formulaires, etc.) sont dtaills au chapitre 15 afin den maitriser leur utilisation. Le chapitre 16 passe en revue le nouveau systme de gestion des types dexceptions au sein du framework. Le dix-septime chapitre prsente ltude dun cas dutilisation afin de nous permettre la mise en application de tout ce que lon a pu voir au sein des chapitres prcdents. Le dernier chapitre explique en dtail les tapes afin de pouvoir contribuer au Zend Framework: correction de bogue, cration de fonctionnalit, suivi du calendrier de dveloppement, etc.

Remerciements
Je tiens remercier tous ceux qui mont aid rdiger cet ouvrage, que ce soit par leur relecture ou leur soutient : Clment Caubet pour sa magnifique couverture de livre; Michel Di Bella pour le template et la mise en page du livre; Yoann Gobert pour le design et lintgration du site au-coeur-de-zend-framework-2.fr; Christophe Mailfert et Frdric Blanc pour la relecture technique du livre; Claude Leloup et Fabien Le Bris pour leur relecture orthographique; Michael Gallego pour sa prface; au site developpez.com et ainsi qu ses membres pour leurs relectures et commentaires; Marie qui ma soutenu et cout pendant les six mois dcriture; lquipe du Zend Framework pour leur trs bon travail, ce qui fait que ce livre existe; vous qui venez de lacheter et qui lisez jusqu la dernire ligne des remerciements.

14

Avant-propos Chapitre

Le Zend Framework
Le Zend Framework 1

La premire rvision du framework tait la version0.1, sortie en 2006 avant quune premire version stable voie le jour en juillet2007 aprs plus dun an et demi de dveloppement. Cette premire version nintgrait pas toutes les fonctionnalits que lon connat maintenant. De nombreux composants majeurs, comme le Zend_ Layout, sont sortis avec la version1.5 et dautres comme le Zend_Application, avec la version1.8. La version1.5 a marqu un tournant pour le Zend Framework, cest une des versions qui ont apport le plus de fonctionnalits. Cest dailleurs sur cette version que repose la certification Zend Framework 1. Seulement, au fur et mesure du temps, le Zend Framework, mme sil est compatible avec PHP 5.3, ne tire pas tous les avantages des dernires versions de PHP. La version1.11 du framework est considre comme trs stable et ctait donc loccasion pour commencer de dvelopper une nouvelle version qui casserait compltement la compatibilit avec la version prcdente. Cest aussi loccasion pour lquipe de dveloppement de mettre en place de nombreux patterns et bonnes pratiques pour les dveloppeurs qui manquent actuellement au framework.

Le Zend Framework 2
PHP 5.3.3
Le Zend Framework 2 est pens et crit pour PHP 5.3, branche dvolution majeure sortie en 2009. PHP 5.3 a intgr un grand nombre dvolutions comme les espaces de nom, le garbage collector, les amliorations de la SPL (Standard PHP Library, bibliothque standard de PHP qui fournit classes et interfaces pour rsoudre des problmes rcurrents), etc. Le Zend Framework 2 utilise abondamment ces nouveauts, en particulier les espaces de noms qui sont omniprsents dans le framework o chaque classe possde son propre espace de nom. Le framework utilise aussi beaucoup les nouveauts de la SPL comme le GlobIterator ou la SplPriorityQueue qui permettent une meilleure compatibilit. Typiquement, ces classes et mthodes de la SPL sont rgulirement utilises travers la bibliothque

16

Avant-propos Chapitre 1

standard du Zend Framework 2 pour homogniser le code au sein du framework. Il y a donc une premire cassure avec le Zend Framework 1 o les espaces de noms ne pouvaient pas tre utiliss pour garder une compatibilit avec les versions infrieures PHP 5.3. Au niveau de la branche5.3, le cur du langage, dont le Zend Engine, a t profondment remani ce qui entraine des gains de performance ainsi que des rductions de consommation mmoire assez importantes. Il nest pas ncessaire de stendre plus sur les nouveauts de PHP 5.3, nous sommes aujourdhui en version 5.4, la branche5.3 ayant dj trois ans, la documentation PHP sera plus prcise ce sujet pour ceux qui souhaitent dcouvrir les volutions de cette version. Si la version2 du framework est reste en dveloppement pendant plus de deux ans, il semblerait que l'quipe de dveloppement souhaite acclrer lgrement les sorties de versions majeures pour toujours bnficier au maximum des dernires branches de PHP ainsi que les bonnes pratiques et design pattern. Pour venir discuter avec l'quipe, contributeurs ou autres dveloppeurs Zend Framework 2, n'hsitez pas venir sur le channel IRC ddi #zftalk.2.

Design pattern
Le patron de conception principal est le patron darchitecture MVC (modle - vue - contrleur), qui spare le code dune application entre le modle (traitement des donnes et interaction avec la base de donnes), la vue (gestion des fichiers de vue et du rendu) et le contrleur qui est le chef dorchestre de lapplication et va interagir avec les deux premiers composants pour injecter les donnes la vue. Dans cette nouvelle version du framework, la programmation par contrat est encore plus prsente. Chaque classe abstraite et chaque composant indpendant implmentent une interface, ce qui va permettre de faciliter linjection de dpendance. En effet, un objet inject dans un autre objet devra respecter un contrat (implmenter une interface) afin de pouvoir sassurer que celui-ci va pouvoir rpondre aux attentes de lobjet qui le reoit. Cela permet dtendre les possibilits et de supprimer les dpendances de son code. Comme nous le verrons, linjection de dpendances est trs prsente dans le Zend Framework 2 et autorise la suppression des dpendances codes en dur, ce qui permet aux dveloppeurs damliorer la qualit de leur code. Une des grandes nouveauts du Zend Framework 2 est la programmation vnementielle. Le principe est doffrir un systme de communication au travers de tous les composants du framework sans que celui casse le principe du pattern MVC. La programmation vnementielle a t introduite pour faciliter les changes entre composants et supprimer les mauvaises pratiques que pouvaient mettre en place les dveloppeurs pour pallier ce manque de transversalit. De plus, les vnements permettent la suppression des hooks, trop prsents dans la premire version du framework. La programmation vnementielle est une manire de programmer largement rpandue dans dautres langages comme le Java ou lActionScript, Zend Framework y intgre les bonnes pratiques. Un chapitre est consacr aux vnements et aux gestionnaires d'vnements.

Avant-propos Chapitre 1

17

Les axes damlioration


Les premiers axes damlioration du Zend Framework 2 concernent lamlioration des performances qui devenaient de moins en moins bonnes au fur et mesure des versions, ainsi que lamlioration de la courbe dapprentissage du framework. Lutilisation des mthodes magiques est en cause sur les baisses de performance, dans le Zend Framework 1, la magie est utilise tout va. Le deuxime point damlioration est le systme de chargement de classes qui reste trop gourmand, nous verrons que le Zend Framework 2 utilise un systme de chargement beaucoup plus performant. Le framework veut aussi supprimer lutilisation excessive de la directive include_path qui rend le chargement des classes beaucoup trop lent. Un nouveau systme de rsolution de nom de classe par tableau PHP dcrivant le chemin pour chacune des classes utilises en cl du tableau a t introduit. Ce chargement bas sur le couple cl/valeur devient alors beaucoup plus performant, mme sil reste la possibilit dutiliser un chargement bas sur les prfixes, espaces de noms ou sur la directive include_path. Au-del des composants trop gourmands en termes de performances ou de temps dapprentissage, la deuxime version du framework veut amliorer lenvironnement du dveloppeur en mettant laccent sur la documentation et les exemples mis en uvre. Par exemple, des classes comme le Zend_Form et ses dcorateurs sont considres comme trop complexes dutilisation. Afin de mettre en place toutes ces bonnes pratiques, les dveloppeurs ont d rcrire certains composants entirement et en crer dautres. Si le dveloppement a paru long pour ses utilisateurs, la priorit de lquipe tait davoir un produit fini qui leur convienne parfaitement. Nous allons maintenant voir comment mettre en place rapidement un squelette de projet sous Zend Framework 2, ce qui nous servira ensuite de base pour notre tude approfondie du framework.

Le squelette de lapplication
Lors des chapitres suivants, tous nos exemples de configurations et de modules seront directement tirs du ZendSkeletonApplication, disponible sur compte github de ZendFramework : https://github.com/zendframework/ZendSkeletonApplication. Ce projet propose un squelette dapplication fonctionnel afin de comprendre les principales tapes de la mise en place dun projet sous Zend Framework 2. Voici la hirarchie des fichiers proposs :

18

Le Zend Framework Chapitre 1

Au fil des chapitres, lorsquune section sera traite, un aperu des fichiers concerns sera joint aux explications et chaque partie du cur de Zend Framework passe en revue sera affiche dans les explications de louvrage afin den faciliter la comprhension.

Les autoloaders
Dans la liste des axes damlioration du Zend Framework 2 figurent les autoloaders. Trop souvent bases sur la directive include_path, les classes de chargements du Zend Framework 1 sont trop lentes. Si lutilisation des prfixes de classe permet de rendre le chargement un peu plus performant, cette tape reste tout de mme coteuse en performances. De nouveaux autoloaders ont fait leur apparition pour rendre le processus encore plus rapide, faisons un tour dhorizon pour les prsenter.

Lautoloader standard
Lautoloader standard, de type Zend\Loader\StandardAutoloader, permet de charger une classe suivant un prfixe de nom ou un espace de nom. Il est aussi possible de configurer lautoloader standard afin dutiliser la directive include_path, ce qui serait prjudiciable en termes de performances. Prenons un exemple dutilisation avec le fichier dinitialisation des autoloaders qui lutilise par dfaut avec linstruction : init_autoloader.php
Zend\Loader\AutoloaderFactory::factory(array( 'Zend\Loader\StandardAutoloader' => array( 'autoregister_zf' => true ) ));

Lautoloader standard est utilis explicitement, mais sans argument, la fabrique instancie un objet de type StandardAutoloader par dfaut comme nous le voyons depuis la fabrique : Zend/Loader/AutoloaderFactory.php
const STANDARD_AUTOLOADER = 'Zend\Loader\StandardAutoloader';

20

Les autoloaders Chapitre 2

Zend/Loader/AutoloaderFactory.php
public static function factory($options = null) { if (null === $options) { if (!isset(static::$loaders[static::STANDARD_AUTOLOADER])) { $autoloader = static::getStandardAutoloader(); $autoloader->register(); static::$loaders[static::STANDARD_AUTOLOADER] = $autoloader; } return; } [] }

La mthode getStandardAutoloader() cre une instance de cet objet : Zend/Loader/AutoloaderFactory.php


protected static function getStandardAutoloader() { if (null !== static::$standardAutoloader) { return static::$standardAutoloader; } $stdAutoloader = substr(strrchr(static::STANDARD_AUTOLOADER, '\\'), 1); if (!class_exists(static::STANDARD_AUTOLOADER)) { require_once __DIR__ . "/$stdAutoloader.php"; } $loader = new StandardAutoloader(); static::$standardAutoloader = $loader; return static::$standardAutoloader; }

La mthode register() enregistre ensuite la fonction autoload() de lobjet courant, comme mthode de chargement, auprs de la SPL : Zend/Loader/StandardAutoloader.php
public fonction register() { spl_autoload_register(array($this, 'autoload')); }

Le constructeur de lautoloader standard enregistre par dfaut lespace de nom Zend si cela lui est demand, comme dans linitialisation des autoloaders, depuis le tableau doptions afin quil soit prt lemploi pour le chargement de classes de la bibliothque du framework : Zend/Loader/StandardAutoloader.php
const AUTOREGISTER_ZF = 'autoregister_zf';

Les autoloaders Chapitre 2

21

Zend/Loader/StandardAutoloader.php
public fonction setOptions($options) { [] foreach ($options as $type => $pairs) { switch ($type) { case self::AUTOREGISTER_ZF: if ($pairs) { $this->registerNamespace('Zend', dirname(__ DIR__)); } break; [] } return $this; }

Ceci explique la possibilit dinstancier ensuite un objet de lespace de nom Zend sans difficult. Plusieurs mthodes de cette classe permettent lenregistrement de prfixes et espaces de nom : Zend/Loader/StandardAutoloader.php
public function registerNamespace($namespace, $directory) { $namespace = rtrim($namespace, self::NS_SEPARATOR). self::NS_ SEPARATOR; $this->namespaces[$namespace] = $this->normalizeDirectory($directo ry); return $this; }

Zend/Loader/StandardAutoloader.php
public function registerPrefix($prefix, $directory) { $prefix = rtrim($prefix, self::PREFIX_SEPARATOR). self::PREFIX_ SEPARATOR; $this->prefixes[$prefix] = $this->normalizeDirectory($directory); return $this; }

Prenons dautres exemples et ajoutons un dossier la racine, nomm autoloaddir, qui contient deux dossiers Namesp/ et Prefix/. Le dossier Namesp possde un premier fichier Test.php : Test.php :
namespace Namesp; class Test{}

Le dossier Prefix/ contient un autre fichier Test.php : Test.php


class Prefix_Test{}

22

Les autoloaders Chapitre 2

Afin de charger ces deux classes, il nous suffit de rcuprer lautoloader standard et dy ajouter les espaces de noms et prfixes dont on a besoin : Utilisation des prfixes
$autoloader = Zend\Loader\AutoloaderFactory::getRegisteredAutoloader( Zend\Loader\AutoloaderFactory::STANDARD_AUTOLOADER); $autoloader->registerPrefix('Prefix_', __DIR__ . '/autoload-dir/ Prefix'); new Prefix_Test();

Utilisation des espaces de nom


$autoloader = Zend\Loader\AutoloaderFactory::getRegisteredAutoloader( Zend\Loader\AutoloaderFactory::STANDARD_AUTOLOADER); $autoloader->registerNamespace('Namesp', __DIR__ . '/autoload-dir/ Namesp'); new Namesp\Test();

Comme nous le remarquons, lutilisation de la classe StandardAutoloader est trs simple. Comme on la dit prcdemment, il est aussi possible dutiliser la directive include_path depuis la mthode setFallbackAutoloader() : Utilisation de la directive include_path
set_include_path(implode(PATH_SEPARATOR, array( realpath(__DIR__ . '/autoload-dir'), get_include_path() ))); $autoloader = Zend\Loader\AutoloaderFactory::getRegisteredAutoloader( Zend\Loader\AutoloaderFactory::STANDARD_AUTOLOADER); $autoloader->setFallbackAutoloader(true); new Namesp\Test();

Si le chargement de classe est activ avec la directive include_path et que des espaces de noms ou prfixes sont enregistrs, le framework leur donnera la priorit lors du chargement : Zend/Loader/StandardAutoloader.php
const NS_SEPARATOR = '\\';

Zend/Loader/StandardAutoloader.php
public function autoload($class) { $isFallback = $this->isFallbackAutoloader(); if (false !== strpos($class, self::NS_SEPARATOR)) { if ($this->loadClass($class, self::LOAD_NS)) { return $class; } elseif ($isFallback) { return $this->loadClass($class, self::ACT_AS_FALLBACK); } return false; }

Les autoloaders Chapitre 2


f (false !== strpos($class, self::PREFIX_SEPARATOR)) { i if ($this->loadClass($class, self::LOAD_PREFIX)) { return $class; } elseif ($isFallback) { return $this->loadClass($class, self::ACT_AS_FALLBACK); } return false; } if ($isFallback) { return $this->loadClass($class, self::ACT_AS_FALLBACK); } return false; }

23

Lautoloader vrifie alors si le chargement concerne un nom de classe avec espace de nom (simple vrification de la prsence dun double-slash) et utilise la mthode loadClass() afin de la charger. Cest seulement si le chargement na pas abouti que lautoloader appelle cette mme mthode, qui utilisera alors la directive include_path, comme nous lavons spcifi : Zend/Loader/StandardAutoloader.php
protected fonction loadClass($class, $type) { [] if ($type === self::ACT_AS_FALLBACK) { $filename = $this->transformClassNameToFilename($class, ''); $resolvedName = stream_resolve_include_path($filename); if ($resolvedName !== false) { return include $resolvedName; } return false; } [] }

Comme son nom lindique, la mthode de chargement stream_resolve_include_ path() est base sur la directive include_path. Mme si lutilisation de celle-ci est viter, nous pouvons nous en servir une fois nos prfixes et autres espaces de noms enregistrs, ceux-ci tant prioritaires lors du chargement. Notons quil est aussi possible de fournir un tableau doptions directement dans le constructeur de notre objet de chargement de classe ou depuis sa mthode setOptions() : Utilisation dun tableau doptions
$autoloader = Zend\Loader\AutoloaderFactory::getRegisteredAutoloader( Zend\Loader\AutoloaderFactory::STANDARD_AUTOLOADER); $autoloader->setOptions(array( 'namespaces'=>array('Namesp'=>'./autoload-dir/Namesp'), 'prefixes'=>array('Prefix_'=>'./autoload-dir/Prefix'), ) ); new Namesp\Test(); new Prefix_Test();

24

Les autoloaders Chapitre 2

Afin de gagner en performances, le framework propose dans cette version, un nouvel autoloader plus rapide et encore plus simple d'utilisation, le ClassMapAutoloader.

Avec le Zend Framework 1


Dans la version1 du framework, la classe de chargement Zend_Loader_ Autoloader permet de charger des espaces de noms (prfixes en ralit), la classe StandardAutoloader reprend un peu ce principe. La classe Zend_ Loader_Autoloader peut aussi enregistrer dautres autoloaders pour un mme prfixe et ceux-ci seront utiliss en priorit. Il faut aussi noter quil ny a pas de notion despace de nom PHP dans le Zend Framework 1, lautoloader se base donc sur ce que fournit le dveloppeur. Dans cette nouvelle version, espace de nom PHP et prfixes sont bien diffrencis, ce qui rend les performances meilleures car le framework peut maintenant diffrencier les deux.

Le ClassMapAutoloader
Associant nom de classe et chemin, le Zend\Loader\ClassMapAutoloader est certainement lautoloader le plus performant. Ds lors que le tableau de chemins correspondant aux noms de classes est configur, le ClassMapAutoloader est fonctionnel. Prenons un exemple avec la mme structure de dossier que pour lautoloader standard de la section prcdente : Utilisation de la classe ClassMapAutoloader
chdir(dirname(__DIR__)); require_once (getenv('ZF2_PATH') ?: 'vendor/ZendFramework/library') . '/Zend/Loader/AutoloaderFactory.php'; Zend\Loader\AutoloaderFactory::factory(); $maps = include 'config/application.autoload_classmap.php'; Zend\Loader\AutoloaderFactory::factory(array('Zend\Loader\ ClassMapAutoloader' => array($maps))); new Namesp\Test(); new Prefix_Test();

Le fichier de configuration est un simple tableau de type cl/valeur : config/application.autoload_classmap.php


<?php return array( 'Namesp\Test' => __DIR__ . '/../autoload-dir/Namesp/Test.php', 'Prefix_Test' => __DIR__ . '/../autoload-dir/Prefix/Test.php', );

Nous indiquons la mthode factory() de la fabrique de classe de chargement

Les autoloaders Chapitre 2

25

que nous souhaitons enregistrer une instance de la classe ClassMapAutoloader auprs de la SPL. La mthode sattend recevoir en paramtre un nom de classe avec sa configuration associe : Zend/Loader/AutoloaderFactory.php
public static fonction factory($options = null) { if (null === $options) { [] } if (!is_array($options) && !($options instanceof \Traversable)) { [] } foreach ($options as $class => $options) { if (!isset(static::$loaders[$class])) { $autoloader = static::getStandardAutoloader(); [] if ($class === static::STANDARD_AUTOLOADER) { $autoloader->setOptions($options); } else { $autoloader = new $class($options); } $autoloader->register(); static::$loaders[$class] = $autoloader; } else { static::$loaders[$class]->setOptions($options); } } }

La mthode de fabrication des autoloaders contrle lexistence de l'instance demande avant de la crer avec les options passes en paramtre. Lobjet cr senregistre ensuite automatiquement auprs de la SPL, ce qui nous vite de lenregistrer explicitement depuis sa mthode register(). Nous remarquons aussi que si linstance de notre objet est existante, la fabrique se contente alors de mettre jour les options de cette instance. La mthode daltration des options setOptions() ne remplace pas mais ajoute les nouvelles options la configuration existante, comme nous le remarquons dans la classe ClassMapAutoloader : Zend/Loader/ClassMapAutoloader.php
public fonction setOptions($options) { $this->registerAutoloadMaps($options return $this; }

Zend/Loader/ClassMapAutoloader.php
public fonction registerAutoloadMaps($locations) { [] foreach ($locations as $location) { $this->registerAutoloadMap($location); } return $this; }

26

Les autoloaders Chapitre 2

Zend/Loader/ClassMapAutoloader.php
public fonction registerAutoloadMap($map) { if (is_string($map)) { $location = $map; if ($this === ($map = $this->loadMapFromFile($location))) { return $this; } } [] $this->map = array_merge($this->map, $map); if (isset($location)) { $this->mapsLoaded[] = $location; } return $this; }

La mise jour des options se fait ensuite avec la fusion des anciennes configurations avec les nouvelles, avant de stocker le rsultat. Attention, pour deux cls identiques, la fusion utilise en priorit la cl du nouveau tableau doptions, car la dernire valeur rencontre crase lancienne. Pour plus de prcision, je vous conseille daller voir directement la documentation PHP de la fonction array_merge(). Dans notre exemple, nous avons pass en paramtre de la fabrique un tableau doptions, mais il est galement possible dindiquer au constructeur du ClassMapAutoloader un chemin de fichier de configuration. Lautoloader soccupe alors de rcuprer automatiquement les options en utilisant linstruction de langage include, par exemple : Utilisation dun fichier de correspondance
Zend\Loader\AutoloaderFactory::factory(array( ' Zend\Loader\ClassMapAutoloader' => array('config/application. autoload_classmap.php') ));

Zend/Loader/ClassMapAutoloader.php
public fonction registerAutoloadMap($map) { if (is_string($map)) { $location = $map; if ($this === ($map = $this->loadMapFromFile($location))) { return $this; } } [] }

La mthode loadMapFromFile() permet le chargement de configuration depuis un fichier. Notons que lobjet de type ClassMapAutoloader enregistre tous les chemins de fichiers fournis dans la variable $mapsLoaded, ce qui permet de sassurer de ne pas faire le traitement deux fois. En lui passant un tableau directement en paramtre comme dans le premier exemple, cest au dveloppeur de sassurer de ne pas faire deux fois le traitement car aucun contrle ne sera effectu du ct de lautoloader :

Les autoloaders Chapitre 2

27

Zend/Loader/ClassMapAutoloader.php
protected fonction loadMapFromFile($location) { if (!file_exists($location)) { [] } if (!$path = static::realPharPath($location)) { $path = realpath($location); } if (in_array($path, $this->mapsLoaded)) { return $this; } $map = include $path; return $map; }

Avec quelques traitements en plus, les deux manires de faire sont identiques. Nous savons prsent paramtrer les classes de chargement, analysons maintenant lenregistrement auprs de la SPL ainsi que la fonction appele lors du chargement : Zend/Loader/ClassMapAutoloader.php
public function register() { spl_autoload_register(array($this, 'autoload'), true, true); }

Le troisime paramtre pass la mthode de la SPL indique que lon souhaite placer cette classe de chargement en haut de la pile des autoloaders de notre application, par dfaut les enregistrements se placent la fin. tant donn les trs bonnes performances de lobjet ClassMapAutoloader, il est dans l'intrt de lapplication de lutiliser en premier. Comme il est indiqu lors de lenregistrement auprs de la SPL, cest la mthode autoload() qui soccupe du chargement de la classe : Zend/Loader/ClassMapAutoloader.php
public fonction autoload($class) { if (isset($this->map[$class])) { require_once $this->map[$class]; } }

Nous comprenons alors tout de suite lintrt dutiliser la classe ClassMapLoader, le traitement est simple et performant. Il suffit que la classe demande soit bien configure depuis les options de lautoloader afin de pouvoir linclure directement dans notre code sans plus de recherche. Maintenant que nous avons saisi lintrt de la classe de chargement ClassMapAutoloader, nous pourrions vouloir lutiliser pour la bibliothque du Zend Framework plutt que de passer par lenregistrement dun espace de nom auprs de notre autoloader standard. Les dveloppeurs du framework mettent disposition un fichier classemap_generator.php qui permet la gnration automatique dune configuration partir dun rpertoire cible. Il suffit de consulter les options

28

Les autoloaders Chapitre 2

du script afin den comprendre le fonctionnement : Aide sur la gnration de fichier de mapping
php classmap_generator.php --help

Une fois le fichier de configuration gnr, toutes les classes de lespace de nom Zend seront automatiquement charges depuis cet autoloader, ce qui permet un gain de performances.

La classe ModuleAutoloader
Comme son nom lindique et comme nous le verrons dans le chapitre consacr aux modules, le Zend\Loader\ModuleAutoloader sadapte uniquement nos modules, ce que nous prouvent les premires lignes du chargement de classe : Zend/Loader/ModuleAutoloader.php
public fonction autoload($class) { if (substr($class, -7) !== '\Module') { return false; } [] }

Lautoloader ne travaille que si nous recherchons une classe nomme Module, point dentre des modules de lapplication. Lutiliser comme autoloader pour le reste de notre application naurait alors aucune utilit. La classe ModuleAutoloader sera tudie en dtail dans la section ddie aux modules.

Linterface SplAutoloader
Les autoloaders ClassMapAutoloader et StandardAutoloader devraient suffire pour nos applications, leur fonctionnement tant assez complet. Cependant, il est possible de crer sa propre classe de chargement en implmentant linterface Zend\Loader\SplAutoloader qui dfinit quatre mthodes : Zend/Loader/SplAutoloader.php
interface SplAutoloader { public function __construct($options = null); public function setOptions($options); public function autoload($class); ublic function register(); p }

Les autoloaders Chapitre 2

29

Le constructeur doit pouvoir accepter les options de lautoloader en paramtre afin de permettre la mthode factory(), de la fabrique AutoloaderFactory, de crer directement linstance en lui passant les options en paramtre comme nous lavons vu prcdemment. La mthode setOptions() sera utilise pour la mise jour de la configuration de lautoloader par la fabrique lorsque linstance de la classe demande existe dj. Les deux autres mthodes, register() et autoload(), sont galement utilises par la mthode de fabrication factory() de lAutoloaderFactory.

30

Les autoloaders Chapitre 2

Les vnements

La programmation vnementielle est au cur du Zend Framework 2. Les vnements permettent aux diffrents composants de communiquer, dinteragir et de partager des informations entre eux. Ce type de programmation nest pas antipattern et permet dinstaurer de bonnes pratiques de codage. Cela va aussi permettre de supprimer les hooks que lon connat du Zend Framework 1 tout en augmentant la souplesse de notre application. Nous verrons que la gestion des priorits sur les couteurs de nos vnements est une bonne solution de remplacement aux pr et postvnements (preDispatch et postDispatch par exemple) que lon connat.

Prsentation des vnements


Un nouveau composant a fait son apparition dans Zend Framework 2, le gestionnaire dvnements Zend\EventManager\EventManager. Dfinissons dabord les diffrents termes : un event ou vnement est une action; un listener ou couteur est un couteur d'vnements; un event manager ou gestionnaire dvnements est un objet qui contient un ensemble dcouteurs et qui va lancer des vnements. Toute mthode coutant un vnement est notifie lorsque celui-ci est lanc. Le gestionnaire dvnements de type EventManager fait la passerelle entre les couteurs et les vnements qui se produisent. Il est possible dattacher un vnement un objet travers la mthode attach() et de lancer un vnement depuis la mthode trigger(). Afin de comprendre l'intrt et le fonctionnement des vnements, prenons un exemple dutilisation du gestionnaire d'vnements.

32

Les vnements Chapitre 3

Exemple dutilisation dvnement


class Developpeur { protected $events; protected $pauseHandler; ublic function travail() p { $this->pauseHandler = $this->getEventManager()->attach('pause', function($e) { printf('Evenement en cours "%s" sur objet "%s", avec les paramtres %s', $e->getName(), get_class($e->getTarget()), json_encode($e->getParams()) ); }); } public function termine() { if(!$this->pauseHandler){ return; } $this->getEventManager()->detach($this->pauseHandler); $this->pauseHandler = null; } ublic function setEventManager(EventManagerInterface $events) p { $this->events = $events; } public function getEventManager() { if (!$this->events){ $this->setEventManager(new EventManager( array(__CLASS__, get_called_class()) )); } return $this->events; } ublic function pause($params) p { $this->getEventManager()->trigger(__FUNCTION__, $this, $params); } } $developpeur = new Developpeur(); $developpeur->travail(); $developpeur->pause(array('cafe', 'court')); $developpeur->termine(); $developpeur->pause(array('cafe', 'long'));

Seule la phrase Evenement en cours "pause" sur lobjet Developpeur, avec les paramtres ["cafe","court"] saffiche. En effet, lobjet $developpeur sest dsabonn de lvnement pause lorsque la mthode termine() sest effectue.

Les vnements Chapitre 3

33

Chaque gestionnaire dvnements est identifi par une chane de caractres que lon passe son constructeur (ici le nom de la classe en cours avec __CLASS__), ce qui lui permet ensuite d'effectuer une liaison avec le gestionnaire partag.

Le gestionnaire partag
Un deuxime type de gestionnaire dvnements est disponible, le Zend\EventManager\SharedEventManager. Assez simple dutilisation, il possde un tableau dobjets de type EventManger en interne et permet aussi dattacher des couteurs un gestionnaire identifi depuis la mthode attach() : Cration de gestionnaire partag
$events = new SharedEventManager(); $events->attach('Developpeur', 'pause', function($e) { printf('Evenement en cours "%s" sur objet "%s", avec les paramtres %s', $e->getName(), get_class($e->getTarget()), json_encode($e->getParams()) ); });

Ce code instancie en ralit un gestionnaire dvnements avec comme identifiant la chane de caractres passe en premier paramtre, ici Developpeur. Analysons le traitement effectu dans la mthode attach() de la classe SharedEventManager : Zend/EventManager/SharedEventManager.php
public function attach($id, $event, $callback, $priority = 1) { $ids = (array) $id; foreach ($ids as $id) { if (!array_key_exists($id, $this->identifiers)) { $this->identifiers[$id] = new EventManager(); } $this->identifiers[$id]->attach($event, $callback, $priority); } }

Lidentifiant Developpeur sert de cl dans le tableau associatif $identifiers, o est alors instanci un gestionnaire dvnements auquel on ajoute lcouteur pour lvnement pass en deuxime paramtre. Tentons maintenant de comprendre le lien entre un gestionnaire dvnements que lon a cr plus haut et lobjet SharedEventManager. Examinons de plus prs la mthode trigger() de la classe EventManager.

34

Les vnements Chapitre 3

Zend/EventManager/EventManager.php
public function trigger($event, $target = null, $argv = array(), $callback = null) { [] return $this->triggerListeners($event, $e, $callback); }

Voici la mthode triggerListeners() : Zend/EventManager/EventManager.php


protected function triggerListeners($event, EventInterface $e, $callback = null) { $responses = new ResponseCollection; $listeners = $this->getListeners($event); sharedListeners $ = $this->getSharedListeners($event); $sharedWildcardListeners = $this->getSharedListeners('*'); $wildcardListeners = $this->getListeners('*'); f (count($sharedListeners) || count($sharedWildcardListeners) || i count($wildcardListeners)) { $listeners = clone $listeners; } $this->insertListeners($listeners, $sharedListeners); $this->insertListeners($listeners, $sharedWildcardListeners); $this->insertListeners($listeners, $wildcardListeners); f ($listeners->isEmpty()) { i return $responses; } foreach ($listeners as $listener) { $ responses->push(call_user_func($listener->getCallback(), $e)); if ($e->propagationIsStopped()) { $responses->setStopped(true); break; } if ($callback && call_user_func($callback, $responses->last())) { $responses->setStopped(true); break; } } return $responses; }

Cette mthode rcupre la liste des couteurs du gestionnaire partag depuis la mthode getSharedListeners() :

Les vnements Chapitre 3

35

Zend/EventManager/EventManager.php
protected function triggerListeners($event, EventInterface $e, $callback = null) { [] $sharedListeners = $this->getSharedListeners($event); [] }

Cette mthode rcupre tous les couteurs des gestionnaires dvnements du gestionnaire partag, dont lidentifiant correspond un des identifiants du gestionnaire courant. Le tableau de chanes de caractres pass au constructeur du gestionnaire sert didentifiant, nous comprenons maintenant son utilit. Par exemple, la cl passe au constructeur du gestionnaire suivant : Les identifiants du gestionnaire
new EventManager( array(__CLASS__, get_called_class()) )

Ici __CLASS__ ou get_called_class(), doit tre de la mme valeur que le premier argument de la mthode attach() de la classe SharedEventManager vue prcdemment : Attache dcouteur au gestionnaire partag
$events->attach('Developpeur', 'pause', function($e) { [] });

Dans cet exemple, le lien entre le gestionnaire dvnements instanci prcdemment (qui possde comme cl la constante PHP __CLASS__, qui a pour valeur le nom de la classe, ici Developpeur) et lajout dcouteurs sur l'instance de l'objet de type SharedEventManager (dont on demande la cration dun gestionnaire pour identifiant Developpeur) peut tre fait. Cette manire de fonctionner peut savrer assez utile pour ajouter facilement des vnements sans avoir disposition linstance du gestionnaire dvnements concern, si le partage est effectu. Cependant, ce comportement peut savrer parfois dstabilisant et il est possible de le dsactiver. Si lon examine la mthode qui s'occupe de rcuprer la liste des couteurs du gestionnaire partag, nous remarquons quelle va tout dabord rcuprer la liste des gestionnaires dvnements du SharedEventManager avant de faire correspondre avec les identifiants. Zend/EventManager/EventManager.php
protected function getSharedListeners($event) { if (!$sharedManager = $this->getSharedManager()) { return array(); } $identifiers = $this->getIdentifiers(); $sharedListeners = array();

36

Les vnements Chapitre 3


foreach ($identifiers as $id) { if (!$listeners = $sharedManager->getListeners($id, $event)) { continue; } if (!is_array($listeners) && !($listeners instanceof Traversable)) { continue; } foreach ($listeners as $listener) { if (!$listener instanceof CallbackHandler) { continue; } $sharedListeners[] = $listener; } } return $sharedListeners; }

La premire ligne tente dobtenir une connexion avec notre gestionnaire partag. Cette connexion est matrialise par lexistence dune instance de type SharedEventManager : Zend/EventManager/EventManager.php
protected function getSharedListeners($event) { if (!$sharedManager = $this->getSharedManager()) { return array(); } [] }

Zend/EventManager/EventManager.php
public function getSharedManager() { if (false === $this->sharedManager || $this->sharedManager instanceof SharedEventManagerInterface ) { return $this->sharedManager; } if (!StaticEventManager::hasInstance()) { return false; } $this->sharedManager = StaticEventManager::getInstance(); return $this->sharedManager; }

La relation entre le gestionnaire dvnements courant et lobjet SharedEventManager se fait automatiquement. Pour dsactiver cette fonctionnalit et bloquer la passerelle entre nos gestionnaires dvnements et notre gestionnaire statique, il suffit dappeler la mthode unsetSharedManager() qui permet cette dsactivation en passant la valeur false lattribut $sharedManager, valeur qui est synonyme de dsactivation comme nous venons de le voir :

Les vnements Chapitre 3

37

Dsactivation de la liaison avec le gestionnaire partag


$this->events->unsetSharedManager();

Il est alors possible de ractiver cette liaison en rejouant le code de la mthode getSharedManager() : Activation de la liaison avec le gestionnaire partag
$this->events->setSharedManager(StaticEventManager::getInstance());

Le gestionnaire dvnements rcupre ensuite la liste des couteurs prsents dans les objets de type EventManager du gestionnaire partag pour lvnement courant : Zend/EventManager/EventManager.php
protected function getSharedListeners($event) { [] $identifiers = $this->getIdentifiers(); $sharedListeners = array(); oreach ($identifiers as $id) { f if (!$listeners = $sharedManager->getListeners($id, $event)) { continue; } if (!is_array($listeners) && !($listeners instanceof Traversable)) { continue; } foreach ($listeners as $listener) { if (!$listener instanceof CallbackHandler) { continue; } $sharedListeners[] = $listener; } } return $sharedListeners; }

Afin de bien matriser la programmation vnementielle du Zend Framework, il est important de bien assimiler que le gestionnaire partag agit comme un simple conteneur de gestionnaire dvnements. Ce nest en aucun cas un gestionnaire dvnements, il a pour seule responsabilit de stocker des objets de type EventManager qui peuvent avoir les mmes identifiants que dautres gestionnaires que lon a crs. Il peut ensuite faire la liaison avec dautres gestionnaires dvnements qui possdent le mme identifiant que lun de ses objets stocks. Cette liaison peut tre supprime et il est important de bien comprendre quil est diffrent dattacher un vnement sur le gestionnaire partag plutt que sur le gestionnaire dvnements lui-mme. Nous avons remarqu que le gestionnaire partag est par dfaut une instance de la classe StaticEventManager :

38

Les vnements Chapitre 3

Zend/EventManager/EventManager.php
public function getSharedManager() { [] $this->sharedManager = StaticEventManager::getInstance(); return $this->sharedManager; }

La classe StaticEventManager est en fait une reprsentation statique de la classe SharedEventManager : Zend/EventManager/StaticEventManager.php
ublic static function getInstance() p { if (null === static::$instance) { static::$instance = new static(); } return static::$instance; }

La classe StaticEventManager hrite de la classe SharedEventManager et se comporte comme un singleton afin doffrir une instance unique dobjet de type SharedEventManager. Ce comportement permet aux gestionnaires dvnements de partager la mme instance de gestionnaire partag afin de pouvoir ajouter des vnements depuis nimporte quelle instance de gestionnaire dvnements. Examinons limplmentation du gestionnaire dvnements partag du framework avec la fabrique Zend\Mvc\Service\EventManagerFactory qui est responsable de la fabrication du gestionnaire dvnements de lapplication. Pour plus dinformations sur le gestionnaire de services, qui permet de grer les fabriques dobjets, reportez-vous au chapitre qui lui est consacr. Voici la fabrique EventManagerFactory du gestionnaire dvnements : Zend/Mvc/Service/EventManagerFactory.php
class EventManagerFactory implements FactoryInterface { public function createService(ServiceLocatorInterface $serviceLocator) { $em = new EventManager(); $em->setSharedManager($serviceLocator>get('SharedEventManager')); return $em; } }

La classe utilise la fabrique partage SharedEventManager afin de toujours utiliser la mme instance de gestionnaire d'vnements partag. En effet, la fabrique SharedEventManager est une fabrique dont l'instance sera partage, car la cration systmatique dune nouvelle instance du gestionnaire partag ferait perdre tout son sens la notion de partage. Nous pouvons donc constater que linstance du gestionnaire partag sera identique

Les vnements Chapitre 3

39

avec une instanciation sans la fabrique du gestionnaire dvnements si lon utilise toujours la fabrique SharedEventManager.

Lvnement wildcard
Nous avons parcouru le code de la classe EventManger et nous avons remarqu la prsence dun vnement sous forme de joker : Analyse de la rcupration des vnements wildcard
protected function triggerListeners($event, EventInterface $e, $callback = null) { $responses = new ResponseCollection; $listeners = $this->getListeners($event); sharedListeners $ = $this->getSharedListeners($event); $sharedWildcardListeners = $this->getSharedListeners('*'); $wildcardListeners = $this->getListeners('*'); [] }

Pour chaque vnement, le gestionnaire dvnements rcupre les couteurs attachs sur lvnement *, ce qui leur permet dtre notifi ds quun vnement est lanc. Lcouteur notifi pourra alors faire appel la mthode getName() de l'vnement afin de savoir quel est lvnement qui a t lanc. Prenons un exemple dans la classe Module de notre module Application : Application/Module.php
class Module implements AutoloaderProvider { public function init(ModuleManager $moduleManager) { $events = $moduleManager->events()->getSharedManager(); $events->attach('Zend\Mvc\Application', '*', array($this, 'onApplicationEvent'),100); [] } public function onApplicationEvent(Event $e) { echo $e->getName(); // affiche successivement route dispatch render finish } [] }

Les vnements wildcard doivent tre utiliss avec prcaution, car nous ne connaissons pas lavance tous les vnements qui peuvent tre lancs sur une instance de gestionnaire dvnements, il est important de sassurer de pouvoir agir lors des diffrents vnements possibles.

40

Les vnements Chapitre 3

Le gestionnaire dvnements global


Le gestionnaire dvnements global est trs simple dutilisation. Toutes les mthodes sont statiques, il ny a pas de notion didentifiants comme pour le gestionnaire partag, ni dinteraction avec dautres gestionnaires dvnements. La classe GlobalEventManager est donc accessible de nimporte o et fonctionne de manire indpendante : Zend/EventManager/GlobalEventManager.php
GlobalEventManager::attach('random-event', function($e) { echo $e->getName() . ' en cours, paramtre reu : ' . $e->getParam('myparam'); } ); GlobalEventManager::trigger('random-event', null, array('myparam'=>'value1'));

Affichage en sortie
random-event en cours, paramtre reu : value1

Si le gestionnaire global est plus simple de comprhension, il est conseill de modrer son utilisation et de ne pas sen servir comme fourre-tout afin dviter les collisions de noms dvnements. Utiliser les identifiants des gestionnaires dvnements permet une scurit supplmentaire, ainsi quun code mieux organis. Cette manire de regrouper les vnements va permettre de donner du sens leur gestion et permettre nimporte quel dveloppeur travaillant sur lapplication de comprendre plus vite lutilit et le domaine dinteraction de chacun des gestionnaires et de leurs vnements.

Le type ListenerAggregate
Nous pouvons parfois avoir besoin dune classe capable de grer une multitude dvnements avec les couteurs associs. Afin de pouvoir confier cette responsabilit une classe indpendante, linterface Zend\EventManager\ListenerAggregate est disponible, voici les mthodes que lon devra alors implmenter au sein de notre classe responsable du traitement des vnements : Zend/EventManager/ListenerAggregate.php
interface ListenerAggregate { public function attach(EventManagerInterface $events); ublic function detach(EventManagerInterface $events); p }

Voici un squelette de classe pouvant grer diffrents types dvnements en un seul objet :

Les vnements Chapitre 3

41

Exemple de conteneur dcouteur


class TravailleurEvents implements ListenerAggregate { protected $handlers = array(); ublic function attach(EventManagerInterface $events) p { $this->handlers[] = $events->attach('pause-dejeuner', array($this, 'absent')); $this->handlers[] = $events->attach('reunion', array($this, 'occupe')); } ublic function detach(EventManagerInterface $events) p { foreach ($this->handlers as $key => $handler) { $events->detach($handler); unset($this->handlers[$key]); } $this->handlers = array(); } ublic function absent(Event $e) p { $horaire = $e->getParam(retour); echo Je suis actuellement absent du bureau.; if($horaire) { echo Je serai de retour vers . $horaire; } } public function occupe(Event $e) { $remplacant = $e->getParam(collegue,null); echo Je suis actuellement indisponible.; if($remplacant) { echo Pour toutes questions, merci de vous adresser . $remplacant; } } }

Ce conteneur capable de grer les couteurs peut tre attach comme ceci : Utilisation dun conteneur dcouteur
$events = new EventManager('Travailleur'); $gestionHoraire = new TravailleurEvents(); $events->attachAggregate($gestionHoraire); $midiEvent = new Event(); $midiEvent->setName('pause-dejeuner'); $midiEvent->setParam('retour', '14h'); $brainstormingEvent = new Event(); $brainstormingEvent->setName('reunion'); $brainstormingEvent->setParam('collegue', 'Fred'); $events->trigger($midiEvent); $events->trigger($brainstormingEvent);

42

Les vnements Chapitre 3

Affichage en sortie
Je suis actuellement absent du bureau. Je serai de retour vers 14 h Je suis actuellement indisponible. Pour toutes questions, merci de vous adresser Fred

Les exemples ici sont trs simples, mais permettent davoir une premire approche sur l'intrt de ce genre de pratique assez rpandue dans le framework. Dcouvrons maintenant la mthode attachAggregate() de la classe EventManager : Zend/EventManager/EventManager.php
public function attachAggregate(ListenerAggregate $aggregate, $priority = 1) { return $aggregate->attach($this, $priority); }

Comme on peut sy attendre, celle-ci passe en paramtre linstance du gestionnaire courant lagrgateur afin quil puisse attacher lensemble des vnements grs par la classe TravailleurEvents. Nous pourrions donc remplacer directement linstruction suivante : Attache des couteurs du conteneur
$events->attachAggregate($gestionHoraire);

Par le code suivant : Attache des couteurs du conteneur


$gestionHoraire->attach($events);

Pour une question de comprhension et de lisibilit, nous conserverons la premire instruction. Notons quil est aussi possible dutiliser la mthode attach() de la classe EventManager pour ajouter lagrgateur, car celle-ci a prvu de grer ce cas en vrifiant le type dargument reu : Zend/EventManager/EventManager.php
public function attach($event, $callback = null, $priority = 1) { if ($event instanceof ListenerAggregate) { return $this->attachAggregate($event, $callback); } [] }

La mthode attach() de la classe TravailleurEvents soccupe, elle, dattacher les vnements pause-dejeuner et reunion. Notons quil est aussi possible de passer la priorit de lcouteur en paramtre. Nous verrons dans la section consacre lordre de la gestion des notifications, lintrt de celle-ci.

Les vnements Chapitre 3

43

Il est important de stocker les objets retourns depuis la mthode attach() du gestionnaire dvnements, car ces objets reprsentent un couteur dvnements : Zend/EventManager/EventManager.php
public function attach($event, $callback = null, $priority = 1) { [] $listener = new CallbackHandler($callback, array('event' => $event, 'priority' => $priority)); $this->events[$event]->insert($listener, $priority); return $listener; }

Lobjet Zend\Stdlib\CallbackHandler est le type de lobjet qui reprsente lcouteur et qui est retourn par cette mthode, il est important de garder une trace de cet objet afin de pouvoir utiliser la mthode detach() qui permet de supprimer lcoute dun vnement depuis son identifiant : Zend/EventManager/EventManager.php
public function detach($listener);

Le paramtre $listener peut tre un objet de type ListenerAggregate (comme on la vu pour la mthode attach(), cela fonctionne de la mme manire pour la mthode detach()), ou un objet de type CallbackHandler. Nous pourrions vouloir amliorer lexemple prcdent avec lutilisation dvnements personnaliss plutt que de passer en paramtre les donnes que lon souhaite transmettre lcouteur. Cela permettrait une encapsulation et un dcouplage plus complet et un descriptif des vnements plus intressant.

Crer ses vnements personnaliss


Lors du lancement dvnement, si la mthode trigger() reoit un vnement sous forme de chane de caractres, elle soccupe de lenvelopper sous la forme dobjet : Zend/EventManager/EventManager.php
public function trigger($event, $target = null, $argv = array(), $callback = null) { if ($event instanceof EventInterface) { $e = $event; $event = $e->getName(); $callback = $target; } elseif ($target instanceof EventInterface) { $e = $target; $e->setName($event); $callback = $argv; } elseif ($argv instanceof EventInterface) { $e = $argv; $e->setName($event); $e->setTarget($target);

44

Les vnements Chapitre 3


} else { $e = new $this->eventClass(); $e->setName($event); $e->setTarget($target); $e->setParams($argv); } [] }

Lobjet construit sera du type de lattribut $eventClass : Zend/EventManager/EventManager.php


protected $eventClass = 'Zend\EventManager\Event';

La classe Zend\EventManager\Event reprsente le type des vnements utilis par dfaut, il est possible de le changer avec la mthode : Zend/EventManager/EventManager.php
public function setEventClass($class);

Le paramtre $class est une chane de caractres qui reprsente le nom de la classe enveloppant les nouveaux vnements crs au sein du gestionnaire dvnements. Cela permet dinitialiser l'vnement avec la classe de notre choix implmentant linterface descriptive des vnements Zend\EventManager\EventInterface, car comme nous le voyons, la mthode triggerListeners() qui notifie les couteurs sattend un objet de ce type : Zend/EventManager/EventManager.php
protected function triggerListeners($event, EventInterface $e, $callback = null);

La premire des choses faire lors de la cration dvnements personnaliss est donc de crer une classe implmentant cette interface : Zend/EventManager/EventInterface.php
interface EventInterface { public function getName(); public function getTarget(); public function getParams(); public function getParam($name, $default = null); public function setName($name); public function setTarget($target); public function setParams($params); public function setParam($name, $value); public function stopPropagation($flag = true); public function propagationIsStopped(); }

Voici comment construire et utiliser un vnement personnalis en lui fournissant des options :

Les vnements Chapitre 3

45

Construction dun vnement personnalis


$event = new MyEvent(); $event->setMyCustomParam($value);

La classe MyEvent hrite de la classe de base Event et dfinit une mthode setMyCustomParam(). Il ne reste alors plus qu linjecter dans la mthode trigger() : Lancement de lvnement personnalis
$events->trigger(mon-evenement, $this, $event);

Nous pouvons dfinir lattribut $target lvnement : Modification de la cible de lvnement


$event->setTarget($this);

Ou encore le nom de lvnement : Modification du nom de lvnement


$event->setName(mon-evenement);

Ce qui nous donne une encapsulation complte : Encapsulation de lvnement


$event = new MyEvent(); $event->setMyResult($value); $event->setTarget($this); $event->setName(mon-evenement); $events->trigger($event);

Il est toujours possible de passer un callback pour le court-circuitage en deuxime argument lorsque lon utilise ce systme dencapsulation : Lancement de lvnement personnalis
$events->trigger($event, $callback);

Nous verrons la notion de court-circuit et de propagation dans les deux prochains chapitres. Maintenant que nous savons manipuler les objets de type EventInterface, nous allons nous intresser aux objets de rponse des couteurs, ainsi quau processus de propagation.

46

Les vnements Chapitre 3

Lobjet de rponse
Chaque couteur dvnements retourne une rponse qui est stocke par le gestionnaire dvnements, et il peut tre parfois utile davoir accs cette liste de rponses. Nous avons pu remarquer que la mthode triggerListeners(), du gestionnaire dvnements, soccupe de grer les notifications et la rcupration des rponses afin de les retourner en fin de traitement : Zend/EventManager/EventManager.php
protected function triggerListeners($event, EventInterface $e, $callback = null) { $responses = new ResponseCollection; $listeners = $this->getListeners($event); [] foreach ($listeners as $listener) { $ responses->push(call_user_func($listener->getCallback(), $e)); i f ($e->propagationIsStopped()) { $responses->setStopped(true); break; } i f ($callback && call_user_func($callback, $responses->last())) { $ responses->setStopped(true); break; } } return $responses; }

Lobjet $responses de type Zend\EventManager\ResponseCollection est responsable du stockage de la liste des rponses des couteurs notifis. Lobjet ResponseCollection hrite de la classe SplStack, pile base sur une liste doublement chane. La liste des mthodes de la classe ResponseCollection est assez courte : Zend/EventManager/ResponseCollection.php
class ResponseCollection extends SplStack { public function stopped(); public function setStopped($flag); public function first(); public function last(); ublic function contains($value); p }

La mthode stopped() permet de connatre ltat de la propagation, si elle a t stoppe ou non. Le premier objet de rponse peut tre rcupr par la mthode first() qui reprsente lobjet tout en bas de la pile, le dernier objet de rponse est extrait depuis la mthode last(). Enfin, la mthode contains() permet de savoir si une valeur donne est contenue dans la liste des rponses des couteurs. Nous allons maintenant expliquer la notion de court-circuit, ainsi que la manire

Les vnements Chapitre 3

47

dagir sur la propagation des vnements, ce qui va nous permettre de contrler intgralement le workflow de lvnement.

Stopper la propagation dun vnement


Lors du lancement dun vnement, il est possible de stopper la propagation des notifications depuis un couteur. La premire manire de court-circuiter la propagation dun vnement est de lindiquer explicitement au sein du callback de lcouteur : Arrt de la propagation dun vnement
$events->attach(pause, function($e) { $e->stopPropagation(); return new MonObjetDeReponse(); });

Expliquons maintenant en dtail le comportement de la mthode stopPropagation() : Zend/EventManager/Event.php


public function stopPropagation($flag = true) { $this->stopPropagation = (bool) $flag; } public function propagationIsStopped() { return $this->stopPropagation; }

Ces deux mthodes permettent le maintien et la consultation de lattribut stopPropagation qui est utilis dans le gestionnaire dvnements au sein de la mthode triggerListeners(). En effet, cette mthode soccupe de rcuprer les couteurs de lvnement afin de les notifier : Zend/EventManager/EventManager.php
protected function triggerListeners($event, EventInterface $e, $callback = null) { $responses = new ResponseCollection; $listeners = $this->getListeners($event); [] foreach ($listeners as $listener) { $responses->push(call_user_func($listener->getCallback(), $e)); if ($e->propagationIsStopped()) { $responses->setStopped(true); break; } [] } [] }

48

Les vnements Chapitre 3

Le gestionnaire notifie les vnements un un et ajoute leur retour au tableau de rponses. Si lattribut $stopPropagation, que lon vrifie laide de la mthode propagationIsStopped(), est pass la valeur false au sein dun couteur, alors la pile de rponses est notifie et la propagation de lvnement est stoppe. La deuxime manire darrter la propagation dun vnement est de passer une fonction de callback la mthode trigger(), qui prend en paramtre la rponse retourne par le dernier couteur notifi et retourne un boolen. Si le retour de cette fonction de callback est vrai, alors la propagation sarrte. Reprenons la mthode triggerListener() du gestionnaire dvnements pour mieux comprendre comment est gr le court-circuitage : Zend/EventManager/EventManager.php
protected function triggerListeners($event, EventInterface $e, $callback = null) { [] foreach ($listeners as $listener) { $ responses->push(call_user_func($listener->getCallback(), $e)); i f ($e->propagationIsStopped()) { $ responses->setStopped(true); break; } i f ($callback && call_user_func($callback, $responses->last())) { $responses->setStopped(true); break; } } [] }

Lobjet de rponse est pass en paramtre la fonction de callback si elle existe, et si le retour de la fonction est vrai, alors la propagation est stoppe. Voici un exemple trivial : Exemple dutilisation de court-circuitage
$events->attach('pause', function($e) { return new A(); }); $events->attach('pause', function($e) { return new B(); }); $events->attach('pause', function($e) { return new C(); }); $events->trigger(pause, $this, $params, function($r) { return $r instanceof B; // arrte la propagation ds quun couteur retourne un objet de type B });

La propagation va alors sarrter ds la deuxime notification. Attention, comme indiqu plus haut, il suffit que le boolen de retour soit vrai, un

Les vnements Chapitre 3

49

court-circuit comme ci-dessous arrte immdiatement la propagation : Court-circuitage depuis une valeur vraie
$events->trigger(pause, $this, $params, function($r) { return false; });

En effet, la condition darrt ne vrifie pas la valeur true mais si celle-ci est vrai. Pour rappel, en PHP tout ce qui nest pas faux est considr comme vrai. Pour plus de dtails, je vous suggre daller consulter la documentation de PHP pour voir que ce qui peut tre considr comme faux lors dune conversion en boolen, comme: le boolen FALSE, lentier0, la chane vide, un tableau avec lments, le type NULL, etc. Nous avons donc vu quel point la notification des vnements est trs maniable et totalement contrle par le dveloppeur. Il est aussi ncessaire de comprendre comment ordonner les notifications afin de les matriser pleinement.

Grer lordre des notifications


Lors du lancement dun vnement, il est important de comprendre lordre utilis pour la notification des couteurs. Le gestionnaire dvnements se base sur un objet de queue priorit, la classe SplPriorityQueue qui permet de grer facilement les priorits. Les mthodes attach() du gestionnaire dvnements et du gestionnaire partag SharedEventManager possdent un argument facultatif qui permet de dfinir la priorit. Voici le prototype des deux mthodes : Zend/EventManager/EventManager.php
public function attach($event, $callback = null, $priority = 1);

Zend/EventManager/SharedEventManager.php
public function attach($id, $event, $callback, $priority = 1);

Par dfaut la priorit est gale 1. Il est donc possible de dfinir une priorit plus haute, suprieure ou gale 1, ce qui permet lcouteur dtre notifi le premier, ou une priorit plus basse qui notifiera lcouteur en fin de liste. Notons aussi quil est possible de connatre la priorit maximum de la liste en rcuprant lcouteur avec la plus haute priorit : Depuis la classe EventManager
$handler = $events->getListeners('pause')->top();

Depuis la classe SharedEventManager


$handler = $events->getListeners('Developpeur','pause')->top();

Ces instructions retournent un objet de type Zend\Stdlib\CallbackHandler dont

50

Les vnements Chapitre 3

il suffit de rcuprer la mtadonne associe la priorit : Rcupration de la priorit


$priority_max = $handler->getMetadatum('priority');

Nous comprenons maintenant que ces instructions vont tre utilises lors du lancement dun vnement afin de classer les couteurs par priorit : Zend/EventManager/EventManager.php
protected function insertListeners($masterListeners, $listeners) { if (!count($listeners)) { return; } oreach ($listeners as $listener) { f $priority = $listener->getMetadatum('priority'); if (null === $priority) { $priority = 1; } elseif (is_array($priority)) { $priority = array_shift($priority); } $masterListeners->insert($listener, $priority); } }

Lobjet $masterListeners de type PriorityQueue soccupe dajouter les couteurs par priorit afin de pouvoir les parcourir dans lordre choisi par le dveloppeur. Chacun sera ensuite notifi son tour.

Les vnements du framework


Afin de matriser la gestion des vnements qui interviennent dans le framework et des interactions que lon va pouvoir effectuer dans notre application, nous devons tablir la liste des principaux vnements utiliss par le cur du Zend Framework 2: Liste des vnements du gestionnaire de modules : Zend\ModuleManager\ModuleManager : loadModules lors du dbut de linitialisation des modules Pour chacun des modules : Zend\ModuleManager\ModuleManager : loadModule.resolve lors du chargement du module, rsolution de la classe Module du module en cours, Zend\ModuleManager\ModuleManager : loadModule lorsque le module est charg, Zend\ModuleManager\ModuleManager : loadModules.post lorsque les modules sont initialiss.

Les vnements Chapitre 3

51

Liste des vnements de lapplication : Zend\Mvc\Application: bootstrap en fin de traitement, lorsque la mthode bootstrap a initialis les ressources; Zend\Mvc\Application: route lors de la demande de routage ; Zend\Mvc\Application: dispatch lors de la demande du dispatch de laction en cours. Cet vnement est cout par le contrleur Zend\Mvc\Controller\ActionController qui lance un vnement : Zend\Mvc\Controller\ActionController : dispatch lors de la distribution par le contrleur abstrait; Zend\Mvc\Application: render lors de la demande du rendu de la vue; Zend\View\View: renderer lors de la demande du gestionnaire de rendus utiliser ; Zend\View\View: response lorsque le processus de rendu est termin, la rponse est prte ; Zend\Mvc\Application: finish lors de la fin du processus MVC, la rponse de lapplication est prte et retourne. Un vnement li aux erreurs du framework est disponible: Zend\Mvc\Application: dispatch.error lorsquune erreur est survenue lors du dispatch de laction (contrleur invalide, aucune route correspondante, etc.). Les vnements prsents lors du rendu de la vue ne sont pas lists ici, vous les retrouverez en dtail dans le chapitre consacr aux vues.

Avec le Zend Framework 1


La premire version du framework nutilise pas les vnements mais des hooks qui permettent de notifier les plugins ou contrleurs en fonction de la prsence ou non dune mthode de nom donne. Pour rappel, voici la liste des hooks disponibles sur le Zend Framework 1 : - routeStartup() et routeShutdown() pour notifier le dbut et la fin du routage. - dispatchLoopStartup() et dispatchLoopShutdown() qui notifie aprs le routage (et donc avant la distribution) et aprs la distribution. - preDispatch() et postDisptach() qui permettent aux plugins dtre notifis du dbut et de la fin de la distribution sur le contrleur - preDispatch() et postDisptach() qui permettent aux contrleurs dtre notifis avant et aprs laction courante. Nous avons maintenant toutes les cartes en main pour mieux comprendre les vnements dans le Zend Framework 2. Cela nous permet de comprendre l'intrt

52

Les vnements Chapitre 3

de la programmation vnementielle compare aux hooks du Zend Framework 1 et l'tendue de tout ce quil est possible de faire. Nous verrons dans le chapitre consacr la classe Zend\Mvc\Application, point dentre de notre application, la gestion dtaille des vnements lis au cur du framework.

Linjection de dpendances
Zend Framework 2 propose dans cette nouvelle version un composant capable de grer linjection de dpendances. Linjection de dpendances permet de crer une application o chacun des composants et chacune des classes ne seront plus dpendants fortement les uns des autres. Il ny a plus de dpendance code en dur. Prenons par exemple une classe A qui consomme un objet B, ncessaire son fonctionnement : Classe avec des dpendances dans le code
class A { protected $b; public function __construct() { $this->b = new B(); } public function uneaction() { $this->b->quelquechose(); } }

La classe A est fortement couple avec la classe B. La dpendance avec B est crite en dur dans le code, ce qui diminue la flexibilit de notre application. Reprenons lexemple avec lajout dune interface, qui permet de sassurer que lobjet reu en paramtre sera capable de remplir ses responsabilits en passant un contrat avec cette interface : Classe sans les dpendances dans le code
Class A { protected $b; public function __construct(capabledefaireQuelquechose $b) { $this->b = $b; }

54

Linjection de dpendances Chapitre 4


public function uneaction() { $this->b->quelquechose(); } } interface capabledefaireQuelquechose { public function quelquechose(); } class B implements capabledefaireQuelquechose { protected $b; public function quelquechose() { // traitements } }

Lobjet A est alors totalement dcoupl de lobjet B. Il est maintenant possible de crer un nouvel objet C, qui implmente cette nouvelle interface et que lon peut donner comme paramtre lobjet A. Il est aussi ncessaire dajouter les mthodes daltration de A, getter et setter , afin de pouvoir rcuprer ou modifier lobjet reu dans le constructeur. Examinons limplmentation de linjection de dpendances et du composant qui lui est ddi dans le Zend Framework 2.

Configuration manuelle du composant Di


Le composant du framework qui va nous servir grer nos injections de dpendances est le Zend\Di\Di. Ce composant doit tre configur afin de lui indiquer quelles vont tre les dpendances de chacun des objets. Pour le configurer manuellement, il est possible de lui fournir une liste de dfinitions sous forme de tableau qui lui permet denregistrer une liste de classes avec leurs dpendances. Pour les objets avec une configuration manquante, le composant se chargera lui-mme de lintrospection, il est donc prfrable dutiliser une configuration statique afin de gagner en performances. Voici un exemple de tableau de dfinitions que lon pourrait utiliser pour dcrire le router de lapplication ou encore la stratgie de rendu par dfaut, objets dont nous verrons l'utilit ultrieurement : Configuration du composant dinjection de dpendance
$diConfig = new DiConfiguration(array('definition' => array('class' => array( 'Zend\Mvc\Router\RouteStack' => array( 'instantiator' => array( 'Zend\Mvc\Router\Http\TreeRouteStack', 'factory' ), ),

Linjection de dpendances Chapitre 4


'Zend\Mvc\Router\Http\TreeRouteStack' => array( 'instantiator' => array( 'Zend\Mvc\Router\Http\TreeRouteStack', 'factory' ), ), [] 'Zend\Mvc\View\DefaultRenderingStrategy' => array( 'setLayoutTemplate' => array( 'layoutTemplate' => array( 'required' => false, 'type' => false, ), ), ), ))));

55

Chaque entre du sous-tableau class du tableau definition reprsente le nom de la classe avec pour valeur un tableau indiquant la procdure afin de linstancier et les paramtres qui lui sont ncessaires. Nous remarquons que pour obtenir une instance dobjet correspondant lidentifiant Zend\Mvc\Router\RouteStack il est ncessaire dutiliser la mthode factory() de la classe Zend\Mvc\Router\ Http\TreeRouteStack. Lidentifiant de la configuration reprsente un type dobjet, qui nest pas ncessairement le type de la classe qui sera instancie. Lidentifiant Zend\Mvc\Router\ RouteStack est en ralit une interface au sein du framework, elle ne peut donc pas rellement tre instancie. Lorsque nous souhaiterons obtenir, depuis notre composant dinjection de dpendances, une instance de la classe Zend\Mvc\ Router\RouteStack, le gestionnaire soccupera de lire la dfinition de cet objet et pourra alors instancier la classe concerne depuis les paramtres de la dfinition. Ceci nous permet dattacher une classe de notre choix aux diffrentes interfaces, comme ici pour linterface RouteStack o nous lui affectons la correspondance avec un objet de type TreeRouteStack. Une fois les dfinitions cres, nous pouvons indiquer les paramtres que l'on souhaite utiliser avec les dfinitions lors de l'instanciation. Prenons un exemple de configuration avec linstanciation dun adaptateur de base de donnes et le passage de nos paramtres : Configuration du Di
$di = new Zend\Di\Di; $diConfig = new Zend\Di\Configuration( array('instance' => array( 'Zend\Db\Adapter\Adapter' => array( 'parameters' => array( 'driver' => array( 'driver' => 'Pdo', 'dsn' => 'mysql:dbname=db;host=host', 'username' => 'username', 'password' => 'password' ), ) ), ))); $diConfig->configure($di); $db = $di->get('Zend\Db\Adapter\Adapter');

56

Linjection de dpendances Chapitre 4

Nous savons maintenant configurer et utiliser le composant dinjection de dpendances. Voyons comment nous pouvons automatiser ce genre de configuration afin dviter dcrire manuellement toutes les configurations dont on pourrait avoir besoin.

Configuration automatique du composant Di


Nous venons de voir comment configurer manuellement le composant dinjection de dpendances. Seulement, nous pouvons nous demander comment celui-ci gre la dfinition dune classe dont nous ne lui avons pas fourni de description. Dans ce cas, le framework va alors charger les dfinitions des classes la demande, ce qui permet de nutiliser des ressources que lorsque cela est ncessaire. Prenons un exemple en lui demandant de rcuprer une instance dont il ne connait pas la dfinition. La demande dobjet se fait depuis la mthode get() de la classe Di. Nous ne rentrerons pas dans les dtails du gestionnaire dinstances qui sera tudi la prochaine section. Demandons par exemple notre composant linstance de la classe Zend\Barcode\Barcode, dfinition dont il na pas connaissance : Instanciation de la classe Barcode
$barcode = $di->get(Zend\Barcode\Barcode);

Le composant Zend\Di\Di ne connat pas encore ce composant et va donc tenter de linstancier afin de nous retourner lobjet demand si celui-ci n'existe pas dj dans son gestionnaire d'instances : Zend/Di/Di.php
Zend/Di/Di.php public function get($name, array $params = array()) { // vrification au sein du gestionnaire [] $instance = $this->newInstance($name, $params); array_pop($this->instanceContext); return $instance; }

Lorsque le Di tente dinstancier la classe souhaite, il vrifie dabord si elle existe dans sa liste de dfinitions au sein de la mthode newInstance() qui est responsable de la cration de nouvelles instances : Zend/Di/Di.php
public function newInstance($name, array $params = array(), $isShared = true) { $definitions = $this->definitions; [] if (!$definitions->hasClass($class)) { [] // erreur

Linjection de dpendances Chapitre 4


} $instantiator [] }

57

= $definitions->getInstantiator($class);

Nous voyons qu'il est obligatoire que la dfinition soit renseigne pour ne pas lever d'erreur. Cependant, nous allons voir que la vrification de dfinition va donner lieu la cration automatique de celle-ci si elle n'existe pas. Lattribut $definitions, objet de type Zend\Di\DefinitionList, qui contient la liste des dfinitions, est bas sur une liste doublement chane, la SplDoublyLinkedList : Zend/Di/DefinitionList.php
class DefinitionList extends SplDoublyLinkedList implements Definition\ Definition { public function __construct($definitions) { if (!is_array($definitions)) { $definitions = array($definitions); } foreach ($definitions as $definition) { $this->push($definition); } } ublic function addDefinition(Definition\Definition $definition, p $addToBackOfList = true) { if ($addToBackOfList) { $this->push($definition); } else { $this->unshift($definition); } } [] }

La liste contient toutes les instances des objets de description de classe qui permettent de dcrire le processus dinstanciation dune classe. Ces objets doivent passer un contrat avec linterface Zend\Di\Definition\Definition. Parmi les classes qui limplmentent, nous retrouvons la classe Zend\Di\Definition\ClassDefinition qui encapsule la dfinition dune classe. Un autre objet plus intressant de type Zend\Di\Definition\RuntimeDefinition est capable de rcuprer les dfinitions de classes la vole. Comme nous pouvons nous en douter, cest cet objet qui est utilis par dfaut dans le composant dinjection de dpendances lors de labsence de dfinition : Zend/Di/Di.php
public function __construct(DefinitionList $definitions = null, InstanceManager $instanceManager = null, Configuration $config = null) { $this->definitions = ($definitions) ?: new DefinitionList(new Definition\ RuntimeDefinition());

58

Linjection de dpendances Chapitre 4


$this->instanceManager = ($instanceManager) ?: new InstanceManager(); if ($config) { $this->configure($config); } }

Par dfaut, le composant Di ajoute un objet RuntimeDefinition la liste des dfinitions de classe, ce qui lui permet de lutiliser en cas de dfinition manquante. Seulement, cet objet sera le dernier des objets de dfinitions notifis, car comme nous lavons vu dans le constructeur de la classe DefinitionList, les objets reus en paramtre sont, par dfaut, ajouts avec la mthode push(), ce qui les place la fin de la liste. Si lon souhaite utiliser un conteneur de dfinitions en priorit, il est ncessaire de lajouter en utilisant la valeur du deuxime paramtre que lon passera avec la valeur true : Zend/Di/DefinitionList.php
public function addDefinition(Definition\Definition $definition, $addToBackOfList = true) { if ($addToBackOfList) { $this->push($definition); } else { $this->unshift($definition); } }

En effet, il est normal que lobjet RuntimeDefinition soit le dernier utilis afin de trouver une dfinition car il trouvera toujours la dfinition dune classe existante. En effet, le fait de le faire la vole est moins performant quun objet qui contiendrait dj cette dfinition, il est donc ncessaire de le placer la fin si lon ne souhaite pas systmatiquement obtenir les dfinitions lexcution. Analysons comment lobjet RuntimeDefinition cre nos dfinitions la vole. Lorsque la classe RuntimeDefinition est interroge sur une dfinition de classe, celle-ci se contente de vrifier lexistence de la classe dans la liste de ses dfinitions dj en mmoire, et si elle ne la possde pas, vrifie son existence : Zend/Di/Definition/RuntimeDefinition.php
public function hasClass($class) { if ($this->explicitLookups === true) { return (array_key_exists($class, $this->classes)); } return class_exists($class) || interface_exists($class); }

Si les autoloaders sont capables de localiser la classe ou linterface demande, notre objet sera alors capable de la charger afin de lintrospecter. Ensuite lorsque le composant Di reoit la confirmation de lexistence de la dfinition, il souhaite connaitre la mthode dinstanciation de la classe demande afin de la crer, et interroge donc la mthode getInstantiator() de notre conteneur de dfinition :

Linjection de dpendances Chapitre 4

59

Zend/Di/Definition/RuntimeDefinition.php
public function getInstantiator($class) { if (!array_key_exists($class, $this->classes)) { $this->processClass($class); } return $this->classes[$class]['instantiator']; }

Le composant vrifie sil possde la dfinition au sein de son tableau de dfinitions en mmoire, et comme ce ne sera pas le cas dans notre exemple, il va ensuite devoir rcuprer la dfinition de la classe depuis une introspection : Zend/Di/Definition/RuntimeDefinition.php
protected function processClass($class) { $strategy = $this->introspectionStrategy; $rClass = new Reflection\ClassReflection($class); $className = $rClass->getName(); [] }

Il nest pas ncessaire danalyser le reste de la classe, le code est assez long et nous pouvons voir que lintrospection de la classe est base sur la classe Zend\Code\ Reflection\ClassReflection et Zend\Di\Definition\IntrospectionStrategy qui vont servir construire la dfinition. La configuration automatique, ou la vole, des dfinitions est pratique car celle-ci ne se ralise que sur demande, mais est videmment beaucoup moins performante quune configuration manuelle. Afin dviter de devoir crire des centaines de lignes de configuration, nous allons voir comment charger semi-automatiquement les dfinitions dont on a besoin, en les compilant dans un fichier de configuration.

Configuration semi-automatique du Di
Zend Framework 2 propose plusieurs classes permettant denglober la gestion des dfinitions. Nous venons de voir la classe RuntimeDefinition et nous avons aussi parl de ClassDefinition qui enveloppe une seule et unique dfinition de classe. Examinons maintenant une classe qui va nous permettre de grer les dfinitions en compilant celles dun dossier complet. Le composant responsable de ce traitement est le Zend\Di\Definition\CompilerDefinition. Voici tout de suite un exemple dutilisation de cette classe : Compilation de dfinitions
$compiler = new \Zend\Di\Definition\CompilerDefinition(); $compiler->addDirectoryScanner( n ew \Zend\Code\Scanner\DirectoryScanner('/path/to/library/Zend/ Acl') );

60

Linjection de dpendances Chapitre 4


$compiler->addCodeScannerFile( n ew \Zend\Code\Scanner\FileScanner(/path/to/library/Zend/ Barcode/Barcode.php') ); $compiler->compile(); $di->definitions()->addDefinition($compiler, false);

La classe CompilerDefinition permet dajouter un fichier ou un rpertoire entier scanner. Nous pouvons ajouter ce conteneur de dfinitions notre liste de gestionnaires, en noubliant pas dindiquer le deuxime paramtre de la mthode addDefinition() la valeur false afin de pouvoir ajouter ce gestionnaire en tout dbut de liste. La compilation de dfinitions reprsente un cot en termes de performances, il est donc conseill denvelopper la liste des dfinitions dans un objet de type ArrayDefinition, ce qui va nous permettre dexporter nos dfinitions dans un fichier de cache : Compilation et cache des dfinitions
if(!file_exists(__DIR__ . '/di-compilier-definitions.php')) { $compiler = new \Zend\Di\Definition\CompilerDefinition(); $compiler->addDirectoryScanner( new \Zend\Code\Scanner\DirectoryScanner('library/Zend/Acl') ); $compiler->addCodeScannerFile( new \Zend\Code\Scanner\FileScanner('library/Zend/Barcode/ Barcode.php') ); $compiler->compile(); file_put_contents(__DIR__ . '/di-compilier-definitions.php','<?php return ' . var_export($compiler->toArrayDefinition()->toArray(), true) . ';'); } else { $compiler = new \Zend\Di\Definition\ArrayDefinition( include __DIR__ . '/di-compilier-definitions.php' ); }

Il est galement possible de crer des dfinitions depuis des objets de type Zend\ Di\Definition\Builder\PhpClass que lon ajoute au conteneur de dfinitions Zend\Di\Definition\BuilderDefinition : Utilisation de la classe PhpClass
$builder = new \Zend\Di\Definition\BuilderDefinition(); $phpClass = new \Zend\Di\Definition\Builder\PhpClass(); $phpClass->setName('Zend\EventManager\EventManager'); $phpClass->setInstantiator('__construct'); $phpClass->createInjectionMethod('setStaticConnectio ns')->addParameter('connections','Zend\EventManager\ StaticEventCollection',false); $phpClass->createInjectionMethod('setIdentifiers')->addParameter('iden tifiers','array',false);

Linjection de dpendances Chapitre 4

61

$builder->addClass($phpClass); $this->getLocator()->definitions()->addDefinition($builder,false); $events = $this->getLocator()->newInstance('Zend\EventManager\ EventManager', array( 'connections'=>\Zend\EventManager\StaticEventManager::getInstance(), 'identifiers'=>'MonIdentifiant') );

Cette notation permet de dcrire facilement la dfinition de classes PHP. Nous avons pris lexemple du gestionnaire dvnements dont on a cr la dfinition pour lajouter lobjet de type BuilderDefinition. Nous avons parcouru les diffrents conteneurs de dfinitions, intressons-nous maintenant au processus dinstanciation et de partage dinstance du composant dinjection de dpendances.

Le gestionnaire dinstances
Le composant Di fonctionne avec dautres composants afin de pouvoir dlguer certaines de ses tches. Il dispose dune classe permettant de conserver la liste des instances quil a cres, afin de lui permettre de les utiliser ou de les partager. La classe utilise est Zend\Di\InstanceManager qui reprsente le gestionnaire dinstances du composant Di. Examinons la liste des mthodes du composant Di qui vont nous permettre de rcuprer les instances demandes : Mthodes dinstanciation depuis le Di
class Di implements DependencyInjection { [] public function get($name, array $params = array()); public function newInstance($name, array $params = array(), $isShared = true); [] }

Deux mthodes sont disponibles afin de pouvoir rcuprer les instances dobjets. La premire mthode get() permet de rcuprer linstance sans connatre son existence dans le gestionnaire dinstances. Cette mthode vrifie si celle-ci existe dans le gestionnaire et nous la retourne si cest le cas. Si linstance nest pas disponible, le composant Di va alors crer un nouvel objet avant de le stocker automatiquement dans le gestionnaire : Zend/Di/Di.php
public function get($name, array $params = array()) { [] $im = $this->instanceManager; if ($params) { $fastHash = $im->hasSharedInstanceWithParameters($name, $params, true);

62

Linjection de dpendances Chapitre 4


if ($fastHash) { array_pop($this->instanceContext); return $im->getSharedInstanceWithParameters(null, array(), $fastHash); } } else { if ($im->hasSharedInstance($name, $params)) { array_pop($this->instanceContext); return $im->getSharedInstance($name, $params); } } $instance = $this->newInstance($name, $params); [] return $instance; }

Les mthodes hasSharedInstanceWithParameters() et hasSharedInstance() permettent de vrifier lexistence dune instance partage au sein du gestionnaire. Si celle-ci est disponible, les mthodes getSharedInstanceWithParameters() et getSharedInstance() retournent alors lobjet existant, sinon une nouvelle instance est cre. La mthode newInstance() permet de crer une nouvelle instance de la classe demande. Lappel direct la mthode newInstance() depuis nos contrleurs sera donc plus couteux en termes de ressources, car celle-ci instancie un nouvel objet sans se soucier de lexistence de celui-ci, mais aura lavantage de pouvoir sen servir sans avoir connatre son contexte dutilisation dans le framework. Bien videmment, si le contexte nous importe peu et que laltration de lobjet par dautres mthodes du framework na pas dimportance, il est prfrable dutiliser la mthode get() afin de bnficier du gain de performances et du contexte de cet objet. Nous ne dtaillerons pas tout le code de la mthode newInstance() que nous avons dj lgrement observ et qui est plutt long, mais les principales tapes sont les suivantes : Zend/Di/Di.php
public function newInstance($name, array $params = array(), $isShared = true) { [] if ($instanceManager->hasAlias($name)) { $class = $instanceManager->getClassFromAlias($name); $alias = $name; } [] if (!$definitions->hasClass($class)) { [] } instantiator $ $injectionMethods [] if ($instantiator $instance = $params, $alias); = $definitions->getInstantiator($class); = $definitions->getMethods($class); === '__construct') { $this->createInstanceViaConstructor($class,

Linjection de dpendances Chapitre 4


if (array_key_exists('__construct', $injectionMethods)) { unset($injectionMethods['__construct']); } } [] if ($isShared) { [] else { $this->instanceManager->addSharedInstance($instance, $name); } } [] return $instance; }

63

Le composant vrifie que le nom demand n'est pas un alias, auquel cas il rcupre le nom rel de la classe, puis vrifie lexistence de la dfinition. Il rcupre ensuite les informations dont il a besoin, depuis les dfinitions disponibles afin dinstancier la classe comme indiqu dans la configuration. Si le paramtre $isShared de la mthode a pour valeur true, le gestionnaire soccupe alors de partager linstance au sein du manager. Lors de lappel la mthode get(), comme ce paramtre nest pas disponible, celle-ci appellera toujours la mthode newInstance() sans indiquer de valeur de partage. Le paramtre tant positionn la valeur true par dfaut, linstance sera donc automatiquement partage si celle-ci nexiste pas dj dans le gestionnaire. Les mthodes daltration vont ensuite permettre linjection des paramtres fournis avant de retourner linstance de lobjet.

64

Linjection de dpendances Chapitre 4

Le gestionnaire de services

Si le composant dinjection de dpendances prsente de nombreux avantages, comme une plus grande flexibilit dans la gestion de nos diffrents services et de nos dpendances, celui-ci a les inconvnients dtre difficile apprhender et de dgrader les performances. En effet, si les dfinitions des classes utilises ne sont pas connues, le composant Di soccupe alors de rcuprer la dfinition des objets l'excution. Seulement, le fait de prcompiler les dfinitions des composants et bibliothques ne sera peut-tre pas suffisant pour retrouver de meilleures performances, et cela ajouterait une couche de complexit supplmentaire notre application. Afin de rpondre cette problmatique, un nouveau composant laisse la possibilit aux dveloppeurs de crer leurs propres objets de fabrique sans la gestion automatique des dfinitions et des dpendances, le Zend\ServiceManager. Ce composant, bien quautonome, possde un sous-composant Zend\ServiceManager\Di capable de tirer les avantages de linjection de dpendances avec la simplicit de ce gestionnaire de services. Ce nouveau composant permet une approche plus simple et plus performante aux dveloppeurs qui font face des problmatiques de cration et de partage de services. Le ServiceManager sinitialise avec des objets de configuration que lon va examiner afin de comprendre toutes les possibilits de ce gestionnaire de services.

La configuration du gestionnaire
Le gestionnaire Zend\ServiceManager\ServiceManager sinitialise grce un objet de type Zend\ServiceManager\ConfigInterface : Zend/ServiceManager/ServiceManager.php
ublic function __construct(ConfigurationInterface $configuration = p null) { if ($configuration) { $configuration->configureServiceManager($this); } }

Une classe de base implmentant linterface de configuration Zend\ServiceMana-

66

Le gestionnaire de services Chapitre 5

ger\ConfigInterface existe dans le framework : Zend/ServiceManager/Config.php


class Config implements ConfigInterface { protected $config = array(); public function __construct($config = array()) { $this->config = $config; } ublic function getFactories() p { return (isset($this->config['factories'])) ? $this>config['factories'] : array(); } ublic function getAbstractFactories() p { return (isset($this->config['abstract_factories'])) ? $this>config['abstract_factories'] : array(); } [] ublic function configureServiceManager(ServiceManager p $serviceManager) { $allowOverride = $this->getAllowOverride(); isset($allowOverride) ? $serviceManager->setAllowOverride($allo wOverride) : null; foreach ($this->getFactories() as $name => $factory) { $serviceManager->setFactory($name, $factory); } foreach ($this->getAbstractFactories() as $factory) { $serviceManager->addAbstractFactory($factory); } [] } }

La classe de configuration encapsule la configuration du gestionnaire de services afin de pouvoir linitialiser depuis ses mthodes d'altration du mme nom. Voici les diffrents attributs de lobjet de configuration: invokables: les classes renseignes comme tant invokables seront instancies directement sans la ncessit dutiliser une fabrique. Ce type conviendra aux objets sans dpendance particulire; factories: la configuration lie la cl factories permet de dfinir la liste des classes de fabrique. Une classe de fabrique est associe un type dobjet et retourne une instance de celui-ci; abstract_factories: les fabriques abstraites ressemblent aux classes de fabrique la diffrence que celles-ci ne sont pas attaches un objet en particulier, elles sont appeles lorsquaucune fabrique nexiste pour lobjet demand; services: les valeurs renseignes dans le champ services peuvent tre de tout type (entier, chane de caractres, tableau, objets, etc.) et sont associes la

Le gestionnaire de services Chapitre 5

67

cl donne; aliases: les alias permettent de dfinir plusieurs cls pour un mme objet. Il est aussi possible de faire des alias dalias autant que lon souhaite; shared: les services que lon dfinit comme partags permettent de ne pas instancier un nouvel objet chaque appel. Linstance cre est enregistre afin dtre partage chaque appel; initializers: la configuration lie cette cl permet dinitialiser les objets une fois instancis; Maintenant que nous connaissons les configurations possibles pour le gestionnaire de services, voyons comment les mettre en uvre.

Mise en uvre
Afin de comprendre les diffrentes possibilits du ServiceManager, nous partirons de larchitecture dexemple ci-dessous pour lillustration de nos propos :

Le script dexemple utilis est le fichier servicemanager.php, le fichier configuration.php est un fichier de configuration et le fichier di.php est le fichier pour linitialisation du composant Zend\Di. Commenons par instancier la classe ServiceManager :

68

Le gestionnaire de services Chapitre 5

Exemple de configuration
$serviceManager = new ServiceManager(new Configuration(array( 'invokables' => array( 'simple' => 'Invokables\SimpleClass', ), 'factories' => array( 'cache' => 'Factories\CacheStorageFactory', 'navigation' => 'Factories\NavigationFactory', ), 'abstract_factories' => array( 'generic' => 'AbstractFactories\AbstractFactory', ), 'aliases' => array( 'simpleclass' => 'simple', ), ))); $serviceManager->setService('Configuration', include 'configuration. php');

La premire ligne de notre configuration indique que nous avons accs la classe SimpleClass de lespace de noms invokables depuis lidentifiant simple. Il est alors possible dobtenir une instance de cette classe depuis linstruction suivante : Utilisation des classes invokables
$simpleClass = $serviceManager->get('simple'); echo get_class($simpleClass);

Affichage de la sortie
Invokables\SimpleClass

La rcupration dinstances existantes ou la cration de celles-ci passe par la mthode get(), nom de mthode que lon utilisait dj avec notre composant dinjection de dpendances. Afin de comprendre comment est retourn lobjet dsir, analysons la cration dinstances depuis le ServiceManager avec la mthode get() : Zend/ServiceManager/ServiceManager.php
public function get($name, $usePeeringServiceManagers = true) { $cName = $this->canonicalizeName($name); $rName = $name; if ($this->hasAlias($cName)) { do { $cName = $this->aliases[$cName]; } while ($this->hasAlias($cName)); if (!$this->has(array($cName, $rName))) { [] } } f (isset($this->instances[$cName])) { i return $this->instances[$cName]; }

Le gestionnaire de services Chapitre 5

69

instance $ = null; $retrieveFromPeeringManagerFirst = $this->retrieveFromPeeringManagerF irst(); if ($usePeeringServiceManagers && $retrieveFromPeeringManagerFirst) { $instance = $this->retrieveFromPeeringManager($name); } if (!$instance) { if ($this->canCreate(array($cName, $rName))) { $instance = $this->create(array($cName, $rName)); } elseif ($usePeeringServiceManagers && !$retrieveFromPeeringManagerFirst) { $instance = $this->retrieveFromPeeringManager($name); } } if (!$instance && !is_array($instance)) { [] } if ($this->shareByDefault() && (!isset($this->shared[$cName]) || $this->shared[$cName] === true) ) { $this->instances[$cName] = $instance; } return $instance; }

La premire instruction formate le nom demand avant de vrifier si celui-ci nest pas un alias. Le fait de vrifier lexistence dalias en boucle permet dajouter des alias dalias autant que lon souhaite. Le tableau $instances est ensuite contrl afin de vrifier l'existence de linstance de lobjet demand. Pour rappel, les objets instancis partags et les valeurs passes lors de notre configuration depuis le paramtre services (entier, chane de caractres, tableau, objets, etc.) sont enregistrs dans cet attribut : Zend/ServiceManager/Configuration.php
public function configureServiceManager(ServiceManager $serviceManager) { [] foreach ($this->getServices() as $name => $service) { $serviceManager->setService($name, $service); } [] }

Zend/ServiceManager/ServiceManager.php
public function setService($name, $service, $shared = true) { [] $this->instances[$name] = $service; $this->shared[$name] = (bool) $shared; return $this; }

70

Le gestionnaire de services Chapitre 5

Si aucun objet ne correspond la requte dans le tableau dinstances, la procdure de cration va tre effectue. Pour comprendre la procdure de cration il faut garder lesprit que chaque gestionnaire de services peut fonctionner avec dautres instances de ce mme objet en tant que gestionnaire parent ou gestionnaire enfant. En effet, il est possible de relier plusieurs gestionnaires de services entre eux afin de tirer parti des fabriques de chacun dentre eux, tout en permettant de sparer et redfinir chaque fabrique au sein des diffrents gestionnaires. Les mthodes createScopedServiceManager() et addPeeringServiceManager() permettent de relier un gestionnaire de services lobjet courant : Zend/ServiceManager/ServiceManager.php
public function createScopedServiceManager($peering = self::SCOPE_ PARENT) { $scopedServiceManager = new ServiceManager(); if ($peering == self::SCOPE_PARENT) { $scopedServiceManager->peeringServiceManagers[] = $this; } if ($peering == self::SCOPE_CHILD) { $this->peeringServiceManagers[] = $scopedServiceManager; } return $scopedServiceManager; }

Cette mthode permet dattacher un nouveau gestionnaire un autre. Par dfaut, lobjet courant est attach comme gestionnaire partag au nouveau. Si nous indiquons que la liaison doit se faire comme un objet enfant, alors le nouveau gestionnaire sera inscrit dans la liste des gestionnaires partags de lobjet courant. La mthode addPeeringServiceManager() ralise la mme chose mais avec un gestionnaire existant. Lors de la cration de lobjet demand, nous avons le choix d'utiliser ou non en priorit les gestionnaires partags pour linstanciation de lobjet. Ce choix peut tre ralis depuis la mthode setRetrieveFromPeeringManagerFirst() qui permet de dfinir un boolen afin dindiquer si lon doit utiliser ou non les gestionnaires partags en premier. La mthode retrieveFromPeeringManagerFirst() permet de consulter ltat de ce boolen. La procdure dinstanciation se charge alors de vrifier cet tat avant de procder la cration depuis le gestionnaire choisi : Zend/ServiceManager/ServiceManager.php
public function get($name, $usePeeringServiceManagers = true) { [] $instance = null; $retrieveFromPeeringManagerFirst = $this->retrieveFromPeeringManagerF irst(); if ($usePeeringServiceManagers && $retrieveFromPeeringManagerFirst) { $instance = $this->retrieveFromPeeringManager($name); } if (!$instance) { if ($this->canCreate(array($cName, $rName))) { $instance = $this->create(array($cName, $rName)); } elseif ($usePeeringServiceManagers && !$retrieveFromPeeringManagerFirst) { $instance = $this->retrieveFromPeeringManager($name);

Le gestionnaire de services Chapitre 5


} } [] }

71

La mthode retrieveFromPeeringManager() permet de rcuprer linstance depuis un gestionnaire partag. Cette mthode se contente dappeler la mthode get() de ses gestionnaires : Zend/ServiceManager/ServiceManager.php
protected function retrieveFromPeeringManager($name) { foreach ($this->peeringServiceManagers as $peeringServiceManager) { if ($peeringServiceManager->has($name)) { return $peeringServiceManager->get($name); } } return null; }

Le gestionnaire partag peut lui aussi avoir des gestionnaires partags et ainsi de suite, ce qui gnre des appels rcursifs et peut complexifier la comprhension de la cration dun objet. Il est donc conseill de ne pas abuser des partages de gestionnaires et den matriser le processus. Lors de la cration de lobjet, la premire vrification de la mthode create() porte sur la prsence dune fabrique avant de vrifier le tableau de classes instanciables marques comme invokables : Zend/ServiceManager/ServiceManager.php
public function create($name) { $instance = false; if (is_array($name)) { list($cName, $rName) = $name; } else { $rName = $name; $cName = $this->canonicalizeName($rName); } f (isset($this->factories[$cName])) { i $instance = $this->createFromFactory($cName, $rName); } if (!$instance && isset($this->invokableClasses[$cName])) { $instance = $this->createFromInvokable($cName, $rName); } [] }

La mthode createFromFactory() cre lobjet depuis la mthode createService() de la fabrique si celle-ci implmente linterface FactoryInterface :

72

Le gestionnaire de services Chapitre 5

Zend/ServiceManager/ServiceManager.php
protected function createFromFactory($canonicalName, $requestedName) { [] if ($factory instanceof FactoryInterface) { $instance = $this->createServiceViaCallback(array($factory, 'createService'), $canonicalName, $requestedName); } elseif (is_callable($factory)) { $instance = $this->createServiceViaCallback($factory, $canonicalName, $requestedName); } else { [] } return $instance; }

Linterface FactoryInterface ne dfinit que la mthode de construction : Zend/ServiceManager/FactoryInterface.php


interface FactoryInterface { public function createService(ServiceLocatorInterface $serviceLocator); }

Notons que la cration depuis une fabrique transite par la mthode createServiceViaCallback() qui permet de se prmunir contre les rfrences circulaires. En effet, les fabriques reoivent linstance du gestionnaire de services, ce qui peut entraner des boucles si lon demande des objets qui dpendent chacun les uns des autres. Afin dillustrer nos propos, voici un exemple de fabrique avec la fabrication dun simple cache: Utilisation des fabriques
$cache = $serviceManager->get('cache'); echo get_class($cache);

Affichage de la sortie
Zend\Cache\Storage\Adapter\Filesystem

La configuration indique que la fabrique correspondant lobjet didentifiant cache est la classe Factories\CacheStorageFactory dont voici le code : Factories/CacheStorageFactory.php
class CacheStorageFactory implements FactoryInterface { public function createService(ServiceLocatorInterface $serviceLocator) { $configuration = $serviceLocator->get('Configuration');

Le gestionnaire de services Chapitre 5


return StorageFactory::factory($configuration['cache']); } }

73

La fabrique de cache rcupre aussi la configuration que lon avait ajoute depuis linstruction de notre exemple : Ajout de la configuration au gestionnaire de services
$serviceManager->setService('Configuration', include 'configuration. php');

Ce fichier contient la configuration globale de notre exemple. La fabrique lutilise alors pour crer linstance de lobjet demand. Lors de la cration, si aucune fabrique nexiste pour lobjet demand, une vrification est faite sur les classes de type invokable : Zend/ServiceManager/ServiceManager.php
public function create($name) { [] if (isset($this->factories[$cName])) { $instance = $this->createFromFactory($cName, $rName); } if (!$instance && isset($this->invokableClasses[$cName])) { $instance = $this->createFromInvokable($cName, $rName); } [] }

La mthode createFromInvokable() gre les classes instanciables directement et se contente dinstancier lobjet demand depuis loprateur new: Zend/ServiceManager/ServiceManager.php
protected function createFromInvokable($canonicalName, $requestedName) { $invokable = $this->invokableClasses[$canonicalName]; if (!class_exists($invokable)) { [] } $instance = new $invokable; return $instance; }

Encore une fois, si aucun objet de fabrique ne correspond lobjet instancier, les fabriques abstraites vont tre parcourues afin de savoir si lune dentre elles peut traiter linstanciation de lobjet demand. Les classes abstraites implmentent linterface Zend\ServiceManager\AbstractFactoryInterface qui dfinit deux mthodes :

74

Le gestionnaire de services Chapitre 5

Zend/ServiceManager/AbstractFactoryInterface.php
interface AbstractFactoryInterface { public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName); ublic function createServiceWithName(ServiceLocatorInterface p $serviceLocator, $name, $requestedName); }

La mthode canCreateServiceWithName() permet de sassurer que la fabrique peut instancier lobjet demand. En effet, cette fabrique est gnraliste et nest pas rattache un type particulier, il faut donc sassurer quelle peut effectuer le travail demand. La mthode createServiceWithName() cre linstance de lobjet et reoit linstance du gestionnaire de services ainsi que le nom de la classe de lobjet souhait. Lors de la cration dobjets, chaque fabrique abstraite est donc parcourue jusqu ce quune dentre elles retourne une instance dobjet : Zend/ServiceManager/ServiceManager.php
public function create($name) { [] if (!$instance && $this->canCreateFromAbstractFactory($cName, $rName)) { $instance = $this->createFromAbstractFactory($cName, $rName); } [] }

Zend/ServiceManager/ServiceManager.php
protected function createFromAbstractFactory($canonicalName, $requestedName) { foreach ($this->abstractFactories as $index => $abstractFactory) { [] try { $this->pendingAbstractFactoryRequests[get_ class($abstractFactory)] = $requestedName; $instance = $this->createServiceViaCallback( array($abstractFactory, 'createServiceWithName'), $canonicalName, $requestedName ); unset($this->pendingAbstractFactoryRequests[get_ class($abstractFactory)]); } catch (\Exception $e) { [] } [] } return $instance; }

Prenons un exemple dutilisation avec la hirarchie prsente en dbut de chapitre :

Le gestionnaire de services Chapitre 5

75

Utilisation des fabriques abstraites


$bar = $serviceManager->get('bar'); echo get_class($bar);

Affichage de la sortie
AbstractFactories\Classes\Bar

Voici le code de la fabrique abstraite AbstractFactories\AbstractFactory de notre exemple : AbstractFactories/AbstractFactory.php


class AbstractFactory implements AbstractFactoryInterface { public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName) { return in_array(strtolower($name), array('foo', 'bar')); } ublic function createServiceWithName(ServiceLocatorInterface p $serviceLocator, $name, $requestedName) { $className = 'AbstractFactories\\Classes\\' . ucfirst($name); return new $className; } }

La fabrique abstraite est capable dinstancier les objets correspondant aux identifiants foo et bar . Lexemple ici est trs simple, la mthode createServiceWithName() se contente dinstancier lobjet sans plus dinstructions. Notons que la mthode reoit deux paramtres diffrents sur le nom de linstance demande: $name et $requestedName. Le second paramtre est le nom utilis lors de lappel et qui correspond lalias si celui-ci existe, alors que le paramtre $name correspond la rsolution de lalias. Si aucun alias nexiste, alors les deux paramtres sont identiques. Prenons par exemple la cration du type bar-alias que nous avons dfini dans notre configuration dexemple : Utilisation dun alias
$bar = $serviceManager->get('bar-alias');

Dans cet exemple, la variable $name vaut bar et la variable $requestedName vaut bar-alias. Une fois les types de cration parcourus (fabrique dobjets, classe invokable, fabrique abstraite), lobjet instanci peut alors tre initialis si lon a ajout nos classes dinitialisation au sein du gestionnaire de services:

76

Le gestionnaire de services Chapitre 5

Zend/ServiceManager/ServiceManager.php
public function create($name) { [] foreach ($this->initializers as $initializer) { if ($initializer instanceof InitializerInterface) { $initializer->initialize($instance, $this); } elseif (is_object($initializer) && is_callable($initializer)) { $initializer($instance, $this); } else { call_user_func($initializer, $instance, $this); } } return $instance; }

Une interface est disponible pour les classes dinitialisation : Zend/ServiceManager/InitializerInterface.php


interface InitializerInterface { public function initialize($instance, ServiceLocatorInterface $serviceLocator); }

Voici un exemple de classe dinitialisation pour lobjet de navigation, de type Zend\ Navigation\Navigation : Utilisation des classes dinitialisation
$navigationInitializer = new Initializers\NavigationInitializer(array (array('uri' => 'zend.com'), array('uri' => 'zend.fr'))); $serviceManager->addInitializer($navigationInitializer);

Initializers/NavigationInitializer.php
class NavigationInitializer implements InitializerInterface { protected $pages = array(); ublic function __construct(array $pages) p { $this->pages = $pages; } ublic function initialize($instance, ServiceLocatorInterface p $serviceLocator) { if(!$instance instanceof Navigation) { return; } if($this->pages && count($instance->getPages()) == 0) { $instance->addPages($this->pages); } } }

Linitialisation et lajout de pages dans le composant de navigation se font depuis

Le gestionnaire de services Chapitre 5

77

la classe dinitialisation. La cration dobjets depuis le gestionnaire de services offre une plus grande souplesse aux dveloppeurs pour la mise en place de fabriques dobjets. Ce composant permet aussi de gagner en performances, compar la cration dobjets depuis le composant Di. Ce dernier peut aussi fonctionner en complment du gestionnaire de services.

Le ServiceManager complmentaire du Di
Le composant ServiceManager na pas t introduit afin de remplacer le composant dinjection de dpendances, mais afin de lui tre complmentaire en offrant dautres possibilits de fabriques dobjets. Les deux composants peuvent fonctionner ensemble, une passerelle existe avec le sous-composant Zend\ServiceManager\Di.

La fabrique du Zend\ServiceManager\Di
Analysons limplmentation dune fabrique base sur le composant Di. Prenons un exemple de cration depuis le composant dinjection de dpendances et un autre avec une fabrique utilisant le ServiceManager coupl au Di: Utilisation du Di
$di = new Di; $config = new DiConfiguration(include 'di.php'); $config->configure($di); $navigation = $di->get('navigation'); echo get_class($navigation) . "\n"; echo count($navigation->getPages());

Affichage de la sortie
Zend\Navigation\Navigation 1

Utilisation du Di avec le ServiceManager


$diFactory = new DiServiceFactory($di, 'navigation', array(), DiServiceFactory::USE_SL_AFTER_DI) $serviceManager->setFactory('di-navigation-after', $diFactory); $navigation = $serviceManager->get('di-navigation-after') echo get_class($navigation) . "\n"; echo count($navigation->getPages());

Affichage de la sortie
Zend\Navigation\Navigation 1

Voici la configuration du composant dinjection de dpendances :

78

Le gestionnaire de services Chapitre 5

di.php
return array( 'instance' => array( 'aliases' => array( 'navigation' => 'Zend\Navigation\Navigation', 'json' => 'Zend\Json\Json', ), 'Zend\Navigation\Navigation' => array( 'parameters' => array( 'pages' => array( array('uri' => 'zend.com'), ), ), ), ), );

La classe DiServiceFactory prend une instance du composant Di en paramtre, le nom de lobjet que la fabrique prend en charge, et la position du ServiceManager par rapport au Di lors de la cration dinstances. Le gestionnaire de services peut tre utilis avant, paramtre DiServiceFactory::USE_SL_BEFORE_DI, ou aprs, paramtre DiServiceFactory::USE_SL_AFTER_DI, le composant dinjection de dpendances. Dans les deux exemples, cest le composant Di qui prend en charge la cration de lobjet de navigation. En effet, le gestionnaire de services est explicitement utilis avec le paramtre DiServiceFactory::USE_SL_AFTER_DI, ce qui le placera alors aprs le Di lors de la cration dobjets. Dans les deux cas, linitialisation est la mme et une seule page existe au sein du composant de navigation. Gardons le premier exemple et modifions le deuxime en demandant lutilisation du ServiceManager avant le composant Di afin que ce dernier soit utilis si seulement aucune fabrique ne correspond lobjet demand au sein du gestionnaire de services: Utilisation du gestionnaire avant le Di
$diFactory = new DiServiceFactory($di, 'navigation', array(), DiServiceFactory::USE_SL_BEFORE_DI); $serviceManager->setFactory('di-navigation-before', $diFactory) $navigation = $serviceManager->get('di-navigation-before'); echo get_class($navigation) . "\n"; echo count($navigation->getPages());

Affichage de la sortie
Zend\Navigation\Navigation 2

Le composant de navigation est cette fois instanci et initialis par le ServiceManager qui lui ajoute deux pages. La classe DiServiceFactory est simple analyser, elle se contente de vrifier la position du gestionnaire de services afin de savoir lequel utiliser en premier:

Le gestionnaire de services Chapitre 5

79

Zend/ServiceManage/Di/DiServiceFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { $this->serviceLocator = $serviceLocator; return $this->get($this->name, $this->parameters, true); }

Zend/ServiceManage/Di/DiServiceFactory.php
public function get($name, array $params = array()) { if ($this->useServiceLocator == self::USE_SL_BEFORE_DI && $this >serviceLocator->has($name)) { return $this->serviceLocator->get($name); } try { $service = parent::get($name, $params) return $service } catch (DiClassNotFoundException $e) { if ($this->useServiceLocator == self::USE_SL_AFTER_DI && $this>serviceLocator->has($name)) { return $this->serviceLocator->get($name); } else { [] // exception } } }

Il est aussi possible de dsactiver lutilisation du ServiceManager en utilisant le paramtre DiServiceFactory ::USE_SL_NONE lors de sa construction.

La fabrique abstraite du Zend\ServiceManager\Di


Une fabrique abstraite base sur le composant dinjection de dpendances est disponible, il sagit de la classe Zend\ServiceManager\Di\DiAbstractServiceFactory. Cette classe plus gnrale permet lajout du composant Di sur lensemble des chargements du ServiceManager. Voici comment ajouter une fabrique abstraite au ServiceManager qui utilisera le composant dinjection de dpendances : Utilisation de la fabrique abstraite base sur le Di
$di = new Di; $config = new DiConfiguration(include 'di.php'); $config->configure($di); $diAbstractFactory = new DiAbstractServiceFactory($di); $serviceManager->addAbstractFactory($diAbstractFactory);

Comme pour la fabrique, si nous ne prcisons pas la position du gestionnaire de services au sein de la classe abstraite, celui-ci ne sera pas utilis. En effet, lintrt dutiliser le gestionnaire de services en premier est limit dans ce cas, le but tant dajouter le composant dinjection de dpendances en dernire couche comme fabrique abstraite, afin de lutiliser uniquement si aucune de nos fabriques ne

80

Le gestionnaire de services Chapitre 5

convient pour lobjet demand. Il devient alors possible de rcuprer nimporte quel composant gr par le Di, comme le composant de date dont nous avons ajout lalias prcdemment : Rcupration du composant JSON
$json = $serviceManager->get('json'); echo $json->encode(array('foo' => 'bar'));

Affichage de la sortie
{"foo":"bar"}

Il est aussi possible de le rcuprer directement depuis le nom de sa classe, comme pour lutilisation classique du Di : Rcupration du composant JSON
$json = $serviceManager->get('Zend\Json\Json'); echo $json->encode(array('foo' => 'bar'));

Affichage de la sortie
{"foo":"bar"}

Flexible et performant, le gestionnaire de services a aussi t intgr dans le processus MVC du framework o il occupe une grande place.

Implmentation dans le framework


Le gestionnaire de services est utilis pour construire les principaux lments du framework comme le gestionnaire de modules, de vues ou la classe Application, chef dorchestre du processus MVC. Le gestionnaire de services utilis par le processus MVC est construit dans la mthode dinitialisation de la classe Application, point dentre de notre application Web : Zend/Mvc/Application.php
public static function init($configuration = array()) { $smConfig = isset($configuration['service_manager']) ? $configuration['service_manager'] : array(); serviceManager = new ServiceManager(new Service\ServiceManagerConfig( $ $smConfig)); [] }

Comme nous le remarquons, lobjet Zend\Mvc\Service\ServiceManagerConfig dfinit les fabriques de base du framework.

Le gestionnaire de services Chapitre 5

81

Zend/Mvc/Service/ServiceManagerConfig.php
class ServiceManagerConfig implements ConfigInterface { protected $invokables = array( 'SharedEventManager' => 'Zend\EventManager\SharedEventManager', ); protected $factories = array( 'EventManager' => 'Zend\Mvc\Service\EventManagerFactory', 'ModuleManager' => 'Zend\Mvc\Service\ModuleManagerFactory', ); protected $abstractFactories = array(); protected $aliases = array( 'Zend\EventManager\EventManagerInterface' => 'EventManager', ); protected $shared = array( 'EventManager' => false, ); [] }

Nous remarquons que la fabrique du gestionnaire d'vnements ne partage pas son instance afin de pouvoir en crer une nouvelle chaque demande. Les autres fabriques dfinissant les objets utiliss par le processus MVC sont dfinies par la classe Zend\Mvc\Service\ServiceListenerFactory, couteur permettant denrichir la configuration du gestionnaire de services qui dfinit les fabriques principales du framework : Zend/Mvc/Service/ServiceListenerFactory.php
class ServiceListenerFactory implements FactoryInterface { [] protected $defaultServiceConfig = array( 'invokables' => array( 'DispatchListener' => 'Zend\Mvc\DispatchListener', 'RouteListener' => 'Zend\Mvc\RouteListener', ), 'factories' => array( 'Application' => 'Zend\Mvc\Service\ApplicationFactory', 'Config' => 'Zend\Mvc\Service\ConfigFactory', [] ), 'aliases' => array( 'Configuration' > 'Config', 'Console' => 'ConsoleAdapter', 'ControllerPluginBroker' => 'ControllerPluginManager', ), ); [] }

Lattribut factories contient la liste des fabriques qui permettront linitialisation des composants MVC du framework. Ces fabriques seront dtailles dans les chapitres concerns par chacune dentre elles.

82

Le gestionnaire de services Chapitre 5

Les modules
Dans le Zend Framework 2, la notion de module a volu. Un module est un composant indpendant qui possde au minimum son propre espace de noms ainsi quune classe nomme Module dans un fichier Module.php. Chaque module possde ses fichiers de configuration, ainsi que ses contrleurs et vues et chacun peut maintenant tre indpendant de lenvironnement afin dtre rutilis facilement dans une autre application. Sa configuration tant propre son fonctionnement, son installation peut donc se faire simplement en ajoutant son nom et son chemin la configuration du gestionnaire de modules, ainsi quen adaptant la configuration du module sa propre application.

Configuration et couteurs
Comme nous lavons vu dans le chapitre prcdent, cest le gestionnaire de services qui est charg de fabriquer le gestionnaire de modules. Nous remarquons depuis la classe Zend\Mvc\Application, point dentre de notre application, que la fabrique est enregistre sous la cl ModuleManager : Zend/Mvc/Application.php
public static function init($configuration = array()) { [] $serviceManager->get('ModuleManager')->loadModules(); return $serviceManager->get('Application')->bootstrap(); }

Analysons le comportement de la fabrique ModuleManagerFactory et le rle quelle occupe au sein du framework. Nous aborderons les principales tapes avant de les dtailler dans les sections suivantes. Voici le code correspondant la cration du service : Zend/Mvc/Service/ModuleManagerFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { if (!$serviceLocator->has('ServiceListener')) {

84

Les modules Chapitre 6


$ serviceLocator->setFactory('ServiceListener', 'Zend\Mvc\Service\ ServiceListenerFactory'); } $configuration = $serviceLocator->get('ApplicationConfig'); $listenerOptions = new ListenerOptions($configuration['module_ listener_options']); $defaultListeners = new DefaultListenerAggregate($listenerOptions); $serviceListener = $serviceLocator->get('ServiceListener'); [] $events = $serviceLocator->get('EventManager'); $events->attach($defaultListeners); $events->attach($serviceListener); moduleEvent = new ModuleEvent; $ $moduleEvent->setParam('ServiceManager', $serviceLocator); $moduleManager = new ModuleManager($configuration['modules'], $events); $moduleManager->setEvent($moduleEvent); return $moduleManager; }

La premire instruction dfinit la fabrique de lcouteur du gestionnaire de services, si celle-ci ne lest pas dj, qui permet dinitialiser les fabriques du framework par dfaut qui sont utilises tout au long du framework : Zend/Mvc/Service/ModuleManagerFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { if (!$serviceLocator->has('ServiceListener')) { $serviceLocator->setFactory('ServiceListener', 'Zend\Mvc\ Service\ServiceListenerFactory'); } [] }

Nous verrons plus loin la liste des fabriques de lcouteur du gestionnaire de services. La configuration globale de lapplication est ensuite rcupre : Zend/Mvc/Service/ModuleManagerFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { [] $configuration = $serviceLocator->get('ApplicationConfiguration'); [] }

Nous prendrons, comme exemple, le fichier de configuration suivant : application.config.php


<?php return array( 'modules' => array( 'Application', ),

Les modules Chapitre 6


'module_listener_options' => array( 'config_cache_enabled' => false, 'cache_dir' => 'data/cache', 'module_paths' => array( './module', './vendor', ), ), );

85

Linstruction suivante concerne linitialisation de lobjet responsable des options du gestionnaire de modules avec la cration dun objet de type Zend\ModuleManager\Listener\ListenerOptions : Zend/Mvc/Service/ModuleManagerFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { [] $listenerOptions = new ListenerOptions($configuration['module_ listener_options']); [] }

Lobjet ListenerOptions enregistre les options que lon utilisera pour la gestion des modules, il possde peu dattributs : Zend/ModuleManager/Listener/ListenerOptions.php
class ListenerOptions extends Options { protected $modulePaths = array(); protected $configGlobPaths = array(); protected $configStaticPaths = array(); protected $extraConfig = array(); protected $configCacheEnabled = false; protected $configCacheKey; protected $cacheDir; [] }

Lattribut $modulePaths est un tableau qui contient la liste des chemins o se situent nos modules. Le premier chemin ./module contient les modules propres notre application et ./vendor la liste des modules externes ajouts lapplication. Les trois attributs suivants: $configGlobPaths, $configStaticPaths, $extraConfig permettent de stocker les chemins de fichiers de configuration globale lapplication. Lattribut suivant $configCacheEnabled permet dactiver ou non le cache interne de configuration des modules. Ce cache, sous forme de fichier, sera stock dans le dossier dont le chemin est reprsent par lattribut $cacheDir . Le nom du cache aura pour nom module-config-cache.ma-cle. php o ma-cle sera remplac par la variable $configCacheKey. Dans notre

86

Les modules Chapitre 6

exemple, il ny a pas de valeur renseigne dans le tableau de configuration concernant le nom de la cl du fichier de cache, mais nous pouvons la personnaliser en ajoutant la valeur comme ceci: Dfinition dune cl de cache
<?php return array( [] 'module_listener_options' => array( 'config_cache_enabled' => true, 'config_cache_key' => 'ma-cle', 'cache_dir' => 'data/cache', 'module_paths' => array( './module', './vendor', ), ), [] );

Notons que la classe ListenerOptions hrite de Zend\Stdlib\AbstractOptions qui fournit des mthodes de base afin de peupler un objet depuis un tableau doptions. La classe AbstractOptions transforme le nom des cls du tableau doptions en nom de mthode prfix par set ce qui permet de peupler automatiquement lobjet qui tend cette classe. Pour plus de dtails sur cette classe, reportez-vous au chapitre traitant la bibliothque standard Zend\Stdlib. Retournons la fabrique du gestionnaire de modules o linstruction suivante instancie un objet de type ListenerAggregate, expliqu dans un chapitre prcdent: Zend/Mvc/Service/ModuleManagerFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { [] $defaultListeners = new DefaultListenerAggregate($listenerOptions); [] }

La classe DefaultListenerAggregate contient lensemble des couteurs qui vont intervenir lors du chargement de nos diffrents modules, de la classe de chargement de module en passant par lobjet responsable des initialisations de modules. Les couteurs de cette classe seront dtaills dans la section suivante. Linstruction suivante initialise un conteneur dcouteurs spcialiss dans la rcupration de configuration lie au gestionnaire de services: Zend/Mvc/Service/ModuleManagerFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { [] $serviceListener = $serviceLocator->get('ServiceListener'); [] }

Les modules Chapitre 6

87

La fabrique de lcouteur du gestionnaire de services initialise son objet avec sa liste de fabriques par dfaut: Zend/Mvc/Service/ServiceListenerFactory.php
class ServiceListenerFactory implements FactoryInterface { [] protected $defaultServiceConfig = array( 'invokables' => array( 'DispatchListener' => 'Zend\Mvc\DispatchListener', 'RouteListener' => 'Zend\Mvc\RouteListener', ), 'factories' => array( 'Application' => 'Zend\Mvc\Service\ApplicationFactory', 'Config' => 'Zend\Mvc\Service\ConfigFactory' [] ), [] ); [] }

La configuration par dfaut reprsente la majorit des fabriques disponibles dans le framework. Une fois cette configuration ajoute lcouteur charg dinitialiser le gestionnaire de services, la fabrique du gestionnaire de modules lui ajoute des configurations sur les cls et mthodes permettant de surcharger la configuration du gestionnaire de services de lapplication:
ublic function createService(ServiceLocatorInterface p $serviceLocator) { [] $serviceListener->addServiceManager( $serviceLocator, 'service_manager', 'Zend\ModuleManager\Feature\ServiceProviderInterface', 'getServiceConfig' ); $serviceListener->addServiceManager( 'ControllerLoader', 'controllers', 'Zend\ModuleManager\Feature\ControllerProviderInterface', 'getControllerConfig' ); $serviceListener->addServiceManager( 'ControllerPluginManager', 'controller_plugins', 'Zend\ModuleManager\Feature\ControllerPluginProviderInterface', 'getControllerPluginConfig' ); $serviceListener->addServiceManager( 'ViewHelperManager', 'view_helpers', 'Zend\ModuleManager\Feature\ViewHelperProviderInterface', 'getViewHelperConfig' ); [] }

88

Les modules Chapitre 6

Nous verrons plus loin lintrt et le principe de cette configuration et comment la personnaliser. Nous verrons que ces quelques lignes nous permettront de comprendre comment configurer le gestionnaire de services rapidement depuis nos modules. Les couteurs lis la configuration du gestionnaire de services et les couteurs par dfaut sont ensuite attachs au gestionnaire dvnements: Zend/Mvc/Service/ModuleManagerFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { [] $events = $serviceLocator->get('EventManager'); $events->attach($defaultListeners); $events->attach($serviceListener); [] }

Enfin, vnement et gestionnaire sont initialiss: Zend/Mvc/Service/ModuleManagerFactory.php


public function createService(ServiceLocatorInterface $serviceLocator) { [] $moduleEvent = new ModuleEvent; $moduleEvent->setParam('ServiceManager', $serviceLocator); $moduleManager = new ModuleManager($configuration['modules'], $events); $moduleManager->setEvent($moduleEvent); return $moduleManager; }

Nous voici maintenant prts pour le chargement des modules. La configuration lie aux modules est enregistre et les couteurs sont initialiss, il ne reste plus qu orchestrer le tout avec le gestionnaire de modules.

Chargement des modules


Lors de la cration du gestionnaire de modules ModuleManager, la liste des modules utiliser dans notre application lui a t passe en paramtre: Zend/Mvc/Service/ModuleManagerFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { [] $moduleManager = new ModuleManager($configuration['modules'], $events); [] }

Les modules Chapitre 6

89

Dans notre exemple, le fichier de configuration possde le module suivant: application.config.php


<?php return array( 'modules' => array( 'Application', ), [] );

Le gestionnaire de modules est prt pour le chargement des modules, il ne ncessite pas plus dinitialisations ou de traitements. Le chargement de modules seffectue laide de la mthode loadModules() du gestionnaire: Zend/Mvc/Application.php
public static function init($configuration = array()) { [] $serviceManager->get('ModuleManager')->loadModules(); return $serviceManager->get('Application')->bootstrap(); }

Le gestionnaire dvnements lance un premier vnement loadModules, reprsent par la constante ModuleEvent::EVENT_LOAD_MODULES , afin de notifier le dbut du chargement des modules: Zend/ModuleManager/ModuleManager.php
public function loadModules() { [] $this->getEventManager()->trigger(ModuleEvent::EVENT_LOAD_MODULES, $this, $this->getEvent()); [] }

Le premier couteur notifi, attach depuis le conteneur par dfaut vu prcdemment, est la classe ModuleAutoloader qui senregistre comme autoloader lors de linitialisation des modules afin de pouvoir tre utilise lors du chargement de chacun dentre eux: Zend/ModuleManager/Listener/DefaultListenerAggregate.php
public function attach(EventManagerInterface $events) { [] $moduleAutoloader = new ModuleAutoloader($options->getModulePaths()); $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULES, array($moduleAutoloader, 'register'), 9000); [] }

90

Les modules Chapitre 6

Cest en effet cette classe qui va permettre le chargement des classes Module. php de chacun des modules: Zend/Loader/ModuleAutoloader.php
public function register() { spl_autoload_register(array($this, 'autoload')); }

Sa mthode autoload() sera utilise, si besoin, afin de localiser les classes de modules. Nous verrons le fonctionnement en dtail de cette mthode lorsque nous rencontrerons ltape de chargement de module. Lcouteur qui est ensuite notifi est un objet de type ConfigListener depuis sa mthode onloadModulesPre(), attach depuis le conteneur dcouteurs par dfaut: Zend/ModuleManager/Listener/DefaultListenerAggregate.php
public function attach(EventManagerInterface $events) { [] $configListener = $this->getConfigListener(); [] $this->listeners[] = $events->attach($configListener); [] }

Zend/ModuleManager/Listener/ConfigListener.php
public function attach(EventManagerInterface $events) { $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULES, array($this, 'onloadModulesPre'), 1000); if ($this->skipConfig) { return $this; } this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, $ array($this, 'onLoadModule')); $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULES, array($this, 'onLoadModulesPost'), -1000); return $this: }

Lobjet spcialis dans la gestion de la configuration sinjecte au sein de lvnement, ce qui permet aux autres couteurs davoir accs lobjet de configuration des modules: Zend/ModuleManager/Listener/ConfigListener.php
public function onloadModulesPre(ModuleEvent $e) { $e->setConfigListener($this); return $this; }

Les modules Chapitre 6

91

Lorsque les traitements prcdant le chargement des modules sont termins, lcouteur attach par dfaut au sein du gestionnaire de modules sur le mme vnement est ensuite notifi, ce qui lui permet de lancer le chargement des modules: Zend/ModuleManager/ModuleManager.php
protected function attachDefaultListeners() { $events = $this->getEventManager(); $events->attach(ModuleEvent::EVENT_LOAD_MODULES, array($this, 'onLoadModules')); }

Zend/ModuleManager/ModuleManager.php
public function onLoadModules() { if (true === $this->modulesAreLoaded) { return $this; } foreach ($this->getModules() as $moduleName) { $this->loadModule($moduleName); } $this->modulesAreLoaded = true; }

Cest la mthode loadModule() qui soccupe du chargement, nous dtaillerons cette mthode plus loin: Zend/ModuleManager/ModuleManager.php
public function loadModule($moduleName) { if (isset($this->loadedModules[$moduleName])) { return $this->loadedModules[$moduleName]; } event = ($this->loadFinished === false) ? clone $this->getEvent() : $ $this->getEvent(); $event->setModuleName($moduleName); $this->loadFinished = false; $result = $this->getEventManager()->trigger(ModuleEvent::EVENT_LOAD_ MODULE_RESOLVE, $this, $event, function ($r) { return (is_object($r)); }); [] $event->setModule($module); $this->getEventManager()->trigger(ModuleEvent::EVENT_LOAD_MODULE, $this, $event); $this->loadedModules[$moduleName] = $module; this->loadFinished = true; $ return $module; }

Avant de continuer et de voir le chargement des modules et les vnements qui lui sont consacrs, notons que deux autres couteurs sont attachs sur lvnement loadModules avec des priorits trs faibles, ils seront donc notifis aprs le chargement des modules, et nous verrons en dtail leur fonctionnement dans la

92

Les modules Chapitre 6

section concernant le postchargement: la mthode onLoadModulesPost() de lobjet ConfigListener est attache avec une priorit de -1000 et charge de la fusion des configurations avec la mise en cache; la mthode onLoadModulesPost() de lobjet LocatorRegistrationListener qui permet denregistrer en tant que service le gestionnaire de modules et ses diffrents modules. Revenons la mthode loadModule() o la mthode contrle, dans un premier temps, si le module na pas dj t charg. Cela, afin de ne pas faire ce traitement plusieurs fois, et lance immdiatement lvnement loadModule.resolve, reprsent par la constante ModuleEvent::EVENT_LOAD_MODULE_RESOLVE, afin de notifier les couteurs capables de rsoudre le chargement du module. Le conteneur dcouteurs possde un objet spcialis dans la rsolution de module: Zend/ModuleManager/Listener/DefaultListenerAggregate.php
public function attach(EventManagerInterface $events) { [] $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE_ RESOLVE, new ModuleResolverListener); [] }

Lobjet Zend\ModuleManager\Listener\ModuleResolverListener est le composant responsable du chargement des classes de modules, il ne possde quune seule mthode: Zend/ModuleManager/Listener/ModuleResolverListener.php
public function __invoke($e) { $moduleName = $e->getModuleName(); $class = $moduleName . '\Module'; f (!class_exists($class)) { i return false; } $module = new $class; return $module; }

La mthode __invoke() a t introduite en PHP 5.3, elle est appele lorsque lon tente dappeler un objet comme une fonction, ce qui est le cas ici en affectant linstance de lobjet de type ModuleResolverListener comme fonction de callback. La fonction __invoke recherche alors une classe qui possde le nom Module associ lespace de noms du module, cela explique pourquoi nous devons nommer nos classes dinitialisation de module Module. La fonction class_exists() va ensuite sappuyer sur la classe de chargement ModuleAutoloader, spcialise dans la localisation de classes de module, afin de tester

Les modules Chapitre 6

93

son existence. La mthode autoload() de lautoloader ModuleAutoloader est alors appele: Zend/Loader/ModuleAutoloader.php
public function autoload($class) { if (substr($class, -7) !== '\Module') { return false; } $moduleName = substr($class, 0, -7); if (isset($this->explicitPaths[$moduleName])) { $classLoaded = $this->loadModuleFromDir($this>explicitPaths[$moduleName], $class); [] } moduleClassPath $ $moduleName); [] = str_replace('\\', DIRECTORY_SEPARATOR,

oreach ($this->paths as $path) { f $path = $path . $moduleClassPath; $classLoaded = $this->loadModuleFromDir($path, $class); if ($classLoaded) { return $classLoaded; } if ($pharSuffixPattern) { [] } } return false; }

Lautoloader recherche la classe MonModule/Module.php dans les chemins de lattribut $explicitPaths, qui contient les chemins que lon a associs nos modules dans notre fichier de configuration. Pour rappel, le fichier de configuration contient les chemins suivants: application.config.php
return array( [] 'module_paths' => array( './module', './vendor', ), );

Cependant, comme nous navons pas associ le nom du module chacun des chemins, le chargement ne va pas pouvoir se faire depuis un chemin statique, il va devoir tre effectu depuis un rpertoire. Analysons lenregistrement des chemins de nos modules:

94

Les modules Chapitre 6

Zend/Loader/ModuleAutoloader.php
public function registerPath($path, $moduleName = false) { if (!is_string($path)) { [] } if ($moduleName) { $this->explicitPaths[$moduleName] = static::normalizePath($path); } else { $this->paths[] = static::normalizePath($path); } return $this; }

Nous remarquons que la classe ne fait la liaison entre nom de module et chemin, que si nous indiquons en cl le nom du module. Nous pouvons donc modifier notre fichier de configuration afin de bnficier dun chargement plus rapide: application.config.php
<?php return array( [] 'module_paths' => array( Application => './module/Application', ), [] );

Lautoloader naura alors aucun traitement supplmentaire effectuer pour localiser les diffrents modules. Dans le cas contraire, chacun des dossiers indiqus dans la configuration est parcouru afin de tenter le chargement du module depuis le nom de ce rpertoire, suivi du nom de notre module: Zend/Loader/ModuleAutoloader.php
public function autoload($class) { [] foreach ($this->paths as $path) { $path = $path . $moduleClassPath; $classLoaded = $this->loadModuleFromDir($path, $class); [] }

Ensuite, lorsque la localisation du rpertoire correspondant au module est faite, soit par lintermdiaire dun chemin statique soit depuis le parcours de la liste de dossiers, lautoloader fait appel la mthode loadModuleFromDirectory() afin de procder son chargement: Zend/Loader/ModuleAutoloader.php
protected function loadModuleFromDir($dirPath, $class) { $file = new SplFileInfo($dirPath . '/Module.php');

Les modules Chapitre 6


if ($file->isReadable() && $file->isFile()) { require_once $file->getRealPath(); if (class_exists($class)) { $this->moduleClassMap[$class] = $file->getRealPath(); return $class; } } return false; }

95

La mthode vrifie que la classe MonModule\Module est bien prsente dans le fichier nomm Module.php du dossier. Nous comprenons aussi pourquoi nous devons nommer la classe du module Module.php. Une fois la localisation de la classe russie, lobjet ModuleResolverListener peut alors instancier le module: Zend/ModuleManager/Listener/ModuleResolverListener.php
public function __invoke($e) { [] $module = new $class; return $module; }

Nous venons dexpliquer le processus de chargement li lvnement loadModule.resolve o lcouteur ModuleResolverListener retourne une instance du module. tant le seul couteur de rsolution de nom de module, le retour de lcouteur satisfait alors la condition de court-circuitage qui sattend au retour dun objet: Zend/ModuleManager/ModuleManager.php
public function loadModule($moduleName) { [] $result = $this->getEventManager()->trigger(ModuleEvent::EVENT_LOAD_ MODULE_RESOLVE, $this, $event, function ($r) { return (is_object($r)); }); $module = $result->last(); [] }

Maintenant que la classe du module est effectivement instancie, revenons la mthode de chargement de module du gestionnaire de modules: Zend/ModuleManager/ModuleManager.php
public function loadModule($moduleName) { [] $result = $this->getEventManager()->trigger(ModuleEvent::EVENT_LOAD_ MODULE_RESOLVE,$this, $event, function ($r) { return (is_object($r)); });

96

Les modules Chapitre 6


$module = $result->last(); if (!is_object($module)) { [] } $event->setModule($module); $this->getEventManager()->trigger(ModuleEvent::EVENT_LOAD_MODULE, $this, $event); $this->loadedModules[$moduleName] = $module; $this->loadFinished = true; return $module; }

Lvnement loadModule, reprsent par la constante ModuleEvent::EVENT_ LOAD_MODULE, correspondant au chargement du module, est lanc avant denregistrer le module au sein du tableau dinstances de modules charges. la suite de cet vnement, les couteurs suivants vont tre notifis: la classe Zend\ModuleManager\Listener\AutoloaderListener, qui comme la classe ModuleResolverListener sert de callback, est notifie depuis sa mthode __invoke. Cet couteur permet linitialisation de la configuration des autoloaders du module depuis la mthode getAutoloaderConfi() du fichier Module.php et sera le premier couteur notifi; un objet de type Zend\ModuleManager\Listener\InitTrigger qui lui aussi est pass comme fonction de callback. Lcouteur permet linitialisation du module depuis la mthode init() et est attach lvnement sans priorit; un objet de type Zend\ModuleManager\Listener\OnBootstrapListener qui lui aussi est pass comme fonction de callback. Lcouteur permet la notification du module lors du processus de bootstrap si celui-ci implmente linterface BootstrapListenerInterface ou la mthode onBootstrap(); la classe Zend\ModuleManager\Listener\ServiceListener et sa mthode onLoadModule permettent linitialisation et lenrichissement de la configuration des gestionnaires de services de lapplication. Elle est attache sans priorit; un objet de type Zend\ModuleManager\Listener\LocatorRegistrationListener permet le partage dinstances des modules et est attach lvnement sans priorit; lobjet de type Zend\ModuleManager\Listener\ConfigListener et sa mthode loadModule permettent la mise jour de la configuration des modules depuis leur mthode getConfig(). Elle est attache sans priorit. Afin de comprendre toutes les tapes dinitialisation du module, analysons chacun des couteurs.

Initialisation de lautoloader du module


Le premier couteur notifi permet dajouter lapplication, les autoloaders propres au chargement des classes lies au module courant. Afin denrichir la configuration du chargement de classe, lcouteur de type AutoloaderListener est notifi depuis lappel de la mthode getAutoloaderConfig() du module, si celui implmente

Les modules Chapitre 6

97

linterface AutoloaderProvider qui dfinit cette seule mthode: Zend/ModuleManager/Feature/AutoloaderProviderInterface.php


interface AutoloaderProviderInterface { public function getAutoloaderConfig(); }

Zend/ModuleManager/Listener/AutoloaderListener.php
class AutoloaderListener extends AbstractListener { public function __invoke(ModuleEvent $e) { $module = $e->getModule(); if (!$module instanceof AutoloaderProviderInterface && !method_exists($module, 'getAutoloaderConfig') ) { $autoloaderConfig = $module->getAutoloaderConfig(); AutoloaderFactory::factory($autoloaderConfig); } }

Dans notre exemple, voici le contenu de cette mthode au sein de notre module Application: Application/Module.php
public function getAutoloaderConfig() { return array( 'Zend\Loader\ClassMapAutoloader' => array( __DIR__ . '/ autoload_classmap.php'), 'Zend\Loader\StandardAutoloader' => array('namespaces' => array( __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__, ), ), ); }

La mthode getAutoloaderConfig() du module Application retourne un premier autoloader bas sur un fichier autoload_classmap.php, ainsi quun autoloader bas sur un espace de noms propre au module. Pour des raisons de performances, il est bien sr prfrable dutiliser au maximum le ClassMapAutoloader.

Initialisation du module
Lcouteur suivant est responsable de linitialisation du module. Lobjet de type InitTrigger, qui sert de fonction de callback sur lvnement loadModule, initialise le module en appelant la mthode init() du module si elle existe:

98

Les modules Chapitre 6

Zend/ModuleManager/Listener/InitTrigger.php
public function __invoke(ModuleEvent $e) { $module = $e->getModule(); if (!$module instanceof InitProviderInterface && !method_exists($module, 'init') ) { return; } $module->init($e->getTarget()); }

Notification lors de linitialisation de lapplication


Lcouteur de type OnBootstrapListener permet de faciliter la vie du dveloppeur en ajoutant un couteur sur lvnement de bootstrap afin de notifier les plugins de linitialisation de lapplication. La classe OnBootstrapListener attache un couteur sur la mthode onBootstrap() du module si celui-ci implmente cette mthode ou linterface BootstrapListenerInterface qui la dfinit: Zend/ModuleManager/Feature/BootstrapListenerInterface.php
interface BootstrapListenerInterface { public function onBootstrap(EventInterface $e); }

La classe OnBootstrapListener utilise la possibilit du partage de gestionnaires dvnements afin dattacher lcouteur: Zend/ModuleManager/Listener/OnBootstrapListener.php
class OnBootstrapListener extends AbstractListener { public function __invoke(ModuleEvent $e) { $module = $e->getModule(); if (!$module instanceof BootstrapListenerInterface && !method_exists($module, 'onBootstrap') ) { return; } $moduleManager = $e->getTarget(); $events = $moduleManager->getEventManager(); $sharedEvents = $events->getSharedManager(); $sharedEvents->attach('Zend\Mvc\Application', 'bootstrap', array($module, 'onBootstrap')); } }

Notre module possde alors un couteur fonctionnel si celui-ci implmente linterface BootstrapListenerInterface.

Les modules Chapitre 6

99

Configuration du gestionnaire de services


Lcouteur suivant a pour responsabilit denrichir la configuration du gestionnaire de services. La mthode onLoadModule() de la classe ServiceListener enrichit la configuration du gestionnaire de services de lapplication laide des mthodes et cls de configuration que lon a enregistres prcdemment: Zend/Mvc/Service/ModuleManagerFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { [] $serviceListener->addServiceManager( $serviceLocator, 'service_manager', 'Zend\ModuleManager\Feature\ServiceProviderInterface', 'getServiceConfig' ); $serviceListener->addServiceManager( 'ControllerLoader', 'controllers', 'Zend\ModuleManager\Feature\ControllerProviderInterface', 'getControllerConfig' ); $serviceListener->addServiceManager( 'ControllerPluginManager', 'controller_plugins', 'Zend\ModuleManager\Feature\ControllerPluginProviderInterface', 'getControllerPluginConfig' ); $serviceListener->addServiceManager( 'ViewHelperManager', 'view_helpers', 'Zend\ModuleManager\Feature\ViewHelperProviderInterface', 'getViewHelperConfig' ); [] }

Lcouteur va parcourir lensemble des cls et vrifier la liste des mthodes enregistres afin de pouvoir rcuprer la configuration propre chacune de ses entres. Chaque entre reprsente un type de configuration spcifique: la cl service_manager permet de dfinir ses propres fabriques dans la configuration. La mthode getServiceConfig() o linterface Zend\ModuleManager\ Feature\ServiceProviderInterface devra tre implmente si lon souhaite dfinir les fabriques depuis son module; la cl controllers permet de dfinir ses fabriques de contrleurs dans la configuration. La mthode getControllerConfig() o linterface Zend\ModuleManager\Feature\ControllerProviderInterface devra tre implmente si lon souhaite dfinir les fabriques depuis son module; la cl controller_plugins permet de dfinir ses propres fabriques daides dactions dans la configuration. La mthode getControllerPluginConfig() o linterface Zend\ModuleManager\Feature\ControllerPluginProviderInterface devra tre implmente si lon souhaite dfinir les fabriques depuis son module;

100

Les modules Chapitre 6

la cl view_helpers permet de dfinir ses propres fabriques daides de vue dans la configuration. La mthode getViewHelperConfig() o linterface Zend\ ModuleManager\Feature\ViewHelperProviderInterface devra tre implmente si lon souhaite dfinir les fabriques depuis son module. Nous comprenons donc maintenant pourquoi ces cls sont utilises dans les configurations pour dfinir nos propres fabriques et plugins. Voici limplmentation de la mthode couteur onLoadModule() qui se charge de rcuprer les configurations correspondantes: Zend/ModuleManager/Listener/ServiceListener.php
public function onLoadModule(ModuleEvent $e) { $module = $e->getModule(); foreach ($this->serviceManagers as $key => $sm) { if (!$module instanceof $sm['module_class_interface'] && !method_exists($module, $sm['module_class_method']) ) { continue; } $config = $module->{$sm['module_class_method']}(); [] $fullname = $e->getModuleName() . '::' . $sm['module_class_ method'] . '()'; $this->serviceManagers[$key]['configuration'][$fullname] = $config; } }

Chaque entre enregistre est parcourue afin de tester lexistence de la mthode ou de limplmentation de linterface correspondante pour sassurer de pouvoir rcuprer la configuration correspondante. Celle-ci est ensuite enregistre pour un traitement ultrieur o elle sera fusionne. Chaque module a la possibilit de surcharger la configuration du gestionnaire de services de lapplication en passant des contrats avec les interfaces dcrites plus haut. Cette possibilit laisse plus de libert aux dveloppeurs. Cependant, la possibilit dextension ne sarrte pas l, il est galement possible de dfinir ses propres triplets de cls/interfaces/mthodes depuis la configuration de lapplication. Reprenons le code de la fabrique de la classe: Zend/Mvc/Service/ServiceListenerFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { $configuration = $serviceLocator->get('ApplicationConfig'); [] if (isset($configuration['service_listener_options'])) { [] foreach ($configuration['service_listener_options'] as $key => $newServiceManager) { if (!isset($newServiceManager['service_manager'])) { [] }

Les modules Chapitre 6


if (!isset($newServiceManager['config_key'])) { [] } if (!isset($newServiceManager['interface'])) { [] } if (!isset($newServiceManager['method'])) { [] } $serviceListener->addServiceManager( $newServiceManager['service_manager'], $newServiceManager['config_key'], $newServiceManager['interface'], $newServiceManager['method'] ); } } return $serviceListener; }

101

La cl service_listener_options du tableau de configuration de lapplication est parcourue et chaque tableau de cette cl est vrifi avant dtre enregistr dans lcouteur. Une fois celui-ci enregistr, lcouteur adoptera le mme comportement pour nos cls personnalises que pour celles utilises par le framework. Dans la configuration fournir, la cl service_manager reprsente linstance du gestionnaire de services qui devra automatiquement tre configur par les fabriques que lon dfinit. Si nous analysons les valeurs par dfaut du framework, nous comprenons quil est ncessaire de passer comme paramtre, soit une instance dun gestionnaire, soit la cl sous laquelle il est enregistr par le gestionnaire de services: Zend/Mvc/Service/ModuleManagerFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { [] $serviceListener->addServiceManager( $serviceLocator, [] ); $serviceListener->addServiceManager( 'ControllerLoader', [] ); $serviceListener->addServiceManager( 'ControllerPluginManager', [] ); $serviceListener->addServiceManager( 'ViewHelperManager', [] ); [] }

Chaque cl reprsente un objet qui sera rcupr par le gestionnaire avant dtre

102

Les modules Chapitre 6

configur. Cet objet doit tendre la classe ServiceManager. Voici les instructions effectues par le framework lors des instructions de postchargement que nous verrons plus loin:

Zend/ModuleManager/Listener/ServiceListener.php
public function onLoadModulesPost(ModuleEvent $e) { [] foreach ($this->serviceManagers as $key => $sm) { [] if (!$sm['service_manager'] instanceof ServiceManager) { $instance = $this->defaultServiceManager>get($sm['service_manager']); [] } $serviceConfig = new ServiceConfig($smConfig); $serviceConfig->configureServiceManager($sm['service_manager']); } }

Nous remarquons que la cl service_manager est utilise pour rcuprer linstance de lobjet tendant de ServiceManager qui sera alors configur.

Initialisation des instances partages


Lcouteur suivant, de type LocatorRegistrationListener, qui implmente linterface LocatorRegistered est charg denregistrer les modules comme instance partage: Zend/ModuleManager/Listener/LocatorRegistrationListener.php
public function loadModule(ModuleEvent $e) { if (!$e->getModule() instanceof LocatorRegistered) { return; } $this->modules[] = $e->getModule(); }

Ceci lui permettra par la suite dajouter au gestionnaire dinstances nos diffrents objets de modules afin de permettre un accs aux modules depuis lapplication.

Mise jour de la configuration des modules


Le dernier couteur intervenir est lobjet ConfigListener qui se charge de vrifier limplmentation de la mthode getConfig() ou linterface ConfigProviderInterface par le module, afin de rcuprer la configuration propre au module et ainsi la fusionner avec les autres:

Les modules Chapitre 6

103

Zend/ModuleManager/Listener/ConfigListener.php
public function onLoadModule(ModuleEvent $e) { $module = $e->getModule(); if (!$module instanceof ConfigProviderInterface && !is_callable(array($module, 'getConfig')) ) { return $this; } $config = $module->getConfig(); $this->addConfig($e->getModuleName(), $config); return $this; }

Notons que cet couteur nest attach que si le cache interne de configuration des modules nest pas activ, car dans le cas contraire la configuration est rcupre depuis le cache existant: Zend/ModuleManager/Listener/ConfigListener.php
public function attach(EventManagerInterface $events) { $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULES, array($this, 'onloadModulesPre'), 1000); f ($this->skipConfig) { i return $this; } $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, array($this, 'onLoadModule')); this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULES, $ array($this, 'onLoadModulesPost'), -1000); return $this; }

Comme vu prcdemment, il est possible dactiver ce cache depuis la configuration de lapplication. Il est assez intressant dactiver le cache, car celui-ci se met jour une fois les configurations fusionnes, ce qui lui vite ensuite de devoir le faire nouveau lors dune prochaine requte par le client. La mthode updateCache() a pour responsabilit de mettre le fichier de cache jour: Zend/ModuleManager/Listener/ConfigListener.php
protected function updateCache() { if (($this->getOptions()->getConfigCacheEnabled()) && (false === $this->skipConfig) ) { $configFile = $this->getOptions()->getConfigCacheFile(); $this->writeArrayToFile($configFile, $this>getMergedConfig(false)); } return $this; }

La mthode utilise pour la mise en cache sous forme de fichier est la mthode

104

Les modules Chapitre 6

writeArrayToFile(): Zend/ModuleManager/Listener/AbstractListener.php
protected function writeArrayToFile($filePath, $array) { $content = "<?php\nreturn " . var_export($array, 1) . ';'; file_put_contents($filePath, $content); return $this; }

La mthode utilise pour la mise en cache sous forme de fichier est la mthode writeArrayToFile() : Zend/ModuleManager/Listener/ConfigListener.php
protected function writeArrayToFile($filePath, $array) { $content = "<?php\nreturn " . var_export($array, 1) . ';'; file_put_contents($filePath, $content); return $this; }

La mthode se contente dcrire dans le fichier de cache une instruction PHP qui retourne un tableau associatif. Le fichier na ensuite plus qu tre inclus dans le code PHP depuis linstruction include afin davoir un accs aux donnes du tableau. Nous retrouvons ici le mme fonctionnement que pour les fichiers de configuration que lon utilise pour lapplication ou celui des modules.

Traitement postchargement
Les modules de lapplication sont maintenant chargs et il reste, comme vu plus haut, deux couteurs notifier sur lvnement loadModules. Lcouteur de type ConfigListener qui met jour la configuration avec lajout de fichiers de configuration supplmentaires. Ces chemins sont indiqus dans la configuration de lapplication: application.config.php
return array( [] module_listener_options' => array( 'config_glob_paths' => array( 'config/autoload/{,*.}{global,local}.php', ), [] );

Ces fichiers sont enregistrs dans le constructeur de lobjet:

Les modules Chapitre 6

105

Zend/ModuleManager/Listener/ConfigListener.php
public function __construct(ListenerOptions $options = null) { [] } else { $this->addConfigGlobPaths($this->getOptions()>getConfigGlobPaths()); $this->addConfigStaticPaths($this->getOptions()>getConfigStaticPaths()); } }

Chaque chemin, statique ou non, est alors enregistr dans la variable $paths. La mthode utilise par la classe ConfigListener afin dajouter des fichiers de configuration globale est la mthode addConfigGlobPath() , base sur la fonction glob() de PHP. Cette fonction recherche tous les chemins vrifiant le pattern donn en paramtre. Cependant, si des chemins statiques suffisent notre application, il est possible dutiliser les mthodes de chargement bases sur des chemins statiques: Zend/ModuleManager/Listener/ConfigListener.php
public function addConfigStaticPaths($staticPaths); public function addConfigStaticPath($staticPath);

Si un ou plusieurs fichiers sont charger pour lenvironnement en cours, ces mthodes seront plus adaptes afin de profiter pleinement des performances que lon peut tirer du Zend Framework: Utilisation de chemins statiques
return array( [] module_listener_options' => array( 'config_static_paths' => array( "config/autoload/global.config.php", ), [] );

Ces mthodes permettent de venir surcharger la configuration de lapplication, initialise depuis celles de nos modules. Comme nous venons de le voir, lajout de fichiers de configuration se fait aprs le chargement des modules, lors de la notification de la fin de chargement. Dans lapplication, les fichiers de configuration globale et locale que lon utilise se situent dans le rpertoire /config/autoload. Revenons au chargement des modules et lcouteur notifi de la fin du chargement des modules qui est la mthode loadModules() de lobjet ConfigListener:

106

Les modules Chapitre 6

Zend/ModuleManager/Listener/ConfigListener.php
public function onLoadModulesPost(ModuleEvent $e) { foreach ($this->paths as $path) { $this->addConfigByPath($path['path'], $path['type']); } $this->mergedConfig = $this->getOptions()->getExtraConfig() ?: array(); foreach ($this->configs as $key => $config) { $this->mergedConfig = ArrayUtils::merge($this->mergedConfig, $config); } f ($this->getOptions()->getConfigCacheEnabled()) { i $this->updateCache(); } return $this; }

Les fichiers de configuration sont lists afin denregistrer leur configuration. Une fois toutes les configurations rcupres, le tableau de configuration finale est initialis par le tableau de configuration exra_config que lon peut dfinir dans la configuration de lapplication: application.config.php
return array( [] 'module_listener_options' => array( [] 'extra_config' => array( 'view_manager' => array( 'display_not_found_reason' => false, 'display_exceptions' => false, ), ), [] );

La configuration extra_config tant enregistre en premire, cela lui permet de dfinir une configuration par dfaut qui pourra alors tre surcharge par les modules si besoin. Chaque configuration des modules et configuration globale est ensuite fusionne avec la configuration actuelle avant dtre cache si le cache interne est activ. Il est important de comprendre lordre de la fusion des configurations si lon ne souhaite pas voir des valeurs crases par dautres fichiers de configuration. Voici le rcapitulatif de lordre: configuration enregistre sous la cl extra_config, configuration des modules dans leur ordre de chargement et enfin les configurations enregistres sous les cls config_glob_paths ou config_static_paths. Le deuxime couteur est la classe LocatorRegistrationListener, qui attache deux couteurs sur lvnement bootstrap:

Les modules Chapitre 6

107

Zend/ModuleManager/Listener/LocatorRegistrationListener.php
public function loadModulesPost(Event $e) { $moduleManager = $e->getTarget(); $events = $moduleManager->getEventManager()>getSharedManager(); events->attach('Zend\Mvc\Application', 'bootstrap', function ($e) $ use ($moduleManager) { $moduleClassName = get_class($moduleManager); $application = $e->getApplication(); $services = $application->getServiceManager(); if (!$services->has($moduleClassName)) { $services->setService($moduleClassName, $moduleManager); } }, 1000); if (0 === count($this->modules)) { return; } events->attach('Zend\Mvc\Application', 'bootstrap', array($this, $ 'onBootstrap'), 1000); }

Zend/ModuleManager/Listener/LocatorRegistrationListener.php
public function onBootstrap(Event $e) { $application = $e->getApplication(); $services = $application->getServiceManager(); oreach ($this->modules as $module) { f $moduleClassName = get_class($module); if (!$services->has($moduleClassName)) { $services->setService($moduleClassName, $module); } } }

Les deux couteurs de la mthode bootstrap permettent lenregistrement du gestionnaire de modules ainsi que lenregistrement des diffrents modules qui souhaitent tre partags, modules que lobjet avait prcdemment enregistrs. Une fois linitialisation de nos modules russie, la mthode de chargement notifie la fin du traitement avec lvnement loadModules.post, reprsent par la constante ModuleEvent::EVENT_LOAD_MODULES_POST, qui ne comporte quun seul couteur avec lobjet ServiceListener: Zend/ModuleManager/ModuleManager.php
public function loadModules() { [] $this->getEventManager()->trigger(ModuleEvent::EVENT_LOAD_MODULES_ POST, $this, $this->getEvent()); return $this; }

108

Les modules Chapitre 6

Zend/ModuleManager/Listener/ServiceListener.php
public function attach(EventManagerInterface $events) { [] $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULES_ POST, array($this, 'onLoadModulesPost')); return $this; }

Zend/ModuleManager/Listener/ServiceListener.php
public function onLoadModulesPost(ModuleEvent $e) { $configListener = $e->getConfigListener(); $config = $configListener->getMergedConfig(false); oreach ($this->serviceManagers as $key => $sm) { f [] } }

La liste des cls et mthodes pour lenrichissement du gestionnaire de services est parcourue et filtre afin de mettre jour la configuration. Nous ne dtaillerons pas le contenu de la mise jour qui est assez complexe dcrypter avec laccession de tableau de tableaux. Retenons que cest cet couteur qui a la responsabilit de filtrer la configuration que nous ajoutons depuis les cls ou mthodes spciales destines lenrichissement du gestionnaire de services. Vous retrouverez en dtail cette liste dans la section de ce chapitre consacre lcouteur li la configuration du gestionnaire de services. Nous avons parcouru le chemin effectu par le framework lors du chargement des modules. Nous avons pu voir que chacun des couteurs est responsable dune tche prcise, ce qui permet de dcoupler et de rendre plus flexible le chargement des modules. Les configurations et le chargement des modules sont assez simples prendre en main et la gestion du cache interne permet daugmenter sensiblement les performances en permettant de ne pas excuter chaque fois la compilation des configurations.

Rcapitulatif du processus
Processus de chargement
Voici un schma rcapitulatif des diffrentes tapes et vnements du chargement de nos modules.

Les modules Chapitre 6

109

Squelette de notre classe Module


Voici un squelette de module type qui comprend lajout de fichier de configuration pour lautoloader, les contrleurs, le gestionnaire de services, les aides de vue et daction, linitialisation du module ainsi que le partage de linstance du module au sein du gestionnaire dinstances: Squelette de module
class Module implements InitProviderInterface, AutoloaderProvider, LocatorRegisteredInterface, ControllerPluginProviderInterface, ControllerProviderInterface, ViewHelperProviderInterface, ServiceProviderInterface, BootstrapListenerInterface, ConfigProviderInterface { public function init(ModuleManagerInterface $manager) { // initialisation du module } public function onBootstrap(EventInterface $e) { // initialisation au bootstrap } public function getAutoloaderConfig() { return array( // dfinitions de nos autoloaders ici

110

Les modules Chapitre 6


); } public function getControllerConfig() { return array( // dfinitions des contrleurs ici ); } public function getControllerPluginConfig() { return array( // on dfinit nos aides dactions ici ); } public function getViewHelperConfig() { return array( // on dfinit nos aides de vue ici ); } public function getServiceConfig() { return array( // on dfinit nos services ici ); } public function getConfig() { // on inclut le fichier de configuration du module } }

Dans cet exemple, toutes les interfaces sont implmentes afin de montrer toutes les possibilits, mais chacun implmentera les interfaces dont il a besoin. Il est conseill de faire implmenter la classe de module par les interfaces concernes mme si, comme nous lavons vu plus haut, il nest pas obligatoire de le faire. Linterface permet de dfinir un contrat et dindiquer explicitement ce que le module peut fournir comme donnes. Cela permet de s'assurer que les contrats sont respects et que lapplication reoit les donnes quelle demande.

Les configurations

Le systme de configuration du Zend Framework a bien volu. Habitu aux fichiers INI ou XML lors de la premire version, il est maintenant possible dutiliser une configuration peut-tre un peu moins lisible au premier abord, mais beaucoup plus performante, le fichier PHP.

Avec le Zend Framework 1


Lorsque lon utilise un fichier de configuration INI ou XML pour la configuration gnrale de lapplication, la classe Zend_Application se charge de le convertir en tableau pour le traiter. Au final, Zend Framework 2 propose une mthode pour supprimer ltape de la conversion du fichier INI ou XML qui pouvait savrer lourde.

Afin de maitriser les configurations de lapplication, nous allons analyser comment la rcuprer depuis un module ou contrleur avant de voir les possibilits de sparation suivant lenvironnement de travail du dveloppeur, ainsi que les formats qui soffrent nous.

Accs la configuration
Nous avons vu, dans le chapitre concernant les modules, que la configuration est cre partir de la fusion de celles des modules, surcharge de fichiers de configurations plus globales. Nous pourrions avoir besoin de rcuprer l'objet de configuration depuis notre application, ce qui peut tre fait facilement depuis nos classes de modules : Rcupration de la configuration
class Module implements BootstrapListenerInterface { public function onBootstrap(EventInterface $e) { $config = $e->getApplication()->getConfig(); } }

112

Les configurations Chapitre 7

Afin de rcuprer la configuration depuis le module, nous faisons appel la mthode getApplication() de lvnement de type MvcEvent qui reprsente lobjet Application, instance qui dispose dune mthode de rcupration de configuration: Zend/Mvc/Application.php
public function getConfig() { return $this->serviceManager->get('Config'); }

La mthode getConfig() utilise le gestionnaire de services afin de rcuprer la configuration de lapplication, nous pourrions donc changer notre code par les instructions suivantes : Rcupration de la configuration avec le gestionnaire de services
class Module implements BootstrapListenerInterface { public function onBootstrap(EventInterface $e) { $config = $e->getApplication()->getServiceManager()>get('Config'); } }

La configuration de lapplication peut aussi tre rcupre depuis un de nos contrleurs suivant le mme principe : Rcupration de la configuration depuis un contrleur
class IndexController extends ActionController { public function indexAction() { $config = $this->getServiceLocator()->get('Config'); } }

Nous savons maintenant rcuprer la configuration depuis nos modules et contrleurs. Cependant, lorsque notre module ajoute la sienne, il peut tre ncessaire de la dfinir suivant lenvironnement de travail du dveloppeur. En effet, les accs aux bases de donnes, fichiers temporaires ou autres paramtres qui diffrent en environnement de dveloppement ou de production. Nous allons maintenant passer en revue les diffrentes manires de configurer son application en fonction de lenvironnement de travail.

Configuration par environnement


Comme on la vu, il est prfrable dutiliser des fichiers de configurations de type PHP o les tableaux permettent un traitement immdiat. Cependant, comme nous avons besoin de configurations diffrentes suivant notre environnement (dve-

Les configurations Chapitre 7

113

loppement, production, prproduction, etc.), nous analyserons quatre manires de grer facilement ses diffrentes configurations. Afin de continuer cette section, nous partons du principe que lenvironnement en cours est dfini depuis notre fichier htaccess ou de vhosts avec la valeur development pour la variable APPLICATION_ENV.

Configuration par fichiers PHP


Rappelons, dans un premier temps, les principales tapes du chargement de la configuration. Chaque module est initialis, et chaque configuration du module, donne par la mthode getConfig() de la classe Module, est fusionne la configuration prcdente avec la fonction PHP array_replace_recursive(), qui ajoute rcursivement toutes les entres des tableaux au tableau existant. Si une cl est dj prsente pour un tableau ou sous-tableau existant, celle-ci est alors remplace par la nouvelle valeur. Ensuite, lorsque le Zend Framework a fusionn les configurations des modules, le rsultat est surcharg par une configuration globale que lon ajoute lors de linitialisation du gestionnaire de modules travers les mthodes addConfigGlobPath() ou addConfigStaticPath() du gestionnaire de configurations : application.config.php
<?php return array( [] 'module_listener_options' => array( 'config_glob_paths' => array( 'config/autoload/{,*.}{global,local}.php', ), 'config_static_paths' => array( 'config/autoload/config.override.php', ), [] );

Nous pouvons donc agir lors du chargement de la configuration des modules ou lors de la configuration globale. Pour cela, nous allons crer un fichier pour chaque environnement en indiquant les paramtres de chacun. Dans notre exemple, pour le chargement des modules nous crons deux fichiers dans le dossier de configuration du module : config/development.module.config.php; config/production.module.config.php. Il suffit ensuite de changer la mthode getConfig() de notre classe Module :

114

Les configurations Chapitre 7

Application/Module.php
public function getConfig() { return include __DIR__ . '/config/'. APPLICATION_ENV.'.module.config. php'; }

Nous pouvons ensuite faire de mme pour le fichier de configuration globale : config/autoload/development.application.config.php; config/autoload/production.application.config.php. Et nous modifions alors la surcharge de configuration globale : application.config.php
<?php return array( [] 'module_listener_options' => array( 'config_static_paths' => array( config/autoload/ . APPLICATION_ENV . .application.config.php, ), [] );

Ceci nous permet de surcharger la configuration de lapplication depuis un fichier spcifique li lenvironnement en cours. Cest une premire manire de surcharger les configurations en fonction de notre environnement. Cependant, il est parfois prfrable de garder un hritage dans les configurations, comme ce que lon avait dans la premire version du framework, afin de garder une certaine cohrence et ne pas se retrouver avec des configurations dupliques ou les effets de bord peuvent exister.

Configuration par hritage de fichier


Afin de conserver un systme dhritage de configuration, nous crons un fichier de base qui dfinira toutes les configurations dont on a besoin, et chaque fichier spcifique un environnement peut dfinir uniquement ses propres valeurs. Dans notre exemple, lors du chargement de la configuration du module Application, nous avons deux fichiers dans notre dossier : config/module.config.php; config/production.module.config.php. Le fichier de configuration module.config.php dfinit toutes les configurations de base, et le fichier production.module.config redfinit uniquement les valeurs propres cet environnement. Prenons un exemple basique avec le gestionnaire de vues et laffichage des erreurs :

Les configurations Chapitre 7

115

config/module.config.php
<?php return array( [] 'view_manager' => array( 'display_not_found_reason' => true, 'display_exceptions' => true, [] );

config/production.module.config.php
<?php return array( [] 'view_manager' => array( 'display_not_found_reason' => false, 'display_exceptions' => false, [] );

Dans cet exemple, nous redfinissons les cls display_not_found_reason et display_exceptions du gestionnaire de vues. Si lexemple est trivial, dans la ralit de nombreuses cls seront redfinies dans les diffrents environnements, comme laccs la base de donnes, la gestion des caches, etc. Voici les instructions pour la gestion des diffrents environnements dans la classe de Module : Application/Module.php
public function getConfig() { return array_replace_recursive( include __DIR__ . '/config/module.config.php', include __DIR__ . '/config/' . APPLICATION_ENV . '.module.config. php' ); }

Nous utilisons ici la mme mthode que lors de la fusion des configurations des modules, la fonction PHP array_replace_recursive() qui permet de remplacer rcursivement dans les sous-tableaux les valeurs redfinies dans les autres tableaux. Nous avons maintenant laffichage des erreurs suivant lenvironnement de travail en cours. Nous savons maintenant grer des configurations denvironnements diffrents tout en conservant un hritage entre les fichiers. Une autre solution soffre nous et ceux qui prfrent la gestion des configurations depuis un fichier INI, ce qui peut savrer parfois un peu plus lisible.

116

Les configurations Chapitre 7

Configuration par fichier INI


Il est aussi possible de grer ses configurations depuis des fichiers INI afin de rendre la configuration un peu plus lisible. Voici un exemple de ce que peut donner un fichier de configuration de ce format, lexemple prend le fichier de configuration du module Application : config/config.module.ini
[router] routes.home.type = "Zend\Mvc\Router\Http\Literal" routes.home.options.route = "/" routes.home.options.defaults.controller = "index" routes.home.options.defaults.action = "index" [controller] classes.index = "Application\Controller\IndexController" [view_manager] display_not_found_reason = true display_exceptions = true []

Ce fichier INI a t cr avec la classe Zend\Config\Writer\Ini : Tranformation dune configuration en fichier INI
$config = new \Zend\Config\Writer\Ini(); $config->toFile( _ _DIR__ . '/config/module.config.ini', include __DIR__ . '/config/module.config.php' );

Cela nous permet davoir un aperu de la syntaxe utiliser pour le fichier INI. Voici comment injecter notre fichier de configuration dans notre module : Application/Module.php
public function getConfig() { $config = new \Zend\Config\Reader\Ini(); return $config->fromFile(__DIR__ . '/config/module.config.ini'); }

Il est aussi possible de fusionner les fichiers INI avec les fichiers de configuration en tableau PHP : Application/Module.php
public function getConfig() { $config = new \Zend\Config\Reader\Ini(); return array_replace_recursive( $config->fromFile(__DIR__ . '/config/module.config.ini'), include __DIR__ . '/config/module.config.php' ); }

Les configurations Chapitre 7

117

Il existe plusieurs manires de grer ses configurations, chacun choisira celle qui lui conviendra le mieux mme si pour des raisons de performances il est prfrable de conserver les tableaux PHP. Notez quil est aussi possible dajouter des fichiers XML ou YAML si besoin, une classe de lecture et dcriture existent pour chacun de ces types de fichier.

Configuration recommande
Nous avons vu comment utiliser plusieurs configurations par environnement comme nous avions lhabitude lors de lutilisation du Zend Framework 1. Cependant, cette manire de fonctionner nest pas recommande par lquipe de dveloppement du framework. En effet, avoir plusieurs fichiers de configuration versionns peut poser des problmes de scurit avec les informations sensibles quils peuvent contenir, comme les mots de passe de la base de donnes ou d'API. Lquipe de dveloppement prconise de sappuyer sur des fichiers locaux qui seraient dploys sur les environnements respectifs sans tre versionns. Chacun ne contiendrait uniquement que ce qui lui permettrait de fonctionner sur cet environnement prcis et se trouverait donc dans le dossier config/autoload de lapplication qui permet dtre charg automatiquement grce aux instructions de la configuration : application.config.php
<?php return array( 'module_listener_options' => array( 'config_glob_paths' => array( 'config/autoload/{,*.}{global,local}.php', ), [] ), );

La convention tablie par le framework veut que les fichiers considrs comme locaux soient ignors du systme de gestion de versions, ce qui permet alors de ne plus partager dinformations sensibles. Lorsque ceci aura t fait, vous naurez plus qu modifier votre processus de dploiement pour venir ajouter le fichier local la configuration. Cette manire de fonctionner permet aussi de saffranchir de la manipulation de variables denvironnement. Il est ncessaire de garder lesprit que chaque valeur concernant un environnement spcifique doit se trouver dans le dossier local qui lui est consacr. Cela permet galement dobtenir une meilleure sparation de la configuration.

118

Les configurations Chapitre 7

Le router

Le router est le composant du Zend Framework qui nous permet de trouver, partir de lURL de la requte de notre client, le couple contrleur/action qui sera utilis comme action effectuer. La configuration du router permet lutilisation de nombreuses routes comme les routes statiques, avec expressions rgulires, bases sur des noms de domaines, etc.

Le router et la brique MVC


Le composant responsable du routage a subi quelques amliorations depuis la premire version du framework, et il reste toujours important de bien le maitriser afin doptimiser les performances de ses applications. En effet, beaucoup dapplications bases sur le Zend Framework ont tendance systmatiquement utiliser des routes qui font appel un moteur de correspondance complexe au lieu de simples routes statiques, plus performantes, lorsque cela est possible. Le router de lapplication est initialis dans le processus de bootstrap de lapplication o il est enregistr au sein de lvnement utilis par la classe Application : Zend/Mvc/Application.php
public function bootstrap() { [] $this->event = $event = new MvcEvent(); $event->setTarget($this); $event->setApplication($this) ->setRequest($this->getRequest()) ->setResponse($this->getResponse()) ->setRouter($serviceManager->get('Router')); [] }

Le router est instanci depuis le gestionnaire de services qui utilise la fabrique RouterFactory :

120

Le router Chapitre 8

Zend/Mvc/Service/RouterFactory.php
class RouterFactory implements FactoryInterface { $config = $serviceLocator->get('Configuration'); if( $rName === 'ConsoleRouter' || ($cName === 'router' && Console::isConsole()) ){ [] } else { $routerConfig = isset($config['router']) ? $config['router'] : array(); $router = HttpRouter::factory($routerConfig); } return $router; }

La fabrique vrifie tout dabord si le client est en ligne de commande afin dutiliser le router ddi la console. Pour plus dinformations sur la gestion de la console et son intgration dans le framework, reportez-vous au chapitre consacr aux composants et la section qui lui est ddie. Nous remarquons que la cl utilise dans la configuration pour la dfinition des routes est router. Si nous souhaitons dfinir nos routes, il sera ncessaire dutiliser un tableau de la forme suivante : module.config.php
<?php return array( 'router' => array( 'routes' => array( 'home' => array( 'type' => 'Zend\Mvc\Router\Http\Literal', 'options' => array( 'route' => '/', 'defaults' => array( 'controller' => 'index', 'action' => 'index', ) ), ), ) ), [] );

La classe Application ne conserve pas linstance du router comme attribut, celui-ci tant disponible auprs du gestionnaire de services, il est alors possible de rcuprer cette instance depuis les instructions suivantes :

Le router Chapitre 8

121

IndexController.php
class IndexController extends ActionController { public function indexAction() { $router = $this->getServiceLocator()->get('Router'); } }

La configuration permet de dfinir le type de route que lon souhaite utiliser pour chacune de nos routes. Notons quil est possible dutiliser le nom complet de la route ou lalias correspondant : Routes avec nom court
<?php return array( 'router' => array( 'routes' => array( 'home' => array( 'type' => 'literal', 'options' => array( 'route' => '/', 'defaults' => array( 'controller' => 'index', 'action' => 'index', ) ), ), ) ), [] );

Les noms courts sont disponibles grce au gestionnaire de plugins utilis par le router : Zend/Mvc/Router/Http/TreeRouteStack.php
protected function init() { $routes = $this->routePluginManager; foreach(array( 'hostname' => __NAMESPACE__ . '\Hostname', 'literal' => __NAMESPACE__ . '\Literal', 'part' => __NAMESPACE__ . '\Part', 'regex' => __NAMESPACE__ . '\Regex', 'scheme' => __NAMESPACE__ . '\Scheme', 'segment' => __NAMESPACE__ . '\Segment', 'wildcard' => __NAMESPACE__ . '\Wildcard', 'query' => __NAMESPACE__ . '\Query', 'method' => __NAMESPACE__ . '\Method', ) as $name => $class ) { $routes->setInvokableClass($name, $class); }; }

122

Le router Chapitre 8

Pour nous aider comprendre lintrt des diffrents types de routes, analysons le comportement du router lors de lvnement route, lanc afin de connatre laction effectuer. Nous verrons que cest la classe couteur RouteListener qui soccupe de grer le routage de lapplication : Zend/Mvc/RouteListener.php
public function onRoute($e) { $target = $e->getTarget(); $request = $e->getRequest(); $router = $e->getRouter(); $routeMatch = $router->match($request); f (!$routeMatch instanceof Router\RouteMatch) { i $e->setError($target::ERROR_ROUTER_NO_MATCH); $results = $target->getEventManager()->trigger(MvcEvent::EVENT_ DISPATCH_ERROR, $e); if (count($results)) { $return = $results->last(); } else { $return = $e->getParams(); } return $return; } $e->setRouteMatch($routeMatch); return $routeMatch; }

Le router, objet de type Zend\Mvc\Router\Http\TreeRouteStack rcupre lobjet de requte quil passe ensuite sa mthode match(). Lobjet qui lui est retourn correspond au type Zend\Mvc\Router\Http\RouteMatch qui tend Zend\Mvc\ Router\RouteMatch contenant deux paramtres : Zend/Mvc/Router/RouteMatch.php
class RouteMatch { protected $params = array(); protected $matchedRouteName; [] }

Ces paramtres permettent la classe de conserver le nom de la route correspondant la requte, ainsi que les paramtres de la route si celle-ci en utilise. Revenons la mthode qui vrifie la correspondance de lobjet TreeRouteStack, afin de mieux comprendre les tapes de la vrification des routes : Zend/Mvc/Router/Http/TreeRouteStack.php
public function match(Request $request) { [] if ($baseUrlLength !== null) { $pathLength = strlen($uri->getPath()) - $baseUrlLength;

Le router Chapitre 8
foreach ($this->routes as $name => $route) { if (($match = $route->match($request, $baseUrlLength)) instanceof RouteMatch && $match->getLength() === $pathLength) { $match->setMatchedRouteName($name);

123

f oreach ($this->defaultParams as $name => $value) { if ($match->getParam($name) === null) { $match->setParam($name, $value); } } return $match; } } } else { return parent::match($request); } return null; }

Toutes les routes sont parcourues, et le moteur de chacune delles est appel afin de vrifier la correspondance. On peut dores et dj noter limportance davoir ses routes les plus statiques (donc le plus rapide vrifier la correspondance), les routes les plus utilises ou celles avec les besoins de performances les plus importants (routes statiques pour des actions marketings, routes les plus crawles ou apportant le plus de trafic par exemple) en premier afin de pouvoir effectuer un minimum doprations de correspondance. Le conteneur de routes utilis est de type LIFO, la dernire route ajoute sera la premire vrifie. Cependant, il est possible de casser ce fonctionnement avec lajout de priorit. Le conteneur sera dabord tri sur les priorits et reprend son fonctionnement de type LIFO sur les routes de priorits quivalentes. Analysons ce fonctionnement du router afin den comprendre la gestion. Le composant TreeRouteStack nayant pas de constructeur, celui de sa classe parent SimpleRouteStack sera donc celui qui sera appel lors de linstanciation : Zend/Mvc/Router/SimpleRouteStack.php
public function __construct() { $this->routes = new PriorityList(); $this->routeBroker = new RouteBroker(); $this->init(); }

Lobjet Zend\Mvc\Router\PriorityList reprsente le conteneur de routes, il utilise deux attributs pour le classement des routes : Zend/Mvc/Router/PriorityList.php
public function insert($name, Route $route, $priority) { $this->sorted = false; $this->count++; $this->routes[$name] = array( 'route' => $route, 'priority' => (int) $priority,

124

Le router Chapitre 8
'serial' ); } => $this->serial++,

Le conteneur trie les routes suivant leurs priorits, puis en fonction de lordre dentre dans la pile pour deux priorits quivalentes : Zend/Mvc/Router/PriorityList.php
protected function compare(array $route1, array $route2) { if ($route1['priority'] === $route2['priority']) { return ($route1['serial'] > $route2['serial'] ? -1 : 1); } return ($route1['priority'] > $route2['priority'] ? -1 : 1); }

Il est donc possible d'ajouter une priorit haute sur notre page daccueil pour la mettre en haut de la pile : module.config.php
<?php return array( 'router' => array( 'routes' => array( 'home' => array( 'type' => 'Zend\Mvc\Router\Http\Literal', 'options' => array( 'route' => '/', 'defaults' => array( 'controller' => 'index', 'action' => 'index', ), ), 'priority' => 10, ), [] ), );

Ceci permet la route correspondant la page daccueil dtre la premire de la liste tre analyse. Examinons ensuite les diffrents types de routes que lon peut utiliser dans la configuration.

Les routes Http\Literal


Cette route est la plus simple comprendre et mettre en place. La vrification porte sur une comparaison brute de la route, de chane chane. Ce type de route est donc trs rapide vrifier car elle demande simplement une vrification dgalit :

Le router Chapitre 8

125

Zend/Mvc/Router/Http/Literal.php
public function match(Request $request, $pathOffset = null) { [] if ($pathOffset !== null) { if ($pathOffset >= 0 && strlen($path) >= $pathOffset) { if (strpos($path, $this->route, $pathOffset) === $pathOffset) { return new RouteMatch($this->defaults, strlen($this->route)); } } return null; } if ($path === $this->route) { return new RouteMatch($this->defaults, strlen($this->route)); } return null; }

Nous remarquons que la mthode accepte un paramtre $pathOffset qui permet de faire la correspondance sur une partie de l'URL et non l'URL entire. Toutes les routes peuvent utiliser cette possibilit, le router TreeRouteStack utilise ce paramtre pour grer ses arbres dURL. Arbres qui seront dtaills dans la section de la route Http\Part. Le paramtre $pathOffset nest utilis que si lobjet de requte est un objet de requte HTTP. En effet, la possibilit de faire correspondre juste une partie de l'URL lors de la gestion d'arbre d'URL et de sous-URL empche la comparaison brute d'galit. Une fois la correspondance sur lgalit effectue, le rsultat est envelopp dans un objet RouteMatch, qui permet la rcupration des paramtres et nom de route correspondante, avant dtre retourn : Zend/Mvc/Router/Http/Literal.php
public function match(Request $request, $pathOffset = null) { [] if (strpos($path, $this->route, $pathOffset) === $pathOffset) { return new RouteMatch($this->defaults, strlen($this->route)); } [] }

Avec le Zend Framework 1


La route Zend\Mvc\Router\Http\Literal correspond aux routes Zend_ Controller_Router_Route_Static.

126

Le router Chapitre 8

Les routes Http\Regex


Les routes bases sur la vrification de chanes avec expressions rgulires utilisent le moteur dexpressions rgulires de PHP avec la fonction preg_match() : Zend/Mvc/Router/Http/Regex.php
public function { [] if ($pathOffset $result = $matches, null, } else { $result = $matches); } match(Request $request, $pathOffset = null) !== null) { preg_match('(\G' . $this->regex . ')', $path, $pathOffset); preg_match('(^' . $this->regex . '$)', $path,

f (!$result) { i return null; } matchedLength = strlen($matches[0]); $ foreach ($matches as $key => $value) { if (is_numeric($key) || is_int($key)) { unset($matches[$key]); } else { $matches[$key] = urldecode($matches[$key]); } } eturn new RouteMatch(array_merge($this->defaults, $matches), r $matchedLength); }

Comme nous le voyons, la mthode vrifie la chane de caractres avec la fonction preg_match() et rcupre les paramtres qui correspondent au pattern afin de les enregistrer dans les paramtres de lobjet RouteMatch. L'utilisation d'expressions rgulires offre de nombreuses possibilits pour la dfinition de ses routes dont voici un exemple: module.config.php
<?php return array( 'router' => 'routes' => array( [] 'edit' => array( 'type' => 'Regex', 'options' => array( 'regex' => '/edit/(\d+)', 'spec' => '/edit/%user_id%', 'defaults' => array( 'action' => 'edit', ), ), ),

Le router Chapitre 8
[] ), ), );

127

Les routes Http\Scheme


La route Zend\Mvc\Router\Http\Scheme permet de faire la correspondance avec le nom du protocole de lURL en cours : Zend/Mvc/Router/Http/Scheme.php
public function match(Request $request) { [] $scheme = $uri->getScheme(); if ($scheme !== $this->scheme) { return null; } return new RouteMatch($this->defaults); }

Nous voyons que le protocole est rcupr depuis lobjet de requte afin dtre vrifi avec celui reu en paramtre.

Les routes Http\Method


La route Zend\Mvc\Router\Http\Method permet de vrifier la correspondance avec la mthode utilise dans la requte du client : Zend/Mvc/Router/Http/Method.php
public function match(Request $request) { if (!method_exists($request, 'getMethod')) { return null; } requestVerb = strtoupper($request->getMethod()); $ $matchVerbs = explode(',', strtoupper($this->verb)); $matchVerbs = array_map('trim', $matchVerbs); f (in_array($requestVerb, $matchVerbs)) { i return new RouteMatch($this->defaults); } return null; }

La route se base sur un attribut verb qui contient la liste des mthodes acceptes spares par une virgule. La mthode explode() de PHP transforme une chane de caractres en tableau depuis un dlimiteur qui est ici une virgule. Il est possible de passer cette liste de mthodes en paramtres depuis la cl verb comme on le voit dans la fabrique de la classe :

128

Le router Chapitre 8

Zend/Mvc/Router/Http/Method.php
public static function factory($options = array()) { [] if (!isset($options['verb'])) { throw new Exception\InvalidArgumentException('Missing "verb" in options array'); } [] }

Il est donc trs simple de crer une route depuis la fabrique : Cration de route Method
$route = Method::factory(array( 'verb' => 'get, post, put', 'defaults' => array( 'controller' => 'mon-controller', 'action' => 'mon-action', ), ));

Notons que la casse et les espaces dans la chane de caractres nont aucune importance car celle est est passe en majuscules et les espaces supprims depuis la mthode trim() de PHP : Zend/Mvc/Router/Http/Method.php
public function match(Request $request) { [] $requestVerb = strtoupper($request->getMethod()); [] $matchVerbs = array_map('trim', $matchVerbs); [] }

Les routes Http\Hostname


Les routes de type Zend\Mvc\Router\Http\Hostname permettent de vrifier la correspondance avec un nom de domaine. Cette route utilise le moteur dexpressions rgulires pour chaque partie du domaine qui est spare suivant le caractre du point, chaque partie est ensuite vrifie avec preg_match() ce qui rend ce type de route moins performant que les autres, la vrification tant plus longue faire : Zend/Mvc/Router/Http/Hostname.php
public function match(Request $request) { [] $hostname = explode('.', $uri->getHost()); $params = array();

Le router Chapitre 8
if (count($hostname) !== count($this->route)) { return null; } foreach ($this->route as $index => $routePart) { if (preg_match('(^:(?<name>.+)$)', $routePart, $matches)) { if (isset($this->constraints[$matches['name']]) && !preg_match('(^' . $this->constraints[$matches['name']] . '$)', $hostname[$index])) { return null; } $params[$matches['name']] = $hostname[$index]; } elseif ($hostname[$index] !== $routePart) { return null; } } return new RouteMatch(array_merge($this->defaults, $params)); }

129

Cette mthode peut tre utile pour rcuprer la correspondance de sous-domaine, car comme nous le voyons, la route utilise le moteur dexpressions rgulires et conserve les correspondances trouves. Prenons un exemple avec la recherche de correspondance dune URL avec un sous-domaine : Vrification de sous-domaine
$route = \Zend\Mvc\Router\Http\Hostname::factory(array( 'route' => ':subdomain.domaine.tld', 'constraints' => array( 'subdomain' => '\w{2,}-(\d)' ), 'defaults' => array( 'type' => 'montype', ), )); $match = $route->match($this->getRequest());

Lobjet $match conserve le paramtre correspondant au sous-domaine, accessible depuis linstruction : Rcupration du paramtre li au sous-domaine
$match->getParam('subdomain');

Ce type de route peut aussi tre utilis pour filtrer laccs un sous-domaine de notre application Web.

Les routes Http\Segment


La route Zend\Mvc\Router\Http\Segment vrifie la correspondance de la mme manire que la route Http\Regex. La seule diffrence est que la classe Segment construit lexpression rgulire quil doit vrifier partir des segments passs en paramtres :

130

Le router Chapitre 8

Zend/Mvc/Router/Http/Segment.php
public function { [] if ($pathOffset $result = $matches, null, } else { $result = $matches); } match(Request $request, $pathOffset = null) !== null) { preg_match('(\G' . $this->regex . ')', $path, $pathOffset); preg_match('(^' . $this->regex . '$)', $path,

f (!$result) { i return null; } matchedLength = strlen($matches[0]); $ foreach ($matches as $key => $value) { if (is_numeric($key) || is_int($key)) { unset($matches[$key]); } else { $matches[$key] = urldecode($matches[$key]); } } eturn new RouteMatch(array_merge($this->defaults, $matches), r $matchedLength); }

La route de type Segment est simplement une manire plus lisible de dcrire ses routes, il est donc prfrable dutiliser la route Http\Regex afin doptimiser les performances.

Les routes Http\Wildcard


La route de type Wildcard sutilise dans les routes en tant que routes filles en utilisant le paramtre child_routes lors de la configuration des routes. Ce type de route permet de rcuprer les paramtres passs dans lURL sous nimporte quelle forme que ce soit, y compris avec lutilisation de routes statiques : module.config.php
'router' => array( 'routes' => array( 'default' => array( 'type' => 'literal', 'options' => array( 'route' => '/wildcard', 'defaults' => array( 'controller' => 'index', 'action' => 'index', ), ), 'may_terminate' => true, 'child_routes' => array( 'wildcard' => array(

Le router Chapitre 8

131

'type' => 'wildcard', 'options' => array( 'key_value_delimiter' => '-', 'param_delimiter' => '/', ), 'may_terminate' => true, ), ), ), [] ), ),

Il est alors possible dutiliser lURL monsite.com/wildcard/param1-value afin de rcuprer les paramtres depuis le contrleur courant par exemple : IndexController.php
public function indexAction() { $event = $this->getEvent(); $routeMatch = $event->getRouteMatch(); $value = $routeMatch->getParam('param1'); // value eturn new ViewModel(array('key'=>$value)); r }

La classe Wildcard gre la sparation de lURL suivant les dlimiteurs indiqus dans la configuration et enregistre chacun des paramtres suivant des couples cl/ valeur : Zend/Mvc/Router/Http/Wildcard.php
public function match(Request $request, $pathOffset = null) { [] $matches = array(); $params = explode($this->paramDelimiter, $path); if (count($params) > 1 && ($params[0] !== '' || end($params) === '')) { return null; } if ($this->keyValueDelimiter === $this->paramDelimiter) { $count = count($params); for ($i = 1; $i < $count; $i += 2) { if (isset($params[$i + 1])) { $matches[urldecode($params[$i])] = urldecode($params[$i + 1]); } } } else { array_shift($params); foreach ($params as $param) { $param = explode($this->keyValueDelimiter, $param, 2); if (isset($param[1])) { $matches[urldecode($param[0])] = urldecode($param[1]); } } }

132

Le router Chapitre 8

eturn new RouteMatch(array_merge($this->defaults, $matches), r strlen($path)); }

Chaque paramtre est donc ensuite accessible depuis lobjet RouteMatch retourn par le router, car nous le voyons, chaque paramtre est trait afin d'tre stock dans l'objet RouteMatch.

Les routes Http\Part


La route Zend\Mvc\Router\Http\Part permet de crer un arbre ou une arborescence de routes possibles partir dune URL. Prenons un exemple en modifiant le router de lapplication pour utiliser un objet de type Http\Part. Pour lexemple, le code est plac dans un couteur de lvnement de routage route au sein du module Application : Cration dune arborescence de routes
$part = \Zend\Mvc\Router\Http\Part::factory(array( 'route' => array( 'type' => 'literal', 'options' => array( 'route' => '/', 'defaults' => array( 'controller' => 'index', 'action' => 'index', ), ) ), 'may_terminate' => true, 'route_broker' => $e->getRouter()->routeBroker(), 'child_routes' => array( 'blog' => array( 'type' => 'literal', 'options' => array( 'route' => 'blog', 'defaults' => array( 'controller' => 'blog', 'action' => 'index', ), ), 'may_terminate' => true, 'child_routes' => array( 'actu' => array( ' type' => 'Zend\Mvc\Router\Http\Literal', 'options' => array( 'route' => '/actu', 'defaults' => array( 'action' => 'actu', ), ), 'may_terminate' => true, ), ), ), ), )); $e->setRouter($part);

Le router Chapitre 8

133

On voit que lon a cr ici une arborescence telle que lURL : / nous envoie sur le contrleur IndexController et laction index; /blog nous envoie sur le contrleur BlogController et laction index; /blog/actu nous envoie sur le contrleur BlogController et laction actu. Il est possible de grer des arbres dURL complexes. Cependant, si cette utilisation offre de nombreuses possibilits, ce nest certainement pas la plus performante.

Lcouteur pour les modules


La classe ModuleRouteListener est un couteur spcialis dans la construction de routes par dfaut pour un module donn. En effet, regardons les routes du module Application de notre squelette pour comprendre le cheminement: module.config.php
return array( 'router' => array( [] 'application' => array( 'type' => 'Literal', 'options' => array( 'route' => '/application', 'defaults' => array( '__NAMESPACE__' => 'Application\ Controller', 'controller' => 'Index', 'action' => 'index', ), ), 'may_terminate' => true, 'child_routes' => array( 'default' => array( 'type' => 'Segment', 'options' => array( 'route' => '/ [:controller[/:action]]', 'constraints' => array( 'controller' => '[a-zA-Z] [a-zA-Z0-9_-]*', 'action' => '[a-zA-Z] [a-zA-Z0-9_-]*', ), 'defaults' => array(), ), ), ), ), ), [] );

La premire route dfinie, /application, est celle correspondant au point dentre du module Application. Cette route, qui possde des routes filles, laisse la pos-

134

Le router Chapitre 8

sibilit dutiliser le couple contrleur/action la suite du nom du module. Ce systme permet de dterminer la route qui correspond au module en laissant ensuite les noms des contrleurs et actions par dfaut. Analysons le code de la classe ModuleRouteListener afin de comprendre son fonctionnement: Zend/Mvc/ModuleRouteListener.php
public function onRoute(MvcEvent $e) { $matches = $e->getRouteMatch(); if (!$matches instanceof Router\RouteMatch) { return; } module = $matches->getParam(self::MODULE_NAMESPACE, false); $ if (!$module) { return; } controller = $matches->getParam('controller', false); $ if (!$controller) { return; } f (0 === strpos($controller, $module)) { i return; } $matches->setParam(self::ORIGINAL_CONTROLLER, $controller); $controller = $module . '\\' . str_replace(' ', '', ucwords(str_ replace('-', ' ', $controller))); $matches->setParam('controller', $controller); }

Lcouteur vrifie quune route a t trouve avec la validit de la proprit RouteMatch avant de vrifier sil existe une cl correspondant la constante ModuleRouteListener:: MODULE_NAMESPACE qui a pour valeur __NAMESPACE__. Cette cl permet de spcifier le namespace du module afin de construire automatiquement le nom du contrleur. Nous comprenons donc son intrt dans la configuration. Une fois cette opration effectue, lcouteur vrifie la prsence du contrleur et la validit du nom du contrleur. Le nom du contrleur inscrit dans la configuration est sauvegard, et le nom du contrleur est construit partir du module: Zend/Mvc/ModuleRouteListener.php
public function onRoute(MvcEvent $e) { [] $controller = $module . '\\' . str_replace(' ', '', ucwords(str_ replace('-', ' ', $controller))); $matches->setParam('controller', $controller); }

Cette approche nest peut-tre pas forcment intuitive pour les dveloppeurs. Il

Le router Chapitre 8

135

est cependant assez facile de modifier ce comportement afin dobtenir une route base sur le triplet module/contrleur/action. La premire chose faire est dajouter la route qui nous permettra davoir ce comportement par dfaut: module.config.php
return array( 'router' => array( 'routes' => array( [] 'module-controller-action' => array( 'type' => 'Segment', 'options' => array( 'route' => '/[:module[/:controller[/:act ion]]]', 'constraints' => array( 'module' => '[a-zA-Z][a-zA-Z0-9_-]*', 'controller' => '[a-zA-Z] [a-zA-Z0-9_-]*', 'action' => '[a-zA-Z] [a-zA-Z0-9_-]*', ), ), ), [] ) ) );

Une fois la route par dfaut dfinie, il suffit de reprendre la mme dynamique que lcouteur prcdent en supprimant la vrification du nom du contrleur et en ajoutant le formatage ncessaire. ZendX/Mvc/DefaultModuleRouteListener.php
public function onRoute(MvcEvent $e) { $matches = $e->getRouteMatch(); if (!$matches instanceof Router\RouteMatch) { return; } module = $matches->getParam(self::MODULE_NAMESPACE, false); $ if (!$module) { return; } controller = $matches->getParam('controller', false); $ if (!$controller) { return; } controller = ucfirst($module) . '\\' . 'Controller\\' . str_ $ replace(' ', '', ucwords(str_replace('-', ' ', $controller))) . 'Controller'; $matches->setParam('controller', $controller); }

136

Le router Chapitre 8

Une fois ces oprations effectues, il suffit dajouter lcouteur dans notre module: Module.php
public function onBootstrap($e) { $eventManager = $e->getApplication()->getEventManager(); defaultModuleRouteListener = new DefaultModuleRouteListener(); $ $defaultModuleRouteListener->attach($eventManager); $moduleRouteListener = new ModuleRouteListener(); $moduleRouteListener->attach($eventManager); }

Comme nous le voyons, il est trs simple de surcharger le comportement par dfaut du router afin de crer le fonctionnement que lon attend.

Les contrleurs
Le contrleur est llment qui permet de mettre en uvre les traitements lis la demande du client. Il utilise lenvironnement de lapplication (modle, vue, configuration, vnements, etc.) afin de pouvoir injecter ses donnes dans la vue qui sera ensuite retourne au client. Afin de simplifier la vie des dveloppeurs et de leur permettre davoir des contrleurs dactions prts lemploi, le framework implmente plusieurs contrleurs abstraits qui fournissent de nombreuses mthodes permettant de grer ces principales problmatiques, ainsi quun fonctionnement par dfaut.

Le contrleur AbstractActionController
Le contrleur dactions abstrait fourni par le Zend Framework est un objet de type Zend\Mvc\Controller\AbstractActionController dont voici le squelette: Zend/Mvc/Controller/AbstractActionController.php
abstract class AbstractActionController extends AbstractController { public function indexAction() {} public function notFoundAction() {} ublic function onDispatch(MvcEvent $e); p }

Ce contrleur permet de fournir un comportement et des fonctionnalits par dfaut afin de fournir un contrleur dactions prt lemploi aux dveloppeurs. Le contrleur dactions abstrait hrite du contrleur de base AbstractController qui permet la gestion des objets ncessaires son bon fonctionnement: Zend/Mvc/Controller/AbstractController.php
abstract class AbstractController implements Dispatchable, EventManagerAwareInterface, InjectApplicationEventInterface, ServiceLocatorAwareInterface {

138

Les contrleurs Chapitre 9


abstract public function onDispatch(MvcEvent $e); ublic function dispatch(Request $request, Response $response = p null); public function getRequest(); public function getResponse(); public function setEventManager(EventManagerInterface $events); public function getEventManager(); public function setEvent(Event $e); public function getEvent(); public function setServiceLocator(ServiceLocatorInterface $serviceManager); public function getServiceLocator(); public function getPluginManager(); public function setPluginManager(PluginManager $plugins); public function plugin($name, array $options = null); protected function attachDefaultListeners(); public static function getMethodFromAction($action); }

Nous comprenons que le contrleur de base fournit un ensemble de mthodes qui permet la gestion des objets ncessaires au fonctionnement minimal dun contrleur avec par exemple la gestion du gestionnaire dvnements, gestionnaire de services, gestionnaire de plugins, etc. Le contrleur dactions abstrait permet, lui, de dfinir un comportement par dfaut pour les contrleurs dactions classiques. Nous ne reviendrons pas sur ce contrleur abstrait de base qui ne possde aucune mthode spcifique, si ce nest la mthode setEventManager() qui dfinit les identifiants de notre contrleur: Zend/Mvc/Controller/AbstractController.php
public function setEventManager(EventManagerInterface $events) { $events->setIdentifiers(array( 'Zend\Stdlib\DispatchableInterface', __CLASS__, get_called_class(), $this->eventIdentifier, substr(get_called_class(), 0, strpos(get_called_class(), '\\')) )); this->events = $events; $ $this->attachDefaultListeners(); return $this; }

La liste de ces identifiants va nous permettre de comprendre comment il est possible dagir sur les contrleurs ou ensembles de contrleurs dun module. En effet, chaque contrleur possde comme identifiant: Zend\Stdlib\DispatchableInterface qui est utilis lors de la construction des vues sur tout contrleur qui peut tre distribu; __CLASS__ qui reprsente le nom complet de la classe du contrleur abstrait; get_called_class() qui reprsente le nom de la classe de lobjet instanci;

Les contrleurs Chapitre 9

139

$this->eventIdentifier qui reprsente un identifiant que lon peut personnaliser en le surchargeant au sein de son contrleur; substr(get_called_class(), 0, strpos(get_called_class(), '\\')) qui reprsente la premire partie de lespace de noms du contrleur, soit lquivalent de son module correspondant. Tous ces identifiants nous permettent dagir aussi bien sur le contrleur lui-mme que sur son module correspondant ou encore sur un primtre que lon aurait dfini depuis la surcharge de lattribut $eventIdentifier. Revenons notre contrleur dactions et son fonctionnement. Nous verrons dans le chapitre consacr au cur du framework que le point dentre du contrleur dactions est la mthode dispatch(), appele par lobjet DispatchListener responsable de la distribution : Zend/Mvc/DispatchListener.php
public function onDispatch(MvcEvent $e) { [] try { $return = $controller->dispatch($request, $response); } catch (\Exception $ex) { [] }

Voici le descriptif de la mthode dispatch(), point dentre de la classe AbstractController: Zend/Mvc/Controller/AbstractController.php


public function dispatch(Request $request, Response $response = null) { $this->request = $request; if (!$response) { $response = new HttpResponse(); } this->response = $response; $ $e = $this->getEvent(); $e->setRequest($request) ->setResponse($response) ->setTarget($this); result = $this->getEventManager()->trigger(MvcEvent::EVENT_DISPATCH, $ $e, f unction($test) { return ($test instanceof Response); }); f ($result->stopped()) { i return $result->last(); } return $e->getResult(); }

Les premires instructions initialisent les objets de requte et de rponse du contrleur qui peuvent ensuite tre partags au sein des vnements du contrleur.

140

Les contrleurs Chapitre 9

Avec le Zend Framework 1


Les attributs de requte et de rponse sont nomms $_request et $_response. Si les noms ont chang et lunderscore retir, les mthodes getRequest() et getResponse() sont toujours disponibles pour laccs aux objets respectifs. La mthode dispatch() est gre par la classe Zend_Controller_Action. Le manque dvnement oblige le framework crer des hooks afin de pouvoir agir sur le contrleur, ainsi que sur lobjet de requte ou de rponse. Ces deux objets sont maintenant partags au sein des vnements du contrleur, tout couteur peut donc intervenir dessus. Le framework injecte ensuite les deux objets de requte et de rponse dans lobjet dvnement avant de lancer lvnement dispatch qui permet la distribution de laction effectuer: Zend/Mvc/Controller/AbstractController.php
public function dispatch($action) { [] $result = $this->getEventManager()->trigger(MvcEvent::EVENT_DISPATCH, $e, function($test) { return ($test instanceof Response); }); [] }

Le contrleur attache un couteur sur lvnement dispatch avant de lancer cet vnement dont il est aussi un couteur depuis la mthode onDispatch() lors de lajout des couteurs par dfaut: Zend/Mvc/Controller/AbstractController.php
protected function attachDefaultListeners() { $events = $this->getEventManager(); $events->attach(MvcEvent::EVENT_DISPATCH, array($this, 'onDispatch')); }

Nous pouvons nous demander lintrt dune telle procdure et le fait que la mthode onDispatch() nest pas appele directement par le contrleur. Cette manire de fonctionner permet dautres couteurs de venir sattacher avec une priorit diffrente et dtre aussi notifis de lvnement. Nous pouvons crer ici un systme identique ce que lon connaissait avec les hooks preDisptach et postDispatch du Zend Framework 1 en attachant des couteurs avant et aprs la notification du contrleur lui-mme. Cependant, attention au fait que le courtcircuitage peut exister si un lment de type Zend\Stdlib\ResponseInterface est renvoy depuis les couteurs de priorits les plus fortes:

Les contrleurs Chapitre 9

141

Zend/Mvc/Controller/AbstractController.php
public function dispatch($action) { [] $result = $this->getEventManager()->trigger(MvcEvent::EVENT_DISPATCH, $e, function($test) { return ($test instanceof Response); }); [] }

Le premier couteur renvoyer un objet de type ResponseInterface arrte la propagation de lvnement. Si cest le cas, la mthode dispatch() va logiquement retourner le dernier objet de rponse des couteurs, sinon elle renverra le rsultat de lvnement depuis la mthode getResult(): Zend/Mvc/Controller/AbstractController.php
public function dispatch(Request $request, Response $response = null) { [] $result = $this->getEventManager()->trigger(MvcEvent::EVENT_DISPATCH, $e, function($test) { return ($test instanceof Response); }); f ($result->stopped()) { i return $result->last(); } return $e->getResult(); }

Notons que la mthode ne retourne pas systmatiquement le dernier objet de rponse reu. Cela sexplique par le fait que rien ne lui indique que le dernier objet retourn sera bien un objet de type ResponseInterface. Il est donc important de bien peupler lvnement de notre rsultat. Analysons maintenant lcouteur par dfaut de AbstractActionController: Zend/Mvc/Controller/AbstractActionController.php
public function onDispatch(MvcEvent $e) { $routeMatch = $e->getRouteMatch(); if (!$routeMatch) { [] } $action = $routeMatch->getParam('action', 'not-found'); $method = static::getMethodFromAction($action); if (!method_exists($this, $method)) { $method = 'notFoundAction'; } $actionResponse = $this->$method();

142

Les contrleurs Chapitre 9


$e->setResult($actionResponse); return $actionResponse; }

La mthode rcupre la route vrifie par le router depuis lvnement en cours et la mthode getRouteMatch(), qui stocke lobjet de type RouteMatch. Cet objet permet de rcuprer les informations lies la route en cours (nom, paramtres de la route, contrleur et action utiliser). Si aucune route na t trouve lors de la correspondance entre notre configuration et la demande du client, le contrleur utilise la route derreur par dfaut notfound. Il formate ensuite le nom de laction distribuer depuis les paramtres de la route courante: Zend/Mvc/Controller/AbstractActionController.php
public function onDispatch(MvcEvent $e) { [] $action = $routeMatch->getParam('action', 'not-found'); $method = static::getMethodFromAction($action); if (!method_exists($this, $method)) { $method = 'notFoundAction'; } $actionResponse = $this->$method(); [] }

Zend/Mvc/Controller/AbstractActionController.php
public static function getMethodFromAction($action) { $method = str_replace(array('.', '-', '_'), ' ', $action); $method = ucwords($method); $method = str_replace(' ', '', $method); $method = lcfirst($method); $method .= 'Action'; return $method; }

La mthode getMethodFromAction() nous permet de comprendre pourquoi les actions des contrleurs doivent avoir comme suffixe le mot Action . Le contrleur peut maintenant appeler la mthode daction: Zend/Mvc/Controller/AbstractActionController.php
public function onDispatch(MvcEvent $e) { [] $actionResponse = $this->$method(); [] }

Le rsultat de cette mthode sera stock dans le rsultat de lvnement courant et la rponse de laction est tout de mme retourne:

Les contrleurs Chapitre 9

143

Zend/Mvc/Controller/AbstractActionController.php
public function onDispatch(MvcEvent $e) { [] $actionResponse = $this->$method(); $e->setResult($actionResponse); return $actionResponse; }

Ces instructions rejoignent le point voqu tout lheure avec limportance de peupler lattribut de rsultat de lvnement, afin de sassurer de la bonne transmission du rsultat la mthode dispatch() qui retourne ensuite ce rsultat: Zend/Mvc/Controller/AbstractController.php
public function dispatch(Request $request, Response $response = null) { [] return $e->getResult(); }

Cependant, nous pouvons nous demander pourquoi la mthode retourne tout de mme la rponse depuis la mthode onDispatch(). Comme nous lavons vu lors de la gestion du court-circuitage de notre vnement dispatch, ds lors quun couteur retourne un objet de type ResponseInterface, la propagation sarrte. Et nous ne connaissons pas ici lobjet qui est retourn par la mthode de contrleur, cela pourrait aussi tre un objet de ce type. Comme nous le verrons dans le chapitre consacr au cur du framework, la classe Application cherche construire un objet de rponse afin de pouvoir envoyer son contenu au client. Nous comprenons donc lintrt de court-circuiter le traitement si lobjet de rponse est dj construit.

Mthodes daction par dfaut


Dans le contrleur dactions abstrait AbstractActionController, deux mthodes daction existent par dfaut: Zend/Mvc/Controller/AbstractActionController.php
public function indexAction() { return new ViewModel(array( 'content' => 'Placeholder page' )); }

Zend/Mvc/Controller/AbstractActionController.php
public function notFoundAction() { $response = $this->response; $event = $this->getEvent(); $routeMatch = $event->getRouteMatch();

144

Les contrleurs Chapitre 9


$response->setStatusCode(404); $routeMatch->setParam('action', 'not-found'); return new ViewModel(array( 'content' => 'Page not found' )); }

La mthode indexAction() permet dobtenir un rendu de la vue sur laction index sans pour autant dfinir une mthode de ce nom dans notre contrleur. Cette mthode par dfaut permet de ne pas sencombrer de code inutile si nous possdons une vue statique correspondant laction index. La deuxime mthode, notFoundAction(), est lie aux erreurs de route. Si aucune route ne vrifie la demande du client, cette action est appele par dfaut, comme nous le voyons depuis la mthode onDispatch(): Zend/Mvc/Controller/AbstractActionController.php
public function onDispatch(MvcEvent $e) { [] $action = $routeMatch->getParam('action', 'not-found'); $method = static::getMethodFromAction($action); f (!method_exists($this, $method)) { i $method = 'notFoundAction'; } actionResponse = $this->$method(); $ $e->setResult($actionResponse); return $actionResponse; }

Nous remarquons que la mme route est utilise si le contrleur ne trouve pas de mthode correspondant laction en cours au sein de son instance.

Avec le Zend Framework 1


Dans la premire version, un contrleur derreur est attribu la gestion des erreurs, ce qui force une nouvelle distribution. Ici, seul le nom de la mthode est modifi ce qui permet un affichage de la page derreur plus rapide. Le plugin Zend_Controller_Plugin_ErrorHandler rinitialise les contrleurs, actions et modules avant de remettre le flag de distribution dans son tat initial. Nous avons maintenant une action derreur gre directement par le contrleur.

Les interfaces du contrleur de base


Le contrleur de base abstrait AbstractController implmente quatre interfaces: Dispatchable, EventManagerAwareInterface, InjectApplicationEventInterface et ServiceLocatorAwareInterface. La premire interface Dispatchable, ne dfinit quune seule mthode:

Les contrleurs Chapitre 9

145

Zend/Stdlib/Dispatchable.php
interface Dispatchable { public function dispatch(Request $request, Response $response = null); }

Le contrat pass avec cette interface permet de sassurer que le contrleur est dispatchable. Cela signifie quil va pouvoir tre distribu depuis la classe Application, mais aussi que nous pouvons effectuer nous-mmes ce traitement depuis les contrleurs pour en rcuprer la rponse. La deuxime interface EventManagerAwareInterface dfinit seulement une mthode: Zend/EventManager/EventManagerAwareInterface.php
interface EventManagerAwareInterface { public function setEventManager(EventManagerInterface $eventManager); }

Linterface EventManagerAwareInterface permet de sassurer de la possibilit dajouter linstance du gestionnaire de services de lapplication au sein de lobjet cr. Comme pour le gestionnaire dvnements, linstance du gestionnaire de services est automatiquement enregistre lors de la cration dun objet depuis le gestionnaire de contrleurs: Zend/Mvc/Controller/ControllerManager.php
public function __construct(ConfigInterface $configuration = null) { [] $this->addInitializer(array($this, 'injectControllerDependencies'), false); }

Zend/Mvc/Controller/ControllerManager.php
public function injectControllerDependencies($controller, ServiceLocatorInterface $serviceLocator) { [] if ($controller instanceof EventManagerAwareInterface) { $controller->setEventManager($parentLocator>get('EventManager')); } [] }

Cette mthode permet de sassurer de la possibilit dajouter des couteurs au gestionnaire dvnements de la classe en rcuprant son instance depuis la mthode getEventManager(). Linterface InjectApplicationEventInterface dfinit deux mthodes:

146

Les contrleurs Chapitre 9

Zend/Mvc/InjectApplicationEventInterface.php
interface InjectApplicationEventInterface { public function setEvent(Event $event); public function getEvent(); }

Le contrat pass avec cette interface permet de sassurer que lobjet peut utiliser des instances dvnements. Il va aussi servir lors de la cration des contrleurs dactions afin de leur injecter lvnement de type MvcEvent. Cet vnement est propre au processus de distribution de lapplication, comme on peut le voir dans la liste de ses attributs: Zend/Mvc/MvcEvent.php
class MvcEvent extends Event { protected $application; protected $request; protected $response; protected $result; protected $router; protected $routeMatch; protected $viewModel; [] }

La liste des mthodes daltration correspondant chacun de ces attributs na pas besoin dtre liste. Nous comprenons lintrt de cet vnement qui partage lobjet de requte, de rponse, de route et de vue, ce qui permet nimporte quel plugin ou contrleur d'agir avec le contexte MVC de lapplication. Linterface permet quant elle linjection de cet objet lors de la distribution: Zend/Mvc/DispatchListener.php
public function onDispatch(MvcEvent $e) { [] if ($controller instanceof InjectApplicationEventInterface) { $controller->setEvent($e); } [] }

Linterface ServiceLocatorAwareInterface dfinit deux mthodes: Zend/ServiceManager/ServiceLocatorAwareInterface.php


interface ServiceLocatorAwareInterface { public function setServiceLocator(ServiceLocatorInterface $serviceLocator); public function getServiceLocator(); }

Les contrleurs Chapitre 9

147

Ce contrat permet de sassurer de la possibilit dajouter un objet de type ServiceLocatorInterface linstance utilise. Par exemple, le contrleur AbstractActionController reoit une instance de type ServiceManager depuis le gestionnaire de contrleurs qui permet linstanciation de contrleur: Zend/Mvc/Controller/ControllerManager.php
public function __construct(ConfigInterface $configuration = null) { [] $this->addInitializer(array($this, 'injectControllerDependencies'), false); }

Zend/Mvc/Controller/ControllerManager.php
public function injectControllerDependencies($controller, ServiceLocatorInterface $serviceLocator) { [] if ($controller instanceof ServiceLocatorAwareInterface) { $controller->setServiceLocator($parentLocator->get('Zend\ ServiceManager\ServiceLocatorInterface')); } [] }

Cette interface est trs utilise dans le framework afin de sassurer de pouvoir transmettre le gestionnaire de services au maximum de composants qui pourraient en avoir besoin. En effet, le gestionnaire de services est un des piliers du framework et il est important que les composants entrant en jeu lors de la partie principale de la couche MVC puissent en bnficier. Ce comportement se retrouve dans le gestionnaire de plugins ou encore dans certaines aides de vue. Toutes ces interfaces permettent, dans le processus MVC, linjection des lments dont a besoin le contrleur afin de pouvoir fonctionner au mieux. Tout contrleur hritant du contrleur abstrait AbstractActionController a donc un accs toutes ces informations. Nous venons de comprendre les diffrentes injections faites notre contrleur, nous allons maintenant analyser le fonctionnement du gestionnaire de contrleurs que nous avons pu dcrire en partie prcdemment. Le gestionnaire de contrleurs hrite du gestionnaire de plugins de base et possde donc ses mthodes dinitialisation ainsi que sa mthode de validation: Zend/Mvc/Controller/ControllerManager.php
public function __construct(ConfigInterface $configuration = null) { [] $this->addInitializer(array($this, 'injectControllerDependencies'), false); }

148

Les contrleurs Chapitre 9

Zend/Mvc/Controller/ControllerManager.php
public function injectControllerDependencies($controller, ServiceLocatorInterface $serviceLocator) { if (!$controller instanceof DispatchableInterface) { return; } $parentLocator = $serviceLocator->getServiceLocator(); if ($controller instanceof ServiceLocatorAwareInterface) { $controller->setServiceLocator($parentLocator->get('Zend\ ServiceManager\ServiceLocatorInterface')); } if ($controller instanceof EventManagerAwareInterface) { $controller->setEventManager($parentLocator>get('EventManager')); } f (method_exists($controller, 'setPluginManager')) { i $controller->setPluginManager($parentLocator->get('ControllerPl uginBroker')); } }

Le gestionnaire ajoute la mthode dinitialisation injectControllerDependencies() afin dinitialiser les contrleurs en lui injectant les objets ncessaires son fonctionnement. Le gestionnaire de services, le gestionnaire dvnements ainsi que le gestionnaire de plugins sont donc injects au sein du contrleur afin quil soit oprationnel. Pour plus dinformations sur le fonctionnement du gestionnaire de plugins de base, reportez-vous la section qui lui est consacre.

Le contrleur AbstractRestfulController
Un deuxime contrleur dactions abstrait prt lemploi est disponible dans le framework, il sagit du AbstractRestfulController, qui tend aussi la classe AbstractController. La classe AbstractRestfulController dfinit galement la mthode dispatch() qui lui servira de point dentre. Une de leurs diffrences repose sur la mthode onDispatch(), couteur principal de la distribution du contrleur: Zend/Mvc/Controller/AbstractRestfulController.php
public function onDispatch(MvcEvent $e) { $routeMatch = $e->getRouteMatch(); if (!$routeMatch) { [] } request = $e->getRequest(); $ $action = $routeMatch->getParam('action', false); if ($action) { $method = static::getMethodFromAction($action); if (!method_exists($this, $method)) { $method = 'notFoundAction';

Les contrleurs Chapitre 9

149

} $return = $this->$method(); } else { switch (strtolower($request->getMethod())) { case 'get': if (null !== $id = $routeMatch->getParam('id')) { $action = 'get'; $return = $this->get($id); break; } if (null !== $id = $request->getQuery()>get('id')) { $action = 'get'; $return = $this->get($id); break; } $action = 'getList'; $return = $this->getList(); break; case 'post': $action = 'create'; $return = $this->processPostData($request); break; case 'put': $action = 'update'; $return = $this->processPutData($request, $routeMatch); break; case 'delete': if (null === $id = $routeMatch->getParam('id')) { if (!($id = $request->getQuery()->get('id', false))) { [] } } $action = 'delete'; $return = $this->delete($id); break; default: throw new Exception\DomainException('Invalid HTTP method!'); } $routeMatch->setParam('action', $action); } $e->setResult($return); return $return; }

Comme nous le remarquons, cette mthode est conue spcialement pour fonctionner avec larchitecture de webservice REST. Son fonctionnement est plutt simple, le contrleur implmente des mthodes prtes lemploi afin de pouvoir rpondre aux oprations basiques. Une requte de type GET qui contient un paramtre id est automatiquement redirige vers la mthode get(), sinon la mthode getList() est appele. La mthode POST appellera toujours la mthode processPostData(), PUT la mthode processPutData() et la mthode DELETE provoque lappel de la mthode delete(). Toutes ces fonctions sont implmentes dans cette mme classe:

150

Les contrleurs Chapitre 9

Zend/Mvc/Controller/AbstractRestfulController.php
abstract class AbstractRestfulController extends AbstractController { [] abstract public function getList(); abstract public function get($id); abstract public function create($data); abstract public function update($id, $data); abstract public function delete($id); [] public function processPostData(Request $request) { return $this->create($request->getPost()->toArray()); } public function processPutData(Request $request, $routeMatch) { [] $content = $request->getContent(); parse_str($content, $parsedParams); return $this->update($id, $parsedParams); } }

Cette classe permet de bnficier dun contrle minimum lors de la cration dun web service bas sur larchitecture REST. Il ne reste alors plus qu implmenter les mthodes cites au-dessus. Une mthode notFoundAction est aussi implmente afin de rpondre aux demandes daction qui nexistent pas. Comme nous le remarquons, les mthodes que lon a prsentes sont appeles uniquement si aucune action spcifique nest demande: Zend/Mvc/Controller/AbstractRestfulController.php
public function onDispatch(MvcEvent $e) { $routeMatch = $e->getRouteMatch(); [] $request = $e->getRequest(); $action = $routeMatch->getParam('action', false); if ($action) { $method = static::getMethodFromAction($action); if (!method_exists($this, $method)) { $method = 'notFoundAction'; } $return = $this->$method(); } [] }

Les actions demandes depuis le paramtre action seront donc prioritaires. La classe fournit des mthodes de confort qui permettent lutilisateur de ne pas sencombrer du comportement par dfaut pour une action inexistante. Avec le contrleur abstrait AbstractRestfulController qui prsente un squelette de classe ddie ce type de traitement, limplmentation de web service REST nen sera que facilite.

Les aides daction


Le Zend Framework 2 a profondment modifi certaines aides daction peu performantes ou trop complexes pour la tche qui leur est confie. Une aide daction particulirement intressante a t ajoute au framework, il sagit de laide Forward. Cette aide daction vient remplacer la mthode _forward() du Zend Framework 1 qui est particulirement couteuse en ressources car elle ncessite de refaire le processus de distribution de lapplication. Comme nous allons le voir, cette aide daction tire ses performances de la possibilit de distribuer les actions de chaque contrleur en dehors du processus MVC. Dautres aides daction ont t totalement rcrites afin de simplifier leur interface en rduisant leur nombre de mthodes, comme laide Redirect ou Url. La magie lie aux appels des aides daction a t supprime, ce qui permet de gagner en clart et en performances.

10

Avec le Zend Framework 1


La mthode de redirection appele par $this->_helper->redirector(monaction,mon-controller,mon-module); fait appel une mthode magique du helper daction. En effet, aucune mthode du nom de redirector() n'existe dans le contrleur daction, cest donc la mthode __call qui appelle elle-mme la mthode direct() de laide daction Redirector.

Laide daction Forward


Laide daction Forward permet la distribution dun nouveau couple contrleur/ action afin den rcuprer le rsultat. Voyons un exemple de cette aide daction dans notre contrleur dindex: IndexController.php
class IndexController extends ActionController { public function indexAction() { return new ViewModel(); }

152

Les aides daction Chapitre 10


public function forwardAction() { return $this->plugin('forward')->dispatch('Application\ Controller\IndexController',array('action'=>'index')); } }

Laction forwardAction() retourne maintenant le rsultat de laction index. Analysons le comportement de laide daction Forward: Zend/Mvc/Controller/Plugin/Forward.php
public function dispatch($name, array $params = null) { $event = clone($this->getEvent()); $locator = $this->getLocator(); $scoped = false; f ($locator->has('ControllerLoader')) { i $locator = $locator->get('ControllerLoader'); $scoped = true; } $controller = $locator->get($name); if (!$controller instanceof Dispatchable) { [] } if (!$scoped) { if ($controller instanceof InjectApplicationEventInterface) { $controller->setEvent($event); } if ($controller instanceof ServiceLocatorAwareInterface) { $controller->setServiceLocator($locator); } } if ($params) { $event->setRouteMatch(new RouteMatch($params)); } [] return = $controller->dispatch($event->getRequest(), $event$ >getResponse()); [] return $return; }

Laide daction rcupre lenvironnement du contrleur courant, vnements et gestionnaire de services, afin de pouvoir les injecter dans les paramtres du nouveau contrleur distribuer. Elle utilise la classe de chargement de contrleur ControllerLoader reprsent par la fabrique ControllerLoaderFactory que lon a prsente prcdemment. Le gestionnaire de services, lvnement courant et lobjet de correspondance des routes sont injects la nouvelle instance de contrleur avant sa distribution.

Les aides daction Chapitre 10

153

Avec le Zend Framework 1


Dans la premire version du framework, aucune aide daction ne permet de distribuer un autre couple contrleur/action sans perdre le contexte de laction en cours. La mthode _foward() du contrleur de base permet de relancer la boucle de distribution en modifiant le contrleur, le module et laction courante. Cette mthode possde linconvnient de refaire tous les traitements de la boucle de distribution, mais permet de simuler une autre requte sans devoir forcer une redirection HTTP. Une aide de vue permet par contre de retourner la vue dun triplet module/contrleur/ action, il sagit de laide action(). Mais comme on la vu, celle-ci est trs gourmande et est viter. Laide de vue Action fait un clone de la requte, de la rponse et du dispatcher, initialise la rponse et fait la distribution. Rendre nimporte quel contrleur distribuable permet une plus grande libert et une plus grande flexibilit aux dveloppeurs.

Laide daction Redirect


Laide daction Redirect a t totalement rcrite afin de simplifier les mthodes proposes au dveloppeur. Deux mthodes sont maintenant disponibles: Zend/Mvc/Controller/Plugin/Redirect.php
public function toRoute($route, array $params = array(), array $options = array()) { $controller = $this->getController(); [] $response = $this->getResponse(); $urlPlugin = $controller->plugin('url'); if (is_scalar($options)) { $url = $urlPlugin->fromRoute($route, $params, $options); } else { $url = $urlPlugin->fromRoute($route, $params, $options, $reuseMatchedParams); } $response->headers()->addHeaderLine('Location', $url); $response->setStatusCode(302); return $response; }

Zend/Mvc/Controller/Plugin/Redirect.php
public function toUrl($url) { $response = $this->getResponse(); $response->headers()->addHeaderLine('Location', $url); $response->setStatusCode(302); return $response; }

Ce sont deux mthodes simples comprendre et implmenter. La premire per-

154

Les aides daction Chapitre 10

met de rediriger le client suivant une route, ce qui permet de construire automatiquement lURL concerne, et la deuxime redirige le client suivant une URL dfinie en paramtre. Voici un exemple trivial de la mthode toRoute(), base sur les noms de route: IndexController.php
public function redirectAction() { return $this->plugin('redirect')->toRoute('home'); }

Comme nous venons de le voir, laide de redirection retourne un objet de type ResponseInterface, ce qui permet de court-circuiter lvnement dispatch du contrleur ActionController et faciliter le travail de la classe Application qui reoit alors un objet prt tre envoy au client. La deuxime mthode de laide daction est toute aussi simple dutilisation : IndexController.php
public function forwardAction() { return $this->plugin('redirect')->toUrl('http://www.zend.com/fr'); }

La mthode toUrl() se contente de rediriger le client vers lURL indique en paramtre, en effectuant une vrification sur sa valeur afin de dterminer si celle-ci est une URL externe valide, dans le cas contraire lURL sera transforme en une URL relative. Comme pour la mthode prcdente, la mthode toUrl() retourne un objet de rponse dont les headers ont t modifis afin de permettre une redirection. Aucune redirection nest effectue directement depuis laide daction. Nous pouvons noter que les deux mthodes crent des redirections avec un code HTTP 302 qui reprsente une redirection temporaire. Si lon souhaite mettre en place des redirections permanentes, nous avons la possibilit de changer le code HTTP la vole: IndexController.php
public function forwardAction() { return $this->plugin('redirect')->toUrl('http://www.zend.com/fr')>setStatusCode(301); }

Laide daction Redirect est maintenant plus simple prendre en main. Il nexiste plus de redirections effectues sur les triplets module/contrleur/action qui peuvent gnrer quelques effets de bord sur les URL, mais uniquement par les identifiants de route ou saisis directement dans le code.

Les aides daction Chapitre 10

155

Avec le Zend Framework 1


Pour rappel, dans le Zend Framework 1, le routage effectu sur le module/ contrleur/action se base sur les routes par dfaut. Le router nutilise donc pas les routes rcrites si elles existent, ce qui peut donner des URL rcrites dun ct de lapplication et des URL statiques, bases sur la rgle module/contrleur/action, de lautre. Cela peut savrer pnalisant pour les clients de lapplication ou encore pour lapplication elle-mme et son rfrencement, deux URL pointant sur un contenu identique est pnalisant.

Laide daction Url


Laide daction Url permet de gnrer une route depuis un contrleur en se basant sur le nom de la route. En effet, une seule mthode est disponible: Zend/Mvc/Controller/Plugin/Url.php
public function fromRoute($route, array $params = array(), array $options = array()) { $controller = $this->getController(); [] $event = $controller->getEvent(); $router = null; [] $options['name'] = $route; return $router->assemble($params, $options); }

La mthode fromRoute() rcupre le router de lapplication en utilisant les proprits de lvnement du contrleur courant, et peut ainsi composer la route demande afin de la retourner. Voici un exemple dutilisation simple: IndexController.php
public function indexAction() { $home = $this->plugin('url')->fromRoute('home'); $homeCanonical = $this->plugin('url')->fromRoute('home', array(), array('force_canonical' => true)); }

Les routes retournes vont tre successivement / et http://monserveur.local:80/.

156

Les aides daction Chapitre 10

Avec le Zend Framework 1


Une autre mthode est disponible dans laide daction Url, il sagit de la mthode simple(). Cette mthode permet de crer une route en fonction du triplet module/contrleur/action bas sur les routes par dfaut. Comme pour lancienne aide daction Redirector, les mmes problmes de routes multiples peuvent se poser car le router nutilise pas les routes rcrites.

Laide daction PostRedirectGet


Laide daction PostRedirectGet permet de mettre en uvre le pattern PRG (Post/ Redirect/Get) qui rsout les problmes de soumissions multiples de formulaire. Laide daction vrifie si des donnes ont t postes depuis la mthode POST afin de les enregistrer en session avant deffectuer une redirection: Zend/Mvc/Controller/Plugin/PostRedirectGet.php
public function __invoke($redirect, $redirectToUrl = false) { $controller = $this->getController(); $request = $controller->getRequest(); [] $container = new Container('prg_post1'); if ($request->isPost()) { $container->setExpirationHops(1, 'post'); $container->post = $request->getPost()->toArray(); [] } [] }

Le contenu des donnes en POST est enregistr en session sous la cl prg_ post1 afin dtre rcupr depuis la page o lon sera redirig: Zend/Mvc/Controller/Plugin/PostRedirectGet.php
public function __invoke($redirect, $redirectToUrl = false) { [] if ($request->isPost()) { [] } else { if ($container->post !== null) { $post = $container->post; unset($container->post); return $post; } return false; } }

Laide daction retourne les donnes enregistres lors de la soumission des donnes. Une fois celles-ci rcupres, elles se dtruisent alors automatiquement. Voici

Les aides daction Chapitre 10

157

un exemple simple d'utilisation depuis un contrleur daction: IndexController.php


class IndexController extends AbstractActionController { public function indexAction() { $this->request->setMethod('POST'); $this->request->setPost(new Parameters(array( 'post_key' => 'value' ))); return $this->prg('/forward', true); } ublic function forwardAction() p { $datas = $this->prg(null); } }

La mthode index utilise laide daction PostRedirectGet afin deffectuer sa redirection sur laction forward. Une fois rediriges, les donnes passes en POST peuvent alors tre rcupres pour effectuer le traitement souhait. Laide daction PostRedirectGet peut aussi tre rcupre depuis lidentifiant postredirectget, comme nous le voyons depuis le gestionnaire de plugins: Zend/Mvc/Controller/PluginManager.php
class PluginManager extends AbstractPluginManager { protected $invokableClasses = array( [] ' postredirectget' => 'Zend\Mvc\Controller\Plugin\ PostRedirectGet', [] ); protected $aliases = array( 'prg' => 'postredirectget', ); [] }

Comme nous pouvons le voir, prg nest quun alias de postredirectget, les deux identifiants peuvent tre utiliss.

Cration dune aide daction


Lorsque vous dveloppez votre application, vous pouvez avoir besoin de crer une aide daction personnalise afin de factoriser le code redondant de votre application. La premire tape est dtendre la classe de base Zend\Mvc\Controller\ Plugin\AbstractPlugin qui donne un accs au contrleur courant:

158

Les aides daction Chapitre 10

Zend/Mvc/Controller/Plugin/AbstractPlugin.php
abstract class AbstractPlugin { protected $controller; public function setController(Dispatchable $controller) { $this->controller = $controller; } ublic function getController() p { return $this->controller; } }

Une fois laide daction crite, il ne reste plus qu lenregistrer auprs du gestionnaire de plugins. Cette opration peut se faire depuis la mthode getControllerPluginConfiguration() de la classe de module: Module.php
ublic function getControllerPluginConfig() p { return array( 'invokables' => array( 'myplugin' => 'MyLib\Mvc\Controller\Plugin\ MyPlugin', ), ); }

Il est galement possible dinscrire laide de vue depuis le fichier de configuration du module sous le nom de cl controller_plugins: module.config.php
<?php return array( [] 'controller_plugins' => array( 'invokables' => array( 'myplugin' => 'MyLib\Mvc\Controller\Plugin\MyPlugin', ), ),);

Pour plus dinformations sur la gestion interne de la configuration, reportezvous au chapitre consacr aux modules. Lexemple ci-dessus permet lajout d'une autre dfinition du plugin myplugin que lon peut alors rcuprer depuis notre contrleur avec linstruction: IndexController.php
public function indexAction() { $myPlugin = $this->plugin('myplugin'); [] }

Le gestionnaire de plugins
Le terme plugin est utilis dans le Zend Framework 2 au sens large et englobe de nombreux objets. Il peut tre utilis pour dsigner les aides daction ou de vue, les filtres, les validateurs ou encore les adaptateurs des diffrents composants. Afin de simplifier la gestion des plugins, une classe de base existe et implmente une mthode de chargement afin de permettre aux gestionnaires d'en tirer profit, facilitant le chargement et la personnalisation des noms de plugins.

11

La classe de base
Le gestionnaire de plugins gre la cration et lenregistrement des instances de plugins, mais est aussi capable de grer la rsolution des noms de plugins. Chaque gestionnaire du framework tend le gestionnaire de plugins de base Zend\ServiceManager\AbstractPluginManager qui est une implmentation du gestionnaire de services. En effet, il nexiste plus de composant spcifique au chargement de plugins, le gestionnaire de services est utilis comme lment de base pour le chargement de classes car il dispose de toutes les mthodes pour rpondre aux principaux besoins de chargement : Zend\ServiceManager\AbstractPluginManager.php
abstract class AbstractPluginManager extends ServiceManager implements ServiceLocatorAwareInterface {}

Notons aussi que le gestionnaire de base implmente linterface ServiceLocatorAware, ce qui va lui permettre de recevoir une instance du gestionnaire de services. Nous aurons donc accs notre gestionnaire de services principal au sein de tous les gestionnaires du framework : gestionnaire des aides de vue, des aides daction, etc. Ce comportement pourra savrer pratique afin d'utiliser les fabriques que lon a dfinies dans notre application au sein de nos diffrents gestionnaires. Le composant ServiceManager est spcialis dans le chargement, linstanciation et linitialisation de classe, il serait dommage de ne pas utiliser sa puissance afin de charger les plugins du framework. Cela permet dautant plus une meilleure cohsion dans le framework. Voyons comment est initialis et configur le gestionnaire de base :

160

Le gestionnaire de plugins Chapitre 11

Zend\ServiceManager\AbstractPluginManager.php
public function __construct(ConfigurationInterface $configuration = null) { parent::__construct($configuration); self = $this; $ $this->addInitializer(function ($instance) use ($self) { if ($instance instanceof ServiceLocatorAwareInterface) { $instance->setServiceLocator($self); } if ($instance instanceof ServiceManagerAwareInterface) { $instance->setServiceManager($self); } }); }

Le gestionnaire accepte un objet de configuration dans son constructeur, ce qui va permettre de personnaliser le gestionnaire qui sera instanci. Une fois le gestionnaire configur, celui-ci ajoute une mthode dinitialisation qui permettra de fournir aux objets crs linstance du gestionnaire de services. Pour rappel, linterface ServiceManagerAwareInterface dfinit une mthode permettant lobjet qui limplmente de sassurer que celui-ci peut recevoir une instance du gestionnaire de services : Zend\ServiceManager\ServiceManagerAwareInterface.php
interface ServiceManagerAwareInterface { public function setServiceManager(ServiceManager $serviceManager); }

Cest la mthode get() qui ralise le chargement de linstance du plugin. Voici le code de la mthode de chargement : Zend\ServiceManager\AbstractPluginManager.php
public function get($name, $options = array(), $usePeeringServiceManagers = true) { if (!$this->has($name) && $this->autoAddInvokableClass && class_ exists($name)) { $this->setInvokableClass($name, $name); } $this->creationOptions = $options; $instance = parent::get($name, $usePeeringServiceManagers); $this->creationOptions = null; $this->validatePlugin($instance); return $instance; }

Le gestionnaire vrifie en premier lieu sil est capable de charger lobjet demand depuis la mthode has() du gestionnaire de services. Si celui-ci nest pas en mesure de charger lobjet demand mais que la classe existe bien, le gestionnaire

Le gestionnaire de plugins Chapitre 11

161

ajoute alors la classe comme tant invokable si l'ajout automatique est activ depuis l'attribut autoAddInvokableClass, ce qui va lui permettre tout de mme le chargement. Le fait de pouvoir charger une classe existante depuis son nom complet est une des particularits du gestionnaire de plugins. Les options reues en paramtres sont enregistres afin de pouvoir tre utilises dans la fabrique de classes dites invokables. Le gestionnaire de services utilise la mthode createFromInvokable() afin dinstancier les classes de ce type. Cette fabrique est redfinie dans le gestionnaire de plugins afin de pouvoir utiliser les options fournies en paramtre : Zend\ServiceManager\AbstractPluginManager.php
protected function createFromInvokable($canonicalName, $requestedName) { $invokable = $this->invokableClasses[$canonicalName]; if (!class_exists($invokable)) { [] } if (null === $this->creationOptions || (is_array($this->creationOptions) && empty($this>creationOptions)) ) { $instance = new $invokable(); } else { $instance = new $invokable($this->creationOptions); } return $instance; }

Nous retrouvons lattribut creationOptions lors linstanciation de la classe de plugin, et qui est ensuite remis la valeur null. Une fois le plugin instanci, la mthode validatePlugin() dfinie par le gestionnaire qui tend la classe AbstractPluginManager permet de valider le type du plugin instanci. Maintenant que nous avons vu ltape de linstanciation, il est ncessaire de comprendre comment se passe la rsolution des noms de plugins. Chaque plugin dispose dun nom court qui facilite lcriture. Nous avons vu que le gestionnaire de base tend la classe ServiceManager qui lui permet de tirer pleinement profit de ses capacits. Chaque gestionnaire qui hrite de la classe AbstractPluginManager aura donc la possibilit de dfinir ses classes de type invokable et autres fabriques personnalises, ce qui lui permettra de lui affecter comme identifiant de fabriques, le nom court du plugin. Afin de mettre en uvre ce que nous venons dexpliquer, analysons alors les diffrents gestionnaires disponibles dans le framework.

Les gestionnaires du framework


Les gestionnaires de plugins du framework dfinissent chacun leur fabrique pour la rsolution de nom de plugins. Chacun des gestionnaires peut alors ajouter une

162

Le gestionnaire de plugins Chapitre 11

couche de traitement supplmentaire lors du chargement depuis les mthodes dinitialisation fournies par le gestionnaire de services afin de pouvoir peupler le plugin dobjets propres son fonctionnement. Prenons un premier exemple avec le composant Zend\Mvc\Controller\PluginManager, gestionnaire des aides daction : Zend/Mvc/Controller/PluginManager.php
class PluginManager extends AbstractPluginManager { protected $invokableClasses = array( 'flashmessenger' => 'Zend\Mvc\Controller\Plugin\FlashMessenger', 'forward' => 'Zend\Mvc\Controller\Plugin\Forward', [] ); protected $controller; ublic function __construct(ConfigurationInterface $configuration = p null) { parent::__construct($configuration); $this->addInitializer(array($this, 'injectController')); } ublic function setController(DispatchableInterface $controller) p {[]} public function getController() {[]} ublic function injectController($plugin) p { [] if (!method_exists($plugin, 'setController')) { return; } $controller = $this->getController(); if (!$controller instanceof DispatchableInterface) { return; } $plugin->setController($controller); } ublic function validatePlugin($plugin) p { if ($plugin instanceof Plugin\PluginInterface) { return; } throw new Exception\InvalidPluginException([]); } }

Le gestionnaire de plugins ajoute une mthode dinitialisation lors de son instanciation. Cette mthode permet linjection du contrleur courant laide daction utilise afin quelle puisse lutiliser lors de la tche quelle effectue :

Le gestionnaire de plugins Chapitre 11

163

Zend/Mvc/Controller/PluginManager.php
public function __construct(ConfigurationInterface $configuration = null) { [] $this->addInitializer(array($this, 'injectController')); }

Notons aussi que la mthode de validation des plugins vrifie que celui-ci implmente bien linterface Zend\Mvc\Controller\Plugin\PluginInterface qui dfinit deux mthodes : Zend\Mvc\Controller\Plugin\PluginInterface.php
interface PluginInterface { public function setController(Dispatchable $controller); public function getController(); }

Pour rpondre ce besoin, votre aide d'action devra comporter ces deux mthodes ou tendre l'aide d'action abstraite AbstractPlugin qui implmente dj ces deux mthodes. Le gestionnaire daides de vue du framework fonctionne de la mme manire : Zend/View/HelperPluginManager.php
class HelperPluginManager extends AbstractPluginManager { protected $invokableClasses = array( [] 'headlink' => 'Zend\View\Helper\HeadLink', 'headmeta' => 'Zend\View\Helper\HeadMeta', 'headscript' => 'Zend\View\Helper\HeadScript', [] ); protected $renderer; ublic function __construct(ConfigurationInterface $configuration = p null) { parent::__construct($configuration); $this->addInitializer(array($this, 'injectRenderer')); } public function setRenderer(Renderer\RendererInterface $renderer) {[]} public function getRenderer() {[]} public function injectRenderer($helper) { $renderer = $this->getRenderer(); if (null === $renderer) { return; } $helper->setView($renderer); }

164

Le gestionnaire de plugins Chapitre 11


public function validatePlugin($plugin) { if ($plugin instanceof Helper\HelperInterface) { return; } throw new Exception\InvalidHelperException([]); } }

Le gestionnaire daides de vue dfinit ses propres classes invokables et injecte lobjet de rendu chacun des plugins. Le type du plugin est valide si celui-ci implmente les deux mthodes de linterface Zend\View\Helper\HelperInterface : Zend/View/Helper/HelperInterface.php
interface HelperInterface { public function setView(Renderer $view); public function getView(); }

Tous les gestionnaires de plugins fonctionnent de la mme manire. Les gestionnaires tendent le gestionnaire de base, qui tend la classe ServiceManager afin de tirer profit de ses possibilits de fabrication, et redfinissent chacun leur classe de validation et ajoutent leurs mthodes dinitialisation. Dautres composants utilisent galement la classe de base AbstractPluginManager afin de charger leurs plugins. Le composant de cache, par exemple, utilise le gestionnaire de base afin de charger les plugins qui interviendront lors de la cration de cache : Zend/Cache/Storage/PluginManager.php
class PluginManager extends AbstractPluginManager { protected $invokableClasses = array( 'clearexpiredbyfactor' => 'Zend\Cache\Storage\Plugin\ ClearExpiredByFactor', [] ); protected $shareByDefault = false; ublic function validatePlugin($plugin) p { if ($plugin instanceof Plugin\PluginInterface) { return; } throw new Exception\RuntimeException([]); } }

Le composant de pagination, de cryptage ou encore de gestion de logs utilisent le gestionnaire de base pour grer le chargement de leurs plugins. Cette manire de fonctionner est largement rpandue dans le framework et nos composants personnaliss peuvent galement reprendre ce fonctionnement pour la gestion des chargements de plugins.

Les vues
Le composant de vue a t refondu entirement afin d'amliorer sa flexibilit et sa rpartition des tches. Capable de grer les imbrications de vues entre elles ou encore le format des rendus, les composants responsables des actions lies la vue sont nombreux et permettant maintenant aux dveloppeurs une grande libert sur le rendu de leur application.

12

Les responsabilits de rendu (conteneur de variables, injection de valeurs, localisation de template, rendu de vue, injection de rendu, gestion des erreurs) ont chacune t affectes un composant ddi sa tche. Il est maintenant beaucoup moins complexe de manier les vues ou dintervenir sur leur rendu tant lorganisation est spare.

Avec le Zend Framework 1


Le Zend_View a trop de responsabilits, ce qui le rend peu flexible. Le Zend_Layout est indpendant du Zend_View alors quil se comporte comme une vue mais avec un niveau de hirarchisation suprieur. Le ViewRenderer a aussi t supprim afin de sparer les responsabilits lors du processus de rendu.

Le gestionnaire de vues
Afin de comprendre rapidement le rle de chacun des composants lis la vue, il est important de se faire une rapide ide sur le cycle de la gestion des vues. Les vues sont construites depuis le retour des mthodes dactions de nos contrleurs. Le retour de laction est rcupr depuis des couteurs spcialiss qui transforment ce retour en un objet de vue. Une fois la vue construite, le template lui est inject avant qu'elle ne soit elle-mme injecte au sein de la vue principale, le layout. Une fois lvnement demandant le rendu lanc, un objet spcialis prend le relais afin de rendre la vue pour tre ensuite inject lobjet de rponse un peu plus tard. Un dernier vnement permet de notifier un objet spcialis dans le renvoi dobjet de rponse au client. Chacune de ces grandes tapes va tre approfondie dans ce chapitre au fil des sections. Ce chapitre est assez dense et il est conseill davoir le code du framework

166

Les vues Chapitre 12

sous la main afin de mieux comprendre les explications et les relations entre objets. Avant d'examiner en dtail le comportement des composants intervenant lors du rendu de la vue, il est important de comprendre les vnements et couteurs qui entrent en jeu. La liste des couteurs qui sattachent aux actions lies la vue est disponible dans la classe du gestionnaire de vues. Nous dtaillerons chacun des composants, mais le nom de chacun peut vous donner un rapide aperu de son rle prcis : Zend/Mvc/View/Http/ViewManager.php
public function onBootstrap($event) { [] $routeNotFoundStrategy = $this->getRouteNotFoundStrategy(); $exceptionStrategy = $this->getExceptionStrategy(); $mvcRenderingStrategy = $this->getMvcRenderingStrategy(); $createViewModelListener = new CreateViewModelListener(); $injectTemplateListener = new InjectTemplateListener(); $injectViewModelListener = new InjectViewModelListener(); $sendResponseListener = new SendResponseListener(); [] $sharedEvents->attach('Zend\Stdlib\DispatchableInterface', MvcEvent::EVENT_DISPATCH, array($createViewModelListener, 'createViewModelFromArray'), -80); $sharedEvents->attach('Zend\Stdlib\DispatchableInterface', MvcEvent::EVENT_DISPATCH, array($routeNotFoundStrategy, 'prepareNotFoundViewModel'), -90); $sharedEvents->attach('Zend\Stdlib\DispatchableInterface', MvcEvent::EVENT_DISPATCH, array($createViewModelListener, 'createViewModelFromNull'), -80); $sharedEvents->attach('Zend\Stdlib\DispatchableInterface', MvcEvent::EVENT_DISPATCH, array($injectTemplateListener, 'injectTemplate'), -90); $sharedEvents->attach('Zend\Stdlib\DispatchableInterface', MvcEvent::EVENT_DISPATCH, array($injectViewModelListener, 'injectViewModel'), -100); }

Notons pour l'instant que le gestionnaire de vues gre la liste entire des couteurs de vues. Cela nous permettra par la suite, et au cours des dveloppements de nos projets, de savoir o regarder lorsque nous recherchons intervenir sur le rendu de vue ou tout simplement comprendre un dtail qui nous aurait chapp. Examinons en dtail le travail de chacun des couteurs pour mieux comprendre leur rle dans le rendu de la vue et les grandes tapes de celle-ci. Les premiers couteurs intervenir sont ceux qui s'occupent de prparer l'objet de vue.

Prparation des vues


La prparation des vues est la premire tape du cycle de rendu. Cette tape permet de transformer le retour de laction courante en un objet de vue qui pourra

Les vues Chapitre 12

167

facilement tre travaill par le framework. Lors du lancement de lvnement dispatch par lapplication, le premier objet notifi est lobjet DispatchListener qui soccupe de grer la distribution de laction effectuer. En effet, il est ncessaire que laction soit distribue si lon souhaite intervenir sur le rsultat de celle-ci. Les autres couteurs de l'vnement sont ensuite notifis, dont ceux destins la prparation des vues. Les couteurs responsables de la prparation des vues agissent sur lvnement dispatch avec une faible priorit, ce qui les place la fin des notifications. Voici leurs actions: Zend/Mvc/View/Http/CreateViewModelListener.php
public function createViewModelFromArray(MvcEvent $e) { $result = $e->getResult(); if (!ArrayUtils::hasStringKeys($result, true)) { return; } $model = new ViewModel($result); $e->setResult($model); }

Zend/Mvc/View/Http/CreateViewModelListener.php
public function createViewModelFromNull(MvcEvent $e) { $result = $e->getResult(); if (null !== $result) { return; } $model = new ViewModel; $e->setResult($model); }

Les couteurs agissent sur le retour de laction courante si celle-ci n'est pas au format attendu. Ces deux mthodes interviennent sur lattribut result de lobjet MvcEvent, car comme nous l'avons vu dans la section ddie aux contrleurs, le retour de laction en cours peuple cet attribut: Zend/Mvc/Controller/AbstractActionController.php
public function onDispatch(MvcEvent $e) { [] $actionResponse = $this->$method(); $e->setResult($actionResponse); return $actionResponse; }

Si lobjet retourn par laction est un tableau, alors la mthode createViewModelFromArray() se charge de le convertir en objet de type ViewModel. Cet couteur offre la possibilit de retourner un tableau lors de la mthode daction:

168

Les vues Chapitre 12

Retour de tableau depuis une vue


public function indexAction() { return array('key'=>'value'); }

La deuxime mthode, createViewModelFromNull(), se charge de convertir les retours de valeurs null en une instance de ViewModel sans paramtre. Cela nous permet de ne pas faire de retour inutile au sein de notre action: Retour null depuis une action
public function indexAction() { }

Ces deux couteurs offrent plus de flexibilit avec le type de retour de l'action courante. Analysons maintenant la prparation des vues d'erreurs.

Prparation des vues derreurs


Les mthodes qui grent la prparation des vues derreurs coutent lvnement dispatch.error. Il y a trois mthodes distinctes qui agissent sur les vues et lvnement derreur lors de la distribution: Zend/Mvc/View/Http/RouteNotFoundStrategy.php
public function detectNotFoundError(MvcEvent $e) { $error = $e->getError(); if (empty($error)) { return; } switch ($error) { case Application::ERROR_CONTROLLER_NOT_FOUND: case Application::ERROR_CONTROLLER_INVALID: case Application::ERROR_ROUTER_NO_MATCH: $this->reason = $error; $response = $e->getResponse(); if (!$response) { $response = new HttpResponse(); $e->setResponse($response); } $response->setStatusCode(404); break; default: return; } }

Cette mthode permet de modifier le code HTTP de la rponse pour les erreurs de routes et de contrleur inexistant ou invalide. La cause de lerreur est enregistre dans lattribut $reason afin de pouvoir sen resservir ultrieurement pour une description de lerreur en cours. Une fois ces modifications faites, la mthode de

Les vues Chapitre 12

169

prparation de la vue derreur peut donc intervenir: Zend/Mvc/View/Http/RouteNotFoundStrategy.php


public function prepareNotFoundViewModel(MvcEvent $e) { $vars = $e->getResult(); if ($vars instanceof Response) { return; } $response = $e->getResponse(); if ($response->getStatusCode() != 404) { return; } model = new ViewModel\ViewModel(); $ $model->setVariable('message', 'Page not found.'); $model->setTemplate($this->getNotFoundTemplate()); $this->injectNotFoundReason($model, $e); $this->injectException($model, $e); $this->injectController($model, $e); $e->setResult($model); }

Cette mthode ne fonctionne que si le code HTTP a bien t modifi en code404. Dans le cas derreur sur la route ou de contrleur inexistant ou invalide, lcouteur prcdent sest occup de modifier le code HTTP. Une vue est donc cre afin de linjecter au rsultat de lvnement courant, actuellement de valeur null car normalement peupl par le retour de notre action courante lors dune distribution sans erreur. Enfin, trois mthodes injectent les donnes ncessaires la description de lerreur: Zend/Mvc/View/Http/RouteNotFoundStrategy.php
public function prepareNotFoundViewModel(MvcEvent $e) { [] $this->injectNotFoundReason($model, $e); $this->injectException($model, $e); $this->injectController($model, $e); [] }

La premire mthode injecte lorigine de lerreur: Zend/Mvc/View/Http/RouteNotFoundStrategy.php


protected function injectNotFoundReason($model) { if (!$this->displayNotFoundReason()) { return; } if ($this->reason) { $model->setVariable('reason', $this->reason); return; } $model->setVariable('reason', Application::ERROR_CONTROLLER_CANNOT_ DISPATCH); }

170

Les vues Chapitre 12

Lattribut $reason, descriptif de lerreur en cours, peupl dans une mthode prcdente est maintenant utilis. Les deux autres mthodes dinjection compltent le descriptif : Zend/Mvc/View/Http/RouteNotFoundStrategy.php
protected function injectException($model, $e) { if (!$this->displayExceptions()) { return; } $exception = $e->getParam('exception', false); if (!$exception instanceof \Exception) { return; } $model->setVariable('exception', $exception); }

Zend/Mvc/View/Http/RouteNotFoundStrategy.php
protected function injectController($model, $e) { if (!$this->displayExceptions() && !$this->displayNotFoundReason()) { return; } [] $controllerClass = $e->getControllerClass(); $model->setVariable('controller', $controller); $model->setVariable('controller_class', $controllerClass); }

Les erreurs sont affiches dans notre exemple, le fichier de configuration permet laffichage de lerreur et de son exception: module.config.php
<?php return array( [] 'view_manager' => array( 'display_not_found_reason' => true, 'display_exceptions' => true, [] ), ), );

Les paramtres dfinis pour le gestionnaire de vues sont injects lors de la construction des objets correspondants. La construction des couteurs responsables de la gestion des erreurs se fait dans lcouteur du bootstrap du gestionnaire de vue: Zend/Mvc/View/Http/ViewManager.php
public function onBootstrap($event) { [] $routeNotFoundStrategy = $this->getRouteNotFoundStrategy(); $exceptionStrategy = $this->getExceptionStrategy(); [] }

Les vues Chapitre 12

171

Voici la construction de lcouteur responsable la gestion des exceptions: Zend/Mvc/View/Http/ViewManager.php


public function getExceptionStrategy() { [] $this->exceptionStrategy = new ExceptionStrategy(); $displayExceptions = false; $exceptionTemplate = 'error'; if (isset($this->config['display_exceptions'])) { $displayExceptions = $this->config['display_exceptions']; } if (isset($this->config['exception_template'])) { $exceptionTemplate = $this->config['exception_template']; } $this->exceptionStrategy->setDisplayExceptions($displayExceptions); $this->exceptionStrategy->setExceptionTemplate($exceptionTemplate); [] }

Nous retrouvons ici les cls display_exceptions et exception_template que lon a dfinies dans la configuration ainsi que les valeurs par dfaut qui leur sont affectes: false et error. Lcouteur concernant la gestion derreurs utilise aussi la configuration: Zend/Mvc/View/Http/ViewManager.php
public function getRouteNotFoundStrategy() { [] $this->routeNotFoundStrategy = new RouteNotFoundStrategy(); $displayExceptions = false; $displayNotFoundReason = false; $notFoundTemplate = '404'; if (isset($this->config['display_exceptions'])) { $displayExceptions = $this->config['display_exceptions']; } if (isset($this->config['display_not_found_reason'])) { $displayNotFoundReason = $this->config['display_not_found_ reason']; } if (isset($this->config['not_found_template'])) { $notFoundTemplate = $this->config['not_found_template']; } $this->routeNotFoundStrategy->setDisplayExceptions($displayExceptio ns); $this->routeNotFoundStrategy->setDisplayNotFoundReason($displayNotFou ndReason); $this->routeNotFoundStrategy->setNotFoundTemplate($notFoundTemplate); [] }

Nous retrouvons cette fois les cls display_exceptions, not_found_template et display_not_found_reason avec les valeurs par dfaut false, false et 404.

172

Les vues Chapitre 12

Pour les autres erreurs de lapplication, qui ne se produisent pas lors dun problme de route ou de contrleur, la mthode prepareExceptionViewModel() de la classe Zend\Mvc\View\Http\ExceptionStrategy gre le rsultat de lvnement MvcEvent, et modifie aussi le code HTTP de lobjet de rponse par le code500. Cette mthode est peu diffrente des autres mthodes: Zend/Mvc/View/Http/ExceptionStrategy.php
public function prepareExceptionViewModel(MvcEvent $e) { $error = $e->getError(); if (empty($error)) { return; } $result = $e->getResult(); if ($result instanceof Response) { return; } switch ($error) { [] case Application::ERROR_EXCEPTION: default: $model = new ViewModel\ViewModel(array( 'message' => 'An error occurred during execution; please try again later.', 'exception' => $e->getParam('exception'), 'display_exceptions' => $this>displayExceptions(), )); $model->setTemplate($this->getExceptionTemplate()); $e->setResult($model); $response = $e->getResponse(); if (!$response) { $response = new HttpResponse(); $e->setResponse($response); } $response->setStatusCode(500); break; } }

Une mthode utilise lors de la prparation du rendu derreur est attache lvnement dispatch, il sagit de la mthode prepareNotFoundViewModel() que nous avons vue prcdemment. Cet couteur ne travaille que lors de la dtection dun code HTTP404: Zend/Mvc/View/Http/RouteNotFoundStrategy.php
public function prepareNotFoundViewModel(MvcEvent $e) { $vars = $e->getResult(); if ($vars instanceof Response) { return; } $response = $e->getResponse(); if ($response->getStatusCode() != 404) { return; } [] }

Les vues Chapitre 12

173

La modification du code HTTP au sein de notre action aurait alors pour effet de gnrer une page derreur: Erreur depuis le changement de code HTTP
public function indexAction() { $this->response->setStatusCode(-404); return new ViewModel(array('key'=>'value')); }

Lanalyse des couteurs, avec la responsabilit du contrle et de la prparation des vues est termine. Nous pouvons maintenant nous intresser au cur du systme du rendu de vue.

Rendu de la vue
Le processus daffichage de la vue peut tre dcompos en deux tapes. La premire tape concerne la construction et la prparation des vues que nous venons de voir, suivi par le rendu effectif de la vue o de nombreux composants interviennent. Examinons le cheminement prcis de chacune de ces tapes.

Construction des vues


Comme prsent dans la section prcdente, la construction de la vue se fait lors de la notification des couteurs sur lvnement dispatch. Une fois lobjet de vue cr, la mthode injectTemplate() de la classe Zend\Mvc\View\InjectTemplateListener, notifie aussi par lvnement dispatch, se charge de modifier le nom du fichier de vue qui doit tre rendu: Zend/Mvc/View/Http/InjectTemplateListener.php
public function injectTemplate(MvcEvent $e) { $model = $e->getResult(); [] $template = $model->getTemplate(); [] $routeMatch = $e->getRouteMatch(); $controller = $e->getTarget(); [] $module = $this->deriveModuleNamespace($controller); $controller = $this->deriveControllerClass($controller); $template = $this->inflectName($module); if (!empty($template)) { $template .= '/'; } $template .= $this->inflectName($controller); $action = $routeMatch->getParam('action'); if (null !== $action) { $template .= '/' . $this->inflectName($action); } $model->setTemplate($template); }

174

Les vues Chapitre 12

Nous remarquons que, par dfaut, le nom du template est compos du nom du module, concatn avec le nom du contrleur et de laction: Zend/Mvc/View/Http/InjectTemplateListener.php
public function injectTemplate(MvcEvent $e) { [] $module = $this->deriveModuleNamespace($controller); [] $template = $this->inflectName($module); [] $template .= $this->inflectName($controller); [] if (null !== $action) { $template .= '/' . $this->inflectName($action); } $model->setTemplate($template); }

Nous remarquons galement que linjection de fichier de vue est effectue si celuici nexiste pas dj: Zend/Mvc/View/Http/InjectTemplateListener.php
public function injectTemplate(MvcEvent $e) { [] $template = $model->getTemplate(); if (!empty($template)) { return; } [] }

En effet, nous comprenons alors quil est possible de changer le template la vole lors de la construction de notre objet de vue sans que celui-ci soit systmatiquement modifi ultrieurement: IndexController.php
public function indexAction() { $viewModel = new ViewModel(); $viewModel->setTemplate('index/promotion'); return $viewModel; }

Cet exemple permet dafficher la vue index/promotion lors de lappel la page dindex. Si le template na pas t modifi au cours de laction, celui-ci prend le nom du contrleur courant suivi du nom de son action. La mthode injectViewModel() de la classe InjectViewModelListener injecte ensuite la vue courante dans la vue parente existante, le layout. Cette partie est intressante du fait que ce systme de fonctionnement est une nouveaut du Zend Framework 2:

Les vues Chapitre 12

175

Zend/Mvc/View/Http/InjectViewModelListener.php
public function injectViewModel(MvcEvent $e) { $result = $e->getResult(); if (!$result instanceof ViewModel) { return; } $model = $e->getViewModel(); if ($result->terminate()) { $e->setViewModel($result); return; } $model->addChild($result); }

La variable $result contient la vue courante retourne par notre action courante, ou construite depuis les mthodes de prparation que nous avons vues, et lobjet $model reprsente la vue parente. Linitialisation du layout est effectue par le gestionnaire de vues: Zend/Mvc/View/Http/ViewManager.php
public function getViewModel() { if ($this->viewModel) { return $this->viewModel; } $this->viewModel = $model = $this->event->getViewModel(); $model->setTemplate($this->getLayoutTemplate()); return $this->viewModel; }

Lappel la mthode getViewModel() de lvnement MvcEvent construit une instance de lobjet View\ViewModel si celle-ci a la valeur null: Zend/Mvc/MvcEvent.php
public function getViewModel() { if (null === $this->viewModel) { $this->setViewModel(new ViewModel\ViewModel()); } return $this->viewModel; }

Le gestionnaire de vues injecte alors le chemin du layout cette nouvelle vue: Zend/Mvc/View/Http/ViewManager.php
public function getViewModel() { [] $model->setTemplate($this->getLayoutTemplate()); [] }

176

Les vues Chapitre 12

Zend/Mvc/View/Http/ViewManager.php
public function getLayoutTemplate() { $layout = 'layout/layout'; if (isset($this->config['layout'])) { $layout = $this->config['layout']; } return $layout; }

Le fichier de layout dfini dans la configuration est alors inject comme nom de template. La vue courante est ensuite attache au layout comme vue fille: Zend/Mvc/View/Http/InjectViewModelListener.php
public function injectViewModel(MvcEvent $e) { [] $model->addChild($result); }

Le systme de rendu des diffrentes vues repose maintenant sur une imbrication des vues entre elles, et non sur des lments de vues spars comme dans la premire version du framework. Le layout et la vue courante sont maintenant initialiss et imbriqus, le layout englobe la vue courante en tant que vue fille, tout est enfin prt pour le rendu des vues. Analysons maintenant la manire de dsactiver le layout dans cette nouvelle gestion des vues. Nous venons de voir que la vue courante est injecte au layout depuis ces instructions: Zend/Mvc/View/Http/InjectViewModelListener.php
public function injectViewModel(MvcEvent $e) { $result = $e->getResult(); // reprsente la vue courante if (!$result instanceof ViewModel) { return; } $model = $e->getViewModel(); if ($result->terminate()) { $e->setViewModel($result); // la vue remplace le layout return; } $model->addChild($result); // la vue est injecte au layout }

Nous remarquons que cette injection se ralise uniquement si l'attribut $terminate de lobjet ViewModel est positionn sur la valeur false, dans le cas contraire la vue courante remplace la vue principale, et la supprime. Il est donc possible de faire appel la mthode setTerminate() depuis lobjet de vue courante afin de supprimer le layout:

Les vues Chapitre 12

177

Suppression du layout
public function nolayoutAction() { $model = new ViewModel(array( 'key' => 'value' )); $model->setTerminal(true); return $model; }

Rendu des vues


Le rendu est opr lors de lvnement render, lanc par lobjet Application la suite de la distribution de laction courante: Zend/Mvc/Application.php
public function run() { [] $result = $events->trigger(MvcEvent::EVENT_DISPATCH, $event, $shortCircuit); [] return $this->completeRequest($event); }

Zend/Mvc/Application.php
protected function completeRequest(MvcEvent $event) { $events = $this->getEventManager(); $event->setTarget($this); $events->trigger(MvcEvent::EVENT_RENDER, $event); $events->trigger(MvcEvent::EVENT_FINISH, $event); return $event->getResponse(); }

Pour rappel, lcouteur de cet vnement est la mthode render() de lobjet Zend\Mvc\View\Http\DefaultRenderingStrategy, attach depuis le gestionnaire de vues: Zend/Mvc/View/Http/ViewManager.php
public function onBootstrap($event) { [] $mvcRenderingStrategy = $this->getMvcRenderingStrategy(); [] $events->attach($mvcRenderingStrategy); [] }

178

Les vues Chapitre 12

Zend/Mvc/View/Http/ViewManager.php
public function getMvcRenderingStrategy() { [] $this->mvcRenderingStrategy = new DefaultRenderingStrategy($this >getView()); [] return $this->mvcRenderingStrategy; }

Zend/Mvc/View/Http/DefaultRenderingStrategy.php
public function attach(EventManagerInterface $events) { $this->listeners[] = $events->attach(MvcEvent::EVENT_RENDER, array($this, 'render'), -10000); }

Lcouteur dlgue le rendu de la vue lobjet Zend\View\View qui porte cette responsabilit: Zend/Mvc/View/Http/DefaultRenderingStrategy.php
public function render(MvcEvent $e) { [] $view = $this->view; $view->setRequest($request); $view->setResponse($response); $view->render($viewModel); return $response; }

Aprs avoir inject les objets de rponse et de requte au sein de lobjet View, la mthode render() de cet objet est appele afin de rendre lobjet de vue, contenant la vue courante imbrique dans le layout. La mthode render() de la classe View organise alors le travail de rendu: Zend/View/View.php
public function render(Model $model) { $event = $this->getEvent(); $event->setModel($model); $events = $this->getEventManager(); $results = $events->trigger(ViewEvent::EVENT_RENDERER, $event, function($result) { return ($result instanceof Renderer); }); $renderer = $results->last(); [] if ($model->hasChildren() && (!$renderer instanceof Renderer\TreeRendererInterface || !$renderer->canRenderTrees()) ) { $this->renderChildren($model); } $event->setModel($model);

Les vues Chapitre 12


$event->setRenderer($renderer); $rendered = $renderer->render($model); $options = $model->getOptions(); if (array_key_exists('has_parent', $options) && $options['has_ parent']) { return $rendered; } $event->setResult($rendered); $events->trigger(ViewEvent::EVENT_RESPONSE, $event); }

179

Lobjet de type ViewEvent, qui est linstance de lvnement dans le composant de vue, est initialis avec la requte et la rponse de notre application: Zend/View/View.php
public function render(Model $model) { $event = $this->getEvent(); [] }

Zend/View/View.php
protected function getEvent() { $event = new ViewEvent(); $event->setTarget($this); if (null !== ($request = $this->getRequest())) { $event->setRequest($request); } if (null !== ($response = $this->getResponse())) { $event->setResponse($response); } return $event; }

La mthode de rendu rcupre ensuite lobjet capable de restituer la vue depuis la stratgie que lon a dfinie, grce au lancement de lvnement renderer: Zend/View/View.php
public function render(Model $model) { [] $events = $this->getEventManager(); $results = $events->trigger(ViewEvent::EVENT_RENDERER, $event, function($result) { return ($result instanceof Renderer); }); [] }

Noublions pas que dans le Zend Framework 2, chacun des composants est ddi une tche. La classe Zend\View\View agit comme un point dentre capable de grer les composants de rendu. Il pilote lobjet responsable du rendu et partage le rsultat avec lvnement courant.

180

Les vues Chapitre 12

Afin de rcuprer lobjet qui va restituer la vue, la mthode lance lvnement renderer, cout par les classes responsables des objets de rendu de vue. Les stratgies de rendu se trouvent dans lespace de nom Zend\View\Strategy. Lcouteur notifi est lobjet PhpRendererStrategy, stratgie de rendu par dfaut. Nous verrons plus tard quil est possible de la modifier. La classe PhpRendererStrategy retourne l'objet de rendu qui lui est associ, de type PhpRenderer: Zend/View/Strategy/PhpRendererStrategy.php
public function selectRenderer(ViewEvent $e) { return $this->renderer; }

Rappelons-nous que cest le gestionnaire de vues qui attache les vnements de la classe PhpRendererStrategy lors de l'initialisation de la vue: Zend/Mvc/View/Http/ViewManager.php
public function getView() { [] $this->view->getEventManager()->attach($this->getRendererStrategy()); [] }

Zend/Mvc/View/Http/ViewManager.php
public function getRendererStrategy() { [] $this->rendererStrategy = new PhpRendererStrategy( $this->getRenderer() ); [] }

La mthode orchestrant le rendu vrifie ensuite si la vue possde des vues filles, afin de les rendre suivant lordre dimbrication pour les injecter dans le contenu des vues parentes: Zend/View/View.php
public function render(Model $model) { [] if ($model->hasChildren() && (!$renderer instanceof Renderer\TreeRendererInterface || !$renderer->canRenderTrees()) ) { $this->renderChildren($model); } [] }

Les vues Chapitre 12

181

La mthode contrle lexistence de vues filles et vrifie si la vue principale est capable de grer le rendu de son arborescence elle-mme. Lobjet PhpRenderer ne peut pas grer lui-mme le rendu de son arbre de vues: Zend/View/Renderer/PhpRenderer.php
private $__renderTrees = false; public function canRenderTrees() { return $this->__renderTrees; }

La classe JsonRenderer par exemple, est capable de traiter elle-mme le rendu de son arbre de vues au format JSON: Zend/View/Renderer/JsonRenderer.php
public function canRenderTrees() { return true; }

Chacune des vues filles est ensuite rendue individuellement en appelant cette mme mthode rcursivement en passant par la mthode renderChildren(): Zend/View/View.php
protected function renderChildren(Model $model) { foreach ($model as $child) { [] $child->setOption('has_parent', true); $result = $this->render($child); $child->setOption('has_parent', null); $capture = $child->captureTo(); if (!empty($capture)) { $model->setVariable($capture, $result); } } }

Cette mthode injecte les vues enfants dans la variable de la vue parente correspondant la valeur du retour de la mthode captureTo() de la vue enfant: Zend/View/Model/ViewModel.php
class ViewModel implements ModelInterface { protected $captureTo = 'content'; [] }

Le contenu est inject par dfaut dans la variable content, ce qui explique linstruction de rendu dans la vue de layout:

182

Les vues Chapitre 12

Fichier de layout
<?php echo $this->content; ?>

Lobjet de vue et de rendu sont ensuite enregistrs dans lvnement de la vue pour laisser la possibilit des couteurs intervenant plus tard dy avoir accs: Zend/View/View.php
public function render(Model $model) { [] $event->setModel($model); $event->setRenderer($renderer); [] }

Notons que lobjet de vue est nouveau enregistr au cas o il aurait chang entretemps. Lobjet de type PhpRenderer peut alors restituer la vue depuis sa mthode render(): Zend/View/View.php
public function render(Model $model) { [] $rendered = $renderer->render($model); [] }

Zend/View/Renderer/PhpRenderer.php
public function render($nameOrModel, $values = null) { if ($nameOrModel instanceof Model) { $model = $nameOrModel; $nameOrModel = $model->getTemplate(); [] $options = $model->getOptions(); foreach ($options as $setting => $value) { $method = 'set' . $setting; if (method_exists($this, $method)) { $this->$method($value); } unset($method, $setting, $value); } unset($options); $helper = $this->plugin('view_model'); $helper->setCurrent($model); $values = $model->getVariables(); unset($model); } $this->addTemplate($nameOrModel); unset($nameOrModel); $this->__varsCache[] = $this->vars();

Les vues Chapitre 12


f (null !== $values) { i $this->setVars($values); } unset($values); __vars = $this->vars()->getArrayCopy(); $ if (array_key_exists('this', $__vars)) { unset($__vars['this']); } extract($__vars); unset($__vars); hile ($this->__template = array_pop($this->__templates)) { w $this->__file = $this->resolver($this->__template); [] ob_start(); include $this->__file; $this->__content = ob_get_clean(); } $this->setVars(array_pop($this->__varsCache)); return $this->getFilterChain()->filter($this->__content); }

183

Dans notre exemple, le paramtre reu par la mthode render() est un objet de type ViewModel. Le nom du fichier de vue est alors rcupr et la liste des options du modle de vue parcourue, afin de pouvoir modifier les attributs de la classe: Zend/View/Renderer/PhpRenderer.php
public function render($nameOrModel, $values = null) { [] $options = $model->getOptions(); foreach ($options as $setting => $value) { $method = 'set' . $setting; if (method_exists($this, $method)) { $this->$method($value); } unset($method, $setting, $value); } unset($options); [] }

Les options de rendu peuvent tre passes en argument la vue lors de sa cration, dans une de nos actions par exemple: Exemple dajout doptions
public function indexAction() { $filterChain = new \Zend\Filter\FilterChain(); $filterChain->attachByName('StripTags'); $filterChain->attachByName('StringToUpper'); eturn new ViewModel(array('key'=>'value'), array('filterchain'=>$filte r rChain)); }

Dans cet exemple, la vue sera filtre aprs le rendu avec la chane de filtres, et

184

Les vues Chapitre 12

supprimera donc tous les tags avant de passer tous les caractres en majuscules. Attention, comme les vues filles sont rendues avant la vue principale ce filtre sera aussi appliqu la vue parente si celle-ci ne redfinit pas ses propres options. Il est donc ncessaire de passer une nouvelle instance de la classe FilterChain au layout si lon ne souhaite pas la suppression des tags sur la vue principale: Exemple dajout doptions
public function indexAction() { $event = $this->getEvent(); $model = $event->getViewModel(); $model->setOptions(array('filterchain'=>new \Zend\Filter\ FilterChain)); filterChain = new \Zend\Filter\FilterChain(); $ $filterChain->attachByName('StripTags'); $filterChain->attachByName('StringToUpper'); eturn new ViewModel(array('key'=>'value'), array('filterchain'=>$filte r rChain)); }

La mthode de rendu se charge de peupler le plugin view_model par linstance de vue courante et le nom du template de vue est ensuite enregistr dans un tableau interne qui sera dpil afin de rendre chacun de ses templates: Zend/View/Renderer/PhpRenderer.php
public function render(Model $model) { [] $this->addTemplate($nameOrModel); [] }

Les variables de vue dfinies dans nos actions sont extraites comme variables locales: Zend/View/Renderer/PhpRenderer.php
public function render($nameOrModel, $values = null) { [] $this->__varsCache[] = $this->vars(); if (null !== $values) { $this->setVars($values); } unset($values); $__vars = $this->vars()->getArrayCopy(); f (array_key_exists('this', $__vars)) { i unset($__vars['this']); } extract($__vars); unset($__vars); [] }

Les vues Chapitre 12

185

Lextraction de ce tableau au sein de notre mthode permet daccder aux variables de vue comme des variables locales: Accs depuis la vue
<?php echo $key; ?>

Ou comme un attribut grce la magie : Zend/View/Renderer/PhpRenderer.php


public function __get($name) { $vars = $this->vars(); return $vars[$name]; }

Accs depuis la vue


<?php echo $this->key; ?>

Ou comme un attribut grce la magie: Zend/View/Renderer/PhpRenderer.php


public function __get($name) { $vars = $this->vars(); return $vars[$name]; }

Accs depuis la vue


<?php echo $this->key; ?>

Les variables existantes de linstance courante sont sauvegardes dans un tableau $__varsCache, ce qui permettra de les restaurer par la suite. La classe PhpRenderer sapprte maintenant rendre la vue. Pour cela, il doit localiser le template de vue afin den rcuprer le contenu: Zend/View/Renderer/PhpRenderer.php
public function render($nameOrModel, $values = null) { [] while ($this->__template = array_pop($this->__templates)) { $this->__file = $this->resolver($this->__template); if (!$this->__file) { [] } ob_start(); include $this->__file; $this->__content = ob_get_clean(); } [] }

186

Les vues Chapitre 12

Dans notre exemple, le tableau de templates contient seulement celui qui est associ laction courante dont il faut rsoudre le chemin correspondant: Zend/View/Renderer/PhpRenderer.php
public function render($nameOrModel, $values = null) { [] while ($this->__template = array_pop($this->__templates)) { $this->__file = $this->resolver($this->__template); [] }

La tche de localisation de template est dlgue la classe Zend\View\Resolver\ AggregateResolver, spcialise dans la rsolution des noms de templates. Cette classe est initialise dans le gestionnaire de vues et comporte deux objets de rsolution de noms: Zend/Mvc/View/Http/ViewManager.php
public function getResolver() { if (null === $this->resolver) { $this->resolver = $this->services->get('ViewResolver'); } return $this->resolver; }

Zend/Mvc/Service/ViewResolverFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { $resolver = new ViewResolver\AggregateResolver(); $resolver->attach($serviceLocator->get('ViewTemplateMapResolver')); $resolver->attach($serviceLocator->get('ViewTemplatePathStack')); return $resolver; }

La classe AggregateResolver liste les objets capables de rsoudre les noms de template, et les parcourt jusqu trouver lobjet qui peut localiser le fichier souhait. Dans notre exemple, cette classe de rsolution contient deux objets de type ResolverInterface: lobjet de type TemplateMapResolver qui associe un nom de fichier un chemin et lobjet de type TemplatePathStack qui base sa recherche partir dun chemin de rpertoire de vues. Notons que chacun peut tre configur avec les cls template_path_stack et template_map depuis la configuration comme on le voit depuis leur fabrique: Zend/Mvc/Service/ViewTemplatePathStackFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { [] if (is_array($config) && isset($config['view_manager'])) {

Les vues Chapitre 12

187

$config = $config['view_manager']; if (is_array($config) && isset($config['template_path_stack'])) { $stack = $config['template_path_stack']; } } $templatePathStack = new ViewResolver\TemplatePathStack(); $templatePathStack->addPaths($stack); return $templatePathStack; }

Zend/Mvc/Service/ViewTemplateMapResolverFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { $config = $serviceLocator->get('Config'); $map = array(); if (is_array($config) && isset($config['view_manager'])) { $config = $config['view_manager']; if (is_array($config) && isset($config['template_map'])) { $map = $config['template_map']; } } return new ViewResolver\TemplateMapResolver($map); }

Analysons le fonctionnement du premier composant enregistr, la classe TemplateMapResolver et sa mthode de rsolution de nom: Zend/View/Resolver/TemplateMapResolver.php
public function resolve($name, Renderer $renderer = null) { return $this->get($name); }

Zend/View/Resolver/TemplateMapResolver.php
public function get($name) { if (!$this->has($name)) { return false; } return $this->map[$name]; }

Ce composant de localisation de template se base sur un tableau qui associe un nom de fichier de vue un chemin. Il suffit donc de vrifier lexistence de ce nom de template afin de pouvoir retourner le chemin du fichier correspondant. Voici le tableau de correspondance construit dans notre configuration: module.config.php
<?php return array( 'view_manager' => array( []

188

Les vues Chapitre 12


'template_map' => array( 'application/layout/layout' => __DIR__ . '/../ view/layout/layout.phtml', ), [] ), );

Le layout est la seule vue enregistre, le composant ne trouvera donc pas la vue correspondant laction. Ce composant de rsolution de nom, bas sur un tableau associatif, est lobjet de localisation le plus performant, ce qui explique limportance quil soit le premier de la liste des classes de rsolution. Si cet objet ne sait pas rsoudre le nom du template reu, lobjet suivant sera notifi de la recherche. Examinons alors le comportement de la classe Zend\View\ Resolver\TemplatePathStack: Zend/View/Resolver/TemplatePathStack.php
public function resolve($name, Renderer $renderer = null) { [] $defaultSuffix = $this->getDefaultSuffix(); if (pathinfo($name, PATHINFO_EXTENSION) != $defaultSuffix) { $name .= '.' . $defaultSuffix; } foreach ($this->paths as $path) { $file = new SplFileInfo($path . $name); if ($file->isReadable()) { if (($filePath = $file->getRealPath()) === false && substr($path, 0, 7) === 'phar://') { $filePath = $path . $name; if (!file_exists($filePath)) { break; } } if ($this->useStreamWrapper()) { $filePath = 'zend.view://' . $filePath; } return $filePath; } } $this->lastLookupFailure = static::FAILURE_NOT_FOUND; return false; }

Le fichier est cherch parmi la liste des chemins enregistrs depuis notre configuration: module.config.php
<?php return array( 'view_manager' => array( [] 'template_path_stack' => array(

Les vues Chapitre 12


'application' => __DIR__ . '/../view', ), [] );

189

Nous avons vu prcdemment que linjection du template ajoute un nom de template sous la forme module/contrleur/action au modle de vue courant. Cependant, attention de ne pas croire que le fait dindiquer application en cl du tableau de configuration template_path_stack nous permet de crer un automatisme au sein du framework. Le tableau template_path_stack se contente denregistrer les valeurs des chemins comme nous avons pu le voir plus haut. La valeur de la cl na aucune importance, si ce n'est pour ne pas interfrer avec d'autres lors de la fusion des configurations entre modules. En effet, la mthode PHP array_replace_recursive() se contente de fusionner les tableaux en crasant les cls identiques. Lors de la recherche d'un fichier de vue, la classe TemplatePathStack va parcourir l'ensemble des chemins sans tenir compte des cls indiques. Cependant, si vous souhaitez crer un automatisme, il vous suffit de faire correspondre le nom de vos module/contrleur/action la mme hirarchie de dossiers et de fichiers dans votre rpertoire de vue. Si certains fichiers de vue ne respectent pas cette rgle, indiquez-les manuellement dans lobjet de rsolution TemplateMapResolver. Lorsque le chemin est trouv, celui est retourn lobjet PhpRenderer par lintermdiaire de lagrgateur de classes de localisation. Afin de pouvoir restituer la vue, lobjet de rendu inclut la vue dans son code quil capture depuis les contrleurs de buffers de sortie. Le fichier, excut par la fonction include, est captur et retourn directement dans la variable $__content. Zend/View/Renderer/PhpRenderer.php
public function render($nameOrModel, $values = null) { [] while ($this->__template = array_pop($this->__templates)) { $this->__file = $this->resolver($this->__template); [] ob_start(); include $this->__file; $this->__content = ob_get_clean(); } [] }

Les variables initiales sont ensuite rtablies loriginal pour le prochain rendu et la vue est filtre par lobjet de type FilterChain avant dtre rendue: Zend/View/Renderer/PhpRenderer.php
public function render($nameOrModel, $values = null) { [] $this->setVars(array_pop($this->__varsCache)); return $this->getFilterChain()->filter($this->__content); }

190

Les vues Chapitre 12

La vue est alors retourne lobjet de vue View: Zend/View/View.php


public function render(Model $model) { [] $rendered = $renderer->render($model); $options = $model->getOptions(); if (array_key_exists('has_parent', $options) && $options['has_ parent']) { return $rendered; } $event->setResult($rendered); $events->trigger(ViewEvent::EVENT_RESPONSE, $event); }

Lorsque la vue est rendue, la mthode vrifie le type grce l'attribut has_parent des options de la vue traite afin de sassurer quelle correspond la vue principale. Si ce nest pas le cas, le traitement est interrompu car cette mthode est appele rcursivement pour chacune des vues filles. Le rendu est ensuite inject dans le rsultat de lvnement de la vue. La mthode render() lance enfin lvnement response afin de notifier de la fin de son traitement et le besoin dinjection du rendu lobjet de rponse. Cet vnement est cout par la classe PhpRendererStrategy, attach depuis le gestionnaire de vues: Zend/View/Strategy/PhpRendererStrategy.php
public function attach(EventManagerInterface $events, $priority = 1) { $this->listeners[] = $events->attach(ViewEvent::EVENT_RENDERER, array($this, 'selectRenderer'), $priority); this->listeners[] = $events->attach(ViewEvent::EVENT_RESPONSE, $ array($this, 'injectResponse'), $priority); }

La mthode injectResponse() de la classe PhpRendererStrategy est alors notifie: Zend/View/Strategy/PhpRendererStrategy.php


public function injectResponse(ViewEvent $e) { $renderer = $e->getRenderer(); if ($renderer !== $this->renderer) { return; } $result = $e->getResult(); $response = $e->getResponse(); f (empty($result)) { i $placeholders = $renderer->plugin('placeholder'); $registry = $placeholders->getRegistry(); foreach ($this->contentPlaceholders as $placeholder) {

Les vues Chapitre 12


if ($registry->containerExists($placeholder)) { $result = (string) $registry>getContainer($placeholder); break; } } } $response->setContent($result); }

191

La mthode sassure que lobjet de rendu utilis correspond sa stratgie de restitution des vues afin de pouvoir traiter lvnement. Cest une prcaution prendre si lon utilise une stratgie de rendu particulire car la classe PhpRendererStrategy tant ajoute par dfaut, elle sera systmatiquement notifie de lvnement. Le rsultat de lvnement peut ensuite tre inject dans lobjet de rponse. Une vrification est tout de mme effectue avant linjection du rsultat afin de vrifier que celui-ci nest pas une chane vide. Dans le cas contraire, les principaux conteneurs de donnes sont utiliss afin dextraire leur contenu.

Rcapitulatif du rendu
Nous venons de dcrire la chane de traitement pour le rendu des vues, ce qui nous permet de bien assimiler le nouveau systme de gestion de vues du Zend Framework 2. Les explications et le code pass en revue tant assez dense, et les sauts de mthode en mthode un peu droutants, voici un schma rcapitulatif du processus de rendu: Prparation des vues:

192

Les vues Chapitre 12

Rendu des vues:

Les vues Chapitre 12

193

Une fois la vue rendue, un dernier couteur entre en jeu afin denvoyer la rponse, qui contient la vue, au client. Cet couteur, qui agit sur lvnement finish est le Zend\Mvc\View\SendResponseListener que lon a prsent rapidement au dbut de chapitre: Zend/Mvc/View/SendResponseListener.php
public function attach(EventManagerInterface $events) { $this->listeners[] = $events->attach(MvcEvent::EVENT_FINISH, array($this, 'sendResponse'), -10000); }

Zend/Mvc/View/SendResponseListener.php
public function sendResponse(MvcEvent $e) { $response = $e->getResponse(); if (!$response instanceof Response) { return false; } if (is_callable(array($response,'send'))) { return $response->send(); } }

Cet couteur possde lavantage de retourner automatiquement la vue au client. Lcouteur sattache avec une priorit faible sur lvnement, il suffit de le courtcircuiter sur une priorit plus haute afin de ne pas envoyer automatiquement la rponse au client.

Types de vue
Comme nous venons de le voir, la stratgie de rendu par dfaut est gre par lobjet de type PhpRendererStrategy qui utilise la classe PhpRenderer afin de restituer la vue. Cependant, il existe dautres stratgies de rendu, par exemple celle qui permet de retourner une vue au format JSON. Voici un exemple afin dillustrer l'utilisation de cette stratgie de rendu: Rendu de JSON
public function jsonAction() { $locator = $this->getServiceLocator(); $jsonRendererStrategy = $locator->get('ViewJsonStrategy'); $view = $locator->get('View'); $view->getEventManager()->attachAggregate($jsonRendererStrategy, 1000); eturn $model = new JsonModel(array( r 'key' => 'value' )); }

La stratgie de rendu correspondant au format JSON et lobjet de vue sont rcu-

194

Les vues Chapitre 12

prs afin de pouvoir attacher les couteurs de notre nouvelle stratgie avec une priorit plus importante que celle par dfaut. Cela nous permet de court-circuiter la classe PhpRendererStrategy, stratgie de rendu par dfaut. Nous devrons maintenant retourner un objet de type JsonModel dans nos actions si lon souhaite utiliser la stratgie de rendu JSON. Analysons lcouteur de slection de lobjet de rendu de la classe JsonStrategy: Zend/View/Strategy/JsonStrategy.php
public function selectRenderer(ViewEvent $e) { $model = $e->getModel(); if ($model instanceof Model\JsonModel) { return $this->renderer; } [] $accept = $headers->get('Accept'); f (($match = $accept->match('application/json, application/ i javascript')) == false) { return; } if ($match->getTypeString() == 'application/json') { return $this->renderer; } if ($match->getTypeString() == 'application/javascript') { if (false != ($callback = $request->getQuery()>get('callback'))) { $this->renderer->setJsonpCallback($callback); } return $this->renderer; } }

Lobjet de rendu de JSON nest utilis que si le modle en cours est de type JsonModel. Notons que la classe de stratgie de rendu vrifie les headers afin de valider lobjet de rendu si lobjet de vue nest pas de type ViewModel. Une autre possibilit nous permet donc de rendre une vue au format JSON tout en conservant un objet de type ViewModel: Rendu de JSON avec un objet de type ViewModel
public function jsonAction() { $locator = $this->getServiceLocator(); $jsonRendererStrategy = $locator->get('ViewJsonStrategy'); $view = $locator->get('View'); $view->getEventManager()->attachAggregate($jsonRendererStrategy, 1000); $this->getRequest()->getHeaders()->get('accept')>addMediaType('application/json'); eturn new \Zend\View\Model\ViewModel(array( r 'key' => 'value', 'state'=> 'ok' )); }

Les vues Chapitre 12

195

En modifiant les headers de la rponse, nous obtenons un rsultat JSON, mais au contenu lgrement diffrent: Rendu de la vue de type ViewModel
{"content":{"key":"value","state":"ok"}}

Voici le rendu avec l'utilisation de lobjet JsonModel: Rendu de la vue de type JsonModel
{"key":"value","state":"ok"}

Cette diffrence vient du fait que la mthode de rendu du JsonRenderer ne restitue pas une vue de type JsonModel de la mme manire que les autres: Zend/View/Renderer/JsonRenderer.php
public function render($nameOrModel, $values = null) { if ($nameOrModel instanceof Model) { if ($nameOrModel instanceof Model\JsonModel) { $values = $nameOrModel->serialize(); } else { $values = $this->recurseModel($nameOrModel); $values = Json::encode($values); } return $values; } [] }

Cependant, il y a peu dintrt changer les headers depuis une action afin den rcuprer du JSON. Cette fonctionnalit a t cre afin de pouvoir rcuprer un format JSON depuis une API ou un script utilisant une commande telle que: Forcer lutilisation dune vue JSON
curl -H Accept: application/json http://www.monsite.com/monurl

Nous savons maintenant parfaitement utiliser les diffrentes vues et stratgies de rendu.

Manipulation des vues


Lors du dveloppement dapplications Web, il est souvent ncessaire de dsactiver la vue ou le layout, ou bien de changer le template de vue au cours de laction. Maintenant que nous comprenons la logique interne, voici comment les manipuler. Dsactiver le layout Comme nous lavons vu prcdemment, il est possible de dsactiver linjection du modle de vue dans celui du layout depuis lattribut terminate de la vue cou-

196

Les vues Chapitre 12

rante. Il est donc possible de dsactiver le layout depuis les instructions: Dsactivation du layout
public function monAction() { $viewModel = new ViewModel(); $viewModel->setTerminal(true); return $viewModel; }

Dsactiver la vue Nous avons vu que la vue peut tre construite depuis un tableau ou le retour null, afin de fournir un objet de type Zend\View\Model lcouteur qui soccupe de linjection de template. Nous pouvons donc dsactiver la vue courante depuis les instructions: Dsactiver la vue
public function monAction() { return false; }

Modifier le template du layout Le gestionnaire dvnements du contrleur abstrait AbstractActionController dfinit comme identifiant lespace de nom du module courant: Zend/Mvc/Controller/AbstractActionController.php
public function setEventManager(EventManagerInterface $events) { $events->setIdentifiers(array( 'Zend\Stdlib\DispatchableInterface', __CLASS__, get_class($this), substr(get_class($this), 0, strpos(get_class($this), '\\')) )); $this->events = $events; $this->attachDefaultListeners(); return $this; }

Nous pouvons donc attacher des vnements sur le nom de notre module afin de changer le template du layout pour les contrleurs de ce module: Modification du template pour un module
class Module implements AutoloaderProvider { public function init(ModuleManager $moduleManager) { $sharedEvents = $moduleManager->getEventManager()>getSharedManager();

Les vues Chapitre 12


$sharedEvents->attach('MonModule', MvcEvent::EVENT_DISPATCH, function($e) { $controller = $e->getTarget(); $controller->plugin('layout')->setTemplate('layout/ application'); }, 100); } [] }

197

Modifier le template de vue Modification du template de vue


public function monAction() { $viewModel = new ViewModel(); $viewModel->setTemplate('index/other'); return $viewModel; }

La modification du template se fait facilement depuis la mthode setTemplate() qui lui est consacre. Dsactiver la vue et le layout Lobjet Application distribue la requte courante afin de pouvoir construire un objet de rponse avec le rsultat de celle-ci. Cependant, si un objet de rponse est reu par la classe Application, celui-ci naura pas le construire et pourra alors le retourner directement. La vue courante et le layout sont donc dsactivables depuis le retour de lobjet de rponse courante vide ou depuis un nouvel objet de rponse vide: Dsactiver la vue et le layout depuis un objet de rponse vide
public function monAction() { return new \Zend\Http\PhpEnvironment\Response(); }

Il est galement possible de retourner l'objet de rponse courant qui est vide lors de la distribution: Dsactiver la vue et le layout
public function monAction() { return $this->response; }

198

Les vues Chapitre 12

Les aides de vue


Les aides de vue offrent des fonctionnalits supplmentaires lors du rendu de laffichage des donnes, et permettent de factoriser du code que lon crit de manire rptitive. Nous passerons en revue quelques aides de vue prsentes dans le framework, la plupart dentre elles existent dj dans la version prcdente du framework. Analysons tout de suite les appels aux aides de vue depuis un template afin de comprendre le chemin effectu par chacun des appels. Voici comment utiliser une aide de vue avec laide Partial : Utilisation des aides de vue
echo $this->partial();

13

Lappel pour des aides de vue lies un sous-ensemble, comme la navigation, se fait comme ceci : Utilisation des aides de vue
echo $this->navigation()->breadcrumbs()->render();

Rappelons-nous que lorsque notre objet de rendu, de type PhpRenderer par dfaut, souhaite obtenir le rendu de la vue courante, celui-ci inclut le fichier de vue directement au sein de sa mthode : Zend/View/Renderer/PhpRenderer.php
public function render($nameOrModel, $values = null) { [] ob_start(); include $this->__file; $this->__content = ob_get_clean(); [] }

Lorsque laide de vue est appele depuis le fichier de vue, celle-ci est donc appele au sein de lobjet PhpRenderer, celui-ci venant dinclure le code de la vue dans sa mthode de rendu. Seulement, notre gestionnaire de rendus ne dispose pas de

200

Les aides de vue Chapitre 13

mthodes correspondant aux noms des aides de vue dans sa classe, celles-ci vont donc tre prises en charge par la mthode magique __call() : Zend/View/Renderer/PhpRenderer.php
public function __call($method, $argv) { $helper = $this->plugin($method); if (is_callable($helper)) { return call_user_func_array($helper, $argv); } return $helper; }

La mthode magique rcupre le plugin demand grce la mthode plugin() et appelle ensuite lobjet daide de vue comme une fonction de callback, ce qui dclenche alors lappel de la mthode __invoke() de lobjet. Il est donc possible de rcuprer et utiliser une aide de vue en supprimant ltape de la magie lors de la rcupration du plugin : Suppression de la magie lors de lappel
<?php $plugin = $this->plugin('partial'); echo $plugin('template.phtml'); ?>

Le premier plugin que lon va tudier est gnralement peu utilis et assez complexe prendre en main lors de la premire approche, il sagit du fil dAriane.

Le fil dAriane
Laide de vue du fil dAriane repose sur les notions de conteneur, en particulier sur le composant Zend\Navigation\Navigation. Ce composant hrite de lobjet Zend\Navigation\AbstractContainer : Zend/Navigation/Navigation.php
class Navigation extends AbstractContainer { public function __construct($pages = null) { [ ] if ($pages) { $this->addPages($pages); } } }

Lobjet Zend\Navigation\AbstractContainer reprsente un conteneur dobjet, qui lui-mme peut contenir des sous-objets de type Zend\Navigation\Page\AbstractPage qui tendent la classe AbstractContainer, ce qui explique la possibilit davoir un niveau dencapsulation infini. Nous obtenons donc des conteneurs imbriqus les uns dans les autres afin de former une hirarchie.

Les aides de vue Chapitre 13

201

Pour crer cette hirarchie, une fabrique existe et permet de construire le conteneur suivant les paramtres inscrits en configuration. Cette fabrique est la classe DefaultNavigationFactory qui tend AbstractNavigationFactory : Zend/Navigation/Service/AbstractNavigationFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { $pages = $this->getPages($serviceLocator); return new Navigation($pages); }

La fabrique cre les pages avant de retourner le conteneur. Voici la cration des pages : Zend/Navigation/Service/AbstractNavigationFactory.php
protected function getPages(ServiceLocatorInterface $serviceLocator) { if (null === $this->pages) { $configuration = $serviceLocator->get('Configuration'); if (!isset($configuration['navigation'])) { [] } if (!isset($configuration['navigation'][$this->getName()])) { [] } $application = $serviceLocator->get('Application'); $routeMatch = $application->getMvcEvent()->getRouteMatch(); $router = $application->getMvcEvent()->getRouter(); $pages = $this->getPagesFromConfig($configuration['navigati on'][$this->getName()]); $this->pages = $this->injectComponents($pages, $routeMatch, $router); } return $this->pages; }

La fabrique rcupre la configuration de lapplication ainsi que lobjet contenant les paramtres de la route et le router pour les injecter aux diffrentes pages qui les utilisent pour construire les routes. Une fois les pages construites et les injections faites, le composant peut alors tre construit et renvoy. Nous remarquons galement que les pages sont recherches sous la cl navigation et la sous-cl correspondant au nom de la fabrique, ici default : Zend/Navigation/Service/DefaultNavigationFactory.php
protected function getName() { return 'default'; }

Voici la configuration que nous pouvons crer :

202

Les aides de vue Chapitre 13

module.config.php
<?php return array( [] 'navigation' => array( 'default' => array( 'home' => array('type' => 'mvc', 'route' => 'home','active'=>true, 'label' => 'Home'), [] 'contact' => array('type' => 'mvc', 'route' => 'contact','active'=>false, 'label' => 'Contact'), ), ), );

Noublions pas dajouter la fabrique DefaultNavigationFactory au gestionnaire de services car celle-ci nest pas inscrite par dfaut : module.config.php
<?php return array( 'service_manager' => array( 'factories' => array( 'DefaultNavigation' => 'Zend\Navigation\Service\ DefaultNavigationFactory', ), ), [] );

Nos pages sont cres, et il est possible den ajouter dautres dynamiquement : module.config.php
public function categoriesAction() { $navigation = $this->getServiceLocator()->get('DefaultNavigation'); $navigation->current()->addPage( array('type'=>'mvc','active'=>true,'route'=>'category','label'= >'Cat gories') ); }

Notons quil existe un autre type de page, le Zend\Navigation\Page\Uri qui permet de spcifier directement une URL lors de la cration de la page : Ajout dune page de type URI
public function categoriesAction() { $navigation = $this->getServiceLocator()->get('DefaultNavigation'); $navigation->current()->addPage(array( 'type'=>'uri','active'=>true,'uri'=>'/ mapage','label'=>'Catgories')

Les aides de vue Chapitre 13


); }

203

La page est ajoute avec lintitul Catgories. Une fois les pages ajoutes, l'aide de vue utilisera la page qui est marque comme active pour dterminer quelles sont les pages afficher. Il n'est cependant pas ncessaire de forcer le paramtre active pour bnficier de ce comportement, les pages de type Mvc fournissent une mthode capable de dterminer automatiquement si celles-ci sont actives ou non depuis l'objet contenant les paramtres de route: Zend/Navigation/Page/Mvc.php
public function isActive($recursive = false) { if (!$this->active) { [] if ($this->routeMatch instanceof RouteMatch) { [] if (null !== $this->getRoute() && $this->routeMatch->getMatchedRouteName() === $this->getRoute() && (count(array_intersect_assoc($reqParams, $myParams)) == count($myParams)) ) { $this->active = true; return true; } } } return parent::isActive($recursive); }

Nous pouvons maintenant afficher le fil dAriane dans nos vues depuis ces instructions : Affichage du fil dAriane
<?php echo $this->navigation()->breadcrumbs(DefaultNavigation)>setMinDepth(0)->setLinkLast(true)->render(); ?>

Afin de pouvoir utiliser laide Breadcrumbs, il est ncessaire de lui fournir un nom ou objet de conteneur. Si le paramtre est une chaine de caractres, alors le gestionnaire de services tente de rcuprer le service qui lui correspond : Zend/View/Helper/Navigation/AbstractHelper.php
public function setContainer($container = null) { $this->parseContainer($container); $this->container = $container; return $this; }

204

Les aides de vue Chapitre 13

Zend/View/Helper/Navigation/AbstractHelper.php
protected function parseContainer(&$container = null) { [] if (is_string($container)) { [ ] $container = $sl->get($container); } [] }

Le passage du paramtre laide de vue implique la modification du conteneur depuis la mthode setContainer() qui gre le type de paramtre reu. Dans notre cas, nous nous rappelons que le service DefaultNavigation correspond la fabrique DefaultNavigationFactory qui renvoie un objet de navigation. Laide de vue Navigation est charge de rcuprer laide de type Navigation\Breadcrumbs. Ensuite, la premire mthode, setMinDepth(), sassure que larborescence affiche possde un niveau de profondeur suprieur zro, cest--dire sil y a au moins une page dans notre conteneur principal. La mthode setLinkLast() permet dindiquer si nous souhaitons afficher un lien sur le dernier lment du fil dAriane. Notre vue rendra alors : Rendu du fil dAriane
Accueil > Catgories

Si nous ne souhaitons pas de lien sur le dernier lment nous devrons passer le paramtre de la mthode setLinkLast() la valeur false : Suppression du lien sur le dernier lment
<?php echo $this->navigation()->breadcrumbs(DefaultNavigation)>setMinDepth(0)->setLinkLast(false)->render(); ?>

Le rendu sera alors : Affichage du fil dAriane


Accueil > Catgories

La mthode setMinDepth() dfinit une condition daffichage en imposant un niveau de profondeur minimum, ce qui donne la possibilit de grer la pertinence de laffichage du fil dAriane en fonction de la profondeur de la navigation. Dans lexemple prcdent, nous avions deux lments dans le fil dAriane, modifions maintenant la valeur du niveau de profondeur minimum requis : Modification du niveau de profondeur minimum
<?php echo $this->navigation()->breadcrumbs(DefaultNavigation)>setMinDepth(2)->setLinkLast(false)->render(); ?>

Les aides de vue Chapitre 13

205

Le fil dAriane ne saffiche alors qu partir dune hirarchie de trois lments minimum, laide de vue retournera donc une chane vide. Analysons maintenant lutilit de passer par laide de vue Navigation pour rcuprer celle correspondant au Breadcrumbs. Laide de vue Navigation dispose de son propre gestionnaire de plugins afin de charger les sous-aides de vue qui lui correspondent : Zend/View/Helper/Navigation/PluginManager.php
class PluginManager extends HelperPluginManager { protected $invokableClasses = array( 'breadcrumbs' => 'Zend\View\Helper\Navigation\Breadcrumbs', 'links' => 'Zend\View\Helper\Navigation\Links', 'menu' => 'Zend\View\Helper\Navigation\Menu', 'sitemap' => 'Zend\View\Helper\Navigation\Sitemap', ); }

En appelant la mthode breadcrumbs() sur lobjet Navigation, notre appel est redirig sur la mthode magique __call de laide de navigation : Zend/View/Helper/Navigation.php
public function __call($method, array $arguments = array()) { $helper = $this->findHelper($method, false); if ($helper) { [] return call_user_func_array($helper, $arguments); } [] }

Zend/View/Helper/Navigation.php
public function findHelper($proxy, $strict = true) { $plugins = $this->getPluginManager(); if (!$plugins->has($proxy)) { [] } $helper = $plugins->get($proxy); [] return $helper; }

Une autre aide de vue dpendant de la navigation peut tre intressante utiliser en complmentarit du fil dAriane, il sagit de laide lie la gnration du sitemap.

Le sitemap
Comme nous lavons vu prcdemment, laide de vue Sitemap fait partie des aides de vue dpendant de celle de la navigation, et utilise par dfaut notre conteneur Navigation sil est prsent dans le registre. Reprenons lexemple prcdent, et ajou-

206

Les aides de vue Chapitre 13

tons une action sitemap dans notre contrleur : Utilisation de laide de vue sitemap
public function sitemapAction() { $this->getResponse()->headers()->addHeaderLine('Content-Type: application/xml'); $viewModel = new ViewModel(); $viewModel->setTerminal(true); return $viewModel; }

Les headers de lapplication sont modifis afin dindiquer le type du contenu de retour et le layout dsactiv. Voici le code de notre vue : Utilisation de laide de vue du sitemap
<?php echo $this->navigation()->sitemap('DefaultNavigation')->render(); ?>

Le rendu sera donc le suivant : Rendu du sitemap


<urlset> <url> <loc>http://monsite.com/</loc> </url> [] <url> <loc>http://monsite.com/contact</loc> </url> </urlset>

Le sitemap retourne alors lensemble des pages que nous avons inscrites dans la configuration au chapitre prcdent. Plusieurs options soffrent alors nous. Soit nous crons toutes les pages de notre application depuis la configuration ou soit nous ajoutons toutes les pages uniquement lors de laction sitemap afin de raliser cette opration uniquement sur demande. Si nous choisissons la premire option, celle-ci ne va crer aucun effet de bord sur le fil dAriane, car cette aide de vue se base sur la valeur de lattribut $active des diffrentes pages. Pour des raisons de performances, nous pouvons prfrer linitialisation de nos pages dans la mthode sitemap() afin de ne pas charger un grand nombre de pages non utilises : Initialisation du conteneur de navigation
public function sitemapAction() { $this->getResponse()->headers()->addHeaderLine('Content-Type: application/xml'); $navigation = $this->getServiceLocator()->get('DefaultNavigation');

Les aides de vue Chapitre 13


$home = $navigation->current(); $home->addPage(array('type'=>'mvc','route'=>'blog')); $home->addPage(array('type'=>'mvc','route'=>'contact')); viewModel = new ViewModel(); $ $viewModel->setTerminal(true); return $viewModel; }

207

Les attributs $active et $label des pages nont pas dintrt ici car ils ne seront pas utiliss. Pour les sites volumineux, la cration du sitemap la vole depuis laide de vue ne conviendra certainement pas et il reste prfrable de les gnrer depuis des tches automatises ou de privilgier lutilisation dun cache.

Laide Layout et ViewModel


Certaines aides de vue permettent aussi davoir la main sur les instances de vue cres dans notre application, comme le layout ou le modle courant. Laide de type Layout donne laccs la vue correspondant au layout, ce qui nous permet la rcupration ou la modification des variables du layout depuis la vue courante. La vue courante est rendue par lobjet de type Renderer avant le layout. Il est donc possible d'accder la vue parente depuis la vue lie laction courante : Utilisation du layout depuis la vue courante
<?php $this->layout()->setVariable('test','passage depuis la vue'); ?>

La variable test devient alors une nouvelle variable du layout. Notons que nous pourrions avoir le mme rsultat en passant par laide de vue ViewModel qui permet davoir accs aux instances de vues, mre et fille: Utilisation du layout depuis la vue courante
<?php $this->viewModel()->getRoot()->setVariable('test','passage depuis la vue'); ?>

Si nous analysons le code de laide de vue Layout, nous comprenons que notre premier exemple excute les mmes instructions que lors du deuxime exemple : Zend/View/Helper/Layout.php
class Layout extends AbstractHelper { [] public function __invoke($template = null) { if (null === $template) { return $this->getRoot();

208

Les aides de vue Chapitre 13


} return $this->setTemplate($template); } rotected function getRoot() p { $helper = $this->getViewModelHelper(); if (!$helper->hasRoot()) { [] } return $helper->getRoot(); } [] }

La mthode __invoke fait appel la mthode getRoot() qui utilise laide de vue ViewModel afin davoir accs la vue principale. Une mthode permet aussi de modifier le fichier de la vue utilise par le layout depuis la vue courante : Modification du template de layout
<?php $this->layout()->setTemplate(layout/layout_2.phtml); ?>

Ce comportement est possible, car comme nous l'avons vu, la vue courante est rendue avant le template correspondant au layout.

Laide Partial
Laide de vue Partial permet de rendre dautres fichiers de vue, depuis la vue courante. Laide Partial rend un fichier dont le nom est pass en paramtre, la vue rendue naura accs quaux variables fournies en tableau du deuxime paramtre : Test de l'aide Partial
<?php echo $this->partial('index/bloc.phtml',array('param' => 'value')); ?>

Il est galement possible de passer un objet de type ArrayObject ou dfinissant une mthode toArray() l'objet en second paramtre. En effet, laide de vue accepte en paramtre un tableau ou un objet en rcuprant ses variables soit depuis une mthode toArray(), soit par introspection en utilisant la fonction get_object_vars() : Zend/View/Helper/Partial.php
public function __invoke($name = null, $model = null) { [] if (!empty($model)) { if (is_array($model)) {

Les aides de vue Chapitre 13


$view->vars()->assign($model); } elseif (is_object($model)) { if (null !== ($objectKey = $this->getObjectKey())) { $view->vars()->offsetSet($objectKey, $model); } elseif (method_exists($model, 'toArray')) { $view->vars()->assign($model->toArray()); } else { $view->vars()->assign(get_object_vars($model)); } } } return $view->render($name); }

209

Lorsque lon analyse le fonctionnement de laide de vue Partial, nous pouvons remarquer que le gestionnaire de rendus de vue courant est clon. En effet, cest bien lobjet PhpRenderer qui est clon, et non lobjet ViewModel. L'objet ViewModel ne reprsente maintenant qu'un conteneur, c'est l'objet de rendu qui gre les variables et aides de vue. Voici les instructions de clonage : Zend/View/Helper/Partial.php
public function __invoke($name = null, $model = null) { [] $view = $this->cloneView(); [] }

Zend/View/Helper/Partial.php
public function cloneView() { $view = clone $this->view; $view->setVars(array()); return $view; }

Lobjet de rendu est ensuite vid de ses variables, ce qui permet de ne pas interfrer avec les variables de la vue courante. La vue peut ensuite tre rendue. Cette manire de rendre une vue dcorrle du contexte en cours peut savrer pratique, car le dveloppeur a la maitrise des variables passes en paramtre. Cependant, ce processus fera lgrement baisser les performances, car lobjet de rendu doit tre clon, initialis et repeupl. Une deuxime possibilit de rendu est disponible en utilisant directement la fonction render() de lobjet de rendu. Il est alors ncessaire de faire attention la collision des noms de variables, celles dfinies dans la vue courante sont accessibles depuis le nouveau fichier de vue. Le rendu avec laide de vue Partial :

210

Les aides de vue Chapitre 13

Rendu depuis laide Partial


<?php echo $this->partial('index/bloc.phtml',array( 'param' => 'value', 'param2' => 'value2' )); ?>

Ce mme rendu depuis la mthode render() : Rendu depuis la mthode render()


<?php $vars = $this->vars(); $vars['param'] = 'value'; $vars['param2'] = 'value2'; echo $this->render('index/bloc.phtml'); ?>

Le rendu avec lutilisation de la magie est parfois plus pratique : Rendu depuis la mthode render()
<?php $this->param = 'value'; $this->param2 = 'value2'; echo $this->render('index/bloc.phtml'); ?>

Le rendu peut aussi se faire en utilisant les mthodes de lobjet de rendu : Rendu depuis la mthode render()
<?php $this->declarevars(array('param'=>'value'),array('param2'=>'val ue2')); echo $this->render('index/bloc.phtml'); ?>

Lutilisation que lon fera de laide de vue Partial ou de lutilisation de la mthode render() dpend du contexte de lapplication. Il est cependant prfrable dutiliser la mthode render() lorsque le contexte nous le permet, celle-ci sera plus performante que laide de vue Partial.

Le rendu dlment HTML


Dans la liste des lments HMTL gnrs, les aides de vue proposent le rendu dlments comme les objets Flash ou QuickTime. Voici un exemple de rendu d'objet Flash :

Les aides de vue Chapitre 13

211

Rendu dun lment de type Flash


<?php echo $this->htmlFlash('monflash.swf', array('width' => 800, => 400), array('transparent' => 'wmode')); ?> 'height'

Et voici un exemple de rendu d'objet QuickTime : Rendu dun lment de type QuickTime
<?php echo $this->htmlQuicktime('monfilm.mov'); ?>

Le rendu dlment HTML est simple mettre en uvre, mais il est peut-tre parfois plus pratique que ces lments soient intgrs directement par les quipes de dveloppeurs frontend, cest pourquoi ces aides sont peu utilises.

Le rendu de JSON
Le framework fournit une aide de vue afin de gnrer du format JSON. Cette aide de vue peut tre utilise facilement pour crire des blocs de code JSON dans nos vues HTML ou afin de rendre une vue entire dans ce format. Laide de vue JSON permet de modifier automatiquement les headers de notre objet de rponse : Zend/View/Helper/Json.php
public function __invoke($data, array $jsonOptions = array()) { $data = JsonFormatter::encode($data, null, $jsonOptions); if ($this->response instanceof Response) { $headers = $this->response->headers(); $headers->addHeaderLine('Content-Type', 'application/json'); } return $data; }

Linitialisation de laide de vue JSON se fait simplement avec lobjet de rponse de lapplication au sein de la classe Module et linitialisation de la vue : Module.php
public function initializeView($e) { $app = $e->getTarget(); $locator = $app->getServiceManager(); $renderer = $locator->get('ViewManager')->getRenderer(); $renderer->plugin('json')->setResponse($app->getResponse()); }

Une fois lobjet de rponse pass laide de vue, celle-ci peut alors modifier les headers de la rponse afin dindiquer que le rendu de notre application sera au format JSON. Voici le code de laction dans notre exemple :

212

Les aides de vue Chapitre 13

Rendu de JSON
public function jsonrenderAction() { $view = new ViewModel(array('content'=>array('value1','value2'))); $view->setTerminal(true); return $view; }

Notre vue contient simplement : Rendu de JSON


<?php echo $this->json($this->content); ?>

Grce cette aide de vue, nous connaissons maintenant une troisime manire de rendre une vue au format JSON.

Laide Url
Laide de vue Url du framework ne possde quune seule mthode qui se base sur les identifiants de route afin de retourner lURL correspondante. Laide de vue est donc dpendante de lobjet de router et de lobjet contenant les paramtres de routes pour assembler les routes demandes. Cet objet est inject dans la fabrique du gestionnaire de vues depuis une fabrique spcialise : Zend/Mvc/Service/ViewHelperManagerFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { [] $plugins->setFactory('url', function($sm) use($serviceLocator) { $helper = new ViewHelper\Url; $helper->setRouter($serviceLocator->get('Router')); $match = $serviceLocator->get('application') ->getMvcEvent() ->getRouteMatch(); if ($match instanceof RouteMatch) { $helper->setRouteMatch($match); } return $helper; }); [] }

Laide de vue est alors prte fonctionner, nous pouvons lutiliser : Cration dune route
<?php echo $this->url('home');?>

Si le nom de notre route nest pas nul, le router se contente de faire appel la

Les aides de vue Chapitre 13

213

mthode assemble() afin de crer lURL : Zend/View/Helper/Url.php


public function __invoke($name = null, array $params = array(), $options = array(), $reuseMatchedParams = false) { [] $options['name'] = $name; return $this->router->assemble($params, $options); }

Si la route passe en paramtre a pour valeur null, laide de vue utilise lobjet RouteMatch retourn par le router afin dutiliser la route courante : Zend/View/Helper/Url.php
public function __invoke($name = null, array $params = array(), $options = array(), $reuseMatchedParams = false) { if ($name === null) { [] $name = $this->routeMatch->getMatchedRouteName(); [] } [] $options['name'] = $name; return $this->router->assemble($params, $options); }

Nous pouvons maintenant crire dans notre vue : Cration de lURL courante
<?php echo $this->url();?>

LURL de la route courante est alors gnre. Il est conseill de toujours utiliser cette aide de vue pour grer ses URL dans les diffrentes vues afin de toujours avoir la main sur les URL des routes depuis les fichiers de configuration.

Cration dune aide de vue


Lorsque vous dveloppez votre application, vous pouvez avoir besoin de crer une aide de vue afin de factoriser le code des vues. La premire tape est de faire tendre votre classe de la classe de base Zend\View\Helper\AbstractHelper qui vous donnera accs lobjet de rendu : Zend/View/Helper/AbstractHelper.php
abstract class AbstractHelper implements HelperInterface { protected $view = null; ublic function setView(Renderer $view) p {

214

Les aides de vue Chapitre 13


$this->view = $view; return $this; } ublic function getView() p { return $this->view; } }

Une fois laide de vue crite, il ne reste plus qu lenregistrer auprs du gestionnaire de plugins. Cette opration peut se faire depuis la mthode getViewHelperConfiguration() de la classe de module : Module.php
class Module implements ViewHelperProviderInterface { [] public function getViewHelperConfig() { return array( 'invokables' => array( 'title' => 'Zend\View\Helper\HeadTitle', ), ); } }

Il est galement possible dinscrire laide de vue depuis le fichier de configuration du module sous le nom de cl view_helpers : module.config.php
return array( [] 'view_helpers' => array( 'invokables' => array( 'title' => 'Zend\View\Helper\HeadTitle' ), ), );

Pour plus dinformations sur la gestion interne de la configuration et les cls de configuration disponibles pour l'enrichissement du gestionnaire de services, reportez-vous au chapitre consacr aux modules. Les exemples ci-dessus permettent dajouter un alias au plugin HeadTitle que lon peut alors rcuprer depuis la vue : Vue de layout
<?php echo $this->title('Titre de mon application') ?>

Le cur du framework
La plupart des composants de Zend Framework 2 ont t passs en revue, il est maintenant temps de nous plonger au cur du framework afin danalyser le processus de bootstrap et de lancement de lapplication. Il est indispensable aux dveloppeurs de bien comprendre le fonctionnement interne du framework afin den tirer pleinement profit, doptimiser les performances de leur application et de pouvoir rpondre toutes les problmatiques auxquelles ils pourraient tre confronts. Comprendre les points dentres et fonctionnement internes du framework nous permettra de dvelopper plus facilement la couche mtier de notre application Web. Par exemple, lextension du Zend_Application dans la version1 du framework pouvait parfois savrer trs judicieuse afin de permettre quelques optimisations propres notre application. De plus, connatre les rouages du fonctionnement interne vous donnera les cls pour dboguer plus rapidement notre application et comprendre le cheminement effectu jusquau problme rencontr.

14

Initialisation des autoloaders


Afin de pouvoir travailler avec le framework et nos propres composants, il est ncessaire de configurer la gestion du chargement de classes. La premire instruction de notre fichier index.php est la configuration des autoloaders : index.php
chdir(dirname(__DIR__)); include 'init_autoloader.php';

La premire instruction permet de toujours utiliser les chemins relatifs et de ne plus utiliser de chemin absolu afin de simplifier la gestion du chargement de classes. Cela permet de faciliter la vie du dveloppeur, lapplication entire peut alors tre dplace comme on le souhaite. Analysons linitialisation du chargement automatique de classe :

216

Le cur du framework Chapitre 14

init_autoloader.php
if (file_exists('vendor/autoload.php')) { $loader = include 'vendor/autoload.php'; } if (($zf2Path = getenv('ZF2_PATH') ?: (is_dir('vendor/ZF2/library') ? 'vendor/ZF2/library' : false)) !== false) { f (isset($loader)) { i $loader->add('Zend', $zf2Path . '/Zend'); } else { include $zf2Path . '/Zend/Loader/AutoloaderFactory.php'; Zend\Loader\AutoloaderFactory::factory(array( 'Zend\Loader\StandardAutoloader' => array( 'autoregister_zf' => true ) )); } }

Les premires instructions vrifient lexistence dun autoloader afin de pouvoir lutiliser. Si celui-ci existe, le namespace du framework est directement ajout la classe de chargement, sinon la fabrique AutoloaderFactory utilisera lautoloader standard afin de charger les classes du framework. Lutilisation du paramtre autoregister_zf permet denregistrer automatiquement la bibliothque, comme nous le voyons dans le code de lautoloader : Zend/Loader/StandardAutoloader.php
class StandardAutoloader implements SplAutoloader { [] const AUTOREGISTER_ZF = 'autoregister_zf'; ublic function setOptions($options) p { [ ] foreach ($options as $type => $pairs) { switch ($type) { case self::AUTOREGISTER_ZF: if ($pairs) { $this->registerNamespace('Zend', dirname(__DIR__)); } break; [] } } }

Une fois le chargement de classe configur, le framework est fonctionnel et linitialisation de l'application peut commencer. Notez que le fichier init_autoloader. php est une proposition faite par les dveloppeurs afin de bnficier d'un autoloader rapide, mais libre vous de le modifier partiellement ou compltement suivant les besoins de votre application.

Le cur du framework Chapitre 14

217

Le bootstrap
Le premier lment intervenir lors du processus de lancement de lapplication est la mthode de bootstrap qui initialise les ressources ncessaires au processus MVC du framework afin de pouvoir laisser la classe Zend\Mvc\Application travailler, vritable chef dorchestre du framework. Lapplication est dabord initialise par la mthode statique init() qui soccupe de grer le chargement des modules, de construire le gestionnaire de services ainsi que de crer linstance de lapplication afin de lancer le processus de bootstrap : index.php
Zend\Mvc\Application::init(include 'config/application.config.php')>run();

Zend/Mvc/Application.php
public static function init($configuration = array()) { $smConfig = isset($configuration['service_manager']) ? $configuration['service_manager'] : array(); serviceManager = new ServiceManager(new Service\ServiceManagerConfigu $ ration($smConfig)); $serviceManager->setService('ApplicationConfiguration', $configuration); $serviceManager->get('ModuleManager')->loadModules(); return $serviceManager->get('Application')->bootstrap(); }

Nous remarquons que le gestionnaire de services est initialis avec la cl service_ manager de la configuration passe en paramtre. Il est donc possible dinitialiser le gestionnaire de services depuis son fichier de configuration : application.config.php
return array( [] 'service_manager' => array( 'invokables' => array( 'ma-fabrique' => 'My\Factory', ), ), );

La configuration est ensuite enregistre comme service sous la cl ApplicationConfiguration avant que le gestionnaire de modules ne charge les modules et enregistre leur configuration. Pour plus de dtails sur le chargement des modules, reportez-vous au chapitre consacr aux modules. Nous pourrons donc accder notre configuration depuis le gestionnaire de services et la cl ApplicationConfiguration. Afin dinitialiser lapplication, le gestionnaire de services utilise la fabrique sous lidentifiant Application disponible dans les fabriques du framework :

218

Le cur du framework Chapitre 14

Zend/Mvc/Service/ServiceListenerFactory.php
class ServiceListenerFactory implements FactoryInterface { protected $defaultServiceConfig = array( [], 'factories' => array( 'Application' => 'Zend\Mvc\Service\ ApplicationFactory', 'Config' => 'Zend\Mvc\Service\ ConfigFactory', 'ControllerLoader' => 'Zend\Mvc\Service\ ControllerLoaderFactory', [] ), [] ); [] }

La fabrique ddie la construction de la classe Application est la classe Zend\ Mvc\Service\ApplicationFactory qui se contente de passer la configuration de lapplication son constructeur : Zend/Mvc/Service/ApplicationFactory.php
class ApplicationFactory implements FactoryInterface { public function createService(ServiceLocatorInterface $serviceLocator) { return new Application($serviceLocator->get('Configuration'), $serviceLocator); } }

La classe Application enregistre la configuration ainsi que linstance du gestionnaire de services : Zend/Mvc/Application.php
public function __construct($configuration, ServiceManager $serviceManager) { $this->configuration = $configuration; $this->serviceManager = $serviceManager; $this->setEventManager($serviceManager->get('EventManager')); this->request $ $this->response } = $serviceManager->get('Request'); = $serviceManager->get('Response');

Le constructeur initialise aussi lobjet de rponse et de requte qui lui seront ncessaires, et rcupre les instances du gestionnaire de modules et dvnements. Une fois notre classe initialise, la mthode de bootstrap est ensuite appele :

Le cur du framework Chapitre 14

219

Zend/Mvc/Application.php
public static function init($configuration = array()) { [] return $serviceManager->get('Application')->bootstrap(); }

La mthode bootstrap() initialise alors la classe de gestion de la distribution des actions, le router ainsi que le gestionnaire de vues : Zend/Mvc/Application.php
public function bootstrap() { $serviceManager = $this->serviceManager; $events = $this->getEventManager(); $events->attach($serviceManager->get('RouteListener')); $events->attach($serviceManager->get('DispatchListener')); $events->attach($serviceManager->get('ViewManager')); this->event = $event = new MvcEvent(); $ $event->setTarget($this); $event->setApplication($this) ->setRequest($this->getRequest()) ->setResponse($this->getResponse()) ->setRouter($serviceManager->get('Router')); $events->trigger(MvcEvent::EVENT_BOOTSTRAP, $event); return $this; }

Lidentifiant RouteListener reprsente lagrgateur dcouteur Zend\Mvc\ RouteListener qui gre le routage de lapplication : Zend/Mvc/RouteListener.php
class RouteListener implements ListenerAggregateInterface { [] public function attach(EventManagerInterface $events) { $this->listeners[] = $events->attach(MvcEvent::EVENT_ROUTE, array($this, 'onRoute')); } [] }

Le routage est effectu lorsque lapplication dclenche lvnement route. Comme pour le router, lagrgateur Zend\Mvc\DispatchListener soccupe de grer la distribution de laction de lapplication :

220

Le cur du framework Chapitre 14

Zend/Mvc/DispatchListener.php
class DispatchListener implements ListenerAggregateInterface { [] public function attach(EventManagerInterface $events) { $this->listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH, array($this, 'onDispatch')); } [] }

Les classes RouteListener et DispatchListener sont marques comme invokables dans le gestionnaire de services, ce qui explique quaucune fabrique nexiste pour ces deux classes. En effet, les agrgateurs ne ncessitent pas dinitialisation particulire. Le dernier lment construit par la mthode de bootstrap est le gestionnaire de vues ViewManager qui initialise tous les lments ncessaires la gestion des vues : Zend/Mvc/View/Http/ViewManager.php
public function attach(EventManagerInterface $events) { $this->listeners[] = $events->attach(MvcEvent::EVENT_BOOTSTRAP, array($this, 'onBootstrap'), 10000); }

Le gestionnaire de vues attache tous les couteurs ncessaires la prparation, construction et rendu des vues sur lvnement bootstrap qui est lanc la fin de la mthode bootstrap() de la classe Application. Pour plus de dtails sur le gestionnaire de vues, rendez-vous au chapitre consacr aux vues. Le bootstrap construit alors lobjet dvnement utilis lors des diffrentes notifications avec les lments dont il a besoin, et lance enfin lvnement bootstrap : Zend/Mvc/Application.php
public function bootstrap() { [] $this->event = $event = new MvcEvent(); $event->setTarget($this); $event->setApplication($this) ->setRequest($this->getRequest()) ->setResponse($this->getResponse()) ->setRouter($serviceManager->get('Router')); $events->trigger(MvcEvent::EVENT_BOOTSTRAP, $event); return $this; }

Une fois tous les lments de lapplication initialiss, le processus principal peut alors dmarrer.

Le cur du framework Chapitre 14

221

Avec le Zend Framework 1


Une classe de bootstrap spcialise dans linitialisation est utilise afin de charger les ressources indiques par lutilisateur. Contrairement la premire version, Zend Framework 2 ninitialisera que les ressources qui seront ncessaires pour laction en cours et qui sont demandes explicitement par lutilisateur.

Le run
Le point dentre et de lancement de lapplication est la mthode run() de la classe Application : index.php
Zend\Mvc\Application::init(include config/application.config.php)>run();

Cette mthode rcupre dabord le gestionnaire dvnements ainsi que lobjet dvnement de la classe Application : Zend/Mvc/Application.php
public function run() { $events = $this->getEventManager(); $event = $this->getMvcEvent(); [] }

Lobjet dvnement est cr dans la mthode bootstrap(), alors que le gestionnaire dvnements est rcupr depuis le gestionnaire de services, ce qui permet de rcuprer linstance partage au sein de lapplication. Lors de la rcupration du gestionnaire dvnements, ses identifiants lui sont ajouts: Zend/Mvc/Application.php
public function setEventManager(EventManagerInterface $eventManager) { $eventManager->setIdentifiers(array( __CLASS__, get_called_class(), )); $this->events = $eventManager; return $this; }

L'identifiant Zend\Mvc\Application est ajout, ce qui permet alors dattacher statiquement des couteurs sur le gestionnaire dvnements partag avec cet identifiant pour couter la classe Application.

222

Le cur du framework Chapitre 14

Lvnement route est ensuite immdiatement lanc par la mthode run() de la classe Application, lcouteur onRoute() de lobjet RouteListener, attach dans le bootstrap est alors tre notifie : Zend/Mvc/Application.php
public function run() { $events = $this->getEventManager(); $event = $this->getMvcEvent(); $shortCircuit = function ($r) use ($event) { if ($r instanceof ResponseInterface) { return true; } if ($event->getError()) { return true; } return false; }; $result = $events->trigger(MvcEvent::EVENT_ROUTE, $event, $shortCircuit); [] }

Avec le Zend Framework 1


Dans la version1 du framework, la mthode run() du Zend_Application appelle la mthode dispatch() du Zend_Controller_Front. Cette classe cre les objets de requtes et de rponses, puis notifie les plugins avec la mthode routeStartup() avant deffectuer le routage. Chaque tche et chaque rle sont maintenant spars ce qui donne plus de sens chacun des composants. Suite lvnement route, lapplication est capable de savoir quel est le couple contrleur/action utiliser lors de la distribution. Ltape de vrification des routes est dtaille dans le chapitre consacr aux routes. La classe Application notifie ensuite ses couteurs de lvnement dispatch afin de lancer la distribution de laction de la requte : Zend/Mvc/Application.php
public function run() { [] $result = $events->trigger(MvcEvent::EVENT_DISPATCH, $event, $shortCircuit); [] }

Cet vnement notifie la mthode onDispatch() de lobjet DispatchListener, agrgateur spcialis dans la distribution.

Le cur du framework Chapitre 14

223

Avec le Zend Framework 1


Il nexiste pas dvnements dispatch.pre et dispatch.post dans le Zend Framework 2, ceci est li au fait que la mthode attach() permet de grer les priorits de la SplPriorityQueue, il suffit donc de jouer avec les priorits pour obtenir le mme effet que les mthodes preDispatch() et postDispatch() de la premire version.

La mthode onDispatch() rcupre les objets dont elle a besoin pour travailler: Zend/Mvc/DispatchListener.php
public function onDispatch(MvcEvent $e) { $routeMatch = $e->getRouteMatch(); $controllerName = $routeMatch->getParam('controller', 'not-found'); $application = $e->getApplication(); $events = $application->getEventManager(); $controllerLoader = $application->getServiceManager() >get('ControllerLoader'); [] }

Lobjet $routeMatch, cr lors du routage, enveloppe les noms du contrleur et de laction distribuer. Le nom du contrleur est rcupr ainsi que le gestionnaire de contrleurs ControllerManager qui tend le gestionnaire de plugins de base AbstractPluginManager. Pour plus de dtails sur le gestionnaire de plugins, reportez-vous au chapitre qui lui est consacr. Le gestionnaire de contrleurs est construit depuis la fabrique ControllerLoader dont voici la classe Zend\Mvc\ Service\ControllerLoaderFactory correspondante: Zend/Mvc/Service/ControllerLoaderFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { $controllerLoader = new ControllerManager(); $controllerLoader->setServiceLocator($serviceLocator); $controllerLoader->addPeeringServiceManager($serviceLocator); config = $serviceLocator->get('Config'); $ if (isset($config['di']) && isset($config['di']['allowed_controllers']) && $serviceLocator->has('Di')) { $diAbstractFactory = new DiStrictAbstractServiceFactory( $serviceLocator->get('Di'), DiStrictAbstractServiceFactory::USE_SL_BEFORE_DI ); $diAbstractFactory->setAllowedServiceNames($config['di'] ['allowed_controllers']); $controllerLoader->addAbstractFactory($diAbstractFactory); } return $controllerLoader; }

224

Le cur du framework Chapitre 14

Les premires instructions crent un gestionnaire de contrleurs avant dy injecter le gestionnaire de services et de le dfinir dans le primtre comme gestionnaire parent de lobjet cr hritant de la classe de base ServiceManager: Zend/Mvc/Service/ControllerLoaderFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { $controllerLoader = new ControllerManager(); $controllerLoader->setServiceLocator($serviceLocator); $controllerLoader->addPeeringServiceManager($serviceLocator); [] }

La mthode addPeeringServiceManager() enregistre le gestionnaire de services courant comme gestionnaire enfant du gestionnaire de contrleurs. Cette manire de faire permet dexploiter les possibilits du gestionnaire de services depuis un objet indpendant avec ses propres fabriques et identifiants de fabriques, tout en laissant la possibilit dutiliser le gestionnaire fils ou parent en cas de ncessit. Pour plus dinformations sur la gestion des gestionnaires de services imbriqus, reportez-vous au chapitre consacr au gestionnaire de services. Une fois le nouveau gestionnaire instanci, le composant dinjection de dpendance est ajout comme couche supplmentaire si celle-ci existe dans le gestionnaire courant: Zend/Mvc/Service/ControllerLoaderFactory.php
public function createService(ServiceLocatorInterface $serviceLocator) { [] if (isset($config['di']) && isset($config['di']['allowed_controllers']) && $serviceLocator->has('Di')) { $diAbstractFactory = new DiStrictAbstractServiceFactory( $serviceLocator->get('Di'), DiStrictAbstractServiceFactory::USE_SL_BEFORE_DI ); $diAbstractFactory->setAllowedServiceNames($config['di'] ['allowed_controllers']); $controllerLoader->addAbstractFactory($diAbstractFactory); } [] }

Le composant Di pour le ServiceManager est expliqu en dtail dans le chapitre sur le gestionnaire de services. Une fois le gestionnaire de services ddi aux contrleurs instanci, la distribution reprend en chargeant le contrleur de laction en cours:

Le cur du framework Chapitre 14

225

Zend/Mvc/DispatchListener.php
public function onDispatch(MvcEvent $e) { [] try { $controller = $controllerLoader->get($controllerName); } catch (ServiceNotFoundException $exception) { [] } $request = $e->getRequest(); $response = $application->getResponse(); f ($controller instanceof InjectApplicationEventInterface) { i $controller->setEvent($e); } try { $return = $controller->dispatch($request, $response); } catch (\Exception $ex) { [] } return $this->complete($return, $e); }

Le contrleur est instanci depuis la mthode get() du gestionnaire de services, et une vrification est faite sur le bon chargement du contrleur. Si celui-ci na pas pu tre charg, alors les instructions de la gestion des erreurs sont excutes. noter que le type derreur est choisi en fonction de lexception lance afin de pouvoir fournir l'utilisateur un descriptif derreur plus explicite.

Avec le Zend Framework 1


La distribution du Zend_Controller_Front gre les erreurs depuis les exceptions, et se contente de renvoyer aussi lexception si cela est prcis, sinon il renseigne lobjet de rponse de cette exception. La mthode sassure ensuite que le contrleur soit distribuable et injecte lvnement courant au contrleur avant de distribuer la requte: Zend/Mvc/DispatchListener.php
public function dispatch(MvcEvent $e) { [] if (!$controller instanceof DispatchableInterface) { [] } $request = $e->getRequest(); $response = $application->getResponse(); if ($controller instanceof InjectApplicationEventInterface) { $controller->setEvent($e); } try { $return = $controller->dispatch($request, $response); } [] }

226

Le cur du framework Chapitre 14

Une fois laction distribue et le retour de celle-ci obtenu, la mthode injecte ce contenu dans le rsultat de lobjet dvnement: Zend/Mvc/DispatchListener.php
public function dispatch(MvcEvent $e) { [] return $this->complete($return, $e); }

Zend/Mvc/DispatchListener.php
protected function complete($return, MvcEvent $event) { if (!is_object($return)) { if (ArrayUtils::hasStringKeys($return)) { $return = new ArrayObject($return, ArrayObject::ARRAY_ AS_PROPS); } } $event->setResult($return); return $return; }

La rponse de lcouteur est ensuite retourne au gestionnaire dvnements. Une fois la distribution effectue, la mthode run() rcupre la dernire rponse reue par le gestionnaire et linjecte lobjet de rponse: Zend/Mvc/Application.php
public function run() { [] $result = $events->trigger(MvcEvent::EVENT_DISPATCH, $event, $shortCircuit); $response = $result->last(); if ($response instanceof ResponseInterface) { $event->setTarget($this); $events->trigger(MvcEvent::EVENT_FINISH, $event); return $response; } response = $this->getResponse(); $ $event->setResponse($response); return $this->completeRequest($event); }

Le processus se termine avec la mthode completeRequest() qui lance successivement lvnement de rendu de vue, suivi de la notification de fin de traitement: Zend/Mvc/Application.php
public function run() { [] $response = $this->getResponse();

Le cur du framework Chapitre 14


$event->setResponse($response); return $this->completeRequest($event); }

227

Zend/Mvc/Application.php
protected function completeRequest(MvcEvent $event) { $events = $this->getEventManager(); $event->setTarget($this); $events->trigger(MvcEvent::EVENT_RENDER, $event); $events->trigger(MvcEvent::EVENT_FINISH, $event); return $event->getResponse(); }

Lvnement finish, reprsent par la constante MvcEvent::EVENT_FINISH, ne possde quun seul couteur avec lobjet Zend\Mvc\View\SendResponseListener et sa mthode sendResponse() qui permet dafficher la rponse pour le client: Zend/Mvc/View/SendResponseListener.php
public function attach(EventManagerInterface $events) { $this->listeners[] = $events->attach(MvcEvent::EVENT_FINISH, array($this, 'sendResponse'), -10000); }

Zend/Mvc/View/SendResponseListener.php
public function sendResponse(MvcEvent $e) { $response = $e->getResponse(); if (!$response instanceof Response) { return false; } if(is_callable(array($response,'send'))){ return $response->send(); } }

Zend/Http/PhpEnvironment/Response.php
public function sendContent() { if ($this->contentSent()) { return $this; } echo $this->getContent(); $this->contentSent = true; return $this; }

228

Le cur du framework Chapitre 14

Zend/Http/PhpEnvironment/Response.php
public function send() { $this->sendHeaders() ->sendContent(); return $this; }

Avec le Zend Framework 1


La mthode dispatch() du contrleur frontal rend automatiquement la vue si on ne le lui spcifie pas le contraire depuis la mthode returnResponse() qui permet de modifier la valeur de lattribut de rendu. Le processus de bootstrap et de lancement de lapplication ont t modifis afin de mieux sparer et organiser toutes les tches, tout en laissant un peu plus de libert aux dveloppeurs avec la gestion des vnements.

Les composants
Le Zend Framework fournit tout un ensemble de composants que lon peut utiliser indpendamment de l'ensemble du framework ou depuis notre application. Certains composants de la premire version sont jugs peu performants ou mal crits, et le besoin de compatibilit ascendante fait quaucun changement majeur na t effectu. Cette deuxime version du framework permet de faire voluer ces composants qui ne rpondent plus aux besoins en termes de fonctionnalits ou de performances. Les dveloppeurs ont aussi profit de cette deuxime version pour effectuer des changements au niveau des diffrents dpts fournis par l'quipe. Si la premire version du Zend Framework englobait tous les composants, ce n'est plus le cas aujourd'hui. En effet, les composants agissant sur des services externes ne sont plus embarqus avec le code du framework. Ceci a t fait pour la simple et bonne raison que ce sont des produits avec des contraintes lies aux API des services qui leur sont rattachs. Une mise jour de l'API de Twitter ou Amazon obligeait le framework publier une nouvelle version de son code, bien que seul ce service soit mis jour. C'est donc pour grer plus facilement ce genre de contrainte que chaque service dpendant d'un service externe a t dissoci du framework. Vous retrouverez donc ces services sur le compte Github du zend framework qui seront mis jour en fonction des besoins et toujours maintenus.

15

Zend\Db
Ladaptateur de base de donnes
Ladaptateur de base de donnes permet de piloter la connexion la base de donnes depuis nimporte quel SGBD support (Mysqli, Pgsql, Sqlserver ou encore l'utilisation de Pdo). La construction de ladaptateur de base de donnes se fait simplement en passant en paramtre le driver souhait : Construction de ladaptateur la base de donnes
$adapater = new \Zend\Db\Adapter\Adapter(array('driver'=>'mysqli'));

Lobjet Zend\Db\Adapter\Adapter accepte un tableau de paramtres ou une ins-

230

Les composants Chapitre 15

tance de lobjet de driver, de type Zend\Db\Adapter\Driver\DriverInterface, en entre : Zend/Db/Adapter/Adapter.php


public function __construct($driver, Platform\PlatformInterface $platform = null, ResultSet\ResultSet $queryResultPrototype = null) { if (is_array($driver)) { $driver = $this->createDriverFromParameters($driver); } elseif (!$driver instanceof Driver\DriverInterface) { throw new Exception\InvalidArgumentException([...]); } [] }

Si nous passons un tableau en paramtre, comme dans lexemple, le constructeur se charge dinstancier lobjet : Zend/Db/Adapter/Adapter.php
protected function createDriverFromParameters(array $parameters) { [] $driverName = strtolower($parameters['driver']); switch ($driverName) { case 'mysqli': $driver = new Driver\Mysqli\Mysqli($parameters, null, null, $options); break; case 'sqlsrv': $driver = new Driver\Sqlsrv\Sqlsrv($parameters); break; case 'pgsql': $driver = new Driver\Pgsql\Pgsql($parameters); break; case 'pdo': default: if ($driverName == 'pdo' || strpos($driverName, 'pdo') === 0) { $driver = new Driver\Pdo\Pdo($parameters); } } [] return $driver; }

Nous remarquons que lobjet de driver Mysqli est instanci avec les paramtres passs au constructeur de ladaptateur. Le constructeur de la classe Mysqli instancie un objet de type Zend\Db\Adapter\Driver\Mysqli\Connection responsable de la gestion de la connexion la base de donnes : Zend/Db/Adapter/Driver/Mysqli/Mysqli.php
public function __construct($connection, Statement $statementPrototype = null, Result $resultPrototype = null, array $options = array()) {

Les composants Chapitre 15


if (!$connection instanceof Connection) { $connection = new Connection($connection); } [] }

231

Zend/Db/Adapter/Driver/Mysqli/Connection.php
public function __construct($connectionInfo = null) { if (is_array($connectionInfo)) { $this->setConnectionParameters($connectionInfo); } elseif ($connectionInfo instanceof \mysqli) { $this->setResource($connectionInfo); } [] }

Les paramtres de connexion la base de donnes sont conservs afin d'tablir la connexion lorsque celle-ci sera rellement ncessaire : Zend/Db/Adapter/Driver/Mysqli/Connection.php
public function connect() { [] $p = $this->connectionParameters; $findParameterValue = function(array $names) use ($p) { foreach ($names as $name) { if (isset($p[$name])) { return $p[$name]; } } return null; }; hostname = $findParameterValue(array('hostname', 'host')); $ $username = $findParameterValue(array('username', 'user')); $password = $findParameterValue(array('password', 'passwd', 'pw')); $database = $findParameterValue(array('database', 'dbname', 'db', 'schema')); $port = (isset($p['port'])) ? (int) $p['port'] : null; $socket = (isset($p['socket'])) ? $p['socket'] : null; $this->resource = new \Mysqli($hostname, $username, $password, $database, $port, $socket); [] }

Nous remarquons que nous pouvons utiliser plusieurs cls pour spcifier les valeurs de l'utilisateur, le mot de passe ou encore la base de donnes: Utilisation des noms de paramtres
public function connect() { [] $password = $findParameterValue(array('password', 'passwd', 'pw')); [] }

232

Les composants Chapitre 15

Le fait de pouvoir utiliser password, passwd ou pw offre un peu plus de souplesse l'utilisateur sans devoir se souvenir de la cl exacte pour configurer sa connexion. Nous pouvons alors passer la configuration de connexion dans le constructeur de ladaptateur: Construction dun adaptateur avec sa configuration
$adapater = new \Zend\Db\Adapter\Adapter(array( 'driver'=>'mysqli', 'hostname'=>'localhost', 'username'=>'root', 'password'=>'root' , 'database'=>'zf2' ));

Une fois le driver instanci, le constructeur se charge de vrifier lenvironnement en cours afin de contrler la bonne configuration des extensions PHP: Zend/Db/Adapter/Adapter.php
public function __construct($driver, Platform\PlatformInterface $platform = null, ResultSet\ResultSet $queryResultPrototype = null) { [] $driver->checkEnvironment(); $this->driver = $driver; [] }

Zend/Db/Adapter/Driver/Mysqli/Mysqli.php
public function checkEnvironment() { if (!extension_loaded('mysqli')) { throw new \Exception('The Mysqli extension is required for this adapter but the extension is not loaded'); } }

Lorsque les extensions ont t vrifies, le driver est enregistr. Ladaptateur construit ensuite son objet de gestion de plateforme qui lui permet de bnficier dune API propre celle-ci, qui sera utilise ensuite lors de la construction des requtes: Zend/Db/Adapter/Adapter.php
public function __construct($driver, Platform\PlatformInterface $platform = null, ResultSet\ResultSet $queryResultPrototype = null) { [] if ($platform == null) { $platform = $this->createPlatformFromDriver($driver); } $this->platform = $platform;

Les composants Chapitre 15


$this->queryResultSetPrototype = ($queryResultPrototype) ?: new ResultSet\ResultSet(); }

233

L'adaptateur est maintenant prt l'emploi et peut interroger directement la base de donnes depuis sa mthode query() en lui fournissant une chaine de caractres correspondant une requte SQL: Requte en base de donnes
$results = $adapater->query('SELECT * FROM matable',\Zend\Db\Adapter\ Adapter::QUERY_MODE_EXECUTE);

Lobjet retourn par la mthode query() est de type Zend\Db\ResultSet\ ResultSet, conteneur de rsultats par dfaut si celui-ci nest pas redfini dans les arguments du constructeur: Zend/Db/Adapter/Adapter.php
public function __construct($driver, Platform\PlatformInterface $platform = null, ResultSet\ResultSet $queryResultPrototype = null) { [] $this->queryResultSetPrototype = ($queryResultPrototype) ?: new ResultSet\ResultSet(); }

Zend/Db/Adapter/Adapter.php
public function query($sql, $parametersOrQueryMode = self::QUERY_ MODE_PREPARE) { [] if ($mode == self::QUERY_MODE_PREPARE) { [] } else { $result = $this->driver->getConnection()->execute($sql); } f ($result instanceof Driver\ResultInterface && $resulti >isQueryResult()) { $resultSet = clone $this->queryResultSetPrototype; $resultSet->initialize($result); return $resultSet; } return $result; }

Le deuxime paramtre de la fonction permet de spcifier le mode de prparation de la requte avant lenvoi en base de donnes. Ladaptateur offre aussi la possibilit dutiliser les spcificits de la plateforme utilise depuis l'objet de type PlatformInterface cr lors de sa construction: Utilisation de lAPI lie la plateforme
$sql = 'SELECT * FROM ' . $adapater->getPlatform()>quoteIdentifier('matable');

234

Les composants Chapitre 15

Requte SQL
SELECT * FROM `matable`

La prparation de la requte se ralise grce au driver enregistr ainsi qu' l'objet correspondant la plateforme: Prparation de requte
$sql = 'SELECT * FROM ' . $adapater->getPlatform()>quoteIdentifier('matable') . ' WHERE id = ' . $adapater->getDriver()>formatParameterName('id'); $statement = $adapater->query($sql); $parameters = array('id' => 1); $results = $statement->execute($parameters);

L'objet retourn par la mthode execute() est de type Zend\Db\Adapter\Driver\Mysqli\Result, capable d'tre parcouru par itrations.

Labstraction SQL
Les objets de types Zend\Db\Sql\* reprsentent des couches dabstraction qui permettent de gnrer des chanes SQL. Voici un exemple simple de ce qu'il est possible d'crire depuis ces objets: Utilisation de labstraction
$select = new \Zend\Db\Sql\Select(); $select->from('matable') ->where->equalTo('matable.id', '1'); echo $select->getSqlString();

Affichage de la sortie
SELECT "matable".* FROM "matable" WHERE "matable"."id" = '1'

Ils permettent aussi de crer des statements depuis ladaptateur: Utilisation de labstraction
$select = new \Zend\Db\Sql\Select(); $select->from('matable') ->where->equalTo('matable.id', '1'); $statment = $adapater->createStatement(); $select->prepareStatement($adapater, $statment); $results = $statment->execute(); $row = $results->current();

L'objet Zend\Db\Sql\Where, reprsentant les conditions sur les requtes est dsormais riche en fonctionnalits. Cette classe hrite de Zend\Db\Sql\Predicate\ Predicate reprsentant les prdicats:

Les composants Chapitre 15

235

Zend/Db/Sql/Predicate/Predicate.php
class Predicate extends PredicateSet { [] public function equalTo($left, $right, $leftType = self::TYPE_ IDENTIFIER, $rightType = self::TYPE_VALUE); ublic function lessThan($left, $right, $leftType = self::TYPE_ p IDENTIFIER, $rightType = self::TYPE_VALUE); [] public function literal($literal, $parameter); public function isNotNull($identifier); public function in($identifier, $valueSet = null); ublic function between($identifier, $minValue, $maxValue); p }

Il est dsormais plus facile de composer des clauses complexes avec la refonte des objets dabstraction SQL.

Linterface TableGateway
L'interface Zend\Db\TableGateway\TableGatewayInterface permet aux classes qui limplmentent de pouvoir reprsenter une table en base de donnes, voici ses prototypes: Zend/Db/TableGateway/TableGatewayInterface.php
interface TableGatewayInterface { public function getTableName(); public function select($where = null); public function insert($set); public function update($set, $where = null); ublic function delete($where); p }

La classe Zend\Db\TableGateway\TableGateway implmente cette interface et fournit des mthodes prtes lemploi afin deffectuer toutes les oprations usuelles sur une table en base de donnes: Utilisation de la classe TableGateway
$maTable = new \Zend\Db\TableGateway\TableGateway('matable', $adapater); $rowset = $maTable->select(array('id' => 1)); $row = $rowset->current();

Il est possible dutiliser lobjet de table avec les objets Zend\Db\Sql\Select:

236

Les composants Chapitre 15

Utilisation de la classe TableGateway


$maTable = new \Zend\Db\TableGateway\TableGateway('matable', $adapater); $select = new \Zend\Db\Sql\Select(); $select->from('matable') ->where->equalTo('matable.id', '1'); $rowset = $maTable->selectWith($select); $row = $rowset->current();

Il est galement possible dutiliser les closures dans la mthode select() de la classe: Zend/Db/TableGateway/AbstractTableGateway.php
public function select($where = null) { if (!$this->isInitialized) { $this->initialize(); } $select = $this->sql->select(); if ($where instanceof \Closure) { $where($select); } elseif ($where !== null) { $select->where($where); } return $this->selectWith($select); }

La mthode initialize() permet de construire les objets ncessaires la confection des requtes SQL. En effet, ceux-ci se ralisent la demande afin de ne pas construire des objets qui ne seraient pas utiliss. Si le paramtre dentre est de type \Closure, la fonction anonyme sera alors utilise de la mme manire que l'objet de type Zend\Db\Sql\Select: Utilisation des closures
$maTable = new \Zend\Db\TableGateway\TableGateway('matable', $adapater); $rowset = $maTable->select(function (\Zend\Db\Sql\Select $select) { $ select->where->equalTo('matable.id', '1'); }); $row = $rowset->current();

Le corps de la fonction permet de comprendre quil est aussi possible de passer des clauses directement la mthode, avec une chane de caractres par exemple: Ajout de clause sur la requte depuis une chaine
$maTable = new \Zend\Db\TableGateway\TableGateway('matable', $adapater); $rowset = $maTable->select('id = 1'); $row = $rowset->current();

Le tableau peut tre galement utilis:

Les composants Chapitre 15

237

Ajout de clause sur la requte depuis un tableau


$maTable = new \Zend\Db\TableGateway\TableGateway('matable', $adapater); $rowset = $maTable->select(array('id'=>'1')); $row = $rowset->current();

Une notation spciale pour la prparation de requte est disponible: Ajout de clause sur la requte depuis la notation de prparation
$maTable = new \Zend\Db\TableGateway\TableGateway('matable', $adapater); $rowset = $maTable->select(array('id > ?'=>1)); $row = $rowset->current();

Ces diffrentes possibilits sont consultables dans la mthode where() de la classe Select, utilise par dfaut dans lobjet TableGateway lors de lappel la mthode select(): Zend/Db/Sql/Select.php
public function where($predicate, $combination = Predicate\ PredicateSet::OP_AND) { if ($predicate instanceof Where) { $this->where = $predicate; } elseif ($predicate instanceof \Closure) { $predicate($this->where); } else { if (is_string($predicate)) { $predicate = new Predicate\Expression($predicate); $this->where->addPredicate($predicate, $combination); } elseif (is_array($predicate)) { foreach ($predicate as $pkey => $pvalue) { [] } } } return $this; }

Une autre classe hrite de TableGateway afin de permettre la gestion automatique de deux adaptateurs pour la distribution en lecture et criture sur un couple Master/Slave. Voici une description de cette classe nomme MasterSlaveTableGateway: Zend/Db/TableGateway/Feature/MasterSlaveTableGateway.php
class MasterSlaveTableGateway extends TableGateway { public function __construct(Adapter $slaveAdapter) { $this->slaveAdapter = $slaveAdapter; }

238

Les composants Chapitre 15


ublic function postInitialize() p { $this->masterAdapter = $this->tableGateway->adapter; } ublic function preSelect() p { $this->tableGateway->adapter = $this->slaveAdapter; } ublic function postSelect() p { $this->tableGateway->adapter = $this->masterAdapter; } }

Son utilisation est assez simple, il suffit de passer les deux adaptateurs correspondant au serveur matre ainsi qu' celui du serveur esclave la classe afin qu'elle puisse distribuer les requtes de lecture au slave et les critures au master. L'adaptateur correspondant au serveur esclave est utilis uniquement pour les requtes de type select grce aux mthodes preSelect() et postSelect() qui sont notifies avant l'excution de ces requtes: Zend/Db/TableGateway/AbstractTableGateway.php
protected function executeSelect(Select $select) { [] $this->featureSet->apply('preSelect', array($select)); [] $this->featureSet->apply('postSelect', array($statement, $result, $resultSet));` return $resultSet; }

La classe MasterSlaveTableGateway peut donc changer l'adaptateur pour les requtes de type select qu'elle peut distribuer au serveur esclave.

Linterface RowGateway
Le framework offre aussi la possibilit de reprsenter les lignes de rsultats en base de donnes par des objets. Linterface Zend\Db\RowGateway\RowGatewayInterface qui dfinit le contrat respecter pour ces objets possde deux mthodes: Zend/Db/RowGateway/RowGatewayInterface.php
interface RowGatewayInterface { public function save(); public function delete(); }

L'implmentation de cette interface permet d'agir sur les lignes de rsultats de requte en base de donnes. Par dfaut, les lignes de rsultats retournes par la mthode select() de lobjet TableGateway sont encapsules dans une instance

Les composants Chapitre 15

239

de Zend\Db\ResultSet\ResultSet: Zend/Db/TableGateway/TableGateway.php
public function __construct($table, Adapter $adapter, $features = null, ResultSetInterface $resultSetPrototype = null, Sql $sql = null) { [] $this->resultSetPrototype = ($resultSetPrototype) ?: new ResultSet;; [] }

La classe ResultSet implmente par dfaut un objet de type ArrayObject, avec la proprit ARRAY_AS_PROPS qui permet d'accder aux cls du tableau comme un objet , afin denvelopper chacune des lignes: Zend/Db/ResultSet/ResultSet.php
public function __construct($returnType = self::TYPE_ARRAYOBJECT, $arrayObjectPrototype = null) { $this->returnType = (in_array($returnType, array(self::TYPE_ARRAY, self::TYPE_ARRAYOBJECT))) ? $returnType : self::TYPE_ARRAYOBJECT; f ($this->returnType === self::TYPE_ARRAYOBJECT) { i $this->setArrayObjectPrototype(($arrayObjectPrototype) ?: new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS)); } }

Avec limplmentation par dfaut, nous ne pouvons donc pas intervenir sur la base de donnes depuis les lignes de rsultats. La classe RowGateway dfinit un conteneur de rsultats qui offre la possibilit dinteragir avec la base de donnes. Voici un exemple de son utilisation: Utilisation de la classe RowGateway
$featureset = new \Zend\Db\TableGateway\Feature\ RowGatewayFeature('id'); $maTable = new \Zend\Db\TableGateway\TableGateway('matable', $adapater, $featureset)$rowset = $maTable->select(array('id' => 1)); $row = $rowset->current(); $row['myfield'] = 'myupdate'; $affected = $row->save(); $rowset = $maTable->select(array('id' => 1)); $row = $rowset->current(); echo $row['myfield'];

Sortie de l'cran
myupdate

La deuxime instruction injecte un objet de type RowGateway dans le conteneur de rsultats afin quil soit utilis pour envelopper les diffrentes lignes de rsultats issues de la base de donnes travers la classe RowGatewayFeature qui injecte l'objet dsir:

240

Les composants Chapitre 15

Zend\Db\TableGateway\Feature\RowGatewayFeature.php
class RowGatewayFeature extends AbstractFeature { public function postInitialize() { [] if (isset($args[0])) { if (is_string($args[0])) { $primaryKey = $args[0]; $rowGatewayPrototype = new RowGateway($primaryKey, $this->tableGateway->table, $this->tableGateway->adapter, $this>tableGateway->sql); $resultSetPrototype->setArrayObjectPrototype($rowG atewayPrototype); } elseif ($args[0] instanceof RowGatewayInterface) { $rowGatewayPrototype = $args[0]; $resultSetPrototype->setArrayObjectPrototype($rowG atewayPrototype); } } [] }

Une fois l'objet cre avec la cl primaire passe en premier argument, l'objet RowGateway s'inscrit au sein du conteneur de rsultats pour envelopper les lignes de base de donnes que l'on va rcuprer. Il serait donc galement possible de modifier soi-mme le conteneur de lignes pour lui fournir un objet de type RowGateway, mais les objets du composant Zend/Db/TableGateway/Feature permettent de venir ajouter les fonctionnalits demandes au moment o elles seront ncessaires. L'instruction suivante effectue une requte en base de donnes, puis le premier lment de la liste de rsultats est rcupr dans la variable $row. La modification des donnes de la ligne de rsultats seffectue comme celle dun tableau. Cette manire de faire est autorise par linterface Zend\Db\ResultSet\RowObjectInterface, dont implmente RowGateway qui hrite de ArrayAccess, interface permettant d'accder aux proprits des objets de la mme faon que pour l'accs aux donnes des tableaux. La mthode save() permet ensuite la mise jour de la ligne de rsultats avec la base de donnes: Zend/Db/RowGateway/AbstractRowGateway.php
public function save() { [] $this->initialize(); if ($this->rowExistsInDatabase()) { [] $statement = $this->sql->prepareStatementForSqlObject($this>sql->update()->set($data)->where($where)); $result = $statement->execute(); $rowsAffected = $result->getAffectedRows(); [] else { $insert = $this->sql->insert(); $insert->values($this->data);

Les composants Chapitre 15


[] } $statement = $this->sql->prepareStatementForSqlObject($this->sql >select()->where($where)); $result = $statement->execute(); $rowData = $result->current(); unset($statement, $result); $this->populate($rowData, true); return $rowsAffected; }

241

La sauvegarde consiste faire une mise jour si la ligne existe en base de donnes et un insert si ce n'est pas le cas. Une fois cette opration effectue, un select est effectu pour rcuprer les donnes enregistres. L'intrt de faire un rafraichissement des donnes est de rcuprer les donnes rellement en base, car celles-ci peuvent changer si un trigger est utilis. Il n'est donc pas ncessaire de devoir rcuprer manuellement la nouvelle ligne de rsultats comme nous lavions fait dans notre exemple.

Zend\Cache
Le composant de cache du framework a t entirement revu afin de pouvoir mieux sparer les diffrentes responsabilits lies la cration du cache de lapplication. Les sous-composants qui grent le stockage des caches se trouvent dans lespace de nom Zend\Cache\Storage\Adaptateur o lon retrouve les principaux gestionnaires de stockage connus: APC, memcached, systme de fichiers, etc.

Les adaptateurs
La configuration des adaptateurs se ralise laide de classes doptions ddies chacun dentre eux. Prenons un exemple avec la gestion du cache par systme de fichiers: Utilisation du cache par fichiers
$cache = new \Zend\Cache\Storage\Adapter\Filesystem(); $cacheOptions = new \Zend\Cache\Storage\Adapter\FilesystemOptions( array('cache_dir'=>__DIR__ . '/data/') ); $cache->setOptions($cacheOptions); if(!$cache->hasItem('ma-cle')) { $cache->addItem('ma-cle','ma valeur'); }

Les classes doptions portent le nom de ladaptateur concern concatn avec le suffixe Options: ApcOptions, MemcachedOptions, etc., et chacune hrite de la classe Zend\Stdlib\Options qui offre la possibilit de recevoir un tableau doptions afin de peupler lobjet. Les cls du tableau doptions sont transformes en nom de mthode daltration, la proprit cache_dir est, par exemple, transforme en nom de mthode setCacheDir() o la valeur __DIR__ . '/data/' lui

242

Les composants Chapitre 15

est fournie. Pour plus de dtails, le chapitre6.1 sur la configuration des modules analyse le fonctionnement de la classe Zend\Stdlib\Options. Lorsque ladaptateur est instanci et configur, il est maintenant utilisable. Notre exemple ajoute la valeur ma valeur au fichier cr dans le cache. Celui-ci contient la valeur brute ma valeur que lon a dfinie. Afin de nous viter de devoir crer les diffrents objets adaptateurs et de les peupler manuellement, une fabrique de caches existe avec la classe Zend\Cache\StorageFactory qui permet de crer directement les adaptateurs et plugins depuis la fabrique: Utilisation de la fabrique de caches
$cache = \Zend\Cache\StorageFactory::factory(array( 'adapter' => array( 'name' => 'filesystem', 'options' => array('cache_dir'=>__DIR__ . '/data/') ), 'plugins' => array( 'exception_handler' => array('throw_exceptions' => false), ), )); $cache->addItem('ma-cle','ma valeur');

Dautres mthodes de rcupration, de suppression ou de mise jour de caches sont disponibles depuis ladaptateur et il est galement possible dagir sur le contenu de notre fichier de cache grce lutilisation des diffrents plugins.

Les plugins
Les plugins offrent des fonctionnalits supplmentaires ladaptateur de cache, et permettent aussi d'agir sur son contenu. Ils vont permettre, par exemple, la srialisation ou encore la dsactivation du lancement des exceptions gnres par les adaptateurs de cache. Prenons lexemple de deux plugins. Le plugin de gestion dexception Reprenons le premier exemple en dsactivant le lancement des exceptions, ce qui nous permet de ne pas contrler lexistence du fichier de cache: Gestion automatique des exceptions
$cache = new \Zend\Cache\Storage\Adapter\Filesystem(); $cacheOptions = new \Zend\Cache\Storage\Adapter\FilesystemOptions( a rray('cache_dir'=>__DIR__ . '/data/') ); $cache->setOptions($cacheOptions); $cachePlugin = new \Zend\Cache\Storage\Plugin\ExceptionHandler(); $cachePluginOptions = new \Zend\Cache\Storage\Plugin\PluginOptions( array('throw_exceptions'=>false) ); $cachePlugin->setOptions($cachePluginOptions); $cache->addPlugin($cachePlugin); $cache->addItem('ma-cle','ma valeur');

Les composants Chapitre 15

243

Aucune exception nest lance ici, mais la valeur de notre cache naura pas t mise jour. Si nous souhaitons nous assurer de la bonne suppression du cache pour la mise jour des donnes sans nous soucier des exceptions, il est ncessaire dajouter linstruction lie la suppression: Suppression avec la gestion des exceptions
$cache->removeItem('ma-cle'); $cache->addItem('ma-cle','ma valeur');

Le plugin de srialisation Le plugin de srialisation permet de srialiser automatiquement la valeur du cache en dfinissant un objet de srialisation de type Zend\Serializer\Adapter: Utilisation du plugin de srialisation
$cache = new \Zend\Cache\Storage\Adapter\Filesystem(); $cacheOptions = new \Zend\Cache\Storage\Adapter\FilesystemOptions( a rray('cache_dir'=>__DIR__ . '/data/') ); $cache->setOptions($cacheOptions); $cachePlugin = new \Zend\Cache\Storage\Plugin\Serializer(); $cachePluginOptions = new \Zend\Cache\Storage\Plugin\PluginOptions( a rray('serializer'=>new \Zend\Serializer\Adapter\PhpSerialize()) ); $cachePlugin->setOptions($cachePluginOptions); $cache->addPlugin($cachePlugin); $cache->addItem('ma-cle',' ma valeur');

Le fichier de cache contient maintenant la chane de caractres "s:10:" ma valeur";". De nombreux objets de srialisation sont disponibles dans le framework: Json, PhpCode, PhpSerialize, etc.

Analyse des plugins


Les plugins profitent pleinement des possibilits de gestion dvnements du framework. Chacun coute les vnements de l'adaptateur depuis la classe AbstractAdapter qui les notifient avant et aprs chacune des actions effectues. Ce fonctionnement permet aux plugins d'effectuer leurs modifications sur lobjet de stockage ou les valeurs de nos caches. Prenons comme exemple lajout d'un couple cl/valeur: Zend/Cache/Storage/Adapter/Filesystem.php
public function addItem($key, $value, array $options = array()) { $options = $this->getOptions(); if ($options->getWritable() && $options->getClearStatCache()) { clearstatcache(); } return parent::addItem($key, $value); }

244

Les composants Chapitre 15

Zend/Cache/Storage/Adapter/AbstractAdapter.php
public function addItem($key, $value, array $options = array()) { if (!$this->getOptions()->getWritable()) { return false; } $this->normalizeKey($key); $args = new ArrayObject(array( 'key' => & $key, 'value' => & $value, )); try { $eventRs = $this->triggerPre(__FUNCTION__, $args); if ($eventRs->stopped()) { return $eventRs->last(); } $result = $this->internalAddItem($args['key'], $args['value']); return $this->triggerPost(__FUNCTION__, $args, $result); } catch (\Exception $e) { $result = false; return $this->triggerException(__FUNCTION__, $args, $result, $e); } }

La mthode addItem() de la classe AbstractAdapter gre lajout dans le cache. Une fois les contrles et les normalisations effectus, la classe mre de notre adaptateur cre un tableau de paramtres contenant les rfrences de notre cl et valeur: Zend/Cache/Storage/Adapter/AbstractAdapter.php
public function addItem($key, $value, array $options = array()) { [] $args = new ArrayObject(array( 'key' => & $key, 'value' => & $value, )); [] }

Les variables tant enregistres dans notre tableau par leur rfrence, nous aurons donc accs aux valeurs modifies par les plugins depuis ces rfrences. Une fois cette opration effectue, un premier vnement de prtraitement est lanc afin de notifier les plugins qui souhaitent agir sur les contenus des variables avant enregistrement: Zend/Cache/Storage/Adapter/AbstractAdapter.php
public function addItem($key, $value, array $options = array()) { [] try { $eventRs = $this->triggerPre(__FUNCTION__, $args); if ($eventRs->stopped()) { return $eventRs->last(); } $result = $this->internalAddItem($args['key'], $args['value']); return $this->triggerPost(__FUNCTION__, $args, $result);

Les composants Chapitre 15


} catch (\Exception $e) { $result = false; return $this->triggerException(__FUNCTION__, $args, $result, $e); } }

245

Les deux mthodes qui permettent de lancer les vnements de prtraitement et de posttraitement sont triggerPre() et triggerPost(): Zend/Cache/Storage/Adapter/AbstractAdapter.php
protected function triggerPre($eventName, ArrayObject $args) { return $this->getEventManager()->trigger(new Event($eventName . '.pre', $this, $args)); }

Zend/Cache/Storage/Adapter/AbstractAdapter.php
protected function triggerPost($eventName, ArrayObject $args, &$result) { $postEvent = new PostEvent($eventName . '.post', $this, $args, $result); $eventRs = $this->getEventManager()->trigger($postEvent); if ($eventRs->stopped()) { return $eventRs->last(); } return $postEvent->getResult(); }

Ces deux mthodes sont responsables du lancement de lvnement correspondant au nom de la fonction suivi du suffixe .pre ou .post. Examinons quels vnements coutent les plugins et comment ceux-ci rpondent aux notifications. Les plugins senregistrent dabord ds leur passage ladaptateur: Zend/Cache/Storage/Adapter/AbstractAdapter.php
public function addPlugin(Plugin $plugin) { $registry = $this->getPluginRegistry(); if ($registry->contains($plugin)) { [] } $plugin->attach($this->getEventManager()); $registry->attach($plugin); return $this; }

Le registre interne stocke ensuite linstance du plugin afin de sassurer de son unicit au sein de ladaptateur. Ses couteurs sont ensuite attachs aux diffrents vnements. Prenons un exemple avec la classe Serializer:

246

Les composants Chapitre 15

Zend/Cache/Storage/Plugin/Serializer.php
public function attach(EventManagerInterface $events) { $index = spl_object_hash($events); if (isset($this->handles[$index])) { throw new Exception\LogicException('Plugin already attached'); } $handles = array(); $this->handles[$index] = & $handles; [] $handles[] = $events->attach('setItem.pre', array($this, 'onWriteItemPre')); $handles[] = $events->attach('setItems.pre', array($this, 'onWriteItemsPre')); $handles[] = $events->attach('addItem.pre', array($this, 'onWriteItemPre')); $handles[] = $events->attach('addItems.pre', array($this, 'onWriteItemsPre')); [] return $this; }

Le plugin Serializer sattache sur lvnement addItem.pre, ce qui lui permet dagir sur la valeur du cache avant son criture: Zend/Cache/Storage/Plugin/Serializer.php
public function onWriteItemsPre(Event $event) { $serializer = $this->getOptions()->getSerializer(); $params = $event->getParams(); foreach ($params['keyValuePairs'] as &$value) { $value = $serializer->serialize($value); } }

Lcouteur rcupre lobjet de srialisation afin de pouvoir agir sur la valeur du cache. Il nest pas ncessaire de retourner la valeur du cache modifie, celle-ci tant passe par rfrence, ladaptateur aura accs au contenu de la variable maintenant srialise. Il devient alors trs simple de crer son propre plugin de cache. Nos plugins personnaliss devront simplement tendre la classe de base Zend\Cache\Storage\ Plugin\AbstractPlugin afin de leur permettre de recevoir des options et de sattacher sur les vnements dont ils ont besoin pour leur travail.

Le composant Zend\Cache\Pattern
Le composant Zend\Cache\Pattern permet de rsoudre des problmatiques de performances prcises. Ce composant permet de cacher les retours de fonctions de callback ou dobjet. Cependant, ce composant tient compte du contexte et du rsultat du retour des mthodes des objets cachs. En effet, pour un mme objet et une mme mthode, si cette dernire retourne un rsultat diffrent, un objet de cache sera cr chaque fois.

Les composants Chapitre 15

247

Analysons un exemple avec un cache d'objet avec un stockage sous forme de mmoire: Utilisation du cache dobjet
$config = new \Zend\Config\Config(array('cle'=>'valeur')); $proxyConfig = new \Zend\Cache\Pattern\ObjectCache(); $proxyConfigOptions = new \Zend\Cache\Pattern\PatternOptions( array( 'object' => $config, 'storage' => new \Zend\Cache\Storage\Adapter\Memory() ) ); $proxyConfig->setOptions($proxyConfigOptions); $value = $proxyConfig->offsetGet('cle');

Au premier passage de ces instructions, un fichier de cache propre au retour de la fonction est gnr. Les prochains appels cette mthode de ce mme objet pourront alors utiliser ce fichier de cache. Ce cache peut savrer pratique lors de traitements longs , rcurrents et ayant des rsultats identiques. Cependant, il est ncessaire de sassurer que lobjet appel fonctionne dune manire identique chaque appel au risque de devoir gnrer des caches chaque passage. Afin de matriser la mise en cache des objets, il est ncessaire d'analyser la cration et la gestion du cache du composant. Lors de lappel l'objet Config depuis le composant ObjectCache, celui-ci gnre une cl base sur le contexte de lobjet afin que chaque mise en cache soit relie un contexte prcis en limitant les effets de bord comme on l'a vu lors de lexemple prcdent. En effet, il est ncessaire de vrifier la composition interne de lobjet, car une mthode ne retournera peut-tre pas le mme rsultat si un de ses attributs est modifi. Lors de la cration de la cl, gre par la classe parente Zend\Cache\Pattern\CallbackCache qui gnre le nom du cache, le composant srialise lobjet utilisqu'il pilote: Zend/Cache/Pattern/CallbackCache.php
protected function generateCallbackKey($callback, array $args) { [] $callbackKey = strtolower($callbackKey); [] if (isset($object)) { ErrorHandler::start(); try { $serializedObject = serialize($object); } [] $callbackKey.= $serializedObject; } return md5($callbackKey) . $this->generateArgumentsKey($args); }

La srialisation de l'objet tient compte des noms et valeurs des attributs. L'objet pilot doit alors tre identique d'un appel l'autre afin d'utiliser le cache gnr. La mise en cache des objets et de leurs valeurs de retour peut s'avrer trs pra-

248

Les composants Chapitre 15

tique lors de longs traitements relativement identiques. La gnration de cache et les vrifications que cela engendre ncessitent de ne pas en abuser et de pouvoir justifier lutilisation dun tel composant. Une mauvaise utilisation de ce cache peut savrer plus pnalisante que performante.

Zend\Console
Le composant Zend\Console offre une API qui permet facilement d'interagir avec le mode CLI de PHP: criture de ligne, nettoyage de la console de sortie, rcupration dargument ou dentre utilisateur, etc.

Utilisation basique de la console


Afin de comprendre le fonctionnement de la console, voici un exemple simple daffichage de ligne dans le terminal: Affichage dune ligne sur le terminal
$console = Console\Console::getInstance(); $console->writeLine('Hello world');

La classe Console est un singleton et possde une mthode statique permettant de rcuprer linstance de lobjet de console de sortie. Voici le code de cette mthode: Zend/Console/Console.php
public static function getInstance($forceAdapter = null, $forceCharset = null) { if (static::$instance instanceof Adapter\AdapterInterface) { return static::$instance; } if ($forceAdapter !== null) { if (substr($forceAdapter, 0, 1) == '\\') { [] } else { $className = __NAMESPACE__.'\\Adapter\\'.$forceAdapter; } [] } else { $className = static::detectBestAdapter(); } static::$instance = new $className(); if ($forceCharset !== null) { [] static::$instance->setCharset(new $className()); } return static::$instance; }

Nous remarquons que la mthode getInstance() pilote en ralit un objet du namespace Zend\Console\Adapter qui permet de faire la correspondance avec lenvironnement du dveloppeur. La mthode prend en premier paramtre le nom

Les composants Chapitre 15

249

de ladaptateur, nous aurions pu alors crire le nom de ladaptateur pour forcer linitialisation (lenvironnement de travail courant est Linux): Utilisation de lenvironnement courant
$console = Console\Console::getInstance(posix); $console->writeLine('Hello world');

Nous aurions pu passer directement par ladaptateur pour avoir le mme rsultat: Utilisation de ladapteur
$console = new Console\Adapter\Posix(); $console->writeLine('Hello world');

Il est videmment conseill de passer par la fabrique de la classe Console afin de ne conserver quune seule instance de ladaptateur. Si aucun argument nest pass en paramtre de la fabrique, la mthode statique detectBestAdapter() permet de connatre ladaptateur utiliser. Une fois la console initialise, nous pouvons utiliser son API, voici quelques possibilits: criture dune ligne en couleur
$console = Console\Console::getInstance(); $console->writeLine('Hello world', Console\ColorInterface::GREEN);

Nettoyage du terminal
$console = Console\Console::getInstance(); $console->clear();

Il est galement possible de rcuprer la saisie de lutilisateur: Rcupration de saisie


$console = Console\Console::getInstance(); $line = $console->readLine(); echo "Vous avez tape: " . $line . "\n";

La classe Console permet aussi de connatre le type de console utilise: Type de console
echo (integer)Console\Console::isConsole(); echo (integer)Console\Console::isWindows();

cran de sortie
10

250

Les composants Chapitre 15

Les classes dinteractions


Le composant Console possde un sous-composant Zend\Console\Prompt qui permet dinteragir avec lutilisateur en demandant la saisie dun texte, entier ou encore de choisir parmi une liste de rponses. Voici une mise en uvre simple: Saisie dun texte
$prompt = new Console\Prompt\Line('Entrez votre texte:'); $line = $prompt->show(); echo "Vous avez tape: " . $line . "\n";

Si lon observe le fonctionnement interne de la classe Console\Prompt\Line, nous remarquons que celui-ci se base sur la mthode readLine() de la classe Console que lon a prsente plus haut: Zend/Console/Prompt/Line.php
public function show() { do { $this->getConsole()->write($this->promptText); $line = $this->getConsole()->readLine($this->maxLength); } while (!$this->allowEmpty && !$line); return $this->lastResponse = $line; }

La console affiche la ligne correspondant la question que lon a passe en paramtre et retourne le rsultat de la mthode readLine() en sassurant que la ligne nest pas vide. Il est galement possible de rcuprer la saisie dun nombre: Saisie dun nombre
$prompt = new Console\Prompt\Number('Entrez un nombre:'); $number = $prompt->show(); echo "Vous avez tape: " . $number . "\n";

Notons que si la saisie ne correspond pas un nombre, un message derreur est automatiquement affich: Zend/Console/Prompt/Number.php
public function show() { do { $valid = true; $number = parent::show(); if ($number === "" && !$this->allowEmpty) { $valid = false; } elseif ($number === ""){ $number = null; } elseif (!is_numeric($number)){ $this->getConsole()->writeLine("$number is not a number\n"); $valid = false;

Les composants Chapitre 15

251

} [] } while (!$valid); if ($number !== null) { $number = $this->allowFloat ? (double)$number: (int)$number; } return $this->lastResponse = $number; }

Comme nous le voyons, la classe soccupe de grer les diffrents types derreur comme le type de la saisie mais encore le minimum et maximum que lon peut passer au constructeur de la classe: Utilisation du minimum et maximum
$prompt = new Console\Prompt\Number('Entrez un nombre:', false, false, 30, 40); $number = $prompt->show(); echo "Vous avez tape: " . $number . "\n";

Dans lexemple ci-dessus, lutilisateur doit alors taper un nombre plus petit ou gal 40 et plus grand ou gal 30. Le composant Zend\Console\Prompt permet aussi la saisie dune rponse parmi une liste de propositions: Liste de propositions
$prompt = new Console\Prompt\Select('Choisissez une reponse: ', array( '1' => '1er choix', ' 2' => '2e choix', )); $option = $prompt->show(); echo "Vous avez choisi: " . $option . "\n";

Notons quil est important de choisir un chiffre ou lettre pour la cl du tableau doptions, car la vrification porte sur les caractres taps par lutilisateur et prsents en cl du tableau doptions: Zend/Console/Prompt/Select.php
public function show() { $console = $this->getConsole(); $console->writeLine($this->promptText); foreach ($this->options as $k => $v) { $console->writeLine(' '.$k.') '.$v); } $mask = implode("",array_keys($this->options)); if ($this->allowEmpty) { $mask .= "\r\n"; } $this->setAllowedChars($mask); $oldPrompt = $this->promptText; $this->promptText = 'Pick one option: '; $response = parent::show(); $this->promptText = $oldPrompt;

252

Les composants Chapitre 15


return $response; }

La mthode show() affiche dabord la liste des options disponibles et marque comme caractres autoriss la concatnation de tous les caractres des cls du tableau: Zend/Console/Prompt/Select.php
public function show() { [] $mask = implode("",array_keys($this->options)); if ($this->allowEmpty) { $mask .= "\r\n"; } $this->setAllowedChars($mask); [] $response = parent::show(); [] }

La classe Select tend la classe Char qui vrifie si un des caractres prsents dans la liste des autoriss existe: Zend/Console/Prompt/Char.php
public function show() { [ ] if (stristr($this->allowedChars,$char)) { if ($this->echo) { echo trim($char)."\n"; } else { echo "\n"; } break; } } while (true); return $this->lastResponse = $char; }

Une autre possibilit est offerte par le framework avec la confirmation daction et la classe Zend\Console\Prompt\Confirm: Utilisation de confirmation
$prompt = new Console\Prompt\Confirm('Souhaitez-vous continuer ? (y/n)'); $continue = $prompt->show(); if(!$continue){ $console->writeLine("Good bye!"); exit(); }

Si la question est personnalisable, le choix de confirmation y/n lest aussi. Le constructeur attend en deuxime et troisime paramtres les deux caractres de confirmation:

Les composants Chapitre 15

253

Zend/Console/Prompt/Confirm.php
public function __construct( $promptText = 'Are you sure?', $yesChar = 'y', $noChar = 'n' ) { if ($promptText !== null) { $this->setPromptText($promptText); } if ($yesChar !== null) { $this->setYesChar($yesChar); } if ($noChar !== null) { $this->setNoChar($noChar); } }

Deux mthodes daltration sont aussi possibles pour ces caractres. La mthode show() vrifie ensuite lgalit avec le caractre correspondant la confirmation, y par dfaut: Zend/Console/Prompt/Confirm.php
public function show() { $response = parent::show() === $this->yesChar; return $this->lastResponse = $response; }

Labstraction de lenvironnement
Lorsque lon utilise le CLI de PHP, il peut tre ncessaire de rcuprer les arguments ou variables denvironnement. La classe Zend\Console\Request soccupe de grer cela pour vous: Utilisation de Zend\Console\Request
$request = new Console\Request(); $firstParameter = $request->getParams()->get(0); $scriptName = $request->getScriptName(); $environment = $request->env();

Notons que le premier paramtre du tableau nest pas le nom du script, qui est plac dans une variable part, mais bien les arguments que lon passe au script. Examinons la construction de cet objet: Zend/Console/Request.php
public function __construct(array $args = null, array $env = null) { if ($args === null) { if (!isset($_SERVER['argv'])) { [] } $args = $_SERVER['argv'];

254

Les composants Chapitre 15


} if ($env === null) { $env = $_ENV; } if (count($args) > 0) { $this->setScriptName(array_shift($args)); } $this->params()->fromArray($args); $this->setContent($args); $this->env()->fromArray($env); }

Nous remarquons que le nom du script est spar de la liste de ses arguments, et que la rcupration des variables est base sur les variables PHP $_ENV et $_SERVER. Pour finir la prsentation de ce composant, nous remarquerons que la classe Getopt est toujours prsente dans cette version du framework: Utilisation de Getopt
$getopt = new Console\Getopt(array( ' nom|n=s' => 'argument en chaine de caractre', )); $getopt->parse(); $nom = $getopt->getOption('nom'); echo "Votre nom est " . $nom . "\n";

Lancement du script php


console.php --nom=blanchon

Le composant Zend\Console rend le code beaucoup plus facile lorsque lon utilise le CLI de PHP. Tout ce que nous devions coder se trouve parfaitement intgr dans le framework pour une meilleure utilisation de la ligne de commande.

Le router pour la console


Si le composant de console peut parfaitement grer les interactions avec le terminal pour lcriture de scripts, il peut galement grer votre application depuis le terminal. En effet, un objet de requte et un router sont implments pour grer les interactions avec la console. Il est possible de dfinir des routes composes darguments pour la ligne de commande afin de piloter lapplication. Ce nouveau fonctionnement particulirement souple va permettre aux dveloppeurs davoir une API disponible pour grer ses actions ou tches automatises depuis la ligne de commande. Plus besoin de dveloppement spcifique, votre application est prte fonctionner depuis votre terminal. Examinons le code de la fabrique du router afin de comprendre comment est gre en interne la console:

Les composants Chapitre 15

255

Zend/Mvc/Service/RouterFactory.php
public function createService(ServiceLocatorInterface $serviceLocator, $cName = null, $rName = null) { $config = $serviceLocator->get('Configuration'); if( $ rName === 'ConsoleRouter' || ($cName === 'router' && Console::isConsole()) ){ if(isset($config['console']) && isset($config['console'] ['router'])) { $routerConfig = $config['console']['router']; } else { $routerConfig = array(); } $router = ConsoleRouter::factory($routerConfig); } else { [] } return $router; }

Lors de la cration du router, la fabrique vrifie si nous utilisons la console afin de pouvoir fabriquer le router qui lui correspond, de type Zend\Mvc\Router\ Console\SimpleRouteStack. Nous remarquons que les routes correspondant la console doivent tre enregistres sous la cl ['console']['routes']. Voici un exemple de configuration utilisant les routes pour la console: module.config.php
return array( 'console' => array( 'router' => array( 'routes' => array( 'catch-all' => array( 'type' => 'catchall', 'options' => array( 'route' => '', 'defaults' => array( 'controller' => 'Application\ Controller\Index', 'action' => 'error', ), ), ), 'cron-application' => array( 'type' => 'simple', 'options' => array( 'route' => '--run-cronapplication=value', 'defaults' => array( 'controller' => 'Application\ Controller\Index', 'action' => 'cron', ), ), ), ), ), ),);

256

Les composants Chapitre 15

La configuration des routes emploie les arguments utiliser lors du lancement du script. Examinons le router pour la console afin de voir comment se construisent les routes: Zend/Mvc/Router/Console/SimpleRouteStack.php
protected function init() { $routes = $this->routePluginManager; foreach(array( 'catchall' => __NAMESPACE__ . '\Catchall', 'simple' => __NAMESPACE__ . '\Simple', ) as $name => $class) { $routes->setInvokableClass($name, $class); }; }

Linitialisation du router nous apprend quil existe deux types de routes disponibles: le type Simple qui permet de dfinir des arguments en ligne de commande qui serviront de route ; le type Catchall qui se vrifie chaque fois. Si vous dfinissez une route de type Catchall, elle doit tre la dernire dans la pile afin de laisser les traitements spcifiques en premier et celle-l sexcuter si aucun nest trouv. Lajout des routes depuis un tableau fait en ralit appel au contrleur de base Zend\Mvc\Router\SimpleRouteStack que nous connaissons. Le seul point qui diffre est que les routes sont, par dfaut, de type Simple si aucun type nest dfini: Zend/Mvc/Router/Console/SimpleRouteStack.php
protected function routeFromArray($specs) { [] if(!isset($specs['type'])) $specs['type'] = 'simple'; $route = parent::routeFromArray($specs); [] }

Une fois les routes ajoutes, la correspondance se fera en fonction de la comparaison des arguments: arguments obligatoires prsents et nombre darguments. Il est donc possible de dfinir une liste darguments obligatoires avec dautres qui ne le sont pas, par exemple: Utilisation darguments facultatifs
return array( 'console' => array( 'router' => array( 'routes' => array( 'cron-application' => array( 'type' => 'simple', 'options' => array( 'route' => '--run-cronapplication=value [--other=value]',

Les composants Chapitre 15


} if ($env === null) { $env = $_ENV; } if (count($args) > 0) { $this->setScriptName(array_shift($args)); } $this->params()->fromArray($args); $this->setContent($args); $this->env()->fromArray($env); }

257

Une fois nos routes configures, il y a quelques modifications apporter nos modules et actions. Tout dabord, nous pouvons grer le cas o les arguments en ligne de commande ne correspondent aucune route depuis linterface Zend\ ModuleManager\Feature\ConsoleUsageProviderInterface qui permet de fournir une mthode dcrivant lutilisation de la console depuis le module correspondant: Module.php
class Module implements ConsoleUsageProviderInterface { [] public function getConsoleUsage(AdapterInterface $console) { return 'Use --run-cron-application=value.' . "\n"; } [] }

Le descriptif fourni par la mthode getConsoleUsage() sera affich dans le terminal si aucune route ne correspond: Utilisation de mauvais arguments
php index.php --unknow-arg Zend Framework 2.0.0rc2 application. Usage: Use --run-cron-application=value.

Attention, pour notre exemple, la route de type Catchall a t supprime. Si celle-ci est conserve, son couple contrleur/action sera excut.

La vue pour la console


Maintenant que nous savons grer laide en ligne de commande, voyons les modifications apporter nos actions pour interagir avec la console: IndexController.php
class IndexController extends AbstractActionController { public function cronAction() {

258

Les composants Chapitre 15


$param = $this->getRequest()->getParam('cron'); // rcupration du paramtre return new ConsoleModel(array(ConsoleModel::RESULT => 'ok')); } }

Tout dabord, il est possible de rcuprer les valeurs de nos arguments depuis la mthode getParam() de lobjet de requte qui est de type Zend\Console\ Request. Ensuite, la console utilise une vue de type Zend\View\Model\ConsoleModel. Cette vue est assez particulire car elle nutilise quune variable correspondant la cl result: Zend/View/Model/ConsoleModel.php
class ConsoleModel extends ViewModel { const RESULT = 'result'; [] public function setResult($text) { $this->setVariable(self::RESULT, $text); return $this; } ublic function getResult() p { return $this->getVariable(self::RESULT); } }

La vue nutilisera que cette variable pour rendre la vue depuis la mthode getResult(), comme nous le voyons depuis sa stratgie de rendu : Zend/Mvc/View/Console/DefaultRenderingStrategy.php
public function render(MvcEvent $e) { [] $responseText .= $result->getResult(); $response->setContent( $response->getContent() . $responseText ); [] }

Comme il peut tre un peu fastidieux de toujours crer une instance de lobjet ConsoleModel pour construire sa vue, la console bnficie aussi de ses couteurs prparateurs de vues. Pour plus dinformations sur la cration et prparation des vues, reportez-vous au chapitre consacr aux vues. En effet, ces couteurs permettent de construire une vue depuis une chaine de caractres: Zend/Mvc/View/Console/CreateViewModelListener.php
public function createViewModelFromString(MvcEvent $e) { $result = $e->getResult(); if (!is_string($result)) { return;

Les composants Chapitre 15


} $model = new ConsoleModel; $model->setVariable(ConsoleModel::RESULT, $result); $e->setResult($model); }

259

Si laction retourne une chaine de caractres, lcouteur construit et peuple automatiquement la vue avec la bonne variable. Nous pouvons donc rcrire notre action sous la forme simple: IndexController.php
class IndexController extends AbstractActionController { public function cronAction() { $param = $this->getRequest()->getParam('cron'); return ok; } }

La console affichera cette instruction en sortie. Nous venons de voir lutilisation de la console au sein de lapplication et toutes les possibilits que celle-ci offre en redfinissant ses propres couteurs et types de vues, ainsi que ses propres objets de requte ou encore de router. La maitrise de la console et de ce qui lentoure permettra aux dveloppeurs doptimiser la gestion de leurs scripts et tches automatises en ligne de commande.

Zend\Crypt
Le composant de cryptage a t rcrit afin de proposer une API plus simple et intuitive. Il est dsormais trs simple de produire des informations cryptes depuis les algorithmes de cryptage symtrique ou sens unique.

Hachage sens unique


Le sous-composant Zend\Crypt\Password permet deffectuer simplement un cryptage sens unique indchiffrable. Le cryptage sens unique est ralis depuis la mthode crypt() qui permet dutiliser un salage, identifiant salt, qui est pris en compte lors du chiffrement afin damliorer la scurit lie au cryptage. En effet, trop dapplications Web se basent uniquement sur la mthode php md5(), qui est moins scurise dans le sens o il est possible de retrouver un mot de passe avec la comparaison de la chaine crypte avec dautres chaines brouilles depuis la mme commande. Lajout dun identifiant salt permet dajouter une couche de scurit supplmentaire, il ne suffit plus de comparer des chaines cryptes afin de dcoder un mot de passe, il est ncessaire de connatre cet identifiant. Voici un exemple simple:

260

Les composants Chapitre 15

Cryptage sens unique


$bcrypt = new Zend\Crypt\Password\Bcrypt(); $bcrypt->setSalt('mylongsaltforsecurity'); $passwordCrypted = $bcrypt->create('mypassword'); echo $passwordCrypted;

Sortie l'cran
$2a$14$bXlsb25nc2FsdGZvcnNlYuRPAxktiNvSUqE4ijz4CgGOyN9J/.hle

Par mesure de scurit, la longueur du salt doit tre dau moins 16octets. La vrification dun mot de passe valide se fait depuis la mthode verify(): Vrification du cryptage
$valid = $bcrypt->verify('mypassword', $passwordCrypted); echo intval($valid);

Sortie l'cran
1

Le composant Crypt permet aussi le chiffrage double sens depuis la classe Zend\ Crypt\Symmetric\Mcrypt bas sur les fonctions mcrypt_*() de PHP.

Hachage symtrique
Le sous-composant Zend\Crypt\Symmetric permet le chiffrage double sens. Base sur les mthodes mcrypt_encrypt() et mcrypt_decrypt(), la classe Mcrypt ncessite une cl ainsi quun identifiant de salage pour fonctionner. Cryptage symtrique
$key = Zend\Crypt\Key\Derivation\Pbkdf2::calc('md5', 'my-key', 'mylongsaltforsecurity', 1, 32); $padding = new Zend\Crypt\Symmetric\Padding\Pkcs7(); $mcrypt = new Zend\Crypt\Symmetric\Mcrypt(); $mcrypt->setAlgorithm(MCRYPT_DES); $mcrypt->setSalt('mylongsalt'); $mcrypt->setKey($key); $mcrypt->setPadding($padding); $datasCrypted = $mcrypt->encrypt('long-datas-to-crypt'); $dataDecrypted = $mcrypt->decrypt($datasCrypted);

Le mme traitement peut tre effectu depuis un tableau de configuration: Cryptage symtrique depuis une configuration en tableau
$mcrypt = new Zend\Crypt\Symmetric\Mcrypt(array( 'algorithm' => MCRYPT_DES, ' salt' => 'mylongsalt', ' padding' => 'pkcs7', ' key' => Zend\Crypt\Key\Derivation\Pbkdf2::calc('md5', 'my-key', 'mylongsaltforsecurity', 1, 32), ));

Les composants Chapitre 15


$datasCrypted = $mcrypt->encrypt('long-datas-to-crypt'); $dataDecrypted = $mcrypt->decrypt($datasCrypted);

261

Le composant Zend\Crypt facilite grandement lusage des fonctions natives de cryptage pour les oprations les plus communes au sein du Zend Framework 2.

Zend\Form
Les formulaires de la premire version ont souvent fait dbat au sein des dveloppeurs: difficiles apprhender et mettre en uvre, avec l'utilisation des dcorateurs par exemple. Lquipe de dveloppement du Zend Framework a souhait revoir compltement lorganisation et la structure du composant.

Le formulaire, les fielsets et les lments


Le Zend Framework 2 modifie compltement larchitecture du composant de formulaire. L'lment de base et au centre de tout le composant est le Zend\Form\ Element. Lobjet Element possde des attributs et des messages, le nom de l'lment sera lui-mme un attribut avec pour cl name. Un lment est dcrit par linterface Zend\Form\ElementInterface qui lui est associe: Zend/Form/ElementInterface.php
interface ElementInterface { public function setName($name); public function getName(); public function setOptions($options); public function getOptions(); public function getOption($option); public function setAttribute($key, $value); public function getAttribute($key); public function hasAttribute($key); public function setAttributes($arrayOrTraversable); public function getAttributes(); public function setValue($value); public function getValue(); public function setLabel($label); public function getLabel(); public function setMessages($messages); public function getMessages(); }

La classe Zend\Form\Fieldset hrite de cette classe de base pour la reprsentation dun ensemble d'lments. Afin de grer la liste des lments, les objets Fieldset disposent de mthodes reprsentes dcrites par linterface Zend\Form\FieldsetInterface: Zend/Form/FieldsetInterface.php
interface FieldsetInterface extends Countable, IteratorAggregate, ElementInterface { public function add($elementOrFieldset, array $flags = array());

262

Les composants Chapitre 15


ublic p public public public public public public public public public public public public public } function function function function function function function function function function function function function function has($elementOrFieldset); get($elementOrFieldset); remove($elementOrFieldset); setPriority($elementOrFieldset, $priority); getElements(); getFieldsets(); populateValues($data); setObject($object); getObject(); allowObjectBinding($object); setHydrator(HydratorInterface $hydrator); getHydrator(); bindValues(array $values = array()); allowValueBinding();

Nous remarquons quune mthode getFieldsets() est disponible. En effet, les objets de type Fieldset peuvent contenir dautres objets du mme type. Ce systme de conteneur permet lajout dlments de mme nom dans des objets de type Fieldset diffrents, cette gestion sapproche du concept despaces de noms que lon connat. Chaque nom dlment ajout un conteneur de type Fieldset est englob dans lespace de nom du conteneur. Un formulaire doit pouvoir contenir plusieurs lments et avoir des attributs tels que lURL daction ou la mthode de passage des lments laction, il hrite donc de la classe Fieldset. La classe de formulaire Zend\Form\Form hrite de la classe Fieldset. Rcapitulons le rle de chacune de ces classes: Zend\Form\Element: reprsentation de base, dfinit des attributs et des messages; Zend\Form\Fieldset: hrite de Zend\Form\Element, ce conteneur reprsente un ensemble dobjets de type Element et/ou de type Fieldset; Zend\Form\Form : hrite de Zend\Form\BaseForm et gre la fabrique de formulaires, fieldsets et lments. La classe soccupe de la gestion du composant Zend\InputFilter du formulaire, qui contient filtres et validateurs. Afin de pouvoir crer et peupler facilement les formulaires, des composants dhydratations et une fabrique sont disponibles.

Les fabriques et hydrateurs


La fabrique utilise par dfaut dans le composant Zend\Form est de type Zend\ Form\Factory. La fabrique permet la cration dobjets de formulaires, fieldsets et lments depuis un tableau de description pass en paramtre: Cration dun lment depuis la fabrique
$fabrique = new Zend\Form\Factory(); $element = $fabrique->create(array( 'type' => 'Zend\Form\Element', 'name' => 'nom', 'attributes' => array( 'type' => 'text',

Les composants Chapitre 15


'label' => 'Nom:', ), ));

263

Il est galement possible dutiliser la fabrique dlments sans devoir indiquer le type dobjet que lon souhaite crer: Cration dun lment depuis la fabrique
$fabrique = new Zend\Form\Factory(); $element = $fabrique->createElement(array( 'name' => 'nom', 'attributes' => array( 'type' => 'text', 'label' => 'Nom:', ), ));

La fabrique est utilise lors de la cration dlments depuis la configuration en tableau au sein dun objet de formulaire: Formulaire utilisant la fabrique
class SimpleForm extends Form { public function __construct() { parent::__construct(); $this->setAttribute('method', 'post'); $this->add(array( 'name' => 'nom', 'attributes' => array( 'type' => 'text', 'label' => 'Nom:', ), )); } }

Avec le Zend Framework 1


La mthode init() qui est appele aprs la construction du formulaire permet dinitialiser les champs de formulaire. Cette mthode nexiste plus et la construction se fait directement depuis le constructeur. Voici la mthode add() du composant Zend\Form qui permet d'ajouter les lments: Zend/Form/Form.php
public function add($elementOrFieldset, array $flags = array()) { if (is_array($elementOrFieldset) || ($elementOrFieldset instanceof Traversable && !$elementOrFieldset instanceof ElementInterface) ) { $factory = $this->getFormFactory();

264

Les composants Chapitre 15


$elementOrFieldset = $factory->create($elementOrFieldset); } eturn parent::add($elementOrFieldset, $flags); r if ($elementOrFieldset instanceof Fieldset && $elementOrFieldset >useAsBaseFieldset()) { $this->baseFieldset = $elementOrFieldset; } return $this; }

La fabrique intervient afin de crer lobjet demand si le paramtre correspondant llment est un tableau. Notons que nous navons pas pass le type dlment crer en paramtre, la fabrique utilise le type Zend\Form\Element par dfaut: Zend/Form/Factory.php
public function create($spec) { $spec = $this->validateSpecification($spec, __METHOD__); $type = isset($spec['type']) ? $spec['type']: 'Zend\Form\Element'; [] if (self::isSubclassOf($type, 'Zend\Form\ElementInterface')) { return $this->createElement($spec); } [] }

Il est aussi possible de crer un lment directement sans utiliser la fabrique: Cration dun lment
$element = new Zend\Form\Element\Text(); $element->setName('nom'); $element->setAttribute('label', 'Nom );

Avec le Zend Framework 1


Les classes dlments types nexistent plus. La cration dune classe personnalise hritant de Zend\Form\Element permet de retrouver ce comportement. Une fois le formulaire construit, avec ou sans la fabrique, il doit tre peupl afin de pouvoir valider les informations injectes. Deux mthodes peuvent tre utilises pour linjection de donnes: depuis un tableau et la mthode setData() ; depuis un objet auquel on affecte un objet de type Zend\Stdlib\Hydrator\HydratorInterface afin de permettre lextraction et linjection de donnes. La mthode setData() est simple utiliser, il suffit de lui passer un tableau en paramtre et les donnes valides seront retournes sous la forme dun tableau de valeurs:

Les composants Chapitre 15

265

Utilisation de la mthode setData()


$form = new SimpleForm(); $filter = new SimpleFormFilter(); $form->setInputFilter($filter); $form->setData( array( 'nom' => 'Vincent', 'sujet' => 'information', 'message' => 'Ceci est un <strong>message</strong> depuis Zend\ Form', ) ); $valid = $form->isValid(); $datas = $form->getData();

Le formulaire SimpleForm est un formulaire compos de trois lments de type text. Afin de valider les valeurs passes en paramtres, le formulaire a besoin dun lment de type Zend\InputFilter\InputFilterInterface contenant filtres et validateurs utiliser, nous tudierons ce composant la prochaine section. Les donnes valider sont ensuite fournies la mthode setData() et rcupres depuis la mthode getData() une fois ces dernires valides et filtres. Le composant de formulaire laisse la possibilit de lui fournir un objet enveloppant les donnes valider, ce qui aura lavantage de pouvoir rcuprer les donnes filtres et valides depuis ce mme objet. Cependant, afin dindiquer au formulaire comment extraire et peupler cet objet, il est impratif de lui fournir un objet dhydratation afin quil sache quelle stratgie adopter pour le traitement de lobjet. Trois hydrateurs dobjet sont disponibles dans le namespace Zend\Stdlib\Hydrator: ArraySerializable: lextraction des donnes se fait depuis la mthode getArrayCopy() et lhydratation depuis la mthode exchangeArray() ou populate(). Lutilisation dun objet hritant de \ArrayObject permet la prise en charge automatique de ces deux premires mthodes; ObjectProperty: bas sur les proprits publiques de lobjet, il suffit de dfinir une proprit publique pour chaque lment que lon souhaite extraire ou hydrater; ClassMethods: bas sur les mthodes daltration, cet hydrateur permet dinteragir facilement avec les objets que lon a crs. Pour plus de renseignements sur les hydrateurs, reportez-vous la section qui leur est consacre dans le chapitre sur la bibliothque standard du framework. Prenons un exemple avec lhydrateur ClassMethods, hydrateur que jai eu loccasion dimplmenter dans le framework. Le formulaire utilis est le formulaire SimpleForm qui contient trois lments (nom, prenom, email) et les filtres et validateurs sont stocks dans la classe SimpleFormFilter qui gre uniquement la suppression des espaces avant et aprs les valeurs, ainsi que la suppression des tags:

266

Les composants Chapitre 15

Gestion des filtres et validateurs


class SimpleFormFilter extends InputFilter { public function __construct() { $this->add(array( 'name' => 'nom', 'required' => true, 'filters' => array( array( 'name' => 'Zend\Filter\StripTags', ), array( 'name' => 'Zend\Filter\StringTrim', ), ), )); $this->add(array( 'name' => 'prenom', 'required' => true, 'filters' => array( array( 'name' => 'Zend\Filter\StripTags', ), array( 'name' => 'Zend\Filter\StringTrim', ), ), )); $this->add(array( 'name' => 'email', 'required' => true, 'filters' => array( array( 'name' => 'Zend\Filter\StripTags', ), ), 'validators' => array( array( 'name' => 'Zend\Validator\EmailAddress', ), ), )); } }

Notre objet de donnes devra donc contenir les mthodes correspondant aux champs du formulaire: Objet qui sera utilis par lhydrateur
class SimpleObject { protected $nom; protected $prenom; protected $email; ublic function __construct($nom, $prenom, $email) p { $this->nom = $nom; $this->prenom = $prenom; $this->email = $email;

Les composants Chapitre 15


} ublic function getNom() p { return $this->nom; } [] }

267

Voici un exemple dutilisation de ce filtre et du peuplement depuis un objet et un hydrateur: Utilisation dun hydrateur
$form = new SimpleForm(); $filter = new SimpleFormFilter(); $form->setInputFilter($filter); $form->setHydrator(new Zend\Stdlib\Hydrator\ClassMethods()); $object = new SimpleObject(' <strong>blanchon</strong>', 'vincent', blanchon.vincent@gmail.com); $form->bind($object); $valid = $form->isValid(); $datas = $form->getData(); $nom = $datas->getNom(); echo $nom;

Sortie l'cran blanchon Il est galement possible de crer ses propres hydrateurs en implmentant linterface Zend\Stdlib\Hydrator\HydratorInterface qui dfinit deux mthodes: Zend/Stdlib/Hydrator/HydratorInterface.php
interface HydratorInterface { public function extract($object); public function hydrate(array $data, $object); }

La mthode extract() extrait les attributs avec leurs valeurs alors que la mthode hydrate() injecte les donnes lobjet. Les objets contenant les filtres et validateurs tendent la classe InputFilter qui fournit une API permettant la validation et le filtrage des donnes.

Le composant Zend\InputFilter
Bas sur des chaines de filtres et de validateurs, le composant Zend\InputFilter\ InputFilter permet le filtrage et la validation de donnes. Voici un exemple de sa mise en uvre:

268

Les composants Chapitre 15

Mise en uvre du composant Zend\InputFilter\InputFilter


$inputFilter = new Zend\InputFilter\InputFilter(); $input = new Zend\InputFilter\Input('data'); $validators = new Zend\Validator\ValidatorChain(); $validators->addByName('emailaddress', array()); $input->setValidatorChain($validators); $filters = new Zend\Filter\FilterChain(); $filters->attachByName('striptags'); $filters->attachByName('stringtrim'); $input->setFilterChain($filters); $inputFilter->add($input); $inputFilter->setData(array('data' => ' vincent@gmail.com')); $valid = $inputFilter->isValid();

<strong>blanchon<strong>.

Chaque donne traiter doit tre contenue dans un objet de type Input qui dfinit filtres et validateurs. Les filtres agissent sur la donne avant de la valider: Zend/InputFilter/Input.php
class Input implements InputInterface { [] public function getValue() { $filter = $this->getFilterChain(); return $filter->filter($this->value); } ublic function isValid($context = null) p { $this->injectNotEmptyValidator(); $validator = $this->getValidatorChain(); $value = $this->getValue(); return $validator->isValid($value, $context); } [] }

Le composant dispose aussi dune fabrique qui permet la cration de filtres et validateurs depuis un tableau de paramtres: Utilisation de la fabrique du Zend\InputFilter\InputFilter
$inputFilter = new Zend\InputFilter\InputFilter(); $inputFilter->add(array( 'name' => 'data', 'required' => true, 'validators' => array( array( 'name' => 'emailaddress', ), ), 'filters' => array( array(

Les composants Chapitre 15


'name' => 'striptags', ), array( 'name' => 'stringtrim', ), ), )); $inputFilter->setData(array('data' => ' vincent@gmail.com')); $valid = $inputFilter->isValid();

269

<strong>blanchon<strong>.

La fabrique utilise par dfaut est la classe Zend\InputFilter\Factory qui permet la cration dobjets de type Input depuis sa mthode createInput(), utilise par la classe InputFilter: Zend/InputFilter/InputFilter.php
public function add($input, $name = null) { if (is_array($input) || ($input instanceof Traversable && !$input instanceof InputFilterInterface) ) { $factory = $this->getFactory(); $input = $factory->createInput($input); } return parent::add($input, $name); }

Dans lexemple prcdent, lobjet de type InputFilter utilise la fabrication dobjets Input depuis la description en tableau. Il est galement possible de ne pas sparer les filtres et validateurs des lments en implmentant linterface Zend\InputFilter\InputProviderInterface dans la classe de llment, ce qui loblige dfinir la mthode getInputSpecification(), descriptif des filtres et validateurs utiliser. Reprenons lexemple du formulaire SimpleForm en ajoutant des classes pour chacun des lments. Voici la classe associe le-mail: Cration de filtres et validateurs au sein du formulaire
class Email extends Element implements InputProviderInterface { public function getInputSpecification() { return array( 'name' => $this->getName(), 'attributes' => array( 'type' => 'text', 'label' => 'Email:', ), 'required' => true, 'filters' => array( array('name' => 'Zend\Filter\StringTrim'), array('name' => 'Zend\Filter\StripTags'), ), 'validators' => array( array('name' => 'Zend\Validator\EmailAddress'), ), ); } }

270

Les composants Chapitre 15

Limplmentation du formulaire se fait lgrement diffremment: Implmentation du formulaire avec filtres et validateurs
$form = new SimpleForm(); $form->setInputFilter(new Zend\InputFilter\InputFilter); $form->add(new Nom('nom')); $form->add(new Prenom('prenom')); $form->add(new Email('email')); $form->setHydrator(new Zend\Stdlib\Hydrator\ClassMethods()); $object = new SimpleObject('blanchon', 'vincent', ' <strong>blanchon<strong>.vincent@gmail.com'); $form->bind($object); $valid = $form->isValid(); $datas = $form->getData(); $email = $datas->getEmail();

Le composant de formulaire offre plusieurs manires simples de mettre en place des filtres et validateurs pour les lments, ce qui permet lutilisateur de sparer les diffrentes couches afin de les rutiliser entre les diffrents formulaires de lapplication.

Les aides de vue


Une fois le formulaire construit, il est ncessaire de le rendre dans la vue. Il existe dans le namespace Zend\Form\View\Helper des aides de vue capables de rendre un lment de type Zend\Form\ElementInterface. Prenons lexemple de laide FormRow qui permet le rendu des lments dun formulaire de n'importe quel type. Cette aide de vue se base sur les autres aides qu'elle utilise en fonction du type d'lment et du besoin: Rendu de formulaire
<?php $form = $this->form; $form->prepare(); $form->setAttribute('action', $this->url('my/url')); $form->setAttribute('method', 'post'); echo $this->form()->openTag($form); ?> <?php echo $this->formRow($form->get('nom')); echo $this->formRow($form->get('prenom')); echo $this->formRow($form->get('email')); ?> <?php echo $this->form()->closeTag() ?>

Analysons le fonctionnement de laide de vue capable de rendre les lments: Zend/Form/View/Helper/FormRow.php


public function render(ElementInterface $element) { [] $labelHelper = $this->getLabelHelper(); $elementHelper = $this->getElementHelper(); $elementErrorsHelper = $this->getElementErrorsHelper();

Les composants Chapitre 15


[] $label = $element->getLabel(); [] $elementErrors = $elementErrorsHelper->render($element); [] $elementString = $elementHelper->render($element); if (!empty($label)) { [] if ($this->renderErrors) { $markup .= $elementErrors; } } } else { if ($this->renderErrors) { $markup = $elementString . $elementErrors; } else { $markup = $elementString; } } return $markup; }

271

L'aide de vue rcupre les diffrentes aides de vue dont elle a besoin pour rendre tous les lments (aide pour le label, l'lment et les erreurs)et construit ensuite le code HTML de llment avant de le renvoyer. Nous pourrions galement rendre manuellement chacun des lments grce chacune des aides de vue. Voici quoi ressemblerait alors notre code: Rendu depuis les aides de vue du formulaire
<?php $form = $this->form; $form->prepare(); $form->setAttribute('action', $this->url('my/url')); $form->setAttribute('method', 'post'); echo $this->form()->openTag($form); ?> <?php echo $this->formLabel($form->get('nom')); echo $this->formElement($form->get('nom')); echo $this->formElementErrors($form->get('nom')); ?> <?php echo $this->formLabel($form->get('prenom')); echo $this->formElement($form->get('prenom')); echo $this->formElementErrors($form->get('prenom')); ?> <?php echo $this->formLabel($form->get('email')); echo $this->formElement($form->get('email')); echo $this->formElementErrors($form->get('email')); ?> <?php echo $this->form()->closeTag() ?>

L'aide de vue FormElement permet de rendre n'importe quel lment car cette aide utilise celle qui est implmente par l'lment correspond, comme nous le voyons dans la mthode de rendu:

272

Les composants Chapitre 15

Zend/Form/View/Helper/FormElement.php
public function render(ElementInterface $element) { [] $type = $element->getAttribute('type'); if ('checkbox' == $type) { $helper = $renderer->plugin('form_checkbox'); return $helper($element); } if ('color' == $type) { $helper = $renderer->plugin('form_color'); return $helper($element); } if ('date' == $type) { $helper = $renderer->plugin('form_date'); return $helper($element); } [] $helper = $renderer->plugin('form_input'); return $helper($element); }

Le type de llment est rcupr pour obtenir l'aide de vue qui lui correspond. Nous aurions donc galement pu remplacer l'aide de vue FormElement de notre code HTML par l'aide de vue correspondant au type du champ.

Zend\Mail
Le composant pour la gestion des e-mails a t rcrit en profondeur. Chaque tche est maintenant clairement spare et de nouvelles fonctionnalits ont t ajoutes. La classe Zend\Mail\Message permet au dveloppeur dencapsuler dans un objet tout ce qui va correspondre au message envoyer: sujet de le-mail, contenu de lemail, adresse du destinataire, adresse de rponse, etc. Voici un exemple de cration de message: Cration de message
$message = new Mail\Message(); $message->addTo('contact@au-coeur-de-zend-framework-2.fr'); $message->addReplyTo('blanchon.vincent@gmail.com', 'BLANCHON Vincent'); $message->setFrom('blanchon.vincent@gmail.com', 'BLANCHON Vincent'); $message->setSubject('Question sur le livre'); $message->setBody('Comment puis-je acheter le livre ?');

Cependant, comme les tches sont dcouples au maximum, il est possible dutiliser une meilleure encapsulation pour la gestion des adresses e-mail:

Les composants Chapitre 15

273

Cration de message avec encapsulation des adresses


$message = new Mail\Message(); $message->addTo( new Mail\Address('contact@au-coeur-de-zend-framework-2.fr') ); $message->addReplyTo( n ew Mail\Address('blanchon.vincent@gmail.com', 'BLANCHON Vincent') ); $message->setFrom( new Mail\Address('blanchon.vincent@gmail.com', 'BLANCHON Vincent') ); $message->setSubject('Question sur le livre'); $message->setBody('Comment puis-je acheter le livre ?');

De mme pour le corps du message de le-mail, il est possible de lencapsuler depuis les classes Mime afin de grer son type: Encapsulation du type du corps du message
$message = new Mail\Message(); $message->addTo( n ew Mail\Address('contact@au-coeur-de-zend-framework-2.fr') ); $message->addReplyTo( n ew Mail\Address('blanchon.vincent@gmail.com', 'BLANCHON Vincent') ); $message->setFrom( n ew Mail\Address('blanchon.vincent@gmail.com', 'BLANCHON Vincent') ); $message->setSubject('Question sur le livre'); $body = new Mime\Message(); $text = new Mime\Part('Comment puis-je acheter le livre ?'); $text->type = 'text/plain'; $body->addPart($text); $message->setBody($body);

Enfin, il galement possible de vrifier si le message est bien valide: Vrification de la validit du message
$message->isValid();

Lors de la vrification du message, lobjet se contente de vrifier la liste des adresses denvoi: Zend/Mail/Message.php
public function isValid() { $from = $this->getFrom(); if (!$from instanceof AddressList) { return false; }

274

Les composants Chapitre 15


return (bool) count($from); }

Une fois le message configur et valid, nous pouvons utiliser un objet de transport afin denvoyer le message: Envoi du message
$smtp = new Mail\Transport\Smtp(); $connection = $smtp->plugin('login', array( ' host' => 'smtp.gmail.com', ' username' => 'username', ' password' => 'password', )); $connection->connect()->auth(); $smtp->setConnection($connection); $smtp->send($message);

Notons que la classe Zend\Mail\Transport\Smtp possde son propre gestionnaire de plugins qui lui permet un accs facilit aux diffrents types disponibles: Zend/Mail/Protocol/SmtpPluginManager.php
class SmtpPluginManager extends AbstractPluginManager { protected $invokableClasses = array( 'crammd5' => 'Zend\Mail\Protocol\Smtp\Auth\Crammd5', 'login' => 'Zend\Mail\Protocol\Smtp\Auth\Login', 'plain' => 'Zend\Mail\Protocol\Smtp\Auth\Plain', 'smtp' => 'Zend\Mail\Protocol\Smtp', ); [] }

Le composant de gestion de mails permet aussi la gestion des e-mails depuis de nombreux formats de stockage existants: gestion de la sauvegarde de courriel depuis la structure de rpertoire maildir, gestion du format de stockage ouvert mbox ou encore la gestion des e-mails depuis les protocoles pop3 ou imap. Voici un exemple de la gestion du protocole pop3: Gestion du protocole pop3
$mail = new Mail\Storage\Pop3(array( 'host' => 'pop.gmail.com', ' user' => 'username', 'password' => 'password', 'ssl' => true, )); $ids = $mail->getUniqueId();

De mme, il est possible de grer facilement ses e-mails depuis le format ouvert mbox:

Les composants Chapitre 15

275

Gestion des e-mails avec le format mbox


$mail = new Mail\Storage\Mbox( array('filename' => path/to/my/mbox/INBOX') ); $message = $mail->getMessage(3); $content = $message->getContent(); echo "Voici le contenu de l'e-mail: " . $content;

Le composant Mail a enrichi la liste de ses fonctionnalits et en fait un composant incontournable du framework. Sa structure revue en profondeur en fait un composant que lon peut tendre facilement pour rpondre aux diffrentes problmatiques que lon peut rencontrer avec la messagerie et gestion des e-mails.

Zend\Session
Le composant de gestion des sessions a t totalement rcrit dans cette version du framework. La gestion par espace de nom est conserve afin de pouvoir sparer les diffrents conteneurs de session. Voici maintenant comment implmenter une session avec un espace de nom: Cration dune session avec son espace de nom
$session = new \Zend\Session\Container('monnamespace'); $session->offsetSet(attribut, 'valeur');

Examinons en dtail le constructeur du conteneur. Zend/Session/Container.php


public function __construct($name = 'Default', Manager $manager = null) { if (!preg_match('/^[a-z][a-z0-9_\\\]+$/i', $name)) { [] } $this->name = $name; $this->setManager($manager); parent::__construct(array(), ArrayObject::ARRAY_AS_PROPS); $this->getManager()->start(); }

La classe Container hrite de la classe ArrayObject et appelle le constructeur parent avec le paramtre ARRAY_AS_PROPS, ce qui nous permet d'accder aux cls de l'objet comme des attributs: Utilisation dun conteneur de session
$session = new \Zend\Session\Container('monnamespace'); $session->attribut = 'valeur';

Le constructeur initialise un gestionnaire de sessions qui permet le pilotage de la session dans sa globalit : destruction, identifiant de session, rgnration de session, etc. Afin de construire cet espace de nom dans la session courante, le constructeur demande avant au gestionnaire de sessions de dmarrer son mca-

276

Les composants Chapitre 15

nisme si celui-ci ne la pas dj t. Nous expliquerons en dtail le gestionnaire de sessions aprs la notion de conteneur. Dans un conteneur de session, cest uniquement lors de la modification dune proprit que celle-ci est cre: Zend/Session/Container.php
public function offsetSet($key, $value) { $this->expireKeys($key); $storage = $this->verifyNamespace(); $name = $this->getName(); $storage[$name][$key] = $value; }

Zend/Session/Container.php
protected function verifyNamespace($createContainer = true) { $storage = $this->getStorage(); $name = $this->getName(); if (!isset($storage[$name])) { if (!$createContainer) { return; } $storage[$name] = $this->createContainer(); } if (!is_array($storage[$name]) && !$storage[$name] instanceof ArrayObject) { [] } return $storage; }

Lorsqu'une proprit est modifie, si le conteneur courant n'existe pas dans le gestionnaire de stockages, alors celui-ci est cr. La cration la demande permet d'optimiser les ressources en allouant de l'espace uniquement en cas de besoin. L'espace de stockage par dfaut est un objet de type Zend\Session\Storage\SessionStorage qui permet de piloter directement la variable $_SESSION de PHP. Le test d'existence de l'espace de nom et la cration de son conteneur associ sont effectus depuis la mthode verifyNamespace(). Le couple attribut/valeur est ensuite ajout notre objet de stockage. Le gestionnaire de sessions, qui permet de piloter l'objet de session, est accessible depuis nimporte quel conteneur de session depuis linstruction: Accs au gestionnaire de sessions
$session = new \Zend\Session\Container('namespace'); $manager = $session->getManager();

Le gestionnaire est aussi accessible depuis la rcupration du gestionnaire par dfaut: accs au gestionnaire de sessions
$manager = \Zend\Session\Container::getDefaultManager();

Les composants Chapitre 15

277

En effet, la mthode getManager() et getDefaultManager() retourne le mme objet, car si l'on observe le constructeur du conteneur, nous remarquons que le gestionnaire est construit cet endroit partir du gestionnaire par dfaut: Zend/Session/Container.php
public function __construct($name = 'Default', Manager $manager = null) { [] $this->setManager($manager); [] }

La mthode setManager() utilise comme instance le gestionnaire par dfaut si nous ne lui avons fourni aucun objet de type Manager: Zend/Session/Container.php
protected function setManager(Manager $manager = null) { if (null === $manager) { $ manager = self::getDefaultManager(); [] } $this->manager = $manager; return $this; }

Cependant, dans le cas o nous fournissons notre gestionnaire personnalis, les deux objets seront diffrents. Le gestionnaire de sessions doit respecter un contrat avec linterface Zend\Session\ManagerInterface: Zend/Session/ManagerInterface.php
interface ManagerInterface { [] public function start(); public function destroy(); [] public function setId($id); public function getId(); public function regenerateId(); public function rememberMe($ttl = null); public function forgetMe(); [] }

Nous comprenons que c'est ce gestionnaire qui va permettre de dmarrer, arrter ou rgnrer l'identifiant de la session. Le gestionnaire Zend\Session\SessionManager tend Zend\Session\AbstractManager qui dfinit entre autres une mthode getConfig() donnant accs la configuration de la session sous la forme dun objet de type Zend\Session\Configuration\SessionConfiguration, ainsi qu'une mthode setSaveHandler() , qui offre la possibilit de modifier les fonctions de gestion de stockage des sessions.

278

Les composants Chapitre 15

Comme pour chacun des composants du framework, la gestion des sessions est divise en de multiples classes o chacune est responsable d'une tche prcise (configuration, stockage, conteneur). Il est possible de remplacer chaque souscomposant par un composant personnalis, condition que celui-ci respecte le contrat dfini par linterface lie ce sous-composant. Ainsi, si nous souhaitons tendre le composant de session, nous devrons utiliser les interfaces: Zend\Session\ManagerInterface qui dfinit un contrat avec le gestionnaire de sessions; Zend\Session\ConfigurationInterface qui dfinit un contrat avec le gestionnaire doptions de nos sessions; Zend\Session\SaveHandler\SaveHandlerInterface qui dfinit un contrat avec le gestionnaire de stockages des sessions; Zend\Session\Storage\StorageInterface qui dfinit un contrat avec le gestionnaire de stockages des valeurs en session. Nos composants personnaliss peuvent alors tre injects depuis une fabrique personnalise ou depuis les mthodes daltration du composant de session.

Zend\Stdlib
Le composant Zend\Stdlib est une bibliothque de classes permettant de rsoudre les problmes rcurrents que lon rencontre sur les projets Web. Ce composant se veut tre complmentaire la SPL (bibliothque standard de PHP) que lon connat. Les classes de ce composant sappuient aussi souvent sur ce standard afin denrichir un concept ou objet existant. Le framework sappuie beaucoup sur ce composant qui permet dintroduire une certaine homognit dans le framework. Nous allons prsenter dans ce chapitre quelques classes que lon retrouvera beaucoup au sein du framework et que vous pourrez utiliser dans de vos projets.

La classe AbstractOptions
La classe abstraite AbstractOptions fournit des mthodes de base aux classes permettant de grer la configuration dun composant. Cette classe offre un mcanisme dinitialisation qui, partir dun tableau doptions, permet de distribuer aux mthodes daltrations, les donnes quelles doivent recevoir. Afin de pouvoir automatiser cette tche, la classe formate les noms de cls du tableau de paramtres en nom de mthode correspondant cette cl en la prfixant du mot set: Zend/Stdlib/AbstractOptions.php
public function __construct($options = null) { if (null !== $options) { $this->setFromArray($options); } }

Les composants Chapitre 15

279

Zend/Stdlib/AbstractOptions.php
public function setFromArray($options) { if (!is_array($options) && !$options instanceof Traversable) { [] } foreach ($options as $key => $value) { $this->__set($key, $value); } }

Zend/Stdlib/AbstractOptions.php
protected function assembleSetterNameFromKey($key) { $parts = explode('_', $key); $parts = array_map('ucfirst', $parts); $setter = 'set' . implode('', $parts); if (!method_exists($this, $setter)) { [] } return $setter; }

Le constructeur fait appel la mthode setFromArray() qui soccupe dinjecter les options depuis un tableau afin que chaque valeur de la configuration soit traite par la mthode magique __set(). Cette mthode en recherche une autre du nom de setMaVariable() o ma_variable est une cl du tableau de configuration. Par exemple, la cl config_cache_key est injecte depuis la mthode setConfigCacheKey() dont le nom est format en CamelCase tout en supprimant les underscores du nom de la variable. Nous comprenons donc que le fait de ne pas respecter cette rgle, en nommant ou en insrant une cl qui ne correspond aucune mthode interne, lvera une exception: Zend/Stdlib/Options.php
protected function assembleSetterNameFromKey($key) { [] if (!method_exists($this, $setter)) { throw new Exception\BadMethodCallException( 'The option "' . $key . '" does not ' . 'have a matching ' . $getter . ' getter method ' . 'which must be defined' ); } return $setter; }

Lexception leve dans notre exemple


Fatal error: Uncaught exception 'Zend\Stdlib\Exception\ BadMethodCallException' with message 'The option "ma_cle_inconnu" does not have a matching setMaCleInconnu

Il devient alors trs simple de composer une classe responsable de la gestion doptions dun composant. Il suffit dcrire les setters et getters, mthodes de

280

Les composants Chapitre 15

rcupration et de modification, correspondant aux noms des attributs et la classe devient fonctionnelle. Cette classe est utilise de nombreuses reprises dans le framework: Zend\Cache, Zend\Mail, Zend\Serializer, etc.

La classe ArrayUtils
La classe ArrayUtils offre de nombreuses fonctions afin de manipuler au mieux les tableaux. La classe offre des mthodes afin de connatre le type des cls dun tableau, par exemple en testant sil existe des cls du type de la chane de caractres: Zend/Stdlib/ArrayUtils.php
public static function hasStringKeys($value, $allowEmpty = false) { if (!is_array($value)) { return false; } if (!$value) { return $allowEmpty; } return count(array_filter(array_keys($value), 'is_string')) > 0; }

La mthode hasStringKeys() applique un filtre sur la liste des cls du tableau afin de rcuprer les cls de type chane de caractres. Deux autres mthodes similaires existent afin de tester sil existe des cls de type entier ou numrique. La mthode statique iteratorToArray() permet de complter la fonction native de PHP iterator_to_array() en permettant de parcourir rcursivement le tableau ou lobjet implmentant linterface Traversable pass en paramtre. En effet, la mthode laisse la possibilit de passer un tableau en paramtre, auquel cas il retournera simplement le tableau si lon n'a pas demand de le parcourir rcursivement: Zend/Stdlib/ArrayUtils.php
public static function iteratorToArray($iterator, $recursive = true) { if (!is_array($iterator) && !$iterator instanceof Traversable) { throw new Exception\InvalidArgumentException(__METHOD__ . ' expects an array or Traversable object'); } if (!$recursive) { if (is_array($iterator)) { return $iterator; } return iterator_to_array($iterator); } [] }

La mthode vrifie ensuite si lobjet ne comporte pas une mthode toArray(), auquel cas elle est utilise avant de parcourir le tableau rcursivement:

Les composants Chapitre 15

281

Zend/Stdlib/ArrayUtils.php
public static function iteratorToArray($iterator, $recursive = true) { [] if (method_exists($iterator, 'toArray')) { return $iterator->toArray(); } $array = array(); foreach ($iterator as $key => $value) { if (is_scalar($value)) { $array[$key] = $value; continue; } if ($value instanceof Traversable) { $array[$key] = static::iteratorToArray($value, $recursive); continue; } if (is_array($value)) { $array[$key] = static::iteratorToArray($value, $recursive); continue; } $array[$key] = $value; } return $array; }

chaque fois que la boucle rencontre un tableau ou un objet de type Traversable, la mthode est appele rcursivement. Nous comprenons lintrt de la souplesse de la mthode sur le type en premier paramtre. Cette mthode permet de grer facilement les compositions de variables entre tableaux, objets de type Traversable et objets implmentant la mthode toArray().

La classe CallbackHandler
La classe CallbackHandler permet de surcharger le comportement des fonctions natives de PHP call_user_func() ou encore call_ user_ func_ array() qui permettent dappeler des fonctions de rappels. La classe offre la possibilit de prendre en paramtre tout ce qui peut tre appel en tant que fonction: Zend/Stdlib/CallbackHandler.php
public function __construct($callback, array $metadata = array()) { $this->metadata = $metadata; $this->registerCallback($callback); }

Zend/Stdlib/CallbackHandler.php
protected function registerCallback($callback) { if (!is_callable($callback)) { throw new Exception\InvalidCallbackException('Invalid callback provided; not callable');

282

Les composants Chapitre 15


} [] }

Il est donc possible de passer de nombreux paramtres au constructeur de la classe: tableau, closure, chane de caractres, etc. Voici quelques exemples dutilisation o nous dfinissons quelques classes qui serviront de mthodes de rappel: Utilisation du CallbackHandler
class Callback { public function doSomething() { echo "ok\n"; } ublic function doSomethingWithArg($arg) p { echo $arg . "\n"; } } function doSomething() { echo "ok\n"; }

Maintenant voici comment utiliser la classe CallbackHandler: Utilisation du CallbackHandler


$callbackObject = new Callback(); $callback = new Stdlib\CallbackHandler(array('Callback', 'doSomething')); $callback->call(); $callback = new Stdlib\CallbackHandler(array($callbackObject, 'doSomething')); $callback->call(); $callback = new Stdlib\CallbackHandler(function() { echo "ok\n"; }); $callback->call(); $callback = new Stdlib\CallbackHandler(array($callbackObject, 'doSomethingWithArg')); $callback->call(array('ok')); $callback = new Stdlib\CallbackHandler('doSomething'); $callback->call(); $callback = new Stdlib\CallbackHandler('\Callback::doSomething'); $callback->call();

Comme nous le voyons, la mthode call() permet de fournir des arguments la mthode de callback. videmment, cette mthode sappuie sur les fonctions call_user_func() et call_ user_ func_ array() de PHP que lon connat:

Les composants Chapitre 15

283

Zend/Stdlib/CallbackHandler.php
public function call(array $args = array()) { [] $argCount = count($args); [] switch ($argCount) { case 0: if (self::$isPhp54) { return $callback(); } return call_user_func($callback); case 1: if (self::$isPhp54) { return $callback(array_shift($args)); } return call_user_func($callback, array_shift($args)); case 2: $arg1 = array_shift($args); $arg2 = array_shift($args); if (self::$isPhp54) { return $callback($arg1, $arg2); } return call_user_func($callback, $arg1, $arg2); case 3: $arg1 = array_shift($args); $arg2 = array_shift($args); $arg3 = array_shift($args); if (self::$isPhp54) { return $callback($arg1, $arg2, $arg3); } return call_user_func($callback, $arg1, $arg2, $arg3); default: return call_user_func_array($callback, $args); } }

La classe offre aussi la possibilit dutiliser linstance de lobjet CallbackHandler directement comme une fonction: Zend/Stdlib/CallbackHandler.php
public function __invoke() { return $this->call(func_get_args()); }

Nous pouvons donc crire ces instructions: Utilisation de linstance comme fonction
$callback = new Stdlib\CallbackHandler(array($callbackObject, 'doSomething')); $callback();

Ce composant peut savrer particulirement utile pour nos gestions de fonctions de rappel afin de fournir un peu plus de flexibilit dans nos applications.

284

Les composants Chapitre 15

La classe ErrorHandler
La classe ErrorHandler fournit des mthodes de gestion pour capturer les erreurs de lapplication. Voici un exemple de capture de notice: Capture derreur
class ErrorHandlerTest { public function doSomething() { trigger_error(lancement de notice, \E_USER_NOTICE); } } Stdlib\ErrorHandler::start(\E_ALL); $errHandler = new ErrorHandlerTest(); $errHandler->doSomething(); $err = Stdlib\ErrorHandler::stop(); echo $err->getMessage();

Sortie l'cran
lancement de notice

La capture derreur est dmarre par la mthode start() qui fait appel la mthode set_error_handler() de PHP afin de modifier la fonction qui gre les erreurs: Zend/Stdlib/ErrorHandler.php
public static function start($errorLevel = \E_WARNING) { if (static::started() === true) { throw new Exception\LogicException('ErrorHandler already started'); } static::$started = true; static::$errorException = null; set_error_handler(array(get_called_class(), 'addError'), $errorLevel); }

La mthode prend le type derreur grer en paramtre et enregistre sa mthode addError() comme mthode de gestion derreurs. Cette mthode se contente denregistrer lerreur dans lattribut $errorException que lon peut rcuprer automatiquement sous forme dexception depuis la mthode statique stop(): Zend/Stdlib/ErrorHandler.php
public static function stop($throw = false) { if (static::started() === false) { throw new Exception\LogicException('ErrorHandler not started'); } $errorException = static::$errorException; static::$started = false; static::$errorException = null;

Les composants Chapitre 15


restore_error_handler(); f ($errorException && $throw) { i throw $errorException; } return $errorException; }

285

La classe ErrorHandler possde une API relativement facile et agrable prendre en main qui permet de se dtacher de la gestion des erreurs que lon implmente dans chaque projet.

La classe Glob
La classe Glob permet dutiliser la fonction native glob() avec les systmes qui ne grent pas la mthode glob() avec lutilisation du flag GLOB_BRACE. Une fonction a t spcialement porte de la bibliothque GLIBC afin de rendre lutilisation de GLOB_BRACE possible: Zend/Stdlib/Glob.php
public static function glob($pattern, $flags, $forceFallback = false) { if (!defined('GLOB_BRACE') || $forceFallback) { return self::fallbackGlob($pattern, $flags); } else { return self::systemGlob($pattern, $flags); } }

Nous remarquons que cette fonction fallbackGlob() est utilise si le systme ne dfinit pas la constante GLOB_BRACE. Cette classe permet donc d'utiliser cette fonctionnalit sans bloquer les plates-formes qui ne sont pas compatibles. Il est videmment conseill dutiliser ce composant lorsque vous souhaitez utiliser la fonction glob() avec le flag GLOB_BRACE.

Les hydrateurs
Les hydrateurs permettent dextraire les donnes et de peupler les attributs dun objet suivant une stratgie dfinie. Les hydrateurs de la Stdlib se trouvent dans le namespace Zend\Stdlib\Hydrator et offrent plusieurs stratgies: extraction et modification depuis les mthodes getXXX() et setXXX() de lobjet ; extraction et modification depuis les proprits publiques de lobjet ; extraction et modification depuis une analyse, laide de la classe reflectionClass, de lobjet ; extraction et modification depuis les mthodes getArrayCopy() et exchangeArray() propres aux objets travaillant avec les tableaux.

286

Les composants Chapitre 15

Voici un exemple dutilisation de lhydrateur bas sur les getters et setters de lobjet: Utilisation de lhydrateur ClassMethod:
class Exemple { protected $ma_propriete = 'ma valeur'; ublic function getMaPropriete() p { return $this->ma_propriete; } ublic function setMaPropriete($ma_propriete) p { $this->ma_propriete = $ma_propriete; return $this; } } $exemple = new Exemple(); $hydrateur = new Stdlib\Hydrator\ClassMethods(); $datas = $hydrateur->extract($exemple);

Lhydrateur implmente linterface Zend\Stdlib\Hydrator\HydratorInterface qui dfinit le contrat de base que doivent respecter les hydrateurs: Zend/Stdlib/Hydrator/HydratorInterface.php
interface HydratorInterface { public function extract($object); public function hydrate(array $data, $object); }

La mthode extract() permet dextraire les donnes de lobjet suivant la stratgie de lhydrateur et la mthode hydrate() permet de peupler lobjet depuis les donnes fournies en paramtre. Dans notre exemple, nous utilisons lhydrateur bas sur les mthodes de lobjet. Les mthodes getMaPropriete() et setMaPropriete() vont lui permettre dextraire de lobjet, un tableau avec comme entre une cl ma_propriete avec la valeur ma valeur. Examinons le fonctionnement de lhydrateur Zend\Stdlib\Hydrator\ClassMethods: Zend/Stdlib/Hydrator/ClassMethods.php
public function extract($object) { [] $transform = function($letters) { $letter = array_shift($letters); return '_' . strtolower($letter); }; $attributes = array(); $methods = get_class_methods($object);

Les composants Chapitre 15


foreach ($methods as $method) { if (!preg_match('/^(get|has|is)[A-Z]\w*/', $method)) { continue; } if (preg_match('/^get/', $method)) { $setter = preg_replace('/^get/', 'set', $method); $attribute = substr($method, 3); $attribute = lcfirst($attribute); } else { $setter = 'set' . ucfirst($method); $attribute = $method; } if (!in_array($setter, $methods)) { continue; } if ($this->underscoreSeparatedKeys) { $attribute = preg_replace_callback('/([A-Z])/', $transform, $attribute); } $attributes[$attribute] = $this->extractValue($attribute, $object->$method()); } return $attributes; }

287

Lhydrateur rcupre tout dabord la liste des mthodes de lobjet avant de les parcourir: Zend/Stdlib/Hydrator/ClassMethods.php
public function extract($object) { [] $methods = get_class_methods($object); foreach ($methods as $method) { [] } }

Comme nous souhaitons extraire des donnes, lhydrateur se base alors sur le getter de lobjet et donc ses mthodes commenant par get, tout en vrifiant que le setter correspondant existe bel et bien: Zend/Stdlib/Hydrator/ClassMethods.php
public function extract($object) { foreach ($methods as $method) { if (!preg_match('/^(get|has|is)[A-Z]\w*/', $method)) { continue; } if (preg_match('/^get/', $method)) { [] } else { $setter = 'set' . ucfirst($method); $attribute = $method; } if (!in_array($setter, $methods)) { continue; } }}

288

Les composants Chapitre 15

Nous remarquons que l'hydrateur rcupre aussi les mthodes de rcupration de donnes commenant par has ou is. La seule diffrence de ces mthodes avec celles commenant par get rside dans le nom de setter. Une fois les vrifications faites, le nom de lattribut est format afin dtre enregistr dans le tableau de retour: Zend/Stdlib/Hydrator/ClassMethods.php
public function extract($object) { [] foreach ($methods as $method) { [] if (preg_match('/^get/', $method)) { $setter = preg_replace('/^get/', 'set', $method); $attribute = substr($method, 3); $attribute = lcfirst($attribute); } else { $setter = 'set' . ucfirst($method); $attribute = $method; } [] $attributes[$attribute] = $this->extractValue($attribute, $object->$method()); } [] }

Nous remarquons que nous avons la possibilit de transformer les noms de cls au format CamelCase ou un format avec underscore grce lattribut $underscoreSeparatedKeys que nous pouvons dfinir depuis le constructeur: Zend/Stdlib/Hydrator/ClassMethods.php
public function __construct($underscoreSeparatedKeys = true) { parent::__construct(); $this->underscoreSeparatedKeys = $underscoreSeparatedKeys; }

Par dfaut, la classe utilise les underscores. Nous aurons donc la cl ma_propriete pour la mthode getMaPropriete(). Si nous passons le paramtre false, nous obtiendrons la cl maPropriete. Lors de lhydratation, ou peuplement de lobjet, la mthode extract() utilisera les mthodes commenant par set de lobjet si celles-ci existent: Exemple dhydratation
$hydrateur = new Stdlib\Hydrator\ClassMethods(); $datas = array('ma_propriete' => 'nouvelle valeur'); $hydrateur->hydrate($datas, $exemple);

Revenons lextraction et remarquons que lhydrateur utilise la mthode extractValue() pour enregistrer la valeur plutt quune affectation classique. Ceci permet

Les composants Chapitre 15

289

dajouter une surcouche lors de lextraction ou de lhydratation. Voici comment se comporte la mthode extractValue(): Zend/Stdlib/Hydrator/AbstractHydrator.php
public function extractValue($name, $value) { if ($this->hasStrategy($name)) { $strategy = $this->getStrategy($name); $value = $strategy->extract($value); } return $value; }

L'AbstractHydrator dont hritent les hydrateurs vrifie si une stratgie particulire existe sur la proprit hydrater afin de lutiliser. Par dfaut, la valeur est simplement retourne. Voici un exemple de stratgie ajoute sur lhydrateur: Ajout de stratgie
class FilterStrategy implements StrategyInterface { public function extract($value) { return strip_tags($value); } ublic function hydrate($value) p { return "<p>" . $value . "</p>"; } } $exemple->setMaPropriete('<p>ma valeur</p>'); $hydrateur = new Stdlib\Hydrator\ClassMethods(); $hydrateur->addStrategy('ma_propriete', new FilterStrategy); $datas = $hydrateur->extract($exemple);

La stratgie permet ici de supprimer les tags de la cl correspondant ma_propriete lors de lextraction et de les ajouter lors de lhydratation. Attention, nous utilisons les underscores pour les cls. Si nous passons au format CamelCase, cela ne fonctionnera plus, sauf si nous modifions la cl pour la stratgie: Utilisation du format CamelCase
$exemple->setMaPropriete('<p>ma valeur</p>'); $hydrateur = new Stdlib\Hydrator\ClassMethods(false); $hydrateur->addStrategy('maPropriete', new FilterStrategy); $datas = $hydrateur->extract($exemple);

Les hydrateurs sont trs utiliss avec les formulaires et peuvent ltre dans de nombreuses applications. Ils sont trs extensibles et trs simples dutilisation. Les autres hydrateurs fonctionnent de la mme manire, ils ne diffrent que dans la rcupration des donnes de lobjet depuis ses mthodes de classes ou directement depuis ses proprits.

290

Les composants Chapitre 15

Les exceptions

16

Le framework a galement mis jour son systme dexception afin de simplifier la gestion des erreurs lors du dveloppement. Chaque composant implmente une interface correspondant son espace de nom, par exemple avec le composant des contrles daccs : Zend/Permissions/Acl/ExceptionExceptionInterface.php
interface ExceptionInterface {}

Cette interface est ensuite implmente par toutes les classes dexception de cet espace de nom, ce qui laisse la possibilit de faire rfrence une exception provenant dun espace de nom ou composant prcis, lorsque nous attrapons les exceptions. Le nom de la classe de chacune des exceptions de cet espace de nom permet de filtrer ensuite lerreur plus prcisment: Filtrage des erreurs
try { [] // bloc dactions } catch(Zend\Permissions\Acl\Exception\RuntimeException $e) { [] // exception la plus spcialise } catch(Zend\Permissions\Acl\Exception\ExceptionInterface $e) { [ ] // exception la plus gnrique qui fait partie du composant Zend\Permissions\Acl } catch(\RuntimeException $e) { [] // exception de base }

Le framework est maintenant plus riche en classes dexception afin daugmenter la qualit du code et la gestion des erreurs. Il devient plus facile de crer ses propres exceptions dans la bibliothque de son application tout en conservant la rgle dune interface par composant et dune ou plusieurs exceptions pour les souscomposants. Lajout dune exception se rsume alors la cration une classe vide possdant un

292

Les exceptions Chapitre 16

nom propre lexception, nom gnralement assez explicite, ainsi que de limplmentation de linterface dexception de lespace de nom correspondant. Il convient ensuite de la faire hriter de la classe dexception de base qui la dcrit au mieux. Par exemple, lexception de type Zend\Permissions\Acl\Exception\Runtime n'tendra pas \Exception mais \RuntimeException qui lui correspond davantage : Zend/Permissions/Acl/RuntimeException.php
class RuntimeException extends \RuntimeException implements ExceptionInterface {}

Avec le Zend Framework 1


Les exceptions tendent toutes Zend_Exception (cette classe tend ellemme \Exception), ce qui ne permet pas de pouvoir filtrer et grer les exceptions avec prcision. Prendre les bonnes habitudes du Zend Framework 2 au niveau de la gestion des exceptions permet le maintien dun code o la gestion des erreurs en sera plus simple, ce qui facilitera alors les corrections.

Cas dutilisation
Afin de mettre en uvre ce qui a t expliqu dans les chapitres prcdents, nous allons construire un petit projet qui permettra de comprendre les tapes de la cration dune application Web avec le framework. Ce projet sera aussi loccasion de dcouvrir des composants du framework qui nont pas t analyss dans les derniers chapitres. La premire partie prsente le concept de lapplication ainsi que les fonctionnalits que nous implmenterons et dtaillerons lors de lanalyse. Larchitecture, les bibliothques utilises, la configuration et les contrleurs suivront afin de faire un tour complet du code de lapplication. Vous pouvez galement retrouver le code du projet sur mon compte github afin de visualiser le code en mme temps que la lecture : https://github.com/blanchonvincent/au-coeur-de-zend-framework-2. Le code de ce projet pouvant voluer, vous retrouverez le code utilis lors de lcriture de cet ouvrage avec le tag 2.0.0 qui est la version du framework utilise.

17

Concept et fonctionnalits
Prsentation du projet
Le projet tudi est une application ddie lagrgation de contenus autour du Zend Framework 2: articles, tutoriels, tweets, vidos, dveloppeurs, etc. Jai choisi de raliser ce projet afin de ne pas reprendre lexemple classique de la gestion de livres que lon connait et qui reste limit en termes de fonctionnalit. Si ce projet na pas dintrt particulier, il va permettre dutiliser de nombreux composants que lon n'a peut-tre pas loccasion dutiliser souvent comme lAPI Twitter ou YouTube, et dautres qui font partie intgrante de la vie dun projet comme les bases de donnes ou les formulaires. Ce projet est hberg sur le nom de domaine http://zend-framework-2.fr et permettra de regrouper les diffrentes ressources sur le framework afin de pouvoir aider, je lespre, quelques dveloppeurs dans leur apprentissage.

294

Cas dutilisation Chapitre 17

Les besoins du projet


Afin de pouvoir dvelopper ce projet, il est ncessaire de devoir tlcharger les projets suivants: ZendService; ZendGData; ZendRest. Ces projets sont maintenus sparment du framework pour des questions pratiques comme nous lavons expliqu dans le chapitre ddi aux composants. Ils sont disponibles sur le compte Github du Zend Framework. Le projet utilise aussi une base de donnes MySQL pour la sauvegarde des donnes et une version de PHP suprieure ou gale PHP 5.3.3, prrequis pour l'utilisation du framework. tous ceux qui se demandent pourquoi cette version prcise, depuis PHP 5.3.3, les mthodes ayant le mme nom que la classe dans laquelle elles se trouvent ne sont plus traites comme des constructeurs. En effet, l'utilisation d'une mthode de fabrique fabrique() dans un fichier Fabrique.php poserait un problme avec l'utilisation d'une version antrieure, la mthode serait alors considre comme son constructeur. Afin d'tudier le projet, listons les fonctionnalits qui seront disponibles dans l'application: la page d'accueil listera les derniers tweets et tutoriels disponibles sur le site; une page listera les tweets propos du Zend Framework et sera pagine. Une autre fera la mme chose avec les tutoriels suivant la langue franaise ou anglaise; une page qui liste les donnes du framework sur les rseaux sociaux Facebook, Slideshare et YouTube; une page listant les dveloppeurs Zend Framework en France avec leurs liens Viadeo, Linkedin et Twitter, ainsi que leurs certifications; un formulaire capable d'inscrire un nouveau dveloppeur qui en ferait la demande; un formulaire de contact qui permet l'envoi d'un simple e-mail. Nous ferons souvent rfrence ces besoins tout au long de l'tude du projet afin d'expliquer les solutions mises en uvre.

Organisation et architecture
Pour mettre en uvre notre projet, il est important de dfinir une arborescence. Trois modules sont prsents: Application, qui reprsente le cur de l'application (fabriques, configurations, modles de donnes, etc.) et qui contient les contrleurs et vues de la partie utilisateur;

Cas dutilisation Chapitre 17

295

Administration qui contient les contrleurs et vue de ladministration du site. Dans notre projet, l'administration n'a pas t dveloppe, mais le module est prsent pour indiquer comment peut tre faite la sparation entre les diffrentes fonctionnalits du site et sert aussi d'exemple pour la gestion de la restriction de chargement de modules que l'on verra plus loin; Cron qui contient les contrleurs pour la gestion des tches automatises. L'avantage de sparer ces trois modules est qu'il est dsormais possible de dsactiver l'un d'entre eux temporairement sans affecter le cur de l'application. Le module Application contient l'ensemble des fabriques et modles de l'application, ce module ne peut pas tre dsactiv. Voici la hirarchie des fichiers:

Le dossier vendor contient trois dossiers: ZF2 qui est toutes les bibliothques du Zend Framework : le framework, ZendService, ZendGData et ZendRest; ZFBook qui est la bibliothque propre au projet; ZFMLL qui est la bibliothque permettant de raliser la restriction de chargement de modules que nous verrons dans la prochaine section. Maintenant que notre architecture est dfinie, analysons la bibliothque pour la restriction de chargement de modules qui va nous permettre de nous plonger au

296

Cas dutilisation Chapitre 17

cur du composant ModuleManager.

Personnalisation du chargement de modules


La restriction de chargement
Lors du chargement des modules du projet, le framework charge et initialise chacun des modules avant de garder la configuration en mmoire. Une fois cette tape ralise, lorsqu'un nouvel utilisateur fait une requte sur la plateforme Web, le framework pourra supprimer l'tape de cration de configuration qui est en cache et aura juste initialiser les modules. Cependant, dans certains cas, le fait d'initialiser tous les modules peut tre pnalisant. En effet, nous pouvons souhaiter restreindre l'accs la zone d'administration un certain nombre d'adresses IP ou l'accs en HTTPS uniquement. Nous pouvons galement envisager de charger le module de Cron uniquement si nous utilisons la SAPI CLI de PHP car ce module ne doit pas tre accessible par un autre moyen. C'est partir de ce raisonnement que j'ai mis en place une bibliothque permettant le chargement la demande en indiquant les conditions de chargement. Ce module tend les fonctionnalits du gestionnaire de modules. Il peut tre tlcharg sur mon compte Github. Il est conseill de bien avoir assimil le chapitre sur la gestion des modules avant de lire l'implmentation de cette bibliothque afin de comprendre les explications fournies. Voyons maintenant son implmentation.

Limplmentation du chargement la demande


Afin de grer la restriction du chargement, la premire tape est d'tendre la classe ListenerOptions qui gre le stockage des options lies aux modules. Cette nouvelle classe hrite de celle propose par le framework et dfinit un nouvel attribut: ZFMLL/ModuleManager/Listener/ListenerOptions.php
class ListenerOptions extends BaseListener { protected $lazyLoading = array(); ublic function getLazyLoading() p { return $this->lazyLoading; } public function setLazyLoading(array $lazyLoading) { $this->lazyLoading = $lazyLoading; return $this; } }

L'attribut lazyLoading contiendra toutes les restrictions de chargement que l'on aura dfinies. Voici un exemple d'utilisation depuis la configuration de l'application:

Cas dutilisation Chapitre 17

297

application.config.php
<?php return array( [] 'module_listener_options' => array( [] 'lazy_loading' => array( 'Cron' => array( 'sapi' => 'cli', ), 'Administration' => array( 'port' => 443, 'remote_addr' => array('127.0.0.1'), ), ), [] );

Comme pour les options, la classe DefaultListenerAggregate qui contient la liste des couteurs du gestionnaire de modules a t tendue afin de grer ses propres vnements: ZFMLL/ModuleManager/Listener/AuthListenerAggregate.php
class AuthListenerAggregate extends DefaultListenerAggregate { public function attach(EventManagerInterface $events) { $options = $this->getOptions(); $lazyLoading = $options->getLazyLoading(); $listenerManager = new AuthManager($lazyLoading); $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_ MODULE_AUTH, array($listenerManager, 'authorize')); return parent::attach($events); } }

Un nouvel vnement, avec pour identifiant loadModuleAuth va permettre de court-circuiter le chargement de base afin d'effectuer les vrifications ncessaires. Une fois les classes de base redfinies, il n'y a plus qu' surcharger le comportement du gestionnaire de modules. La vrification des restrictions doit se faire juste avant le chargement afin de ne charger que ceux qui remplissent les conditions dfinies. Le nouvel vnement peut donc tre lanc avant la phase de chargement: ZFMLL/ModuleManager/ModuleManager.php
public function loadModules() { $this->getEventManager()->trigger(ModuleEvent::EVENT_LOAD_MODULES_ AUTH, $this, $this->getEvent()); return parent::loadModules(); }

La mthode onLoadModulesAuth() qui coute cet vnement se contente de mettre jour le tableau de module en fonction du besoin de chargement de chacun

298

Cas dutilisation Chapitre 17

d'entre eux: ZFMLL/ModuleManager/ModuleManager.php


public function onLoadModulesAuth() { [] $modules = array(); foreach ($this->getModules() as $moduleName) { $auth = $this->loadModuleAuth($moduleName); if($auth) { $modules[] = $moduleName; } } $this->setModules($modules); }

La mthode loadModuleAuth() lance l'vnement loadModuleAuth qui permet de dfinir si le module doit tre charg ou non. Une fois que chacun des modules a t vrifi, le tableau est mis jour afin de reprendre le cycle habituel de chargement. Plus le nombre de modules sera restreint, plus notre application Web se chargera vite. Dans cet exemple, les modules d'administration et de gestion des tches automatises ne seraient pas chargs lorsque l'on demande l'accs la page d'accueil. D'aprs les benchmarks effectus, le temps de chargement des modules diminue de 5 70% en fonction des rgles et besoins dfinis dans la configuration. Cela peut aussi tre intressant d'un point de vue scurit en ne laissant aucune possibilit d'agir sur la zone d'administration ou de gestion des crons depuis un accs sur le port80 ou depuis un simple navigateur par exemple. Cette bibliothque utilise aussi un gestionnaire de plugins afin de grer les classes de vrification de restriction, ce qui nous permet de consulter les possibilits offertes par la bibliothque: ZFMLL/ModuleManager/Listener/ListenerManager.php
class ListenerManager extends AbstractPluginManager { protected $invokableClasses = array( 'datetime' => 'ZFMLL\ModuleManager\Listener\Server\DateTime', 'hostname' => 'ZFMLL\ModuleManager\Listener\Server\ DomainListener', 'getopt' => 'ZFMLL\ModuleManager\Listener\Environment\ GetoptListener', 'http_method' => 'ZFMLL\ModuleManager\Listener\Server\ HttpMethod', 'https' => 'ZFMLL\ModuleManager\Listener\Server\HttpsListener', 'port' => 'ZFMLL\ModuleManager\Listener\Server\PortListener', 'remoteaddr' => 'ZFMLL\ModuleManager\Listener\Server\ RemoteAddrListener', 'sapi' => 'ZFMLL\ModuleManager\Listener\Environment\ SapiListener', 'url' => 'ZFMLL\ModuleManager\Listener\Server\UrlListener', 'user_agent' => 'ZFMLL\ModuleManager\Listener\Server\ UserAgent', ); protected $aliases = array( 'php_sapi' => 'sapi', 'domain' => 'hostname',

Cas dutilisation Chapitre 17


'uri' => 'url', 'remote_addr' => 'remoteaddr', 'ip' => 'remoteaddr', 'http_user_agent' => 'user_agent', ); [] }

299

Nous remarquons que la restriction peut porter sur l'heure courante, la mthode HTTP utilise, le nom de domaine, l'URL d'accs, le port utilis, l'agent utilisateur, etc. Une fois cette tape effectue, le chargement reprend son cours normal et la configuration de l'application est cre. Analysons celle de notre projet.

La configuration
Notre projet ne comporte pas de variable d'environnement, car comme nous l'avons expliqu dans le chapitre sur les configurations, un fichier de configuration locale sera dploy sur les serveurs concerns en mme temps que le code d'application. Adopter cette mthodologie permet de ne plus avoir versionner les informations sensibles et permet de limiter les effets de bord avec l'utilisation d'une variable d'environnement. Le fichier local.config.php dans le dossier config/autoload contient donc les configurations propres l'environnement. Dans notre projet, nous retrouverons les configurations des bases de donnes et paramtres SMTP: config/autoload/local.config.php
<?php return array( 'db' => array( 'driver' => 'Pdo', 'dsn' => 'mysql:dbname=zf2book;host=127.0.0.1', 'username' => 'root', 'password' => 'root', ), 'smtp_options' => array( 'host' => '', 'connection_config' => array( 'username' => '', 'password' => '', ) ), );

Un fichier, non versionn, est dploy sur le serveur de production avec les valeurs qui seront utilises sur cet environnement. Aucune information sensible n'est donc versionne. En ce qui concerne le reste des configurations, le module Application dfinit une partie de sa configuration dans le fichier module.config.php:

300

Cas dutilisation Chapitre 17

module.config.php
<?php return array( 'router' => include 'routes.config.php', 'service_manager' => array( 'factories' => array( 'DefaultNavigation' => 'Zend\Navigation\Service\ DefaultNavigationFactory', ), ), 'controllers' => array( 'invokables' => array( 'application-index' => 'Application\Controller\ IndexController', ), ), 'controller_plugins' => array( 'invokables' => array( 'flashmessenger' => 'ZFBook\Mvc\Controller\Plugin\ FlashMessenger', ), ), 'view_helpers' => array( 'invokables' => array( 'tags' => 'ZFBook\View\Helper\Tags', 'messages' => 'ZFBook\View\Helper\FlashMessenger', 'userTwitter' => 'ZFBook\View\Helper\UserTwitter', ), ), [] );

Ce fichier nous permet de dfinir nos propres aides de vue et d'action ainsi que nos contrleurs. Le reste de la configuration est dfini depuis la classe Module: Module.php
class Module implements AutoloaderProviderInterface, ServiceProviderInterface, ConfigProviderInterface { [] public function getServiceConfig() { return array( 'invokables' => array( 'TweetService' => 'Application\Model\Service\ TweetService', [] ), 'factories' => array( 'SmtpOptions' => function($sm) { $config = $sm->get('config'); return new SmtpOptions($config['smtp_ options']); }, 'DbAdapter' => function($sm) { $config = $sm->get('config'); $config = $config['db']; $dbAdapter = new DbAdapter($config); return $dbAdapter;

Cas dutilisation Chapitre 17


}, 'TweetTable' => function($sm) { return new Table\TweetTable('tweet', $sm>get('DbAdapter')); }, [] ), 'aliases' => array( 'TweetModel' => 'TweetTable', [] ), ); }

301

Chaque fabrique de modles de table est dfinie dans la partie rserve au gestionnaire de services et une fabrique permet d'englober les options de notre application: SmtpOptions. Cette classe permet de piloter les options propres l'envoi d'e-mails. Toutes nos configurations et fabriques sont prtes, il ne reste plus qu' les utiliser dans nos contrleurs.

Les contrleurs de l'application


Les contrleurs du module Application se contentent d'utiliser les modles pour afficher les donnes avec le paginateur, dont voici quelques exemples: Listing des tweets et tutoriels de la page d'accueil
public function indexAction() { $sm = $this->getServiceLocator(); return array( 'tweets' => $sm->get('TweetModel')->fetchAllLastValid(25), 'tutofr' => $sm->get('TutorielModel')->fetchAllLastValidByLang( 'fr',3), 'tutoen' => $sm->get('TutorielModel')->fetchAllLastValidByLang( 'en',3), ); }

Voici une capture d'cran de la page d'accueil:

La page listant les tweets utilise le paginateur du framework:

302

Cas dutilisation Chapitre 17

Listing des tweets pagins


public function tweetAction() { $page = $this->getRequest()->getQuery()->get('page', 1); $numByPage = 25; $sm = $this->getServiceLocator(); $tweets = $sm->get('TweetModel')->getQueryLastValid(); paginator = new Paginator\Paginator(new Paginator\Adapter\ $ DbSelect($tweets, $sm->get('DbAdapter'))); $paginator->setItemCountPerPage($numByPage); $paginator->setCurrentPageNumber($page); $paginator->setPageRange(5); eturn array('tweets' => $paginator); r }

Chaque modle est rcupr depuis le gestionnaire de services afin de toujours utiliser la mme instance d'objet. En effet, il n'est pas ncessaire de crer un objet de modle chaque fois que l'on souhaite interroger la base de donnes. De plus, comme nous avons fait un alias de TweetModel vers TweetTable, il devient facile de changer le mode de stockage des donnes si besoin en redfinissant l'alias. La mthode du modle rcupre simplement la liste des derniers tweets valides: Application/Model/Table/TweetTable.php
public function getQueryLastValid() { $select = $this->getSql()->select() ->columns(array('date','user','text')) ->join('language','language.id = tweet.language') ->where(array('moderate'=>1)) ->order('date DESC'); return $select; }

Voici une capture d'cran de la page listant les tweets:

Les formulaires sont utiliss dans l'action qui permet d'enregistrer un profil de dveloppeur sur le site:

Cas dutilisation Chapitre 17

303

Formulaire d'ajout d'un utilisateur


public function registerdeveloperAction() { $form = new Form\Developpeur(); $request = $this->getRequest(); if ($request->isPost()) { $formData = $request->getPost(); $form->setData($formData); if ($form->isValid()) { $formData = $form->getData(); $sm = $this->getServiceLocator(); $sm->get('DeveloperModel')->register($formData); $this->plugin('flashmessenger')->addValidMessage('Merci pour l\'inscription, celle-ci est gnralement prise en compte sous 48h.'); return $this->plugin('redirect')->toRoute('registerdeveloper'); } $this->flashMessenger()->addErrorMessage('Merci de corriger les erreurs du formulaire.'); } eturn array('form' => $form); r }

Le formulaire d'enregistrement vrifie la validit des donnes avant de les enregistrer. Voici le formulaire d'enregistrement: Application/Form/Form/Developpeur.php
class Developpeur extends AbstractForm { public function init() { $this->add(new Fieldset\WebIdentityFieldset()); $this->add(new Fieldset\PHPKnowledge()); $this->add(new Fieldset\SocialLinks()); $this->add(new Element\Button\Add()); } }

L'appel la mthode init() est fait depuis la classe AbstractForm, aucune mthode de ce type n'est prvue dans les formulaires du framework. Afin de pouvoir rutiliser les lments et conteneurs d'lments dans d'autres formulaires, des classes de base ont t cres afin de construire rapidement de simples formulaires. Ces classes fournissent un lment avec un type, label et nom par dfaut. Le conteneur WebidentityFieldset comporte des lments de base: Application/Form/Fieldset/WebIdentityFieldset.php
class WebIdentityFieldset extends AbstractFieldset { public function __construct($name = null) { parent::__construct('web_identity'); $this->add(new Personnal\Firstname()); $this->add(new Personnal\Name());

304

Cas dutilisation Chapitre 17


$this->add(new Personnal\Email()); } }

Tous les lments de base ont t crs pour les besoins du projet car d'autres formulaires les utilisent. Cette manire de faire dpend des besoins du projet, celui-ci ne ncessite pas de personnalisation avance et la cration d'lments gnriques permet un gain de temps lors de la cration de nouveaux formulaires. Une fois les informations soumises, celles-ci sont enregistres dans le modle de la table des dveloppeurs: Application/Model/Table/DeveloperTable.php
public function register($datas) { $data = array( 'name' => $datas['web_identity']['name'], 'email' => $datas['web_identity']['email'], 'is_php_5_certified' => (boolean)$datas['php_knowledge'] ['php5certification'], 'is_php_53_certified' => (boolean)$datas['php_knowledge'] ['php53certification'], 'is_php_zf1_certified' => (boolean)$datas['php_knowledge'] ['zf1certification'], 'viadeo' => $datas['social_links']['viadeolink'], 'linkedin' => $datas['social_links']['linkedinlink'], 'twitter' => $datas['social_links']['twitterlink'], 'valid' => 0, ); return $this->insert($data); }

Le rendu du formulaire est trs simple: Rendu du formulaire


<?php echo $this->messages(array('valid', 'error')); ?> <?php $form = $this->form; $form->prepare(); $form->setAttribute('action', $this->url()); $form->setAttribute('method', 'post'); echo $this->form()->openTag($form); ?> <dl class="zend_form"> <?php echo $this->formRow($form->get('web_identity')->get('name')); ?> <?php echo $this->formRow($form->get('web_identity') >get('firstname')); ?> <?php echo $this->formRow($form->get('web_identity')->get('email')); ?> <?php echo $this->formRow($form->get('php_knowledge') >get('php5certification')); ?> <?php echo $this->formRow($form->get('php_knowledge') >get('php53certification')); ?>

Cas dutilisation Chapitre 17


?php echo $this->formRow($form->get('php_knowledge')< >get('zf1certification')); ?> <?php echo $this->formRow($form->get('social_links') >get('twitterlink')); ?> <?php echo $this->formRow($form->get('social_links') >get('viadeolink')); ?> <?php echo $this->formRow($form->get('social_links') >get('linkedinlink')); ?> <?php echo $this->formRow($form->get('add')); ?> </dl> <?php echo $this->form()->closeTag() ?>

305

Voici le rendu du formulaire:

Le module Application contient la mise en forme des donnes. L'ajout de donnes, comme les tweets, se fait depuis le module Cron que l'on va maintenant expliquer.

Les tches automatises


Implmentation
La gestion des tches automatises utilise le composant de la console et son router afin de grer le lancement des actions en ligne de commande. La configuration de ce module indique les options des services ainsi que les routes utiliser:

306

Cas dutilisation Chapitre 17

module.config.php
<?php return array( 'cron_options' => array( 'twitter_options' => array( 'queries' => array( '#zf2', 'zend framework 2', '#zendframework2', '#zf2conf' ), 'languages' => array( 'fr', 'en', ), ), ), 'controllers' => array( 'invokables' => array( 'cron-crawl' => 'Cron\Controller\CrawlController', 'cron-publish' => 'Cron\Controller\PublishController', ), ), 'console' => array( 'router' => array( 'routes' => array( 'crawl-tweet' => array( 'type' => 'simple', 'options' => array( 'route' => '--crawl-tweet', 'defaults' => array( 'controller' => 'cron-crawl', 'action' => 'tweet', ), ), ), [] ), ), ), );

Chaque action sera alors dfinie depuis les routes indiques dans le router. Notons que nous avons enregistr un tableau de configuration sous la cl cron_options qui nous permettra d'utiliser les options dfinies depuis un objet cr par la fabrique CronModuleOptions: Module.php
public function getServiceConfig() { return array( 'factories' => array( 'CronModuleOptions' => function($sm) { $config = $sm->get('Config'); return new Options\ModuleOptions($config['cron_ options']); }, ), ); }

Cas dutilisation Chapitre 17

307

Ces options vont pouvoir tre utilises facilement depuis les contrleurs d'actions qui recherchent les nouveaux tweets ou vidos propos du Zend Framework. Cette recherche va tre effectue grce au composant ZendService qui fournit des classes pour piloter les API de Twitter ou YouTube par exemple. Passons maintenant leur implmentation.

Utilisation du composant ZendService


Le contrleur Cron\Controller\CrawlController effectue la recherche de tweets, prsentation sur Slideshare et vidos sur YouTube, voici l'implmentation de la recherche sur Twitter: Cron/Controller/CrawlController.php
public function tweetAction() { $sm = $this->getServiceLocator(); $twitterOptions = $sm->get('CronModuleOptions')->getTwitterOptions(); $queries = $twitterOptions->getQueries(); $langs = $twitterOptions->getLanguages(); $results_type = array('recent', 'popular'); $list = 0; foreach($langs as $lang) { foreach($queries as $query) { foreach($results_type as $result_type) { $search = new Twitter\Search(); $search->setOptions(new Twitter\ SearchOptions(array( 'rpp' => 25, 'include_entities' => true, 'result_type' => $result_type, 'lang' => $lang, ))); $results = $search->execute($query); foreach($results['results'] as $result) { $list += (integer)$sm->get('TweetService')>addTweet($result); } } } } return $list . ' tweets ajouts'; }

Les options de recherche sont rcupres depuis la fabrique CronModuleOptions et une boucle est effectue pour couvrir l'ensemble des recherches. Une fois celles-ci excutes, les tweets trouvs sont enregistrs dans le modle et le nombre total est retourn. Voici un exemple de sortie aprs le lancement de cette action: Sortie l'cran
5 tweets ajouts

Les composants fournis par le framework pour piloter les API de services externes sont trs simples d'utilisation et permettent un gain de temps non ngligeable.

308

Cas dutilisation Chapitre 17

Les modules du framework


L'quipe de dveloppement du framework met disposition un site qui rpertorie les modules crs pour le framework par les dveloppeurs qui souhaitent partager leurs dveloppements: http://modules.zendframework.com. De nombreux modules sont dj disponibles, par exemple: intgration de Doctrine; intgration de Google Analytics; intgration de solutions de paiement; gestion des API d'emailing comme Amazon, Mailchimp, etc.; gestion du moteur de template TWIG. Vous retrouverez la liste complte des modules sur le site, et il est trs facile pour vous d'y contribuer. Pour cela, il suffit de laisser le code de votre module en tlchargement libre, sur Github par exemple, et de demander l'ajout de votre module la liste. Vous ferez ainsi bnficier la communaut de votre module.

Contribuer au framework
Beaucoup de dveloppeurs se demandent ou aimeraient savoir comment contribuer au Zend Framework. Sil existe de nombreuses aides afin dexpliquer les tapes de la participation, il nexiste pas dendroit ou toutes les informations sont centralises et expliques de faon prcise. Afin dtre le plus complet possible, nous verrons comment suivre le planning de dveloppement du framework, savoir o trouver le code de dveloppement qui volue au fur et mesure des changements effectus par les dveloppeurs, comment reporter et suivre un bogue que lon a rencontr sur le framework et enfin comment proposer une correction de bogue ou apporter une fonctionnalit.

18

Le planning
Lquipe de Zend utilise le service AgileZen comme planning de dveloppement. AgileZen est un outil de gestion de projet en ligne, disponible lURL suivante: http://www.agilezen.com. Si vous dsirez suivre le projet sur loutil, il est ncessaire de demander tre invit sur le projet, vous trouverez les informations ncessaires sur le site devzone.zend.com. Sur la page du projet, vous retrouverez les tches en cours, ce quil reste faire et ce qui est fait. Voici un aperu du projet sous AgileZen:

310

Contribuer au framework Chapitre 18

Chaque colonne reprsente un tat de tche en cours. Cela permet de voir lavance de la version en cours du framework. Cette capture dcran a t faite juste avant la sortie de la bta 4, on remarque quil reste peu de choses avant la finalisation de cette version, et lon remarque aussi le tag 2.0.0beta5 qui nous indique lexistence dune bta 5. Il est possible aussi dobtenir plus dinformations sur une tche prcise en cliquant sur celle-ci:

Contribuer au framework Chapitre 18

311

La liste des actions associes la tche effectuer permet de connatre en dtail ce qui va tre fait et ce quil reste faire. Le dveloppeur indique parfois des commentaires lis la tche en cours afin de fournir plus dinformations dventuels contributeurs. Lorsquune tche est termine, celle-ci glisse dans la colonne Complete et est ensuite fusionne avec le code du framework dans la branche de dveloppement en cours. Il est alors possible davoir accs immdiatement la fonctionnalit.

La gestion des versions


Le gestionnaire de versions utilis par lquipe de dveloppement est Git. Le code est disponible sur Github ladresse suivante: http://github.com/zendframework/zf2. Sur ce dpt vous retrouverez la dernire version du framework ainsi que la branche de dveloppement mise jour en continu. Vous y retrouverez aussi les tests unitaires et la documentation du framework. Sur ce dpt, vous pouvez aussi voir les demandes dajout de code faites par les dveloppeurs de Zend comme celles faites par nimporte quel dveloppeur souhaitant contribuer au framework. Ces demandes dajout sont appeles Pull Requests (PR) et sont visibles dans longlet du mme nom: http://github.com/ zendframework/zf2/pulls. Cest par les Pulls Resquests que passera chaque code qui sera ajout au framework. Longlet Graphs permet davoir des statistiques sur lactivit du dpt ou encore des statistiques sur les dveloppeurs du framework. Vous pouvez par exemple retrouver lactivit en fonction du nombre de commits effectus (http://github.com/ zendframework/zf2/graphs/commit-activity), les statistiques de contribution des dveloppeurs (http://github.com/zendframework/zf2/graphs/contributors), ou encore la liste des contributions dun dveloppeur, par exemple pour mon profil: http://github.com/zendframework/zf2/commits?author=blanchonvincent. Gnralement, les Pulls Requests sont divises en trois catgories: Les bugfixs, ou corrections de bogue;

312

Contribuer au framework Chapitre 18

Les features ou ajouts de fonctionnalit; La modification de documentation ou de rgles de codage. Pour les deux premiers lments, il est impratif douvrir un ticket sur le gestionnaire de bogues du framework avant de proposer une Pull Request.

Le bug tracker
Le gestionnaire de bogues, ou bug tracker, est disponible ladresse http://framework.zend.com/issues. Une fois inscrit, nous avons alors accs lensemble des tickets ouverts pour le framework:

Lorsque lon souhaite ouvrir un ticket, il est seulement ncessaire de dcrire le bogue ou la fonctionnalit ainsi que de choisir sur quel composant ce ticket sera reli. Le fait de relier un ticket un composant permet daffecter directement le ticket au dveloppeur concern et responsable du composant:

Contribuer au framework Chapitre 18

313

Lorsque lon crit le ticket, si lon souhaite proposer soi-mme le correctif, il est ncessaire de lindiquer dans le ticket afin que le travail ne soit pas fait deux fois. Le dveloppeur responsable du composant affectera le ticket son crateur et sera en attente de la Pull Request.

Corriger un bogue ou proposer une amlioration


Afin de proposer une correction de bogue ou une nouvelle fonctionnalit, il est important douvrir un ticket afin den avertir la communaut, car ce bogue peut dj tre en cours de correction ou la fonctionnalit en cours dimplmentation par un autre dveloppeur. Une fois cette tape faite, nous pouvons passer la modification du code. La premire tape est de se crer un compte sur Github. En effet, sans compte, il ne va pas tre possible de proposer ses modifications aux dveloppeurs. Lorsque lon possde son compte, il faut ensuite forker le projet de Zend Framework 2 depuis le bouton fork de la page du projet. Une fois le projet fork, il faut rcuprer le code sur son environnement de travail local. En fonction de votre poste de travail (Windows, Mac ou Linux) vous suivrez les instructions de Github. Notez quil existe un excellent logiciel pour Mac afin dinteragir avec celui-ci. Lorsque le code du framework est sur notre machine locale, il est ncessaire de tirer une nouvelle branche afin dy apporter les modifications que lon souhaite ajouter. Lajout dune branche permettra de conserver la branche master jour avec lvolution du code du framework. Lorsque la branche est cre, nous allons pouvoir lui apporter toutes les modifications que lon souhaite faire. Une fois celles-ci effectues, il est indispensable de mettre jour les tests unitaires lis notre modification. Si les tests unitaires nont pas t modifis en consquence, les modifications seront refuses par lquipe de dveloppement.

314

Contribuer au framework Chapitre 18

Lorsque les modifications sont faites et que les tests unitaires sont jour, il est alors temps de pousser notre code sur notre dpt github. La nouvelle branche va alors tre cre, et il est possible den faire une Pull Request depuis le bouton Pull Request. Une fois notre requte valide, nous pouvons la visualiser dans la liste des Pull Requests en attente dajout au framework: https://github.com/ zendframework/zf2/pulls.

Rcapitulatif des tapes


Voici le rcapitulatif des tapes mener avant de pouvoir contribuer au framework: crer un compte Github; crer un compte sur le bug tracker; ouvrir un ticket sur le bug tracker; forker le projet Zend Framework 2; crer une branche pour sa modification; faire ses modifications et mettre les tests unitaires jour; pousser son code local sur sa nouvelle branche; faire une demande de Pull Request; attendre et rpondre aux commentaires ventuels sur sa Pull Request; fermer le ticket une fois la Pull Request fusionne dans le framework; Vous avez maintenant toutes les cartes en main pour contribuer au framework.

Vous aimerez peut-être aussi