Académique Documents
Professionnel Documents
Culture Documents
Au Coeur de Zend Framework 2
Au Coeur de Zend Framework 2
Zend Framework 2
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-
Michal Gallego
Contributeur au Zend Framework 2
Administrateur du forum ddi la communaut franaise du Zend Framework
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
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
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
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
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
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
La configuration 299
Les contrleurs de l'application301
Les tches automatises 305
Implmentation.......................................................................................... 305
Utilisation du composant ZendService.................................................. 307
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
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
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
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
)
));
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;
}
[]
}
= '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;
}
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{}
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();
= '\\';
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
24
Les autoloaders
Chapitre 2
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();
Les autoloaders
Chapitre 2
25
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;
}
}
[]
}
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];
}
}
28
Les autoloaders
Chapitre 2
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;
}
[]
}
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
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.
32
Les vnements
Chapitre 3
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())
);
});
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);
}
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())
)
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
38
Les vnements
Chapitre 3
Zend/EventManager/EventManager.php
public function getSharedManager()
{
[]
$this->sharedManager = StaticEventManager::getInstance();
return $this->sharedManager;
}
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
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
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
}
Les vnements
Chapitre 3
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);
}
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);
}
[]
}
Les vnements
Chapitre 3
43
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);
44
Les vnements
Chapitre 3
} else {
$e = new $this->eventClass();
$e->setName($event);
$e->setTarget($target);
$e->setParams($argv);
}
[]
}
Les vnements
Chapitre 3
45
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);
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
}
Les vnements
Chapitre 3
47
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
Les vnements
Chapitre 3
49
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.
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();
50
Les vnements
Chapitre 3
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);
}
}
Les vnements
Chapitre 3
51
52
Les vnements
Chapitre 3
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
}
}
Linjection de dpendances
Chapitre 4
55
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.
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);
}
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'];
}
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);
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')
);
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;
}
= $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
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);
}
}
66
Le gestionnaire de services
Chapitre 5
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;
}
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
Le gestionnaire de services
Chapitre 5
71
}
}
[]
}
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;
}
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
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');
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);
}
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;
}
Le gestionnaire de services
Chapitre 5
75
Affichage de la sortie
AbstractFactories\Classes\Bar
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;
}
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);
}
}
}
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
Affichage de la sortie
Zend\Navigation\Navigation
1
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'),
),
),
),
),
);
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
}
}
}
80
Le gestionnaire de services
Chapitre 5
Affichage de la sortie
{"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.
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,
);
[]
}
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();
}
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');
[]
}
Les modules
Chapitre 6
85
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;
[]
}
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',
),
),
[]
);
Les modules
Chapitre 6
87
88
Les modules
Chapitre 6
Les modules
Chapitre 6
89
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'));
}
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
Zend/ModuleManager/ModuleManager.php
public function onLoadModules()
{
if (true === $this->modulesAreLoaded) {
return $this;
}
foreach ($this->getModules() as $moduleName) {
$this->loadModule($moduleName);
}
$this->modulesAreLoaded = true;
}
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
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;
}
Les modules
Chapitre 6
93
= str_replace('\\', DIRECTORY_SEPARATOR,
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(
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);
[]
}
Les modules
Chapitre 6
95
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();
[]
}
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;
}
Les modules
Chapitre 6
97
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__,
),
),
);
}
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());
}
Notre module possde alors un couteur fonctionnel si celui-ci implmente linterface BootstrapListenerInterface.
Les modules
Chapitre 6
99
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
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;
}
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.
Ceci lui permettra par la suite dajouter au gestionnaire dinstances nos diffrents
objets de modules afin de permettre un accs aux modules depuis lapplication.
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;
}
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',
),
[]
);
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",
),
[]
);
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,
),
),
[]
);
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);
}
}
}
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
[]
}
}
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
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.
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');
}
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.
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.
114
Les configurations
Chapitre 7
Application/Module.php
public function getConfig()
{
return include __DIR__ . '/config/'. APPLICATION_ENV.'.module.config.
php';
}
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,
[]
);
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
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
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',
)
),
),
)
),
[]
);
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;
}
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();
}
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,
),
[]
),
);
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;
}
126
Le router
Chapitre 8
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);
}
Le router
Chapitre 8
127
[]
),
),
);
Nous voyons que le protocole est rcupr depuis lobjet de requte afin dtre
vrifi avec celui reu en paramtre.
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');
}
[]
}
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);
[]
}
Le router
Chapitre 8
129
Ce type de route peut aussi tre utilis pour filtrer laccs un sous-domaine de
notre application Web.
130
Le router
Chapitre 8
Zend/Mvc/Router/Http/Segment.php
public function
{
[]
if ($pathOffset
$result =
$matches, null,
} else {
$result =
$matches);
}
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.
Le router
Chapitre 8
131
'type' => 'wildcard',
'options' => array(
'key_value_delimiter' => '-',
'param_delimiter' => '/',
),
'may_terminate' => true,
),
),
),
[]
),
),
132
Le router
Chapitre 8
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.
Le router
Chapitre 8
133
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);
}
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) {
[]
}
140
Les contrleurs
Chapitre 9
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();
}
142
Les contrleurs
Chapitre 9
$e->setResult($actionResponse);
return $actionResponse;
}
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;
}
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();
}
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'
));
}
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.
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);
}
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;
[]
}
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();
}
[]
}
10
152
153
Zend/Mvc/Controller/Plugin/Redirect.php
public function toUrl($url)
{
$response = $this->getResponse();
$response->headers()->addHeaderLine('Location', $url);
$response->setStatusCode(302);
return $response;
}
154
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');
}
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.
155
156
Laide daction retourne les donnes enregistres lors de la soumission des donnes. Une fois celles-ci rcupres, elles se dtruisent alors automatiquement. Voici
157
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.
158
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',
),
);
}
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 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;
}
162
Le gestionnaire de plugins
Chapitre 11
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([]);
}
}
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([]);
}
}
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.
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.
Les vues
Chapitre 12
167
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
Ces deux couteurs offrent plus de flexibilit avec le type de retour de l'action
courante. Analysons maintenant la prparation des vues d'erreurs.
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
170
Les vues
Chapitre 12
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
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'));
}
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.
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;
}
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;
}
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;
}
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();
}
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);
}
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);
}
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;
}
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
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
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
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));
}
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
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);
[]
}
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];
}
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
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);
}
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
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();
}
}
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'
));
}
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
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
196
Les vues
Chapitre 12
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;
}
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);
}
[]
}
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
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
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;
}
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);
}
}
}
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;
}
202
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'),
),
),
);
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')
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
Zend/View/Helper/Navigation/AbstractHelper.php
protected function parseContainer(&$container = null)
{
[]
if (is_string($container)) {
[
]
$container = $sl->get($container);
}
[]
}
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();
?>
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',
);
}
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
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');
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.
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
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'));
?>
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);
}
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
211
'height'
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;
}
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
Rendu de JSON
public function jsonrenderAction()
{
$view = new ViewModel(array('content'=>array('value1','value2')));
$view->setTerminal(true);
return $view;
}
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;
});
[]
}
Si le nom de notre route nest pas nul, le router se contente de faire appel la
213
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);
}
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.
214
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',
),
);
}
}
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.
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
)
));
}
}
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();
}
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',
[]
),
[]
);
[]
}
= $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();
}
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);
}
Une fois tous les lments de lapplication initialiss, le processus principal peut
alors dmarrer.
Le cur du framework
Chapitre 14
221
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();
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;
}
222
Le cur du framework
Chapitre 14
Le cur du framework
Chapitre 14
223
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');
[]
}
224
Le cur du framework
Chapitre 14
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);
}
226
Le cur du framework
Chapitre 14
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;
}
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();
}
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;
}
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'));
230
Les composants
Chapitre 15
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
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);
}
[]
}
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
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');
}
}
Les composants
Chapitre 15
233
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;
}
234
Les composants
Chapitre 15
Requte SQL
SELECT * FROM `matable`
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'
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
}
236
Les composants
Chapitre 15
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();
Les composants
Chapitre 15
237
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();
}
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;;
[]
}
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
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');
}
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');
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');
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);
}
}
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
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 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');
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
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.
Les composants
Chapitre 15
249
Nous aurions pu passer directement par ladaptateur pour avoir le mme rsultat:
Utilisation de ladapteur
$console = new Console\Adapter\Posix();
$console->writeLine('Hello world');
Nettoyage du terminal
$console = Console\Console::getInstance();
$console->clear();
cran de sortie
10
250
Les composants
Chapitre 15
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";
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";
252
Les composants
Chapitre 15
return $response;
}
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();
}
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";
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.
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;
}
256
Les composants
Chapitre 15
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);
}
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;
}
}
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.
260
Les composants
Chapitre 15
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);
Les composants
Chapitre 15
261
$datasCrypted = $mcrypt->encrypt('long-datas-to-crypt');
$dataDecrypted = $mcrypt->decrypt($datasCrypted);
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.
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();
Les composants
Chapitre 15
263
'label' => 'Nom:',
),
));
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;
}
Les composants
Chapitre 15
265
266
Les composants
Chapitre 15
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;
}
[]
}
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
<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>.
270
Les composants
Chapitre 15
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
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',
);
[]
}
De mme, il est possible de grer facilement ses e-mails depuis le format ouvert
mbox:
Les composants
Chapitre 15
275
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');
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';
276
Les composants
Chapitre 15
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;
}
Les composants
Chapitre 15
277
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;
}
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
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;
}
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;
}
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
}
[]
}
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);
}
}
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);
}
Les composants
Chapitre 15
285
restore_error_handler();
f ($errorException && $throw) {
i
throw $errorException;
}
return $errorException;
}
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);
}
}
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
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
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 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
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
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
}
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
{}
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
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
296
Cas dutilisation
Chapitre 17
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'),
),
),
[]
);
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.
302
Cas dutilisation
Chapitre 17
Les formulaires sont utiliss dans l'action qui permet d'enregistrer un profil de
dveloppeur sur le site:
Cas dutilisation
Chapitre 17
303
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());
}
}
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);
}
Cas dutilisation
Chapitre 17
305
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.
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.
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
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
312
Contribuer au framework
Chapitre 18
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:
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.
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.