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

23

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;
}

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

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);

41

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

55

'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,
),
),
),
))));

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

63


if (array_key_exists('__construct', $injectionMethods)) {

unset($injectionMethods['__construct']);
}
}
[]
if ($isShared) {

[]

else {

$this->instanceManager->addSharedInstance($instance,
$name);
}
}
[]
return $instance;
}

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

73


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

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

85

'module_listener_options' => array(



'config_cache_enabled' => false,

'cache_dir' => 'data/cache',

'module_paths' => array(

'./module',

'./vendor',
),
),
);

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

95

if ($file->isReadable() && $file->isFile()) {



require_once $file->getRealPath();

if (class_exists($class)) {

$this->moduleClassMap[$class] = $file->getRealPath();

return $class;
}
}
return false;
}

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

101


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;
}

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

123


foreach ($this->routes as $name => $route) {

if (($match = $route->match($request, $baseUrlLength))
instanceof RouteMatch && $match->getLength() === $pathLength) {
$match->setMatchedRouteName($name);

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

129

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));

}

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.

10

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.

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');

[]
}

11

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.

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.

12

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.

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

179

$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);

}

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

183

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);

}

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

189


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

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

191


if ($registry->containerExists($placeholder)) {

$result = (string) $registry>getContainer($placeholder);

break;
}
}
}
$response->setContent($result);
}

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

197

$sharedEvents->attach('MonModule', MvcEvent::EVENT_DISPATCH,
function($e) {

$controller = $e->getTarget();

$controller->plugin('layout')->setTemplate('layout/
application');
}, 100);
}

[]
}

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

13

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();

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

207

$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;
}

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

209


$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);
}

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') ?>

14

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.

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

227

$event->setResponse($response);
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();
}

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.

15

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.

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

231

if (!$connection instanceof Connection) {



$connection = new Connection($connection);
}
[]
}

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

233

$this->queryResultSetPrototype = ($queryResultPrototype) ?: new


ResultSet\ResultSet();
}

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

241


[]
}
$statement = $this->sql->prepareStatementForSqlObject($this->sql
>select()->where($where));
$result = $statement->execute();

$rowData = $result->current();

unset($statement, $result);
$this->populate($rowData, true);
return $rowsAffected;
}

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

245

} catch (\Exception $e) {



$result = false;

return $this->triggerException(__FUNCTION__, $args, $result,
$e);
}
}

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

257

}
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);
}

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

259

}
$model = new ConsoleModel;

$model->setVariable(ConsoleModel::RESULT, $result);
$e->setResult($model);
}

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

261

$datasCrypted = $mcrypt->encrypt('long-datas-to-crypt');
$dataDecrypted = $mcrypt->decrypt($datasCrypted);

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

263


'label' => 'Nom:',
),
));

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

267

}
ublic function getNom()
p
{
return $this->nom;
}
[]
}

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'));

<strong>blanchon<strong>.

$valid = $inputFilter->isValid();

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

271

[]
$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;
}

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

285

restore_error_handler();
f ($errorException && $throw) {
i
throw $errorException;
}
return $errorException;
}

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

287

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;
}

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

16

Les exceptions

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.

17

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.

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

299


'uri' => 'url',

'remote_addr' => 'remoteaddr',

'ip' => 'remoteaddr',

'http_user_agent' => 'user_agent',
);
[]
}

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

301


},

'TweetTable' => function($sm) {

return new Table\TweetTable('tweet', $sm>get('DbAdapter'));

},

[]

),

'aliases' => array(

'TweetModel' => 'TweetTable',

[]
),
);
}

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

305

?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() ?>

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.

18

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.

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