Hallo, ich heiße Matthias Zeis - schön, dass Sie den Weg zu meiner Übersetzung von ZFSTDE gefunden haben!
Falls Sie mehr über Magento 2 oder meine Arbeit als Technical Lead bei LimeSoda erfahren möchten, kommen Sie doch auf meiner Website vorbei.
Inhaltsverzeichnis
In diesem Kapitel werden
wir ein simples Webdesign für unseren Blog erstellen. Das gehört eindeutig
zur View-Schicht des Model-View-Controller-Architektur-Designpatterns
(MVC). Auf einen einfachen Nenner gebracht werden wir vor allem HTML für
die Auszeichnungen der Inhalte (das "Markup") und CSS für die Gestaltung
des Blogs verwenden. Wie in vielen Frameworks wird das Markup in Form von
Templates in unserer Anwendung gespeichert, die Templates werden am Ende
jedes Requests gerendert und als Antwort an den Benutzer gesendet. Die
Sache wird interessanter, wenn Sie bedenken, dass eine komplexe HTML-Seite
in (bedingt) wiederverwendbare Elemente heruntergebrochen werden kann. Zum
Beispiel hat vermutlich jede HTML-Seite wiederverwendbare Elemente wie
einen Header, einen Footer, ein Navigationsmenü, eine Breadcrumb-Leiste
etc. Neben diesen Elementen gibt es dynamische Inhalte und einige andere
Elemente, die nur auf bestimmten Seiten benötigt werden. In anderen Fällen
müssen Sie vielleicht spezielle Formatierungsregeln anwenden oder Daten
aus der Datenbank formatiert ausgeben. Indem man Zend Frameworks
Templatesystem mittels Zend_View
einsetzt, kann man
die Duplizierung von Code zum Bearbeiten von Markup und Templates
vermindern. Ermöglicht wird das durch View-Helper ("View-Helfer"),
Partials ("Partielle"), Placeholder ("Platzhalter") und
Zend_Layout
für die gebräuchlichsten
Auszeichnungselemente wie <head>
,
<title>
, <html>
und andere Elemente
wie Links zu CSS-Stylesheets oder Javascript-Dateien, die in die Seite
eingebunden werden müssen.
Ich bin ein ziemlich schlechter Webdesigner (tut mir leid!), weswegen ich das Design für unseren Blog ziemlich einfach halten werden werde, damit es jemand anderer später verschönern kann. Für das Markup wird HTML 5 verwendet. HTML 5 ist die nächste größere Überarbeitung von HTML. Daher erschien es mir angemessen, mit dem neuen Standard etwas zu experimentieren. Da das XHTML-2.0-Projekt (zumindest vorerst) beendet wurde, handelt es sich auch um den nächsten größeren Pseudo-Ersatz für XHTML. Um das festzuhalten: HTML 5 kann technisch gesehen als HTML- oder XML-basierte Serialisierung ausgeliefert werden (das heißt: entweder als text/html oder application/xml+xhtml in Kombination mit den jeweils zutreffenden Syntaxregeln). Aufgrund der Browserunterstützung (IE akzeptiert den XML-Content-Type nicht) ist nur die HTML-Serialisierung wirklich verwendbar, wobei ich XHTML-Konventionen wie das Schließen aller Tags und die Verwendung von klein geschriebenen Attributen und Tag-Namen befolgen werde, da ich mit diesen am meisten vertraut bin.
Mein Einsatz von HTML 5
sollte als experimentell angesehen werden. Es liegt noch nicht in einer
finalen Fassung vor und wird nicht von allen Browsern unterstützt. Für
HTML 5 kommen Firefox 3.5, Internet Explorer 8 oder neuere
Browserversionen in Frage - alles, was älter ist, unterstützt den Standard
nur sehr lückenhaft. Allerdings ist HTML 5 bis zu einem gewissen Grad
abwärtskompatibel zu HTML 4. Macht man sich an die Gestaltung der Seite,
muss man Internet Explorer erst etwas gut zureden, damit alles
funktioniert (IE erkennt die neuen HTML5-Elemente nicht und kann ihr
Design im Gegensatz zu anderen Browsern nicht ändern), aber wir werden
dieses Problem lösen, indem wir eine sehr nette kleine
JavaScript-Bibliothek namens html5shiv
verwenden.
Um uns die Gestaltung zu
erleichtern, werde ich zudem das Yahoo User Interface (YUI)
Library's CSS framework
verwenden. Ich werde es einsetzen, um für
alle Browser denselben Ausgangszustand herzustellen (die gleichen
Schriftarten, Schriftgrößen, Abstände zwischen den Elementen und ihre
Ränder etc.). Außerdem werde ich mittels der Grid-Implementierung die
Erstellung der einzelnen Abschnitte der Seite vereinfachen. YUI CSS ist
nur eines von vielen CSS-Frameworks. In der Vergangenheit habe ich Elastic
und Blueprint CSS
verwendet.
Diese Frameworks dienen primär dazu, die Entwicklungszeit zu verkürzen und
(hoffentlich) weniger CSS zu schreiben - für mich ein wichtiger Faktor, da
meine Designkünste einiges zu wünschen übrig lassen und ich Stunden damit
verbringen könnte, mit den CSS-Einstellungen zu spielen!
Ich mag es wirklich
nicht, CSS-Code zu bearbeiten, wenn ich einen Affen mit einem großen Sack
voller Erdnüsse dazu bringen könnte, diese Aufgabe besser zu erledigen.
Oder einen Webdesigner (das wäre wahrscheinlich noch besser als ein Affe).
Vorerst brauche ich einfach nur ein Standard-Design, das im Notfall als
ganz ordentlich durchgeht. In diesem Artikel werden wir uns daran
versuchen, ein Standard-Blog-Design aufzusetzen, es mit Inhalten zu
befüllen und schließlich das Design mit
Zend_View
-Templates zu erfassen.
Wir PHP-Progammierer haben Template-Engines seit dem Anbeginn von PHP verwendet. Eigentlich ist PHP selbst eine Template-Engine. Ursprünglich war das sogar der einzige Daseinszweck - man kann PHP sehr leicht in HTML oder andere Auszeichnungssprachen einbringen. Nicht so großartig waren wir allerdings im Unterscheiden zwischen der Präsentation (Auszeichnung / Formatierung) und der Präsentationslogik (kapselt die Logik, um Markup zu generieren, Elemente zu formatieren oder auf das Model zuzugreifen). Meistens bestand die Unterscheidung darin, PHP und HTML voneinander zu trennen, was das Ziel verfehlt , weil PHP so nur durch eine weitere, speziell angepasste Tag-Sprache ersetzt wird. Diese Strategie hatte nur eine gute Seite: Webdesigner wurden dadurch oft davon abgehalten, PHP selbst zu verwenden - zweifellos ein berechtigtes Anliegen. Es handelt sich um einen wesentlichen Bestandteil davon, wie PHP arbeitet. Der bedachte Einsatz als Teil eines Templating-Systems leitet uns nur in die richtige Richtung.
Zend_View
ist die Template-Rendering-Lösung des Zend Framework und formalisiert das
Konzept der Trennung von Präsentation und und Darstellungslogik. Es
handelt sich um eine objektorientierte Lösung, die ohne ein Tag-basiertes
System auskommt (wie es bei Smarty oder JavaServer Pages (JSP) in Java der
Fall ist). Templates, die von Zend_View
umgesetzt
werden, verwenden stattdessen direkt PHP. Auf einer höheren Ebene kapselt
Zend_View
die View-Schicht einer Anwendung, die
Benutzereingaben annimmt und eine Darstellung der Anwendung in Form von
HTML, XML, JSON etc. ausgibt, je nachdem welche Daten ihr übergeben wurden
oder welche sie selbstständig abgefragt hat. Genau genommen nimmt
Zend_View
keine Benutzereingaben an, da dies in der
Controller-Schicht gehandhabt wird - aufgrund der Funktionsweise von HTTP
gibt es hier eine undichte Stelle zwischen der View und den Controllern,
sobald man MVC für Webapplikationen anstatt für Desktopanwendungen
einsetzt. Überdies wirkt der Controller selbst bei der Präsentation mit.
Er beschränkt sich strikt auf die aktuelle Anfrage und regelt die
Erstellung sowie Ausgabe der Antwort.
Der wichtigste Aspekt
der Verwendung von Templates mit Zend_View
ist die
Objektorientierung. Sie können in den Templates alle Wertetypen verwenden:
Arrays, skalare Werte, Objekte und sogar PHP-Ressourcen. Es gibt kein
zwischengelagertes Tagsystem, das sich zwischen Sie und den gewaltigen
Funktionsumfang von PHP stellt. Teil dieses OOP-Ansatzes ist, dass alle
Templates innerhalb des Variablen-Geltungsbereichs der aktuellen
Zend_View
-Instanz ausgeführt werden. Zur Erklärung
sehen wir uns einmal das folgende Template an.
<html>
<head><title>My Page</title></head>
<body>
<?php echo strtolower($this->myName) ?>
</body>
</html>
Wir sehen, dass PHP in diesem Template direkt verwendet wird. Wir sehen auch eine Referenz auf $this - es repräsentiert die aktuelle Objekt-Instanz. Aber das Template ist kein Objekt! Woher kommt "this"?
Templates werden in der
geschützten Methode Zend_View::_run()
eingebunden. Der evaluierte Inhalt des Templates wird dann mittels
Output-Buffering abgefangen. Somit wird jeglicher Inhalt eines Templates
als Code innerhalb von Zend_View::_run()
behandelt, was heißt, dass Sie auf alle Klassenmethoden, Eigenschaften
(wie die öffentliche Eigenschaft Zend_View::$myName)
und andere Features zugreifen können, als ob Sie eine Methode in
Zend_View
selbst schreiben würden (was Sie effektiv
auch tun!).
Wie beeinflusst das
unser Denken über die View? Da alle Templates im Endeffekt (in der einen
oder anderen Form) Methodenaufrufe sind, sind Templates indirekt offen für
all die Vorteile, die sich aus der objektorientierten Programmierung
ergeben. Templates können verschachtelt, wiederholt, refaktoriert und
durch Komposition in andere Objekte (View-Helfer) gekapselt werden. Dies
wird offensichtlicher, sobald Sie die verschiedenen Konzepte kennenlernen,
die von Zend_View
angeboten werden: View-Helfer,
Placeholder und Layouts. Einleuchtend ist es auch durch unsere bisherigen
Diskussionen rund um die Rolle des Model. Wie ich in Kapitel 3: Das Model
erwähnt habe, können
Views mit Hilfe von View-Helfern direkt auf das Model zugreifen und es
auslesen, ohne dass man Controller-Code benötigt, der diese Interaktion
steuert.
Das Konzept des
Layouts wird durch Zend_Layout
implementiert.
Beim Layout handelt es sich um jenen Teil der Präsentation, der über
viele (oder alle) Seiten relativ statisch bleibt. In einem
Standard-HTML-Dokument trifft dies üblicherweise auf den Header, den
Footer, das Navigationsmenü und andere Bereiche der Seite zu, die auf
vielen Seiten ident sind. Da diese Teile in hohem Maße statisch sind,
wäre es sinnlos, sie in jedem Seiten-Template zu duplizieren. Die
Wartung wäre schlicht unmöglich, da für jede Änderung zahllose Templates
bearbeitet werden müssten. Aus diesem Grund können wir
Zend_Layout
verwenden, um eine kleine Zahl an
Layout-Templates zu erstellen, in welche die restliche Template-Ausgabe
eingefügt wird. So wird sichergestellt, dass sich die restlichen
Templates der Anwendung nicht mit dem gemeinsamen Layout herumschlagen
müssen - diese Aufgabe wird an Zend_Layout
abgegeben.
Ein einfaches Layout-Template könnte so aussehen:
<html>
<head>
<title>My Page</title>
</head>
<body>
<?php echo $this->layout()->content ?>
</body>
</html>
Wie in vorherigen
Kapiteln erwähnt implementiert der Controller das Rendern der View
standardmäßig, indem der ViewHelper-Action-Helfer verwendet wird.
Zend_Layout
registriert einen neuen Action-Helfer
und ein FrontController-Plugin,
Zend_Layout_Controller_Action_Helper_Layout
und
Zend_Layout_Controller_Plugin_Layout
, die es
erlauben, ein Layout zu rendern und von den Controllern aus auf die
Layout-Instanz zuzugreifen. Wir werden uns später noch genauer mit
Helfern und Plugins auseinandersetzen, aber vorerst müssen wir nur
wissen, dass sie Hook-Methoden implementieren können, die vom
FrontController automatisch aufgerufen werden, sobald bestimmte
Ereignisse aufgetreten sind. In diesem Fall interessiert uns eine
Methode postDispatch(), die aufgerufen wird, wenn eine Anfrage an den
Controller übergeben wird. Die Ausgabe der View, die zu dem Controller
(bzw. der Action) gehört, wird der öffentlichen Eigenschaft $content des
Layout-Templates zugewiesen. Den Inhalt von $content können wir an einer
beliebigen Stelle im Layout-Template ausgeben, in unserem Beispiel
zwischen den <body>
-Tags.
Zend_Layout
ermöglicht Ihnen über den Action-Helfer auch, in Controllern wahlweise
das Rendern des Layouts zu deaktivieren. Das ist dann wichtig, wenn Sie
etwas anderes als HTML (zum Beispiel JSON) ausgeben. JSON oder XML in
einem HTML-Layout auszugeben, ist vermutlich nicht in Ihrem Interesse.
Sie können zudem das Layout austauschen oder im Controller andere
Layout-Variablen setzen. Es handelt sich also um ein sehr flexibles
System.
Was Designpattern
anbelangt, implementiert Zend_Layout
eine nicht
allzu strikte Form der Two-Step-View
,
wie sie von Martin Fowler in Patterns Of Enterprise
Application Architecture (POEAA) beschrieben wird. Fowlers
Definition der Two-Step-View lautet wie folgt:
Turns domain data into HTML in two steps: first by forming some kind of logical page, then rendering the logical page into HTML.
In unserem Fall werden
die Domain-Daten unübersehbar direkt in HTML gerendert, bevor sie an
einer bestimmten Stelle in den HTML-Code des Layouts eingefügt werden.
Die Verwendung des Two-Step-View-Ansatzes für das Layout ergänzt den
Template-Kompositionsansatz von Zend_View
, wie er
unten bei der Verwendung von Partials beschrieben wird.
Ihnen sollte bewusst
sein, dass Zend_Layout
sich darum kümmert, wie
die Zusammensetzung und Zuweisung des Layouts funktioniert, das
Layout-Template selbst aber wieder von einer
Zend_View
-Instanz gerendert wird. Somit kann Ihr
Layout Gebrauch von den typischen
Zend_View
-Delikatessen wie Partials, Placeholdern
und View-Helfern machen. Das kann aber auch verwirrend sein und zu
suboptimalen Lösungen führen, wie ich jetzt gleich erwähnen
möchte.
Auf einen Aspekt bei
Zend_Layout
muss man nämlich aufpassen: wie im
Referenzhandbuch steht, können Layouts und benannte Antwortsegmente in
Verbindung mit dem ActionStack-Action-Helfer verwendet werden, um Seiten
aus mehreren Widgets zusammenzusetzen. Das würde bedeuten, dass jede
Action eine View rendert, die dann an der definierten Stelle in das
Template eingefügt wird. Ein extrem wichtiger Punkt muss hierbei
berücksichtigt werden - eine Aktion abzuarbeiten ist ein sehr teurer
Vorgang. Wie der Name ActionStack suggeriert, erzeugt dieser
Action-Helfer einen Stapel von Controller-Aktionen. Um diese
abzuarbeiten, muss jeweils beinahe das gesamte MVC-Framework in Bewegung
gesetzt werden. Das sollte unbedingt vermieden werden, falls es
irgendwie möglich ist. Es gibt fast nie einen Grund, den ActionStack zu
verwenden, da alles, was damit erreicht werden kann, genauso einfach mit
View-Helfern erreicht werden, wobei nur ein Bruchteil der
Performancekosten anfällt. Ryan Mauger hat dies genauer in seinem
Blogpost Why
the Zend Framework Actionstack is Evil
erklärt. Das soll nicht
heißen, dass Sie diesen Aspekt des Zend Framework nie verwenden sollten,
aber der Einsatz muss sehr gut begründet sein und Sie müssen einen
großen Performanznachteil in Kauf nehmen.
Partials haben einen sehr simplen Anwendungsfall. Sie stellen Fragmente eines höher gelagerten Templates dar, welche entweder einmal oder mehrfach an einer bestimmten Stelle eines Elterntemplates gerendet werden können. Partials können demnach auch "Template-Fragmente" genannt werden. Ein einfacher Anwendungsfall wäre die Indexseite unseres Blogs, die eine Anzahl von Artikeln darstellt. Das Markup jedes Artikels ist nahezu ident und unterscheidet sich nur durch den Text der Artikel. Wir könnten dieses gemeinsame Artikel-Markup leicht in ein einzelnes Partial auslagern, über eine Sammlung von Artikeln iterieren und jeden Artikel einzeln rendern. Wir reduzieren die Menge des Markups in dem Template, wir bieten einen einfachen Mechanismus an, über diesen Datensatz zu iterieren und wir können den Code wiederverwenden. Wir könnten das Markup für den Artikel auf der Indexseite, der Seite des jeweiligen Eintrags und an verschiedenen anderen Stellen verwenden. Indem wir ein Partial verwenden, können wir dieses wiederholte Markup isolieren und globale Änderungen an einer einzigen Stelle vornehmen.
Diese
Template-Fragmente bilden gemeinsam mit allen anderen
Markup-generierenden Mechanismen die Implementierung des Composite-View
-Designpatterns,
das in dem Buch Core J2EE Patterns definiert
wird.
Use composite views that are composed of multiple atomic subviews. Each component of the template may be included dynamically into the whole and the layout of the page may be managed independently of the content.
Eine Beobachtung, die ich bei unserer vorhergehenden Auseinandersetzung mit Layouts gemacht habe ist, dass die Two-Step-View selbst ein Subpattern der Composite-View ist. Fowler hatte in POEAA einige heute häufig verwendete View-bezogene Designpattern ausgelassen, zum Beispiel die Composite-View und View-Helfer. Diese wurden einige Zeit später im Buch Core J2EE Patterns erstmals formell definiert.
Zend_View
-Partials
werden durch die View-Helfer
Zend_View_Helper_Partial
und
Zend_View_Helper_PartialLoop
implementiert.
View-Helfer werden wir im nächsten Abschnitt behandeln. Der erste Helfer
rendert ein Partial-Template bei jedem Aufruf und kann entweder für ein
einmaliges Rendering oder in einer Schleife innerhalb eines
übergeordneten Templates verwendet werden. Die zweite Klasse unterstützt
das Durchlaufen einer Schleife intern, so dass die Schleife nicht im
übergeordneten Template eingebettet werden muss.
An ein Partial können
nicht beliebige Parameter übergeben werden; es muss sich um ein
assoziatives Array oder um ein Objekt handeln, das die Methode
toArray()
implementiert. Andernfalls wird das
Objekt mittels get_object_vars()
auf ein Array von
Objekt-Eigenschaften reduziert. Die Namen für die lokalen Eigenschaften
der Klasse werden aus den Schlüsseln des resultierenden assoziativen
Arrays bezogen und die Eigenschaften mit den entsprechenden Werten des
Arrays versehen.
Im folgenden Beispiel werden ein paar Daten an ein typisches Partial ohne Schleife übergeben.
<?php echo $this->partial('article.phtml', array(
'title'=>'Title',
'content'=>'My Content!'
)) ?>
Die Datei
article.phtml ist wie jedes andere Template, allerdings wird sie als
Partial aufgerufen. Die übergebenen Daten sind hier lokale
Klasseneigenschaften einer eigenen
Zend_View
-Instanz.
<article>
<h3><?php echo $this->escape($this->title) ?></h3>
<div class="content">
<?php echo $this->content ?>
</div
<article>
Wird der
Partial-Helfer für Schleifen verwendet, dann sollte der Parameter ein
Array von Einträgen sein, über die iteriert werden kann (jeder Eintrag
sollte dieselben Regeln wie ein normal Partial-Parameter erfüllen, siehe
oben) oder irgendein iterierbares Object, das valide Partial-Parameter
liefert. Die Klasse Zend_Db_Table_Rowset
zum
Beispiel stellt eine Sammlung von
Zend_Db_Table_Row
-Objekten dar, über die iteriert
werden kann. Jede Instanz von Zend_Db_Table_Row
implementiert eine Methode toArray()
. Ein
Rowset ist somit ein gültiger Partial-Loop-Parameter.
Wir könnten nun zum Beispiel unser Partial von vorher verwenden und den Partial-Loop-Helfer zum Einsatz bringen, indem wir ihm ein Array von Artikeln übergeben.
<?php echo $this->partialLoop('article.phtml', array(
array('title'=>'Title', 'content'=>'My Content!'),
array('title'=>'Title2', 'content'=>'More Content!')
)) ?>
Anstatt eines Arrays
kann man auch direkt Objekte übergeben und den Versuch umgehen, das
Object in ein Array zu konvertieren. Dies erreicht man, indem man einen
Objekt-Schlüssel setzt, bevor man die Methoden
Zend_View::partial()
oder
Zend_View::partialLoop()
samt Parametern
aufruft. Denken Sie daran, dass der Aufruf der primären Methode eines
View-Helfers ohne Parameter üblicherweise nur das View-Helfer-Objekt
zurückgibt, um die Verkettung von Methoden ("Method-Chaining") zu
ermöglichen. Zum Beispiel:
<?php echo $this->partialLoop()->setObjectKey('article')
->partialLoop($articleCollection) ?>
Im obigen Beispiel
werden der Klasse des Partial-Templates alle Objekte des iterierbaren
Collection
-Objekts (etwas, was wir vielleicht in
unserem Domain-Model implementieren werden) als öffentliche Eigenschaft
$article hinzugefügt. Innerhalb des Partials
könnten wir etwas wie dies hier schreiben:
<article>
<h3><?php echo $this->escape($this->article->title) ?></h3>
<div class="content">
<?php echo $this->article->content ?>
</div>
</article>
Da wir Objekte verwenden, müssen wir für die Referenzierungen des Partials mehr schreiben, als das bei einem Array der Fall wäre. Ein Array wäre einfacher zu handhaben, doch es gibt Situationen, in denen es Vorteile mit sich bringt, Objekte zu verwenden, zum Beispiel wenn das Domainobjekt Lazy-Loading für Referenzen auf andere Domainobjekte verwendet, wie es beim Autor unseres Artikels in Kapitel 9 der Fall ist.
Zwei Aspekte sind bei
Partials noch zu erwähnen. Erstens haben sie einen anderen
Geltungsbereich für ihre Variablen als die übergeordneten Templates. Das
bedeutet, dass sie nur auf jene Daten zugreifen können, die ihnen
übergeben werden, wenn die Methoden
Zend_View::partial()
oder
Zend_View::partialLoop()
aufgerufen werden (die
Aufrufe werden auf die Methoden
Zend_View_Helper_Partial::partial()
respektive
Zend_View_Helper_Partial::partialLoop()
umgelegt, da es sich dabei um die primären Methoden des jeweiligen
View-Helfers handelt). Sie besitzen keine Kenntnis von Daten, die ein
Controller der übergeordneten View zuweist (oder dem übergeordneten
Partial, falls es sich um Fragment handelt, das in einer zweiten Ebene
verschachtelt ist). Diese selbst auferlegte Einschränkung fördert eine
stärker objektorientierte Herangehensweise an den Einsatz von Templates
und verhindert, dass Daten frei über verschiedenste Templates und
Templatefragmente hinweg verfügbar sind (ein Problem, das bei
OOP-Designs mit globalen Variablen auftritt). So vorzugehen, ist einfach
guter Stil. Wir erlauben es globalen Variablen (hoffentlich) nicht, den
Variablengeltungsbereich unseres Objekts zu kontaminieren - warum
sollten wir es dann bei Templates tun? Alle Templates sollten
voneinander getrennt sein, um das Auftreten unerwünschter Abhängigkeiten
zu vermeiden, die das Testen, die Wartung und Anpassung des Codes
verkomplizieren würden.
Zuletzt können Sie Partials in andere Partials verschachteln. Falls nötig, können Sie einen ganzen Hierarchiebaum erstellen. Es kommt ganz darauf an, was Ihr Partial repräsentiert: ein ganz kleines Stück Markup oder einen wesentlichen Teil ihres Seiten-Templates bzw. -Layouts. Es liegt in der Natur des Templates-Spiels, eine einzelne View aus vielen einzelnen Teilen zusammenzusetzen. Wie diese Teile zusammengesetzt oder getrennt werden, um die Duplizierung von Markup zu verhindern und die Wiederverwendung der Bestandteile zu ermöglichen, hängt von Ihren Bedürfnissen ab.
Wenn Partials dazu da sind, Markup in wiederverwendbare Pakete zu schnüren, dann sind View-Helfer ("View-Helper") ihre Pendants auf der Seite der Präsentationslogik. View-Helfer werden verwendet, um eine wiederverwendbare Operation auf ein Template zu erstellen. Stellen Sie sich zum Beispiel vor, dass einem Partial ein Domainobjekt übergeben wurde, das einen Benutzerkommentar repräsentiert. Es enthält einen Namen, eine E-Mail-Adresse, eine URL, Text und ein Datum. Bei den ersten vier gibt es kein Problem, aber wie formatieren Sie das Datum für die Darstellung? Ihr Partial könnte PHP-Code enthalten, der das Datum (abhängig vom verwendeten Standard) parst und es wieder in der gewünschten lesbaren Form zusammensetzt. Aber Einträge haben Veröffentlichungsdaten und sogar Bearbeitungsdaten, dasselbe trifft auf Kommentare zu. Wollen Sie diesen PHP-Code überall wieder einbauen?
Diese Lösung wäre
eindeutig nicht sehr gut wartbar. Wenn sich etwas ändert und Sie die
Parsinglogik rekonfigurieren müssen, müssen Sie viele Templates
bearbeiten. Es wäre weitaus einfacher, diese PHP-Funktionalität in einen
View-Helfer zu kapseln, eine wiederverwendbare Klasse, die jederzeit von
jedem Templates aus aufgerufen werden kann. Sie könnten den selbst
geschriebenen Helfer in einer Klasse
ZFExt_View_Helper_FormatDate
implementieren, der
in der Methode formatDate()
ein beliebiges
Datum entgegennimmt und es derart formatiert zurückgibt, wie es ihm in
Form eines Musters vorgegeben wurde, zum Beispiel "YYYY-MM". Dadurch
erhält man eine einfach wiederverwendbare Klasse, die von Unit-Testing
profitieren kann und die ebenso zur Wiederverwendung in andere Projekte
portiert werden kann.
Wie ich bereits in
Kapitel 3: Das Model
erwähnt habe,
können View-Helfer auch verwendet werden, um ein Model zu kapseln, zum
Beispiel die Implementierung eines simplen Data-Mappers wie im letzten
Kapitel. Ihr Helfer kann nun an das Domain-Model eine Anfrage stellen,
um von ihm Daten zu erhalten, sie dann zu formatieren, Auszeichnungen
hinzuzufügen und das Endergebnis zurückzugeben, damit es direkt an einer
bestimmten Stelle in einem Template eingefügt werden kann. Wichtig bei
diesem Ansatz ist, dass Sie immer daran denken, dass ein View-Helfer
niemals das Domain-Model verändern sollte, mit dem es interagiert - das
ist Aufgabe des Controllers oder von wem auch immer, der die Eingaben
des Benutzers handhabt. View-Helfer sind daher also ein sehr nützliches
Feature. Sie können spezielle Formatierungen, die Generierung von Menüs,
das Stellen von Anfragen an Models, die Dekoration von Elementen mit
Markup und vieles mehr auf View-Helfer abladen.
View-Helfer werden im
Buch Core J2EE Patterns als eigenes Pattern
definiert, das View-Helper
-Designpattern.
Alle View-Helfer
folgen einem ähnlichen Design. Sie implementieren eine Methode, deren
Name dem Klassennamen entspricht.
ZFExt_View_Helper_FormatDate
definiert
dementsprechend die Methode
ZFExt_View_Helper_FormatDate::formatDate()
.
Diese "primäre" Methode nimmt üblicherweise Parameter entgegen, der
Helfer verarbeitet diese und retourniert die Ergebnisse. Handelt es sich
um komplexere Helfer mit vielen öffentlichen Methoden, dann gibt diese
primäre Methode möglicherweise auch nur das Objekt selbst zurück. Da es
sich bei ZFExt_View_Helper_FormatDate
um einen
selbstgeschriebenen View-Helfer handelt, müssen Sie
Zend_View
auch mitteilen, wo er zu finden ist und
wie der Klassen-Namespace lautet.
Sehen wir uns ein
einfaches Beispiel an. Aufgrund einer Analyse der Ladezeiten unserer
Website möchten wir dafür sorgen, dass JavaScript- und CSS-Dateien durch
die Clients der Besucher für eine längere Zeit gecacht werden können.
Dieses Thema wird durch die gut dokumentierten Yahoo
Performance Best Practices
im Abschnitt Add
an Expires or a Cache-Control Header
behandelt. Aufgrund dessen
ziehen wir los und konfigurieren Apache so, dass er weit in der Zukunft
liegende Expires-Header setzt, wenn er statische Dateien wie CSS oder
JavaScript ausliefert. Nun stehen wir aber vor einem anderen Problem -
wenn der Client diese Dateien für immer cacht, wie können wir dann
Änderungen und Aktualisierungen ausliefern? Eine verbreitete Strategie
ist, einen URI-Querystring an den URI der statischen Dateien anzuhängen,
welcher in unserem Markup aktualisiert wird, wenn sich die referenzierte
Datei ändert. Die zwischengespeicherte Resource verwendet den originalen
URI (Sie können URIs mit Querystrings cachen, wenn ein explizierter
Expires-Header gesendet wird), aber wenn sich der URI ändert, dann wird
der Klient die Datei vom neuen URI laden, da sich der Querystring
geändert hat - es handelt sich für den Client also um eine neue
Resource.
Daraus würde sich ein
URI in dieser Art ergeben:
http://zfblog.tld/css/style.css?1185131970
. Der finale
Querystring ist - unschwer zu erkennen - ein Timestamp, der das
Änderungsdatum der Datei repräsentiert. Sie könnten genauso einfach eine
Versionsnummer verwenden. Schreiben wir uns nun einen View-Helfer, der
diese Querystrings automatisch hinzufügt, damit unsere Updates ihre URIs
ohne manuelle Eingriffe ändern. Ihr maßgeschneiderter Helfer erweitert
Zend_View_Helper_Abstract
, der einige
Standardfunktionalitäten anbietet, falls sie benötigt werden (was hier
nicht der Fall ist).
<?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;
}
}
Der neue View-Helfer ist kurz und knackig gehalten. Er nimmt den relativen Pfad zu einer Datei auf unserem Server entgegen, wie er normalerweise im Markup eingebunden werden würde, fragt das Datum der letzten Bearbeitung der Datei ab und fügt das Datum als Timestamp an den Pfad an. Der resultierende Pfad kann dann an alle weiteren Helfer weitergereicht (z.B. an die HeadLink- oder HeadScript-View-Helfer) oder direkt in das relevante Markup ausgegeben werden. Hier ein Template-Fragment, das unseren neuen Helfer verwendet:
<link rel="stylesheet" href="<?php echo $this->appendModifiedDate('/css/style.css') ?>" type="text/css" media="screen" />
Neben selbst
geschriebenen Helfern bietet das Zend Framework einige
Standard-View-Helfer für häufige Aufgaben wie die Generierung von Markup
an (z.B. für Form- und Kopfelemente). Wir werden bald einige von ihnen
verwenden, und sie verwenden ein weiteres Feature von
Zend_View
namens Placeholders.
Platzhalter
("Placeholder") befassen sich mit einem speziellen Bedürfnis, das bei
der Verwendung von Templates mit Zend Framework auftritt. Sie erlauben
es Templates, ungeachtet ihres Variablengeltungsbereiches auf eine
gemeinsame Registry für Daten zuzugreifen. Das kommt dem
Registry-Pattern sehr nahe, welches in Zend Framework häufig mittels
Zend_Registry
implementiert wird, doch dieser
Anwendungsfall ist spezifischer. Stellen Sie sich ein Szenario vor, in
dem Sie feststellen, dass eine Webseite ein bestimmtes Stück
JavaScript-Code benötigt, das so früh wie möglich geladen werden soll.
Ihr Layout enthält diesen Code nicht, da er nicht für jede Seite
gebraucht wird. Platzhalter können dieses Dilemma lösen, indem sie es
Templates und Partials ermöglichen, einem Platzhalter Werte
hinzuzufügen, einem Schlüssel in dieser gemeinsamen Registry. When ein
übergeordnetes Template oder Layout gerendert wird, kann es dann die
Daten dieses Platzhalters an einem beliebigen Punkt rendern. Im Falle
von JavaScript wird dies durch einen spezialisierten Platzhalter
bewerkstelligt, der von
Zend_View_Helper_HeadScript
implementiert wird.
Mit ihm kann man von jedem Subtemplate aus beliebiges Javascript am Ende
oder am Beginn anfügen, überschreiben oder mit einem bestimmten
Index-Wert versehen, bevor es letztendlich im Layout gerendert
wird.
In Zend Framework gibt es eine Auswahl an spezialisierten Platzhaltern wie HeadLink, HeadMeta etc. Daneben können Sie aber auch den Basis-Placeholder-View-Helfer für beliebige Daten verwenden, die über verschiedene Templates hinweg kommuniziert werden müssen. Sehen wir uns ein Beispiel an, in dem ein Layout die Ausgabe eines Platzhalters einfügen kann. Das Layout ermöglicht es somit jedem Template, den Text in der Hauptüberschrift zu ändern.
<html>
<head>
<title>My Page</title>
</head>
<body>
<h1><?php echo $this->placeholder('pageHeader') ?></h1>
<?php echo $this->layout()->content ?>
</body>
</html>
Wir können diesen Wert nun von jedem beliebigen Template (inklusive Partials) aus setzen.
<?php $this->placeholder('pageHeader')->set('About') ?>
<p>My name is Joe. Joe Bloggs.</p>
Im Gegensatz zu den spezifischeren Placeholder-Implementierungen kann der allgemeine Placeholder-Helfer für alle Arten von Werten verwendet werden. Haben Sie einen bestimmten Wert, der entsprechend bestimmter Regeln formatiert werden soll, können Sie eine Subklasse vom allgemeinen Helfer ableiten, um die Formatierung vorzunehmen, wenn die Daten in ein Template ausgegeben werden. Als Beispiel implementieren wir einen eigenen View-Helfer, der die Überschrift der Seite für HTML 5 formatiert.
<?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>";
}
}
Es handelt sich um ein
sehr einfaches Beispiel, das lediglich einen konkreten Wrapper für den
Placeholder-Container mit dem Namen "pageHeader" darstellt. Zudem
schließt es den Wert des Seitentitels in <h1>
-Tags
ein. Hier ist unser Layout und unser Seitentemplate, das den neuen
Platzhalter verwendet.
<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>
Sie haben vielleicht
bemerkt, dass ich in diesem Buch bevorzugt die Langform der PHP-Tags
verwende, also <?php (echo) ... ?>
anstatt der
Kurztags <?(=) ... ?>
. Bei der Kurzform (im
Englischen als short tags bezeichnet) handelt es sich um eine optionale
PHP-Einstellung. Viele Server haben die Kurzform zwar aktiviert, um die
Kompatibilität zu Anwendungen zu erhöhen, die diese Form verwenden, doch
ebenso viele Server haben die Option deaktiviert, da dies auch in der
PHP-Konfigurationsdatei php.ini.recommended der Fall ist. Darüber hinaus
wissen wir, dass PHP 6 die Kurzform voraussichtlich "missbilligen" wird,
um einen Konflikt mit der XML-Deklaration zu lösen, die in ähnlicher
Form <?xml ... ?>
verwendet. XML-Deklarationen müssen
herkömmlich mittels "echo" ausgegeben werden, wenn das XML aus einem
Template mit short tags generiert wird. Ich persönlich finde es
ärgerlich, dass diese Notation mit PHP6 vermutlich fallen gelassen wird.
<?php echo ... ?>
öfter als einige wenige Male zu
schreiben, ist für mich einfach nur nervig; andererseits handelt es sich
aber eben nur um eine optionale Einstellung, die deswegen in zur
Verteilung bestimmten Anwendungen ohnehin nicht verwendet werden sollte.
Auf jeden Fall ist es besser, mit der Variante zu arbeiten, die auf
jeden Fall funktioniert als zu riskieren, dass der Code später nicht
mehr läuft.
Zend Framework
ermöglicht die Nutzung der Kurzform unabhängig von der PHP-Konfiguration
durch den Einsatz eines Streamwrappers. Falls die Option zur Verwendung
der Kurzform in Ihrer Konfigurationsdatei php.ini deaktiviert ist (also
das Flag short_open_tag), ist dieser Wrapper aktiviert und Sie können
die Option useStreamWrapper auf true setzen. Das machen Sie, indem Sie
den Schlüssel useStreamWrapper
in der Datei application.ini
verwenden oder indem Sie in Ihrer Bootstrap die Methode
Zend_View::setUseStreamWrapper()
aufrufen. Die
Verwendung des Streamwrappers wird sich vermutlich auf die Performance
auswirken; falls Sie wirklich auf Kurztags angewiesen sind, ist es
besser, sie einfach in Ihrer PHP-Konfiguration zu aktivieren.
Die neue
Blog-Applikation einzurichten, ist nicht furchtbar schwer. Kopieren Sie
einfach das "Hallo Welt"-Beispiel in das Projektverzeichnis der
Blog-Anwendung, in dem Sie die erste Ausbaustufe des Domain-Models unserer
Anwendung abgelegt haben. Damit haben wir eine ausgezeichnete
Ausgangsbasis. Sie können eine neue lokale Domain und einen virtuellen
Host speziell für den Blog anlegen, indem Sie den Anweisungen im Anhang
zum Aufsetzen von virtuellen Hosts in Apache folgen. Für dieses Buch werde
ich die Domain http://zfblog.tld
verwenden.
Weitere Änderungen sind nicht nötig. Im Rest dieses Kapitels wird genau beschrieben, wie wir weiter vorgehen.
Die Indexseite unseres
Blogs wird (vorhersehbarerweise) eine Liste von Einträgen darstellen.
Fangen wir damit an, dass wir uns die ersten HTML-Tags von HTML 5 ansehen.
Später werden wir uns um das Design und weitere Elemente sowie
Informationen auf der Seite kümmern. Zunächst bearbeiten wir
/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 verwendet einen
vereinfachten Doctype, der im Prinzip nur aussagt, dass es sich um HTML
handelt. Die Version oder andere Informationen müssen nicht angegeben
werden. Da wir wissen, dass wir den Doctype setzen können, der von
Zend_View
und den davon abhängigen Komponenten
verwendet wird, können wir diesen Code in unserem Template mittels des
View-Helfers Zend_View_Helper_Doctype
ausgeben. Die
Unterstützung von Zend Framework für HTML 5 geht nur so weit, dass der
Doctype alle abhängigen View-Helfer anweist, Tags in Form von HTML 4 zu
verwenden. Freilich kann HTML 5 auch in ähnlicher Weise wie XHTML
geschrieben werden: Sie können also kleingeschriebene Tag- und
Attributnamen verwenden, Attributwerte in Anführungszeichen einschließen,
alle Tags schließen etc. Ich bevorzuge diese Form, HTML 5 zu schreiben, da
es sich sauberer anfühlt und ich mich nicht zu weit von dem mir bekannten
XHTML-Stil entfernen muss. Zend Framework wirft hierbei ein kleines
Problem auf - es unterscheidet nicht zwischen HTML 5 und XHTML 5 (HTML 5,
geschrieben unter Berücksichtigung der Regeln für wohlgeformtes XML). Wir
werden das Problem beseitigen, indem wir einen eigenen View-Helfer
schreiben, der die Unterstützung für einen XHTML-5-Doctype
hinzufügt.
Wie immer bei HTML/XHTML
ist das Root-Element <html>
. Unmittelbar danach folgt
ein <head>
-Element, das alle von uns benötigte
<link>
-, <meta>
-,
<title>
- oder <style>
-Elemente
enthält. Sie können in einem der üblichen Stile geschrieben werden, doch
wie ich erklärt habe, bevorzuge ich den XHTML-Stil. HTML 5 bringt hier ein
neues Feature mit sich - ein neues Attribut für das Element
<meta>
namens "charset". Mit diesem neuen Attribut soll
man dem Parser leichter mitteilen können, welche Zeichenkodierung das
Dokument verwendet, weswegen es sich um einen adäquaten Ersatz für das
häufig verwendete <meta>
-Element Content-Type handelt.
Auch dieses Attribut wird von Zend Framework noch nicht unterstützt,
weswegen wir einen zweiten eigenen View-Helfer hinzufügen werden, der es
implementiert.
Ihnen wird auffallen,
dass ich diese Tags als View-Helfer implementiere, obwohl wir sie
stattdessen direkt in unsere Templates schreiben könnten. Dabei handelt es
sich abermals um guten Stil, da die anderen Templates den Inhalt von
<head>
durch die Verwendung von View-Helfern
modifizieren können, indem Stylesheets, Skripte und Meta-Informationen
hinten und vorne angefügt oder gar überschrieben werden. Den Vorteil
werden Sie in jenen Situationen erkennen, in denen eine bestimmte Seite
zusätzliches Styling oder andere JavaScript-Dateien benötigt, die nicht
für alle Seiten erforderlich sind.
Ich habe auch zwei CSS-Stylesheets aus dem "Yahoo User Interface Framework" hinzugefügt. Diese werden direkt vom Yahoo-Server geladen. Daher brauchen wir lokal nichts abzuspeichern. Die erste Datei ist eine minifizierte Version der drei zusammengelegten Stylesheets "reset", "fonts" und "grid". Die ersten zwei stellen sicher, dass alle Browser die gleiche Ausgangslage haben, indem Unterschiede in der standardmäßigen Darstellung von Elementen ausradiert werden. Somit muss man sich nicht mit den Inkonsistenzen der Browser bei der Darstellung der Standardelemente herumschlagen. Das Stylesheet "grid" werden wir später verwenden, um uns das Erstellen eines Spaltenlayouts zu erleichtern. Das zweite eingebundene Stylesheet "base" stellt eine hervorragende Basis für das Design der wichtigsten Elemente her. Somit sollte ich - außer dem, was für mein bevorzugtes Blogdesign nötig ist - ziemlich wenig CSS zu schreiben haben.
Zum Schluss haben wir
eine JavaScript-Datei des Projekts html5shiv
eingebunden. Dabei handelt es sich um ein relativ simples JavaScript, das
dafür sorgt, dass Internet Explorer die neuen HTML-5-Elemente erkennt.
Ansonsten könnten wir ihr Aussehen nicht verändern. Nachdem dieser Code
nur für Internext Explorer benötigt wird, haben wir den Aufruf in
Conditionals gepackt, die auf alle Versionen von Internet Explorer (aber
auf keine anderen Browser) zutreffen.
Fügen wir nun einen Kopfabschnitt hinzu, der den Seitentitel und den oberen Navigationsbereich repräsentiert.
<!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>
Wir können bereits
einige der neuen Elemente von HTML 5 sehen. Unser Kopfabschnitt ist in
einen Tag <header> gekleidet, der unsere gewohnte Überschrift
<h1>
und ein weiteres neues Mitglied umfasst, nämlich
das Element <nav>
, das unser oberes Navigationsmenü
umschließt. Das beste daran neben der semantischen Bedeutung ist
vermutlich, dass wir endlich die Unmenge von
<div>
-Elementen vergessen können, die das
Klassenattribut verwenden müssen um uns zu verdeutlichen, was sie
repräsentieren.
HTML 5 liefert für diese neuen Elemente die folgende Definition:
Das Element "header" repräsentiert eine Gruppe von Einführungs- oder Navigationshilfen. Ein header-Element ist üblicherweise dafür vorgesehen, die Überschrift eines Abschnitts (ein h1- bis h6-Element oder ein hgroup-Element) zu enthalten; dies ist aber nicht zwingend erforderlich. Das header-Element kann ebenso dazu verwendet werden, ein Inhaltsverzeichnis, ein Suchformular oder relevante Logos eines Abschnitts zusammenzufassen.
Das Element "nav" repräsentiert einen Seitenabschnitt, welcher Verknüpfungen zu anderen Seiten oder zu Abschnitten innerhalb derselben Seite herstellt: einen Abschnitt mit Navigationslinks. Nicht alle Linkgruppen auf einer Seite müssen in einem nav-Element zusammengefasst werden — nur bei Abschnitten mit größeren Navigationsblöcken ist ein nav-Element angemessen. Besonders in Fußzeilen ist eine Liste von Links zu verschiedenen bedeutenden Teilen einer Website üblich, doch in diesen Fällen ist es passender, das Element footer zu verwenden. Ein nav-Element ist für diese Links nicht notwendig.
Als nächstes benötigen wir den "body" unseres Markups, der zeigt, wo wir unsere Blogeinträge rendern würden, und abschließend den footer-Bereich, der das Copyright bzw. ähnliche Hinweise enthält.
<!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>
Nun, das ist neu! Wie
nicht zu übersehen ist, hat HTML 5 noch weitere neue Elemente mit sich
gebracht. Viele kommen an Stellen zum Einsatz, an denen wir üblicherweise
<div>
-Tags mit Klassenattributen setzen würden, die den
Zweck des <div>
erläutern. Auf einer simplen Seite
würden Sie vielleicht erwarten, den Inhalt einfach in einen
<article>
-Tag einzubetten, da der Inhalt einen
einzelnen Eintrag darstellt. Da wir jedoch wissen, dass in unserem Blog
jeder Eintrag auf der Seite einen selbstständigen Artikel darstellt,
verwenden wir mehrere <article>
-Elemente und fassen sie
in einem einzelnen <section>
-Element zusammen (falls
die Seite weiter unterteilt ist, können wir auch mehrere
<section>
-Elemente verwenden und sie im Gegensatz zu
Docbook XML verschachteln). Jeder Artikel hat wiederum einen Kopf- und
einen Fußbereich, für welche wir die entsprechenden HTML-5-Tags verwenden.
Zuletzt werden alle Detailinformationen zum Autor in
<address>
-Tags eingebettet. Es mag etwas verwirrend
sein, doch der Tag <address>
bezieht sich nicht auf
eine physische Adresse - er bezieht sich mehr auf andere Details einer
Person wie ihren Namen, die Website, E-Mail, Beschreibung etc.
Werfen wir einen Blick auf die Definitionen der HTML-5-Spezifikation für diese neuen Elemente in HTML 5.
Das Element "section" repräsentiert einen allgemeinen Dokument- oder Anwendungsabschnitt. Ein Abschnitt ist in diesem Zusammenhang eine thematische Gruppierung von Inhalten, die typischerweise einen Kopfbereich und eventuell einen Fußbereich aufweist.
Das Element "article" repräsentiert einen Abschnitt einer Seite, der sich aus Elementen zusammensetzt, die miteinander einen unabhängigen Teil eines Dokuments, einer Seite, Anwendung oder Site formen. Es kann sich um ein Posting in einem Forum, einen Artikel in einem Magazin oder einer Zeitung, einen Weblogeintrag, einen Benutzerkommentar, ein interaktives Widget oder jegliche andere Art von unabhängigem Inhalt handeln. Ein article-Element ist insofern "unabhängig", als es für sich allein stehen und dabei Sinn ergeben könnte, zum Beispiel auf einer Seite mit syndizierten Inhalten oder als austauschbare Komponente auf einer vom Benutzer konfigurierbaren Portalseite.
Das Element "footer" repräsentiert einen Fußbereich für den nächstgelegenen übergeordneten Inhaltsabschnitt. Ein Fußbereich enthält typischerweise Informationen über den Abschnitt wie zum Beispiel den Autor, Verknüpfungen zu verwandten Dokumenten, Urheberrechtsinformationen und ähnliches. Kontaktinformationen sollten - möglicherweise selbst in einem footer-Element - in einem address-Element untergebracht werden.
Das Element "address" repräsentiert die Kontaktinformation für das nächstgelegene übergeordnete article- oder body-Element. Falls es sich dabei um das body-Element handelt, dann gilt die Kontaktinformation für das gesamte Dokument. Das Element address darf nicht verwendet werden, um beliebige Adressen zu repräsentieren (z.B. Postanschriften), falls es sich dabei nicht tatsächlich um relevante Kontaktinformationen handelt.
Das Element "time" repräsentiert entweder die Zeit auf einer 24-Stunden-Uhr oder ein präzises Datum im "Fortlaufenden Gregorianischen Kalender", optional mit einer Uhrzeit und einer Information über die Verschiebung durch die Zeitzone. Das Ziel dieses Elements ist es, Daten und Uhrzeiten in maschinenlesbarer Form bereitzustellen, damit Useragenten die Möglichkeit anbieten können, diese Termine zum Kalender des Benutzers hinzuzufügen. So können zum Beispiel Geburtstagserinnerungen oder Termine hinzugefügt.
Es sei kurz angemerkt,
dass das neue Element <time>
nicht ohne Probleme
auskommt. Es ist strikt an den Gregorianischen Kalender gebunden, womit
nicht-Gregorianische Daten vor seiner Einführung nicht repräsentiert
werden können. Da <time>
das Element
<abbr>
im Mikroformat hCalendar ersetzen soll, handelt
es sich eigentlich um einen Fehlschlag, weil historische Daten so nicht
repräsentiert werden können. Hoffentlich wird die finale Spezifikation von
HTML 5 das korrigieren.
Starten Sie Ihren
Browser und navigieren Sie zu http://zfblog.tld
. Sie sollten das
neue Template sehen, das mit dem Standarddesign von YUI CSS dargestellt
wird.
Nachdem wir nun ein erstes Design haben, müssen wir identifizieren, welche Teile dieses Templates statisch sind, sich also von Seite zu Seite kaum ändern werden. Ein kurzer Bliick genügt um zu sehen, dass wir auf unserer Indexseite nur bei den Einträgen Änderungen erwarten. Die Anzahl der anzuzeigenden Beiträge kann variieren und der Inhalt der Artikel wird regelmäßig aktualisiert. Bewaffnet mit dieser Erkenntnis können wir unser Index-Template auf die dynamischen Teile reduzieren - den Rest werden wir gleich in ein Layout auslagern.
Hier ist das
überarbeitete Template
/application/views/scripts/index/index.phtml
.
<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>
Verlegen wir das
übriggebliebene Markup in ein neues Template (bezeichnet als Layout) in
/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>
Sie werden bemerkt
haben, dass wir nun eine Methode layout()
von
Zend_View
referenzieren. Wie wir bei der
Auseinandersetzung mit den View-Helfern angemerkt haben, reflektiert der
Klassenname eines View-Helfers den Namen seiner primären Methode, welcher
typischerweise (zumindest) dazu verwendet wird, eine Instanz des
View-Helfer-Objekts zu erhalten. In diesem Fall holen wir uns eine Instanz
von Zend_View_Helper_Layout
und geben aus, was auch
immer in seiner öffentlichen Eigenschaft $content
enthalten ist.
Um zu verstehen, wie
unser Index-Template in diese Eigenschaft gelangt, müssen wir verstehen,
dass das Rendern standardmäßig von den Controllern mittels des
Action-Helfers ViewRenderer bewerkstelligt wird. Dieser Helfer rendert das
Template für den aktuellen Controller und die aktuelle Action in die
Antwort (auf die Anfrage) hinein. Zend_Layout
kann
dann den Body der gerenderten Antwort abfangen und ihn in das
Layout-Template einfügen. Dann wiederum wird das gesamte Layout gerendert,
um schließlich die gesamte Seite zu produzieren.
Sie sollten im
Hinterkopf behalten, dass Layouts wie alle Templates innerhalb des
Variablengeltungsbereiches eines Zend_View
-Objekts
ausgeführt werden: alle View-Helfer und Methoden von
Zend_View
sind also weiterhin von Ihren Layouts aus
erreichbar.
Um
Zend_Layout
zu aktivieren, müssen wir es für die
Verwendung konfigurieren. Normalerweise muss man dafür die statische
Methode Zend_Layout::startMvc()
aufrufen und die
Instanz konfigurieren. Da wir jedoch
Zend_Application
für unseren Bootstraping-Prozess
verwenden, müssen wir nur einige Konfigurationswerte für die
Layout-Ressource in /config/application.ini
ablegen.
Die zwei benötigten Optionen (die wir am Ende des Abschnitts "Standard
Resource Options" hinzufügen) setzen den Standardnamen des
Layout-Templates ohne die Dateiendung .phtml sowie den Pfad, in dem die
Layout-Templates platziert werden können.
[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
Kehren Sie zu Ihrem
Browser zurück und navigieren Sie zu http://zfblog.tld
. Sie sollten exakt
dieselbe Seite wie zuvor ohne Unterschiede siehen. Unser Index-Template
wird nun in das neue Layout gerendert.
Um das Bild abzurunden,
nutzen wir die Gelegenheit und bearbeiten auch gleich das zweite
Seitentemplate in unserer Anwendung unter
/application/views/scripts/error/error.phtml
. Sie
werden feststellen, dass der Inhalt nicht in
<article>
-Tags eingebettet ist, da es sich um eine
allgemeine Nachricht und nicht um ein alleinstehendes Artikeldokument
handelt. Ich verwende nun auch eine Überschrift der zweiten Ebene
<h2>
, die für den <article>
von
einem Element <header>
eingeschlossen wird.
<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>
Ziehen Sie los und
starten Sie einen Testlauf, indem Sie eine ungültige URL wie http://zfblog.tld/foo/bar
aufrufen. Denken Sie daran, dass Sie das Werfen von Ausnahmen kurzfristig
in application.ini deaktiveren müssen, um die Fehlerseite zu sehen.
Unser Layout ist nun
eingerichtet, aber wir können noch weiter gehen und es den Seitentemplates
(oder gar Partials) ermöglichen, Teile des Layouts je nach Bedarf zu
modifizieren, besonders was die Stylesheets, den Titel der Seite,
JavaScript und die Meta-Tags betrifft. Zend Framework ermöglicht solche
Änderungen durch die Verwendung von Platzhaltern, genauer gesagt durch
einige spezielle Implementierungen wie
Zend_View_Helper_HeadTitle
,
Zend_View_Helper_Doctype
, etc. Wie bereits erwähnt
erlauben Platzhalter es den Templates, Einträge innerhalb eines
eigenständigen Datencontainers hinzuzufügen, zu entfernen oder gar zu
ersetzen. Der Inhalt wird dann in seiner finalen Form in die äußere
Schicht der Template-Hierarchie eingefügt.
Lassen Sie uns anfangen,
indem wir unser Layout in
/application/views/layouts/default.phtml
modifizieren, damit es verschiedene dieser Placeholder-View-Helfer
verwendet.
<?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
und Zend_View_Helper_HeadMeta
werden beide ohne
Änderung ausgegeben, außer dass wir - wo angebracht - Informationen über
die Einrückung des Codes und Zeilenumbrüche hinzufügen. Dadurch sorgen wir
nur dafür, dass der Quelltext leichter zu lesen ist. Anstatt Werte für
diese zwei Helfer innerhalb des Layouts zu setzen, werden wir sie von der
Bootstrap aus konfigurieren, indem wir Optionen aus der
application.ini
verwenden.
Zend_View_Helper_HeadTitle
wird aufgerufen, um den Titel auf "Pádraic Brady's Blog" zu setzen. Sie
können weitere Teile hinzufügen, die innerhalb des Elements
<title>
ausgegeben werden - getrennt durch ein
beliebiges Zeichen, das Sie mittels
Zend_View_Helper_HeadTitle::setSeparator()
bestimmen. Sie können der primären Methode
headTitle()
ein optionales zweites Argument mit
dem Wert "APPEND" (die Standardeinstellung) übergeben, um einen String
anzufügen, oder "PREPEND" verwenden, um den neuen Teil des Seitentitels
vorne anzufügen.
Zend_View_Helper_HeadLink
kann verwendet werden, um Links zu Stylesheets hinzuzufügen,
voranzustellen oder zu ersetzen. Genauso können andere allgemeine Links
hinzugefügt werden, indem man ein Array von Attributen und Werten an
Zend_View_Helper_HeadLink::headLink()
übergibt.
Alternative Links setzen Sie, indem Sie appendAlternate() und die
gleichgelagerten Methoden
(prepend/set/offsetSetAlternate()
) verwendet.
Fügt man Stylesheets hinzu, dann kann man mit optionalen Parametern die
Attribute "media" und "type" setzen sowie in einem Array Konditionen
festlegen (so wie wir es in der nächsten Zeile verwenden werden, um eine
JavaScript-Datei nur im Internet Explorer einzubinden). Wenn Sie einen
Block von Styles hinzufügen müssen, können Sie das stattdessen durch die
Verwendung von Zend_View_Helper_Style
erreichen.
Zend_View_Helper_Script
schließlich erlaubt das
Einbinden von Skripten und JavaScript-Dateien.
Sie können bei diesen
Placeholder-Implementierungen einige Gemeinsamkeiten beobachten. Sie haben
Methoden, um Einträge zu setzen, anzufügen und voranzustellen. Viele
bieten auch offsetSet*()
-Methoden an, um Einträge
an einer bestimmten Stelle im Einträge-Array hinzuzufügen.
Ausgelassen habe ich
bisher das Markup für den charset-<meta>
-Tag. Dieser
kann bis jetzt nicht durch das Zend Framework gerendert werden, doch wir
werden dieses Problem im nächsten Abschnitt beseitigen. Zuerst nehmen wir
aber einige Änderungen an unserer Methode
ZFExt_Bootstrap::_initView()
vor, um die zu
rendernden Meta-Tags und den Doctype zu konfigurieren.
<?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;
}
// ...
}
Nun können wir
application.ini
modifizieren, um ein letztes Stück
Meta-Information hinzuzufügen, nämlich die Sprache der Seite. Es handelt
sich dabei nicht um einen Standard-Konfigurationswert für
Zend_View
, sondern nur um einen Standardwert, den
wir in unserer Bootstrap-Klasse einlesen, wenn wir die Meta-Informationen
für die Seite setzen.
[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
Momentan geht die Unterstützung von HTML 5 durch Zend Framework nicht über den HTML-Doctype hinaus. Der verwendete Doctype ist wichtig - nicht nur weil er ganz am Beginn unseres Templates steht, sondern weil er zudem für mehrere View-Helfer bestimmt, wie die Tags konstruiert werden. Wie ich bereits erwähnt habe, bevorzuge ich bei HTML 5 den XHTML-Stil mit geschlossenen Tags, kleingeschriebenen Attributnamen und in Anführungszeichen eingeschlossenen Attributwerten (neben anderen Facetten). HTML 5 selbst verhält sich gegenüber diesen Details relativ indifferent, doch als Alternative zur typischen HTML-Variante gibt es auch eine Definition für eine XML-Serialisierung.
Wenn wir unseren Doctype
auf "HTML 5" setzen, wird es problematisch wenn wir XHTML testen wollen,
da die View-Helfer kontrollieren, ob der Doctype-Name mit "XHTML" beginnt.
HTML5 (unser aktueller Doctype-Optionswert) tut das nicht. Damit wir das
Problem überwinden, sollten wir eine eigene Implementierung des Helfers
Zend_View_Helper_Doctype
hinzufügen (eine einfache
Subklasse ist alles, was wir benötigen), um die Unterstützung für eine
Option namens "XHTML5" hinzuzufügen, die dafür sorgt, dass XHTML-Regeln
auf die Ausgabe der View-Helfer angewandt werden.
Wir werden unseren
View-Helfer in /library/ZFExt/View/Helper/Doctype.php
abspeichern. Um unserer Anwendung bzw. Zend_View
mitzuteilen, wo es unsere maßgeschneiderten Helfer findet, können wir neue
Optionen in der Sektion "Standard Resource Options" unserer
application.ini definieren. Eintrag werden wir dort einen neuen Pfad, in
dem View-Helfer gefunden werden können sowie den Klassen-Präfix, den sie
verwenden. Danach brauchen wir keine weiteren Änderungen durchzuführen -
angepasste View-Helfer, die den Namen eines Zend-Framework-View-Helfers
widerspiegeln (das heißt: sie enden mit demselben Ausdruck in
CamelCase-Schreibweise), setzen die Helfer des Frameworks außer Kraft.
Rufen wir Zend_View::doctype()
auf, wird somit
zuerst nach einem angepassten View-Helfer dieses Namens gesucht.
[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
Diesen neuen View-Helfer
zu implementieren hat ein einziges Ziel - einen neuen XHTML5-Doctype zu
erlauben, der den normalen HTML5-Doctype in einem Template ausgibt. Eine
Abfrage des Doctype sollte den korrekten Wert zurückgeben, um den
XHTML-Test zu bestehen, der von anderen View-Helfern verwendet wird. Falls
Sie mir bei den vorangegangenen Domain-Model-Tests gefolgt sind und eine
neue Datei AllTests.php aufgesetzt haben, können Sie diesen Prozess für
alle View-Helfer wiederholen, die wir hinzufügen wollen. Hier sind unsere
Unit-Tests für eine solche Implementierung. Wir speichern sie in
/tests/ZFExt/View/Helper/DoctypeTest.php
ab.
<?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());
}
}
Um den Helfer zu
implementieren, reicht eine simple Subklasse in
/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;
}
}
Wie Sie in der Klasse sehen können, übergeben wir die Kontrolle für alle möglichen Doctypes außer das XHTML5-spezifische Handling zurück an die Elternklasse.
Eine weitere Facette der
Unterstützung von HTML 5, die wir angehen können, ist das neue Attribute
charset, das im <meta>
-Tag verwendet werden kann. Das
Attribut ersetzt diesen Code
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
durch eine kürzere Form, die keinen Content-Type deklariert. Natürlich können Sie wenn Sie wollen weiterhin den Content-Type hinzufügen, aber die Webanwendung sollte ohnehin mit dem korrekten Typ ausgeliefert werden.
<meta charset="utf-8"/>
Unglücklicherweise
erkennt der View-Helfer Zend_View_Helper_HeadMeta
,
der Templates und Layouts das Hinzufügen von Metainformationen erlaubt,
das charset-Attribut nicht als valide an. Wir können ihn dazu überreden,
indem wir einen weiteren eigenen View-Helfer hinzufügen, und wieder müssen
wir uns dafür über die originale Klasse hinwegsetzen. Wir werden ein
weiteres Mal eine Subklasse der Originalklasse erstellen, damit wir
möglichst wenig Code schreiben müssen.
Hier die relevanten
Unit-Tests, die in
/tests/ZFExt/View/Helper/HeadMetaTest.php
gespeichert
werden.
<?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]);
}
}
/**
* Dieser spezielle Helfer adressiert nur die (X)HTML5-Unterstützung
* ZFExt-Doctype-Helfer wird für den XHTML-Stil verwendet
*/
$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);
}
}
Die Implementierung ist
etwas komplizierter als gewohnt, da wir die Unterstützung für einen
komplett neuen <meta>
-Tag-Typen hinzufügen, den es
bisher nicht gab. Die neue Klasse wird in
/library/ZFExt/View/Helper/HeadMeta.php
geschrieben.
<?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;
}
}
Keine Sorge, falls das jetzt für Sie unverständlich wirkt! Wir werden uns in späteren Kapiteln ein paar anwendungsspezifische View-Helfer ansehen, aber vorerst benötigen wir nur diese zwei maßgeschneiderten Helfer, um sicherzustellen, dass wir eine vollständigere HTML-5-Unterstützung zur Hand haben.
In der obigen Klasse
ersetzen wir zwei Originalmethoden: _isValid()
und itemToString()
. Die erste validiert die
Details einiger Metainformationen, die wir in einem
<meta>
-Tag rendern wollen. Die Daten werden als eine
Instanz von stdClass gespeichert. Es handelt sich also um einen simplen
Datencontainer, der genauso gut ein Array hätte sein können. Die
hauptsächliche Neuerung, die ich bei der Validierung eingeführt habe ist,
dass ich einen neuen Meta-Typ namens "charset" erlaube, falls der Doctype
einem der HTML-5-Typen entspricht. In der Methode
itemToString()
habe ich die Fähigkeit
hinzugefügt, diesen neuen Meta-Typ in einen <meta>-Tag zu
transformieren und den Tag zu schließen, falls der verwendete Doctype
XHTML5 ist.
Ich habe auch eine neue
Methode setCharset()
hinzugefügt, damit die
Erstellung dieses neuen Tags von den zwei ursprünglich unterstützten
Meta-Typen (den einfachen name/value-Typen und den http-equiv-Typen)
getrennt wird.
Nachdem wir unsere
erweiterte Unterstützung von HTML 5 abgeschlossen haben, sehen wir uns
application.ini
erneut an und fügen eine neue
HTML-Markup-Option namens "charset" zusammen mit dem neuen Doctype ein.
Wir könnten auch einfach die Zend_View
-Einstellung
für die Zeichencodierung verwenden, aber ich habe mich entschieden, das zu
trennen, damit wir den neuen Meta-Tag-Typen explizit dann hinzufügen
können, wenn seine spezifische Option gesetzt wurde.
[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
Wir können nun auch die
Methode _initView()
der Bootstrap-Klasse
abändern, um die neue Charset-Option zu verwenden.
<?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;
}
// ...
}
Laden Sie nun die Index-Seite neu. Wenn Sie den Quelltext kontrollieren, sollten Sie nun sehen, dass er vollständig im Einklang mit dem originalen Markup steht, welches wir am Beginn dieses Kapitels definiert haben.
Durch die Verwendung des YUI-CSS-Framework haben wir einen Basisstil für den Blog, der sehr schlicht gehalten, aber dafür über alle größeren Browser gleich ist. Wir können den Code nun durch unser eigenes angepasstes Design ergänzen, um dem Blog einen abgerundeten Stil zu verpassen.
Um das zu erreichen,
müssen wir eine Änderung vornehmen, damit unser Layout-Template eine Datei
style.css in /public/css/style.css
einbindet. In
dieser Datei können wir weitere CSS-Regeln ablegen. Hier ist der
überarbeitete <head>
-Abschnitt des geänderten
Layouts.
<?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>
Unter Berücksichtigung
der Tatsache, dass wir uns die Yahoo
Performance Best Practices
zu Herzen nehmen, fügen wir unser
vorhergehendes View-Helfer-Beispiel mit ein paar Änderungen ein. Hier
haben wir die eigentlichen Unit-Tests, die zu unserem Helfer
ZFExt_View_Helper_IncludeModifiedDate
gehören. Sie
verwenden eine leere Datei style.css
, die Sie in
/tests/ZFExt/View/Helper/_files/style.css
speichern
können, um ein Bearbeitungsdatum zu erhalten. Die Unit-Tests werden in
/tests/ZFExt/View/Helper/IncludeModifiedDateTest.php
abgelegt. Wie bei all diesen Tests fügen Sie sie zu der nächstgelegenen
Datei AllTests.php
hinzu, damit sie von PHPUnit
aufgerufen wird. Der einzige knifflige Teil dabei ist das aktuelle
Arbeitsverzeichnis von PHP so gestalten, dass der relative URI in diesem
Test Sinn macht und die Datei style.css damit aufgefunden werden
kann.
<?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));
}
}
Wie Ihnen wahrscheinlich
auffällt, haben wir den Helfer dahingehend aufgepeppt, dass er mit einem
weiteren Fall umgehen kann, in dem der URI einer Datei bereits einen
Query-String in irgendeiner Form enthält. Dieses Mal wird der Timestamp
des Bearbeitungsdatums auch innerhalb des Dateinamens anstatt im
Querystring hinzugefügt. Dadurch wird das Szenario abgedeckt, wonach viele
Proxies den Query-String beim Cachen von Dateien ignorieren. Das Ändern
des Dateinamens oder des Pfades ist somit weitaus effektiver. Natürlich
ändern wir nicht tatsächlich den Dateinamen - stattdessen fügen wir
/public/.htaccess
eine neue Rewrite-Rule hinzu, damit
alle URIs zu Dateien in der Form von
/name.timestamp.extension
(z.B.
/style.1252108229.css
) korrekt auf den eigentlichen
Dateinamen umgeschrieben werden. Hier ist die Implementierung für
/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
);
}
}
Die folgende Änderung fügt die Unterstützung in
/public/.htaccess
dafür hinzu, Dateien auf diese Art
mit einem Timestamp zu versehen und die darunterliegende Datei zu laden,
die keinen Timestamp in ihrem Namen besitzt. Die Rewrite-Rule entfernt
lediglich den Timestamp-Teil aus dem Namen der angefragten Datei. Sie
können je nach Anforderung weitere Dateiendungen und Verzeichnisse
hinzufügen. Ich habe einige häufig betroffene Standardtypen und
-verzeichnisse eingetragen.
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]
Unser Layout-Template kann nun den neuen, selbst geschriebenen Helfer wie folgt verwenden.
<?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>
Wir werden den
View-Helfer nicht gemeinsam mit URIs von YUI CSS verwenden, da sie bereits
eine Referenz auf eine Versionsnummer eingebaut haben. Wir können die URIs
manuell im Layout-Template ändern ohne weitere Maßnahmen setzen zu müssen,
wenn eine neue Version des CSS-Frameworks veröffentlicht wird. Behalten
wir das Markup unseres Layouts im Auge und sehen wir uns an, wie der
Quelltext unseres <head>
-Abschnitts momentan
aussieht:
<!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>
Wie Sie sehen können, befindet sich im URI von style.css nun wie erwartet ein Bearbeitungsdatum im Query-String.
Falls Sie sich fragen,
wie man die Verwendung der Header Expires
und
Cache-Control
in der Praxis umsetzt: normalerweise wird das
über die Konfiguration Ihres virtuellen Hosts geregelt. Sie müssen zuvor
in Ihrer Webserverkonfiguration das Apache-Modul mod_expires
aktivieren.
Hier ist eine Beispielkonfiguration für den virtuellen Host des Blogs. Er implementiert einen Expires-Header, der sechs Monate nach dem ersten Zugriff des Clients auf die statische Datei wirksam wird. Ich habe zwei Regeln hinzugefügt, um die Optionen zu illustrieren - die eine bestimmt die betroffenen Dateien über einen regulären Ausdruck, die andere wählt die Dateien explizit über den Dateityp aus. Die Regeln werden in eine Bedingung eingebettet, damit sie ignoriert werden, falls das benötigt Modul von Apache nicht geladen wurde. Es ist sehr wichtig, dass Sie folgendes in Erinnerung behalten: falls Sie als URI zu einer Ressource einen Query-String ohne das Bearbeitungsdatum verwenden, wird der Client diese Ressource für sechs Monate zwischenspeichern und während dieser Zeit nie nach Aktualisierungen fragen, solange er nicht seinen Cache verliert oder ein kompletter Reload erzwungen wird (z.B. mittels CTRL+SHIFT+R in 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>
Diese Konfiguration bewirkt, dass alle Dateien dieser Dateitypen mit zwei neuen Headern an den Client ausgeliefert werden. Zum Beispiel:
Cache-Control: max-age=31536000 Expires: Sun, 05 Sep 2010 00:39:27 GMT
Expires
gibt ein Datum sechs Monate in der Zukunft an, ab dem die Datei als
verfallen angesehen und einmal vom Server neu angefordert werden sollte.
Der zweite Header Cache-Control
gibt die maximale Lebenszeit
der Ressource in Sekunden an. Technisch gesehen ist der
Cache-Control
-Header der wichtigere, da er in den meisten
Fällen die Einstellung des Expires-Headers überschreibt. Sie können dies
von Ihrer .htaccess
-Datei aus einstellen, indem Sie
dieselben Regeln verwenden.
Bevor wir uns dem
maßgeschneiderten Stylesheet zuwenden, nutzen wir zuerst, was wir bereits
mit dem Yahoo User Interface CSS eingefügt haben. Mit den folgenden
Änderungen in unserem Standard-Layouttemplate in
/application/views/layouts/default.phtml
verpassen
wir dem Blog ein typisches Website-Layout mit einem Kopf- und Fußbereich,
einem Hauptteil und einer linksseitigen Navigation für zusätzliche
Navigationslinks oder ergänzende Inhalte.
<?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>
Da wir HTML 5 verwenden,
setzen große Teil des Markups diese neuen Elemente ein - außer dort, wo
das Markup eindeutig nur dazu benötigt wird, das Raster-Layout von YUI CSS
zu verwenden. In diesen Fällen habe ich das normale
<div>
-Element verwendet. Dadurch behalten wir den Satz
logisch richtiger Elemente bei, wobei es nur zwei unterschiedliche
<section>
-Elemente gibt: eines für den Hauptinhalt, das
andere für die linke Seitenleiste, die ich eingeführt habe.
<div>
-Elemente steigen damit in die langweilige
Kategorie der "CSS-Hooks" im Markup ab, die dazu dienen, das Design zu
verschönern - nicht gerade der sauberste Ansatz, doch da ich ein
CSS-Framework verwende, kann ich damit leben.
Ich habe zudem in
Übereinstimmung mit einem weiteren neuen Standard neben HTML 5 einigen
Elementen Rollen zugewiesen. Bei dem Standard handelt es sich um Accessible Rich Internet
Applications Suite (WAI–ARIA 1.0)
, der versucht, Webinhalte und
-anwendungen für Menschen mit Beeinträchtigungen zugänglicher zu machen.
Es handelt sich dabei um eine kleine Geste, die wir machen, während wir
HTML 5 adoptieren und ein Layout von Grund auf neu aufbauen. Der Standard
definiert einen Satz sogenannter "document landmark roles", die einer
Person mit der entsprechenden Software theoretisch erlauben würden, sich
direkt den Teilen des HTML-Dokuments zuzuwenden, die für sie interessant
sind. Die Rollen sind deswegen sehr intuitiv gehalten: Banner (banner),
Hauptinhalt (main), Navigation (navigation), Information zum Inhalt
(contentinfo), ergänzende Inhalte (complementary), Suche (search) etc. Es
gibt viele Rollen, aber ich verwende nur ein Minimum davon. Als Anreiz für
die Verwendung werden "landmark roles" aktuell durch den JAWS
10 screen reader
unterstützt.
Ich habe dem Blog einen
Untertitel und somit gleich ein neues Element <hgroup>
hinzugefügt. Dieses Element gruppiert zusammengehörende Überschriften bzw.
Bestandteile der Kopfzeile und wird in der HTML-5-Spezifikation wie folgt
definiert.
Laden Sie den URI http://zfblog.tld
in Ihrem Browser
neu, um zu sehen, welche Auswirkung die Änderungen haben.
Das aktualisierte Design
sieht - langsam - nach etwas aus, das wir ertragen könnten, wenn wir
endlose Geduld besäßen. Lassen Sie es uns noch etwas verbessern, indem wir
diesmal /public/css/style.css
bearbeiten.
/** * Basis-Elemente */ 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-Anzeige * * Von den meisten (wenn nicht allen) Browsern benötigt, * bis die Unterstützung für die Elemente hinzugefügt wird. */ article, aside, dialog, figure, footer, header, hgroup, nav, section { display:block; } /** * Seiten-Elemente */ 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; } /** * Überschriften */ 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%; } /** * Horizontales Navigationsmenü im Header */ 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; } /** * Vertikale Sidebar für Menü/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; } /** * Styling für die Artikel */ 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; }
Somit haben wir ein sehr einfaches, aber akzeptables Design, mit dem wir fürs Erste arbeiten können.
In diesem Kapitel haben
wir die Grundlagen abgedeckt, um ein Webdesign zu erstellen und es unter
Einsatz von Zend_View
,
Zend_Layout
und einer Zahl von View-Helfern in das
Templatingsystem des Zend Framework zu migrieren. Wir werden in den
folgenden Kapiteln noch viel mehr auf die Erstellung von eigenen
View-Helfern eingehen. Darüber hinaus haben wir die Grundlagen von HTML 5,
dem kommenden neuen Standard für HTML, kennengelernt und uns mit den
Implikationen auseinandergesetzt, die sich aus der Implementierung des
Standards momentan ergeben, darunter der Notwendigkeit der Verwendung
eigens geschriebener View-Helfer. Ich habe aber keinen Zweifel, dass sich
die Unterstützung von HTML 5 im Zend Framework in sehr naher Zukunft
materialisieren wird, also bleiben Sie dran!
In Kapitel 11 werden wir
zum nächsten fundamentalen Themenkreis weitergehen. Wir werden
Blogeinträge aus unserer Datenbank anzeigen müssen, und natürlich müssen
wir erst einmal einen Weg finden, sie zu schreiben! Daher werden wir uns
ansehen, wie wir unser bereits vorbereitetes Domain-Model verwenden können
und wie wir neue Blogeinträge verfassen können, indem wir Formulare
mittels Zend_Form
generieren.