Chapitre 10. Mettre en place le Design avec Zend_View, Zend_Layout, HTML 5 et Yahoo! User Interface Library

Table des matières

10.1. Introduction
10.2. Zend_View: Templating Orienté Objet
10.2.1. Layouts
10.2.2. Partials
10.2.3. View Helpers
10.2.4. Placeholders
10.2.5. Short Tags vs Full Tags
10.3. La mise en place de l'application ZFBlog
10.4. Créer une page d'Index avec HTML 5
10.5. Extraire le balisage statique vers un Layout
10.6. Remplacer les Eléments modifiables par des Placeholders
10.7. Améliorer le support de HTML 5 avec des Aides de Vues Personnalisées
10.8. Ajouter un Lien vers une Feuille de Style Personnalisée
10.9. Personnaliser le Style
10.10. Conclusion
[Important]Important

Ceci est un chapître en cours de rédaction. Son contenu peut être modifié, et de nouvelles sections peuvent être ajoutées au cours des prochains jours. Comme toujours, votre feedback est bienvenu, par le biais des commentaires aux niveaux paragraphe ou chapitre, et peut inclure à la fois toute critique (plus que bienvenues !) ou suggestion pour de prochaines mises à jour de ce chapitre.

10.1. Introduction

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.

10.2. Zend_View: Templating Orienté Objet

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 :

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.

10.2.1. Layouts

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.

10.2.2. Partials

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.

10.2.3. View Helpers

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.

10.2.4. 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>

10.2.5. Short Tags vs Full Tags

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.

10.3. La mise en place de l'application ZFBlog

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à.

10.4. Créer une page d'Index avec HTML 5

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 :

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 :

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 :

header

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.

nav

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 :

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 :

section

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.

article

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.

footer

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.

address

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.

time

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 :

Initial Blog Index Page

10.5. Extraire le balisage statique vers un Layout

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 :

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 :

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 :

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>.

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.

Initial Blog Error Page

10.6. Remplacer les Eléments modifiables par des Placeholders

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 :

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 :

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.

10.7. Améliorer le support de HTML 5 avec des Aides de Vues Personnalisées

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.

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 :

Implémenter cette aide de vue ne requiert qu'une simple sous-classe, définie dans /library/ZFExt/View/Helper/Doctype.php :

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 :

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.

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 :

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 :

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.

Nous pouvons maintenant modifier la méthode _initView() de notre classe de bootstrap pour gérer et utiliser la nouvelle option charset :

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.

10.8. Ajouter un Lien vers une Feuille de Style Personnalisée

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 :

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.

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 :

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 :

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> :

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.

10.9. Personnaliser le Style

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 :

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 :

hgroup

L'élément hgroup est typiquement utilisé pour regrouper un ensemble d'un ou plusieurs éléments h1-h6 — pour regrouper, par exemple, un titre de section et le sous-titre l'accompagnant.

Rechargez l'URI http://zfblog.tld dans votre navigateur pour voir les impacts que tout ceci a eu :

Basic Blog Index Style

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 :

Final Blog Index Style

10.10. Conclusion

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.