Table des matières
Au cours de ce chapitre,
nous mettrons en place un simple design web pour notre blog. De toute
évidence, cela revient à la couche Vue du design pattern d'architecture
Modèle-Vue-Contrôleur (MVC). D'un point de vue plus bas niveau, cela
correspond au markup HTML et au styling CSS que le blog utilisera. Comme
avec beaucoup d'autres Frameworks, toutes les balises seront stockées sous
forme de templates au sein de notre application, qui seront rendus à la
fin de toute requête, et envoyés comme réponse à l'utilisateur. Les choses
deviennent plus intéressantes si vous considérez qu'une page HTML complexe
peut être découpée en éléments réutilisables, ou même conditionnellement
réutilisables. Par exemple, il est probable que toute page HTML ait des
éléments réutilisables tels une en-tête ou un pieds de page, un menu de
navigation, un chemin de fer, ... A côté de ces éléments, vous aurez du
contenu dynamique et d'autres types d'éléments qui ne seront requis que
pour certaines pages spécifiques. A d'autres endroits, vous aurez besoin
d'appliquer des règles de présentation spécifiques, ou d'insérer du
contenu formaté depuis votre base de données. Utiliser les fonctionnalités
de templating de Zend Framework via Zend_View
peut
réduire la duplication de tout ce code de balisage et de traitement de
templates en utilisant des aides de Vues, des Partials, et des
Placeholders, ainsi que Zend_Layout
, pour tout le
markup externe commun comme les balises <head>
,
<title>
, <html>
et d'autres éléments
incluant les liens vers les feuilles de styles CSS ou les fichiers
Javascript qui ont besoin d'être inclus.
Je ne suis pas un très
bon web deisgner (désolé !), donc, pour notre application de blogging, je
vais rester sur un design relativement simple que quelqu'un d'autre pourra
améliorer ultérieurement. Les balises utilisées pour le blog
correspondront à du HTML 5. HTML 5 est la prochaine révision majeure de
HTML, et il me semble intéressant de réaliser quelques expérimentations
avec le nouveau standard. Puisque le projet XHTML 2.0 a été abandonné
(pour l'instant, du moins), c'est aussi le prochain pseudo-remplacement
majeur pour XHTML. Notons que HTML 5 peut techniquement être envoyé sous
forme d'une sérialisation basée sur HTML ou sur XML (autrement dit,
text/html
ou application/xml+xhtml
, avec les
règles de syntaxe que chacune de ces possiblités implique). En fonction du
support par les navigateurs (IE n'accepte pas le content type XML), seule
la sérialisation HTML est réellement viable, même si j'utilise autant que
possible des conventions XHTML, telles que fermer tous les tags et
utiliser des noms d'attributs et de balises en minuscules, puisque je suis
plus familier avec celles-ci.
Mon utilisation de HTML
5 devrait être considérée comme expérimentale. Ce n'est pas encore une
recommandation finale, et tous les navigateurs ne le supportent pas. Vous
devriez utiliser Firefox 3.5, Internet Explorer 8, ou tout autre version
de navigateur plus récente lorsque vous travaillez avec du HTML 5 - quoi
que ce soit d'autre plus ancien n'aura un support que des plus imparfait.
Cela dit, HTML 5 est en parti rétro-compatible avec HTML 4. Il nécessite
quelques astuces sous Internet Explorer lorsque l'on souhaite styler un
document (IE ne reconnait pas les nouveaux éléments HTML 5 et ne peut les
styler - contrairement aux autres navigateurs), mais nous résoudrons ce
problème un peu plus tard, en utilisant une petite bibliothèque Javascript
bien pratique, html5shiv
.
Pour faciliter la mise
en forme, j'utiliserai aussi le Framework Yahoo User
Interface (YUI) Library's CSS
. J'utiliserai ceci pour normaliser les
comportements entre tous les principaux navigateurs (notamment, types et
tailles de polices, espacements et marges des éléments, etc.).
J'utiliserai aussi son implémentation de grilles pour mettre en place des
sections de pages plus facilement. YUI CSS est un des nombreux Frameworks
CSS disponibles. Dans le passé, j'ai déjà utilisé Elastic
et Blueprint CSS
. Ces
Frameworks sont principalement utilisés pour économiser du temps de
développement, et (espérons le) écrire moins de CSS - un facteur important
pour moi, puisque mes talents de designer laissent quelque peu à désirer,
et que je pourrais passer des heures à jouer avec du paramétrage de CSS
!
Je n'aime vraiment pas
éditer trop de CSS lorsque je peux convaincre un singe avec un gros sac de
cacahuètes de faire un meilleur boulot que moi à ce niveau. Enfin, ça ou
un web designer professionnel (probablement meilleur que le singe). Pour
le moment, je n'ai besoin que d'un design par défaut qui soit correct et
ait l'air à peu près OK au premier coup d'oeil. Pour cet article, nous
allors voir comment créer un style de blog par défaut, utiliser quelques
contenus de remplissage, et finalement coder le design en utilisant des
templates Zend_View
.
Nous autres développeurs PHP avons utilisé des moteurs de templating depuis l'aube de PHP. En fait, PHP lui-même est un moteur de templating, et cela était la raison d'être de toute son existence : vous pouvez mélanger du PHP au sein de votre code HTML très facilement. Un point sur lequel nous n'avons pas été très bon est de fixer une limite entre la présentation (balises / formatage) et la logique de présentation (qui regroupe la logique de génération des balises, le formatage des éléments, ou même les accès au Modèle). Globalement, la seule distinction a été de séparer le PHP du HTML, ce qui rate complètement l'objectif, puisque vous ne faites rien d'autre que remplacer PHP par un language à balises personnalisées. Le seul avantage de cette stratégie est qu'elle évite souvent aux web designers d'utiliser PHP lui-même ; ce qui est sans aucun doute un objectif louable. PHP est un moteur de template ; il n'y a donc rien d'incorrect dans le fait de mélanger du PHP et du HTML. C'est une partie du fonctionnement de PHP. Le faire de manière plus délibérée, en tant que composant d'un système de template, nous pousse simplement à le faire de manière correcte.
Zend_View
est la solution de rendering de templates de Zend Framework, qui formalise
l'idée de séparer la présentation de la logique de présentation. C'est une
solution orientée objet qui n'utilise pas un système basé sur des balises
(comme celles utilisées par Smarty ou les JavaServer Pages (JSP) de Java).
A la place, les templates rendus par Zend_View
utilisent directement du PHP. A un plus haut niveau,
Zend_View
encapsule une Vue de l'application, à
partir du MVC, qui accepte des entrées utilisateur et renvoit une
présentation de l'application, qu'il s'agisse de HTML, JSON, XML, ...
basée sur les données qui lui sont passées ou qu'elle a été conçue pour
récupérer indépendamment. Techniquement, Zend_View
n'accepte pas d'entrée utilisateur puisqu'elle ceci est géré au niveau de
la couche Contrôleur - encore qu'il y ait quelques fuites entre les Vues
et les Contrôleurs à ce niveau, lorsque vous utilisez le MVC pour des
applications Web et non pour des applications de bureau, du fait de la
nature de HTTP ; et, en plus de cela, le Contrôleur est lui-même un
mécanisme de présentation. Il est strictement lié à la requête courante et
gére la construction et le renvoi de la réponse.
L'aspect le plus
important de l'écriture de templates avec Zend_View
est que c'est une solution orientée objet. Vous pouvez utiliser absolument
n'importe quel type de valeurs dans un template : des listes, des
scalaires, des objets, et même des ressources PHP. Il n'y a aucun système
de tags qui agisse comme intermédiaire entre vous et toute la puissance de
PHP. Une partie de cette approche OOP est que tous les templates sont
exécutés au sein de la portée de variables de l'instance courante de
Zend_View
. Pour expliquer ceci, considérons le
template qui suit :
<html>
<head><title>My Page</title></head>
<body>
<?php echo strtolower($this->myName) ?>
</body>
</html>
Dans ce template, nous voyons que PHP est utilisé directement. Nous voyons aussi une référence à $this, qui représente l'instance courante de l'objet. Mais le template n'est pas un objet ! D'où est-ce que cela vient ?
Les templates sont
inclus dans la méthode protégée Zend_View::_run()
lorsqu'ils sont rendus, et de l'ouput buffering est utilisé pour capturer
le contenu évalué. Donc, tout contenu de template est en fait traité comme
du code évalué au sein de Zend_View::_run()
, ce
qui signifie que vous pouvez accéder aux méthodes de la classe, aux
propriétés (comme la propriété publique
Zend_View::$myName) et à d'autres fonctionnalités,
exactement comme si vous écriviez une méthode au sein de la classe
Zend_View
elle-même (ce que vous faites, en réalité
!).
Comment est-ce que cela
influence nos pensées à propos de la Vue ? Puisque que tous les templates
sont, essentiellement, des appels à des méthodes (d'une façon ou d'une
autre), il semble raisonable de considérer que les templates disposent
indirectement de tous les avantages offerts par la programmation orientée
objet. Cela devient plus évident une fois que vous rencontrez plusieurs
concepts offerts par Zend_View
: les aides de vues,
les Placeholders, et les Layouts. C'est aussi évident par rapport aux
discussions que nous avons eu jusqu'à présent autour du rôle du Modèle.
Comme je le faisais remarquer au Chapitre 3:
Le Modèle
, les Vues peuvent accéder directement au Modèle en
utilisant des aides de Vues, sans avoir besoin de code au niveau du
Contrôleur pour gérer cette interaction.
Le concept de Layouts
est implémenté par Zend_Layout
. Un Layout est la
partie de votre présentation qui reste relativement statique entre
plusieurs (ou toutes les) pages de votre site. Pour un document HTML
standard, cela incluerait probablement l'en-tête, le pieds de page, le
menu de navigation, et toute autre section commune à plusieurs pages.
Puisque ces portions sont en grande partie statiques, il est inutile de
les dupliquer au niveau des templates de chaque page. Cela serait
impossible à maintenir, demandant à modifier un nombre incroyable
d'autres templates pour toute modification. Pour cette raison, nous
pouvons utiliser Zend_Layout
pour créer un
certain nombre de templates de Layout, au sein desquels la sorties des
autres templates sera injectée. Cela permet de s'assurer que les
templates du reste de l'application n'aient pas à se préoccuper du
layout commun : celui-ci est laissé à la charge de
Zend_Layout
.
Un simple template de Layout pourrait être :
<html>
<head>
<title>My Page</title>
</head>
<body>
<?php echo $this->layout()->content ?>
</body>
</html>
Comme nous le disions
dans les chapitres précédentes, le Contrôleur implémente par défaut le
rendering de la Vue en utilisant l'Action Helper ViewHelper.
Zend_Layout
enregitre un nouvel Action Helper et
un Plugin FrontController,
Zend_Layout_Controller_Action_Helper_Layout
et
Zend_Layout_Controller_Plugin_Layout
, qui
permettent respectivement le rendering d'un layout et l'accès à
l'instance de Layout depuis les Contrôleurs. Nous étudierons les Helpers
et Plugins plus en détails ultérieurement, mais ils peuvent implémenter
une méthode de hook qui sera appelée automatiquement par le
FrontController après certains événements ; dans ce cas, une méthode
postDispatch()
, qui est appelée lorsqu'une
requête est dispatchée vers un Contrôleur. La sortie de la Vue
correspondant au Contrôleur est assignée à la propriété publique
$content du template de Layout, que nous pouvons à
son tour afficher là où nous le souhaitons dans le template de Layout ;
entre les balises <body>
, dans notre exemple.
Zend_Layout
vous permet aussi de désactiver le rendering du layout depuis les
Contrôleurs, en utilisant l'Action Helper enregistré. Ceci est important
lorsque vous souhaitez renvoyer autre chose que du HTML, comme du JSON,
par exemple. Encapsuler une sortie JSON ou XML dans un Layout HTML ne
jouera probablement pas en notre faveur. Vous pouvez aussi changer de
layout, ou définir différentes variables de layout depuis les
Contrôleurs, ce qui en fait un système très flexible.
En termes de Design
Patterns, Zend_Layout
implémente une forme souple
de Two
Step View
telle que décrite par Martin Fowler dans
Patterns Of Enterprise Application Architecture
(POEAA). La définition de Fowler du pattern Two Step View dit
que :
Transforme des données du domaine en HTML en deux étapes : tout d'abord en formant une sorte de page logique, puis en rendant celle-ci en HTML.
De toute évidence,
dans notre cas, les données métier sont rendues immédiatement en HTML
avant d'être insérées en un point spécifique du HTML du layout.
L'utilisation d'une approche Two Step View par les layouts complémente
l'approche par composition des templates de
Zend_View
, telle que décrite ci-dessous via
l'utilisation de Partials.
Vous devriez aussi
noter que bien que Zend_Layout
gère tous les
mécanismes d'affectations de layouts, le template de Layout en lui-même
est rendu par une instance de Zend_View
. Cela
signifie que vos Layouts peuvent utiliser pleinement les fonctionnalités
typiques de Zend_View
, comme les Partials, les
Placeholders, et les View Helpers. C'est quelque chose qui peut être
source de confusions, et peut parfois encourager une solution non
optimale, que je vais mentionner ci-dessous.
Un aspect de
Zend_Layout
avec lequel il faut être prudent est
que le Guide de Référence dit que les Layouts et les segments de réponse
nommés peuvent utilisés en conjonction avec l'Action Helper ActionStack
pour créer des pages sous forme de widgets, pour lesquelles chaque
Action rend une Vue qui est insérée dans le template à l'endroit prévu.
Il y a un point extrêmement important à prendre en compte ici :
dispatcher une action est une tâche coûteuse. Comme le nom ActionStack
le suggère, cet Aide d'Action crée une pile d'Actions de Contrôleurs à
exécuter, requérant à son tour une multitude de dispatches à travers
quasiment tout le Framework MVC. Ceci devrait être évité, à moins que
cela ne soit absolument nécessaire. Il n'y a quasiment aucune raison
d'utiliser ActionStack, puisque tout ce qu'il fait peut être effectué
tout aussi facilement en utilisant des Aides de Vues, pour un coût en
performances nettement inférieur. Voici un blog post de Ryan Mauger
expliquant ceci un peu plus en détails : Why
the Zend Framework Actionstack is Evil
. Notez que cela ne signifie
pas que vous ne devriez jamais utiliser cette fonctionnalité de Zend
Framework, mais que vous devriez avoir un besoin clairement identifié,
et une tolérance certaine envers le coût inévitable que cela implique
pour les performances.
Les Partials correspondent à un cas d'utilisation très simple. Ce sont des fragments d'un template de plus haut niveau, qui peuvent être rendus, que ce soit une fois ou de manière répétée, en un point précis du template père. Les Partials pourrait aussi être appelés "fragments de template", pour refléter ce comportement. Un cas d'utilisation simple serait la page d'accueil de notre blog, qui peuvent afficher plusieurs articles. Le code HTML pour chaque article est quasiment identique : la seule différence est le texte de l'article inclu. Nous pourrions facilement extraire ce balisage commun d'article vers un partial, et utiliser une boucle pour rendre chaque article de la collection. En plus de réduire la quantité de markup dans un template, et d'offrir un mécanisme simple pour boubler sur une collection de données, les Partials apportent aussi un peu de réutilisabilité. Nous pourrions avoir du balisage pour afficher un article sur la page d'index, la page spécifique à une entrée, et aussi quelques autres emplacements. Utiliser un Partial isole ce markup répété, nous permettant d'effectuer des modifications visibles globalement à partir d'un seul endroit.
Ces fragments de
templates, couplés avec d'autres mécanismes de génération de balisage,
constituent une implémentation du design patter Composite
View
défini dans le livre Core J2EE Patterns
:
Utilise des vues composites qui sont composées de multiples sous-vues atomiques. Chaque composant du template peut être inclu dynamiquement dans l'ensemble, et l'organisation de la page peut être gérée indépendamment du contexte.
Suite à l'examination des Layouts que nous avons mené précédemment, une remarque que je ferais est que le Two Step View est lui-même un sous-modèle du principe de Vue Composite. Fowler a sauté quelques design patterns en rapport avec les Vues, qui sont maintenant communément utilisés, comme Composite View et View Helper ; ceux-ci ont, à la place, été formellement défini dans le livre Core J2EE Patterns quelques temps après.
Les partials de
Zend_View
sont implémentés par les aides de Vues
Zend_View_Helper_Partial
et
Zend_View_Helper_PartialLoop
. Nous verrons les
aides de vues plus en détail dans la prochaine section. Le premier
helper rend un template partiel à chaque appel, et peut être utilisé
soit une seule fois, soit imbriqué dans une boucle au sein du template
parent. La seconde classe supporte les boucles par un mécanisme interne,
afin que celles-ci n'aient pas à être imbriquées dans le template
père.
Le paramètre passé à
un partial ne peut pas être arbitraire : il doit s'agir d'un tableau
associatif, ou d'un objet implémentant une méthode
toArray()
. Dans le cas contraire, il sera
réduit à la liste des propriétés de l'objet en utilisant
get_object_vars()
. A partir du tableau associatif
obtenu en résultat, les clefs sont utilisées comme noms de propriétés
locales, et les valeurs correspondantes leur sont assignées.
Considérons l'exemple suivant, où nous passons quelques données simples à un partial non-bouclant :
<?php echo $this->partial('article.phtml', array(
'title'=>'Title',
'content'=>'My Content!'
)) ?>
Le fichier
article.phtml
est construit comme tout autre
template, hormis le fait qu'il soit appelé comme un partial. Il pourra
accéder aux données reçues via des propriétés locales de classe à
travers sa propre instance de Zend_View
:
<article>
<h3><?php echo $this->escape($this->title) ?></h3>
<div class="content">
<?php echo $this->content ?>
</div
<article>
Lorsque nous utilisons
l'aide partial loop, le paramètre devrait être une liste de données au
sein desquelles nous bouclerons (chaque enregistrement devant suivre les
mêmes règles que pour le paramètre d'un partial normal) ou tout objet
itérable retournant un paramètre de partial valide. Par exemple, la
classe Zend_Db_Table_Rowset
est une collection
itérable d'objets de type Zend_Db_Table_Row
.
Chaque instance de Zend_Db_Table_Row
implémente
une méthode toArray()
. Cela signifie qu'un
rowset est un paramètre valide pour un partial loop.
Par exemple, nous pourrions utiliser notre partial défini plus haut en passant une liste d'articles, et en utilisant l'aide partial loop :
<?php echo $this->partialLoop('article.phtml', array(
array('title'=>'Title', 'content'=>'My Content!'),
array('title'=>'Title2', 'content'=>'More Content!')
)) ?>
En plus des listes,
vous pouvez passer directement des objets, et contourner la tentative de
transtype d'objet en tableau. Ceci se fait en définissant une clef
d'objet avant d'appeler les méthodes
Zend_View::partial()
ou
Zend_View::partialLoop()
avec quelque paramètre
que ce soit. Souvenez-vous qu'un appel à la méthode principale d'une
aide de Vue sans lui passer de paramètre va généralement juste retourner
l'objet d'aide de Vue pour permettre l'enchainement de méthodes. Par
exemple :
<?php echo $this->partialLoop()->setObjectKey('article')
->partialLoop($articleCollection) ?>
Dans l'exemple
ci-dessous, tous les objets dans l'objet itérable
Collection
(quelque chose que nous pourrions
implémenter dans notre Modèle) sont ajoutés dans la classe du template
partial sous une propriété publique nommée
$article. Au sein du partial, nous pourrions alors
utiliser quelque chose de ce type :
<article>
<h3><?php echo $this->escape($this->article->title) ?></h3>
<div class="content">
<?php echo $this->article->content ?>
</div>
</article>
Cela n'est pas tout à fait l'exemple le plus court qui soit, cela dit : utiliser des objets signifie que les références au partial sont encore plus longues. Utiliser un tableau peut être plus simple, mais il y a des cas où utiliser des objets en eux-même peut avoir des avantages, puisque l'un objet du Modèle peut porter des références à d'autres objets qui seront chargés paresseusement, comme l'auteur de notre article au chapitre 9.
Les Partials ont deux
autres aspects importants. Tout d'abord, ils travaillent dans une portée
de variables différente de celle du template père. Dans la pratique,
cela signifie qu'ils ne connaissent que les données qui leur sont
passées spécifiquement lorsque vous appelez les méthodes
Zend_View::partial()
ou
Zend_View::partialLoop()
(celles-ci étant
respectivement mappées vers les méthodes
Zend_View_Helper_Partial::partial()
et
Zend_View_Helper_Partial::partialLoop()
, qui
sont les méthodes principales des aides de vues). Ils n'ont pas
connaissance de toute donnée définie dans la Vue parente par un
Contrôleur, ou, dans le cas d'un fragment inclu au second niveau, de
toute donnée définie dans le père du partial. Cette restriction imposée
encourage une approche plus orientée-objet du templating, et évite les
fuites de données entre de multiples templates et fragments de templates
(autrement dit, le problème de l'utilisation de variables globales).
Ceci est tout simplement une bonne pratique : nous ne permettons (je
l'espère) pas aux variables globales de contaminer l'espace de noms de
nos objets, alors pourquoi devrions-nous le permettre pour nos templates
? Tous les templates devraient être isolés les uns des autres pour
éviter de se retrouver avec des dépendances qui rendraient les tests, la
maintenance, et les évolutions, plus coûteux.
Pour terminer, vous pouvez imbriquer un partial au sein d'un autre partial. Vous pourriez même en avoir toute une hiérarchie, si cela s'avère nécessaire. Tout dépend réellement de ce que vos partials représentent : un petit bout de balises, ou une portion plus importante d'un template de page ou de layout. La nature du jeu de templating est de composer une seule vue à partir d'un grand nombre de parties. Comment est-ce que ces parties seront construites ou divisées pour éviter la duplication de balisage et l'encapsulation de fonctionnalités réutilisables ne dépendra que de vos besoins.
Si les partials sont le moyen de découper le balisage en composants réutilisables, leurs contreparties pour ce qui est de la logique de présentation sont les aides de Vues. Une aide de vue est utilisée pour créer une opération réutilisable appliquée à un template. Par exemple, supposez qu'un partial ait reçu en paramètre un objet métier représentant un commentaire posté par un utilisateur. Celui-ci contient un nom, une adresse email, une URL, un peu de texte, et une date. Les quatre premières données sont simples... Mais comment allez-vous formater la date pour la présentation ? Votre partial pourrait inclure un peu de code PHP pour analyser la date (en fonction du standard de date utilisé), et la recomposer sous forme d'une date lisible. Mais les articles ont une date de publication et une date de modification ; même chose pour les commentaires, d'ailleurs. Allez-vous répéter ce code PHP partout ?
De toute évidence,
cela ne serait pas une solution des plus maintenable. Si quelque chose
change et que vous avez à reconfigurer la logique d'analyse utilisée,
vous pourriez avoir une quantité importante de templates à éditer. Il
serait bien plus simple d'encapsuler cette fonctionnalité PHP dans une
aide de Vue, une classe réutilisable qui peut être appelée depuis
n'importe quel template à n'importe quel moment. Vous pourriez
implémenter ceci sous la forme d'une classe
ZFExt_View_Helper_FormatDate
(une aide
personnalisée), contenant une méthode
formatDate()
qui accepterait en paramètre une
date quelconque, et retournerait une date formatée en accord avec un
schéma spécifié, tel que "YYYY-MM". De la sorte, vous auriez une classe
facile à réutiliser, qui pourrait bénéficier de tests unitaires, et même
être portée vers, et réutilisée dans, d'autres projets.
Comme je le disais
aussi dans le Chapitre 3: Le Modèle
,
les aides de Vues peuvent aussi être employées pour encapsuler un Modèle
utilisant, par exemple, l'implémentation d'un Data Mapper proposée au
chapitre précédent. Votre aide peut maintenant interroger la couche
Modèle pour obtenir des données, les formater et ajouter du code de
balisage, et retourner le résultat final pour qu'il soit immédiatement
inséré à l'endroit où il est attendu dans le template. La principale
difficulté avec cette approche est qu'il faut se souvenir qu'une aide de
Vue ne devrait jamais modifier le Modèle avec lequel elle interagit :
cela revient au Contrôleur qui se charge de la gestion des entrées
utilisateur. En conséquence, les aides de Vues sont une fonctionnalité
réellement utile. Vous pouvez décharger du formatage spécialisé, la
génération de menus, l'interrogation du modèle, la décoration avec du
balisage, et encore plein d'autres choses vers les aides de Vues.
Les aides de Vues sont
aussi définies comme un pattern précis, nommé design pattern View
Helper
, dans le livre Core J2EE
Patterns.
Tous les aides de vues
suivent une conception similaire. Elles implémentent une méthode portant
un nom correspondant à celui de la classe, ce qui signifie que
ZFExt_View_Helper_FormatDate
définirait une
méthode
ZFExt_View_Helper_FormatDate::formatDate()
.
Cette méthode "primaire" va généralement accepter des paramètres sur
lesquels l'aide travaillera, et retournera le résultat. Pour des aides
plus compliqués, avec plusieurs méthodes publiques, cette méthode
primaire peut aussi retourner l'objet lui-même. Puisque
ZFExt_View_Helper_FormatDate
serait un aide de
vue personnalisé, vous auriez aussi besoin de dire à
Zend_View
où le trouver, et quel serait son
espace de noms.
Prenons un exemple
simple. Nous avons décidé de prendre en compte quelques analyses des
performances de chargement de nos pages web pour assurer que les
fichiers javascript et CSS peuvent être mis en cache par les clients
pour une durée étendue. Ceci est recommandé par l'article Yahoo
Performance Best Practices
dans la section Add
an Expires or a Cache-Control Header
. Donc, nous y allons, et
configurons Apache pour utiliser une en-tête Expires loin dans le futur
lorsque nous servons des fichiers statiques tels des CSS ou du
Javascript. Nous faisons maintenant face à une autre problème : si le
client cache ces fichiers pour une éternité, comment pouvons-nous gérer
les modifications et mises à jour ? Une stratégie communément utilisée
est d'ajouter une Query String aux URIs des fichiers statiques, qui sera
mise à jour dans notre code de balisage lorsque les fichiers référencés
sont modifiés. Les ressources en cache utiliseront l'URI d'origine (vous
pouvez cacher les URIs avec une Query String si une en-tête Expires
explicite est définie), mais lorsqu'elle changera, le client
téléchargera depuis la nouvelle URI, puisque la Query String aura changé
; autrement dit, puisqu'il s'agira d'une nouvelle ressource.
Cela entrainerait la
création d'une URI de la forme
http://zfblog.tld/css/style.css?1185131970
. La Query String
finale est, de toute évidence, un timestamp représentant la date de
modification du fichier. Vous pourriez tout aussi simplement utiliser un
numéro de version. Ecrivons une aide de vue qui assignera
automatiquement ces Query Strings, de façon à ce que nos mises à jour
altèrent les URIs directement, automatiquement, sans aucune intervention
manuelle. Nos aides personnalisés peuvent hériter de
Zend_View_Helper_Abstract
qui offre un certain
nombre de fonctionnalités standard qui peuvent être utiles (elles ne le
sont pas, ici) :
<?php
class ZFExt_View_Helper_AppendModifiedDate extends Zend_View_Helper_Abstract
{
public function appendModifiedDate($file) {
$root = getcwd();
$filepath = $root . $file;
$mtime = filemtime($filepath);
return $file . '?' . $mtime;
}
}
Notre nouvelle aide de vue est courte et va droit au but. Elle accepte le chemin relatif vers le fichier sur notre serveur, tel que nous l'utiliserions normalement dans nos balises, vérifie la date de dernière modification du fichier, et ajoute cette date au chemin sous forme d'un timestamp. Le chemin obtenu en résultat peut alors être passé à n'importe quelle autre aide (par exemple, les aides de Vues HeadLink ou HeadScript), ou renvoyé directement dans le code de balisage correspondant. Voici un fragment de template utilisant notre nouvelle aide de vue :
<link rel="stylesheet" href="<?php echo $this->appendModifiedDate('/css/style.css') ?>" type="text/css" media="screen" />
En plus de permettre
l'écriture d'aides de vues personnalisées, Zend Framework fourni un
certain nombre d'aides de vues standard pour des tâches communes telles
que la gestion de génération de balises (Pour les éléments de
formulaires et d'en-tête, par exemple). Nous verrons prochainement un
certain nombre de celles-ci, qui utilisent une autre fonctionnalité de
Zend_View
, nommée Placeholders.
Les Placeholders
répondent à un besoin spécifique du templating avec Zend Framework. Ils
permettent aux templates d'accèder à un registre partagé de données,
indépendamment de leur portée de variables. C'est extrêmement proche du
Pattern Registry, qui est souvent implémenté avec Zend Framework en
utilisant Zend_Registry
, mais a un cas
d'utilisation plus spécifique ici. Considérez un scénario où vous
découvrez qu'une page web a besoin d'utiliser une portion donnée de
Javascript, chargée dès que possible. Votre layout ne la contient pas
puisqu'elle n'est tout simplement pas nécessaire pour tout. Les
Placeholders peuvent résoudre ce problème en permettant aux templates et
aux Partials d'ajouter des valeurs à un Placeholder, une clef dans ce
registre partagé. Lorsqu'un template parent ou un layout sera rendu, il
pourra rendre rendre les données du Placeholder là où il le spécifie.
Pour du Javscript, ceci est géré par un Placeholder spécialisé
implémenté par Zend_View_Helper_HeadScript
, avec
lequel vous pouvez ajouter à la fin ou au début, re-définir ou définir à
une position particulière toute portion de Javascript dont vous avez
besoin, à partir de n'importe quel sous-template, avant qu'il ne soit
finalement rendu dans un layout.
En plus d'offrir une sélection de Placeholders spécialisés comme HeadLink ou HeadMeta, vous pouvez utiliser l'aide de vue Placeholder de base pour toute autre donnée qui aurait besoin d'être communiquée entre templates. Jetons un coup d'oeil à un exemple où nous avons un layout qui peut insérer la sortie d'un Placeholder. Le layout fournit un point de rendering qui permet à tout template de modifier le texte correspondant à une en-tête standard :
<html>
<head>
<title>My Page</title>
</head>
<body>
<h1><?php echo $this->placeholder('pageHeader') ?></h1>
<?php echo $this->layout()->content ?>
</body>
</html>
Nous pouvons maintenant définir cette valeur à partir de n'importe quel autre template (Partials inclus) :
<?php $this->placeholder('pageHeader')->set('About') ?>
<p>My name is Joe. Joe Bloggs.</p>
Contrairement aux implémentations de Placeholder plus spécifiques, l'aide Placeholder générique peut être utilisée pour n'importe quelle valeur. S'il s'agit d'une valeur spécifique qui suit des règles de formatage spécifiques, vous pouvez sous-classer l'aide générale pour aussi ajouter ce formatage au moment où les données sont affichées dans le template. Par exemple, implémentons une aide de Vue personnalisée pour formater l'en-tête d'une page en utilisant HTML 5 :
<?php
class ZFExt_View_Helper_PageHeader extends Zend_View_Helper_Placeholder
{
public function pageHeader()
{
return $this;
}
public function set($value)
{
parent::placeholder('pageHeader')->set($value);
}
public function __string()
{
return "<h1>" . (string) parent::placeholder('pageHeader') . "</h1>";
}
}
C'est un exemple très
simple qui permet tout juste de créer un wrapper concret autour du
Placeholder correspondant à la clef "PageHeader". Il encapsule aussi le
titre de la page entre des balises <h1>
. Voici
maintenant notre layout et notre template en page en utilisant notre
nouveau Placeholder concret :
<html>
<head>
<title>My Page</title>
</head>
<body>
<?php echo $this->pageHeader() ?>
<?php echo $this->layout()->content ?>
</body>
</html>
<?php $this->pageHeader()->set('About') ?>
<p>My name is Joe. Joe Bloggs.</p>
Vous avez peut-être
remarqué que tout au long de ce livre j'ai montré une nette préférence
pour l'utilisation de tags PHP longs ; c'est-à-dire <?php
(echo) ... ?>
; plutôt que pour les tags courts,
<?(=) ... ?>
. Les short tags sont un point de
configuration optionnel de PHP. Bien que de nombreux serveurs aient les
short tags activés pour améliorer la compatibilité avec les applications
qui les utilisent, beaucoup de serveurs en configuration par défaut ne
les supporteront pas, puisqu'ils sont désactivés dans le fichier de
configuration recommandé de PHP,
php.ini.recommended
. En plus de cela, nous savons
que PHP 6 pourrait complètement déprécier les short tags pour résoudre
un conflit avec les déclarations XML qui utilisent une forme similaire :
<?xml ... ?>
. Traditionnellement, les déclarations
XML ont été echo-ées si l'on générait du XML à partir d'un template en
utilisant des short tags. Personnellement, je considère que la probable
dépréciation en PHP 6 est une mauvaise chose. Ecrire <?php echo
... ?>
plus de quelques fois est tout simplement ennuyeux,
mais, encore une fois, puisqu'il s'agit d'un point de configuration
optionnel, les applications distribuées ne devraient de toute manière
jamais l'utiliser. En tout cas, il vaut mieux en rester à ce qui marche
dans tous les cas, plutôt que de se fier à quelque chose qui risque de
casser plus tard.
Zend Framework a
autorisé l'usage de short tags, indépendamment de la configuration, en
utilisant un stream wrapper. Celui-ci est activé si l'option short tags
est désactivée dans votre fichier de configuration
php.ini
(autrement dit, en fonction du flag
short_open_tag
) et si vous avez défini l'option
useStreamWrapper
à true
. Vous pouvez effectuer
ceci en utilisant la clef d'option useStreamWrapper
dans
votre ficher application.ini
ou en appelant la
méthode Zend_View::setUseStreamWrapper()
dans
votre bootstrap. Utiliser le stream wrapper aura certainement un impact
sur les performances, ce qui signifie que si vous avez réellement besoin
des short tags, il vaut mieux juste les activer dans votre configuration
de PHP.
Mettre en place la
nouvelle application de blogging n'est pas horriblement difficile : vous
n'avez qu'à copier les exemples Hello World vers le répertoire project de
l'application de blog, où vous avez actuellement le début de Modèle de
notre application. Cela fera un excellent point de départ pour commencer.
Vous pouvez souhaiter ajouter un nouveau nom de domaine local et un
nouveau Virtual Host spécifiquement pour le blog, en suivant les
instructions du chapitre correspondant ainsi que celles de l'annexe dédiée
à la configuration d'Apache et des Virtual Hosts. Pour les besoins de ce
livre, j'utiliserai le nom de domaine http://zfblog.tld
.
Aucune autre modification n'est nécessaire. Le reste du chapitre va détailler où aller à partir de là.
La page d'index de notre
blog va, comme nous aurions pu nous y attendre, être la page qui affichera
une liste d'entrées. En utilisant HTML 5, commençons par jeter un coup
d'oeil aux tags HTML d'ouverture. Par la suite, nous ajouterons le style
et nous soucierons de l'ajout de plus d'éléments et d'informations à la
page. Pour l'instant, nous éditerons le fichier
/application/views/scripts/index/index.phtml
:
<!DOCTYPE html>
<html>
<head>
<meta name="language" content="en" />
<meta charset="utf-8"/>
<title>Pádraic Brady's Blog</title>
<link href="http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/reset-fonts-grids.css" media="screen" rel="stylesheet" type="text/css" />
<link href="http://yui.yahooapis.com/2.7.0/build/base/base-min.css" media="screen" rel="stylesheet" type="text/css" />
<!--[if IE]><script type="text/javascript" src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
</head>
HTML 5 utilise utilise
un Doctype simplifié qui ne fait pas grand chose de plus que de dire qu'il
s'agit de HTML. Il n'y a pas de numéro de version ou d'autre information
attachée ni nécessaire. Puisque nous savons que nous pouvons définir le
Doctype utilisé par Zend_View
et tout composant qui
en dépend, nous pouvons afficher cela dans notre template en utilisant
l'aide de Vue Zend_View_Helper_Doctype
. Le support
HTML 5 de Zend Framework va jusqu'au Doctype qui dit aux aides de Vues
dépendantes qu'elles doivent utiliser des balises de type HTML 4. En fait,
HTML 5 peut aussi être écrit d'une manière similaire à XHTML ; autrement
dit, en utilisant des noms de balises et d'attributs en minuscules, en
délimitant les valeurs d'attributs avec des guillemets doubles, en fermant
toutes les balises, etc. Ceci est ma méthode préférée lorsqu'il s'agit
d'écrire du HTML 5, car cela semble plus propre, et je n'ai pas besoin de
trop m'éloigner du XHTML avec lequel je me suis familiarisé. Zend
Framework pose alors un problème mineur : il ne fait pas la distinction
entre HTML 5 et XHTML 5 (HTML 5 écrit avec des règles de formatage XML).
Nous résoudrons ce problème en écrivant une Aide de Vue personnalisée,
pour ajouter le support pour un Doctype XHTML 5.
Comme avec tout document
HTML/XHTML, l'élément racine est <html>
. Il est
immédiatement suivi par un élément <head>
qui contient
tout élément <link>
, <meta>
,
<title>
ou <style>
dont nous aurions
besoin. Ceux-ci peuvent être écrit avec n'importe que style typique, même
si, comme je l'expliquais au-dessus, je préfères les habitudes XHTML. HTML
5 ajoute ici une fonctionnalité : un nouvel attribut pour l'élément
<meta>
, nommé charset
. Ce nouvel attribut
est conçu comme moyen simplifié d'indiquer au parseur quel encodage de
caractères est utilisé par le document, et agit donc comme remplacement
valide de l'élément couramment utilisé <meta>
Content-type. Encore une fois, Zend Framework ne supporte pas encore ceci,
donc nous ajouterons une seconde aide de Vue pour l'implémenter.
Vous pouvez remarquer
que, bien que nous puissions écrire toutes ces balises directement dans
nos templates, je préféres considérer que nous les implémenterons sous
forme de Views Helpers. Ceci est une bonne pratique, puisqu'utiliser les
Aides de Vues permet aux autres templates de modifier ce qui apparait dans
<head>
en ajoutant au début, à la fin, ou même en
re-définissant quelles feuilles de styles, scripts, et meta informations
sont initialement définies. Vous pourriez voir à quel point ceci est
intéressant dans des situations où une page spécifique a besoin de styling
supplémentaire ou d'un ensemble différent de code Javascript, qui ne soit
pas requis pour toutes les pages.
J'ai aussi ajouté deux feuilles de style CSS issues du Framework Yahoo User Interface. Elles sont chargées directement depuis un serveur de Yahoo, afin que nous n'ayons pas à stocker quoi que ce soit localement. La première de celles-ci est une version minifiée de trois feuilles de styles fusionnées : reset, fonts, et grid. Les deux premières assurent que tous les principaux navigateurs afficheront la même page, en éradiquant les différences de rendering que chacun implémente par défaut. Il n'y a rien de tel que d'essayer de gérer les inconsistences entre navigateurs sur des simples points de style. Les styles de grilles seront utilisés plus tard pour créer un layout en colonnes plus facilement. La seconde feuille de styles inclue, base, impose un socle de styling de base pour tous les éléments principaux. A ce rythme, je ne devrais pas avoir beaucoup de CSS à écrire, hormis ce qui s'applique spécifiquement à mes styles préférés pour le blog.
Finalement, nous avons
inclu un fichier Javascript issu du projet html5shiv project
.
C'est une portion de Javascript relativement simple utilisée pour
s'assurer que Internet Explorer puisse reconnaitre les nouveaux éléments
HTML 5. Autrement, il ne nous serait pas possible de les styler. Ceci
étant uniquement requis pour Internet Explorer, nous avons entouré cette
inclusion par un couple de conditions reconnaissant toutes les versions
d'Internet Explorer, mais excluant tout autre navigateur.
Ajoutons maintenant une section header représentant le titre de la page et le bandeau de navigation supérieur :
<!DOCTYPE html>
<html>
<head>
<meta name="language" content="en" />
<meta charset="utf-8"/>
<title>Pádraic Brady's Blog</title>
<link href="http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/reset-fonts-grids.css" media="screen" rel="stylesheet" type="text/css" />
<link href="http://yui.yahooapis.com/2.7.0/build/base/base-min.css" media="screen" rel="stylesheet" type="text/css" />
<!--[if IE]><script type="text/javascript" src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
</head>
<body>
<header>
<h1><a href="/">Pádraic Brady's Blog</a></h1>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Projects</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
</header>
Avec HTML 5, nous
pouvons déjà voir quelques uns des nouveaux éléments. Notre section
d'en-tête est maintenant entourée par la balise
<header>
qui entoure notre titre d'en-tête traditionnel
<h1>
, et aussi un autre élément, une balise
<nav>
qui entoure notre menu de navigation supérieur.
Le point probablement le plus agréable ici, en dehors de la signification
sémantique, est que nous pouvons nous débarasser de tout un tas d'éléments
<div>
utilisant des attributs class
pour
nous indiquer ce qu'ils représentent.
HTML 5 fourni quelques définitions pour ces nouveaux éléments :
L'élément header représente un groupe d'aides d'introduction ou de navigation. Un élément header est utilisé pour contenir, en général, l'en-tête de la section (un élément h1-h6 ou un élément hgroup), bien que cela ne soit pas requis. L'élément header peut aussi être utilisé pour encapsuler le sommaire d'une section, un formulaire de recherche, ou tout autre logo significatif.
L'élément nav représente une section de page qui contient des liens vers d'autres pages ou vers des parties au sein de la page : une section avec des liens de navigation. Tous les groupes de liens au sein d'une page n'ont pas besoin d'être dans un élément nav ; seules les sections qui constituent des blocs de navigation majeurs sont appropriées pour l'élément nav. En particulier, il est commun pour les bas de pages de contenir une liste de liens vers diverses parties clefs du site, mais l'élément footer est plus approprié dans ce genre de cas, et aucun élément nav n'est nécessaire pour ces liens.
Ensuite, nous voulons que le corps principal du balisage indique où est-ce que nos entrées de blogs seraient rendues, suivies par la section de bas de page finale qui contiendra une notice de copyright et des crédits lorsque cela sera nécessaire :
<!DOCTYPE html>
<html>
<head>
<meta name="language" content="en" />
<meta charset="utf-8"/>
<title>Pádraic Brady's Blog</title>
<link href="http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/reset-fonts-grids.css" media="screen" rel="stylesheet" type="text/css" />
<link href="http://yui.yahooapis.com/2.7.0/build/base/base-min.css" media="screen" rel="stylesheet" type="text/css" />
<!--[if IE]><script type="text/javascript" src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
</head>
<body>
<header>
<h1><a href="/">Pádraic Brady's Blog</a></h1>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Projects</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
</header>
<section>
<article>
<header>
<h2>One Day...Revisited</h2>
<p>Posted on <time datetime="2009-09-07">Tuesday, 08 September 2009</time></p>
</header>
<div>
<p>I forgot!</p>
<p>I already am writing a book!</p>
</div>
<footer>
<address>By Pádraic</address>
</footer>
</article>
<article>
<header>
<h2>One Day...</h2>
<p>Posted on <time datetime="2009-09-07">Monday, 07 September 2009</time></p>
</header>
<div>
<p>I will write a book</p>
</div>
<footer>
<address>By Pádraic</address>
</footer>
</article>
</section>
<footer>
<p>Copyright © 2009 Pádraic Brady</p>
<p>Powered by <a href="http://www.survivethedeepend.com">ZFBlog
</a></p>
</footer>
</body>
</html>
Et bien, en voila des
nouveautés ! HTML 5 a ajouté de beaucoup d'autres nouveaux éléments, et
cela commence à être évident, maintenant. De nombreux endroits où nous
nous serions attendu à voir des balises <div>
avec un
attribut class
faisant référence à la raison d'être de la
<div>
. Dans une page vide typique, vous pourriez vous
attendre à tout simplement encapsuler le contenu avec des balises
<article>
, puisque le contenu serait une seule entrée.
Pour notre blog, cela dit, nous savons que chaque entrée rendue sur la
page est elle-même un article, donc nous utilisons de multiples éléments
<article>
et les entourons tous d'un seul élément
<section>
(il pourrait aussi y avoir plusieurs éléments
<section>
si la page était encore plus divisée, et ils
peuvent être imbriqués ; un peu comme le XML Docbook). Chaque article va
aussi avoir une en-tête et un pieds de page, et nous pouvons donc utiliser
les balises HTML 5 correspondant à ces situations. Finalement, toute
référence aux détails de l'auteur sont encapsulées dans des balises
<address>
. Même si cela peut être source de confusion,
la balise <address>
ne fait pas référence à une adresse
physique : il s'agit plutôt des autres détails d'une personne, comme son
nom, son site web, son e-mail, sa description, etc.
Penchons-nous maintenant sur les définitions de ces nouveaux éléments à partir des spécifications HTML 5 :
L'élément section représente un document générique ou une section d'application. Une section, dans ce contexte, est un regroupement thématique de contenu, typiquement avec une en-tête, et possiblement avec un pieds de page.
L'élément article représente une section de page qui consiste en une composition qui forme une partie indépendante d'un document, d'une page, d'une application, ou d'un site. Il pourrait s'agir d'un post de forum, d'un article de magazine ou de journal, d'une entrée de Web log, d'un commentaire soumis par un utilisateur, d'un widget interactif, ou de tout autre élément de contenu indépendant. Un élément article est "indépendant" dans le sens où son contenu pourrait être présenté seul, par exemple via un mécanisme de syndication, ou comme composant interchangeable sur une page portail configurable par l'utilisateur.
L'élément footer représente un pieds de page pour son ancêtre de section de contenu le plus proche. Un footer contient typiquement des informations à propos de sa section, comme qui l'a écrit, des liens vers des documents liés, des données de copyright, et équivalents. Les informations de contact appartiennent à un élément address, idéalement lui-même placé dans un footer.
L'élément address représente les informations de contact pour son article le plus proche ou l'élément corps ancêtre. S'il s'agit de l'élément body, alors l'information de contact s'applique au document dans son ensemble. L'élément address ne doit pas être utilisé pour représenter une adresse arbitraire (comme une adresse postale), à moins que cette adresse ne soit effectivement l'information de contact attendue.
L'élément time représente soit une heure sur une horloge 24 heures, soit une date précise dans le calendrier Grégorien, éventuellement accompagnée d'une date et d'une time-zone. Cet élément est pensé comme un moyen d'encoder les dates et heures modernes d'une manière lisible par les machines, afin que les navigateurs puisse proposer leur ajout au calendrier de l'utilisateur. Par exemple, en ajoutant des rappels d'anniversaire, ou des événements planifiés.
Notons tout de même au
passage que le nouvel élément <time>
n'est pas dépourvu
de problème. Il est strictement lié au calendrier Grégorien, ce qui
signifie qu'il ne peut pas représenter de date non-grégorienne datant
d'avant l'introduction de celui-ci. Puisque <time>
a
été pensé pensé pour remplacer l'élément de microformat
<abbr>
dans hCalendar, c'est réellement un échec : il
ne peut pas représenter de dates historiques. Espérons que la
recommandation finale de HTML 5 corrigera ce point.
Ouvrez votre navigateur
et naviguez vers http://zfblog.tld
. Vous devriez voir
le nouveau template rendu avec les styles CSS par défaut de YUI appliqués
:
Maintenant que nous avons un design de départ, il nous faut identifier quelles portions de ce template sont statiques ; autrement dit, quelles portions ne changeront que peu de page en page. Un coup d'oeil rapide suffit pour voir que pour notre page d'accueil, seules les entrées sont susceptibles de changer. Nous pourrions en avoir un nombre variable à afficher, et leur contenu sera fréquemment mis à jour. Sachant cela, nous pouvons alléger notre template d'index pour n'inclure que les parties qui sont dynamiques. Nous regrouperons momentanément le reste dans un layout.
Voici le template
/application/views/scripts/index/index.phtml
une fois
revu :
<article>
<header>
<h2>One Day...Revisited</h2>
</header>
<div>
<p>I forgot!</p>
<p>I already am writing a book!</p>
</div>
<footer>
<address>By Pádraic</address>
</footer>
</article>
<article>
<header>
<h2>One Day...</h2>
</header>
<div>
<p>I will write a book</p>
</div>
<footer>
<address>By Pádraic</address>
</footer>
</article>
Déplaçons le code de
balisage restant vers un nouveau template, auquel nous ferons référence
sous le terme de Layout, dans
/application/views/layouts/default.phtml
:
<!DOCTYPE html>
<html>
<head>
<meta name="language" content="en" />
<meta charset="utf-8"/>
<title>Pádraic Brady's Blog</title>
<link href="http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/reset-fonts-grids.css" media="screen" rel="stylesheet" type="text/css" />
<link href="http://yui.yahooapis.com/2.7.0/build/base/base-min.css" media="screen" rel="stylesheet" type="text/css" />
<!--[if IE]><script type="text/javascript" src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
</head>
<body>
<header>
<h1><a href="/">Pádraic Brady's Blog</a></h1>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Projects</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
</header>
<section>
<?php echo $this->layout()->content ?>
</section>
<footer>
<p>Copyright © 2009 Pádraic Brady</p>
<p>Powered by <a href="http://www.survivethedeepend.com">ZFBlog
</a></p>
</footer>
</body>
</html>
Vous remarquerez que
nous faisons maintenant référence à la méthode
layout()
de Zend_View
.
Comme nous l'avons remarqué lorsque nous parlions des aides de Vues, le
nom de classe d'une aide de Vue reflète sa méthode primaire, qui est
typiquement utilisée, au minimum, pour obtenir une instance de l'objet
d'aide de Vue. Ici, nous récupérons une instance de
Zend_View_Helper_Layout
et affichons ce qui est
contenu dans sa propriété publique $content.
Pour comprendre comment
notre template d'index est propulsé dans cette propriété, il nous faut
comprendre que tout le rendering est, par défaut, géré par les Contrôleurs
en utilisant l'aide de Vue ViewRenderer. Cette aide va rendre un template
pour le Contrôleur et Action courants vers une réponse.
Zend_Layout
pourra alors intercepter le corps de la
réponse rendue et l'injecter dans le template de layout. Le layout complet
sera ensuite rendu à son tour, pour finalement produire la page
entière.
Vous devriez aussi noter
que les Layouts, comme tous les templates, sont exécutés au sein de la
portée de variables d'un objet Zend_View
, ce qui
signifie que toutes les aides de Vues et les méthodes de
Zend_View
sont toujours accessibles depuis vos
Layouts.
Pour activer
Zend_Layout
, il nous faut le configurer pour qu'il
soit utilisé. Typiquement, cela passe par un appel à la méthode statique
Zend_Layout::startMvc()
, suivi par toute
configuration de l'instance ; mais, puisque nous utilisons
Zend_Application
pour notre bootstrapping, nous
avons juste besoin de définir quelques valeurs de configuration pour une
Ressource Layout dans /config/application.ini
. Les
deux options requises, et ajoutées en bas de notre section "Options de
Ressources Standard", définissent le nom du template de Layout utilisé par
défaut en omettant l'extension de fichier .phtml. La seconde configure le
chemin sous lequel les templates de Layout peuvent être trouvés :
[production]
; PHP INI Settings
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0
; Bootstrap Location
bootstrap.path = APPLICATION_ROOT "/library/ZFExt/Bootstrap.php"
bootstrap.class = "ZFExt_Bootstrap"
; Standard Resource Options
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
resources.view.encoding = "UTF-8"
resources.modifiedFrontController.contentType = "text/html;charset=utf-8"
resources.layout.layout = "default"
resources.layout.layoutPath = APPLICATION_PATH "/views/layouts"
; HTML Markup Options
resources.view.doctype = "HTML5"
; Autoloader Options
autoloaderNamespaces[] = "ZFExt_"
[staging : production]
[testing : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
resources.frontController.throwExceptions = 1
[development : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
resources.frontController.throwExceptions = 1
En retournant à votre
navigateur, rendez-vous vers http://zfblog.tld
, et vous devriez
voir la même page que précédemment, sans aucune différence. Notre template
d'index est maintenant rendu dans le nouveau Layout.
Pour aller un peu plus
loin, profitons de cette opportunité pour éditer le second template de
page de notre application,
/application/views/scripts/error/error.phtml
. Vous
remarquerez qu'il n'est pas entouré de balises
<article>
puisqu'il s'agit d'un message générique, et
pas d'un document article à part entière. J'ai aussi fait usage d'une
en-tête de second niveau <h2>
entourée par un élément
<header>
pour l'<article>
.
<header>
<h2>An Error Has Occurred</h2>
</header>
<div>
<?php if ($this->statusCode == 500): ?>
<p>We're truly sorry, but we cannot complete your request at this
time.</p>
<p>If it's any consolation, we have scheduled a new appointment for our
development team leader in Torture Chamber #7 to encourage his colleagues
to investigate this incident.</p>
<?php else: ?>
<p>We're truly sorry, but the page you are trying to locate does not
exist.</p>
<p>Please double check the requested URL or consult your local
Optometrist.</p>
<?php endif; ?>
</div>
Lancez-vous et essayez
cette page, en utilisant une URL invalide telle que http://zfblog.tld/foo/bar
.
Rappelez-vous qu'il vous faudra temporairement désactiver la levée
d'Exceptions dans application.ini
pour voir la page
d'erreur.
Notre Layout est à
présent en place, mais nous pouvons aller plus loin et nous assurer que
les templates de pages, ou même les partials, peuvent modifier des
portions du Layout à la demande, en particulier pour ce qui est des
feuilles de styles, titres de pages, Javascript et balises meta. Zend
Framework permet ce genre de modifications via l'utilisation de
Placeholders, en particulier avec un certain nombre d'implémentations
concrètes telles que Zend_View_Helper_HeadTitle
,
Zend_View_Helper_Doctype
, etc. Comme je le
mentionnais plus haut, les Placeholders permettent aux templates
d'ajouter, de supprimer, ou même de remplacer des éléments au sein d'un
conteneur de données, dont le contenu sera finalement rendu sous sa forme
finale dans une couche externe de la hiérarchie de templates.
Commençons par modifier
notre Layout dans
/application/views/layouts/default.phtml
pour
utiliser plusieurs de ces aides de Vues Placeholders :
<?php echo $this->doctype() . "\n" ?>
<html>
<head>
<?php
echo $this->headMeta()->setIndent(' ') . "\n";
echo $this->headTitle('Pádraic Brady\'s Blog')
->setSeparator(' / ')->setIndent(' ') . "\n";
echo $this->headLink()->setIndent(' ')
->appendStylesheet('http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/reset-fonts-grids.css')
->appendStylesheet('http://yui.yahooapis.com/2.7.0/build/base/base-min.css') . "\n ";
echo $this->headScript()->prependFile(
'http://html5shiv.googlecode.com/svn/trunk/html5.js',
'text/javascript',
array('conditional'=>'IE')) . "\n";
?>
</head>
<body>
<header>
<h1><a href="/">Pádraic Brady's Blog</a></h1>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Projects</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
</header>
<section>
<?php echo $this->layout()->content ?>
</section>
<footer>
<p>Copyright © 2009 Pádraic Brady</p>
<p>Powered by <a href="http://www.survivethedeepend.com">ZFBlog
</a></p>
</footer>
</body>
</html>
Zend_View_Helper_Doctype
et Zend_View_Helper_HeadMeta
sont tous deux
affichés sans modification, mis à part pour ajouter quelques indentations
et retours à la ligne là où c'est approprié. Cela permet juste de
s'assurer que le balisage produit soit plus facile à lire. Plutôt que de
définir une quelconque valeur directement dans le Layout pour ces deux là,
nous les configurerons depuis notre bootstrap en utilisant des options
depuis application.ini
.
Zend_View_Helper_HeadTitle
est aussi appelé pour définir une partie de Titre intitulée "Pádraic
Brady's Blog". Vous pouvez définir des portions supplémentaires, et elles
seront rendues au sein d'un élément <title>
, séparées
par tout séparateur que vous pourriez choisir de configurer en utilisant
Zend_View_Helper_HeadTitle::setSeparator()
. Le
second argument optionnel à la méthode primaire
headTitle()
peut être défini à
"APPEND
", la valeur par défaut, ou "PREPEND
"
pour qu'une partie du titre soit rendue au début de celui-ci.
Zend_View_Helper_HeadLink
peut être utilisé pour ajouter des liens vers des feuilles de styles, ou
en modifier. Il fonctionne aussi pour d'autres types de liens génériques
en passant une liste d'attributs et de valeurs à
Zend_View_Helper_HeadLink::headLink()
. Des liens
alternatifs peuvent être définis en utilisant
appendAlternate()
ou ses équivalents
(prepend/set/offsetSetAlternate()
). Lorsque nous
ajoutons des feuilles de styles, les paramètres optionnels incluent les
attributs media
et type
, ainsi que la définition
d'une liste de conditions (Comme celle que nous avons utilisée plus bas
pour inclure un fichier Javascript uniquement pour Internet Explorer). Si
vous avez besoin d'ajouter un bloc de styles, ceci peut être effectué en
utilisant, à la place, Zend_View_Helper_Style
. Pour
finir, Zend_View_Helper_Script
permet d'inclure des
scripts, tels des fichiers Javascript.
Vous pouvez noter
quelques similarités entre des implémentations de Placeholders concrets.
Ils ont des méthodes pour définir des éléments, et en ajouter en début ou
fin de liste. Beaucoup fournissent aussi des méthodes
offsetSet*()
pour définir des éléments à des
emplacements précis de la liste d'éléments à rendre.
Un élément de balisage
que j'ai omis est la balise <meta>
de charset. Elle ne
peut pour l'instant pas être rendue par Zend Framework, mais nous
résoudrons ce problème dans la section suivante. Pour l'instant, apportons
quelques modifications à notre méthode
ZFExt_Bootstrap::_initView()
pour configurer les
balises meta et le Doctype à rendre :
<?php
class ZFExt_Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
// ...
protected function _initView()
{
$options = $this->getOptions();
$config = $options['resources']['view'];
if (isset($config)) {
$view = new Zend_View($config);
} else {
$view = new Zend_View;
}
if (isset($config['doctype'])) {
$view->doctype($config['doctype']);
}
if (isset($config['language'])) {
$view->headMeta()->appendName('language', $config['language']);
}
$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper(
'ViewRenderer'
);
$viewRenderer->setView($view);
return $view;
}
// ...
}
Nous pouvons maintenant
modifier application.ini
pour ajouter au moins un
élément d'information meta, la langue de la page. Il ne s'agit pas d'une
option de configuration standard de Zend_View
, mais
uniquement d'une valeur par défaut que nous imposons depuis notre classe
de bootstrap lorsque nous définissons les informations meta pour la
page.
[production]
; PHP INI Settings
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0
; Bootstrap Location
bootstrap.path = APPLICATION_ROOT "/library/ZFExt/Bootstrap.php"
bootstrap.class = "ZFExt_Bootstrap"
; Standard Resource Options
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
resources.view.encoding = "UTF-8"
resources.modifiedFrontController.contentType = "text/html;charset=utf-8"
resources.layout.layout = "default"
resources.layout.layoutPath = APPLICATION_PATH "/views/layouts"
; Autoloader Options
autoloaderNamespaces[] = "ZFExt_"
; HTML Markup Options
resources.view.doctype = "HTML5"
resources.view.language = "en"
[staging : production]
[testing : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
resources.frontController.throwExceptions = 1
[development : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
resources.frontController.throwExceptions = 1
Pour le moment tout au moins, le support de HTML 5 par Zend Framework ne va pas plus loin que la fourniture d'un Doctype HTML. Le Doctype utilisé est réellement important : tout d'abord, parce qu'il est rendu en haut de votre Layout, mais surtout parce qu'il détermine comment les balises sont construites par un certain nombre de Views Helpers. Comme je l'ai dit précédemment, je préfére une approche de HTML 5 avec un style XHTML utilisant des balises fermées, des noms d'attributs en minuscules, et des valeurs d'attributs entourées de guillemets doubles, entre autres aspects. HTML 5 en lui-même est relativement peu au fait de ces détails ; en fait, il redéfinit une sérialisation XML comme une alternative à la variante HTML standard.
Avec notre Doctype
défini à "HTML5", cela pose problème lorsque nous testons si nous sommes
en mode XHTML : les aides de Vues vérifient si le nom du Doctype commence
par "XHTML". HTML5 (notre valeur actuelle de Doctype) ne correspond de
toute évidence pas. Pour résoudre ce problème, nous devrions ajouter une
implémentation personnalisée du
Zend_View_Helper_Doctype
standard (une simple
sous-classe est tout ce dont nous avons besoin) pour ajouter une option
appelée "XHTML5" qui assurera que les règles XHTML seront appliquées à
toute sortie d'aides de Vues où elles seront appliquées.
Nous enregistrerons
cette aide de vue personnalisée dans
/library/ZFExt/View/Helper/Doctype.php
. Pour que
notre application puisse indiquer à Zend_View
où
trouver ces aides personnalisées, nous pouvons définir quelques nouvelles
options dans le fichier application.ini
, dans notre
section "Options de Ressources Standard". Celles-ci indiqueront un nouveau
chemin où les Aides de Vues personnalisées peuvent être trouvées, et le
préfixe de classes qu'elles utilisent. Aucune autre modification n'est
ensuite nécessaire : les View Helpers dont le nom reflète celui de
n'importe quelle aide de vue de Zend Framework (autrement dit, qui se
terminent par le même terme en camel case) écrasent les aides fournies par
le Framework. Donc, appeler Zend_View::doctype()
devrait maintenant commencer par rechercher une aide de vue personnalisée
répondant à ce nom.
[production]
; PHP INI Settings
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0
; Bootstrap Location
bootstrap.path = APPLICATION_ROOT "/library/ZFExt/Bootstrap.php"
bootstrap.class = "ZFExt_Bootstrap"
; Standard Resource Options
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
resources.view.encoding = "UTF-8"
resources.modifiedFrontController.contentType = "text/html;charset=utf-8"
resources.layout.layout = "default"
resources.layout.layoutPath = APPLICATION_PATH "/views/layouts"
resources.view.helperPath = "ZFExt/View/Helper/"
resources.view.helperPathPrefix = "ZFExt_View_Helper_"
; Autoloader Options
autoloaderNamespaces[] = "ZFExt_"
; HTML Markup Options
resources.view.doctype = "HTML5"
resources.view.language = "en"
[staging : production]
[testing : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
resources.frontController.throwExceptions = 1
[development : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
resources.frontController.throwExceptions = 1
Implémenter cette
nouvelle Aide de Vue répond à un seul besoin : permettre l'utilisation
d'un nouveau Doctype XHTML5 qui renvoit en sortie le doctype HTML5
standard qui sera affiché dans la sortie. Toute récupération du Doctype
devrait aussi retourner la valeur correcte, qui passe le test XHTML
utilisé par les autres View Helpers. Si vous avez suivi la mise en place
d'un nouveau fichier AllTests.php
pour le Modèle un
peu plus tôt, vous pouvez maintenant répéter ce processus pour toute aide
de vue personnalisée que nous prévoierons d'ajouter. Voici nos tests
unitaires pour une implémentation, stockés dans
/tests/ZFExt/View/Helper/DoctypeTest.php
:
<?php
class ZFExt_View_Helper_DoctypeTest extends PHPUnit_Framework_TestCase
{
protected $helper = null;
public function setup()
{
$this->helper = new ZFExt_View_Helper_Doctype;
}
public function testRendersHtml5DoctypeForXhtmlSerialisation()
{
$this->helper->doctype('XHTML5');
$this->assertEquals('<!DOCTYPE html>', (string) $this->helper);
}
public function testReturnsXhtmlDoctypeName()
{
$this->helper->doctype('XHTML5');
$this->assertEquals('XHTML5', $this->helper->getDoctype());
}
}
Implémenter cette aide
de vue ne requiert qu'une simple sous-classe, définie dans
/library/ZFExt/View/Helper/Doctype.php
:
<?php
class ZFExt_View_Helper_Doctype extends Zend_View_Helper_Doctype
{
const XHTML5 = 'XHTML5';
public function __construct()
{
parent::__construct();
$this->_registry['doctypes'][self::XHTML5] = '<!DOCTYPE html>';
}
public function doctype($doctype = null)
{
if (null !== $doctype && $doctype == self::XHTML5) {
$this->setDoctype($doctype);
} else {
parent::doctype($doctype);
}
return $this;
}
}
Comme vous pouvez le voir à partir de la classe, en dehors de toute gestion spécifique du XHTML5, nous avons juste passé le contrôle à la classe mère pour tous les autres types possibles de Doctypes.
Un autre aspect du
support HTML5 sur lequel nous pouvons maintenant nous pencher est le
nouvel attribut charset
que nous avons utilisé dans une
balise <meta>
. En pratique, l'objectif est de remplacer
ceci :
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
Avec une forme plus
courte, qui ne pré-suppose pas un Content-type
. Bien entendu,
vous pouvez toujours ajouter le Content-type
si vous en avez
besoin, mais l'application Web devrait de toute manière être servie sous
le bon type.
<meta charset="utf-8"/>
Malheureusement,
Zend_View_Helper_HeadMeta
, le View Helper qui
permet aux templates et aux Layouts d'ajouter des informations meta ne
reconnait pas l'attribut charset
comme étant valide. Nous
pouvons le persuader du contraire en ajoutant une autre aide de vue
personnalisée, encore une fois en surchargeant la classe d'origine. Ici
encore, nous sous-classerons la classe de départ pour minimiser la
quantité de code que nous aurons besoin d'écrire.
Voici les tests
unitaires correspondant, enregistrés dans
/tests/ZFExt/View/Helper/HeadMetaTest.php
:
<?php
class ZFExt_View_Helper_HeadMetaTest extends PHPUnit_Framework_TestCase
{
protected $helper = null;
protected $view = null;
public function setup()
{
foreach (array(Zend_View_Helper_Placeholder_Registry::REGISTRY_KEY, 'Zend_View_Helper_Doctype') as $key) {
if (Zend_Registry::isRegistered($key)) {
$registry = Zend_Registry::getInstance();
unset($registry[$key]);
}
}
/**
* This custom helper only concerns (X)HTML 5 support
* using the ZFExt doctype helper for the XHTML flavour
*/
$this->view = new Zend_View();
$this->view->addHelperPath('ZFExt/View/Helper', 'ZFExt_View_Helper');
$this->helper = new ZFExt_View_Helper_HeadMeta();
$this->helper->setView($this->view);
}
public function testRendersHtml5CharsetMetaElement()
{
$this->view->doctype('HTML5');
$this->helper->setCharset('utf-8');
$this->assertEquals('<meta charset="utf-8">', (string) $this->helper);
}
public function testRendersXhtml5CharsetMetaElement()
{
$this->view->doctype('XHTML5');
$this->helper->setCharset('utf-8');
$this->assertEquals('<meta charset="utf-8"/>', (string) $this->helper);
}
}
L'implémentation est un
peu plus longue que d'habitude, puisque nous ajoutons le support complet
pour un autre type de balise <meta>
initialement non
acceptée. La nouvelle classe est écrite dans le fichier
/library/ZFExt/View/Helper/HeadMeta.php
:
<?php
class ZFExt_View_Helper_HeadMeta extends Zend_View_Helper_HeadMeta
{
protected $_typeKeys = array('name', 'http-equiv', 'charset');
public function setCharset($charset)
{
$item = new stdClass;
$item->type = 'charset';
$item->charset = $charset;
$item->content = null;
$item->modifiers = array();
$this->set($item);
return $this;
}
protected function _isValid($item)
{
if ((!$item instanceof stdClass)
|| !isset($item->type)
|| !isset($item->modifiers))
{
return false;
}
$doctype = $this->view->doctype()->getDoctype();
if (!isset($item->content)
&& (($doctype !== 'HTML5' && $doctype !== 'XHTML5')
|| (($doctype == 'HTML5' || $doctype == 'XHTML5') && $item->type !== 'charset'))
) {
return false;
}
return true;
}
public function itemToString(stdClass $item)
{
if (!in_array($item->type, $this->_typeKeys)) {
require_once 'Zend/View/Exception.php';
throw new Zend_View_Exception(sprintf('Invalid type "%s" provided for meta', $item->type));
}
$type = $item->type;
$modifiersString = '';
foreach ($item->modifiers as $key => $value) {
if (!in_array($key, $this->_modifierKeys)) {
continue;
}
$modifiersString .= $key . '="' . $this->_escape($value) . '" ';
}
if ($this->view instanceof Zend_View_Abstract) {
if ($this->view->doctype()->getDoctype() == 'XHTML5'
&& $type == 'charset') {
$tpl = '<meta %s="%s"/>';
} elseif ($this->view->doctype()->getDoctype() == 'HTML5'
&& $type == 'charset') {
$tpl = '<meta %s="%s">';
} elseif ($this->view->doctype()->isXhtml()) {
$tpl = '<meta %s="%s" content="%s" %s/>';
} else {
$tpl = '<meta %s="%s" content="%s" %s>';
}
} else {
$tpl = '<meta %s="%s" content="%s" %s/>';
}
$meta = sprintf(
$tpl,
$type,
$this->_escape($item->$type),
$this->_escape($item->content),
$modifiersString
);
return $meta;
}
}
Ne vous inquiètez pas si tout cela a l'air incompréhensible ! Nous verrons quelques aides de vues spécifiques à notre application dans des chapîtres ultérieurs, mais, pour le moment, nous avons juste besoin de ces deux aides personnalisées pour avoir un support plus complet de HTML 5 à notre disposition.
Dans la classe
ci-dessous, nous remplaçons deux méthodes d'origine :
_isValid()
et
itemToString()
. La première valide les détails
des informations meta que nous voulons rendre dans une balise
<meta>
. Les données sont stockées sous forme d'une
instance de stdClass
, c'est-à-dire qu'il s'agit
d'un simple conteneur de données qui aurait tout aussi bien pu être un
tableau associatif. Le principal ajout que j'ai effectué au niveau de la
validation est d'autoriser un nouveau type de meta "charset" dans le cas
où le Doctype est un de ceux correspondant à HTML 5. Dans la méthode
itemToString()
, j'ai ajouté la possibilié de
composer ce nouveau type meta en une balise <meta>
, en
fermant au passage la balise si le Doctype utilisé est XHTML5.
J'ai aussi ajouté une
nouvelle méthode, setCharset()
, pour que la
création de cette nouvelle balise soit différenciée des deux autres types
de meta initialement supportées, c'est-à-dire un simple type clef/valeur
et http-equiv.
Maintenant que notre
support HTML 5 est complet, nous pouvons retourner voir
application.ini
et ajouter une nouvelle option de
balisage HTML nommée "charset", ainsi que notre nouveau Doctype. En fait,
nous pourrions juste utiliser la configuration de
Zend_View
pour l'encodage de caractères, mais j'ai
préféré garder ces deux options distinctes, pour que nous puissions
ajouter la nouvelle balise meta conditionnellement uniquement dans le cas
où cette option spécifique est définie.
[production]
; PHP INI Settings
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0
; Bootstrap Location
bootstrap.path = APPLICATION_ROOT "/library/ZFExt/Bootstrap.php"
bootstrap.class = "ZFExt_Bootstrap"
; Standard Resource Options
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
resources.view.encoding = "UTF-8"
resources.view.helperPath = "ZFExt/View/Helper/"
resources.view.helperPathPrefix = "ZFExt_View_Helper_"
resources.modifiedFrontController.contentType = "text/html;charset=utf-8"
resources.layout.layout = "default"
resources.layout.layoutPath = APPLICATION_PATH "/views/layouts"
; Autoloader Options
autoloaderNamespaces[] = "ZFExt_"
; HTML Markup Options
resources.view.charset = "utf-8"
resources.view.doctype = "XHTML5"
resources.view.language = "en"
[staging : production]
[testing : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
resources.frontController.throwExceptions = 1
[development : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
resources.frontController.throwExceptions = 1
Nous pouvons maintenant
modifier la méthode _initView()
de notre classe
de bootstrap pour gérer et utiliser la nouvelle option charset :
<?php
class ZFExt_Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
// ...
protected function _initView()
{
$options = $this->getOptions();
$config = $options['resources']['view'];
if (isset($config)) {
$view = new Zend_View($config);
} else {
$view = new Zend_View;
}
if (isset($config['doctype'])) {
$view->doctype($config['doctype']);
}
if (isset($config['language'])) {
$view->headMeta()->appendName('language', $config['language']);
}
if (isset($config['charset'])) {
$view->headMeta()->setCharset($config['charset'], 'charset');
}
$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper(
'ViewRenderer'
);
$viewRenderer->setView($view);
return $view;
}
// ...
}
Allons-y, maintenant, rechargeons la page d'accueil de notre blog. Si vous regardez dans le code source, celui-ci devrait à présent réellement correspondre au balisage d'origine introduit au début de ce chapitre.
Utiliser le Framework CSS YUI nous a apporté un style de base pour le blog, qui est très basique, mais agréable sous tous les principaux navigateurs. Nous pouvons enrichir ceci en définissant nos propres styles personnalisés, pour donner à notre blog un aspect un peu plus fini.
Pour cela, nous avons
besoin de modifier notre template de Layout pour aussi inclure un fichier
nommé style.css stocké dans /public/css/style.css
. Au
sein de ce fichier, nous pourrons placer des règles CSS supplémentaires.
Voici la version revue de la section <head>
de notre
Layout :
<?php echo $this->doctype() . "\n" ?>
<html>
<head>
<?php
echo $this->headMeta()->setIndent(' ') . "\n";
echo $this->headTitle('Pádraic Brady\'s Blog')
->setSeparator(' / ')->setIndent(' ') . "\n";
echo $this->headLink()->setIndent(' ')
->appendStylesheet('http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/reset-fonts-grids.css')
->appendStylesheet('http://yui.yahooapis.com/2.7.0/build/base/base-min.css')
->appendStylesheet('/css/style.css') . "\n ";
echo $this->headScript()->prependFile(
'http://html5shiv.googlecode.com/svn/trunk/html5.js',
'text/javascript',
array('conditional'=>'IE')) . "\n";
?>
</head>
En gardant à l'esprit le
fait que nous pourrions vouloir prendre en compte les Yahoo
Performance Best Practices
, ajoutons notre exemple d'aide de vue
précédent au mélange, avec quelques modifications. Voici les tests
unitaires utilisés par l'aide personnalisée
ZFExt_View_Helper_IncludeModifiedDate
. Ils
utilisent un fichier style.css
vide que vous pouvez
enregistrer dans
/tests/ZFExt/View/Helper/_files/style.css
pour
obtenir une date de dernière modification. Les tests en eux-même sont
enregistrés dans
/tests/ZFExt/View/Helper/IncludeModifiedDateTest.php
.
Comme pour tous ces tests, ajoutons les au fichier
AllTests.php
le plus proche, pour qu'ils soient
exécutés par PHPUnit. Le seul point un tant soit peu compliqué est de
gérer le répertoire de travail actuellement utilisé par PHP pour que les
URIs relatives aient un sens dans le test, et atteignent correctement
notre fichier style.css
.
<?php
class ZFExt_View_Helper_IncludeModifiedDateTest extends PHPUnit_Framework_TestCase
{
protected $helper = null;
private $cwd = null;
public function setup()
{
$this->helper = new ZFExt_View_Helper_IncludeModifiedDate;
$this->cwd = getcwd();
chdir(dirname(__FILE__));
}
public function teardown()
{
chdir($this->cwd);
}
public function testAddsTimestampToFilenameBeforeFileExtension()
{
$file = '/_files/style.css';
$timestamped = $this->helper->includeModifiedDate($file);
$this->assertTrue((bool) preg_match("/^\/_files\/style\.\d{10,}\.css\$/", $timestamped));
}
public function testAddsTimestampToFilenameBeforeFileExtensionWithUriQueryString()
{
$file = '/_files/style.css?version=2.0';
$timestamped = $this->helper->includeModifiedDate($file);
$this->assertTrue((bool) preg_match("/^\/_files\/style\.\d{10,}\.css\?version=2\.0$/", $timestamped));
}
}
Comme vous pouvez
probablement le remarquer, nous avons boosté un peu l'aide pour gérer un
cas supplémentaire où l'URI d'un fichier incluerait déjà une query string
quelconque. Dans ce cas, nous ajouterons le timestamp de dernière
modification au sein du nom du fichier, et non dans la query string. Cela
permet de gérer un scénario où de nombreux proxies ignorent la query
string lorsqu'ils cachent les fichiers, ce qui signifie que modifier le
nom du fichier ou son chemin est plus efficace. Bien entendu, nous ne
modifierons pas physiquement le nom du fichier ; à la place, nous
ajouterons une nouvelle règle de ré-écriture au fichier
/public/.htaccess
, afin que toute URI vers un fichier
physique de la forme /name.timestamp.extension
(par
exemple, /style.1252108229.css
) soit redirigée vers
le bon fichier correctement. Voici l'implémentation, enregistrée dans
/library/ZFExt/View/Helper/IncludeModifiedDate.php
:
<?php
class ZFExt_View_Helper_IncludeModifiedDate extends Zend_View_Helper_Abstract
{
public function includeModifiedDate($uri) {
$parts = parse_url($uri);
$root = getcwd();
$mtime = filemtime($root . $parts['path']);
return preg_replace(
"/(\.[a-z0-9]+)(\?*.*)$/",
'.'.$mtime.'$1$2',
$uri
);
}
}
La modification suivante ajoute le support de ce type de marquage de
fichiers à /public/.htaccess
, afin que nous chargions
le bon fichier, qui n'a pas de timestamp inclu dans son nom. La règle de
ré-écriture ne faire guère plus que supprimer la partie timestamp du nom
de fichier arrivant. Vous pouvez ajouter des exensions de fichiers ou des
répertoires supplémentaires sur lesquels ceci a besoin d'être exécuté.
J'ai utilisé par défaut quelques valeurs communes et raisonnables :
SetEnv APPLICATION_ENV development RewriteEngine On RewriteRule ^(scripts|css|images)/(.+)\.(.+)\.(js|css|jpg|gif|png)$ $1/$2.$4 [L] RewriteCond %{REQUEST_FILENAME} -s [OR] RewriteCond %{REQUEST_FILENAME} -l [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteRule ^.*$ - [NC,L] RewriteRule ^.*$ /index.php [NC,L]
Notre template de layout peut maintenant utiliser le nouvel aide de vue personnalisé de la manière qui suit :
<?php echo $this->doctype() . "\n" ?>
<html>
<head>
<?php
echo $this->headMeta()->setIndent(' ') . "\n";
echo $this->headTitle('Pádraic Brady\'s Blog')
->setSeparator(' / ')->setIndent(' ') . "\n";
echo $this->headLink()->setIndent(' ')
->appendStylesheet('http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/reset-fonts-grids.css')
->appendStylesheet('http://yui.yahooapis.com/2.7.0/build/base/base-min.css')
->appendStylesheet($this->includeModifiedDate('/css/style.css')) . "\n ";
echo $this->headScript()->prependFile(
'http://html5shiv.googlecode.com/svn/trunk/html5.js',
'text/javascript',
array('conditional'=>'IE')) . "\n";
?>
</head>
Nous n'utiliserons pas
notre Aide de Vue avec les URIs des CSS YUI, puisqu'elles ont déjà une
référence à un numéro de version inclue. Nous pouvons modifier les URIs
manuellement lorsque de nouvelles versions du Framework CSS seront
publiées, toujours à partir du template de layout, sans avoir à effectuer
quelque autre modification que ce soit. Pour garder trace de notre code de
balisage, voici ce à quoi la sortie du navigateur ressemble lorsque l'on
regarde le code source de la page affichée, à notre niveau, pour notre
section <head>
:
<!DOCTYPE html>
<html>
<head>
<meta name="language" content="en" />
<meta charset="utf-8"/>
<title>Pádraic Brady's Blog</title>
<link href="http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/reset-fonts-grids.css" media="screen" rel="stylesheet" type="text/css" />
<link href="http://yui.yahooapis.com/2.7.0/build/base/base-min.css" media="screen" rel="stylesheet" type="text/css" />
<link href="/css/style.1252108229.css" media="screen" rel="stylesheet" type="text/css" />
<!--[if IE]> <script type="text/javascript" src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
</head>
Comme vous pouvez le constater, l'URI vers style.css inclut maintenant une date de dernière modification attachée à la query string, comme nous le souhaitions.
Si vous vous demandez
comment mettre en place l'utilisation des en-têtes Expires
et
Cache-Control
dans la pratique, c'est généralement fait à
partir de la configuration de votre Virtual Host. Il vous faudra
auparavant activer le module Apache mod_expires
dans la
configuration de votre serveur web.
Voici un exemple de
configuration pour le Virtual Host de notre blog, implémentant une en-tête
Expires
de six mois à partir du moment où le client accéde à
un fichier statique. J'ai ajouté deux règles pour illustrer les options :
définir les fichiers auxquels elles doivent être appliquées via une
expression rationnelle, ou en faisant référence aux types auxquels
l'en-tête devrait être appliquée. Les règles sont intégrées à un bloc
conditionnel, afin qu'elles soient ignorées si le module requis n'est pas
chargé dans Apache. Il est aussi essentiel de se souvenir que, à moins que
vous n'utilisiez une date de modification ou un hash dans la query string
des URIs de vos ressources, le client les cachera pour six mois et
n'essayera jamais de récupérer une version mise à jour, à moins qu'il ne
perdre son cache ou qu'un rechargement complet ne soit forcé (par exemple,
en utilisant CTRL+SHIFT+R sous Firefox).
<VirtualHost *:80> ServerName zfblog.tld DocumentRoot /home/padraic/projects/zfblog/public <Directory "/home/padraic/projects/zfblog/public"> Options Indexes MultiViews FollowSymLinks AllowOverride all Order deny,allow Allow from all <IfModule mod_expires.c> ExpiresActive on <FilesMatch "\.(ico|jpg|jpeg|png|gif)$"> ExpiresDefault "access plus 6 months" </FilesMatch> ExpiresByType text/css "access plus 1 year" ExpiresByType text/javascript "access plus 1 year" </IfModule> </Directory> </VirtualHost>
En utilisant cette configuration, les types de fichiers correspondant aux règles seront envoyés au client avec deux nouveau en-têtes. Par exemple :
Cache-Control: max-age=31536000 Expires: Sun, 05 Sep 2010 00:39:27 GMT
Expires
indique une date six mois dans le futur, à partir de laquelle le fichier
devrait être considéré comme expiré, et re-téléchargé depuis le serveur,
et la seconde, Cache-Control
, indique la durée de vie
maximale de la ressource en secondes. A un niveau plus technique,
l'en-tête Cache-Control
est la plus importante, puisque, dans
la plupart des cas, elle écrase l'en-tête Expires
.
Avant que nous
n'attaquions une feuille de style personnalisée, commençons par utiliser
ce que nous avons déjà inclu avec Yahoo User Interface CSS. Les
modifications qui suivent, apportées à notre template de layout par défaut
/application/views/layouts/default.phtml
, vont
correspondre à une présentation typique de site web, avec une en-tête, un
pieds de page, un corps de page, et une colonne à gauche pour les liens de
navigation ou contenus supplémentaires :
<?php echo $this->doctype() . "\n" ?>
<html>
<head>
<?php
echo $this->headMeta()->setIndent(' ') . "\n";
echo $this->headTitle('Pádraic Brady\'s Blog')
->setSeparator(' / ')->setIndent(' ') . "\n";
echo $this->headLink()->setIndent(' ')
->appendStylesheet('http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/reset-fonts-grids.css')
->appendStylesheet('http://yui.yahooapis.com/2.7.0/build/base/base-min.css')
->appendStylesheet($this->includeModifiedDate('/css/style.css')) . "\n ";
echo $this->headScript()->prependFile(
'http://html5shiv.googlecode.com/svn/trunk/html5.js',
'text/javascript',
array('conditional'=>'IE')) . "\n";
?>
</head>
<body>
<div id="doc" class="yui-t1">
<header id="hd" role="banner">
<hgroup>
<h1><a href="/">Pádraic Brady's Blog</a></h1>
<h2>Musings On PHP And Zend Framework</h2>
</hgroup>
<nav role="navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Projects</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
</header>
<div id="bd">
<div id="yui-main">
<div class="yui-b" role="main">
<section id ="content" class="yui-g">
<?php echo $this->layout()->content ?>
</section>
</div>
</div>
<section class="yui-b">
<nav role="navigation" aria-labelledby="leftnav-label">
<h2 id="leftnav-label">External Links</h2>
<ul>
<li><a href="#">Survive The Deep End</a></li>
<li><a href="#">Zend Framework</a></li>
<li><a href="#">Planet-PHP</a></li>
<li><a href="#">I'm on Twitter!</a></li>
</ul>
</nav>
</section>
</div>
<footer id="ft" role="contentinfo">
<p>Copyright © 2009 Pádraic Brady</p>
<p>Powered by <a href="http://www.survivethedeepend.com">ZFBlog
</a></p>
</footer>
</div>
</body>
</html>
Puisque nous utilisons
HTML 5, une bonne partie du balisage utilise les nouveaux éléments, sauf
dans les cas où ils ne sont réellement nécessaires que dans le seul but
d'appliquer la mise en forme de grille. Dans ces cas, j'ai utilisé des
éléments <div>
habituels. Cela permet de conserver un
ensemble logique d'éléments, où nous n'avons que deux éléments
<section>
distincts. Un pour le contenu principale, et
l'autre pour la barre à gauche que je viens d'ajouter. Les
<div>
sont alors au relégués au rôle peu intéressant
qui permet d'ajouter des "hooks" aux balises pour le styling ; ce n'est
pas vraiment l'approche la plus proche, mais puisque j'utilise un
Framework CSS, je me ferai à cette solution.
J'ai aussi ajouté des
rôles à un certain nombre d'éléments, en accord avec un autre nouveau
standard, Accessible
Rich Internet Applications Suite (WAI–ARIA 1.0)
, qui définit une
manière d'essayer de rendre les contenus et applications web plus
accessible aux personnes souffrant de handicaps. Ce n'est qu'un geste
minime à faire, alors que nous adoptons HTML 5 et créons un layout à
partir de rien. Le standard définit un ensemble de "document landmark
roles" qui, en théorie, devraient permettre à quelqu'un utilisant les bons
logiciels de naviguer à travers le réseau de balises vers les parties du
document HTML qui les intéressent. Les rôles sont donc des plus intuitifs
: banner, main, navigation, contentinfo, complementary, search, etc. Il
existe un grand nombre de ceux-ci, mais je n'utilise que le strict
nécessaire. Si cela peut ajouter un certain poids, ces rôles sont
actuellement supportés par le lecteur
d'écran JAWS 10
.
Pour ajouter un
sous-titre au blog, j'ai ajouté un nouvel élément
<hgroup>
. Celui-ci regroupe les en-têtes liées, et est
défini de la manière suivante par les spécifications HTML 5 :
Rechargez l'URI http://zfblog.tld
dans votre
navigateur pour voir les impacts que tout ceci a eu :
Le design mis à jour est
en train, doucement, de commencer à ressemble à quelque chose qui
deviendrait acceptable, si nous étions doté d'une patience infinie.
Améliorons ceci encore un peu, cette fois en éditant le fichier
/public/css/style.css
:
/** * Basic Elements */ body { font-family: Geneva, Verdana, Helvetica, sans-serif; } a:link, a:visited { color: blue; text-decoration: none; } a:hover { color: red; text-decoration: underline; } /** * HTML 5 Block Display * * Required by most, if not all, browsers until support is added. */ article, aside, dialog, figure, footer, header, hgroup, nav, section { display:block; } /** * Page Elements */ header#hd { text-align:center; } footer#ft { text-align: center; margin-top: 25px; border-top: 1px solid lightgray; border-bottom: 1px solid lightgray; } section#content { border-left: 3px double lightgray; padding-left: 10px; } /** * Headers */ h1, h2, h3, h4, h5, h6 { Helvetica,Arial,Calibri,sans-serif; } header#hd h1 { font-size: 200%; margin-bottom: 0; } header#hd h2 { margin-top: 0.4em; font-size: 100%; } /** * Horizontal Header Navigation Menu */ header#hd nav { border-top: 2px solid #000; border-bottom: 2px solid #000; } header#hd nav ul { list-style: none; margin: 0 0 0 20px; text-align: left; } header#hd nav li { display: inline-block; min-width: 50px; margin: 0 2px 0 2px; } header#hd nav a:link, nav a:visited { color: blue; display: inline-block; height: 20px; padding: 5px 1.5em; text-decoration: none; } header#hd nav a:hover { background-color: lightgray; } /** * Vertical Sidebar Menu/Links */ section nav h2 { font-size: 120%; } section nav ul { list-style: none; width: 100%; margin: 0 auto; } section nav ul li { float: left; display: inline; margin: 0; } /** * Article related styling */ article header { margin-bottom: 1em; } article header h2 { border-bottom: 1px dashed gray; font-size: 130%; color: green; margin-bottom: 0.5em; } article header p { font-style: italic; }
Nous avons maintenant un style très basique, mais acceptable, pour travailler pour la suite :
Au cours de ce chapitre,
nous avons vu les bases qui permettent la création d'un design web simple,
et l'avons migré vers le système de templating de Zend Framework en
utilisant Zend_View
,
Zend_Layout
, et un certain nombre d'aides de vues.
Nous verrons beaucoup plus en détails la création de View Helpers
personnalisés au cours des prochains chapitres. Nous avons aussi vu
quelques bases de HTML 5, la prochaine mise à jour du standard pour HTML,
et l'impact qu'il a sur notre implémentation, puisqu'il requiert
l'utilisation de quelques aides de vues personnalisées. Je n'ai aucun
doute que le support de HTML 5 au sein de Zend Framework se matérialisera
dans un futur très proche, cela dit, donc stay tuned
!
Au chapitre 11, nous
commencerons à travailler sur l'ensemble de fonctionnalités de base. Nous
apprendrons à afficher des entrées de blog à partir de notre Base de
Données, et, bien entendu, nous verrons comment permettre leur saisie !
Nous regarderons donc comment utiliser notre Modèle, et comment gérer
l'écriture de nouvelles entrées de blog en utilisant les formulaires
générés avec Zend_Form
.