/
Автор: Mohrke C.
Теги: programmierung informatik programmiersprachen reverse engineering mikroprozessoren
ISBN: 978-3-8362-1068-3
Текст
e-bol.net
Carsten Möhrke
Zend Framework
Das Entwickler-Handbuch
Galileo Press
e-bol.net
Liebe Leserin, lieber Leser,
mit dem neuen Buch von Carsten Möhrke haben Sie alles an der Hand, was Sie für
den professionellen Einsatz des Zend Framework benötigen. Vielen dürfte unser
Autor bereits durch seine erfolgreichen Bücher zu PHP PEAR und vor allem durch
sein »Besser PHP programmieren«-Buch bekannt sein. Durch die enge Verknüp-
fung von Theorie- und Praxiswissen garantiert Carsten Möhrke, dass Sie Gelerntes
immer unmittelbar in Ihren Projekten umsetzen können.
Das Zend Framework hat innerhalb kurzer Zeit bereits viele überzeugte Anhänger
gefunden. Vieles spricht für den Einsatz des Frameworks bei der Entwicklung kom-
plexer und sicherheitskritischer Aufgaben. So können Sie sich auf die eigentliche
Applikationsstruktur konzentrieren, haben ausgereiften Quellcode zur Verfügung
und sparen so letztendlich deutlich Zeit. Carsten Möhrke begleitet Sie auf diesem
Weg und zeigt Ihnen, wie Sie das Model View Controller-Muster nutzen oder die
zahlreichen Funktionen des Zend Frameworks verwenden. Angefangen von der
Installation über den Umgang mit Datenbanken, der Benutzerverwaltung, Perfor-
mance-Fragen, der E-Mail-Implementierung oder dem Einsatz von Webservices:
Dieses Buch zeigt Ihnen, welche Möglichkeiten das Zend Framework bietet und
wie Sie es für sich nutzen können.
Ein besonderer Dank geht an dieser Stelle an Gaylord Aulke von der Zend Techno-
logies GmbH. Er hat das Manuskript in seiner letzten Phase einer umfassenden Prü-
fung unterzogen und das Buch so kompetent begleitet.
Um die Qualität unserer Bücher zu gewährleisten, stellen wir stets hohe Ansprüche
an Autoren und Lektorat. Falls Sie dennoch Anmerkungen und Vorschläge zu die-
sem Buch formulieren möchten, so freue ich mich über Ihre Rückmeldung.
Ihr Stephan Mattescheck
Lektorat Galileo Computing
stephan.mattescheck@galileo-press.de
www.galileocomputing.de
Galileo Press • Rheinwerkallee 4 • 53227 Bonn
e-bol.net
Auf einen Blick
1 Der Model View Controller.............................. 25
2 Datenbankzugriff mit Zend_Db .......................... 59
3 Benutzer-und Rechtemanagement ........................ 115
4 Infrastruktur-Klassen ................................ 137
5 Webservices........................................... 209
6 Arbeit mit E-Mails und Dateiformaten ................. 279
7 Protokolle und Co..................................... 335
8 Lokalisierung und Internationalisierung .............. 369
e-bol.net
Der Name Galileo Press geht auf den italienischen Mathematiker und Philosophen Galileo
Galilei (1564-1642) zurück. Er gilt als Gründungsfigur der neuzeitlichen Wissenschaft und
wurde berühmt als Verfechter des modernen, heliozentrischen Weltbilds. Legendär ist sein
Ausspruch Eppur se muove (Und sie bewegt sich doch). Das Emblem von Galileo Press ist der
Jupiter, umkreist von den vier Galileischen Monden. Galilei entdeckte die nach ihm benannten
Monde 1610.
Gerne stehen wir Ihnen mit Rat und Tat zur Seite:
stephan.mattescheck@galileo-press.de bei Fragen und Anmerkungen zum Inhalt des Buches
service@galileo-press.de für versandkostenfreie Bestellungen und Reklamationen
stefan.krumbiegel@galileo-press.de für Rezensions- und Schulungsexemplare
Lektorat Stephan Mattescheck
Fachgutachten Gaylord Aulke, Zend Technologies
Korrektorat Rene Wiegand, Bonn
Cover Barbara Thoben, Köln
Titelbild Corbis
Typografie und Layout Vera Brauner
Herstellung Katrin Müller
Satz Typographie & Computer, Krefeld
Druck und Bindung Bercker Graphischer Betrieb, Kevelaer
Dieses Buch wurde gesetzt aus der Linotype Syntax Serif (9,25/13,25 pt) in FrameMaker.
Bibliografische Information der Deutschen Bibliothek
Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie;
detaillierte bibliografische Daten sind im Internet über http://www.d-nb.de abrufbar.
ISBN 978-3-8362-1068-3
© Galileo Press, Bonn 2008
1. Auflage 2008
Das vorliegende Werk ist in all seinen Teilen urheberrechtlich geschützt. Alle Rechte vorbehalten, insbesondere das Recht
der Übersetzung, des Vortrags, der Reproduktion, der Vervielfältigung auf fotomechanischem oder anderen Wegen und der
Speicherung in elektronischen Medien. Ungeachtet der Sorgfalt, die auf die Erstellung von Text, Abbildungen und Program-
men verwendet wurde, können weder Verlag noch Autor, Herausgeber oder Übersetzer für mögliche Fehler und deren Fol-
gen eine juristische Verantwortung oder irgendeine Haftung übernehmen. Die in diesem Werk wiedergegebenen Gebrauchs-
namen, Handelsnamen, Warenbezeichnungen usw. können auch ohne besondere Kennzeichnung Marken sein und als
solche den gesetzlichen Bestimmungen unterliegen.
e-bol.net
Inhalt
Geleitwort des Fachgutachters.............................................. 11
Einleitung ................................................................. 13
1 Der Model View Controller...................................... 25
1.1 Die Theorie des MVC.............................................. 25
1.2 Die Praxis des MVC .............................................. 26
1.2.1 Der Front Controller.................................... 29
1.2.2 Der Action Controller................................... 30
1.2.3 Einbinden eines Views .................................. 34
1.2.4 Die Verarbeitungsschritte .............................. 37
1.2.5 Übergabe von Werten .................................... 38
1.2.6 Das Model .............................................. 41
1.2.7 Error Handling ......................................... 42
1.2.8 Fortgeschrittene Techniken ............................. 47
1.2.9 Ein Beispiel ........................................... 50
2 Datenbankzugriff mit Zend Db ............................ 59
2.1 Datenbankunabhängigkeit ..................................... 59
2.2 Nutzung von Zend_Db ......................................... 60
2.2.1 Transaktionen ....................................... 73
2.2.2 Sequenzen und automatisch generierte IDs ............ 75
2.2.3 Spezielle Datenbankzugriffsmethoden ................. 76
2.3 Datenbankzugriff mit Zend_Db_Table .......................... 88
2.3.1 Einfügen von Daten .................................. 93
2.3.2 Aktualisieren von Daten ............................. 94
2.3.3 Löschen von Daten ................................... 95
2.3.4 Auslesen von Daten .................................. 95
2.3.5 Kaskadierende Lösch- und Update-Vorgänge ........... 109
2.4 Performanceanalyse mit Zend_Db_Profiler .................... 110
3 Benutzer- und Rechtemanagement ...................115
3.1 Rechteverwaltung mit Zend_Acl ............................ 115
3.1.1 Vererbung von Rechten ............................ 118
3.1.2 Verfeinern des Rechtesystems ..................... 120
3.1.3 Manipulieren von Rechten ......................... 123
5
e-bol.net
Inhalt
3.2 Benutzerauthentifikation mit Zend_Auth ....................... 124
3.2.1 Datenbankbasierte Authentifikation .................. 124
3.2.2 Dateibasierte HTTP-Authentifikation ................. 128
3.3 Session-Verwaltung mithilfe von Zend_Session ................. 129
3.3.1 Eine Session starten ................................ 130
3.3.2 Gültigkeit und Schutz von Session-Daten ............. 132
3.3.3 Nutzung eigener Session-Save-Handler ................ 133
4 Infrastruktur-Klassen.........................................137
4.1 Performance-Optimierung mit Zend_Cache ....................... 137
4.1.1 Frontends ........................................... 139
4.1.2 Nutzung von Backends................................. 149
4.1.3 Manuelle Verwaltung von Cache-Einträgen ............. 153
4.2 Prüfen von Werten mit Zend_Validate .......................... 154
4.2.1 Prüfen auf alphanumerische Daten .................... 156
4.2.2 Prüfen von Texten ................................... 156
4.2.3 Prüfen, ob eine Zahl in einem bestimmten
Bereich liegt................................................. 156
4.2.4 Prüfen von Kreditkartennummern ...................... 157
4.2.5 Prüfen eines Datums ................................. 157
4.2.6 Testen von Ziffernfolgen ............................ 157
4.2.7 Validieren von E-Mail-Adressen ...................... 157
4.2.8 Testen eines Strings auf Fließkomma-Eigenschaften.... 160
4.2.9 Prüfen, ob eine Zahl über einer Grenze liegt......... 160
4.2.10 Testen von hexadezimalen Zahlen ..................... 160
4.2.11 Validieren von Hostnames............................. 161
4.2.12 Testen von Array-Inhalten ........................... 163
4.2.13 Validieren von Integer-Werten ....................... 163
4.2.14 Prüfen von IP-Adressen .............................. 163
4.2.15 Prüfen, ob eine Zahl unter einer Grenze liegt ....... 164
4.2.16 Testen, ob eine Variable leer ist ................... 164
4.2.17 Validierung auf Basis eines regulären Ausdrucks ..... 164
4.2.18 Testen eines Strings auf seine Länge hin ............ 165
4.3 Filtern von Daten mit Zend_Filter ............................ 165
4.3.1 Alphanumerische Zeichen mit Zend_Filter_Alnum
filtern ............................................. 167
4.3.2 Buchstaben filtern .................................. 168
4.3.3 Extrahieren eines Basenames.......................... 168
4.3.4 Ziffern mit Zend_Filter_Digits ausfiltern ........... 169
6
e-bol.net
Inhalt
4.3.5 Extrahieren von Verzeichnisnamen ................... 169
4.3.6 Konvertieren von Sonderzeichen in Entitäten ........ 169
4.3.7 Filtern von Integer-Werten ......................... 170
4.3.8 Absolute Pfade mit Zend_Filter_RealPath extrahieren .... 170
4.3.9 Konvertieren in Kleinbuchstaben .................... 170
4.3.10 Konvertieren in Großbuchstaben .................... 171
4.3.11 Entfernen von Whitespaces .......................... 171
4.3.12 Entfernen von HTML-Tags............................. 172
4.3.13 Nutzung eigener Filter ............................. 174
4.4 Formularverarbeitung mit Zend_Filter_lnput .................. 175
4.5 Schreiben von Logs mit Zend_Log.............................. 184
4.5.1 Log-Einträge filtern ............................... 190
4.5.2 Logfile-Einträge formatieren ....................... 191
4.5.3 Eigene Einträge definieren.......................... 194
4.6 Konfigurationsverwaltung Zend_Config......................... 194
4.6.1 Nutzung von Konfigurations-Arrays................... 195
4.6.2 INI-Dateien......................................... 197
4.6.3 XML-Dateien ........................................ 200
4.7 Shell-Programmierung mit Zend_Console_Getopt ................ 203
4.7.1 Optionen, Flags und Parameter....................... 204
4.7.2 Nutzung von Argumenten ............................. 207
5 Webservices ..........................................209
5.1 Feeds mit Zend_Feed verarbeiten ............................ 209
5.1.1 Feeds finden ....................................... 209
5.1.2 Allgemeines zur Verarbeitung von Feeds ............ 210
5.1.3 Verarbeiten von RSS-Feeds ......................... 211
5.1.4 Verarbeiten von Atom-Feeds ........................ 214
5.1.5 Generieren von Feeds .............................. 218
5.2 Zugriff auf Amazon mit Zend_Service_Amazon ................. 220
5.3 Zugriff auf Flickr mit Zend_Service_Flickr ................. 232
5.4 Yahool-Suche mit Zend_Service_Yahoo ........................ 236
5.4.1 Websuche ........................................... 236
5.4.2 News-Suche mit Zend_Service_Yahoo .................. 240
5.4.3 Bildersuche mit Zend_Service_Yahoo ................. 242
5.5 Zugriff auf Google-Dienste mit Zend_Gdata .................. 245
5.5.1 Allgemeines zu Zend_Gdata .......................... 246
5.5.2 Authentifikation ................................... 246
5.5.3 Nutzung von Google Calendar......................... 255
5.5.4 Nutzung von Google Spreadsheets .................... 271
7
e-bol.net
Inhalt
6 Arbeit mit E-Mails und Dateiformaten....................279
6.1 E-Mails mit Zend_Mail verarbeiten ........................ 279
6.1.1 E-Mails versenden................................. 280
6.1.2 Versand über SMTP-Server.......................... 286
6.1.3 E-Mails abholen .................................. 288
6.1.4 Löschen von E-Mails .............................. 298
6.1.5 Erweiterte Möglichkeiten von IMAP ................ 300
6.2 JSON-Daten mit Zendjson verarbeiten ...................... 306
6.3 Generieren von PDF-Dokumenten ............................ 308
6.3.1 Nutzung anderer Schriften ........................ 313
6.3.2 Mehrzeilige Fließtexte............................ 315
6.3.3 Nutzung von Farben ............................... 320
6.3.4 Zeichnen in PDF-Dokumenten ....................... 322
6.3.5 Einbinden von Bildern ............................ 330
6.3.6 Meta-Information en einfügen ..................... 331
6.3.7 Einlesen von PDF-Dokumenten....................... 333
7 Protokolle und Co........................................... 335
7.1 Zugriff auf andere Server mit Zend_Http ................... 335
7.1.1 Das HTTP-Protokoll ................................ 335
7.1.2 Einen HTTP-Client erstellen........................ 336
7.1.3 Übergabe von Werten ............................... 338
7.1.4 Uploads ........................................... 340
7.1.5 HTTP-Authentifikation ............................. 341
7.1.6 Server-Antworten auswerten......................... 342
7.1.7 Cookies ........................................... 346
7.1.8 Nutzung von Adaptern .............................. 351
7.2 URIs mit ZendJJri verarbeiten.............................. 352
7.2.1 URIs analysieren................................... 353
7.3 Nutzung von XML-RPC mit Zend_XmlRpc ....................... 355
7.3.1 Allgemeines zu Zend_XmlRpc ........................ 355
7.3.2 Erstellen eines XML-RPC-Servers.................... 356
7.3.3 Erstellen eines XML-RPC-Clients ................... 359
7.4 Nutzung von REST mitZend_Rest ............................. 361
7.4.1 Zugriff auf offene REST-Schnittstellen ............ 362
7.4.2 Implementation eines REST-Servers ................. 363
8
e-bol.net
Inhalt
8 Lokalisierung und Internationalisierung........................369
8.1 Lokalisierung mit Zend_Locale .............................. 369
8.1.1 Standardtexte und Standardformate lokalisieren...... 371
8.2 Mehrsprachige Oberflächen mit Zend_Translate ............... 381
8.2.1 Nutzung von CSV-Dateien ............................ 384
8.3 Konvertieren von und Rechnen mit Maßeinheiten mittels
Zend_Measure ..................................................... 385
8.4 Währungsdarstellung mit Zend_Currency ...................... 389
8.5 Datums-und Zeitangaben mit Zend_Date verarbeiten ........... 395
8.5.1 Ableiten eines Zend_Date-Objekts ................... 395
8.5.2 Rechnen mit Daten .................................. 402
8.5.3 Vergleich von Daten ................................ 404
8.5.4 Prüfen von Datumsinformationen ..................... 407
Inhalt der CD-ROM .................................................................. 409
Index
411
9
e-bol.net
e-bol.net
Geleitwort des Fachgutachters
Wie schön war die Welt 1997. Und wie einfach. Wir schrieben unsere Websites
(damals hießen sie noch nicht »Anwendungen«) und waren fasziniert davon,
wenn der Server überhaupt etwas auslieferte. Ich kann mich noch gut an die
Revolution des ersten animierten GIF erinnern: ein Briefumschlag, in dem ein
kleiner Brief verschwand. Immer und immer wieder! Unsere Vision damals:
Webseiten, die automatisch generiert werden und ohne Technikkenntnisse ver-
waltbar wären! Damals schon in Java, Perl und neuerdings auch in PHP betrie-
ben. Seitdem hat sich einiges getan. Die New Economy kam und ging, aus Inter-
net wurde Web 2.0 und das Web ist vom Spielplatz der Freaks zu einer wichtigen
Wirtschaftsplattform geworden. Und aus PHP/FI wurde PHP 5. Nachdem bis
PHP 4 so ziemlich jedes Team seine Erfahrungen in der PHP-Programmierung in
ein eigenes Framework gegossen hatte, wurden parallel zu Version 5 neue Her-
ausforderungen sichtbar wie: Content Syndication, Ajax, Security. Für viele kam
zusätzlich erstmals auch die Forderung nach modularen, wartbaren Anwendun-
gen auf, die in einem professionellen Teamansatz betreut und weiterentwickelt
werden konnten. Gleichzeitig wurden mit PHP 5 die Sprachkonstrukte einge-
führt, die Generalisierung und Modularisierung weit besser unterstützen, als dies
bisher der Fall war. Was also tun? Das PHP 4-Framework einmotten und ein
neues in PHP 5 aufbauen? Viele machen genau das. Aber glücklicherweise ent-
scheiden sich immer mehr Teams zur Nutzung von einem der mittlerweile über
50 Open-Source-PHP-Frameworks. Statt Energie in einen Eigenbau zu investie-
ren, helfen sie beim weiteren Ausbau eines Standard-Frameworks.
Das Zend Framework ist angetreten, einer möglichst breiten Nutzerschaft eine
Basis für moderne, feature-reiche, sichere und zukunftssichere Webanwendun-
gen in PHP zu bieten. Dabei bewegt man auf dem schmalen Grad zwischen sinn-
voller Generalisierung und der Komplexität von üblichen Java-Anwendungen
(die man ja in PHP bewusst nicht will). Bereits jetzt, kurz nach Erscheinen der 1.0
Version zeichnet sich ab, dass mit Zend Framework nicht nur ein nützlicher Bau-
kasten von Komponenten ausgeliefert wird, sondern dass sich hier »best
practices« der PHP-Programmierung niederschlagen und De-facto-Standards im
PHP Sektor entstehen. Das Zend Framework ist durch seine konzeptionelle
Offenheit und die breite Akzeptanz zu einem der wichtigsten Faktoren moderner
Anwendungsentwicklung in PHP geworden. Das vorliegende Buch fasst die
grundlegenden Konzepte des Zend Frameworks zusammen und beleuchtet die
Möglichkeiten, die sich aus den unterschiedlichen Komponenten ergeben. Es bie-
tet damit einen guten Einstieg in das Framework und die damit verbundenen
Programmierpraktiken. Ich empfehle Ihnen dieses Buch, wenn Sie bisher noch
11
e-bol.net
Geleitwort des Fachgutachters
nicht mit dem Zend Framework gearbeitet haben und eine solide Einführung
wünschen. Aber auch, wenn Sie bereits erste Erfahrungen mit dem Zend Frame-
work gemacht haben, wird Ihnen das Buch von Carsten Möhrke als wertvolles
Nachschlagewerk in der praktischen Entwicklungsarbeit dienen.
Gaylord Aulke
Zend Technologies GmbH
12
e-bol.net
You never win a game unlessyou beat the guy in front ofyou. The score
on the board doesn't mean a thing. That'sfor the fans. You've got to
win the war with the man in front ofyou. You've got to getyour man.
- Vince Lombardi, Football Coach
Einleitung
Zend Framework wurde lange und mit viel Spannung erwartet. Nach einigen
Monaten Entwicklungszeit ist die erste Version am 30. Juni 2007 veröffentlicht
worden.
Neben Zend Framework existieren auch viele andere etablierte Frameworks am
Markt. Warum sollte man also Zend Framework nutzen? Meiner Ansicht nach
gibt es eine ganze Reihe Gründe, dies zu tun.
Zuerst einmal ist da natürlich die hohe Qualität des Codes. Der Name Zend bürgt
für Qualität. Neben einer robusten Implementation sind auch viele Ansätze des
modernen Software Engineerings zu finden. Die Entwicklung ist komplett testge-
trieben, sodass die Anzahl der Bugs eher gering ist. Natürlich gibt es hier und da
mal einen Bug, wie in jeder anderen Software auch, aber in den meisten Fällen
sind diese wenig dramatisch und werden schnell korrigiert. Selbstverständlich
gibt es Coding Standards und Namenskonventionen an die sich alle Entwickler
halten. Ich würde Ihnen empfehlen, dass Sie diese bei der Entwicklung Ihrer
Anwendungen auch zu Grunde legen. Sie können diese hier nachlesen:
http://framework.zend.com/manual/coding-standard.html.
In den Reihen des Entwicklerteams finden Sie viele Entwickler, die sich in der
PHP-Szene seit vielen Jahren einen Namen gemacht haben, erfahren sind und
hochwertigen Code erstellen.
Für die Nutzung spricht auch das interessante Lizenz-Modell (http://framework.
zend.com/license). Es ist wesentlich freier und flexibler als viele andere Lizenzen.
Es ist auch ohne Probleme möglich, Code der auf dem Zend Framework basiert
kommerziell zu vertreiben.
Ein weiterer Punkt ist die wirklich gute Implementation des Model View Con-
trollers (MVC), der Ihnen gerade bei umfangreichen Applikationen sehr viel
13
e-bol.net
Einleitung
Arbeit abnehmen kann, zu einem deutlich strukturierten Code führt und sehr
robust umgesetzt ist.
Aber keine Angst, Sie sind nicht gezwungen auf MVC aufzusetzen. Jede Kompo-
nente aus dem Zend Framework kann einzeln genutzt werden, sodass Sie voll-
kommen flexibel sind.
Eine besondere Stärke des Zend Frameworks sind sicherlich die Klassen zur
Internationalisierung bzw. Lokalisierung von Code. Hier finden sich viele sehr
leistungsfähige Klassen, die Sie gut unterstützen, wenn Sie mehrsprachige
Anwendungen für eine internationale Zielgruppe erstellen. Auch die Klassen, die
Ihre Applikation »im Hintergrund« unterstützen, den Zugriff auf eine Datenbank
ermöglichen, Daten cachen, Logs schreiben oder E-Mails verschicken, sind wirk-
lich sehr umfangreich, performant und stabil.
Aber genug des Lobes. Ich möchte nicht verschweigen, dass das Zend Framework
auch noch ein paar deutliche Schwächen hat. So könnte beispielsweise die eine
oder andere Webservice-Klasse mehr Funktionalitäten gebrauchen, und auch die
PDF-Klasse ist noch ein wenig schwach auf der Brust. Dennoch sind auch diese
Klassen auf einem guten Weg. Sollten Sie zum jetzigen Zeitpunkt allerdings eine
dieser Klassen nutzen wollen, so ist es empfehlenswert, vorher zu prüfen, ob die
benötigten Funktionalitäten enthalten sind.
Einen Punkt - und das ist meiner Ansicht nach ein absolut unschlagbarer Vorteil
- gibt es aber noch. Und zwar finden Sie in der Zend-IDE Zend Studio for Eclipse
eine komplette Syntaxunterstützung für das Zend Framework. Das heißt, die IDE
kennt alle Klassen und Methoden und schlägt Ihnen diese bei der Code Comple-
tion vor. Damit aber nicht genug. Möchten Sie ein neues Projekt anlegen, so kön-
nen Sie dem Tool auch von vornherein mitteilen, dass Sie ein neues MVC-Projekt
auf Basis des Zend Frameworks anlegen wollen. Das Zend Studio generiert dann
den kompletten »Standard-Code«, damit Sie ihn nicht tippen müssen. Sie können
sich sicher vorstellen, dass Sie Ihren Code mit diesen Funktionalitäten deutlich
schneller entwickeln können. Arbeiten Sie professionell mit PHP, so sollten Sie
auf jeden Fall einen Blick auf Zend Studio for Eclipse werfen. Die Investition lohnt
sich.
Informationsquellen
Zwar hoffe ich als Autor natürlich, dass das Buch, das Sie gerade in Händen hal-
ten, die meisten Fragen zum Zend Framework beantworten wird. Aber ich weiß
natürlich, dass noch Fragen offenbleiben. Bestimmt werden Sie noch eine Frage
zu einem Paket haben, das hier nicht erläutert wird. Oder es gibt eine Änderung
14
e-bol.net
Einleitung
im Framework, die dazu führt, dass das Buch nicht mehr ganz aktuell ist. Wo
können Sie dann weitere Informationen erhalten?
Zum Ersten ist natürlich die Website des Zend Frameworks eine gute Anlauf-
stelle. Dort finden Sie eine sehr brauchbare Dokumentation vor. Als Program-
mierer werden Sie das Problem kennen, dass man nicht gerne dokumentiert.
Aber man muss wirklich sagen, dass das Manual des Zend Frameworks eine
rühmliche Ausnahme darstellt. Die Dokumentation finden Sie unter der Internet-
adresse http://framework. zend. com/manual/.
Sie sollten hierbei die englische Variante der Dokumentation bevorzugen, weil
sie in Teilen einfach aktueller ist, und sich bei anderen Sprachen bei der Überset-
zung schon mal ein Fehler einschleichen kann.
Da Programmierer lieber programmieren als schreiben, kann es auch sehr hilf-
reich sein, sich die Demos anzuschauen, die für einige Pakete vorhanden sind.
Diese finden Sie im Download-Archiv des Zend Frameworks im Ordner demos.
Auf der Website des Zend Frameworks sehen Sie oben in der Navigation auch
einen Link, der mit »Support« beschriftet ist. Hier finden Sie einige Möglichkei-
ten, wie Sie Unterstützung erhalten oder sich mit anderen Anwendern austau-
schen können. Sehr interessant ist sicherlich auch das Wiki zum Zend Frame-
work, auf dem Sie eine ganze Menge an Hintergrundinformationen finden. Sie
erreichen es unter http://framework.zend.com/wiki/display/ZFDEV/Home.
Im Support-Bereich finden Sie einen Link, unter dem Sie sich für diverse Mailing-
Listen anmelden können. Diese Listen sind, auch wenn sie englischsprachig sind,
sehr zu empfehlen. Zum einen sind diese Listen wirklich sehr aktiv und werden
von vielen Benutzern abonniert. Zum anderen sind auch viele Entwickler der
Pakete sehr aktiv. Das heißt, Sie bekommen oft sehr schnell eine qualifizierte
Antwort. Es lohnt sich also, die Listen zu abonnieren. Sollten Sie eine Frage
haben, sollten Sie aber zuvor vielleicht das Archiv der Mailing-Listen durchfors-
ten, ob die Frage nicht schon einmal gestellt wurde.
Auf der Support-Seite finden Sie zudem Links zu mehreren Blogs. Auch wenn Sie
nach dem Zend Framework googeln, finden Sie schnell einige Blogs sowie auch
Beiträge in Foren. Bitte genießen Sie solche Informationen immer mit Vorsicht.
Gerade in der Zeit vor der Version 1.0 des Zend Frameworks haben sich noch
viele Änderungen ergeben, die dazu führen, dass die Informationen in den Blogs
veraltet sind. Sofern Sie eine solche Informationsquelle nutzen, so prüfen Sie
bitte zunächst, wie alt der Beitrag ist. Gleiches gilt auch für viele Tutorials, die Sie
in verschiedenster Form im Internet finden.
15
e-bol.net
Einleitung
Ein weiterer guter Anlaufpunkt ist die Website der Firma Zend selbst, die Sie
unter www.zend.de erreichen. Dort finden Sie neben einigen Artikeln und Tuto-
rials vor allem einige Webinare. Bei Webinaren handelt es sich um Präsentatio-
nen bzw. Schulungen, die Sie im Browser betrachten können. Teilweise sind sie
aufgezeichnet, sodass Sie sie jederzeit betrachten können, teilweise sind sie aber
auch live. Ich denke, dass Webinare einen guten und vor allem entspannten Ein-
stieg in ein Thema liefern können. Daher möchte ich sie Ihnen empfehlen.
Neben den Angeboten, die in engem Zusammenhang mit Zend stehen oder von
Zend stammen, gibt es natürlich noch andere Informationsquellen im Web. Inte-
ressant ist sicher die Website ZFTutorials, die Sie unter der Adressse
http://www.zfiutorials.com erreichen. Hier werden Tutorials rund um das Zend
Framework gesammelt. Auch hier gilt, dass die Daten veraltet ein können.
Eine zweite Website, die ich Ihnen empfehlen möchte, ist das deutschsprachige
Zend Framework-Forum, das Sie unter http://www.z_fiorum.de finden. Hier sind
sehr engagierte Mitglieder anzutreffen, die teilweise auch zu den Entwicklern des
Zend Frameworks gehören, wodurch eine qualitativ hochwertige Hilfe sicherge-
stellt ist.
Neben den bisher vorgestellten Informationsquellen gibt es noch zwei Anlauf-
stellen, die ich ebenfalls für sehr wichtig halte. Die erste ist der Zend Framework
Issue Tracker. Dabei handelt es sich um das System, das für das Bug-Reporting
genutzt wird. Wenn Sie also einmal ein Problem haben, dann kann es sich durch-
aus lohnen, hier nachzuschauen, ob es sich eventuell um einen bekannten Bug
handelt. Sollten Sie selbst einmal einen Bug finden, wäre es sehr nett, wenn Sie
ihn hier eintragen und somit helfen, die Qualität des Zend Frameworks weiter zu
verbessern.
Eine weitere interessante Webadresse ist die folgende: http://framework.zend.
com/changelog. Dabei handelt es sich um das Changelog zum Zend Framework.
Hier finden Sie Informationen zu den Veränderungen innerhalb der einzelnen
Versionen. Wenn Sie eine neue Version von Zend Framework installieren, so
schauen Sie bitte dort nach, was sich geändert hat. Das Zend Framework ist noch
jung und ein sehr »lebendiges« System. Es kann also durchaus einmal passieren,
dass sich das Verhalten einer Methode oder eine API ändert.
Als letzten Punkt möchte ich auf die Website http://www.zfibuch.de verweisen.
Mit dieser Website möchte ich Sie als Leser ein wenig unterstützen. Sie finden
dort Informationen zum Zend Framework und Ergänzungen sowie Korrekturen
zu diesem Buch.
16
e-bol.net
Einleitung
Installation
Das Zend Framework zu installieren, ist erfreulich einfach. Systemseitig gibt es
eigentlich nur die Voraussetzung, dass mindestens PHP 5.1.4 vorhanden sein
muss, wobei allerdings die Version 5.2.3 empfohlen wird. Ist diese Vorausset-
zung erfüllt, müssen Sie einfach nur das Archiv, das Sie unter http://framework.
zend.com finden, herunterladen und entpacken. In dem dabei entstehenden Ord-
ner finden Sie ein Verzeichnis namens library und darin ein Verzeichnis namens
Zend. In diesem Ordner liegt alles, was Sie benötigen. Sie können den Ordner
library oder auch nur den Ordner Zend an eine beliebige Stelle auf Ihrem Server
kopieren, falls Sie einen eigenen Server nutzen. Wenn möglich sollten Sie den
Ordner nicht unterhalb des DocumentRoot-Verzeichnisses des Webservers able-
gen. Der Webserver benötigt nur Leserechte auf das Verzeichnis; aber es gibt kei-
nen Grund, warum das Verzeichnis von außen ansprechbar sein sollte.
Sollte Ihnen nur gemieteter Webspace zur Verfügung stehen, ist das auch kein
Problem. In dem Fall können Sie den Ordner auch mit im Webspace ablegen. Sie
sollten den Ordner allerdings so sichern, dass er nicht von außen aufgerufen wer-
den kann.
Nachdem Sie den Ordner bereitgestellt haben, müssen Sie nur noch dafür sorgen,
dass der Ordner oberhalb des Ordners Zend mit im Include-Path liegt. Haben Sie
das Verzeichnis library kopiert müsste dieser Ordner also in den Include-Path.
Bei dem Include-Path handelt es sich um ein oder mehrere Verzeichnisse, die
PHP automatisch durchsucht, wenn Sie eine Datei mit requi re o.Ä. einbinden.
Um den Ordner mit im Include-Path unterzubringen, gibt es verschiedene Mög-
lichkeiten. Die beste Variante ist sicherlich, die Konfigurationsdatei php.ini ent-
sprechend zu editieren. Nutzen Sie einen eigenen Server, finden Sie diese Datei
meist im Ordner/etc. Sollten Sie für die Entwicklung auf Xampp, MAMP o.Ä.
setzen, so müssen Sie in den entsprechenden Unterverzeichnissen nach der Datei
suchen.
In der Datei php.ini finden Sie eine Direktive namens include_path. Hier könnte
beispielsweise
include_path = "usr/share/php"
stehen oder Ähnliches. Wenn Sie den Ordner Zend beispielsweise nach
/usr/share kopiert haben, müssten Sie diesen Pfad hier ergänzen. Das heißt, die
Direktive müsste danach (zumindest unter einem UNIX-Betriebssystem) so ausse-
hen:
include_path = /usr/share/php:/usr/share"
17
e-bol.net
Einleitung
Nutzen Sie Windows und haben Sie den Ordner unterhalb von d:\Webentwick-
lung abgelegt, müssten Sie die ursprünglich vorhandene Angabe
include_path = ".;c:\php\includes"
so ergänzen:
include_path = ".;c:\php\includes:d:\Webentwicklung"
Was aber, wenn Sie keinen Zugriff auf die Datei php.ini haben? Die einfachste
Variante ist dann sicher, dass Sie den Pfad direkt in der PHP-Datei festlegen, die
ausgeführt wird. Würde sich der Ordner Zend unterhalb von /usr/share befin-
den, könnten Sie die folgenden Zeilen am Anfang eines jeden PHP-Scripts ergän-
zen:
Spath = '/usr/share';
set_include_path(get_include_path() . PATH_SEPARATOR . Spath);
Dabei würde es sich natürlich anbieten, die Zeilen in eine Datei auszulagern, die
dann immer mit requi re_once eingebunden wird. Mit diesen Zeilen wird der
aktuell gesetzte Pfad ausgelesen, dann um den zusätzlichen Pfad ergänzt und das
Ganze wieder gespeichert.
Eine weitere Möglichkeit ist, dass Sie den Pfad mit einer ,/itaccess-Datei setzen,
die Sie in dem Verzeichnis ablegen, in dem auch die Applikation liegt. In dieser
können Sie dann mit
php_value include_path Zusr/1ocal/Iib/php;/usr/share"
den Pfad setzen. Wichtig ist, dass Sie vorher mithilfe von get_incl ude_path()
oder phpi nfo() die aktuellen Pfadeinstellungen auslesen und mit in die Zeile ein-
fügen. Andernfalls würden die anderen Pfade verloren gehen.
Danach können Sie sofort loslegen und das Zend Framework nutzen. Ist Ihnen
das alles zu aufwändig, dann können Sie auch einfach zu Zend Core greifen.
Dabei handelt es sich um einen Apache Webserver der ein fertig konfiguriertes
PHP, inklusive Zend Framework, mitbringt.
Allgemeines zu diesem Buch
Es gibt ein paar Punkte, die Sie zu diesem Buch wissen sollten, um Dinge besser
verstehen zu können. Der erste wichtige Punkt ist sicher, dass die meisten Bei-
spiele nicht auf dem Model View Controller-Pattern basieren. Warum das so ist,
fragen Sie? Nun, zum Ersten wäre es einfach zu komplex gewesen, die Pakete zu
erläutern und dabei auch ständig die Funktionalitäten mit zu implementieren,
18
e-bol.net
Einleitung
die für ein MVC genutzt werden. Der zweite Punkt ist, dass man vielleicht nicht
jede Anwendung mit einem MVC erstellen möchte. Wollen Sie beispielsweise
nur Teile des Zend Frameworks in eine Applikation integrieren, ist es sicher sehr
hilfreich, dass die Pakete standalone beschrieben sind. Das ist auch der Grund,
warum Sie nur sehr selten Beispiele finden, in denen zwei Klassen gemischt wer-
den. Das ist nur in wenigen Fällen gegeben, beispielsweise wenn ein Datenbank-
zugriff mit Zend_Db erfolgt oder ein Datum mit Zend_Date konvertiert wird.
Trotzdem finden Sie natürlich ein ausführliches Kapitel zum Aufbau und zur Nut-
zung des MVC-Patterns.
Ein weiterer Punkt, auf den ich hinweisen möchte, ist das Exception Handling.
Hier mag man zuerst den Eindruck haben, dass ich geschlampt habe, aber dem ist
nicht so. Ich habe bei den meisten Listings sehr bewusst darauf verzichtet, mit
try-catch-Blöcken zu arbeiten. Der Grund dafür ist, dass dies bei der Vielzahl
der Listings sehr viel Platz in Anspruch genommen hätte. Dennoch finden Sie an
Stellen, an denen ich es für wichtig gehalten habe, auch entsprechende Blöcke.
Wichtig ist in diesem Zusammenhang, dass Sie auf jeden Fall try-catch-Blöcke
ergänzen sollten, sofern Sie Beispiele aus diesem Buch in Ihren Anwendungen
nutzen möchten.
In diesem Zusammenhang ist auch zu beachten, dass die meisten Beispiele darauf
ausgelegt sind, Dinge zu erklären. Sie sind daher nicht immer elegant oder gar
performant.
Wahrscheinlich arbeiten die meisten von Ihnen nach wie vor im ISO-8859-1/-15-
Zeichensatz. Daher sind die Listings alle auf diesen ISO-Zeichensatz ausgelegt.
Trotzdem wird in vielen Zusammenhängen der UTF-8-Zeichensatz benötigt. Um
das zu verdeutlichen, wird in diesen Listings immer ein UTF-8-Header mitge-
schickt, und die Textdaten werden meist manuell mithilfe von utf8_encode()
und utf8_decode() codiert bzw. decodiert.
Falls Sie einen Fehler im Buch finden sollten, so würde ich mich freuen, wenn Sie
mir eine kurze E-Mail an die E-Mail-Adresse zf-buch@netviser.de schicken wür-
den.
Allgemeines zur Nutzung
Das Zend Framework ist nicht nur einfach zu installieren, sondern auch einfach
zu nutzen. Außerdem ist es sehr klar strukturiert.
19
e-bol.net
Einleitung
Struktur des Zend Frameworks
Das Zend Framework ist klar strukturiert. Am Namen der Klasse können Sie stets
sofort erkennen, wo sich eine Datei befindet. Wollen Sie beispielsweise die
Klasse Zend_Currency nutzen, so lautet die dazugehörige Klassen-Datei
Zend/Currency.php. Jede Klasse besitzt eine eigene Datei, die Sie inkludieren
bzw. laden können. Benötigt eine Klasse weitere Dateien, was fast immer der Fall
ist, finden Sie diese in einem Unterverzeichnis, das dem Namen der Klasse ent-
spricht. So benötigt beispielsweise Zend_Currency noch die Klasse Zend_
Currency_Exception. Diese ist somit in der Datei Zend/Currency/Exception.php
deklariert. Grundsätzlich gilt also, dass Sie den Namen der Klasse nehmen, die
Unterstriche durch Slashes ersetzen und die Endung .php anhängen, um den
Namen der Datei zu erhalten.
Ein wenig schade ist, dass die meisten Klassen nicht nach Themenbereichen
gruppiert sind. Nur wenige Klassen, wie beispielsweise die Webservice-Klassen,
sind nach Themenbereichen zusammengefasst. Diese Klassen, wie Zend_
Service_Amazon oder Zend_Service_Yahoo, finden Sie im Unterverzeichnis
Zend/Service, wie Sie sicher schon vermutet haben.
Erwähnt werden sollte noch, dass es einige Klassen gibt, bei denen in einem der
Namensbestandteile ein Großbuchstabe vorkommt, wie beispielsweise bei Zend_
XmlRpc.
Laden der Klassen-Dateien
Sie können die Klassen-Dateien, die Sie benötigen, jederzeit mithilfe des Sprach-
konstrukts requi re_once einbinden. Dies ist gleichzeitig das Vorgehen, das in
diesem Buch an den meisten Stellen verwendet wird.
Allerdings können Sie auch die Klasse Zend_Loader nutzen. Zend_Loader bietet
eine Reihe von Möglichkeiten, um Dateien einzubinden. Um eine Klasse aus dem
Zend Framework zu laden, müssen Sie lediglich den Namen der Klasse an die sta-
tische Methode loadClassO übergeben. Diese lädt die Klassen-Datei und prüft
dabei automatisch, ob die Klasse in der Datei enthalten ist. Sollte die Klasse schon
vorhanden sein, so wird sie nicht doppelt eingebunden. Kommt es beim Laden
zu einem Problem, so wirft die Methode eine Exception.
Der ideale Weg, um eine Klasse aus dem Zend Framework zu laden, ist daher:
requi re_once(’Zend/Loader.php');
Zend_Loader::loadClass('Zend_Gdata_Calendar');
20
e-bol.net
Einleitung
Hiermit würde also automatisch die Datei Zend/Gdata/Calendar.php geladen.
Wie bereits erwähnt, habe ich in dem Buch üblicherweise mit requi re_once()
gearbeitet. Die Entscheidung für require_once() resultierte daraus, dass ich
hoffe, auf diesem Weg die Transparenz der Beispiele zu erhöhen. Dennoch
möchte ich Ihnen empfehlen, Zend_Loader:: 1 oadCl ass () zu nutzen, da Sie dann
auch in der Lage sind, ein sauberes Exception Handling zu nutzen.
Exception Handling
Wie schon erwähnt, sind die Beispiele in diesem Buch nicht immer mit einem
korrekten Exception Handling versehen. Genau genommen ist das sogar eher sel-
ten der Fall. Das sollte Sie aber keinesfalls davon abhalten, dies besser zu
machen.
Grundsätzlich gilt, dass alle Fehler immer in einer Exception resultieren. An kei-
ner Stelle geben die Methoden eine Fehlermeldung als Rückgabewert zurück.
Hier und da lässt sich sicher darüber streiten, was ein Fehler ist und was nicht.
Das heißt, einige Entwickler neigen auch dazu, bei kleineren Problemchen eine
Exception auszulösen, wohingegen andere Entwickler das nur bei echten Fehlern
tun. Daher sollten Sie immer damit rechnen, dass eine Exception auftreten kann.
Das Zend Framework ist auch bei den Exceptions sehr strukturiert: Jede Klasse
bringt ihre eigenen Exceptions mit. Somit können Sie sehr genau erkennen, an
welcher Stelle ein Fehler entstanden ist. Der Name der Exception-Klasse ergibt
sich (fast) immer aus dem Namen der Hauptklasse, die verwendet wird. Deren
Namen wird einfach nur ein _Exception angehängt. Einige wenige Klassen ver-
folgen hier allerdings einen anderen Ansatz, wie beispielsweise die Gdata-Klas-
sen.
Zuweilen wirft eine Klasse eine Exception, mit der man nicht gerechnet hat.
Wenn Sie also beispielsweise einen RSS-Feed mit Zend_Feed auslesen, wird im
Hintergrund die Klasse Zend_Http für den Verbindungsaufbau genutzt. Somit
kann es passieren, dass Sie eine Exception dieser Klasse erhalten, obwohl Sie
damit nicht gerechnet haben.
Vor diesem Hintergrund empfiehlt es sich immer, dass Sie zusätzlich zu einem
catch-Block für die Exceptions einer Klasse auf jeden Fall noch ein catch für die
Klasse Exception nutzen. Da alle Exceptions Kind-Klassen der Klasse Exception
sind, können Sie dann sicher sein, dass eine Exception, die nicht von einem vor-
hergehenden catch »gefangen« wurde, spätestens dann beachtet wird. Bezogen
auf die Verarbeitung eines RSS-Feeds könnte dies so aussehen:
21
e-bol.net
Einleitung
try
{
// Einlesen des RSS-Feeds
}
catch (Zend_Http_Exception $http_exception)
{
// Fehler beim Verbindungsaufbau verarbeiten
}
catch (Zend_Feed_Exception $feed_exception)
{
// Fehler bei der Verarbeitung der Daten
}
catch (Exception $al1gemeine_exception)
{
// Hier landen alle Exceptions mit dem man
// nicht gerechnet hatte
}
Man kann an dieser Stelle sicherlich darüber diskutieren ob es Sinn macht Aus-
nahmen zu fangen, die man nicht behandeln kann. Wollen Sie beispielsweise auf
einen anderen Server zugreifen und dieser ist nicht verfügbar, dann können Sie
daran meist auch nichts ändern. Meine persönliche Meinung ist aber, dass man
eine Ausnahme immer behandeln sollte. Eine nett formulierte Fehlermeldung
macht die meisten Benutzer glücklicher als eine »Unhandled Exception« Mel-
dung von PHP.
Performance
Es liegt leider in der Natur der Sache, dass die meisten Frameworks eine Anwen-
dung langsamer machen. Jede Software-Schicht, jede Abstraktion führt dazu,
dass mehr Klassen und Objekte Verwendung finden. Soll Code robust sein, so
müssen viele Abfragen für Eventualitäten enthalten sein. All das kostet Perfor-
mance. Auch das Zend Framework stellt da keine Ausnahme dar. Um solche Ein-
flüsse zu kompensieren empfiehlt es sich einen Opcode-Cache auf dem Produk-
tiv-Server zu installieren. Dabei handelt es sich um einen Cache, der fertig
übersetzten PHP-Code Zwischenspeichern kann. Somit können Sie den größten
Teil der Geschwindigkeitsverluste die durch die Analyse und Interpretation des
Codes entstehen kompensieren. In diesem Bereich gibt es eine ganze Menge
empfehlenswerter Lösungen wie eAccellerator, APC oder Zend Platform.
22
e-bol.net
Einleitung
Sonstige Besonderheiten
Sollten Sie noch nicht sicher im Umgang mit Objekten und Klassen sein, wäre
jetzt ein guter Zeitpunkt, sich in die Thematik einzuarbeiten. Das Zend Frame-
work nutzt durchweg modernste Entwicklungsansätze, sodass Interfaces, ab-
strakte Klassen und Ähnliches keine Fremdworte für Sie sein sollten. Zwar haben
Sie mit diesen Dingen nicht unbedingt immer direkt zu tun, aber es kann nicht
schaden, die Ideen dahinter verstanden zu haben.
Im Zusammenhang mit der Objektorientierung soll auch erwähnt werden, dass
das Zend Framework an den meisten Stellen auf ein »Fluent Interface« setzt. Bei
diesem »fließenden« Interface liefern Methoden, die keinen anderen Wert
zurückgeben müssen, immer dasjenige Objekt zurück, aus dem heraus sie aufge-
rufen wurden. Dies hat zur Folge, dass Sie mehrere Methoden direkt hinterein-
ander aufrufen können. War Ihnen das zu abstrakt? Betrachten Sie hierzu die fol-
gende Klasse:
dass Rechner
{
private $_divident;
private $_divisor;
public function setzeWerte ($1, $r)
{
if ($r == 0)
throw new Exception("Division durch Null");
1
$this->_divident = $1;
$this->_divisor = $r;
return $this;
public function dividieret)
{
return ($this->_divident / $this->_divisor);
}
Die Methode setzeWertet) muss keinen Wert zurückgeben, da hier nur Parame-
ter für die Berechnung gesetzt werden. In einem konventionellen, prozeduralen
Ansatz würde die Methode sicher true zurückgeben, falls die Zuweisung erfolg-
reich war, und fal se, wenn es zu einem Problem kam. Da das Zend Framework
aber mit Exceptions arbeitet, ist das nicht nötig. Bei einem Problem wird eine
23
e-bol.net
Einleitung
Exception geworfen und andernfalls $thi s zurückgegeben. Das hat zur Folge,
dass Sie die Klasse wie folgt nutzen können:
Srechner = new Rechner!):
echo $ rechner- >setzeWerted, 3 )->di vidiere!);
In vielen Fällen können Sie so eine recht stattliche Anzahl von Methoden mitein-
ander verknüpfen. Das kann die Lesbarkeit verbessern, kann aber auch schnell
verwirren. Überlegen Sie also gut, wie viele Methodenaufrufe Sie miteinander
verknüpfen wollen.
Ein weiterer Punkt, auf den ich hinweisen möchte, ist die Standard PHP Library
(SPL). Sie ist ein PHP-Feature, das mit PHP 5 eingeführt wurde, und meiner
Ansicht nach leider viel zu wenig Beachtung findet. Die SPL definiert eine ganze
Reihe von Interfaces, welche für eigene Klassen genutzt werden können. In vie-
len Klassen des Zend Frameworks finden Sie eine Implementation des Interfaces
Iterator. Eine Klasse, die dieses Interface implementiert, kann beispielsweise
direkt in einer foreach-Schleife genutzt werden. Enthält ein Objekt zum Beispiel
verschiedene andere Objekte, könnte die Schleife bei jeder Iteration eines dieser
Objekte auslesen.
Sollten Sie nicht mit der SPL vertraut sein, so würde ich Ihnen empfehlen, dass
Sie sich ein wenig mit ihr befassen. Eine Einleitung finden Sie in der PHP-Doku-
mentation unter http://www.php.net/spl bzw. unter http://www.php.net/~helly/
php/ext/spl. Insbesondere das Interface Iterator sollten Sie sich dort anschauen.
Hilfreich ist auch, wenn Sie verstehen, wozu die Interfaces ArrayAccess und
Recursi velterator sowie die Klasse Recursi velteratorlterator dienen.
e-bol.net
No one has ever drowned in sweat.
- Lou Holtz, Football Coach
1 Der Model View Controller
Das Kernstück des Zend Frameworks sind die Klassen zur Umsetzung des MVC-
Entwurfsmusters. Die Abkürzung MVC steht für Model View Controller und
beschreibt, wie eine Anwendung strukturiert werden kann. Das Zend Frame-
work bringt hierbei eine große Menge Funktionalitäten mit, die Ihnen die Erstel-
lung einer Applikation auf Basis eines MVC ermöglichen.
Zur Umsetzung eines MVC werden mehrere Klassen miteinander kombiniert,
sodass in diesem Kapitel - anders als in anderen Kapiteln - mehrere Klassen
gleichzeitig genutzt und erläutert werden.
Sie werden feststellen, dass das Thema recht komplex ist. Daher kann dieses
Kapitel nicht alles erläutern. Dennoch denke ich, dass die wichtigen Inhalte hier
abgebildet sind. Es kann aber auch nicht schaden, wenn Sie noch ein wenig in der
Dokumentation stöbern. Sie finden dort noch viele hilfreiche Dinge wie bei-
spielsweise die Helper.
Bevor ich auf den praktischen Teil eingehe, möchte ich zunächst die Theorie
erläutern.
1.1 Die Theorie des AAVC
Wie schon erwähnt, ist die Idee des Model View Controllers, eine Anwendung in
verschiedene Teile, nämlich das Model, den View und den Controller, aufzuglie-
dern.
Lassen Sie mich mit dem letzten Teil, nämlich dem Controller beginnen. Der
Controller ist derjenige Teil der Applikation, der auf Benutzereingaben reagiert,
die Eingaben verarbeitet und dafür sorgt, dass die korrekten Seiten dargestellt
werden.
Die eigentliche Darstellung der Daten wird vom View übernommen. Der View
ist hierbei als eine abstrakte Komponente zu verstehen, da er sich aus mehreren
25
e-bol.net
1 | Der Model View Controller
Teilen zusammensetzt. Neben der intern genutzten View-Klasse wird auch noch
ein Template, also eine Design-Vorlage, genutzt, die für die Darstellung der
Daten verwendet wird.
Der Dritte im Bunde, das Model, ist für die Datenhaltung und -Verwaltung ver-
antwortlich. Hierbei handelt es sich um eine Komponente, die dafür zuständig
ist, die genutzten Daten unter anderem zu lesen und zu speichern. Gerne wird
hierbei auch von einem Datenmodell gesprochen, wobei das vielleicht ein wenig
verwirrend ist, da es sich nicht um ein Modell im Sinne eines Entity-Relation-
ship-Modells, sondern um eine konkrete Klasse handelt.
In den meisten Fällen finden Sie hier auch die Geschäftslogik. Die Geschäftslogik
ist dafür zuständig Objekte und Vorgänge aus der realen Welt in die Programmie-
rung umzusetzen. Das heißt, im Fall eines Shops könnte sich hier eine Klasse fin-
den die dafür zuständig ist Bestellungen anzulegen und zu verwalten. Die
Geschäftslogik im Model anzusiedeln macht Sinn, da sie sehr eng mit den Daten
verknüpft ist. Beim Anlegen einer Bestellung müssen die Informationen bei-
spielsweise in eine Datenbank geschrieben werden. Die Business-Logik stellt also
einen weiteren Abstraktionslevel dar. Durch dieses Konzept haben Sie den Vor-
teil, dass der komplette Zugriff auf die Daten gekapselt ist. Im Controller müssen
Sie also nur noch die Eingaben des Benutzers entgegen nehmen und sie an die
Methoden der Model-Klassen übergeben. Durch diese klare Struktur wird der
Code deutlich wartbarer und übersichtlicher.
Sollten Sie sich nun fragen, wie man Model und Controller trennscharf definiert,
so ist das berechtigt. Die Idee hinter der Aufteilung ist, die Daten und Funktiona-
litäten zu kapseln. Das heißt, das Model muss so strukturiert sein, dass alles, was
die Speicherung und das Auslesen betrifft, hierin enthalten ist. Möchten Sie die
Daten in einer Datenbank abspeichern, so muss des Escapen von Sonderzeichen
ein Teil des Models sein. Auch Methoden zum Berechnen von Preisen, der Ver-
waltung von Lagerbeständen und Ähnliches sind hier anzusiedeln, da diese Funk-
tionalitäten zur Geschäftslogik gehören.
Die Aufbereitung der Daten für die Darstellung hingegen ist Teil des Controllers.
1.2 Die Praxis des AAVC
Eine Applikation auf Basis eines MVC umzusetzen, erscheint auf den ersten Blick
ein wenig komplex, aber keine Angst, so schlimm ist's nicht.
Eine MVC-Applikation besitzt immer einen zentralen Einstiegspunkt. Es gibt also
exakt eine Datei, die bei jedem Aufruf angesprochen wird. Hierbei handelt es sich
26
e-bol.net
Die Praxis des MVC | 1.2
um den Front-Controller, der auch Bootstrap-File genannt wird. Bei genauer
Betrachtung ist die Bezeichnung Front Controller vielleicht ein wenig verwirrend.
Die Datei bietet die Funktionalität eines Front-Controllers, nutzt aber gleichzeitig
auch ein Objekt der Klasse Zend_Cont rol l er_Front. Da Datei und Objekt sehr eng
miteinander verknüpft sind, kann man mit dieser kleinen Ungenauigkeit leben.
Die Datei ist allerdings nicht so komplex, wie Sie vielleicht gerade befürchten. Der
Front Controller ist im Endeffekt nur dafür zuständig, die Anfrage entgegenzuneh-
men und sie an einen »Action-Controller« weiterzugeben, in dem die eigentliche
Logik enthalten ist. Sie können sich den Front Controller also wie einen Manager
vorstellen, der dafür zuständig ist, die Applikation zu verwalten. Rein technisch
ist das zwar nicht ganz korrekt, aber an dieser Stelle soll das so erst einmal reichen.
Wie Sie vielleicht schon ahnen, wird im Hintergrund eine Menge »Magie«
genutzt, welche die einzelnen Komponenten verknüpft. Das heißt, das Frame-
work ist in der Lage, automatisch die notwendigen und korrekten Komponenten
einzubinden. Dazu muss Ihre Applikation sich an eine vordefinierte Verzeich-
nisstruktur halten. Die Dokumentation des Zend Frameworks schlägt dafür diese
Verzeichnisstruktur vor:
application/
controllers/
models/
views/
scripts/
heipers/
fl1ters/
html /
Die Ordner html und application befinden sich in diesem Beispiel auf einer
Ebene, wobei nicht erforderlich ist. Es könnte sich beispielsweise auch um die
Ordner /var/application und /var/www/html handeln. Das Directory html ist das
eigentliche Document-Root-Verzeichnis des Servers. Hier würden normalerweise
die HTML- bzw. PHP-Dateien abgelegt. Der Ordner muss übrigens nicht unbe-
dingt html heißen. Er könnte genauso gut htdocs oder public_html heißen.
Der Ordner application sollte nicht direkt unterhalb des Document-Root-Ver-
zeichnisses des Servers liegen. Somit ist sichergestellt, dass niemand unberechtig-
ten Zugriff auf eine der Dateien hat. Der Name application ist übrigens frei
gewählt. Sie könnten hier auch einen beliebigen anderen Namen nutzen. Die
Namen der Verzeichnisse, die darunter genutzt werden, sollten Sie allerdings
übernehmen. Im Unterverzeichnis Controllers werden die Controller-Klassen
abgelegt, im Ordner views/scripts die Templates für die Darstellung und in
models die Dateien, die für den Datenzugriff erforderlich sind.
27
e-bol.net
1 | Der Model View Controller
Im Ordner html befindet sich unter Umständen nur eine einzige Datei, der Front
Controller. Abhängig von der Applikation wären hier natürlich noch Grafiken,
CSS-, JavaScript-Dateien oder Ähnliches zu finden.
Bevor ich auf die Controller eingehe, stellt sich noch die Frage, wie Sie sicherstel-
len können, dass die Anfragen auch wirklich alle beim Front Controller landen.
Um das zu gewährleisten, sollten Sie auf die Rewrite-Engine Ihres Webservers
zurückgreifen. Alle aktuellen Webserver unterstützen die Möglichkeit des URL-
Rewritings, wobei unter Umständen, wie beim IIS, zusätzliche Module notwen-
dig sind. Beim URL-Rewriting wird die URL, die ein Browser aufgerufen hat, ein-
fach so umgeschrieben, dass ein bestimmtes Ziel angesprochen wird. In diesem
Fall würde also jeder eingehende Aufruf auf die Datei index.php »umgebogen«.
Genau genommen handelt es sich nicht um jeden Aufruf. Grafiken, statische
HTML-Seiten, CSS-Dateien und alles andere, was nicht unmittelbar mit der Appli-
kation zu tun hat, kann direkt ausgeliefert werden.
Leider sind die Webserver alle unterschiedlich zu konfigurieren. Daher werde ich
hier nur auf den Apache-Webserver eingehen. Für andere Webserver ziehen Sie
bitte das Manual Ihres Webservers zu Rate.1
Im Fall von Apache können Sie die Rewrite-Rule entweder in der entsprechenden
Konfigurationsdatei Ihres Servers angeben oder Sie nutzen eine .htaccess-Datei,
was wahrscheinlich einfacher und flexibler ist. Der Inhalt der Datei könnte bei-
spielsweise so aussehen:
RewriteEngine on
RewriteRule I\.(js|ico|gif|jpg|png|ess)$ index.php
Diese beiden Zeilen müssten unter dem Namen .htaccess im Document-Root-Ver-
zeichnis des Servers gespeichert werden. Hiermit werden zwei Dinge definiert.
In der ersten Zeile wird die Rewrite-Engine des Servers eingeschaltet. Üblicher-
weise ist das kein Problem. Sollte das dafür notwendige Modul mod_rewrite
nicht verfügbar sein, müssten Sie es nachinstallieren oder sich mit Ihrem Admi-
nistrator bzw. Provider in Verbindung setzen.
In der zweiten Zeile finden Sie die eigentliche Regel, die für das Rewriting zustän-
dig ist. Im Endeffekt handelt es sich bei dem ersten Teil (! \. (js | i co | gi f |
jpg|png|css|css|htm)$) um einen regulären Ausdruck, der auf alle URLs
zutrifft, die nicht auf eine der enthaltenen Endungen enden. Sollten Sie noch
weitere Dateiformate nutzen, die nicht in der Liste enthalten sind, beispielsweise
1 Die Dokumentation zum Zend Framwork beinhaltet auch einige Informationen zu anderen
Webservern: http://framework.zend.com/manual/en/zend.controller.router.html
28
e-bol.net
Die Praxis des MVC | 1.2
RSS oder WSDL, so müssten Sie diese noch ergänzen. Der zweite Teil der Regel
(i ndex. p hp) ist das Ziel, auf das umgeleitet wird, also die Datei index.php.
Sollten Sie das MVC-Projekt nicht im Root-Verzeichnis des Servers entwickeln,
kann es passieren, dass Sie noch die Direktive RewriteBase ergänzen müssen.
Hinter RewriteBase geben Sie dann bitte den absoluten Pfad des Verzeichnisses
an, das Sie nutzen.
1.2.1 Der Front Controller
Somit ist nun sichergestellt, dass jede Anfrage beim Front Controller landet. Der
Front Controller kann im Prinzip sehr einfach aufgebaut sein. Diese beiden Zei-
len würden eigentlich schon reichen:
requi re_once 'Zend/Controller/Front.php’;
Zend_Controller_Front::run(’/var/www/applicati on/control l ers');
Das wäre eigentlich schon alles, was Sie benötigen. Der statischen Methode
run() wird in diesem Fall einfach nur der Pfad zum Verzeichnis des Controllers
übergeben. Der Rest ist Magie!
Allerdings würde ich Ihnen eine etwas umfangreichere Variante vorschlagen, da
Sie dann über bessere Möglichkeiten zur Konfiguration verfügen. Diese Variante
könnte so aussehen:
requi re_once 'Zend/Controller/Front.php’;
// Controller-Instanz auslesen
$fc = Zend_Controller_Front::getlnstance():
// Verzeichns setzen
$fc->setControllerDirectoryi'/var/www/appl/controllers’):
// Nutzung von Views unterdrücken
$fc->setParam('noViewRenderer’, true):
// So einstellen, dass Ausnahmen geworfen werden
$fc->throwExceptions(true);
// Error-Handler ausschalten
$fc->setParam('noErrorHandler’, true):
$fc->di spatch();
Listing 1.1 Ein einfacher Front Controller
Diese Einstellungen sehen auf den ersten Blick vielleicht ein wenig komplex aus,
dafür bieten sie aber ein hohes Maß an Flexibilität.
Durch den Aufruf der Methode Zend_Control l er_Front: :getlnstance(), die
übrigens keine Parameter akzeptiert, wird die aktuelle Instanz des Front Control-
29
e-bol.net
1 | Der Model View Controller
lers ausgelesen, der als Singleton-Pattern implementiert ist. Damit stellt das Fra-
mework sicher, dass immer nur ein Objekt des Front Controllers abgeleitet wird.
Da der Front Controller sich darum kümmern soll, dass der korrekte Action Con-
troller eingebunden wird, muss er wissen, wo die Action Controller zu finden
sind. Sie teilen ihm dies mittels der Methode setControl l erDi rectory() mit.
Um die ersten Beispiele möglichst einfach zu halten, wird die Nutzung eines
Views mithilfe von $fc->setParam( ’noViewRenderer', true) unterdrückt. Die
Methode setParam() kann natürlich noch einiges mehr für Sie tun, wie Sie spä-
ter noch sehen werden.
Damit wäre die grundsätzliche Konfiguration abgeschlossen. Gerade für die Ent-
wicklung ist es sinnvoll, noch einige zusätzliche Einstellungen festzulegen.
Da wäre zunächst der Umgang mit Exceptions. Die Model View Controller-Im-
plementation des Zend Frameworks ist darauf ausgelegt, möglichst robust zu sein.
Das heißt, dass sie eine Exception erst einmal fängt, um ein möglichst gutes
Error-Handling zu ermöglichen. Allerdings führt das bei der Entwicklung und
dem Debugging dazu, dass Fehler unter Umständen schwer zu finden sind.
Daher ist es hilfreich, dem Controller mitzuteilen, dass Exceptions tatsächlich
geworfen werden sollen, was mit $fc->throwExceptions(true) geschieht. In
der nächsten Zeile wird mithilfe von $fc->setParam('noErrorHandler’, true)
sichergestellt, dass das interne Error-Handling unterdrückt wird. Die eigentliche
Verarbeitung des Aufrufs übernimmt die Methode di spatch(). Etwas verallge-
meinert formuliert bindet sie nun den entsprechenden Action Controller ein.
1.2.2 Der Action Controller
Nachdem Sie nun einen Überblick über den Front Controller erhalten haben,
stellt sich die Frage, wie der Action Controller aufgebaut sein muss. Im nächsten
Listing sehen Sie eine Minimalversion eines Action Controllers:
requi re_once 'Zend/Controller/Action.php';
dass Indexcontroller extends Zend_Control ler_Action
(
public function indexAction()
{
echo "Hallo, ich bin die Index-Action
aus dem Index-Controller";
}
Listing 1.2 Der erste Action Controller
30
e-bol.net
Die Praxis des MVC
1.2
Die Namen der Klasse und der Methode habe ich mir übrigens nicht einfach nur
ausgedacht. Warum beide mit Index bzw. Index anfangen, erfahren Sie gleich
noch. An dieser Stelle möchte ich lediglich darauf hinweisen, dass der Name
einer Controller-Klasse immer mit einem Großbuchstaben anfangen und auf
Controller enden muss. Des Weiteren muss ein Action Controller immer die
Klasse Zend_Control 1 er_Action erweitern.
Die Namen der Methoden, die von außen ansprechbar sein sollen, müssen mit
einem Kleinbuchstaben beginnen und auf Action enden.
Dieser Controller muss nun in einer Datei namens IndexController.php im Con-
troller-Verzeichnis abgelegt werden, das dem Front Controller mit der Methode
setControl 1 erDi rectory() mitgeteilt wurde. Der Front Controller bindet auto-
matisch die Datei ein, leitet ein Objekt der Klasse ab und ruft die Methode 1ndex-
Actionl) auf. Das Ergebnis sehen Sie in Abbildung 1.1.
Abbildung 1.1 Die erste Ausgabe der Anwendung
Wie Sie sehen, wird der Index-Controller aufgerufen, ohne dass ich dem Front
Controller mitgeteilt habe, dass er dies tun soll. Aber warum ist das so? Solange
Sie dem Front Controller nichts anderes mitteilen, ruft er automatisch die Index-
Action im Index-Controller auf. Auch die dazugehörige Datei wird dementspre-
chend automatisch eingebunden, sodass Sie sich um nichts kümmern müssen.
Diese gesamte Funktionalität kann nur dann gewährleistet werden, wenn alle
Komponenten korrekt benannt sind.
Natürlich werden Sie nicht nur den Index-Controller aufrufen wollen. Dann
müssten Sie ja die komplette Logik in den Index-Controller einbauen, was ihn
ziemlich komplex werden ließe.
31
e-bol.net
1 | Der Model View Controller
Daher hier ein zweiter Controller - nennen wir ihn FooControl l er:
requi re_once 'Zend/Controller/Action.php';
dass FooControl ler extends Zend_Control ler_Action
{
public function indexAction()
{
echo "Hier ist die indexAction aus dem FooController";
}
Dieser Controller muss in einer Datei mit dem Namen FooController.php gespei-
chert werden. Um ihn anzusprechen, können Sie den Front Controller auf zwei
Wegen aufrufen:
http://127.O.O.l/Foo
http://! 27.0.0.1/index.php/Foo
Die Angabe 127.0.0.1 müssten Sie eventuell durch den Namen oder die IP-
Adresse des Rechners ersetzen, den Sie ansprechen wollen. In allen Beispielen
dieses Kapitels werde ich immer die 127.0.0.1 nutzen.
Über beide URLs wird nun dem Action Controller mitgeteilt, dass der Controller
FooController genutzt werden soll. Sollte die erste Variante bei Ihnen in einem
»404-Fehler« resultieren, dann liegt das daran, dass das URL-Rewriting bei Ihnen
nicht funktioniert. Der zweite Aufruf müsste aber funktionieren. Sollten beide
Varianten eine Exception zur Folge haben, prüfen Sie bitte, ob die Pfade alle kor-
rekt gesetzt und die Klasse und die Methode korrekt benannt sind.
Falls Sie noch eine zusätzliche Action ergänzen, die beispielsweise ba rActi on hei-
ßen könnte, haben Sie schon deutlich mehr Möglichkeiten:
public function barAction()
{
echo "Hier ist die barAction aus dem FooController";
1
Diese neue Action können Sie folgendermaßen ansprechen:
h ttp://127.0.0.1 /Foo/bar
http://127.0.0.1/index.php/Foo/bar
Wobei Sie natürlich auch FOO.foo oder f00 schreiben könnten. Das Zend Fra-
mework normalisiert die Schreibweise so, dass der Aufruf immer beim korrekten
Controller landet.
32
e-bol.net
Die Praxis des MVC | 1.2
Wie Sie sich nun sicher schon denken, ist der erste Parameter, der nach dem
Slash übergeben wird, der Name des Controllers, der genutzt werden soll, und
der zweite ist der Name der Action, die ausgeführt werden soll. Geben Sie weder
Controller nach Action an, wird beides implizit durch Index ersetzt, sodass die
Anfrage beim Indexcontroller landet und an die indexAction weitergereicht
wird. Geben Sie nur einen Controller an, wird dieser Controller genutzt und
i ndexActi on in diesem Controller angesprochen. Daher sollten Sie aufjeden Fall
immer eine Methode namens i ndexActi on in jedem Controller haben. Natürlich
können Sie den IndexControl 1er auch explizit ansprechen, indem Sie den Front
Controller so aufrufen:
http://! 2 7.0.0.1/index/index
http'.//l 27.0.0.1/index.php/index/index
Gar nicht so schwer, oder? Bei sehr großen Anwendungen können Sie die Con-
troller auch noch in Module untergliedern, um Ihre Anwendungen noch weiter
zu strukturieren. Sie können also mehrere Verzeichnisse für die Controller nut-
zen.
In dem Fall übergeben Sie über die URL als ersten Wert den Namen des Moduls,
gefolgt von dem Namen des Controllers und der Action. Auch in diesem Fall set-
zen Sie die Namen der Verzeichnisse mit der Methode setControl1erDirec-
toryO. Hier bekommt sie allerdings ein Array übergeben und nicht nur den
Namen eines Verzeichnisses:
$fc->setControl1erDi rectoryl
array(
’default' => '/var/www/appl/control1ers',
’cms' => '/var/www/cms/control1ers’ ,
’admin' => '/var/www/admin/control1ers'
));
Hierbei ist wichtig, dass es einen Eintrag namens defaul t gibt. Das Verzeichnis,
das damit angegeben wird, ist das Default-Verzeichnis, das beispielsweise ange-
sprochen wird, wenn Sie das Modul nicht explizit angeben. Bei den Verzeichnis-
sen ist zu beachten, dass die Controller immer in einem Verzeichnis namens Con-
trollers liegen sollten. Anders formuliert: Sie können die Verzeichnisebenen
dazwischen (appl, cms, admi n) nutzen, um Ihre Anwendung zu strukturieren.
Das Interessante dabei ist, dass der Front Controller erkennt, ob Sie den Modul-
namen angegeben haben oder nicht. Die folgenden URLs würden alle den Index-
Controller im Default-Modul ansprechen:
33
e-bol.net
1 | Der Model View Controller
http://12 7.0.0.1 /default/index/index
http://l 2 7.0.0.1/index
http://127.0.0A
Wollen Sie ein anderes Modul ansprechen, müssen Sie den Namen des Moduls
explizit angeben:
http://12 7.0.0.1/cms/
http://l 2 7.0.0.1/cms/index
http://12 7.0.0.1/cms/index/index
Das Ganze funktioniert so, dass der Front Controller die übergebene URL analy-
siert und schaut, ob als erster Parameter der Name eines Moduls übergeben
wurde. Ist das nicht der Fall, wird automatisch das Default-Modul genutzt. Diese
Vorgehensweise hat natürlich zur Folge, dass Sie im Default-Modul keinen Con-
troller nutzen sollten, der den selben Namen hat wie ein Modul. Andernfalls
könnte der Aufruf zweideutig sein, oder Sie müssen sicherstellen, dass dieser
Controller nur mit vorangestelltem Modulnamen aufgerufen wird.
In allen Beispielen werde ich allerdings auf die Nutzung von Modulen verzichten
und immer nur mit einem Controller-Verzeichnis arbeiten.
1.2.3 Einbinden eines Views
Nachdem Sie nun wissen, wie der Controller eingebunden wird, stellt sich die
interessante Frage, wie die View-Komponente ins Spiel kommt. Dazu müssen Sie
im Front Controller zunächst die Nutzung der Views aktivieren. Ändern Sie dazu
die Zeile, in der der Parameter noViewRenderer gesetzt wird, so ab, dass ihr der
Wert fal se zugewiesen wird:
$fc->setParam('noViewRenderer' , false):
Versuchen Sie jetzt einen der Controller aufzurufen, resultiert das in einer Zend_
View_Exception, die Ihnen mitteilt, dass das entsprechende Template nicht
gefunden wurde.
Das bedeutet, dass Sie nun ein Template anlegen müssen. Bei den Templates han-
delt es sich um ganz normale HTML-Seiten, die auch PHP enthalten dürfen. Bei
genauerer Betrachtung muss hier sogar ein wenig PHP enthalten sein. Ein solches
Template wird direkt vom Front Controller, genau genommen vom Dispatcher,
der im Hintergrund die Arbeit erledigt, mit eingebunden. Das Template, auf dem
die folgenden Beispiele aufbauen, sieht so aus:
CDOCTYPE html PUBLIC " -//W3C//DTD HTML 4.01 Transitional//EN”
"http://www.w3.org/TR/html4/loose.dtd">
34
e-bol.net
Die Praxis des MVC | 1.2
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;
charset=IS0-8859-l">
<title>Mein erstes Tempiate</title>
</head>
<body>
<?php
?>
</body>
</html>
Listing 1.3 Grundlegendes Template
Wie Sie sehen, ist es wirklich eine ganz normale HTML-Datei. Der PHP-Abschnitt
in der Mitte wird gleich noch mit Inhalt gefüllt. Abgespeichert werden muss das
Template im Ordner views/scripts, der sich unterhalb des Verzeichnisses appl
befindet. Wie und wo genau Sie die Datei abspeichern, hängt davon ab, für wel-
chen Controller und welche Action sie gedacht ist. Standardmäßig geht das Zend
Framework davon aus, dass Sie für jeden Controller ein eigenes Unterverzeichnis
anlegen, und dass die Datei den Namen der Action bekommt, für die das Temp-
late zuständig ist. Wenn also das Template für den Indexcontroller und die
indexAction gedacht ist, so würde es mit dem Namen index.phtml im Ordner
index unterhalb von views/scripts gespeichert. Der komplette Pfad würde also
/var/www/appl/views/scripts/index/index.phtml lauten. Ein Template für die
barAction aus dem FooController würde also unter /var/www/appl/views/
scripts/foo/bar.phtml gespeichert. Aber keine Angst, Sie können ein Template
auch für mehrere Actions nutzen, sodass Sie nicht in einer Flut von Templates
ertrinken. Wie das funktioniert, erfahren Sie später.
Zwar können Sie die Seite jetzt schon aufrufen, aber es gibt noch ein kleines Pro-
blem. Das fällt allerdings erst dann auf, wenn Sie sich den Quelltext anschauen,
der im Browser ankommt. Dieser lautet folgendermaßen:
CDOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
Cmeta http-equiv="Content-Type” content="text/html;
charset=IS0-8859-l">
<title>Mein erstes Tempiate</title>
</head>
<body>
</body>
35
e-bol.net
1 | Der Model View Controller
</html>
Hallo, ich bin die Index-Action
aus dem Index-Controller
Grundsätzlich sieht das ja nicht schlecht aus, nur hätte der Text nicht hinter dem
schließenden </html>-Tag landen sollen, sondern im Body der Seite. Das Pro-
blem ist, dass das System natürlich nicht wissen kann, wo die Ausgabe landen
soll. Eine Ausgabe mit echo oder print ist daher keine gute Idee, falls Sie die
Daten in einem Template ausgeben wollen.
Zu Beginn des Kapitels hatte ich angedeutet, dass der View im MVC aus mehre-
ren Komponenten besteht. Nun kennen Sie das Template, aber im Hintergrund
gibt es auch noch ein Objekt der Klasse Zend_Vi ew, das für die Ausgabe zuständig
ist. Mithilfe dieses Objekts können die Action Controller auch mit dem Template
»kommunizieren«, ihm also Daten übergeben.
Und das geht so: Innerhalb der Action, die ausgeführt wird, lesen Sie eine Refe-
renz auf das Zend_Vi ew-Objekt aus. In diesem können Sie nun Eigenschaften mit
Werten belegen. Dieses Objekt steht dann auch im Template zur Verfügung und
kann über $this angesprochen werden. Die Methode indexAction im Index-
Controller müssten Sie folgendermaßen umstellen:
public function indexAction!)
{
$view = $this->initView();
$view->ausgabe = "Hallo, ich bin die Index-Action
aus dem Index-Controller";
}
Listing 1.4 Zuweisen eines Textes
Der Action Controller legt das View-Objekt in der Eigenschaft view ab. Die
Methode i ni tVi ew() liest diese Eigenschaft aus und gibt Ihnen eine Referenz auf
das View-Objekt zurück. Zwar könnten Sie auch direkt auf die Eigenschaft zugrei-
fen, aber die Nutzung von i ni tVi ew() hat den Vorteil, dass die Eigenschaft kor-
rekt initialisiert wird, sofern dort noch kein Objekt der Klasse Zend_Vi ew enthal-
ten ist.
Dem so ausgelesenen Objekt können Sie dann direkt die Werte zuweisen, wie Sie
das in diesem Beispiel sehen. Der Name der Eigenschaft ist frei gewählt. Alterna-
tiv können Sie auch eine andere Syntax nutzen, die für Außenstehende vielleicht
besser zu verstehen ist. Und zwar haben Sie die Möglichkeit, einer Eigenschaft
mithilfe der Methode assignt) einen Wert zuzuweisen. Bezogen auf dieses Bei-
spiel würde das so aussehen:
36
e-bol.net
Die Praxis des MVC | 1.2
$view = $this->initView();
$view->assign(’ausgabe', 'Hallo, ich bin die Index Action
aus dem Index Controller'):
Wählen Sie einfach die Variante, die Ihnen am besten gefällt. In beiden Fällen
geschieht dasselbe. Wobei die Aussage nicht ganz richtig ist. Der Methode
assi gn() können Sie auch ein assoziatives Array übergeben. In dem Fall werden
die Schlüssel des Arrays als neue Eigenschaften genutzt, denen dann die dazuge-
hörigen Werte zugewiesen werden.
In beiden Fällen gilt allerdings, dass die Namen der Eigenschaften, die Sie verge-
ben, nicht mit einem Unterstrich (_) beginnen dürfen. Versuchen Sie einen sol-
chen Namen zu nutzen, resultiert das in einer Exception der Klasse Zend_Vi ew_
Exception. Der Hintergrund ist, dass die internen Eigenschaften alle mit einem
Unterstrich beginnen und keinesfalls überschrieben werden dürfen.
Im Template selbst könnten Sie den Inhalt der Eigenschaft einfach mit echo
$this ->ausgabe; ausgeben. Allerdings sollten Sie natürlich immer sicherstellen,
dass die Daten, die Sie ausgeben, nicht problematisch sein können. Das heißt, es
muss sichergestellt sein, dass kein HTML-Code enthalten ist, der dafür sorgen
könnte, dass die Darstellung leidet, oder dass der Browser, der die Daten anzeigt,
mit JavaScript kompromittiert wird. Dafür ist in der Klasse Zend_View die
Methode escape() deklariert. Die bessere Variante zur Ausgabe der Daten ist
also diese:
echo $this->escape($this->ausgabe);
Die Methode escapel) ersetzt HTML-Sonderzeichen (< , >, &, ") durch Entitäten.
Sollten die Daten schon escapet sein, weil sie beispielsweise aus Zend_Filter_
Input übernommen wurden, dürfen sie hier natürlich nicht noch einmal codiert
werden.
Damit haben Sie die erste kleine Implementation eines MVC umgesetzt.
1.2.4 Die Verarbeitungsschritte
Nachdem Sie nun einen guten Überblick haben, wie ein MVC funktioniert,
möchte ich einen kleinen Rückschritt machen und noch ein paar Worte dazu ver-
lieren, was im Hintergrund passiert.
Wie schon erwähnt, ist der Front Controller derjenige Teil, der sich um den
gesamten Ablauf der Verarbeitung kümmert. Das heißt, er nimmt die eingehende
Anfrage entgegen und sorgt dafür, sie an den Router weiterzugeben. Der Router
analysiert die Anfrage und prüft, welcher Action Controller zu nutzen ist. Mit
37
e-bol.net
1 | Der Model View Controller
dieser Information ausgestattet ist der Dispatcher dann der Nächste in der Reihe.
Er bindet den Action Controller ein und ruft die entsprechende Methode auf.
Danach wird die Antwort an den Client geschickt.
Damit alle Beteiligten miteinander kommunizieren können, werden die Anfrage
(der Request) und die Antwort (die Response) in eigenständigen Objekten abge-
legt. Auf das Request-Objekt kann von allen Komponenten zugegriffen werden,
wohingegen das Response-Objekt nur von einem Action Controller aus genutzt
werden kann.
Zugegebenermaßen ist das nur ein kurzer Abriss, wie die Verarbeitungsschritte
aussehen. Ich meine aber, dass diese Information erst einmal ausreichend ist, um
das meiste zu verstehen. Weitere Informationen dazu können Sie der Dokumen-
tation unter der Adresse http://framework.zend.com/manual/en/zend.controller.
basics.html entnehmen.
1.2.5 Übergabe von Werten
Die meisten Webanwendungen geben nicht nur Daten aus, sondern benötigen
auch Informationen vom Benutzer. Meist kann der Anwender Daten in ein For-
mular eingeben, die dann von der Anwendung verarbeitet werden. Dabei stellt
sich die Frage, wie Ihre Anwendung an diese Daten kommt.
Die einfachste Möglichkeit ist sicher, wenn Sie in altbekannter Weise direkt auf
$_GET, $_POST etc. zugreifen. Auch in Zeiten des Model View Controllers funkti-
oniert das ohne Probleme.
Allerdings sieht das Zend Framework an dieser Stelle auch eine elegantere Mög-
lichkeit vor.
Das Request-Objekt
Alle Daten, die mit der clientseitigen Anfrage im Zusammenhang stehen, werden
im Request-Objekt abgelegt. Dieses Request-Objekt ist im Action Controller
bekannt und kann mit der Methode getRequestO ausgelesen werden. Das
Objekt der Klasse Zend_Control l er_Request_Http stellt verschiedene Möglich-
keiten zur Verfügung, wie Sie auf die Daten zugreifen können, die mit dem
Request übertragen wurden. Die Methoden, deren Name immer mit get beginnt,
haben alle ein einheitliches Call-Interface. Sie können also alle auf dieselbe Art
und Weise aufgerufen werden.
38
e-bol.net
Die Praxis des MVC | 1.2
Wollen Sie einen Wert auslesen, der mit der Methode POST aus einem Formular
übergeben wurde, können Sie die Methode getPostO nutzen. Sie greift im
Hintergrund auf das superglobale Array $_POST zu und bekommt den Namen des
Schlüssels übergeben, den Sie auslesen wollen. Wollen Sie also auf $_POST [' name']
zugreifen, so würden Sie im Action Controller die folgenden Zeilen benötigen:
Srequest = $this->getRequest();
$name = $request->getPost(’name'):
Die Nutzung der Methode getPost() hat zwei Vorteile. Erstens nutzt getPostt)
- und alle anderen get-Methoden tun das auch - intern ein i sset(), sodass Sie
sich keine Gedanken machen müssen, ob ein bestimmter Wert vorhanden ist. Ist
ein Wert in dem entsprechenden superglobalen Array (in diesem Fall also $_POST)
nicht vorhanden, gibt die Methode NULL zurück.
Der zweite Vorteil ist, dass Sie der Methode einen Default-Wert übergeben kön-
nen, der zurückgegeben wird, falls der gesuchte Wert nicht im Array enthalten
ist. Die Zeile
$name = $request->getPost(’name', 'kein Name übergeben');
speichert in $name also entweder den Inhalt von $_POST[' name' ] oder, falls der
Schlüssel in dem Array nicht vorhanden ist, »kein Name übergeben«.
In Tabelle 1.1 finden Sie die verschiedenen Methoden, die für den Zugriff auf die
superglobalen Arrays deklariert sind.
Methode Superglobales Array
getPost() $_POST
getQuery() $_GET
getServer() $_SERVER
getCookie() $_COOKIE
getEnv() $_ENV
Tabelle 1.1 Methoden zum Auslesen von superglobalen Arrays
Für $_SESSION ist keine Zugriffsmethode deklariert, da zur Verwaltung von Ses-
sions die Klasse Zend_Session genutzt werden sollte.
Wenn Sie eine dieser Methoden aufrufen, ohne ihr einen Parameter zu überge-
ben, erhalten Sie übrigens das komplette superglobale Array zurück.
Eine besondere Rolle kommt noch der Methode getParam() zu. Sie liest einen
Parameter aus, welcher dem Action Controller übergeben wurde. Wenn Sie nun
fragen, was ein Parameter ist, dann kann ich nur antworten: Das kommt darauf
39
e-bol.net
1 | Der Model View Controller
an. Es gibt drei Möglichkeiten, was die Methode Ihnen zurückgeben kann. Die
erste Variante ist ein Parameter, der über die URL übergeben wurde. Wenn Sie
Ihre Applikation also folgendermaßen
http:/ /127.0.0.1/i ndex/index/ort/Bi elefeld/plz/33602
ansprechen, werden hier die Parameter ort und pl z übergeben. Dabei bekommt
ort bekommt den Wert »Bielefeld« zugewiesen und plz erhält »33602«. Das
heißt, $request->getParam( 'ort'): liefert »Bielefeld« zurück. Der Vollständig-
keit halber sollte ich an dieser Stelle erwähnen, dass Sie auch die Parameter
modale, Controller und action abfragen können. Der erste enthält in diesem
Beispiel default und die beiden anderen Index.
Sie können also Werte über die URL übergeben, wobei diese dann immer paar-
weise zusammengefasst werden. Der erste Wert nach dem Namen der Action ist
der Name des ersten Parameters, worauf der dazugehörige Wert folgt. Dann
kommt der Name des zweiten Parameters, der dazugehörige Wert usw.
Nun hatte ich eingangs erwähnt, dass es nicht ganz einfach zu beantworten ist,
was ein Parameter ist. Das liegt daran, dass die Methode getParam() erst nach
einem Wert schaut, der über die URL übergeben wurde, anschließend $_GET
prüft, ob ein entsprechender Schlüssel vorhanden ist, und dann noch in $_POST
danach sucht. Erst wenn in allen Fällen keine Daten zu finden sind, gibt die
Methode NULL bzw. den Default-Wert, den Sie als zweiten Parameter übergeben
haben, zurück. Die Methode liefert dabei jeweils den ersten Treffer, der gefun-
den wird.
Der Zugriff über get Par am () empfiehlt sich meiner Ansicht nach nur dann, wenn
Sie auch wirklich einen Wert über die URL übergeben. Sie sollten die Methode
nicht nutzen, falls Sie Werte auslesen wollen, die mit GET oder POST übergeben
wurden. Ebenso sollten Sie auch eine Namensgleichheit zwischen URL-Parame-
tern und Namen von Formularfeldern vermeiden, da die übergebenen Werte
sonst eventuell nicht eindeutig sind.
Wichtig ist, dass Sie immer beachten, dass in der URL die Angabe von Controller
und Action nicht fehlen darf.
Parameter, die Sie über die URL übergeben, können beispielsweise sehr hilfreich
sein, wenn es sich um Daten handelt, die Bestandteil eines Links sein sollen. Das
könnte zum Beispiel die Übergabe einer ID oder der Name eines Produkts sein.
Dies kann den angenehmen Nebeneffekt haben, dass Sie das Suchmaschinen-
Ranking Ihrer Anwendung verbessern. Ein suchmaschinenfreundlicher Link auf
eine Produktseite zu einem Apple iPod könnte in einem Shop dann beispiels-
weise so aussehen:
40
e-bol.net
Die Praxis des MVC | 1.2
http://www.example.com/produkte/anzei gen/name/Apple%201Pod%20nano/
ean/8859091650494
Es gibt übrigens auch noch die Methode getParamsO, die Ihnen ein Array
zurückgibt. Dieses Array beinhaltet dann alle Werte, die über die URL, GET und
POST übergeben wurden. Auch hierbei gilt, dass bei Namensgleichheit der Wert
erhalten bleibt, der zuerst gefunden wird. Die URL »überschreibt« GET und GET
»überschreibt« POST.
Zu jeder der get-Methoden gibt es auch ein Gegenstück, das mit set beginnt und
Ihnen die Möglichkeit liefert, einen Wert zu setzen.
Übrigens ist es auch möglich, die übergebenen Werte direkt in Form von Eigen-
schaften des Request-Objekts auszulesen. Ich hätte also auch einfach auf
$request->ort zugreifen können. Allerdings würde ich davon abraten, da das
noch weniger eindeutig ist. In diesem Fall wird zuerst geprüft, ob es einen
URL-Parameter mit dem Namen gibt. Danach werden die Arrays $_GET, $_POST,
$_COOKIE, REQUESTJJRI, PATH_INFO, $_SERVER und $_ENV abgefragt, ob es einen
Schlüssel mit dem Namen gibt. Wobei REQUEST_URI und PATH_INFO natürlich
keine Arrays sind. Es handelt sich dabei um Eigenschaften, die vom System auf
verschiedenen Wegen gefüllt werden können. Der Hintergrund ist, dass $_SER-
VER[ ’ REQUEST_URI' ] bzw. $_SERVER[ ’ PATH_INFO' ] nicht immer korrekt belegt
sind.
Damit kennen Sie auch schon die grundlegenden Funktionalitäten des MVC.
Zugegebenermaßen fehlt noch das M, nämlich das Model.
1.2.6 Das Model
Eigentlich bedürfte das Model keiner eigenen Überschrift, da Sie es völlig frei
definieren können. Hier gibt es keine wirklichen Vorgaben. Das hat den Grund
darin, dass es hier keinerlei »Magie« gibt, die das Model automatisch einbindet
oder automatisch Daten daraus bezieht. Das heißt, Sie können eine eigene Klasse
erstellen, diese manuell im Action Controller einbinden und wie gewohnt nut-
zen.
Dennoch möchte ich ein paar Punkte hierzu anmerken. Innerhalb der Verzeich-
nisstruktur ist das Verzeichnis models zum Speichern der Model-Klassen bzw.
-Dateien gedacht. Natürlich könnten Sie diese auch an einem anderen Ort spei-
chern, allerdings es ist doch sehr hilfreich, wenn Sie sich an diese Empfehlung
halten. Eine klare Struktur hilft, das Dritte Ihren Code verstehen und sorgt auch
dafür, dass Sie nicht lange suchen müssen, um die Komponenten zu finden.
41
e-bol.net
1 | Der Model View Controller
Der zweite Punkt, den ich Ihnen empfehlen möchte, ist, dass Sie eine eigene
Exception-Klasse für Ihr Model nutzen. Diese muss nicht sonderlich umfangreich
sein. Eine Variante wie
Model_Exception extends Exception
{}
reicht schon völlig aus. Damit haben Sie dann die Möglichkeit, diejenigen Excep-
tions, die zum Beispiel beim Datenbankzugriff oder Ähnlichem entstehen, in
eine eigene Exception zu überführen. Damit meine ich ein Konstrukt wie dieses:
try
{
// Datenbankzugriff
l
catch (Zend_Db_Exception $e)
{
throw new Model_Exception ($e->getMessage(), $e->getCode());
l
Dies gibt Ihnen den Vorteil, sich bei der Nutzung des Models innerhalb des
Action Controllers keinerlei Gedanken darum machen zu müssen, welche Klas-
sen innerhalb des Models genutzt werden.
1.2.7 Error Handling
Was Sie bisher über den MVC wissen, bringt Sie schon recht weit und Sie können
schon eine ganze Menge damit umsetzen. Was passiert aber, wenn ein Fehler
auftritt?
Die erste Stelle, an der ein Fehler auftreten kann, betrifft die Situation, dass ein
Controller oder eine Action angesprochen werden, die nicht existieren. In der
momentanen Konfiguration wirft das System eine Zend_Control l er_
Dispatcher_Exception, wenn Sie versuchen, einen Controller anzusprechen,
den es nicht gibt, und eine Zend_Controller_Action_Exception, wenn es zwar
den Controller, aber nicht die entsprechende Action gibt.
Hier sind verschiedene »Schrauben« vorhanden, an denen Sie »drehen« können,
um das System möglichst zuverlässig zu machen.
Zunächst ist zu überlegen, was passieren soll, wenn ein Controller angesprochen
wird, den es nicht gibt. Um solche Fälle möglichst elegant zu lösen, empfiehlt es
sich, im Front Controller den Parameter useDefaul tControl l erAl ways mit dem
Wert true zu belegen:
$fc->setParam(’useDefaultControllerAlways', true);
42
e-bol.net
Die Praxis des MVC | 1.2
Sollte der Front Controller nun mit einem Action Controller aufgerufen werden,
den es nicht gibt, wird die Anfrage automatisch an den Index-Controller weiter-
geleitet. Das ist aber nur die halbe Miete. Der Aufruf http://127.0.0.1/mich-
gibtsnicht stellt kein Problem dar. Er landet bei der indexAction von Index-
Controller. Der Aufruf http://127.0.0.'l/michgibtsnicht/michauchnicht resultiert
aber nach wie vor in einer Exception, weil es die Methode mi chauchni chtActi on
nicht gibt. Um dieses Problem zu lösen, können Sie die magische Methode
__cal 1 () nutzen. Sie ist seit PHP 5 verfügbar und wird immer dann aufgerufen,
wenn eine Methode einer Klasse aufgerufen werden soll, die nicht implementiert
ist. Falls also eine unbekannte Action angesprochen werden soll, wird_cal 1 ()
aufgerufen. Man könnte in der Methode auch einfach eine Exception werfen,
aber das würde Sie nicht wirklich weiter bringen, da Sie nur eine Exception gegen
eine andere getauscht hätten. Es ist aber auch möglich, die Anfrage an eine andere
Methode weiterzuleiten, die Anfrage also zu »forwarden«. Dafür ist die Methode
_forward() deklariert. Eine solche Implementation könnten Sie wie folgt umset-
zen:
dass Indexcontroller extends Zend_Control 1 er_Action
{
public function indexAction()
{
// normal implementierte indexAction
// Wird aufgerufen wenn die gesuchte
// Methode nicht deklariert ist
public function ___cal1($methode, Sparameter)
{
// Endet der Name der aufgerufenen Methode auf Action?
if ('Action' ==— substr($methode, -6))
// Dann ein Forward auf indexAction
$ t h is->_forward('i ndex’);
1
el se
throw new Exception("Methode Smethode existiert nicht"):
}
}
Listing 1.5 Nutzung der magischen Methode_call()
43
e-bol.net
1 | Der Model View Controller
Die Methode___cal l () prüft, ob der Name der Methode, die aufgerufen werden
soll, auf Action endet. Ist das der Fall, erfolgte der Aufruf vom Dispatcher und
daraus resultiert, dass jemand eine falsche URL genutzt hat. Sollte der Aufruf
nicht auf Action enden, wird es sich um einen Programmierfehler handeln, der
natürlich nach wie vor in einer Exception resultieren sollte.
Mit der Methode _forward() können Sie die Anfrage auch an einen anderen
Action Controller und sogar an ein komplett anderes Modul weiterreichen. Der
erste Parameter ist dabei der Name der Action, wie Sie gesehen haben. Als zwei-
ten Parameter können Sie den Namen des Controllers angeben. Auch hierbei gilt,
dass nur der eigentliche Name angegeben wird. Also beispielsweise foo für den
FooControl ler. Als dritten Parameter können Sie dann noch den Namen eines
Moduls angeben.
So schön diese Technik auch sein mag - ich muss an dieser Stelle dennoch eine
deutliche Warnung anbringen. Nutzen Sie Parameter, die über die URL überge-
ben werden, kann diese Technik schnell verwirren. Bei der folgenden Vorge-
hensweise würden beide URLs bei der Methode indexAction() aus dem Index -
Control ler landen:
http:/ /127.0.0.1/i ndex/index/ort/Bi elefeld/plz/33602
http://127.0.0.1/ort/Bielefeld/plz/33602
Die erste URL ist eindeutig und unproblematisch. Die zweite hingegen würde
beim Auslesen der Daten mit getParams!) diese Informationen liefern:
array(4) {
["control1 er"]=>
string(3) "ort"
["action"]=>
string(9) "Bielefeld"
E"plz"]=>
string(5) "33602"
E’module"]=>
string(7) "default"
}
Es wäre eventuell möglich, die Werte im Request-Objekt mithilfe von setPa-
ram() neu zu setzen, um eine Verarbeitung möglich zu machen. Wenn Sie das
tun, sollten Sie dabei aber im Hinterkopf behalten, dass es mehrere Möglichkei-
ten gibt, warum der Aufruf bei_cal 1 () gelandet ist.
44
e-bol.net
Die Praxis des MVC | 1.2
Neben der Methode _forward() ist auch die Methode _redi rect() deklariert.
Hiermit können Sie ein echtes Redirect auf eine andere URL realisieren. Sie
bekommt als ersten Parameter die Ziel-URL übergeben. Diese können Sie absolut
angeben (was zu empfehlen ist) oder auch relativ. Das heißt, Sie können hier
auch lediglich
$this->_redirect('/i ndex/1ndex');
nutzen. Wichtig ist dabei, dass Sie bei einer relativen Angabe mit einem Slash am
Anfang arbeiten sollten, weil Sie sonst schnell in einer Redirect-Endlosschleife
landen.
Ein Redirect hat gegenüber einem Forward den Vorteil, dass der Client den Sta-
tuscode 302 (Moved Temporarily) mitgeteilt bekommt. Haben Sie die URLs auf
Ihrem Server umgestellt, und wollen Sie den Suchmaschinen mitteilen, dass die
Struktur sich dauerhaft geändert hat, so übergeben Sie _redi rect() als zweiten
Parameter array (’code'=>'301'). Damit wird dann der HTTP-Code 301
(Moved Permanently) mit gesendet.
Nun kann es aber immer noch passieren, dass ein Fehler bei der Ausführung des
Codes auftritt.
Auch in diesem Zusammenhang gibt es wieder zwei »Schrauben«, die man justie-
ren kann. Zum ersten ist da die Methode throwExceptions(). Diese haben Sie
schon kurz am Anfang des Kapitels kennengelernt. Normalerweise unterdrückt
die MVC-Implementation die Ausgabe von Exceptions. Für die Entwicklung ist es
allerdings sicherlich hilfreich, dass Exceptions geworfen werden. Für den pro-
duktiven Einsatz sollten Sie das natürlich wieder ändern. Am besten übergeben
Sie der Methode den Wert false:
$fc->throwExceptions(false);
Alternativ können Sie die Zeile auch einfach löschen. Tritt jetzt eine Exception
auf, bleibt das Browserfenster einfach leer, was natürlich auch nicht so schön ist.
Um das zu verhindern, können Sie das Error-Handling-Plug-in nutzen. Und zwar
habe ich das Plug-in Front Controller mit dieser Zeile ausgeschaltet:
$fc->setParam('noErrorHandler ’, true);
Übergeben Sie hier fal se (beachten Sie die doppelte Verneinung), ist das Plug-in
wieder aktiv. Wenn jetzt ein Fehler auftritt, wird automatisch die errorAction
im ErrorControl 1 er aufgerufen. Dabei handelt es sich um einen ganz normalen
Controller, den Sie selbst implementieren müssen. Außerdem müssen Sie auch
ein Template für ihn anlegen. Dieses muss unter dem Namen error.phtml im
Ordner error unterhalb von views/scripts abgespeichert werden.
45
e-bol.net
1 | Der Model View Controller
Damit haben Sie nun die Möglichkeit, eine schicke Fehlermeldung für den Benut-
zer auszugeben. Nur würden Sie als Betreiber der Website nie merken, dass es
ein Problem gibt, weil die Exceptions alle vom System gefangen werden. Genau
genommen speichert das Error-Handling-Plug-in sie im Response-Objekt. Das ist
normalerweise dazu da, die Antwort für den Client zu verwalten. Allerdings kön-
nen Sie das Response-Objekt auch aus dem Action bzw. aus dem Error Controller
heraus auslesen und dann die dort enthaltenen Exceptions verarbeiten. In einer
ganz einfachen Variante könnte ein solcher Error Controller so aussehen:
requi re_once "Zend/Controller/Action.php";
dass ErrorControl ler extends Zend_Control ler_Action
{
public function errorAction()
{
$view = $this->initView():
$response = $this->getResponse();
$exceptions = $response->getException();
$ausgabe :
foreach (texceptions as $exception)
$ausgabe[]=$exception->getMessage();
l
$view->ausgabe = $ausgabe:
}
Listing 1.6 Ein einfacher Error-Controller
Das Response-Objekt wird hier mit der Methoden getResponse() ausgelesen.
Die Exceptions, die darin enthalten sind, stellt die Methode getException() zur
Verfügung. Da es schnell passieren kann, dass mehrere Fehler auftreten, liefert
die Methode ein Array mit Exceptions zurück.
Der Body-Abschnitt des dazugehörigen Templates könnte beispielsweise so aus-
sehen:
<body>
Die folgenden Fehler traten auf:<br>
<ul >
< ? php
foreach ($this->ausgabe as $ausgabe)
(
46
e-bol.net
Die Praxis des MVC | 1.2
echo "<li >".$thi s->escape(Sausgabe). "</li >";
}
?>
< / ul >
</body>
Listing 1.7 Das Template error.phtml
Zugegebenermaßen ist das keine Variante, die Sie für eine produktive Anwen-
dung nutzen sollten, weil Sie sonst ja auch gleich direkt die Exception ausgeben
könnten. Es geht an dieser Stelle viel mehr darum, Ihnen die Funktionsweise zu
zeigen. Für eine produktive Anwendung würde es sich anbieten, die Fehlermel-
dungen in eine Log-Datei zu schreiben, den Administrator per E-Mail zu benach-
richtigen und für den Benutzer eine »kundenfreundliche« Fehlermeldung auszu-
geben. In diesem Zusammenhang sollten Sie auch einen Blick auf die Klasse
Zend_Log werfen.
Hinweisen möchte ich Sie auch noch darauf, dass Sie im Manual des Zend Frame-
works unter der Adresse http://framework.zend.com/manual/en/zend.controller.
plugins.html eine etwas ausgefeiltere Variante zur Fehlerbehandlung finden.
1.2.8 Fortgeschrittene Techniken
In den folgenden Abschnitten finden Sie noch einige weitergehende Techniken,
die Sie beim Umgang mit den Komponenten unterstützen.
Action-Controller
Die Klassen, die Sie als Action Controller nutzen, sollten den Konstruktor nicht
überschreiben. Sollten Sie den Konstruktor dennoch einmal überschreiben wol-
len, so beachten Sie bitte, dass Sie den Konstruktor des Eltern-Objekts mit
parent::___construct($this->getRequest().
$ this->getResponse(), Sthis->getInvokeArgs())
aufrufen. Allerdings haben Sie auch eine andere Möglichkeit, Standardaufgaben
auszuführen. Dazu können Sie die Methode ini t() implementieren. Diese wird
vom Konstruktor automatisch ausgeführt:
public function initO
{
// View initialisieren etc.
}
47
e-bol.net
1 | Der Model View Controller
Wie Sie schon gesehen haben, können Sie aus einem Action Controller heraus auf
das Response-Objekt zugreifen, welches für die Antwort an den Client zuständig
ist. Grundsätzlich können Sie die meisten Punkte, die mit der Antwort verbun-
den sind, auch beeinflussen.
Ich möchte an dieser Stelle nicht auf den kompletten Funktionsumfang des
Response-Objekts eingehen; Sie können ihn unter der Adresse http://framework.
zend.com/manual/en/zend.controller.response.html nachlesen. Erwähnen möchte
ich aber, wie Sie einen Header versenden. Das kann in einigen Fällen sehr hilf-
reich sein. Einen Header können Sie mit der Methode setHeader() übergeben.
Sie bekommt als ersten Parameter den Namen des Headers übergeben und als
zweiten den dazugehörigen Wert. Da ein Header nur dann gesetzt werden kann,
wenn noch keine Daten zum Client geschickt wurden, sollten Sie vor dem Setzen
des Headers mit canSendHeaders() prüfen, ob dieser Schritt möglich ist. Falls Sie
aus einer Methode in einem Action Controller einen Header senden wollen,
könnte das wie folgt umgesetzt werden:
public function indexActionC)
{
$response = $this->getResponse():
if (true —== $response->canSendHeaders())
{
$response->setHeader('Content-Type',
'text/html; charset=utf-8’):
// Weiterer Code
}
Mit diesen Zeilen würde der Antwort ein UTF-8-Header mitgeschickt. Die
Methode canSendHeadersO kann übrigens auch sofort eine Exception werfen,
wenn die Header nicht mehr gesetzt werden können. Falls Sie das wünschen,
übergeben Sie ihr true. Die Methode setHeader() akzeptiert auch noch einen
booleschen Wert als weiteren Parameter. Übergeben Sie dort true, wird ein Hea-
der mit gleichem Namen, der eventuell schon gesetzt ist, überschrieben. Stan-
dardmäßig ist das allerdings nicht der Fall.
Bei der Einführung der Templates hatte ich erwähnt, dass Sie nicht unbedingt für
jede Action ein eigenes Template anlegen müssen. Dabei bin ich Ihnen aber bis-
her die Antwort schuldig geblieben, wie Sie das realisieren. Ich empfehle Ihnen,
dass Sie zumindest für jeden Action Controller ein Template behalten. Das ver-
bessert die Übersichtlichkeit der Anwendung. Möchten Sie aber von einer Action
ein anderes Template nutzen, ist das kein Problem. Um so etwas zu beeinflussen,
ist ein sogenannter Helper, nämlich ViewRenderer, vorgesehen. Mit ihm haben
48
e-bol.net
Die Praxis des MVC | 1.2
Sie die Möglichkeit, verschiedene Parameter zu verändern, welche die Darstel-
lung und die Nutzung der Templates betreffen. Zuerst benötigen Sie das entspre-
chende Objekt, das Sie mit der statischen Methode getStaticHelper() aus der
Klasse Zend_Control l er_Action_Hel perBroker auslesen. Sobald Sie das Objekt
haben, können Sie die Methode setRender () aufrufen und ihr den Namen eines
anderen Templates, genau genommen den Namen einer anderen Action, überge-
ben.
public function indexAction()
{
$viewRenderer = Zend_Controller_Action_HelperBroker::
getStaticHelper('viewRenderer');
$ v iewRenderer->setRender('allgemei n');
}
Listing 1.8 Nutzung eines anderen Templates
In diesem Beispiel wird also nicht mehr das Template index.phtml genutzt, son-
dern allgemein.phtml.
Plug-ins
Den Begriff Plug-in haben Sie in diesem Kapitel ja schon gelesen. Bei Plug-ins
handelt es sich um Methoden, die an einem bestimmten Punkt der Verarbeitung
automatisch ausgeführt werden. Mit Plug-ins können Sie eine ganze Menge
Dinge erledigen lassen, die Sie sonst mehrfach manuell ausführen müssten.
Einige Plug-ins bringt das Zend Framework schon mit, andere können Sie selbst
implementieren. Diejenigen, die Sie selbst implementieren können, werden an
einer bestimmten Stelle in den Verarbeitungsprozess eingehängt oder »einge-
hakt«. Daher spricht man an dieser Stelle auch von Hooks. Ist eine dieser Stellen
erreicht, wird das entsprechende Plug-in automatisch ausgeführt. Ich möchte
hier nicht auf alle Stellen eingehen, an denen Sie ein Plug-in aufrufen lassen kön-
nen. Zwei Stellen finde ich aber sehr hilfreich. Das ist zum ersten preDispatch
und zum anderen postDi spatch. Hierbei handelt es sich um den Zeitpunkt direkt
vor sowie direkt nach dem Dispatching. Oder anders formuliert, vor und nach
dem Ausführen der Action aus dem Controller.
Müssten Sie beispielsweise bei jeder Seite einen UTF-8-Header mitsenden,
würde es sich anbieten, dies mit dem preDi spatch-Plug-in zu tun. Die Plug-ins
werden alle in einer Klasse implementiert, die innerhalb des Front Controllers
beim System angemeldet werden sollte.
49
e-bol.net
1 | Der Model View Controller
Ein Plug-in, das jede Seite mit einem UTF-8-Header versieht, könnte so aussehen:
requi re_once 'Zend/Controller/Front.php’;
require_once 'Zend/Controller/Plugin/Abstract.php';
dass MeinePlugins extends Zend_Control ler_Pl ugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract
Srequest)
{
$response = $this->getResponse();
if (true ==— $response->canSendHeaders())
$response->setHeader('Content-Type',
’text/html; charset=utf-8'):
}
}
// normale Implementation des Front-Controllers
$fc->regi sterPlugi n(new MeinePlugins()):
$fc->di spatch();
Listing 1.9 Implementation eines Plug-ins
Die Plug-ins werden alle in einer Klasse zusammengefasst. Diese Klasse muss die
abstrakte Klasse Zend_Control l er_Pl ugin_Abstract implementieren. Jedes
Plug-in muss das Request-Objekt als Parameter akzeptieren. Am Namen des Plug-
ins erkennt der Plug-in-Broker, an welcher Stelle es ausgeführt werden muss.
Wie bereits gesagt, wird das preDi spateh-Plug-in stets vor dem Dispatching und
somit vor der Verarbeitung der Action ausgeführt.
Nachdem die Klasse entsprechend implementiert ist, muss nur noch ein Objekt
von ihr abgeleitet werden, das mithilfe der Methode regi sterPl ugin() beim
Plug-in-Broker angemeldet wird.
Wollten Sie noch ein postDi spatch-Plug-in nutzen, würde dies einfach mit der
oben dargestellten Klasse implementiert.
1.2.9 Ein Beispiel
Da sicher das eine oder andere beim MVC ein wenig abstrakt ist, folgt jetzt noch
ein kleines Beispiel. Es ist wirklich nur eine ganz kleine Anwendung, die noch
einmal die Prinzipien verdeutlichen soll.
50
e-bol.net
Die Praxis des MVC | 1.2
Und zwar dient die folgende »Anwendung« dazu, Telefonnummern zu verwal-
ten. Die Möglichkeiten beschränken sich allerdings darauf, neue Telefonnum-
mern anzulegen und bestehende anzuzeigen. Die dazu verwendete Tabelle hat
den folgenden Aufbau:
CREATE TABLE 'telefonliste' (
'id' int(ll) NOT NULL AUTO_INCREMENT,
'name' varchar(lOO) DEFAULT NULL,
'telefon' varchar(lOO) DEFAULT NULL,
PRIMARY KEY ('id' )
) ENGINE=MyISAM DEFAULT CHARSET=latinl
Listing 1.10 Datenbanktabelle
Der Front Controller
Der Front Controller der Anwendung macht im Endeffekt das, was Sie schon
kennengelernt haben. Hier gibt es keine Besonderheiten:
requi re_once ’Zend/Controller/Front.php’;
// Controller-Instanz auslesen
$fc = Zend_Controller_Front::getlnstance():
// Verzeichns setzen
$fc->setControllerDi rectoryi’/var/www/appl/controllers’):
// Nutzung von Views unterdrücken
$fc->setParam(’noViewRenderer’, false):
// So einstellen, dass Ausnahmen geworfen werden
$fc->throwExceptions(false);
// Error Händler ausschalten
$fc->setParam(’noErrorHandl er’ , false):
$fc->setParam(’useDefaultControllerAlways’, true);
$fc->di spatch();
Listing 1.11 Der Front Controller
In diesem Front Controller wird zwar das Error Handling eingeschaltet, aber ich
werde darauf verzichten, hier den Error Controller vorzustellen.
Index Controller
Interessanter, aber auch unspektakulär ist der Index Controller umgesetzt. Er soll
in diesem Beispiel nur dafür genutzt werden, ein statisches Hauptmenü auszuge-
ben. Daher muss die Action nichts machen. Trotzdem muss die Action deklariert
werden:
51
e-bol.net
1 | Der Model View Controller
requi re_once 'Zend/Controller/Action.php';
dass Indexcontroller extends Zend_Control ler_Action
{
// Die Action muss nichts machen
public function indexAction!)
{ }
// Falsche Aufrufe abfangen
public function ___call($methode, Sparameter)
{
if ('Action' === substr!$methode, -6))
$this->_forward('i ndex’);
l
throw new Exception!"Methode $methode existiert nicht”);
}
Listing 1.12 Der Index Controller
Auch wenn der Controller selbst nicht sonderlich viel tut, muss das Template ein
wenig mehr Inhalt haben. Der Body-Bereich des Templates sieht folgendermaßen
aus:
<body>
<h2>Willkommen zur Telefonliste!</h2>
Was darf’s sein?
<ul >
<l iXa href='/neu' >Neue Nummer anlegen</a></l i>
<liXa href='/anzeigen ’ >Al l e Nummern anzei gen</aX/l i >
</ul>
</body>
Listing 1.13 Template für den Index-Controller
Hier sind also nur zwei statische Links auf die beiden Controller der Anwendung
enthalten. Eine Action habe ich nicht angegeben; hier wird einfach die Index-
Action genutzt.
Der Controller »Neu«
Der Controller »Neu« ist dafür zuständig, neue Telefonnummern zu speichern. Es
gibt hier also zwei Aktionen, die zu erledigen sind: Das Ausgeben des Formulars
und das Speichern der Daten. Daher hat der Controller auch zwei entsprechende
Methoden zuzüglich der Methode___cal l (). Auf deren Darstellung habe ich hier
52
e-bol.net
Die Praxis des MVC | 1.2
verzichtet. Dafür finden Sie aber die Methode i ni t(), die dafür zuständig ist, das
Model zu initialisieren:
requi re_once "Zend/Controller/Action.php";
requi re_once '/appi/models/AdressenDbModel.php';
dass NeuController extends Zend_Control ler_Action
{
pri vate Smodel = null;
// initialisiert das Model
public function init()
{
$this->model = new AdressenModel():
}
// Initialisiert die Ausgabe des Formulars
public function indexAction()
{
$view = $this->initView();
$ v iew->valueName = '';
$view->disableName = false:
$view->valueTelefon =
$view->disableTelefon = false:
$view->showSubmit = true:
$ v iew->meldung = ’’:
}
public function speichernAction()
{
$request = $this->getRequest();
$view = $this->initView():
$viewRenderer = Zend_Controller_Action_HelperBroker::
getStati cHelper('vi ewRenderer');
$viewRenderer->setRender(* i ndex'):
// Feld fuer Namen initialisieren
$name = $request->getPost('name’):
$view->valueName = $name:
$view->disableName = true:
$telefon = $request->getPost(’telefon’):
$view->valueTelefon = $telefon;
$view->disableTelefon = true:
$view->showSubmit = false:
$view->meldüng = 'Vielen Dank, die folgenden
53
e-bol.net
1 | Der Model View Controller
Daten wurden gespeichert!';
$daten = array ('name' => $name,
’telefon' => Stelefon);
$this->model->datenSpei ehern (Sdaten);
}
I
Listing 1.14 Controller zum Anlegen neuer Daten
Sicher hatten Sie hier etwas anderen Code erwartet. Es wäre ein einfaches gewe-
sen zwei Templates zu nutzen - eins mit einem Formular und das andere mit
einer Ausgabe die dem Benutzer mitteilt, dass die Daten gespeichert wurden. Da
ich aber nur ein Template nutzen wollte wird das Template durch die Aktionen
initialisiert. Für die beiden Eingabefelder gibt es dazu jeweils zwei Eigenschaften.
Im Fall des Feldes für den Namen sind das val ueName und di sabl eName. Mit der
ersten Eigenschaft kann ein Wert für das Value-Attribut übergeben werden und
mit der zweiten kann gesteuert werden ob das Feld aktiv ist. Wird hiermit true
übergeben, dann wird das Attribut di sabl ed in das Tag eingefügt.
Des Weiteren ist eine Eigenschaft namens showSubmit vorgesehen mit deren
Hilfe Sie den Submit-Button ausblenden können.
In diesem Beispiel wird das Formular also zunächst zur Eingabe der Daten
genutzt. Nach dem Abschicken sorgt die Methode speichernActioni) dafür,
dass die Daten an das Model übergeben werden. Darüber hinaus werden sie auch
noch einmal im Formular ausgegeben, wobei die Felder nicht aktiv sind und der
Submit-Button ausgeblendet wird. So kann der Benutzer noch einmal sehen was
er eingegeben hatte.
Die Nutzung des Formulars, wie ich sie umgesetzt habe, ist nicht sonderlich ele-
gant. Sollten Sie öfter mit Formularen zu tun haben, so werfen Sie einmal einen
Blick auf die View-Helper und Zend_Fi l ter_Input.
Wie Sie hier aber schön erkennen, muss dieser Teil der Anwendung sich nicht
weiter um die Daten kümmern. Er übergibt sie einfach an das Model, das die
Arbeit leistet.
Der Body-Bereich des Templates, das diese beiden Methoden nutzen, ist folgen-
dermaßen aufgebaut:
<body>
<form action=/neu/speichern" method="post">
<? php
echo $this->meldüng;
54
e-bol.net
Die Praxis des MVC | 1.2
?>
<table>
<trXtd>Name</td>
CtdXinput type="text" name-’name"
value ='<?php echo $this->valueName; ?>'
<?php if ($this->disableName) lecho ’disabled’;) ?>
>
</td>
</1 r>
<trXtd>Tel efon</td>
CtdXinput type="text" name="telefon"
value ='<?php echo $this->valueTelefon; ?>'
<?php if ($this->disableTelefon) (echo ’disabled’:) ?>
>
</td>
</tr>
CtrXtd colspan="2" al ign=’,center">
<?php if (true — $this>showSubmit)
I
echo xinput type='submit' value='Spei ehern’>";
1
?>
</td>
</tr>
</1a ble>
</form>
</body>
Listing 1.15 Template zum Anlegen der Daten
X X Telefonliste CD
Name Spongebob Squarepants
Telefon +1021) 953222
( Speichern )
Abbildung 1.2 Eingabe der Daten
Nun fehlt noch der Teil, der die Daten wieder ausgibt.
55
e-bol.net
1 | Der Model View Controller
Der Controller »Anzeigen«
Dieser Controller bekommt die Daten vom Model übergeben und stellt sie dar.
Die Daten werden als Array übergeben, sodass sie im Template mit einer for-
each-Schleife ausgegeben werden können:
requi re_once "Zend/Controller/Action.php";
requi re_once '/appi/models/AdressenDbModel.php';
dass AnzeigenController extends Zend_Controller_Action
{
private $model = null:
public function init()
{
$this->model = new AdressenModel():
public function indexAction()
{
$view = $this->initView():
$daten = $this->model->datenl_esen();
$view->daten = Sdaten:
}
Listing 1.16 Der Controller zum Anzeigen der Daten
Auch hier ist nicht mehr viel zu erläutern. Das Template ist ähnlich einfach auf-
gebaut:
<body>
< ? php
echo "Ctable border=’1'>";
foreach ($this->daten as $zeile)
{
echo "<trXtd>":
echo $ t hi s->escape($zei1e[’name']):
echo "</td><td>":
echo $ t hi s->escape($zei1e[’telefon’]);
echo "</tdX/tr>";
}
echo "</table>":
?>
</body>
Listing 1.17 Das Template für die Ausgabe
56
e-bol.net
Die Praxis des MVC | 1.2
Telefonliste aWiOÖ »
Homer Simpson +1 (3735)4810278
Monty Bums +1 (3735) 373533
Patrick Star +1 (921)98151
Spongcbob Squarcpants +1 (921)953222
4
Abbildung 1.3 Die Darstellung der Daten im Browser
Das Model
Zu guter Letzt fehlt noch das Model, das in den beiden Controllern ja schon
genutzt wurde. Ich habe hier auf Zend_Db zurückgegriffen, um die Komponente
möglichst einfach und schnell umsetzen zu können. Auch wenn Sie sich vielleicht
noch nicht mit Zend_Db auskennen, denke ich, dass die beiden Methoden doch
gut zu verstehen sind:
requi re_once('Zend/Loader.php');
Zend_Loader::loadClass('Zend_Db’);
dass AdressenModel
{
pri vate $db = null :
public function ___construct()
{
// Daten für den Zugriff auf die Datenbank
$optionen = array(
'host’ => *127.0.0.1' ,
’username' => 'root’,
’password’ => '’,
’dbname’ => 'test’
);
// Objekt ableiten
$this->db = Zend_Db::factory(’mysqli',$optionen);
* Speichert Daten in der Datenbank
* @param array $daten
* @return true
57
e-bol.net
1 | Der Model View Controller
*/
public function datenSpeichern (array $daten)
{
$sql = "INSERT INTO telefonliste (name, telefon)
VALUES (?. ?)";
// Befehl vorbereiten
$stmt = $this->db->prepare($sql);
// Befehl ausführen
$stmt->execute(Sdaten);
* Liest alle Daten aus Tabelle
* und gibt sie als Array zurück
* @return array
*/
public function datenLesen ()
{
$sql = ’SELECT * FROM telefonliste';
return $this->db->fetchAll($sql);
}
Listing 1.18 Das Model zum Zugriff auf die Datenbank
Sie sehen, an dem Model ist wirklich nichts Besonderes. Es ist einfach nur eine
normale Klasse.
58
e-bol.net
Nukular, das Wort heißt nukular....
- Homer Simpson
2 Datenbankzugriff mit Zend.Db
Die meisten Anwendungen nutzen heutzutage eine Datenbank, um Daten zu ver-
walten. Nun könnten Sie in Ihrer Applikation, die Sie mit dem Zend Framework
erstellen, einfach die Datenbankfunktionen nutzen, die Sie schon immer genutzt
haben. Der unschöne Nebeneffekt ist allerdings, dass Sie, so lange Sie keine eige-
nen Funktionen erstellen, redundanten Code haben, weil Sie beispielsweise Feh-
ler mehrfach abfangen müssen. Der zweite Punkt ist, dass Sie nicht datenbank-
unabhängig arbeiten. Wenn Sie also Ihre Anwendung auf eine andere
Datenbanksoftware umstellen wollen, müssen Sie den gesamten Code überarbei-
ten. Diese und ähnliche Probleme versuchen die Zend_Db-Klassen auszumerzen.
Die meisten Informationen in diesem Kapitel beziehen sich auf die Nutzung einer
MySQL-Datenbank, da diese bei Webanwendungen sicher die größte Verbrei-
tung besitzt.
2.1 Datenbankunabhängigkeit
Einer der Gründe, warum man einen Datenbank-Layer einsetzt, liegt darin, dass
man unabhängig von der Datenbank bleiben möchte. Sie können somit mit sehr
einfachen Mitteln von einer Datenbank zu einer anderen migrieren. Allerdings
muss man fairerweise dazu sagen, dass das eher die Theorie ist und sich in der
Praxis nicht so einfach umsetzen lässt. Dabei stellt sich natürlich die Frage,
warum das so ist. Schließlich nutzen die Datenbanken doch alle SQL. Ja, zwar
nutzen sie alle SQL, aber in den meisten Fällen bieten sie alle mehr Möglichkei-
ten als das, was in reinem SQL vorgesehen ist.
Lassen Sie mich dies an einem Beispiel erläutern. Sicher kennen Sie in MySQL die
Möglichkeit, eine Spalte mit dem Attribut auto_increment zu versehen. Damit
wird der Integer-Wert der Spalte bei jedem INSERT um 1 erhöht. Gerade für das
Generieren einer ID ist das eine sehr beliebte Funktionalität. Nutzen Sie diese
Funktionalität, so sind Sie allerdings an MySQL gebunden, da es sich hierbei um
59
e-bol.net
2 | Datenbankzugriff mit Zend_Db
ein MySQL-Feature handelt, das nicht in SQL deklariert ist. Andere Datenbank-
systeme nutzen an dieser Stelle Sequenzen, die MySQL aber nicht kennt. Was
nun also tun, um möglichst flexibel zu bleiben? Die Lösung ist einfach: Sie legen
eine zusätzliche Tabelle mit einer Spalte an, die nur einen Integer-Wert enthält,
nämlich die aktuelle ID. Möchten Sie in der Tabelle, welche die eigentlichen
Daten speichert, einen Wert einfügen, so selektieren Sie zunächst die ID aus der
»ID-Tabelle«. Die ID-Tabelle sollte allerdings zuvor gesperrt werden, damit kein
anderer Prozess dieselbe ID ausliest. Nach dem Auslesen führen Sie ein Update
auf die Tabelle aus, wobei der enthaltene Wert um 1 erhöht wird. Nachdem das
erfolgt ist, können Sie die Tabelle wieder freigeben und die Daten, die Sie eigent-
lich abspeichern wollten, in die Daten führende Tabelle schreiben. Neben der
Tatsache, dass der programmiertechnische Aufwand hier natürlich viel größer ist,
hat diese Vorgehensweise auch zur Folge, dass die Performance leidet.
Was heißt das nun alles konkret? Wenn Sie mehr oder minder datenbankunab-
hängig bleiben wollen, so müssen Sie zunächst analysieren, welche Funktionali-
täten von den potenziellen Zieldatenbanken unterstützt werden. Danach müssen
Sie einen gemeinsamen Nenner finden und die benötigten Funktionalitäten mit
den vorhandenen Möglichkeiten implementieren. Alternativ können Sie natür-
lich auch für jede mögliche Datenbank eine eigene Zugriffsklasse erstellen, was
allerdings einen Datenbankabstraktionslayer wie Zend_Db weitgehend ad
absurdum führen würde.
2.2 Nutzung von Zend_Db
Um auf eine Datenbank zuzugreifen, ist es sinnvoll, mithilfe der statischen
Methode factoryO aus der Klasse Zend_Db ein Objekt abzuleiten. Sie bindet
automatisch die benötigte Klasse ein und gibt ein instantiiertes Objekt zurück.
Alternativ könnten Sie auch selbst ein Objekt ableiten.
Die Methode bekommt alle benötigen Werte als Parameter übergeben. Der erste
Parameter ist dabei ein String, der definiert, welche der PHP-Datenbankfunktio-
nen intern genutzt werden sollen. Sie können also selbst definieren, ob Sie lieber
mit PDO oder mit den mysqli-Funktionen auf MySQL zugreifen wollen. Zulässig
sind hier die Strings aus Tabelle Tabelle 2.1.
String PHP-Funktionen/Datenbank
' Db2 ’ db2_*
'Mysqli ' mysqli_*
Tabelle 2.1 Parameter für die factory-Methode
60
e-bol.net
Nutzung von Zend_Db | 2.2
String PHP-Funktionen/Datenbank
'Oracle’ oci8_
’Pdo_Ibm' PDO für IBM DB2 bzw. IBM IDS
'Pdo_Mssql' PDO für MS SQL-Server und Sybase
'Pdo_Mysql' PDO für MySQL
'Pdo_Oci' PDO für Oracle
'Pdo_Pgsql' PDO für PostreSQL
'Pdo_Sqli te’ PDO für SQLite
Tabelle 2.1 Parameter für die factory-Methode (Forts.)
Wie Sie sehen, werden die veralteten Funktionen mit dem Präfix mysql_ nicht
mehr unterstützt.
Der zweite Parameter, den die Methode factory() benötigt, ist ein Array mit
den Informationen, die benötigt werden, um auf den Datenbankserver zuzugrei-
fen. Das heißt, der Name bzw. die IP, der Benutzername u. Ä. müssen auf diesem
Weg angegeben werden. Damit das Paket die Parameter eindeutig zuordnen
kann, müssen Sie bestimmte Schlüssel verwenden. Diese finden Sie in Tabelle
Tabelle 2.2.
Schlüssel Beschreibung
host Name oder IP-Adresse des Datenbankservers
username Benutzername des Datenbankbenutzers, über den die Verbindung aufgebaut werden soll
password Passwort des Datenbankbenutzers
dbname Name der Datenbank, die genutzt werden soll
port Damit können Sie optional einen anderen Port als den Standardport übergeben.
options Mit diesem Schlüssel können Sie ein assoziatives Array übergeben, das allgemeine Optionen definiert.
dri ver_opti ons Array mit Optionen, das direkt an den PDO-Konstruktor übergeben wird
adapterNamespace Hiermit können Sie einen eigenen Namespace deklarieren, falls Sie eine eigene Datenbankklasse nutzen wollen.
Tabelle 2.2 Optionen für die factory-Methode
Auf die letzten drei Schlüssel in der Tabelle möchte ich noch kurz eingehen. Der
Schlüssel opti ons kann auf ein assoziatives Array verweisen, welches allgemeine
Optionen enthält, die für alle Datenbankadapter gültig sind. Innerhalb dieses
Arrays dürfen zwei Schlüssel genutzt werden. Hierbei handelt es sich um die
61
e-bol.net
2 | Datenbankzugriff mit Zend_Db
Konstanten Zend_Db::CASE_FOLDING und Zend_Db::AUTO_QUOTE_IDENTIFIERS.
Mit der ersten können Sie festlegen, ob Sie das Case-Folding einschalten wollen.
Normalerweise werden die Namen der Tabellenspalten so zurückgegeben, wie
sie in der Datenbank deklariert wurden, das heißt, die Groß- und Kleinschrei-
bung wird nicht geändert. Möchten Sie die Bezeichner komplett in Großbuchsta-
ben erhalten, so übergeben Sie mit diesem Schlüssel den Wert Zend_Db: :CASE_
UPPER. Bevorzugen Sie die Namen in Kleinbuchstaben, so nutzen Sie die Kon-
stante Zend_Db: :CASE_LOWER als Wert. Möglich ist auch die Konstante Zend_
Db: :CASE_NATURAL, die aber der Default-Einstellung entspricht. Mit ihr erhalten
Sie die Namen in derjenigen Schreibweise, wie sie in der Datenbank verwendet
wird.
Der Schlüssel Zend_Db::AUTO_QUOTE_IDENTIFIERS akzeptiert die Werte true
oder fal se und entscheidet darüber, ob Bezeichner wie Tabellen- oder Spalten-
namen automatisch »gequotet«, also mit Backticks versehen werden sollen. Stan-
dardmäßig geschieht dies durch Zend_Db nicht.
Mit dem Schlüssel dri ver_opti ons können Sie ein Array übergeben, das Optio-
nen enthält, die direkt an den Konstruktor der PHP-Klasse PDO übergeben wer-
den. Das ist natürlich nur dann von Interesse, wenn Sie einen PDO-Adapter nut-
zen. Weitere Informationen zu den Optionen finden Sie im PHP-Manual unter
http://www.php.net/pdo.
Der letzte Schlüssel adapterNamespace gibt Ihnen die Möglichkeit, einen selbst
erstellten Datenbankadapter zu verwenden. Wie Sie selbst einen Adapter erstel-
len, werde ich hier nicht erläutern, da alle relevanten Datenbanken durch das
Zend Framework abgedeckt sind. Weitere Informationen hierzu können Sie dem
Manual zum Zend Framework entnehmen.
Wollen Sie also beispielsweise eine Verbindung zu einem lokal installierten
MySQL-Server über die mysqli-Funktionen aufbauen, so könnte dies wie folgt
aussehen:
require_once 'Zend/Db.php';
Soptionen = array(
'host’ => '127.0.0.1’,
'username’ => 'root',
'password’ => '',
’dbname' => 'test'
h
$db = Zend_Db::factory(’mysqli',$optionen);
Listing 2.1 Ableiten eines Objekts
62
e-bol.net
Nutzung von Zend_Db | 2.2
Nach der Instantiierung enthält $db ein Objekt der Klasse Zend_Db_Adapter_My-
sql i. Übergeben Sie einen anderen String aus Tabelle 2.1, erhalten Sie natürlich
auch ein Objekt einer anderen Klasse.
Eine vielleicht etwas ungewöhnliche Eigenschaft der Klassen ist, dass der Verbin-
dungsaufbau zur Datenbank nicht sofort erfolgt. Die Daten werden zunächst nur
gespeichert, und eine Verbindung wird erst dann aufgebaut, wenn es nötig ist.
Möchten Sie explizit eine Verbindung aufbauen, müssen Sie die Methode get-
Connection() aufrufen. Das hat den Vorteil, dass ein eventueller Fehler beim
Verbindungsaufbau sofort erfolgt und nicht erst später, wenn man vielleicht
nicht mehr damit rechnet.
Die Zend_Db-Klassen bieten eine recht große Anzahl an verschiedensten Metho-
den an, um auf Tabellen zuzugreifen. Um die dafür notwendigen SQL-Statements
zu erstellen, kann es passieren, dass Sie die Werte oder Bezeichner von Tabellen
oder Spalten »quoten« müssen, damit Sonderzeichen oder spezielle Namen nicht
zu einem Problem werden. Da die Anforderungen in diesem Bereich von der ver-
wendeten Datenbank abhängig sind, bietet Zend_Db entsprechende Methoden
an. Um Werte, die in einem SQL-Statement genutzt werden, zu quoten, ist die
Methode quote() vorgesehen. Sie bekommt als ersten Parameter den Wert über-
geben, der genutzt werden soll. Als zweiten Parameter können Sie noch einen
Datentyp übergeben. Dafür sind in der Klasse Zend_Db die Konstanten INT_TYPE
(32 Bit Integer), BIGINT_TYPE (64 Bit Integer) und FLOAT_TYPE (Float oder
Double) definiert. Nutzen Sie keinen zweiten Parameter, geht die Methode
davon aus, dass es sich um einen String handelt.
Bitte beachten Sie bei der Nutzung der Datentypen, dass die übergebenen Daten
konvertiert werden. I NT_TYPE und FLOAT_TYPE nutzen die PHP-Funktionen int-
val() und floatvalO zur Konvertierung, wohingegen bei BIGINT_TYPE ein
regulärer Ausdruck genutzt wird, der auch eine hexadezimale Darstellung verar-
beiten kann:
Sstring = "Hallo
'Weit'!":
echo $db->quote($string); // ’HalloXn VWeltV!’
echo $db->quote(”2 Bücher", Zend_Db::INT_TYPE); // 2
echo $db->quote("Bücher: 2", Zend_Db::INT_TYPE): // 0
echo $db->quote("OxFF Bücher", Zend_Db::BIGINT_TYPE); // OxFF
echo $db->quote("OxFF Bücher", Zend_Db::INT_TYPE); // 0
Eine Variante der Methode quote() ist quotelnto(). Sie gibt Ihnen die Möglich-
keit, einen Wert direkt in ein SQL-Statement einzufügen. Als ersten Parameter
bekommt die Methode ein SQL-Statement übergeben, bei dem ein Wert durch
ein Fragezeichen als Platzhalter ersetzt wurde. Der zweite Parameter ist der Wert,
63
e-bol.net
2 | Datenbankzugriff mit Zend_Db
der escapet und anstelle des Fragezeichens in den SQL-String eingefügt werden
soll. Als dritten Parameter können Sie eine der Konstanten angeben, die schon
bei quote() vorgestellt wurden.
Sstring = "Hallo
'Welt*!";
Ssql = "INSERT INTO daten(begruessung) VALUES (?)";
$sql_safe = $db->quotelnto(Ssql , $str1ng);
// Ssql_safe enthält jetzt:
// INSERT INTO daten(begruessung) VALUES (’HalloXn \'Welt\'!')
Listing 2.2 Quoting von Daten
Zusätzlich zu den eigentlichen Werten können Sie auch die Namen von Tabellen
oder Spalten quoten lassen. Neben der Möglichkeit, dies global über die Optio-
nen einzuschalten, sind hierzu mehrere Methoden deklariert. Eine universell
verwendbare Methode ist quoteldenti fi er(). Sie kann alle Arten von Tabellen-
bezeichnern (Tabellennamen, Spaltennamen etc.) quoten und erhält entweder
einen String oder ein Array übergeben und liefert die enthaltenen Daten korrekt
formatiert zurück. Bei einem Array werden die enthaltenen Strings einfach hin-
tereinander gruppiert, sodass Sie beispielsweise die Datenbank und die Tabelle
nacheinander angeben können:
Stabelle = $db->quoteldentifier(’plz');
Sspalte = Sdb->quoteldentifier(array('plz*, ’id'));
Ssql = "SELECT Sspalte FROM Stabelle":
// Ssql enthält jetzt SELECT 'plz'.'id' FROM 'plz'
Listing 2.3 Quoten eines Identifiers
In vielen Fällen ist es sinnvoll oder sogar notwendig, mit einem Alias zu arbeiten.
Das bedeutet, dass Sie eine Tabelle oder eine Spalte nicht mehr unter ihrem
eigentlichen Namen ansprechen, sondern einen Aliasnamen vergeben. Auch da-
für sind zwei spezielle Methoden, nämlich quoteCol umnAs() und quoteTa-
bleAsO, vorgesehen. Beide bekommen als ersten Parameter den Namen der
Spalte bzw. der Tabelle als String oder Array übergeben. Der zweite Parameter ist
der Aliasname, der genutzt werden soll:
Stabelle = Sdb->quoteTableAs(’plz Stabei 1enalias);
Sspalte = $db->quoteColumnAs(array($tabellenalias, ’id’), ’s’);
Sspalte_where = Sdb->quoteldentifier(
arraylStabellenalias.'id’)):
Ssql = "SELECT Sspalte FROM Stabelle
WHERE Sspalte_where > 100”:
64
e-bol.net
Nutzung von Zend_Db | 2.2
// $sql enthält jetzt:
// SELECT 't'.'id' AS 's' FROM 'plz' AS 't'
// WHERE 't'.'id' > 100"
Listing 2.4 Nutzung eines Alias
Nachdem Sie nun wissen, wie Sie ein SQL-Statement erstellen, ist noch zu klären,
wie Sie es an den Server senden. Es gibt eine ganze Reihe von Möglichkeiten,
einen SQL-Befehl an einen Server zu senden. Die universellste Möglichkeit ist
hierbei die Methode query().
Dieser Methode können Sie direkt ein komplettes SQL-Statement übergeben:
$erg = $db->query(’INSERT INTO plz(plz) VALUES(33602)’);
Die Methode query() liefert Ihnen immer ein Objekt einer Statement-Klasse
zurück. Diese sind für jeden Adapter einzeln deklariert.
Als zweite Möglichkeit können Sie der Methode ein Statement übergeben, in
dem noch Platzhalter in Form von Fragezeichen enthalten sind. Die dazugehöri-
gen Werte werden dann als zweiter Parameter in Form eines Arrays übergeben.
Hierbei ist allerdings zu beachten, dass nicht alle Datenbankadapter die Nutzung
von Platzhaltern unterstützen. Welche Adapter damit umgehen können, können
Sie Tabelle 2.3 entnehmen. Unterstützt der Adapter diese Vorgehensweise, so
wäre das folgende Statement äquivalent zu dem vorhergehenden:
$erg = $db->query(1INSERT INTO plz(plz) VALUES(?)1,array(33602)):
Sollten in dem Statement mehrere Fragezeichen zu finden sein, so werden die
Werte aus dem Array der Reihe nach zugeordnet.
Damit aber nicht genug, es gibt noch eine dritte Möglichkeit. Und zwar können
Sie auch benannte Platzhalter nutzen. Dies bedeutet, dass Sie als Platzhalter einen
String nutzen, der mit einem Doppelpunkt eingeleitet wird. Auch hier werden
die Werte wieder als Array übergeben. In diesem Fall handelt es sich allerdings
um ein assoziatives Array, bei dem die Namen der Platzhalter, die im SQL-String
verwendet wurden, als Schlüssel für die entsprechenden Werte genutzt werden.
Sdaten = array (’:zahl' => 33602):
$erg = $db->query('INSERT INTO plz(plz) VALUES(:zahl)',$daten);
Angenehm an dieser Form der Wertübergabe an die Methode ist die Tatsache,
dass sie sich in beiden Varianten automatisch um das Quoting kümmert, sodass
Sie sich keine Gedanken darum machen müssen.
65
e-bol.net
2 | Datenbankzugriff mit Zend_Db
Skunde = array (’: vorname ,=>,'Marcy", 1:nachname’=>"D'Arcy"):
$erg = $db->query('INSERT INTO kundendaten (vorname, nachname)
VALUES(:vornamenachname)’, $ künde);
Auch hierbei gilt, dass nicht alle Datenbankadapter diese Notation unterstützen,
wie Sie in Tabelle 2.3 sehen können.
Adapter Fragezeichen Platzhalter Benannter Platzhalter
AAysqli Ja Nein
Oracle Nein Ja
Db2 Ja Nein
PDO-Adapter Ja Ja
Tabelle 2.3 Unterstützung von Platzhaltern
Die Variante, ein Statement an die Datenbank zu schicken, nutzt »Prepared State-
ments«. Dabei wird die Abfrage zunächst vorbereitet, wobei auch hier Platzhalter
verwendet werden können. Später können die Platzhalter durch konkrete Werte
ersetzt und das Statement vom Server verarbeitet werden. Der Vorteil dieser Vor-
gehensweise besteht darin, dass das eigentliche Statement bereits an den Server
gesendet und von ihm analysiert wurde. Somit müssen immer nur die Werte aus-
getauscht werden, wenn der Befehl ausgeführt wird. Falls Sie einen Befehl mehr-
fach mit unterschiedlichen Werten ausführen wollen, ist eine solche Vorgehens-
weise sehr hilfreich. Dadurch, dass nur die Werte übertragen werden müssen
und der Befehl bereits analysiert ist, steigt die Performance deutlich. Die
Methode queryO nutzt übrigens dieselbe Vorgehensweise und erledigt beide
Schritte auf einmal.
Um einen SQL-Befehl vorzubereiten, ist die Methode prepare() vorgesehen. Sie
erhält den Befehl als String übergeben, wobei Sie sowohl das Fragezeichen als
auch benannte Platzhalter nutzen können. Auch hierbei gilt natürlich, dass der
entsprechende Datenbankadapter die Nutzung unterstützen muss. Der Rückga-
bewert der Methode ist ein Statement-Objekt. Um den Befehl auszuführen, müs-
sen Sie aus diesem Objekt heraus die Methode execute() aufrufen. Die Werte,
die anstelle der Platzhalter eingefügt werden sollen, übergeben Sie der Methode
als indiziertes oder als assoziatives Array. Die Verwendung könnte also beispiels-
weise wie folgt aussehen:
// Daten die eingefügt werden sollen
Skunden = array (
array("Homer", "Simpson"),
array("Spider", "Pig"));
// Statement vorbereiten
66
e-bol.net
Nutzung von Zend_Db | 2.2
Squery = $db->prepare('INSERT INTO kundendaten
(vorname, nachname)
VALUES(?.?)’);
// Datensätze an den Server senden
foreach ($kunden as $kunde)
{
$query->execute($ künde);
}
Listing 2.5 Ausführen von Prepared Statements
Eine zweite Möglichkeit, um mit Prepared Statements zu arbeiten, stellt die Bin-
dung von Parametern an Variablen dar. In diesem Fall werden die Werte nicht
direkt an executel) übergeben. Vielmehr wird ein Platzhalter mit einer Variab-
len oder einem Wert verbunden. Sobald die Methode executeO aufgerufen
wird, wird der Wert der Variablen bzw. der zugewiesene Wert in das Statement
übernommen:
//Variablen initialisieren
Svorname = nul1 :
Snachname = nul1;
// Statement vorbereiten
Ssql = 'INSERT INTO kundendaten (vorname, nachname) VALUES(?,?)':
Squery = $db->prepare(Ssql);
// Parameter an Variablen binden
Squery->bi ndParam(1,$vorname):
Squery->bindParam(2,$nachname):
// Werte zuweisen
Svorname = 'Marge':
Snachname = 'Simpson';
// Statement ausführen
$query->execute();
Listing 2.6 Prepared Statement mit Bind-Variablen
Wie Sie hier sehen, bekommt die Methode bi ndParam() zwei Parameter überge-
ben. Der erste bezeichnet den Parameter. Bei einem benannten Parameter wird
hier also der Name übergeben. Falls Sie Fragezeichen nutzen, wird die Ordnungs-
nummer genutzt, wobei der erste Parameter die Nummer 1 hat. Wie schon ange-
deutet, können Sie auch Konstanten an Parameter binden, wobei das wohl eher
selten vorkommen wird. Dafür ist die Methode bi ndVal ue() deklariert. Auch Sie
bekommt als ersten Parameter die Information übergeben, um welchen Platzhal-
ter es sich handelt, und als zweiten die Konstante, durch die der Platzhalter
ersetzt werden soll.
67
e-bol.net
2 | Datenbankzugriff mit Zend_Db
Bei den bisher vorgestellten Möglichkeiten, ein Statement zum Server zu senden,
habe ich immer auf Befehle zurückgegriffen, die kein Datenbankergebnis liefern.
Dennoch geben sie, wie schon erwähnt, ein Statement-Objekt zurück. Hiermit
können Sie beispielsweise die Anzahl der Zeilen, die vom letzten Befehl betroffen
waren, über die Methode rowCountl) auslesen.
Sollte bei einem der Befehle ein Fehler auftreten, wird eine Exception geworfen.
Was aber passiert, wenn Sie ein SELECT ausführen, das ja in den meisten Fällen
ein Ergebnis liefert? Zunächst gilt, dass das Ergebnis der Abfrage mit im State-
ment-Objekt gespeichert wird. Das heißt, Sie erhalten genau dieselbe Art von
Ergebnis-Objekt, wie Sie es bereits kennengelernt haben.
Um die Daten aus dem Statement-Objekt auszulesen, stehen relativ viele Metho-
den zur Verfügung. Am einfachsten wird sicherlich die Methode fetchO zu
handhaben sein. Sie liefert immer eine Zeile aus der Ergebnismenge zurück bzw.
NULL, falls keine Daten mehr gelesen werden können. Somit eignet sich die
Methode ganz wunderbar für den Einsatz in einer whi 1 e-Schleife, wie Sie in Lis-
ting Listing 2.7 sehen können:
require_once ' Zend/Db.php':
Soptionen = array(
'host’ => '127.0.0.1’,
'username’ => 'root',
'password’ => '',
’dbname' => 'test'
);
$db = Zend_Db::factory(’mysqli',$optionen);
// Statement vorbereiten
$sql = 'SELECT vorname, nachname FROM kundendaten LIMIT 0,3’:
// Query ausführen
$res = $db->query($sql):
// Daten ausgeben
while (Szeile = $res->fetch())
{
echo ’<p>':
echo 'Vorname: '.$zei1e['vorname'].'<br>' ;
echo 'Nachname: '.$zei1e['nachname<br>';
echo '</p>':
}
Listing 2.7 Auslesen von Daten mit fetchO
Die Ausgabe des Listings sehen Sie in Abbildung 2.1.
68
e-bol.net
Nutzung von Zend_Db | 2.2
httpi//.. Db/l.php CD
Vorname: Marge
Nachname: Simpson
Vorname: Homer
Nachname: Simpson
Vorname: Monty
Nachname: Bums
4
Abbildung 2.1 Ausgabe von Daten mit fetch()
Wie Sie sehen, werden die Daten per Default als assoziatives Array zurückgege-
ben. Dies können Sie allerdings über den Fetch-Mode beeinflussen. Diesen kön-
nen Sie entweder über die Methode setFetchMode() festlegen, oder Sie überge-
ben der Methode fetch() eine Konstante, die definiert, auf welche Art Sie die
Daten erhalten möchten. So könnten Sie mit
$db->setFetchMode(Zend_Db::FETCH_OBJ);
definieren, dass Sie die Daten als ein Objekt der Klasse stdCl ass erhalten möch-
ten. Den gleichen Effekt erzielen Sie, wenn Sie die Konstante direkt an fetch()
übergeben:
$res->fetch(Zend_Db::FETCH_OBJ)
Neben FETCH_OBJ sind noch eine Reihe anderer Kostanten deklariert. Mit FETCH_
ASSOC können Sie explizit festlegen, dass Sie die Daten als assoziatives Array
erhalten möchten, was ja auch der Default-Wert ist. Diese Konstante kann hilf-
reich sein, wenn Sie mit setFetchModel) den Modus global umgeschaltet haben
und nur bei einem fetchO ein assoziatives Array auslesen wollen. Nutzen Sie
den Modus FETCH_NUM, so erhalten Sie die Daten als indiziertes Array zurück.
Sollte es notwendig sein, können Sie auch FETCH_BOTH nutzen. In diesem Fall
erhalten Sie ein Array, bei dem die Daten über den Index und über den Spalten-
namen ansprechbar sind. Bezogen auf die Abfrage aus dem vorhergehenden Lis-
ting könnte ein solches Array wie folgt aufgebaut sein:
array(4) {
[0]=>
string(5) "Marge"
[1]=>
string(7) "Simpson"
69
e-bol.net
2 | Datenbankzugriff mit Zend_Db
["vorname"]=>
string(5) "Marge"
["nachname"]=>
string(7) "Simpson"
}
Allerdings sind das noch nicht alle Möglichkeiten. Möchten Sie sämtliche Daten
einer Abfrage in einem Array übergeben bekommen, so steht die Methode
fetchAl1 () zur Verfügung. Genau genommen müsste man sagen die »Metho-
den« fetchAl 1 (), da zwei Methoden mit diesem Namen existieren. Die erste ist
in den Statement-Klassen deklariert. Das heißt, Sie können die Abfrage zuerst mit
der Methode query() zur Datenbank schicken und dann alle Daten auf einmal
mit fetchAl 1 () auslesen, wobei Sie auch dabei einen Fetch-Mode übergeben
können. Die zweite Möglichkeit besteht darin, die Methode fetchAl 1 () aus dem
eigentlichen Datenbankobjekt heraus aufzurufen. Dabei übergeben Sie als ersten
Parameter das SQL-Statement, welches Platzhalter enthalten darf. Sollten Platz-
halter enthalten sein, so müssen Sie die entsprechenden Werte als zweiten Para-
meter übergeben. Der Fetch-Mode kann dabei nicht direkt gesetzt werden, son-
dern muss vorher mithilfe von setFetchModel) festgelegt werden. Somit wären
die folgenden Code-Fragmente äquivalent:
// Erste Möglichkeit
Ssql = 'SELECT vorname, nachname FROM kundendaten LIMIT 0,3’:
Sres = $db->query(Ssql):
Sdaten = Sres->fetchAl 1 (Zend_Db::FETCH_NUM);
// Zweite Möglichkeit
Sdb->setFetchMode(Zend_Db::FETCH_NUM);
Ssql = 'SELECT vorname, nachname FROM kundendaten LIMIT 0,3’:
Sdaten = $db->fetchAl 1($sql):
Ein wenig unflexibler, aber oft völlig ausreichend ist die Methode f etchAssoc().
Diese Member-Funktion führt eine Datenbankabfrage aus und gibt die Daten als
Array zurück. In der ersten Ebene ist dieses indiziert, und in der zweiten Ebene
handelt es sich um ein assoziatives Array. Als ersten Parameter bekommt die
Methode ein SELECT-Statement übergeben, welches auch Platzhalter enthalten
darf. Die dazugehörigen Werte können Sie dann als zweiten Parameter überge-
ben. Im Bedarfsfall werden diese automatisch escapet.
Ssql = 'SELECT vorname, nachname FROM kundendaten LIMIT 0,?’:
Sdaten = Sdb->fetchAssoc(Ssql,3):
Sollten Sie es bevorzugen, das Ergebnis einer Abfrage als Objekt auszulesen, ist
dies ebenfalls kein Problem. Die Methode fetchObject(), die die Daten für Sie
70
e-bol.net
Nutzung von Zend_Db | 2.2
auslesen kann, ist allerdings nur für das Ergebnis einer Abfrage definiert. Das
bedeutet, dass Sie diese zuvor mit query() ausführen müssen. Sie können dann
jeweils eine Zeile aus dem Ergebnis mit fetchObject!) auslesen:
$sql = 'SELECT vorname, nachname FROM kundendaten LIMIT 0,3’:
$erg = $db->query($sql);
while($daten= $erg->fetchObject())
{
echo "Vorname: ".$daten->vorname:
// Weitere Verarbeitung
}
Wie in PHP üblich, wird hier standardmäßig die Klasse stdClass genutzt. Die
Spaltennamen werden dabei gleichzeitig als Namen für die Eigenschaften ver-
wendet.
Sehr praktisch ist, dass Sie nicht darauf festgelegt sind. Sie können der Methode
zwei optionale Parameter übergeben. Mit dem ersten definieren Sie den Namen
der Klasse, die genutzt werden soll. Ein bereits abgeleitetes Objekt zu übergeben,
ist leider nicht möglich. Mit dem zweiten Parameter können Sie ein Array mit
Daten übergeben. Dieses Array, also tatsächlich das komplette Array und nicht
die einzelnen Werte, wird dem Konstruktor der Klasse als Parameter übergeben.
Möchten Sie nur eine Spalte aus einer Abfrage erhalten, so steht die Methode
fetchColO zur Verfügung. Auch sie bekommt direkt die Datenbankabfrage
übergeben und kann dabei auch mit Platzhaltern umgehen. Als Rückgabewert
erhalten Sie ein indiziertes Array mit allen Werten, die in der ersten Spalte des
Ergebnisses enthalten waren. Das mag sich zuerst ein wenig kurios anhören.
Sofern Sie aber nur eine Datenbankspalte auslesen wollen, ist die Methode
durchaus praktisch, da sie Ihnen die Verarbeitung eines mehrdimensionalen
Arrays erspart.
Bevorzugen Sie es, Ihre Abfrage mit query() zu versenden, so können Sie die
Daten dennoch spaltenweise auslesen. Dafür ist die Methode fetchCol umnO
gedacht. Sie leistet im Endeffekt das Gleiche wie fetchColO, wird allerdings
direkt auf das Ergebnis einer Abfrage angewandt. Ein kleiner Unterschied besteht
aber. fetchCol umn() liefert jeweils immer nur einen einzelnen Wert zurück und
kein Array, wie dies bei fetchCol () der Fall ist. Bitte verwechseln Sie die beiden
Methoden nicht! Die beiden folgenden Beispiele leisten also das Gleiche:
//Variante 1: Nutzung von fetchCol!)
$sql = 'SELECT vorname, nachname FROM kundendaten LIMIT 0,3’:
Sdaten = $db->fetchCol($sql):
foreach!$daten as $vorname)
(
71
e-bol.net
2 | Datenbankzugriff mit Zend_Db
echo "Vorname: ".Svorname."<br>";
}
//Variante 2: Nutzung von fetchColumn()
$sql = ’SELECT vorname, nachname FROM kundendaten LIMIT 0,3’:
$erg = $db->query($sql):
whi1e($vorname = $erg->fetchColumn())
{
echo "Vorname: ".Svorname."<br>";
}
Listing 2.8 Ausgeben von Spalten
Eine ähnliche Idee wie beim Auslesen der Spalten liegt der Methode fetchRowl)
zugrunde. Allerdings liefert sie nicht die erste Spalte, sondern die Zeile des
Ergebnisses zurück, wobei auch sie das SQL-Statement als Parameter übergeben
bekommt. Sie beachtet übrigens den voreingestellten Fetch-Mode, sodass Sie die
Daten beispielsweise auch als Objekt auslesen können. Wenn Sie nur eine Zeile
auslesen, kann diese Methode Ihnen Arbeit abnehmen.
Falls Sie nur einen einzelnen Wert aus einer Datenbank auslesen wollen, ist
fetchOnel) Ihr Kandidat. Diese Funktion liefert den Wert, der sich als erster in
der ersten Spalte befindet. Im Endeffekt ist diese Methode also wirklich nur dann
hilfreich, wenn Sie nur exakt einen Wert aus der Datenbank auslesen wollen.
Dieser eine Wert wird dann direkt als skalare Variable und nicht in Form eines
Arrays zurückgegeben.
Eine Methode, die auch ungemein praktisch sein kann, ist fetchPai rs(). Sie
bekommt ebenfalls ein SELECT-Statement übergeben. Die Methode verarbeitet
nur die ersten beiden Spalten der Ergebnismenge und gibt sie als Array zurück.
Dabei nutzt sie die Werte der ersten Spalte als Schlüssel und die der zweiten
Spalte als Werte. Das ist sehr praktisch, wenn Sie beispielsweise den Primär-
schlüssel und einen zusätzlichen Wert aus der Datenbank auslesen wollen. So
könnte eine Abfrage wie die folgende
$sql = ’SELECT id, nachname FROM kundendaten LIMIT 0,3':
Sdaten = $db->fetchPai rs($sq1):
ein Ergebnis liefern, das folgendermaßen aufgebaut ist:
array(3) {
E27»
string(7) "Simpson"
E28»
string(7) "Simpson"
72
e-bol.net
Nutzung von Zend_Db | 2.2
[29»
string(5) "Burns"
}
Hier verweist der Wert aus der Spalte i d auf den dazugehörigen Wert aus der
Spalte nachname.
2.2.1 Transaktionen
Zend_Db gibt Ihnen auch die Möglichkeit, transaktionsorientiert zu arbeiten.
Transaktionsorientierung bedeutet, dass Sie die Möglichkeit haben, ein »Undo«
der letzen Datenbankaktionen auszuführen. Dies kann sehr praktisch sein, falls
Sie beispielsweise mehrere Tabellen aktualisieren müssen und die Daten vonein-
ander abhängig sind. Führen Sie die notwendigen Befehle im Rahmen einer
Transaktion aus, so können Sie jederzeit alle Befehle rückgängig machen, falls
einer der Befehle fehlschlägt.
Das setzt allerdings voraus, dass Sie eine Datenbank nutzen, die transaktionsfähig
ist. Sollten Sie MySQL verwenden, ist das kein Problem. Allerdings müssen Sie in
diesem Fall InnoDB-Tabellen nutzen. Die üblichen MylSAM-Tabellen unterstüt-
zen leider keine Transaktionen.
Für das folgende Beispiel wurde diese MySQL-Tabelle genutzt:
CREATE TABLE 'telefonnummern' (
'id' int(ll) NOT NULL AUTO_INCREMENT,
'name' varchar(lOO) DEFAULT NULL,
'telefon' varchar(lOO) DEFAULT NULL,
PRIMARY KEY ('id' )
) ENGINE=InnoDB DEFAULT CHARSET=latinl
Eine Transaktion wird mit beginTransaction() eingeleitet. Alle Befehle, die
danach kommen, werden erst temporär in einem Bereich ausgeführt, auf den nur
der aktuelle Benutzer Zugriff hat. Sie können von dem Benutzer auch schon
selektiert werden. Wurden alle Befehle korrekt ausgeführt, können Sie die Trans-
aktion mit commitO bestätigen. Alle Daten werden daraufhin in den globalen
Kontext übernommen, sodass jeder Benutzer die Änderungen sieht. Wollen Sie
die Auswirkungen der Befehle verwerfen, so rufen Sie die Methode rol l Back()
auf, die alle Änderungen, Lösch- und Einfügeoperationen der Transaktion ver-
wirft. Das folgende Listing veranschaulicht das Verhalten:
require_once ’Zend/Db.php’;
// Liest alle Daten aus der Tabelle und gibt sie aus
function datenAuslesen(Sdb)
73
e-bol.net
2 | Datenbankzugriff mit Zend_Db
$sql = 'SELECT * FROM telefonnummern';
// Query ausführen
$res = $db->query($sql);
// Daten ausgeben
echo ’<table>';
while ($zeile = $res->fetch())
{
echo '<tr>';
echo ’<td>ID: '.$zei1e[’id'1.'</td>’;
echo ’<td>Name: ’.$zei1e['name'].'</td>’;
echo '<td>Telefon: '.$zei1e['telefon'].'</td>';
echo ' </tr>';
echo '</table>';
Soptionen = array(
'host’ => '127.0.0.1',
'username’ => 'root',
'password’ => '',
’dbname' => 'Warenwirtschaft'
);
$db = Zend_Db::factory(’mysqli',$optionen);
echo 'Daten vorher:<br>';
datenAuslesen($db):
// Normales INSERT ohne Transaktion
$sql = 'INSERT INTO telefonnummern (name, telefon)
VALUES ("Monty Burns", "+1 1343 1111");';
$db->query($sql);
// Transaktion beginnen
$db->begi nTransaction();
// INSERT in einer Transaktion
$sql = 'INSERT INTO telefonnummern (name, telefon)
VALUES ("Patrick Star", "+1 312 343431947");';
$db->query($sql);
echo ’CbODaten vor Rol 1 back:<br>';
datenAuslesen($db);
// Daten verwerfen
$db->rol1Back();
// Neue Transaktion
$db->begi nT ransaction();
74
e-bol.net
Nutzung von Zend_Db | 2.2
$sql = 'INSERT INTO telefonnummern (name. telefon)
VALUES ("Eugene Krabs", "+1 312 615235398");’:
$db->query($sq1):
// Transaktion bestätigen
$db->commi t();
echo ’CbODaten nachher:<br>’;
datenAuslesen($db);
Listing 2.9 Nutzung von Transaktionen
In diesem Listing werden drei INSERT-Anweisungen ausgeführt: Die erste kom-
plett ohne eine Transaktion. Die zweite INSERT-Anweisung innerhalb einer
Transaktion, die mit rol 1 Back() verworfen wird, und die letzte INSERT-Anwei-
sung befindet sich ebenfalls in einer Transaktion, wobei dieses Statement aller-
dings mit commit() bestätigt wird. Die generierte Ausgabe sehen Sie in Abbil-
dung 2.2.
http//127.0.0.1/~carsten/zf-buch/Db/5.php
(£ W & Ü (Ü Google > |Uj
Daten vorher
ID: 1 Name: Homer Simpson Telefon: +1 1343 2232222
Daten vor Rollback:
ID: 1 Name: Homer Simpson Telefon: +1 1343 2232222
ID: 20 Name: Monty Bums Telefon: +1 1343 1111
ID: 21 Name: Patrick Star Telefon: +1 312 343431947
Daten nachher
ID: 1 Name: Homer Simpson Telefon: +1 1343 2232222
ID: 20 Name: Monty Bums Telefon: +1 1343 1111
ID: 22 Name: Eugene Krabs Telefon: +1 312 615235398
Abbildung 2.2 Nutzung von Transaktionen
2.2.2 Sequenzen und automatisch generierte IDs
In vielen Fällen werden Sie nach dem Einfügen eines Datensatzes wissen wollen,
welche ID ihm von dem Datenbanksystem zugewiesen wurde. Wie schon
erwähnt, gibt es hier verschiedene Vorgehensweisen, wie die Datenbanksysteme
arbeiten. Zunächst ist aber in allen Adaptern die Methode lastlnsertldO defi-
niert, die Ihnen die letzte ID liefert, die genutzt wurde. Da die Datenbanken sich
in ihren Möglichkeiten unterscheiden, ist diese Methode auch deutlich unter-
schiedlich implementiert. Die MySQL-Adapter liefern beispielsweise einfach die
75
e-bol.net
2 | Datenbankzugriff mit Zend_Db
zuletzt vergebene ID einer Auto-Inkrement-Spalte, wohingegen die Oracle-Adap-
ter auch auf Sequenzen zugreifen können. Das heißt, die Methode akzeptiert
zwei Parameter, die aber bei Nutzung von MySQL ignoriert werden. Der erste ist
der Name der Tabelle und der zweite der Name der Spalte, die den Primärschlüs-
sel darstellt. Rufen Sie die Methode mit den Parametern 'kundendaten' und
’id_kunde' auf, so liest die Methode den aktuellen Wert der Sequenz
kundendaten_i d_kunde_seq aus. Übergeben Sie den Namen des Primärschlüssels
nicht, greift die Methode auf die Sequenz kündendaten_seq zu. Rufen Sie die
Methode ohne Parameter auf, liefert sie Ihnen die zuletzt eingefügte ID zurück.
Sollten Sie Sequenzen nutzen, die nach einem anderen Namensschema benannt
sind, können Ihnen die Methoden 1 astSequenceldf) und nextSequenceldf)
helfen. Die beiden Methoden bekommen den Namen der Sequenz übergeben
und liefern Ihnen den letzten bzw. den nächsten Wert der Sequenz zurück. Bitte
beachten Sie hierbei, dass die Methoden nur dann definiert sind, sofern die ent-
sprechende Datenbank Sequenzen unterstützt.
2.2.3 Spezielle Datenbankzugriffsmethoden
Die bereits erläuterten Methoden sind eher allgemeiner Natur bzw. beziehen
sich primär auf die Verarbeitung eines SELECT-Statements, wobei die Methode
query () universell einsetzbar ist. Zend_Db kennt allerdings noch eine ganze Reihe
von Methoden, die auf bestimmte SQL-Befehle spezialisiert sind. Mithilfe dieser
Befehle können Sie ein SQL-Statement generieren, ohne sich um die Syntax der
Befehle kümmern zu müssen. Das hat natürlich zur Folge, dass Sie nicht ganz so
flexibel sind, als würden Sie den Befehl von Hand erstellen. Auch wenn es darum
geht, einen möglichst performanten Befehl zu erstellen, ist es meiner Ansicht
nach sinnvoll, den Befehl manuell zu erstellen und zu optimieren. Trotzdem
kann es hilfreich sein, die SQL-Statements maschinell generieren zu lassen.
Um einen neuen Befehl zu generieren, stehen innerhalb des Datenbankobjekts
entsprechende Methoden zur Verfügung. Wenn Sie also einen SELECT-Befehl
erstellen wollen, so rufen Sie die Methode selectt) auf, und bei einem INSERT-
Befehl nutzen Sie dementsprechend i nsert().
Daten einfügen
Um Daten in eine Tabelle einzufügen, können Sie die Methode insert() aufru-
fen. Sie bekommt als ersten Parameter den Namen der Tabelle übergeben. Der
zweite Parameter ist das Array mit denjenigen Daten, die in die Tabelle eingefügt
werden sollen. Dabei wird der Spaltenname als Schlüssel genutzt und der dazu-
gehörige Array-Wert wird in das Statement als Einfügewert übernommen. Dabei
76
e-bol.net
Nutzung von Zend_Db | 2.2
müssen Sie sich auch nicht um das Quoting der Werte kümmern. Das übernimmt
die Methode für Sie. Um das folgende SQL-Statement
INSERT INTO 'kundendaten' ('vorname', 'nachname')
VALUES ('Patrick', 'Star');
zu generieren und auszuführen, würde der Methodenaufruf etwa so aussehen:
$db = Zend_Db::factory(’mysqli',$optionen);
Sdaten = array('vorname'=>'Patrick',
'nachname'=>'Star');
$erg = $db->insert('kundendaten',$daten);
echo "Es wurden $erg Zeilen eingefügt";
Listing 2.10 Einfügen von Daten mithilfe von insertO
Der Rückgabewert enthält, wie Sie nun vielleicht schon erahnen, die Anzahl der
eingefügten Zeilen. Da die Methode aber keine Möglichkeit vorsieht, mehr als
eine Zeile einzufügen, wird üblicherweise die Zahl 1 zurückgeliefert.
Löschen von Daten
Auf diesem Weg können Sie auch recht einfach einen Datensatz löschen. Das
übernimmt die Methode del ete() für Sie. Sie bekommt als ersten Parameter den
Namen der Tabelle übergeben. Bei dem zweiten Parameter handelt es sich um die
Bedingung, die in der WHERE-Bedingung genutzt werden soll. Diese wird direkt
als String übergeben. Falls Sie den Datensatz, der im letzten Beispiel eingefügt
wurde, wieder entfernen wollen, würde der Aufruf folgendermaßen lauten:
$db = Zend_Db::factory(’mysqli',$optionen);
$erg = $db->delete('kundendaten',"vorname=’Patrick'
AND nachname='Star'");
echo "Es wurden $erg Datensätze gelöscht";
Listing 2.11 Löschen von Datenbankeinträgen mithilfe von deleteO
Auch hier wird die Anzahl der betroffenen bzw. gelöschten Zeilen zurückgege-
ben. Bitte stellen Sie aufjeden Fall sicher, dass der zweite Parameter nicht leer ist.
Sollte hier eine leere Variable oder ein leerer String übergeben werden, wird kein
WHERE an das DELETE-Statement angefügt und der gesamte Inhalt der Tabelle
gelöscht.
Daten aktualisieren
Natürlich gibt es auch eine Funktion, mit der Sie Daten aktualisieren können.
Wie Sie nun sicher schon annehmen, lautet der Name der Funktion update().
Auch sie bekommt als ersten Parameter den Namen der Tabelle übergeben. Beim
77
e-bol.net
2 | Datenbankzugriff mit Zend_Db
zweiten Parameter handelt es sich wiederum um ein Array, bei dem die Namen
der Schlüssel als Spaltennamen genutzt werden. Die Werte werden dementspre-
chend in die Spalten übernommen. Um dem System mitzuteilen, welcher Daten-
satz aktualisiert werden soll, geben Sie nach dem Array noch einen String an, der
die Bedingung für die WHERE-Klausel enthält. Auch hierbei gilt, dass Sie unbedingt
darauf achten sollten, keinen Leerstring zu übergeben. Andernfalls wird das
UPDATE-Statement auf alle Zeilen angewandt. Ein Aufruf der Methode könnte so
aussehen:
Sdaten = array (’vonname’=>’Spongebob',
’nachname'=>'Squanepants'):
$eng = $db->update('kundendaten', Sdaten, *id=33’);
echo "Es wurden $erg Datensätze aktualisiert":
Listing 2.12 Aktualisieren von Daten mithilfe von updateO
Daten selektieren
Vielleicht haben Sie schon nach einer Möglichkeit gesucht, um ein SELECT-State-
ment generieren zu lassen. Natürlich besteht auch diese Möglichkeit. Allerdings
bietet ein SELECT-Statement in SQL deutlich mehr Features als die anderen
Befehle. Dieser Problematik trägt das Zend Framework Rechnung. Daraus resul-
tiert eine etwas andere Vorgehensweise als bei den übrigen Befehlen. Rufen Sie
die zuständige Methode selectt) auf, wird nicht direkt ein Befehl ausgeführt.
Daher akzeptiert sie auch keine Parameter. Vielmehr stellt sie eine Art Factory
dar und generiert ein Objekt der Klasse Zend_Db_Select, die auf SELECT-State-
ments spezialisiert ist. Die Klasse kennt eine Vielzahl von Methoden, die Sie
dabei unterstützen, einen SELECT-Befehl zu generieren.
Auch diese Klasse unterstützt ein fluent-Interface, sodass Methoden direkt hin-
tereinander aufgerufen werden können, was in diesem Fall die Lesbarkeit verbes-
sern dürfte.
Wie schon erwähnt, können Sie die Methode selectt) direkt aus dem Daten-
bankobjekt heraus aufrufen, um ein neues SELECT-Objekt zu erhalten. Das so
generierte Objekt benötigt natürlich noch einige Informationen von Ihnen. Da ist
zunächst die Frage, was von woher selektiert werden soll. Diese Daten legen Sie
mit der Methode from() fest. Sie erhält als ersten Parameter den Namen der
Tabelle. Wollen Sie die Tabelle komplett auslesen, können Sie es dabei bewenden
lassen. Mit den folgenden Zeilen würde die komplette Tabelle kundendaten aus-
gelesen:
Sselect = $db->select():
$select->from('kundendaten');
Seng = $db->fetchAH($select):
78
e-bol.net
Nutzung von Zend_Db | 2.2
Nachdem Sie das SELECT-Objekt korrekt konfiguriert haben, können Sie es an
eine Methode übergeben, welche die Abfrage ausführt. Eine der Methoden, die
Sie dafür nutzen können, ist fetchAl 1(). Sie ist, genau wie die Methoden
fetchAssoc(), fetchCol(), fetchPai rs(), fetchRowi) und fetchOne(), in der
Lage, ein solches Objekt zu verarbeiten.
Alternativ können Sie auch aus dem SELECT-Objekt die Methode query() aufru-
fen, welche dann das Ergebnis in derselben Form zurückgibt wie die Methode
query() des Datenbankobjekts.
Gerade bei umfangreichen SELECT-Statements kann es interessant sein, das State-
ment ausgeben zu lassen. Das können Sie beispielsweise dadurch erreichen, dass
Sie das SELECT-Objekt direkt ausgeben lassen oder es explizit in einen String kon-
vertieren:
// Direkte Ausgabe des Statements
echo $select:
// Statement in Variable speichern
Sstatement = (string) $select:
Natürlich können Sie auch angeben, welche Spalten Sie selektieren wollen. Dazu
geben Sie nach dem Namen der Tabelle einfach den Namen der Spalte als String
an. Sollte es sich um mehrere Spalten handeln, so übergeben Sie die Namen als
Array. Als dritten Parameter können Sie noch ein Schema, also beispielsweise
den Namen der Datenbank, übergeben. Um aus der Tabelle kundendaten, die
sich in der Datenbank warenwi rtschaft befindet, die Spalten vorname und nach-
name auszulesen, würde der entsprechende Befehl so lauten:
Sspalten = array('vorname', 'nachname'):
Sdatenbank = 'Warenwirtschaft':
Sselect = $db->select():
$select->from('kundendaten', tspalten, $datenbank);
Listing 2.13 Generieren eines Statements
Der SQL-Befehl, der in diesem Fall genutzt wird, lautet wie folgt:
SELECT 'kundendaten'.'vorname', 'kundendaten'.'nachname’
FROM 'Warenwirtschaft'.'kundendaten’
Wie Sie sehen, werden die Spalten-, Tabellen- und Schemanamen automatisch
gequotet.
In vielen Fällen werden Sie die den Tabellennamen bzw. einen Spaltennamen
durch ein Alias ersetzen wollen. Oder Sie möchten nicht direkt das Ergebnis
einer Spalte auslesen, sondern eine Aggregat-Funktion auf die Spalte anwenden.
79
e-bol.net
2 | Datenbankzugriff mit Zend_Db
Auch das ist alles möglich. In diesen Fällen übergeben Sie die Daten in einem
Array, bei dem der Schlüssel dem Parameter entspricht. Der Wert wird dann
direkt in die Abfrage übernommen. Diese Technik funktioniert sowohl bei den
Tabellennamen als auch bei den Spaltennamen. Möchten Sie beispielsweise die
Anzahl der IDs ermitteln, so können Sie in der Abfrage die Funktion CDU NT ()
nutzen. Das Generieren einer entsprechenden Abfrage könnte beispielsweise so
aussehen:
Stabelle = array ('k' => 'kundendaten');
Sspalte = array ('anzahl’ => 'COUNT(k.id)');
Sselect = $db->select():
Sselect->from(Stabei le, Sspalte);
Das Statement, das hier generiert wird, lautet:
SELECT COUNT(k.id) AS 'anzahl' FROM 'kundendaten' AS 'k'
Der Tabellenname wurde hier durch ein Alias ersetzt, das dann beim Zugriff auf
die Spalten genutzt wurde. Die Tabellennamen mit einem Alias zu ersetzen, ist
auch bei einem Self-Join sehr hilfreich, wie Sie später noch sehen werden.
Nutzen von WHERE-Klauseln
Natürlich haben die meisten SELECT-Befehle wenig Sinn, wenn Sie nicht noch die
eine oder andere WHERE-Klausel nutzen können. Um ein WHERE zu ergänzen, über-
geben Sie die Bedingung an die Methode where(). Sollten Sie mehrere Bedingun-
gen hinzufügen wollen, können Sie die Methode mehrfach aufrufen. Die einzel-
nen Bestandteile der Bedingung werden dann jeweils mit einem AND verknüpft.
Sollten Sie eine OR-Verknüpfung benötigen, so übergeben Sie die Bedingung an
die Methode orWhere():
Sselect = $db->select():
Sselect->from(’kontaktdaten'nachname’):
Sselect->where('id = 12'):
Sselect->orWhere(' id = 20'):
Mit diesen Zeilen wird das folgende SQL-Statement generiert:
SELECT 'kontaktdaten'.'nachname' FROM 'kontaktdaten' WHERE (id = 12)
OR (id = 20)
Was aber, wenn Sie eine WHERE-Bedingung benötigen wie ((id = 10) OR (id =
20)) AND (vorname = 'Marge')? Da Sie die Klammern nicht manuell setzen kön-
nen, müssen Sie die beiden Teile, die durch AND verbunden werden, mit der
Methode where() setzen:
8o
e-bol.net
Nutzung von Zend_Db | 2.2
$select->from('kontaktdaten'nachname');
$select->where('(id = 10) OR (id = 20)');
$select->where('vorname = ?’, 'Marge');
Wie Sie sehen, wurde bei dem zweiten Aufruf von where() mit einem Platzhalter
gearbeitet. Der Wert wird danach als Parameter angegeben. Wenn er übernom-
men wird, quotet das System ihn automatisch. Bei dem ersten Aufruf war diese
Vorgehensweise nicht möglich, da where() immer nur einen Wert für die Platz-
halter akzeptiert. Mehrere Werte zu übergeben, ist daher zurzeit leider nicht
möglich. Allerdings können Sie mehrere Platzhalter nutzen und alle mit dem sel-
ben Wert belegen. Die Nutzung benannter Platzhalter ist bei diesen Methoden
momentan1 auch noch nicht möglich. Ich kann mir aber vorstellen, dass der
Funktionsumfang dieser Methoden noch erweitert wird. Daher sollten Sie einen
Blick in die Dokumentation werfen, sofern Sie die das Paket nutzen möchten.
Nutzung von Limits
Möchten Sie seitenweise durch ein Abfrageergebnis blättern, ist es hilfreich,
wenn Sie nur eine bestimmte Anzahl von Werten pro Abfrage erhalten. Wie viele
Werte Sie erhalten möchten, können Sie dabei mit der Methode 1 imi t() festle-
gen. Sie bekommt als ersten Parameter die Anzahl der Zeilen, die ausgelesen wer-
den sollen, übergeben. Der zweite Parameter definiert den Offset, das heißt, wie
viele Zeilen ausgehend von der ersten Zeile der Ergebnismenge ausgelesen wer-
den sollen. Um die Daten gleich ab der ersten Zeile zu erhalten, lassen Sie diesen
Parameter einfach wegfallen, da er optional ist. Mit der Anweisung
$select->from('kundendaten', array('vorname','nachname'));
$select->limit(5,10);
würde ein Statement generiert, das Ihnen fünf Zeilen zurückgibt, wobei die ers-
ten zehn Zeilen übersprungen werden. Falls Sie seitenweise blättern wollen,
kann es ein aufwändig sein mitzurechnen, welcher Offset zu nutzen ist. Um
Ihnen das Leben an dieser Stelle ein wenig zu erleichtern, ist die Methode 1 i mi t -
PageO definiert. Sie bekommt als ersten Parameter die Nummer der »Seite«
übergeben, von der Sie die Daten auslesen wollen. Der zweite Parameter ist die
Anzahl der Zeilen, die eine Seite umfassen soll. Diese Zeilen generieren also die-
selbe Ergebnismenge wie das vorhergehende Beispiel:
$select->from('kundendaten', array('vorname','nachname'));
$seiect->11 mitPage(3,5);
1 Bezieht sich auf Version 1.0.3.
81
e-bol.net
2 | Datenbankzugriff mit Zend_Db
Damit erhalten Sie also die dritte »Seite« aus dem Ergebnis, wobei jede Seite fünf
Zeilen umfasst. Anders formuliert: Das Ergebnis umfasst die Zeilen 11 bis 15,
somit werden die Zeilen 1 bis 10 übersprungen.
Ein Ergebnis sortieren
Um die Sortierreihenfolge für ein Ergebnis festzulegen, können Sie die Methode
ordert) nutzen. Ihr können Sie den Namen einer Spalte als String oder die
Namen mehrerer Spalten als Array übergeben. Sie können nach dem Namen der
Spalte jeweils noch ASC oder DESC angeben, um die Sortierrichtung festzulegen.
Falls Sie nichts angeben, ergänzt die Methode automatisch ASC, um eine aufstei-
gende Sortierung zu erzielen. Wollen Sie beispielsweise die Namen Ihrer Kunden
auslesen und die Nachnamen dabei absteigend sortieren, können Sie das auf fol-
gende Weise lösen:
$select->from('kundendaten', arrayt'vorname','nachname'));
$select->order(array('nachname DESC,'vorname')):
Bei gleichen Nachnamen würde in diesem Beispiel aufsteigend nach Vornamen
sortiert, da die Methode automatisch ASC ergänzt.
GROUP BY und HAVING
Selbstverständlich unterstützt die Klasse auch die Möglichkeit, mit GROUP BY und
HAVING zu arbeiten. Um Daten nach einer Spalte zu gruppieren, übergeben Sie
den Spaltennamen zusammen mit der Methode group(). Wenn Sie nach mehre-
ren Spalten gruppieren wollen, können Sie die Namen der einzelnen Spalten
auch als Array an die Methode weiterleiten.
Benötigen Sie zusätzlich eine HAVING-Klausel, so übergeben Sie die gewünschte
Bedingung als String an die Methode having(). Hierbei können Sie auch mit
einem Platzhalter in der Bedingung arbeiten und den dazugehörigen Wert als
zweiten Parameter übergeben. Also beispielsweise so etwas wie havi ng (' Umsatz
> ? ’, 30000). Auch hier gilt, dass dies nur mit einem Parameter möglich ist. Falls
Sie mehrere Bedingungen bei der HAVING-Klausel benötigen, können Sie die
Methode mehrfach aufrufen. Die einzelnen Teile werden dann mit AND ver-
knüpft. Sollten Sie eine Verknüpfung mit 0R benötigen, können Sie auf die
Methode orHaving() zurückgreifen, welche die übergebene Bedingung mit 0R
anhängt, ansonsten aber identisch mit havi ng() ist.
DISTINCT und FOR UPDATE
Neben den schon erläuterten Funktionalitäten kennt ein SELECT-Statement noch
mehr Features. Wollen Sie verhindern, dass Ergebnisse doppelt vorkommen, so
82
e-bol.net
Nutzung von Zend_Db | 2.2
ist das Schlüsselwort DI ST INCT hilfreich. Dieses können Sie der Abfrage dadurch
hinzufügen, dass Sie die Methode di stinctC) aufrufen.
Wollen Sie Daten beim Auslesen mit einem Lock versehen, ist die Methode
forUpdate() hilfreich, die der Abfrage den Modifikator FOR UPDATE hinzufügt.
Verknüpfen von Tabellen mittels JOIN
Die Klasse Zend_Db_Sel ect unterstützt auch die gesamte Bandbreite an Joins, die
SQL zu bieten hat. Um die Funktionalität besser zu veranschaulichen, habe ich
auf die beiden folgenden Tabellen aus einer Software zur Verwaltung von Haus-
tieren zurückgegriffen.
Tabelle tl ere:
+-----|---------------1.----4.
id | name | id_rasse |
+-----4---------------1.----4.
| 1 | Jake | 1 |
| 2 | Ganymede | 1 |
3 | N i n c h e n | 2 |
| 4 | Spongebob | NULL |
+----4---------------1.-------4.
Tabelle rassen:
4----4----------------------4-
| id | rasse |
4----4----------------------4-
| 1 | Irish Setter |
2 | Kaninchen
3 | Rhodesian Ridgeback
4 4------------------------4-
Sollten Sie sich wundern, dass »Spongebob« keine Tierrasse zugeordnet ist, so
liegt das daran, dass es sich dabei um einen Schwamm handelt, und Schwämme
bei den Tierrassen für Haustiere leider nicht mit eingeplant waren.
Die verschiedenen Joins werden nicht von allen Datenbanken unterstützt. Des
Weiteren kann es bei einem Datenbanksystem auch innerhalb der einzelnen Ver-
sionen Unterschiede im Verhalten geben. Prüfen Sie daher bitte, welche Mög-
lichkeiten Ihre Datenbank unterstützt.
Um ein JOIN zu konstruieren, sind verschiedene Methoden deklariert. Sie nutzen
fast alle die gleichen Parameter, sodass die Verwendung angenehm einfach ist.
Als ersten Parameter bekommen die Methoden den Namen der Tabelle überge-
ben, mit welcher »gejoint« werden soll. Der zweite Parameter ist die JOI N-Bedin-
83
e-bol.net
2 | Datenbankzugriff mit Zend_Db
gung. Optional können Sie danach noch angeben, welche Spalten aus der zweiten
Tabelle ausgelesen werden sollen. Eine Spalte deklarieren Sie in Form eines
Strings; bei mehreren Spalten greifen Sie auf ein Array zurück. Geben Sie diesen
Parameter nicht an, so werden alle Spalten selektiert. Um keine Spalte aus der
zweiten Tabelle zu selektieren, übergeben Sie an dieser Stelle ein leeres Array.
Mit dem vierten und letzten Parameter können Sie schließlich noch ein Schema
bzw. eine Datenbank angeben, falls die zweite Tabelle nicht im gleichen Kontext
abgelegt sein sollte wie die erste.
Wollen Sie ein einfaches INNER JO IN ausführen, können Sie auf zwei Methoden
zurückgreifen. Sowohl Joint) also auch joinInner!) leisten hier dasselbe. Bei
genauer Betrachtung ruft join!) auch nur joinInner!) auf. Ein sehr einfach
gestalteter Aufruf könnte so aussehen:
// Neues Seiect-Objekt ableiten
Sselect = $db->select():
// Spalte name aus Tabelle tiere
$select->from('tiere’, 'name');
// INNER JOIN mit rassen und entsprechender Bedingung
$select->join('rassen', ’tiere.id_rasse = rassen.id’):
$select->query();
Listing 2.14 INNER JOIN mit Zend_Db_Select
Die Abfrage, die von diesen Zeilen generiert wird, lautet:
SELECT 'tiere'.'name', 'rassen'.*
FROM 'tiere'
INNER JOIN 'rassen' ON tiere.id_rasse = rassen.id
Als Ergebnis werden dabei die folgenden Daten ermittelt:
name id rasse
Jake 1 Irish Setter
Ganymede 1 Irish Setter
Ninchen 2 Kaninchen
Bei der Spalte id handelt es sich um diejenige aus der Tabelle rassen. Da nicht
explizit angegeben wurde, welche Spalten interessant sind, hat die Methode alle
selektiert. Um nur die Spalte rasse auszulesen, würde der Aufruf von join!) so
aussehen:
$select->join(’rassen’, ’tiere.id_rasse = rassen.id’, 'rasse');
84
e-bol.net
Nutzung von Zend_Db
I 2.2
Einen LEFT JOIN setzen Sie mit der Methode joi nl_eft() um:
Sselect = $db->select():
$select->from('tiere’, ’name’):
$ sei ect-> joi nl_eft(' rassen ’,
'tiere.id_rasse = rassen.id', 'rasse'):
Listing 2.15 Ausführen eines LEFT JOIN
Das genutzte Statement lautet folgendermaßen:
SELECT 'tiere'.'name', 'rassen'.'rasse' FROM 'tiere'
LEFT JOIN 'rassen' ON tiere.id_rasse = rassen.id
Das ermittelte Ergebnis finden Sie in der nachfolgenden Tabelle:
name rasse
Jake Irish Setter
Ganymede Irish Setter
Ninchen Kaninchen
Spongebob NULL
Auf die gleiche Art und Weise können Sie auch einen RIGHT JOIN nutzen:
Sselect = $db->select():
$select->from(’tiere’, ’name’):
$select->joinRight(’rassen’,
'tiere.id_rasse = rassen.id', 'rasse');
Listing 2.16 Nutzung von RIGHT JOIN
Hierbei wird dieser SQL-Befehl ausgeführt:
SELECT 'tiere'.’name', 'rassen'.'rasse' FROM 'tiere'
RIGHT JOIN 'rassen' ON tiere.id_rasse = rassen.id
Er liefert dieses Ergebnis:
name rasse
Jake Irish Setter
Ganymede Irish Setter
Ninchen Kaninchen
NULL Rhodesian Ridgeback
Den Befehl FULL JOIN, der quasi eine Kombination aus dem LEFT und RIGHT JOIN
darstellt, können Sie mit dem Befehl joinFull () ausführen. Da ich davon aus-
85
e-bol.net
2 | Datenbankzugriff mit Zend_Db
gehe, dass die meisten Benutzer MySQL einsetzen, sei mir der Hinweis gestattet,
dass MySQL FULL JO IN nicht unterstützt.
Sselect = $db->select():
Sselect->from('tiere’, 'name');
Sselect->joinFul1('rassen',
'tiere.id_rasse = rassen.id', 'rasse');
Listing 2.17 Ausführen eines FULL JOIN
Diese Zeilen führen zu dem folgenden Befehl:
SELECT 'tiere'.'name', 'rassen'.'rasse' FROM 'tiere'
FULL JOIN 'rassen' ON tiere.id_rasse = rassen.id
Das selektierte Ergebnis sieht folgendermaßen aus:
name rasse
Jake Irish Setter
Ganymede Irish Setter
Ninchen Kaninchen
Spongebob NULL
NULL Rhodesian Ridgeback
Die beiden JOI N-Befehle, auf die ich noch nicht eingegangen bin, sind CROSS JOIN
und NATURAL JOIN. Auch für diese beiden Varianten sind eigene Methoden im
schon bekannten Namensschema vorgesehen. Sie heißen joinCross() und
joinNatural (). Allerdings unterscheiden sie sich ein wenig von den anderen
Methoden. Aufgrund der zugrunde liegenden Ideen benötigen die beiden keine
Bedingung, auf deren Basis der Befehl erstellt wird. Sie benötigen lediglich den
Namen der Tabelle und akzeptieren danach noch optional die Namen der
gewünschten Spalte(n) sowie den Namen der Datenbank bzw. den Namen des
Schemas.
CROSS JOIN, welches ein kartesisches Produkt erzeugt, wird beispielsweise so
genutzt:
Sselect = $db->select();
Sselect->from(’tiere’, ’name’);
Sselect->joinCross('rassen', 'rasse'):
Listing 2.18 CROSS JOIN mit Zend_Db_Select
86
e-bol.net
Nutzung von Zend_Db | 2.2
Hieraus wird der folgende SQL-Befehl generiert:
SELECT 'tiere'.'name'. 'rassen'.'rasse' FROM 'tiere'
CROSS JOIN 'rassen'
Das daraus resultierende kartesische Produkt sehen Sie in der nachfolgenden
Tabelle:
name rasse
Jake Irish Setter
Jake Kaninchen
Jake Rhodesian Ridgeback
Ganymede Irish Setter
Ganymede Kaninchen
Ganymede Rhodesian Ridgeback
Ninchen Irish Setter
Ninchen Kaninchen
Ninchen Rhodesian Ridgeback
Spongebob Irish Setter
Spongebob Kaninchen
Spongebob Rhodesian Ridgeback
Das Ergebnis eines NATURAL JOIN sorgt bei der vorliegenden Tabellenstruktur
zwar für Verwirrung, aber falls Sie es benötigen, so unterstützt das Zend Frame-
work Sie auch an dieser Stelle:
Sselect = $db->select();
$select->from(’tiere’, ’name’);
$select->joinNatural(’rassen’, ’rasse’);
Listing 2.19 Nutzung eines NATURAL JOIN
Das dazugehörige SQL-Statement lautet:
SELECT 'tiere'.'name', 'rassen'.'rasse' FROM 'tiere'
NATURAL JOIN 'rassen'
Da ein NATURAL JOIN zu einer Verknüpfung von zwei Tabellen über die Spalten-
namen führt, lautet das Ergebnis so:
name rasse
Jake Irish Setter
Ganymede Kaninchen
Ninchen Rhodesian Ridgeback
87
e-bol.net
2 | Datenbankzugriff mit Zend_Db
Sonstige Funktionalitäten
Die Klasse Zend_Db_Select gibt Ihnen auch die Möglichkeit, Teile der SQL-
Abfrage wieder auszulesen. Dafür ist die Methode getPartO vorgesehen. Sie
bekommt eine Konstante übergeben, die definiert, welchen Teil der Abfrage Sie
auslesen wollen. Hierbei sind die nachfolgenden Kostanten zulässig, die in der
Klasse Zend_Db_Select deklariert sind: DISTINCT, FORJJPDATE, COLUMNS, FROM,
WHERE, GROUP, HAVING, ORDER, LIMIT_COUNT, LIMIT_OFFSET.
Die gleichen Konstanten können Sie auch beim Aufruf der Methode reset() nut-
zen, die Teile einer bestehenden Abfrage wieder zurücksetzt. Wollen Sie also ein
Objekt, das Sie bereits für eine Abfrage genutzt haben, noch einmal nutzen, kann
das sehr praktisch sein. Indem Sie das Statement $select->reset(Zend_Db_
Seiect::WHERE) aufrufen, wird die WHERE-Klausel entfernt, und Sie können sie
erneut setzen.
2.3 Datenbankzugriff mit Zend_Db_Table
Nachdem Sie nun eine ganze Menge Möglichkeiten für einen mehr oder minder
konventionellen Datenbankzugriff kennengelernt haben, möchte ich Ihnen nun
Zend_Db_Tabl e vorstellen. Hierbei handelt es sich um eine Klasse, die ein soge-
nanntes Active Recordset Pattern implementiert. Aber was bedeutet das konkret?
Nun, die Idee ist einfach. Im Prinzip betrachtet man eine Datenbanktabelle hier-
bei einfach als ein PHP-Objekt. Das heißt, die Tabelle wird mithilfe einer Klasse
gekapselt. Eine solche Klasse muss eine Kind-Klasse der Klasse Zend_Db_Table
oder Zend_Db_Tabl e_Abstract sein. Für welche Klasse Sie sich entscheiden, ist
im Endeffekt egal, da Zend_Db_Tabl e eine Kind-Klasse von Zend_Db_Table_Ab-
stract ist, aber komplett leer ist.
Die Klasse muss die Information erhalten, welche Tabelle zu nutzen ist. Diese
Information können Sie dadurch übergeben, dass Sie die Klasse genauso benen-
nen wie die Tabelle. Ebenso ist es möglich, dass Sie den Namen der Tabelle in der
Eigenschaft $_name ablegen.
Bezogen auf die Tabelle kundendaten könnte das also beispielsweise so aussehen:
dass Tabel 1 eKundendaten extends Zend_Db_Table
{
protected $_name = 'kundendaten':
// Weitere Deklarationen
1
88
e-bol.net
Datenbankzugriff mit Zend_Db_Table | 2.3
Oder so:
dass kundendaten extends Zend_Db_Table
{
// Weitere Deklarationen
}
Ich würde Ihnen die erste Variante empfehlen, die ich nachfolgend auch ver-
wende. Sie ist meiner Ansicht nach eindeutiger und lässt im Code besser erken-
nen, dass es sich um eine Datenbankzugriffsklasse handelt.
In einigen Fällen kann es vorkommen, dass der Name einer Tabelle dynamisch
generiert werden muss. So gibt es hier und da den Anwendungsfall, dass jede
Woche oder jeden Monat eine neue Tabelle genutzt werden soll, weil die Daten-
mengen pro Tabelle sonst zu groß werden. Das könnte beispielsweise dann der
Fall sein, wenn Sie Klicks auf einer Website protokollieren möchten. In so einem
Fall ist es hilfreich, wenn der Name der Tabelle dynamisch von der Klasse gene-
riert werden kann. Dazu können Sie die Methode _s et upTa bl eName() überladen,
wobei sichergestellt sein muss, dass sie die gleichnamige Methode in der Eltern-
Klasse aufruft. Die Methode _setupTableName() wird automatisch durch den
Konstruktor aufgerufen. Wollen Sie beispielsweise für jeden Monat eine eigene
Tabelle nutzen, so könnte die Deklaration der Methode so aussehen:
dass Tabel 1 eAktuel 1 eKl ickdaten extends Zend_Db_Table
{
protected function _setupTableName()
{
$aktuel1er_monat = date('m');
$this->_name = 'kl 1ckdaten_'.$aktuel1er_monat;
parent::_setupTableName():
// Weitere Deklarationen
1
Listing 2.20 Dynamisches Generieren des Tabellennamens
In diesem Beispiel würde also auf eine Tabelle zugegriffen, deren Name sich
immer aus kl i ckdaten_ und der Nummer des aktuellen Monats zusammensetzt,
im Dezember also beispielsweise kl i ckdaten_12.
Die zweite Frage bei der Initialisierung ist, wie bzw. ob Sie ein Schema, also die
Datenbank, deklarieren, in der sich die Tabelle befindet. Auch hier gibt es eine
Vielzahl von Möglichkeiten. Aus Gründen der Übersichtlichkeit würde ich Ihnen
empfehlen, dass Sie für jede Tabelle eine eigene Klasse nutzen und die Daten-
bank bzw. das Schema direkt in der Klasse fest codieren. Das ist entweder
89
e-bol.net
2 | Datenbankzugriff mit Zend_Db
dadurch möglich, dass Sie die Information direkt mit an Eigenschaft $_name über-
geben oder den Namen des Schemas in der Eigenschaft $_schema speichern. Um
auf die Tabelle kundendaten zuzugreifen, wenn diese in der Datenbank Waren-
wirtschaft liegt, können Sie also entweder die Eigenschaft $_name mit dem
String 'warenwi rtschaft. kundendaten' belegen oder in $_name den String
'kundendaten' sowie in $_schema den String 'Warenwirtschaft' abspeichern.
Auch hier können Sie eine Methode nutzen, um den Namen dynamisch generie-
ren zu lassen. Diese Methode muss hierfür den Namen _setupMetadata()
haben. Sie wird ähnlich aufgebaut wie _setupTableName(). Der Unterschied
besteht darin, dass sie die Methode _setupMetadata() aus der Eltern-Klasse auf-
rufen muss.
Der dritte Wert, den Sie setzen sollten, ist die Eigenschaft $_pri mary. Dabei han-
delt es sich um die Spalte, die den Primärschlüssel der Tabelle darstellt. Die
Klasse kann zwar auch selbst versuchen zu erkennen, welches der Primärschlüs-
sel ist. Aber das kostet Performance, und es könnte zudem passieren, dass die
entsprechende Information nicht ermittelt werden kann. Darüber hinaus ist es
für einen Dritten, der den Code lesen soll, natürlich besser zu erkennen, welches
der Primärschlüssel ist, wenn dies im Code steht. Die Klasse prüft nicht, ob es
sich bei der angegebenen Spalte wirklich um einen Primärschlüssel in der Daten-
bank handelt. Bitte beachten Sie, dass die Klasse auf jeden Fall einen Primär-
schlüssel benötigt und diesen auch nutzt. Das hat zur Folge, dass Lösch- und
Update-Operationen auf Basis dieser Information ausgeführt werden.
Bei einem Primärschlüssel ist festzulegen, wie dieser an den entsprechenden
Wert gelangt. Zum ersten wäre es natürlich möglich, ihn manuell zu übergeben.
Das kommt wahrscheinlich zumeist zum Tragen, wenn es sich um so etwas wie
einen EAN-Code oder eine Personalnummer handelt, die von einem anderen Sys-
tem generiert wird. Die zweite Variante, und diese wird Ihnen wahrscheinlich
am ehesten vertraut sein, ist die Nutzung einer Auto-Inkrement-Spalte, wie sie
von verschiedenen Datenbanksystemen zur Verfügung gestellt wird. Bei einer
solchen Spalte, die auch Autowert-Spalte genannt wird, ermittelt das System
selbst einen eindeutigen Wert für die Spalte. Die dritte Variante ist die Nutzung
einer Sequenz. In diesem Fall muss der Wert zunächst aus einer Sequenz ausge-
lesen werden und kann dann in das INSERT-Statement integriert werden.
Diese Möglichkeiten deckt die Klasse ab. In allen Fällen sollten Sie die Eigen-
schaft $_s eq uence mit einem korrekten Wert belegen. Fügen Sie den Wert manu-
ell in das Statement ein - man spricht dabei auch von einem »Natural Key« -, so
belegen Sie die Eigenschaft mit dem Wert f al se:
protected $_sequence = false;
90
e-bol.net
Datenbankzugriff mit Zend_Db_Table | 2.3
Bei der Nutzung einer Auto-Inkrement-Spalte ist die Eigenschaft mit dem boole-
schen Wert true zu belegen:
protected $_sequence = true:
Sollten Sie beispielsweise Oracle einsetzen und eine Sequenz nutzen, dann wei-
sen Sie der Eigenschaft den Namen der Sequenz zu:
protected $_sequence = 'kundenndaten_sequenz':
In beiden Fällen ermittelt die Klasse automatisch den Wert und fügt ihn in das
Statement ein.
Sollte es aus irgendwelchen Gründen notwendig sein, ist es auch hier möglich,
den Namen dynamisch zu konstruieren. In diesem Fall ist die entsprechende
Methode _setupPrimaryKey () zu benennen.
Damit wissen Sie nun schon das wichtigste über die Deklaration einer entspre-
chenden Klasse. Um auf die Tabelle zuzugreifen, benötigen Sie natürlich ein ent-
sprechendes Objekt. Dabei stellt sich die Frage, wie die Klasse auf die Datenbank
zugreifen kann. Auch hier gibt es wieder eine große Anzahl von Möglichkeiten.
Die beiden aus meiner Sicht sinnvollsten Möglichkeiten möchte ich Ihnen jetzt
vorstellen. Die erste Möglichkeit ist, dass Sie dem Konstruktor der Klasse ein ini-
tialisiertes Objekt der Klasse Zend_Db übergeben:
requi re_once 'Zend/Db/Table.php';
// Deklaration der Klasse
dass Tabel 1 eKundendaten extends Zend_Db_Table
{
protected $_name = 'kundendaten':
protected $_schema = 'Warenwirtschaft':
protected $_primary = 'id':
protected $_sequence = true:
// Weitere Deklarationen
1
Soptionen = array(
'host’ => '127.0.0.1'.
'username’ => 'root',
'password’ => '',
’dbname' => ''
):
// Datenbankobjekt ableiten
$db = Zend_Db::factory(’mysqli',$optionen);
91
e-bol.net
2 | Datenbankzugriff mit Zend_Db
// Tabellenobjekt ableiten
$tbl_kundendaten = new Tabel1eKundendaten($db);
Listing 2.21 Zugriff auf eine Tabelle mit Zend_Db_Table
Die zweite Möglichkeit besteht darin, dass Sie den Verbindungsaufbau direkt in
der Klasse mit der Methode _setupDatabaseAdapter() umsetzen. Das hat den
Vorteil, dass die Kapselung besser ist. Andererseits könnte es aber verwirren, da
an der Stelle, an der auf die Datenbank zugegriffen wird, nicht gleich zu erken-
nen ist, in welcher Datenbank die Tabelle abgelegt ist. Eine entsprechende Im-
plementation könnte beispielsweise so aussehen:
requi re_once ’Zend/Db/Table.php’;
dass TabelleKundendaten extends Zend_Db_Table
{
protected $_name = 'kundendaten';
protected $_schema = 'Warenwirtschaft';
protected $_primary = 'id';
protected $_sequence = true;
protected function _setupDatabaseAdapter()
{
$optionen = array(
'host' => '127.0.0.1'.
'username' => ’root' ,
'password’ => ’',
'dbname' => ’'
);
$db = Zend_Db;;factory(’mysqli',$optionen);
$ this->_setAdapter($db);
parent::_setupDatabaseAdapter();
$tbl_kundendaten = new Tabel1eKundendaten();
Listing 2.22 Nutzung von _setupDatabaseAdapterO
Die Ableitung des Zend_Db-Objekts erfolgt genau so, wie Sie es schon kennenge-
lernt haben. Damit es korrekt eingebunden wird, wird es an die Methode _set-
Adapter() übergeben. Hier gäbe es auch noch andere Wege, das Zend_Db-Objekt
zu übergeben; aber auf diesem Weg werden alle sinnvollen Prüfungen mit ausge-
führt. In der letzten Zeile der Methode wird die Methode _setupDatabaseAdap-
92
e-bol.net
Datenbankzugriff mit Zend_Db_Table | 2.3
ter() der Eltern-Klasse aufgerufen, um sicherzustellen, dass die dort enthaltenen
Funktionsaufrufe korrekt ausgeführt werden.
In einigen Fällen kann es hilfreich sein, die Konfiguration des Zend_Db_Table-
Objekts auszulesen. Die Methode info() stellt Ihnen dazu ein Array mit allen
Tabelleninformationen zur Verfügung, die in dem aktuellen Objekt enthalten
sind. Das heißt, Sie finden beispielsweise einen Schlüssel mit dem Namen name,
der den Namen der Tabelle enthält, und einen namens col s, der die Namen aller
Spalten enthält. Der Schlüssel metadata verweist auf ein indiziertes Array, in
dem Sie Informationen zu den einzelnen Spalten finden. Falls Sie die Daten benö-
tigen, lassen Sie das Array einfach mit var_dump() ausgeben, um die komplette
Struktur zu sehen.
Weitere Informationen finden Sie auch im Manual unter der folgenden URL:
http://framework.zend.eom/manual/en/zend.db.table.html#zend.db.table.info
Eine andere Methode, die ich noch erwähnen möchte, ist getAdapter(). Mit ihr
können Sie immer eine Referenz auf das aktuelle Datenbankzugriffsobjekt, also
den genutzten Adapter, auslesen. Damit haben Sie die Möglichkeit, auf die in der
entsprechenden Klasse enthaltenen Methoden zurückzugreifen und beispiels-
weise die quote-Funktionen zu nutzen.
2.3.1 Einfügen von Daten
Um Daten in die Tabelle einzufügen, übergeben Sie den neuen Datensatz an die
Methode i nsert(). Diese erwartet ein assoziatives Array, bei dem die Schlüssel
des Arrays den Spaltennamen entsprechen:
$tbl_kundendaten = new TabelleKundendaten();
Sdaten = array('vorname' => 'Males'.
'nachname' => "O’Brian");
$tbl_kundendaten->insert($daten);
Wie Sie an diesem Beispiel schon erahnen können, kümmert die Methode sich
auch darum, dass die Daten gequotet werden. Somit haben Sie an dieser Stelle
kein Problem mehr.
Was aber, wenn Sie eine Datenbankfunktion im Statement nutzen wollen? In
dem Fall sollten Sie diese nicht einfach als Wert angeben. Sollten Sie dies den-
noch tun, geht die Klasse davon aus, dass es sich um einen String handelt, quotet
ihn und setzt ihn in Anführungszeichen. In den meisten Fällen dürfte dies dazu
führen, dass die Funktion als String in die Spalte eingefügt und nicht vor dem
Einfügen ausgeführt wird.
93
e-bol.net
2 | Datenbankzugriff mit Zend_Db
Um solche Fälle nicht zu einem Problem werden zu lassen, ist die Klasse Zend_
Db_Expr vorgesehen. Die gewünschte Funktion bzw. der gewünschte Ausdruck
wird als Objekt dieser Klasse übergeben. Den Ausdruck übergeben Sie dem Kon-
struktor der Klasse; Sie können das Objekt auch direkt übergeben:
$tbl_kundendaten = new Tabel1eKundendaten();
$expr_now = new Zend_Db_Expr('NOW()'):
Sdaten = array('vorname' => 'Patrick',
'nachname' => 'Star' ,
'anlegedatum' => Sexpr_now);
$tbl_kundendaten->i nsert(Sdaten);
Listing 2.23 Nutzung der Klasse Zend_Db_Expr
Die Methode liefert übrigens immer den Wert derjenigen Spalte zurück, die als
Primärschlüssel deklariert wurde.
2.3.2 Aktualisieren von Daten
Möchten Sie einen Datensatz aktualisieren, so ist die Methode updateO Ihr
Freund. Auch updateO bekommt, genau wie insertO, seine Daten als assozia-
tives Array übergeben. Der zweite Parameter, den die Methode erwartet, ist die
Bedingung, die genutzt wird, um die WHERE-Klausel zu konstruieren. Bitte beach-
ten Sie, dass die Daten der gesamten Tabelle verändert werden, wenn Sie an die-
ser Stelle vergessen, eine Variable zu übergeben oder einen leeren String überge-
ben.2 Sie sollten also sicherstellen, dass die zweite Variable nicht versehentlich
leer ist.
// Neue Daten
Sdaten = array('vorname' => 'Patrick',
'nachname' => 'Seestern');
// WHERE-Bedingung korrekt quoten
Swhere = Stbl_kundendaten->getAdapter()
->quotelnto('id = ?', Sid);
if (true — empty(Swhere))
{
throw new Exception('Leere WHERE-Klausel übergeben');
}
$tbl_kundendaten->update(Sdaten, Swhere);
Listing 2.24 Nutzung der Methode updateO
2 In dem Fall werden zwar Warnungen ausgegeben, aber die Methode wird dennoch ausge-
führt.
94
e-bol.net
Datenbankzugriff mit Zend_Db_Table | 2.3
Auch hier gilt natürlich, dass Funktionsaufrufe in der Datenbank mithilfe eines
Zend_Db_Expr-Objekts zu kapseln sind. Der Rückgabewert der Funktion ist die
Anzahl der aktualisierten Zeilen.
2.3.3 Löschen von Daten
Auch die Methode zum Löschen von Daten ist denkbar einfach zu handhaben.
Sie bekommt nur einen Parameter übergeben, nämlich die Bedingung, die Ver-
wendung in der WH ER E-Klausel findet. Auch bei dieser Methode gilt, dass Sie
sicherstellen sollten, dass tatsächlich eine Bedingung übergeben wird. Übergeben
Sie einen leeren String oder eine nicht initialisierte Variable, so wird der kom-
plette Inhalt der Tabelle gelöscht.
// Bedingung aufbauen
Swhere = $tbl_kundendaten->getAdapter()
->quotelnto(’id = ?', 48);
if (true — empty(Swhere))
{
throw new Exception('Leere WHERE-Klausel übergeben’);
}
// Löschvorgang ausführen
$tbl_kundendaten->delete($where);
Listing 2.25 Löschen von Datensätzen
Möchten Sie wissen, wie viele Zeilen gelöscht wurden, ist dies kein Problem.
Denn die Methode gibt die Anzahl der entfernten Zeilen zurück.
2.3.4 Auslesen von Daten
Nachdem Sie nun wissen, wie Sie Daten in einer Tabelle speichern bzw. diese
manipulieren, stellt sich noch die Frage, wie Sie die Daten wieder auslesen kön-
nen. Es gibt verschiedene Methoden, mit denen Sie Daten aus der Tabelle ausle-
sen können. Allerdings haben alle etwas gemeinsam: Sie liefern kein Array
zurück, sondern ein Objekt der Klasse Zend_Db_Table_Rowset. Das Rowset-
Objekt enthält wiederum einzelne Objekte der Klasse Zend_Db_Table_Row, wel-
che die Daten enthalten. Eine Ausnahme stellt die Methode fetchRow() dar, die
immer exakt eine Zeile zurückgibt und somit sofort ein Row-Objekt nutzt. Dazu
erfahren Sie später mehr.
Die vielleicht einfachste Methode, um Daten zu selektieren, ist die Methode
findO. Sie »sucht« innerhalb der Tabelle auf Basis des Primärschlüssels. Das
heißt, sie bekommt einen Wert übergeben, den sie anschließend mit dem Pri-
märschlüssel abgleicht. Sollte ein Datensatz gefunden werden, gibt sie ihn als
95
e-bol.net
2 | Datenbankzugriff mit Zend_Db
Zend_Db_Tabl e_Rowset-Objekt zurück. Ein Rowset deswegen, weil Sie der
Methode auch mehrere Werte in Form eines Arrays übergeben können. Ein Auf-
ruf könnte so aussehen:
$tbl_kundendaten = new Tabel1eKundendaten();
Srowset = $tbl_kundendaten->find( 52);
$row = $rowset->current();
echo "ID: ".$row->id:
echo "<br>Vorname: ".$row->vorname;
echo "<br>Nachname: ".$row->nachname:
Listing 2.26 Selektieren von Daten
In diesem Fall wird exakt ein Datensatz gefunden, da der Methode nur ein Wert
übergeben wurde. Da es exakt ein Row-Objekt war, das in dem Rowset-Objekt
enthalten ist, können Sie dieses mithilfe der Methode current!) auslesen. Das
Rowset-Objekt implementiert übrigens das SPL-Interface Iterator, sodass es
auch direkt in einer foreach-Schleife genutzt werden kann.
Nachdem die Referenz auf das Row-Objekt ausgelesen wurde, können Sie auf die
darin enthaltenen Werte zugreifen, indem Sie die Namen der Spalten als Eigen-
schaften nutzen. Die Ausgabe des Scripts sehen Sie in Abbildung 2.3.
~ http:/.,,b/3.php CD
ID: 52
Vorname: Milcs
Nachname: O'Brian
Abbildung 2.3 Ausgabe von selektierten Daten
Sollten Sie die Nutzung von Arrays bevorzugen, ist dies auch kein Problem.
Sowohl das Rowset-Objekt als auch das Row-Objekt kennen die Methode toAr-
ray(). Rufen Sie diese bei dem Rowset-Objekt auf, so erhalten Sie ein zwei-
dimensionales Array. In der ersten Dimension ist dieses indiziert, wobei jedes
Element eine komplette Tabellenzeile darstellt. Die zweite Ebene nutzt die
Namen der Tabellenspalten als Schlüssel und enthält die Werte der jeweiligen
Zeile. Rufen Sie die Methode aus einem Row-Objekt heraus auf, erhalten Sie ein
indiziertes Array, bei dem die Spaltennamen auf den jeweiligen Wert verweisen.
96
e-bol.net
Datenbankzugriff mit Zend_Db_Table | 2.3
Eine zweite Möglichkeit, um Werte aus der Tabelle auszulesen, stellt die
Methode fetchAl 1 () dar. Übergeben Sie ihr keinen Parameter, liefert sie Ihnen
den kompletten Inhalt der Tabelle zurück. Allerdings können Sie ihr auch einige
Parameter übergeben. Wenn Sie ihr an erster Stelle eine Bedingung in Form
eines Strings übergeben, wird sie in einer WHERE-Klausel genutzt. Mit dem zwei-
ten Parameter können Sie eine Sortierreihenfolge bestimmen. Hier existieren
mehrere Möglichkeiten. Übergeben Sie nur einen Spaltennamen, so wird ent-
sprechend dieser Spalte aufsteigend sortiert. In der zweiten Variante übergeben
Sie einen Spaltennamen gefolgt von der Sortierrichtung, also ASC oder DESC.
Zugegebenermaßen stellt das keinen großen Unterschied zu der ersten Variante
dar, da in der ersten Variante die Angabe ASC implizit von der Klasse ergänzt
wird. Die dritte Möglichkeit ist, dass Sie mehrere Spaltennamen mit oder ohne
Sortierreihenfolge in einem Array übergeben. Mit den Parametern drei und vier
können Sie ein LIMIT realisieren. Der erste der beiden gibt an, wie viele Werte
ausgelesen werden sollen. Der zweite definiert den Offset, liefert also die Infor-
mation, wie viele Zeilen übersprungen werden sollen.
Swhere = 'anlegedatum > "2007-10-12"';
Ssortierung = array ('nachname ASC,
'vorname ASC');
Sanzahl = 10;
Soffset = 20;
Srowset = Stbl_kundendaten->fetchAl1(Swhere, Ssortierung,
Sanzahl, Soffset);
Mit diesen Zeilen wird ein SELECT-Statement generiert, das zehn Datensätze aus-
liest und die ersten 20 überspringt. Dabei werden nur Daten selektiert, bei denen
das Anlegedatum »größer« als der 12.10.2007 ist. Sortiert wird in erster Ebene
nach dem Nachnamen und in zweiter Instanz nach dem Vornamen.
Die Methode fetchRow() liefert immer exakt eine Zeile zurück. Somit hat es auch
keinen Sinn, mit einem Rowset zu arbeiten. Die Methode liefert nur ein Objekt
der Klasse Zend_Db_Table_Row. Die genutzte SQL-Abfrage kann allerdings auch
mehrere Treffer liefern. In dem Fall wird nur die erste Zeile aus der Ergebnis-
menge zurückgegeben. Um die Abfrage zu präzisieren, können Sie der Methode
als ersten Parameter eine Bedingung für ein WH ERE mit auf den Weg geben. Der
zweite Parameter, der ebenfalls optional ist, bestimmt, wie die Ergebnisse zu sor-
tieren sind. Hierbei existieren die gleichen Möglichkeiten, wie sie schon bei
fetchAl l () beschrieben wurden. Sollte die Abfrage kein Ergebnis ermitteln, gibt
die Methode - im Gegensatz zu den beiden anderen Methoden, die immer ein
Rowset-Objekt liefern - den Wert nul l zurück.
97
e-bol.net
2 | Datenbankzugriff mit Zend_Db
Erweiterte Techniken mit Zend_Db_Table_Rowset und Zend_Db_Table_Row
Die Zend_Db_Rowset- und Zend_Db_Table_Row-Objekte können noch einiges
mehr als das bisher Besprochene. Was sicherlich besonders spannend ist, ist die
Tatsache, dass Sie mithilfe von Row-Objekten Daten auch manipulieren und wie-
der abspeichern können.
Zuerst aber zu dem, was die Rowset-Klasse noch so zu bieten hat. Nachdem Sie
eine Abfrage ausgeführt haben, stellt sich die Frage, ob Sie ein Ergebnis erhalten
haben bzw. wie viele Zeilen darin enthalten sind. Da die Methoden fi nd () und
fetchAl 1 () immer ein Rowset-Objekt liefern, das auch leer sein kann, ist diese
Frage nicht unerheblich. Die Methode exi sts() bestätigt Ihnen durch Rückgabe
des booleschen Wertes true, dass Zeilen gefunden wurden und im Objekt ent-
halten sind. Sollten Sie den Wert false erhalten, ist das Objekt leer. Die exakte
Anzahl der Zeilen, die enthalten sind, ermittelt die Methode count(). Darüber
hinaus sind noch die Methoden definiert, die durch das SPL-Interface Iterator
vorgeschrieben sind, und einige weitere Methoden, mit denen Sie das Table-
Objekt auslesen können.
Wie schon angedeutet, können Sie mithilfe des Row-Objekts auch Daten manipu-
lieren. Im einfachsten Fall verändern Sie die Werte, die in den Eigenschaften
gespeichert sind, einfach direkt und rufen die Methode save() auf. Sie speichert
den Datensatz bzw. führt den UPDATE-Befehl aus. Interessant in diesem Zusam-
menhang ist auch die Methode setFromArray (). Sie bekommt ein Array überge-
ben, bei dem die Schlüssel den Spaltennamen der Tabelle entsprechen. Die
Werte werden dabei automatisch übernommen, sodass Sie alle Werte auf einmal
setzen können.
Um eine Zeile zu löschen, rufen Sie die Methode del ete() aus dem Row-Objekt
heraus auf, das gelöscht werden soll. Genau genommen wird natürlich nicht nur
das Row-Objekt, sondern auch die entsprechende Zeile in der Datenbank
gelöscht. An dieser Stelle möchte ich noch einmal nachdrücklich darauf hinwei-
sen, dass Sie einen gültigen Primärschlüssel benötigen, da die Löschoperation auf
Basis dieses Schlüssels ausgeführt wird. Das heißt, wenn Sie eine Spalte als Pri-
märschlüssel deklariert haben, in der Werte doppelt vorkommen, so werden alle
Zeilen gelöscht, die den gleichen Wert in der Spalte des »Primärschlüssels« ver-
wenden.
Bitte beachten Sie, dass Sie nach jeder Veränderung einer Zeile die Daten im
Rowset-Objekt wieder aktualisieren müssen, da die Operationen direkt in der
Datenbank stattfinden.
Das folgende Beispiel zeigt das Zusammenspiel der Methoden:
98
e-bol.net
Datenbankzugriff mit Zend_Db_Table | 2.3
// Tabellenobjekt ableiten
$tbl_kundendaten = new Tabel1eKundendaten();
// Alle Zeilen auslesen
Srowset = $tbl_kundendaten->fetchAl1();
// Anzahl der Zeilen ermitteln
$anzahl_zei1 en = $rowset->count():
echo "<p>Anzahl Zeilen: ".$anzahl_zei1 en;
echo "<table>";
foreach ($rowset as $row)
{
// Zeile ausgeben
echo "<tr>
<td>$row->id</td>
<td>$row->vorname</td>
<td>$row->nachname</td>
</tr>":
// Zeile löschen
$row->delete();
}
echo "</tableX/p>":
// Neue Zeile anlegen
$expr = new Zend_Db_Expr('NOW()'):
Sdaten = array ('vorname' => 'Anika',
'nachname' => 'Perroquet',
’anlegedatum’ => $expr);
// Daten einfügen
$id = $tbl_kundendaten->insert($daten);
// Rowset neu auslesen
Srowset = $tbl_kundendaten->fetchAl1();
$anzahl_zei1 en = $rowset->count():
echo "<p>Anzahl Zeilen: ".$anzahl_zei1 en;
echo "<table>";
foreach ($rowset as $row)
{
echo "<tr>
<td>$row->id</td>
<td>$row->vorname</td>
<td>$row->nachname</td>
</tr>":
}
echo "</table></p>":
Sadapter = $tbl_kundendaten->getAdapter();
Swhere = $adapter->quotelnto('id = ?',$id):
99
e-bol.net
2 | Datenbankzugriff mit Zend_Db
// Zuletzt eingefügte Zeile via id auslesen
$row = $tbl_kundendaten->fetchRow(Swhere);
// Spalte nachname ändern
$row->nachname ='Papagei’;
// Daten speichern
$row->save();
// Datensatz neu auslesen
Srowset = $tbl_kundendaten->fetchAl1();
echo "<p><table>";
foreach (Srowset as $row)
{
echo "<tr>
<td>$row->id</td>
<td>$row->vorname</td>
<td>$row->nachname</td>
</tr>";
}
echo "</table></p>";
Listing 2.27 Manipulation von Datensätzen mit Zend_Db_Table_Row
Die Ausgabe von Listing 2.27 sehen Sie in Abbildung 2.4.
n n n http://127.0....-buch/Db/4.php O
Anzahl Zeilen: 3
49 Miles O'Brian
70 Spongcbob Squarepants
69 Patrick Star
Anzahl Zeilen: 1
76 Anika Pcrroquct
76 Anika Papagei
Abbildung 2.4 Mit Zend_Db_Table_Row manipulierte Daten
Abhängigkeiten
Nachdem Sie nun schon das meiste über die Datenbankklassen wissen, möchte
ich Ihnen noch eine wirklich praktische Funktionalität vorstellen, die Code in
vielen Fällen besser lesbar macht.
Bei den meisten datenbankgestützten Anwendungen hängen Tabellen voneinan-
der ab. Resultierend aus der Normalisierung der Datenstrukturen sind Informati-
100
e-bol.net
Datenbankzugriff mit Zend_Db_Table | 2.3
onen über mehrere Tabellen verteilt. Ein Beispiel dafür sind beispielsweise die
Kundeninformationen in einem Shop-System. Jeder Kunde sollte nur einmal
angelegt sein. Da er allerdings mehrfach Ware bestellen könnte, sollten die
Bestellinformationen in einer anderen Tabelle gepflegt werden. Jede Bestellung
kann dann noch weitere Produkte enthalten. Das es auch wenig sinnvoll wäre,
die Produktinformationen direkt an die Bestellungen zu hängen, werden die Arti-
kelstammdaten in einer gesonderten Tabelle verwaltet.
Solche Tabellen manuell abzufragen, kann recht umständlich werden. Mit Zend_
Db haben Sie allerdings ein recht mächtiges Mittel zur Hand, das Ihnen viel Arbeit
abnimmt.
Für die Beispiele habe ich vier Tabellen benutzt. Das ist zum ersten die Tabelle
künden, welche die Stammdaten der Kunden verwaltet:
CREATE TABLE 'künden' (
'id' int(ll) NOT NULL AUTO_INCREMENT,
'vorname' varchar(lOO) DEFAULT NULL,
'nachname' varchar(lOO) DEFAULT NULL,
PRIMARY KEY ('id' )
) ENGINE=MyISAM DEFAULT CHARSET=1atinl
Die Bestellungen der Kunden finden sich in der Tabelle bestel 1 ungen. Welcher
Kunde welche Bestellungen ausgelöst hat, wird über die Spalte id_kunde festge-
halten, in welcher der Primärschlüssel aus der Tabelle künden genutzt wird. Auf-
gebaut ist die Tabelle so:
CREATE TABLE 'bestel1ungen' (
'id' int(ll) NOT NULL AUT0_INCREMENT,
'id_kunde' int(ll) NOT NULL,
'bestel1 datum' datetime DEFAULT NULL,
PRIMARY KEY ('id' )
) ENGINE=MyISAM DEFAULT CHARSET=1atinl
Auch die Tabelle zur Verwaltung der Artikelstammdaten, die den Namen Pro-
dukte hat, ist recht einfach aufgebaut:
CREATE TABLE 'produkte' (
'id' int(ll) NOT NULL AUT0_INCREMENT,
'bezeichnung' varchar(lOO) DEFAULT NULL,
PRIMARY KEY ('id' )
) ENGINE=MyISAM DEFAULT CHARSET=1atinl
Nun fehlt noch eine Tabelle, die die Produkte den Bestellungen zuordnet. Dabei
handelt es sich um eine kleine Verknüpfungstabelle, die bestel 1 ungenProdukte
heißt und nur die jeweiligen IDs speichert, um eine Verknüpfung herzustellen:
101
e-bol.net
2 | Datenbankzugriff mit Zend_Db
CREATE TABLE 'bestel1ungenProdukte' (
'id_bestel1ung' Int(ll) NOT NULL,
~id_produkt' int(ll) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=1atinl
Da diese Tabelle keine ID hat, werde ich die beiden Spalten als einen zusammen-
gesetzten Schlüssel nutzen. Das würde für einen realen Einsatz natürlich wenig
Sinn ergeben, da bei dieser Konstruktion ein Produkt in jeder Bestellung nur ein-
mal vorkommen darf. Sie sollten diese Tabellen also nicht unbedingt als Grund-
lage für einen Shop nutzen.
Nachdem Sie nun die Tabellen kennen, ist zu überlegen, wie diese Tabellen mit-
hilfe von Zend_Db miteinander verknüpft werden. Zunächst einmal müssen Sie
die Klassen für die Tabellen ganz normal auf Basis von Zend_Db_Table bzw.
Zen d_Db_Ta b 1 e_Ab s t ra c t deklarieren.
Allerdings müssen Sie noch einige Dinge zusätzlich deklarieren. Da wäre
zunächst die Eigenschaft $_dependentTabl es. Mit ihr geben Sie an, welche Tabel-
len von der Tabelle abhängen, deren Klasse Sie gerade deklarieren. Bezogen auf
das oben geschilderte Szenario würde also in der Klassendeklaration für die
Tabelle künden die Tabelle bestel 1 ungen in dieser Eigenschaft angegeben, da sie
von der ID des Kunden abhängig ist. Diese Eigenschaft können Sie mit einem
String oder einem Array belegen, falls es sich um mehrere voneinander abhän-
gige Tabellen handeln sollte.
Bei der Deklaration einer abhängigen Tabelle müssen Sie noch festlegen, wie sie
von einer anderen Tabelle abhängt. Dazu werden Regeln definiert, die in Form
eines assoziativen Arrays in der Eigenschaft $_referenceMap abgelegt werden.
Jede der Regeln erhält einen eigenen Namen und besteht wiederum aus einem
Array. Dieses Array muss mindestens die Schlüssel 1 col umns1, ’ refTabl eCl ass ’
und ' ref Col umn ’ enthalten. Der erste definiert dabei, welche Spalten in der aktu-
ellen Tabelle, deren Klasse Sie gerade definieren, von der anderen Tabelle abhän-
gen. Mit ’ refTabl eCl ass' geben Sie den Namen der Klasse an, welche für die
referenzierte Tabelle zuständig ist. Bitte beachten Sie, dass es sich dabei um den
Namen der Klasse und nicht um den Namen der Tabelle handelt. Der letzte
Schlüssel ’ refCol umn ’ definiert den Namen der Spalte, die den Wert enthält, der
referenziert wird. Bei der ersten sowie der letzten Eigenschaft können Sie übri-
gens auch ein Array nutzen, um die Namen der Spalten anzugeben, was aber
wohl nicht so oft vorkommen dürfte.
Damit besitzen alle Klassen die notwendigen Informationen, um eine Beziehung
zwischen den Daten herzustellen. Optional können Sie allerdings noch weitere
Informationen in den Arrays angeben, welche die Regeln definieren. Aber Vor-
102
e-bol.net
Datenbankzugriff mit Zend_Db_Table | 2.3
sicht: Diese sollten Sie nur dann verwenden, wenn die von Ihnen genutzte
Datenbankarchitektur keine »Deklarative Referentielle Integrität« (DRI) unter-
stützt. DRI bedeutet, dass Sie beim Anlegen der Tabellen gleichzeitig definieren
können, dass eine Tabelle von einer anderen abhängt. Dabei können Sie auch
festlegen, wie sich eine abhängige Tabelle verhalten soll, wenn eine Zeile
gelöscht wird. Konkret hieße das hier beispielsweise: Was soll in der Tabelle
bestell ungen passieren, falls ein Kunde, zu dem es Bestellungen gibt, gelöscht
wird? Würden die Bestellungen in der Tabelle erhalten bleiben, wären die Daten
inkonsistent, da der dazugehörige Kunden, der durch die ID in der Spalte i d_
künde referenziert wird, nicht mehr existiert. Datenbanksysteme mit DRI unter-
stützen hier ein kaskadierendes Löschen. Damit sorgt das Datenbanksystem auto-
matisch dafür, dass die abhängigen Daten gelöscht werden. DRI wird von MySQL
zurzeit nur dann unterstützt, wenn Sie InnoDB als Datenbank-Engine nutzen. Es
wäre also die bessere Variante, wenn Sie auf InnoDB setzen und der Datenbank
die Pflege der referentiellen Integrität überlassen würden. Ich habe hier bewusst
auf MylSAM gesetzt, damit ich Ihnen die Funktionalitäten von Zend_Db vorstel-
len kann.3
Sollte Ihr Datenbanksystem kein DRI unterstützen, oder sollten Sie MylSAM aus
irgendwelchen Gründen den Vorzug geben, so können Sie die beiden Schlüssel
’onDelete' und ’onUpdate' bei der Deklaration der Regeln nutzen. Der erste
definiert, was passieren soll, wenn in der Tabelle, die referenziert wird, ein
Datensatz gelöscht wird. Der zweite bestimmt, was bei einem UPDATE in der
»Eltern«-Tabelle passiert. Diesen beiden Schlüsseln können Sie entweder
sei f::CASCADE oder sei f:: RESTRICT zuweisen. Mit dem ersten Wert wird ein
kaskadierendes Verhalten bewirkt. Geben Sie 'onDelete' => seif:: CASCADE an,
so führt ein Löschen in der Eltern-Tabelle dazu, dass der dazugehörige Datensatz
bzw. die dazugehörigen Datensätze in der Kind-Tabelle gelöscht werden. Glei-
ches gilt für den Schlüssel ’onUpdate’ bei einem UPDATE, das in der Eltern-
Tabelle ausgeführt wird. Mit der Konstanten seif::RESTRICT können Sie dies
verhindern. Geben Sie diesen optionalen Schlüssel nicht an, so führt Zend_Db
übrigens auch keine weitergehenden Aktionen aus.
Nach so viel Vorrede hier nun der Code, der die Klassen deklariert:
requi re_once ’Zend/Db/Table.php’;
// Klasse um die Verbindungsdaten nur ein Mal
// deklarieren zu müssen
3 Leider sind diese Funktionalitäten momentan (ZF Version 1.0.3) noch nicht zuverlässig im-
plementiert. Bitte testen Sie vorher, ob kaskadierende Lösch- oder Update-Vorgänge funktionie-
ren.
103
e-bol.net
2 | Datenbankzugriff mit Zend_Db
dass Verbindungsdaten extends Zend_Db_Table
{
protected $_name = 'kundendaten';
protected $_schema = 'Warenwirtschaft';
protected $_primary = 'id';
protected $_sequence = true;
protected function _setupDatabaseAdapter()
{
$optionen = array(
'host' => '127.0.0.1'.
'username' => ’root'.
'password' => ’'.
'dbname' => 'Warenwirtschaft'
);
$db = Zend_Db;:factory(’mysqli',$optionen);
$ this->_setAdapter($db);
parent::_setupDatabaseAdapter();
}
// Zugriffsklasse für Tabelle künden
dass tabel 1 eKunden extends Verbindungsdaten
{
protected $_name = 'künden';
protected $_primary = 'id';
protected $_dependentTables = 'bestel1ungen’;
protected $_sequence = true;
}
// Zugriffsklasse für Tabelle bestellungen
// diese Tabelle hängt von Tabelle künden ab
// und die Tabelle bestel1ungenProdukte haengt
// von bestellungen ab
dass tabel 1 eBestel 1 ungen extends Verbindungsdaten
{
protected $_name = 'bestellungen';
protected $_primary = 'id';
protected $_sequence = true;
// Klasse der Tabelle, die von dieser abhängt
protected $_dependentTables = 'bestel1ungenProdukte’;
protected $_referenceMap = array(
// Regel für die Abhaengigkeit von künden
'Kunde' => array(
104
e-bol.net
Datenbankzugriff mit Zend_Db_Table | 2.3
’columns’ => ’id_kunde',
’refTableClass' => 'tabel1eKunden',
’refColumn' => 'id',
’onDelete' => seif::CASCADE,
’onUpdate' => seif::CASCADE
// Zugriffsklasse für Tabelle produkte
dass tabel 1 eProdukte extends Verbindungsdaten
{
protected $_name = 'produkte':
protected $_primary = 'id':
protected $_sequence = true:
// Tabelle bestel1ungenProdukte hängt von dieser ab
protected $_dependentTables = 'bestel1ungenProdukte’:
}
// Zugriffsklasse für Tabelle bestel1ungenProdukte
dass tabel 1 eBestel 1 ungenProdukte extends Verbindungsdaten
{
protected $_name = 'bestel1ungenProdukte':
// Zusammengesetzter Schlüssel
protected $_primary = array('id_bestel1ung','id_produkt'):
protected $_referenceMap = array(
'Bersteilungen' => array(
’columns’ => 'id_bestel1ung',
'refTableClass' => 'tabel1eBestel1ungen’,
'refColumn' => 'id',
’onDelete' => seif::CASCADE,
’onUpdate' => seif::CASCADE
),
'Produkte' => array(
’columns’ => 'id_produkt',
'refTableClass' => 'tabel1eProdukte’,
'refColumn' => 'i d',
’onDelete' => seif::CASCADE,
’onUpdate' => seif::CASCADE
)
);
}
Listing 2.28 Deklaration von abhängigen Tabellen
105
e-bol.net
2 | Datenbankzugriff mit Zend_Db
Damit ist die Deklaration abgeschlossen. Nun stellt sich noch die Frage, welchen
Vorteil das alles bringt. - Stellen Sie sich vor, dass Sie die Bestellungen des Kunden
Homer Simpson auslesen wollen. Dieser Kunde hat in diesem Beispiel die ID 1.
Zunächst wird die Zeile ausgelesen, die zur ID 1 aus der Tabelle künden gehört.
Sobald Sie das entsprechende Zend_Db_Tabl e_Row-Objekt ausgelesen haben,
können Sie die Methode f1 ndDependentRowsetf) die Zeilen auslesen lassen, die
davon abhängig sind. Dazu übergeben Sie der Methode den Namen der Tabellen-
klasse, die für die Kind-Tabelle definiert ist:
$tbl_kunden = new tabel1eKunden():
// Kunden auslesen
Skunde = $tbl_kunden->find(1):
// Row-Objekt auslesen
Skunde = $kunde->current():
// Abhängige Bestellungen auslesen
Jbestel1ungen = $kunde
- >f 1ndDependentRowsett’tabelleBestellungen');
echo "Bestellungen von: $kunde->vorname $kunde->nachname<br>";
foreach (Sbestel1ungen as tbestellung)
{
echo "<p>id: $bestel1ung->id";
echo "CbOBestel Idatum: $bestel 1 ung->bestel ldatum</p>" ;
1
Listing 2.29 Auslesen von abhängigen Zeilen
Die Ausgabe, die dieses Listing generiert, sehen Sie in Abbildung 2.5.
A O O http://127.0.0.1/~..en/zf-buch/Db/6.php '
»
Bestellungen von: Homer Simpson
id: 1
Bcstclldatum: 2007-11-12 12:34:29
J
Abbildung 2.5 Ausgabe einer abhängigen Zeile
Wie Sie sehen, hat die Klasse selbstständig die abhängigen Daten gefunden. Dass
in diesem Fall nur ein Datensatz ermittelt wurde, liegt daran, dass Herr Simpson
noch nicht mehr bestellt hat.
106
e-bol.net
Datenbankzugriff mit Zend_Db_Table | 2.3
Ein anderer Fall, der hier nicht zum Tragen kommt, könnte sein, dass es mehrere
Abhängigkeiten zwischen zwei Tabellen gibt. In dem Fall müssten Sie auch meh-
rere Regeln definieren. Damit der Aufruf von fi ndDependentRowset() eindeutig
bleibt, müssen Sie als zweiten Parameter noch den Namen der Regel, die genutzt
werden soll, angeben.
Das Zend Framework sieht an dieser Stelle noch eine andere Syntax vor. Und
zwar können Sie den Namen der Methode selbst »konstruieren«. Setzen Sie den
Namen einfach aus dem Schlüsselwort find und dem Namen der Klasse, auf
deren Tabelle sie zugreifen wollen, zusammen. In diesem Fall hätte der Funkti-
onsaufruf $kunde->findtabel 1 eßestel 1 ungen(); lauten müssen. Auch hier
können Sie den Namen einer Regel angeben, sofern das nötig sein sollte. Den
Namen der Regel hängen Sie dann, abgetrennt durch das Schlüsselwort By, an
den Namen der Klasse an. In diesem Beispiel würde sich der folgende Aufruf
ergeben:
Sbestel1ungen = $kunde->findtabel1eßestel1ungenByKunde():
Ob diese Vorgehensweise die Lesbarkeit des Codes verbessert, muss jeder für
sich selbst entscheiden.
Das Ganze funktioniert übrigens aus in umgekehrter Richtung. Wenn Sie Daten
aus der Tabelle bestellungen ausgelesen haben und herausfinden wollen, wel-
cher Kunde dazu gehört, ist das kein Problem. In diesem Fall ist die Methode
findParentRow() Ihr Kandidat. Sie liefert zu einem Row-Objekt die dazugehö-
rige Zeile aus der Eltern-Tabelle.
Das nächste Listing liest alle Bestellungen aus und liefert zu jeder Bestellung die
Angabe, wer sie getätigt hat:
$tbl_bestel1ungen = new tabelleßestel1ungen();
Sbestel1ungen = $tbl_bestel1ungen->fetchAl1();
foreach ($bestel1ungen as $bestellung)
{
tkunde = $bestel1ung->findParentRow('tabel1eKunden');
echo "<p>Bestel1ung: $bestel1ung->id":
echo "<br>Bestel1 datum: $bestel1ung->bestelIdatum";
echo "<br>Kunde: $kunde->vorname $kunde->nachname</p>":
}
Listing 2.30 Auslesen einer »Eltern-Zeile«
Die Methode benötigt den Namen der Klasse, die für die Eltern-Tabelle zuständig
ist, als Parameter. Zusätzlich können Sie auch den Namen einer Regel angeben.
107
e-bol.net
2 | Datenbankzugriff mit Zend_Db
Auch hier können Sie die schon oben beschriebene alternative Syntax nutzen.
Hierbei leiten Sie den Namen der Methode allerdings nicht nur mit find, son-
dern mit fi ndParent ein. Somit würden die Methodennamen in diesem Beispiel
fi ndParenttabel 1 eKunden() und findParenttabelleKundenByKunde!) lauten.
Die letzte und sicher interessanteste Methode in diesem Zusammenhang ist
fi ndManyToManyRowset!). Sie ist in der Lage, Daten auszulesen, die über eine
m.n-Verknüpfung verbunden sind. Eine solche m.n-Verknüpfung liegt in diesem
Beispiel bei der Verknüpfung der Bestellungen und der Produkte vor. Die
Methode bekommt den Namen der Zieltabelle (produkte) und den Namen der
Verknüpfungstabelle (bestel 1 ungenProdukte) übergeben. Genau genommen
natürlich nicht den Namen der Tabellen, sondern die Namen der Zend_Db_Tabl e-
Klassen, die für den Zugriff auf die Tabellen verwendet werden. Optional können
Sie als dritten und vierten Parameter noch Namen von Regeln übergeben, die
genutzt werden sollen. Die Methode liefert dann ein Rowset mit den entspre-
chenden Zeilen aus der Zieltabelle zurück.
Im folgenden Beispiel werden zunächst alle Kunden ausgelesen, dann alle Bestel-
lungen der Kunden und darauf basierend die Produkte, die zu der jeweiligen
Bestellung gehören:
$tbl_kunden = new tabel1eKunden();
Skunden = $tbl_kunden->fetchAl1();
foreach (tkunden as $kunde)
{
// Bestellungen pro Kunde auslesen
tbestel1ungen = $kunde->findDependentRowset
('tabel1eBestel1ungen');
echo "Bestellungen von: $kunde->vorname $kunde->nachname<br>";
foreach ($bestel1ungen as $bestellung)
{
echo "<p>id: tbestel1ung->id";
echo "<br>BestelIdatum: $bestel1ung->bestelIdatum";
echo "<br>Artikel:<ul>":
// Produkte pro Bestellugn auslesen
// Zieltabelle = 'tabel1eProdukte'
// Verknüpfungstabelle = 'tabel1eBestel1ungenProdukte'
$produkte = $bestel1ung->findManyToManyRowset(
'tabelleProdukte',
'tabel1eBestel1ungenProdukte');
foreach (tprodukte as $produkt)
echo "<1i>$produkt->bezeichnung</li>":
108
e-bol.net
Datenbankzugriff mit Zend_Db_Table | 2.3
echo "</ul><hr></p>";
I
}
Listing 2.31 Auslesen einer m:n-Abhängigkeit
Die generierte Darstellung sehen Sie in Abbildung 2.6.
° http://127.0.0.l/~c en/zf-buch/Db/6.php CD
U W ö w
Bestellungen von: Homer Simpson
id: 1
Bestelldatum: 2007-11-12 12:34:29
Artikel:
• Ausreden für Sicherheitsingenieure
• Bleiwestc gegen atomare Strahlung
Bestellungen von: Apu Nahasapccmapctilon
id: 2
Bestendatum: 2007-11-11 19:41:59
Artikel:
• 24h wach bleiben - Leitfaden für Ladenbesitzer
• Verändern von Haltbarkcitsdatcn
• Verklagt! Was nun?
Abbildung 2.6 Daten aus einer m:n-Verknüpfung
Auch an dieser Stelle können Sie wieder die schon beschriebene alternative Syn-
tax nutzen. Hierbei benutzen Sie das Schlüsselwort find und geben dann den
Namen der Zieltabelle, gefolgt von Vi a und dem Namen der Verknüpfungstabelle
an. Auch hier sind natürlich die Namen der Klassen zu gebrauchen. In diesem
Beispiel würde der Methodenaufruf wie folgt aussehen:
$bestellung->findtabelleProdukteVi ata bei 1eBestel1ungenProdukte():
Möchten Sie noch explizit eine Regel angeben, so können Sie By anhängen und
danach den Namen der Regel ergänzen. Benötigen Sie danach noch eine zweite
Regel, so geben Sie And und dann den Namen der zweiten Regel an.
2.3.5 Kaskadierende Lösch- und Update-Vorgänge
Wie eingangs bereits erwähnt, unterstützt Zend_DB_Tabl e auch kaskadierende
Lösch- und Update-Vorgänge, falls Ihr Datenbankserver kein DRI unterstützen
109
e-bol.net
2 | Datenbankzugriff mit Zend_Db
sollte. Haben Sie die Schlüssel onUpdate und onDelete bei den Regeln mit der
Konstanten sei f:: CASCADE belegt, kümmert sich die Klasse automatisch um alles
Notwendige. Hierbei ist allerdings zu beachten, dass die Änderungen nur dann
ausgeführt werden, wenn die Änderungen über die Row-Objekte vorgenommen
werden. Das heißt, wenn Sie Änderungen mit save() speichern oder eine Zeile
mit del ete() löschen, werden auch die abhängigen Daten geändert. Dies bedeu-
tet: Würden Sie so die ID eines Kunden ändern, würde auch die Spalte id_kunde
in der Tabelle bestel 1 ungen aktualisiert:
Skunden = $tbl_kunden->find(2);
Skunde = $kunden->current():
$kunde->id = 668;
$kunde->save();
2.4 Performanceanalyse mit Zend_Db_Profiler
Bei umfangreichen Anwendungen ist die Geschwindigkeit der Datenbankanfra-
gen oft ein entscheidendes Problem. Stellen Sie fest, dass eine Anwendung zu
langsam ist, so gibt es verschiedene Strategien, wie Sie vorgehen können. Um Sie
an dieser Stelle zu unterstützen, ist in Zend_Db ein Profiler integriert. Dieser kann
für Sie protokollieren, wie lange die Ausführung der Abfragen gedauert hat.
Sie können den Profiler auf verschiedenste Weisen einschalten. Ich möchte
Ihnen hier nur zwei Möglichkeiten vorstellen. Weitere finden Sie in der Doku-
mentation zu Zend_Db_Profi1 er.
Die erste Möglichkeit ist, dass Sie dem Zend_Db-Objekt, das Sie ableiten, die ent-
sprechende Information direkt übergeben. Und zwar können Sie dazu in den
Optionen für den Verbindungsaufbau zusätzlich den Schlüssel profi 1 er mit dem
Wert true belegen.
Die zweite Variante - und das ist auch diejenige, die ich hier nutzen werde -
besteht darin, den Profiler über einen Methodenaufruf zu aktivieren. Dazu müs-
sen Sie zunächst eine Referenz auf den Profiler auslesen, was Sie mit getProf i -
1 er() machen können. Mit dieser Objektreferenz können Sie die Methode set-
Enabled() aufrufen, der Sie ein true oder false übergeben, um den Profiler ein-
bzw. auszuschalten. Bitte setzen Sie den Profiler nur gezielt ein, da er das System
langsamer macht. Er sollte daher nicht ständig im Hintergrund mitlaufen. Eine
Nutzung könnte beispielsweise so aussehen:
$db = Zend_Db::factory(’mysqli',$optionen);
// Referenz auf Profiler auslesen
Sprofiler = $db->getProfiler();
110
e-bol.net
Performanceanalyse mit Zend_Db_Profiler | 2.4
// Profiler einschalten
$profiler->setEnabled(true);
// 1000 INSERTs ausführen
for ($i = 0; $i<1000; $i++)
{
$db->query("INSERT INTO plz (plz) VALUES (?)",
mt_rand(1000.99999));
I
// 1000 SELECTs ausführen
for ($i = 0; $i<1000; $i++)
{
$db->query("SELECT * FROM plz WHERE id = ? OR id = ?".
array(mt_rand(l,1000), mt_rand(1,1000)));
}
// Ergebnisse des Profilers ausgeben
echo "Gesamtzahl der Abfragen: ".$profi1er->getTotalNumQueriesI);
echo "<br>- Anzahel der INSERTs: ";
echo $profiler->getTotalNumQueries(Zend_Db_Profi1er::INSERT);
echo "<br>- Anzahel der SELECTs: ";
echo $profiler->getTotalNumQueries(Zend_Db_Profi1er::SELECT);
echo "<br>Dauer aller Abfragen:
echo $profi1er->getTotalElapsedSecsf)." Sekunden";
echo "<br>- Dauer der INSERTs: ";
echo Sprofi1er->getTotalElapsedSecs(Zend_Db_Profi1 er::INSERT).
" Sekunden";
echo "<br>- Dauer der SELECTs: ";
echo $profiler->getTotalElapsedSecs(Zend_Db_Profi1er::SELECT).
" Sekunden";
Listing 2.32 Nutzung des Profilers
Dieser Code erzeugt die Darstellung, die Sie in Abbildung 2.7 sehen.
C> n n http://127.O.O.l/.../zf-buch/Db/7.php CD
»
Gesamtzahl der Abfragen: 2000
- Anzahcl der INSERTs: 1OOO
- Anzahcl der SELECTs: 1000
Dauer aller Abfragen: 0.677295207977 Sekunden
- Dauer der INSERTs: 0273243188858 Sekunden
- Dauer der SELECts; 0.404052019119 Sekunden
—
Abbildung 2.7 Daten, die der Profiler ermittelt hat
111
e-bol.net
2 | Datenbankzugriff mit Zend_Db
Die Methode getTotal NumQueries() liefert Ihnen die Gesamtzahl der Anfragen
zurück, die protokolliert wurden. Wie bei den meisten anderen Methoden auch
können Sie hier einen Parameter übergeben, der dafür sorgt, dass Sie genauere
Daten erhalten. Das heißt, Sie können mit der Konstante Zend_Db_Prof i -
ler:: INSERT spezifizieren, dass Sie nur Daten zu den INSERT-Statements auslesen
wollen, die ausgeführt wurden. Weitere Konstanten sind: Zend_Db_Prof i -
ler::SELECT, Zend_Db_Profiler::CONNECT, Zend_Db_Profi 1 er::DELETE, Zend_
Db_Profi 1 er: :TRANSACTION und Zend_Db_Profi 1 er::UPDATE. Die meisten
Namen sind hier sicher selbsterklärend. Mit TRANSACTION rufen Sie Informatio-
nen zu den Punkten ab, die eine Transaktion betreffen, also COMMIT etc. Interes-
sant ist noch die Konstante Zend_Db_Profiler::QUERY, mit der Sie Informatio-
nen über alle Queiy-Typen abrufen können, die durch die bereits genannten
Konstanten noch nicht abgedeckt sind.
Die Methode getTotal El apsedSecs() teilt Ihnen mit, wie lange die Ausführung
der Abfragen gedauert hat. Auch hier können Sie wieder die Konstanten nutzen,
um genauer spezifizierte Informationen zu erhalten.
Damit erhalten Sie schon einen schnellen Überblick. Allerdings sind diese Daten
noch recht allgemein. Gerade wenn Sie ein umfangreiches Script analysieren
wollen, sind die Informationen oft zu allgemein. Der Profiler protokolliert aber
die Daten zu jeder Abfrage einzeln. Um Daten zu einer Abfrage zu erhalten, kön-
nen Sie die Methode getQueryProfi1e() nutzen, welche die ID einer Abfrage
übergeben bekommt. ID heißt an dieser Stelle, dass die Abfragen einfach der
Reihe nach, beginnend mit 0, durchnummeriert werden. In den meisten Fällen
wird man vermutlich nicht unbedingt wissen, welche der Abfragen man genauer
analysieren sollte. Daher können Sie auch alle Profile auf einmal auslesen, indem
Sie die Methode getQueryProfi 1 es () aufrufen. Bei dieser Methode können Sie
auch die bereits erwähnten Konstanten nutzen, um nur SELECT-Satements,
UPDATE-Statements o.Ä. auszulesen. Interessant in diesem Zusammenhang ist,
dass Sie diese Konstanten auch mit einem Bit-Oder (|) kombinieren können.
Sollte Sie nur die letzte Abfrage interessieren, dann ist getLastQueryProfi 1 e ()
Ihr Freund.
Mit diesen Methoden erhalten Sie ein Objekt der Klasse Zend_Db_Profi 1 er_
Query bzw. bei getQueryProfi1es() ein Array mit entsprechenden Objekten.
Die folgenden Zeilen lesen die Informationen zu einer bestimmten Abfrage aus:
// Query auslesen
Squery = $profiler->getQueryProfile( 1004);
// Abfrage
echo "<br>Query: ".$query->getQuery();
// Parameter bei prepared Statements
112
e-bol.net
Performanceanalyse mit Zend_Db_Profiler | 2.4
echo "<br>Parameter: <pre>";
print_r($query->getQueryParams()):
echo "</pre>";
// Typ der Abfrage
echo "<br>AbfrageTyp: ".$query->getQueryType();
// Laufzeit des Statements
echo "<br>Laufzeit: ".$query->getElapsedSecs();
Die Methode getQuery() liest die Abfrage aus und gibt sie als String zurück.
Hierbei bleiben auch die Platzhalter in der Abfrage erhalten. Die in der Abfrage
genutzten Parameter gibt Ihnen die Methode getQueryParamsO in Form eines
Arrays zurück. Der Typ der Abfrage wird mit der Methode getQueryTypet)
ermittelt. Sie liefert Ihnen einen Integer-Wert zurück. Diesen können Sie mit den
Zend_Db_Profi 1 er-Konstanten vergleichen. Dieses Beispiel liefert den Wert 32
zurück, was Zend_Db_Profi 1 er: :SELECT entspricht. Wie lange der Datenbank-
server für die Ausführung des Statements benötigt hat, gibt die Methode get-
E1 apsedSecs() an. Die Ausgabe, die von diesen Zeilen generiert wird, sehen Sie
in Abbildung 2.8.
n http://127.0.0.l/~carsten/zf-buch/Db/7.php G3
Query: SELECT * FROM plz WHERE id = ? OR id = ?
Parameter
Array
(
[1] -> 55
(2) -> 729
AbfirageTyp: 32
Laufzeit: 0.000493049621582
Abbildung 2.8 Informationen zu einer einzelnen Query
Diese Funktionalitäten liefern Ihnen ein mächtiges Mittel, um das Performance-
Verhalten Ihrer Anwendung zu analysieren und zu verbessern. Allerdings kann
die Menge der Informationen schnell sehr groß werden. Da Sie in den meisten
Fällen sicher nur Informationen über die Statements haben möchten, die eine
höhere Laufzeit aufweisen, können Sie daher auch hinsichtlich der Laufzeit fil-
tern. Mit der Methode setFi 1 terEl apsedSecs(5) legen Sie fest, dass nur SQL-
Statements protokolliert werden, die eine Laufzeit von mindestens fünf Sekun-
den gehabt haben. Übergeben Sie der Methode den Wert nul 1, so wird der Filter
wieder gelöscht.
113
e-bol.net
e-bol.net
Die Wahrheit ist selten so oder so. Meist ist sie so oder so.
- Geraldine Chaplin
3 Benutzer- und Rechtemanagement
Die meisten Anwendungen, die heutzutage entwickelt werden, benötigen
zumindest die Möglichkeit, bestimmte Bereiche mit einem Passwort zu sichern.
In vielen Fällen müssen die Möglichkeiten auch deutlich ausgefeilter sein, und es
muss unterschiedliche Rechte für verschiedene Benutzergruppen geben. In die-
sem Kapitel finden Sie alles, was Sie dazu benötigen, inklusive der Verwaltung
von Sessions mithilfe von Zend_Session.
3.1 Rechteverwaltung mit Zend_Acl
Sicher kennen Sie das folgende Problem: Sie wollen eine Webanwendung erstel-
len und bestimmte Funktionalitäten mit Passwörten sichern. Falls es nur ganz all-
gemein einen oder mehrere Administratoren, die alle die gleichen Rechte haben,
gibt, ist dies unproblematisch. Wollen Sie nun aber mehrere Benutzergruppen
einrichten, die unterschiedlich viele Rechte haben, so wird dies schnell aufwän-
dig.
Gehen wir davon aus, dass Sie eine kleine Online-Zeitung erstellen wollen. Es
soll »einfache« Leser geben sowie Leser, die sich registriert haben und somit
kommentieren dürfen. Darüber hinaus soll es Redakteure geben, die neue Texte
erstellen dürfen. Zu guter Letzt gibt es natürlich noch einen Administrator, der
allerdings nicht zur eigentlichen Redaktion gehört und somit keine Artikel verfas-
sen darf. Auf den ersten Blick ist das schnell verwirrend, wie ich finde. Aber las-
sen Sie uns das Ganze ein wenig strukturierter angehen.
Der einfache Leser darf nur lesen, er hat also die wenigsten Rechte. Ein registrier-
ter Leser darf kommentieren, aber natürlich auch lesen. Das heißt, er hat mehr
Rechte. Man könnte auch einfach sagen, dass er die Rechte des einfachen Lesers
hat bzw. erbt und noch eigene Rechte hinzu bekommt. Ein Redakteur wiederum
darf auch lesen und kommentieren und darf darüber hinaus Artikel schreiben.
Ähnlich verhält es sich bei dem Administrator. Dieser darf auch lesen und kom-
115
e-bol.net
3 | Benutzer- und Rechtemanagement
mentieren. Er erbt somit die Rechte des registrieren Benutzers, allerdings darf er
selbst keine Artikel schreiben. Somit erbt er nicht die Rechte des Redakteurs.
In diesem kleinen Beispiel haben wir es mit verschiedenen Gruppen zu tun. Sol-
che Gruppen werden in der Anwendungsentwicklung gerne als Rollen bezeich-
net. Etwas abstrakter formuliert ist eine Rolle ein Objekt, das etwas anfordert.
Das was angefordert wird, sind die Rechte, von denen in dem Beispiel die Rede
war. Diese werden in diesem Zusammenhang auch gerne als Ressourcen bezeich-
net. Mit anderen Worten: Eine Rolle fordert eine Ressource an. Eine Rolle kann
ein einzelner Benutzer oder eben auch eine Gruppe von Benutzern sein, sofern
Sie die Benutzer der Rolle entsprechend zuordnen. Eine Ressource muss nicht
immer nur ein Recht im eigentlichen Sinn sein, sondern kann beispielsweise
auch die Nutzung einer echten Ressource (wie der Zugriff auf eine Datenbank)
darstellen.
Mit Zend_Acl, das ACL steht übrigens für Access Control List, können Sie ein sol-
ches Rechtesystem schnell und einfach aufbauen. Nachdem Sie ein ACL-Objekt
abgeleitet haben, können Sie in diesem Rollen anlegen. Die Klasse sieht hierbei
keine Unterscheidung vor, ob es sich bei den Rollen um einzelne Benutzer oder
um Gruppen handelt. Das ist im Endeffekt eine Frage der Implementation, die
Ihnen überlassen bleibt.
Nachdem die Rollen angelegt sind, werden Ressourcen angelegt und schließlich
die Rechte der Rollen zu den Ressourcen gesetzt. Bezogen auf das obige Beispiel
könnte der Aufbau des Rechtesystems so aussehen:
// Benötigte Klassen inkludieren
require_once 'Zend/Acl.php';
requi re_once 'Zend/Acl/Ro1e.php’;
requi re_once 'Zend/Acl/Resource.php';
// Neue Access Control List ableiten
$acl = new Zend_Acl():
// einfachen Leser anlegen
$acl->addRole(new Zend_Acl_Role(’leser’));
// Registrierten User anlegen: er erbt von leser
$acl->addRole(new Zend_Acl_Role('reg_leser'),1leser’);
// Redakteur anlegen: er erbt von leser und reg_leser
$acl->addRole(new Zend_Acl_Role('redakteur'),'reg_leser’);
// Ressourcen anlegen
$acl->add(new Zend_Acl_Resource('lesen'));
$acl->add(new Zend_Acl_Resource('kommentieren'));
116
e-bol.net
Rechteverwaltung mit Zend_Acl | 3-1
$acl->add(new Zend_Acl_Resource('artikel_schreiben'));
// Rechte vergeben
$acl->allow('lesen', 'lesen');
$acl->allow(’reg_leser', 'kommentieren');
$acl->allow(’redakteur', 'artikel_schreiben’);
// Rechte abfragen
if (true === $acl->isAllowed('leser', 'lesen'))
{
echo "Ja, 'lesen' darf lesen<br>";
}
if (true === $acl->isAllowed('leser', 'kommentieren'))
{
echo "Ja, 'lesen' darf kommentieren<br>";
}
el se
{
echo "Nein, 'lesen' darf nicht kommentieren<br>";
}
if (true === $acl->isAllowed('redakteur', 'kommentieren'))
{
echo "Ja, 'redakteur' darf kommentieren<br>";
}
Listing 3.1 Erstellen einer ACL
Nachdem das neue ACL-Objekt abgeleitet wurde, können die einzelnen Rollen
mithilfe von addRol e() angemeldet werden. Eine Rolle besteht in diesem Fall aus
einem Zend_Acl_Role-Objekt. Der Konstruktor der Klasse Zend_Acl_Role
bekommt als Parameter den Namen der Rolle übergeben. Genau genommen han-
delt es sich dabei nicht um den Namen, sondern um eine eindeutige ID. So kön-
nen Sie also auch eine Zahl, wie beispielsweise eine ID aus einer Datenbank, nut-
zen. Wenn Sie ein solches Objekt einmal abgeleitet haben, können Sie den
Namen bzw. die ID auch jederzeit mithilfe der Methode getRol eld() wieder aus
dem Objekt auslesen.
Die Methode addRol e() unterstützt auch einen zweiten Parameter, wie Sie bei
dem zweiten und dritten Aufruf sehen. An zweiter Stelle können Sie der
Methode die Information übergeben, von welcher Rolle oder von welchen Rol-
len die neue Rolle Rechte erben soll. Das heißt, Sie können hier auch ein Array
117
e-bol.net
3 | Benutzer- und Rechtemanagement
mit Namen von Rollen übergeben. Anstelle der Namen können Sie alternativ
auch Rollen-Objekte übergeben.
Das Anlegen der Ressourcen erfolgt auf einem ähnlichen Weg. Zunächst wird
eine Ressource generiert, in diesem Fall wird also ein Objekt der Klasse Zend_
Acl_Resource abgeleitet. Auch hier erhält der Konstruktor wieder einen Namen
bzw. eine ID übergeben. Das so generierte Objekt wird an die Methode add()
übergeben, die es dann in der Ressourcenliste einträgt. Übrigens ist es auch hier
möglich, eine oder mehrere übergeordnete Ressourcen als zweiten Parameter zu
übergeben.
Mit der Methode al 1 ow() wird schließlich festgelegt, welche Rolle Zugriff auf
welche Ressource hat. Mithilfe der Methode deny() können Sie übrigens auch
explizit den Zugriff auf eine Ressource unterbinden. Im Normalfall ist das nicht
nötig, kann bei der Vererbung von Rechten aber einen deutlichen Unterschied
machen, wie Sie in Abschnitt 3.1.1 nachlesen können. Falls Sie mit al 1 ow() und
deny() arbeiten, sollten Sie im Hinterkopf behalten, dass die beiden Methoden
sich gegenseitig überschreiben. Wenn Sie also einen Zugriff mit deny() verboten
haben, so können Sie ihn danach wieder mit al 1 ow() gewähren.
Mithilfe von 1 sAl 1 owed() kann schließlich geprüft werden, ob eine Rolle Zugriff
auf eine bestimmte Ressource hat. Vergeben Sie keine Zugriffs rechte, so geht das
Paket standardmäßig davon aus, dass das Recht nicht gewährt wurde. Hierbei
müssen Sie bitte beachten, dass sowohl die Ressource als auch die Rolle angelegt
sein müssen, da sonst die Abfrage in einer Exception resultiert.
Die Ausgabe von Listing 3.1 sehen Sie in Abbildung 3.1.
ODO http://127.0.0.1/~ca . en/zf-buch/acl/l.php
0 Q Google »
□□ Planet PHP (122) Apple (19)t Amazon eBay »
Ja, ’lcscr1 darf lesen
Nein, ’lcscr* darf nicht kommentieren
Ja, 'redaktcuf darf kommentieren
Abbildung 3.1 Ausgabe der Benutzerverwaltung
3.1.1 Vererbung von Rechten
Im vorherigen Kapitel haben Sie schon ein kleines Beispiel gesehen, wie eine Ver-
erbung von Rechten aussehen kann. Allerdings ist das in einigen Fällen ein wenig
trickreich, wie die folgenden Beispiele zeigen. Das erste Beispiel verhält sich so,
118
e-bol.net
Rechteverwaltung mit Zend_Acl | 3-1
wie Sie es erwarten. Die Rollen redakteur und redakteur_2 haben beide das
Recht zu kommentieren:
$acl->addRole(new Zend_Acl_Role('leser'));
$acl->addRole(new Zend_Acl_Role('reg_leser'), 'leser');
$acl->add(new Zend_Acl_Resource('lesen'));
$acl->add(new Zend_Acl_Resource('kommentieren'));
$acl->allow('leser' , 'lesen');
$acl->al1ow(’reg_leser', 'kommentieren');
$parents_redakteur = array ('reg_leser', 'leser');
$acl->addRole(new Zend_Acl_Role('redakteur'),$parents_redakteur);
if (true === $acl->isAl1owed('redakteur','kommentieren'))
{
echo "'redakteur' darf kommentieren";
}
$parents_redakteur = array ('leser', 'reg_leser’);
$acl->addRole(new Zend_Acl_Role('redakteur_2'),
$parents_redakteur);
if (true === $acl->isAl1owed('redakteur_2',’kommentieren'))
{
echo "’redakteur_2' darf kommentieren";
}
Im nächsten Beispiel wurde nur eine Zeile ergänzt. Hier wurde der Rolle leser
das Recht zu kommentieren explizit entzogen, was keinen großen Unterschied
machen sollte, da ein Recht, das nicht explizit zugestanden wurde, als nicht
gewährt gilt.
// Anlegen der Ressourcen und Rollen wie vorher
$acl->allow('leser' , 'lesen');
$acl->deny('leser', ’kommentieren');
$acl - >al1ow(’reg_leser', 'kommentieren');
$parents_redakteur = array ('reg_leser', 'leser');
$acl->addRole(new Zend_Acl_Role('redakteur'),$parents_redakteur);
if (true === $acl->isAl1owed('redakteur','kommentieren'))
{
echo "'redakteur' darf kommentieren";
}
$parents_redakteur = array ('leser', 'reg_leser’);
119
e-bol.net
3 | Benutzer- und Rechtemanagement
$acl->addRole(new Zend_Acl_Role('redakteur_2'),
$parents_redakteur);
if (true === $acl->isAl1owed('redakteur_2',’kommentieren'))
{
echo "’redakteur_2' darf kommentieren";
}
// Ausgabe:
// 'redakteur_2' darf kommentieren
In diesem Beispiel hat die Rolle redakteur kein Recht mehr, auf die Ressource kom-
mentieren zuzugreifen, obgleich redakteur und redakteur_2 augenscheinlich
gleich angelegt sind. Dieses Verhalten resultiert aus einer Besonderheit von Zend_
Acl. Und zwar werden die übergeordneten Gruppen analysiert, und es wird
geprüft, ob für die fragliche Ressource ein Recht gesetzt ist. Solange kein entspre-
chender Eintrag gefunden wurde, wird der nächste Eintrag abgearbeitet. Das heißt:
Falls nicht explizit eine Information zu einer Ressource gefunden wird, wird die
nächste Rolle analysiert. Sobald eine Information gefunden wird, wird die Bearbei-
tung abgebrochen und die gefundene Information zurückgegeben. Da die überge-
ordneten Rollen aber von hinten nach vorne analysiert werden, wird bei der Rolle
redakteur erst die Rolle leser analysiert. Bei dieser ist im zweiten Beispiel ein
explizites Verbot enthalten, das in der ersten Variante noch nicht vorhanden war.
Das Verbot wird gefunden und die Verarbeitung abgebrochen. Möchten Sie tat-
sächlich mit Vererbung arbeiten, so sollten Sie dieses Verhalten immer beachten.
3.1.2 Verfeinern des Rechtesystems
In den bisherigen Beispielen wurden immer Rollen genutzt, denen der komplette
Zugriff auf eine Ressource gestattet oder entzogen wurde. Möchten Sie ein kom-
plexeres Rechtesystem entwerfen, so wird diese Vorgehensweise aber schnell
sehr aufwändig. Daher können Sie das System noch verfeinern. Und zwar kön-
nen Sie für jede Ressource noch explizit Rechte vergeben:
// Anlegen der Ressourcen und Rollen
// leser dürfen Artikel und Kommentare
// lesen aber nicht schreiben
$acl->allow('leser' , ’artikel', 'lesen');
$acl->allow('leser' , 'kommentar', 'lesen');
$acl->deny('1eser', 'kommentar', 'schreiben');
// reg_leser erbt von leser, darf Kommentare aber auch schreiben
$acl->al1ow(’reg_leser', 'kommentar',
array ('lesen', 'schreiben'));
120
e-bol.net
Rechteverwaltung mit Zend_Acl | 3-1
if (true === $acl->isAllowed('reg_leserkommentar’))
{
// Wird nicht ausgegeben
echo "'reg_leser' darf etwas mit Kommentaren machen";
}
if (true === $acl->iSAUowed('reg_leser',
'kommentar’, *schreiben’))
{
// Wird ausgegeben
echo "'reg_leser' darf Kommentare schreiben";
}
Listing 3.2 Verfeinern der Zugriffsrechte
In diesem Beispiel werden für die Ressourcen artikel und kommentar explizit
Rechte vergeben. Die Rolle l eser darf Artikel und Kommentare lesen, wohinge-
gen die Rolle reg_user auch das Recht schreiben auf die Ressource kommentar
hat. Die Rechte werden also einfach als weiterer Parameter in Form eines Strings
oder Arrays an die Methode al l ow() bzw. denyO übergeben. Auch bei der
Abfrage, ob eine Rolle ein bestimmtes Recht hat, muss neben der gewünschten
Ressource noch das fragliche Recht mit angegeben werden. Bei der ersten
Abfrage mithilfe von isAllowed() wurde dieser dritte Parameter nicht überge-
ben, was dazu führte, dass die Methode fal se zurückgab.
Die Nutzung von Rechten vereinfacht zwar schon vieles, aber an einigen Stellen
wird das sicher noch unzureichend sein. In vielen Fällen werden Sie noch das
Problem haben, dass das Gewähren eines Rechts von bestimmten zusätzlichen
Faktoren abhängt. So könnte es beispielsweise sinnvoll sein, dass ein Redakteur
nachts in der Zeit zwischen 2 und 4 Uhr keine Artikel schreiben darf, da zu dem
Zeitpunkt gerade ein Backup der Daten durchgeführt wird.
Auch solche Szenarien können ohne Probleme abgebildet werden. Dazu wird mit
einer sogenannten Assertion, einer Annahme, gearbeitet. Eine solche Assertion
wird in Form einer eigenen Klasse implementiert, in der es eine Methode
namens assertO geben muss. Diese Klasse muss das Interface Zend_Acl_
Assert_Interface implementieren. Ein Objekt dieser Klasse wird dann als letz-
ter Parameter, also nach Rolle, Ressource und Recht, an die Methode allow()
übergeben:
$acl->allow(’redakteur’, 'artikel*, 'schreiben',
new Uhrzei tCheck());
121
e-bol.net
3 | Benutzer- und Rechtemanagement
Bei jedem Test, ob eine Ressource von einer Rolle genutzt werden darf, wird
auch die Methode assert() in dem Objekt aufgerufen. Die Methode muss das
ACL-Objekt, ein Rollen-Objekt, ein Ressourcen-Objekt sowie ein Recht als Para-
meter akzeptieren, wobei das Recht ein optionaler Parameter ist, da es ja nicht
immer genutzt wird. Da die Klassen für das Rollen- und das Ressourcen-Objekt
jeweils Interfaces implementieren, ist es sinnvoll, hier mit Typehinting zu arbei-
ten. Die Methode assertO sollte immer dann true zurückgeben, wenn »aus
Sicht der Methode« nichts dagegen spricht, der Rolle den Zugriff auf die Res-
source zu gewähren. Eine solche Klasse könnte beispielsweise wie folgt imple-
mentiert werden:
dass UhrzeitCheck Implements Zend_Acl_Assert_Interface
{
public function assert(Zend_Acl $acl,
Zend_Acl_Role_Interface $role = null,
Zend_Acl_Resource_Interface $resource = null,
$pri vi1 ege = nul1)
{
// Nur zur Sicherheit...
// Wurde die Methode mit der passenden Ressource
// und dem passenden Recht aufgerufen?
if (’artikel’ 1= $resource->getResource!d() ||
'schreiben' != $privilege)
return true:
1
// Uhrzeit auslesen
$stunde = date( 'M');
// Innerhalb der "verbotenen Zeit"?
if ($stunde >= 2 && $stunde <= 4)
{ // Dann geben wir false zurueck
return false:
1
el se
{ // Sonst ist alles OK und wir geben true zurück
return true:
}
}
Listing 3.3 Erstellen einer Assertion
Auch der Methode deny () können Sie ein solches Objekt übergeben. Momentan
ist es allerdings noch so, dass der Rückgabewert der Methode assertO hierbei
122
e-bol.net
Rechteverwaltung mit Zend_Acl | 3-1
keinen Einfluss auf das Verhalten von deny() hat, was ja auch Sinn hat, da etwas,
das verboten ist, immer verboten sein sollte, um kein Sicherheitsleck zu provo-
zieren.
Interessant ist übrigens auch die Möglichkeit, dass Sie das Verhalten der Access
Control List global mithilfe einer Assertion steuern können. Wenn Sie in der Zeit
zwischen 2 und 4 Uhr alle Aktionen unterbinden wollen, so könnten Sie das so
implementieren:
$acl->allow(null, null, null, new UhrzeitCheck()):
Dazu müssten Sie in der Assertion allerdings die Abfrage der übergebenen Para-
meter entfernen.
3.1.3 Manipulieren von Rechten
Die Rechtevergabe, wie Sie sie bisher kennengelernt haben, ist für viele Fälle aus-
reichend. Die Initialisierung der Access Control Liste können Sie sicher in den
meisten Fällen in das Bootstrap-File integrieren. Sollten Sie allerdings ein sehr
komplexes Rechtesystem nutzen, so könnte es vorteilhaft sein, das Zend_Acl-
Objekt im Dateisystem oder in einer Session zu speichern. In diesem Fall können
Sie es einfach serialisieren und speichern. Soll ein ACL-Objekt allerdings über
einen längeren Zeitraum genutzt werden, so kann es passieren, dass Sie die dort
enthaltenen Rechte ändern müssen.
Möchten Sie eine oder mehrere Ressourcen entfernen, so helfen remove() und
removeAl l () Ihnen weiter. Die erste Methode erhält eine bestimmte Ressource
als Parameter übergeben, die dann zusammen mit allen eventuell vorhandenen
Kind-Ressourcen entfernt wird. Die zweite Methode eliminiert alle vorhandenen
Ressourcen auf einmal. Das funktioniert natürlich ebenso mit Rollen. Diese kön-
nen Sie mit removeRole() einzeln oder mit removeRol eAl l () alle auf einmal ent-
fernen. Um einzelne Rechte oder Verbote zu entfernen, sind die Methoden r emo -
veAllowO und removeDeny() deklariert. Beide Methoden akzeptieren bzw.
benötigen die gleichen Parameter wie die Methoden al low() bzw. deny(), mit
denen die Rechte im Vorfeld vergeben wurden:
// Vergabe des Rechts
$acl->allow('leser', ’artikel', 'lesen'):
// Hier ist natürlich noch ganz viel Code dazwischen
// Entziehen des Rechts
$acl->removeAllow('leser',’artikel' , 'lesen');
In solchen Zusammenhängen kann es hilfreich sein herauszufinden, ob
bestimmte Rollen oder Ressourcen im ACL bekannt sind. Für solche Abfragen
123
e-bol.net
3 | Benutzer- und Rechtemanagement
können Sie auf die Methoden has() und hasRole() zurückgreifen. Die erste
bekommt eine Ressource übergeben und bestätigt das Vorhandensein derselben
mit true bzw. liefert false zurück, um Ihnen mitzuteilen, dass die Ressource
nicht bekannt ist. Die Methode hasRole() bietet die gleiche Funktionalität für
Rollen.
Um festzustellen, ob Ressourcen oder Rollen Rechte an Kinder vererbt haben,
sind die Methoden inheritsO und inheritsRole() vorgesehen. Auch hier ist
die erste für Ressourcen und die zweite für Rollen gedacht. Beide bekommen
zuerst das potenzielle Kind-Element und dann das eventuelle Eltern-Element
übergeben. In beiden Fällen können Sie die Elemente als Objekte oder die
Namen als String übergeben. Mit einem booleschen Wert, den Sie an dritter
Stelle übergeben, legen Sie fest, ob eine direkte Eltern-Kind-Beziehung geprüft
werden soll, oder ob das Kind sich nur in dem Teilbaum unter dem Eltern- bzw.
Vorfahren-Objekt befinden soll.
3.2 Benutzerauthentifikation mit Zend_Auth
Die Klasse Zend_Auth ermöglicht Ihnen die Authentifikation von Benutzern. Das
bedeutet, dass Sie anhand eines Benutzernamens und eines Passwortes erken-
nen, ob ein Benutzer berechtigt ist, Zugriff auf eine bestimmte Ressource zu
erhalten. Es handelt sich also nicht um eine Rechteverwaltung mit verschiedenen
Zugriffsrechten. Dafür ist Zend_Acl zuständig.
Zend_Auth kennt standardmäßig die Möglichkeit, Benutzerdaten gegen eine
Datenbank oder gegen Dateien zu authentifizieren.
3.2.1 Datenbankbasierte Authentifikation
Möchten Sie den Benutzernamen und das Passwort, die ein Benutzer eingegeben
hat, mit einem Datenbankinhalt vergleichen, so benötigen Sie hierfür zuerst eine
Tabelle. Zend_Auth verlangt dabei nicht, dass eine bestimmte Tabellenstruktur
eingehalten wird. Sie können die Tabelle also selbst entwerfen. Für die hier auf-
geführten Beispiele wurde die folgende Tabelle genutzt:
CREATE TABLE 'zend_user' (
'id' int(ll) NOT NULL AUTO_INCREMENT,
'username' varchar(20) NOT NULL,
'Passwort' varchar(32) NOT NULL,
'bemerkungen' varchar(255) DEFAULT NULL,
PRIMARY KEY ('id' ).
124
e-bol.net
Benutzerauthentifikation mit Zend_Auth | 3-2
UNIQUE KEY 'username' ('username')
) ENGINE=MyISAM DEFAULT CHARSET=1atinl
Der Zugriff auf die Tabelle erfolgt über eine der Adapter-Klassen, die Zend_Db zur
Verfügung stellt. Da ich hier mit MySQL arbeite, habe ich den Pdo_Mysql-Adap-
ter (Klasse Zend_Db_Adapter_Pdo_Mysql) genutzt. Das Datenbankobjekt wird
dann an den Konstruktor der Klasse Zend_Auth_Adapter_DbTabl e übergeben.
Zend_Auth benötigt aber auch die Information, wie die Tabelle in der Datenbank
heißt und wie die Spalten für den Benutzernamen und das Passwort lauten. Diese
Angaben können Sie direkt an den Konstruktor übergeben. Alternativ können Sie
die Methoden setTableName(), setldentityCol umn() und setCredentialCo-
1 umn() nutzen, um die Namen der Tabelle der Benutzernamen- bzw. Passwort-
Spalte zu übergeben.
Danach können Sie auch schon loslegen und Benutzer authentifizieren. Der
Benutzername muss dabei mit der Methode setldentity() an die Klassenin-
stanz übergeben werden; das Passwort wird mithilfe von setCredenti al () zuge-
wiesen. Nachdem dies erfolgt ist, können Sie authenticate() aufrufen, womit
die Benutzerinformationen gegen die Datenbank geprüft werden:
// Klassen einbinden
require_once ’Zend/Db. php’;
require_once ’Zend/Auth/Adapter/DbTable.php’;
// Datenbankzugriff konfigurieren
$opts = array('host’ => ’localhost’,
'username' => ’root',
'password’ .
'dbname' => 'test'):
// Datenbankadapter ableiten
$db = Zend_Db::factory('Pdo_Mysql',$opts):
// Benötigte Auth-Klasse ableiten
$auth = new Zend_Auth_Adapter_DbTable($db):
// Namen der Tabelle und Namen der Spalten setzen
$auth->setTableName(’zend_user')
->setldenti tyColumn('username')
->setCredenti alColumn(’passwort');
if (true — empty ($_POST['username']) ||
true — empty ($_POST['passwort']))
(
125
e-bol.net
3 | Benutzer- und Rechtemanagement
// Loginformular ausgeben
echo "<form method=’post'>";
echo "<table>”;
echo "<tr>
<td>Username</td>
CtdXinput type=’text' name='username'>C/td>
C/tr>";
echo "<tr>
<td>Passwort</td>
CtdXinput type=’password' name=’Passwort1>C/td>
C/tr>";
echo "<tr>
<td colspan=’2' align='center'>
Cinput type='submit' value='Einloggen'>
</td>
C/tr>";
echo "</table>":
}
el se
{
// Daten an Objekt übergeben
Sauth->setldenti ty($_POST[’username'])
->setCredenti al($_POST['passwort*])
->setCredenti alTreatment('MD5(?)');
// Authentifikation durchführen
Sresult = $auth->authenticate();
// Rückgabecode auslesen
Scode = Sresult->getCode();
// Rückgabewert auswerten
switch (Scode)
{
case Zend_Auth_Result::FAILURE:
Stehler = 'Unbekannter Fehler';
break;
case Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND :
Stehler = 'Username unbekannt':
break;
case Zend_Auth_Result::FAILURE_IDENTITY_AMBIGUOUS :
Stehler = 'Username nicht eindeutig’;
break;
126
e-bol.net
Benutzerauthentifikation mit Zend_Auth | 3-2
case Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID :
Sfehler = 'Falsches Passwort
break;
case Zend_Auth_Result::FAILURE_UNCATEGORIZED :
Stehler = 'Nicht kategorisierter Fehler’;
break;
// Haben wir eine Fehlermeldung?
if (isset (Stehler))
{
echo Stehler;
d 1 e ();
echo "Sie sind eingeloggt";
}
Listing 3.4 Authentifikation gegen eine Datenbanktabelle
In diesem Listing wird entweder ein Formular ausgegeben, oder die Daten, die in
das Formular eingegeben wurden, werden mit den Benutzern verglichen, die in
der Datenbank angelegt sind. Bei der Übergabe der Benutzerdaten an das Objekt
wird zusätzlich noch die Methode setCredentialTreatment() aufgerufen. Mit
ihr legen Sie fest, ob das eingegebene Passwort im Klartext oder verschlüsselt mit
der Datenbankspalte verglichen werden soll. Wenn Sie diese Methode also nicht
nutzen, wird das Passwort so mit der Datenbankspalte verglichen, wie es einge-
geben wurde. Da Sie Passwörter wenn möglich immer verschlüsselt in der Daten-
bank ablegen sollten, können Sie mit dieser Methode definieren, welcher Ver-
schlüsselungsalgorithmus genutzt werden soll. Sie können hier beispielsweise
MD5( ?) oder PASSW0RD( ?) angeben. Die Fragezeichen werden dann jeweils durch
das eingegebene Passwort ersetzt, wenn die Daten an die Datenbank geschickt
werden. Der übergebene String wird nicht daraufhin geprüft, ob er eine der bei-
den Funktionen enthält. Sie können also auch eine andere Funktion ansprechen.
Wichtig ist nur, dass das Fragezeichen enthalten sein muss. Diesen String könn-
ten Sie auch als fünften Parameter an den Konstruktor übergeben.
Die Methode authenticate() liefert ein Objekt der Klasse Zend_Auth_Resul t
zurück. In diesem Objekt ist ein Code enthalten, der Ihnen mitteilt, ob der Benut-
zer erfolgreich angemeldet werden konnte. Den Code können Sie mit den Kon-
stanten aus Tabelle 3.1 vergleichen.
127
e-bol.net
3 | Benutzer- und Rechtemanagement
Konstante Bedeutung
FAILURE unbekannter Fehler
FAILURE_IDENTITY_NOT_FOUND Das Log-in wurde nicht gefunden.
FAILURE_IDENTITY_AMBIGUOUS Der Benutzer ist mehrfach vorhanden. (Achtung: Die Log-ins sind nicht case-sensitive.)
FAILURE_CREDENTIAL_INVALID Das Passwort ist ungültig.
FAILUREJJNCATEGORIZED unkategorisierter Fehler
SUCCESS Das Log-in war erfolgreich.
Tabelle 3.1 Rückgabewerte bei der Authentifikation
3.2.2 Dateibasierte HTTP-Authentifikation
Wie eingangs erwähnt, können Sie Benutzerdaten auch mit Benutzerinformatio-
nen vergleichen, die in einer Datei hinterlegt sind.
Das Dateiformat ist so aufgebaut, dass eine Zeile eine Zugangsberechtigung ent-
hält. Die Zeile wird mit dem Benutzernamen eingeleitet, dann folgt »Realm«, also
derjenige Bereich, zu dem der Benutzer Zugang haben soll. Dann schließt sich das
Passwort an. Die drei Teile sind jeweils durch einen Doppelpunkt voneinander
getrennt.
Ein Benutzername darf in einer Datei durchaus mehrfach auftauchen, allerdings
muss die Kombination aus Bereichsangabe und Passwort eindeutig sein. Somit
können Sie auch bei größeren Projekten mit nur einer Passwort-Datei arbeiten
und damit unterschiedliche Bereiche absichern.
Das Paket Zend_Auth unterstützt zwei Authentifikationsverfahren: Basic und
Digest. Hierbei gilt, dass die Passwörter in Abhängigkeit vom Authentifikations-
verfahren unterschiedlich abgelegt werden müssen. Bei einer Datei für das ältere
Basic-Verfahren muss das Passwort Base64 codiert sein, wohingegen bei dem
neueren Digest-Verfahren eine MD5-Verschlüsselung genutzt wird. Um einen
Benutzer in einer Datei für das Digest-Verfahren anzulegen, könnten Sie bei-
spielsweise die folgenden Zeilen verwenden:
$user = 'carsten ’;
Srealm = 'Mein privates Verzeichnis':
Spassword = ’total geheim’;
$fp = fopen('userliste.txt',’a');
Szeile = "$user:$realm:".md5($password)."\n";
fputs($fp, Szeile):
fclose($fp):
Listing 3.5 Einfaches Beispiel zum Anlegen von Benutzern
128
e-bol.net
Session-Verwaltung mithilfe von Zend_Session | 3-3
Für das Basic-Verfahren müssten Sie den Aufruf von md5() nur durch den Aufruf
von base64_encode() ersetzen. Wenn Sie die Datei über den Webserver anlegen
und/oder verwalten, vergessen Sie bitte nicht, dass der Webserver Leserechte auf
die Datei hat. Ein Angreifer könnte die Datei also direkt aufrufen, sofern er den
Pfad kennt. Bitte stellen Sie daher sicher, dass die Datei an einem Ort abgelegt
wird, an dem der Webserver nicht direkt lesen kann.
Abschließend noch eine Anmerkung. Falls Sie Zend_Auth in einer MVC-Anwen-
dung nutzen wollen, so wirft das die Frage auf, an welcher Stelle Sie die Authen-
tifikation ausführen. Hierbei bietet es sich an, ein Plug-in wie beispielsweise pre-
Di spatchf ) zu benutzen.
3.3 Session-Verwaltung mithilfe von Zend_Session
Mithilfe der Klasse Zend_Session können Sie auf einfache Art und Weise Sessi-
ons verwalten. Auch wenn die native Session-Verwaltung von PHP durchaus ein-
fach zu handhaben und zuverlässig ist, so bringt sie doch ein paar kleinere Pro-
bleme mit sich. Die aus meiner Sicht größte Einschränkung ist sicherlich, dass
PHP die Session-Daten standardmäßig im temporären Verzeichnis des Servers
ablegt. Somit ist die Session stets nur auf diesem einen Server vorhanden, und
der Benutzer kann nicht einfach auf einen anderen Server wechseln, ohne die
Session-Daten zu verlieren. Gerade bei umfangreichen Anwendungen, die über
mehrere Server verteilt sind, kann das unvorteilhaft sein, insbesondere dann,
wenn die Benutzer-Log-ins ebenfalls über Sessions verwaltet werden.
Diese Probleme merzt Zend_Session aus. Darüber hinaus stellt sie noch ein sau-
ber strukturiertes, objektorientiertes Interface zur Verfügung.
Ein wenig gewöhnungsbedürftig ist vielleicht die Tatsache, dass Zend_Session
auf Basis von Namensräumen arbeitet. Die Idee hinter diesen Namespaces ist ein-
fach: In einer komplexen Anwendung müssen unter Umständen viele Daten in
Sessions abgelegt werden. Nutzen die Klassen und der restliche Code alle den sel-
ben Speicherbereich, so kann es schnell passieren, dass Variablen in der Session
den selben Namen haben und die Werte überschrieben werden. Zend_Session
verfolgt den Ansatz, dass jeder Teil der Anwendung einen eigenen Namensraum
anlegen kann. Dieser Namensraum ist getrennt von den anderen Namensräu-
men, sodass kein Wert überschrieben werden kann, selbst dann nicht, wenn die
Bezeichner bzw. Variablen identisch sind.
129
e-bol.net
3 | Benutzer- und Rechtemanagement
3.3.1 Eine Session starten
Bitte beachten Sie, dass Ihre PHP-Installation Sessions nicht automatisch starten
sollte. Das heißt, die Direktive session.auto_start sollte in der Datei php.ini
mit dem Wert 0 belegt werden.
Um eine Session mit Zend_Session zu nutzen, rufen Sie die statische Methode
Session_Zend: :start() auf. Der Aufruf der Methode ist zwar nicht unbedingt
notwendig, da sie automatisch beim Anlegen des Namensraums ausgeführt wird,
aber start!) bietet zwei Vorteile. Zum ersten ist für alle, die den Code später
lesen, eindeutig, dass mit Sessions gearbeitet wird. Der zweite Vorteil ist, dass Sie
den Aufruf der Methode »weit vorne« im Code, also beispielsweise im Bootstrap-
File platzieren können. Da start!), ebenso wie das normale Session-Handling in
PHP, ein Cookie setzen muss, würde der Methodenaufruf fehlschlagen, falls
schon Daten an den Client gesendet worden wären.
Nachdem Sie eine Session gestartet haben, können Sie ein neues Objekt der
Klasse Zend_Session_Namespace ableiten. Der Konstruktor der Klasse startet die
Session übrigens auch implizit, wenn Sie das zuvor noch nicht gemacht haben
sollten Der Konstruktor erwartet einen String, den Namespace, als Parameter,
um die Variablen zuordnen zu können. Nutzen Sie den Namespace dann inner-
halb eines anderen Scripts, das in derselben Session ausgeführt wird, so stehen
die Werte, die in dem Namespace abgelegt wurden, wieder zur Verfügung.
Bitte beachten Sie, dass der Name der Namespaces nicht mit Zend_ beginnen
sollte, da dieses Präfix für die Klassen des Zend Frameworks reserviert ist. Auch
darf der Name nicht mit einem Unterstrich beginnen. Übergeben Sie keinen
Namen, so wird ein Namenspace mit dem Namen Default genutzt. Diesen
Namen sollten Sie also ebenfalls nicht für einen eigenen Namespace nutzen, um
Überschneidungen zu verhindern.
Nachdem Sie ein Namespace-Objekt abgeleitet haben, können Sie die Session-
Daten darin ablegen, indem Sie einer beliebigen Eigenschaft einen Wert oder ein
Array zuweisen. Im folgenden kleinen Beispiel wird der Zeitstempel der Log-in-
Zeit in einer Session abgelegt:
require_once ’Zend/Session.php';
requi re_once 'Zend/Session/Namespace.php';
// Session starten
Zend_Session::start();
// Neuen Namespace registrieren
Ssession = new Zend_Session_Namespace('UserDaten');
130
e-bol.net
Session-Verwaltung mithilfe von Zend_Session | 3-3
// Wurde der Wert schon in der Session abgelegt?
if (false —== isset($session->login_zeit))
{
// Noch nicht => Erster Aufruf => Zeit speichern
$session->login_zeit = timeO;
}
echo "Sie haben sich um ".
date( ’H:i :s’, $session->login_zeit).
" Uhr eingeloggt.":
Listing 3.6 Nutzung von Zend_Session
Sie sehen - die Nutzung ist einfach und effizient.
Zu beachten ist allerdings, dass Namespaces mit gleichem Namen dasselbe
Objekt referenzieren, auch wenn das auf den ersten Blick unwahrscheinlich
erscheint. Im folgenden Beispiel werden zwei eigenständige Objekte instantiiert,
bei denen die Namen der Namespaces allerdings identisch sind:
Ssession = new Zend_Session_Namespace('MeinSpace'):
$session_2 = new Zend_Session_Namespace('MeinSpace'):
$session->wert =’Hallo':
$session_2->wert ='Welt':
echo $session->wert: // Gibt Welt aus
echo $session_2->wert: // Gibt Welt aus
Listing 3.7 Umgang mit Namespaces
Ob dieses Verhalten nun einen Vor- oder ein Nachteil darstellt, wird wohl jeder
für sich selbst entscheiden müssen. Möchten Sie allerdings verhindern, dass ein
schon genutzter Namensraum noch einmal genutzt werden kann, so können Sie
dem Konstruktor der Klasse Zend_Sessi on_Namespace als zweiten Parameter den
Wert true übergeben. Versucht Ihr Script dann denselben Namensraum noch
einmal zu initialisieren, resultiert dies in einer Exception:
Ssession = new Zend_Session_Namespace('MeinSpace',true):
// Die nächste Zeile wirft eine Exception
$session_2 = new Zend_Session_Namespace('MeinSpace'):
Das ist natürlich nur dann der Fall, wenn der Namensraum innerhalb eines
Scripts doppelt initialisiert werden soll. Handelt es sich um einen zweiten Aufruf
des Scripts oder um ein anderes Script, so ist das kein Problem.
131
e-bol.net
3 | Benutzer- und Rechtemanagement
3.3.2 Gültigkeit und Schutz von Session-Daten
Die Daten, die Sie in einer Session ablegen, sind standardmäßig so lange gültig, wie
der Browser nicht geschlossen wird und der Server die Session-ID von ihm ausle-
sen kann. Zend_Sessi on gibt Ihnen aber die Möglichkeit, die gesamte Session oder
auch nur einzelne Werte in der Session mit einem Time-out zu versehen, was unge-
mein praktisch ist. Nach Ablauf des Time-outs verfallen die Daten automatisch.
Hierbei sind zwei Fälle zu unterscheiden: Um eine ganze Session mit einem
Time-out zu versehen, rufen Sie die statische Methode rememberMe() aus der
Klasse Zend_Sessi on auf und übergeben ihr die Anzahl von Sekunden, für die die
Session gültig sein soll. In diesem Fall schickt PHP ein persistentes Cookie an den
Client. Damit kann die Session auch dann noch aufgenommen werden, falls der
Browser zwischenzeitlich geschlossen wurde. Andererseits verfällt das Cookie,
wenn der Browser noch geöffnet und der Verfallszeitpunkt erreicht ist. Wichtig
ist, dass Sie die Methode vor dem Start der Session bzw. vor dem Ableiten eines
neuen Namespaces aufrufen:
// Session bleibt eine Stunde gueltig
Zend_Session::rememberMe(3600):
Zend_Session::start();
Ssession = new Zend_Session_Namespace(’MeinSpace',true):
Möchten Sie die Session vorher beenden, beispielsweise weil der Benutzer sich
abgemeldet hat, so stehen Ihnen mehrere Möglichkeiten zur Verfügung. Die
erste Möglichkeit ist, dass Sie die Methode Zend_Session:: forgetMe() vor dem
nächsten Start der Session aufrufen. In diesem Fall wird die Lebensdauer des
Cookies auf 0 gesetzt. Somit wird das persistente Cookie zu einem normalen Ses-
sion-Cookie und verfällt, wenn der Browser geschlossen wird. Bis zu diesem Zeit-
punkt bleibt die Session allerdings noch bestehen. Die zweite Möglichkeit ist die
Nutzung der Methode expi reSessi onCooki e(). Auch sie muss vor dem Start der
Session aufgerufen werden und legt das Verfallsdatum des Cookies in die Vergan-
genheit, sodass das Cookie sofort ungültig wird.
Die dritte Möglichkeit besteht darin, Zend_Session: :destroy() zu nutzen. Diese
Methode können Sie allerdings erst nach dem Start der Session einsetzen. Bei
dieser Methode handelt es sich im Endeffekt um session_destroy() in PHP mit
ein paar zusätzlichen Features. Sie können der Methode nämlich noch zwei boo-
lesche Werte mit auf den Weg geben. Der erste definiert, ob das Session-Cookie
»ungültig gemacht« werden soll. Übergeben Sie hier true, was gleichzeitig auch
der Default-Wert ist, so wird das Verfallsdatum des Cookies in die Vergangenheit
gelegt. Der zweite Parameter, der standardmäßig ebenfalls true ist, legt fest, ob
die Session-Daten mit einem Schreibschutz versehen werden sollen (= true) oder
noch geschrieben werden können (= fal se).
132
e-bol.net
Session-Verwaltung mithilfe von Zend_Session | 3-3
3.3.3 Nutzung eigener Session-Save-Handler
Wie eingangs bereits erwähnt, bietet Zend_Session auch die Möglichkeit, einen
eigenen Session-Save-Handler zu nutzen. Standardmäßig werden die Session-
Daten als Datei im temporären Verzeichnis des Servers abgelegt. Das ist aller-
dings nicht immer gewünscht. Abgesehen davon, dass die Festplatte bzw. das
Verzeichnis zu voll werden kann, stellt diese Vorgehensweise ein Problem dar,
wenn ein Benutzer von einem zu einem anderen Server weitergeleitet werden
soll, da dieser die Session-Daten nicht kennt. Das Gleiche kann natürlich auch
dann passieren, wenn Sie Server im Cluster betreiben.
Einen anderen Save-Handler können Sie mit der statischen Methode setSave-
Handl er() anmelden, welche aufgerufen werden muss, bevor die Session gestar-
tet wird. Die Methode erhält ein Objekt einer Klasse übergeben, die das Interface
Zend_Session_SaveHandler_Interface implementieren muss. Das Interface ist
folgendermaßen deklariert:
interface Zend_Session_SaveHandler_Interface
(
public function open($save_path, $name);
public function closeO;
public function read($id);
public function write($id, $data);
public function destroy($id):
public function gc($maxlifetime);
Ich möchte hier nicht ausführlich auf die Funktionalität der einzelnen Methoden
eingehen. Sollten Sie schon einmal einen eigenen Save-Handler erstellt haben, so
kennen Sie die Funktionalität bereits. Andernfalls werfen Sie bitte einen kurzen
Blick in PHP-Dokumentation zur Funktion session_set_save_handl er(). Sie
finden sie unter der folgenden Internet-Adresse: http://www.php.net/manual/
de/function.session-set-save-handler.php.
Dennoch möchte ich Ihnen eine kleine Beispiel-Implementation mithilfe von
Zend_Db vorstellen. Möchten Sie einen eigenen Save-Handler implementieren, so
ist bei Zend_Session eine Besonderheit zu beachten. Aus Sicherheitsgründen
generiert die Klasse immer eine neue Session-ID, wenn das Objekt instanziiert
wird. Das hat zur Folge, dass Ihr Save-Handler zwar beim Lesen noch die »alte«
Session-ID bekommt, beim Speichern der Daten aber bereits die neue genutzt
wird. Sie sollten also immer dafür sorgen, dass die Daten, die mit der alten Ses-
sion-ID gespeichert wurden, entfernt werden.
133
e-bol.net
3 | Benutzer- und Rechtemanagement
Das folgende Beispiel basiert auf dieser Tabelle:
CREATE TABLE 'session_data' (
'sid' char(32) NOT NULL,
'timestmp' int(ll) DEFAULT NULL,
'data' blob,
PRIMARY KEY ('sid' )
) ENGINE=MyISAM DEFAULT CHARSET=1atinl
Listing 3.8 Tabelle für Session-Daten
Die Spalte sid speichert die Session-ID, die Spalte timestmp die Uhrzeit, zu der
die Session gestartet wurde, und in der Spalte data werden die eigentlichen
Daten abgelegt.
Die Klasse für den Save-Handler könnte dann folgendermaßen implementiert
werden:
require_once ’Zend/Db.php’;
require_once ’Zend/Session/SaveHandler/Interface.php’;
dass SaveHandler implements Zend_Session_SaveHandler_Interface
{
private $db = nul1;
private $old_sid = null;
private $startime = null;
// Öffnen der Verbindung zur Datenbank
public function open($save_path, $name)
{
// Verbindungsdaten
$config = array(
'host’ => '127.0.0.1’,
'username' => 'root',
'password' => '',
’dbname' => 'test'
);
$this->db = Zend_Db::factory(’Mysqli',$config);
// Schließen der Datenbankverbindung
public function closed
{
$this->db->closeConnection();
134
e-bol.net
Session-Verwaltung mithilfe von Zend_Session | 3-3
// Auslesen der Session-Daten
public function read($id)
{
// Daten auslesen
$sql = "SELECT timestmp, data
FROM session_data
WHERE sid = ?";
$res = $this->db->fetchAl 1($sql . $id);
// Haben wir Daten bekommen?
if (true === is_array($res) &&
1 === count($res))
// Alte ID abspeichern um alte Daten
// aktualisieren zu können
$this->old_sid = Sid:
// Ursprüngliche Startzeit merken
Sthis->startime = SresEO]['timestmp;
// Session-Daten zurückgeben
return $res[0][’data ' ];
1
return nul1 ;
// Speichern der Session-Daten
public function write($id, $data)
{
// Gibt es alte Daten?
if (null !== $this->startime)
// Daten mit alter ID entfernen
Ssql = "DELETE FROM session_data
WHERE sid = ?";
$this->db->query($sql, $this->old_si d);
// Startzeit der Session merken
Stirne = $this->startime;
1
el se
// Neue Session => aktuelle Zeit als Startzeit
Stirne =time();
1
// Daten in Tabelle abspeichern
Ssql = "INSERT INTO session_data
135
e-bol.net
3 | Benutzer- und Rechtemanagement
(sid, timestmp, data)
VALUES (?. ?, ?)";
$this->db->query($sql . array($id,$time, $data));
return true;
// Session-Daten löschen
public function destroy($id)
{
$sql = "DELETE FROM session_data
WHERE sid = ?";
$this->db->query(Ssql, Sid):
// Müllabfuhr => alle veralteten Daten löschen
public function gc(Smaxlifetime)
{
Ssql = "DELETE FROM session_data
WHERE timestmp < ?";
Stirne = time()-Smaxlifetime;
Sthis->db->query(Ssql, Stirne);
}
Listing 3.9 Implementation eines eigenen Session-Händlers
Wie Sie sehen, ist es etwas aufwändiger, einen Session-Händler für diese Klasse
zu implementieren. Aber es ist durchaus handhabbar.
Das hier vorgestellte Beispiel sollten Sie allerdings nicht als Referenz-Implemen-
tation ansehen. Es sollte lediglich die Vorgehensweise beispielhaft erläutern und
ist nicht für den Produktiveinsatz gedacht.
136
e-bol.net
People get annoyed whenyou try to debug them.
- Larry Wall (Open Sources, 2999 0'Reilly and Associates)
4 Infrastruktur-Klassen
Dieses Kapitel behandelt verschiedene Klassen, die bei der Entwicklung einer
Applikation im Hintergrund arbeiten. Neben der Möglichkeit, die Performance
mit Caching zu steigern, finden Sie hier auch Features, um Logs zu schreiben
oder Eingaben zu filtern.
4.1 Performance-Optimierung mit Zend_Cache
Die Nutzung ausgefeilter Caching-Strategien kann einen Webserver bzw. den
Datenbankserver deutlich entlasten. Zend_Cache verfolgt hierbei den typischen
Ansatz eines User-Land Caches. Das heißt, es werden fertig aufbereitete Seiten
oder die Ergebnisse von Funktionen gecachet. Es handelt sich hierbei nicht um
einen Opcode-Cache, welcher den fertig interpretierten PHP-Code cachet und
somit die Ausführung des Codes beschleunigt. Allerdings können Sie Zend_Cache
in Kombination mit dem Opcode-Cache APC (Alternative PHP Cache) nutzen. Sie
finden beide im PECL-Repositoiy (http://pecl.php.net). Gerade bei größeren
Installationen ist aber auch die Nutzung von Zend Platform eine sehr spannende
Alternative.
Die Funktionsweise von Zend_Cache ist recht einfach. Zuerst müssen Sie sich ent-
scheiden, was Sie cachen wollen. Damit ist auch festgelegt, welches Frontend Sie
nutzen. Es stellt sich dann die Frage, wo die Daten abgelegt werden sollen. Sollen
die zu speichernden Daten auf der Festplatte oder an einem anderen Ort gespei-
chert werden? Diese Angabe legt fest, welches Backend Sie nutzen. Damit haben
Sie auch schon alle Informationen, um loszulegen. Diese Daten werden an eine
statische Methode namens factoryt) übergeben, die Ihnen das benötigte Objekt
liefert. Das so generierte Objekt kann dann Daten im Cache ablegen bzw. Daten,
die bereits im Cache liegen, wieder auslesen und zur Verfügung stellen. Um
Daten aus dem Cache auszulesen, müssen diese natürlich eindeutig referenzier-
bar sein. Das heißt, für jeden Eintrag im Cache benötigen Sie einen eindeutigen
Bezeichner, beispielsweise in Form einer ID.
137
e-bol.net
4 | Infrastruktur-Klassen
Das folgende kleine Beispiel zeigt, wie die komplette Ausgabe eines Scripts ge-
cachet werden kann.
require_once ' Zend/Cache.php';
// Die Ausgabe soll gecachet werden
$frontend = 'Output';
// Speichern in Datei
Sbackend = 'Fi 1e’;
// Generiert das benötigte Objekt
Scache = Zend_Cache::factory($frontend, $backend):
// Generieren einer eindeutigen ID
Sid = md5($_SERVER['PHP_SELF’]);
// Daten auslesen oder Caching starten
$is_cached = $cache->start(Sid);
// Waren die Daten im Cache?
if (false — Sis_cached)
{
echo 'Aktuelle Zeit: '. date(*H:i:s');
// Caching beenden
$cache->end();
1
Listing 4.1 Caching von Ausgaben
Die statische Methode factory bekommt als ersten Parameter die Information
übergeben, was gecachet werden soll. In diesem Fall wird das Frontend Output
genutzt, welches die Ausgabe des gesamten Scripts mithilfe von Output Buffering
cachet. Der zweite Parameter File, sorgt dafür, dass die Daten in einer Datei
gespeichert werden. Optional können Sie noch zwei weitere Parameter, Arrays
mit Optionen, nutzen. Auf diese werde ich gleich noch eingehen.
Das wirklich Interessante in diesem Beispiel ist die Methode s t a r t (), welche die
ID übergeben bekommt. Sie erledigt mehrere Dinge auf einmal. Sie prüft
zunächst, ob die Daten bereits im Cache liegen. Ist das der Fall, so werden sie
direkt ausgegeben. Die Methode gibt in diesem Fall true zurück. Falls die Daten
noch nicht gespeichert waren, liefert sie den Wert false. Dieser Rückgabewert
wird in der nachfolgenden i f-Abfrage geprüft. Sind die Daten noch nicht ausge-
geben worden, so erfolgt das hier. Die Methode end(), die anschließend aufge-
rufen wird, sorgt dafür, dass die Daten ausgegeben (zuvor waren sie ja nur im
138
e-bol.net
Performance-Optimierung mit Zend_Cache | 4-1
Ausgabepuffer) und korrekt im Backend (in diesem Fall also in einer Datei)
gespeichert werden.
Grundsätzlich finden Sie diese Vorgehensweise bei jeder Kombination aus Front-
end und Backend wieder. Allerdings variieren die Namen der Methoden etwas.
4.1.1 Frontends
Die schon erwähnten Optionen, mit denen Sie das Verhalten des Frontends steu-
ern können, gliedern sich in allgemeine und spezielle Optionen, die zusammen
in einem Array übergeben werden. Nachfolgend finden Sie eine Aufstellung der
allgemeinen Optionen. Die speziellen Optionen werden später bei den jeweili-
gen Frontends erläutert.
Derjenige Schlüssel, den Sie am häufigsten benötigen werden, ist lifetime. Er
steuert, wie lange die Daten im Cache gültig sind. Die Gültigkeitsdauer wird als
Zahl in Sekunden angegeben. Mit dem Standardwert 3600 bleiben die Daten also
eine Stunde im Cache (60 Sekunden * 60 Minuten - 3600). Übergeben Sie hier
null, so bleiben die Daten unbegrenzt gültig.
Sehr hilfreich kann automatic_serialization sein. Übergeben Sie mit diesem
Schlüssel den Wert true (der Default-Wert ist fal se), so werden die zu cachen-
den Daten zuerst serialisiert, bevor sie im Cache abgelegt werden. Das ist immer
dann sehr hilfreich, wenn Sie Datenstrukturen wie Arrays oder Objekte spei-
chern wollen, da diese nach dem Auslesen sonst nicht mehr genutzt werden kön-
nen. Bei skalaren Typen wie Integer oder String ist es nicht notwendig, diese
Funktion einzuschalten, da sie das Lesen und Speichern der Daten verlangsamt.
Allerdings werden dann alle Werte in Form eins Strings zurückgegeben, was in
PHP üblicherweise nicht weiter tragisch ist. Übrigens handelt es sich bei dem
Frontend Output um einen String, der nicht serialisiert werden muss.
Praktisch ist auch caching. Mit diesem Schlüssel können Sie einen booleschen
Wert übergeben. Der Default-Wert true sorgt dafür, dass das Caching eingeschal-
tet ist. Mit fal se deaktivieren Sie den Speichermechanismus. Diese Funktionali-
tät kann sehr hilfreich sein, wenn Sie Ihre Software testen. Andernfalls müssten
Sie immer warten, bis der Cache verfallen ist, um die aktuelle Ausgabe zu sehen.
Der Schalter wri te_control dient dazu, die Daten, nachdem sie im Cache abge-
legt wurden, zu überprüfen. Dazu werden sie nach dem Speichern einmal gele-
sen, was zwar etwas Performance kostet, die Datensicherheit dafür aber erhöht.
Mit dem Wert true (der Default-Wert) schalten Sie diese Funktion ein.
139
e-bol.net
4 | Infrastruktur-Klassen
Des Weiteren können Sie mit dem Schlüssel 1 oggi ng, der auch boolesche Werte
akzeptiert, das Logging mittels Zend_Log aktivieren, um einen Überblick über das
Verhalten von Zend_Cache zu erhalten.
Gerade bei großen Installationen kann es hilfreich sein, Einfluss auf die Garbage
Collection zu nehmen. Mit Garbage Collection wird die Funktionalität bezeich-
net, die dafür zuständig ist, veraltete Cache-Einträge zu löschen. Diese Müllab-
fuhr wird immer dann im Hintergrund ausgeführt, wenn Sie etwas im Cache
ablegen. Die Notwendigkeit für eine solche Funktion ergibt sich daraus, dass
unter Umständen sehr viele veraltete Informationen im Cache liegen, die nicht
mehr ausgeliefert werden. Das kann zum Beispiel dann passieren, wenn Sie eine
Community betreiben, in der jeder Benutzer eine personalisierte Startseite hat.
Die gecacheten Daten können schon lange veraltet sein, weil der Benutzer sich
seit Wochen oder Monaten nicht mehr angemeldet hat. Um zu verhindern, dass
die gecacheten Daten überhandnehmen, können Sie den Schlüssel automatic_
cleaning_factor nutzen. Mit seiner Hilfe wird ein Integer-Wert übergeben.
Handelt es sich dabei um 0, so werden die Daten nicht gelöscht. Bei einer Zahl x
größer als Null wird in einer von x Cache-Schreiboperationen ein Löschvorgang
initiiert. Das heißt, bei einer 1 werden bei jedem Schreibvorgang die veralteten
Daten gelöscht. Bei einer 10 wird in einer von zehn Operationen die Garbage
Collection gestartet usw. Welcher Wert für Ihre Zwecke ideal ist, muss individu-
ell entschieden werden. Ein sehr häufiges Löschen verlangsamt viele Schreibope-
rationen. Seltenes Löschen macht sich nur selten bemerkbar, dauert dafür aber
länger. Möchten Sie die automatische Garbage Collection aus Performance-Grün-
den nicht nutzen, so können Sie die Daten auch manuell löschen. Weitere Infor-
mationen dazu erhalten Sie in Abschnitt 4.1.3.
Das Frontend »Output«
Zum Großteil haben Sie das Frontend ja schon in den vorangegangenen Absätzen
kennengelernt. Dennoch möchte ich noch einige Ergänzungen anbringen. In Lis-
ting 4.1 wird die Ausgabe eines ganzen Scripts gecachet. Das Frontend Output ist
allerdings auch in der Lage, nur die Ausgabe bestimmter Bereiche zu speichern.
Wenn Sie also nur einen Teil einer Seite speichern wollen, so rufen Sie start()
erst an der Stelle auf, ab der gecachet werden soll. Nach dem Aufruf der Methode
end() können Sie wieder direkt Daten ausgeben. Somit verfügen Sie über ein
sehr hohes Maß an Flexibilität. Möchten Sie nur einen Teil der Seite cachen,
dann sollten Sie die ID natürlich nicht nur auf Basis von $_SERVER[' PHP_SELF' ]
erstellen. In so einem Fall könnten Sie die Teile beispielsweise zusätzlich durch-
nummerieren.
140
e-bol.net
Performance-Optimierung mit Zend_Cache | 4-1
Wie bereits erwähnt, können Sie der Methode factory ein Array mit Optionen
übergeben. Output kennt keine eigenen Optionen, sodass Sie nur auf die allge-
meinen zugreifen können.
Die Methode start() akzeptiert neben der ID noch einen zweiten Parameter.
Übergeben Sie an dieser Stelle true, wird nicht geprüft, ob die gecachten Daten
noch gültig sind. Die Gültigkeitsdauer wird ignoriert und der Cache-Inhalt bleibt
beliebig lange gültig.
Auch die Methode end() akzeptiert noch Parameter. An erster Stelle können Sie
hier ein Array mit Tags übergeben, die dann dem Cache-Eintrag zugeordnet wer-
den. Als zweiten Parameter können Sie noch eine spezielle Lebensdauer für den
Cache-Eintrag übergeben. Somit ist es möglich, den Default-Wert oder den Wert,
den Sie via Array übergeben haben, zu überschreiben.
Cachen von Seiten mit dem Frontend »Page«
Das Frontend Page ist der Spezialist, wenn es darum geht, die Ausgabe ganzer
Seiten zu cachen. Die Flexibilität von Output, nur einzelne Blöcke in den Zwi-
schenspeicher auszulagern, ist hier nicht gegeben. Bei einer einfachen Seite,
deren Ausgabe nicht von eingehenden GET-, POST-, Session- oder sonstigen
Werten abhängt, können Sie beruhigt auf Output setzen. Die meisten PHP-Seiten
werden allerdings auf Inhalte bestimmter Variablen reagieren. In diesem Fall bie-
tet sich die Nutzung von Page an. Page generiert die benötigte Cache-ID nämlich
automatisch, und Sie können dem Frontend dabei mitteilen, ob bestimmte Vari-
ablen beim Generieren der Cache-ID einbezogen werden sollen. Auf diesem Weg
kann so sichergestellt werden, dass eine Seite nur dann aus dem Cache kommt,
wenn beispielsweise auch die übergebenen GET-Werte identisch sind. Dieses
Verhalten wird über die speziellen Optionen gesteuert, die in diesem Fall zur
Verfügung stehen. In dem Optionen-Array können Sie mit dem Schlüssel
defaul t_options ein verschachteltes Array übergeben, in dem Sie die Schlüssel
aus Tabelle 4.1 mit einem booleschen Wert belegen.
Schlüssel Default-Wert
cache_wi th_get_vari abl es fal se
cache_wi th_post_vari a b1 es fal se
cache_wi th.sessi on_variables fal se
cache_wi th_f 11es_vari a b1 es fal se
cache_wi th_cooki e_vari ables fal se
make_i d_with_get_variables true
make_i d_with_post_vari ables true
Tabelle 4.1 Mögliche Schlüssel für Optionen
141
e-bol.net
4 | Infrastruktur-Klassen
Schlüssel Default-Wert
make_i d_wi th_sessi on_vari abl es true
make_i d_with_files_vari ables true
make_i d_with_cookie_vari ables true
Tabelle 4.1 Mögliche Schlüssel für Optionen (Forts.)
Die Schlüssel, die mit make_id beginnen, sorgen jeweils dafür, dass die danach
genannten, superglobalen Arrays mit in die ID einbezogen werden. Standardmä-
ßig gilt das für jedes der superglobalen Arrays, was meist auch sinnvoll ist. Die
cache_wi th-Schlüssel geben Ihnen die Möglichkeit, bestimmte superglobale
Arrays mit im Cache abzulegen. Allerdings ist dies nur bedingt hilfreich, da die
Seite eh aus dem Cache kommt.
In diesem Array können Sie auch noch den Schlüssel cache nutzen, dem Sie
einen booleschen Wert übergeben können. Dieser überschreibt den Wert des
Schlüssels caching aus den allgemeinen Optionen. Der Hintergrund dieser Mög-
lichkeit wird gleich plausibel.
Ein ganz besonders interessantes Feature besteht darin, dass Sie das Caching
anhand der URL steuern können. Die Idee dahinter ist: Vielleicht haben Sie das
ganze Projekt auf Basis des MVC-Patterns aufgebaut und die Caching-Funktiona-
lität ist nur einmal im Front Controller vorhanden. Allerdings greift der Mecha-
nismus auch ohne MVC, da lediglich nur die aufgerufene URL auf Basis eines
regulären Ausdrucks geprüft wird. Der reguläre Ausdruck wird dabei als Schlüs-
sel in einem Array genutzt, der wiederum auf ein Array verweist. In diesem
Array können Sie dann erneut alle Optionen nutzen, die bereits oben unter dem
Schlüssel defaul t_options erläutert wurden. Stimmt der reguläre Ausdruck mit
der URL überein, wird die aktuelle Ausgabe entsprechend den Vorgaben ge-
cachet oder eben auch nicht. Somit wird klar, warum Sie mit cache steuern kön-
nen, ob die Seite gecachet wird. Sie können dies somit anhand der URL entschei-
den.
Neben den vorgenannten Schlüsseln können Sie auch noch debug_header auf
true oder fal se setzen. Übergeben Sie true, wird in den Kopf einer Seite die aus
dem Cache kommt, DEBUG HEADER : Thi s i s a cached page ! eingefügt. Das kann
bei der Fehlersuche durchaus sehr praktisch sein.
In Listing 4.2 sehen Sie die Funktionsweise. Hierbei handelt es sich um eine
Datei, die an erster Stelle in anderen Dateien inkludiert werden kann:
require_once ’Zend/Cache.php';
// Die komplette Seite soll gecachet werden
142
e-bol.net
Performance-Optimierung mit Zend_Cache | 4-1
$frontend = ’Page' ;
$opts = array(
// Einen Tag im Cache
'lifetime' => 24*60*60,
// Globales Verhalten
'default_options' =>
array(
'cache_with_post_variables' => true,
1make_id_with_session_variables1 => false,
1make_id_with_cookie_variables' => false,
’cache’=>true
// Verhalten fuer einzelne Dateien
’regexps’ => array (
// Einstellungen fuer index.php
'A/index.php' =>
array (
'cache'=>false
),
// Konfiguration fuer Unterverzeichnis /mail
'A/mail '=>
array (
'make_id_with_session_variables' => true,
'make_id_with_cookie_variables' => true,
'cache' => true
// Speichern in Datei
Sbackend = 'Fi 1e’;
// Generiert das benötigte Objekt
Scache = Zend_Cache::factory($frontend, $backend, $opts);
// Daten auslesen oder Caching starten
$cache->start();
Listing 4.2 Nutzung des Frontends Page mit regulären Ausdrücken
Eine Datei, die gecachet werden soll, könnte so aussehen:
require_once 'cache.php’;
echo date( ’H:i:s’):
143
e-bol.net
4 | Infrastruktur-Klassen
Wird diese Datei unter dem Namen index.php im Root-Verzeichnis gespeichert,
wird sie nach den Regeln, wie sie mithilfe der regulären Ausdrücke definiert wur-
den, nicht zwischengespeichert. Würden Sie die Datei allerdings im Unterver-
zeichnis /mail speichern, so würde sich das Verhalten ändern und sie würde
gecachet. In allen anderen Fällen würde das Regelwerk greifen, das mit defaul t_
options definiert ist.
Die hier genutzten regulären Ausdrücke sind eher allgemeiner Natur und eignen
sich für jede Art von Applikation. Basiert Ihre Applikation auf dem MVC-Pattern,
so beachten Sie bitte, dass die Aufrufe von index.php/mail und /mail identisch
sind.
Wichtig ist noch der Hinweis, dass die Methode start() das Script auch dann
automatisch beendet, wenn die Daten im Cache gefunden wurden. Wenn Sie
nach dem starte) noch Operationen wie Logfile-Zugriffe oder Datenbankzu-
griffe ausführen wollen, ist dies somit nicht möglich, sofern die Daten aus dem
Cache kommen.
Funktionsergebnisse mit dem Frontend »Function« cachen
Die beiden bereits vorgestellten Frontends haben sich primär um die »Bild-
schirm-Ausgabe« einer Webseite oder eines Scripts gekümmert. Die nachfolgen-
den Frontends verfolgen eine andere Zielsetzung. Der erste Vertreter in dieser
Reihe ist Function, mit dem Sie die Rückgabewerte von Funktionen cachen kön-
nen.
Damit die Ergebnisse von Funktionsaufrufen ohne Probleme gespeichert wer-
den, sollten Sie beachten, dass alle Werte, die die Funktion benötigt, beim Aufruf
übergeben werden müssen. Falls also die Funktion auf globale Werte oder Werte
aus den superglobalen Arrays zugreift, kann das Frontend dies nicht erkennen.
Gleiches gilt, wenn die Funktion beispielsweise mit Zufallszahlen arbeitet.
Im Gegensatz zu anderen User-Land Caches ist das Paket aber in der Lage, auch
die direkten »Bildschirm-Ausgaben« von Funktionen zu speichern. Datei- oder
Datenbankoperationen sind davon nicht betroffen.
Das Frontend kennt neben den allgemeinen Optionen auch einige spezielle Mög-
lichkeiten, um das Verhalten zu steuern. An erster Stelle ist hier der Schlüssel
cacheByDefaul t zu nennen. Mit ihm können Sie steuern, ob die Funktionen
standardmäßig gecachet werden sollen. Ist in der globalen Option caching ein
true enthalten, so wird dieses von einem false, das in cacheByDefault enthal-
ten ist, überschrieben. Damit haben Sie die Möglichkeit, das Caching global ein-
zuschalten, die Funktionen aber getrennt zu verwalten. Enthält caching aller-
dings false, so wird cacheByDefault nicht weiter ausgewertet.
144
e-bol.net
Performance-Optimierung mit Zend_Cache | 4-1
Außerdem können Sie mithilfe der Schlüssel cachedFunctions und nonCached-
Functions jeweils noch Arrays mit Funktionsnamen übergeben. Im ersten Fall
handelt es sich um Funktionen, die immer gecachet werden (auch wenn cache-
ByDefault ausgeschaltet ist), wohingegen diejenigen, die in der zweiten Liste
enthalten sind, nie gespeichert werden.
require_once ' Zend/Cache.php';
// Liefert die aktuelle Zeit zurück
function get_time (Sformat)
{
$zeit = date ($format);
return $zeit;
l
// Liefert das aktuelle Datum zurück
function get_date ()
{
$datum = date (’d.m.Y');
return Sdatum;
l
// Liefert eine Zufallszahl zurück
function get_random()
{
mt_srand(100*microtime(true)):
$zahl = mt_rand();
return $zahl;
l
// Optionen
$opts = array(
’caching’=>true,
'cacheByDefault'=>true,
'cachedFunctions' =>
array (’get_time’), // Funktion wird immer gecachet
’nonCachedFunctions' =>
array ('get_random') // Funktion wird nicht gecachet
);
Scache = Zend_Cache::factoryl'Function', 'File', $opts);
// Dieser Aufruf wird entsprechend der Einstellungen
// von cacheByDefault und caching gecachet
Sdatum = $cache->call('get_date'):
145
e-bol.net
4 | Infrastruktur-Klassen
echo "Aktuelles Datum: Sdatum";
echo "<br>":
Sparams = array( ’ H: i: s '):
$zeit = $cache->cal 1(’get_time', $params):
echo "Zeit: $zeit":
echo "<br>":
Szufall = $cache->cal1(’get_random');
echo "Zufallszahl: Szufall";
Listing 4.3 Nutzung des Frontends »Function«
Wie Sie in Listing 4.3 sehen, erfolgt der eigentliche Aufruf der Funktionen über
die Methode c a 11 (). Sie bekommt als ersten Parameter den Namen der Methode
übergeben, die aufgerufen werden soll. Benötigt die Funktion Parameter, so
übergeben Sie diese als zweiten Parameter in Form eines Arrays.
Speichern von Methodenaufrufen mit dem Frontend »Class«
Möchten Sie die Ergebnisse von Methodenaufrufen Zwischenspeichern, so gibt
es dafür das Frontend Class. Dieses funktioniert im Prinzip sehr ähnlich wie das
Frontend Function. Und es bietet auch die gleichen Optionen. Darüber hinaus
erwartet dieses Frontend aber noch eine zusätzliche Option, die mit dem Array
übergeben werden muss. Hierbei handelt es sich um den Schlüssel cachedEn-
ti ty, der entweder den Namen einer Klasse oder bereits ein Objekt der entspre-
chenden Klasse übergeben bekommt. Weisen Sie der Eigenschaft ein Objekt zu,
so erfolgt der Aufruf der Methode dynamisch, wohingegen ein statischer Aufruf
durchgeführt wird, wenn Sie den Namen zuweisen.
Um die Methoden aufzurufen, hängen Sie den Namen der gewünschten Funk-
tion mithilfe des ->-Operators direkt an das Cache-Objekt an. In beiden Fällen
tun Sie also einfach so, als würde es sich um eine Methode des Cache-Objekts
handeln, die dynamisch aufgerufen wird. Benötigt die Methode Parameter, so
werden diese beim Aufruf direkt mit übergeben.
require_once 'Zend/Cache.php’;
class datum
{
private $timestamp:
public function ____construct($timestamp)
$this->timestamp = Stimestamp:
146
e-bol.net
Performance-Optimierung mit Zend_Cache | 4-1
)
public function getDayName ()
return date('l',$this->timestamp);
l
public static function getTimestamp()
return time();
l
}
// Beispiel für einen dynamischen Aufruf
// Optionen
$opts = array(
'caching' => true,
’cacheByDefault' => true,
’cachedEntity' => new datum(time())
);
Scache = Zend_Cache::factory('Glass', 'File', $opts):
$name = $cache->getDayName();
echo $name;
echo "<br>";
// Beispiel für einen statischen Aufruf
// Optionen
$opts = array(
'caching' => true,
’cacheByDefault' => true,
’cachedEntity' => "datum"
);
Scache = Zend_Cache::factory('Glass', 'File', $opts):
Stimestamp = $cache->getTimestamp();
echo $timestamp;
Listing 4.4 Cachen von AAethodenaufrufen
Dateien mit dem Frontend »File« cachen
Die Idee hinter dem Frontend File ist nicht, die Ausgabe von Daten in Dateien zu
cachen, wie man vielleicht zuerst glauben mag. Es geht vielmehr darum, das Ein-
lesen von Dateien zu beschleunigen. Dabei ist es natürlich nicht sinnvoll, eine
Datei zu cachen, die einfach nur eingelesen und anschließend direkt genutzt wer-
147
e-bol.net
4 | Infrastruktur-Klassen
den soll. Insbesondere dann, wenn Sie den Cache als Datei anlegen, hätten Sie
damit nichts gewonnen. Die Idee ist vielmehr, dass das Ergebnis eines Lesevor-
gangs im Cache abgelegt wird. Wenn Sie also beispielsweise eine Konfigurations-
datei einlesen, diese analysieren und das Ergebnis in Form eines Arrays oder
eines Objekts bereitstellen, kann dieses Array oder Objekt im Cache abgelegt
werden. Sobald die ursprüngliche Datei verändert wird, erkennt das Frontend
dies und verwirft den Inhalt des Caches. Eine sehr praktische Lösung, wie ich
finde.
Die Nutzung des Frontends gestaltet sich recht einfach und erinnert ein wenig an
das Frontend Output. Wichtig ist allerdings, dass File mindestens eine Option
benötigt, nämlich master_file. Mit ihr definieren Sie, welche Datei eingelesen
und überwacht werden muss. Sollten Sie ein Array oder ein Objekt im Cache
ablegen wollen, müssen Sie darüber hinaus noch die automatische Serialisierung
mithilfe von automatic_seri al ization aktivieren.
Für die Nutzung von File müssen Sie wiederum manuell eine ID generieren,
wobei es sich anbietet, den Namen der zu überwachenden Datei inklusive des
absoluten Pfads zu nutzen.
Die ID übergeben Sie an die Methode load(), welche versucht, die Daten aus
dem Cache zu lesen. Ist das nicht möglich, liefert sie fal se zurück. Um die Daten
zu speichern, greifen Sie auf save() zurück. Hiermit werden die Daten im selek-
tierten Backend abgelegt. Das Cache-Objekt hat sich die ID, mit der gearbeitet
wird, übrigens schon beim Ladeversuch »gemerkt«, sodass die Methode außer
den Daten keine weiteren Informationen mehr benötigt. Optional können Sie die
ID noch als zweiten Parameter angeben, sofern dies erforderlich sein sollte.
require_once ' Zend/Cache.php';
// Frontend File
$frontend = 'File';
Sdatei = '/var/www/wwwl/config.ini' ;
Sid = md5(Sdatei);
Sopts = array (
'master_fi1e’ => Sdatei,
'automatic_serialization’ => true);
// Speichern in Datei
Sbackend = 'Fi 1e’;
// Generiert das benötigte Objekt
Scache = Zend_Cache::factory($frontend, Sbackend, Sopts);
148
e-bol.net
Performance-Optimierung mit Zend_Cache | 4-1
// Daten auslesen oder Caching starten
Sdaten = $cache->load($id);
if (false — $daten)
{
$daten = parse_ini_file($datei);
$cache->save(tdaten);
}
// Weiterer Programmablauf
Listing 4.5 Nutzung des Frontends »File«
Das Frontend »Core«
Das letzte Frontend, das ich Ihnen vorstellen möchte, ist Core. Bei genauer
Betrachtung ist Core vielleicht kein echtes Frontend, sondern eher »die Mutter
aller Frontends«. Das liegt daran, dass Core diejenige Klasse ist, auf der alle ande-
ren Frontends aufbauen. Mithilfe von Core können Sie somit recht einfach ein
eigenes Frontend erstellen bzw. beliebige Inhalte cachen, für die noch kein Front-
end vorgesehen ist. Die Funktionsweise von Core ist weitestgehend identisch mit
der von File. Daher möchte ich nicht weiter auf Core eingehen. Sollten Sie
allerdings einmal ein eigenes Frontend entwickeln wollen, dann ist diese Klasse
Ihr Kandidat. Eine Erläuterung des Frontends finden Sie im Manual zum Zend
Framework.
4.1.2 Nutzung von Backends
Nachdem Sie nun wissen, was Sie wie speichern können, stellt sich noch die
Frage, wo die Daten abgelegt werden können. Hierfür stehen verschiedene
Backends zur Verfügung. In den Beispielen wurde immer das Backend File
genutzt, das die Daten auf der Festplatte ablegt. File stellt in den meisten Fällen
tatsächlich eine wirklich gute Lösung dar, da der Zugriff auf Dateien sehr schnell
ist und kein großer Rechenaufwand erforderlich ist.
Die Backends lassen sich teilweise noch weiter konfigurieren. Dazu übergeben
Sie der Methode factory() noch ein weiteres Array mit Optionen als vierten
Parameter.
Caching auf der Festplatte mit dem Backend »File«
Die grundsätzliche Funktionsweise von File kennen Sie nun bereits. Standardmä-
ßig werden die Dateien, die die Daten enthalten, im temporären Ordner des Ser-
vers abgelegt, also meist in /tmp. Das kann, sofern mehrere Domains auf dem
Server laufen, unter Umständen ein Problem sein, da es passieren kann, dass die
149
e-bol.net
4 | Infrastruktur-Klassen
Dateien nicht mehr eindeutig sind. Des Weiteren sind andere Benutzer eventuell
in der Lage, den Inhalt der Cache-Dateien auslesen, was ein Sicherheitsproblem
darstellen würde.
Möchten Sie die Daten in einem anderen Verzeichnis ablegen, so können Sie in
dem Array, das als vierter Parameter genutzt werden kann, mithilfe des Schlüssel
cache_di r ein anderes Verzeichnis festlegen.
Der Schlüssel fi 1 e_l ockl ng, der boolesche Werte akzeptiert, gibt Ihnen die
Möglichkeit festzulegen, ob die Cache-Dateien während der Zugriffe mit einem
Lock gesperrt werden sollen. Dies verhindert, dass eine unvollständige Datei aus-
geliefert wird. Anzumerken ist allerdings, dass diese Vorgehensweise nicht auf
allen Plattformen genutzt werden und den Zugriff verlangsamen kann.
Eine Alternative, um zu verhindern, dass verfälschte Daten ausgeliefert werden,
ist die Nutzung der Lesekontrolle. Diese schalten Sie ein, indem Sie dem Schlüs-
sel read_control den Wert true übergeben. Schalten Sie diese Funktion ein,
wird in der Datei eine Prüfsumme mit abgelegt, die nach dem Lesen der Datei mit
einer neu berechneten Summe verglichen wird. Das ist zwar ein wenig zeitauf-
wändiger, kann sich bei wichtigen Daten aber durchaus lohnen.
In diesem Zusammenhang kann es auch praktisch sein zu definieren, wie die
Prüfsumme erstellt wird. Hierfür ist der Schlüssel read_control_type vorgese-
hen. Ihm können Sie den String crc32, md5 oder strlen zuweisen. Standardmä-
ßig wird die Prüfsumme mit dem CRC32-Verfahren bestimmt, welches relativ
zuverlässig und recht schnell ist. MD5-Prüfsummen sind noch ein ganzes Stück
präziser, allerdings auch langsamer. Mit strlen wird nur die Länge des Strings
geprüft, was natürlich nicht hundertprozentig zuverlässig ist. Ich meine, dass für
einen Großteil der Anwendungsfälle diese Präzision aber ausreicht und das Ver-
fahren hierfür eine hohe Geschwindigkeit bietet.
Sollten auf einem Server mehrere Anwendungen mit ZencLCache arbeiten, so
kann es passieren, dass die Dateien nicht eindeutig zugeordnet werden, falls sie
alle in einem Verzeichnis liegen. Zwar wäre es die bessere Lösung, für jede Instal-
lation ein eigenes Verzeichnis zu nutzen, aber alternativ können Sie auch einfach
das Präfix der Dateinamen verändern. Übergeben Sie dazu den Dateinamen, der
genutzt werden soll, an den Array-Schlüssel file_name_prefix. Somit kann
Zend_Cache die Dateien den einzelnen Installationen zuordnen.
Caching im Speicher mit den Backends »Memcached« und »APC«
Möchten Sie kleinere Datenmengen cachen, die sehr schnell zur Verfügung ste-
hen sollen, so können Sie diese auch im Arbeitsspeicher des Servers ablegen. Da
der Arbeitsspeicher meist deutlich kleiner ist als das Platzangebot der Festplatten,
150
e-bol.net
Performance-Optimierung mit Zend_Cache | 4-1
sollten Sie überlegen, bei welchen Dateien es sinnvoll ist, diese im Speicher abzu-
legen. Kleinere Datenmengen, die auf jeder Seite inkludiert werden, bieten sich
hier an.
Das Backend Memcached unterstützt allerdings auch die Möglichkeit, einen ande-
ren Server zu nutzen, sodass der eigentliche Webserver entlastet wird. Sollten Sie
diese Lösung interessant finden, würde ich vorher einen Benchmark empfehlen,
um die resultierende Performance besser einzuschätzen. Ein interessanter Aspekt
bei Memcached ist aber, dass Sie auch über mehrere Webserver hinweg cachen
können. Wenn Sie mehrere Webserver in einem Cluster betreiben, dann muss
also nur einer der Server den Cache-Eintrag generieren und alle anderen können
darauf zugreifen.
Zend_Cache kennt zwei Arten, um Daten im Speicher zu halten. Sie können hier-
bei entweder auf APC oder Memcached zurückgreifen. Bei beiden handelt es
sich um Erweiterungen aus dem PECL-Repositoiy. Unter Linux und anderen
UNIX-verwandten Betriebssystemen können Sie diese Pakete mit peel install
memcache bzw. peel i nstal l apc installieren. Bitte beachten Sie, dass das PECL-
Paket memcache und nicht memcached heißt. Bei der Nutzung von memcached
benötigen Sie zusätzlich den entsprechenden Server (http://www.danga.com/
memcached/). Installieren Sie diesen bitte über das Installationsprogramm Ihres
Betriebssystems. Falls Sie Windows nutzen, so finden Sie im Internet auch fertige
Varianten.
Der PECL-Installer lädt die Dateien direkt aus dem Internet herunter und kompi-
liert sie. Danach teilt er Ihnen mit, welche Zeile Sie in der Datei php.ini ergänzen
müssen, um die Erweiterung zu laden. Nutzen Sie Windows, so finden Sie fertig
kompilierte DLLs unter http://pecl4win.php.net. Dort finden Sie auch Informati-
onen zur Installation der DLLs.
Ich persönlich empfehle die Nutzung von APC. Auch wenn memcached ein spezi-
eller Caching-Mechanismus und somit wahrscheinlich ein wenig performanter
als APC ist, so bietet APC doch Vorteile. Der »Alternative PHP Cache«, kurz APC,
ist ein Cache für den Byte-Code, der aus einem PHP-Script generiert wird. Das
heißt, Ihr Script wird einmal übersetzt und danach im Cache gehalten. Das stei-
gert die Performance Ihrer Anwendung unter Umständen noch einmal deutlich.
Weitere Informationen zur Nutzung und Konfiguration von APC finden Sie unter
http://www.php.net/apc.
Nutzen Sie APC, können Sie den Namen des Backends einfach direkt an die Fac-
tory-Methode übergeben, da APC keine weitere Parameter benötigt:
Scache = Zend_Cache::factory('Class', 'APC', $opts);
151
e-bol.net
4 | Infrastruktur-Klassen
Wie schon erwähnt, können Sie bei der Nutzung von memcached auch mit ande-
ren Servern arbeiten. Dabei muss das Cache-Backend wissen, wie diese angespro-
chen werden. Diese Information können Sie mit dem vierten Parameter als Array
übergeben. Mit dem Schlüssel Servers übergeben Sie ein Array, in dem jeder
Server einen eigenen Eintrag hat. Mit dem Schlüssel host geben Sie den Namen
des Servers an, port enthält den genutzten Port, und mit persi stent übergeben
Sie einen booleschen Wert, der definiert, ob eine persistente Verbindung genutzt
werden soll. Dabei wird pro Server-Prozess eine Verbindung offen gehalten.
Neben dem Array mit den Schlüsseln können Sie mit dem Schlüssel compressi on
noch einen booleschen Wert übergeben, der festlegt, ob die Daten bei der Über-
tragung komprimiert werden sollen. Ob diese Option sinnvoll ist, hängt von der
Struktur Ihrer Daten ab. Also davon, ob sich diese komprimieren lassen. Im
Zweifelsfall sollten Sie mit einem Benchmark prüfen, ob die Option Ihre Anwen-
dung schneller oder eventuell sogar langsamer macht.
Geben Sie diesen vierten Parameter nicht an, versucht das System eine Verbin-
dung zu einem lokal installierten Memcached-Server aufzubauen. Hierbei werden
der Port 11211 und eine persistente Verbindung genutzt, wobei die Kompression
ausgeschaltet ist:
$opts_backend = array(
'Servers' => array(array(
'host' => '192.168.0.3* .
'port' => 11211,
'persistent' => true
)),
'compression’ => false
);
Scache = Zend_Cache::factory('Class', 'Memcached',
$opts, $opts_backend);
Listing 4.6 Nutzung von »Memcached«
Weitere Backends
Zusätzlich zu den schon erwähnten Backends können Sie auch noch Zend Plat-
form sowie SQLite als Backend einsetzen. Bei Zend Platform handelt es sich um
ein kommerzielles Produkt aus dem Hause Zend. Zend Platform stellt einige
wirklich sehr nützliche Funktionalitäten zur Verfügung. Sollten Sie Wert auf gute
Performance, hohe Verfügbarkeit oder ein gute Überwachung des Server legen,
dann könnte Zend Platform eine interessante Alternative sein. SQLite ist eine
dateibasierte Datenbank, die in PHP 5 enthalten ist. Nach meinen Benchmarks ist
ein direkter Dateizugriff über das Backend File allerdings performanter.
152
e-bol.net
Performance-Optimierung mit Zend_Cache | 4-1
Daher gehe ich hier nicht auf diese beiden Varianten ein. Weitergehende Infor-
mationen hierzu können Sie dem Manual entnehmen.
4.1.3 Manuelle Verwaltung von Cache-Einträgen
In der Regel müssen Sie sich nicht darum kümmern, welche Einträge im Cache
vorhanden sind und wie alt diese sind, da die Garbage Collection sich dieses Pro-
blems annimmt. Nun könnte es aber sein, dass Sie gecachete Daten manuell
löschen wollen. Das könnte beispielsweise dann der Fall sein, wenn Sie das Lay-
out aller Seiten oder eine Information geändert haben, die auf allen Seiten einge-
bunden ist.
Zend_Cache kennt verschiedene Möglichkeiten, um Inhalte zu löschen. Am ein-
fachsten ist folgender Aufruf:
$cache->clean(Zend_Cache::CLEANING_MODE_ALL);
Diese Zeile löscht alle Daten, die sich momentan im Cache befinden. Die
Methode cleanO kann übrigens auch nur veraltete Cache-Einträge löschen,
sofern Sie sie mit der Konstante Zend_Cache: :CLEANING_MODE_OLD aufrufen.
Damit löschen Sie die Daten recht rigoros. Allerdings haben Sie auch die Mög-
lichkeit, gezielter zu löschen, was allerdings ein wenig Planung voraussetzt. Und
zwar können Sie beim Abspeichern der Daten mittels save() als letzten Parame-
ter ein Array mit sogenannten Tags übergeben. Hierbei handelt es sich um frei
definierbare Strings, mit denen Sie Cache-Einträge zu Gruppen zusammenfassen,
wenn Sie die Daten beispielsweise so ablegen:
$cache->save(tdaten, Sid. array('news’, 'politik'));
Die Daten dieses Eintrags sind jetzt mit den Tags news und politik versehen,
wobei hier noch weitere Tags angegeben werden könnten. Möchten Sie jetzt alle
Cache-Einträge löschen, die mit dem Tag news versehen sind, so rufen Sie
clean() auf:
$cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG,
array('news')):
Die Tags (auch wenn es sich hier nur um ein Tag handelt) müssen in Form eines
Arrays übergeben werden. Mit der Konstante Zend_Cache: :CLEANING_MODE_
MATCH ING_TAG wird definiert, dass alle Einträge gelöscht werden, die mit diesem
Tag versehen sind. Wollen Sie alle löschen, die nicht mit diesem bzw. diesen Tags
versehen sind, dann übergeben Sie die Konstante Zend_Cache: :CLEANING_MODE_
NOT_MATCHING_TAG als ersten Parameter.
153
e-bol.net
4 | Infrastruktur-Klassen
Mit den Tags haben Sie die Möglichkeit auf einfache Weise Abhängigkeiten zwi-
schen bestimmten Teilen des Caches darzustellen. Wird einer der Teile von dem
andere Teile abhängen ungültig, dann können Sie die anderen Informationen
direkt mit löschen lassen.
Sollte Ihnen das immer noch zu ungenau sein, so können Sie einen Eintrag auch
anhand seiner ID löschen. Diese übergeben Sie an die Methode remove(), die
den Löschvorgang vornimmt.
4.2 Prüfen von Werten mit Zend_Validate
Daten, die Sie aus einer nicht vertrauenswürdigen Quelle wie einem Webformu-
lar, einer Webschnittstelle oder einer Datei übernehmen, müssen häufig validiert
werden. Am bekanntesten ist hierbei sicherlich die Prüfung von E-Mail-Adres-
sen. Meist beschränkt sich die Prüfung einer E-Mail-Adresse allerdings darauf, ob
der Benutzer ein ©-Zeichen eingegeben hat. Dies ist natürlich nicht ausreichend!
Zend_Val i date stellt eine recht stattliche Anzahl von Klassen zur Verfügung, mit
denen Sie solche und ähnliche Probleme lösen. Die Nutzung ist recht einfach. Um
einen Variableninhalt zu validieren, leiten Sie zunächst ein Objekt der benötigten
Klasse ab. Jede Klasse kennt eine Methode namens i s Va 1 i d (), welche den Wert
übergeben bekommt. Sie liefert true oder fal se zurück, um Ihnen mitzuteilen,
ob der Variableninhalt den Vorgaben entspricht. Sollte das nicht der Fall sein,
können Sie mithilfe der Methode getMessagesI) die Fehlermeldung(en) ausle-
sen. Sie erhalten ein Array zurück, sodass Sie die Methode auch direkt in eine
foreach-Schleife integrieren können.
Alternativ können Sie die Werte auch statisch prüfen lassen, was aber den Nach-
teil hat, dass Sie keine Fehlermeldungen abfragen können. Ich werde hierauf
nicht eingehen.
Möchten Sie überprüfen, ob ein Wert eine gültige Integer-Zahl darstellt, könnte
die Prüfung wie folgt aussehen:
requi re_once('Zend/Validate/Int.php'):
$wert = "12a3";
Svalidator = new Zend_Validate_Int();
if ($validator->isValid($wert))
{
echo "Es ist ein Integer-Wert":
}
154
e-bol.net
Prüfen von Werten mit Zend_Validate | 4-2
el se
I
echo "FehlerlCbr />":
foreach ($validator->getMessages() as $message)
{
echo ”$message<br />";
}
Listing 4.7 Prüfen eines Werts mit Zend_Validate_lnt
Der Wert, der hier geprüft werden soll, ist natürlich keine Integer-Zahl. Die Aus-
gabe des Scripts sehen Sie in Abbildung 4.1.
rs O 0 http://127.0.0.1/~c.../validator/eins.php CD
OuOO______________________________________>
j Camino Info .News Mac News Tabs |Q|Coogie |t A Paypal
Fehler!
' 12a3' does not appear to be an integer
Abbildung 4.1 Ausgabe des Scripts
Wie Sie sehen, liefert das System eine eigene Fehlermeldung auf Englisch. Diese
können Sie an Ihre Erfordernisse anpassen bzw. übersetzen. Um die Meldungen
anzupassen, ist die Methode setMessage() vorgesehen. Sie erhält als ersten Para-
meter die Fehlermeldung übergeben und als zweiten eine Konstante, die defi-
niert, für welchen Fehler die Meldung »zuständig« ist. Bei der Fehlermeldung
können Sie den Platzhalter %val ue% verwenden, der dann bei der Ausgabe durch
den geprüften Wert ersetzt wird. Die Konstanten sind von Validator zu Validator
verschieden. Sie finden sie jeweils bei der Beschreibung der Klasse. Um die Feh-
lermeldung im obigen Beispiel anzupassen, könnten Sie diesen Befehl einfügen:
$validator->setMessage("’%value%' ist kein Integer-Wert",
Zend_Validate_Int::NOT_INT);
Alternativ ist auch noch die Methode setMessages() vorgesehen, welche ein
Array übergeben bekommt. Hierbei ist die Konstante der Schlüssel und die dazu-
gehörige Meldung der Wert.
Abhängig von der Validierungsklasse können auch noch weitere Platzhalter
neben dem für den Wert möglich sein. Nachfolgend finden Sie die einzelnen
Klassen beschrieben.
155
e-bol.net
4 | Infrastruktur-Klassen
4.2.1 Prüfen auf alphanumerische Daten
Die Klasse Zend_Validate_Alnum gibt Ihnen die Möglichkeit zu überprüfen, ob
ein übergebener String aus alphanumerischen Zeichen besteht. Zusätzlich kön-
nen Sie dem Konstruktor einen booleschen Wert übergeben, welcher definiert,
ob die Überprüfung Whitespaces akzeptieren soll. Bitte beachten Sie, dass deut-
sche Sonderzeichen nicht als alphanumerische Zeichen angesehen werden.
Konstante Bedeutung
N0T_ALNUM Der übergebene Wert ist nicht alphanumerisch.
$TRING_EMPTY Es wurde ein Leerstring übergeben.
4.2.2 Prüfen von Texten
Die Klasse Zend_Val idate_Al pha ist weitgehend identisch mit Alnum, mit dem
Unterschied, dass nur alphabetische Zeichen - derzeit allerdings ohne deutsche
Sonderzeichen - akzeptiert werden.
Konstante Bedeutung
N0T_ALPHA Der übergebene Wert ist nicht alphanumerisch.
$TRING_EMPTY Es wurde ein Leerstring übergeben.
4.2.3 Prüfen, ob eine Zahl in einem bestimmten Bereich liegt
Der Validator Zend_Val idate_Between gibt Ihnen die Möglichkeit zu testen, ob
ein Wert zwischen zwei anderen liegt. Der Konstruktor akzeptiert drei Werte: die
untere Grenze, die obere Grenze sowie einen booleschen Wert, der definiert, ob
der Wert gleich den Grenzwerten sein darf oder größer bzw. kleiner sein muss.
Sie können die Werte auch über die Methoden setMi n (), setMax() und set I n -
clusi ve() setzen, falls Sie dies bevorzugen. Die aktuellen Einstellungen können
Sie mithilfe der »Umkehrmethoden«, deren Namen jeweils mit get beginnen,
wieder auslesen. Für die Fehlermeldungen stehen bei dieser Klasse die Platzhal-
ter %mi n% und %max% zur Verfügung, welche bei der Ausgabe durch den minima-
len und den maximalen Wert ersetzt werden.
Konstante I Bedeutung
NOT_BETWEEN NOT_ BETWEEN_STRICT 156 Der übergebene Wert ist nicht größer oder gleich bzw. kleiner oder gleich den Grenzen. Der zu testende Wert ist nicht echt größer bzw. echt kleiner als die Grenzen.
e-bol.net
Prüfen von Werten mit Zend_Validate
| 4.2
4.2.4 Prüfen von Kreditkartennummern
Zend_Validate_Ccnum überprüft eine Kreditkartennummer auf ihre Gültigkeit
hin. Genau genommen wird nicht wirklich die Gültigkeit geprüft, sondern nur
die Plausibilität. Das heißt, es wird geprüft, ob in der übergebenen Zahl die kor-
rekten Prüfziffern enthalten sind und die Länge der Zahl stimmt. Möchten Sie
Kreditkartenzahlungen abwickeln, so ist eine zusätzliche Prüfung der Kreditkar-
tendaten dringend erforderlich, was aber meistens durch den Dienstleister
erfolgt, der die Zahlungsabwicklung für Sie übernimmt.
Die Klasse benötigt keine weiteren Informationen, ebenso erwartet der Kon-
struktor keine Daten. Bitte achten Sie darauf, die Kreditkartennummer als String
ohne Leerzeichen zu übergeben.
Konstante Bedeutung
LENGTH Die übergebene Zahl hat nicht die korrekte Länge.
CHECKSUM Bei der Prüfung ist ein Prüfsummenfehler aufgetreten.
4.2.5 Prüfen eines Datums
Bei der Klasse Zend_Val i date_Date liefert die Methode i sVal i d() nur dann den
Wert true, falls ein gültiges Datum im Format JJJJ-MM-TT übergeben wurde.
Konstante Bedeutung
NOT_YYYY_MM_DD Das Datum scheint das falsche Format zu haben.
INVALID Das Datum ist nicht gültig.
4.2.6 Testen von Ziffernfolgen
Mithilfe von Zend_Val idate_Di gi ts können Sie prüfen, ob ein String nur aus
Ziffern besteht.
Konstante Bedeutung
$TRING_EMPTY Es wurde ein leerer String zur Prüfung übergeben.
NOT_DIGITS Der String besteht nicht nur aus Ziffern.
4.2.7 Validieren von E-Mail-Adressen
Eine E-Mail-Adresse auf ihre Gültigkeit hin zu prüfen, ist sicher eine der größe-
ren Herausforderungen bei der Validierung von Formulardaten. Hierbei liefert
Zend_Val idate_Emai 1 Address eine gute Hilfestellung.
157
e-bol.net
4 | Infrastruktur-Klassen
Die Klasse ist nicht nur in der Lage, eine E-Mail-Adresse auf syntaktische Richtig-
keit hin zu prüfen, sondern kann auch prüfen, ob es für die Domain überhaupt
einen MX-Eintrag1 gibt. Eine Prüfung, ob es die E-Mail-Adresse auf dem entspre-
chenden Server gibt, ist nicht vorgesehen. Dies ist meist auch nicht sinnvoll, da
der größte Teil der Mail-Server diese Überprüfung nicht zulässt, um das Spam-
Aufkommen zu reduzieren.
Im einfachsten Fall leiten Sie einfach ein Objekt der Klasse ab und übergeben der
Methode isVal id() die E-Mail-Adresse, die geprüft werden soll. In diesem Fall
wird dann lediglich geprüft, ob die Syntax der Adresse gültig ist. Dabei sind aller-
dings nur Hostnames zulässig, die per DNS aufgelöst wurden. Um auch lokale
Hostnames bzw. IP-Adressen zuzulassen, können Sie Konstanten der Klasse Zend_
Val idate_Hostname an den Konstruktor übergeben. Die Erläuterung der Kon-
stanten ALLOW_DNS, ALLOW_IP und ALLOW_LOCAL finden Sie in Abschnitt 4.2.11.
Mit dem zweiten Parameter - dabei handelt es sich um einen booleschen Wert -
legen Sie fest, dass die Klasse prüft, ob es einen MX-Eintrag zu der übergebenen
Domain gibt.
Der dritte Parameter erlaubt Ihnen, ein Objekt der Klasse Zend_Validate_Host-
name zu übergeben. Sollten Sie für eine andere Validierung schon ein Objekt die-
ser Klasse abgeleitet haben, ist es nicht sinnvoll, dies an dieser Stelle zu überge-
ben. Damit muss nicht noch ein weiteres Objekt der Klasse abgeleitet werden,
wodurch Ressourcen gespart werden.
Das Hostname-Objekt wird intern in der Eigenschaft hostnameValidator abge-
legt. Möchten Sie noch Methoden des Objekts aufrufen, weil Sie beispielsweise
IDN-Domains nutzen wollen, so können Sie dies über diese Eigenschaft tun.
Möchten Sie prüfen, ob es einen MX-Eintrag zu der fraglichen Domain gibt, setzt
dies voraus, dass die Funktion dns_get_mx() vorhanden ist. Ist das nicht der Fall,
wirft die Methode isVal id() eine Exception. Wollen Sie vorher prüfen, ob die
Funktion vorhanden ist, können Sie die Methode val idateMxSupported() aufru-
fen, die true zurückliefert, falls die Funktion auf dem System vorhanden ist. Lei-
der kann diese Methode nicht statisch aufgerufen werden. Sie sollten daher
zuerst ein Objekt ableiten, um mit diesem zu testen, ob MX-Einträge geprüft wer-
den können. Anschließend lässt sich mithilfe der Methode setVal idateMxl) die
Einstellung ändern. Bitte beachten Sie, dass es zurzeit noch nicht möglich ist, den
MX-Eintrag von IDN-Domains zu prüfen.
1 MX steht für Mail Exchange. Weitere Informationen finden Sie unter der Adresse
http://de.wikipedia.org/wiki/MX_record.
158
e-bol.net
Prüfen von Werten mit Zend_Validate
| 4-2
requi re_once('Zend/Vali date/Emai1 Add ress.php');
Semail = 'postmaster@moehrke.de';
// Wir akzeptieren Hostnames und IPs
Sallow = Zend_Validate_Hostname::ALLOW_DNS |
Zend_Validate_Hostname::ALLOW_IP;
// Neues Objekt ableiten
Svalidator = new Zend_Validate_Emai1Addressl$al1ow);
$mx_validieren = false;
// Können MX-Einträge geprüft werden?
if ($validator->vali dateMxSupported())
{
$mx_validieren = true;
}
// MX-Einstellung setzen
$validator->setVali dateMx($mx_validi eren);
// IDN-Prüfung ausschalten
// Entspricht dem Default. Aufruf nur als Beispiel
$val i dator->hostnameValidator->setValidateldn(false);
if ($validator->isValid($emai1))
{
echo "E-Mai 1-Adresse korrekt!";
}
el se
{
echo "E-Mai 1-Adresse nicht akzeptiert!<br>";
foreach ($validator->getMessages() as Smessage)
echo "$message<br>";
1
}
Listing 4.8 Validieren einer E-Mail-Adresse
Für die Fehlermeldungen können Sie bei dieser Klasse die Platzhalter %local -
Part% und %hostname% nutzen, welche bei der Ausgabe durch den lokalen Teil
der E-Mail-Adresse bzw. den Hostname ersetzt werden.
Konstante Bedeutung
INVALID Die E-Mail-Adresse ist nicht korrekt.
INVALID_HOSTNAME Der Hostname-Teil der E-Mail-Adresse ist nicht korrekt.
INVALID_MX_RECORD Es konnte kein korrekter MX-Eintrag für die E-Mail-Adresse ermittelt werden.
159
e-bol.net
4 | Infrastruktur-Klassen
Konstante Bedeutung
DOT_ATOM Der lokale Teil der E-Mal-Adresse besitzt kein korrektes Dot-Atom-Format.
QUOTED_STRING Der lokale Teil der E-Mail-Adresse liegt nicht in einem korrekten Quoted-String-Format vor.
INVALID_LOCAL_PART Der lokale Teil der E-Mail-Adresse ist ungültig.
4.2.8 Testen eines Strings auf Fließkomma-Eigenschaften
Die Klasse Zend_Val i date_Fl oat prüft, ob ein übergebener Wert eine Fließkom-
mazahl enthält. Hierbei erkennt die Methode i sVal id() sowohl Zahlen in der
üblichen Dezimalschreibweise (z.B. 12.30) als auch in der Exponentialschreib-
weise (z.B. 12e3). Hierbei ist zu beachten, dass sich bei dieser Klasse auch die
Lokalisierungseinstellungen auswirken. Wurde beispielsweise de_DE als Lokali-
sierung eingestellt, werden auch Zahlen akzeptiert, bei denen der Nachkomma-
anteil mit einem Komma und nicht mit einem Dezimalpunkt abgetrennt ist.
Konstante Bedeutung
NOT_FLOAT Es handelt sich nicht um eine Fließkommazahl.
4.2.9 Prüfen, ob eine Zahl über einer Grenze liegt
Mithilfe des Validators Zend_Val idate_GreaterThan testen Sie, ob ein Wert grö-
ßer als eine Grenze ist.
Der Grenzwert kann direkt an den Konstruktor übergeben oder nachträglich mit
setMi n() gesetzt werden. Um die Grenze auszulesen, steht getMi n() zur Verfü-
gung.
Für die Fehlermeldung steht in diesem Fall der Platzhalter %mi n% zur Verfügung,
welcher bei der Ausgabe durch den Grenzwert ersetzt wird.
Konstante
Bedeutung
NOT_GREATER
Der Wert ist nicht größer als die Grenze.
4.2.10 Testen von hexadezimalen Zahlen
Die Methode isVal id(), die in Zend_Val idate_Hex definiert ist, liefert nur dann
true, wenn der übergebene String Zeichen enthält, die hexadezimale Ziffern
darstellen. Er darf also nur aus den Ziffern von 0 bis 9 sowie den Buchstaben von
A-Z in Groß- oder Kleinschrift bestehen.
160
e-bol.net
Prüfen von Werten mit Zend_Validate | 4-2
Konstante
Bedeutung
NOT_HEX
Der Wert besteht nicht nur aus hexadezimalen Ziffern.
4.2.11 Validieren von Hostnames
Die Klasse Zend_Val i date_Hostname ist ein sehr interessanter Validator. Mit ihm
können Sie prüfen, ob ein übergebener String einen gültigen Hostname darstellt.
Diese Klasse erspart Ihnen viel Arbeit, da es recht aufwändig ist, eine solche Prü-
fung manuell zu implementieren. Die Klasse kann hierbei drei Arten von Anga-
ben prüfen. Zum ersten sind das »normale Hostnames«, deren Name über DNS
aufgelöst wird, zum zweiten IP-Adressen und zum dritten werden auch lokale
Namen wie beispielsweise l ocal host unterstützt.
Die Information, welche Arten von Hostnames zulässig sind, wird dem Konstruk-
tor als erster Parameter übergeben. Die Klasse kennt dafür die drei Konstanten
ALLOW_DNS, ALLOW_IP und ALLOW_LOCAL. Wenn Sie also nur IPs als Hostname
zulassen wollen, so übergeben Sie dem Konstruktor als ersten Parameter Zend_
Val i date_Hostname: :ALLOW_IP. Möchten Sie mehrere Möglichkeiten kombinie-
ren, können Sie die verschiedenen Konstanten einfach mit einem |-Operator (Bit-
Oder-Operator) verbinden. Möchten Sie alle Varianten zulassen, können Sie auch
einfach die Konstante ALLOW_ALL nutzen. Der Default-Wert an dieser Stelle ist
ALLOW_DNS.
Mit einem booleschen Wert, der als zweiter Parameter übergeben wird, lässt sich
festlegen, ob IDN-, also beispielsweise sogenannte »Umlaut-Domains«, zulässig
sein sollen. Standardmäßig ist der Wert auf true gesetzt, was bedeutet, dass sie
akzeptiert werden. Bitte beachten Sie dabei, dass die Hostnames in diesem Fall in
UTF-8-Codierung übergeben werden müssen.
Der dritte Parameter, auch ein boolescher Wert, der standardmäßig true lautet,
definiert, ob die Top Level Domain geprüft werden soll. Das heißt, die Klasse ver-
fügt über eine Liste mit allen momentan gültigen Top Level Domains und ver-
gleicht den übergebenen Wert mit dieser Liste. Damit ist sichergestellt, dass
keine Domains übergeben werden können, die nicht existieren. Sie müssen aller-
dings darauf achten, dass das Zend Framework aktualisiert wird, damit die Liste
der Domains stets komplett ist.
Sollten Sie schon ein Objekt der Klasse Zend_Validate_Ip abgeleitet haben, so
können Sie dieses als vierten Parameter übergeben. Dies dient nur dazu, unnö-
tige Instanzen der Klasse zu verhindern, da sonst intern ein Objekt dieser Klasse
abgeleitet wird.
161
e-bol.net
4 | Infrastruktur-Klassen
Auch bei dieser Klasse gilt, dass Sie die Einstellungen, die Sie dem Konstruktor
übergeben, auch noch nachträglich setzen können. Dazu sind die Methoden
setAllowO, setVal idateldn(), setVal idatenTld() und setlpVal idator()
vorgesehen. Ein nachträgliches Übergeben eines IP-Validators hat allerdings wenig
Sinn, da das entsprechende Objekt direkt bei der Instantiierung des Zend_
Val idate_Hostname-Objekts abgeleitet wird. Ein wenig erstaunlich ist, dass zur-
zeit nur eine Funktion zum Auslesen der Einstellungen, nämlich getAllowO,
vorhanden ist.
require_once('Zend/Validate/Hostname.php');
$host = utf8_encode("www.möhrke.de");
// DNS und IP werden akzeptiert
Sallow = Zend_Validate_Hostname::ALLOW_DNS |
Zend_Vali date_Hostname::ALL0W_IP;
Svalidator = new Zend_Validate_Hostname($al1ow,
true, //IDN ist OK
true); // TLD prüfen
if ($ validator->i sValid($host))
{
echo "Domain akzeptiert";
}
el se
{
echo "Fehl er I<br>";
foreach ($validator->getMessages() as Smessage)
echo "$message<br>";
1
}
Listing 4.9 Prüfen von Hostnames
Konstante Bedeutung
IP_ADDRESS_NOT_ALLOWED Es wurde eine IP-Adresse übergeben, obwohl diese nicht zugelassen ist.
UNKNOWN_TLD Die Prüfung von Top Level Domains ist eingeschaltet und der Hostname enthält eine unbekannte Top Level Domain.
INVALID_DASH Der Hostname enthält ein Dash (-) an einer unzulässigen Position.
INVALID_HOSTNAME_SCHEMA Der Hostname ist nach einem nicht zulässigen Schema aufgebaut.
UNDECIPHERABLE_TLD Die Top Level Domain kann nicht extrahiert werden.
162
e-bol.net
Prüfen von Werten mit Zend_Validate | 4-2
Konstante Bedeutung
INVALID_HOSTNAME ungültiger Hostname
INVALID_LOCAL_NAME ungültiger Local Name
LOCAL_NAME_NOT_ALLOWED Es wurde ein lokaler Name übergeben, was allerdings nicht zulässig ist.
4.2.12 Testen von Array-Inhalten
Bei der Klasse Zend_Val i date_InArray handelt es sich primär um einen Wrapper
für die Funktion in_array(). Sie können somit mithilfe dieser Klasse ein Array
darauf hin testen, ob ein bestimmter Wert enthalten ist. Der Konstruktor
bekommt das Array übergeben, das durchsucht werden soll. Als zweiten Parame-
ter können Sie einen booleschen Wert übergeben. Handelt es sich dabei um
true, wird nicht nur der Wert, sondern auch der Datentyp geprüft. Bei einem
fal se, was den Default-Wert darstellt, wird der Datentyp nicht getestet.
Möchten Sie die Parameter erst nach dem Ableiten des Objekts setzen, so können
Sie die Methode setHaystack() zum Setzen des Arrays und setStrictt) zum
Festlegen des zweiten Parameters benutzen, wobei es auch hier wieder Metho-
den gibt, welche die entsprechenden Werte auslesen. Sie heißen getHaystack()
und getStrictt).
Der gesuchte Wert wird dann der Methode i sVal i d() übergeben.
Konstante Bedeutung
NOT_IN_ARRAY Der gesuchte Wert ist nicht in dem Array enthalten.
4.2.13 Validieren von Integer-Werten
Die Methode isValidO der Klasse Zend_Val idate_Int liefert true, wenn der
übergebene Wert eine Integer-Zahl darstellt. Hierbei darf die Zahl auch als Daten-
typ String übergeben werden.
Konstante
Bedeutung
NOT_INT
Der übergebene Wert ist keine Integer-Zahl.
4.2.14 Prüfen von IP-Adressen
Mit der Klasse Zend_Validate_Ip können Sie IP-Adressen validieren. Zurzeit
kann die Klasse nur mit IPv4-Adressen umgehen, da im Hintergrund auf die
Funktion i p2l ong() zugegriffen wird.
163
e-bol.net
4 | Infrastruktur-Klassen
Konstante Bedeutung
NOT_IP_ADDRESS Es handelt sich bei dem übergebenen Wert nicht um eine gültige IP-Adresse.
4.2.15 Prüfen, ob eine Zahl unter einer Grenze liegt
Mithilfe von Zend_Val idate_LessThan können Sie testen, ob der zu prüfende
Wert kleiner als ein Grenzwert ist. Der Grenzwert wird hierbei direkt an den
Konstruktor übergeben.
Der Grenzwert kann, nachdem das Objekt abgeleitet wurde, mithilfe von set-
Max() verändert und mit getMax() ausgelesen werden. Für die Fehlermeldung
können Sie zusätzlich den Platzhalter %max% nutzen.
Konstante I Bedeutung
NOT_LESS Der übergebene Wert ist nicht kleiner als die Grenze.
4.2.16 Testen, ob eine Variable leer ist
Zend_Val idate_NotEmpty testet, ob eine übergebene Variable nicht leer ist. Bitte
beachten Sie, dass die Funktion empty(), die im Hintergrund arbeitet, den String
" 0" als leer ansieht.
Konstante Bedeutung
IS.EMPTY Die übergebene Variable ist leer.
4.2.17 Validierung auf Basis eines regulären Ausdrucks
Diese Klasse Zend_Val idate_Regex bekommt bei der Objektinstantiierung einen
regulären Ausdruck in PCRE-Syntax übergeben. Die Methode isValidO prüft
dann, ob der reguläre Ausdruck einen Treffer mit dem übergebenen Wert liefert.
Nachdem Sie das Objekt abgeleitet haben, können Sie den regulären Ausdruck
ändern, indem Sie die Methode setPattern() aufrufen und ihr den neuen Aus-
druck übergeben. Sie können den »gerade aktuellen« Ausdruck übrigens mit get-
PatternO auslesen. Bitte beachten Sie, dass die Klasse beim Verwenden eines
ungültigen regulären Ausdrucks eine Exception wirft.
Möchten Sie bei der Fehlermeldung den regulären Ausdruck mit ausgeben, kön-
nen Sie dafür den Platzhalter %pattern% nutzen.
Konstante Bedeutung
N0T_MATCH Der reguläre Ausdruck lieferte keinen Treffer.
164
e-bol.net
Filtern von Daten mit Zend_Filter | 4-3
4.2.18 Testen eines Strings auf seine Länge hin
Möchten Sie einen String auf seine Länge hin überprüfen, so ist Zend_Val idate_
StringLength Ihr Freund. Der Konstruktor der Klasse kann eine Mindest- und
eine Höchstlänge übergeben bekommen. Beide Werte sind optional und können
auch später mit den Methoden setMi n() und setMax() gesetzt werden. Die aktu-
ellen Parameter können Sie mit getMin() und getMax() auslesen.
Für die Fehlermeldung stehen bei diesem Paket noch die Platzhalter %mi n% und
%max% zur Verfügung, welche dann durch die Unter- bzw. Obergrenze ersetzt
werden.
Konstante Bedeutung
TOO_SHORT Der übergebene String ist zu kurz.
TOO_LONG Der übergebene String ist zu lang.
4.3 Filtern von Daten mit Zend_Filter
Wenn Sie Daten aus Datenbanken, Formularen oder Webschnittstellen überneh-
men, werden Sie schnell damit konfrontiert, dass Sie diese Daten filtern oder
konvertieren müssen. Dabei kann es darum gehen, Daten zu vereinheitlichen,
Sonderzeichen zu konvertieren oder potenziell gefährliche Zeichen zu entfernen.
PHP kennt hierfür schon eine ganze Menge nützlicher Funktionen, auf die Zend_
Filter aufsetzt. Der große Vorteil von Zend_Filter besteht darin, dass die
Klasse ein einheitliches Interface bietet und erweiterungsfähig ist. Sie können
also die Klasse um eigene Filter ergänzen, die dann genau wie die bereits vorge-
fertigten Filter genutzt werden.
Bevor ich auf die Klasse(n) eingehe, noch ein Wort der Warnung. Ein Filter kann
nur dann korrekt arbeiten, wenn Art und Struktur der eingehenden Daten
bekannt sind. Bitte beachten Sie also immer, dass die Lokalisierungseinstellungen
korrekt sind, und dass Sie dem Filter mitteilen, in welchem Zeichensatz die Daten
vorliegen.
Die Funktionalität der Klasse können Sie auf zwei Wegen nutzen. Zum ersten
können Sie ein Objekt eines speziellen Filters ableiten. Die zweite Möglichkeit ist
ein statischer Aufruf des Filters mithilfe der Methode get(), welche die Daten
für Sie konvertiert. In den meisten Fällen wird es aber sicher eher von Vorteil
sein, ein Objekt abzuleiten und mit diesem zu arbeiten, da Sie es für mehrere
Konvertierungsvorgänge nutzen können.
165
e-bol.net
4 | Infrastruktur-Klassen
Die einzelnen Filter-Klassen finden sich im Unterverzeichnis Filter unterhalb des
Zend-Ordners. Der Name der Klassendatei entspricht dabei dem Namen des
jeweiligen Filters. Wenn Sie also beispielsweise den Filter Html Entities benöti-
gen, müssen Sie die Klassendatei Zend/Filter/HtmlEntities.php inkludieren.
Jede der Filter-Klassen verfügt über eine Methode f i 1 ter(), mit der Sie den Fil-
ter anwenden können. Sie bekommt die Daten übergeben, konvertiert sie und
gibt sie danach zurück:
requi re_once 'Zend/Fi 1 ter/HtmlEntities.php';
Sfilter = new Zend_Fi1ter_HtmlEntities():
echo $fi1ter->fi1ter('Süße Soße'):
//Ausgabe: Sü:ß:e Soß:e
Der vergleichbare statische Aufruf sähe so aus:
require_once 'Zend/Filter.php':
echo Zend_Filter::get('Süße Soße', 'HtmlEntities'):
Bei dem Aufruf der Methode get() übergeben Sie den zu filternden Text als ers-
ten Parameter und den Namen des Filters an zweiter Stelle. Sollte der Konstruk-
tor der eigentlichen Klasse noch weitere Parameter akzeptieren, dann können Sie
diese nach dem Namen angeben.
Bitte beachten Sie, dass Sie für den statischen Aufruf die Klasse Zend_Fi 1 ter ein-
binden und nutzen müssen. Diese sorgt dafür, dass andere benötigte Klassen
inkludiert werden.
In vielen Fällen wird es nötig sein, dass Sie Daten durch mehrere Filter schicken.
Um solche Fälle zu vereinfachen, sind »Chains« vorgesehen. Eine Chain ist nichts
anderes als eine Aneinanderreihung von Filtern, die ein Datensatz durchläuft,
bevor Sie die fertig konvertierten Daten erhalten.
Eine solche Chain ist ein Objekt der Klasse Zend_Fi 1 ter, bei dem die verschiede-
nen Filter mithilfe von addFi 1 ter() angemeldet werden. Wollen Sie bei einer
Eingabe beispielsweise die Sonderzeichen durch Entitäten ersetzen und gleich-
zeitig sicherstellen, dass alle Buchstaben in Kleinschrift dargestellt werden, dann
ist das kein Problem. Neben dem Filter Html Enti ti es wäre in diesem Fall noch
der Filter StringToLower beteiligt:
require_once 'Zend/Filter.php':
requi re_once 'Zend/Fi 1 ter/HtmlEntities.php';
requi re_once 'Zend/Fi 1ter/StringToLower.php’:
Schain = new Zend_Fi1ter():
166
e-bol.net
Filtern von Daten mit Zend_Filter | 4-3
$f11ter_entity = new Zend_Fi1ter_HtmlEntities();
$fi 1 ter_lower = new Zend_Fi 1 ter_Stri ngTol_ower();
$chain->addFilter($filter_enti ty)
->addFi1ter($fi1ter_lower);
echo $chain->fi1ter(’ÄÖÜ');
// Ausgabe: äöü:
Wie auch in den meisten anderen Klassen ist auch hier wieder ein »fluent Inter-
face« umgesetzt, sodass sich verschiedene addFi 1 ter ()-Aufrufe miteinander ver-
knüpfen lassen.
Eine Kleinigkeit möchte ich an dieser Stelle noch anmerken. Das obige Beispiel
funktioniert nur, weil die Umlaute zuerst in Entitäten konvertiert und dann in
Kleinbuchstaben umgesetzt werden. Somit wird aus einem Ä zuerst ein Ä
bei welchem dann das A in ein a konvertiert werden kann. Da hier nicht explizit
angegeben ist, mit welcher Lokalisierung gearbeitet wird, würde das System die
deutschen Umlaute nicht als Buchstaben erkennen. Möchten Sie sicherstellen,
dass die Filter immer korrekt funktionieren, so achten Sie bitte darauf, dass die
korrekte Lokalisierung gesetzt ist:
setl ocale(LC_ALL,’de_DE');
Die Verwendung der Klasse Zend_Loca 1 e ist an dieser Stelle noch nicht vorgesehen.
Wie Sie schon in den Beispielen gesehen haben, kennt Zend_Filter eine ganze
Reihe vorgefertigter Filter, die direkt eingesetzt werden können. Jede der Filter-
Klassen kennt die Methode filtert), welche die ihr übergebenen Daten ent-
sprechend den Vorgaben bearbeitet. Nachfolgend finden Sie eine Beschreibung
der vordefinierten Klassen.
4.3.1 Alphanumerische Zeichen mit Zend_Filter_Alnum filtern
Der Filter Ain um akzeptiert nur Buchstaben und Zahlen. Alle anderen Zeichen
werden aus dem String entfernt. Optional können Sie dem Konstruktor noch
true übergeben, sofern Sie auch Whitespaces akzeptieren wollen. Bitte beachten
Sie, dass nicht nur Leerzeichen, sondern alle nicht druckbaren Zeichen wie zum
Beispiel Zeilenumbrüche zu den Whitespaces gehören. Diese Tatsache kann
schnell ein Sicherheitsproblem nach sich ziehen.
Dieser Filter ist - wie ein paar andere Filter auch - mit Vorsicht zu genießen.
Momentan ist die Funktionsweise folgende: Die Klasse prüft, ob die installierte
PHP-Version bei der Verarbeitung von regulären Ausdrücken Unicode unter-
stützt. Abhängig von dieser Überprüfung nutzt das System unterschiedliche regu-
läre Ausdrücke, was dazu führt, dass auf Systemen ohne Unicode-Unterstützung
167
e-bol.net
4 | Infrastruktur-Klassen
keine Umlaute akzeptiert werden, wohingegen Systeme mit Unicode-Unterstüt-
zung Umlaute akzeptieren. Ein etwas unschönes Verhalten, wie ich finde.2
Um zu prüfen, ob Ihre PHP-Installation mit Unicode-Unterstützung für die PCRE-
Engine erstellt wurde, können Sie die nachfolgende i f-Abfrage benutzen:
if(l =—@preg_match('/\pL/u’, ’a'))
{
// Ja, mit Unicode
1
el se
{
// Nein, ohne Unicode
1
Listing 4.10 Testen der UTF-8-Unterstützung der PCRE-Engine
Unterstützt Ihr System Unicode, und wollen Sie sichergehen, dass Umlaute
akzeptiert werden, so muss der zu prüfende Text im UTF-8-Format vorliegen.
4.3.2 Buchstaben filtern
Der Filter Zend_Fi l ter_Al pha akzeptiert nur Buchstaben. Ansonsten ist sein Ver-
halten identisch mit dem von Al num.
4.3.3 Extrahieren eines Basenames
Bei der Klasse Zend_Fi l ter_BaseName handelt es sich um einen Wrapper für die
PHP-Funktion basenameO. Die Methode filter() bekommt einen Dateipfad
inklusive des Dateinamens übergeben, extrahiert den Namen der Datei und lie-
fert diesen zurück. Als Dateiname wird hierbei der letzte Teil des übergebenen
Pfads angesehen. Es kann sich dabei auch um den Namen eines Unterverzeichnis-
ses handeln:
requi re_once 'Zend/Filter/BaseName.php';
Sfilter = new Zend_Filter_BaseName():
// Gibt index.php aus
echo $filter->filter('/va r/www/htdocs/index.php’);
// Gibt htdocs aus
echo $filter->filter('/var/www/htdocs/');
Listing 4.11 Basename extrahieren
2 Bitte prüfen Sie beim Einsatz dieser Klasse, ob sich dieses Verhalten geändert hat.
168
e-bol.net
Filtern von Daten mit Zend_Filter | 4-3
4.3.4 Ziffern mit Zend_Filter_Digits ausfiltern
Der Filter Digits akzeptiert ausschließlich Ziffern; alle anderen Zeichen werden
entfernt. Auch wenn Ziffern im Unicode und im ISO-Zeichensatz identisch darge-
stellt werden, so unterscheidet die Klasse intern doch, ob Unicode-Unterstützung
vorhanden ist. Ist keine Unicode-Unterstützung vorhanden, so wird der reguläre
Ausdruck [A0-9] genutzt, wohingegen bei Unicode-Unterstützung [\p(AN} ]
genutzt wird. Die Ausdrücke sollten sich identisch verhalten. Sollte es aber doch
einmal zu einem unerwarteten Verhalten kommen, so prüfen Sie bitte im PHP-
Change-Log, ob sich das Verhalten der zweiten Variante eventuell geändert hat.
4.3.5 Extrahieren von Verzeichnisnamen
Zend_Fi 1 ter_Di r stellt einen Wrapper für die PHP-Funktion dirname() dar.
Übergeben Sie der Methode einen Pfad inklusive Dateinamen, so liefert sie den
eigentlichen Pfad ohne Dateinamen zurück. Es handelt sich also um das Gegen-
stück zum Filter BaseName. Es wird also der letzte Teil des übergebenen Pfads ent-
fernt. Sollte es sich dabei nicht um einen Dateinamen handeln, kann die Klasse
dies nicht erkennen.
requi re_once 'Zend/Fi 1 ter/Di r.php';
Sfilter = new Zend_Fi1ter_Dir();
// Ausgabe: /var/www/htdocs
echo $fi1ter->fi1ter('/var/www/htdocs/index.php’);
// Ausgabe: /var/www
echo $fi1ter->fi1ter('/var/www/htdocs/');
4.3.6 Konvertieren von Sonderzeichen in Entitäten
Den Filter Zend_Fi 1 ter_Html Enti ties haben Sie schon in den Beispielen am
Anfang des Kapitels kennengelernt. Er konvertiert die Sonderzeichen in einem
übergebenen Text in die korrekten HTML-Entitäten. Bei dieser Klasse arbeitet im
Hintergrund die PHP-Funktion htmlentitiest), wie Sie sich sicher schon den-
ken. Somit akzeptiert der Konstruktor auch die selben Parameter, wie die Funk-
tion sie zusätzlich zu dem String akzeptieren würde. An erster Stelle können Sie
dem Konstruktor eine der Konstanten ENT_COMPAT, ENT_QUOTES oder ENT_NOQUO-
TES übergeben. Die erste, die auch den Default-Wert darstellt, sorgt dafür, dass
einfache Anführungszeichen nicht in Entitäten konvertiert werden. Die zweite
Konstante stellt sicher, dass einfache Anführungszeichen durch die Entität
' ersetzt werden. Bei der Verwendung des dritten Wertes werden weder
einfache noch doppelte Anführungszeichen verändert.
169
e-bol.net
4 | Infrastruktur-Klassen
Als zweiten Parameter können Sie den Zeichensatz angeben, in welchem der zu
filternde Text vorliegt. Hierbei ist ISO-8859-1 der Standardwert. Sollte es einmal
notwendig sein, so können Sie diese beiden Werte auch nachträglich verändern
bzw. auslesen, welche Werte gesetzt wurden. Hierzu sind die Methoden
getCharSetO und setCharSetO bzw. setQuoteStyle() und getQuoteStyle()
deklariert.
4.3.7 Filtern von Integer-Werten
Der Filter Int konvertiert einen übergebenen String in einen Integer-Wert. Auch
wenn das ein wenig an den Filter Digits erinnert, so unterscheiden die beiden sich
doch deutlich. Di gi ts entfernt alle Nicht-Zahl-Zeichen aus einem String und gibt
ihn zurück. Int hingegen führt ein Casting durch. Das heißt, der Rückgabewert ist
vom Datentyp Integer, und nur die Ziffern, die am Anfang des übergebenen Strings
zu finden waren, werden zurückgegeben. Aus 12a34 würde somit 12, wohingegen
Digits den Wert 1234 zurückgeben würde. Zeichensätze oder Ähnliches müssen
hierbei nicht beachtet werden. Der Konstruktor akzeptiert keine Parameter.
4.3.8 Absolute Pfade mit Zend_Filter_RealPath extrahieren
Mithilfe des Filters Real Path können Sie eine relative Pfadangabe in eine abso-
lute konvertieren. Für die Bestimmung des absoluten Pfads wird das aktuell
genutzt Unterverzeichnis als Grundlage genutzt:
requi re_once 'Zend/Fi 1 ter/Real Path.php';
Sfilter = new Zend_Fi1ter_RealPath();
// Datei liegt in: /var/www/htdocs/fi1tertest
// Ausgabe: /var/www/index.php
echo $fi1ter->fi1ter('../../index.php');
4.3.9 Konvertieren in Kleinbuchstaben
Zend_Fi 1 ter_StringToLower dient dazu, einen übergebenen String in Klein-
buchstaben zu konvertieren. Auch an dieser Stelle sind Umlaute, die eventuell in
dem String enthalten sind, nicht ganz unproblematisch. Die Klasse geht standard-
mäßig davon aus, dass der übergebene String im ISO-8859-1-Zeichensatz vorliegt
und nutzt die PHP-Funktion strtolower() für die Konvertierung. Sollten Sie
allerdings einen anderen Zeichensatz nutzen wollen, so teilen Sie das dem Objekt
dadurch mit, dass Sie den Zeichensatz an die Methode setEncoding() überge-
ben. In diesem Fall wird im Hintergrund die Funktion mb_strtolower() genutzt.
170
e-bol.net
Filtern von Daten mit Zend_Filter | 4-3
Das hat zur Folge, dass Sie - wenn Sie setEncodi ng() nicht aufrufen - die Loka-
lisierung mit setlocale() setzen müssen, damit Umlaute erkannt werden. Im
zweiten Fall ist das dann nicht mehr notwendig, da mb_strtol ower() diese auto-
matisch konvertiert. Wenn Sie Umlaute also korrekt konvertieren wollen, so gibt
es hierfür zwei mögliche Vorgehensweisen.
Ansatz 1:
setl ocale(LC_ALL,’de_DE');
requi re_once 'Zend/Fi 1ter/Stri ngToLower.php’;
Sfilter = new Zend_Fi1ter_StringToLower!);
echo $fi1 ter->fi1 ter('ÄÖÜ');
Ansatz 2:
requi re_once 'Zend/Fi 1ter/Stri ngToLower.php’;
Sfilter = new Zend_Fi1ter_StringToLower!);
$fi1ter->setEncoding('ISO-8859-1');
echo $fi1 ter->fi1 ter('ÄÖÜ');
4.3.10 Konvertieren in Großbuchstaben
Das Verhalten von StringToUpper ist identisch mit dem von StringToLower,
wobei der übergebene String in Großbuchstaben konvertiert wird.
4.3.11 Entfernen von Whitespaces
StringTrim stellt einen Wrapper für die PHP-Funktion trim!) dar. Mithilfe die-
ses Filters können Sie Whitespaces am Anfang und Ende eines Strings entfernen.
Zusätzlich können Sie dem Konstruktor noch einen String übergeben, der eine
Liste von Zeichen enthält, die von Beginn und Ende des Strings entfernt werden
sollen. Diesen String können Sie alternativ auch nach Ableiten des Objekts mit-
hilfe von setCharList() setzen oder mit getCharList!) auslesen.
$ref = "http://192.168.0.23/”;
requi re_once 'Zend/Filter/Stri ngTrim.php';
Sfilter = new Zend_Filter_StringTrim();
// Zeichenliste hätte auch dem Konstruktor
// übergeben werden können
$filter->setCharList(':/htp');
// Ausgabe: 192.168.0.23
echo $fi1 ter->fi1 ter($ref):
Listing 4.12 Trimmen eines Strings
171
e-bol.net
4 | Infrastruktur-Klassen
4.3.12 Entfernen von HTML-Tags
Auch der Filter Stri pTags stellt einen Ersatz für stri ptags() aus PHP dar. Aller-
dings handelt es sich nicht um einen einfachen Wrapper, sondern um eine kom-
plett neue Implementation, die deutlich leistungsfähiger ist. Der Filter kann Tags
aus einem übergebenen String entfernen. Wie die PHP-Variante ist die Methode
dabei in der Lage, bestimmte Tags beizubehalten und andere zu entfernen. Aller-
dings kann der Filter auch Kommentare im Code belassen und bestimmte Attri-
bute beibehalten.
Diese Vielfalt an Möglichkeiten führt allerdings dazu, dass der Konstruktor ein
wenig komplex in der Handhabung ist. In der einfachsten Variante übergeben Sie
dem Konstruktor keine Parameter. In diesem Fall werden alle Tags, Attribute und
Kommentare entfernt. Möchten Sie bestimmte Tags zulassen, übergeben Sie
diese Tags in Form eines Arrays als ersten Parameter an den Konstruktor. Sollten
Sie bei einigen Tags bestimmte Parameter zulassen wollen, ist dies auch über den
ersten Parameter realisierbar. In diesem Fall nutzen Sie die Namen der Tags, die
Sie akzeptieren möchten als Schlüssel, und übergeben die Namen der Attribute,
die bei einem bestimmten Tag akzeptiert werden sollen, in Form eines Arrays als
Wert an diesen Schlüssel. Sollten Sie bei einem Tag keine Attribute akzeptieren
wollen, so übergeben Sie ein leeres Array.
Mit dem zweiten Parameter des Konstruktors können Sie eine Liste von Attribu-
ten übergeben, die bei allen Tags akzeptiert werden. Diese Liste wird einfach als
Array übergeben, wobei die Namen der Attribute die Werte des Arrays darstel-
len.
Der dritte Parameter ist vom Typ Boolean und legt fest, ob Kommentare im Code
erhalten bleiben (true) oder entfernt werden (fal se) sollen, wobei die Kommen-
tare standardmäßig entfernt werden.
Die folgenden Beispiele verdeutlichen die Verwendung ein wenig:
Shtml = "
<!-- body -->
<body text='black'>
Hier ist der Body
<img src='bild.jpg’ width='125’ />
<iframe src='daten.php’ width='50%’ height='50%’>
</i frame>
</body>
IT «
J
require_once 'Zend/Filter/StripTags.php’;
172
e-bol.net
Filtern von Daten mit Zend_Filter | 4-3
// Keine Tags oder Kommentare zugelassen
Sfilter = new Zend_Fi1ter_StripTags();
echo $fi1 ter->fi1 ter($html);
/* Ausgabe:
Hier ist der Body
// Die Tags img und iframe werden zugelassen.
// Alle Tags dürfen über das Attribut src verfügen.
// Kommentare sind zugelassen.
$filter = new Zend_Fi1ter_StripTags(array('img','iframe’),
array('src'). true);
echo $fi1 ter->fi1 ter($html);
/* Ausgabe:
<!-- body -->
Hier ist der Body
<img src='bild.jpg’ />
<iframe src='daten.php’>
</i frame>
// Die Tags img und iframe werden zugelassen.
// iframe darf die Attribute width und height haben.
// Alle Tags dürfen über das Attribut src verfügen.
// Kommentare sind nicht zugelassen
$fiIter = new Zend_Fi1ter_StripTags(array('img’=>array(),
'i frame'=>
array('width' ,
'height'))
array('src'). false):
echo $fi1 ter->fi1 ter($html);
/* Ausgabe:
Hier ist der Body
<img src='bild.jpg' />
<iframe src='daten.php’ width=’50%’ height=’50%'>
</i frame>
*/
Listing 4.13 Entfernen von Tags
173
e-bol.net
4 | Infrastruktur-Klassen
Möchten Sie diese Werte nicht direkt über den Konstruktor setzen oder später
wieder verändern, so können Sie die Tags über die Methode setTagsAllowed()
und die Attribute über setAttributesAll owed() festlegen. Möchten Sie die
Werte wieder auslesen, so stehen entsprechende Methoden zur Verfügung,
deren Namen nicht mit set, sondern mit get beginnen.
4.3.13 Nutzung eigener Filter
Die bereitgestellten Filter sind zwar schon sehr hilfreich und leistungsfähig, aber
in vielen Fällen sicher nicht ausreichend. Benötigen Sie zum Beispiel einen Filter,
der nur IP-Adressen akzeptiert, so müssten Sie diesen selbst implementieren.
Natürlich könnten Sie auch einfach eine normale Funktion nutzen. Erstellen Sie
aber einen Filter, können Sie diesen beispielsweise auch in einer Filter-Chain ver-
wenden.
Um einen eigenen Filter zu implementieren, benötigen Sie eine Klasse, welche
das Interface Zend_Filter_Interface implementiert. Somit muss Ihre Klasse
auch die Methode f i l ter() implementieren, was aber auch die einzige Anforde-
rung ist. filterO muss einen Parameter, den zu filternden Wert, akzeptieren
und den gefilterten Wert zurückgeben. In dem folgenden Beispiel sehen Sie, wie
ein solcher Filter implementiert wird. Hier wurde ein Filter umgesetzt, der dafür
sorgt, dass Jahreszahlen immer vierstellig sind, wobei Zahlen, die weniger als
vier Stellen haben, entsprechend ergänzt werden. Bei Zahlen mit mehr als vier
Stellen werden nur die letzten vier Stellen übernommen:
requi re_once 'Zend/Filter/Interface.php’;
dass JahresFilter implements Zend_Filter_Interface
{
private $_offset:
public function ___construct($offset = 2000)
{
$this->_offset = $offset;
public function filter($string)
{
$jahr = (int) substr($string,strien($string)-4);
if (4 > strien($jahr))
$jahr+=$this->_offset;
l
return $jahr;
174
e-bol.net
Formularverarbeitung mit Zend_Filter_lnput | 4-4
}
$filter = new JahresFi1ter();
echo $fi1 ter->fi1 ter(7); // 2007
echo $fi1 ter->fi1 ter(1701); // 1701
echo $fi1ter->fi1 tert 12007); // 2007
Listing 4.14 Implementation eines eigenen Filters
4.4 Formularverarbeitung mit Zend_Filter_lnput
Würde man dem Namen nach gehen, so könnte man vermuten, dass Zend_
Fi l ter_Input lediglich ein weiterer Filter ist. Aber Zend_Fi l ter_Input ist mehr
und wurde daher auch in ein eigenes Kapitel ausgelagert.
Die Möglichkeiten der anderen Filter-Klassen beschränkten sich darauf, Daten zu
normalisieren oder zu extrahieren. Allerdings ist keine Möglichkeit der Validie-
rung vorgesehen. Wollten Sie nun beispielsweise überprüfen, ob eine E-Mail-
Adresse gültig ist, müssten Sie wieder auf Zend_Validate zurückgreifen. Sie
müssten also unter Umständen mehrere Filter und Validatoren nacheinander
anwenden. Das ist recht umständlich und verwirrend und führt außerdem
schnell dazu, dass man etwas übersieht. Zend_Fi l ter_Input gibt Ihnen die Mög-
lichkeit, Filter und Validatoren miteinander zu kombinieren. Auf dieser Basis
können Sie schnell und einfach Werte aus Formularfeldern übernehmen und
prüfen.
Zend_Fi l ter_Input ist darauf spezialisiert, Werte zu validieren, zu normalisieren
und zu escapen. Das kann Ihnen erhebliche Arbeit abnehmen und Ihren Quell-
code deutlich aufräumen. Besonders praktisch ist, dass man ganze Arrays wie
zum Beispiel $_POST auf einmal filtern kann.
Die grundsätzliche Idee ist einfach. Sie übergeben dem Konstruktor ein Array mit
Filtern, eines mit Validatoren sowie ein Array mit den Daten. Um die einzelnen
Filter und Validatoren den Formular- bzw. Array-Feldern zuordnen zu können,
werden die Array-Schlüssel bzw. die Feldnamen als Schlüssel genutzt. Als Werte
können Sie dann die Namen der Klassen nutzen. Oder Sie übergeben direkt ein
Objekt der jeweiligen Filter- oder Validator-Klasse. Die Nutzung von Objekten
dürfte gerade bei umfangreichen Formularprüfungen performanter sein, da Sie
die Objekte mehrfach nutzen können. Vor allem haben Sie bei der Nutzung von
Objekten den Vorteil, dass Sie die Objekte auch entsprechend konfigurieren kön-
nen. Allerdings müssen Sie dann die benötigen Klassen selbst inkludieren.
175
e-bol.net
4 | Infrastruktur-Klassen
Wollen Sie das Array-Element nachname mithilfe von StripTags filtern, könnten
Sie das Array so aufbauen:
Sfilters = array (
'nachname' => 'StripTags'
);
Oder so:
Sstriptags = new Zend_Fi1ter_StripTags():
Sfilters = array (
'nachname' => Sstriptags
);
In einigen Fällen kann es vorkommen, dass Sie mehrere Filter oder Validatoren
benötigen. In diesen Fällen übergeben Sie diese als Array an den Schlüssel:
Sfilters = array (
'nachname' =>
array ('StripTags', 'StringTrim')
);
Auch hier wäre natürlich die Nutzung von Objekten möglich gewesen. Wollen
Sie, dass eine der Regeln auf alle Felder angewandt wird, so können Sie den Filter
oder Validator auch der Wildcard * zuweisen.
Der dritte Parameter, der dem Konstruktor von Zend_Fi1ter_Input übergeben
wird, ist das Array mit den Daten.
Sobald das Objekt initialisiert ist, können Sie mit der Methode i sVal i d() prüfen,
ob die übergebenen Daten den Vorgaben der Validatoren entsprechen. Die
Methode liefert einen booleschen Wert zurück und kann somit direkt in einer i f-
Abfrage genutzt werden. Übergeben Sie isValidO keinen Parameter, werden
alle Werte geprüft. Optional können Sie auch den Namen eines Feldes überge-
ben, um nur dieses eine Feld zu validieren. Das folgende Listing veranschaulicht
die Verwendung:
requi re_once 'Zend/Fi 1 ter/Input.php';
// Fi 1terklasssen inkludieren
require_once 'Zend/Fi1ter/StringTrim.php';
require_once 'Zend/Filter/StripTags.php’;
// Validator-Klassen inkludieren
require_once 'Zend/Validate/Int.php’;
176
e-bol.net
Formularverarbeitung mit Zend_Filter_lnput | 4-4
// Filterobjekte ableiten
Sstringtrim = new Zend_Filter_StringTrim();
Sstriptags = new Zend_Fi1ter_StripTags();
// Filter-Array aufbauen
$filter = array (
'eins' =>
array($stringtrim,
$striptags).
'zwei’ =>
array ($stringtrim,
Sstriptags)
);
// Val idator-Objekt ableiten
$int = new Zend_Validate_Int();
// Validator-Array aufbauen
$validatoren = array (
'eins' => $int,
'zwei' => $int
):
// Array mit Daten erstellen
Sdaten = array ('eins' => '42',
'zwei' => 'Zweiundvierzig');
// Fi 1ter_Input-Objekt ableiten
Sinput = new Zend_Fi1ter_Input($fi1 ter, $validatoren, Sdaten);
// Sind die Daten gültig?
if (true — $input->isValid())
{
echo "Daten waren gültig";
}
el se
{
$fehler = $input->getMessages();
echo "Daten waren nicht gültig";
echo "<br>Fehler: " .$fehler['zwei' HO];
echo "<br>Wert von eins: ".$input->getEscaped(’eins ');
echo "<br>Wert von zwei: ".$input->getEscaped(’zwei');
I
Listing 4.15 Nutzung von Zend_Filter_lnput
177
e-bol.net
4 | Infrastruktur-Klassen
Da in diesem Fall die Daten nicht gültig sein konnten (der String zwei und vier zig
ist kein Integer-Wert), liefert i sVal id() den Wert fal se zurück. Daher habe ich
nur den el se-Teil der i f-Abfrage ausgeführt.
Die Fehler, die von den Validatoren gemeldet wurden, werden im Objekt abge-
legt und können mit der Methode getMessagesl) ausgelesen werden. Überge-
ben Sie die Validatoren als Objekte, sollten Sie die Fehlermeldungen natürlich
sprachlich anpassen. Nutzen Sie nur die Namen der Validatoren, können Sie die
Meldungen auch anpassen. Entnehmen Sie dies bitte der Dokumentation.
Die Methode liefert ein mehrdimensionales Array zurück, bei dem in der ersten
Ebene die Namen der geprüften Felder als Schlüssel genutzt werden. Jeder dieser
Schlüssel verweist wieder auf ein indiziertes Array, welches die Fehlermeldun-
gen enthält. In diesem Beispiel ist die vorhandene Fehlermeldung also im Ele-
ment [' zwei' ] [ ’ 0' ] enthalten.
Die Methode getEscaped() liest den Wert des jeweiligen Feldes aus dem Objekt
aus. Hierbei wird der Wert an den Escape-Filter übergeben. Dabei handelt es sich
standardmäßig um Zend_Fi 1 ter_Html Enti ties. Damit ist ein recht hohes Maß
an Sicherheit gewährleistet, falls die Daten danach wieder ausgegeben werden
sollen. Bevorzugen Sie allerdings die »Rohdaten«, so sollten Sie die Methode
getUnescaped() verwenden. In dem Fall ist der Begriff »Rohdaten« allerdings
nicht ganz exakt, da die Daten gefiltert und validiert, aber nicht escapet wurden.
Die Ausgabe im Browser sehen Sie in Abbildung 4.2.
O O O http://127.0.0.1/~cars...zf-buch/Filter/l l.php CD
fi- Google
Daten waren nicht gültig
Fehler 'Zweiundvierzig' does not appear to bc an integer
Wert von eins: 42
Wen von zwei:
Abbildung 4.2 Ausgabe von Zend_Filter_lnput
Wie Sie sehen, werden Werte, die nicht validiert werden konnten, vom System
nicht wieder bereitgestellt.
So weit, so gut. Was aber ist, wenn Sie ein komplettes Formular validieren wol-
len? Grundsätzlich macht das natürlich keinen Unterschied zu dem vorangegan-
genen Beispiel. Bei genauerer Betrachtung ergeben sich allerdings einige kleinere
Probleme. Das erste Problem ist, dass Zend_Fi 1 ter_Enti ties- sofern möglich -
UTF-8-Daten akzeptieren sollte, um sicherzustellen, dass es keine Probleme mit
178
e-bol.net
Formularverarbeitung mit Zend_Filter_lnput | 4-4
Sonderzeichen gibt. Dazu muss das entsprechende Objekt aber manuell abgelei-
tet werden. Sie können es dann mit der Methode setDefaul t Escape Fi 1 ter () als
Escape-Filter anmelden, um den normalen Escape-Filter zu überschreiben.
Das zweite Problem betrifft die Pflichtfelder. Standardmäßig ist jedes Feld, das
der Klasse bekannt ist, ein Pflichtfeld. Sobald Sie einen Filter für ein Feld ange-
meldet haben, ist es somit ein Pflichtfeld. Um das zu vermeiden, haben Sie die
Möglichkeit, einen sogenannten Meta-Befehl zu nutzen. Dabei handelt es sich
um einen Schlüssel, den Sie mit im Array der Validatoren angeben können. Bele-
gen Sie den Schlüssel Zend_Fi 1 ter_Input: :ALLOW_EMPTY mit dem Wert true,
darf das Feld leer bleiben. Was aber, wenn ein Pflichtfeld nicht mit einem Wert
belegt war? Dann erhalten Sie eine entsprechende Meldung, sobald Sie die
Methode getMessages!) aufrufen. Standardmäßig lautet die Meldung You must
give a non-empty value for field '%fi eld%', wobei %field% durch den Namen
des Feldes ersetzt wird. Diese Meldung können Sie dadurch ersetzen, dass Sie
dem Konstruktor noch ein viertes Array als Parameter übergeben. In diesem kön-
nen Sie den Schlüssel Zend_Fi 1 ter_Input::NOT_EMPTY_MESSAGE mit einer eige-
nen Meldung belegen. Diese Option können Sie auch später noch mit der
Methode setOptions() festlegen.
In dem folgenden Listing wird ein Formular mit drei Feldern ausgegeben. Der
Vorname ist ein optionales Feld. Nachname und E-Mail-Adresse sind Pflichtfel-
der, wobei die E-Mail-Adresse auch auf Validität hin geprüft wird:
header('Content-Type: text/html; charset=utf-8'):
requi re_once 'Zend/Fi 1 ter/Input.php';
// Fi 1terklasssen inkludieren
require_once 'Zend/Fi1ter/StringTrim.php';
require_once 'Zend/Filter/StripTags.php’;
requi re_once 'Zend/Fi1 ter/HtmlEntities.php’;
// Validator-Klassen inkludieren
require_once 'Zend/Validate/EmailAddress.php';
function formular_ausgeben!$i nput)
{
// Werte auslesen
$v_vorname = $input->getEscaped('Vorname'):
$v_nachname = $input->getEscaped(’Nachname'):
$v_email = $input->getEscaped(’E-Mai 1'):
// Eventuelle Fehlermeldungen auslesen
179
e-bol.net
4 | Infrastruktur-Klassen
$fehler = $input->getMessages();
echo "<form method=’post'>";
echo "Vorname: <input type='text'
name=’Vorname’ value='$v_vorname’><br>”;
echo "Nachname: <input type='text'
name=’Nachname' value='$v_nachname'><br>";
if (true —== isset ($fehler['Nachname']))
{
echo implode('<br>',$fehler['Nachname']).'<br>';
echo "E-Mai 1-Adresse: <input type=’text'
name='E-Mail' value='$v_emai1'><br>";
if (true —== isset ($fehler['E-Mai 1']))
{
echo implode('<br>',$fehler['E-Mail’]).'<br>';
echo "<input type=’submit' value=’Abschicken'><br>";
echo "</form>";
// Loaklisierungseinstel1ungen setzen
setl ocale(LC_ALL,'de_DE');
// Filterobjekte ableiten
Sstringtrim = new Zend_Filter_StringTrim();
Sstriptags = new Zend_Fi1ter_StripTags():
// Validator-Objekt ableiten
Semail = new Zend_Validate_Emai1Address():
$emai1->setMessage('Die E-Mai 1-Adresse ist nicht gültig*,
Zend_Validate_Emai1 Address::INVALID);
// Hier noch weitere Meldungen setzen...
// Escape-Fi 1 ter ableiten
SescapeFi1 ter = new Zend_Fi1ter_HtmlEntities(ENT_COMPAT,
’UTF-81):
// Filter-Array aufbauen
$filter = array (
'Vorname' =>
array($stringtrim,
$striptags).
'Nachname' =>
array($stringtrim,
180
e-bol.net
Formularverarbeitung mit Zend_Filter_lnput | 4-4
$striptags).
’E-Mai 1 * =>
array ($stringtrim,
$stri ptags)
// Val idator-Array aufbauen
$va1idatoren = array (
'Vorname' => array(
Zend_Fi1ter_Input::ALLOW_EMPTY => true),
'Nachname' => array(
Zend_Fi1ter_Input::BREAK_CHAIN => false),
'E-Mail’=> $emai1
) ;
// Meldung für Pflichtfelder
Soptionen = array (
Zend_Fi1ter.Input::NOT_EMPTY_MESSAGE =>
'Das Feld "%field%" darf nicht leer sein'
// Fi 1ter_Input-Objekt ableiten
Sinput = new Zend_Fi1ter_Input($fi1 ter, $validatoren,
$_POST, $optionen);
// Escape-Filter setzen
$input->setDefaultEscapeFilter($escapeFilter);
// Wurden Daten übergeben
// Und waren die Daten gültig?
if (0 !== count($_POST) &&
true === $input->isVa1id())
echo "Sie gaben folgende Daten ein:";
echo "<br>Vorname: ".$input->getEscaped('Vorname'):
echo "<br>Nachname: $input->getEscaped('Nachname');
echo "<br>E-Mail: $input->getEscaped('E-Mai 1’);
I
el se
{
formular_ausgeben(Sinput);
}
Listing 4.16 Validieren eines Formulars
e-bol.net
4 | Infrastruktur-Klassen
Die meisten Elemente sind Ihnen ja inzwischen bekannt, daher nur ein paar
kleine Ergänzungen. Bei dem E-Mail-Validator habe ich aus Platzgründen nur
eine Fehlermeldung eingedeutscht. Die anderen sollten natürlich auch übersetzt
werden.
Bei dem Array mit den Validatoren finden Sie noch einen weiteren Meta-Befehl.
Mit Zend_F11ter_Input: :BREAK_CHAIN -> false wird sichergestellt, dass die
Validatoren auch dann weiter durchlaufen, wenn einer fehlschlägt, also wenn in
diesem Fall das Feld Nachname leer wäre. Ein kleiner Tipp am Rande: Sollten Sie
für ein Feld keine Validatoren benötigen, so muss das entsprechende Feld trotz-
dem mit in dem Validator-Array genannt werden. Andernfalls geht der Wert des
Feldes verloren. Belegen Sie das Feld in diesem Fall einfach mit einem leeren
Array, also mit array().
Die i f-Abfrage, die prüft, ob die Daten korrekt sind, muss beim ersten Aufruf
feststellen können, ob überhaupt Daten aus dem Formular übergeben wurden.
Das geschieht dadurch, dass die Anzahl der Werte in $_POST ermittelt wird.
isValich ) liefert, auch wenn keine Daten übergeben wurden, nämlich ebenfalls
true zurück. Man hätte an dieser Stelle vorher auch die Methode processt) auf-
rufen können. Sie wirft eine Exception, wenn erwartete Felder nicht vorhanden
sind. Das Formular hätte daher im Rahmen des Exception Handlings ausgegeben
werden lassen können.
Das Formular wird von der Funktion formiil ar_ausgeben() ausgegeben. Dabei
fallt auf, dass die Namen der Formularfelder ungewöhnlich sind. Üblicherweise
würde man ein Formularfeld eher email nennen und nicht E-Mail. Diese
Namensgebung ist aber bewusst so gewählt, weil diese Namen auch in der Feh-
lermeldung genutzt werden, die ausgegeben wird, falls ein Pflichtfeld leer ist.
In Abbildung 4.3 sehen Sie das Formular, wie es dargestellt wird, wenn kein
Nachname eingegeben wurde und die E-Mail-Adresse ungültig war.
n http://127.0.0...ilter/zwei.php CD
Vorname: Car>ttn |
Nachname:
Das Feld "Nachname" darf nicht leer sein
E-Mail-Adrcssc:
Die E-Mail-Adresse ist nicht gültig
Abschicken '
Abbildung 4.3 Validiertes Formular
182
e-bol.net
Formularverarbeitung mit Zend_Filter_lnput | 4-4
Mit der oben vorgestellten Lösung können Sie schon einen Großteil der üblichen
Anforderungen abdecken. Allerdings gibt es noch einige andere Funktionalitä-
ten, die in dem Paket umgesetzt sind. Die Methode getMessages() liest alle Mel-
dungen aus, die bei der Prüfung der Werte generiert wurden. Möchten Sie nur
bestimmte Meldungen erhalten, so stehen die Methoden get Inval id(), getMi s-
si ng() und getUnknown() zur Verfügung. Die erste liest die Namen und Meldun-
gen derjenigen Felder zurück, die nicht validiert werden konnten. Das Format
entspricht dem, das bereits bei getMessagesI) beschrieben wurde. getMis-
si ng() liefert Ihnen Informationen zu Feldern, die erwartet wurden, aber nicht
im Daten-Array enthalten waren. Grundsätzlich gelten alle Felder als optional,
werden also nicht unbedingt erwartet. »Nicht erwartet« bedeutet, dass das Feld
gar nicht mit den Formulardaten verschickt wurde, beziehungsweise im Daten-
Array kein Schlüssel mit diesem Namen vorhanden ist. Um festzulegen, dass ein
Feld im Ergebnis enthalten sein muss, nutzen Sie den Meta-Befehl ist Zend_
Fi 1ter_Input::PRESENCE. Mit Zend_Fi1ter_Input::PRESENCE=>Zend_Fi1ter_
Input::PRESENCE_REQUIRED legen Sie fest, dass das Ergebnis enthalten sein
muss. Und mit Zend_Fi 1 ter_Input:: PRESENCE=>Zend_Fi 1 ter_Input:: PRESENCE_
OPTIONAL definieren Sie, dass das Feld optional ist. Welche Fehlermeldung
genutzt werden soll, können Sie im Optionen-Array (vierter Parameter des Kon-
truktors) über den Schlüssel Zend_Fi 1ter_Input: :MISSING_MESSAGE definieren.
Abschließend gibt es noch die Methode getUnknown(), welche Ihnen die Namen
von Feldern zurückgibt, die im Daten-Array enthalten waren, für die aber keine
Regeln zur Verarbeitung vorgesehen waren. Bei der Nutzung von getMessages()
haben Sie schon gesehen, dass Sie diese Methoden jederzeit aufrufen können,
selbst wenn noch keine Meldungen vorhanden sind. In diesem Fall erhalten Sie
ein leeres Array zurück. Wollen Sie aber vorher testen, ob Meldungen vorliegen,
können Sie die Methoden haslnvalid(), hasMissing() und hasUnknown()
abfragen, die jeweils einen booleschen Wert zurückgeben.
In einigen Fällen kann es passieren, dass Sie in einem Formular ein Array benö-
tigen. Wollen Sie beispielsweise ein Datum abfragen, besteht dieses unter
Umständen aus drei Textfeldern, die logisch zusammengehören. Die Felder im
HTML-Formular könnten so aussehen:
Cinput type="text" name=''dat[tag]" size="2">
<input type="text" name=''dat[inonat]" size="2">
Cinput type="text" name=''dat[jahr]" size="4">
Wollen Sie auf alle Felder die selben Filter oder Validatoren anwenden, ist dies
kein Problem. Nutzen Sie einfach dat als Namen. Das Paket geht dann durch alle
untergeordneten Array-Felder und verarbeitet sie.
183
e-bol.net
4 | Infrastruktur-Klassen
Wenn Sie unterschiedliche Filter nutzen wollen, sieht die Sache ein wenig kom-
plizierter aus. In diesem Fall können Sie folgenden Aufbau nutzen:
$val1datoren = array (
’dat' => array(
array(
' Int',
Zend_Fi1ter_Input::FIELDS => 'tag’),
array(
' Int',
Zend_Fi1ter_Input::FIELDS => ’monat'),
array(
' Int' ,
Zend_Fi1ter_Input::FIELDS => 'jähr')
));
Der Meta-Befehl Zend_Filter_Input:: FI ELDS bietet Ihnen also die Möglichkeit,
auf einzelne Felder zuzugreifen. Allerdings ist die Nutzung von Arrays momen-
tan leider noch fehlerbehaftet. Daher würde ich Ihnen zum jetzigen Zeitpunkt
(Version 1.0.3) noch davon abraten, Arrays zu nutzen. Sollten Sie eine neuere
Version nutzen, so prüfen Sie bitte genau, ob die Fehlermeldungen korrekt zuge-
ordnet werden und die Nutzung von mehreren Validatoren möglich ist. Derzeit
kann nur ein Validator genutzt werden.
Auch bei dieser Klasse gilt, dass es noch verschiedene weitere Vorgehensweisen
gibt, um die hier beschriebenen Funktionalitäten zu nutzen. Sie finden sie in der
Dokumentation zur Klasse.
4.5 Schreiben von Logs mit Zend_Log
Wenn Sie schon größere Applikationen entwickelt haben, werden Sie das Gefühl
kennen, dass Sie gerne wüssten, was im Inneren der Applikation abläuft. Ab
einer bestimmten Größe sind Applikationen schwer zu überwachen. Zwar kann
ein Error-Handler Alarm schlagen, sobald ein Fehler auftritt, aber oft kann man
dann nicht mehr erkennen, was zum Problem geführt hat. Ein Error-Handler
kann Ihnen ebenfalls nicht dabei helfen, Performance-Engpässe aufzuspüren.
Um solche Probleme in den Griff zu bekommen oder Sie beim Debuggen Ihrer
Applikation zu unterstützen, ist es sehr hilfreich, ein Log zu schreiben. Genau
dies leistet Zend_Log. Die Nachrichten, die das System speichert, können unter-
schiedliche Schweregrade ausdrücken. Das heißt, Sie können beispielsweise zwi-
schen einfachen Informationen, Warnungen, Fehlern und schweren Fehlern
unterscheiden. Zend_Log besitzt dabei ein Feature, das wirklich sehr praktisch ist.
184
e-bol.net
Schreiben von Logs mit Zend_Log | 4-5
Und zwar können Sie Nachrichtenlevel filtern. Sie können während der Debug-
Phase so mehr Informationen speichern und später umschalten und nur noch
echte Fehler speichern, wenn das System in den Echtbetrieb geht. Ein wirklich
hilfreiches Feature, wie ich finde.
Ein Log-Eintrag der Klasse besteht standardmäßig aus der eigentlichen Meldung,
einem Timestamp und der Priorität, die als Zahl und als Text eingefügt wird. Der
Timestamp liegt dabei im ISO 8601-Format vor, das wie folgt aufgebaut ist: YYYY -
MM- DDTHH: MM: SS+HH: MM. Bei dem Teil vor dem T handelt es sich um das Datum.
Das T ist konstant. Danach folgt die Uhrzeit. An diese schließt sich (getrennt von
einem + oder einem -) die Abweichung der aktuellen Zeitzone von Greenwich
Mean Time an. Die Uhrzeit wird dabei in der für die Zeitzone korrekten Uhrzeit
ausgegeben. Bei der Verarbeitung eines Timestamps kann Zend_Date sehr nütz-
lich sein.
Standardmäßig kennt das Paket acht Prioritäten, die auch als Log-Level bezeich-
net werden. Sie finden diese in Tabelle 4.2.
Name Zahl Bedeutung Kurzname
Emergency 0 Schwerwiegender kritischer Fehler. System steht kom- plett. EMERG
Alert 1 Kritischer Zustand, der sofortiges Eingreifen erfordert ALERT
Critical 2 Kritischer Zustand des Systems CRIT
Error 3 Fehler bei der Programmausführung, der das System nicht gefährdet ERR
Warning 4 Eine Warnung ist ein unerwarteter Zustand, der aber (noch) kein Fehler ist. Beispiel: Die Festplatte des Servers ist zu 90% belegt. WARN
Notice 5 Normale Ausführung des Codes, wobei es zu Auffälligkei- ten (z. B. verlängerte Laufzeit) kam. NOTICE
Informational 6 Rein informative Nachricht Beispiel: Anzahl der Datensätze, die bei einem Update verändert wurden INFO
Debug 7 Debug-Information, die beim Debugging helfen soll DEBUG
Tabelle 4.2 Verschiedene Log-Level
Möchten Sie ein Log schreiben, so benötigen Sie dazu mindestens zwei Kompo-
nenten. Das ist zum einen der Logger selbst, der für die Verwaltung der Nach-
richten zuständig ist. Zum anderen ist dies mindestens ein sogenannter Writer.
Dieser kümmert sich darum, die Daten in eine Datei zu schreiben oder in einem
anderen Speichermedium abzulegen.
185
e-bol.net
4 | Infrastruktur-Klassen
Optional können Sie die Nachrichten noch formatieren oder filtern. Dazu erfah-
ren Sie später mehr.
Ein Log-Objekt ohne Writer-Objekt ist nicht besonders sinnvoll, da die Log-Ein-
träge sonst nirgendwo gespeichert werden könnten. Daher können Sie das Wri-
ter-Objekt direkt an den Konstruktor der Klasse Zend_Log übergeben. Alternativ
können Sie ihn auch später mithilfe der Methode addWri ter() hinzufügen. Das
wäre auch die Vorgehensweise, wenn Sie mehr als einen Writer nutzen wollen.
Dies kann beispielsweise dann hilfreich sein, wenn Sie die Daten zwar in einer
Datenbank ablegen möchten, sie aus Sicherheitsgründen aber auf jeden Fall auch
noch auf die Festplatte des Servers schreiben wollen.
requi re_once('Zend/Log.php');
requi re_once('Zend/Log/Weiter/Stream.php');
try {
$writer = new Zend_Log_Writer_Stream(1Zvar/1og/my_log');
$logger = new Zend_Log($writer);
$logger->info('Hallo Welt
} catch (Zend_Log_Exception $e)
{
die ($e->getMessage());
}
Listing 4.17 Nutzung des Loggers
In Listing 4.17 sehen Sie ein kleines Beispiel. Hier wird zunächst der Writer abge-
leitet und dann an den Logger übergeben. Wie Sie sehen, müssen die Klassen-
Dateien des Writers und des Loggers inkludiert werden. Der hierbei generierte
Log-Datei-Eintrag lautet:
2007-09-13T20:23:02+02:00 INFO (6): Hallo Welt ;-)
Der Stream-Writer ist ein recht flexibler Writer. Sie können ihm direkt den
Namen und den Pfad der Log-Datei übergeben, was sicherlich am einfachsten ist.
Alternativ können Sie auch einen bereits geöffneten Stream übergeben oder
einen String, der einen Stream3 definiert.
In allen Fällen muss es natürlich möglich sein, dass der Logger in den Stream
schreiben kann, wobei bestehende Log-Einträge nicht überschrieben werden.
Eine andere Variante, um Daten zu speichern, besteht darin, die Daten in eine
Datenbank zu schreiben. In diesem Fall wäre der Writer Zend_Log_Writer_Db
3 Weitere Informationen zu Streams finden Sie unter http://www.php.net/streams.
186
e-bol.net
Schreiben von Logs mit Zend_Log | 4-5
dafür zuständig. In dem folgenden kleinen Beispiel wurde die folgende MySQL-
Tabelle zugrunde gelegt:
Create Table: CREATE TABLE 'log_data' (
'id' int(ll) NOT NULL AUT0_INCREMENT.
'meldung' text NOT NULL,
'prio' int(ll) DEFAULT NULL,
'prio_name' varchar(6) DEFAULT NULL.
'Zeitpunkt' datetime DEFAULT NULL,
PRIMARY KEY ('id' )
) ENGINE=MyISAM DEFAULT CHARSET=1atinl
Listing 4.18 Tabelle zum Speichern von Log-Einträgen
Der Datenbank-Logger benötigt ein Zend_Db-Objekt, um die Verbindung zur
Datenbank herzustellen. Das Datenbankobjekt wird dann direkt an den Kon-
struktor des Writers übergeben. Als zweiten Parameter bekommt der Writer den
Namen der Tabelle als String übergeben. Der dritte Parameter ist ein Array, das
dazu dient, die Log-Informationen den Datenbankspalten zuzuordnen. Standard-
mäßig werden dabei intern in der Log-Klasse die folgenden Bezeichner verwen-
det:
Bezeichner Inhalt
priority Priorität als Zahl
pri ori tyName Priorität als Text
message Text des Log-Eintrags
timestamp Zeitpunkt des Ereignisses
Tabelle 4.3 Schlüssel für das AAapping der Datenbankspalten
In dem Array wird der Name der Datenbankspalte als Schlüssel genutzt und der
dazugehörige Bezeichner aus Tabelle 4.3 als Wert.
requi re_once(’Zend/Db.php’);
requi re_once(’Zend/Log/Writer/Db.php’);
requi re_once(’Zend/Log.php’);
$params = array ('host' => *127.0.0.1',
’username' => 'root’,
’password' => ’’.
’dbname’ => 'test’):
$db = Zend_Db::factory('PDO_MYSQL', $params):
// Mapping der Spalten auf die Bezeichner
Smapping = array('prio' => ’priority',
’prio_name' => ’priorityName',
187
e-bol.net
4 | Infrastruktur-Klassen
’meldung’ => ’message',
’ zei tpunkt'=>'timestamp'
) ;
try {
// Writer-Objekt ableiten
$writer = new Zend_Log_Writer_Db($db, 'log_data', $mapping);
// Neues Log-Objekt ableiten
$logger = new Zend_Log($writer);
// Eintrag in die Datenbank schreiben
$logger->info('Dies ist eine schicke Info');
} catch (Zend_Log_Exception $e)
{
die ($e->getMessage());
}
Listing 4.19 Generieren eines Log-Eintrags in einer Datenbank
Ein Eintrag in der Datenbank sieht dann beispielsweise so aus:
mysql> select * from log_data \G
k "k "k k "k k k “k "k "k k k -k k k -k k k k k k k k k k k k J FOW "kkkkkkkkkkkkkkkkkkkkkkkkkkk
id: 1
meldung: Dies ist eine schicke Info
prio: 6
prio_name: INFO
Zeitpunkt: 2007-0913 19:59:21
1 row in set (0.00 sec)
Die Zeitzoneninformation geht beim Schreiben in eine normale Datetime-Spalte
verloren, was üblicherweise aber nicht dramatisch ist.
Ob Sie die Daten nun in einen Stream, in eine Datei oder eine Datenbank schrei-
ben, ist eine Geschmacksfrage. Ich empfehle Ihnen, mit einer Datei zu arbeiten.
Eine Datenbank zu nutzen, kann unter Umständen - gerade dann, wenn Netz-
werk- oder Datenbankprobleme auftreten -, schwierig sein.
Während der Entwicklung kann es durchaus hilfreich sein, mit einem Mock-Wri-
ter4 zu arbeiten oder die Nachrichten direkt zu verwerfen. Um die Nachrichten
direkt zu verwerfen, können Sie Zend_Log_Wri ter_Nul 1 nutzen. Nutzen Sie die-
sen Writer, so können Sie zwar Einträge generieren, diese erzeugen aber kein
Resultat.
4 Mock: engL: Fälschung, Attrappe
188
e-bol.net
Schreiben von Logs mit Zend_Log | 4-5
Ein Mock-Writer, der von der Klasse Zend_Log_Wri ter_Mock abgeleitet wird, legt
die Nachrichten direkt in dem Objekt ab. Das heißt, Sie können dann beispiels-
weise einfach mit var_dump() ausgeben lassen, welche Log-Informationen bis
dato aufgelaufen sind. Damit werden die Log-Datei bzw. die Datenbank nicht
unnötig belastet.
Bisher haben Sie nur die Methode i nfo() kennengelernt, mit der Log-Einträge
generiert wurden. Auch für die anderen Log-Level ist jeweils eine Methode defi-
niert, die Sie nutzen können. Die Namen der Methoden entsprechen jeweils den
Kurznamen der Events, die Sie in Tabelle 4.2 kennengelernt haben: info(),
debug(), notice(), warnt), errort), crit() und emerg(). Alle Methoden erhal-
ten jeweils den Text der Meldung als Parameter übergeben.
Alternativ können Sie auch die Methode logt) benutzen. Diese erhält als ersten
Wert die Log-Meldung und als zweiten die Priorität des Eintrags als Zahl überge-
ben.
Ich meine, dass die vorhandenen Log-Level üblicherweise ausreichend sein soll-
ten. Haben Sie zu viele Level, so ist die feine Granularität in den meisten Fällen
eher verwirrend und nicht wirklich hilfreich. Hier und da kann es aber vorkom-
men, dass sich im Laufe der Entwicklung noch ein spezielles Problem ergibt, das
einen neuen Level erfordert. Das kann zum Beispiel dann passieren, wenn ein
spezieller Level vorhanden sein sollte, bei dem nur der Datenbankadministrator
benachrichtigt werden soll.
In solch einem Fall können Sie mithilfe von
$logger->addPriority(’DB_ADMIN', 8);
einen neuen Level, in diesem Beispiel 8, hinzufügen. Hier ist der Kurzname des
Levels DB_ADMI N. Die Methode, über die der Log-Eintrag erstellt wird, wird dyna-
misch generiert und hat ebenfalls den Namen db_admin().
Die Tatsache, dass die Methoden auf diesem Weg dynamisch generiert werden,
hat übrigens auch eine unter Umständen verwirrende Nebenerscheinung. Versu-
chen Sie, aus einem Logger-Objekt heraus eine Methode aufzurufen, die es nicht
gibt, so erhalten Sie die Fehlermeldung, dass der Log-Level nicht korrekt ist. Hin-
tergrund ist, dass das Paket mithilfe der magischen Methode_cal 1 () den Ein-
trag in die Log-Datei vornimmt. Diese bekommt den Namen der Methode über-
geben, die Sie aufgerufen haben, und interpretiert ihn als Log-Level.
189
e-bol.net
4 | Infrastruktur-Klassen
4.5.1 Log-Einträge filtern
Wie schon erwähnt, gibt es in Zend_Log die interessante Möglichkeit, Log-Nach-
richten zu filtern und nur bestimmte Einträge zuzulassen. Ein Filter wird immer
von seiner eigenen Klasse abgeleitet und dann an den Logger übergeben, wozu
der Logger die Methode addFilterO vorsieht. Sie bekommt das Filter-Objekt
direkt als Parameter übergeben. Das Paket kennt dazu drei Arten von Filter-Klas-
sen.
Der einfachste Filter ist Zend_Log_Fi l ter_Suppress. Mit ihm können Sie alle
Einträge unterdrücken oder alle komplett akzeptieren. Das kann dann hilfreich
sein, wenn Sie eine Änderung an einem Live-System vornehmen und nicht riskie-
ren möchten, eine zu große Anzahl von Log-Einträgen zu generieren. Praktisch
daran ist, dass der Filter entweder alle Nachrichten passieren lässt oder alle Ein-
träge ignoriert. Das Verhalten wird mithilfe der Methode suppress() gesteuert,
die einen booleschen Wert übergeben bekommt. Der Wert true sorgt dabei
dafür, dass alle Nachrichten ignoriert werden.
requi re_once('Zend/Log.php');
requi re_once('Zend/Log/Wri ter/Stream.php');
requi re_once('Zend/Log/Filter/Suppress.php');
try {
// In Datei schreiben
$writer = new Zend_Log_Writer_Stream('logfile');
// Neuen Logger ableiten
$logger = new Zend_Log($writer);
// Neuen Suppress-Filter
$filter = new Zend_Log_Filter_Suppress();
// Filter an Logger übergeben
$logger->addFilter($filter);
// Unterdrücken aller Nachrichten einschalten
$filter->suppress(true);
// Dieser Eintrag wird unterdrückt
$logger->info('Diese Meldung kommt nicht an');
} catch (Zend_Log_Exception $e)
{
die ($e->getMessage());
}
Listing 4.20 Filtern von Log-Einträgen
190
e-bol.net
Schreiben von Logs mit Zend_Log | 4-5
Der zweite Filter, der üblicherweise sehr praktisch ist, lautet Zend_Log_Fi 1 ter_
Pri ori ty. Hierbei werden nur Einträge übernommen, welche einen bestimmten
Log-Level übersteigen. Das heißt, Sie können beispielsweise dafür sorgen, dass
nur Einträge mit einem Level übernommen werden, der »wichtiger« ist als INFO.
Der Konstruktor bekommt hierbei den Log-Level übergeben, dem ein Eintrag
mindestens entsprechen muss, damit er übernommen wird. Um also die INFO-
und DEBUG-Nachrichten zu unterdrücken, könnte man dem Kontruktor die Kon-
stante Zend_Log: :WARN übergeben. Die Konstanten entsprechen den Kurznamen
der Level aus Tabelle 4.2.
// Nur Nachrichten deren Level größer oder gleich Warning ist
Sfilter = new Zend_Log_Fi1ter_Priority(Zend_Log::WARN);
// Filter hinzufügen
$logger->addFilter($filter);
// Diese Meldung kommt ins Log...
$1ogger->warn('Diese Meldung kommt an');
// ...diese nicht
$1ogger->info('Diese Meldung kommt nicht an’);
Bitte beachten Sie, dass ein Level, den Sie selbst hinzugefügt haben, diesem Filter
schnell zum Opfer fallen kann. Er besitzt immer den niedrigsten Level. Der Prio-
rity-Filter muss übrigens nicht explizit inkludiert werden. Das macht die Log-
Klasse selbst.
In so einem Fall kann der letzte Filter Ihnen aber eventuell weiterhelfen. Er kann
Nachrichten mithilfe eines regulären Ausdrucks bewerten und in Abhängigkeit
davon passieren lassen oder verwerfen. Beim Ableiten des Objekts der Filter-
Klasse Zend_Log_Fi 1 ter_Message wird der reguläre Ausdruck direkt an den Kon-
struktor übergeben. Sobald Sie den Filter zugewiesen haben, wird die Nachricht
jedes Log-Eintrags mit dem regulären Ausdruck verglichen. Nur eine Nachricht,
auf die der Ausdruck zutrifft, wird ins Log übernommen.
Sie können übrigens auch mehrere Filter kombinieren, indem Sie mehrfach die
Methode addFi 1 ter() aufrufen und jeweils einen Filter übergeben.
4.5.2 Logfile-Einträge formatieren
Neben der Möglichkeit, Daten zu filtern, können Sie Log-Einträge auch umfor-
matieren, was natürlich nur dann sinnvoll ist, wenn Sie eine Log-Datei schreiben.
Bei der Nutzung einer Datenbank werden die Daten erst beim Auslesen aus der
Datenbank formatiert.
191
e-bol.net
4 | Infrastruktur-Klassen
Ein gewöhnlicher »Standardeintrag« in einer Log-Datei ist wie folgt aufgebaut:
2007-09-13T20:23:02+02:00 INFO (6): Hallo Welt ;-)
Möchten Sie die Daten aber beispielsweise im CSV-Format oder vielleicht in
einem XML-Format erzeugen, so müssen Sie diese umformatieren.
Um Daten zu formatieren, benötigen Sie einen Formatter. Zurzeit kennt das Fra-
mework zwei Klassen, die diesen Zweck erfüllen: Zend_Log_Formatter_Simple
und Zend_Log_Formatter_Xml. Der erste dient dazu, Textdateien zu formatieren.
Der zweite unterstützt Sie dabei, Daten im XML-Formt auszugeben.
In beiden Fällen muss zuerst ein Objekt der entsprechenden Klasse abgeleitet
werden, das dann mithilfe von setFormatterl) an das Writer-Objekt übergeben
wird.
Der Simple-Formatter bekommt einen Format-String übergeben, welcher defi-
niert, wie ein Eintrag aussehen soll. In diesem String können Sie Platzhalter
benutzen, um festzulegen, an welcher Stelle welche Information ausgegeben
werden soll. Die Namen der Platzhalter entsprechen denen aus Tabelle 4.3,
wobei Sie bitte vor und hinter dem Platzhalter ein Prozentzeichen (%) ergänzen.
Alle anderen Zeichen, die Sie in dem Format-String nutzen, werden direkt in die
Ausgabe übernommen. Somit können Sie also beispielsweise auch ein Semikolon
(;) nutzen, falls Sie eine CSV-Datei erzeugen möchten:
requi re_once('Zend/Log.php');
requi re_once('Zend/Log/Writer/Stream.php');
requi re_once('Zend/Log/Formatter/Simple.php’);
try {
// Neuen Writer ableiten
$writer = new Zend_Log_Writer_Stream(11ogfi1e');
// Format definieren
$format = '%timestamp%;%priorityName%;'
.'(%priority%);%message%' . PHP_EOL;
// Formatter-Objekt ableiten
$formatter = new Zend_Log_Formatter_Simple($format);
// Formatter an den Writer übergeben
$wri ter->setFormatter(tformatter);
$logger = new Zend_Log($writer);
$logger->info('Diese Datei ist im CSV-Format');
} catch (Zend_Log_Exception $e)
192
e-bol.net
Schreiben von Logs mit Zend_Log | 4-5
die ($e->getMessage()):
}
Listing 4.21 Schreiben in eine CSV-Datei
Das Script aus Listing 4.21 erzeugt Einträge in diesem Format:
2007-09-14T12:11:05+02:00:INFO:(6);Diese Datei ist im CSV-Format
Auch die Verwendung des XML-Formatters ist recht unproblematisch. Allerdings
sollte an dieser Stelle erwähnt werden, dass der Formatter von sich aus nicht in
der Lage ist, valide XML-Dateien zu erzeugen. Das Problem besteht in dem Root-
Element, das der Formatter immer vom Ende der Datei entfernen und dann wie-
der anfügen müsste. Möglich wäre das natürlich, aber leider auch sehr Perfor-
mance intensiv. Daher hat man auf die Nutzung eines Root-Elements verzichtet.
Nur jeder Eintrag selbst ist von einem »Root-Element« eingeschlossen. Möchten
Sie die Datei also mit DOM oder SimpleXML verarbeiten, müssen Sie beim Aus-
lesen manuell ein Root-Element ergänzen.
Der XML-Formatter benötigt keine Parameter. Sie können daher einfach ein ent-
sprechendes Objekt an den Writer übergeben, und die einzelnen Elemente erhal-
ten daraufhin die Bezeichner aus Tabelle 4.3 als Namen zugewiesen.
<1ogEntry>
<timestamp>2007-09-14T12:23:24+02:00</timestamp>
<message>Eintrag im XML-Format</message>
<pri ori ty>6</pri ori ty>
<pri ori tyName>INFO</pri ori tyName>
</logEntry>
Allerdings können Sie dem Konstruktor des Formatters auch eigene Vorgaben für
die Formatierung der XML-Daten übergeben. Der erste Parameter ist dabei ein
String, der den Namen des Root-Elements definiert. Danach folgt ein Mapping-
Array. Hierbei wird der Tag-Name, den Sie vergeben wollen, als Schlüssel
genutzt. Der Wert stellt dann den internen Namen dar. Mit der Deklaration
Smapping = arrayl'prio' => ’priority’,
’prio_name' => ’priorityName',
’meldung’ => ’message',
’zei tpunkt'=>'timestamp’
):
Sformatter = new Zend_Log_Formatter_Xml('EINTRAG', $mapping);
$wri ter->setFormatter($ Formatter);
193
e-bol.net
4 | Infrastruktur-Klassen
würden also folgende Einträge erzeugt:
<EINTRAG>
<pri o>6</pri o>
<pri o_name>INFO</pri o_name>
<meldung>Diese ist im CSV-Form at</meldung>
<zei tpunkt>2007-09-14T12:54:01+02:00</zei tpunkt>
</EINTRAG>
4.5.3 Eigene Einträge definieren
Alle bisher vorgestellten Varianten haben den gleichen Informationsgehalt gespei-
chert. Hier und da kann es aber hilfreich sein, mehr Informationen zu speichern.
Auch das ist möglich. Und zwar können Sie neue »Items« definieren, also Einträge,
die dann mit ins Log geschrieben werden. Ein Item muss immer mit setEvent-
Item() beim Logger angemeldet werden. Die Methode bekommt als ersten Para-
meter den »internen Namen« übergeben und als zweiten den Wert, der gespei-
chert werden soll. Zurzeit ist dieser Wert bei jedem Eintrag konstant; es ist
momentan also nicht möglich, eine Funktion anzugeben, die bei jedem Eintrag
aufgerufen wird. Dennoch kann die Funktionalität sehr hilfreich sein, wenn Sie
zum Beispiel einen Benutzernamen, den Namen des Datenbankservers oder Ähn-
liches speichern wollen. Einen neuen Eintrag legen Sie beispielsweise wie folgt an:
$logger->setEventItem(1userid' , $_SESSION['userid'1):
Wichtig ist aber, dass die Standard-Writer den Eintrag noch nicht speichern, da
sie das Item nicht kennen. Bei dem Stream-Writer benutzen Sie bitte einen For-
matter, der das neue Item inkludiert. Bei dem Datenbank-Writer müssten Sie das
Item mit in das Mapping-Array aufnehmen.
4.6 Konfigurationsverwaltung Zend_Config
Sicher kennen Sie das folgende Problem auch. Sie erstellen eine größere Applika-
tion, die konfiguriert werden muss. Um zu verhindern, dass Daten im Quelltext
geändert werden müssen, lagern Sie die Konfiguration in eine andere Datei aus.
Oft findet man hierbei die Vorgehensweise, dass die Werte direkt an PHP-Vari-
ablen übergeben werden. Das ist aber häufig nicht gewünscht. In diesem Fall
haben Sie die Möglichkeit, mit einer INI-Datei zu arbeiten oder, was sich gerade
in komplizierten Fällen anbietet, XML-Code zu nutzen.
Um die Konfigurationsdaten auf möglichst einfachem Weg zur Verfügung zu stel-
len, bietet sich Zend_Config an. Das Paket unterstützt sowohl die Arbeit mit
einem einfachen Array als auch die Nutzung von INI- und XML-Dateien.
194
e-bol.net
Konfigurationsverwaltung Zend_Config | 4-6
4.6.1 Nutzung von Konfigurations-Arrays
Die Daten, die in einer solchen Konfigurationsdatei enthalten sind, stellt die
Klasse dann als Eigenschaften des Objekts, das Sie ableiten, zur Verfügung. Bevor
ich mich noch länger in der Theorie verliere, möchte ich Ihnen an dem folgenden
kleinen Beispiel zeigen, wie die Nutzung von Zend_Conf i g auf Basis eines Arrays
aussieht:
require_once 'Zend/Config.php';
// Konfigurationsdaten
$conf_daten = array (
'Server' => '127.0.0.1',
’ user' => 'root',
’password' => '',
’tabelle’ =>
array ('name' => 'test.userdaten',
'spaltel' => ’benutzer',
'spalte2' => ’passwort'
)
):
try {
// Ableiten des Objekts und Übergabe der Daten
tconfig = new Zend_Config ($conf_daten);
//Nutzung von Daten der ersten Ebene
$db = mysql_connect($config->server,
$config->user,
$config->password);
// Zugriff auf Daten der zweiten Ebene
// $user und $password wurden vorher belegt
$sql = 'SELECT * FROM ’.$config->tabel1e->name.' WHERE '.
$config->tabel1e->spaltel . "= '$user' AND ".
$config->tabel1e->spalte2 . " = ’$password:
} catch (Zend_Config_Exception $e)
{
die ($e->getMessage()):
1
Listing 4.22 Nutzung eines Arrays mit Konfigurationsdaten
Wie Sie in diesem Beispiel sehen, können Sie das Array einfach an den Konstruk-
tor übergeben, der die Daten dann als Eigenschaften zur Verfügung stellt. In die-
195
e-bol.net
4 | Infrastruktur-Klassen
sem Beispiel wird ein zweidimensionales Array genutzt. Sie können allerdings
auch mehr Dimensionen nutzen.
Beim Verbindungsaufbau zur Datenbank werden die Daten der ersten Hierar-
chieebene genutzt, wohingegen beim Zugriff auf die Tabelle die Daten des ver-
schachtelten Arrays genutzt werden. In diesem Fall werden also die beiden rele-
vanten Schlüssel als Eigenschaftsnamen verwendet.
Vielleicht fragen Sie sich gerade, welchen Vorteil diese Vorgehensweise bringt,
da man doch auch direkt auf das Array zugreifen könnte. Nun, der erste Punkt ist,
dass die Eigenschaften des Objekts, also die Konfigurationsdaten, nur gelesen,
nicht aber geschrieben werden können. Somit können die Konfigurationsdaten
zur Laufzeit nicht verfälscht werden. Möchten Sie Änderungen zur Laufzeit zulas-
sen, so übergeben Sie als zweiten Parameter den booleschen Wert true. Der
zweite Vorteil ist, dass Sie den Code bei dieser Vorgehensweise jederzeit von
einem Array auf die Nutzung einer Datei umstellen können.
Sollten Sie nicht so direkt auf die Eigenschaften zugreifen können oder wollen,
so können Sie die Daten auch mit einer foreach-Schleife durchlaufen, da die
Klasse das SPL-Interface Iterator implementiert. Die Anzahl der Konfigurations-
werte in einer Ebene lässt sich jederzeit durch den Aufruf der Methode count()
auslesen.
Ein Problem bei der Nutzung von Konfigurationsdateien ist die Frage, ob alle
Werte gesetzt wurden, beziehungsweise wie Sie Default-Werte in das System
integrieren. Versuchen Sie, einen Wert auszulesen, der nicht gesetzt ist, so stellt
dies kein Problem dar. Die Klasse wirft keine Exception o.Ä., sondern liefert
Ihnen nul 1 zurück, wenn Sie auf die Eigenschaft zugreifen. So könnten Sie auf
dieser Basis schnell und einfach mithilfe einer i f-Abfrage einen Default-Wert
vorsehen. Allerdings geht das auch einfacher. Die Klasse kennt die Methode
get(), welche zwei Parameter übergeben bekommt. Der erste ist hierbei die
Eigenschaft bzw. der Konfigurationswert, den Sie auslesen wollen. Der zweite
Parameter ist der Default-Wert, den die Methode liefert, falls die gewünschte
Eigenschaft nicht mit einem Wert belegt wurde:
// Ableiten des Objekts und Übergabe der Daten
Sconfig = new Zend_Config ($conf_daten);
// $db_typ ist jetzt null
$db_typ = $config->db_typ:
if (null == $db_typ)
{
$db_typ = ’mysql’:
}
196
e-bol.net
Konfigurationsverwaltung Zend_Config | 4-6
// user ist nicht gesetzt
// => $user bekommt root zugewiesen
$user = $config->get('user'root');
// password ist gesetzt, allerdings null
// => $password wird null zugewiesen
Spassword = $config->get('password','geheim');
Listing 4.23 Nutzung von Default-Werten
4.6.2 INI-Dateien
INI-Dateien stellen quasi einen Industriestandard für Konfigurationsdateien dar
und werden oft genutzt. Der Aufbau einer solchen Datei ist einfach. Es gibt ver-
schiedene Abschnitte, die einen Namen haben, der jeweils von eckigen Klammen
eingeschlossen ist. Danach können Sie einzelnen Bezeichnern, deren Namen nur
aus den Buchstaben von A-Z in Groß- und Kleinschrift sowie dem Unterstrich
bestehen sollen, Werte zuweisen. Die Zuweisung erfolgt mithilfe eines Gleich-
heitszeichens.
Möchten Sie eine INI-Datei kommentieren, ist dies kein Problem. Nutzen Sie ein-
fach ein Semikolon am Zeilenanfang, um einen einzeiligen Kommentar einzulei-
ten.
Möchten Sie einer Konfigurationsvariablen einen Wert zuweisen, der Zeichen
enthält, die nicht alphanumerisch sind, wie beispielsweise Slashes oder Ähnli-
ches, so muss der Wert in Anführungszeichen gesetzt werden.
Des Weiteren müssen Sie auch beachten, dass die Werte yes und true in die Zahl
1 konvertiert werden. Möchten Sie den String yes bzw. true zuweisen, müssen
Sie die Texte in Anführungszeichen setzen. Die Werte null, no und fal se hinge-
gen werden in einen leeren String konvertiert.
In dem folgenden Beispiel wird die Datei config.ini genutzt, welche die folgen-
den Daten enthält:
[dbserver]
host = localhost
user = root
password = geheim
pwd_nutzen = yes
[pfade]
bilder = "/var/www/img/";
197
e-bol.net
4 | Infrastruktur-Klassen
[text]
; Hier bitte die lokalisierten Texte eingeben
willkommen = "Datenbankverbindung wurde aufgebaut"
Listing 4.24 Aufbau einer Konfigurationsdatei
Diese INI-Datei enthält drei Abschnitte, sogenannte Sections. Um die Datei zu
nutzen, übergeben Sie dem Konstruktor der Klasse Zend_Config_Ini den Pfad
und den Namen der Datei sowie als zweiten Parameter die Information, welche
Sektionen genutzt werden sollen. Hier können Sie einen String übergeben, wenn
nur einer der Abschnitte genutzt werden soll, ein Array mit mehreren Namen,
wenn mehr als ein Abschnitt genutzt werden soll, oder null, falls alle Sections
relevant sind.
requi re_once 'Zend/Confi g/Ini.php';
Sdatei = 'config.ini ’;
try {
// Ableiten des Objekts und Übergabe der Daten
Sconfig = new Zend_Config_Ini (Sdatei, null);
// pwd_nutzen wurde yes zugewiesen
// somit enthält die Eigenschaft den Wert 1
if (1 == $config->dbserver->pwd_nutzen)
{
Spassword = $config->dbserver->password;
el se
{
Spassword = ’':
Suser = $config->dbserver->user;
Shost = $config->dbserver->host;
Serg = mysql_connect(Shost, Suser, Spassword);
if (false != Serg)
{
echo $config->text->wil1 kommen;
} catch (Zend_Config_Exception Se)
{
die (Se->getMessage());
}
Listing 4.25 Nutzung einer INI-Datei
198
e-bol.net
Konfigurationsverwaltung Zend_Config | 4-6
Wie Sie sehen, ist der Zugriff auf die Konfiguratio ns werte identisch mit der
Array-Variante. Somit sind keine großen Besonderheiten zu beachten. Einen klei-
nen Fallstrick gibt es aber dennoch. Sollten Sie eine Konstante definiert haben,
deren Name identisch ist mit einem der zugewiesenen Werte, wird der Wert
durch die Konstante ersetzt. Wenn also in der Konfigurationsdatei die folgenden
Zeilen
[dbserver]
host = localhost
enthalten sind, und in dem PHP-Code, der die Konfiguration einliest, das fol-
gende Statement
define ('localhost','127.0.0.1');
steht, so resultiert das darin, dass $config->dbserver->host den Wert
127.0.0.1 enthält und nicht localhost. Dieses Verhalten kann möglicherweise
für etwas Verwirrung sorgen.
Wie eingangs erwähnt, können Sie dem Konstruktor den Namen einer Section
übergeben, die interpretiert werden soll. Leider hat das zur Folge, dass das Ver-
halten der Klasse sich ein wenig ändert. Wie Sie schon gesehen haben, wird der
Name einer Section als Eigenschaft bzw. als Hierarchieebene genutzt. Übergeben
Sie explizit, welche Abschnitte genutzt werden sollen, so entfällt diese Zwischen-
ebene. Würde dem Konstruktor ein Array mit den Namen der Sektionen über-
geben, müsste der Zugriff folgendermaßen erfolgen:
Ssektionen = array ('dbserver', 'pfade', 'text');
Sconfig = new Zend_Config_Ini (Sdatei, $sektionen);
$user = Sconfig->user;
$host = $config->host;
Listing 4.26 Nutzung von Sektionen
Vor diesem Hintergrund sollten Sie im Vorfeld genau planen, wie Sie Ihre Konfi-
guration aufbauen wollen. Möchten Sie die Namen der Sektionen übergeben,
aber trotzdem etwas mehr Struktur in Ihre Konfiguration bringen, so haben Sie
die Möglichkeit, die Bezeichner zu verschachteln. Hierbei trennen Sie einfach die
verschiedenen Namensteile mithilfe eines Delimiters (standardmäßig ein Punkt)
ab. Wenn Sie in einer Section, die Sie explizit einlesen, die Zeile
db.name = local host
angeben, können Sie im PHP-Code auf $config->db->name zugreifen. Möchten
einen anderen Delimiter als den Punkt nutzen, ist dies auch kein Problem. Sie
können dies mithilfe des dritten Parameters des Konstruktors beeinflussen. An
199
e-bol.net
4 | Infrastruktur-Klassen
dieser Stelle können Sie ein Array übergeben, welches die Schlüssel nestSepa ra-
tor und al 1owModifications unterstützt. Mit dem ersten übergeben Sie ein
alternatives Trennzeichen, wohingegen der zweite einen booleschen Wert unter-
stützt, der festlegt, ob die eingelesenen Konfigurationswerte verändert werden
dürfen. Dieses Verhalten können Sie übrigens auch dadurch steuern, dass Sie als
dritten Parameter lediglich einen booleschen Wert übergeben.
Ein interessantes Feature ist, dass Zend_Confi g_Ini bestimmte Konfigurations-
abschnitte mit anderen überschreiben kann. Damit haben Sie die spannende
Möglichkeit, einen Teilbereich schnell und einfach durch einen anderen zu erset-
zen. Somit können Sie beispielsweise völlig unproblematisch zwischen zwei
Datenbankservern umschalten. In der folgenden INI-Datei finden Sie zwei Sec-
tions. Die erste enthält die Daten der Produktivdatenbank, die zweite die Daten
des Entwicklungsservers:
[dbserver]
host = localhost
db = umsaetze
user = root
password = geheim
[entwicklung:dbserver]
db = test
password = totalgeheim
Wird nun beim Ableiten des Objekts der String entwicklung als Section-Name
angegeben, so werden die Abschnitte mit dem Präfix entwicklung eingelesen.
Der eigentliche Section-Name, der nach einem Doppelpunkt folgt, sorgt dafür,
dass auch andere Abschnitte mit diesem Namen eingelesen werden. Konfigurati-
onswerte, die doppelt vorhanden sind, werden allerdings durch die explizit ange-
gebene Sektion überschrieben:
// Neues Objekt fuer die Entwicklung
Sconfig = new Zend_Config_Ini (Sdatei, 'entwicklung');
echo $config->host: //Gibt localhost aus
echo $config->db; // Gibt test aus
4.6.3 XML-Dateien
Neben den schon erwähnten INI-Dateien können Sie auch mit XML-Dateien
arbeiten. Auch wenn der Aufwand, eine XML-Datei zu erstellen, etwas größer ist,
so bietet das System doch auch ein wenig mehr Sicherheit und Flexibilität. Soll-
ten Sie noch nicht sicher im Umgang mit XML sein, würde ich Ihnen empfehlen,
200
e-bol.net
Konfigurationsverwaltung Zend_Config | 4-6
dass Sie erst ein XML-Tutorial lesen, bevor Sie diese Technik bei Konfigurations-
dateien einsetzen.
Die Arbeit mit XML-Dateien ist weitgehend identisch mit der Nutzung von INI-
Dateien, wobei hier spezielle Ausdrücke wie yes, no, true und false natürlich
nicht konvertiert werden.
Wichtig ist allerdings die Information, dass die Klasse Zend_Config_Xml im Hin-
tergrund auf SimpleXML aus PHP zugreift. Standardmäßig ist diese Erweiterung
bei einer PHP 5-Installation vorhanden. Sollte das aber einmal nicht der Fall sein,
so funktioniert Zend_Conf1g_Xml nicht.
Wie auch schon bei Zend_Confi g_I ni geben Sie als ersten Parameter den Namen
der Datei mit den Konfigurationsdaten an. Der zweite Parameter, der auch in die-
sem Fall obligatorisch ist, gibt wieder an, welche Sektionen der Konfiguration
interpretiert werden sollen. Die Namen der Sektionen können Sie auch hier als
String oder als Array angeben, wobei die Angabe von nul 1 dafür sorgt, dass alle
Abschnitte genutzt werden. Bei einer XML-Datei stellt sich dabei die Frage, was
eine Sektion ist. Zend_Config_Xml geht hierbei davon aus, dass es ein Root-Ele-
ment gibt. Die darauffolgenden Kind-Elemente fassen die Sektionen zusammen,
die dann wiederum diejenigen Elemente enthalten, welche die eigentlichen
Daten enthalten:
<root>
<sekti onl>
< ei genschaft>wert</ei genschaft>
< ei genschaftl>wert</ei genschaft>
</se kt ionl>
<sekti on2>
< !-- weitere Daten -->
</se kt ion2>
</root>
Listing 4.27 Aufbau einer XML-Konfigurationsdatei
Der Konstruktor akzeptiert also nur Namen von Elementen der ersten Ebene (in
diesem Fall sekti onl und sekti on2) als Abschnittsnamen. Auch hier gilt wieder,
dass Sie beim Zugriff auf die Eigenschaften die Namen der Sektionen angeben
müssen, falls Sie als zweiten Parameter null angegeben haben. Sollten Sie den
oder die Namen der Sektion(en) explizit beim Ableiten des Objekts angegeben
haben, so entfällt dieser beim Zugriff auf die Eigenschaften. Bezogen auf das
obige Beispiel müssten Sie also, falls Sie das Objekt wie folgt
$obj = new Zend_Config_Xml(’datei.xml', ’sektionl');
201
e-bol.net
4 | Infrastruktur-Klassen
ableiten, so auf die Eigenschaften zugreifen:
echo $obj->eigenschaft:
Sollten Sie das Objekt allerdings in dieser Form
$obj = new Zend_Config_Xml(’datei.xml', null):
abgeleitet haben, würde der korrekte Zugriff wie folgt aussehen:
echo $obj->sektionl->eigenschaft:
Möchten Sie die Daten noch weiter verschachteln, ist dies kein Problem. Zwi-
schen Sektions- und Daten-Tags können Sie noch Zwischenebenen einfügen.
In dem folgenden Beispiel wird die nachfolgende XML-Datei zugrunde gelegt:
<?xml version="l.0" encoding="UTF-8"?>
<confi g>
<!-- Hier kommt die Konfiguration -->
<dbserver>
<host>localhost</host>
<user>root</user>
<password></password>
</dbserver>
<entwicklung extends='dbserver’>
<host>192.168.0.2</host>
</entwicklung>
</confi g>
Sie sehen hier, dass das Element entwicklung noch um das Attribut extends =
'dbserver' ergänzt wurde. Hiermit ist es (wie auch schon bei den INI-Dateien)
möglich, bestimmte Elemente einer anderen Sektion zu überschreiben.
requi re_once 'Zend/Confi g/Xml.php';
Sdatei = 'config.xml’;
try {
// Ableiten des Objekts und Übergabe der Daten
// Sektion entwicklung wird geladend und lädt
// automatisch dbserver nach.
tconfig = new Zend_Config_Xml ($datei, 'entwicklung');
$host = $config->host: // ist 192.168.0.2
tuser = $config->user;
tpassword = $config->password;
$db = mysql_connect($host, $user, $password):
} catch (Zend_Config_Exception $e)
(
202
e-bol.net
Shell-Programmierung mit Zend_Console_Getopt | 4-7
die ($e->getMessage()):
I
Listing 4.28 Einlesen einer XML-Datei
Auch hier haben Sie wieder die Möglichkeit, zwischen einer oder mehreren Kon-
figurationen umzuschalten, indem Sie den Namen einer anderen Sektion überge-
ben.
4.7 Shell-Programmierung mit Zend_Console_Getopt
Sicher kennen Sie auch das Problem, dass es irgendwelche Befehlsketten auf dem
Computer gibt, die Sie immer und immer wieder eintippen müssen. Auch wenn
dies auf dem heimischen Rechner nicht so oft passiert, so ist das auf Servern doch
ein ganz alltägliches Übel.
An dieser Stelle greift der geübte Administrator schnell zu einem Bash- oder Perl-
Script. Sollte es Ihnen aber gehen wie mir und »sprechen« Sie besser PHP als eine
der anderen Sprachen, so fällt es Ihnen sicher auch leichter, ein solches Pro-
gramm mit PHP zu erstellen. Sollten Sie sich noch nie mit dieser Thematik
beschäftigt haben, empfehle ich Ihnen, einen Blick auf das entsprechende Kapitel
des PHP-Manuals zu werfen. Die entsprechende Seite finden Sie unter
http://de.php.net/manual/de/features.commandline.php.
Eines der Probleme, mit dem man in diesem Zusammenhang oft zu tun hat, ist
die Übergabe von Parametern an das Script. Zwar stellt PHP in der Variablen
$_SERVER[' argv' ] alle Werte zur Verfügung, die in der Kommandozeile an das
Script übergeben wurden, aber der Aufwand, diese Daten auszuwerten, ist recht
hoch. Um diesen Vorgang zu vereinfachen ist die Klasse Zend_Console_Getopt
entwickelt worden.
Um mit Zend_Consol e_Getopt zu arbeiten, sollten Sie sich zunächst mit den hier
genutzten Begriffen anhand der folgenden Zeile vertraut machen:
ssh -1 root 192.168.0.1
Der Befehl ssh, mit dem Sie eine Verbindung zu einem anderen Server aufbauen
können, erhält hier verschiedene Werte über die Kommandozeile übergeben.
Die einzelnen Bestandteile werden dabei mit Leerzeichen getrennt. Bei der ers-
ten Information, dem -1 root, handelt es sich um eine Option. Diese Option
setzt sich aus zwei Teilen zusammen: dem Flag -1 und dem Parameter root. Hier
wird dem Befehl ssh ein String (root) übergeben, wobei das Flag (-1) ihn darü-
ber informiert, was der String bedeutet.
203
e-bol.net
4 | Infrastruktur-Klassen
Bei der zweiten Information, der Angabe 192.168.0.1, handelt es sich um ein
Argument. Argumente sind eigenständige Informationen, die ohne ein vorange-
stelltes Minuszeichen genutzt werden. Welche Information von einem Argument
transportiert wird, erkennt ein Befehl üblicherweise daran, an welcher Stelle in
der Reihenfolge das Argument übergeben wurde.
Der letzte Begriff, den Sie kennen sollten, ist Cluster. Bei einem Cluster handelt
es sich um mehrere Flags, die zusammengezogen und nur mit einem Minuszei-
chen eingeleitet werden. Wenn Sie beispielsweise anstelle von 1 s - a -1 - h also
somit nur 1 s -al h eintippen. In dem Fall handelt es sich bei al h um ein Cluster
von Flags.
4.7.1 Optionen, Flags und Parameter
So viel zur Theorie. Lassen Sie uns nun in die Praxis einsteigen. Der einfachste
Weg, der Klasse mitzuteilen, welche Flags Sie erwarten, besteht darin, dem Kon-
struktor eine Liste mit Flags als Parameter zu übergeben. Diese Flags können Sie
dabei einfach als String übergeben.
#! /usr/bi n/php
< ? php
requi re_once 'Zend/Console/Getopt.php';
Sopts = new Zend_Console_Getopt('up');
// Hier werden die Parameter verarbeitet....
?>
Listing 4.29 Übergabe von Flags
In diesem Beispiel akzeptiert das Script die beiden Flags u und p. Sie können
diese also optional beim Aufruf des Scripts angeben. In dieser Variante können
Sie allerdings keine Werte übergeben. Möchten Sie ein Flag deklarieren, mit dem
eine Option übergeben werden kann, so setzen Sie einen Doppelpunkt hinter
den Buchstaben des Flags. In diesem Fall würden Sie also " u: p:" als Parameter
übergeben. Um die Daten auszulesen, können Sie auf die Eigenschaften u und p
des Objekts Sopts zugreifen, oder Sie lesen die Werte mit der Methode getOp-
tion() aus. Diese bekommt den Namen des Flags übergeben:
#! /usr/bi n/php
< ? php
requi re_once 'Zend/Console/Getopt.php';
Sopts = new Zend_Console_Getopt(’u:p:qr');
echo "Wert von u: ".$opts->u."\n";
echo "Wert von p: ".Sopts->getOption(*p’)."\n";
echo "Wert von q: ".Sopts->getOption('q’). "\n";
204
e-bol.net
Shell-Programmierung mit Zend_Console_Getopt | 4-7
echo "Wert von r: ".$opts->getOption('r'). "\n";
?>
Rufen Sie dieses Script also über die Kommandozeile mit Skript.php -u root -p
geheim -q auf, erhalten Sie die folgende Ausgabe:
Wert von u: root
Wert von p: geheim
Wert von q: 1
Wert von r:
Hier werden also q und r wirklich als reine Flags betrachtet. Selbst wenn Sie
einen Wert anhängen, wird dieser ignoriert. Für Flags, die beim Aufruf nicht
übergeben wurden, wie das Flag r in diesem Beispiel, liefert die Klasse null
zurück.
Wichtig ist, dass ein Flag, bei dem Sie einen Doppelpunkt angefügt haben, stets
einen Parameter haben muss. Ist der Parameter nicht vorhanden, wirft die Klasse
eine Exception. Sie sollten also immer auf ein korrektes Exception Handling ach-
ten.
Mit diesen Möglichkeiten können Sie sicher schon einiges erreichen; aber die
Klasse kann noch mehr. Viele Befehle kennen die Möglichkeit, anstelle von ein-
fachen Flags auch eine Langschreibweise zu benutzen. So können Sie beispiels-
weise bei dem Befehl grep -c oder - -count angeben, um den Befehl zu veranlas-
sen, Treffer zu zählen. Die längere Schreibweise ist oft einfacher zu erlernen.
Ebenso ist bei langen Befehlszeilen besser zu erkennen, was ein Befehl machen
soll. Eine solche Deklaration könnte so aussehen:
requi re_once ’ Zend/Console/Getopt.php';
try {
// Objekt ableiten
$opts = new Zend_Console_Getopt(
array (
’user|u=w' => 'Username',
’password|p=s' => 'Passwort',
’quit|q'=>'Verbindung nach Befehl beenden'));
// Ausgabe der übergebenen Werte
echo "Wert von u: ".$opts->u. "\n";
echo "Wert von p: ".$opts->getOption('p’)."\n";
echo "Wert von q: ".$opts->getOption('q’)."\n";
205
e-bol.net
4 | Infrastruktur-Klassen
catch (Zend_Console_Getopt_Exception $e)
{
echo $e->getUsageMessage():
}
Listing 4.30 Nutzung der Langschreibweise
In diesem Fall wurde dem Konstruktor ein Array übergeben. Jeder Schlüssel
beinhaltet die Lang- und die Kurzschreibweise des entsprechenden Flags, die
durch ein Pipe-Symbol (|) getrennt werden. Nach der Kurzschreibweise des Flags
folgt in zwei Fällen noch die Information, dass ein Wert erwartet wird und wel-
che Art von Wert akzeptiert wird. Ein Doppelpunkt an dieser Stelle ist unzuläs-
sig. Das =w steht dafür, dass ein »Wort«, also ein String ohne Leerzeichen, erwar-
tet wird. Die Angabe =s bedeutet, dass das Flag einen String erwartet. Hier
können Sie ein Wort übergeben, aber auch einen String, der Leerzeichen enthält,
sofern Sie ihn in Anführungszeichen einfassen. Das würde bei =w in einer Excep-
tion resultieren. Außerdem können Sie auch noch =i für einen Integer-Wert
angeben. Ein Platzhalter für einen Float-Wert ist nicht vorgesehen, wobei die
Notwendigkeit im Bereich der Shell-Programmierung wohl eher gering ist.
Diese Platzhalter sorgen stets dafür, dass ein Wert obligatorisch ist, also angege-
ben werden muss. Das bezieht sich allerdings nur auf den Wert. Geben Sie das
Flag nicht an, ist das kein Problem. Wird das Flag aber angegeben, so müssen Sie
auch zwingend einen Parameter angeben. Soll ein Wert optional sein, so ersetzen
Sie das Gleichheitszeichen einfach durch ein Minus-Zeichen.
Bei den Werten, die in dem Array genutzt wurden, handelt es sich um eine Erläu-
terung, die kurz beschreibt, wozu das Flag benötigt wird oder welchen Wert es
erwartet. Diese Erläuterungen können Sie als Hilfetexte nutzen, was in Listing
4.30 im Exception Handling geschieht. Der Catch-Block verarbeitet eine Excep-
tion vom Typ Zend_Consol e_Getopt_Excepti on, welche die Methode getUser-
Messaget) kennt. Sie liefert den Hilfetext zurück, sodass Sie ihn ausgeben kön-
nen. In diesem Beispiel würde die Ausgabe folgendermaßen aussehen:
Usage: Skript.php [ options 1
--user|-u <word> Username
--password|-p <string> Passwort
--quit|-q Verbindung nach Befehl beenden
Sie können an dieser Stelle auch auf die Methode getMessaget) zurückgreifen.
Diese liefert Ihnen eine Fehlermeldung, welches Flag nicht korrekt genutzt
wurde.
Ich möchte nicht unerwähnt lassen, dass Sie die Kurz- und die Langschreibweisen
bei der Festlegung der Flags auch kombinieren können. Die Kurzschreibweise
206
e-bol.net
Shell-Programmierung mit Zend_Console_Getopt | 4-7
bleibt in dem Fall unberührt, die Flags werden also direkt an den Konstruktor
übergeben. Die Flags in der Langschreibweise werden dann als Array an die
Methode addRules() übergeben und auf diesem Weg hinzugefügt.
Damit die Nutzung der Flags in Kurzschreibweise auch in der Hilfe auftaucht,
können Sie entsprechende Erläuterungen mithilfe von setHel p() hinzufügen:
Sopts = new Zend_Console_Getopt('u:'):
Sopts->setHelp(array( ’u' => ’Username'):
4.7.2 Nutzung von Argumenten
Um Argumente auszulesen, können Sie auf die Methode getRemainingArgs()
zurückgreifen. Sie liefert Ihnen ein Array mit allen Kommandozeilenwerten
zurück, die keinem Flag zugeordnet werden konnten:
requi re_once ’Zend/Console/Getopt.php1;
try {
Sopts = new Zend_Console_Getopt('u:1:'):
Sopts->setHelp(array (’u' => 'Username’,
’p' => 'Passwort')):
// Alles auslesen was nicht zu den Flags gehört
Sargs = Sopts->getRemainingArgs():
if (true —== empty(SargsEO]))
{
// Exception bekommt zuerst die normale Message und dann
// die Usage-Message übergeben
throw new Zend_Console_Getopt_Exception(
'Geben Sie einen Host an’,
'Nutzung: skript.php -u <User> -p <Passwort> Host'):
Sserver = SargsEO];
// Weitere Verarbeitung
1
catch (Zend_Console_Getopt_Exception Se)
{
echo Se->getUsageMessage(). "\n";
echo Se->getMessage(). "\n";
1
Listing 4.31 Auslesen von Argumenten
207
e-bol.net
4 | Infrastruktur-Klassen
Zend_Consol e_Getopt ist eine kleine, aber sehr praktische Klasse, wie ich finde.
Über die hier genannten Möglichkeiten hinaus gibt es noch einige weitere. So
können Sie die übergebenen Werte beispielsweise auch im XML- oder im JSON-
Format auslesen.
208
e-bol.net
Prinzipien sind der jämmerlichste Grund,
den es gibt, um sich unbeliebt zu machen.
- George Bernhard Shaw
5 Webservices
5.1 Feeds mit Zend_Feed verarbeiten
Im Zeitalter der Blogs sind Feeds für viele Menschen eine wichtige Informations-
quelle. Bei Feeds handelt es sich um Informationen, die auf Basis von XML zur
Verfügung gestellt werden. Informationen heißt an dieser Stelle, dass es meist
Daten wie Blogeinträge, Teaser von Zeitungsartikeln oder Ähnliches sind. In den
meisten Fällen handelt es sich um eine Überschrift sowie einen kurzen Text, die
teilweise mit einem Bild ergänzt werden. Üblicherweise sind auch ein Link auf
den eigentlichen Artikel sowie ein Publikationsdatum enthalten. Feeds haben
gegenüber dem eigentlichen Blog den Vorteil, dass man sie gut in Feedreadern
zusammenfassen kann. Dabei handelt es sich um Programme oder Websites, die
verschiedene Feeds einlesen, aufbereiten und darstellen, sodass man sich schnell
einen Überblick über die neuen Einträge verschaffen kann.
Feeds werden in verschiedenen Formaten angeboten, wobei die Zend-Klassen
mit RSS- und Atom-Feeds umgehen können.
Bitte beachten Sie bei der Arbeit mit Feeds, dass Sie immer ein Exception Hand-
ling implementieren sollten. Bei der Arbeit mit solchen Informationsangeboten
kann es nämlich schnell passieren, dass Sie nicht auf eine Ressource zugreifen
können.
5.1.1 Feeds finden
In den meisten Fällen wird es sicher so sein, dass Sie die URL eines Feeds kennen
bzw. von einer Website übernehmen können. Allerdings gibt es auch oft die
Möglichkeit, die URLs der Feeds, die zur Verfügung stehen, automatisiert zu
übernehmen. Dazu müssen die URLs der Feeds mithilfe des Tags <l 1 nk> auf der
eigentlichen Webseite vermerkt sein. Zend_Feed ist in der Lage, diese Tags zu fin-
den und die dort enthaltenen URLs zu extrahieren.
209
e-bol.net
5 | Webservices
Die gefundenen Feed-URLs werden dann direkt eingelesen. Die Methode gibt
Ihnen ein Array mit Feed-Objekten zurück. Leider ist momentan keine Möglich-
keit vorgesehen, die URL des Feeds auszulesen.
Die Methode f i ndFeeds() ist statisch deklariert, sodass Sie nicht erst ein Objekt
ableiten müssen:
require_once ’Zend/Feed.php’;
try {
$feeds = Zend_Feed::findFeeds('http://www.mygadgetblog.de'):
1
catch (Exception $e)
{
die ($e->getMessage()):
1
if (0 === count($feeds))
{
echo "Es wurden keine Feeds gefunden":
1
el se
{
//Hier können die Feeds verarbeitet werden
1
Listing 5.1 Automatisches Finden von Feeds
5.1.2 Allgemeines zur Verarbeitung von Feeds
Unabhängig davon, mit welcher Art von Feed Sie es zu tun haben, gibt es zwei
Möglichkeiten, die Feeds einzulesen. Die erste Variante, die auch nachfolgend
genutzt wird, ist ein Objekt der entsprechenden Klasse abzuleiten und dem Kon-
struktor die URL des Feeds zu übergeben. Die zweite Variante ist, dass Sie die sta-
tische Klasse import() aus der Klasse Zend_Feed aufrufen und ihr die gewünschte
URL übergeben. Sie ermittelt dann selbstständig die Art des Feeds und liefert ein
Objekt der entsprechenden Klasse zurück. Auch wenn Importe) komfortabler
erscheint, stellt sich dabei unter Umständen doch das Problem, dass nicht ganz
klar ist, ob ein Atom- oder ein RSS-Feed eingelesen wird. Daher gebe ich in den
folgenden Beispielen dem expliziten Ableiten der Objekte den Vorzug.
Ist der Feed eingelesen, wird er mithilfe der PHP-DOM-Funktionen aufbereitet.
Um auf ein Element, also den Textinhalt eines XML-Tags, zuzugreifen, nutzen Sie
einfach den Namen des Tags als Methodennamen. Der Inhalt wird dann direkt
als String zurückgegeben:
$feed->title()
210
e-bol.net
Feeds mit Zend_Feed verarbeiten | 5-1
Ist das Element nicht vorhanden, gibt der Methodenaufruf null zurück.
Attribute von Tags lesen Sie aus, indem Sie den Namen des Attributs als Array-
Schlüssel an den Namen des Elements anhängen. In dem Fall wird der Name des
Arrays nicht als Methode, sondern nur als Array-Name genutzt. Wollen Sie den
Wert des href-Attributs eines Elements namens link auslesen, würde das so aus-
sehen:
$feed- >1i nk[’href']
Auch wenn Sie nur Atom- oder nur RSS-Daten verarbeiten wollen, sollten Sie
beide Abschnitte lesen, da die dortigen Informationen sich auf beide Fälle bezie-
hen.
5.1.3 Verarbeiten von RSS-Feeds
RSS ist das Format für Feeds, das sicher die größte Verbreitung hat. Auch wenn
RSS als veraltet gilt, so ist es auf absehbare Zeit nicht wegzudenken. Das Problem
ist, dass bei einem RSS-Feed, schon alleine aufgrund der Vielzahl der unter-
schiedlichen Unterformate, nicht ganz genau gesagt werden kann, welche Ele-
mente vorhanden sind und welche nicht. Daher kann ich an dieser Stelle nicht
dafür garantieren, dass alle Elemente, die in dem Beispiel genutzt werden, auch
später bei einem Feed verfügbar sind, den Sie einlesen wollen. Am einfachsten
und sichersten ist es, wenn Sie einen Blick auf die XML-Daten werfen, die der
Feed zur Verfügung stellt. Dann kann der Feed sich zwar später noch ändern,
aber Sie haben zunächst einmal einen guten Anhaltspunkt.
Die Verarbeitung eines RSS-Feeds könnte so umgesetzt werden:
< ? php
requi re_once 'Zend/Feed/Rss.php’;
header('Content-Type: text/html; charset=utf-8'):
try {
// Feed einlesen
$feed = new Zend_Feed_Rss(
'http://www.mygadgetblog.de/index.php/feed/');
I
catch (Exception $e)
{
die ($e->getMessage()):
}
// Titel des Blogs bzw. Feeds ausgeben
echo ’<p>Titel des Blogs: '.$feed->title():
211
e-bol.net
5 | Webservices
// Anzahl der Einträge ermitteln
echo ’<br>Anzahl der Einträge: '.$feed->count().'</p>';
echo ’<table>';
// Über die Beiträge iterieren
foreach ($feed as $item)
{
echo '<tr><td>';
if ($item->link() != null)
{
echo ’<a href="$item->link( ;
echo $item->title();
echo ’</a>’;
el se
{
echo $item->title();
echo '</td></tr>';
echo ' <trXtd>';
if ($item->description() != null)
{
echo $item->description();
el se
{
echo 'Keine Beschreibung vorhanden';
echo ’</tdX/tr>';
echo '<trXtd> </tdX/tr>’;
}
echo ’</table>';
Listing 5.2 Verarbeiten eines RSS-Feeds
Wie Sie in diesem Listing sehen, ist die Nutzung der Klasse denkbar einfach.
Nach dem Einlesen des Feeds stehen die Informationen, die unterhalb des Chan-
nel -Elements deklariert wurden, direkt zur Verfügung. Somit kann also beispiels-
weise der Titel des Feeds durch den Aufruf der Methode titlet) ausgelesen
werden. Die Methode count (), die die Anzahl der Einträge liefert, ist übrigens in
der Klasse definiert. Es handelt sich dabei nicht um ein Element aus dem Feed.
Um alle Einträge auszulesen, können Sie das Objekt direkt in einer foreach-
Schleife verwenden, da die Klasse das SPL-Interface Iterator implementiert. Bei
dem Objekt, das Sie bei jedem Durchlauf erhalten, handelt es sich um ein item-
Element aus den XML-Daten. Alle Kind-Elemente, die unterhalb von item dekla-
212
e-bol.net
Feeds mit Zend_Feed verarbeiten | 5-1
riert sind, stehen dann wieder als Methoden zur Verfügung. Bitte beachten Sie,
dass Sie auf jeden Fall auf die Methoden zugreifen sollten. Greifen Sie auf die
Eigenschaften zu, so würde das zwar auch funktionieren, solange im Feed ein
XML-Element mit dem entsprechenden Namen vorhanden ist. Ist dieses nicht
vorhanden, liefert der Zugriff auf die Eigenschaft ein leeres DOM-Element
zurück. Die Methode hingegen gibt null zurück, was in einer i f-Abfrage leichter
zu testen ist. Dabei sollten Sie beachten, dass die Methode nur dann null zurück-
gibt, wenn das Element nicht vorhanden ist. Ist das Element leer, gibt die
Methode einen leeren String zurück. In dem obigen Beispiel wird diese Eigen-
schaft genutzt.
In einem normalen Vergleich werden ein leerer String und null als gleich einge-
stuft. Die i f-Abfragen im Körper der Schleife testen somit, ob das Element, das
abgefragt werden soll, im Feed vorhanden bzw. möglicherweise leer war. Der
Titel eines Elements wird sicher immer vorhanden sein, weswegen ich an der
Stelle auf eine weitere Abfrage verzichtet habe. Die XML-Daten aus dem Feed,
die hier genutzt wurden, haben die nachfolgende Struktur:
<?xml version="l.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modul es/content/"
xmlns:wfw="http://wel1formedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.l/">
<channel>
<title>myGadgetßlog</title>
<1ink>http://www.mygadgetblog.de</li nk>
<1tem>
<title>LED am Einsatzwagen: so muss das :)</title>
<1 i nk>http://www.mygadgetblog.de/i ndex.php/1ed-am-
ei nsatzwagen-so-muss-das/</link>
<description>Und weil ja bald Weihnachten...</description>
</i tem>
<i tem>
<title>Fotos vom Philips Fizz sehr beiiebt</title>
<1 i nk>http://www.mygadgetblog.de/index.php/fotos-vom-philips-
fizz-sehr-beiiebt/</li nk>
<description>Offenbar habe ich das Foto .... </description>
</i tem>
<!-- weitere Items -->
</channel>
</rss>
213
e-bol.net
5 | Webservices
Diese XML-Daten sind stark gekürzt und sollen nur helfen, den PHP-Code besser
verständlich zu machen. In einem echten Feed sind natürlich auch noch Datums-
und Sprachinformationen und Ähnliches vorhanden.
Wie der fertig aufbereitete Feed im Browser dargestellt wird, sehen Sie in Abbil-
dung 5.1.
http://12 7.0.0.1/zf-buch/feed/l.php
Q.Q.00I© *^0.* Google pi~j rrJ
Titel des Blogs: myGadgetBlog
Anzahl der Einträge: 30
LED am Einsatzwagen: so muss das :)
Und weil ja bald Weihnachten ist, und da alles so festlich beleuchtet wird, hier mal ein Beispiel, wie
man ein Einsatzfahrzeug beleuchtet, dass es auch wirklich auffällt *schmacht* Social Bookmarks:
Fotos vom Philips Fizz sehr beliebt
Offenbar habe ich das Foto von meinem ersten Handy, dem Philips Fizz, gut getroffen. Kürzlich
wunderte ich mich über eine sprunghaft gestiegene Zahl von Zugriffen auf das Blog. Also habe ich
mir mal die Log-Files angeschaut und festgcstellt, dass 90 Prozent der Besucher von ein und der
selben Website zu mir kamen, also hab ich da [...]
Trust Spyker Fl
Wie vor ein paar Tagen geschrieben, hab ich ja auf der Wcihnachtstombola eine Drahtlose PC
Funktmaus “Trust Spyker Fl" gewonnen. Nun kam ein Kollege um die Ecke, er hätte auch so
eine Maus, ob den bei mir das Mausrad ginge;). Scheint ja ein besonderes Feature dieser Maus zu
sein, dass es nicht geht. Also hab [...]
Körpertyp Olivenöl?
Arbeiten beim Fernsehen nur noch V*lldeppen? Das wäre wieder so ein Eintrag unter der Rubrik
“Unglaublichkeiten und Unfassbares”. Marge Simpson sucht sich soeben eine Figur für ihren
Avatar in einem Online-Rollenspicl aus, und klickt auf Körpertyp “Olivenöl”
(Originalsynchronisation Pro7). Die meinen “Olivia Öl” - die Angebetete von Popeye dem
Seemann. Meine Güte, wie schlecht! Social Bookmarks:
Neue Maus: Trust Spyker Fl limited edition
Heule war Tombola in der Firma, wie immer hab ich Alkohol gewonnen, wenn’s denn wenigstens
mal Rotwein wäre aber gut, besser als die sonst übliche Salami Ein Kollege bekam eine Maus eine ”
Trust Spyker Fl ” in einer limitierten Edition mit Unterschrift von irgendsonem Formel 1 Piloten,
schickes Teil, aber irgendwie könnt [...]
CD ATA mit PHP 5.1 und SimplcXML verarbeiten
Weil ich kürzlich darüber gestolpert bin, dass ein XML-Dokument einen CDATA-Abschnitt
Abbildung 5.1 Ausgabe des eingelesenen Feeds
5.1.4 Verarbeiten von Atom-Feeds
Möchten Sie Daten aus einem Atom-Feed verarbeiten, ist das genau so einfach
wie die Nutzung von RSS. Lediglich die Elemente haben andere Namen. Die Ver-
arbeitung eines Atom-Feeds könnte beispielsweise so aussehen:
214
e-bol.net
Feeds mit Zend_Feed verarbeiten | 5-1
< ? php
requi re_once ’Zend/Feed/Atom.php';
header('Content-Type: text/html; charset=utf-8');
try {
// Feed einlesen
$feed = new Zend_Feed_Atom(
'http://www.adminblogger.de/blog/feed/atom/');
}
catch (Exception $e)
{
die ($e->getMessage()):
}
// Titel ausgeben
echo *<p>Titel: '.$feed->title();
echo ’ - ' .$feed->tagline():
// Anzahl der Einträge ermitteln
echo ’CbOAnzahl der Einträ ;ge: '.$feed->count().'</p>':
echo "<table>";
// Über die Beiträge iterieren
foreach ($feed as $item)
{
echo '<tr><td>':
if ($item->link() !== null &&
$item->link[’href'1 != ’’)
{
echo ’<a href="$item->link['href;
echo $item->title();
echo ’ </a> ’;
el se
{
echo $item->title();
echo '</td></tr>';
echo '<trXtd>':
echo 'Autor: '.$item->author->name();
echo ' </tdXtr>';
echo '<trXtd>':
if ($item->summary() != null)
{
echo $item->summary();
el se
215
e-bol.net
5 | Webservices
{
echo 'Keine Beschreibung vorhanden';
echo '</td></tr>';
echo '<tr><td> </tdX/tr>’;
}
echo ’</table>';
Listing 5.3 Einlesen eines Atom-Feeds
Wie Sie sehen, ist die grundsätzliche Struktur identisch mit der bei der Verarbei-
tung eines RSS-Feeds. Daher möchte ich auch nur einige Kleinigkeiten genauer
erläutern, wie beispielsweise die folgende i f-Abfrage:
if ($item->1ink() !== null &&
$item->l1nk[’href'] != ’’)
In Atom gibt es zwar ein Element namens link, aber die eigentliche Ziel-URL ist
nicht als Text enthalten, sondern als Wert des Attributs href. Das heißt, der erste
Teil der Abfrage prüft, ob das Element vorhanden ist. Wichtig ist hier, dass der
»Nicht-Identitätsoperator« genutzt wird, da die Methode auf jeden Fall einen
Leerstring zurückgibt, da link ein leeres Element ist. Mit dem zweiten Teil der
Abfrage wird sichergestellt, dass das Attribut href vorhanden ist und einen Wert
enthält. Wie am Anfang des Kapitels erwähnt, erfolgt der Zugriff auf Attribute
dadurch, dass der Name des Elements nicht als Methode, sondern als Eigenschaft
genutzt wird, die ein Array enthält. Der Schlüssel ist in diesem Fall der Name der
Eigenschaft, die ausgelesen werden soll. Sollte das Attribut nicht vorhanden sein,
liefert dieser Zugriff einen Leerstring zurück, generiert aber keine Notice oder
einen anderen Fehler.
Der nächste interessante Punkt ist der Zugriff auf den Namen des Autors. Hier
nutzt Atom zwei ineinander verschachtelte Elemente, nämlich author und name.
In diesem Beispiel erfolgt der Zugriff über:
echo 'Autor: '.$item->author->name();
Das äußere Element author wird als Eigenschaft und nicht als Methode genutzt.
Korrekterweise hätte man hier mit einer i f-Abfrage vorher testen müssen, ob das
Element vorhanden ist. Die 1 f-Abfrage müsste in dem Fall so aufgebaut sein:
if ($item->author() !== null &&
$item->author->name() !== null)
Mit dieser Abfrage wird also zunächst geprüft, ob das Element author vorhanden
ist. Mit der zweiten Abfrage wird dann sichergestellt, dass das verschachtelte Ele-
ment name vorhanden ist.
216
e-bol.net
Feeds mit Zend_Feed verarbeiten | 5-1
In diesem Fall wäre ein Zugriff auf author() ausreichend gewesen, da die Werte
aller Elemente, die sich unterhalb von author befinden, automatisch mit ausgele-
sen werden. Zum Vergleich hier noch einmal ein Ausschnitt aus den gekürzten
XML-Daten:
<?xml version="l.0" encoding="UTF-8"?>
<feed version="0.3" xmlns="http://pur!.org/atom/ns#" xmlns:dc="http:
//pur1.org/dc/elements/1.1/"
xml:1ang="en">
<ti tle>admi nblogger,de</ti tle>
<tagline>Geschichten aus dem Leben eines Linux-SysAdmins</tagline>
<entry>
<author>
<name>Marcel</name>
</author>
<title type="text/html” mode="escaped">
Licht an (Aber richtig)
</ti tle>
<link rel="alternate" type=”text/html"
href="http://www.admi nblogger.de/blog/2007/12/08/licht-an-
aber-richtig/"/>
<summary type="text/plain" mode=”escaped">
Anstatt zum Lichtabschalten aufzurufen, wie es Google...
</summary>
</entry>
<entry>
<author>
<name>Marcel</name>
</author>
<title type="text/html” mode="escaped">
Neues Licht fürs Auto</title>
<link rel=”alternate" type="text/htmr’
href="http://www.adminblogger.de/blog/2007/12/08/neues-licht-
fuers-auto-osram-ni ghtbreaker-seat/"/>
<summary type="text/plain" mode="escaped">
Für mein rollendes Gefä:hrt, ...
</summary>
</entry>
</feed>
Die Ausgabe des Scripts sehen Sie in Abbildung 5.2.
217
e-bol.net
5 | Webservices
O http://127.0.0.1/zf-buch/feed/2.php CD
uäöö http://127.0.0.1/zf-buch/feed/2.php - Q- Google
Titel: adminblogger.de - Geschichten aus dem Leben eines Linux-SysAdmins A
Anzahl der Einträge: 10
Licht an (Aber richtig)
Autor Marcel
Anstatt zum Lichtabschalten aufzurufen, wie es Google u.a. gerade tut, unterstütze ich lieber die Aktion Licht an. Die Hintergründe
fasst Heise ganz gut zusammen.
Neues Licht fürs Auto
Autor Marcel
Für mein rollendes Gefährt, was mich auch bei Dunkelheit sicher ans Ziel bringen soll, habe ich neue Halogenlampen gekauft.
Angesteckt durch einen Arbeitskollegen, der bereits mit Philips X-tremc Power Halogcnlampcn gute Erfahrungen gemacht hat,
habe ich mich für zwei OSRAM Nightbreakcr Halogcnlamper entschieden. Auf folgendem Bild sicht man inks die nach
Herstellerangaben bis zu 90% hellere und [...1
Der Gewinner der US-Präsidentschaftswahlen 2008 steht fest
Autor Marcel
Der Gewinner der US-Präsidcntschaftswahlcn 2008 steht bereits fest. So zumindest umschreibt die Gruppe des HashClash-Projekts
der Fakultät Mathematik und Informatik der technischen Universität Eindhoven das Experiment, bei der cs der Gruppe gelungen ist,
mit Hilfe einer Playstation 3 zwölf unterschiedliche PDF-Dokumente zu generieren, die den gleichen MD5-Hash bilden. Alle 12
PDF-Dokumente enthalten jeweils den Namen eines [...1
Session-IDs in veröffentlichten URLs sind evil
Autor Marcel
Das zeigt das aktuelle Beispiel beim Shopblogger Eine Unachtsamkeit seitens des Bloggers Björn Harste der auf einen Artikel in
seinem Shop vcrLnkcn wollte, aber vergaß, die Session-ID aus der URL zu entfernen. Jedem Besucher seines Blogs offenbarte er
damit unfreiwillig (s?)einen Account, den auch scheinbar sofort Leute nutzten, um Artikel aus seinem Shop zu bestellen: Einmal im
Shop [.„1
Operation Kingdom
Abbildung 5.2 Ausgabe eines Atom-Feeds
5.1.5 Generieren von Feeds
Das Schöne an Zend_Feed ist, dass Sie auf eine sehr einfache Art und Weise selbst
Feeds generieren können, ohne dass Sie sich um XML oder die Struktur des
Dokuments kümmern müssen.
Die Daten, die im Feed enthalten sein sollen, übergeben Sie in Form eines asso-
ziativen Arrays an die statische Methode importArray(). Als zweiten Parameter
teilen Sie ihr dann noch mit, welches Format Ihr Feed haben soll. Das heißt, Sie
können hier ’atom’ oder ’rss’ angeben, wobei ' atom’ der Default-Wert ist.
Das Format des Arrays ist leider zu komplex, um es hier komplett zu erläutern.
Daher werde ich nur die wichtigsten Elemente benutzen. Die komplette Struktur
können Sie der Dokumentation unter der Adresse http://framework.zend.com/
manual/de/zend.feed.importing.html entnehmen.
Dort finden Sie auch Informationen dazu, welche Array-Schlüssel genutzt wer-
den müssen und welche optional sind. Bitte beachten Sie, dass einige Elemente
bei einem Feed-Format benötigt werden und bei einem anderen nicht. Abhängig
vom Format werden einige Elemente sogar ignoriert.
218
e-bol.net
Feeds mit Zend_Feed verarbeiten | 5-1
In dem Array werden zunächst allgemeine Daten zum Feed deklariert, wie der
Titel, die URL und Ähnliches. Die Beiträge selbst finden sich in einem indizierten
Array, das unterhalb des Schlüssels entries zu finden ist. Jeder Beitrag muss
über einige erforderliche Einträge verfügen. Das sind primär die Schlüssel title,
link und descri pti on. Der erste deklariert den Titel des Beitrags, der zweite legt
fest, unter welcher URL er aufgerufen werden kann, und mit descri ption kön-
nen Sie einen kurzen Abriss des Inhalts geben.
Wenn Sie das Array an die Klasse übergeben haben, erhalten Sie ein Objekt einer
entsprechenden Zend_Feed-Klasse zurück. Rufen Sie sendO auf, werden die
Daten in XML umgewandelt, mit den notwendigen Headern versehen und direkt
an den Client gesendet. Möchten Sie die Daten lieber abspeichern oder ander-
weitig nutzen, rufen Sie stattdessen saveXml () auf. Mit dieser Methode erhalten
Sie die kompletten XML-Daten als String zurück.
require_once ’Zend/Feed.php’;
// Erster Eintrag
Seintragl = array(
'title' => 'Erster Eintrag',// Titel
'link' => '127.0.0.1/blog/1',//URL zum kompletten Beitrag
’description’ => 'Mein erster kleiner Eintrag zum Testen'
);
// Zweiter Eintrag
Seintrag2 = array(
'title' => 'Zend_Feed ist toll',
'link' => '127.0.0.1/blog/2',
'description’=> 'Ja, Zend_Feed ist wirklich kinderleicht'
);
$rss_daten =array(
'title' => 'Mein Blog', //Titel des Feeds
'link’ => ’127.0.0.1/blog’, // URL des Blogs
’charset' => 'UTF-8', // Zeichensatz
'entries' => array( // Array für die Beiträge
Sei ntrag2,
Sei ntragl
)
);
try
{
// RSS-Feed-Objekt ableiten
Sfeed = Zend_Feed::importArray(Srss_daten,'rss’);
}
219
e-bol.net
5 | Webservices
catch (Zend_Feed_Exception $e)
I
die ($e->getMessage());
I
// Daten an Client senden
$feed->send();
Die Ausgabe dieses Scripts sehen Sie in Abbildung 5.3
Abbildung 5.3 Selbst generierter Feed
5.2 Zugriff auf Amazon mit Zend_Service_Amazon
Amazon ist sicher einer der bekanntesten Online-Händler. Wie viele andere
große Internet-Plattformen bietet auch Amazon die Möglichkeit, mithilfe einer
API (Application Programmers Interface) auf das Online-Angebot zuzugreifen.
Damit haben Sie die Möglichkeit, Produkte von Amazon auf Ihrer eigenen Home-
page anzubieten und das Angebot in Ihr Layout zu integrieren.
Um Zugriff auf die Amazon-API zu haben, müssen Sie zunächst einen Account
anlegen. Diesen können Sie unter http://aws.amazon.com beantragen. AWS ist
übrigens die Abkürzung für Amazon Webservices. Unter AWS fasst der Anbieter
alle Schnittstellen zusammen, die er zur Verfügung stellt.
Nachdem Sie einen Account angelegt haben, erhalten Sie eine E-Mail. In dieser
finden Sie einen Link, unter dem Sie Ihre »Access Key ID« herunterladen können.
220
e-bol.net
Zugriff auf Amazon mit Zend_Service_Amazon | 5-2
Diesen Schlüssel benötigen Sie, damit Amazon Sie bei einem Zugriff auf die API
identifizieren kann. Er hat allerdings nichts mit anderen Keys zu tun, die Amazon
beispielsweise für Werbekostenprogramme nutzt. Er dient einzig und allein zur
Authentifikation gegenüber der API. Nachdem Sie den Key erhalten haben, kön-
nen Sie direkt auf die API zugreifen.
Zend_Service_Amazon bietet momentan eine reine Abfragemöglichkeit an. Das
heißt, Sie können bestimmte Produkte auslesen, nach Stichworten suchen und
Rezensionen zu Produkten auslesen. Einige andere Klassen unterstützen nur den
Zugriff auf die amerikanische Amazon-API. Zend_Servi ce_Amazon kann da mehr.
Das Paket unterstützt den Zugriff auf die Amazon-Angebote der folgenden Län-
der: Kanada (CA), Deutschland (DE), Frankreich (FR), Japan (JP), England (UK,)
und USA (US). Die Countiy-Codes, die Sie nach den Namen der Länder jeweils in
Klammern finden, stellen hierbei Strings dar, die Sie als zweiten Parameter an
den Konstruktor übergeben. Der erste Parameter ist der Key, der obligatorisch
übergeben werden muss. Der zweite Wert ist optional. Übergeben Sie keinen
Countiy-Code, nutzt das Paket den Code US als Default-Wert.
Im einfachsten Fall leiten Sie ein Objekt der Klasse ab und rufen dann die
Methode i temSearch() auf, welche ein Array mit Optionen von Ihnen erwartet.
Sie übergeben hier die Information, welche Stichworte Sie suchen und in wel-
cher Kategorie Sie diese suchen. Den oder die Suchbegriffe übergeben Sie mit-
hilfe des Schlüssels Keywords und die Kategorie mit dem Schlüssel Searchlndex.
Im folgenden Beispiel wird Books genutzt, womit in der Kategorie »Bücher«
gesucht wird. Dazu später aber mehr.
requi re_once('Zend/Servi ce/Amazon.php');
// Daten von Amazon kommen als UTF-8
header('Content-Type: text/html; charset=utf-8'):
// API-Key
$key = ’W2V8EGGB0FH52S1AZ12’ ;
// Amazon "Niederlassung"
$land = ’DE':
Samazon = new Zend_Service_Amazon($key,$land):
//Suche nach php in Büchern
Sparams = array(’Searchlndex’ => 'Books',
'Keywords' => 'php');
// Suche abschicken
Sergebnisse = $amazon->itemSearch(Sparams):
echo "Folgende Büjeher wurden gefunden:<br>";
foreach ($ergebnisse as $ergebnis)
(
221
e-bol.net
5 | Webservices
echo "<p>";
echo "Ti tel: ";
// Link zur Detailseite bei Amazon
echo "<a href='$ergebnis->Detai1PageURL’>";
// Titel ausgeben
echo $ergebnis->Title . '</a><br>*;
// Autor(en) ausgeben
i f (i s_array($ergebnis->Author))
{
echo "Autoren:<br>";
foreach ($ergebnis->Author as Sautor)
echo Sautor."<br>";
}
el se
{
echo "Autor:<br>";
echo " .Sergebnis->Author;
echo "</p>";
}
Listing 5.4 Suchen von Büchern bei Amazon
Die Ausgabe von Listing 5.4 finden Sie in Abbildung 5.4.
Die Methode i temSearch() ist recht einfach zu nutzen. Aber gerade die Einfach-
heit und Flexibilität, welche die Methode bietet, kompliziert das System schnell.
Zuerst stellt sich die Frage, in welchen Kategorien Sie suchen können. Diese Kate-
gorien unterscheiden sich von Land zu Land. In Deutschland können Sie zurzeit
die folgenden Kategorien nutzen: Apparel, Baby, Books, Classical, DVD,
Electronics, ForeignBooks, HealthPersonalGare, HomeGarden, Kitchen,
Magazines, Music, OutdoorLiving, PCHardware, Photo, Software, Software-
VideoGames, SportingGoods, Tools, Toys, VHS, Video, VideoGames und Wat-
ches.1 Die Kategorienamen sind zum größten Teil selbsterklärend. Wenn Sie her-
ausfinden wollen, welche Kategorien in dem jeweiligen Land verfügbar sind,
finden Sie die Information in der Amazon-Dokumentation.1 2 Sollte Ihnen das zu
aufwändig sein, so können Sie in der Abfrage auch einfach eine ungültige Kate-
gorie nutzen. Dann wirft das System eine Exception, der Sie die gültigen Katego-
rien entnehmen.
1 In absehbarer Zeit wird auch MusicTracks verfügbar sein.
2 http ://docs .amazonwebservices .com/AWSEcommerceService/2005-10-05/
222
e-bol.net
Zugriff auf Amazon mit Zend_Service_Amazon | 5-2
http://12 7.0.0. l/~carsten/zf-buch/Services/Amazon/2.php
L± http://127.0.0. l/~carsten/zf-buch/Services/Amazon/2 * '(V Google
QQ Planet PHP (113) Apple (7)v Amazon eBay Yahoo! Newsv my deldcio.us
Folgende Bücher wurden gefunden:
Titel: Joomla! Das Handbuch für Einstiger (Galileo Computing)
ASIN: 3898426327
Verlag: Galileo Press
Autoren:
- Anja Ebersbach
- Markus Glaser
- Radovan Kubani
Titel: Einstieg in PHP 5 und MySQL 5, Einführung in die Webprogrammiening (Galileo Computing)
ASIN: 3898428540
Verlag: Galileo Press
Autor Thomas Theis
Titel: PHP und MySQL.Easy.. Dynamik für Ihre Webseiten ^Leicht, klar, sofort £M u. T easy)
ASIN:3827269326
Vertag: Markt und Technik
Autor Giesbcrt Damaschke
Titel: Einstieg in TYPO3 4.0. Installation, Grundlagen, TypoScript (Galileo Computing)
ASIN: 3898428362
Vertag: Galileo Press
Autoren:
- Andreas Stöckl
- Frank Bongers
Abbildung 5.4 Bei Amazon gefundene Bücher
Zusätzlich können Sie auch noch Blended nutzen. In dem Fall wird in allen Kate-
gorien gesucht. Dabei erhalten Sie allerdings oft mehr Ergebnisse als die zehn,
die Sie standardmäßig erhalten. Die Angabe einer Kategorie muss erfolgen, um
die Abfrage durchführen zu können.
Neben dem Schlüssel Keywords können Sie noch eine große Anzahl von anderen
Funktionalitäten nutzen. Allerdings handelt es sich dabei um eine so große
Anzahl von Schlüsseln, dass ich sie hier nicht komplett erläutern kann. Die aus
meiner Sicht wichtigsten Schlüssel finden Sie in Tabelle 5.1.
Schlüssel Beschreibung
Aval 1 abi 1 i ty Legt fest, ob nur Artikel gefunden werden sollen, die auch verfügbar sind.
Möglicher Wert ist Aval 1 abl e. Bitte beachten Sie, dass Sie bei der Nut-
zung dieses Schlüssels Conditi on auf Al 1 setzen müssen. Merchantld
sollte auf Al 1 gesetzt sein.
Searchlndex
Kategorie, in der gesucht werden soll (s. o.)
Tabelle 5.1 Schlüssel für die Suche bei Amazon
223
e-bol.net
5 | Webservices
Schlüssel Beschreibung
Keywords Schlüsselworte, nach denen gesucht werden soll, werden in Form eines Strings angegeben.
Title Begriffe, die Sie mit diesem Schlüssel übergeben, werden nur im Titel der Produkte gesucht.
Artist Künstler, der das Werk veröffentlicht hat. Zulässig bei CI assi cal und Music.
Author Autor des Werks; zulässig bei Forei gnBooks und Books
Actor Schauspieler, der an dem Film mitgewirkt hat. Kann bei VHS, Video und DVD genutzt werden.
Di rector Regisseur des Films. Kann in den Kategorien VHSr Video und DVD genutzt werden.
Composer Komponist eines Musikstückes, kann in der Kategorie CI assi cal genutzt werden.
Publi sher Unternehmen oder Verlag, das/der ein Produkt herausgibt oder veröffent- licht. Kann in den Rubriken VHS, Video, Books, DVD, ForeignBooks und Magazi nes genutzt werden.
ItemPage i temSearch() liefert pro Suche 10 Treffer; werden mehr Ergebnisse gefunden, können Sie mit ItemSearch eine Seite (-10 Ergebnisse) ange- ben, die abgerufen werden soll. Die erste Seite hat die Nummer 1.
MinimumPrice Minimaler Preis des Produkts. Der Preis muss in Cent angegeben werden, also z. B. 500 für 5 Euro.
MaximumPri ce Maximaler Preis des Produkts. Der Preis muss in Cent angegeben werden, also z. B. 650 für 6,50 Euro.
Merchant Id Hiermit können Sie definieren, bei welchen Anbietern die Angebote durchsucht werden sollen. Übergeben Sie Al 1, werden alle durchsucht, bei dem Wert Featured werden nur Anbieter durchsucht, deren Angebot in den Einkaufswagen gelegt wird, wenn Sie den »In den Einkaufswagen«- Button anklicken. Alternativ können Sie auch die ID eines einzelnen Anbieters übergeben.
Conditi on Zustand des gesuchten Artikels. Mögliche Werte sind Al 1, New, Used, Refurbi shed und Col1ecti ble.
ResponseGroup Definiert, welche Daten Sie als Antwort erwarten.
Tabelle 5.1 Schlüssel für die Suche bei Amazon (Forts.)
Wie schon gesagt, ist die Liste aus Tabelle 5.1 nicht komplett. Eine vollständige
Liste finden Sie unter dem Menüpunkt »ItemSearch« auf der Website
http://docs.amazonwebservices.com/AWSEcommerceService/2005-W-05/. Hier finden
Sie unter dem Menüpunkt »Searchlndex/Parameter Combinations« auch die
Information, bei welchen Suchanfragen welche Parameter mit welchen anderen
kombiniert werden dürfen bzw. müssen.
224
e-bol.net
Zugriff auf Amazon mit Zend_Service_Amazon | 5-2
Zwar ist itemSearch( )sehr flexibel, aber auch sehr komplex. Möchten Sie also
eine Suchfunktion damit umsetzen, sollten Sie sich vorher gut mit den Möglich-
keiten und Anforderungen vertraut machen, um keine Exceptions zu erhalten.3
Nun ist noch zu klären, was die Methode zurückgibt. Auch dieses Problem ist
nicht ganz trivial, wie Sie gleich sehen werden. Bei einem direkten Rückgabewert
handelt es sich um ein Objekt der Klasse Zend_Service_Amazon_Resul tSet. Da
diese einen Iterator implementiert, können Sie das Objekt direkt an eine for-
each-Schleife übergeben, die bei jedem Durchlauf ein Objekt der Klasse Zend_
Service_Amazon_Item erhält. In diesem Objekt sind die einzelnen Werte in
Eigenschaften abgelegt. Standardmäßig sind in der Antwort für jedes Produkt
mindestens die Eigenschaften ASIN und Detai 1 PageURL enthalten. Die erste ent-
hält die Amazon-eigene Artikelnummer und die zweite einen Link auf die Detail-
seite des Produkts. Die anderen Eigenschaften sind ein wenig schwieriger zu
handhaben. Das hat zwei Gründe: Erstens nutzt Amazon die Eigenschaften nicht
ganz konsistent. Der zweite Grund ist, dass die Eigenschaften auch teilweise ein-
fach leer sein können. Das heißt, die Eigenschaft im Objekt wird mit NULL belegt.
Darüber hinaus kann es vorkommen, dass eine Eigenschaft mehrere Werte bein-
haltet, die nicht nur als String, sondern als Array übergeben werden.
In dem nachfolgenden Beispiel soll nach Büchern gesucht werden, wobei nach
den Begriffen PHP und MySQL gesucht werden soll:
requi re_once('Zend/Servi ce/Amazon.php');
// Daten von Amazon kommen als UTF-8
header('Content-Type: text/html; charset=utf-8'):
// API-Key
$key = ’02V8DF43B0TF2S1AZ02’;
// Amazon "Niederlassung"
$land = •DE';
Samazon = new Zend_Service_Amazon($key,$land);
//Suche nach php in Buechern
Sparams = array(’Searchlndex’ => ’Books’,
'Keywords'=>'PHP, Mysql'):
// Suche abschicken
Sergebnisse = $amazon->itemSearch(Sparams):
echo "Folgende Büjeher wurden gefunden:<br>";
foreach ($ergebnisse as $ergebnis)
(
3 Momentan wirft die Methode eine Exception, wenn keine Ergebnisse gefunden wurden.
Das soll sich allerdings in Version 1.1.0 ändern.
225
e-bol.net
5 | Webservices
// Handelt es sich wirklich um ein Buch?
if ('Book' != Sergebnis->ProductGroup)
{
conti nue;
echo "<p>":
echo "Titel: ";
// Link zur Detailseite bei Amazon
echo "<a href=*$ergebnis->Detai1PageURL’>";
// Titel ausgeben
echo $ergebnis->Title . '</a><br>’;
// ASIN ausgeben
echo "ASIN: ".$ergebnis->ASIN .'<br>';
// Verlag ausgeben
echo "Verlag:
if (!empty(Sergebnis->Manufacturer))
{
echo Sergebnis->Manufacturer;
el se
{
echo "Unbekannt":
echo "<br>";
// Autor(en) ausgeben
i f (1s_array($ergebnis->Author))
{
echo "Autoren:<br>”;
foreach ($ergebnis->Author as Sautor)
echo ".Sautor."<br>";
}
el se
{
echo "Autor: ";
if (!empty(Sergebnis->Author))
echo Sergebnis->Author;
1
el se
echo "Unbekannt":
)
226
e-bol.net
Zugriff auf Amazon mit Zend_Service_Amazon | 5-2
echo "</p>";
}
Listing 5.5 Komplexere Buch-Suche
Innerhalb der foreach-Schleife werden die Daten ausgegeben. Die Eigenschaften
erläutere ich sofort. Zuvor lassen Sie mich aber noch auf die folgende i f-Abfrage
eingehen:
if ('Book' != tergebnis->ProductGroup)
{
conti nue:
}
Sie sollte eigentlich nicht nötig sein, ist aber hilfreich. In einigen Fällen liefert
Amazon Datensätze von Produkten zurück, die man nicht erwartet hatte. Daher
testet diese Abfrage, ob in der Eigenschaft ProductGroup der String Book enthal-
ten ist. Sollte das nicht der Fall sein, macht die Schleife mit der nächsten Iteration
weiter. Welche Produktgruppen es gibt, entnehmen Sie bitte der Amazon-Doku-
mentation.
Innerhalb der Schleife wird zunächst ein Link auf die Detailseite des Produkts
generiert. Der Link setzt sich aus dem eigentlichen Link-Ziel, das in der Eigen-
schaft Detai 1 PageURL enthalten ist und dem Link-Text zusammen. Als Link-Text,
also das, was der Benutzer anklicken kann, wird hier der Titel des Buches ver-
wendet, der in der Eigenschaft Title abgelegt ist.
Danach wird die ASIN ausgegeben. Bei der ASIN, der Amazon Standard Identifi-
cation Number, handelt es sich um eine Nummer, über die jedes Produkt bei
Amazon eindeutig referenziert werden kann. Diese ist sehr hilfreich, wenn Sie
eine Seite mit Detailinformationen zu den Produkten erstellen möchten.
Die Ausgabe des Verlags ist mit einer i f-Abfrage verknüpft. Leider sind im Sys-
tem von Amazon nicht immer alle Daten gepflegt. Zwar wird jedes Buch aus
einem Verlag kommen, aber es kann sein, dass dieser einfach nicht im Amazon-
System bekannt ist. Der Verlag wird unter anderem in der Eigenschaft Manufac-
turer geführt.
Interessant ist wieder das Auslesen des Autors bzw. der Autoren. Hier kann es
vorkommen, dass nicht nur ein Wert enthalten ist. Ist nur ein Autor vorhanden,
enthält die Eigenschaft einen String. Sollte es mehrere Autoren geben, so ist hier
ein Array enthalten. Das gilt auch für alle anderen Eigenschaften. Auch wenn es
nicht sehr wahrscheinlich ist, könnte es durchaus sein, dass mehrere Verlage ein-
getragen sind oder Ähnliches. Sie sollten daher bei einer produktiven Anwen-
dung bei jeder Eigenschaft prüfen, ob sie ein Array enthält.
227
e-bol.net
5 | Webservices
Sie sehen, die Suche nach bestimmten Produkten ist nicht sonderlich schwierig.
Bei genauer Betrachtung ist die Auswertung der gelieferten Ergebnisse allerdings
nicht so ganz einfach. Neben den schon angesprochenen Punkten stellt sich
immer die Frage, welche Elemente überhaupt im Ergebnis enthalten sein kön-
nen. Das hängt von mehreren Faktoren ab. Da wäre zuerst der Punkt, nach wel-
cher Art von Produkten Sie gesucht haben, bzw. in welcher Kategorie Sie gesucht
haben. Zum zweiten stellt sich die Frage, welche Response-Group Sie ausgewählt
haben. Durch die Definition der Response-Group teilen Sie Amazon mit, welche
Daten in der Antwort enthalten sein sollen. Standardmäßig nutzt das Paket die
Response-Group Smal 1. Möchten Sie eine andere Gruppe nutzen, so können Sie
diese auch über das Array definieren, welches Sie an die Methode i temSearch()
übergeben. Mit dem dafür zuständigen Schlüssel ResponseGroup dürfen ein oder
mehrere Antwort-Schemata übergeben werden. Möchten Sie mehrere Schemata
übergeben, so fassen Sie diese einfach in einem String zusammen, wobei die ein-
zelnen Werte jeweils durch ein Komma zu trennen sind. Bitte beachten Sie, dass
hier wirklich nur ein Komma zwischen den einzelnen Gruppen stehen darf. Soll-
ten Sie zusätzlich ein Leerzeichen oder einen anderen Whitespace nutzen, führt
das (momentan noch) zu einer Exception. Alternativ können Sie die Werte auch
als Array übergeben.
Die folgenden Antwort-Gruppen sind hierbei zulässig: Request, Itemlds,
Smal1 , Medi um, Large, OfferFul1 , Öfters , OfferSummary, Vari ations , Vari -
ationMinimum, VariationSummary, ItemAttributes, Tracks, Accessories,
EditorialReview, SalesRank, BrowseNodes, Images, Simi1arities, Reviews,
ListmaniaLists, SearchBins, Subjects. Die Gruppen liefern jeweils einen
unterschiedlichen Umfang an Daten zurück und sorgen dafür, dass nicht die
eigentlichen Produkte, sondern beispielsweise die dazugehörigen Bilder (Images)
oder Kundenkommentare (Reviews) ausgelesen werden. Da es den Rahmen des
Buches sprengen würde, die einzelnen Response-Gruppen zu besprechen, muss
ich Sie an dieser Stelle leider wieder auf die Dokumentation verweisen.
Allerdings sei mir der Hinweis gestattet, dass eine Gruppe unter Umständen
andere Gruppen mit einschließen kann. So umfasst die Gruppe Large beispiels-
weise auch die Inhalte von Small , Request, ItemAttributes, OfferSummary,
SalesRank, Editorial Review, Images, Tracks, BrowseNodes, Reviews, List-
maniaLi sts , Simi1arities, Öfters und Accessori es.
Im folgenden Beispiel sollen Informationen zum Buch »eZ Components« gesucht
werden. Um keine Stichwortsuche durchführen zu müssen, wird in diesem Bei-
spiel nicht i temSearch(), sondern i temLookup() genutzt. Die Methode sucht ein
bestimmtes Produkt heraus und bekommt als ersten Parameter die ASIN des Arti-
kels übergeben. Als zweiten Parameter können Sie der Methode ein Array mit
228
e-bol.net
Zugriff auf Amazon mit Zend_Service_Amazon | 5-2
Optionen übergeben, wobei die hier akzeptierten Parameter identisch mit denen
von 1 temSea rch() sind. Hier ist es allerdings nicht sinnvoll, Stichworte oder eine
Kategorie zu übergeben, da das Produkt direkt selektiert wird. Als Response-
Group wird in diesem Fall Large genutzt. Da itemLookup() exakt einen Treffer
liefert, müssen die Daten auch nicht in einer Schleife verarbeitet werden. Sollte
zu der ASIN, die Sie übergeben haben, kein Treffer gefunden werden, resultiert
das zurzeit noch in einer Exception.
Wie Sie sehen werden, enthält das Ergebnis-Objekt verschiedene andere
Objekte. Für Bilder, Kundenrezensionen und Ähnliches sind innerhalb des
Pakets eigene Klassen vorgesehen. Welche Klassen definiert und welche Eigen-
schaften deklariert sind, können Sie in der Dokumentation unter http://framework.
zend.com/manual/de/zend.service.amazon.html nachlesen.
requi re_once('Zend_reposi tory/Service/Amazon.php');
// Daten von Amazon kommen als UTF-8
header('Content-Type: text/html; charset=utf-8');
// API-Key
Skey = ’04VEP4GCS1AZ0AF' ;
// Amazon "Niederlassung"
$1 and = ’DE';
Samazon = new Zend_Service_Amazon(Skey,$land);
//Suche nach ASIN 3836210738
Sparams = array(’ResponseGroup' => 'Large');
// Suche abschicken
Sergebnis = $amazon->itemLookup('3836210738',$params);
// Bild ausgeben
// Neben Mediumimage gibt es auch noch Largelmage und Small Image
$bild_url = Sergebnis->MediumImage->Url;
$bild_breite = Sergebnis->MediumImage->Width;
$bild_hoehe = Sergebnis->MediumImage->Height;
echo"<tableXtrXtd>";
echo "<img src=’Sbi1d_ur1’ width='$bi1d_breite'
height='$bild_hoehe’>";
echo "</td><td>";
echo "Titel: ";
// Link zur Detailseite bei Amazon
echo "<a href='Sergebnis->Detai1PageURL'>";
// Titel ausgeben
echo Sergebnis->Title . ’</aXbr>';
229
e-bol.net
5 | Webservices
// Verlag ausgeben
echo "Verlag: ".Sergebnis->Manufacturer;
echo "<br>";
// Autoren ausgeben
echo "Autoren:<br>";
foreach (Sergebnis->Author as $autor)
{
echo .Sautor. "<br>";
}
// Seitenzahl
echo "Seiten: ".Sergebnis->NumberOfPages:
echo "<br>":
// ISBN-Nummer ausgeben
echo "ISBN-Nummer: Sergebnis->ISBN;
echo "<br>":
// EAN-Nummer ausgeben
echo "EAN-Nummer: Sergebnis->EAN;
echo "<br>":
// Kundenbewertung
echo "Durchschnittliche Kundenbewertung:
echo Sergebnis->AverageRating;
echo " Sterne <br>";
// Anzahl der Kundenrezesionen
echo "Anzahl der Kundenbewertungen:
echo Sergebnis->TotalReviews:
echo "</tdX/trX/table>":
// Guenstigsten Anbieter auslesen:
Sanbieter = Sergebnis->Offers:
// Preis ausgeben
echo "Preis:
// Achtung! Preis ist ein Integer-Wert
printf (’%.2f',Sanbieter->LowestNewPrice/100):
echo " ".Sanbieter-SLowestNewPriceCurrency:
// Kundenrezensionen ausgeben:
echo "<hrXbrXb>Kundenrezensionen</b>";
Srezensionen = Sergebnis->CustomerReviews:
foreach (Srezensionen as Srezension)
{
echo "<p><b>Srezension->Summary</b><br>";
echo ”$rezension->Content</p>":
}
Listing 5.6 Ausgabe von Details zu einem Buch
230
e-bol.net
Zugriff auf Amazon mit Zend_Service_Amazon | 5-2
http://12 7.0.0.1/-carsten/zf-buch/Services/Amazon/3.php
< ► : + | 0 C * 'Q’ Google
OQ Planet PHP (113) Apple (7)▼ Amazon eBay Yahoo! News^ my del.ido.us
Titel: eZ Components
Verlag: Galileo Press
Autoren:
- Tobias Schlitt
- Kore Nordmann
Seiten: 454
ISBN-Nummcr. 3836210738
EAN-Nummer. 9783836210737
Durchschnittliche Kundenbewertung: 5 Sterne
Anzahl der Kundcnbcwcrtungcn: 1
Preis: 39.90 EUR
Kundenrezensionen
Tobias Schlitt und Kore Nordmann stammen aus dem Entwicklerteam und bieten mit ihrem Buch
einen umfassenden Einstieg in eZ Compo
Tobias Schlitt und Kore Nordmann stammen aus dem Entwicklcrteam und bieten mit ihrem Buch einen
umfassenden Einstieg in eZ Components. Auch komplexe PHP-Applikationen lassen sich so einfach und
schnell mit der Klasscnbibliothck erstellen.
Mit eZ Components erreicht zum ersten Mal eine professionelle Komponcnten-Bibliothek die PHP-
Gemeinde. Die hochqualitativen PHP-5-Bausteine lassen sich in beliebige Anwendungen und jedes
Framework integrieren. Neben Komponenten zur Datenbankabstraktion sind unter anderem auch eine
modulare Template-Engine, verschiedene Pakete zur Grafikbearbeitung, Daten Visualisierung (Charts) oder
zur Realisierung von Plug-In-Architekturcn (SignalSlot) im Angebot. Insgesamt stellt eZ Components zurzeit
26 Komponenten und 7 Tie-In-Komponcntcn zur Verfügung.
Abbildung 5-5 Darstellung von Details zu einem Buch
Die meisten Punkt in diesem Listing sind sicher selbsterklärend, sodass ich nur
auf die interessanten Punkte eingehen werde.
Jedes Produkt verfügt über drei verschieden große Bilder. In diesem Beispiel
wurde das Bild-Objekt genutzt, das in der Eigenschaft Mediumimage abgelegt ist.
Zusätzlich existieren noch die Eigenschaften Small Image und Largelmage, die
Informationen zu dem kleinen und dem großen Bild enthalten. Beide können
identisch mit Medi um Image genutzt werden.
Der günstigste Anbieter für das Produkt wird in diesem Fall dadurch ermittelt,
dass das Objekt Offers ausgelesen wird. In den hier genutzten Eigenschaften
sind die Informationen über den günstigsten Anbieter direkt enthalten. Darüber
hinaus ist in dem Objekt noch ein Array namens Offers enthalten, in dem Sie
Detailinformationen zu dem günstigsten Anbieter bzw. noch weitere Anbieter
finden. Bitte beachten Sie bei den Preisen, dass Amazon diese immer als Integer-
Werte, also als Cent-Beträge, ausliefert.
Bei der Arbeit mit der Amazon-API stellen Sie schnell fest, dass sie technisch zwar
gut, aber nicht unbedingt einfach zu benutzen ist. Das hauptsächliche Problem
besteht darin, dass die Eigenschaften nicht immer konsistent genutzt werden.
231
e-bol.net
5 | Webservices
Des Weiteren beachten Sie bitte, dass Zend_Service_Amazon bisher noch einige
Features vermissen lässt. So können Sie beispielsweise noch nicht herausfinden,
wie der Name eines Rezensenten lautet. Prüfen Sie vor dem Einsatz also, ob das
Paket Ihre Anforderungen abdeckt.
5.3 Zugriff auf Flickr mit Zend_Service_Flickr
Flickr ist zurzeit eine der größten, wenn nicht sogar die größte, Foto-Communi-
ties im Internet. Viele Hobby- und Profifotografen stellen hier ihre Bilder der
Öffentlichkeit vor. Die Bilder können von den Eigentümern dabei nach bestimm-
ten Kriterien verschlagwortet und sortiert werden.
Zend_Servi ce_Fl i ckr gibt Ihnen die Möglichkeit, die öffentlichen Bilder der
Benutzer nach bestimmten Kriterien zu suchen. Sie können nach bestimmten
Schlagworten oder nach Bildern eines Benutzers suchen. Weitere Funktionalitä-
ten, die Flickr unterstützt, werden momentan durch die Klasse nicht abgedeckt.
Bevor Sie loslegen können, benötigen Sie allerdings auch hier einen API-Schlüs-
sel. Diesen erhalten Sie, indem Sie einen Account bei Flickr anlegen. Alle not-
wendigen Informationen finden Sie unter der Adresse http://www.ßickr.com/
services/api/.
Nachdem Sie ein Objekt der Klasse abgeleitet haben, wobei Sie den API-Schlüssel
an den Konstruktor übergeben, können Sie mit den Methoden tagSearch() und
userSearch() auf Bildersuche gehen. Mit tagSearch() können Sie nach Bildern
zu bestimmten Schlagworten suchen, wohingegen userSearch() die Bilder eines
bestimmten Anwenders liefert.
Die Schlagworte, zu denen Sie Bilder suchen, können Sie tagSearch() als ersten
Parameter in Form eines Arrays oder als String übergeben, wobei die Suchbe-
griffe jeweils durch ein Komma zu trennen sind.
userSearch() akzeptiert die E-Mail-Adresse eines Benutzers oder den Benutzer-
namen als Parameter.
Beide Methoden akzeptieren als zweiten Parameter ein Array, mit welchem Sie
das Verhalten der jeweiligen Methode beeinflussen. Sie können damit steuern,
wie viele Treffer auf einmal zurückgegeben und welche zusätzlichen Informatio-
nen ermittelt werden sollen. Die wichtigsten Schlüssel in diesem Zusammenhang
sind sicher per_page und page. Mit dem ersten Schlüssel definieren Sie, wie viele
Treffer pro Anfrage zurückgegeben werden. Der Default-Wert, den die Klasse
nutzt, ist 10. Maximal können Sie hier 500 angeben. Mit page teilen Sie Flickr
mit, auf welcher Seite Sie sich gerade befinden, wenn mehr Treffer gefunden
232
e-bol.net
Zugriff auf Flickr mit Zend_Service_Flickr | 5-3
wurden, als auf einer Seite dargestellt werden können. Die Gesamtzahl der mög-
lichen Schlüssel ist leider zu groß, um sie hier komplett zu erläutern. Sie können
sie der Flickr-Dokumentation entnehmen. Die möglichen Schlüssel bei tag-
Search() finden Sie unter der Adresse http://www.flickr.com/services/api/flickr.
people.getPublicPhotos.html. Und unter http://www.flickr.com/services/api/flickr.
photos.search.html finden Sie die Möglichkeiten, die tagSearchf) Ihnen bietet.
Die Methoden liefern als Rückgabewert jeweils ein Objekt vom Typ Zend_
Service_Fl ickr_ResultSet. Diese Klasse implementiert das Interface Seek-
abl elterator aus der SPL von PHP.4 Dadurch lässt sich das Objekt auch direkt in
einer foreach-Schleife nutzen.
Wichtige Eigenschaften in diesem Objekt sind total Resul tsAvai 1 abl e (Gesamt-
zahl der Treffer), total Resul tsReturned (Anzahl der zurückgelieferten Treffer)
sowie fi rstResultPosition (Position des ersten gelieferten Treffers in der
Gesamtliste).
Bei jedem einzelnen Treffer, den Sie mit einer Schleife oder durch direkten
Zugriff auf eine der Interface-Methoden auslesen, handelt es sich um ein Objekt
der Klasse Zend_Servi ce_Fl 1 ckr_Resul t. In ihm sind die wichtigsten Informati-
onen zu einem Bild enthalten. Die wichtigsten Eigenschaften dieser Klasse finden
Sie in Tabelle 5.2.5
Eigenschaft Wert
1 d eindeutige ID des Bildes
title Titel des Bildes
ownername Benutzername des Eigentümers
dateupload Datum, zu dem das Bild zu Flickr hochgeladen wurde (UNIX-Timestamp)
datetaken Datum, an dem das Bild aufgenommen wurde (MySQL-Datetime-Format)
Tabelle 5.2 Eigenschaften des Flickr-Result-Objekts
In den vorgenannten Eigenschaften sind also nur Meta-Informationen zu dem
Bild zu finden. Der Hintergrund ist, dass Flickr zu einem Bild immer gleich
Kopien in verschiedenen Größen anlegt. Informationen zu diesen einzelnen Bil-
dern sind in Form von Zend_Service_Fl ickr_Image-Objekten in den Eigen-
schaften aus Tabelle 5.3 abgelegt.
4 Eine Dokumentation der SPL finden Sie unter http://www.php.net/~hell_y/php/ext/spl/.
5 Eine komplette Liste der Eigenschaften finden Sie hier: http://framework.zend.com/manual/
en/zend.Service.flickr.html#zend.Service.flickr.classes.result
233
e-bol.net
5 | Webservices
Eigenschaft Bildinformation
Square Ein 75 x 75 Pixel großes Thumbnail des Bildes
Thumbnai1 Ein 100 Pixel großes Thumbnail
Smal 1 Ein 240 Pixel großes Thumbnail
Medi um Eine Variante mit 500 Pixeln
Large Eine Variante mit 1024 Pixeln
Original Das Originalbild
Tabelle 5.3 Eigenschaften des Image-Objekts
Falls Sie sich gerade wundern sollten, warum die Größenangaben in Tabelle 5.3
mit z. B. 500 Pixel ein wenig ungewöhnlich sind, so gibt es dafür eine einfache
Erklärung. Bei Hochformaten handelt es sich dabei um die Höhe, bei Querforma-
ten hingegen um die Breite.
Für jede dieser Eigenschaften ist jeweils ein Objekt der Klasse Zend_Servi ce_
Flickr_Image hinterlegt oder NULL, falls ein Bild in der entsprechenden Größe
nicht vorhanden sein sollte. Die einzelnen Bild-Objekte enthalten noch die
Eigenschaften height und width mit der Höhen- und Breitenangabe sowie uri
und clickUri. Die Angabe uri enthält dabei die URI des eigentlichen Bildes,
wohingegen cl i ckUri auf eine Seite bei Flickr verweist, auf der das Bild betrach-
tet werden kann.
< ? php
requi re_once 'Zend/Servi ce/Flickr.php';
try {
$flickr = new Zend_Service_Flickr('758eb981d3a88b3121f77 ');
// Zwei Bilder pro Seite
$opts = array(’per_page’=>2);
// Wir wollen nur die Bilder von Benutzer thermoman
Streffer = $flickr->userSearch("thermoman",$opts):
// Wurden Bilder zurückgegeben?
if (0 < $treffer->totalResultsReturned)
// Bilder in Tabelle ausgeben ausgeben
echo ’'<table>";
foreach ($treffer as $bild)
{
// Pro Bild Thumbnail und Titel
echo "<tr>";
echo "<td><img src=’{$bild->Smal1->uri}'></td>";
234
e-bol.net
Zugriff auf Flickr mit Zend_Service_Flickr | 5-3
echo "<td>Titel: $ bi 1d->1111e<br>
Flickr-User: $bild->ownername<br>
Aufgenommen: $bi1d->datetaken<br>
<a href-'{$biId->Large->clickUri} ’>
Bild bei Flickr betrachen</a>
</td>";
echo "</tr>";
1
echo ”</table>";
1
el se
echo "Keine Bilder gefunden!";
}
} catch (Zend_Service_Exception $e)
{
die ($e->getMessage()):
1
Listing 5.7 Suche nach Bildern eines Benutzers bei Flickr
Die Ausgabe von Listing 5.7 sehen Sie in Abbildung 5.6. Natürlich ist das Script
noch nicht perfekt. So fehlt beispielsweise noch eine Blätterfunktion.
http://carsten-mohrkes-com...en/zf-buch/flickr/eins.php CD
QQOO O http://ca'sten-m< -fcb Google
OCamino Info u ?News Mac News Tabs [Q|Cooqle । ]Paypal
Titel: wind power
Flickr-User thcrmoman
Aufgenommen: 2007-09-08 11:03:42
Bild bei Flickr betrachen
Titel: katrin
Flickr-User thcrmoman
Aufgcnommen: 2007-05-19 09:59:26
Bild bei Flickr betrachen
Abbildung 5.6 Bilder des Benutzers »thermoman«
235
e-bol.net
5 | Webservices
Weitere Funktionalitäten sind in der Klasse momentan leider noch nicht vorgese-
hen. Es bleibt zu hoffen, dass diese noch ergänzt werden, da Flickr eine wirklich
mächtige API anbietet.
5.4 Yahool-Suche mit Zend_Service_Yahoo
Auch Yahoo! als einer der größten Anbieter von Dienstleistungen im Internet
bietet eine API an, über die die Dienste angesprochen werden können. Damit Sie
sich gegenüber der API authentifizieren können, benötigen Sie auch hier einen
Schlüssel. Diesen erhalten Sie, wenn Sie auf der Seite http://developer.yahoo.com
auf den Link »Get an Application ID« klicken.
Zend_Service_Yahoo unterstützt den Zugriff auf verschiedene Dienste von
Yahoo!. Neben der altbekannten Websuche ist es auch möglich, nach Bildern,
Nachrichten oder lokalen Adressen zu suchen.
Um an dieser Stelle schon eine Sache vorwegzunehmen: Alle Suchen liefern eine
Ergebnisobjekt zurück, welches das SPL-Interface Seekabl elterator implemen-
tiert. Sie können das Ergebnisobjekt somit direkt in einer foreach-Schleife nut-
zen. Oder Sie können mithilfe der Methoden auf die Inhalte zugreifen, die das
SPL deklariert. Eine Erläuterung der Methoden finden Sie unter http://www.
php. net/~helly/php/ext/spl/.
Die einzelnen Suchabfragen werden jeweils mit einem Zend_Service_Yahoo-
Objekt ausgeführt. Es gibt also keine spezialisierten Klassen für die Abfragen,
sondern lediglich spezielle Methoden. Der API-Schlüssel wird hierbei direkt an
den Konstruktor übergeben.
5.4.1 Websuche
Die Websuche bei Yahoo! erfolgt mithilfe der Methode webSearch(). Der Such-
string wird der Methode dabei direkt als Parameter übergeben. Über einen zwei-
ten, optionalen Parameter können Sie noch das Suchverhalten beeinflussen. An
dieser Stelle können Sie ein assoziatives Array übergeben. Die gültigen Schlüssel
finden Sie in Tabelle 5.4.
236
e-bol.net
Yahoo!-Suche mit Zend_Service_Yahoo
5-4
Schlüssel Bedeutung
results Anzahl der Treffer, die maximal zurückgeliefert werden sollen (Default: 10, Maximum 100)
start Offset des ersten Treffers, der zurückgegeben werden soll. Die Zählung der Treffer beginnt mit 1.
1anguage ISO-Sprachkürzel der Sprache, in der gesucht werden soll. Es kann immer nur in einer Sprache gesucht werden; die Default-Sprache ist Englisch.6
type Bestimmt, wie die Suchabfrage verarbeitet wird. Zulässig sind die Werte any, al 1 und phrase. Bei any muss mindestens einer der Suchbegriffe auf der Seite zu finden sein, al 1 legt fest, dass alle Wörter zu finden sein müs- sen, und phrase definiert den Suchbegriff als feststehende Phrase.
si te Hiermit können Sie einschränken, auf welcher Website gesucht werden soll. Übergeben Sie hier www.exampl e . com, wird nur auf diesem Server gesucht. Mehrere Sites - bis zu 30 sind möglich - können mit & verknüpft werden.
format Dateiformat, in dem der Treffer vorliegen soll. Möglich sind die Werte any, html, msword, pdf, ppt, rss, txt und xl s. Der Default-Wert any steht für beliebige Datentypen.
adult_ok Hiermit können Sie einen booleschen Wert übergeben, der definiert, ob die Suche auch möglicherweise jugendgefährdende Inhalte liefern darf, was standardmäßig nicht der Fall ist.
similar_ok Übergeben Sie hier true, so werden auch Seiten mit identischem Inhalt und unterschiedlichen URLs zurückgegeben, was standardmäßig nicht so ist.
country Mit diesem Schlüssel geben Sie einen Länder-Code an, der festlegt, dass nur Treffer aus diesem Land geliefert werden sollen.7
11cense Möchten Sie nur Treffer geliefert bekommen, die einer bestimmten Lizenz unterliegen, so können Sie diese hier angeben. Der Default-Wert any lie- fert Inhalte mit jeder beliebigen Lizenz. Außerdem werden die folgenden Creative Commons Lizenzen unterstützt: cc_any, cc_commerci al, und cc_modi fi abl e
Tabelle 5.4 Optionen für die Websuche
Die Methode webSearchf) gibt ein Objekt von Typ Zend_Servi ce_Yahoo_WebRe-
sultSet zurück. Bei diesem Objekt ist die Eigenschaft totalResultsAvai1able
recht hilfreich. Sie enthält die Gesamtzahl der Treffer, die Yahoo! zu der Abfrage
ermitteln konnte. Die Anzahl der Treffer, die geliefert wurden, können Sie der Eigen-
schaft total Resul tsReturned entnehmen. Ebenso wird auch der Offset des ersten
Treffers zurückgegeben; er befindet sich in der Eigenschaft f i rstResul tPosi ti on.
6 Bitte beachten Sie, dass nur bestimmte Sprachen unterstützt werden. Die Auswahl einer nicht
zulässigen Sprache resultiert in einer Exception. Die zulässigen Sprach-Codes finden Sie hier:
http://developer.yahoo.com/search/languages.html
7 Die Liste der unterstützten Länder finden Sie unter http://developer.yahoo.com/search/
countries.html.
237
e-bol.net
5 | Webservices
Die einzelnen Treffer der Suche können mit einer foreach-Schleife ausgelesen
werden. Jeder Treffer liegt in Form eines Zend_Service_Yahoo_WebResult-
Objekts vor. Die Eigenschaften des Objekts entnehmen Sie bitte Tabelle 5.5.
Eigenschaft Erläuterung
Title Titel der Webseite, die gefunden wurde
Uri URL des gefunden Treffers (zur Bildschirmdarstellung)
CIickUrl URL, die zum Verlinken genutzt werden soll. In dem Fall protokolliert Yahoo! den Klick und leitet auf die Zielseite um.
Summary Kurze Zusammenfassung der Seite oder Ausschnitt aus der Seite
MimeType MIM E-Type des gefundenen Dokuments
Modi f1cati onDate Zeitpunkt der letzen Veränderung als UNIX-Timestamp
CacheUrl URL der Seite im Yahoo!-Cache
CacheSize Größe der Datei im Cache
Tabelle 5.5 Eigenschaften des Result-Objekts
< ? php
requi re_once 'Zend/Service/Yahoo.php';
header('Content-Type: text/html; charset=utf-8');
try {
$yahoo = new Zend_Service_Yahoo("vxH86JA6B76D0TZqNSXg--");
$opts = array('1anguage’=>'de',
’start'=>2);
$results = $yahoo->webSearch(utf8_encode('Galileo'),$opts);
if (0 < $results->totalResultsReturned)
echo *<table>*;
foreach ($results as $result)
{
echo "<tr>";
echo "<td colspan='2'>$result->Title</td>";
echo "</tr>";
echo "<tr>";
echo "<td > </tdXtd>$result->Summary</td>";
echo "</tr>";
echo "<tr>";
echo ”<td> :</td>":
echo ”<td>Zur Seite: <a href='$result->Clickl)rl ’>
$ res ult->Url</a></td>";
238
e-bol.net
Yahool-Suche mit Zend_Service_Yahoo | 5-4
echo "</tr>";
echo
echo "<td> </td>":
echo "CtdXa href=’$result->CacheUrl’>
gecachte Seite $result->CacheSize</a></td>";
echo "</tr>";
echo "<tr>";
echo "<td colspan=’2’> </td>";
echo "</tr>";
I catch (Zend_Service_Exception $e)
(
die ($e->getMessage);
Listing 5.8 Nutzung der Yahooi-Websuche
000 Mozilla Firefox
0 http://127.0.0.1 /-carsten/zf-buch/Yal ▼ u* |Gj’Google
Galileo II - Memory Alpha p
Galileo n. aus Memory Alpha, der freien deutschen Star-Trck-Datenbank ... Die Galileo n (NCC-1701/
der Stcmenflotrc der Föderation der...
Zur Seite: http://memory-alpha.org/de/wiki/Galileo II
gecachte Seite 23118
Modernes Webdesign I Web Standards in Germany
Grafikerin und Webdesignerin Manuela Hoffmann fuhrt Sie mit diesem Wegweiser für ... wird voraussit
2008 bei Galileo erscheinen und zeigt, wie Sie ...
Zur Seite: http7/www .webstandardsingermanv .de/2007/09/30/modcmes-webdesign/
gemachte Seite 23.118
Pisa Reiseführer - Wikitravel
Der Open Source Reiseführer für Pisa mit aktuellen Informationen und Tipps über ... Flughafen (IATA-
PSA), der nach Galileo Galilei benannt ist....
Zur Seite: htxp://wikitravcl .org/dc/Pisa
gecachte Seite 23118
Webseiten erstellen für Einsteiger I Web Standards in Germany
Dieses Buch vom Autor Daniel Mies zeigt in lockerer und verständlicher Sprache ... Bei Galileo bestelle
Beiträge: Daniel Mies. Suchmaschinen-Optimierung ...
Zur Seite: http J/www .webstandardsingermanv .de/2007/12/31 /webseitcn-erstellcn-fuer-cinsteigcr/
gecachte Seite 23118
Hildesheim Reiseführer - Wikitravel
........................................................................... • rJ-
Fertig Stitf L N/A O YSIow 1.331s
Abbildung 5.7 Treffer der Websuche zum Suchwort »Galileo«
239
e-bol.net
5 | Webservices
5.4.2 News-Suche mit Zend_Service_Yahoo
Neben der Websuche unterstütz Zend_Servi ce_Yahoo auch die Suche im Nach-
richtenangebot von Yahoo!. Die Nachrichten stammen aus den Angeboten ver-
schiedener Zeitungen und Presseagenturen, welche von Yahoo! durchsucht wer-
den. Auch diese Suchfunktion ist erfreulich einfach in der Handhabung. Sie
müssen lediglich die Methode searchNews() aufrufen, welche den Suchbegriff
als Parameter übergeben bekommt. Auch bei dieser Methode können Sie als
zweiten Parameter ein Array mit Optionen übergeben, welches das Verhalten der
Methode beeinflusst. Die Schlüssel, die Sie in diesem Array benutzen können,
sind: type, resul ts, start, sort, 1anguage und site. Mit Ausnahme von sort
sind diese alle in Tabelle 5.4 erläutert. In diesem Fall erhalten Sie allerdings maxi-
mal 50 Ergebnisse pro Abfrage. Mit sort haben Sie die Möglichkeit, eine Sortier-
reihenfolge zu bestimmen. Mit dem Default-Wert rank sortiert die Ausgabe nach
dem Ranking des Treffers. Der Wert date hingegen führt dazu, dass der aktu-
ellste Treffer als erster ausgegeben wird. Als Ergebnis der Abfrage erhalten Sie
ein Objekt der Klasse Zend_Service_Yahoo_NewsResultSet, welches die Eigen-
schaften totalResultsAvai1able, totalResultsReturned und firstResultPo-
sition kennt. In diesen finden Sie die Gesamtzahl der Treffer, die Anzahl der
zurückgegebenen Ergebnisse sowie den Offset des ersten Elements.
Auch hier gilt, dass Sie das Objekt in einer foreach-Schleife nutzen können, wie
am Anfang des Kapitels erwähnt. Pro Treffer erhalten Sie ein Objekt der Klasse
Zend_Service_Yahoo_NewsResul t, dessen Eigenschaften Sie in Tabelle 5.6 fin-
den.
Schlüssel Erläuterung
Title Titel des Treffers
Summary Ausschnitt aus dem Inhalt
Uri URL des Treffers
CIickUrl URL, die für Links auf den Treffer genutzt werden soll
NewsSource Name der Quelle des Treffers
NewsSourceUrl URL des News-Angebots
Language ISO-Sprach-Code des Treffers
Publi shDate Veröffentlichungsdatum als UNIX-Timestamp
Modi f1cati onDate Zeitpunkt der letzten Änderung als UNIX-Timestamp
Thumbnai1 Hier ist bei einigen wenigen Anbietern ein Thumbnail auf ein Bild zu dem Artikel vorhanden. Die Informationen sind in Form eines Zend_ Servi ce_Yahoo_Image-Objekts enthalten. Informationen dazu finden Sie in Tabelle 5.7.
Tabelle 5.6 Eigenschaften des NewsResult-Objekts
240
e-bol.net
Yahoo!-Suche mit Zend_Service_Yahoo | 5-4
requi re_once 'Zend/Service/Yahoo.php';
header('Content-Type: text/html; charset=utf-8');
try {
$yahoo = new Zend_Servi ce_Yahoo(" vAW6atX6Qhl)zB76D0TZqNSXg--");
// Bei 8 anfangen und 7 Treffer liefern
$opts = array(’start'=>8.
'results'=>7);
// Suche nach den Bush
$results = $yahoo->newsSearch(utf8_encode('Bush'),$opts);
if (0 < $results->totalResultsReturned)
{
echo "<table width='150'>";
foreach ($results as $result)
echo "<tr>";
echo "<td colspan=’2'><b>$result->Title</b></td>";
echo "</tr>";
echo "<tr>";
// Haben wir ein Bild?
if ($result->Thumbnai1)
{
// Dann koennen wir es auch ausgeben
echo "<td>";
echo "<img src='ISresult->Thumbnai1->Url}'";
echo " width='|Sresult->Thumbnai1->Width}' ";
echo " height='{$result->Thumbnai1->Height}’ >";
el se
// Kein Bild also ein leeres Tabellenfeld
echo "<td > </td>";
}
echo "<td>$result->Summary";
echo "<p><a href=’$result->ClickUrl'>$result->Url</a></p>";
echo "</tr>";
echo "<tr>";
echo "<td colspan=’2'> </td>";
echo "</tr>";
} catch (Zend_Service_Exception $e)
241
e-bol.net
5 | Webservices
(
die ($e->getMessage);
I
Listing 5.9 News-Suche bei Yahoo!
Die Ausgabe des Scripts sehen Sie in Abbildung 5.8.
Mozilla Firefox
V’ • e 0 http://127.0.0.1/~carsten/zf-buch/Yahoo/zwei.php ▼ Google
Fatber of Bush tax cuts: Recession likely
Martin Feldstein, the Harvard cconomist credited with bcing one of the fathers of the Bush administration tax
cuts, says the U .S. economy is now likely to slip into a recession, and (hat avoiding one will take a new round
of tax cuts and interest rate cuts from the Federal Reserve.
hffl?y/moncy ,ctm ,rotu/rs«?litk/20Q81 fl7/pc w^^nomy/fcldsKin/index Jitm?^üon~mQnty law?<
Bush urges 'good faith' Kenya dialogue
US President George W. Bush on Monday urged Kenya s govemment and Opposition to hold "good faith
talks and urged an end to violence while leaders seek "a lasting political solution."
http://ncws.yahoo.eom/s/afp/20080107/pl afp/kenyavoteunrestuswhousc 080107235607
Olmert, Abbas try to close gaps abead of Bush v isit
Israeli Prime Minister Ehud Olmert and Palestinian President Mahmoud Abbas meet on Tucsday in a
last-minute attempt to get stalled pcacc talks going before a visit by U.S. President George W. Bush.
http://news.vahoo.eom/s/nm/20080107/ts nm/palestinians Israel dc 8
Bush: Keep Laxes kw in uncertain economic times
Citing "incrcasingly mixed" signs on the U.S. economy's hcalth, President George W. Bush on Monday
urged Congress to make permanent tax cuts enacted since he took Office, saying low taxes were the best
tonic.
http7/ncws.vahoo.com/s/nm/20080107/pl nm/bush economy growth dc 4
Fertig
O fe YSIow 1.241s
Abbildung 5.8 Ergebnis der News-Suche
5.4.3 Bildersuche mit Zend_Service_Yahoo
Wie verschiedene andere Suchmaschinen bietet auch Yahoo! eine Bildersuche an.
Natürlich können Sie nicht in den Bildern selbst nach Personen oder Formen
suchen. Nur die Texte, die um Bilder herum platziert sind, können durchsucht
werden. Sie lassen einen Rückschluss auf das zu, was auf dem Bild zu sehen ist.
Dabei kann es natürlich auch immer wieder einmal zu Fehlinterpretationen kom-
men.
Die Methode, um Bilder zu suchen, heißt imageSearch() und bekommt die Such-
begriffe direkt als ersten Parameter übergeben. Auch in diesem Fall können Sie
als zweiten Parameter ein Array mit Parametern übergeben. Die gültigen Schlüs-
sel in diesem Zusammenhang sind results, start, format, adult_ok, colora-
tion und site. Die Schlüssel resul ts, start, adult_ok und site sind identisch
242
e-bol.net
Yahoo!-Suche mit Zend_Service_Yahoo | 5-4
mit denen der Websuche, die Sie in Tabelle 5.4 finden. Wobei allerdings auch
hier die Einschränkung gilt, dass maximal 50 Ergebnisse pro Suchanfrage gelie-
fert werden.
Mithilfe von format haben Sie die Möglichkeit zu definieren, welche Art von
Grafikdatei Sie suchen. Der Default-Wert any liefert Ihnen alle Formate. Andere
mögliche Werte sind bmp, gi f, jpeg und png. Mit dem Schlüssel col orati on kann
definiert werden, ob die Treffer bunt (Wert: col or) oder schwarz-weiß (Wert: bw)
sein sollen.
Das Rückgabeobjekt gehört zu Klasse Zend_Servi ce_Yahoo_ImageResul tSet und
liefert in den Eigenschaften totalResultsAvailable, totalResultsReturned
und firstResultPosition die Gesamtzahl der Treffer, die Anzahl der zurückge-
gebenen Treffer sowie den Offset des ersten zurückgegebenen Ergebnisses.
Übergeben Sie das Objekt an eine Schleife oder greifen Sie über die Methoden
der SPL auf die einzelnen Bilder zu, erhalten Sie pro Bild jeweils ein Objekt der
Klasse Zend_Service_Yahoo_Image, deren Eigenschaften Sie in Tabelle 5.7 fin-
den.
Eigenschaft Bedeutung
Summary Kurzbeschreibung des Bildes
RefererUrl URL der Seite, auf der das Bild eingebettet ist
Fi 1eSi ze Größe der Bilddatei in Byte
Fi 1eFormat Dateiformat des Bildes
Height Höhe des Bildes in Pixeln
Width Breite in Pixeln
Thumbnai1 Verweis auf ein Thumbnail aus dem Yahool-Cache. Die Eigenschaft Uri enthält die URL des Bildes, und die Eigenschaften Hei ght und Width die Höhe und Breite.
Title Titel des Bildes, üblicherweise der Dateiname
Uri URL des Bildes
CIi ckUrl URL, die zum Anklicken verwendet werden soll. Zurzeit identisch mit dem Feld Uri.
Tabelle 5.7 Eingenschaften eines Image-Objekts
Bei der Nutzung der Bildersuche beachten Sie bitte, dass die Informationen nicht
immer ganz so aktuell wie die der Web- oder Nachrichtensuche sind. Insbeson-
dere Thumbnails können schon einmal veraltet sein. Es kann auch passieren, dass
noch ein Treffer inklusive Thumbnail zurückgegeben wird, obwohl das eigentli-
che Bild schon nicht mehr da ist.
243
e-bol.net
5 | Webservices
requi re_once 'Zend/Service/Yahoo.php';
header('Content-Type: text/html; charset=utf-8');
try {
$yahoo = new Zend_Service_Yahoo("vrJ4xH86JNA6Q0TZqNSg--");
$results = $yahoo->imageSearch(utf8_encode('Patrick Star’)):
if (0 < $results->totalResultsReturned)
{
echo "<table width='150;
foreach (Sresults as $result)
echo "<tr>":
echo "<td>":
echo "<img src='{Sresult->Thumbnai1->Url}’";
echo " width=’|$result->Thumbnail->Width}’ ";
echo " height=’($result->Thumbnai1->Height)' >":
echo "</td>";
echo "<td> $result->Title<br>";
echo nl2br( $resul t->Suminary). "<br>";
echo "Dateigröße: $result->Fi1eSize Bytes<br>";
echo "Seite:<a href='$result->RefererUrl'>
$result->RefererUrl</a><br>";
echo "Bild:<a href='$result->ClickUrl’>
Sresult->Ur1</a>":
echo "</td></tr>";
echo "<tr>":
echo "<td colspan=’2'> :</td>":
echo "</tr>";
}
} catch (Zend_Service_Exception $e)
{
die ($e->getMessage):
}
Listing 5.10 Bildersuche bei Yahoo!
244
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata | 5.5
Abbildung 5.9 Ergebnis der Bildersuche
5.5 Zugriff auf Google-Dienste mit Zend_Gdata
Der Suchmaschinen-Gigant Google bietet neben der eigentlichen Suchmaschine
noch viele andere Dienste im Internet an. So bietet Google Dienste wie eine
Tabellenkalkulation, eine Textverarbeitung oder auch einen Terminplaner an.
Des Weiteren gehören inzwischen auch andere Dienste zum Google-Konzern, bei
denen das vielleicht nicht auf Anhieb ersichtlich ist, wie beispielsweise YouTube.
Viele dieser Dienste können über eine einheitliche Schnittstelle, der Google Data
API, angesprochen werden. Diese API, die auch kurz GData genannt wird, basiert
auf dem Atom Publishing Protocol. GData unterstützt zurzeit noch nicht alle
Google-Angebote, sodass es beispielsweise leider noch nicht möglich ist, darüber
die eigentliche Suchmaschine zu nutzen.
Momentan unterstützt das Zend Framework den Zugriff auf die APIs von: Google
Calendar, Google Spreadsheets, Picasa, Youtube, Google Base und Google Docu-
ments. Die Zugriffsmöglichkeiten sind unterschiedlich umfangreich implemen-
tiert. Ich habe mich hier dafür entschieden, mich auf die Vorstellung der Calen-
dar- und Spreadsheets-Pakete zu beschränken. Diese kennen schon recht viele
245
e-bol.net
5 | Webservices
Möglichkeiten. Haben Sie die Nutzung dieser Pakete verstanden, so können Sie
sich sicherlich schnell in die Nutzung der anderen Pakete einarbeiten.
Resultierend aus der Tatsache, dass die Dienste alle über dieselbe API angespro-
chen werden, gibt es natürlich viele Gemeinsamkeiten bei den entsprechenden
Paketen des Zend Frameworks. Daher möchte ich Ihnen zunächst die Methoden
und Techniken vorstellen, die bei allen Google-Klassen Verwendung finden, und
dann auf die Authentifikation eingehen.
5.5.1 Allgemeines zu Zend_Gdata
Google Data basiert auf dem Atom Publishing Protocol. Dabei handelt es sich um
ein XML-basiertes Protokoll, bei dem die Daten über das HTTP-Protokoll ausge-
tauscht werden.
Der Zugriff auf die verschiedenen Dienste wird über Feeds realisiert. Das heißt,
wenn Sie auf eine bestimmte Informationsressource zugreifen wollen, ist dafür
immer eine Feed-URL definiert, wie beispielsweise http://www.google.com/
calendar/feeds/default/owncalendars/full. In einer solchen URL sind verschie-
dene Informationen enthalten. In diesem Fall wird der Zugriff auf die Kalender
eines Benutzers angestrebt.
Abhängig davon, welche Informationen Sie benötigen, müssen Sie sich gegenü-
ber dem System authentifizieren. Wenn Sie beispielsweise öffentliche Daten aus
einem Blog auslesen, ist keine Anmeldung am System notwendig. Möchten Sie
aber einen neuen Eintrag in Ihrem Kalender anlegen, so müssen Sie sich natürlich
zuvor anmelden. Übrigens wird auch das Schreiben von Daten über solche URLs
verwaltet.
Die Nutzung der verschiedenen Google-Dienste ist in Zend_Gdata - da die glei-
chen Basisklassen genutzt werden - recht ähnlich implementiert. Vor diesem
Hintergrund werde ich diejenigen Funktionalitäten, die in allen Paketen vorkom-
men, lediglich bei Google Calendar erläutern. Auch wenn Sie sich vielleicht nur
mir Spreadsheets beschäftigen wollen, sollten Sie vorher das Kapitel zu Google
Calendar lesen.
5.5.2 Authentifikation
Die Authentifikation gegenüber der Google-API kann auf zwei Wegen erfolgen.
Zum ersten können Sie die »übliche« Vorgehensweise mithilfe von Benutzerna-
men bzw. E-Mail-Adresse und Passwort nutzen. Die zweite Möglichkeit ist die
Anmeldung mithilfe von Googles Account Authentification.
246
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata | 5-5
Für beide Vorgehensweisen ist jeweils eine eigene Klasse definiert, in der es eine
Methode namens getHttpCl i ent() gibt, mit welcher ein neuer, authentifizierter
Client abgeleitet werden kann.
Authentifikation via Client-Log-in
Eine Authentifikation mithilfe eines Client-Log-ins bietet sich immer dann an,
wenn unterschiedliche Benutzer eine Applikation nutzen sollen, die sich mithilfe
ihrer E-Mail-Adresse und ihres Passwortes anmelden können. In den folgenden
Beispielen wurden E-Mail-Adresse und Passwort direkt in den Code integriert,
um die Strukturen einfach zu halten.
Ein Client-Log-in, mit dem Sie sich bei Google Calendar anmelden können, sieht
im einfachsten Fall so aus:
Semail = 'zf-buch@netviser.de':
Spasswd = 'total geheim*;
Sclient = Zend_Gdata_ClientLogin::getHttpClient(
$emai1,$passwd,’ei');
Seal = new Zend_Gdata_Calendar(Seiient):
In diesem kleinen Beispiel wird ein neuer Client generiert, indem der statischen
Methode getHttpCl i ent() die E-Mail-Adresse, also sozusagen der Log-in-Name,
und das Passwort übergeben werden. Der dritte Parameter, der String 'ei’, legt
fest, dass eine Verbindung mit dem Calendar-Dienst aufgebaut werden soll. Hier
können Sie eine der Konstanten aus Tabelle 5.8 nutzen, wobei Sie allerdings auch
auf den universellen String xapi zurückgreifen können. Damit können Sie sich zu
allen Services verbinden.
Abkürzung Service
cl Calendar
blogger Blogger
gbase Google Base
wi se Google Spreadsheet
apps Google Apps
1 h2 Picasa Web Albums
youtube YouTube
xapi Standard Client
Tabelle 5.8 Abkürzungen für Google-Dienste
Allerdings ist diese Vorgehensweise nicht perfekt. Neben den »üblichen« Excep-
tions, die auftreten können, besteht bei dieser Form der Authentifikation noch
247
e-bol.net
5 | Webservices
das Problem, dass eine Exception der Klasse Zend_Gdata_App_CaptchaRequi red-
Exception geworfen werden kann. Das ist dann der Fall, wenn Google die Nut-
zung eines CAPTCHA verlangt. Die Nutzung eines CAPTCHA verlangt Google
immer dann, wenn zu viele Log-in-Versuche in zu kurzer Zeit stattgefunden
haben. Sollte dieser Fall eintreten, so können Sie mit der Methode get-
CaptchaUrK) die URL des CAPTCHA-Bildes auslesen, und die Methode get-
CaptchaToken() liefert ein Token, welches Sie bei der nächsten Anfrage zusam-
men mit der Benutzereingabe an den Server senden müssen, damit diese
verifiziert werden kann.
requi re_once 'Zend/Gdata/ClientLogin.php';
requi re_once 'Zend/Gdata/Calendar.php *;
Semail = 'zf-buch@netviser.de';
Spasswd = 'total geheim*;
Sservice = 'ei';
$cli ent = null;
Ssource = Zend_Gdata_ClientLogin::DEFAULT_SOURCE;
// Wurden Daten aus dem Formular übergeben?
if (true — isset ($_POST['captcha']))
{
// Daten aus Formular übernehmen
$1ogin_captcha = $_POST['captcha;
$login_token = $_POST[’token'];
}
el se
{
// Keine Daten erhalten => null übergeben
$1ogi n_captcha = null;
$login_token = null;
}
try
{
$client = Zend_Gdata_ClientLogin:rgetHttpClient($emai1,
$passwd, $service, $client, $source,
$1ogin_token, $1ogin_captcha);
}
catch (Zend_Gdata_App_CaptchaRequiredException $e)
{
// Google verlangt ein CAPTCHA => Formular ausgeben
echo 'Bitte geben Sie den Code ein, den Sie auf dem
Bi 1d sehen.<br>';
248
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata | 5-5
echo "<form method=’post' action=’$_SERVER[PHP_SELF]'>";
// CAPTCHA-Bild ausgeben
echo '<img src="'.$e->getCaptchaUrl().’" ><br>';
echo 'Code: <input type="text" name="captcha"Xbr>':
// Token in einem versteckten Feld übergeben
echo ’Cinput type="hidden" value="’.$e->getCaptchaToken().
'" name="token" ><br>’;
echo '<input type="submit" value="Abschicken">';
echo '</form>';
}
catch (Zend_Gdata_App_AuthException $e)
{
// Hier werden Authentifikationsfehler abgefangen
die ('Bei der Authentifikation trat folgender Fehler auf:
$e->getMessage());
}
catch (Exception $e)
{
// Hier werden sonstige Exceptions abgefangen
die ('Der folgende Fehler ist aufgetreten: '.$e->getMessage()):
}
Seal = new Zend_Gdata_Calendar(Seiient):
Listing 5.11 Authentifikation via Client-Log-in
In Listing 5.11 finden Sie eine etwas komplexere Lösung zum Ableiten eines Cli-
ents. Die Methode getHttpCl ient() wird auch hier wieder genutzt, wobei in
diesem Beispiel alle Parameter genutzt werden und nicht nur die obligatorischen.
Mit dem vierten Parameter könnten Sie ein Objekt der Klasse Zend_Http_Cl i ent
übergeben. Das bietet sich dann an, wenn Sie bereits ein Objekt dieser Klasse
besitzen. Übergeben Sie an dieser Stelle null oder lassen Sie den Parameter ganz
wegfallen, leitet die Methode selbst ein Objekt der Klasse ab.
Bei dem fünften Parameter, in diesem Beispiel also die Variable $ source, handelt
es sich um die »Kennung« der Applikation. In diesem Beispiel wird der Wert der
Konstante DEFAULT_SOURCE übergeben, welche standardmäßig mit dem String
Zend - ZendFramework belegt ist. An dieser Stelle können und sollten Sie allerdings
die Kennung Ihrer Applikation übergeben. Entsprechend der Google-Dokumen-
tation sollte die Kennung nach folgendem Schema aufgebaut sein: ’firma-
applikation-versionsID'.
An diesen Parameter schließen sich die Parameter an, mit denen das Token und
der Code vom CAPTCHA-Bild übergeben werden. Diese sind natürlich nur dann
249
e-bol.net
5 | Webservices
erforderlich, wenn Google nach einem CAPTCHA verlangt. Mit anderen Worten:
Beim vorhergehenden Aufruf der Methode wurde eine Ausnahme des Typs
Zend_Gdata_App_CaptchaRequi redException geworfen. In dem Fall wird dann
dasjenige Formular ausgegeben, welches in dem catch-Block definiert wird. Die
URL des Bildes und das Token werden mithilfe der Methoden getCaptchaUrl ()
und getCaptchaToken() ausgelesen und in das Formular integriert. Wie ein sol-
ches CAPTCHA-Formular aussehen kann, sehen Sie in Abbildung 5.10.
R n o http://127.0.0.1/~c...f-buch/CData/l.php
fei oBfov Google »
CD Planet PHP (137) Apple (23)v Amazon eBay »
Bitte geben Sie den Code ein, den Sic auf dem Bild sehen,
f Abschicken
Abbildung 5.10 CAPTCHA bei einem Client-Log-in
Wird das Formular abgeschickt, werden die Daten aus dem Formular für den
nächsten Verbindungsaufbau genutzt.
Der zweite catch-Block dient dazu, Authentifikationsfehler, also wenn die Kom-
bination aus Log-in und Passwort nicht gültig ist, abzufangen. Leider liefert die
Google-Dokumentation momentan noch keine exakten Informationen, welche
Codes hier zurückgeliefert werden. Sie enthält lediglich die Information, dass die
Fehlermeldung eine Erläuterung des Fehlers enthält.
Das Beispiel aus Listing 5.11 ist natürlich ein klein wenig realitätsfremd, da die
E-Mail-Adresse und das Passwort hart in die Anwendung codiert sind. Bei einer
Anwendung im »echten Leben« würden diese Daten sicher eher aus einem For-
mular oder einer Konfigurationsdatei übernommen.
Authentifikation via Account-Authentication
Die Account-Authentication nutzt eine gänzlich andere Vorgehensweise, die auf
den ersten Blick ein wenig befremdlich erscheinen mag. Die Idee ist allerdings
gut und bringt einige Vorteile mit sich. Sie wird übrigens auch als AuthSub
bezeichnet, da Google eine solche Authentifikation dann durchführt, wenn ein
AuthSub-Request erfolgte.
250
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata | 5-5
Ihre Applikation leitet den Benutzer zunächst auf eine Seite von Google, wobei
die Applikation ihre eigene URL mit übergibt. Auf der Google-Seite wird der
Benutzer dann gefragt, ob er der Applikation Zugriff auf die Daten gewähren
möchte, und loggt sich ein. Sind die Daten korrekt, schickt Google den Benutzer
zurück zur ursprünglichen Anwendung, wobei dieser ein Token übergeben wird.
Bei diesem Token handelt es sich um ein »One-Time-Use-Token«, das Sie nur für
einen Request nutzen können. Da Sie in den meisten Fällen aber mehrere Anfra-
gen benötigen werden, sollten Sie die eine Anfrage, die Ihnen zur Verfügung
steht, dazu nutzen, um aus dem One-Time-Use-Token ein Session-Token zu
machen, das dann für die gesamte Sitzung gültig ist. Mithilfe dieses Session-
Tokens kann die Anwendung sich bei allen folgenden Requests authentifizieren.
Diese Vorgehensweise bietet sich immer dann an, wenn viele verschiedene
Benutzer eine Anwendung nutzen sollen. Insbesondere dadurch, dass das Log-in
auf einer Seite von Google erfolgt, haben die Benutzer auch ein größeres Gefühl
der Sicherheit.
So viel zur Theorie. Die URL, die Ihre Applikation aufrufen muss, damit sich der
Benutzer authentifizieren kann, setzt sich aus verschiedenen Informationen
zusammen, die Sie an die statische Methode getAuthSubTokenUri (), die in der
Klasse Zend_Gdata_AuthSub definiert ist, übergeben sollten. Als ersten Parameter
übergeben Sie die URL Ihrer Applikation, damit Google den Benutzer auch wie-
der zurückschicken kann. Der zweite Parameter ist die URL der Google-Anwen-
dung, die genutzt werden soll, da Google natürlich wissen muss, für welchen
Dienst authentifiziert werden soll.
Danach folgen zwei optionale Parameter, die entweder 0 oder 1 als Wert haben.
Der erste von beiden legt fest, ob ein sicheres Token genutzt werden soll. Diese
Möglichkeit steht allerdings nur dann zur Verfügung, wenn Sie Ihre Applikation
vorher bei Google registriert haben.8 Somit wird an dieser Stelle üblicherweise
eine 0 zu finden sein. Der zweite Wert teilt Google mit, ob das One-Time-Use-
Token in ein Session-Token konvertiert werden darf. Hier wird also üblicher-
weise eine 1 stehen.
Der Methodenaufruf liefert Ihnen die URL zurück, auf die der Benutzer weiterge-
leitet werden muss. Das kann über einen Link oder direkt durch die Funktion
header() geschehen.
Nachdem der Benutzer sich angemeldet und zugestimmt hat, die Applikation zu
authentifizieren, wird er zurückgeleitet, wobei das One-Time-Use-Token über die
8 Möchten Sie Ihre Applikation registrieren lassen, finden Sie hier weitere Informationen:
http ://code .google .com/apis/accounts/RegistrationFor Web Apps .html
251
e-bol.net
5 | Webservices
URL übergeben wird. Das wiederum übergeben Sie an die statische Methode
getAuthSubSessionToken(), die es in ein Session-Token konvertiert. Ein solcher
Log-in-Mechanismus kann dann so aussehen:
< ? php
requi re_once('Zend/Gdata/AuthSub.php');
requi re_once(’Zend/Gdata/Calendar.php');
sessi on_start();
// Ist die Applikation noch nicht authentifiziert?
if (false —== isset($_SESSION['token']))
{
// Wurde ein Token über die URL übergeben?
if (false === isset($_GET[’token']))
{
// Kein Token über URL => dann Link ausgeben
// URL der Kalender-Applikation
$url_kalendar =’http://www.google.com/calendar/’.
'feeds/default/pri vate/ful1' ;
// URL der eigenen Applikation
$url_applikation = 'http://'. $_SERVER[’SERVER_NAME'].
$_SERVER[’REQUESTJJRI’]:
$sicheres_token = 0:
$session_token_ok = 1:
// Link ausgeben den der User anklicken kann
$google_url = Zend_Gdata_AuthSub:rgetAuthSubTokenUri (
$url_applikation, $url_kalendar,
$sicheres_token, $session_token_ok);
echo "Klicken Sie <a href='SgoogleUri’>hier</a>
um die Applikation zu authentifizieren.";
exit();
el se
{
// Konvertieren des One-Time-Use-Tokens in ein Session-Token
// und Speichern in der Session
try
$_SESSION[’token'] = Zend_Gdata_AuthSub::
getAuthSubSessi onToken($_GET['token']);
1
catch (Zend_Gdata_App_AuthException $e)
252
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata
5-5
die ('Konnte One-Time-Use-Token nicht in
Session-Token konvertieren
<br>Grund: '.$e->getMessage()):
I
// Client mit Token initialisieren
$client = Zend_Gdata_AuthSub::getHttpCli ent($_SESSION C’token']):
// Calendar-Client ableiten
$cal = new Zend_Gdata_Calendar($client);
Listing 5.12 Authentifikation mit AuthSub
In Abbildung 5.11 und Abbildung 5.12 sehen Sie die Google-Seiten, die der
Benutzer in diesem Beispiel zu sehen bekommt.
A O O https://www.google.com/accounts/AuthSub...efault/private/full&secure=0&session = l A
[ 4 A|M^jfcgjM^JK*|https://www.google.com/accounts/AuthSijbReqQj^Ct* alogger Q]
□□ Planet PHP (118) Apple (21)▼ Amazon eBay Yahoo’ News
Sie müssen sich anmelden, um den Dienst eines Drittanbieters für den Zugriff auf Ihr Konto zu autorisieren.
Melden Sie sich hier an:
Go gle Konto
E-Mail: jf-buch@netviser.de
Passwort: [.....|
□ Auf diesem Computer merken.
Anmelden )
Ich kann nicht auf .mein Konto zugr eifer
Abbildung 5.11 Log-in bei Google
Um den Benutzer bzw. die Applikation wieder auszuloggen, müssen Sie Google
mitteilen, dass das Token ungültig gemacht werden soll. Dazu ist die statische
Methode AuthSubRevokeToken() deklariert, der Sie einfach das Token überge-
ben können:
Zend_Gdata_AuthSub::AuthSubRevokeToken($_S ESS ION['token’]);
unset($_SESSION['token']);
253
e-bol.net
5 | Webservices
Abbildung 5.12 Information über die Weiterleitung
Aus Gründen der Übersichtlichkeit werde ich in den folgenden Kapiteln immer
eine Authentifikation via Client-Log-in verwenden, wobei ich auf die Prüfung
einer CAPTCHA-Exception verzichte.
Exception Handling bei Zend_Gdata
Die Gdata-Pakete verhalten sich beim Exception Handling ein wenig anders als
die anderen Klassen im Zend Framework. Aufgrund der Vielzahl der Klassen
wären so viele unterschiedliche Exceptions auch schlecht handhabbar.
Momentan nutzen die Gdata-Pakete die folgenden Exceptions:
► Zend_Gdata_App_AuthException
Diese Exception-Klasse wird immer dann genutzt, wenn das Log-in nicht
erfolgreich war, weil es nicht möglich war, sich mit der Kombination aus
Benutzernamen und Passwort anzumelden.
► Zend_Gdata_App_CaptchaRequi redException
Nutzen Sie ein Client-Log-in, kann es passieren, dass Google bei zu vielen Log-
in-Versuchen in zu kurzer Zeit die Nutzung eines CAPTCHA verlangt. In die-
sem Fall wirft Zend_Gdata die obige Exception.
254
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata | 5-5
► Zend_Gdata_App_HttpException
Eine Exception dieses Typs wird geworfen, wenn ein Fehler bei der Kommu-
nikation mit Google auftritt. Das kann beispielsweise dann passieren, wenn
Sie eine falsche URL nutzen.
► Zend_Gdata_App_Inval1dArgumentExcepti on
Dieser Typ von Exception begegnet Ihnen dann, wenn Sie bei einer Abfrage
falsche Parameter spezifizieren.
► Zend_Gdata_App_BadMethodCal1Exception
Diese Exception wird genutzt, wenn eine HTTP-Methode genutzt wurde, die
der aktuell genutzte Dienst nicht unterstützt. Dieser Typ von Exception sollte
nicht auftreten, solange Sie die vordefinierten Methoden nutzen.
5.5.3 Nutzung von Google Calendar
Mit Google Calendar bietet Google einen sehr umfangreichen Terminmanager
an, der über das Internet genutzt werden kann. Dank modernster Technik ist er
ähnlich komfortabel wie die Terminverwaltung von Outlook und ähnlichen
Tools. Sollten Sie sich noch nicht mit Google Calendar beschäftigt haben, wäre
das jetzt ein guter Zeitpunkt. Es dürfte es Ihnen leichter machen, die nachfolgen-
den Funktionen zu verstehen. Einen neuen Account können Sie unter
http://www.google.com/calendar anlegen.
Aufgrund der Komplexität der API kann ich sie in diesem Zusammenhang nicht
komplett erläutern. Das Kapitel wird Ihnen aber einen guten Einstieg bieten.
Sollten Sie weitere Informationen benötigen, dann ist die Dokumentation der
Google-API eine gute Anlaufstelle. Sie finden sie unter der Adresse http://code.
google. com/apis/calendar/developers_guide_protocol. html.
Auslesen von Events
Das Auslesen von Terminen, die im Kalender eingetragen sind, ist recht einfach.
Nach der Authentifikation müssen Sie der Klasse nur mitteilen, dass Sie die Ter-
mine auslesen wollen. Dabei erhalten Sie ein Feed-Objekt zurück. In diesem Fall
handelt es sich um ein Objekt der Klasse Zend_Gdata_Cal endar_EventFeed. Hier-
bei handelt es sich um eine Kind-Klasse der Klasse Zend_Gdata_Feed. Alle Funk-
tionalitäten, die für Sie wichtig sind, sind auch in dieser Eltern-Klasse realisiert.
Auch die Klasse der anderen Feeds, die Sie an anderer Stelle zurückerhalten, sind
von dieser Klasse abgeleitet, sodass Sie eine weitestgehend einheitliche API
haben.
255
e-bol.net
5 | Webservices
Die Klasse Zend_Gdata_Feed implementiert das SPL-Interface Iterator, sodass
Sie das entsprechende Objekt direkt in einer foreach-Schleife nutzen können.
Innerhalb der Schleife stehen dann die einzelnen Objekte für die einzelnen Ein-
träge zur Verfügung. In diesem Fall handelt es sich dabei um die Klasse Zend_
Gdata_Cal endar_EventEntry. Auch hier gilt, dass es eine Elternklasse gibt, die
einen Großteil der Methoden deklariert. In diesem Fall heißt sie Zend_Gdata_
Entry.
In der Schleife laufen Sie nun über die einzelnen Einträge aus dem Kalender. Wie
bei einem Feed üblich, handelt es sich dabei um eigenständige XML-Knoten, die
weitere Elemente beinhalten. Um die dort enthaltenen Informationen auszule-
sen, gibt es mehrere Möglichkeiten. Möglichkeit eins ist, dass Sie das Entry-
Objekt nutzen und das fragliche XML-Element einfach als Eigenschaft anhängen.
Das könnte beispielsweise so aussehen:
foreach ($eintraege as Seintrag)
{
echo Seintrag->title."<br>";
1
Diese Schleife würde also von jedem Entry-Objekt die Eigenschaft title bzw.
den Inhalt des XML-Elements title ausgeben. Die andere Möglichkeit ist, dass
Sie die Methode getTi tl e() aufrufen, die Ihnen auch den Inhalt zurückgibt. Die
Methode getTi tl e() gehört zu den magischen Methoden, die das System kennt.
Es gibt eine ganze Menge dieser magischen Methoden, die oft auch genutzt wer-
den, um neue Objekte abzuleiten. Diese Factoiys beginnen dann jeweils mit new,
wie Sie noch sehen werden.
Nun stellt sich die interessante Frage, woher man überhaupt weiß, dass es das
Element title gibt. Die Frage ist leider nur mithilfe der Google-Dokumentation
zu beantworten. Dort finden Sie für die verschiedenen Dienste Informationen,
wie die XML-Nachrichten aufgebaut sind. Sie finden Beispiele wie dieses:
<entry>
<id>http://www.google.com/calendar/feeds/jo@gmai1.com/pri vate
/ful1</i d>
<publi shed>2006-03-30T22:00:00.000Z</publi shed>
<updated>2006-03-28T05:47:31.0 0 0Z</updated>
<title type=’text'>Lunch with Darcy</title>
Ccontent type='text'>Lunch to discuss future plans.</content>
<link rel='self' type=*application/atom+xml’
href=’http://www.google.com/calendar/feeds/jo@gmai1.com/pri vate-
magi cCooki e/full/entryID'X/link>
<author>
256
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata | 5-5
<name>Jo March</name>
<emai1>jo@gmai1.com</emai 1 >
</author>
<gd:transparency
val ue='http://Schemas.google.eom/g/2005#event.opaque'>
</gd:transparency>
<gd:eventStatus
val ue='http://Schemas.google.com/g/2005#event.confi rmed’>
</gd:eventStatus>
<gd:when startTime=’2006-03-30T22:00: 00.000Z'
endT ime='2006-03-30T23:00:00.000Z’></gd:when>
<gd:where></gd:where>
</entry>
Dieses Beispiel aus der Google-Dokumentation habe ich deutlich gekürzt, aber
die wichtigsten Elemente können Sie erkennen.
Ein wenig problematisch ist die Tatsache, dass einige Elemente in Form von
Arrays bereitgestellt werden und andere nicht. Leider konnte ich weder in der
Dokumentation von Google noch in der des Zend Frameworks einen Hinweis
finden, wann ein Element ein Array ist und wann nicht. Hier kann ich Sie leider
nur darauf verweisen, dass Sie dies ausprobieren. So werden die Elemente
<author> oder <gd :when > beispielsweise als Array mit einem Element vorgehal-
ten. Um nun einen Kalender komplett auszulesen, könnten Sie folgendermaßen
vorgehen:
requi re_once 'Zend/Gdata/ClientLogin.php';
requi re_once ’Zend/Gdata/Calendar.php';
require_once 'Zend/Gdata/Calendar/EventQuery.php';
require_once ’Zend/Date.php’;
header ('Content-Type: text/html; charset=utf-8’);
Semail = 'zf-buch@netviser.de':
Spasswd = 'total geheim';
Sservice = 'ei':
$cli ent = null;
Ssource = Zend_Gdata_ClientLogin::DEFAULT_SOURCE;
try
{
tclient = Zend_Gdata_ClientLogin::getHttpCl ient( $emai 1,
$passwd, tservice, $client, $source):
}
catch (Exception $e)
257
e-bol.net
5 | Webservices
die ('Folgender Fehler trat auf: ' . Se->getMessage()):
Seal = new Zend_Gdata_Calendar(Seiient):
Seintraege = Seal->getCalendarEventFeed():
echo "<table>";
foreach (Seintraege as Seintrag)
{
echo "<tr><td>Titel: </td>";
echo "<td>".Seintrag->title."</td></tr>";
echo "<tr><td>Ort: </td>";
echo "<td>". Sei ntrag->where[O]. "</tdX/tr>";
echo "<trXtd>Kommentar: </td>":
echo "<td>".Seintrag->content."</tdX/tr>";
echo "<trXtd>Eingetragen von: </td>":
echo "<td>".Seintrag->author[0]->name."</tdX/tr>";
echo "<trXtd>Startzeit: </td>":
Sdat_start = (string)
new Zend_Date(Seintrag->when[O]->startTime,
Zend_Date::ISO_8601, 'de_DE*);
echo "<td>". $dat_start. "</tdX/tr>”;
echo "<trXtd>Endzeit: </td>";
Sdat_ende = (string) new Zend_Date($eintrag->when[O]->endTime,
Zend_Date::ISO_8601, 'de_DE*);
echo "<td>". $dat_ende. "</tdX/tr>";
echo "<trXtd>Eingetragen: </td>";
Sdat_eingetragen = (string) new Zend_Date($eintrag->published,
Zend_Date::ISO_8601, 'de_DE*):
echo "<td>". $dat_ei ngetragen. "</tdX/tr>";
echo "<trXtd>Aktualisiert: </td>":
Sdat_aktualisiert = (string) new Zend_Date(Seintrag->updated,
Zend_Date::ISO_8601, 'de_DE*):
echo "<td>". $dat_aktual i siert. "</tdX/tr>";
echo XtrXtd col spn=’2' > </tdX/tr>";
}
echo "</table>";
Listing 5.13 Auslesen von Kalenderdaten
258
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata | 5-5
Da die Uhrzeiten in einem ISO 8601-kompatiblen Format vorliegen, habe ich
hier der Einfachheit halber zur Klasse Zend_Date gegriffen, um sie zu konvertie-
ren. Die Ausgabe der Kalenderdaten sehen Sie in Abbildung 5.13.
ry O http://127.0.0.1...uch/CData/l_l.php CD
w Q
Titel: Flug nach Berlin Ort: Hannover Flughafen Kommentar Flugnummer LH-42 Eingetragen von: Carsten Möhrkc Startzeit 15.012008 20:00:00 Endzeit 15.01.2008 22:00:00 Eingetragen: 05.01.2008 19:27:05 Aktualisiert: 05.01.2008 21:25:16
Titel: Lottoschein abgeben Ort: Lottoladcn Kommentar Eingetragen von: Carsten Möhrkc Startzeit 13.01.2008 00:00:00 Endzeit: 14.01.2008 00:00:00 Eingetragen: 05.01.2008 19:25:25 Aktualisiert: 05.012008 2124:54
Titel: Zahnarzt Ort Bielefeld Kommentar Vorher die Valium nehmen! Eingetragen von: Carsten Möhrkc Startzeit 14.01.2008 15:00:00 Endzeit: 14.012008 17:00:00 Eingetragen: 05.01.2008 20:27:30 Aktualisiert: 05.01.2008 20:27:30 — ▼
Abbildung 5.13 Ausgabe der Kalenderdaten
Wie Sie sehen, ist der Zugriff auf die Daten nicht sonderlich kompliziert. Die ein-
zelnen Elemente der Einträge liegen in Form von Eigenschaften bzw. Objekten
vor. Einige der Eigenschaften sind allerdings etwas komplexer als andere. So ent-
hält das Element when beispielsweise zwei Informationen in Form von Attribu-
ten: Die Start- und die Endzeit des Termins. Diese komplexeren Elemente sind
innerhalb von Zend_Gdata als eigene Klassen umgesetzt. Klassen, die für alle
Dienste relevant sind, finden Sie im Unterverzeichnis Zend/Gdata/Extension.
Klassen, die nur für bestimmte Dienste genutzt werden, finden Sie im jeweiligen
Unterverzeichnis des Dienstes. Das heißt, die Klassen für Calendar finden Sie
259
e-bol.net
5 | Webservices
unter Zend/Gdata/Calendar/Extension. Die Tatsache, dass es sich dabei um
eigene Klassen handelt, ist eigentlich nicht weiter wichtig. Ich wollte es nur
erwähnt haben, damit Sie in den Klassen ein wenig stöbern, um noch weitere
Funktionalitäten zu entdecken.
Abbildung 5.14 Der Kalender bei Google
Ein wichtiger Punkt sind gelöschte Termine. Diese finden im letzten Beispiel
noch keine Beachtung. Termine, die Sie gelöscht haben, werden nicht sofort
gelöscht. Sie werden zunächst nur als gelöscht markiert. Auch wenn »gelöschte«
Termine bei normalen Abfragen nicht ausgegeben werden, so kann es bei
bestimmten Abfragen doch passieren, dass sie wieder auftauchen. Möchten Sie
sicherstellen, dass solche Termine nicht ausgegeben werden, müssen Sie zusätz-
lich die Eigenschaft eventStatus abfragen. Ist hier der Wert http://Schemas.
google.eom/g/2005#event.canceled enthalten, ist der Termin gelöscht. Ein nor-
maler Termin enthält den Wert http://schemas.google.eom/g/2005#event.
confi rmed.
260
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata | 5-5
Nach diesem Einstieg möchte ich einen kleinen Schritt zurückgehen. Bei dem
vorhergehenden Beispiel habe ich einfach nur einen Kalender ausgelesen, habe
dabei aber nicht beachtet, welcher Kalender das ist.
Auslesen verschiedener Kalender
Haben Sie einen Account bei Google Calendar, so können Sie mehrere Kalender
anlegen. Somit sind Sie in der Lage, private und geschäftliche Termine zu trennen
oder beispielsweise einen eigenen Kalender für Geburtstage zu führen. Fragt man
Google Calendar so ab, wie oben beschrieben, erhalten Sie den »Standard-Kalen-
der«, den Google per Default für Sie anlegt. Darüber hinaus legt Google standard-
mäßig aber noch einen Kalender für Geburtstage an. Um diesen abzufragen,
benötigen Sie eine andere URL für den Feed. Diese URL können Sie, sobald Sie
die entsprechenden Informationen haben, selbst konstruieren.
Dazu müssen Sie zunächst die Informationen zu den Kalendern abfragen. Hierfür
ist die Methode getCalendarl_istFeed() vorgesehen. Sie liefert Ihnen ein
Objekt, in dem die Informationen zu den einzelnen Feeds enthalten sind. Ein
Eintrag in diesem Feed hat einen Aufbau wie diesen:
<entry>
<id>http://www.google.com/calendar/feeds/default/alIcalendars/full/
user%40gmail.com</id>
<publi shed>2007- 07 -11T22:10:30.257Z</publi shed>
<updated>2007-07-HT21:46:35.000Z</updated>
<title type="text">My Primary Calendar</title>
<summary type="text">A primary calendar ....</summary>
<author>
<name>Coach</name>
<emai1>user@gmai1.com</emai1>
</author>
<gCal:timezone value="America/Los_Angeles"/>
<gCal:hidden value="false"/>
<gCal:color value="#2952A3"/>
<gCal:seiected value="true"/>
<gCal :accesslevel value=''owner"/>
<gd:where valueString="Mountain View"/>
</entry>
Interessant dabei ist das Element id. Es enthält im letzten Teil eine eindeutige
Kennung für den entsprechenden Kalender. Mit dieser Kennung, die im Google-
Manual auch als »UserlD« bezeichnet wird, kann man die URL des entsprechen-
den Kalender-Feeds erstellen. Bei dem Hauptkalender entspricht diese ID der
E-Mail-Adresse, mit der Sie sich anmelden. Leider ist zurzeit anscheinend noch
261
e-bol.net
5 | Webservices
keine Methode in Zend_Gdata vorhanden, um diese ID entsprechend aufzuberei-
ten, sodass dies manuell geschehen muss.
Nachdem man die ID extrahiert hat, könnte man die URL manuell erstellen.
Glücklicherweise ist das aber nicht nötig. Die Klasse Zend_Gdata_Calender_
EventQuery hilft Ihnen dabei. Eine solche Queiy-Klasse steht allen GData-Anwen-
dungen zur Verfügung. Die Klasse ist ein Kind der Klasse Zend_Gdata_Query, wel-
che die Funktionalitäten definiert, die in allen Klassen benötigt werden. Ein
Objekt der Klasse Zend_Gdata_Cal ender_EventQuery erhalten Sie, wenn Sie aus
dem Kalender-Objekt heraus die Methode newEventQuery() aufrufen.
Damit die Methode die korrekte URL für den Feed konstruieren kann, benötigt
sie mindestens die User-ID, die Visibility und den Projection-LeveL Die User-ID
ist derjenige Teil, der aus der oben erwähnten ID extrahiert wird. Die Visibility,
also die Sichtbarkeit, bezeichnet, ob Sie nur die Einträge erhalten wollen, die für
alle sichtbar sind, oder ob Sie die Einträge haben wollen, die für Sie als Eigentü-
mer des Kalenders sichtbar sind. Im ersten Fall würden Sie der Methode setVi -
sibilityO 'public' übergeben und im zweiten Fall, der der übliche sein
dürfte, dagegen 'private'. Der Projection-Level legt fest, wie viele Daten Sie
von den Einträgen auslesen wollen. Hier dürfte es üblich sein, der Methode set-
Projection() den Wert 'full' zu übergeben, um alle Daten zu erhalten. Mög-
lich wäre allerdings auch der Wert ’basic'. Nachdem Sie das Objekt entspre-
chend vorbereitet haben, können Sie auch schon den Kalender abfragen, indem
Sie das Objekt als Parameter an die Methode getCal endarEventFeecK ) überge-
ben. Das fertige Listing zum Abfragen aller Kalender könnte beispielsweise so
aussehen:
requi re_once 'Zend/Gdata/ClientLogin.php';
requi re_once 'Zend/Gdata/Calendar .php';
require_once 'Zend/Gdata/Calendar/EventQuery.php';
require_once ’Zend/Date.php’;
header ('Content-Type: text/html; charset=utf-8’);
Semail = 'zf-buch@netviser.de':
Spasswd = 'total geheim*;
Sservice = 'ei' ;
try
{
$client = Zend_Gdata_
ClientLogin::getHttpClient(Semai1, Spasswd, $service):
}
catch (Exception $e)
(
262
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata | 5-5
die ('Folgender Fehler trat auf: ' . Se->getMessage()):
// Auslesen der Kalender
Seal = new Zend_Gdata_Calendar(Seiient):
Sal1e_kalender = Seal->getCalendarListFeed():
foreach (Sal1e_kalender as Skalender)
{
echo *<table bgcolor="'.Skaiender->color. ;
echo '<tr><td><b>Titel des Kalenders:</bX/td>':
echo '<td>' .Skalender->title.'</tdX/tr>';
// User-ID extrahieren:
$kal_id = $kalender->id;
$user_id = substr(strrchr($kal_id, 1 );
echo '<trXtdXb>extrahierte User-ID:</bX/td>':
echo * <td> ’. $user_id.'</tdX/tr>';
echo '<trXtdXb>Autor:</bX/td>';
echo '<td>'.Skaiender->author[0]->name. ’</tdX/tr>';
// Einträge im Kalender abfragen
Squery = Seal->newEventQuery();
// User-ID setzen
Squery->setUser(Suser_i d):
// Sichtbarkeit setzen
Squery->setVisibi 1ity('private');
// Umfang der Daten definieren
Squery->setProjection(’ful1'):
Seintraege = Seal->getCalendarEventFeed(Squery);
foreach (Seintraege as Seintrag)
{
echo "<trXtd>Titel: </td>";
echo "<td>" .$eintrag->title."</tdX/tr>";
echo "<trXtd>Startzeit: </td>";
$dat_start = (string) new Zend_Date(
Seintrag->when[O]->startTime,
Zend_Date::ISO_8601, 'de_DE'):
echo "<td>”. $dat_start. "</tdX/tr>";
echo "<trXtd>Endzeit: </td>":
$dat_ende = (string) new Zend_Date(
$eint rag->when[O]->endTime,
Zend_Date::ISO_8601, 'de_DE'):
echo "<td>”. $dat_ende. "</tdX/tr>";
echo "<trXtd colspn='2'> </tdX/tr>";
263
e-bol.net
5 | Webservices
}
echo '</table>';
Listing 5.14 Ausgabe aller Kalender eines Benutzers
Abbildung 5.15 Ausgabe aller Kalenderdaten
Wie Sie in Abbildung 5.15 sehen, habe ich die Ausgabe der einzelnen Termine
ein wenig gekürzt. Die Kalender sind in diesem Fall farblich hinterlegt. Dabei
handelt es sich um dieselben Farben, die auch bei der Darstellung bei Google
genutzt werden. Die Tatsache, dass bei dem Kalender »Geburtstage« kein Autor
angegeben ist, liegt daran, dass ich ihn bei Google nicht eingepflegt habe. Bei
dem Termin in der Geburtstagsliste handelt es sich um ein ganztägiges Ereignis,
genau wie bei dem Termin »Lottoschein abgeben« im Hauptkalender. Daraus
resultieren die Start- und die Endzeit.
264
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata | 5-5
Nutzung des Event-Query-Objekts
Das EventQuery-Objekt kann aber noch mehr. Die Methoden, die Sie bisher ken-
nengelernt haben, sind spezifisch für die Kalenderabfragen. Allerdings sind im
Query-Objekt einige Abfragen enthalten, die in allen GData-Bereichen zur Verfü-
gung stehen. Das ist beispielsweise die Möglichkeit, mit setQueryO eine
Abfrage, sprich eine Volltextsuche, zu definieren. Das heißt, wenn die Zeile
Squery->setQuery('Zahnarzt');
ergänzt wird, wird nur der eine Termin gefunden, in dem das Wort »Zahnarzt«
vorkommt. Dabei handelt es sich übrigens nicht um eine Substring-Suche. Das
heißt, eine Suche nach »Zahn« würde keinen Termin zurückliefern.
Eine weitere Möglichkeit, die alle Query-Objekte unterstützen, ist die maximale
Anzahl der Treffer, die Sie mit der Abfrage erhalten, einzuschränken. Das können
Sie mit setMaxResul ts() machen. Damit Sie durch die Gesamtzahl der Treffer
blättern können, ist es natürlich auch möglich festzulegen, mit welchem Treffer
Sie beginnen wollen. Dafür ist die Methode setStart!ndex() vorgesehen.
Hilfreich kann es auch noch sein, das Veröffentlichungsdatum bzw. das Aktuali-
sierungsdatum einzuschränken. Dafür sind die Methoden setPubl1shedMin(),
setPublishedMax(), setUpdatedMin() und setUpdatedMax() vorgesehen. Sie
bekommen einen Timestamp im RFC 3339-Format übergeben und schränken die
Ergebnismenge entsprechend ein. Das RFC 3339-Format ist weitestgehend kom-
patibel mit ISO 8601, sodass Sie auf die entsprechenden Funktionalitäten von
Zend_Date zurückgreifen können.
Nur für die Kalenderabfragen gilt, dass Sie die Einträge sortieren können. Stan-
dardmäßig werden die Einträge nach dem Datum der letzten Veränderung sor-
tiert. Mit der Methode setOrderBy() können Sie festlegen, ob nach Startzeit des
Termins ’ starttime' oder nach dem Datum der letzten Änderung 1 lastmodi-
f i ed ’ sortiert werden soll. Die Richtung, in der die Daten sortiert werden sollen,
also absteigend oder aufsteigend, können Sie mit der Methode setSortOrder()
festlegen. Dieser übergeben Sie entweder ’ascending' oder ' a ’ für eine aufstei-
gende Sortierung bzw. 'descending' oder ' d ' für eine absteigende Sortierung.
Darüber hinaus sind noch weitere Funktionalitäten definiert, die Sie bitte der
Dokumentation der Methode Zend_Gdata_Cal endar_EventQuery entnehmen.
Neue Einträge anlegen
Nachdem Sie nun wissen, wie Sie Einträge auslesen, stellt sich die Frage, wie
neue Einträge im Kalender angelegt werden. Auch das ist recht einfach zu erledi-
gen. Zunächst benötigen Sie ein Event En try-Objekt. Dieses muss dann nur mit
265
e-bol.net
5 | Webservices
den entsprechenden Informationen bestückt werden und kann dann zu Google
übertragen werden.
Die wichtigsten Dinge, die bei einem Termin vorhanden sein sollten, sind natür-
lich der Titel des Termins, der Ort sowie die Uhrzeit. Eine kleine Beschreibung ist
sicher auch noch hilfreich. Die entsprechenden Eigenschaften kennen Sie ja
schon vom Auslesen der Daten. Das heißt, die Eigenschaft title, die ausgelesen
wurde, muss hier wieder mit einem Wert, genauer gesagt einem Objekt belegt
werden. In diesem Fall handelt es sich um ein Objekt der Klasse Zend_Gdata_
App_Extension_Title, das durch eine magische Factory namens newTitleO
angelegt wird. Gleiches gilt für die Beschreibung, die der Eigenschaft content
zugewiesen wird, wobei das dazugehörige Objekt durch die Methode newCon-
tent() erstellt wird.
Gleiches gilt grundsätzlich auch für die Eigenschaften where und when, welche die
Zeit und den Ort enthalten. Allerdings ist hier zu beachten, dass die Eigenschaf-
ten mit Arrays bestückt werden müssen. Die Objekte, die mit newWhen() und
newWherei) abgeleitet werden, müssen somit in Form eines indizierten Array an
die Eigenschaft übergeben werden.
Ein komplettes Beispiel, um einen neuen Termin anzulegen, finden Sie in Listing
5.15:
Seal = new Zend_Gdata_Calendar(Seiient):
// Neues Event-Objekt ableiten
$event= Seal->newEventEntry();
// Titel des Termins anlegen
Stext = utf8_encode(’Essen im Möpken');
Stitle = Seal->newTitle(Stext);
$event->title = Stitle;
// Beschreibung festlegen
Stext = utf8_encode(’Den guten Anzug anziehen!');
Scontent = Seal->newContent(Stext);
$event->content = Scontent:
// Ort festlegen
Stext = utf8_encode(’Schloß Neuhaus'):
Swhere = Seal->newWhere(Stext):
$event->where = array(Swhere):
// Zeiten festlegen
Swhen = Seal->newWhen();
266
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata | 5-5
// Zeiten als Zend_Date-Objekte anlegen
Sstart = new Zend_Date('18.01.2008 20:00:00’,'de_DE’):
Sende = new Zend_Date(’18.01.2008 22:00:00',’de_DE');
// Nach ISO konvertieren und den Eigenschaften zuweisen
$when->startTime = $start->get(Zend_Date::ISO_8601);
$when->endTime = $ende->get(Zend_Date::ISO_8601);
$event->when = array(Swhen);
// Termin speichern
Seal->insertEvent($event);
Listing 5.15 Anlegen eines neuen Termins
Ich denke, das Listing ist recht einfach zu verstehen. Ein paar Kleinigkeiten
möchte ich aber dennoch erwähnen. Zu Beginn sollten Sie immer darauf achten,
dass die Daten UTF-8-codiert sind. Nutzen Sie Texte, die nicht UTF-8-codiert
sind, macht sich das nicht sofort bemerkbar. Die Klasse wirft keine Exception;
Google verwirft die Daten lediglich. Das heißt, es könnte unter Umständen ein
Termin angelegt werden, der leer wäre.
Der zweite Punkt, auf den ich hinweisen möchte, ist, dass die Namen der Fac-
tory-Methoden immer den Namen der Eigenschaften mit einem vorangestellten
new entsprechen. Wenn Sie dies stets bedenken, haben Sie eine gute Chance, bei
der Vielzahl der Methoden den Überblick zu behalten.
Der dritte und letzte Punkt ist, dass Sie die Texte, die den Eigenschaften zugewie-
sen werden sollen, direkt an den Konstruktor übergeben sollten. Zwar gibt es
auch Methoden, mit denen Sie die Texte setzen können, aber das führt schnell
dazu, dass Sie die falsche Methode nutzen. Wollen Sie die Texte über Methoden
festlegen, setzt das eine recht gute Kenntnis der Google-API voraus.
Wie der Termin im Google-Kalender dargestellt wird, sehen Sie in Abbildung
5.16. Auf diese Art und Weise wird der Termin im Standard-Kalender angelegt.
Möchten Sie einen anderen Kalender ansprechen, kommt das EvenQuery-Objekt
wieder ins Spiel, das Sie ja schon bei den Abfragen kennengelernt haben. Bei den
Abfragen wurde das Objekt komplett übergeben. Im Hintergrund wurde aber im
Endeffekt nur die URL ausgelesen, die mithilfe des Objekts konstruiert wurde.
Auch beim Speichern eines Termins muss die URL des entsprechenden Kalender-
Feeds konstruiert werden. Die Vorgehensweise ist dabei dieselbe, nur muss hier
die URL manuell mit der Methode getQueryllrl () ausgelesen und dann als zwei-
ter Parameter an die Methode i nsertEvent() übergeben werden.
267
e-bol.net
5 | Webservices
Coogle Kalender
> «i http://www.google.com/calendar/render ▼ G ’ google calendar
Qggft EW Kalender Tgxt & Ta<?g*gr Fgtgs Wertergj>
Google
Kalender Cz bcta
zf-buch@netviser.de | Einstellungen | Hilfe | Abmelden *
Öffentliche Kalender durchsuchen | Meine Kalender durchsuchen
Termin einrichten
• Zurück zum Kalender Speichern | Abbrechen | Löschen |
Weitere Aktionen ..
« Januar 2008 •
S M D M D F S
I 23 24 25 26 27 28 29
130 31 1 2 3 4 5
I 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31 1 2
13 4 5 6 7 8 9
Was Essen im Möpken Gäste
Wann Fr 18. Jan. 20:00 - Fr 18. Jan. 22:00 O Gast» hinaufüflen
Wo Schloß Neuhaus Karte
Kalender | Carsten Möhrke 3d
Beschreibung Den guten Anzug anziebenl
Gäste können
F Andere
einladen
F
Gästeliste
ansehen
▼ Hinzufügen T
Meine Kalender
Carsten Möhrke
Geburtstage
Diskussionsforum für diesen Termin
□ Kommentar hinzufügen ▼ Optionen
Keine Einträge vorhanden. Besuchen Sie stattdessen Erinnerung
Weitere Kalender Google News . KrtwEnnrefurger dngriehtet
9/10 O YSIow 1.671s
Abbildung 5.16 Der neu angelegte Termin bei Google
Da in diesem Beispiel nur der Geburtstagskalender als weiterer Kalender zur Ver-
fügung steht, sollte der Termin auch ganztägig angelegt werden. Um das zu errei-
chen, übergeben Sie die Daten von zwei aufeinanderfolgenden Tagen, wobei Sie
keine Uhrzeit angeben. Einen neuen Eintrag im Geburtstagskalender anzulegen,
könnte also so aussehen:
// Neues Event-Objekt ableiten
$event= Seal->newEventEntry();
// Titel des Termins anlegen
Stext = utf8_encode(’Geburtstag von Anika'):
Stitle = $cal->newTitle(Stext):
$event->title = Stitle;
// Zeiten für ganztägigen Termin festlegen
Swhen = Seal->newWhen();
Swhen->startTime = '2008-03-25':
Swhen->endTime = '2008-03-26':
$event->when = array(Swhen);
// Query-Objekt ableiten
Squery = Seal->newEventQuery();
// User-ID des Geburtstagskalenders
268
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata | 5-5
$query->setUser(
’OfvJ32us2rh0b4t2ntd9sr7b9g%40group.calendar.google.com');
Squery->setVisibility('private');
Squery->setProjection('ful1');
// URL auslesen
Surl = Squery->getQueryUrl();
// Termin speichern
Seal->insertEvent(Sevent, Surl);
Listing 5.16 Anlegen eines Geburtags
Geburtstage und einige andere Termine haben eine Eigenschaft, die sie von nor-
malen Terminen unterscheidet. Und zwar wiederholen sie sich jährlich. Sie kön-
nen auch solche Termine anlegen. Die Vorgehensweise ist dabei allerdings ein
wenig anders. Und zwar übergeben Sie die Zeiten nicht in Form eines when-
Objekts, sondern in Form eines recurrence-Objekts. Leider gibt es hier noch
keine schönen Zugriffsmethoden. Daher müssen Sie die Regeln für die Wieder-
holung des Termins von Hand erstellen. Dabei geben Sie den Beginn des Ter-
mins, das Ende des Termins und das Wiederholungsmuster an. Der Aufbau die-
ses Regelwerks ist in RFC 2445 (http://www.ietf.org/rfc/rfr2445.txt) definiert.
Um den Termin, wie er oben angelegt wurde, jährlich zu wiederholen, würde im
obigen Code die Festlegung der when-Bedingung entfernt. Die Zeilen würden
durch die folgenden ersetzt:
Srecurrence = "DTSTART;VALUE=DATE:20070325\r\n" .
"DTEND;VALUE=DATE:20070326\r\n" .
”RRULE:FREQ=YEARLY\r\n";
$event->recurrence = Seal->newRecurrence(Srecurrence):
Hierbei definiert DTSTART den Starttermin und DTEND den Endtermin des ersten
Termins. Da es sich um ein ganztägiges Ereignis handelt, wird keine Zeit angege-
ben. Mit RRULE wird die Recurrence-Rule, also diejenige Regel definiert, nach
welcher sich der Termin wiederholt. FREQ=Y EARLY definiert eine jährliche Wie-
derholung. Sie könnten hier auch eine monatliche Wiederholung oder eine Wie-
derholung zu einem bestimmten Wochentag angeben. Weitere Informationen
entnehmen Sie bitte dem RFC.
Verändern von Kalendereinträgen
Natürlich haben Sie auch die Möglichkeit, einen Eintrag zu verändern. Auch das
ist recht einfach zu bewerkstelligen. Die Vorgehensweise dabei ist, dass Sie das
Objekt des entsprechenden Eintrags auslesen, die neuen Werte darin speichern
und das Ganze dann wieder zurück auf den Server speichern.
269
e-bol.net
5 | Webservices
So weit, so gut. Allerdings wurde in den vorhergehenden Beispielen immer ein
kompletter Kalender ausgelesen. Natürlich könnte man jetzt auf die Idee kom-
men mitzuzählen, welches Objekt geändert werden soll, aber das wäre recht
umständlich. Sie können aber auch einzelne Einträge gezielt auslesen. Jeder Ein-
trag hat, wie Sie das auch schon von den Kalender-Feeds kennen, eine eigene
URL, unter der er angesprochen werden kann. Diese URL können Sie über die
Eigenschaft i d auslesen. Glücklicherweise müssen Sie diese URL nicht weiter zer-
legen, sondern können sie direkt verwenden. Eine solche URL können Sie an die
Methode getCal endarEventEntry() übergeben, welche dann den einzelnen Ein-
trag ausliest und Ihnen ein Event-Objekt zurückgibt. Dieses können Sie anschlie-
ßend verändern und mithilfe der Methode save(), welche Sie aus dem Event-
Objekt heraus aufrufen, wieder abspeichern:
// Verbindungsaufbau etc.
Seal = new Zend_Gdata_Calendar(Seiient):
// URL eines Termins der aus der Eigenschaft id ausgelesen wurde
SeventURL = "http://www.google.com/calendar/feeds/zf-
buch%40netvi ser.de/private/full/uitntmnOqßealn930bpc93rlcg":
/// Einzelnes Event auslesen
Sevent = Seal->getCalendarEventEntry(SeventURL):
// Neuen Titel setzen
Stext = utf8_encode(’Flug nach Hamburg, nicht nach München'):
$event->title = Seal->newTitle($text):
// Aktualisiertes Event wieder speichern
$event->save();
Listing 5.17 Ändern eines Eintrags
Sie können sonst also genau so vorgehen, als würden Sie den Termin neu anle-
gen.
Löschen von Kalendereinträgen
Das Löschen eines Eintrags im Kalender ist dem Editieren recht ähnlich. Auch in
diesem Fall sollten Sie zunächst das entsprechende Eintrags-Objekt auslesen.
Danach können Sie die Methode del ete() aufrufen, die den Eintrag löscht.
Seal = new Zend_Gdata_Calendar(Seiient):
SeventURL = "http://www.google.com/calendar/feeds/zf-
buch%40netvi ser.de/private/full/uitntmnOqßealn930bpc93rlcg":
Sevent = Seal->getCalendarEventEntry(SeventURL);
$event->delete();
Listing 5.18 Löschen eines Eintrags
270
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata | 5-5
5.5.4 Nutzung von Google Spreadsheets
Unter der URL http://docs.google.com bietet Google die Möglichkeit, eine Text-
verarbeitung, ein Präsentationsprogramm sowie eine Tabellenkalkulation zu nut-
zen. Sollten Sie keine sonderlich anspruchsvollen Büroaufgaben mit einem
Office-Paket erledigen müssen, dann ist die Nutzung der Google-Angebote eine
echte Alternative. Zwar sind die Möglichkeiten nicht ganz so umfangreich wie
bei einem echten Office-Paket, aber der Funktionsumfang ist schon recht beein-
druckend.
Das Zend Framework unterstützt zurzeit nur den Zugriff auf Spreadsheets, also
die Tabellenkalkulation. Innerhalb der Tabellenkalkulation können Sie verschie-
dene Arbeitsmappen (die Spreadsheets) verwalten. Ein Spreadsheet beinhaltet
jeweils einzelne Tabellenblätter, die Worksheets. Innerhalb eines Worksheets
sind schließlich die einzelnen Felder mit den Daten zu finden.
Auch hier möchte ich Ihnen empfehlen, einen Blick in die Google-Dokumenta-
tion zur Spreadsheets-API zu werfen, die Sie unter der folgenden Adresse finden:
http://code.google. com/apis/spreadsheets/developers_guide_protocol. h tml
Der Zugriff auf die Daten erfolgt wieder über Streams, wie Sie es schon kennen-
gelernt haben. Das Anlegen neuer Spreadsheets ist zurzeit noch nicht möglich.
Auslesen von Spreadsheets
Bevor Sie ein Spreadsheet auslesen können, ist es hilfreich zu wissen, welche
Spreadsheets überhaupt zur Verfügung stehen. Die Vorgehensweise ist hier der
bei dem Auslesen der Kalender sehr ähnlich. Sie können einen Feed mit allen
Spreadsheet-Einträgen auslesen, indem Sie die Methode getSpreadsheetFeed()
aus einem Zend_Gdata_Spreadsheets-Objekt heraus aufrufen. Das Zend_Gdata_
Spreadsheets-Objekt wird genau so abgeleitet wie das entsprechende Kalender-
Objekt. Allerdings müssen Sie beim Ableiten des HTTP-Clients die Kennung cl
durch wi se ersetzen.
Die Methode liefert Ihnen ein Objekt der Klasse Zend_Gdata_Spreadsheets_
SpreadsheetFeed zurück, das Sie direkt an eine foreach-Schleife übergeben kön-
nen. Bei jeder Iteration wird dann ein Objekt der Klasse Zend_Gdata_
Spreadsheets_SpreadsheetEntry ausgelesen. Die dahinterliegende XML-Struk-
tur finden Sie in der API-Dokumentation von Google. Die wichtigsten Eigen-
schaften werden auch in Listing 5.19 verwendet:
requi re_once 'Zend/Gdata/ClientLogin.php';
require_once 'Zend/Gdata/Spreadsheets.php';
require_once ’Zend/Date.php’;
271
e-bol.net
5 | Webservices
header ('Content-Type: text/html; charset=utf-8’):
Semail = 'zf-buch@netviser.de':
Spasswd = 'total geheim*;
Sservi ce = 'wi se’:
try
{
$client = Zend_Gdata_ClientLogin::getHttpClient(
$email, tpasswd, $service):
}
catch (Exception $e)
{
die ('Folgender Fehler trat auf: ' . $e->getMessage());
// Spreadsheets Client ableiten
Sspread = new Zend_Gdata_Spreadsheets($client);
// Feed auslesen
Sspreadsheets = $spread->getSpreadsheetFeed():
echo ’<table>';
foreach ($spreadsheets as $spreadheet)
{
echo '<tr>';
echo '<td>Titel</td>':
echo '<td>'.$spreadheet->title.'</td>’;
echo '</tr>';
echo '<tr>';
echo '<td>Autor</td>':
echo ’<td>’.$spreadheet->author[OJ->name.’</td>' :
echo '</tr>';
echo '<tr>';
echo '<td>Geändert</td>':
// Datum mit Zend_Date konvertieren
$datum = new Zend_Date($spreadheet->updated.
Zend_Date::ISCL8601, 'de_DE');
echo '<td>'.$datum.'</td>':
echo '</tr>';
echo '<tr><td colspan=''2"> </td></tr>';
}
echo '</table>’;
Listing 5.19 Auslesen der verfügbaren Spreadsheets
272
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata | 5-5
Auch hier hat Google das Prinzip der IDs verfolgt. Das heißt, jedes Spreadsheet
hat eine eigene ID. Dabei handelt es sich um den letzten Teil der Eigenschaft i d,
die in jedem Spreadsheet-Objekt enthalten ist. Mit dieser ID ist es dann möglich,
auf die einzelnen Arbeitsblätter (Worksheets) zuzugreifen. Auch diese können in
Form eines Feeds ausgelesen werden, wobei auch jedes einzelne Tabellenblatt
über eine Eigenschaft i d verfügt, von der der letzte Teil eine eindeutige Kennung
innerhalb der Arbeitsmappe darstellt. Mit dieser i d ist es dann wiederum mög-
lich, die Feeds abzufragen, welche die Tabellenfelder beinhalten. Ein solcher
Feed beinhaltet für jede Zeile ein eigenes Objekt der Klasse Zend_Gdata_
Spreadsheets_Li stEntry. Auch wenn das bereits eine Zeile darstellt, müssen Sie
auf dieses Objekt zunächst die Methode getCustom() anwenden. Sie liefert
Ihnen ein Array zurück, das für jedes Feld in der Zeile ein Objekt der Klasse
Zend_Gdata_Spreadsheets_Extension_Custom enthält. Diese Vorgehensweise
mag auf den ersten Blick ein wenig komplex erscheinen, ist aber sehr klar struk-
turiert.
In dem folgenden Beispiel wird ein Spreadsheet ausgelesen, das nur ein Work-
sheet beinhaltet. Die ID des Spreadsheets wurde vorher ermittelt. Das Tabellen-
blatt, das ausgelesen werden soll, sehen Sie in Abbildung 5.17.
Abbildung 5.17 Tabellenblatt bei Google Spreadsheets
Der Code, der die Daten ausliest und darstellt, sieht folgendermaßen aus:
Sspread = new Zend_Gdata_Spreadsheets($client);
// ID eines Spreadsheets
$spreadsheetld='http://spreadsheets.google.com/feeds/spreadsheets/
273
e-bol.net
5 | Webservices
O17931546445181751748.995413063132391215' ;
// Key extrahieren
SspreadsheetKey = substr(strrchr(Sspreadsheetld, 1 );
// Neues Query-Objekt erstellen und mit Key initialisieren
Squery = new Zend_Gdata_Spreadsheets_DocumentQuery();
Squery->setSpreadsheetKey($spreadsheetKey);
// Feed mit Tabellenblättern auslesen
Sworksheets = Sspread->getWorksheetFeed(Squery);
// es ist nur ein Blatt => Kann direkt übernommen werden
Sworksheet = $worksheets->current();
// ID extrahieren
Sworksheetld = $worksheet->id;
SworksheetKey = substr(strrchr(Sworksheetld, 1 );
//Neue Abfrage erstellen und mit Key initialisieren
Squery = new Zend_Gdata_Spreadsheets_ListQuery();
Squery->setSpreadsheetKey($spreadsheetKey);
Squery-SsetWorksheetId(SworksheetKey);
// Daten auslesen
SlistFeed = Sspread->getListFeedlSquery);
// Titel des Blattes ausgeben
echo "Inhalt des Blattes <b>".$worksheet->title.’</b><br>';
echo "Ctable border='1'>";
// Daten der ersten Zeile ausgeben
SersteZeile = $1istFeed->current()->getCustom();
// Spaltenüberschriften ausgeben
echo '<tr>';
foreach (SersteZeile as Sentry)
{
echo ’<td>’.Sentry->columnName.'</td>';
}
echo ’</tr>*;
// Werte zeilenweise ausgeben
foreach (SlistFeed as Sentry)
{
echo '<tr>';
Scustom = Sentry->getCustom();
// Einzelnes Feld ausgeben
foreach (Scustom as Sfeld)
{
echo ' <td>' ;
echo $feld->text;
echo '</td>';
274
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata | 5-5
echo '</tr>’;
}
echo ’</table>';
Listing 5.20 Abfrage der Daten aus einem Tabellenblatt
Wie Sie sehen, werden hier zwei unterschiedliche Query-Klassen genutzt. Das ist
zum einen Zend_Gdata_Spreadsheets_DocumentQuery, um eine Abfrage auf
Dokumentebene durchzuführen, sprich das Tabellenblatt zu finden. Zum ande-
ren handelt es sich um die Klasse Zend_Gdata_Spreadsheets_Li stQuery, mit der
auf die Zellinhalte zugegriffen werden kann. Ein wenig verwirrend ist, dass die
eindeutige Kennung für die Dokumente als Key bezeichnet wird, wohingegen die
Kennung für die Blätter eine ID ist, wie Sie an den beiden folgenden Zeilen sehen
können, die die zweite Abfrage initialisieren:
Squery->setSpreadsheetKey($spreadsheetKey);
Squery->setWorksheetId(SworksheetKey):
Die meisten anderen Punkte dürften klar sein. Ich möchte aber noch auf die Aus-
gabe der Spaltenüberschriften eingehen, wofür diese Zeilen zuständig sind:
SersteZeile = $1istFeed->current()->getCustom();
// Spaltenüberschriften ausgeben
echo ’<tr>';
foreach (SersteZeile as Sentry)
{
echo '<td>'.$entry->columnName.'</td>';
1
echo ’</tr>';
Nach der Abfrage enthält S1 i stFeed für jede Zeile ein Objekt. Das erste wird mit
der Methode current!) ausgelesen, und auf das Ergebnis wird sofort getCus-
tom() angewandt. Dadurch erhalten Sie an der Stelle ein Array mit den Daten der
ersten Zeile. Erste Zeile meint bei Google Spreadsheets aber nicht die Zeile, in der
Sie die Worte »Produkt«, »Einkaufspreis« und »Verkaufspreis« sehen. Die erste
Zeile ist vielmehr die darunterliegende Zeile. Die Spaltenüberschriften können
leider nicht so einfach und direkt ausgelesen werden. Dafür ist die Überschrift
jeder Spalte aber in jedem Zellen-Objekt in der Eigenschaft col umnName enthal-
ten, was ich mir hier zunutze mache. Daher wird die erste Zeile einmal vorweg
ausgelesen, und die Eigenschaft col umnName wird ausgegeben.
Die Ausgabe des Scripts sehen Sie in Abbildung 5.18.
275
e-bol.net
5 | Webservices
Abbildung 5.18 Ausgabe der Tabellendaten
Wäre in einer der Zellen eine Formel enthalten gewesen, hätte das System nur
das Ergebnis der Berechnung zurückgegeben.
Wie am Anfang des Kapitels bereits erwähnt, können Sie auch hier nach einzel-
nen Zellinhalten suchen. Wäre bei der zweiten Abfrage setQuery('Hose')
ergänzt worden, hätte die Abfrage nur diejenigen Zeilen geliefert, in denen das
Wort »Hose« vorkommt. Gerade bei einer Tabellenkalkulation wäre es natürlich
langweilig, wenn man lediglich eine Textsuche durchführen könnte. Daher kennt
die Spreadsheet-API auch die Möglichkeit, eine »Structured Query« auszuführen.
Dabei haben Sie deutlich mehr Möglichkeiten. Wollten Sie beispielsweise alle
Zeilen auslesen, bei denen der Einkaufspreis kleiner als 50 ist, so würde das Ini-
tialisieren des Query-Objekts so aussehen:
Squery = new Zend_Gdata_Spreadsheets_ListQuery();
$query->setSpreadsheetKey($spreadsheetKey);
$query->setWorksheetId(SworksheetKey):
$query->setSpreadsheetQuery(’einkaufspreis < 50’):
Listing 5.21 Nutzung einer Structured Query
Die Methode setSpreadsheetQuery () ist also dafür zuständig, eine Structured
Query zu generieren. Hier können Sie auch die Operatoren ! = und — sowie die
Verknüpfungen AND und OR nutzen, wobei Sie Teilausdrücke auch klammern dür-
fen. Eine solche Abfrage bezieht sich immer auf eine ganze Zeile und nicht nur
auf ein Feld. Das heißt, mit der Abfrage
$query->setSpreadsheetQuery(’einkaufspreis < 50 AND
Verkaufspreis < 69’):
erhalten Sie lediglich die Zeile mit der Jacke. Bitte beachten Sie dabei, dass die
Spaltenüberschriften, die hier als Referenz genutzt werden, zwingend kleinge-
schrieben werden müssen, weil alles andere in einer Exception resultiert.
276
e-bol.net
Zugriff auf Google-Dienste mit Zend_Gdata | 5-5
Einfügen von neuen Zeilen
Sie können auch jederzeit eine neue Zeile in eine Tabelle einfügen. Dazu benöti-
gen Sie nur die IDs bzw. die Keys der Arbeitsmappe und des Tabellenblatts.
Sobald Sie diese Informationen haben, können Sie direkt aus dem Zend_Gdata_
Spreadsheets-Objekt heraus die Methode insertRowO aufrufen. Sie erhält als
ersten Parameter die Daten für die neue Zeile in Form eines Arrays übergeben.
Die beiden IDs folgen danach als Parameter.
Das Array muss so aufgebaut sein, dass der Spaltenname jeweils als Schlüssel
genutzt wird und dann auf den Wert verweist, der in der Zeile eingefügt werden
soll. Von daher sollten Sie jeder Spalte einen eindeutigen Namen geben. Wenn
Sie dies nicht tun, vergibt das System automatisch einen Namen für die Spalte.
Diesen können Sie wie oben bereits beschrieben auslesen.
Daten in eine Spalte zu schreiben, die noch nicht initialisiert wurde, ist zurzeit
noch nicht möglich und resultiert in einer Exception.
Das Einfügen einer Zeile könnte so aussehen:
Sspread = new Zend_Gdata_Spreadsheets($client);
// ID eines Spreadsheets
$spreadsheetld=’http://spreadsheets.google.com/feeds/spreadsheets/
O17931546445181751748.995413063132391215' :
// Key extrahieren
SspreadsheetKey = substr(strrchr(Sspreadsheetld, '/’), 1 );
Sworksheetld = 'http://spreadsheets.google.com/feeds/*.
'worksheets/O17931546445181751748.995413063132391215/' .
'pri vate/ful1/od6’;
SworksheetKey = substrfstrrchr(Sworksheetld, '/’), 1 );
Szeile = array (’produkt'=>’Socken' ,
’einkaufspreis'=>'2','Verkaufspreis'=>'5');
$spread->insertRow(Szei1e, SspreadsheetKey, SworksheetKey);
Listing 5.22 Einfügen einer neuen Zeile
Löschen von Zeilen
Auch das Löschen einer Zeile ist kein Problem. Die Vorgehensweise entspricht
der, die Sie schon beim Löschen von Kalendereinträgen kennengelernt haben.
Und zwar können Sie aus einem Zeilen-Objekt heraus die Methode del ete() auf-
rufen, die die Zeile entfernt. Um möglichst einfach an das entsprechende Zeilen-
Objekt zu kommen, nutzen Sie die Methode getLi stEntry(), die direkt aus dem
Spreadsheets-Objekt heraus aufgerufen wird. Sie bekommt die ID der Zeile (auch
in diesem Fall den kompletten Inhalt der Eigenschaft i d) übergeben und liefert
277
e-bol.net
5 | Webservices
die entsprechende Zeile als Objekt zurück. Danach können Sie dann delete()
aufrufen, um die Zeile zu löschen.
Szei1 en Id='http://spreadsheets.google.eom/feeds/li st/ol7931546445181
751748.995413063132391215/od6/pri vate/ful 1/ckd7g' ;
Szeile = Sspread->getListEntry(Szei 1 enld);
Szei1e->delete();
Listing 5.23 Löschen einer Zeile
Aktualisieren von Zeilen
Grundsätzlich ist es auch kein Problem, eine Zeile zu aktualisieren. Die Vorge-
hensweise ist ähnlich wie beim Löschen. Zunächst lesen Sie das entsprechende
Entry-Objekt aus. Dieses übergeben Sie dann zusammen mit einem Array mit
den neuen Daten an die Methode updateRow(). Allerdings ist hierbei ein kleiner
Fallstrick zu beachten: Die Methode übernimmt nur Daten, die auch in dem
Array enthalten sind. Spalten, die nicht genannt werden, verlieren ihre Werte.
Wenn Sie also nur einen Wert aktualisieren wollen, so sollten Sie vorher das
Array mit den alten Werten belegen, damit diese nicht verlorengehen.
Das könnte dann beispielsweise so aussehen:
Szei1enld='.... schrecklich lange ID
Sentry = Sspread->getListEntry(Szei1enld):
Szeile = Sentry->getCustom();
Sdaten = array();
// Alte Daten auslesen
foreach (Szeile as Szeile)
{
$daten[$zel1e->columnName] = Szei1e->text;
}
// Neue Information einfügen
SdatenE’einkaufspreis'] ='1000';
// Daten speichern
Sspread->updateRow(Sentry, Sdaten);
Listing 5.24 Aktualisieren einer Zeile
278
e-bol.net
Der vernünftige Mensch passt sich der Welt an; der unvernünftige
besteht auf dem Versuch, die Welt sich anzupassen. Deshalb hängt aller
Fortschritt vom unvernünftigen Menschen ab.
- George Bernard Shaw
6 Arbeit mit E-Mails und Dateiformaten
6.1 E-Mails mit Zend_Mail verarbeiten
Der Zugriff auf E-Mails ist häufig ein zentraler Bestandteil vieler Anwendungen.
Insbesondere alle Arten von Internetangeboten, bei denen ein Benutzer einen
eigenen Account besitzt, benötigen eine solche Funktionalität. Die Funktion
mail() in PHP ist in diesem Zusammenhang zwar hilfreich, aber absolut unzurei-
chend. So versendet sie E-Mails standardmäßig nur über den lokal installierten E-
Mail-Server, was schnell dazu führt, dass E-Mails in einem Spam-Filter hängen-
bleiben oder aufgrund der Netzwerkinfrastruktur erst gar nicht verschickt wer-
den können. Ein weiterer Punkt ist, dass sie schwierig in der Handhabung ist,
sofern Sie E-Mails mit Anhängen verschicken wollen. Darüber hinaus kann sie
auch keine E-Mails abholen. Eine solche Funktionalität könnten Sie zwar über
andere PHP-Funktionen implementieren, der Aufwand aber wäre recht hoch.
Um Sie bei solchen Problemen zu unterstützen, wurde Zend_Mai l implementiert.
Zend_Mai l unterstützt sowohl den lokalen Versand von E-Mails als auch den Ver-
sand über einen externen SMTP-Server. Beim Abholen von E-Mails werden
neben POP3 auch IMAP und lokale Speichermöglichkeiten wie MBox und Mail-
Dir unterstützt.
Bevor ich auf die Funktionalitäten im Einzelnen eingehe, möchte ich noch einen
Punkt anmerken. Bei E-Mails gibt es einige Komponenten, die nur ein einziges
Mal vorkommen dürfen, wohingegen andere mehrfach genutzt werden können.
Alle Teile einer E-Mail, die nur ein Mal genutzt werden dürfen, können über
Funktionen gesetzt werden, deren Name mit set beginnt. Elemente, die mehr-
fach auftauchen dürfen, werden jeweils mit einer Methode ergänzt, die mit add
anfängt. Diese kleine Eselsbrücke hilft ein wenig, den Überblick bei den vielen
Funktionen zu behalten.
279
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
6.1.1 E-Mails versenden
Im einfachsten Fall können Sie eine E-Mail direkt über den lokal installierten
Mail Transport Agent (MTA) verschicken. Wie schon erwähnt, kann das gerade
bei Shared Webspace schnell dafür sorgen, dass die E-Mails beim Empfänger im
Spam-Filter landen. Daher sollten Sie diese Vorgehensweise mit Bedacht nutzen.
Lassen Sie uns mit einem einfachen Beispiel beginnen:
require_once 'Zend/Mail.php';
// Objekt ableiten
Smail = new Zend_Mail();
//Empfaenger
Smail->addTo('empfaenger@example.com', 'Paulchen Panther');
// Absender
Smail->setFrom('info@netviser.de', 'Carsten Möhrke');
// Betreff der Mail
Smail->setSubject('E-Mail aus dem Internet');
// Inhalt der Mail
Smail->setBodyText('Dies ist eine Test-E-Mail');
// E-Mail versenden
Smail->send();
Listing 6.1 Versenden einer E-Mail über den lokalen MTA
Nach dem Instantiieren des Objekts werden zuerst der Empfänger und der
Absender gesetzt. Beide Methoden bekommen als ersten Parameter die jeweilige
E-Mail-Adresse übergeben und an zweiter Stelle den Namen der Person, wobei
dieser Parameter optional ist. Da eine E-Mail mehrere Empfänger haben kann,
können Sie die Methode addTo() auch mehrfach aufrufen. Angenehm dabei ist,
dass sie sich selbstständig darum kümmert, dass \n und andere Whitespaces ent-
fernt werden, die eventuell zu einem Sicherheitsproblem werden könnten.
Danach werden der Betreff und der Inhalt mit den Member-Funktionen setSub-
jectO und setBodyText() hinzugefügt. Der eigentliche Versand erfolgt dann
durch die Methode send(). Wenn Sie gerade darüber stolpern sollten, dass keine
Fehlerabfrage beim Versenden der E-Mail durchgeführt wird, so gilt auch hier,
dass die Methode im Falle eines Fehlers eine Exception wirft. Die Methoden
geben jeweils immer eine Referenz auf das eigentliche Objekt zurück.
Ein weiterer interessanter Punkt ist die Frage nach dem Zeichensatz. In dem Bei-
spiel habe ich bedenkenlos Umlaute genutzt, was in diesem Fall auch kein Pro-
blem darstellt. Die Klasse geht davon aus, dass die übergebenen Strings in einer
ISO-8859-l-Codierung übergeben werden.
280
e-bol.net
E-Mails mit Zend_Mail verarbeiten | 6.1
Sollten Sie einen anderen Zeichensatz bevorzugen, ist das kein Problem. Überge-
ben Sie die gewünschte Codierung einfach als String an den Konstruktor, wenn
Sie das Objekt ableiten. Mit der Angabe
Smail = new Zend_Mai1(1utf-8’);
leiten Sie also ein Mail-Objekt ab, das mit UTF-8-codierten Texten arbeitet und
die E-Mail in dieser Codierung verschickt.
In diesem Beispiel wurden natürlich noch nicht alle Member-Funktionen
genutzt, welche die Klasse kennt. Möchten Sie bestimmten Empfängern Kopien
der E-Mail zukommen lassen, so können Sie diese Empfänger mit der Methode
addCcO hinzufügen. Auch diese Methode kann mehrfach aufgerufen werden
und bekommt als ersten Parameter die E-Mail-Adresse des Empfängers überge-
ben. Auch hier gilt, dass Sie mit dem zweiten, optionalen Parameter noch den
Namen des Empfängers angeben können. Mit der Methode addBcc() können
der E-Mail Empfänger für »Blind Carbon Copies« hinzugefügt werden. Also E-
Mail-Adressen von Empfängern, welche die anderen Empfänger nicht sehen.
Gerade dann, wenn Sie zum Beispiel einen größeren Kundenkreis erreichen wol-
len, ist das eine beliebte Vorgehensweise um die Privatsphäre der Empfänger zu
schützen. Allerdings akzeptiert diese Methode nur die E-Mail-Adresse als Para-
meter. Einen Namen können Sie hier nicht angeben.
Den »Return-Path« einer E-Mail können Sie mit setReturnPath() setzen. Bei
dem Return-Path handelt es sich um eine E-Mail-Adresse, die beispielsweise
dann genutzt wird, wenn der E-Mail-Server eine automatische Antwort gene-
riert, weil es den Empfänger nicht gibt. Das heißt, Sie können hier eine E-Mail-
Adresse angeben, die für das automatische Bounce-Handling genutzt wird. Es
handelt sich also nicht um die Adresse, an die eine echte Antwort auf die E-Mail
geschickt wird. Bitte beachten Sie, dass sendmai 1 dazu neigt, den Return-Path
durch einen eigenen Wert zu ersetzen.
Eine ganz wichtige Methode kann noch addHeader() sein. Mit dieser Member-
Funktion können Sie Header in die E-Mail einfügen, die nicht durch die anderen
Methoden abgedeckt sind. Falls Sie also versuchen, hiermit einen Empfänger
einer Kopie in die E-Mail aufzunehmen, so wirft die Methode eine Exception.
Allerdings können Sie damit alle Header aufnehmen, für die es keine speziellen
Methoden gibt. Die Methode erhält dazu als ersten Parameter den Namen des
Headers übergeben und als zweiten den Wert, den er bekommen soll.
Mit der Angabe
Smail->addHeader(’X-Mailer', 'Fynn-Mail 1.0’);
281
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
würde der Header X-Mailer mit dem Wert Fynn-Mail 1.0 hinzugefügt. Mit
einem dritten Parameter, der die Werte true und false akzeptiert, können Sie
noch festlegen, ob mehrere Header mit gleichem Namen zusammengefasst wer-
den. Übergeben Sie true, werden die Werte zusammengefasst, was mit einem
fal se (dem Default-Wert) nicht passiert. Die folgenden beiden Zeilen
Smail->addHeader('X-Mailer*, 'Fynn-Mailer 1.0');
Smail->addHeader('X-Mailer*, 'German Release',true);
würden in diesem Header resultieren:
X-Mailer: Fynn-Mailer 1.0,
German Release
Diese Methode können Sie natürlich insbesondere dann gut einsetzen, wenn Sie
die Dringlichkeit der E-Mail festlegen wollen. Da diese Header leider nicht wirk-
lich standardisiert sind, habe ich die Header und die dazugehörigen Werte in
Tabelle 6.1 zusammengestellt.
Header-Name Mögliche Werte Erläuterung
Importance Highr Normal oder Low Standardisiert nach RFC 2156 und RFC 2421
Pri ori ty Urgent, Normal oder Non-urgent In RFC 2156 definiert
X -Pri ori ty 1 (Highest), 2 (High), 3 (Normal), 4 (Low) oder 5 (Lowest) Standard für das E-Mail-Pro- gramm Eudora
X-MSMail -Priority High, Normal oder Low Standard für Microsoft-Produkte
Tabelle 6.1 Header für die Dringlichkeit von E-Mails
Wollen Sie eine E-Mail mit hoher Priorität versenden, empfiehlt es sich, alle vier
Header mit den entsprechenden Werten zu setzen, damit sichergestellt ist, dass
jeder E-Mail-Client die Dringlichkeit erkennt.
Nachdem Sie die elementaren Funktionen kennengelernt haben, mit denen Sie
eine E-Mail verschicken und aufbereiten können, ist zu klären, wie Sie komple-
xere E-Mails versenden können.
An erster Stelle ist hier das Versenden von HTML-E-Mails zu nennen, was mit
Zend_Mail erfreulich einfach ist. Um einer E-Mail einen HTML-formatierten
Body hinzuzufügen, übergeben Sie diesen einfach an die Methode setBody-
Html (). Diese Methode akzeptiert die gleichen Parameter wie setBodyText().
Sie sollten die beiden Methoden immer parallel benutzen. Das hat den Vorteil,
dass ein Client, der HTML nicht darstellen kann, dann alternativ auf die Textver-
sion zurückgreifen kann.
282
e-bol.net
E-Mails mit Zend_Mail verarbeiten | 6.1
Smail->setBodyText('Willkommen bei Zend_Mail!'):
Smail->setBodyHtml('
CDOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
<b><u>Wi11 kommen bei Zend_Mail!</u></b>
</body>
</html>');
Listing 6.2 Setzen eines HTML-Bodys einer E-Mail
Bitte vergessen Sie dabei nicht, dass der HTML-Anteil immer ein komplettes
HTML-Dokument darstellen sollte und somit auch alle notwendigen Tags be-
inhalten muss.
Anhänge
Möchten Sie eine E-Mail mit Anhängen versenden, zieht das bei der Nutzung der
PHP-Funktion mail () einen recht großen Aufwand nach sich. Sie müssen den
Dateianhang einlesen, korrekt codieren und die Boundaries setzen. Einen Groß-
teil diese Aufgaben kann Zend_Mai 1 Ihnen abnehmen.
Im einfachsten Fall können Sie den Dateianhang direkt mit der Methode create-
Attachmenti) erzeugen. Die Methode bekommt die Binärdaten direkt in Form
eines Strings übergeben. Die Daten würden dann direkt an die E-Mail angehängt.
Dabei würde sich allerdings das Problem ergeben, dass die angehängte Datei
noch keinen Namen hat. Die Methode gibt Ihnen aber eine Referenz auf das
Zend_Mime_Part-Objekt zurück, welches genutzt wird, um den Anhang zu spei-
chern. Dieses Objekt kennt eine Eigenschaft namens f i 1 ename, der Sie den Datei-
namen zuweisen können:
require_once 'Zend/Mail .php’;
Smail = new Zend_Mail():
//Empfänger, Absender, Body etc. setzen......
// Daten aus Datei einlesen
$daten_anhang = fi1e_get_contents('kapitel.doc'):
// Anhang hinzufügen
Sanhang = Smai1->createAttachment(Sdaten_anhang);
// Dateinamen zuweisen
$anhang->fi1ename='i ndex.html';
283
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
// E-Mail versenden
$mai1->send();
Listing 6.3 Anfügen eines Dateianhangs
In diesem Fall wird die Datei als echter Anhang mit dem MIME-Type appl i ca-
tion/octet-stream und mit der Content-Disposition attachment verschickt.
Diese Eigenschaften können Sie allerdings überschreiben. Hierzu sind die Eigen-
schaften type für den MIME-Type und disposition vorgesehen. Den MIME-
Type können Sie direkt oder mithilfe von Konstanten zuweisen, die in der Klasse
Zend_Mime definiert sind. Hier sind TYPE_OCTETSTREAM (appl ication/octet-
stream), TYPE_HTML (text/html) und TYPE_TEXT (text/pl ai n) deklariert. Alterna-
tiv können Sie den MIME-Type auch direkt als String zuweisen. Für die Disposi-
tion sind in derselben Klasse die Konstanten DISPOSITION_I NLINE und
DISPOSITION_ATTACHMENT vorgesehen, wobei der zweite Wert der Standardwert
ist. Mit DISPOSITION_I NLINE wird die angehängte Datei direkt in der E-Mail dar-
gestellt, sofern das möglich ist.
Standardmäßig werden die Anhänge Base64-codiert verschickt, was in den meis-
ten Fällen sicherlich nicht geändert werden muss. Sollte es doch einmal erfor-
derlich sein, so können Sie den Wert der Eigenschaft encoding ändern. Dieser
können Sie die Konstanten ENCODING_7BIT, ENCODING_8BIT, ENCODING_QUOTED-
PRINTABLE und ENC0DING_BASE64 zur Verfügung stellen.
Ein Punkt, bei dem Dateianhänge eine wichtige Rolle spielen, sind HTML-E-
Mails. Möchten Sie nämlich eine Grafik in den HTML-Code der Seite einbinden,
so muss diese Grafik auch als Anhang verschickt werden. Um die Grafik aus dem
HTML-Code der Seite anzusprechen, benötigt sie eine ID. Diese kann der Eigen-
schaft i d zugewiesen werden. Dieselbe ID muss dann auch als Name des Bildes
im img-Tag benutzt werden. Als »Protokoll« geben Sie in diesem Fall cid: als
Abkürzung für Content-ID an.
Zusätzlich muss der gesamten E-Mail noch als Content-Type mul tipart/rel ated
zugewiesen werden, damit der E-Mail-Client erkennt, dass die verschiedenen
Teile der E-Mail miteinander in Beziehung stehen. Dazu ist in der Klasse Zend_
Mai 1 die Methode setTypef) vorgesehen, die die Konstante MUTLI PART_RELATED
übergeben bekommt, welche in der Klasse Zend_Mime definiert ist.
Möchten Sie zusätzlich zu der eingebundenen Grafik noch einen weiteren
Anhang mit verschicken, stellt das kein Problem dar. Diesen Anhang können Sie
wie gewohnt erzeugen. In Listing Listing 6.4 sehen Sie ein Beispiel, wie eine
HTML-E-Mail mit eingebundener Grafik und einem weiteren Dateianhang
erzeugt wird:
284
e-bol.net
E-Mails mit Zend_Mail verarbeiten | 6.1
require_once 'Zend/Mail.php’;
// Objekt ableiten
Smail = new Zend_Mail();
// Content-Type korrekt setzen
Smail->setType(Zend_Mime::MULTIPART_RELATED);
//Empfaenger hinzufuegen
Smail->addTo('cmoehrke@netviser.de', 'Carsten Möhrke');
// Absender einfuegen
Smai1->setFrom('info@netviser.de', ’Paulchen Panther');
// Betreff der E-Mail setzen
Smai1->setSubject('HTML-E-Mail Grafik');
// Textinhalt der Mail
Smai1->setBodyText('Leider unterstützt Ihr
E-Mail-Client kein HTML'):
// Zufallswert fuer die ID erzeugen
Sei d = mt_rand();
// HTML-Inhalt der Mail
Smai1->setBodyHtml("
<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>
Chtml>
<head>
</head>
<body>
Das hier ist das PHP-Logo: <br>
<img src='cid:Scid’>
</body>
</html>");
// Inline-Grafik einfuegen
Slogo = file_get_contents(’logo.gif');
Sanhang = Smai1->createAttachment($1ogo);
$anhang->fi1ename='1ogo.gif’;
Sanhang->disposition=Zend_Mime::DISPOSIT10N_INLINE ;
$anhang->type='image/gi f’;
$anhang->i d=$ci d;
// Weiteren Dateianhang erzeugen
Sdatei = fi1e_get_contents(’index.html');
Sanhang = Smai1->createAttachment(Sdatei):
285
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
$anhang->type='applicagti on/ms-word’;
$anhang->filename='datei.doc’;
// E-Mail versenden
$mai1->send();
Listing 6.4 Versenden einer HTML-E-Mail mit Inline-Grafik und Anhang
Die fertig generierte und zugestellte E-Mail sehen Sie in der nächsten Abbildung.
Abbildung 6.1 Zugestellte E-Mail in Thunderbird
6.1.2 Versand über SMTP-Server
Wie schon erwähnt, können Sie E-Mails auch über andere SMTP-Server und
nicht nur über den lokalen MTA verschicken. Bei einem SMTP-Server, der keine
Authentifikation verlangt, können Sie einfach ein Objekt der Klasse Zend_Mail_
Transport_Smtp ableiten. Der Konstruktor der Klasse bekommt dabei den
Namen des SMTP-Servers als Parameter übergeben. Das so erstellte Objekt kön-
nen Sie dann an die statische Methode setDefaultTransport aus der Klasse
Zend_Mail übergeben. Damit legen Sie den vorher übergebenen Server als
Default-Transportweg fest. Dies bietet sich immer dann an, wenn die E-Mails nur
über einen Server verschickt werden sollen, was in der Regel der übliche Weg ist.
Alternativ können Sie das Objekt auch der Methode send () übergeben, wenn Sie
die E-Mail versenden.
286
e-bol.net
E-Mails mit Zend_Mail verarbeiten | 6.1
Variante 1:
// Klassendateien inkludieren
require_once ’Zend/Mail.php’;
requi re_once ’Zend/MailZTransport/Smtp.php';
// Transport-Objekt ableiten
Sserver = new Zend_Mail_Transport_Smtp(’mai1.example.com');
// Server als Default festlegen
Zend_Mai1::setDefaultTransport($Server);
// E-Mai 1-Objekt instantiieren und E-Mail versenden
Variante 2:
// Klassendateien inkludieren
require_once ’Zend/Mail .php’;
requi re_once ’Zend/Mail/Transport/Smtp.php';
// Transport-Objekt ableiten
Sserver = new Zend_Mail_Transport_Smtp(’mai1.example.com');
// Mail-Objekt ableiten
Smail = new Zend_Mail();
// E-Mail aufbauen
// E-Mail über Server versenden
Smail->send($server);
In vielen Fällen wird der SMTP-Server allerdings nach einer Authentifikation mit
Benutzernamen und Passwort verlangen. Hierbei gibt es zwei Vorgehensweisen.
Die erste ist »SMTP after POP« bzw. »SMTP after IMAP«. Dabei müssen Sie sich
zuerst an einem POP- bzw. IMAP-Server anmelden, bevor Sie eine E-Mail über
den SMTP-Server verschicken können. Dieses Verfahren ist nach wie vor bei eini-
gen Providern im Einsatz, allerdings ist es nicht das übliche Verfahren. Sollte Ihr
Provider dieses Verfahren nutzen, so lesen Sie bitte in Abschnitt 6.1.3 nach, wie
Sie sich zum Abholen von E-Mails anmelden.
Die zweite Möglichkeit ist ein direktes Anmelden am SMTP-Server, was durch
die Transport-Klasse unterstützt wird. Hierbei unterstützt die Klasse die Metho-
den plain, login und cram-md5. Eine dieser Methoden übergeben Sie in einem
Array zusammen mit dem Benutzernamen und dem Passwort als zweiten Para-
meter an den Konstruktor der Transport-Klasse. Die Schlüssel, die in dem Array
genutzt werden, müssen die Namen auth, username und password haben.
287
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
require_once 'Zend/Mail.php’;
requi re_once 'Zend/Mail/Transport/Smtp.php’;
Sconfig = array(’auth' => 'login',
'username' => 'versender@netviser.de',
’password' => total geheim);
// Objekt ableiten
Stransport = new Zend_Mail_Transport_Smtp('smtp.provider.de',
Sconfig);
// weitere Vorgehensweise wie oben...
Listing 6.5 Versand einer E-Mail über SMTP
Nachdem Sie das Objekt entsprechend abgeleitet haben, können Sie fortfahren,
wie Sie es bereits kennengelernt haben. Die eigentliche Authentifikation über-
nimmt das Transport-Objekt dann automatisch.
Optional können Sie die Übertragung der Daten auch verschlüsseln. Verschlüs-
seln heißt in diesem Fall, dass die Übergabe der Daten an den SMTP-Server ver-
schlüsselt wird. Dadurch wird sichergestellt, dass Benutzername und Passwort
auf dem Übertragungsweg nicht ausgespäht werden können. Die eigentliche E-
Mail, die der SMTP-Server verschickt, wird allerdings nicht verschlüsselt.
Möchten Sie die Übertragung verschlüsseln, so können Sie dem Array noch den
Schlüssel ssl hinzufügen. Mögliche Werte dabei sind tl s (Transport Layer Secu-
rity) und ssl (Secure Socket Layer). Des Weiteren können Sie mit dem Schlüssel
port einen alternativen Port übergeben, falls der SMTP-Server nicht die Stan-
dard-Ports benutzt. Welche Verschlüsselung Ihr SMTP-Server unterstützt und
welcher Port genutzt wird, erfragen Sie bitte bei Ihrem Provider oder Systemad-
ministrator.
6.1.3 E-Mails abholen
Mithilfe von Zend_Mai l können Sie aber nicht nur E-Mails versenden, sondern
auch einlesen. E-Mails können aus einer mbox oder einem Maildir eingelesen
oder von einem POP3- bzw. IMAP-Server abgeholt werden. Nachfolgend werde
ich vorwiegend auf die Nutzung von POP3 und IMAP eingehen, da die Nutzung
dieser Server sicher weiter verbreitet ist als die Nutzung von mbox oder Maildir.
Das IMAP- und das POP3-Protokoll unterscheiden sich deutlich in den Möglich-
keiten, die zur Verfügung stehen. In beiden Fällen werden die E-Mails auf einem
Server abgelegt. Bei der Nutzung eines P0P3-Servers werden die E-Mails aller-
dings direkt vom Client heruntergeladen. IMAP-Server sind in der Lage, E-Mails
»aufzubewahren«. Das heißt, der Client kann eine Kopie der E-Mail herunterla-
288
e-bol.net
E-Mails mit Zend_Mail verarbeiten | 6.1
den und diese dann anzeigen. Die Original-E-Mail auf dem Server kann dann als
gelesen markiert oder gelöscht werden. Zusätzlich kennt IMAP auch noch die
Möglichkeit, mit Ordnern zu arbeiten, in denen E-Mails abgelegt werden kön-
nen.
Für jede der verschiedenen Abholmöglichkeiten ist eine eigene Klasse vorgese-
hen. Die Namen beginnen jeweils mit Zend_Mai l_Storage_, woran sich dann der
Name des Daten-Containers, also z. B. Pop3 oder Mai 1 di r anschließt.
Die Konstruktoren der Klassen bekommen die relevanten Daten jeweils in Form
eines Arrays übergeben. Für die IMAP- bzw. die POP3-Klasse könnte ein solches
Array beispielsweise so aufgebaut sein:
Sconfig = array(’host' => 'mail.provider.de’,
’user' => 'cmoehrke@geheim.de',
’password' => 'totalgeheim',
’ssl’ => ’TLS’.
’port’ => 1120):
Mit den Schlüsseln host, user und password übergeben Sie den Namen des Ser-
vers sowie Benutzernamen und Passwort an die Klasse. Mit dem optionalen
Schlüssel ssl können Sie festlegen, ob die Kommunikation per SSL bzw. TLS ver-
schlüsselt werden soll. Mit port - auch dieser Schlüssel ist optional - können Sie
noch einen anderen Port definieren, falls kein Standard-Port genutzt werden soll.
Das Auslesen von E-Mails per POP3 und IMAP ist weitgehend identisch, wobei
IMAP weitergehende Möglichkeiten bietet. Daher behandle ich die grundlegen-
den Funktionen hier zunächst zusammen.
Das Array mit den Konfigurationsdaten wird an den Konstruktor der jeweiligen
Klasse, also entweder Zend_Mail_Storage_Pop3 oder Zend_Mail_Storage_Imap
übergeben. Der Konstruktor baut direkt die Verbindung auf. Sollte es dabei zu
einem Fehler kommen, wirft die Klasse eine Exception vom Typ Zend_Mail_
Protocol_Excepti on.
Nach dem Verbindungsaufbau können Sie mithilfe der Methode countMes-
sages() herausfinden, wie viele E-Mails momentan auf dem Server vorhanden
sind. Jede E-Mail können Sie über ihre Ordnungsnummer, wobei die Zählung
immer mit 1 beginnt, direkt ansprechen.
Um eine einzelne E-Mail vom Server abzuholen, können Sie die Methode get-
Message() nutzen, welche die Nummer der E-Mail als Parameter erhält. Alterna-
tiv können Sie das Objekt, mit dem Sie die Verbindung aufgebaut haben, auch
direkt an eine foreach-Schleife übergeben oder als Array nutzen. In allen Fällen
erhalten Sie jeweils ein Objekt der Klasse Zend_Mail_Message zurück. Dieses ent-
289
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
hält die Header sowie den Betreff der E-Mail direkt als Eigenschaften. Möchten
Sie einfach nur eine Liste der E-Mails ausgeben, die auf dem Server vorhanden
sind, dann könnte das so aussehen wie in Listing 6.6:
header ('Content-Type: text/html; charset=UTF-8’):
requi re_once ’Zend/MailZStorage/Pop3.php';
// Dekodiert MIME-Header und wandelt nach UTF-8
function headerNachUtf8(Sheader)
{
Sreturn = '';
// Header dekodieren
$a_header = imap_mime_header_decode(Sheader);
// Alle Teile des Headers durchlaufen
foreach (Sa_header as $header)
// Zeichensatz auslesen
Scharset = $header->charset:
if (Scharset == ’default')
{
Scharset = 'I SO-8859 -1':
}
// Text nach UTF-8 konvertieren
Sreturn .= iconv(Scharset,'UTF-8',$header->text):
1
return Sreturn;
}
Sconfig = array(’host' => 'mail.provider.de',
'user' => ’cmoehrke@geheim.de’,
'password' => ’totalgeheim',
'ss1' => 'tls ’):
try
{
Smail = new Zend_Mail_Storage_Pop3(Sconfig);
1
catch (Zend_Mail_Protocol_Exception Se)
{
die ("Verbindungsfehler: ".Se->getMessage());
1
try
(
290
e-bol.net
E-AAails mit Zend_Mail verarbeiten | 6.1
$anzahl_mai1s = $mai1->countMessages();
echo "Anzahl der E-Mails im Postfach:
$anzahl_mai1s <brXbr>";
echo "<table border=’0' cel1spacing='0' cel1padding='5'>":
echo "<tr><td>Nr.</td><td>Absender</td>
<td>Betreff</tdX/tr>";
// Alle E-Mails durchlaufen
foreach ($mai1 as $nummer => $message)
// Um die Zeilen farblich zu unterscheiden
if (0 == $nummer %2)
{
echo ”<tr>";
1
el se
{
echo "<tr bgcolor='#CCCCCC,>":
1
echo "<td>$nummer</td>":
// Ausgabe der Header
Sabsender = headerNachUtf8($message->from);
echo "<td>".htmlentities(
$absender,ENT_QUOTES.'UTF-8')."</td>";
echo "<td>".headerNachUtf8($message->subject). "</td>";
echo "</tr>";
1
echo "</table>":
1
catch (Zend_Mail_Exception $e)
{
die ($e->getMessage()):
1
Listing 6.6 Auslesen von E-AAails mit Zend_AAail
Die Ausgabe des Scripts finden Sie in Abbildung 6.2.
Wie Sie sehen, werden die Header alle an die Funktion headerNachUtf8() über-
geben. Diese Notwendigkeit resultiert daraus, dass die Header von der Klasse
nicht decodiert werden. Sie werden genau so abgelegt, wie Sie aus der E-Mail
ausgelesen wurden. Die Funktion headerNachUtf8() decodiert die Header und
liefert sie im UTF-8-Zeichensatz zurück.
291
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
Abbildung 6.2 Übersicht über die E-Mails im Browser
In diesem Zusammenhang stellt sich die Frage, welche Header in einer E-Mail
vorhanden sind. Dabei sei vorab erwähnt, dass die Anzahl und der Typ der Hea-
der in einer E-Mail variieren können. Üblicherweise können Sie aber davon aus-
gehen, dass from (der Absender) und subject (der Betreff) vorhanden sind. Ver-
suchen Sie auf einen Header zuzugreifen, der nicht vorhanden ist, wirft die
Klasse eine Exception. Einen Header wie del ivery-date (das Zustellungsdatum)
können Sie natürlich nicht direkt als Eigenschaft nutzen, da PHP die Syntax nicht
akzeptieren würde. Allerdings ist es in diesem Fall möglich die »Camel-Case-
Schreibweise« zurückzugreifen. Der Bindestrich fällt weg und der erste Buch-
stabe des nächsten Wortes wird groß geschrieben. Somit könnten Sie über $mes-
sage->del i veryDate auf diesen Header zugreifen. Das hat allerdings den Nach-
teil, dass der Name der Eigenschaft sich nun von dem Namen des Headers
unterscheidet.
Ich persönlich bevorzuge aber einen anderen Weg. Möchten Sie auf einen Hea-
der zugreifen, der einen Bindestrich oder Ähnliches im Namen hat, so können
Sie den Namen des Headers direkt an die Methode getHeaderO übergeben.
Damit können Sie die Namen der Header immer konsistent nutzen. Der Header
wird als derjenige Datentyp zurückgegeben, als der er gespeichert ist; üblicher-
weise als String. Sollte ein Header allerdings mehrfach vorkommen, wie das bei-
spielsweise bei Received der Fall ist, so erhalten Sie die Daten per Default in
Form eines Arrays. Sie können als zweiten Parameter auch den String 'array'
übergeben, damit die Information als Array zurückgegeben wird; und mit dem
String ’string’ stellen Sie sicher, dass Sie die Daten als String erhalten.
292
e-bol.net
E-Mails mit Zend_Mail verarbeiten | 6.1
Die Nutzung von getHeader() löst aber nicht das Problem mit der Exception.
Versuchen Sie mit dieser Methode, einen nicht vorhandenen Header auszulesen,
quittiert das System dies erneut mit einer Exception.1 Ein möglicher Lösungsan-
satz wäre ein Konstrukt wie dieses:
try
{
$message->getHeader(1x-priority');
1
catch (Exception $e)
I }
In diesem Beispiel soll ein Header abgefragt wurden, mit dem beispielsweise das
E-Mail-Programm Thunderbird die Dringlichkeit eine E-Mail festlegt. Da andere
Programme dies anders machen, würde an dieser Stelle mit einiger Wahrschein-
lichkeit früher oder später eine Exception geworfen.
Die flexibelste Möglichkeit ist aber sicher, die Header komplett mithilfe von
getHeaders() auszulesen. In dem Fall erhalten Sie alle Header, die in der E-Mail
enthalten sind, in Form eines Arrays zurück. Der Name des Headers fungiert
dabei als Schlüssel. Sollte ein Header mehrere Werte enthalten, so ist unter dem
Schlüssel ein weiteres Array zu finden, welches indiziert ist.
Im obigen Beispiel wurden nur die Header der Mails verarbeitet. Nun ist noch zu
klären, wie Sie an den eigentlichen Inhalt der E-Mail gelangen. Die einfachste
Lösung ist, auf die Methode getContent() zurückzugreifen. Handelt es sich um
eine einfache Text-E-Mail, liefert diese Methode den Körper der E-Mail zurück.
Auch hierbei gilt wieder, dass der Content exakt so zurückgegeben wird, wie er
in der E-Mail enthalten ist. Das bedeutet, dass Sie selbst sicherstellen müssen,
dass der Text in den korrekten Zeichensatz konvertiert wird. Wie das funktio-
niert, können Sie dem nächsten Beispiel entnehmen.
Der zweite mögliche Fall ist, dass es sich nicht um eine reine Text-E-Mail handelt,
sondern um eine MIME-Multipart-E-Mail. In diesem Fall sind in der E-Mail meh-
rere Teile enthalten, welche alle einen eigenen MIME-Type haben. Diese Vorge-
hensweise wurde eingeführt, da Dateianhänge oder Ähnliches einen anderen
MIME-Type besitzen als die eigentliche E-Mail. Um zu prüfen, ob eine E-Mail aus
mehreren Teilen besteht, ist die Methode i sMulti part() deklariert. Die einzel-
nen Teile der E-Mail können Sie dann mit der Methode getPart() abholen, wel-
che die Nummer des gewünschten Teils als Parameter übergeben bekommt.
1 Dieses Verhalten ist meiner Ansicht nach deutlich übertrieben. Bitte prüfen Sie, ob die
Methode sich in zukünftigen Versionen nicht anders verhält.
293
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
Alternativ können Sie das Mail-Objekt in Form einer rekursiven Iterators an eine
foreach-Schleife übergeben.2
Das Objekt, das Sie auf diesem Weg erhalten, kennt die Eigenschaft contentType,
in welcher der MIME-Type des Teils enthalten ist. Den Inhalt der Teils können
Sie dann wiederum mit getContent() auslesen.
requi re_once 'Zend/MailZStorage/Pop3.php';
// Daten werden als UTF-8 ausgegeben
header ('Content-Type: text/html: charset=UTF-8’):
// Extrahiert den Zeichensatz aus dem Content-Type
function charsetAuslesen($content_type)
{
preg_match("/charset *=([A:]+)/’’, $content_type, $ret):
return $ret[1];
}
// Konvertiert die Daten eines MIME-Headers nach UTF-8
function headerNachUtf8($header)
{
$return = '’;
$a_header = imap_mime_header_decode($header);
foreach ($a_header as $header)
{
$charset = $header->charset;
if ($charset == ’default’)
tcharset = 'I SO - 8859 -1’ :
1
$return .= iconv($charset,'UTF-8',$header->text):
return $return;
}
$config = array('host' => 'mail.provider.de',
'user' => 'cmoehrke@geheim.de',
'password' => ’totalgeheim’,
'ssl' => 'tls'):
try
(
2 Rekursive Iteratoren sind in der SPL implementiert: http://www.php.net/~helly/php/ext/spl/
294
e-bol.net
E-Mails mit Zend_Mail verarbeiten | 6.1
Smail = new Zend_Mail_Storage_Pop3(Sconf1g);
}
catch (Zend_Mail_Protocol_Exception Se)
{
die ("Verbindungsfehler: ".Se->getMessage()):
}
$mail_id = (int) S_GETE’id*]:
$akt_mail = Smai1ESmail_id]:
// Haben wir eine einfache Text-E-Mail?
if (false === Sakt_mai1->isMultipart())
{
// Es handelt sich um eine Text-E-Mail
// Text kann (fast) direkt ausgegeben werden
Scontent = $akt_mai1->getContent();
// Zeichensatz aus dem Header extrahieren
Scharset = charsetAuslesen(
$akt_mail->getHeader('content-type'));
// Text nach UTF-8 konvertieren und ausgeben
echo nl2br(iconv (Scharset, 'UTF-8'.Scontent));
}
el se
{
//Es handelt sich um eine Muitipart-E-Mai 1
// Variable zum Speichern des E-Mail Textes
Sbody = '';
// Zum Speichern von Links auf Anhänge
$ 1 i n k s = '';
// Jeden einzelnen Part durchlaufen
foreach (new Recursivelteratorlterator($akt_mai1) as Spart)
{
Sheaders = Spart->getHeaders():
// Handelt es sich um einen Anhang?
if (false —== isset(SheadersE’content-disposition']))
//Nein, es ist ein / der Body
//Gibt es schon einen Body bzw. ist der aktuelle Body
// der HTML-Teil?
i f (Sbody === ’' ||
0 — strpos(SheadersE'content-type*1, 'text/html'))
{
// Zeichensatz aus dem Header extrahieren
Scharset = charsetAuslesen($headersE'content-type']);
295
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
// Text nach UTF-8 konvertieren und ausgeben
Sbody = iconv (Scharset, 'UTF-8',Spart->getContent());
// Wenn es ein HTML-Body ist, dann soll nur der
// Teil zwischen <body> und </body> extrahiert werden
if (0 —== strpos(SheadersE'content-type’],'text/html'))
{
preg_match("/<body[A>]*>(,*)<\/body>/si",Sbody,Smatch);
Sbody=$matchEl];
}
el se
{
// Kein HTML, dann brauchen wir Zeilenumbrüche
Sbody = nl2br(Sbody);
}
I
1
el se
// Es handelt sich um einen Anhang
// Anhänge auf der Platte speichern
// Daten Base64 codiert?
i f (strtoiower(SheadersE'content-transfer-encoding'])
— 'base64'
)
{
// Whitespaces ersetzen und Daten decodieren
Stmp = base64_decode(preg_repl ace( "/\s/’’,'',
Spart->getContent()));
el se
{
throw new Exception('Codierung nicht unterstützt');
// Dateinamen extrahieren
preg_match("/fi lename=E' \"]?( EA' \"]+)/’’,
SheadersE'content-disposition'], Sdateiname);
// Datei schreiben
fi1e_put_contents(Sdatei nameEU,Stmp);
// Eine Grafik die in der Mail dargestellt werden soll?
if (true — isset(SheadersE’content-id']))
{
// Content-ID aufbereiten
Seid = str_replace(SheadersE’content-id’]);
296
e-bol.net
E-AAails mit Zend_AAail verarbeiten | 6.1
$cid = str_replace(’>'.’',$cid);
$cid = "cid:$cid";
// Content-ID durch korrekten Dateinamen ersetzen
$body = str_replace($cid,$dateiname[l], $body);
}
el se
{
// Datei wird nicht eingebunden
// Links zum Download erstellen
$11nks.="<a href ='SdateinameEl]'>$dateiname[l]</a> "
}
)
// Ausgabe der E-Mail
echo "<table>";
// Absender und Betreff
echo "<tr><td>Absender</td>";
Sabsender = headerNachUtf8($akt_mail->from);
echo "<td>$absender</td></tr>";
echo "<trXtd>Betreff</td>";
Sbetreff = headerNachl)tf8( $akt_mai 1 ->subject);
echo "<td>$betreff</tdX/tr>";
// Body der E-Mail
echo XtrXtd colspan=’2' bgcolor=’#eeeeee’>
$body</tdX/tr>”;
// Haben wir Links auf Anhänge?
if(false === empty($1inks))
{
// Links ausgeben
echo "CtrXtd col span='2' >$1 i nks</tdX/tr>";
echo "</table>";
}
Listing 6.7 Verarbeiten von E-AAails mit Anhängen
Mit diesem Beispiel können E-Mails verarbeitet werden, die über Inline-Grafiken
und Anhänge verfügen. Bitte beachten Sie, dass es sich wirklich nur um ein klei-
nes Beispiel handelt, das zeigen soll, wie die Klasse genutzt werden kann. Dieses
Beispiel ist nicht dazu gedacht, produktiv eingesetzt zu werden. Möchten Sie eine
297
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
Anwendung erstellen, die E-Mails ausgeben kann, so setzen Sie sich bitte zuerst
mit dem Aufbau von E-Mails auseinander.
Wie eine E-Mail mit dem obigen Code dargestellt wird, sehen Sie in Abbildung
6.3.
OO o http://carsten-mohrkes-computer.local/~carsten/zf-buch/Mail/4.php CJ
Q u. © O O http://carsten-mohrkes-computer.local/~carst header coni
Absender Carsten Möhrke
Betreff Eine HTML-E-Mail mit Inlinc-Bildem und Anhang
Hallo Welt:-)
Das ist das PHP-Logo
Schick, oder?
netviser Internet Beratung e.K.
Carsten Möhrke - Zend Certified Engineer
Dr.-Viktoria-Steinbiß-Str. 1b - 33602 Bielefeld
Tel. 0521 / 9116046 Fax: 0521 / 9116047
Mobil 0171 / 6508784 Web: www.netviser.de
PHP-Funktioncn.pdf
Abbildung 6.3 HTML-E-Mail mit Inline-Grafik und Anhang
6.1.4 Löschen von E-Mails
Natürlich können Sie mit Zend_Mail auch E-Mails löschen. Hierzu ist die
Methode removeMessagef) vorgesehen. Auch diese ist in der POP3- und in der
IMAP-Klasse vorhanden, sodass Sie sie protokollübergreifend nutzen können.
Allerdings gibt es hier einen Unterschied in der Handhabung. Im POP3-Protokoll
kann direkt gelöscht werden, wobei das eigentliche Löschen erst nach der kor-
rekten Abmeldung vom Mail-Server erfolgt. Im IMAP-Protokoll hingegen wird
eine E-Mail zunächst zum Löschen markiert und danach gelöscht. Die IMAP-Vari-
ante der Methode markiert die E-Mail zum Löschen und führt danach den
Expunge-Befehl aus. Das hat zur Folge, dass alle E-Mails, die bereits zum Löschen
markiert waren, vom Server entfernt werden und nicht nur die eine.
Bei den bisher erläuterten Funktionalitäten war es ausreichend, mit der norma-
len ID der E-Mail zu arbeiten. Sollte Ihr Browser sich eventuell »alte IDs« gemerkt
298
e-bol.net
E-Mails mit Zend_Mail verarbeiten | 6.1
haben, und wurde eine E-Mail zwischendurch gelöscht, so könnte das schlimms-
tenfalls dazu führen, dass die falsche E-Mail dargestellt wird.
Gehen Sie beim Löschen aber genau so vor, kann es schnell passieren, dass die
falsche E-Mail gelöscht wird. Um das zu verhindern, unterstützt Sie die Klasse
Unique-IDs, die jede E-Mail eindeutig identifizieren kann - zumindest wenn Ihr
E-Mail-Server dieses Verfahren unterstützt.
Um eine Unique-ID auszulesen, können Sie die Methode getllni queId () nutzen,
die die »normale« ID der E-Mail übergeben bekommt. Die so ermittelte eindeu-
tige ID können Sie dann in Ihrer Anwendung nutzen, um die E-Mails eindeutig
zu identifizieren. Dummerweise benötigt der Mail-Server aber eine »Sequence-
Number«, sprich eine normale ID, um eine E-Mail zu löschen. Das heißt, um den
Löschvorgang zu initiieren, müssen Sie auf Basis der eindeutigen ID wieder die
aktuelle Sequence-ID der E-Mail ermitteln, was mit der Methode getNumberBy-
Uniqueldt) geschieht.
Das Script zum Anzeigen der E-Mails müsste die IDs folgendermaßen ermitteln
und nutzen:
// Verbindungsaufbau etc.
foreach ($mai1 as Snummer => Smessage)
{
Sunique = Smail->getUniqueId($nummer);
// Mail ausgeben
// Link zum Loeschen
echo "<a href=*loeschen.php?uid=".urlencode(Sunique)." ’>":
echo "Diese E-Mail löschen</a>":
1
Listing 6.8 Nutzung einer Unique-ID zum Löschen
Das Script, das dann den eigentlichen Löschvorgang vornimmt, müsste die Uni-
que-ID entgegennehmen und wieder in die Sequence-Nummer umwandeln, um
den Löschvorgang durchzuführen:
// Objekt ableiten etc.
Sid = Smai 1->getNumberByUniqueId(S_GET[’uid’]);
Smai1->removeMessage(Sid):
Listing 6.9 Löschen einer E-Mail auf Basis einer Unique-ID
Ich möchte an dieser Stelle nicht unerwähnt lassen, dass es bei der Nutzung von
POP3 auch eine Methode namens undeletef) gibt, die den Löschvorgang rück-
299
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
gängig macht. Das funktioniert allerdings nur so lange, wie das Script noch läuft
und sich noch nicht beim Server abgemeldet hat. Daher ist eine praktikable Nut-
zung dieser Methode wohl eher selten möglich. In der IMAP-Variante gibt es
keine solche Methode.
6.1.5 Erweiterte Möglichkeiten von IMAP
Wie schon erwähnt, unterscheiden sich das IMAP- und das POP3-Protokoll deut-
lich in ihren Möglichkeiten. IMAP kennt einige sehr interessante zusätzliche
Funktionalitäten. Hierbei handelt es sich zum einen um die Möglichkeit, E-Mails
mit Flags zu versehen, und zum anderen um die Möglichkeit, mit Ordnern zu
arbeiten.
Nutzung von Flags
IMAP unterstützt sechs Flags, die in Zend_Mai l durch entsprechende Konstanten
repräsentiert werden. Die Konstanten, die Sie in Tabelle 6.2 finden, sind in der
Klasse Zend_Mail_Storage deklariert.
Konstante Bedeutung
FLAG_$EEN Die Nachricht wurde angezeigt.
FLAG_ANSWERED Die E-Mail wurde beantwortet.
FLAG_FLAGGED Hierbei handelt es sich um eine Markierung, die frei genutzt werden kann.
FLAG_DELETED Die E-Mail soll gelöscht werden. (Sie liegt im »Papierkorb«.)
FLAG_DRAFT Es handelt sich um einen Entwurf einer E-Mail.
FLAG_RECENT Die E-Mail ist neu.
Tabelle 6.2 IMAP-Flags
Möchten Sie herausfinden, ob bestimmte Flags in einer E-Mail bereits gesetzt
sind, so können Sie das mit der Methode hasFl ag() überprüfen. Sie ist innerhalb
der eigentlichen E-Mail-Objekte (Klasse Zend_Mail_Message) deklariert und
bekommt jeweils eine der Konstanten aus Tabelle 6.2 als Parameter übergeben.
Ein boolescher Wert gibt an, ob das Flag gesetzt ist.
Der Server kann allerdings nicht erkennen, ob eine E-Mail bereits angezeigt
wurde. Das Setzen der Flags muss also ebenfalls vom Client vorgenommen wer-
den. Ihre Anwendung muss somit dafür sorgen, dass das entsprechende Flag
gesetzt wird, wenn eine E-Mail gelesen oder beantwortet wurde. Das wiederum
geschieht mithilfe der Methode set FI ags(). Aber Achtung: Sie ist nicht Teil der
Klasse Zend_Mai l_Message, sondern in der Klasse Zend_Mai l_Storage_Imap defi-
300
e-bol.net
E-AAails mit Zend_Mail verarbeiten | 6.1
niert. Das liegt daran, dass das Objekt der Message-Klasse die bereits herunterge-
ladene Nachricht repräsentiert, aber keine direkte Verbindung zum Server hat.
Die Methode bekommt als ersten Parameter die ID der E-Mail und als zweiten
ein Array mit einer oder mehreren Flag-Konstanten übergeben. Bitte beachten
Sie, dass das Recent-Flag nicht von Ihnen gesetzt werden kann. Darum kümmert
der Server sich selbst, wobei das Flag allerdings nicht von allen Mail-Servern
unterstützt wird.
// Verbindungsaufbau etc.
// Auflistung der E-Mails
foreach ($mai1 as $nummer => $message)
{
$betreff = headerNachl)tf8($message->subject);
// Wurde die Nachricht bereits gelesen?
if (false === $message->hasFlag(Zend_Mail_Storage::FLAG_SEEN))
{
// Nein, wurde noch nicht gelesen
echo "<b>$betreff</b><br>";
el se
{
// Ja, wurde gelesen
echo "$betreff<br>";
// Weitere Ausgaben
}
Listing 6.10 Auslesen von E-AAails via IAAAP
Möchten Sie eine E-Mail als gelesen markieren, so können Sie das so umsetzen:
// Ableiten der Objekte und Verbindundsaufbau
Sid = (int)$_GET[’id'];
Smail->setFlags($id, array(Zend_Mail_Storage::FLAG_SEEN));
In diesem Beispiel muss die ID natürlich per GET übergeben werden.
Haben Sie E-Mails mit dem Flag FLAG_DELETED markiert, können Sie diese alle auf
einmal löschen. Dazu ist im IMAP-Protokoll der Befehl EXPUNGE definiert, den
das Paket unterstützt. Er ist allerdings als Methode in der Klasse Zend_Mail_
Protocol_Imap implementiert, welche im Hintergrund eingesetzt wird und den
»Low-Level-Zugriff« realisiert. Sie können diese Klasse nutzen oder einfach die
schon erwähnte Methode removeMessage() aufrufen, die im Hintergrund ein
EXPUNGE an den Server sendet.
301
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
Nun stellt sich noch die interessante Frage, wie Sie ein Flag wieder entfernen.
Wie können Sie also eine bereits gelesene E-Mail wieder als »ungelesen« kenn-
zeichnen? Zum jetzigen Zeitpunkt ist hierfür leider noch keine Methode vorgese-
hen. Sie können aber zu einem - wie ich finde - etwas merkwürdigen Work-
around greifen.3 Die Konstanten sind als Integer-Werte deklariert, sodass Sie
einer Konstante also einfach ein Minus voranstellen können, um ein Flag wieder
zu entfernen. Um eine E-Mail wieder als ungelesen zu markieren, können Sie die
folgende Zeile nutzen:
$mail->setFlags($id, array(-Zend_Mail_Storage::FLAG_SEEN));
Mit Ordnern arbeiten
IMAP unterstützt Ordner. Dadurch haben Sie die Möglichkeit, neue E-Mails im
Posteingang zu halten, wohingegen E-Mails, die bereits zum Löschen markiert
sind, in einem »Papierkorb-Ordner« abgelegt werden können.
Wenn Sie ein neues IMAP-Postfach anlegen, ist standardmäßig der Ordner
INBOX, also der Posteingang vorhanden. Meist werden Ordner aber auch noch
von den E-Mail-Programmen angelegt. Da es keine Standardisierung für die
Namen oder Funktionalitäten der Ordner gibt, kann es auch schnell mehrere
»Papierkörbe« geben, sofern Sie unterschiedliche E-Mail-Programme nutzen.
Hinzu kommt das Problem, dass Ordner noch Unterordner haben können.
Um diese Problematik möglichst einfach in den Griff zu bekommen, ist die
Methode getFolders() deklariert. Sie ist in der Klasse Zend_Mail_Storage_Imap
deklariert und liefert alle vorhandenen Ordner als Zend_Mail_Storage_Folder-
Objekte zurück. Da diese Klasse das SPL-Interface Recursi velterator implemen-
tiert, ist es am einfachsten, das Objekt an den Konstruktor der SPL-Klasse Recur-
si velteratorlterator zu übergeben, mit welcher Sie dann über die einzelnen
Ordnerobjekte iterieren können. Sollten Sie sich nicht mit der Klasse Recursi -
velteratorlterator auskennen, so könnte es hilfreich sein, einen Blick in die
Dokumentation zu werfen4.
In der Schleife können Sie dann die Informationen über die einzelnen Ordner
abrufen. Die Methode getLocal Name() liefert dabei den lokalen Namen, also nur
den eigentlichen Namen des Ordners, und getGl obal Name() den globalen
Namen mit vorangestellten übergeordneten Ordnern.
Etwas ungewöhnlich an dieser Stelle ist vielleicht, dass Umlaute und Sonderzei-
chen in den Namen als UTF-7-codierte Strings zurückgegeben werden. Hier emp-
3 In der Klasse Zend_Mail_Protocol_Imap existiert die Methode storeO, die Sie ebenfalls nutzen
können.
4 http://www.php.net/~helly/php/ext/spl/
302
e-bol.net
E-AAails mit Zend_AAail verarbeiten | 6.1
fiehlt es sich, die Namen mithilfe von mb_convert_encoding() in einen anderen
Zeichensatz wie zum Beispiel UTF-8 zu wandeln.
// Objekt ableiten, Verbindung aufbauen etc.
// Ordner auslesen
$rec_iterator = $mai1->getFolders();
// Zur Nutzung in der Schleife aufbereiten
Sfolders = new Recursivelteratorlterator($rec_iterator,
Recursivelteratorlterator::SELF_FIRST);
echo "<tt>";
// Rekursiv über alle Ordner laufen
foreach ($folders as $name => $folder)
{
// Namen des Ordners nach UTF-8 konvertieren
$name = mb_convert_encoding($name,'UTF-8','UTF7-IMAP');
// Tiefe ermitteln
Stiefe = $folders->getDepth();
// Ordner einrücken wenn nötig
if ($tiefe > 0)
{
$name = "+->$name";
$name = str_repeat(' :'. ($tiefe-l)*3).$name;
// Globalen Namen als Info ausgeben
echo "Sname (Globaler Name: "
.$folder->getGlobalName().")<br>";
1
echo "</tt>'’;
Listing 6.11 Auslesen einer lAAAP-Ordnerstruktur
Eine mögliche Ausgabe dieses Listings sehen Sie in Abbildung 6.4.
n n n Q ; © Ö © IMAP CD'
Google
Entwürfe (Globaler Name: Entw&APw-rfe)
Gelöscht (Globaler Name: Gel&APY-scht)
Gesendet (Globaler Name: Gesendet)
INBOX (Globaler Name: INBOX)
+->wichtig (Globaler Name: INBOX/wichtig)
+->vom Chef (Globaler Name: INBOX/wichtig/vom Chef)
+->von Mutti (Globaler Name: INBOX/wichtig/von Mutti)
Trash (Globaler Name: Trash)
Abbildung 6.4 lAAAP-Ordnerstruktur
303
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
Unterhalb des Ordners INBOX befindet sich der Ordner »wichtig«, welcher noch
zwei Unterordner hat. Die globalen Namen wurden in diesem Beispiel zur Infor-
mation ausgegeben und nicht decodiert. Wollen Sie diese produktiv nutzen, so
müssen Sie sie in einen brauchbaren Zeichensatz konvertieren.
Die codierte Variante des globalen Ordnernamens können Sie allerdings gut nut-
zen, um in den Ordner hinein zu wechseln. Dabei sollten Sie allerdings zuvor
prüfen, ob der Ordner überhaupt selektiert werden kann, was mit der Methode
isSelectable() geschieht, welche aus dem Folder-Objekt heraus aufgerufen
wird.
Wenn Sie also die E-Mails, die in einem Ordner enthalten sind, auslesen wollen,
können Sie den globalen Namen an die Methode selectFolder() übergeben.
Der Methode können Sie auch direkt ein Folder-Objekt übergeben. Kennen Sie
die Struktur der Ordner schon, so können Sie die Namen der Ordner übrigens
auch direkt als Eigenschaften auf das Rückgabeobjekt der Methode getFoldersO
anwenden. Um direkt auf den Ordner »wichtig« aus dem obigen Beispiel zuzu-
greifen, könnten Sie folgendes Konstrukt nutzen:
$alle_ordner = $mai1->getFolders():
echo $al 1e_ordner->INBOX->wi chti g->getGlobalName();
// Ausgabe: INBOX/wichtig
Diese Syntax funktioniert natürlich nur dann, wenn die Namen der Ordner kom-
patibel zur PHP-Syntax sind. Ordner, die UTF-7-Sonderzeichen oder Leerzeichen
im Namen haben, können Sie so nicht ansprechen. Nach dem Wechsel in einen
Ordner können Sie mit der Methode getCurrentFolder () jederzeit den globalen
Namen des Ordners, in dem Sie sich gerade befinden, auslesen:
Smail->selectFolder(’INBOX/wichtig/von Mutti');
$akt_folder = Smai1->getCurrentFolder();
echo "Globaler Name des aktuellen Ordners: $akt_folder";
// Ausgabe: INBOX/wichtig/von Mutti
Sollten Sie noch komplexere Operationen mit Ordnern umsetzen wollen, so wer-
fen Sie bitte einen Blick auf die Methoden, welche die Klasse Zend_Mail_
Storage_Fol der zur Verfügung stellt. So ist es beispielsweise auch möglich, mit
create() neue Ordner anzulegen oder Ordner mit removeFol der() zu löschen.
E-Mails kopieren und verschieben
Der Server kann natürlich nicht wissen, in welchen Ordner eine E-Mail einsor-
tiert werden soll. Neue E-Mails landen zunächst immer im Ordner INBOX.
Möchten Sie die E-Mails Ihrer Mutter automatisch in den Ordner »von Mutti«
verschieben, müssten Sie dies mithilfe eines Scripts implementieren.
304
e-bol.net
E-Mails mit Zend_Mail verarbeiten | 6.1
Eine E-Mail können Sie mit der Methode copyMessage() in einen anderen Ord-
ner kopieren. Als ersten Parameter bekommt sie die Sequence-Nummer der E-
Mail und als zweiten den globalen Namen des Zielordners bzw. eine Referenz auf
das Ordnerobjekt übergeben. In den meisten Fällen werden Sie eine E-Mail aber
nicht nur kopieren, sondern auch verschieben wollen. Das heißt, Sie müssten die
E-Mail danach mit der Methode removeMessage() löschen. Das ist dann aber lei-
der diejenige Stelle, an der es ein wenig trickreich wird. Sie können die E-Mails
nicht direkt in der Schleife verschieben, mit der Sie den Inhalt eines Ordners aus-
lesen, da der Iterator sonst versucht, auf E-Mails zuzugreifen, die nicht mehr da
sind, was in einer Exception resultiert. Das zweite Problem sind die Sequence-
Nummern. Löschen Sie eine E-Mail, so erhalten die nachfolgenden E-Mails eine
neue Sequence-Nummer, die um eins kleiner ist. Unterstützt Ihr Server eindeu-
tige IDs, sollten Sie diese unbedingt nutzen. Ist das nicht der Fall, müssen Sie die
E-Mails von hinten, also mit der größten ID beginnend, verschieben. Eine solche
Implementation könnte so aussehen:
$cnt = 0:
foreach ($mai1 as $id => $message)
{
$absender = $message->from;
// ist die E-Mail von Mutti?
if (false !== stripos($absender,'mutti@example.com'))
{
// ID merken
$ids[] = $id:
$cnt++;
}
// array mit IDs umdrehen
$ids = array_reverse($ids):
foreach ($ids as $id)
{
// E-Mail in neuen Ordner kopieren
$mai1->copyMessage($id, 'INBOX/wichtig/von Mutti'):
// und im ursprünglichen Ordner löschen
$mai1->removeMessage($id);
}
echo "Achtung: $cnt neue Mails von Mutti":
Listing 6.12 Verschieben von E-Mails ohne Unique-ID
Die hier beschriebenen Funktionen stellen keineswegs den kompletten Umfang
der Klasse dar. Da der Platz in einem Buch aber leider immer beschränkt ist,
305
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
musste ich mich hier auf die wichtigsten Funktionen beschränken. In der Doku-
mentation finden Sie noch weitere Hinweise.
6.2 JSON-Daten mit Zend_Json verarbeiten
Client-Server-Kommunikation ist ein altbekanntes Prinzip, das auch die Basis des
Internets darstellt. Im Zeitalter von Web 2.0 und von AJAX hat sich allerdings
eine Neuerung ergeben. AJAX erfordert, dass Daten im Hintergrund ausge-
tauscht werden können. Und das soll natürlich so schnell wie möglich passieren.
Daher ist es wichtig, das Datenvolumen so gering wie möglich zu halten. Übli-
cherweise setzt AJAX dabei auf XML, wie das X im Namen schon vermuten lässt.
Eine Darstellung von Daten in einem XML-Format hat allerdings oft einen gro-
ßen Overhead zur Folge. Eine sehr einfache Lösung ist, auf XML zu verzichten
und die Daten im JSON-Format darzustellen. JSON ist die Abkürzung für »Java-
Script Object Notation«. Es handelt sich um ein Datenformat, das nativ in Java-
Script übernommen werden kann. Darüber hinaus unterstützen auch viele
andere Sprachen inzwischen JSON, sodass Sie darüber mit anderen Sprachen
Daten austauschen können.
Bitte beachten Sie, dass die Daten, die Sie an JSON übergeben, immer nach UTF-
8 codiert sein sollten.
Diese Klasse zu nutzen, ist sehr unproblematisch. Im Endeffekt benötigen Sie nur
die beiden statischen Methoden encode() und decode(). Sollten Sie sich gerade
fragen, warum es diese Klasse überhaupt gibt, wo in PHP doch schon entspre-
chende Methoden zur Konvertierung existieren, dann ist das recht einfach zu
erklären. Die Methoden sind erst ab PHP 5.2.0. vorhanden. Sind sie vorhanden,
so greift die Klasse auch darauf zurück. Sind sie aber nicht vorhanden, so gene-
riert Zend_Json eigenständig den JSON-Code.
Die erste codiert die Daten und liefert einen String zurück, der direkt in Java-
Script eingebunden werden kann.
< ? php
require_once ’Zend/Json.php’;
// Daten die in JS eingebettet werden sollen
Sdaten = array ('vorname' =>
utf8_encode('Homer'),
'nachname' =>
utf8_encode('Simpson'));
306
e-bol.net
JSON-Daten mit Zend_Json verarbeiten
I 6-2
// Daten kodieren und in einem String ablegen
$json_data=Zend_Json::encode($daten);
?>
<script type="text/javascript">
// Hier werden die Daten mit PHP ausgegeben
// und einer JavaScript-Variable zugewiesen
var name = <?php echo $json_data; ?>;
seif.document.write("Daten: ");
seif.document.write(name.vorname+" ");
seif.document.wri te(name.nachname);
</script>
Listing 6.13 Ausgabe von JSON-codierten Daten
Bei der Ausführung dieses Scripts erhält der Browser die nachfolgenden Daten:
<script type=”text/javascript">
// Hier werden die Daten mit PHP ausgegeben
// und einer JavaScript-Variable zugewiesen
var name = {"vorname":"Homer","nachname":"Simpson"};
seif.document.write("Daten: ");
seif.document.write(name.vorname+" ");
seif.document.wri te(name.nachname):
</script>
O http://l.../eins.php CD
W Q Q 6
Daten: Homer Simpson
Abbildung 6.5 Ausgabe der JSON-Daten mit JavaScript
Die Daten stehen in JavaScript also sofort nativ zur Verfügung. Sollten Sie Daten
dynamisch nachladen, müssten Sie den empfangenen String, also die JSON-
Daten, an die JavaScript-Funktion evalO übergeben, welche Ihnen dann ein
JavaScript-Objekt zurückgibt.
Möchten Sie mithilfe der Klasse JSON-Daten wieder in PHP-Datenstrukturen
umwandeln, ist das ebenfalls kein Problem. Dafür ist die Methode decodet) vor-
gesehen. Sie bekommt die JSON-codierten Daten als Parameter übergeben und
liefert ein Objekt oder ein Array zurück. Standardmäßig liefert die Methode ein
307
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
assoziatives Array zurück, das Sie mithilfe des zweiten Parameters bestimmen
können. Übergeben Sie hier Zend_Json: :TYPE_OBJECT, so liefert die Methode
ein Objekt. Ohne zweiten Parameter oder mit Zend_Json:: TYPE_ARRAY erhalten
Sie das schon angesprochene assoziative Array.
6.3 Generieren von PDF-Dokumenten
Dynamisch PDF-Dokumente zu generieren, ist seit jeher ein interessantes Thema
bei Webanwendungen. Gerade in diesem Bereich gibt es viele altgediente Klas-
sen und Funktionssammlungen, wie beispielsweise FPDF. Diese erfüllen zwar
alle ihren Zweck, sind oft aber etwas störrisch zu handhaben und bieten vor
allem häufig nicht alle Features, die man benötigt.
Zend_Pdf bietet einige Features, die viele andere Tools nicht beherrschen. Dazu
gehört die Möglichkeit, PDF-Dokumente einzulesen. Bald soll es auch möglich
sein, JavaScript einzubinden. Eine Möglichkeit zum Generieren von Formularen
sucht man auch bei Zend_Pdf leider vergebens. Außerdem ist Zend_Pdf zum jet-
zigen Zeitpunkt (Version 1.0.3) noch nicht sehr leistungsfähig. Das heißt, es gibt
noch keine Möglichkeiten, um Texte zu zentrieren oder über mehrere Seiten aus-
geben zu lassen. Dennoch lassen die Ansätze und die Konzeption des Pakets auf
spannende zukünftige Funktionalitäten hoffen.
Die Klasse Zend_Pdf unterscheidet sich in ein paar Punkten von vielen anderen
PDF-Paketen. Daher möchte ich diese Punkte erläutern, um Ihnen den Einstieg
zu erleichtern.
Der »Nullpunkt« einer Seite, also die Stelle, an der die X- und die Y-Koordinate
ihre Nullstelle haben, befindet sich hierbei links unten und nicht oben, wie es
sonst oft der Fall ist.
Die Koordinaten werden in Punkten, genauer gesagt in DPI angegeben. Ein DPI
ist hierbei, wie in der EDV üblich, als 1/72 Inch (ca. 0,35 mm) definiert. Eine
Angabe in Millimeter ist leider nicht vorgesehen. Des Weiteren werden auch nur
Integer-Werte für die Angabe von Positionen unterstützt.
Wenn Sie lieber mit Millimeterangaben arbeiten, könnten Sie beispielsweise eine
Funktion wie die folgende zum Konvertieren nutzen:
function mm2dpi ($mm)
{
$faktor = 25.4/72;
return round($mm / $faktor);
}
308
e-bol.net
Generieren von PDF-Dokumenten | 6.3
Anzumerken bleibt noch, dass Zend_Pdf erstaunlicherweise keine standardisierte
Methode vorsieht, um ein PDF-Dokument direkt an den Browser des Benutzers
zu schicken.
Die Vorgehensweise beim Generieren einer neuen Datei ist recht einfach. Zuerst
wird ein neues PDF-Objekt abgeleitet. Diesem müssen dann neue Seiten hinzu-
gefügt werden. Diesen Seiten wiederum können Stile zugewiesen werden, die
für die Ausgabe von Texten zur Verfügung stehen.
Ein wenig gewöhnungsbedürftig dürfte dabei sein, dass Sie nicht wie bei anderen
Paketen die Schriftlage (z. B. kursiv) oder Schriftstärke (z. B. fett) einfach umschal-
ten können. Zend_Pdf löst diese Anforderungen über unterschiedliche Schriftar-
ten, wie Sie gleich sehen werden.
Das folgende Listing generiert ein kleines Beispiel-PDF-Dokument:
require_once 'Zend/Pdf.php' ;
try {
// Neues PDF-Objekt ableiten
$pdf = new Zend_Pdf();
// Neue Seite generieren
$page = new Zend_Pdf_Page(Zend_Pdf_Page;:SIZE_A4);
// Seite in das PDF-Dokument einfügen
$pdf->pages[] = $page;
// Neues Stil-Objekt ableiten
tstyle = new Zend_Pdf_Style();
// Font zuweisen
$font = Zend_Pdf_Font::fontWithName(
Zend_Pdf_Font::FONT_HELVETICA);
$style->setFont($font, 32);
// Stil der Seite zuweisen
$page->setStyle($style);
// Text in Seite ausgeben
$page->drawText('Hal 10 Welt!', 10, 800);
// PDF als String auslesen
tstring = $pdf->render();
1
catch (Zend_Pdf_Exception $e)
{
die('Fehler: '.$e->getMessage());
}
// Korrekten Header an den Browser schicken
309
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
header('Content-type: appl1cation/pdf'):
header('Content-Di sposition: attachment: fi1ename="datei.pdf"');
// PDF ausgeben
echo $string:
Listing 6.14 Erzeugen eines PDF-Dokuments mit Zend_Pdf
Der Konstruktor der Klasse Zend_Pdf benötigt keine Parameter. Die Seiten wer-
den einzeln generiert. Dazu muss jeweils ein neues Objekt von der Klasse Zend_
Pdf_Page instanziiert werden. Die übliche Vorgehensweise, um eine neue Seite
zu generieren, besteht darin, dem Konstruktor das Seitenformat als Konstante zu
übergeben. Hierzu sind in der Klasse Zend_Pdf_Page die Konstanten SIZE_A4,
SIZE_A4_LANDSCAPE, SIZE_LETTER und SIZE_LETTER_LANDSCAPE vorgesehen. Die
»Landscape-Varianten« sind jeweils für das Querformat der beiden Seitenformate
zuständig.
Nachdem die Seite fertig generiert ist, muss sie manuell in das Array pages ein-
gehängt werden. Auch das mag zunächst ein wenig gewöhnungsbedürftig
erscheinen, gibt Ihnen aber andererseits auch die Möglichkeit, die Seiten jeder-
zeit umzusortieren oder weitere Seiten einzufügen.
Bei dieser Vorgehensweise sollten Sie aber bitte nie vergessen, dass PHP 5 mit
Referenzen arbeitet und Objekte nicht kopiert werden.
Sofern Sie also mit
$page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
$pdf->pages[] = $page;
$pdf->pages[] = $page;
versuchen, zwei Seiten in das Dokument einzufügen, so gelingt das zwar, aber
die beiden Seiten sind absolut identisch, da im Hintergrund dasselbe Objekt refe-
renziert wird. Ein Seiten-Objekt mithilfe von clone zu duplizieren, ist übrigens
nicht möglich und führt zu einer Exception. Zurzeit muss jede Seite durch Instan-
tiieren eines neuen Objekts einzeln generiert werden.
Um Text auszugeben, sollte ein neuer Stil genutzt werden, der dann die Schrift
formatiert. Auch der Stil stellt dabei ein neues Objekt dar:
Sstyle = new Zend_Pdf_Style():
$font = Zend_Pdf_Font::fontWithName(
Zend_Pdf_Font::FONT_HELVETICA);
$style->setFont($font, 32):
Listing 6.15 Nutzen eines Stil-Objekts
310
e-bol.net
Generieren von PDF-Dokumenten | 6.3
Die Schriftart wird mithilfe der Methode setFont() zugewiesen. In diesem Bei-
spiel erhält sie eine der 14 Schriftarten übergeben, die jeder PDF-Reader kennt.
Die Zuweisung erfolgt mithilfe der statischen Methode fontWi thName(), welche
den Namen der Schriftart in Form einer Konstante übergeben bekommt. Hier
können Sie auf die folgenden Konstanten zugreifen:
FONT_COURIER, FONT_COURIER_BOLD, FONT_COURIER_ITALIC . FONT_COURIER_
BOLD_ITALIC. FONT_TIMES. FONT_TIMES_BOLD, FONT_TIMES_ITALIC, FONT_
TIMES_BOLD_ITALIC. FONT_HELVETICA, FONT_HELVETICA_BOLD. FONT_HELVETICA_
ITALIC. FONT_HELVETICA_BOLD_ITALIC. FONT_SYMBOL und FONT_ZAPFDINGBATS.
Ich denke, dass die Namen der Schriftarten für sich sprechen, sodass ich sie nicht
ausführlich erläutern muss. Die BOLD-Varianten stellen jeweils einen fetten
Schriftschnitt dar, und ITALIC steht für kursive Varianten.5 Sie können allerdings
auch TrueType-Fonts nutzen, wie Sie gleich noch sehen werden.
Die Methode setFont() bekommt als zweiten Parameter die Schriftgröße in
Punkten übergeben.
Der so generierte Stil wird mit setStylet) für die nächste Ausgabe selektiert.
Das heißt, die Methode sorgt dafür, dass dieser Stil so lange selektiert bleibt, bis
Sie einen anderen Stil wählen. Somit ist ein solcher Stil also nicht direkt an den
ausgegebenen Text geknüpft. Die Reihenfolge, in der die Methoden ausgeführt
werden, ist demnach entscheidend. Entscheiden Sie anschließend, einen anderen
Stil zu nutzen, sind alle Texte, die bereits ausgegeben wurden, davon nicht
betroffen.
Wenn Sie sich ein wenig näher mit der Klasse beschäftigen, werden Sie feststel-
len, dass man hier nicht unbedingt einen Stil hätte nutzen müssen. Es wäre auch
möglich gewesen, die Schriftart mit der Methode setFontO festzulegen, die
ebenfalls in der Klasse Zend_Pdf_Page deklariert ist. Die Nutzung von Stilen emp-
finde ich aber als flexibler und bevorzuge sie daher.
Die Methode drawText () ist schließlich für die Ausgabe des Textes zuständig. Sie
erwartet mindestens drei Parameter. Das ist zum Ersten der eigentliche Text, der
ausgegeben werden soll. Der zweite Parameter ist die X-Koordinate, also die
Angabe, wie weit der Text vom linken Rand des »Blattes« entfernt sein soll. Oder
anders formuliert: Wie weit er nach rechts verschoben werden soll. Der dritte
Parameter definiert, wie weit der Text vom unteren Rand des Blattes entfernt
sein soll, sprich wie weit er nach oben verschoben werden soll.
5 Die Schriftarten Symbol und ZapfDingbats führten zu Problemen. Bitte testen Sie vor der
Nutzung, ob diese Probleme inzwischen ausgeräumt sind.
311
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
Links vom Text gibt es also einen Rand mit einer Breite von 10 Punkten (ca. 3,5
mm), und bis zum unteren Rand sind es 800 Punkte, was etwa 280 mm ent-
spricht.
Da der Text Hallo Welt! nicht nur stets verwendet wird, um neue Dinge vorzu-
stellen, sondern auch den Vorteil hat, keine Umlaute zu enthalten, war die Aus-
gabe des Textes hier unproblematisch. Möchten Sie einen Text mit Umlauten
oder anderen Sonderzeichen ausgeben, ist es ratsam, den Text UTF-8-codiert zu
übergeben. In diesem Fall müssen Sie der Methode drawText() mit dem vierten
Parameter aber noch den verwendeten Zeichensatz mitteilen:
$page->drawText(utf8_encode('Hallo schöne Welt!’),
10, 800, 'utf-8');
Die Methode renderO liest das gesamte PDF-Dokument schließlich als String
aus. Nachfolgend werden nur noch die Header zum Browser geschickt, und das
Dokument wird anschließend direkt mit echo ausgegeben. Zwar ist an dieser
Klasse sicherlich das eine oder andere ungewöhnlich, aber grundsätzlich ist sie
einfach zu handhaben. Das fertige Dokument sehen Sie in Abbildung 6.6.
Alternativ wäre es auch möglich gewesen, die Datei direkt auf dem Server zu
speichern. Dafür ist die Methode save() deklariert, die den Dateinamen inklu-
sive des Pfads übergeben bekommt und das PDF-Dokument unter diesem Namen
auf der Festplatte des Servers abspeichert. Wird ein PDF-Dokument mehrfach
312
e-bol.net
Generieren von PDF-Dokumenten | 6.3
heruntergeladen, so ist es sicher deutlich performanter, das Dokument nur ein
Mal zu erzeugen und nachfolgend lediglich zum Download anzubieten.
6.3.1 Nutzung anderer Schriften
Nachdem Sie nun einen Überblick über die Nutzung haben, möchte ich auf etwas
komplexere Formatierungen eingehen. Zunächst ist zu klären, wie Sie eine
Schriftart außer den vorgegebenen nutzen können. Dabei sei mir zunächst der
Hinweis gestattet, dass Sie - wenn möglich - bei den vorgegebenen Schriften
bleiben sollten. Das hat zwei entscheidende Vorteile: Erstens funktionieren sie
überall, und zweitens müssen sie nicht in das Dokument eingebettet werden.
Sollten Sie aber eine andere Schriftart benötigen, ist das grundsätzlich kein Pro-
blem. Sie können einen beliebigen TrueType-Font nutzen. Sie benötigen ledig-
lich die TTF-Datei, in der die Schriftart definiert ist. Dummerweise funktionieren
momentan nicht alle TrueType-Dateien. Meist haben Sie aber Erfolg, wenn Sie
einen echten TrueType-Font und keinen OpenType-Font nutzen. Leider können
Sie das nicht unbedingt an der Dateiendung erkennen, da auch OpenType-Fonts
die Endung .ttf haben können. Es gibt allerdings mehrere Programme, welche
Ihnen mitteilen, um was für eine Art von Datei es sich handelt. Sollten Sie den-
noch eine Font-Datei erwischen, die nicht kompatibel ist, so erkennen Sie das
daran, dass das resultierende PDF-Dokument leer ist. Am besten probieren Sie
die Datei vorher also aus.
Der zweite Punkt bei der Nutzung einer anderen Schriftart ist die Dateigröße des
PDF-Dokuments. Diese wächst unter Umständen deutlich. Das liegt daran, dass
die Schriftart in das Dokument eingebettet wird. Zwar können Sie das Einbinden
der Daten auch verhindern, aber das würde voraussetzen, dass die Schriftart, die
Sie genutzt haben, auch auf dem Rechner installiert ist, auf dem das PDF-Doku-
ment angezeigt werden soll.
Um eine andere Schriftart zu nutzen, können Sie anstelle der Methode f ontWi th -
Name() die Methode fontWithPath() verwenden. Als ersten Parameter überge-
ben Sie ihr den Namen der Schriftart-Datei inklusive des Pfads, falls das notwen-
dig ist. Als zweiten Parameter können Sie noch weitere Kostanten übergeben.
Übergeben Sie hier Zend_Pdf_Font:: EMBED_DONT_EMBED, wird die Schriftart
nicht mit in das PDF-Dokument eingebettet. Wie gesagt, muss die Schriftart dann
aber auf dem Zielsystem verfügbar sein, damit die Darstellung korrekt ist. Eine
zweite mögliche Konstante ist hier Zend_Pdf_Font:: EMBED_DONT_COMPRESS.
Hiermit wird die Schriftart zwar mit in das Dokument eingefügt, aber nicht kom-
primiert. Das heißt, dass die Dateigröße zwar wächst, dafür aber steigt die Kom-
patibilität mit einigen PDF-Anzeigeprogrammen. Zugegebenermaßen sollte die
313
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
Kompatibilität heutzutage kein Problem mehr sein. Aber das hängt natürlich
auch von der Zielgruppe und der dort vorhandenen Rechnerausstattung ab.
Der Parameter lässt sich mit der Konstante Zend_Pdf_Font:: EMBED_DONT_SUBSET
kombinieren, indem Sie die beiden Werte mit einem Bit-Oder (|) verbinden. PDF
bietet die Möglichkeit, nur eine Untermenge der Zeichen, die in einer TTF-Datei
enthalten sind, einzubinden, was ja auch sinnvoll ist, da normalerweise nicht alle
Zeichen benötigt werden. Mit dieser Konstante können Sie dieses Verhalten
unterbinden, sodass der komplette Datenbestand der TTF-Datei eingebunden
wird.6
Möchten Sie Texte fett, kursiv oder unterstrichen darstellen, so ist das zum der-
zeitigen Zeitpunkt noch nicht einfach möglich. Für kursive und fette Darstellung
können Sie momentan nur auf eine Datei mit einem anderen Schriftschnitt
zurückgreifen. Um einen Text zu unterstreichen, müssen Sie zurzeit noch manu-
ell eine Linie darunter zeichnen.
Das folgende Listing generiert ein PDF-Dokument unter Verwendung verschie-
dener Schriften:
try {
$pdf = new Zend_Pdf():
$page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
$pdf->pages[] = tpage:
$font = Zend_Pdf_Font::fontWithPath('Comic Sans MS.ttf'):
$page->setFont($font, 32):
$page->drawText('Comic Sans MS’, 10, 800):
$font = Zend_Pdf_Font::fontWithPath('Arial.ttf’):
$page->setFont($font, 32):
$page->drawText('Arial’, 10, 750):
$font = Zend_Pdf_Font::fontWithPath('Arial Bold.ttf'):
$page->setFont($font, 32):
$page->drawText('Arial fett', 10, 700):
tstring = $pdf->render():
I
catch (Zend_Pdf_Exception $e)
{
die("Fehler: ".$e->getMessage());
6 Augenscheinlich wird zurzeit aber immer die komplette Datei inkludiert, sodass diese
Konstante wirkungslos ist.
3M
e-bol.net
Generieren von PDF-Dokumenten | 6.3
header('Content-type: application/pdf');
header('Content-Di sposition: attachment; fi1ename="datei.pdf"'):
echo $string;
Listing 6.16 Nutzung verschiedener Schriften in einem PDF-Dokument
Das generierte PDF-Dokument sehen Sie in Abbildung 6.7.
[*| datei-7.pdf (1 Seite)
Bewegen Text Auswahlen
Seitenleiste
Comic Sans AAS
Arial
Arial fett
Abbildung 6.7 PDF-Dokument mit verschiedenen TrueType-Schriften
Wie Sie sehen, sind die Formatierungsmöglichkeiten für Texte zurzeit noch recht
beschränkt. Lediglich die Farbe können Sie ändern, wie Sie später noch sehen
werden.
6.3.2 Mehrzeilige Fließtexte
Zuvor möchte ich Ihnen aber noch eine Lösung für ein größeres Problem mit auf
den Weg geben. Wie Sie beim Ausprobieren wahrscheinlich schon herausgefun-
den haben, kann drawTextO keine Zeilenumbrüche verarbeiten oder mehrzei-
lige Texte darstellen. Sicherlich wird dieses Feature noch ergänzt werden. Sollte
315
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
das aber noch nicht der Fall sein, wenn Sie die Klasse nutzen bringt Sie das nicht
weiter. Daher hier ein möglicher Lösungsansatz.
Die Idee ist recht einfach. Der Text, der ausgegeben werden soll, wird zunächst
in einzelne Worte zerlegt. Der Text wird dabei jeweils an den Leerzeichen zwi-
schen den Worten »zerschnitten«. Danach wird bei jedem einzelnen Wort für
jedes Zeichen die Breite ermittelt. Die Breiten der einzelnen Zeichen werden
addiert, wodurch sich die Breite des Wortes ergibt. Dabei darf nicht vergessen
werden, ein Leerzeichen mit in die Berechnung einfließen zu lassen, weil dies ja
beim Zerschneiden als Delimiter genutzt wurde und somit verlorengegangen ist.
Die Breitenangaben der einzelnen Worte werden dann so lange addiert, bis die
zulässige Breite für eine Zeile erreicht ist. Danach wird einfach eine neue Zeile
initialisiert.
Damit Sie den Code verstehen können, benötigen Sie noch ein paar Hintergrund-
informationen. Bei einer Glyphe handelt es sich um die Information, wie ein Zei-
chen dargestellt wird. Das heißt, eine Glyphe ist die konkrete Information in der
Schriftartdatei, wie ein Buchstabe darzustellen ist. Anhand einer Glyphe kann
man also auch erkennen, wie breit der Buchstabe dargestellt wird. Die Glyphen
werden den normalen Buchstaben über einen 16 Bit-Code zugeordnet. Daher fin-
det in dem Code auch eine etwas kryptische Umrechnung statt.
Dann ist da noch das Problem mit der Laufweite. Der Abstand zwischen den
Buchstaben bzw. Glyphen wird auch als Laufweite bezeichnet. Leider kann man
die Laufweite zurzeit weder auslesen noch beeinflussen. Das heißt, man kann nur
die Breite der Buchstaben ermitteln. Sollten in einer Zeile sehr viele schmale
Buchstaben wie »i« oder »1« enthalten sein, so sind dort sehr viele Zwischen-
räume vorhanden, weil einfach mehr Buchstaben in die Zeile passen. Da die
Breite dieser Zwischenräume allerdings nicht erfasst werden kann, wird die Zeile
dann deutlich breiter als der berechnete Wert. Um das zu kompensieren, wird
anhand der Anzahl der Buchstaben ein Korrekturwert berechnet. Zugegebener-
maßen ist der dort enthaltene Faktor geschätzt, aber zumindest scheint er gut
geschätzt zu sein und sorgt für eine ordentliche Darstellung.
Allerdings hat das Verfahren noch einige Schwächen. So werden beispielsweise
Worte nicht getrennt oder Satzzeichen nicht erkannt. Größere Einschränkungen
sind auch, dass es den Text nur auf eine Seite verteilt, und dass der Code nur ISO-
8859-1 unterstützt. Beides können Sie aber auch selbstständig erweitern. Des
Weiteren ist der Code auch eher darauf ausgelegt, verständlich zu sein, und
könnte deutlich schneller werden, wenn man ihn ein wenig optimierte.
316
e-bol.net
Generieren von PDF-Dokumenten | 6.3
require_once 'Zend/Pdf.php' ;
function zeichenßreite(Szeichen, Zend_Pdf_Styl e Sstyle)
{
// Schriftgröße auslesen
Sgroesse = $style->getFontSize();
// Font-Objekt auslesen
Sfont = Sstyle->getFont();
// Zeichen nach UTF-16 codieren
Szeichen = iconv('ISO-8859-1', 'UTF-16BE', Szeichen);
// In die interne Code-Darstellung konvertieren
Scode = ord($zeichen{0)) << 8 | ord($zeichen{1));
// Nummer der Glyphe auslesen
Sglyphe = Sfont->cmap->glyphNumberForCharacter($code);
// Breite der Glyphe in Maßeinheit em ermitteln
Sbreite = Sfont->widthForGlyph(Sglyphe);
// Umrechnen von em nach Punkt
Sbreite = ceil(Sbreite / Sfont->getUnitsPerEm() * Sgroesse);
return $breite;
function wortBreite(Swort, Zend_Pdf_Style Sstyle)
{
Sbreite = 0:
for(Scnt = 0: Scnt < strien(Swort); Scnt++)
{
$breite = Sbreite + zeichenBreite(Swort{Scnt), Sstyle);
// Korrekturfaktor um den Abstand
// zwischen den Buchstaben zu kompensieren
Skorrektur = strlen($wort)*0.2;
return (Sbreite + Skorrektur);
function erstel1eZei1 en ($text, Zend_Pdf_Style $style,
Szei1enbrei te)
// Text in einzelne Worte aufsplitten
Swoerter = explode(' Stext);
// Array für die Zeilen initialisieren
Szei1 en = array();
Szei1 en CO ] = '';
317
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
// Zähler um auf die Zeilen zuzugreifen
$zei1enzehler = 0;
// Speichert die Breite der aktuellen Zeile
$aktuel1e_breite = 0;
// Über alle Wörter laufen und Länge ermitteln
foreach (twoerter as $wort)
{
// Leerzeichen nach Wort ergänzen
$wort = $wort.' ’ ;
// Breite des Wortes zzgl. Leerzeichen auslesen
$wortbreite = wortBreite($wort, $style);
//Passt das Wort noch in die Zeile?
if (($aktuel1e_breite + $wortbreite) < $zei1enbreite)
// Zeilenlänge aufaddieren
taktuel1e_breite = $aktuel1e_breite + $wortbreite;
// Wort anfügen
$zei1en[$zei1enzehler] .= $wort;
el se
// Wort passte nicht mehr in die Zeile
// => Neue Zeile
$aktuel1e_breite = 0;
$zei1enzehler++;
$zei1en[$zei1enzehler] = $wort;
return $zeilen;
function gibFliesstextAus($text, Zend_Pdf_Page $page, $y)
{
$papierbreite = $page->getWidth();
$faktor = 25.4/72;
$rand = 10/$faktor; // 5mm Rand
// Berechnen der Zeilenbreite in Punkt
$zei1enbreite = f1oor($papierbreite - $rand*2);
// Style-Objekt auslesen
$style = $page->getStyle();
// Zeilenhoehe manuell berechnen
318
e-bol.net
Generieren von PDF-Dokumenten | 6.3
(zeilenhoehe = (style->getFontSize()+3;
(zeilen = erstel1eZei1en((text,(style. (zei1enbreite);
foreach ((zeilen as $zeile)
{
(page->drawText(utf8_encode((zeile), (rand, (y,’utf-8');
$y = (y-(zei1enhoehe;
try {
$pdf = new Zend_Pdf();
(page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
$pdf->pages[] = (page;
(style = new Zend_Pdf_Style();
(font = Zend_Pdf_Font::fontWithName(
Zend_Pdf_Font::FONT_HELVETICA);
(style->setFont((font. 12);
// Hier kommt der Goethe-Text
(text = 'Mir träumte
(page->setStyle((style);
gibFliesstextAus((text. $page,800);
(string = (pdf->render();
I
catch (Zend_Pdf_Exception (e)
{
dieCFehler: ".(e->getMessage());
}
header('Content-type: application/pdf');
header('Content-Di sposition: attachment; fi1ename="datei.pdf"');
echo (string;
Listing 6.17 Ausgabe von Fließtext mit Zend_Pdf
Der hier verwendete Text (der im Listing natürlich nicht komplett enthalten ist)
stammt übrigens von Goethe. Das fertig generierte PDF-Dokument sehen Sie in
Abbildung 6.8.
319
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
y daiei-22.pdf (1 Seite)
Vorherige Nächste
Zoomen Bewegen Text Auswahlen
(Lfl)
Seitenleiste
Suchen nach
Mir träumte neulich in der Nacht vor Pfingstsonntag, als stünde ich vor einem Spiegel und
beschäftigte mich mit den neuen Sommerkleidern, welche mir die lieben Eltern auf das Fest hatten
machen lassen. Der Anzug bestand, wie ihr wißt, in Schuhen von sauberem Leder, mit großen
silbernen Schnallen, feinen baumwollnen Strümpfen, schwarzen Unterkleidern von Sarsche, und
einem Rock von grünem Berkan mit goldnen Balletten. Die Weste dazu, von Goldstoff, war aus
meines Vaters Bräutigamsweste geschnitten. Ich war frisiert und gepudert, die Locken standen mir
wie Flügelchen vom Kopfe; aber ich konnte mit dem Anziehen nicht fertig werden, weil ich
immer die Kleidungsstücke verwechselte, und weil mir immer das erste vom Leibe fiel, wenn ich
das zweite umzunehmen gedachte. In dieser großen Verlegenheit trat ein junger schöner Mann
zu mir und begrüßte mich aufs freundlichste. "Ei, seid mir willkommen!" sagte ich, "es ist mir
ja gar lieb, daß ich Euch hier sehe " - "Kennt Ihr mich denn?" versetzte jener lächelnd -
"Warum nicht?" war meine gleichfalls lächelnde Antwort. "Ihr seid Merkur, und ich habe Euch oft
genug abgebildet gesehen." - "Das bin ich", sagte jener, "und von den Göttern mit einem
wichtigen Auftrag an dich gesandt. Siehst du diese drei Äpfel?* 1 - Er reichte seine Hand her und
zeigte mir drei Äpfel, die sie kaum fassen konnte, und die ebenso wundersam schön als groß
waren, und zwar der eine von roter, der andere von gelber, der dritte von grüner Farbe.
Abbildung 6.8 Fließtext in einem PDF-Dokument
6.3.3 Nutzung von Farben
PDF-Dokumente ohne Farbe sind meist langweilig. Daher bietet Zend_Pdf auch
die Möglichkeit an, mit Farben zu arbeiten. Sehr angenehm ist, dass Zend_Pdf
verschiedene Farbmodelle unterstützt, sodass Sie nicht auf RGB angewiesen sind.
Alle Farben haben allerdings gemeinsam, dass Sie ein Objekt der jeweiligen Farb-
klasse ableiten müssen, um die Farbe nutzen zu können.
Am ehesten wird Ihnen sicher der RGB-Farbraum bekannt sein, den man bei der
Programmierung ja öfter trifft. Hierbei setzt sich die Zielfarbe aus den Grundfar-
ben Rot, Grün und Blau zusammen. Wenn Sie ein Objekt der dafür zuständigen
Klasse Zend_Pdf_Col or_Rgb ableiten, übergeben Sie diesem drei Zahlen, die defi-
nieren, wie hoch die Intensität der entsprechenden Farbe sein soll. Die erste
Angabe bezieht sich auf den Rot-Anteil, die zweite auf den Grün-Anteil und die
letzte auf den Blau-Anteil. Die Zahlen dürfen jeweils einen Wert zwischen 0 und
1 haben, wobei 0 für 0 Prozent Farbanteil und 1 dementsprechend für 100 Pro-
zent Farbanteil steht.
Wollen Sie ein PDF-Dokument erzeugen, das später in einer Druckerei genutzt
werden soll, so benötigen Sie Farbangaben im CMYK-Format. Hierbei werden
die Farben aus den Grundfarben Cyan, Magenta, Yellow und Black gemischt. Die
dafür zuständige Klasse lautet Zend_Pdf_Col or_Cmyk. Auch hierbei übergeben Sie
dem Konstruktor einfach vier Zahlen, die zwischen 0 und 1 liegen dürfen womit
die Intensität der einzelnen Farbkanäle definiert wird.
320
e-bol.net
Generieren von PDF-Dokumenten | 6.3
Ein wenig ungewöhnlich mutet sicher die Möglichkeit an, HTML-Farbdefinitio-
nen nutzen zu können, aber auch diese Möglichkeit existiert. Der Klasse Zend_
Pdf_Col or_Html können Sie eine Farbdefinition im Zahlenformat, also zum Bei-
spiel '#AF12DE', oder einen Farbnamen wie 'red’, 'gray' o.Ä. übergeben. Bei
den Farbnamen stellt sich die Frage, welche Namen dem System bekannt sind.
Glücklicherweise dürfen Sie mehr Namen als die Namen der 16 Grundfarben
nutzen, die HTML kennt. Insgesamt stehen 140 Farbnamen zur Verfügung. Dabei
handelt es sich um die Farbnamen, die von allen Browsern gleich interpretiert
werden. Eine Liste dieser Namen finden Sie beispielsweise unter http://www.
mountaindragon.com/html/140names. htm.
Die letzte Möglichkeit ist die Nutzung der Klasse Zend_Pdf_Color_GrayScale.7
Hierbei übergeben Sie dem Konstruktur nur eine Zahl zwischen 0 und 1, mit wel-
cher Sie definieren, »wie grau« die Farbe sein soll. Bei 0 ergibt sich Schwarz,
wohingegen 1 für Weiß steht.
Wenn Sie ein entsprechendes Farbobjekt abgeleitet haben, können Sie damit
definieren, welcher Teil der Darstellung diese Farbe erhalten soll. Zend_Pdf
unterscheidet hierbei zwischen Füll- und Linienfarben. Die Farbe können Sie
dabei entweder einem Style-Objekt zuweisen oder direkt an das Page-Objekt
übergeben. In beiden Klassen sind die Methoden setFi 1 ICol or() und setLi ne-
Col or() definiert, denen Sie das Farb-Objekt beim Aufruf übergeben. Um einen
Text in Rot darzustellen, müssen Sie die Füllfarbe auf Rot setzen, bevor der Text
ausgegeben wird. Die Linienfarbe hat bei Texten keine Auswirkung:
// Page-Objekt instantiieren etc.
Sstyle = new Zend_Pdf_Style():
$font = Zend_Pdf_Font::fontWithName(
Zend_Pdf_Font::FONT_HELVETICA_BOLD):
$style->setFont($font, 30):
// Neues Farbobjekt -> 100% Rot, 0% Grün, 0% Blau
$rot = new Zend_Pdf_Color_Rgb(1,0,0);
// Farbe an Stil übergeben
$style->setFillColor($rot);
$page->setStyle($style);
$page->drawText('Hallo Weit’,10,800):
Listing 6.18 Ausgabe eines farbigen Textes
7 Bitte beachten Sie, dass das S von GrayScale wirklich groß zu schreiben ist.
321
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
6.3.4 Zeichnen in PDF-Dokumenten
Nachdem Sie nun schon erfahren haben, wie Sie die Farbe einer Linie bestim-
men, stellt sich noch die Frage, wie Sie Linien oder auch andere Objekte zeich-
nen. Hierzu sind einige Methoden deklariert, die Sie dabei unterstützen. Zurzeit
sind allerdings noch nicht alle Funktionskörper mit Leben gefüllt. Das heißt,
einige Methoden sind deklariert, aber noch leer. Sollten Sie also darüber stol-
pern, dass noch mehr Methoden existieren als die hier besprochenen, dann liegt
das daran, dass sie zum jetzigen Zeitpunkt noch nicht implementiert sind.
Bevor ich zu den Zeichenfunktionen komme, möchte ich noch zwei Methoden
erläutern, mit denen Sie das Aussehen der Linien beeinflussen. Das ist zunächst
die Methode setLi neWidth(). Sie definiert, welche Dicke die Linien haben sol-
len, die das nächste Objekt ausmachen, und bekommt die Liniendicke in Punkten
übergeben. Hierbei können Sie einen Fließkommawert nutzen, sodass Sie auch
eine Linienstärke von 0.1 Punkt nutzen können.
Üblicherweise ist eine Linie durchgehend gezeichnet. Sie können aber auch
gestrichelte Linien mithilfe von Zend_Pdf erstellen. Die zuständige Methode set-
Li neDashingPattern() ist wiederum in der Page- wie auch in der Style-Klasse
definiert. Wirklich schön dabei ist, dass Sie nicht auf vorgegebene Muster festge-
legt sind, sondern diese selbst definieren können. Dazu übergeben Sie der
Methode ein Array mit Zahlen. Jede der Zahlen definiert, wie breit ein Teilstrich
sein soll, wobei sich sichtbare und unsichtbare Striche jeweils abwechseln. Über-
geben Sie der Methode also das Array array(1,3), bedeutet dies, dass ein sicht-
barer Strich mit 1 Punkt Länge gezeichnet wird und danach ein »unsichtbarer«
Strich mit einer Länge von drei Punkten folgt. Anders formuliert: Es wird eine
durchgehende Linie gezeichnet, bei der ein Abschnitt von einem Punkt farbig ist,
dann kommt ein Abschnitt mit einer Länge von drei Punkten, der nicht farbig ist,
dann kommt wieder ein Punkt lang Farbe usw. Dabei ist zu beachten, dass das
Array »endlos« wiederholt wird. Übergeben Sie ein Array mit einer ungeraden
Anzahl von Elemente, wie arrayd, 3, 5), so führt dies zu folgendem Muster
(F = farbig, U = unsichtbar; 3F heißt also drei Punkte lang und farbig): 1F, 3U, 5F,
1U, 3F, 5U, 1F, 3U, 5Fusw.
Optional können Sie als zweiten Parameter noch einen Offset übergeben. Damit
haben Sie die Möglichkeit, die Darstellung zu verschieben. Der Aufruf
Smuster = array (3. 5);
$page->setLineDashingPattern($muster, 2);
sorgt dafür, dass die Linie erst mit einem Abschnitt von einem Punkt anfängt,
dann folgt ein unsichtbarer Abschnitt von fünf Punkten und dann wieder ein
322
e-bol.net
Generieren von PDF-Dokumenten | 6.3
sichtbarer von drei Punkten. Der zweite Parameter, in diesem Fall also die Zahl
2, sorgt dafür, dass ein Teil der Linie »übersprungen« wird.
Übergeben Sie der Methode die Konstante Zend_Pdf_Page:: LINE_DASHING_
SOLID als einzigen Parameter, erhalten Sie wieder eine durchgehende Linie.
Nachdem ich nun so viel über Linien geschrieben habe, muss noch geklärt wer-
den, wie eine Linie gezeichnet wird. Am einfachsten ist es, wenn Sie die Methode
drawLi ne() nutzen, die im Page-Objekt deklariert ist. Sie bekommt die Koordina-
ten des Anfangs- und des Endpunktes übergeben und verbindet diese beiden
Punkte mit einer Linie. Die Punkte werden dabei jeweils mit ihrer X- und Y-Koor-
dinate auf dem Blatt angegeben, sodass die Methode insgesamt vier Zahlen als
Parameter übergeben bekommt.
Das folgende Listing zeichnet einige Linien mit unterschiedlichen Mustern:
require_once ' Zend/Pdf.php' ;
try {
$pdf = new Zend_Pdf();
$page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
$pdf->pages[] = $page:
$page->setLineWidth(l);
$page->setLineDashingPattern(array(5,10));
$page->drawLi ne( 10,190,100,190 ):
$page->setLi neDashingPattern(array(10,5));
$page->drawLi ne( 10,185,100,185):
$page->setLineWidth(3);
$page->setLineDashingPattern(array(10,5) ,3):
$page->drawLi ne( 10,180,100,180 ):
$page->setLi neDashi ngPattern(
Zend_Pdf_Page::LINE_DASHING_SOLID);
$page->drawLi ne(10,1Z5,100,1Z5):
(string = $pdf->render();
}
catch (Zend_Pdf_Exception $e)
{
die("Fehler: ".$e->getMessage()):
323
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
header('Content-type: application/pdf');
header('Content-Di sposi tion: attachment; f i 1ename="datei. pdf'" ):
echo $string;
Listing 6.19 Ausgabe von Linien mit Zend_Pdf
Die so generierten Linien sehen Sie in Abbildung 6.9.
Abbildung 6.9 Verschiedene Linienmuster in einem PDF-Dokument
Um elementare grafische Elemente zu zeichnen, sind in der Klasse auch Metho-
den vorgesehen. Die Methode drawRectangle() beispielsweise zeichnet ein
Rechteck. Sie bekommt die Koordinaten zweier Punkte übergeben. Diese werden
als gegenüberliegende Ecken in einem Rechteck betrachtet, und das Viereck wird
dann nach diesen Vorgaben gezeichnet. Auch hier werden jeweils zuerst der X-
und dann der Y-Wert des entsprechenden Punktes angegeben. Der Aufruf
$page->drawRectangle(20, 50, 120, 150):
zeichnet ein Viereck, bei dem die vier Eckpunkte auf den Koordinaten (20,50),
(120, 50), (120, 150) und (20, 150) liegen. Das Viereck würde alle Vorgaben für
die Linien- und Füllfarbe sowie für die Strichdarstellung der Linien übernehmen.
Das können Sie allerdings über einen weiteren Parameter beeinflussen, für den
diverse Konstanten vorgesehen sind. Diese Konstanten, die in der Klasse Zend_
Pdf_Page deklariert sind, können Sie auch bei den anderen Zeichenfunktionen
nutzen, die ich noch vorstellen werde.
324
e-bol.net
Generieren von PDF-Dokumenten | 6.3
Konstante Bedeutung
SHAPE_DRAW_FILL_AND_STROKE Objekt mit Außenlinie zeichnen und füllen (Default)
SHAPE_DRAW_FILL Objekt füllen aber keine Außenlinie zeichnen
SHAPE_DRAW_STROKE Nur Außenlinie entsprechend den Vorgaben zeichnen, aber nicht füllen
Tabelle 6.3 Konstanten zur Beeinflussung der Zeichenfunktionen
Die folgenden Zeilen geben drei Rechtecke aus, welche die hier genannten Kon-
stanten visualisieren:
$page->setLi neWidth(1);
$page->setLineDashingPattern(array(5,10));
$page->setF111Color(new Zend_Pdf_Color_GrayScale(0.5));
$page->setLi neColor(new Zend_Pdf_Color_Rgb(0,0,0));
//Rechteck mit Default-Einstellungen
$page->drawRectangle(10,40,80,110);
// Nur füllen, keine Linien
$page->drawRectangle(90,40,160,110,
Zend_Pdf_Page::SHAPE_DRAW_FILL);
// Nur Linien, nicht füllen
$page->drawRectangle(1Z0,40,240,110.
Zend_Pdf_Page::SHAPE_DRAW_STROKE);
Sstring = $pdf->render();
Listing 6.20 Ausgabe von drei Rechtecken
Die Darstellung im PDF-Dokument sehen Sie in Abbildung 6.10.
Abbildung 6.10 Drei Rechtecke mit unterschiedlichen Optionen
325
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
Kreise oder Ellipsen zu zeichnen, ist ebenfalls kein Problem. Die Methode draw-
CircleO kann einen Kreis oder ein Kreissegment zeichnen. Sie bekommt die
Koordinaten des Kreismittelpunktes sowie den gewünschten Radius in Punkten
übergeben. Rufen Sie die Methode direkt auf, wird ein Kreis um Außenlinie und
Füllung gezeichnet. Möchten Sie einen kompletten Kreis zeichnen, können Sie
als nächsten Parameter noch eine der bereits erwähnten Konstanten angeben, um
die Darstellung dementsprechend zu beeinflussen.
Möchten Sie nur ein Kreissegment zeichnen, weil Sie beispielsweise ein Torten-
diagramm erstellen wollen, so geben Sie nach dem Radius den Start- und den
Endpunkt des Segments im Bogenmaß an. Danach haben Sie dann noch die Mög-
lichkeit, eine der Konstanten anzugeben.
Im folgenden Beispiel wird zunächst ein kompletter Kreis gezeichnet. Zusätzlich
wird noch ein Tortendiagramm ausgegeben, das aus drei Teilen besteht. Das erste
Segment entspricht 20% (= 72°), das zweite Segment umfasst 50% (= 180°) und
das letzte entspricht 30%, was 108° gleicht, da ein Kreis insgesamt 360° umfasst.
Um das letzte Segment ein wenig hervorzuheben, wurde es mit einem leicht ver-
setzten Mittelpunkt gezeichnet:
$pdf = new Zend_Pdf():
$page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
$pdf->pages[] = $page;
$page->setLineWidth(0.5):
$page->setFi1lColor(new Zend_Pdf_Color_GrayScal e(0.5));
$page->setl_ineColor(new Zend_Pdf_Col or_Rgb(0,0,0)):
// Kompletter Kreis
$page->drawCi rcle(200,200,80):
// Erstes Segment
$page->setFi11Color(new Zend_Pdf_Color_Rgb(1,0,0)):
$page->drawCircle(370, 200, 80, deg2rad(0), deg2rad(72),
Zend_Pdf_Page::SHAPE_DRAW_FILL);
// Zweites Segment
$page->setFi11Color(new Zend_Pdf_Color_Rgb(0,1,0)):
$page->drawCi rcle(370, 200, 80, deg2rad(72), deg2rad(252),
Zend_Pdf_Page::SHAPE_DRAW_FILL);
// Drittes Segment
$page->setFi11Color(new Zend_Pdf_Color_Rgb(0,0,1)):
$page->drawCircle(375, 195, 80, deg2rad(252), deg2rad(360).
326
e-bol.net
Generieren von PDF-Dokumenten | 6.3
Zend_Pdf_Page::SHAPE_DRAW_FILL);
$string = $pdf->render();
Listing 6.21 Ausgabe eines Kreises und eines Tortendiagramms
Abbildung 6.11 Kreis und Tortendiagramm im PDF-Dokument
Sollten Sie eine Ellipse und keinen Kreis benötigen, hilft Ihnen die Methode draw-
Elli pse() weiter. Sie verhält sich ähnlich wie die Kreisfunktion. Da eine Ellipse
allerdings nicht rund ist, bekommt die Methode etwas andere Parameter überge-
ben. Größe und Form der Ellipse werden durch ein Rechteck beschrieben. Das
heißt, Sie geben im Endeffekt die Koordinaten von zwei gegenüberliegenden
Punkten in einem Rechteck an. Das System zeichnet aber kein Rechteck, sondern
eine Ellipse, die genau in das Rechteck passt. Die anderen Parameter sind dann
wieder identisch mit denen der Kreisfunktion. Um das zu veranschaulichen,
generieren die nachfolgenden Zeilen ein »gestricheltes« Rechteck mit einer
innenliegenden Ellipse. Bitte beachten Sie, dass die Methoden drawRectangle()
und drawEl l i pse() dabei die gleichen Koordinaten übergeben bekommen:
Spage->setLineDashingPattern(array(3,2));
$page->drawRectangle(100,100,300,200,
Zend_Pdf_Page::SHAPE_DRAW_STROKE);
$page->setLi neDashi ngPattern(Zend_Pdf_Page::LIN E_DASHING_
SOLID): $page->drawEl1ipse( 100,100,300,200):
Listing 6.22 Ausgabe eines Rechtecks und einer Ellipse
327
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
Die hiermit generierte Zeichnung sehen Sie in Abbildung 6.12.
n n n
Vorherige Nächste
j*| datei-60.pdf (1 Seite)
Zoomen
Abbildung 6.12 Darstellung eines Rechtecks mit innenliegender Ellipse
Die letzte der echten Zeichenmethoden ist drawPolygon(), mit der Sie ein Viel-
eck zeichnen können. Sie bekommt mindestens zwei Arrays übergeben, welche
die Koordinaten der Eckpunkte beschreiben. Das erste Array enthält dabei die X-
Werte und das zweite die Y-Werte. Die Arrays müssen identisch lang sein. Die
einzelnen Werte der Arrays werden jeweils zu Paaren zusammengefasst, die die
Koordinaten eines Punktes ergeben. Diese Punkte werden nacheinander mit
Linien verbunden, sodass sich die Figur ergibt. Der letzte Punkt wird dabei auto-
matisch wieder mit dem ersten verbunden. Als dritten Parameter können Sie
eine der schon bekannten Konstanten übergeben. Diese Methode akzeptiert aber
noch einen weiteren Parameter, mit dem Sie eine der beiden folgenden Konstan-
ten Übergeben: FILL_METHOD_NON_ZERO_WINDING oder FILL_METHOD_EVEN_ODD.
Diese definieren, wie gefüllt werden soll, wenn einige Punkte des Objekts inner-
halb der Fläche liegen. Die erste Variante (die Default-Einstellung) teilt der
Methode mit, dass der gesamte umschlossene Bereich gefüllt werden soll, unab-
hängig davon, ob einige Punkte des Objekts eingeschlossen werden. Die zweite
Konstante sorgt dafür, dass auch Punkte bzw. Linien, die vom Objekt umfasst
werden, nicht ignoriert werden. Somit kann es in der gesamten Fläche noch
einen Bereich geben, der nicht gefüllt ist. Das Bild in Abbildung 6.13 zeigt dies
anschaulich.
Die folgenden Zeilen zeichnen zwei identische Polygone, die mit unterschiedli-
chen Methoden gefüllt werden:
328
e-bol.net
Generieren von PDF-Dokumenten | 6.3
$x = array (100, 400, 400, 150, 350, 100 );
$y = array (450, 450, 750, 500, 500, 750);
$page->drawPolygon($x, $y,
Zend_Pdf_Page::SHAPE_DRAW_FILL_AND_STROKE,
Zend_Pdf_Page::FILL_METHOD_NON_ZERO_WINDING);
$x = array (100, 400, 400, 150, 350, 100 );
$y = array (100, 100, 400, 150, 150, 400);
$page->drawPolygon($x, $y,
Zend_Pdf_Page::SHAPE_DRAW_FILL_AND_STROKE,
Zend_Pdf_Page::FILL_METHOD_EVEN_ODD );
Listing 6.23 Ausgabe von zwei Polygonen mit unterschiedlichen Füllmethoden
[3 datei-68.pdf (1 Seite)
Vorherige Nächste Zoomen
Abbildung 6.13 Zwei Polygone mit unterschiedlichen Füllmethoden
329
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
6.3.5 Einbinden von Bildern
Eine weitere Methode, die auch zu den Zeichenmethoden gehört, aber nicht
wirklich zum Zeichnen dient, ist drawImageO. Mit ihr können Sie ein Bild auf
einer Seite platzieren. Dazu müssen Sie das Bild aber zunächst in ein Zend_Pdf_
Image-Objekt überführen, was so funktioniert:
$bi1d = Zend_Pdf_Image::1mageWithPath('bild.jpg’):
Sie rufen die statische Methode imageWi thPatht) auf und übergeben ihr den
Namen des Bildes inklusive des Pfads. Bei der Methode handelt es sich um eine
Factoiy, die eine entsprechende Klasse einbindet, die die Bilddatei verarbeitet.
Zurzeit werden Bilder im TIF-, JPG- und PNG-Format unterstützt. Ein wenig
kurios mag anmuten, dass die Zuordnung der Formate anhand der Dateiendung
erfolgt.8 Bitte stellen Sie also sicher, dass die Dateien korrekt benannt sind. Sollte
die Dateiendung nicht das korrekte Format widerspiegeln, oder sollte eine unbe-
kannte Dateiendung genutzt werden, resultiert daraus eine Exception.9 Zulässige
Suffixe sind: ti f, ti ff, png, jpg, jpe und jpeg.
Sobald Sie das Bild-Objekt haben, können Sie es an die Methode drawlmagel)
übergeben. Als weitere Parameter folgen die X- und Y-Koordinate der unteren
linken sowie die X- und Y-Koordinate der oberen rechten Ecke des Bereichs, in
dem das Bild dargestellt werden soll. Das Bild wird bei der Darstellung an diesen
Bereich angepasst, es wird also skaliert und gegebenenfalls auch verzerrt. Möch-
ten Sie verhindern, dass das Bild verzerrt wird, können Sie die Originalgröße des
Bildes mithilfe der Methoden getPi xel Hei ght () und getPi xel Wi dth() auslesen.
Damit können Sie auch eine bestimmte Auflösung in DPI errechnen. Im folgen-
den Beispiel wird ein »Powerd By Zend Framework« in ein PDF-Dokument ein-
gebunden:
// Bild einlesen
$bi1d = Zend_Pdf_Image::imageWithPath('powered.png');
// Hoehe und Breite auslesen
Shoehe = $bild->getPixelHeight():
Sbreite = $bi1d->getPixe1Width();
// Bild ausgeben
$page->drawlmage($biId, 10, 800, 10+$breite, 800+$hoehe):
Sstring = $pdf->render();
Listing 6.24 Einbinden eines Bildes mit Zend_Pdf
8 Ein Kommentar im Quelltext besagt, dass dies geändert werden soll.
9 Im Quelltext findet sich ein Hinweis, dass die Methode bei einem ungültigen Bild null zurück-
gibt. Allerdings scheint die Methode bei Problemen immer eine Exception zu werfen. Den-
noch kann es nicht schaden, den Rückgabewert auf null hin zu prüfen.
330
e-bol.net
Generieren von PDF-Dokumenten | 6.3
Abbildung 6.14 Darstellung eines Bildes in einem PDF-Dokument
Eine weitere Funktion, welche die Klasse unterstützt, ist das Clipping. Damit
haben Sie die Möglichkeit, die Ausgabe von Zeichnungen oder Grafiken auf
bestimmte Bereiche zu beschränken. Zu diesem Thema möchte ich Sie allerdings
gerne auf die Dokumentation verweisen, da diese Funktionalität sicher eher für
wenige Anwender interessant ist.
6.3.6 Meta-Informationen einfügen
PDF-Dokumente unterstützen die Möglichkeit, Meta-Informationen im Doku-
ment abzuspeichern. Mit anderen Worten: Sie können im Dokument hinterle-
gen, wer der Autor ist, wann das Dokument erstellt wurde und Ähnliches. Diese
Informationen sind in einem Array hinterlegt, das direkt im Zend_Pdf-Objekt
abgelegt ist. Möchten Sie den Namen des Autors sowie einen Titel hinterlegen,
so könnten Sie das so umsetzen:
$pdf = new Zend_Pdf();
$pdf->properties['Author'] = 'Homer Simpson’;
$pdf->properties['Title'] = 'Sicherheit in Atomkraftwerken';
Listing 6.25 Einfügen von AAeta-lnformationen
Welche Schlüssel insgesamt zur Verfügung stehen, können Sie der Tabelle 6.4
entnehmen.
331
e-bol.net
6 | Arbeit mit E-Mails und Dateiformaten
Schlüssel Bedeutung
Author Autor des Dokuments
Title Titel des Dokuments
Subject kurze Inhaltsangabe
Keywords Schlüsselwörter zum Inhalt des Dokuments
Creator Software, mit der das ursprüngliche Dokument erstellt wurde (z. B. Microsoft Word)
Producer Software, die das PDF-Dokument erstellt hat (also beispielsweise Zend_Pdf)
CreationDate Zeitpunkt der Erstellung
ModDate Zeitpunkt der letzten Modifikation
Tabelle 6.4 Schlüssel für die Arbeit mit Meta-Informationen
Die Datumsfelder CreationDate und ModDate erwarten das Datum in einem
bestimmten Format. Dieses Format ist folgendermaßen aufgebaut: D:YYYYMMD-
DHHmmSSOHH' mm' - das D: wird direkt übernommen.
Die einzelnen Platzhalter des Beispiel-Strings haben die folgenden Bedeutungen:
Platzhalter Bedeutung
YYYY Jahr, vierstellig
MM Monat, zweistellig
DD Tag, zweistellig
HH Stunde im 24-Stunden Format, zweistellig
mm Minuten, zweistellig
SS Sekunden, zweistellig
0 + oder abhängig davon, ob sich die Zeitzone, in der sich das Dokument befindet, östlich (-) oder westlich (+) des Nullmeridians liegt
HHr Anzahl der Stunden, die die eigene Zeitzone gegenüber UTC verschoben ist. Der Wert ist zweistellig mit' dahinter anzugeben.
mm’ Anzahl der Minuten, die die eigene Zeitzone im Vergleich zu UTC verscho- ben ist. Der Wert ist zweistellig mit nachfolgendem ' anzugeben.
Tabelle 6.5 Platzhalter im Datumsformat
Beispielsweise würde sich für den 25.02.2008, 12:15 Uhr in Deutschland die fol-
gende Darstellung ergeben:
0:20080225121500+01'00'
Da dieses Datum in der Winterzeit, also der Normalzeit, liegt, ist die Angabe des
Offsets unproblematisch. Bei einer Sommerzeitangabe kann man sicher darüber
diskutieren, ob Sie dort +01 oder +02 angeben müssen. Ich konnte leider nicht in
332
e-bol.net
Generieren von PDF-Dokumenten | 6.3
Erfahrung bringen, welche Variante korrekt ist. In den meisten Zusammenhän-
gen dürfte das aber auch nicht ins Gewicht fallen.
6.3.7 Einlesen von PDF-Dokumenten
Zum Schluss möchte ich Ihnen noch die statische Methode 1 oad() vorstellen. Sie
ist in der Klasse Zend_Pdf deklariert und ermöglicht es Ihnen, ein bereits beste-
hendes PDF-Dokument zu laden. Sie liefert daraufhin ein Zend_Pdf-Objekt
zurück, das Sie mit allen bereits beschriebenen Methoden verändern können.
Leider gibt es keine Möglichkeit, die bereits vorhandenen Inhalte auszulesen.
Aber auch das ist für zukünftige Versionen geplant. Trotzdem haben Sie damit
aber eine gute Möglichkeit, mit Vorlagen wie zum Beispiel einem fertigen Brief-
kopf zu arbeiten und diesen dann nur um die Anschrift und den Text zu ergän-
zen.
333
e-bol.net
e-bol.net
Bender: »Ich habe von lauter Nullen und Einsen geträumt.
Und ich dachte, ich hätte irgendwo eine 2 gesehen!«
Fry: »Ach Bender, sowas wie eine 2 gibt es doch gar nicht!«
(aus der Serie Futurama)
7
Protokolle und Co.
Es liegt in der Natur der Sache, dass Webanwendungen mit ihren Clients oder
auch mit anderen Servern kommunizieren müssen. Auch dabei unterstützt Sie
das Zend Framework. Neben der Möglichkeit, direkt via HTTP zu kommunizie-
ren, können Sie auch Cookies lesen und schreiben oder Webservices mit XML-
RPC oder REST anbieten oder nutzen.
7.1 Zugriff auf andere Server mit Zend_Http
Mithilfe der Zend_Http-Klassen können Sie einen HTTP-Client mit allen relevan-
ten Features erstellen. Üblicherweise wird ein Browser als HTTP-Client genutzt,
sodass sich die Frage stellt, wofür diese Klassen denn gut sein mögen. Mithilfe
von PHP einen HTTP-Client zu erstellen, ist beispielsweise sehr hilfreich, wenn
Sie aus PHP heraus Daten an ein Formular übergeben wollen, um sich bei einem
Internetdienst anzumelden oder Daten automatisiert hochladen möchten. Kurz
gesagt, wann immer Sie sich wünschen würden, dass PHP die Aufgaben eines
Browser wahrnehmen soll, sind die Zend_Http-Klassen sehr hilfreich.
Das Paket gliedert sich im Wesentlichen in die Klassen Zend_Http_Cl ient, Zend_
Http_Cookie, Zend_Http_CookieJar und Zend_Client_Response. Eine Klasse
Zend_Http existiert nicht.
7.1.1 Das HTTP-Protokoll
Bevor ich auf die Nutzung der Klassen eingehe, möchte ich kurz das HTTP-Proto-
koll betrachten. HTTP (Hypertext Transfer Protocol) ist dasjenige Protokoll, das
für den Austausch von Daten im World Wide Web genutzt wird. Es handelt sich
dabei um ein recht einfach aufgebautes Protokoll, das nach dem Pull-Prinzip
funktioniert. Das heißt, der Client schickt eine Anfrage an den Server, welcher
335
e-bol.net
7 | Protokolle und Co.
daraufhin antwortet. Die Antwort wird vom Client entgegengenommen und
dann verarbeitet bzw. dargestellt. Dies ist bereits die grundsätzliche Idee hinter
der Kommunikation.
Bei dieser Kommunikation übermittelt der Client Daten an den Server. Diese
Daten werden im Klartext versendet. Dabei teilt der Client dem Server mit, wel-
che Information bzw. Webseite er anfordert, welche Sprachen (Deutsch, Englisch
etc.) der Benutzer bevorzugt, um welche Art Client es sich handelt und Ähnli-
ches. Bei der Antwort des Servers sind neben den eigentlichen Daten, wie zum
Beispiel der HTML-Seite, auch Header enthalten, die noch zusätzliche Informati-
onen enthalten.
Nachdem Sie nun einen kleinen Überblick über das Protokoll erhalten haben,
können wir loslegen.
7.1.2 Einen HTTP-Client erstellen
Um eine Information bei einem Server anzufordern, müssen Sie eine Anfrage an
den Server senden. Hierfür ist die Klasse Zend_Http_Cl ient vorgesehen. Diese
schickt die Anfrage, den sogenannten Request, an den Server und nimmt die Ant-
wort entgegen. Die Daten und Informationen, die in der Antwort enthalten
waren, erhalten Sie in Form eines Zend_Http_Response-Objekts zurück.
Das folgende Beispiel zeigt die grundsätzliche Funktionsweise:
require_once ’Zend/Http/Client.php’;
// Objekt ableiten
Sclient = new Zend_Http_Cl1ent('http://www.heise.de');
// Anfrage an Server senden
Sresponse = $client->request():
// Antwort ausgeben
echo $response->getBody():
Listing 7.1 Auslesen von Daten via HTTP
In diesem Listing wird ein Zend_Http_Cl ient-Objekt abgeleitet, dem die URL
übergeben wird, die eingelesen werden soll. Bitte beachten Sie, dass es sich dabei
immer um eine komplette URL inklusive des Protokolls handeln muss. Der Auf-
ruf der Methode request() schickt die Anfrage an den Server und gibt die Ant-
wort als Zend_Http_Response-Objekt zurück. Mit der Methode getBody() kann
dann der Körper der Antwort, in diesem Fall also die Startseite von www.heise.de,
ausgelesen werden. Die Ausgabe des Scripts sehen Sie in Abbildung 7.1.
336
e-bol.net
Zugriff auf andere Server mit Zend_Http | 7-1
heise online
O http://carsten-mohrkes-computei“<Q” Google
heise heise
heise online • c't • iX • Technology Review • Telepolis • mobil • Security • Netze • —— • • Autos • c’t-TV
open rcsaic
[jj] heise online
15.102007
Meldungen des Tages
Suche...
Interpol identifiziert weltweit gesuchten
Sexualstraftäter
|M|news
"fews-Archiv
^ews unterwegs
Mcws einbinden
Das aus verfremdeten digitalen Fotos rekonstruierte und im
Internet veröffentlichte Gesicht des Verdächtigen hatte zu
zahlreichen Hinweisen geführt; eine Überwachungskamera
filmte den Mann auf einem Flughafen in Thailand, mehr...
TELEPOLIS
Re-Reed ucation oder:
Kunst und
Konditionierung
Vom Krieg um das
Unterbewusste zur
Diktatur des Digitalen
rclefontanfc
Motorola wird Miteigentümer der
UIQ-Entwicklerschmiede
[ntemetstömngen
Software/Download
[T-Markt
Der US-amerikanische Handyhcrsteller übernimmt von Sony
Ericsson 50 Prozent der Anteile am Hersteller der
UIQ-Bedicnoberfläche für Symbian-Handys. Die
Unternehmen wollen den Kreis der UIQ-Eigner noch
erweitern, mehr...
heise Security
PHP - aber sicher!
Die Grundsicherung des
eigenen Webspace ist
schnell erledigt und kann
eine Menge Arger sparen.
Transferring data from www.toughbook.eu...
Vi
Abbildung 7.1 Darstellung der ausgelesenen Daten im Browser
Dass die Darstellung nicht perfekt ist und einige Grafiken fehlen, liegt daran, dass
die Pfade im HTML-Code relativ angegeben wurden und die entsprechenden
Dateien nicht auf dem Rechner zu finden sind, der die Anfrage gestellt hat.
Soll die Kommunikation mit dem Server nicht auf dem Standard-Port erfolgen, so
können Sie den gewünschten Port direkt an den Namen des Servers, genauer
gesagt an die Toplevel-Domain bzw. die IP-Adresse anhängen:
http://www.example.com:88/daten.php
Der Client kann noch weitgehender konfiguriert werden. Neben der URL können
Sie dem Konstruktor noch ein Array mit bestimmten Schlüsseln übergeben, die das
Verhalten des Clients verändern. Erlaubt sind dabei die Schlüssel aus Tabelle 7.1.
Schlüssel I Bedeutung
maxredi rects Maximale Anzahl der Weiterleitungen. Default ist 5.
stri ctredi rects Strikte Redirects nach RFC 2626 durchführen. Ist der Wert true, wer-
den die Redirects strikt durchgeführt und die Request-Methode bleibt
erhalten.
Tabelle 7.1 Schlüssel zur Konfiguration des HTTP-Clients
337
e-bol.net
7 | Protokolle und Co.
Schlüssel Bedeutung
useragent User-Agent, den der Client an den Server übermittelt
timeout Maximale Wartezeit bis zum Time-out in Sekunden. Der Default-Wert beträgt 10 Sekunden.
httpversi on Die HTTP-Version, die genutzt werden soll; Version 1.1 oder 1.0; Default-Wert ist 1.1.
keepali ve Keepalive-Verbindungen nutzen (true) oder nicht (false). Default- Wert ist fal se.
Tabelle 7.1 Schlüssel zur Konfiguration des HTTP-Clients (Forts.)
Der Schlüssel, den Sie wahrscheinlich am häufigsten brauchen werden, ist user-
agent. Mit ihm setzen Sie den String, mit dem der Client dem Server mitteilt, um
welche Art »Browser« es sich handelt. Einige Internetangebote liefern unter-
schiedliche Inhalte in Abhängigkeit vom Browser aus oder sperren Inhalte sogar,
falls es sich nicht um einen »normalen« Browser handelt, um automatisierte
Downloads zu unterbinden.
Möchten Sie die Werte nicht direkt an den Konstruktor übergeben, können Sie
die URL auch über die Methode setUri () und das Konfigurations-Array mithilfe
von setConfi g() an das Objekt übergeben.
7.1.3 Übergabe von Werten
Das alles wäre natürlich nur halb so spannend, wenn Sie beim Aufruf einer URL
keine Werte übergeben könnten. Sie haben die Möglichkeit, Daten mithilfe von
GET und/oder POST an den Server zu übergeben und somit das Verhalten eines
Formulars zu simulieren.
In dem folgenden Beispiel werden Daten an ein Script übergeben, das normaler-
weise Daten aus einem Formular übernehmen würde:
Spage = $_GET[1page'1;
Suser = $_POST['user'];
Spasswd = $_POST['password'1;
if ('Jake' == Suser &&
'geheim' — Spasswd)
{
echo "Sie sind eingeloggt und wollten Spage sehen<br>";
echo "Ihr Browser ist ein $_SERVER[HTTP_USER_AGENT]";";
1
el se
(
338
e-bol.net
Zugriff auf andere Server mit Zend_Http | 7-1
echo "Username und/oder Passwort waren falsch";
}
Der Code, um ein Formular zu simulieren und Daten an dieses Script zu überge-
ben, könnte so aussehen:
require_once ’Zend/Http/Client.php’;
// Objekt ableiten
Sclient = new Zend_Http_Client();
// URI setzen
$client->setUri(’http://127.0.0.1/auswertung.php');
// Konfiguration setzen (Kennung eines Firefox unter OS X)
$client->setConfig(array(
’useragent' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X;
'de; rv:1.8.1.7) Gecko/20070914 '.
'Fi refox/2.0.0.7'));
// GET-Parameter setzen
$client->setParameterGet(’page', 'index.html');
// POST-Werte setzen
$cli ent->setParameterPost(array(
’user' => 'Jake’,
’password' => 'geheim'
)):
// Request abschicken
Sresponse = $client->request(Zend_Http_Client::POST);
// Ergebnis ausgeben
echo "<b><u>Antwort des Servers war :</uX/b><br>";
echo $response->getßody();
Listing 7.2 Datenübergabe mithilfe von Zend_Http
Die GET-Parameter werden mithilfe der Methode setParameterGet() festgelegt,
was für die POST-Werte mit der Methode setParameterPost() geschieht. Beiden
Methoden können Sie entweder ein Array übergeben, bei dem die Schlüssel als
Variabiennamen fungieren, oder Sie können zwei Parameter übergeben, bei dem
der erste der Name und der zweite der Wert ist, um nur einen Wert zu setzen.
Bei der Nutzung dieser Methoden ist es nicht notwendig, die Werte vorher an
uri encode() zu übergeben; das Paket codiert sie automatisch korrekt.
Die Methode request() bekommt bei diesem Aufruf den Parameter Zend_Http_
Client:: POST übergeben. Dies ist notwendig, weil POST-Parameter an den Server
übergeben werden sollen. Standardmäßig wird ein Request immer als GET-
339
e-bol.net
7 | Protokolle und Co.
Anfrage verschickt. Wird der Parameter nicht übergeben, resultiert daraus eine
Exception.
Zwar werden Sie andere Request-Arten nicht so oft benötigen, aber Sie können
auch die Konstanten PUT, HEAD, DELETE, TRAGE, OPTIONS oder CONNECT nutzen, um
einen entsprechende HTTP-Request zu initiieren.
7.1.4 Uploads
Oft kann es sehr hilfreich sein, nicht nur reine Textdaten, sondern auch Dateien
an ein Script zu übergeben. Sind Sie beispielsweise stolzer Besitzer einer Digital-
kamera und wollen Ihre Bilder im Internet präsentieren, könnte es sein, dass das
Online-Photoalbum, das Sie nutzen wollen, keine FTP-Uploads unterstützt und
ein paar hundert Bilder über ein HTML-Formular hochzuladen ist vielleicht ein
wenig mühsam. Ein kleines Script wäre in dem Fall hilfreich.
Auch das Übertragen von Dateien ist denkbar einfach. Für einen File-Upload ist
die Methode setFileUpload() zuständig. Interessant an der Methode ist, dass
sie entweder eine Datei einlesen oder auch direkt Daten übergeben bekommen
kann, die als Datei übertragen werden. Das ist beispielsweise dann spannend,
wenn Sie Daten aus einer Datenbank übernehmen und diese als CSV-Datei über-
tragen wollen.
Die Methode erhält als ersten Parameter den Namen der Datei übergeben. Die-
sen müssen Sie auch dann übergeben, wenn die Datei nicht direkt von der Fest-
platte gelesen, sondern als String übergeben wird. In diesem Fall dürfen Sie sich
natürlich einen Namen ausdenken. Der zweite Parameter ist derjenige Name,
den das Input-Feld innerhalb des Formulars hätte (wenn es sich denn um ein For-
mular handeln würde). Bei einem gewöhnlichen Datei-Upload benötigen Sie
keine weiteren Parameter. Wollen Sie Daten direkt übergeben, so übergeben Sie
diese als dritten Parameter. Der vierte Parameter, der auch optional ist, gibt
Ihnen die Möglichkeit, den MIME-Type zu übergeben. Übergeben Sie ihn nicht,
ermittelt die Methode selbst einen MIME-Type. Bei einer Datei wird versucht,
mithilfe der Funktion mime_content_type() den korrekten MIME-Type zu
ermitteln. Ist das nicht möglich oder haben Sie die Daten als Parameter überge-
ben, nutzt die Methode appl ication/octet-stream als MIME-Type.
require_once 'Zend/Http/Client.php';
Sclient = new Zend_Http_Client('http://127.0.0.1/upload.php');
// Textdaten direkt übertragen
// Textdaten aufbereiten (Normalerweise Daten aus Datenbank)
$csv_daten =
"Monat;Umsatz
340
e-bol.net
Zugriff auf andere Server mit Zend_Http | 7-1
Januar:1212.00
Februar;3443.12";
// Upload vorbereiten
$client->setFi1eUpload('umsaetze.csv', // Dateiname
’umsatzdatei', // Feldname im Formular
$csv_daten, // Daten
’text/csv'); // MIME-Type
// Datei von Platte lesen und übertragen
$client->setFi1eUpload('urlaub.jpg' , 'bild');
// Datei von Platte lesen und MIME-Type manuell setzen
$client->setFi1eUpload('einkauf.doc', 'einkaufsliste' ,
null, 'application/msword');
// Dateien als POST-Request übertragen
$cl ient->request(Zend_Http_Client::POST);
Listing 7.3 Upload von Dateien
Sie müssen sich nicht darum kümmern, dass der Encoding-Typ korrekt gesetzt
wird; das übernimmt die Klasse für Sie. Bei der Arbeit mit Dateien sollten Sie
immer auf ein korrektes Exception Handling achten, da die Klasse eine Exception
wirft, falls die Datei nicht gelesen werden kann.
7.1.5 HTTP-Authentifikation
In vielen Fällen ist es sinnvoll, Formulare oder andere Ressourcen mit einem
Passwort zu sichern. Hierbei sind zwei Fälle zu unterscheiden: Haben Sie die
Authentifikation auf Basis eines Formulars selbst implementiert, ist der Sachver-
halt unproblematisch. Sie können Benutzernamen und Passwort einfach als nor-
male Formularwerte übergeben.
Sollten Sie aber eine HTTP-Authentifikation, also beispielsweise eine .htaccess-
Datei nutzen, sieht die Sache anders aus. In diesem Fall müssen Sie den Benutzer-
namen und das Passwort an die Methode setAuth() übergeben:
$cl1ent->setAuth('user', 'geheim');
Standardmäßig führt der Client eine Basic-Authentifikation aus. Die Digest-
Methode ist noch nicht implementiert. Sollte das in Zukunft der Fall sein, so kön-
nen Sie an dritter Stelle die Konstante Zend_Http_Cl i ent: :AUTH_DIGEST überge-
ben, um eine Digest-Authentifikation durchzuführen.
341
e-bol.net
7 | Protokolle und Co.
7.1.6 Server-Antworten auswerten
In den vorangegangenen Beispielen wurde immer nur direkt der Körper der Ant-
wort ausgegeben, der mit getBody() ausgelesen wurde. Diese Vorgehensweise
ist für einen produktiven Einsatz nicht unbedingt geeignet. Immerhin könnte es
sein, dass die gewünschte Seite auf dem Server nicht gefunden werden kann. In
diesem Fall liefert der Server zwar auch ein Dokument zurück, aber diese Fehler-
seite möchte man wahrscheinlich nicht direkt ausgeben. Dazu ist es angebracht,
die Header zu prüfen. Hierin ist nämlich auch ein Statuscode enthalten, dem Sie
entnehmen können, ob die Operation erfolgreich war.
Den Statuscode können Sie mit der Methode getCode() und die dazugehörige
Statusmeldung mit getMessage() auslesen:
require_once ’Zend/Http/Client.php’;
Sclient = new Zend_Http_Client('http://www.netviser.de');
Sresponse = $client->request():
echo "URL: ".$client->getUri()."<br>";
echo ''Statuscode: ’’. $response->getStatus(). "<br>";
echo "Statusmeldung: ".$response->getMessage(). "<br>";
$client->setUri(’http://www.netvi ser.de/gibtsnicht.html’);
$response = $client->request();
echo "<br>URL: ".$client->getUri()."<br>";
echo "Statuscode: ".$response->getStatus()."<br>";
echo "Statusmeldung: ".$response->getMessage().”<br>";
Listing 7.4 Auswerten einer Server-Antwort
Die Ausgabe dieses Listings sehen Sie in Abbildung 7.2.
Abbildung 7.2 Auswerten von Server-Antworten
342
e-bol.net
Zugriff auf andere Server mit Zend_Http | 7-1
Der Statuscode und die Statusmeldung, die das System zurückgeben, entsprechen
dem, was der HTTP-Standard definiert. Bitte beachten Sie, dass diese Codes nur
dann ausgewertet werden können, wenn der angegebene Server antwortet. Exis-
tiert der Server nicht, erhalten Sie kein Antwortobjekt, sondern es wird eine
Exception geworfen.
Den Code manuell auszuwerten, wäre meist zu aufwändig. Daher sind in der
Klasse einige Funktionen definiert, mit denen Sie jeweils mehrere Fälle auf ein-
mal überprüfen können. Das ist recht einfach, da die Codes jeweils in Blöcken
zusammengefasst sind. Die 2XX-Codes teilen dem Client eine erfolgreiche Opera-
tion mit, die 3XX-Codes stehen dafür, dass eine Weiterleitung erfolgte, die 4XX-
Codes teilen Ihnen mit, dass ein Client-Fehler aufgetreten ist, und die Codes, die
mit 5 beginnen, sagen aus, dass ein serverseitiger Fehler aufgetreten ist.
Um dies möglichst einfach zu prüfen, sind die Methoden isSuccessful1(),
i sRedi rect() und i sError() definiert, die feststellen, ob der Statuscode mit 2,
3 bzw. 4 oder 5 beginnt. Zwischen Client- und Server-Fehlern wird hierbei nicht
unterschieden.
Viele Anbieter im Internet nutzen gerne die 3XX-Statuscodes, wenn sich Pfade
beispielsweise durch einen Relaunch geändert haben. Dadurch kann eine alte
URL aufgerufen werden, und der Server kann dem Client mitteilen, dass die Seite
jetzt unter einer anderen URL zu finden ist. Rufen Sie eine solche URL mit Zend_
Http auf und akzeptieren Sie Redirects, so erhalten Sie zum Schluss nur den Sta-
tuscode 200, der Ihnen mitteilt, dass die Seite gefunden wurde. Allerdings wäre
es sinnvoll, die neue URL in Ihrem Datenbestand zu aktualisieren, damit Ihr Cli-
ent nicht immer den Redirects folgen muss. Möchten Sie den gesamten Weg des
Clients mitverfolgen, können Sie das mit den genannten Methoden schnell
umsetzen:
require_once 'Zend/Http/Client.php';
// URL bei der wir starten
Sstart = ’http://12Z.0.0.1/redirect.php’
Sclient = new Zend_Http_Client($start):
// Redirects nicht folgen
$cl ient->setConfig(array(’maxredirects' => 0)):
do {
// Request ausführen
$response = $client->request():
// War es eine Weiterleitung?
ifttrue ==— $response->isRedirect())
343
e-bol.net
7 | Protokolle und Co.
{ // Dann speichern wir den Code und die Ziel-URL
$urls[]= array('url'=>(string)$client->getUri(),
’code’=>$response->getStatus());
// Die Schleife soll jedem Redirect folgen
} while (true —== $response->isRedirect());
// Ist ein Fehler aufgetreten?
if (true === $response->isError())
{
echo "Fehler: ".$response->getStatus():
exlt:
// Sind wir bei einem 2XX-Code angekommen?
if (true — $response->isSuccessful())
{
echo "Start: $start<br>";
foreach ($urls as $url)
{
echo "Code $url[code] => $url[url]<br>";
echo "Ende: ".$client->getUri().”<br>";
echo "Statuscode: ".$response->getStatus()."<br>";
echo "Statusmeldung: ".$response->getMessage()."<br>";
I
Listing 7.5 Umgang mit Redirects
Interessant an diesem kleinen Script ist, dass das Ziel des Redirects, also die URL,
die der Server zurückliefert, direkt im Client-Objekt abgelegt wird. Sie müssen
die URL also nicht auslesen. Die URL, die ursprünglich im Client-Objekt enthal-
ten war, ist überschrieben worden. Somit können Sie bei einem HTTP-Code 301,
also einem »Moved Permanently« die URL in ihrem Datenbestand aktualisieren.
Die Ausgabe dieses Scripts könnte nach einigen Weiterleitungen beispielsweise
so aussehen:
Start: http://127.0.0.1:80/ziel.php
Code 301 => http://127.0.0.1:80/redirect2.php
Code 301 => http://127.0.0.1:80/redirect3.php
Code 307 => http://127.0.0.1:80/ziel.php
Ende: http://127.0.0.1:80/ziel.php
344
e-bol.net
Zugriff auf andere Server mit Zend_Http | "JA
Statuscode: 200
Statusmeldung: OK
Für die meisten Anwendungen werden diese Methoden ausreichen. Allerdings
kennt die Klasse noch mehr Möglichkeiten. Möchten Sie zum Beispiel wissen,
mit was für einer Art Server Sie es zu tun haben, können Sie mit der Methode
getHeaderO die Kennung des Servers auslesen. Dazu übergeben Sie der
Methode den Namen des Headers, dessen Wert Sie auslesen wollen - in diesem
Fall also Server:
echo $response->getHeader('Server');
// mögliche Ausgabe: Apache/1.3.33 (Darwin) PHP/5.2.4
Um alle Header auf einmal auszulesen, rufen Sie getHeadersO auf, womit Sie
alle Header in Form eines Arrays zurückerhalten. Die Namen der Header fungie-
ren hierbei als Schlüssel. Bitte beachten Sie in beiden Fällen, dass ein Header
mehrfach enthalten sein kann. In dem Fall liefern beide Methoden ein Array für
den Header zurück. Benötigen Sie die Header als String zur direkten Ausgabe,
hilft Ihnen getHeadersAsStri ng() weiter.
In den bisherigen Beispielen bin ich immer davon ausgegangen, dass der Server
HTML-Code zurückgibt, der anschließend direkt ausgegeben werden kann. Nun
könnte es aber auch passieren, dass Sie mit einer Weiterleitung auf eine PDF-
Datei verwiesen werden. Um solche Situationen behandeln zu können, müssen
Sie natürlich erkennen, dass es sich nicht um HTML handelt.
Um zu erkennen, welche Art von Inhalt der Client erhalten hat, empfiehlt es sich,
den MIME-Type zu nutzen, den der Server mitgeschickt hat. Mit den folgenden
Zeilen können Sie den aktuellen Katalog von Galileo Press als PDF-Dokument
herunterladen und speichern:
require_once 'Zend/Http/Client.php';
$url = 'http://www.galileocomputing.de/download/'.
'katalog/gali1eo_press_katalog_computing.pdf’:
Sclient = new Zend_Http_Client($url);
Sresponse = $client->request():
if (true — $response->isSuccessful())
{
// MIME-Type auslesen
$mime = $response->getHeader('content-type');
// Sicherheitshalber auf Array prüfen
if (true === is_array($mime))
{
$mime = $mime[0]:
345
e-bol.net
7 | Protokolle und Co.
I
// HTML oder Text?
if ('text/html' == $mime &&
’text/plain' == $mime)
{ // Dann direkt ausgeben
echo $response->getßody();
el se
{
// Kein Text also Daten in Datei speichern
Scontent =$response->getßody();
Sfilename = basename($cl ient->getllri ());
file_put_contents($filename, Scontent);
echo "Sfilename erfolgreich heruntergeladen";
}
Listing 7.6 Herunterladen einer Datei
Die Response-Klasse nutzt intern eine ganze Reihe sehr hilfreicher Funktionen,
um Antworten von Servern zu analysieren. Diese sind alle statisch definiert und
können von außen aufgerufen werden, sodass Sie diese auch dann nutzen kön-
nen, wenn Sie selbst einmal Server-Antworten analysieren wollen.
7.1.7 Cookies
Um einen komplexeren HTTP-Client zu erstellen, werden Sie eine Möglichkeit
benötigen, Cookies zu verwalten. Viele Webanwendungen identifizieren die Cli-
ents mithilfe eines Cookies, indem eine Session-ID auf diesem Weg gespeichert
wird. Darüber hinaus können auf diesem Weg auch andere Daten beim Client
abgelegt werden, die später wieder eingelesen werden. Beliebt ist diese Vorge-
hensweise auch bei Kundennummern und Ähnlichem, wobei sich hier stets die
Frage nach der Sicherheit der Daten stellt. Maximal können in einem Cookie
4096 Bytes an Daten abgelegt werden.
Zend_Http unterstützt ein recht ausgefeiltes Cookie-Management. So können Sie
Cookies vom Server entgegennehmen, speichern und dann beim nächsten
Request wieder mit übertragen. Darüber hinaus können Sie die Cookies auch
analysieren und manipulieren und sogar neue Cookies für eine Domain anlegen.
Cookies auslesen
Die Cookies werden vom Client in einem Objekt der Klasse Zend_Http_Cookie-
Jar verwaltet. Soll Ihr Client Cookies verwalten, müssen Sie zunächst ein
346
e-bol.net
Zugriff auf andere Server mit Zend_Http | 7-1
CookieJar anlegen, indem Sie die Methode setCookieJari) aufrufen. Der Client
legt darin dann die Cookies ab. Dieses Objekt können Sie nach dem Ausfuhren
des Requests mit der Methode getCookieJar() auslesen. Das Auslesen des
Objekts ist ein wichtiges Feature, da das Client-Objekt die Cookies natürlich nicht
speichern kann. Möchten Sie die Cookies später nutzen, damit der Server Sie
wieder identifizieren kann, so müssen Sie das Cooki eJar-Objekt in einer Session,
Datei oder Datenbank Zwischenspeichern. Bitte vergessen Sie nicht, das Objekt
vor dem Speichern zu serialisieren.
Im folgenden Beispiel wurde dieses Script genutzt, um Cookies zu setzen. Es han-
delt sich also so zu sagen um den Server der mithilfe von Zend_Http_Client
angesprochen werden soll.
< ? php
setcookie('Frage’,
'nach dein Leben, dem Universum und dem ganzen Rest');
setcookie('Antwort',42);
?>
Listing 7.7 Setzen von Cookies
Alternativ können Sie für Ihre Tests auch jede beliebige Homepage nutzen, die
Cookies nutzt, wie zum Beispiel www.amazon.de.
Damit Ihr HTTP-Client Cookies entgegennehmen kann, benötigen Sie, wie schon
erwähnt, einen CookieJar. Um diesen zu initialisieren, rufen Sie die Methode
setCookieJar() auf. Dieser können Sie ein CookieJar-Objekt oder true überge-
ben, was dem Default-Wert entspricht. Übergeben Sie nichts oder true, wird ein
neues Objekt abgeleitet und genutzt. Sobald Sie dann den Request an den Server
senden - der CookieJar muss also vorher initialisiert werden - werden die
Cookies als einzelne Objekte im Jar abgelegt. Um die Cookies auszulesen, steht
die Methode getCookiesi) zur Verfügung, welche Ihnen ein Cooki eJar-Objekt
zurückgibt. Anders als viele andere Klassen implementiert die dazugehörige
Klasse leider keinen Iterator, sodass Sie die Cookies zunächst auslesen müssen.
Das kann beispielsweise mit der Methode getAllCookies() geschehen, die
Ihnen die Cookies als Array zurückgibt:
< ? php
require_once 'Zend/Http/Client.php';
$url = ’http://127.0.0.1/set_cookie.php’;
Sclient = new Zend_Http_Client($url);
$client->setCookieJar();
$client->request();
347
e-bol.net
7 | Protokolle und Co.
$cookie_jar = $cl1ent->getCookieJar();
$cookies = $cookie_jar->getAHCookies();
foreach ($cookies as tcookie)
{
echo "<p>";
echo "Domain: ".$cookie->getDomain();
echo "<br>";
echo "Name: ".$cook1e->getName():
echo "<br>";
echo "Wert: ".$cook1e->getValue();
echo "</p>";
I
?>
Listing 7.8 Auslesen von Cookie-Daten
Das Array, das getAl 1 Cookies () zurückliefert, enthält Objekte der Klasse Zend_
Http_Cooki e. Die Informationen, die in den Cookies enthalten sind, können Sie
über bestimmte Methoden auslesen. In diesem Beispiel wurden getDomaint),
getName() und getValue() genutzt, um die Werte der entsprechenden Eigen-
schaften auszulesen. Bei der Domain handelt es sich um die Domain bzw. die IP-
Adresse des Servers; Name und Wert sind selbsterklärend. Die Ausgabe des
Scripts sehen Sie in Abbildung 7.3.
O http://127.0.0.l/~cars.. buch/http_Client/6.php CD
w .«>© O http://127.0-0.1/-CJ'Q’ Google
Domain: 127.0.0.1
Name: Frage
Wert nach dem Leben, dem Universum und dem ganzen Rest
Domain: 127.0.0.1
Name: Antwort
Wert: 42
Abbildung 7.3 Ausgelesene Cookie-Daten
In der Cookie-Klasse sind natürlich noch weitere Methoden deklariert, mit denen
Sie zusätzliche Informationen auslesen können. Mit getExpiryTimet) können
Sie den Timestamp auslesen, der festlegt, wie lange ein Cookie gültig ist. get-
Path() ist diejenige Methode, die Ihnen den Pfad der Datei liefert, welche das
Cookie gesetzt hat. Die Methoden isExpiredO, isSecureO und isSession()
liefern jeweils einen booleschen Wert zurück. Die erste prüft, ob ein Cookie ver-
fallen ist. Übergeben Sie keinen Timestamp, so geht die Methode von der aktuel-
348
e-bol.net
Zugriff auf andere Server mit Zend_Http | "JA
len Serverzeit aus. Sollte ein Cookie keine Verfallszeit haben, liefert die Methode
stets false zurück. Ein Session-Cookie hat übrigens nichts mit einer Session in
PHP zu tun. »Session-Cookie« bedeutet lediglich, dass das Cookie nur während
der Session, also so lange, wie der Browser geöffnet ist, gültig ist. Ein solches Ses-
sion-Cookie können Sie übrigens auch jederzeit daran erkennen, dass die
Methode i sSession() den Wert true zurückgibt. Die Methode i sSecuref) teilt
Ihnen mit einem booleschen Wert mit, ob es sich um ein sicheres Cookie han-
delt, das nur über verschlüsselte Verbindungen ausgetauscht werden sollte.
Die Methode getAl 1 Cooki es () kann Ihnen die Daten eines Cookies auch direkt
als String zurückgeben, falls Ihnen das lieber sein sollte. In diesem Fall können
Sie der Methode beim Aufruf entweder Zend_Http_Cooki eJar:: COOKI E_ST RIN G_
ARRAY oder Zend_Http_CookieJar::COOKIE_STRING_CONCAT übergeben. Im ers-
ten Fall wird jedes Cookie als einzelner String im Array abgelegt, sodass die bei-
den folgenden Strings enthalten sind:
Frage=nach+dem+Leben%2C+dein+Uni versum+und+dem+ganzen+Rest;
Antwort=42;
Die Daten sind also URL-codiert, was Sie ändern können, indem Sie die Daten
mit der Funktion urldecodel) decodieren. Mit der zweiten Konstanten sind die
Daten alle in einem String abgelegt, wobei nach jedem Cookie ein Semikolon
folgt.
Bei den bisherigen Vorgehensweisen wurden immer alle Cookies ausgelesen. Sie
können aber auch einzelne Cookies ansprechen, sofern das für Sie hilfreich sein
sollte. Dazu greifen Sie auf getMatchi ngCookiesf) oder getCookie() zurück.
Die erste Methode bekommt als ersten Parameter die URI übergeben, unter der
die Cookies gesetzt wurden. Die URI setzt sich zusammen aus der Domain bzw.
IP-Adresse des Servers und dem Pfad der Datei, welche die Cookies gesetzt hat.
Sie liefert dann ein Array mit allen Cookie-Objekten zurück, die unter dieser URI
gesetzt wurden. Als zweiten Parameter können Sie einen booleschen Wert über-
geben, der der Methode mitteilt, ob auch Session-Cookies mit zurückgegeben
werden sollen. Dies ist per Default der Fall. Als dritten Parameter können Sie
auch hier die Konstanten Zend_Http_CookieJar: :COOKIE_STRING_ARRAY und
Zend_Http_CookieJar: :COOKIE_STRING_CONCAT nutzen, um die Rückgabewerte
zu beeinflussen.
Die Methode getCookiel) ist ein wenig präziser und bekommt als ersten Para-
meter ebenfalls die URI übergeben. Der zweite Parameter ist der Name des
Cookies, das ausgelesen werden soll.
349
e-bol.net
7 | Protokolle und Co.
Um das Cookie Frage aus dem obigen Beispiel auszulesen, können Sie also die
folgende Zeile nutzen, die Ihnen das Cookie als Objekt zurückgibt:
Scookie = $cookie_jar->getCookie(’http://127.0.0.1/’,'Frage'):
Was allerdings voraussetzen würde, dass die Datei, die das Cookie gesetzt hat, im
Root-Verzeichnis des Servers liegt. Würde sie beispielsweise im Ordner cookies
liegen, so müsste die Methode wie folgt aufgerufen werden:
Scookie = $cookie_jar->getCookie(’http://127.0.0.1/cookies/’,
'Frage’);
Als letzten Parameter können Sie noch eine der beiden String-Konstanten über-
geben, die schon bei getAl 1 Cooki es() erwähnt wurden. In diesem Fall sorgen
aber beide für das gleiche Ergebnis und geben einen String zurück.
Cookies setzen
Natürlich können Sie Cookies auch setzen. Sie können dazu entweder Cookie-
Objekte nutzen, die Sie bereits vom Server erhalten haben, oder Sie leiten neue
Objekte ab. Ein Manipulieren der Inhalte, die in einem empfangenen Cookie ent-
halten sind, ist mit den momentan vorhandenen Methoden nicht möglich.
Um ein neues Cookie-Objekt abzuleiten, müssen Sie dem Konstruktor mindes-
tens den Namen, den Wert und die Domain bzw. die IP des Servers übergeben.
Optional können Sie noch das Verfallsdatum als Timestamp, den Pfad und einen
booleschen Wert übergeben, der festlegt, ob es sich um ein Cookie handelt, das
nur über eine sichere Verbindung übertragen werden darf.
Ein so konstruiertes Cookie-Objekt können Sie mit der Methode addCooki e() an
das CookieJar-Objekt übergeben, welches Sie dann wiederum an das HTTP-
Objekt übergeben müssen:
< ? php
require_once 'Zend/Http/Client.php’;
require_once ’Zend/Http/Cookie.php’;
requi re_once 'Zend/Http/CookieJar .php';
$url = ’http://127.0.0.1/get_cookie.php’;
Sclient = new Zend_Http_Client($url);
Scookie = new Zend_Http_Cookie('Vorname’, // Name des Cookies
'Friederike', // Wert des Cookies
'example.com', // Domain
time()+3600): // Gültigkeit: Jetzt+lh
350
e-bol.net
Zugriff auf andere Server mit Zend_Http | 7-1
$cookie_jar = new Zend_Http_CookieJar();
$cookie_jar->addCookie(Scookie);
$cl 1ent->setCookieJar($cookie_jar);
$client->request();
?>
Listing 7.9 Setzen von Cookies
Das Script, das serverseitig aufgerufen wird, kann die Daten anschließend über
das superglobale Array $_COOKIE auslesen. In diesem Beispiel könnten Sie den
Wert des Cookies also über $_COOKI E[' Vorname' ] auslesen.
Alternativ können Sie ein Cookie-Objekt auch ableiten, indem Sie der statischen
Methode fromString() ebenfalls einen String übergeben, wie er mit dem Set-
Cooki e-Header genutzt wird, um ein Cookie zu setzen:
Scookie = Zend_Http_Cookie::fromString(’Vorname=Friederike:
expires=Wednesday, 31-0ct-2007 23:59:59 GMT;
domain=.example.com: path=/cookies; secure'):
In diesem Beispiel würde ein Cookie mit dem Namen Vorname erstellt, das den
Wert Friederike hat und am 31. Oktober 2007 um 23:59:59 Uhr verfällt. Des
Weiteren gehört es zur Domain example.com, wurde unter dem Pfad cookies
gesetzt und soll nur über sichere Verbindungen übertragen werden. Würden Sie
das Schlüsselwort secure entfallen lassen, könnte das Cookie über jede Art von
Verbindung übertragen werden. Ohne Datumsangabe würde es sich um ein Ses-
sion-Cookie handeln.
7.1.8 Nutzung von Adaptern
Bisher basieren alle Beispiele darauf, dass eine direkte Verbindung zum Internet
besteht. In der Realität ist das natürlich nicht immer so. Nutzen Sie beispielsweise
einen Intranet-Server, kann es schnell passieren, dass dieser über einen Proxy-
Server mit dem Internet verbunden ist. Aber auch dafür ist Zend_Http gerüstet.
Zend_Http unterstützt verschiedene Adapter, die unterschiedliche Zugriffsmög-
lichkeiten ermöglichen. Standardmäßig nutzt das Paket den Socket-Adapter, der
davon ausgeht, dass eine direkte Verbindung zum Internet besteht. Darüber hin-
aus sind auch ein Proxy- und ein Test-Adapter vorhanden. Den Proxy-Adapter
finden Sie nachfolgend erläutert. Der Test-Adapter ermöglicht es Ihnen, Ihre
Applikationen zu testen, indem er zuvor definierte Antworten bereitstellt. Das
heißt, Sie können testen, ohne direkt via HTTP auf den entsprechenden Dienstan-
bieter zuzugreifen. Informationen zur Nutzung finden Sie im Manual.
351
e-bol.net
7 | Protokolle und Co.
Konfiguration des Socket-Adapters
In den meisten Fällen werden Sie den Socket-Adapter nicht weiter konfigurieren
müssen. Allerdings haben Sie die Möglichkeit, noch kleinere Einstellungen vor-
zunehmen. Dazu können Sie dem Konstruktor noch einen zweiten Parameter
übergeben. Dabei handelt es sich um ein Array, das die Schlüssel aus Tabelle 7.2
enthalten darf.
Schlüssel Erläuterung
ssltransport Definiert, welcher Transport-Layer genutzt werden soll, ssl, sslv2 oder tl s sind mögliche Werte, wobei ssl der Standard ist.
sslcert Kann den Pfad zu einem PEM-codierten SSL-Zertifikat enthalten.
sslpassphrase Dient dazu, eine Passphrase für ein SSL-Zertifikat zu übergeben.
Tabelle 7.2 Konfiguration des Socket-Adapters
Verbindungsaufbau über einen Proxy
Möchten Sie eine Verbindung über einen Proxy aufbauen, so können Sie auf den
Proxy-Adapter zurückgreifen. In dem Fall übergeben Sie als zweiten Parameter
ein Array, das die oben genannten Werte enthalten darf. Zusätzlich werden aber
noch die Informationen benötigt, die den Zugriff auf den Proxy ermöglichen.
Hierbei handelt es sich um die Schlüssel aus Tabelle 7.3.
Schlüssel Bedeutung
adapter Muss den Wert Zend_Http_Cl ient_Adapter_Proxy zugewiesen bekommen.
proxy_host Name oder IP-Adresse des Proxy-Servers
proxy_port Port, auf dem der Proxy angesprochen werden soll (Default: 8080)
proxy_user Benutzername für den Proxy, sofern erforderlich
proxy_pass Passwort für den Proxy, sofern erforderlich
proxy_auth Authentifikationsverfahren für die Anmeldung beim Proxy-Server, Standard ist Zend_Http_Cl i ent:: AUTH_BASIC. Eine Digest-Authentifikation ist noch nicht implementiert.
Tabelle 7.3 Konfiguration des Proxy-Adapters
7.2 URIs mit Zend_Uri verarbeiten
Die Klasse Zend_Uri wird intern in vielen Klassen des Zend Frameworks genutzt.
Sie dient dazu, URIs zu generieren und zu validieren. Zurzeit werden nur URIs mit
HTTP- und HTTPS-Schema unterstützt. Im Quelltext findet sich ein Hinweis, dass
352
e-bol.net
URIs mit Zend_Uri verarbeiten | 7-2
demnächst auch mailto-URIs unterstützt werden sollen. Aber das ist zurzeit noch
nicht der Fall. Es kann also nicht schaden, wenn Sie einen Blick in die Dokumen-
tation werfen, um zu erfahren, welche Möglichkeiten die Klasse inzwischen bietet.
7.2.1 URIs analysieren
In den meisten Fällen wird es sicher so sein, dass Sie eine URI analysieren wollen,
beispielsweise weil Sie über ein Formular eingegeben wurde. Die Gültigkeit
einer URI können Sie mit der statischen Methode check() prüfen, welche einen
booleschen Wert zurückliefert.
requi re_once('Zend/Uri.php');
$uri = ’http://www.adminblogger.de/blog/';
if ( true === Zend_Uri::check($uri))
{
echo 1 URI ist gültig':
}
el se
{
echo 'URI ist NICHT gültig':
}
Listing 7.10 Validieren einer URI
In vielen Fällen wird diese Methode sicher schon eine große Hilfe sein, wobei Sie
natürlich auch auf Zend_Val idate_Hostname zurückgreifen könnten. Zend_Uri
kann aber noch mehr. Sie können beispielsweise einzelne Teile einer URI extra-
hieren oder auch Strings, die in einer URI genutzt werden sollen, auf ihre Gültig-
keit hin prüfen.
Um die Einzelteile zu extrahieren, benötigen Sie zunächst ein entsprechendes
Objekt. Sie könnten also direkt ein Zend_Uri_Http-Objekt ableiten oder die Fac-
tory-Methode nutzen, die in der Klasse Zend_Uri deklariert ist:
requi re_once('Zend/Uri.php');
Sstring = 'http://paule:geheim@www.example.com:88':
Sstring .= '/daten/index.php?id=12&show=true#unten';
$uri = Zend_Uri::factory($string);
echo "Protokoll: ".$uri->getScheme();
echo "<br>";
echo "User: ".$uri->getUsername();
echo "<br>";
353
e-bol.net
7 | Protokolle und Co.
echo "Passwort: ".$uri->getPassword():
echo *<br>":
echo "Host: ".$uri->getHost():
echo "<br>";
echo "Port: ".$uri->getPort():
echo "<br>";
echo "Pfad: ".$uri->getPath();
echo "<br>";
echo "Query-String: ".$uri->getQuery();
echo "<br>”;
echo "Anker: ".$uri->getFragment():
Listing 7.11 Bestandteile einer URI auslesen
Die Ausgabe des Scripts sehen Sie in Abbildung 7.4.
http://ca. .URI/l.php CD
QQOU
Protokoll: http
User paule
Passwort: geheim
Host: www.examplc.com
Port: 88
Pfad: /datcn/indcx.php
Query-String: id=12&show=true
Anker unten
Abbildung 7.4 Bestandteile einer URI
Sollten Sie also URIs analysieren müssen, so ist das sicher ein sehr einfacher und
eleganter Weg. Die einzelnen Methoden sind selbsterklärend, sodass ich nicht
weiter darauf eingehe. Ist einer der Werte nicht in der URI enthalten, so liefert
die Methode false zurück. Mit der Methode val id(), die hier nicht genutzt ist,
können Sie die Gültigkeit der übergebenen URI prüfen. Die komplette URI kön-
nen Sie mit der Methode getUri () auslesen. Im obigen Beispiel ist dies nicht
sinnvoll; aber Sie können eine URI mit dieser Klasse auch neu erstellen bzw. eine
bestehende URI manipulieren. Dazu sind für jeden Teil der URI Methoden dekla-
riert, deren Namen mit set beginnen, also zum Beispiel setHostO oder set-
UsernameO. Das Schema, sprich das Protokoll (also HTTP), können Sie nicht
ersetzen. Möchten Sie, bevor Sie die Werte in die URI übernehmen, erst testen,
ob sie gültig sind, so können Sie zu diesem Zweck auf einen Satz von Methoden
zugreifen, deren Name jeweils mit validate beginnt, zum Beispiel validate-
Passwordt). Diese Methoden liefern jeweils einen booleschen Wert zurück.
354
e-bol.net
Nutzung von XML-RPC mit Zend_XmlRpc | 7-3
requi re_once('Zend/Uri.php'):
Suser = 'Barney' ;
Spasswort = 'geheim';
Sport = 80:
Sstring = 'http://www.example.com:88/index.php';
Suri = Zend_Uri::factory(Sstring):
if (true === Suri->validateUsername(Suser))
{
Suri->setUsername(Suser);
}
if (true === Suri->validatePassword(Spasswort))
{
Suri->setPassword(Spasswort);
}
if (true — Suri->validatePort(Sport))
{
Suri->setPort(Sport);
}
echo Suri->getUri();
// Ausgabe: http://Barney:geheim@www.example.com:80/index.php
Listing 7.12 Erstellen einer URI
7.3 Nutzung von XAAL-RPC mit Zend_XmlRpc
XML-RPC ist einer von vielen Standards, mit denen Sie Funktionalitäten auf
anderen Servern ansprechen und ausführen können. Die Abkürzung XML-RPC
steht für »Extensible Markup Language Remote Procedure Call«. Das System
basiert darauf, dass XML-Daten über das HTTP-Protokoll ausgetauscht werden.
Das heißt, es ist möglich, eine Prozedur auf einem entfernten Server anzuspre-
chen und ihr Daten zu übergeben.
7.3.1 Allgemeines zu Zend_XmlRpc
Beim Aufruf einer Prozedur via XML-RPC kommt es häufiger vor, dass die Funk-
tionalität bestimmte Informationen benötigt, um ihre Aufgabe verrichten zu kön-
nen. Diese Daten werden innerhalb des Requests mit zum Server übertragen.
Auch wenn die Daten innerhalb von XML ja nur als einfache Textdaten darge-
stellt werden, so übermittelt XML-RPC dennoch Informationen zu dem XML-
355
e-bol.net
7 | Protokolle und Co.
RPC-Datentyp, welcher der Information zugrunde liegt. Hierbei ist zu beachten,
dass Sie auch immer die korrekten Datentypen übergeben müssen. Standardmä-
ßig können Sie davon ausgehen, dass die PHP-eigenen Datentypen direkt in die
entsprechenden XML-RPC-Datentypen konvertiert werden können, wie Sie in
Tabelle 7.4 sehen.
PHP-Datentyp XML-RPC-Datentyp Klasse im Zend Framework
Integer Integer Zend_Xml Rpc_Value_Integer
Double Double Zend_XmlRpc_Value_Double
Boolean Bolean Zend_XmlRpc_Value_Boolean
String String Zend_XmlRpc_Value_Stri ng
Array Array Zend_XmlRpc_Value_Array
assoziatives Array Struct Zend_XmlRpc_Value_Struct
Object Array Zend_XmlRpc_Value_Array
- Base64 Zend_XmlRpc_Value_Base64
- DateTime.iso8601 Zend_XmlRpc_Value_DateTime
Tabelle 7.4 XML-RPC-Datentypen
Übergeben Sie eine PHP-Variable an eine der Zend_Xml Rpc-Methoden, so sollte
der Wert in den korrekten Datentyp konvertiert werden. Alternativ können Sie
aber auch immer ein Objekt der entsprechenden Klasse ableiten.
7.3.2 Erstellen eines XML-RPC-Servers
Zend_Xml Rpc stellt komfortable Möglichkeiten zur Verfügung, einen XML-RPC-
Server zu erstellen. Die grundsätzliche Idee ist einfach. Sie leiten ein Server-
Objekt ab und teilen ihm mit, welche Funktionen bzw. Klassen ansprechbar sein
sollen. Sobald Sie diese beim Server angemeldet haben, kann er die Anfrage ver-
arbeiten.
Ein ganz einfacher Server könnte so aussehen:
requi re_once 'Zend/XmlRpc/Server.php';
// Funktion die im Server zur Verfügung stehen soll
function hallo_sagen()
{
return "Hallo Welt":
}
// Server-Objekt ableiten
Sserver = new Zend_XmlRpc_Server():
356
e-bol.net
Nutzung von XAAL-RPC mit Zend_XmlRpc | 7-3
// Funktion anmelden
$server->addFunction('hal1o_sagen');
// Anfrage verarbeiten
echo $server->handle();
Listing 7.13 Ein einfacher XML-RPC-Server
Dieser Server kennt also nur die Funktion hal l o_sagen, die von außen angespro-
chen werden kann. Auch wenn diese Funktion nur einen Wert zurückgibt,
könnte sie auch genauso gut ein Array zurückgeben.
Natürlich können Sie auch komplette Klassen beim Server anmelden und nicht
nur einzelne Funktionen. Um eine Klasse anzumelden, nutzen Sie die Methode
setClassO. Die Methoden, die darin enthalten sind, können dann vom Client
auch direkt angesprochen werden, ohne dass der Name der Klasse angegeben
werden muss. Das hat zur Folge, dass es schnell zu Namenskonflikten kommen
kann. Um dies zu verhindern, kennen beide Methoden die Möglichkeit, einen
Namespace zu nutzen. Dabei handelt es sich um einen einfachen String, den Sie
als zweiten Parameter angeben. Wäre die Funktion aus dem letzten Beispiel mit
dem Befehl
$server->addFunction('hallo_sagen', 'stringverarbeitung ’);
angemeldet worden, könnte der Client die Methode unter dem Namen stri ng-
verarbeitung.hallo_sagen ansprechen.
Ein besonders schönes Feature des Zend_Xml Rpc-Servers ist die Tatsache, dass drei
»system-Methoden« implementiert sind. Hiermit kann der Client Informationen
über den Server erhalten. Das heißt, der Client kann auf dem Server beispielsweise
die Methode System. I 1 stMethods aufrufen. Der Server antwortet mit einer Liste
der Methoden bzw. Funktionen, die bekannt sind. Die Methode System. method -
Hel p bekommt vom Client den Namen einer Funktion oder Methode übergeben
und liefert eine Erläuterung, was die Methode leistet. Mit system.methodSigna-
ture kann der Client abfragen, was für einen Datentyp die Funktion zurückgibt
und welche Parameter sie erwartet. Natürlich kann der Server nicht »hellsehen«,
um diese Informationen zu ermitteln. Er kann aber einen DocBlock auslesen und
die dort enthaltenen Informationen auswerten. Geben Sie einen solchen DocBlock
nicht an, erkennt der Server die Anzahl der Parameter, die eine Funktion benötigt,
kann aber die erwarteten Datentypen nicht ermitteln. Auch die Methode method -
Help generiert die benötigten Informationen aus dem DocBlock. Bitte denken Sie
bei der Erstellung des DocBlocks daran, dass die Informationen für Zend_Xml Rpc
gedacht sind. Sie sollten also die XML-RPC-Datentypen verwenden. Wichtig dabei
ist, dass Sie die Namen der Datentypen komplett in Kleinbuchstaben schreiben,
andernfalls werden sie nicht erkannt.
357
e-bol.net
7 | Protokolle und Co.
Fehlerbehandlung
Ein weiteres pfiffiges Feature des Servers ist, dass er zwar auf Exceptions reagiert,
die Meldungen aus den Exceptions aber nicht automatisch an den Client zurück-
liefert. Damit ist sichergestellt, dass der Client keine Fehlermeldungen erhält, die
eigentlich nicht für ihn gedacht sind.
Damit Sie aber die Möglichkeit haben, bestimmte Fehler an den Client auszuge-
ben, können Sie Exception-Klassen beim Server anmelden, deren Meldungen
dann an den Client weitergegeben werden. Dazu müssen Sie die Klasse Zend_
Xml Rpc_Server_Faul t einbinden und den Namen der Exception-Klasse an die
darin enthaltene statische Methode attachFaul tException() übergeben.
Ein kompletter Server, der addieren und dividieren kann, könnte so aussehen:
requi re_once 'Zend/XmlRpc/Server.php';
requi re_once 'Zend/XmlRpc/Server/Excepti on.php';
// Exception-Klasse fürs Error-Handling
class RechnerException extends Exception
1 I
class Rechner
{
* addiert zwei Zahlen
* @param double Seins
* @param double Szwei
* @return double
* /
function addiere (Seins, Szwei)
{
return (Seins+Szwei):
* Dividiert divident durch divisor
* @param struct Szahlen
* @return double
* /
function dividiere (Szahlen)
{
Sdivident = SzahlenE'divident'];
Sdivisor = Szahlen['divisor'];
358
e-bol.net
Nutzung von XAAL-RPC mit Zend_XmlRpc | 7-3
if (0 == $divisor)
throw new RechnerException('Division durch Null');
1
return ($divident / $divisor);
}
Sserver = new Zend_XmlRpc_Server();
Zend_XmlRpc_Server_Fault::attachFaultException(
’RechnerExcepti on');
// Anmelden der Klasse im Namespace "rechne"
$server->setCIass('Rechner', 'rechne');
echo $server->handle():
Listing 7.14 Kompletter XML-RPC-Server
Die beiden Methoden des Servers können jetzt nur mit den korrekten Datenty-
pen aufgerufen werden. Versuchen Sie, die Methode dividiere beispielsweise
mit einem Array und nicht mit einem Struct, also einem assoziativen Array, auf-
zurufen, resultiert das in einer Fehlermeldung.
Die Exception-Klasse, die hier deklariert ist, ist nicht sonderlich umfangreich. Sie
wird lediglich benötigt, damit ein eindeutiger Klassenname vorhanden ist, der
beim Server für die Fehlerbehandlung angemeldet werden kann.
Sollten Sie einmal einen umfangreicheren Server implementieren, könnte es hilf-
reich sein, die Klassen-Dateien zu cachen. Wie das funktioniert, können Sie dem
Manual entnehmen.
7.3.3 Erstellen eines XML-RPC-Clients
Der Client ist erfreulich schnell und einfach erstellt, da er nur aus einem Metho-
denaufruf besteht, nämlich cal l (). Mit cal l () können Sie eine Methode auf
dem Server aufrufen, die der Methode als erster Parameter übergeben wird. Die
URL, unter der der Server zu erreichen ist, übergeben Sie dem Konstruktor der
Klasse Zend_Xml Rpc_Server, welcher das Objekt bereitstellt, aus dem heraus Sie
cal l () aufrufen.
Ein Client, der den oben dargestellten Server anspricht, könnte folgendermaßen
aussehen:
359
e-bol.net
7 | Protokolle und Co.
requi re_once 'Zend_reposi tory/Xml Rpc/Cl ient.php’;
// Objekt ableiten
Sclient = new Zend_XmlRpc_Client(
'http://www.bei spiel.de/Server.php');
try {
// Durchführen der Addition
echo "Addition:<br>";
echo "1 + 2 = ";
echo $cl ient->cal 1 (' rechne. addi ere ’, arrayd.O. 2.0)):
echo ' <br>';
// Ausführen einer Division
echo "<br>Division:<br>”;
echo ”1 / 2 = ":
$parameter = array(’divident'=>l, ’divisor’=>2):
echo $client->cal1('rechne.dividiere’, array($parameter));
echo '<br>';
// Generiert einen Fehler:
$parameter = array(’divident'=>l, ’divisor’=>0);
echo $client->cal1('rechne.dividiere’, array($parameter));
I
catch (Exception $e)
{
echo "Der folgende Fehler ist aufgetreten: ":
echo $e->getMessage():
I
Listing 7.15 XAAL-RPC-Client
Wie Sie sehen, werden die für den Methodenaufruf benötigten Parameter als
Array übergeben. Da die Addition Double-Werte erwartet, müssen hier auch
Fließkomma-Werte übergeben werden, damit die automatische Konvertierung
korrekt funktioniert.
Die Division erfordert ein Struct. Damit dieses korrekt übergeben wird, muss
zunächst ein assoziatives Array angelegt werden, welches die korrekten Schlüssel
nutzt. Der Server kann die Schlüssel zwar nicht selbstständig prüfen, aber die
Methode kann die Werte sonst nicht auslesen. Dieses assoziative Array muss
dann wieder in ein Array verpackt werden, damit es korrekt übergeben wird.
Die Ausgabe, die von diesem Client generiert wird, lautet:
Addition:
1 + 2 = 3
360
e-bol.net
Nutzung von REST mit Zend_Rest | 7-4
Division:
172=0.5
Der folgende Fehler ist aufgetreten: Division durch Null
Auch die Fehlermeldung wird korrekt übermittelt. Nutzen Sie einen fremden
XML-RPC-Server, kann es oft hilfreich sein zu sehen, welche Anfrage Ihr Client
verschickt hat. Das können Sie ohne Probleme in das Exception Handling inte-
grieren. Und zwar können Sie die letzte Anfrage mit getl_astRequest() auslesen.
Die folgenden Zeilen könnten also beim Debugging hilfreich sein:
echo "Debug-Information:<br>";
echo "Request:<br>";
echo nl2br(htmlspeci alchars($ c11 ent->getLa stRequest())):
7.4 Nutzung von REST mit Zend_Rest
Die Abkürzung REST steht für »Representational State Transfer« und bezeichnet
eine weitere wichtige Variante, um Webservices zu implementieren. Etwas unge-
wöhnlich bei REST ist allerdings, dass es sich nicht um einen genau definierten
Standard handelt, sondern vielmehr um die Beschreibung einer Architektur. Das
heißt, dass Client und Server recht genau wissen müssen, wie sie miteinander zu
kommunizieren haben. REST basiert auf den »üblichen« Standards. Es werden
also üblicherweise XML-Daten über das HTTP-Protokoll ausgetauscht. Grundsätz-
lich unterstützt REST die vier HTTP-Befehle GET, POST, DELETE und PUT, die sich
auch als Methoden in der Klasse wiederfinden. In den meisten Fällen findet die
Datenübergabe bei REST mit der GET-Methode statt, sodass die Daten im Endef-
fekt einfach an die URL angehängt werden. Nachfolgend werde ich auf die API
von Yahool-Maps zugreifen. Bei Yahoo! kann eine solche URL, die auch als API-
Endpunkt bezeichnet wird, so aussehen:
http://local.yahooapis.com/MapsService/V1/geocode
Im Fall von Yahoo! folgt nach dem Host (local.yahooapis.com) der Servicename
(MapsService) und die Versionsnummer (V7). Das ist der übliche Aufbau, obwohl
die Struktur des API-Endpunktes natürlich frei gewählt werden kann. Der letzte
Teil der URL (geocode) bezeichnet in diesem Fall die Methode, die genutzt wer-
den soll.
Üblicherweise werden der Methode noch Werte übergeben, wie Sie gleich sehen
werden.
361
e-bol.net
7 | Protokolle und Co.
7.4.1 Zugriff auf offene REST-Schnittstellen
Einen REST-Client mit Zend_Rest zu erstellen, ist denkbar einfach. Allerdings gilt
es dabei, zwei Fälle zu unterscheiden. Nutzen Sie einen Server, der auf Zend_Rest
aufbaut oder nutzen Sie keinen? Im ersten Fall haben Sie ein paar mehr Möglich-
keiten. Ich werde Ihnen aber zuerst den Zugriff auf ein anderes Angebot, nämlich
auf Yahoo!-Maps, vorstellen.
Um einen Client zu erhalten, müssen Sie im Prinzip nur ein Objekt der Klasse
Zend_Rest_Cl i ent ableiten. Beim Ableiten des Objekts können Sie dem Kon-
struktor direkt den API-Endpunkt übergeben. Alternativ können Sie den End-
punkt auch später mit der Methode setllri () an das Objekt übergeben.
Die weitere Vorgehensweise hängt nun allerdings von dem Dienst ab, den Sie
ansprechen wollen. Das heißt, Sie müssen sich mit der Dokumentation des ent-
sprechenden Anbieters befassen. Im folgenden Beispiel sollen mithilfe der
Yahoo!-Maps-API der Längen- und der Breitengrad einer Adresse ermittelt wer-
den. Die Dokumentation zu diesem API-Aufruf finden Sie unter http://developer.
yahooxom/maps/rest/Vl/geocode.html. Die Dokumentation besagt, dass der
Methode geocode bestimmte Informationen mit übergeben werden müssen,
damit sie ihre Aufgabe erfüllen kann. Das ist einleuchtend, denn woher sollte die
Methode sonst wissen, um welche Adresse es sich handelt. Darüber hinaus benö-
tigt die Methode noch Ihren API-Key, mit dem Sie sich identifizieren. Nachfol-
gend wird nur ein Beispiel-Key genutzt, welchen Sie für den Produktivbetrieb
nicht benutzen sollten.
Der Key muss als Parameter appi d übergeben werden und die einzelnen Bestand-
teile der Adresse mithilfe der Parameter Street und city. Eine etwas unschöne
Eigenschaft der API ist, dass deutsche Sonderzeichen vermieden werden sollten.
Das bedeutet, dass ein »ß« durch »ss« ersetzt werden muss, »ä« durch »ae« usw.
In diesem Fall müssen Sie sich also keine Gedanken um den Zeichensatz oder die
Codierung machen. Ansonsten gibt Zend_Rest die Daten allerdings in dem Zei-
chensatz weiter, in dem sie übergeben wurden, wobei die Klasse die Daten natür-
lich korrekt URL-codiert. Kurioserweise enthält die Antwort wieder Umlaute in
UTF-8-Codierung.
Um die Parameter zu setzen, rufen Sie einfach eine Methode auf, die den Namen
des Parameters hat, und übergeben ihr den Wert, der genutzt werden soll. Nach-
dem das erfolgt ist, können Sie die gewünschte Methode auf dem Server aufru-
fen, indem Sie eine der Methoden get(), post(), del ete() oder put() verwen-
den. Jede entspricht dabei dem entsprechenden HTTP-Befehl. Üblich ist, dass die
Daten per GET übergeben werden. Dies entspricht dem, was die Yahoo!-API
erwartet:
362
e-bol.net
Nutzung von REST mit Zend_Rest | 7-4
require_once ' Zend/Rest/CI lent.php’;
$client = new Zend_Rest_Cl1ent(
'http://local.yahooapis.com/MapsService/Vl/geocode');
$client->appid(’YahooDemo'):
$client->street(’Rhoenstrasse');
$client->city('Bielefeld'):
$erg = $c1i ent->get():
echo "Längengrad$erg->Result->Longi tude."<br>";
echo "Breitengrad:",$erg->Result->Latitude. ’’<br>";
echo "Straße: ".utf8_decode($erg->Result->Address."<br>");
echo "Ort: ".utf8_decode($erg->Result->C1ty."<br>");
Listing 7.16 Zugriff auf Yahoo!-Maps
Der Aufruf der Methode get () sendet die Anfrage an den Server und liefert die
Antwort als SimpleXML-Objekt zurück. Dieses kann dann mit den in PHP übli-
chen Methoden verarbeitet werden. Auch dies setzt voraus, dass Sie genau wis-
sen, wie die Antwort des Servers aufgebaut ist. Hierbei ist zu beachten, dass
Zend_Rest zurzeit nur mit Antworten umgehen kann, die ein gültiges XML-
Dokument darstellen. Somit kann Yahoos! Feature, direkt serialisiertes PHP
zurückzuliefern, mit diesem Client nicht genutzt werden.
Die Ausgabe von Listing 7.16 sehen Sie in Abbildung 7.5.
O n http://127.0.0.1/~c.../zf-buch/rest/l.php
0 G - Q7 Google »
£Q Planet PHP (17) Apple (9) ▼ Amazon eBay »
Längcngrad:8.603196
Breitengrad :52.020036
Straße: Rhönstrasse
Ort: 33719 Bielefeld
Abbildung 7.5 Ausgabe der geodätischen Informationen
7.4.2 Implementation eines REST-Servers
Wie schon erwähnt, läuft Zend_Rest erst dann zu voller Leistungsfähigkeit auf,
wenn Client und Server kombiniert werden. Das resultiert daraus, dass der Ser-
363
e-bol.net
7 | Protokolle und Co.
ver noch einige zusätzliche Information mitliefert, die seitens des Clients verwer-
tet werden können.
Möchten Sie einen REST-Server auf Basis von Zend_Rest implementieren, so ver-
fügen Sie über zwei Möglichkeiten, Funktionalitäten zu hinterlegen. Die erste
Methode ist, Funktionen zu nutzen. Diese müssen Sie, nachdem Sie ein Server-
Objekt abgeleitet haben, mithilfe von addFunction() anmelden. Die Methode
bekommt dabei den Namen der Funktion als Parameter übergeben. Möchten Sie
mehrere Funktionen auf einmal anmelden, so übergeben Sie diese einfach als
indiziertes Array. Die zweite Möglichkeit ist, eine Klasse beim Server anzumel-
den. Dazu übergeben Sie den Namen der Klasse an die Methode setClass(). Der
Server erkennt dann selbstständig, welche Methoden in der Klasse enthalten
sind. Benötigt der Konstruktor der übergebenen Klasse Parameter, übergeben Sie
diese in Form eines Arrays als dritten Parameter. Der zweite Parameter wird im
nächsten Absatz erläutert.
Sie können beide Methoden mehrfach aufrufen. Kommt es bei den Funktionen
oder Methoden in den Klassen zu Namensgleichheiten, wird jeweils diejenige
Funktion bzw. Methode genutzt, die zuletzt übergeben wurde. Zwar unterstüt-
zen die beiden Methoden zum Anmelden der Klassen einen zweiten Parameter
für einen Namespace, aber dieser wird intern nicht genutzt.
Nachdem Sie die Funktionen oder Klassen angemeldet haben, rufen Sie die
Methode handlet) auf. - Ihr Server ist nun fertig für den Einsatz.
Es ist nun noch zu klären, wie die Funktionen bzw. Methoden aufgebaut sein
müssen, die Sie nutzen wollen. Im einfachsten Fall können Sie das Ergebnis der
Verarbeitung direkt mit return zurückgeben, wie das folgende Beispiel zeigt:
require_once ’Zend/Rest/Server.php’;
class Rechner
{
// Addiert zwei Werte
public function addiere ($summand_l, $summand_2)
{
return (float) $summand_l + (float) $summand_2;
// Subtrahiert den zweiten Wert vom ersten
public function subtrahiere (Sminuend, tsubtrahend)
{
return (float) $mi nuend - (float) $subtnahend:
1
364
e-bol.net
Nutzung von REST mit Zend_Rest | 7-4
// Neues Server-Objekt ableiten
Sserver = new Zend_Rest_Server();
// Klasse mit den Methoden anmelden
$server->setCIass('Rechner');
// Server "starten"
$server->handle();
Listing 7.17 Aufbau eines REST-Servers
Um den Server anzusprechen, rufen Sie die URL direkt auf, wobei Sie die benöti-
gen Parameter über die URL übergeben können. Hierbei bietet es sich an, den
Server in einem Verzeichnis abzulegen und die Datei index.php zu nennen. Bei
einer komplexeren Anwendung empfiehlt sich natürlich die Applikation als MVC
aufzubauen, wobei Sie den Server dann im Action-Controller initialisieren könn-
ten. Welche Methode aufgerufen wird, definieren Sie hierbei über den Parame-
ter method, dem der Name der gewünschten Methode übergeben wird. Die Para-
meter, die Sie der Methode übergeben wollen, können Sie auf zwei Arten
spezifizieren. Die Server-Klasse analysiert die Methoden und Funktionen und
erfasst dabei die Namen der Parameter, sodass Sie die Namen der Parameter
direkt nutzen können. Als zweite Variante übergeben Sie die Parameter der Reihe
nach. Damit der Server die Argumente eindeutig erkennt, müssen Sie diese argO,
arg1, arg2, arg3 usw. benennen, wobei sich die Reihenfolge aus der Nummerie-
rung und nicht aus der tatsächlichen Reihenfolge in der URL ergibt. Wollen Sie 3
von 4 subtrahieren, so könnten Sie diese
www.zf-buch.de/rest/?method=subtrahi ere&argl=4&arg2=3
oder diese URL
www.zf-buch.de/rest/?method=subtrahi ere&subtrahend=3&minuend=4
für den Aufruf nutzen. Die XML-Struktur der Antwort sieht folgendermaßen aus:
<?xml version="l.0" encoding="UTF-8"?>
<Rechner generator="zend" version="l,0">
<subtrahi ere>
<response>l</response>
<status>success</status>
</subtrahi ere>
</Rechner>
In der XML-Antwort sind die Daten also gekapselt enthalten. Das Root-Element
Rechner entspricht dem Namen der Klasse, danach folgt der Name der Methode
und zum Schluss als Wert des Elements response der Rückgabewert der Funk-
365
e-bol.net
7 | Protokolle und Co.
tion. Mit dem Element Status können Sie dem Client mitteilen, ob der Aufruf
erfolgreich war. Sie werden dies gleich noch sehen.
Da Sie den Server wahrscheinlich nicht direkt ansprechen, sondern eher den Cli-
ent des Pakets nutzen wollen, könnte der Aufruf wie folgt aussehen:
require_once 'Zend/Rest/Client.php';
Sclient = new Zend_Rest_Cl1ent('http://www.zf-buch.de/rest'):
$cl1ent->method('subtrahiere’);
$client->minuend(4);
$client->subtrahend(3);
$erg = $client->get();
echo "Ergebnis der Subtraktion: ",$erg->subtrahiere->response;
Die meisten Punkte kommen Ihnen sicher bekannt vor. Die Methode get() kön-
nen Sie übrigens auch durch post() ersetzen. Neu in diesem Beispiel ist, dass mit
der Methode method() definiert wird, welche der Methoden auf dem Server
angesprochen werden soll.
Die Kombination aus Zend-Server und -Client gestattet Ihnen aber noch eine
zweite Variante:
require_once 'Zend/Rest/Client.php';
Sclient = new Zend_Rest_Client('http://www.zf-buch.de/rest'):
// Deklaration der Methode und der Parameter
$client->subtrahiere(4, 3):
$erg = $client->get();
echo "Ergebnis der Subtraktion: ",$erg->subtrahiere->response;
Durch den Aufruf $client->subtrahiere(4,3): wird festgelegt, welche
Methode genutzt werden soll und welche Parameter diese erhalten soll. Bei die-
ser Vorgehensweise werden die Parameter mithilfe von argO, argl etc. überge-
ben.
In dem vorangegangenen Beispiel hatten die Methoden nur einen Rückgabewert.
Möchten Sie mehrere Werte zurückgeben, ist das auch kein Problem. Liefern Sie
dazu einfach ein assoziatives Array mit Daten zurück. Die Schlüssel des Arrays
werden dabei als Elementnamen in der XML-Antwort übernommen. Wenn also
die Methode subtrahiere auch die übergebenen Parameter mit an den Client
schicken soll, so könnten Sie das so umsetzen:
366
e-bol.net
Nutzung von REST mit Zend_Rest | 7-4
public function subtrahiere ($minuend. $subtrahend)
{
return array('minuend'=>$minuend.
'Subtrahend'=>$Subtrahend,
’ergebnis’ => ((float) $minuend -
(float) $subtrahend));
}
Der XML-Code der Antwort würde nun so aussehen:
<?xml version="l.0" encoding="UTF-8"?>
<Rechner generator="zend" version="l.0">
<subtrahi ere>
<mi nuend>4</minuend>
<subtrahend>3</subtrahend>
<ergebnis>l</ergebnis>
<status>success</status>
</subtrahiere>
</Rechner>
Den Inhalt des Elements Status können Sie auch beeinflussen. Bei diesem Ele-
ment handelt es sich um ein Feature der Zend_Rest-Klassen. Hiermit kann der
Server dem Client mitteilen, ob eine Operation erfolgreich war. Geben Sie ein
Array zurück, bei dem der Schlüssel Status den Wert false enthält, erkennt der
Client, dass ein Fehlschlag vorliegt. Clientseitig können Sie mit den Methoden
isSuccess() bzw. isError() abfragen, ob die Operation erfolgreich war.
Das folgende Beispiel zeigt, wie es funktioniert. Hierbei wird der Klasse Rechner
eine neue Methode für die Division hinzugefügt. Da eine Division durch 0 nicht
definiert ist, liefert die Methode eine Fehlermeldung zurück, falls der Divisor 0
ist:
public function dividiere ($dividend, Sdivisor)
{
if (0 == $divisor)
{
return array ('meldung'=> 'Division durch Null ist
nicht definiert',
'Status' => false):
el se
{
return array(’ergebnis'=>
((float) $dividend / (float) $divisor));
}
367
e-bol.net
7 | Protokolle und Co.
Und hier das dazugehörige clientseitige Script:
require_once ’Zend/Rest/Client.php’;
Sclient = new Zend_Rest_Client('http://www.zf-buch.de/rest'):
$client->dividiere(4, 0):
$erg = $client->get();
if ($erg->isSuccess())
{
echo "Ergebnis der Division: ".$erg->dividiere->ergebnis:
}
el se
{
echo "Fehler! Meldung: ".$erg->dividiere->meldung;
}
368
e-bol.net
There is a difference between knowing the path and walking the path.
- Morpheus, Matrix
8 Lokalisierung und Internationalisierung
Webprojekte werden zunehmend international. Viele Webseiten müssen heutzu-
tage mindestens zweisprachig sein. Wenn ich hier nur Sprachen erwähne, dann
ist das natürlich nur die halbe Wahrheit. Dazu kommt, dass Datums- und Zahlen-
Formate angepasst werden müssen, unterschiedliche Feiertage existieren, Länder
in anderen Zeitzonen liegen und vieles Weiteres. Bei diesen Anpassungen spricht
man von Internationalisierung bzw. Lokalisierung. Sollten Sie mal über die
Abkürzungen II8N oder LION stolpern, bedeuten diese nichts anderes als »Inter-
nationalization« bzw. »Localization«. Die Zahlen geben dabei an, wie viele Zei-
chen ausgelassen wurden, und bei den Buchstaben handelt es sich jeweils um
den ersten und den letzten Buchstaben des Wortes.
Dieses Thema kann sehr schnell sehr komplex werden, wie Sie vielleicht schon
vermuten. Aber auch hier liefert das Zend Framework einige Klassen, die Ihnen
viel Arbeit abnehmen.
8.1 Lokalisierung mit Zend_Locale
Basis für die Nutzung vieler Lokalisierungs-Klassen ist die Klasse Zend_Locale.
Mit ihr legen Sie fest, für welchen Sprachraum bzw. für welche Region lokalisiert
werden soll.
Es fragt sich, wie Sie der Klasse mitteilen, welche Lokalisierung genutzt werden
soll. Dafür gibt es sogenannte Locales. Ein Locale definiert, welche Sprache in
welcher Regionalisierung genutzt werden soll. Der Locale für Deutschland wäre
beispielsweise de_DE. Das bedeutet, dass die deutsche Sprache benutzt werden
soll (de) und es sich um die Region Deutschland (DE) handelt. Das hört sich viel-
leicht ein wenig komisch an, ist aber durchaus sinnvoll. Denken Sie zum Beispiel
an Österreich. Auch dort wird Deutsch gesprochen, aber ein paar kleine Details
sind dann doch anders. So heißt der erste Monat des Jahres in Österreich nicht
Januar, sondern Jänner. Daher hat Österreich einen eigenen Locale, der de_AT
369
e-bol.net
8 | Lokalisierung und Internationalisierung
lautet. Ähnliches gilt beispielsweise auch für England (en_UK) und die USA (en_
US). Zwar haben beide Länder grundsätzlich dieselbe Sprache, aber es bestehen
doch Unterschiede.
Die beiden Landeskürzel, aus denen sich ein Locale zusammensetzt, sind von der
ISO definiert. Eine Liste der möglichen und gültigen Kombinationen finden Sie
beim Unicode-Konsortium.1
Diese Locales definieren allerdings nicht nur die Sprache, sondern alles, was für
einen bestimmten Kulturraum üblich ist, wie zum Beispiel Zahlenformate und
Währungen.
Die Landeseinstellungen, die Sie nutzen möchten, können Sie direkt beim Ablei-
ten des Objekts angeben oder später über die Methode setl_ocale() setzen:
requi re_once('Zend/Locale.php');
// Direkt setzen
Slocale = new Zend_Locale(’de_DE'):
// Erst ableiten und dann setzen
Slocale = new Zend_Locale();
$locale->setLocale(’de_DE');
Es stellt sich die Frage, welche Lokalisierung der Benutzer erwartet. Natürlich
können Sie diese vorschreiben, aber Sie können auch den Browser »fragen«. Ist
der Browser korrekt konfiguriert, so teilt er dem Server mit, welche Sprachen
bzw. Lokalisierungen akzeptiert werden. Übergeben Sie dem Konstruktor keinen
Locale, so wird die Default-Einstellung vom Browser übernommen. Da Ihre
Anwendung wahrscheinlich nicht alle Sprachen der Welt unterstützt, sollten Sie
überprüfen, ob die automatisch eingestellte Lokalisierung genutzt werden kann.
Slocale = new Zend_Locale();
$lang = strtolower($1ocale->getLanguage()):
if ($lang != 'de' )
{
$locale->setLocale(’en_US');
1
Hier wird mithilfe von getLanguage() die aktuelle Einstellung ausgelesen. Ist die
automatisch ermittelte Sprache nicht Deutsch, wird amerikanisches Englisch
selektiert. Sollte der Browser noch andere Sprachen akzeptieren, so können Sie
diese mit der Methode getBrowserf) auslesen, welche die erlaubten Locales als
Array zurückliefert. In diesem Zusammenhang kann es auch hilfreich sein zu prü-
1 http://unicode.org/cldr/data/diff/supplemental/languages_and_territories.html
370
e-bol.net
Lokalisierung mit Zend_Locale | 8.1
fen, welche Zeichensätze der Browser unterstützt. Diese Information erhalten Sie
von der Methode getHttpCharsett) in Form eines Arrays.
8.1.1 Standardtexte und Standardformate lokalisieren
Zend_Local e ist aber nicht nur dafür da, eine Lokalisierungsinformation für eine
andere Klasse bereitzustellen. Die Klasse kann selbst auch schon einiges leisten.
Standardtexte
Neben vielen landesspezifischen Informationen zur Formatierung von Datums-
und Währungsangaben stellt die Klasse auch gleich Übersetzungen von häufig
gebrauchten Wörtern zur Verfügung. Diese Informationen können Sie mit der
Methode getTrans l at1onL1st() auslesen. Sie bekommt einen String übergeben,
der ihr mitteilt, welche Information Sie benötigen. Als zweiten Parameter kön-
nen Sie noch einen weiteren Locale angeben, wenn Sie eine andere Sprache
benötigen als die momentan gesetzte. Als Rückgabewert erhalten Sie ein Array
mit der gewünschten Information. Wollten Sie beispielsweise den russischen
Namen für Deutsch und Englisch haben, könnten Sie das so machen:
header(’Content-Type: text/html; charset=utf-8’);
require_once('Zend/Locale.php');
$locale = new Zend_Locale('ru_RU');
{deutsch = $locale->getTranslationList(’Language' , ’de_DE');
{sprachen = array_flip($deutsch);
{russisch = {1ocale->getTranslationList('Language');
{schluessel = {sprachen['Deutsch*];
echo "Deutsch heißt auf Russisch 'Srussisch[{schluessel]'<br>";
{schluessel = {sprachen[' Engiisch'];
echo "Englisch heißt auf Russisch ’{russisch[{schluessel;
Listing 8.1 Ausgabe von lokalisierten Sprachinformationen
O http;//127.0.0.1...alisierung/2.php GD
Deutsch heißt auf Russisch ’hcmcukhä'
Englisch heißt auf Russisch 'ainvnröcKJuY
Abbildung 8.1 Ausgabe übersetzter Texte
371
e-bol.net
8 | Lokalisierung und Internationalisierung
Der Code sieht auf den ersten Blick vielleicht etwas ungewöhnlich aus. Ich habe
mithilfe von getTranslationList(' Language', ’de_DE’) zuerst die deutschen
Übersetzungen ausgelesen. Dabei wird ein Array zurückgeliefert, das wie folgt
aufgebaut ist:
array(477) {
["aa"]=>
string(4) "Afar"
["ab"]=>
string(lO) "Abchasisch"
// 475 weitere Einträge
Die Funktion array_f 1 ip() vertauscht die Schlüssel und die Werte, sodass
$sprachen[' Deutsch' ]; den ursprünglichen Schlüssel für »Deutsch« zurückgibt.
Da diese Schlüssel konsistent für alle Sprachen genutzt werden, kann damit jetzt
auch die Übersetzung aus dem »russischen Array« ausgelesen werden.
Neben dem Parameter 'Language' unterstützt getTranslatonList() noch wei-
tere Parameter. Sie finden die wichtigsten in Tabelle 8.1.
Parameter Bedeutung
Language Liste der Sprachen
Country Liste der Ländernamen
Terri tory Liste mit Gebietsnamen (Südasien, Westeuropa, Osteuropa usw.)
Month lokalisierte Monatsnamen
Month_short abgekürzte Monatsnamen (zwei bis vier Buchstaben)
Month_narrow abgekürzte Monatsnamen (meist ein Buchstabe)
Day übersetzte Namen der Tage
Day_short abgekürzte Tagesnamen (zwei bis vier Zeichen)
Day_narrow gekürzter Name des Tages (meist ein Zeichen)
Dateformat Datums-Format-Strings für die Nutzung mit date()
Timeformat Zeit-Format-Strings für die Nutzung mit date()
Timezone übersetzte Liste der Zeitzone(n) des Landes mit Ortsangabe
Currency lokalisierte Liste mit Währungen
Characters Liste mit denjenigen Buchstaben, die in der Region genutzt werden
Tabelle 8.1 Parameter für getTranslationListO
Die Liste aus Tabelle 8.1 ist nicht ganz vollständig. Eine komplette Liste finden
Sie in der Dokumentation des Zend Frameworks.2
2 http://framework.zend.com/manual/de/zend.locale.functions.html
372
e-bol.net
Lokalisierung mit Zend_Locale | 8.1
Bei den Parametern Dateformat, Timeformat und Characters ist es natürlich
nicht sinnvoll, mit dem oben vorgestellten array_flip() zu arbeiten. Schauen
Sie sich das Array am besten kurz mit var_dump() oder print_r() an, um zu
erkennen, welche Schlüssel dort zurückgegeben werden.
Ich möchte Ihnen nicht verschweigen, dass auch einige »Convenience-Funktio-
nen« definiert sind, wie getLanguageTranslation(). Diese leisten dasselbe wie
getTranslationl_ist(), benötigen aber keinen Parameter. Auch diese Methoden
werden in der Dokumentation erläutert.
Ähnlich wie die bereits besprochenen Methoden verhält sich auch getQues-
tionO. Sie liefert die Übersetzungen für »Ja« und »Nein« zurück, wobei die
Array-Schlüssel 'yes' und ’no’ sind. Für die deutsche Übersetzung ist also 'yes'
=> 'ja' und ' no' => ’nein’ enthalten. Darüber hinaus finden Sie auch noch die
jeweiligen Abkürzungen unter den Schlüsseln ’yesabbr’ und ’noabbr’. Für
Deutschland würde es sich dabei um j und n handeln.3
Zahlen
Die Arbeit mit Zahlen kann bei der Lokalisierung von Applikationen recht auf-
wändig werden. Wollten Sie die Zahl 1234,10 korrekt formatieren, so wäre
1.234,12 die korrekte deutsche Schreibweise. In einem englischsprachigen Land
würde man allerdings die Formatierung 1,234.10 erwarten. Und wenn Sie die
Zahl für einen Liechtensteiner korrekt darstellen wollen, müssten Sie mit Hoch-
kommas und Dezimalpunkten arbeiten: 1'234.10.
Um diese verschiedenen Formate nicht zum Problem werden zu lassen, sind in
der Klasse Zend_Local e_Format verschiedene statische Methoden definiert, die
Sie unterstützen. Die Klasse wird automatisch mit eingebunden, wenn Sie Zend_
Local e inkludieren.
Die Zahlen können grundsätzlich in beide »Richtungen« konvertiert werden. Um
zum Beispiel eine Fließkommazahl in eine lokalisierte Darstellung zu bringen,
könnten Sie die folgenden Zeilen nutzen:
header('Content-Type: text/html; charset=utf-8'):
requi re_once('Zend/Locale.php');
requi re_once('Zend/Locale/Format.php');
// Locales ableiten
$locale_de = new Zend_Locale(’de_DE'):
$locale_us = new Zend_Locale(’en_US'):
3 Die Dokumentation erwähnt noch zwei weitere Felder mit regulären Ausdrücken zum Prüfen
von Eingaben. Momentan (Version 1.0.3) sind diese allerdings nicht enthalten.
373
e-bol.net
8 | Lokalisierung und Internationalisierung
$zahl = 12345.1;
//Optionen zusammenstel1 en
Soptionen = array('1ocale'=>$1ocale_de, 'precision'=>2);
echo "Deutsche Formatierung:
echo Zend_Locale_Format::toFloat($zahl, $optionen);
// Ausgabe: Deutsche Formatierung: 12.345.10
//Locale neu setzen
$optionen[11ocale' ] = $locale_us;
$zahl = *12345.678' ;
echo "CbOAmerikanisehe Formatierung:
echo Zend_Locale_Format::toFloat($zahl, $optionen);
//Ausgabe: Amerikanische Formatierung: 12.345.68
Die Methode toFl oat() bekommt als ersten Parameter die Zahl übergeben, die
formatiert werden soll. Diese kann als Zahl (erstes Beispiel) oder als String (wie
im zweiten Beispiel) übergeben werden. Als zweiten Parameter erwartet die
Methode ein Array mit Optionen. Die Lokalisierung wird mithilfe eines Zend_
Locale-Objekts angegeben, das als Wert des Schlüssels locale übergeben wird.
Alternativ können Sie an dieser Stelle auch einen String wie ’de’ übergeben.
Optional können Sie mithilfe des Schlüssels precision noch angeben, wie viele
Nachkommastellen genutzt werden sollen. Geben Sie diese nicht an, dann gibt
die Methode genau so viele Nachkommastellen aus, wie die übergebene Zahl hat.
Hat die Zahl mehr Nachkommastellen als der Wert, der in preci si on angegeben
ist, wird die Zahl auf die angegebene Anzahl der Stellen gerundet. Ist preci si on
größer als die Anzahl der vorhandenen Stellen, so werden Nullen ergänzt.
Für die Konvertierung von Fließkommazahlen ist darüber hinaus noch eine wei-
tere Methode vorgesehen: toNumberO. Sie wird im Hintergrund auch von
toF1 oat() genutzt, kann aber noch mehr. Und zwar können Sie toNumber() auch
eine Formatierungsanweisung mitgeben, wohingegen toFloatO immer die
Standardformatierung nutzt, die zu dem Locale gehört. Eine solche Formatanwei-
sung können Sie mithilfe eines Strings definieren, der im Optionen-Array als
Wert des Schlüssels number_format übergeben wird.
Bei einer solchen Formatangabe steht die Zahl Null für den Vorkomma-Anteil
bzw. die Einer-Stelle des Vorkommaanteils. Möchten Sie beim Vorkomma-Anteil
Trennzeichen verwenden, können Sie diese mit einem Komma symbolisieren.
Dabei stellt sich die Frage, wo dieses Trennzeichen erscheinen soll, ob nach der
Einer-, der Zehner- oder der Hunderter-Stelle. Um dieses zu definieren, nutzen
Sie das Doppelkreuz, das für eine weitere Stelle steht. Das Trennzeichen selbst
muss durch ein Komma symbolisiert werden und wird dann später durch das
374
e-bol.net
Lokalisierung mit Zend_Locale | 8.1
Zeichen ersetzt, welches in der Lokalisierung korrekt ist. Um die Anzahl der Stel-
len für den Nachkommanteil anzugeben, geben Sie pro Stelle einfach eine Null
an, die mit einem Punkt von der »Vorkomma-Null« getrennt wird.
requi re_once('Zend/Locale/Format.php');
$locale_de = new Zend_Locale('de_DE');
$optionen=array('locale'=>$locale_de);
$zahl = 12345678.5555;
// Ausgabe ohne Nachkommastellen
$optionenE'number_format'] = '0';
echo Zend_Locale_Format::toNumber($zahl, $optionen);
//Ausgabe: 12345679
echo "<br>";
// Ausgabe mit 2 Nachkommastellen
$optionenE'number_format'] = '0.00';
echo Zend_Locale_Format::toNumber($zahl, $optionen);
// Ausgabe: 123456789,56
echo "<br>";
// 2 Nachkommastellen und "Tausender-Trennzeichen"
// nach jeder Vorkommastelle
$optionenE'number_format'] = '#,0.00';
echo Zend_Locale_Format::toNumber($zahl, $optionen);
// Ausgabe: 1.2.3.4.5.6.7.8,56
echo "<br>";
// 2 Nachkommastellen und "Tausender-Trennzeichen"
// nach jeder zweiten Vorkommastelle
$optionenE'number_format'] = '#,#0.00';
echo Zend_Locale_Format::toNumber($zahl, $optionen);
// Ausgabe: 12.34.56.78,56
echo "<br>";
// 2 Nachkommastellen und "Tausender-Trennzeichen"
// nach jeder dritten Vorkommastelle
$optionenE'number_format'] = '#,##0.00';
echo Zend_Locale_Format::toNumber($zahl, $optionen);
// Ausgabe: 12.345.678,56
echo "<br>";
Listing 8.2 Formatieren von Zahlen
375
e-bol.net
8 | Lokalisierung und Internationalisierung
Die Kombination von number_format und preci sion sollten Sie momentan noch
vermeiden, da sie unter Umständen zu einer fehlerhaften Darstellung führt.
n http:/., g/4.php CD
m u © »
12345679
12345678,56
12.3.4.5.6.7.8,56
12.34.56.78,56
12.345.678,56
Abbildung 8. 2 Ausgabe formatierter Zahlen
Neben toNumberO und toFloatO gibt es übrigens auch noch die Methode
tolnteger(). Diese verhält sich wie die beiden erläuterten Funktionen, entfernt
den Nachkommateil, rundet dabei aber. Außerdem werden die Tausender-Trenn-
zeichen des gewählten Locales ergänzt. Eine manuelle Formatierung wird nicht
unterstützt.
$locale_de = new Zend_Locale('de_DE’):
$optionen=array('1ocale’=>$1ocale_de):
$zahl = 12345678.5555:
echo Zend_Locale_Format::tolnteger!$zahl, Soptionen);
//Ausgabe: 12.345.679
Wie schon erwähnt, können Sie aber auch Zahlen einlesen, die in einer lokalisier-
ten Schreibweise eingegeben wurden. Hierzu sind getNumber(), getFloat!) und
getInteger!) definiert. Diese Methoden bekommen an ersten Stelle denjenigen
String übergeben, der in eine Zahl konvertiert werden soll. Als zweiter Parameter
folgt auch hier wieder ein Array mit Parametern, wobei auch ein Zend_Local e-
Objekt übergeben werden sollte. Die beiden ersten Methoden unterstützen dar-
über hinaus auch den Schlüssel precision. Sollten Sie Werte verarbeiten, die
über ein Formular eingegeben wurden, ist zu ermitteln, ob der Benutzer die Zahl
korrekt formatiert eingegeben hat. Das können Sie mit den Methoden isInte-
ger!), i sNumber!) und i sFl oat!) prüfen. Diese Methoden bekommen an erster
Stelle den String übergeben, der geprüft werden soll, und an zweiter Stelle ein
Array, welches als Wert des Schlüssels locale ein Zend_Locale-Objekt enthält.
Ein wenig kurios mag an dieser Stelle anmuten, dass ein String wie ' 13,445.1 ’
oder '13.445,1' von der Methode islnteger!) als Integer-Wert erkannt wird.
Der Hintergrund ist, dass hier ein normalisierter Integer-Wert (13445) enthalten
ist. Da ein Integerwert wie 12345 auch immer als Fließkommawert interpretiert
werden kann, liefern die drei Methoden zurzeit das gleiche Ergebnis.
376
e-bol.net
Lokalisierung mit Zend_Locale | 8.1
Interessanter sind da schon die Methoden getNumbert), getFloat() und getln-
teger(), welche eine lokalisierte Zahl übergeben bekommen und diese in eine
normalisierte PHP-Darstellung bringen. Die beiden Methoden getNumber() und
getFl oat() unterscheiden sich dabei durch ihren Datentyp. Die erste gibt einen
String zurück, wohingegen die zweite einen Float-Wert liefert:
require_once ' Zend/Locale/Format.php':
$locale_de = new Zend_Locale(’de_DE');
$opti onen=array(' l ocale’=>$locale_de.
'preci si on'=>2);
$zahl = "123.456.555":
// Ausgabe mit zwei
$erg = Zend_Locale_Format::getNumber($zahl, $optionen):
echo $erg." ist vom Typ ";
echo gettype($erg);
echo "<br>";
// Ausgabe mit zwei
$erg = Zend_Locale_Format::getFloat($zahl. Soptionen);
echo $erg.” ist vom Typ
echo gettypet $erg);
echo "<br>";
// Ausgabe ohne Nachkommastallen
$erg = Zend_Locale_Format::getlnteger($zahl, $optionen);
echo $erg.” ist vom Typ
echo gettypet $erg);
echo "<br>";
Wie Sie in Abbildung 8.3 sehen können, beachten die Methoden auch die Option
precision.
O http://l...ung/S.php GD
w . u u
12345655 ist vom Typ string
123456.55 ist vom Typ double
123456 ist vom Typ integer
Abbildung 8. 3 Eingelesene und konvertierte Werte
377
e-bol.net
8 | Lokalisierung und Internationalisierung
Zudem ist es auch noch möglich, die Zahlen in verschiedenen Zahlensystemen
wie beispielsweise dem ostarabischen darzustellen. Informationen dazu finden
Sie in der Dokumentation.
Datums- und Zeitangaben
Da die Angabe eines Datums oder einer Uhrzeit in verschiedenen Ländern unter-
schiedlich gehandhabt wird, kann es recht umständlich sein, Datumsangaben
oder Uhrzeiten einzulesen, um damit zu rechnen.
Möchten Sie mit einem lokalisierten Datum arbeiten und dieses in seine Bestand-
teile zerlegen, ist die statische Methode getDate() sehr hilfreich. Die Methode
bekommt als ersten Parameter einen String übergeben, der das zu analysierende
Datum enthält. Der zweite Parameter ist ein Array, mit dem Sie diverse Informa-
tionen übergeben können. Im einfachsten Fall übergeben Sie mit dem Schlüssel
locale ein Locale-Objekt. Aus diesem liest die Methode den »üblichen« Aufbau
des Datums für die Region aus und liefert die einzelnen Datumsbestandteile als
Array zurück:
requi re_once ’Zend/Locale/Format.php';
Slocale = new Zend_Locale('de_DE'):
Sdatum = '10.03.1970’ :
Steile = Zend_Locale_Format::getDate(Sdatum,
array('locale'=>Slocale));
echo "Datumsformat: ".Zend_Locale_Format::getDateFormat($1ocale);
echo "CbODatum: Sdatum<br>":
echo "Tag: Stei1e[day]<br>";
echo "Monat: Stei1e[month]<br>";
echo "Jahr: Stei1e[year]<br>";
Listing 8.3 Einlesen eines Datums
Die einzelnen Bestandteile des Datums werden nebst einiger anderer Informati-
onen im Array Steile zurückgegeben. Die Methode getDateFormat() liest die
Formatierung aus, die im Locale-Objekt enthalten ist.
Wie bereits gesagt, sind in den Locale-Objekten nur die »üblichen« Datumsfor-
mate enthalten. Möchten Sie beispielsweise ein Datum verarbeiten, das nach
DIN aufgebaut ist, so wäre das nicht ganz so einfach. Ein Datum nach DIN enthält
Jahr, Monat und Tag. In dem Fall könnten Sie dem Array direkt die Formatinfor-
mation übergeben.
378
e-bol.net
Lokalisierung mit Zend_Locale | 8.1
http://...ng/6.php CD
Datumsformat: dd.MM.yyyy
Datum: 10.03.1970
Tag: 10
Monat: 03
Jahn 1970
Abbildung 8. 4 Ausgabe des analysierten Datums
Dazu können Sie dem Schlüssel date_format den String ' yyyy-MM-dd' mitgeben:
Sdatum = '1970-10-03’;
$format - ’yyyy-MM-dd':
Steile = Zend_Locale_Format::getDate($datum,
array(’date_format’=>$format)):
Sie benötigen in diesem Zusammenhang auch kein Locale-Objekt mehr, da Sie
die Formatinformationen ja auf anderem Weg bereitgestellt haben. Innerhalb des
Format-Strings können Sie das y als Platzhalter für das Jahr nutzen, das M steht für
den Monat und das d für den Tag. Erwarten Sie ein vierstelliges Jahr, sollten sich
auch vier y in dem String finden, was dementsprechend auch für den Monat und
den Tag gilt.
Sollte die Methode darüber stolpern, dass ein Datum beispielsweise die 13 als
Monat enthält, wirft sie eine Exception. Hier gibt es allerdings ein interessantes
»Hintertürchen«. Und zwar können Sie im Array mit den Optionen den Schlüssel
’ f 1 x_date' mit dem Wert true belegen. In dem Fall versucht die Methode, den
übergeben Wert zu korrigieren und tauscht den Tag mit dem Monat, falls der
angegebene Tag kleiner oder gleich 12 ist. Um Ihnen mitzuteilen, was die
Methode verändert hat, liefert Sie im Ergebnis-Array einen weiteren Wert im
Feld ’ f i xed ' zurück. Die Bedeutung der Werte finden Sie in Tabelle 8.3.
Wert Bedeutung
0 keine Änderung
1 Monat korrigiert
2 Tag und Jahr getauscht
3 Monat und Jahr getauscht
4 Monat und Tag getauscht
Tabelle 8.2 Rückgabewerte bei einem korrigierten Datum
379
e-bol.net
8 | Lokalisierung und Internationalisierung
Alternativ können Sie das Datum vorher auch an checkDate() übergeben. Diese
Methode akzeptiert die gleichen Parameter und liefert true oder fal se zurück,
um Ihnen mitzuteilen, ob ein Datum verarbeitet werden kann.
Um eine Uhrzeit zu verarbeiten, stehen ähnliche Methoden zur Verfügung. Mit
getTimel) zerlegen Sie eine Uhrzeit in ihre Bestandteile. Die Methode bekommt
an erster Stelle die Uhrzeit als String übergeben und akzeptiert als zweiten Para-
meter wieder ein Array. Mit Ausnahme des Schlüssels ’fix_date' können Sie
hier dieselben Bestandteile nutzen. Um ein Format zu beschreiben, stehen hier
andere Platzhalter zur Verfügung. Das h wird als Platzhalter für die Stunden
genutzt, m steht für die Minuten und mit s werden die Sekunden symbolisiert:
requi re_once ’Zend/Locale/Format.php';
$zeit = '23 11 523* ;
Sformat = 'hh mm ss';
Steile = Zend_Locale_Format::getTime($zeit,
array(’date_format'=>$format));
echo "<br>Zeit: $zeit<br>”;
echo "Stunden: $tei1e[hour]<br>";
echo "Minuten: $tei1e[minute]<br>";
echo "Sekunden: $tei1e[second]<br>";
Listing 8.4 Einlesen einer Uhrzeit
Die einzelnen Bestandteile der Uhrzeit können Sie einem Array entnehmen, das
die Schlüssel hour, minute und second hat. Die hohe Anzahl der Sekunden ist
übrigens kein Tippfehler, sondern sollte nur zeigen, dass die Methode auch mit
Zahlen umgehen kann, die größer sind als das, was in einer gewöhnlichen Uhr-
zeitangabe zulässig ist. Die Obergrenze ist hier nur durch PHP vorgegeben.
Noch nicht erwähnt hatte ich, dass die Methode getDatei) auch in der Lage ist,
mit Uhrzeiten umzugehen. Sollten Sie also Daten haben, in denen Datum und
Uhrzeit enthalten sind, dann kann getDatei) diese Informationen auch verarbei-
ten, sofern Sie der Methode einen korrekten Format-String übergeben, wobei
dieser dann auch h, m und s enthalten darf.
Um zu prüfen, ob eine Uhrzeit das korrekte Format hat, können Sie auch die
Methode checkDatel) verwenden.
380
e-bol.net
Mehrsprachige Oberflächen mit Zend_Translate | 8.2
8.2 Mehrsprachige Oberflächen mit Zend_Translate
Um eine Applikation auf internationaler Ebene zu nutzen, sollte sie natürlich
mehrsprachig sein. Dazu bietet sich die Nutzung von Zend_Transl ate an. Die
Idee dahinter ist einfach. Sie lassen mithilfe einer Methode einen Text in einer
»Standardsprache« ausgeben. Die Methode prüft, in welcher Sprache die Appli-
kation ausgeführt wird, und gibt den Text in der entsprechenden Sprache aus,
sofern eine Übersetzung vorliegt. Der Text in der Standardsprache dient dabei als
eine Art Schlüssel in einem Array. Für jede Sprache, in der die Applikation
genutzt werden soll, muss eine solche Übersetzungstabelle vorliegen. Das heißt,
wenn Sie das Wort »Auto« auf Englisch ausgeben wollten, würde Zend_Trans-
1 ate in der Tabelle prüfen, wo der Text »Auto« zu finden ist, und dann die dazu-
gehörige Übersetzung ausgegeben.
Diese Idee einer »Übersetzungstabelle« ist in verschiedenen Formen implemen-
tiert. Zwar werden auch die oben erwähnten Arrays unterstützt, aber Sie können
auch auf andere Datenquellen zurückgreifen. In Tabelle 8.4 finden Sie eine Liste
der momentan unterstützten Datenquellen.
Adapter Konstante Beschreibung
Array AN-ARRAY Ein normales PHP-Array, das in den Quelltext eingebettet ist. Es kann sich dabei auch um eine inkludierte Datei handeln.
Csv AN-CSV Übersetzung auf Basis einer Textdatei, bei der die einzelnen Werte durch ein Trennzeichen (Komma, Semikolon o.Ä.) getrennt sind.
Gettext AN.GETTEXT Basiert auf einer .mo-Datei. Diese muss kompiliert werden. Aufgrund der Performance ist dies ideal für große Anwendungen.
TMX AN.TMX auf XAAL basierendes Dateiformat
QT AN_QT auf XAAL basierendes Dateiformat
XLIFF AN-XLIFF auf XAAL basierendes Dateiformat
TBX AN.TBX auf XAAL basierendes Dateiformat
XMLTM AN.XMLTM auf XAAL basierendes Dateiformat
Tabelle 8.3 Adapter für Zend_Translate
Im Folgenden werde ich auf die Arbeit mit Arrays und CSV-Dateien eingehen, da
diese in der Lage sind, »Übersetzungstabellen« mit einem recht geringen Auf-
wand zu erstellen. Die Nutzung von CSV können Sie aber unproblematisch auf
alle anderen Dateiformate übertragen.
Sollten Sie eine größere Website lokalisieren wollen, so empfiehlt es sich aller-
dings, mit der Gettext-Variante zu arbeiten, weil diese am performantesten ist.
Da die .mo-Dateien allerdings plattformabhängig erstellt werden müssen, ist es hier
381
e-bol.net
8 | Lokalisierung und Internationalisierung
schwierig, dies für jedes Betriebssystem zu erläutern. Eine Einführung in die
Erstellung von .mo-Dateien finden Sie unter http://www.gnu.org/software/gettext
oder in der Dokumentation des Zend Frameworks. Dort sind auch noch weiter-
gehende Informationen zu den verschiedenen Datenquellen vorhanden. Ebenso
finden Sie dort auch Informationen, wie Sie selbst Adapter erstellen und nutzen.
Um mit Zend_Translate arbeiten zu können, benötigen Sie ein Objekt der
Klasse. Wenn Sie das Objekt ableiten, müssen Sie dem Konstruktor mitteilen,
welche Art von Übersetzungsinformationen vorliegt. Das geschieht dadurch, dass
Sie die Konstante, die Sie neben dem Namen des Adapters (Tabelle 8.3) finden,
als ersten Parameter angeben. Die Konstanten sind alle in der Klasse Zend_Trans-
late deklariert. Der zweite Parameter ist dann die Information, welche Datei
oder welches Array zu nutzen ist. An dritter Stelle können Sie optional die Infor-
mation übergeben, welche Sprache in der Datei bzw. in dem Array enthalten ist.
So lange nur eine Sprache genutzt wird, ist diese Information überflüssig. Möch-
ten Sie diesen Parameter nutzen, können Sie ihn in Form eines Strings oder eines
Zend_Locale-Objekts übergeben.
Möchten Sie die Lokalisierung auf Basis von Arrays vornehmen, könnte das so
aussehen:
requi re_once ’Zend/Translate.php';
Sdeutsch = array ('next' => 'vor',
'back' => ’zurück',
'finish* => 'beenden'
):
Stranslate = new Zend_Translate (Zend_Locale::AN_ARRAY,
tdeutsch);
// Gibt 'zurück' aus
echo $translate->_('back’);
// Gibt 'vor' aus
echo $translate->_('next'):
// Gibt ’commit' aus
echo $translate->_('commit');
Listing 8.5 Nutzung von Zend_Translate
In dem Array finden sich die englischen Wörter als Schlüssel und die deutsch-
sprachigen Begriffe als dazugehörige Übersetzung. Nachdem ein entsprechendes
Objekt abgleitet wurde, können die jeweiligen Übersetzungen mithilfe der
Methode _() ausgelesen werden. Diese bekommt das gewünschte Wort als Para-
382
e-bol.net
Mehrsprachige Oberflächen mit Zend_Translate | 8.2
meter übergeben und gibt die Übersetzung zurück. In der letzten Zeile soll das
Wort commi t in der lokalisierten Variante ausgegeben werden. Wie Sie sehen, ist
das allerdings nicht in dem Array vorhanden. In so einem Fall wird das ursprüng-
liche Wort, also commi t, ausgegeben. Daher empfiehlt es sich, Englisch als »Mas-
tersprache« zu nutzen. Sollte ein Wort mal nicht übersetzt sein, so kommen die
meisten Menschen sicher auch mit der englischen Variante zurecht.
Diese Vorgehensweise bietet sich an, wenn der Benutzer die Sprache manuell
einstellen kann. Allerdings teilt der Browser dem Server ja schon mit, welche
Sprachen bevorzugt werden. Ist der Browser korrekt konfiguriert, so können Sie
diese Information direkt übernehmen. Das setzt allerdings voraus, dass Sie meh-
rere Sprachen beim System anmelden, aus denen dann selektiert werden kann.
Um mehrere Sprachen zu registrieren, steht die Methode addTranslationf) zur
Verfügung. Mit ihr können Sie eine zusätzliche Sprache beim System anmelden:
requi re_once ’Zend/Translate.php';
require_once 'Zend/Locale.php' ;
Sdeutsch = array ('next' => 'vor',
'back' => ’zurück',
’finish’ => 'beenden'
$franzoesisch = array ('next' => 'avant',
'back’ => 'retour’,
’finish' => ’finir'
$locale_de = new Zend_Locale(’de_DE');
$locale_fr = new Zend_Locale('fr_FR');
Stranslate = new Zend_Translate (Zend_Translate::AN_ARRAY,
Sdeutsch, $locale_de):
Stranslate->addTranslation($franzoesisch, $1ocale_fr);
// Gibt 'retour' aus
echo $translate->_('back’, $locale_fr);
// Gibt 'vor' aus
echo $translate->_('next', ’de_DE’);
Listing 8.6 Lokalisierung mit Zend_Translate
In diesem Beispiel wurden die beiden Sprachen mithilfe von Locale-Objekten
angemeldet. Genau so hätte allerdings auch ein String wie 'de' oder 'de_DE'
genutzt werden können. Das Gleiche gilt für den Zugriff auf die Übersetzungen.
383
e-bol.net
8 | Lokalisierung und Internationalisierung
Da zwei Sprachen bekannt sind, sollten Sie der Methode _() mitteilen, welche
der Sprachen genutzt werden soll. Dazu können Sie als zweiten Parameter entwe-
der einen String oder ein Locale-Objekt übergeben. Um das nicht bei jedem Auf-
ruf machen zu müssen, können Sie die Lokalisierungsinformation auch einmalig
mit der Methode setLocale() setzen. Die Methoden lesen diese Daten dann
jeweils aus. Sie sehen, die Nutzung dieser Klasse ist erfreulich einfach und unpro-
blematisch.
Die Klasse stellt auch noch einige recht hilfreiche Methoden zur Verfügung, um
die Übersetzungen zu verwalten. Mit getMessageldsl) können Sie alle Wörter
in der Mastersprache auslesen, die dem Adapter bekannt sind. Bezogen auf das
letzte Beispiel würden Sie hier ein Array erhalten, in dem die Strings next, back
und finish enthalten sind. Optional können Sie der Methode noch eine Lokali-
sierungsinformation in Form eines Strings oder eines Locale-Objekts übergeben,
um herauszufinden, welche Schlüssel in welchen Sprachen vorhanden sind.
Wollen Sie testen, ob eine bestimmte Sprache beim aktuellen Adapter angemel-
det ist, so übergeben Sie die fragliche Sprachinformation an die Methode i s -
Avai 1 abl e(), die Ihnen einen booleschen Wert zurückgibt.
Ob eine einzelne Übersetzung vorhanden ist, können Sie mit der Methode
i sTransl ated() überprüfen, welche den String übergeben bekommt, der über-
setzt werden soll.
8.2.1 Nutzung von CSV-Dateien
CSV-Dateien sind für die Erstellung mehrsprachiger Anwendungen oft sehr prak-
tisch. Zwar ist ihre Nutzung nicht sehr performant, aber sie haben den großen
Vorteil, dass sie mit Excel erstellt werden können. Sie können den Übersetzern
einfach eine Excel-Tabelle schicken, in der eine Spalte mit den Begriffen in der
Mastersprache enthalten ist. Die Übersetzer notieren dann einfach den übersetz-
ten Begriff daneben und exportieren die Datei im CSV-Format, wobei sie idealer-
weise den UTF-8-Zeichensatz nutzen sollten. Eine solche Datei kann dann bei-
spielsweise so aussehen:
next:vor
back;zurück
finish;beenden
Jede Übersetzung ist in einer eigenen Zeile zu finden. Werden diese Daten in
einer Datei namens de.csv gespeichert, kann die Nutzung so aussehen:
384
e-bol.net
Konvertieren von und Rechnen mit Maßeinheiten mittels Zend_Measure | 8.3
requi re_once ’Zend/Translate.php';
require_once 'Zend/Locale.php';
$locale_de = new Zend_Locale(’de_DE'):
Stranslate = new Zend_Translate (Zend_Translate::AN_CSV,
'de.csv'. $locale_de);
Stranslate->setLocale($1ocale_de);
// Gibt 'vor' aus
echo $translate->_('next');
Listing 8.7 Nutzung einer CSV-Datei
Anstelle des Arrays bekommt der Konstruktor einfach nur den Dateinamen
inklusive des Pfads übergeben. In den meisten CSV-Dateien wird das Semikolon
als Trennzeichen zwischen den Spalten genutzt. Sollten Sie allerdings ein anderes
Zeichen nutzen, ist das kein großes Problem. In dem Fall übergeben Sie dem
Konstruktor nach der Lokalisierungsinformation noch ein Array, das den Schlüs-
sel Separator enthält. Der Wert dieses Schlüssels ist dann das genutzte Trennzei-
chen. Mit dieser Instantiierung
Stranslate = new Zend_Translate (Zend_Translate::AN_CSV,
'de.csv*, $locale_de,
array(’Separator' =>
wird die Datei de.csv eingebunden, wobei das Komma als Trennzeichen erwartet
wird. Wie gesagt, können Sie die anderen Dateiformate auf die gleiche Art ein-
binden. Bei den anderen Formaten steht der Schlüssel separater allerdings nicht
zur Verfügung, da er nicht benötigt wird.
Ab der Version 1.1 des Zend Frameworks wird die Möglichkeit zur Verfügung
stehen, dass Sie nur ein Unterverzeichnis angeben, und Zend_Translate dann
selbstständig nach dort enthaltenen Dateien sucht. Da dieses Feature momentan
noch nicht implementiert ist, kann ich Sie an dieser Stelle nur auf das Manual
verweisen. Bitte beachten Sie, dass das Scannen der Unterverzeichnisse unter
Umständen recht zeitintensiv sein kann. Daher sollten Sie darüber nachdenken,
die Dateinamen dynamisch zu konstruieren und auf die oben beschriebene
Weise zu inkludieren.
8.3 Konvertieren von und Rechnen mit Maßeinheiten
mittels Zend_Measure
In verschiedenen Kulturkreisen sind nicht nur unterschiedliche Sprachen oder
Kalendersysteme, sondern auch unterschiedliche Maßsysteme anzutreffen. Am
bekanntesten hierbei sind sicher das metrische System (auch Sl-System genannt),
385
e-bol.net
8 | Lokalisierung und Internationalisierung
das im kontinentalen Europa verwendet wird, und das Zoll-System, welches
noch im Vereinigten Königreich und in den USA Verwendung findet.
Die Konvertierung zwischen den einzelnen Maßeinheiten ist nicht immer ganz
einfach. Zum Ersten müssen Sie natürlich wissen, welche Maßeinheiten in der
jeweiligen Region genutzt werden. Der zweite Punkt ist, dass Sie die Umrech-
nungsfaktoren kennen müssen. So ist beispielsweise ein deutscher Zoll 26,1 mm
lang, wohingegen ein Zoll in England 25,4 mm misst.
Im Gegensatz zu anderen Klassen gibt es in diesem Fall keine Datei namens
Measure.php, die Sie inkludieren könnten. Hier müssen Sie die benötigten
Dateien jeweils für eine »Gattung« von Maßeinheiten inkludieren. Das heißt, es
gibt eine Datei, die für Längeneinheiten zuständig ist, eine für Gewichte, eine für
Flächen usw. Das ist auch sinnvoll, da Sie die Werte natürlich nicht von Meter
nach Grad Celsius oder von Hektar nach Inch umrechnen können. Eine Längen-
einheit kann also nur in eine andere Längeneinheit konvertiert werden etc.
Zurzeit sind 28 Klassen für die verschiedenen Maßeinheiten definiert. Jede
Klasse verfügt über eine eigene Klassen-Datei im Ordner Zend/Measure. Für die
Arbeit mit Frequenzen findet sich hier beispielsweise die Datei Frequency.php, in
der die Klasse Zend_Measure_Frequency deklariert ist. In drei Fällen (Kochen,
Fließen und Viskosität) sind die Klassen noch einmal in eigenen Unterverzeich-
nissen abgelegt. Um die Mengenangabe »ein Teelöffel« in Gramm umzurechnen,
könnten Sie also Zend_Measure_Cooking_Weight nutzen. Welche Klassen insge-
samt vorhanden sind, entnehmen Sie bitte dem Manual unter http://framework.
zend.com/manual/de/zend.measure.types.html.
Sämtliche Klassen erweitern eine abstrakte Klasse, sodass die Nutzung in allen
Fällen identisch ist. In den Klassen sind jeweils Konstanten für die verschiedenen
Maßeinheiten deklariert. Bei der Nutzung ist es natürlich hilfreich, die Maßein-
heiten zu kennen. Dazu können Sie in den Quelltext der Klasse schauen, oder Sie
nutzen diese Zeilen, um sich die deklarierten Konstanten ausgeben zu lassen:
require_once 'Zend/Measure/Cooking/Weight.php';
$ref = new ReflectionClass('Zend_Measure_Cooking_Weight'):
echo "<b>Deklarierte Konstanten:</b><br>”;
foreach ($ref->getConstants() as $konst)
{
echo "$konst<br>";
1
Natürlich müssten Sie immer den Namen der Klasse einsetzen, welche Sie unter-
suchen wollen.
386
e-bol.net
Konvertieren von und Rechnen mit Maßeinheiten mittels Zend_Measure | 8.3
Die Nutzung ist einfach. Zunächst leiten Sie ein Objekt der entsprechenden
Klasse ab. Dem Konstruktor übergeben Sie als ersten Parameter die Information,
um »wie viel« es sich handelt. Sie geben also einfach eine Zahl an. An zweiter
Stelle können Sie die Maßeinheit in Form einer Konstante angeben. Tun Sie dies
nicht, wird eine Standardmaßeinheit zugrunde gelegt. Als dritten Parameter kön-
nen Sie optional noch ein Zend_Local e-Objekt übergeben. Daraus ermittelt die
Klasse, wie Zahlen formatiert sein können bzw. zu formatieren sind. Was dies
konkret bedeutet, sehen Sie im folgenden Beispiel:
requi re_once 'Zend/Measure/Wei ght.php';
// "Zahl" die eingelesen werden soll
$zahl = ”1.234,56";
// Englische Lokalisierung
Slocale = new Zend_Locale('en');
Smeasure = new Zend_Measure_Weight(
$zahl,Zend_Measure_Weight::GRAM, $ locale);
echo $measure->getValue();
echo "<br>";
// Deutsche Lokalisierung
Slocale = new Zend_Locale(* de');
Smeasure = new Zend_Measure_Weight(
$zahl,Zend_Measure_Weight::GRAM, $ locale);
echo $measure->getValue();
Listing 8.8 Nutzung von Zend_Measure
In diesem Beispiel wurde die Zahl in Form eines lokalisierten Strings an den Kon-
struktor übergeben. Abhängig von der Lokalisierung, die der Klasse mitgeteilt
wird, haben Punkte und Kommas eine andere Bedeutung, was dazu führt, dass
im ersten Fall 1.23 ausgegeben wird, wohingegen im zweiten Fall 1234.56 auf
dem Bildschirm erscheint. In zukünftigen Versionen der Klasse soll diese Lokali-
sierung auch Auswirkung auf die Formatierung der Zahlen bei der Ausgabe
haben, was aber noch nicht der Fall ist. Sie können den Wert eines Objekts auch
nachträglich durch Aufruf der Methode setValue() ändern, die die gleichen
Parameter akzeptiert wie der Konstruktor.
Wie Sie dem Beispiel schon entnehmen konnten, können Sie mit der Methode
getVal ue() den Wert auslesen, der in einem Maßeinheiten-Objekt enthalten ist.
Die dazugehörige Maßeinheit stellt die Methode getType() zur Verfügung. Die
Maßeinheiten werden immer als englischsprachige Strings in Großbuchstaben
zurückgegeben, sodass Sie sich selbst um eine Lokalisierung kümmern müssen.
387
e-bol.net
8 | Lokalisierung und Internationalisierung
Es wäre natürlich nicht sonderlich spannend, einen Wert zu übergeben und ihn
gleich wieder auszulesen. Möchten Sie einen Wert in eine andere Maßeinheit
konvertieren, so nutzen Sie die Methode convertTol), welche als Parameter eine
Konstante übergeben bekommt, die definiert, in welche Einheit konvertiert wer-
den soll. Die Methode liefert den konvertierten Wert direkt als String mit ange-
hängter Maßeinheiten-Abkürzung zurück, sodass sie ihn direkt ausgeben kön-
nen. Darüber hinaus wird aber auch die Information innerhalb des Objekts
geändert, sodass Sie getVal ue() und getType() nutzen können:
require_once 'Zend/Measure/Cooking/Weight.php';
Slocale = new Zend_Locale(’de'):
Smeasure = new Zend_Measure_Cooking_Weight("2,5",
Zend_Measure_Cooking_Weight::TEASPOON, $ 1ocale);
echo "Werte vor der Konvertierung:
echo $measure->getValue(). ” ”:
echo $measure->getType():
echo "<br>";
echo "Direkte Ausgabe:
echo $measure->convertTo(Zend_Measure_Cooking_Weight::GRAM);
echo "<br>";
echo "Manuelle Ausgbe:
echo $measure->getValue()."
echo $measure->getType();
Die Ausgabe dieses Beispiels, bei dem 2,5 Teelöffel in Gramm umgerechnet wer-
den, finden Sie in Abbildung 8.5.
O http://carsten-.,.alisierung/8.php CD
Werte vor der Konvertierung: 2.5 TEASPOON
Direkte Ausgabe: 8.86 g
Manuelle Ausgbe: 8.86 GRAM
Abbildung 8.5 Ausgabe der Werte
Ein String, den convertTo(), liefert können Sie auch jederzeit durch Aufruf der
Methode toString() generieren. Den Methoden getVal ue(), convertTol) und
toStri ng() können Sie optional noch die Anzahl der Stellen übergeben, auf wel-
388
e-bol.net
Währungsdarstellung mit Zend_Currency | 8.4
ehe die Zahl gerundet werden soll. toStringO rundet standardmäßig nicht,
wohingegen die beiden anderen per Default nur zwei Nachkommastellen liefern.
Neben der reinen Konvertierung kann die Klasse aber noch mehr. Sie können
auch mit Werten rechnen und diese vergleichen. Injedem Zend_Measure-Objekt
stehen die Methoden add () und sub() zur Verfügung, welche ein Objekt der
gleichen Klasse als Parameter übergeben bekommen. Der Wert des übergebenen
Objekts wird dann entweder addiert oder subtrahiert und der neue Wert im
ursprünglichen Objekt gespeichert, wobei das Ergebnis der Berechnung von den
Methoden direkt zurückgegeben wird. Bitte beachten Sie, dass beide Methoden
intern mit Werten rechnen, die auf die zweite Nachkommastelle gerundet sind.
$ml = new Zend_Measure_Weight(1.1 ,
Zend_Measure_Wei ght::CATTY_THAI);
$m2 = new Zend_Measure_Weight(1.235 ,
Zend_Measure_Wei ght::CATTY_THAI);
echo $ml->add($m2);
// Ausgabe: 2.34 catty
Um zwei Objekte zu vergleichen, stehen die Methoden equal s() und compare()
zur Verfügung. Die erste liefert true zurück, wenn das aktuelle Objekt sowie das-
jenige, das als Parameter übergeben wird, bis zur letzten Kommastelle identisch
sind. Die Methode compare() gibt -1 zurück, wenn das Objekt, aus dem heraus
die Methode aufgerufen wurde, einen kleineren Wert hat als das übergebene
Objekt. Sie gibt 1 zurück, wenn der Wert des übergebenen Objekts größer ist,
und 0, falls beide gleiche Werte haben. Diese Methode können Sie beispielsweise
als Callback für Sortierfunktionen nutzen. Bitte beachten Sie, dass die Einheit des
übergebenen Objekts nach dem Aufruf der Einheit des anderen Objekts ent-
spricht.
8.4 Währungsdarstellung mit Zend_Currency
Die Klasse Zend_Currency ist eine sehr praktische Klasse, wenn es darum geht,
Währungsangaben zu formatieren. Zend_Currency dient allerdings wirklich nur
der Darstellung und bietet keine Möglichkeit, von einer Währung in eine andere
umzurechnen.
Bei der Darstellung von Währungen sind drei Punkte zu beachten. Der erste
Punkt ist die Währung, in der die Summe dargestellt wird. Hier gilt es, die kor-
rekte Abkürzung bzw. das korrekte Währungssymbol zu wählen. Zum Zweiten
gilt es, die korrekten Dezimal- und Tausender-Trennzeichen zu wählen. Der
dritte Punkt betrifft die Darstellung der Zahlen - mit normalen arabischen Ziffern
389
e-bol.net
8 | Lokalisierung und Internationalisierung
oder in einer anderen Notation. Zwar ist das rein technisch nicht so schwierig,
aber die dahinterliegenden Informationen zusammenzutragen, ist ein nicht zu
unterschätzender Aufwand.
Bei der Arbeit mit Zend_Currency stellt sich zunächst die Frage, welche Währun-
gen dem System bekannt sind. Zend_Currency greift im Hintergrund auf die
Informationen von Zend_Locale zurück, sodass der volle Informationsumfang
der Klasse hier zur Verfügung steht, und Sie somit davon ausgehen können, dass
alle relevanten Länder und Währungen hinterlegt sind.
Im einfachsten Fall könnte die Nutzung so aussehen:
header('Content-Type: text/html; charset=utf-8'):
requi re_once 'Zend/Currency.php’;
Scurrency = new Zend_Currency(1 EUR', 'de_DE*):
echo $currency->toCurrency( 10000):
// Ausgabe: 10.000,00 €
Listing 8.9 Nutzung von Zend_Currency
In diesem Beispiel wurden dem Konstruktor zwei Informationen übergeben. Bei
dem ersten Parameter handelt es sich um die Währung, die genutzt werden soll.
Er definiert, entsprechend welcher Lokalisierung die Zahl formatiert werden soll.
Die Währung muss immer in der offiziellen Abkürzung mit drei Buchstaben
übergeben werden: EUR definiert also den Euro, USD steht für US-Dollar und
GBP kürzt britische Pfund ab. Eine Liste aller Abkürzungen finden Sie beispiels-
weise unter http://air-ways.de/waehrungen.htm.
Bei dem zweiten Parameter handelt es sich um einen ganz normalen Locale-
String, der Sprache und Lokalisierung definiert. Er legt fest, wie die Tausender-
und Dezimal-Trennzeichen aussehen sollen. Sie können an dieser Stelle auch ein
Zend_Locale-Objekt mit der entsprechenden Lokalisierung übergeben.
Der Konstruktor ist übrigens sehr flexibel. Sie müssen ihm nicht unbedingt beide
Parameter übergeben. Aufgrund seiner Implementation erkennt er durchaus
auch, wenn Sie ihm nur den Lokalisierungs-String, also zum Beispiel de_DE oder
ein Zend_Local e-Objekt übergeben. In dem Fall werden die anderen Informatio-
nen entsprechend ergänzt, sodass als Währung Euro selektiert wird.
Der Aufruf der Methode toCurrency() wandelt die übergebene Zahl in eine kor-
rekt formatierte Währungsangabe um und gibt diese zurück. Hierbei ist zu beach-
ten, dass die Währung standardmäßig als Symbol zurückgegeben wird. Die Sym-
bole werden dabei grundsätzlich in UTF-8-Codierung zurückgegeben, um
390
e-bol.net
Währungsdarstellung mit Zend_Currency | 8.4
Zeichensatzprobleme zu vermeiden. Wichtig ist auch, dass die Methode nicht
immer mit zwei Nachkommastellen arbeitet. Daraus resultiert im obigen Beispiel
auch, dass 10.000,00 € und nicht 10.000 € ausgegeben wird.
Die Informationen, die Sie dem Konstruktor übergeben haben oder die der Kon-
struktor selbst ermittelt hat, können Sie natürlich auch noch nachträglich verän-
dern. So können Sie der Methode toCurrency() an zweiter Stelle ein Array mit
Informationen übergeben, die für die Formatierung der Zahl genutzt werden.
Alternativ haben Sie auch die Möglichkeit, die Daten mithilfe der Methode set-
Format() festzulegen. In dem Fall gelten sie nicht nur für die eine Ausgabe, son-
dern für alle Konvertierungen, die danach noch erfolgen.
Dieses Array darf bestimmte Schlüssel enthalten. Da ist zum zunächst der Schlüs-
sel di splay. Damit definieren Sie, wie die Währung dargestellt werden soll, ob
als Symbol, Abkürzung oder als ausgeschriebener Text. Diesen Schlüssel können
Sie mit den Konstanten aus Tabelle 8.4 belegen.
Konstante Bedeutung
NO_$YMBOL keine Währungsinformation ausgeben
USE_SYMBOL Währungssymbol ($, €usw.) nutzen
USE_SHORTNAME Abkürzung der Währung (EUR, USD usw.) ausgeben
USE_NAME Name der Währung (American Dollar)
Tabelle 8.4 Konstanten für die Währungsausgabe
In diesem Zusammenhang sind auch die Schlüssel name, currency und Symbol
interessant. Möchten Sie nicht die vorgegebenen Währungsnamen bzw. Symbole
nutzen, so können Sie mit diesen Schlüsseln andere übergeben. Mit name können
Sie den Namen einer Währung übergeben, wie beispielsweise Euro. Der hier ent-
haltene Name wird dargestellt, wenn Sie mit di spl ay USE_NAME übergeben. Soll-
ten Sie di spl ay mit USE_SHORTNAME belegt haben, wird die Währungsabkürzung
dargestellt, die Sie in currency übergeben haben. Um ein eigenes Symbol an
Stelle des Euro-Zeichens oder eines anderen Symbols zu nutzen, weisen Sie das
Symbol zu und übergeben USE_SYMBOL mithilfe von di spl ay.
Der nächste zur Verfügung stehende Schlüssel ist posi ti on. Mit ihm wird dekla-
riert, wo die Ausgabe des Währungssymbols bzw. -namens erfolgen soll. Hierbei
können Sie die Konstanten aus Tabelle 8.5 nutzen.
391
e-bol.net
8 | Lokalisierung und Internationalisierung
Konstante Bedeutung
STANDARD Die Position der Währungsangabe entsprechend der Lokalisierung
RIGHT Die Währungsangabe erscheint rechts neben der Zahl.
LEFT Die Währungsangabe erscheint links neben der Zahl.
Tabelle 8.5 Konstanten zur Positionierung der Währung
Wie viele Nachkommastellen die Ausgabe haben soll, definieren Sie über das Ele-
ment precision, dem Sie eine Zahl übergeben, die die Anzahl der Stellen nach
dem Komma festlegt. Sollten Sie eine andere Lokalisierung nutzen wollen, so
haben Sie die Möglichkeit, format ein Locale-Objekt oder einen -String zuzuwei-
sen. Mit der letzten Option script haben Sie die Möglichkeit, ostarabische Zif-
fern zu nutzen, indem Sie ihr den String Arab zuweisen. Die westarabischen Zif-
fern selektieren Sie mit Latn. Das System kennt noch eine ganze Reihe anderer
Darstellungsarten. Eine komplette Liste finden Sie in der Dokumentation zu
Zend_Local e.4 Die folgenden Zeilen enthalten einige Beispiele, deren Ausgabe
Sie in Abbildung 8.6 sehen.
header('Content-Type: text/html; charset=utf-8');
requi re_once 'Zend/Currency.php’;
Slocale = new Zend_Locale(’de_DE');
Scurrency = new Zend_Currency($1ocale);
$opts = array ('position' => Zend_Currency::LEFT,
'precision' => 3,
'name’ => 'Euronen: ',
'display' => Zend_Currency::USE_NAME
);
echo 'Drei Nachkommastellen, Währung "Euronen" links:<br>';
echo $currency->toCurrency( 10000, $opts).'<br>';
$opts = array ('position' => Zend_Currency::RIGHT,
'script' => 'Latn',
'precision' => 2,
'display' => Zend_Currency::USE_SHORTNAME
);
echo 'Zwei Nachkommastellen, Währung "EUR" rechts:<br>';
echo $currency->toCurrency(123.456, $opts).’<br>';
$opts = array ('precision' => 2,
4 http://framework.zend.eom/manual/en/zend.locale.parsing.html#
zend. locale, appendix.numberscripts.supported
392
e-bol.net
Währungsdarstellung mit Zend_Currency | 8.4
’display' => Zend_Currency::USE_SYMBOL,
’format' => ’enJJS'
):
echo 'Zwei Nachkommastellen, Währungssymbol, amerikanische
Formatierung:<br>':
echo $currency->toCurrency(1234.567, $opts),'<br>' ;
$opts = array (’script' => ’Arab');
echo 'Ostarabische Ziffern, zwei Nachkommastellen, Währungssymbol:
<br>*;
echo $currency->toCurrency(1234.567, $opts).'<br>';
Listing 8.10 Ausgabe verschieden formatierter Währungen
C« M http://12 7.0.0. l/~carsten/zf-buch/Currency/l.php CD
ä w uö "© I’ Q’ Google [u j
Drei Nachkommastcllcn, Währung "Euroncn" links:
Euroncn: 10.000,000
Zwei Nachkommastcllcn, Währung "EUR" rechts:
123,46 EUR
Zwei Nachkommastcllcn, Währungssymbol, amerikanische Formatierung:
1,234.57 €
Ostarabischc Ziffern, zwei Nachkommastcllcn, Währungssymbol:
x.vrt.ov €
Abbildung 8.6 Ausgabe mit verschiedenen Formatierungen
In der Klasse sind noch einige zusätzliche Funktionen deklariert, welche durch-
aus hilfreich sein können, um eigene Ausgaben zu erstellen. Den Namen der
Währung können Sie in der Kurzform mit der Methode getShortName() ausge-
ben lassen, die ausgeschriebene Variante erhalten Sie mit getNameO, und
getSymbol () liefert Ihnen das Währungssymbol im UTF-8 Zeichensatz zurück.
Die drei Methoden können ohne Parameter direkt aus einem Zend_Currency-
Objekt heraus aufgerufen werden und liefern dann die entsprechenden Informa-
tionen für die aktuelle Lokalisierung. Alternativ können Sie ihnen auch eine
Währung und/oder ein Locale-Objekt bzw. einen Locale-String übergeben:
header('Content-Type: text/html; charset=utf-8'):
requi re_once 'Zend/Currency.php’;
Slocale = new Zend_Locale('de_DE'):
Scurrency = new Zend_Currency($locale):
393
e-bol.net
8 | Lokalisierung und Internationalisierung
echo $currency->getName()."<br>": //Euro
echo $currency->getShortName(). "<br>"; //EUR
echo $currency->getSymbol()."<br>": //€
echo $currency->getName(’en_US')."<br>"; // US Dollar
echo $currency->getShortName('en_US')."<br>"; //USD
echo $currency->getSymbol('en_US')."<br>";
Listing 8.11 Ausgabe einzelner Währungsbestandteile
Interessant sind auch noch die Methoden getRegionList() und getCurrency-
Li st(). Die erste bekommt eine Währung übergeben und liefert Ihnen ein Array
mit den Codes aller Länder, welche die Währung einsetzen:
echo "Der Euro wird genutzt in:
echo implodet’, ’,$currency->getRegionList(’EUR’)):
/* Ausgabe:
Der Euro wird genutzt in: AD, AT, AX, BE, CS, CY, DE, ES, FI, FR, GF
, GP. GR, IE, IT, LU. MC, ME. MQ, MT, NL, PM, PT, RE, SI. SM, TF. VA
. YT
*/
Die zweite bekommt den Code eines Landes übergeben und gibt Ihnen ein Array
mit den dort genutzten Währungen:
$erg = $currency->getCurrencyList('DE');
pri nt_r($erg);
/* Ausgabe:
Array
(
[EUR] => 1999-01-01
[DEM] => 1948-06-20
)
*/
Wie Sie sehen, wird hier der Kurzname der Währung als Schlüssel genutzt. Der
dazugehörige Wert ist das Datum, zu dem die Währung eingeführt wurde.
Eine kleine Anmerkung zum Abschluss: Aufgrund der großen Datenmenge, die
Zend_Currency analysieren muss, dauert es oft ein wenig, bis Sie die Daten erhal-
ten. Daher haben die Entwickler von vornherein die Möglichkeit vorgesehen, die
Daten zu cachen. Dazu können Sie ein Objekt der Klasse Zend_Cache ableiten und
es mit der statischen Methode setCache() an die Klasse Zend_Currency überge-
ben. Sollte Ihre Applikation öfter Zend_Currency nutzen, sollten Sie einen Cache
verwenden.
394
e-bol.net
Datums- und Zeitangaben mit Zend_Date verarbeiten | 8.5
8.5 Datums- und Zeitangaben mit Zend_Date verarbeiten
Die Klasse Zend_Date stellt Ihnen sehr umfangreiche Möglichkeiten zur Arbeit
mit Zeit- und Datumsangaben zur Verfügung. Besonders angenehm bei der Nut-
zung der Klasse sind das hohe Maß an Flexibilität und die große Anzahl der
Methoden. Zugegebenermaßen ist die große Anzahl der Funktionen auch ein
wenig verwirrend, aufgrund der klaren Struktur kann man aber gut damit leben.
Zend_Date kann übrigens mit beliebigen Daten rechnen und ist nicht so einge-
schränkt wie die PHP-Funktionen.
Da die Klasse sehr umfangreich ist, werde ich hier nicht alle Methoden und Funk-
tionalitäten erläutern. Interessant ist aber vielleicht noch, dass Sie auch den Son-
nenaufgang und Sonnenuntergang an bestimmten Orten berechnen können.
Informationen dazu entnehmen Sie bitte der Dokumentation.
8.5.1 Ableiten eines Zend_Date-Objekts
Wenn Sie ein Zend_Date-Objekt ableiten wollen, ist zu überlegen, welche
Datums- und Zeitinformationen in dem Objekt enthalten sind bzw. sein sollen.
Leiten Sie mit new ein Objekt ab, wird diesem die aktuelle Zeitinformation des
Servers zugewiesen. Gleiches geschieht, wenn Sie die statische Methode now()
aufrufen, die sicherlich besser zu lesen ist. Die beiden folgenden Zeilen haben
also den gleichen Effekt:
$date = new Zend_Date():
$date = Zend_Date::now();
Einen kleinen Vorteil hat die Nutzung von now() allerdings noch. Und zwar kön-
nen Sie der Methode gleich ein Zend_Local e-Objekt oder einen Locale-Code mit-
geben. Damit wird dann zwar immer noch die lokale Zeit des Servers genutzt,
aber die Darstellung von Datum und Uhrzeit wird an die regionalen Vorgaben
angepasst. Haben Sie das Objekt auf konventionellem Weg abgeleitet, können Sie
die Lokalisierung nachträglich mit setLocaleO vornehmen. Die nachfolgend
abgeleiteten Objekte würden somit alle die gleiche Lokalisierung aufweisen:
$date = Zend_Date::now(’enJJS*);
Slocale = new Zend_Locale('en_US'):
$date2 = Zend_Date::now($locale):
$date3 = new Zend_Date():
$date3->setLocale($locale);
395
e-bol.net
8 | Lokalisierung und Internationalisierung
Auch der Methode setLocale() hätte man natürlich einen Lokalisierungs-String
übergeben können. Sollten Sie sich einmal dafür interessieren, welche Lokalisie-
rungseinstellung ein Objekt aufweist, so können Sie das mit getLocale() ausle-
sen. Wie in diesem Fall sind auch die meisten anderen Methoden »in beide Rich-
tungen« deklariert. Das bedeutet, dass Sie mit der set-Methode immer einen
bestimmten Wert setzen und mit der entsprechenden get-Methode wieder aus-
lesen können.
Zeitzonen
Wahrscheinlich werden Sie in Ihren Anwendungen relativ wenig mit verschiede-
nen Zeitzonen zu tun haben. Sollten Sie damit aber arbeiten müssen, ist das kein
Problem. Zend_Date unterstützt die Nutzung von Zeitzonen, und die meisten
Methoden nutzen intern die Zeitzonen.
Bevor ich zu dem komme, was Zend_Date in diesem Zusammenhang leistet, noch
ein Wort zu Zeitzonen. Um der Tatsache, dass die Sonne wandert, gerecht zu
werden, hat man auf der Welt verschiedene Zeitzonen eingeführt. Die Bezugs-
größe dabei ist immer die UTC (Universal Time Coordinated) bzw. früher GMT
(Greenwich Mean Time). Aus historischen Gründen ist die GMT anhand des
Nullmeridians definiert, der durch London läuft. Bei allen Orten, die westlich
von London liegen, wird daher Zeit »abgezogen«, und bei allen Orten, die östlich
liegen, wird Zeit »dazugerechnet«. Das hat zur Folge, dass es in Deutschland eine
Stunde später ist als zum gleichen Zeitpunkt in London, weil die Sonne hier
schon weiter gewandert ist. Zum gleichen Zeitpunkt ist es in den USA - abhängig
von der Zeitzone - allerdings etwa sieben Stunden früher. Oder mit konkreten
Zahlen: Wenn es in London 12:00 Uhr ist, dann ist es in Deutschland bereits
13:00 Uhr und in den USA erst 5:00 Uhr morgens.
Was heißt das nun für Zend_Date? Die Klasse übernimmt beim Ableiten des
Objekts die Zeitzone, die für das System eingestellt ist. Daher sollten Sie diese
immer korrekt mit der PHP-Funktion date_defaul t_timezone_set( )setzen.
Um die Zeitzone des Zend_Date-Objekts zu verändern, können Sie die Methode
setTimezone() nutzen, die den Namen einer Zeitzone übergeben bekommt. Die
bekannten Zeitzonen können Sie der PHP-Dokumentation unter http://de.php.
net/manual/de/timezones.php entnehmen.
Die aktuelle Zeitzone lesen Sie mit getTimezone() aus, und den zeitlichen Unter-
schied zwischen GMT und der aktuellen Zeitzone liefert Ihnen die Methode
getGmtOffset() in Sekunden:
396
e-bol.net
Datums- und Zeitangaben mit Zend_Date verarbeiten | 8.5
requi re_once('Zend/Date.php');
// Zeitzone korrekt setzen
date_default_timezone_set('Europe/Berlin'):
$date = Zend_Date::now();
echo "Aktuelle Zeit: ".$date;
echo "<br>Zeitzone: ".$date->getTimezone();
echo "<br>Unterschied zu UTC: ".$date->getGmtOffset():
// Zeitzone neu setzen
$date->setTimezone('America/New_York’);
echo "<p>Aktuelle Zeit in New York: ".$date;
echo "<br>Zeitzone: ".$date->getTimezone();
echo "<br>Unterschied zu UTC: ".$date->getGmtOffset():
Listing 8.12 Nutzung von Zeitzonen
http7/127.0.0.1/~c . n/zf-buch/date2.php CD
W W Ä
Aktuelle Zeit: 17.01.2008 12:09:11
Zeitzone: Europc/Berlin
Unterschied zu UTC: -3600
Aktuelle Zeit in New York: 17.01.2008 06:09:11
Zeitzone: Amcrica/New_York
Unterschied zu UTC: 18000
Abbildung 8.7 Unterschiedliche Zeitzonen zum gleichen Zeitpunkt
Ausgabe von Daten
Bei der Nutzung eines Objekts stellt sich die Frage, wie Sie die enthaltenen Infor-
mationen wieder auslesen können. Im einfachsten Fall geben Sie das Objekt
direkt mit echo aus. Da in der Klasse die magische Methode_toStri ng() im-
plementiert ist, wird das Datum automatisch in einen String konvertiert.
Möchten Sie den Wert einer Variablen zuweisen, können Sie die Daten auch
explizit mit (string) konvertieren.
Sollten Sie die gesamten Datumsinformationen in Form eines assoziativen Arrays
bevorzugen, so erhalten Sie dies, indem Sie die Methode toArrayO aufrufen.
Dabei sind alle Informationen in den Feldern ’day', 'month', ’year', ’hour’,
’minute', ’second’, 'timezone', ’timestamp’, 'weekday', ’dayofyear',
’week’ und 'gmtsecs' enthalten.
397
e-bol.net
8 | Lokalisierung und Internationalisierung
Eine andere, etwas komfortablere Vorgehensweise ist die Nutzung der Methode
get(). Rufen Sie diese ohne Parameter auf, liefert Sie die Zeitinformation als
Timestamp zurück. Sie können ihr allerdings mit dem ersten Parameter mitteilen,
welche Information Sie auslesen wollen, bzw. in welchem Format diese ausgege-
ben werden soll. Dafür ist eine beachtliche Anzahl von Konstanten definiert, von
denen Sie einen Teil in Tabelle 8.6 finden. Mit dem zweiten Parameter können
Sie optional noch eine Lokalisierung übergeben, sofern das für die Ausgabe not-
wendig sein sollte.
// Neues Datumsobjekt mit
// amerikanischer Lokalisierung
$date = Zend_Date::now('en_US');
// Normale Ausgabe
echo $date. "<br>";
// "Langes Datum" in amerikanischer Formatierung
echo $date->get(Zend_Date::DATE_FULL)."<br>";
// "Langes Datum" in deutscher Formatierung
echo $date->get(Zend_Date::DATE_FULL, 'de_DE')."<br>";
// "Kurzes Datum" in amerikanischer Formatierung
echo $date->get(Zend_Date::DATE_SHORT)."<br>”;
// "Langes Datum" in deutscher Formatierung
echo $date->get(Zend_Date::DATE_SHORT, ’de_DE')."<br>";
Listing 8.13 Verschiedene Datumsformate
In diesem Beispiel würde die Ausgabe folgendermaßen aussehen:
Jan 5. 2008 3:22:27 PM
Saturday, January 5, 2008
Samstag, 5. Januar 2008
1/5/08
05.01.08
Die Anzahl der Konstanten, die Sie als ersten Parameter nutzen können, ist sehr
umfangreich. Daher werde ich hier nur die wichtigsten erwähnen. Eine kom-
plette Liste entnehmen Sie bitte der Dokumentation unter http://framework.
zend.com/manual/de/zend.date.constants.html.
398
e-bol.net
Datums- und Zeitangaben mit Zend_Date verarbeiten | 8.5
Konstante Bedeutung
DATE_FULL Datum mit ausgeschriebenem Tag und Monat (Beispiel: Samstag, 5. Januar 2008)
DATE_LONG Datum mit ausgeschriebenem Monat (Beispiel: 5. Januar 2008)
DATE_MEDIUM Datum in der »üblichen« Schreibweise, Monat und Tag zweistellig (Beispiel: 05.01.2008)
DATE_SHORT Tag, Monat und Jahr zweistellig (Beispiel: 05.01.08)
TIMES Uhrzeit in der üblichen, lokalisierten Schreibweise (Beispiel: 15:42:11 oder 3:42:11 PM)
TIME_L0NG Wie TIMES allerdings mit Zeitzone
TIME_SHORT lokalisierte Uhrzeit mit Stunden und Minuten (Beispiel: 15:42 oder 3:42 PM)
DAY Tag des Monats, zweistellig
DAY_SHORT Tag des Monats mit ein oder zwei Stellen
MONTH_NAME ausgeschriebener Name des Monats in der Sprache der Lokalisierung (Beispiel: January oder Januar)
MONTH_NAME_SHORT abgekürzter Name des Monats in der Landessprache (Beispiel: Jan)
MONTH Monat, zweistellig
M0NTH_SH0RT AAonat, ein oder zweistellig YEAR Jahr, vierstellig YEAR_SHORT Jahr, zweistellig HOUR Stunden im 24-Stunden-Format, zweistellig HOUR_SHORT Stunden im 24-Stunden-Format, ein- oder zweistellig H0UR_AM Stunden im 12-Stunden-Format, zweistellig H0UR_SH0RT_AM Stunden im 12-Stunden-Format, ein- oder zweistellig MER I DI EM Vor- oder Nachmittag in der gewählten Lokalisierung (Beispiel: nachm., AAA oder PAA) MINUTE AAinuten, zweistellig MINUTE_SHORT AAinuten, ein- oder zweistellig SECOND Sekunden, zweistellig SECOND_SHORT Sekunden, ein - oder zweistellig I S0_8601 vollständige Datumsangabe im ISO 8601-Format (Beispiel: 2008-01-05T15:58:46+01:00) RFC_2822 Datumsangabe nach RFC 2822 (Internet AAessage Format) (Beispiel: Sat, 05 Jan 2008 16:00:41 +0100) TIMESTAMP Unix Timestamp Tabelle 8.6 Konstanten zur Nutzung mit Zend_Date
399
e-bol.net
8 | Lokalisierung und Internationalisierung
Konstante Bedeutung
ATOM Datum für die Nutzung in ATOM-Feeds (Beispiel: 2008-01-05T16:01:48+01:00)
RFC_822 Datumsangabe nach RFC 822 (ARPA Internet Text AAessages) (Beispiel: Sat, 05 Jan 08 15:59:21 +0100)
RFC_850 Datumsangabe nach RFC 850 (Interchange of USENET AAessages) (Beispiel: Saturday, 05-Jan-08 16:02:59 Europe/Berlin)
RFC_1036 Datum nach RFC 1036 (Interchange of USENET AAessages; Nachfolger von RFC 850) (Beispiel: Sat, 05 Jan 08 16:03:59 +0100)
RFC_1123 Datum nach RFC 1123 (Requirements for Internet Hosts) (Beispiel: Sat, 05 Jan 2008 16:09:24 +0100)
RSS Datum zur Nutzung in RSS-Feeds (Beispiel: Sat, 05 Jan 2008 16:11:17 +0100)
W3C Datum im W3C-Format zur Verwendung in HTAAL-Seiten (Beispiel: 2008-01-05T16:17:16+01:00)
Tabelle 8.6 Konstanten zur Nutzung mit Zend_Date (Forts.)
Sie sehen, die Methode ist wirklich sehr flexibel. Diese Konstanten können Sie
übrigens auch noch bei einigen anderen Methoden nutzen, die Sie gleich ken-
nenlernen werden.
Die erste Methode, bei der Sie diese Konstanten wieder nutzen können, ist
set(), mit der Sie eine Uhrzeit bzw. ein Datum setzen können. Das Spannende
dabei ist, dass set() die oben genannten Formate verarbeiten kann und dadurch
extrem flexibel ist:
$date = Zend_Date::now('de_DE');
echo 'Aktuelles Datum: '.$date->get(Zend_Date::DATE_FULL).'<br>';
$date->set(’Mon, OZ Jan 08 16:03:59 +0100',Zend_Date::RFC_1036):
echo 'Neu gesetztes Datum: '.
$date->get(Zend_Date::DATE_FULL).’<br>';
$date->set('Dienstag',Zend_Date::WEEKDAY);
echo 'Datum auf Dienstag gesetzt: '.
$date->get(Zend_Date::DATE_FULL).’<br>';
$date->set('Sunday',Zend_Date::WEEKDAY,’en_US'):
echo 'Datum auf Sonntag gesetzt: '.
$date->get(Zend_Date::DATE_FULL).’<br>';
Listing 8.14 Setzen von Daten
400
e-bol.net
Datums- und Zeitangaben mit Zend_Date verarbeiten | 8.5
In Abbildung 8.8 sehen Sie die generierte Ausgabe.
http://127.0.0.1/.../zf-buch/datel.php O
a « u
Aktuelles Datum: Samstag. 5. Januar 2008
Neu gesetztes Datum: Montag, 7. Januar 2008
Datum auf Dienstag gesetzt: Dienstag, 8. Januar 2008
Datum auf Sonntag gesetzt: Sonntag, 6. Januar 2008
Abbildung 8.8 Ausgabe der Daten
Eingehen möchte ich noch auf die letzten beiden Operationen. Hier wird der Tag
zuerst auf Dienstag gesetzt, was dazu führt, dass das Datum auf den 08.01. korri-
giert wird. Danach wird der Tag auf Sonntag gesetzt. Genauer gesagt auf Sunday,
was kein Problem darstellt, da die Methode zusätzlich die Information bekom-
men hat, dass das Datum in der amerikanischen Variante vorliegt. Interessant ist
aber, dass das Datum danach der 06.01. ist und nicht der 13.01., wie man viel-
leicht erwartet hätte. Das liegt daran, dass das Datum bei dieser Vorgehensweise
immer auf einen Tag in der aktuellen Woche gesetzt wird. Und da die Woche
nach ISO mit dem Sonntag beginnt, resultiert das in 06.01.
Übrigens können Sie eine Zeitinformation mit nachfolgender Konstante und
eventueller Lokalisierung auch direkt an den Konstruktor übergeben, falls Sie
set() nicht nutzen wollen.
Eine weitere Möglichkeit, das Datum zu setzen, ist, ein Array zu nutzen. Auch
diese Vorgehensweise wird von beiden Methoden unterstützt. In dem Fall über-
geben Sie die Datumsbestandteile in Form eines assoziativen Arrays, wobei Sie
die Schlüssel day, month, year, hour, minute und second nutzen können, um den
Tag, den Monat, das Jahr sowie die Stunden, Minuten und Sekunden zu überge-
ben.
Die Methoden set() und get() sind sehr flexibel und können die meisten
Bedürfnisse ohne Probleme abdecken. Allerdings gibt es noch eine stattliche
Anzahl von spezialisierten Methoden, die Sie in Tabelle 8.7 finden. Bei den hier
beschriebenen get-Methoden ist allerdings ein Punkt zu beachten. getTimeO,
getDatei) und getArpal) liefern einen String zurück. Die Methoden, die nur
einen Teil des Datums auslesen, liefern allerdings ein Zend_Date-Objekt zurück,
welches einen Timestamp enthält, das den entsprechenden Teil des Datums defi-
niert. Wenn Sie beispielsweise davon ausgehen, dass Sie ein Objekt nutzen, das
den 13.10.2008 als Grundlage hat, und lesen Sie den Monat mithilfe von get-
401
e-bol.net
8 | Lokalisierung und Internationalisierung
Month() aus, so erhalten Sie ein Objekt, das den 01.10.1970 enthält. Das mag ein
wenig ungewöhnlich erscheinen, aber es ist durchaus sinnvoll, weil Sie mit die-
sen Datumsobjekten gut Weiterarbeiten können.
Get-Methode Set-Methode Erläuterung
Liest oder setzt...
getTime() setT i me() ... die Zeit im TIMEJIEDIUM-Format
getDate() setDate() ... das Datum im DATE_FULL-Format. Beide Methoden beachten dabei die Lokalisierung.
getArpa() setArpa() ... ein Datum im RFC 822-Format
getYear() setYear() ... das Jahr im vierstelligen Format
getMontht) setMonth() ... den Monat
getDay() setDay() ... den Tag im Monat
getWeekday() setWeekday() ... den Wochentag
getDayOfYear() setDayOfYear() ... den Tag im Jahr (1-365)
getHour() setHour() ... die Stunden
getMi nute() setMi nute() ... die Minuten
getSecond() setSecond() ... die Sekunden
getWeek() setWeek() ... die Wochennummer
Tabelle 8.7 Methoden zum Lesen und Setzen von Daten
8.5.2 Rechnen mit Daten
Nachdem Sie nun schon einiges über die Ein- und Ausgabe von Daten wissen,
bleibt zu klären, wie Sie mit Daten und Zeiten rechnen. Auch das ist kein Pro-
blem. Ähnlich wie beim Auslesen von Datumsinformationen stehen auch hier
zwei allgemeine Methoden und eine recht große Anzahl von speziellen Metho-
den zur Verfügung. Mit den Methoden add () und sub() können Sie addieren
bzw. subtrahieren. Das heißt, zu der Zeitinformation, die in dem Objekt enthal-
ten ist, wird eine bestimmte »Menge an Zeit« dazu addiert oder davon abgezo-
gen:
$date = Zend_Date::now('de_DE'):
echo 'Aktuelles Datum: '.$date.'<br>';
$date->add(10, Zend_Date::HOUR);
echo 'Datum plus 10 Stunden: '.$date.'<br>';
$date->add(1, Zend_Date::MONTH);
echo 'Datum plus 10 Stunden und 1 Monat: '.$date.'<br>';
Listing 8.15 Addieren von Stunden
402
e-bol.net
Datums- und Zeitangaben mit Zend_Date verarbeiten | 8.5
Wie Sie sehen, bekommen die Methoden einfach nur eine Zahl übergeben und
danach die Information, um welche Zeiteinheit es sich handelt. Dabei können Sie
wiederum auf die Konstanten aus Tabelle 8.6 zurückgreifen. Bei diesen Metho-
den sind natürlich nur sinnvolle Konstanten zulässig. So können Sie zum Beispiel
nicht MERIDIEM nutzen. Allerdings ist es möglich, dass Sie ein Datum zu einem
anderen Datum addieren. Die folgenden Zeilen sind daher korrekt und funktio-
nieren:
$date = Zend_Date::now('de_DE'):
$date->add('1.1.1971', Zend_Date::DATE_SHORT);
Hierbei werden 1 Tag, 1 Monat und 1971 Jahre zum aktuellen Datum hinzu
addiert. Ob eine solche Rechnung sinnvoll ist, ist eine andere Frage. Sinnvoller
könnte es da schon sein, eine Uhrzeit, oder anders formuliert: eine Zeitspanne in
Stunden, Minuten und Sekunden, zu addieren:
$date->add('13:12:11', Zend_Date::TIMES):
Die Methoden akzeptieren aber nicht nur Strings. Sie können ebenso gut ein
Objekt der Klasse Zend_Date übergeben. Möchten Sie beispielsweise die Uhrzeit
aus einem Objekt auslesen und ein anderes Datum mit der exakt gleichen Uhrzeit
berechnen, können Sie das so lösen:
$datel = new Zend_Date(’23.12.1999 1:23:11', 'de_DE');
$date2 = new Zend_Date('23.12.2000 1:00:00', 'de_DE');
echo 'Datum 1: '.$datel.'<br>';
echo 'Datum 2: '.$date2.’<br>';
$zeit = $datel->getTime():
echo 'Extrahierte Uhrzeit: '.$zeit.'<br>':
$date2->add($zeit);
echo 'Datum 2 mit Uhrzeit: ’.$date2;
Listing 8.16 Addieren einer extrahierten Zeit
http://127.O.O.l.. f-buch/datel.php GZ*
JLWOO__________________________«
Datum 1:23.12.1999 01:23:11
Datum 2: 23.12.2000 01:00:00
Extrahierte Uhrzeit: 01.01.1970 01:23:11
Datum 2 mit Uhrzeit: 23.122000 01:23:11
Abbildung 8.9 Ausgabe der berechneten Werte im Browser
403
e-bol.net
8 | Lokalisierung und Internationalisierung
In diesem Beispiel mutet das zweite Datum vielleicht etwas merkwürdig an.
Sicher hätten Sie eher erwartet, dass dort 00:00 Uhr oder gar keine Uhrzeit steht.
Die Tatsache, dass hier 1:00 Uhr als Zeit genutzt wird, resultiert aus der Zeitzone
Europe/Berlin, in der die Berechnung stattfindet. Sie liegt eine Stunde vor UTC
bzw. GMT. Natürlich hätte man hier auch einfach die Anzahl der Sekunden, die
getGmtOffset() liefert, addieren können. Bei Operationen dieser Art geht der
Offset, der durch die Zeitzone definiert ist, verloren.5
add-Methode sub-Methode Beschreibung
Addiert oder subtrahiert...
addTimel) subTime() ... eine Uhrzeit (12:23:22). Achtung: Diese Methode kümmert sich selbst um den Offset von einer Stunde!
addDatel) subDate() ... ein Datum (28.12.2008)
addlsol) sublsoC) ... eine Datums- oder Zeitangabe im ISO-8601 Format
addArpal) subArpa() ... eine Zeitinformation im ARPA-/ RFC-822-Format
addYear() subYear() ... eine Jahreszahl. Zu zweistelligen Jahren zwischen 0 und 69 wird 2000 addiert, zu Jahren von 70 bis 99 wird 1900 addiert. Drei oder vierstellige Jahre werden direkt übernommen.
addMonthl) subMonth() ... eine Monatszahl, wobei der Jahresübergang beachtet wird.
addDayl) subDay() ... eine Anzahl Tage, wobei der Monatsübergang beachtet wird.
addHourl) subHour() ... eine bestimmte Anzahl Stunden. Achtung: Die Methode beachtet selbstständig den Offset.
addMi nute() subMi nute() ... die übergebene Anzahl an Minuten.
add$econd() subSecondC) ... die übergebene Anzahl an Sekunden.
Tabelle 8.8 Methoden zum Rechnen mit Zeiten
Bei den Methoden addTime(), subTime(), addDate() und subDate() können Sie
als zweiten Parameter eine Konstante übergeben, um ihr mitzuteilen, wie die
Angabe formatiert ist. Ansonsten akzeptieren alle Methoden als letzten Parame-
ter noch eine Information zur Lokalisierung.
8.5.3 Vergleich von Daten
Arbeiten Sie öfter mit Daten oder Zeiten, so werden Sie das Problem kennen:
Datums- oder Zeitinformationen zu vergleichen, ist oft recht umständlich. Wenn
5 Ich könnte mir vorstellen, dass dieses Verhalten sich in zukünftigen Versionen des
Zend Frameworks ändert. Bitte prüfen Sie das Verhalten der Methode, falls Sie sie nutzen.
404
e-bol.net
Datums- und Zeitangaben mit Zend_Date verarbeiten | 8.5
Sie aus dem Stegreif sagen sollten, ob 1000 Stunden mehr sind als 42 Tage, dürfte
das sicher nicht ganz einfach sein. 42 Tage entsprechen übrigens 1008 Stunden.
Auch hier gibt es wieder eine recht stattliche Anzahl an Methoden, die Ihnen zur
Verfügung stehen. Die »Universalmethode«, die in diesem Fall deklariert ist,
heißt compare(). Sie vergleicht den Wert, der in dem Objekt gespeichert ist, mit
einer zweiten Datumsinformation, die als Parameter übergeben wird. Als Wert
gibt Sie -1 zurück, wenn das Datum im Objekt kleiner ist, also zeitlich vor dem
übergebenen Zeitpunkt liegt. Ist das übergebene Datum kleiner, dann ermittelt
die Methode 1 als Ergebnis. Falls die beiden Zeiten gleich sind, liefert sie 0 als
Ergebnis:
Sdatei = new Zend_Date('12.12.1999', 'de_DE*);
Sdate2 = new Zend_Date('23.12.2000 11:00:00’, ’de_DE'):
Sergebnis = $datel->compare(Sdate2);
switch (Sergebnis)
{
case -1: echo "Sdatei ist kleiner als Sdate2":
break;
case 1: echo "Sdatei ist größer als $date2”;
break;
case 0: echo "Sdatei und Sdate2 sind gleich";
}
Listing 8.17 Vergleich von Zeiten
Dieses kleine Listing ermittelt zielsicher, dass der Wert von Sdate2 größer ist als
der von Sdatei. Somit erzeugt das Listing folgende Ausgabe:
12.12.1999 00:00:00 ist kleiner als 23.12.2000 11:00:00
Wichtig ist, dass Sie beim Vergleich von zwei Daten die Zeitzonen im Hinterkopf
behalten. Die folgenden Daten sind gleich, wie Sie sehen werden, allerdings wer-
den den Objekten unterschiedliche Zeitzonen zugewiesen:
Sdatei = new Zend_Date('12.12.1999 11:00:00’, ’de_DE');
Sdatei->setTimezone(’Europe/Berli n');
Sdate2 = new Zend_Date('12.12.1999 11:00:00’, ’de_DE');
Sdate2->setTimezone(’Europe/London');
Werden diese beiden Objekte nun mittels der obigen Routine miteinander ver-
glichen, würde folgendes Ergebnis ausgegeben:
12.12.1999 11:00:00 und 12.12.1999 10:00:00 sind gleich
405
e-bol.net
8 | Lokalisierung und Internationalisierung
Das Ergebnis ist absolut korrekt. Allerdings fehlt hierbei die Information, dass es
sich um denselben Zeitpunkt an unterschiedlichen Stellen der Welt, sprich unter-
schiedliche Zeitzonen, handelt.
Interessant ist auch, dass Sie der Methode als zweiten Parameter eine der Part-
Konstanten übergeben können, falls Sie nur einen Teil der Information verglei-
chen wollen. Wenn Sie also als zweiten Parameter Zend_Date::HOUR nutzen,
werden lediglich die Stunden miteinander vergleichen. Eine sehr praktische
Funktionalität, wie ich finde.
Auch hier ist wieder eine stattliche Anzahl von spezialisierten Methoden dekla-
riert. Diese finden Sie in der nächsten Tabelle.
Methode Beschreibung
Vergleicht....
compareTime() ... nur die Uhrzeiten, ignoriert das Datum.
compareDateC) ... nur das Datum, ignoriert dabei die Zeit.
comparelso() ... die Zeitinformation im ISO-Format, die ihr übergeben wurde, mit dem Datum im Objekt.
compareArpaC) ... die Zeitinformation im ARPA-Format, die ihr übergeben wurde, mit dem Datum im Objekt.
compareYear() ... die Jahreszahlen
compareMonth() ...die Monate
compareDay() ... die Tage im Monat (1 -31)
compareWeekday() ... die Tage in der Woche (1 -7)
compareDayOfYear() ... die Tage im Jahr (1-365)
compareHour() ... die Stunden
compareMi nute() ... die Minuten
compareSecondC) ... die Sekunden
compareWeekC) ... die Wochennummern
compareTimestampC) ... den übergebenen Timestamp mit der enthaltenen Zeit
Tabelle 8.9 Methoden zum Vergleich von Daten
Die Methoden compareTime() und compareDate() akzeptieren neben der Zeit-
bzw. Datumsinformation, die als erster Parameter übergeben wird, noch einen
zweiten Parameter zur Beschreibung des Formats. Außerdem akzeptieren diese
beiden sowie auch alle anderen Methoden noch einen weiteren Parameter, näm-
lich die Lokalisierung. Bitte beachten Sie, dass Methoden, die lediglich einen Teil
des Datums vergleichen, die Zeitzonen nicht beachten.
406
e-bol.net
Datums- und Zeitangaben mit Zend_Date verarbeiten | 8.5
Zwei Methoden, die ein wenig aus der Reihe fallen, sind isEarl ier() und i sLa-
te r(). Sie liefern einen booleschen Wert zurück, um Ihnen mitzuteilen, dass die
übergebene Zeitinformation einen früheren oder späteren Zeitpunkt definiert als
den, der im Objekt hinterlegt ist. Dabei unterstützen Sie dieselben Parameter wie
compare().
8.5.4 Prüfen von Datumsinformationen
Die berühmte Frage, woran man ein Schaltjahr erkennt, kann auch nicht jeder
Programmierer auf Anhieb beantworten. Daher kennt Zend_Date auch einige
Möglichkeiten, um Daten zu testen. Um gleich beim Schaltjahr zu bleiben: Falls
Sie herausfinden wollen, ob das Datum, das in einem Objekt hinterlegt ist, in
einem Schaltjahr liegt, so können Sie die Methode isLeapYear() aufrufen. Sie
liefert true zurück, falls es sich um ein Schaltjahr handelt. Alternativ können Sie
auch die statische Methode checkLeapYeart) verwenden, die ein Datumsobjekt
oder eine Jahreszahl übergeben bekommt und ebenfalls einen booleschen Wert
zurückgibt.
Ob das Datum, das in einem Objekt enthalten ist, dem heutigen Tag entspricht,
kann i sToday() Ihnen sagen. Ob es sich um gestern oder morgen handelt, ermit-
teln die Methoden i sYesterday() und i sTomorrow().
Sehr hilfreich ist auch die statische Methode i sDatet). Sie bekommt einen String
übergeben, der eine Zeitinformation enthält bzw. enthalten könnte. Als zweiten
Parameter übergeben Sie eine der Konstanten aus Tabelle 8.6. Optional können
Sie auch noch eine Lokalisierungsinformation als dritten Parameter übergeben.
Mittels true oder false teilt die Methode Ihnen mit, ob der übergebene String
eine korrekte Zeitinformation darstellt. Das kann sehr hilfreich sein, falls Sie ein
Datumsobjekt auf Basis dieses Strings ableiten und dabei keine Exception riskie-
ren wollen.
407
e-bol.net
e-bol.net
Inhalt der CD-ROM
Auf der CD zu diesem Buch finden Sie neben allen Listings, die im Buch enthalten
sind, auch noch weitere Software. Da wäre natürlich zuerst das Zend Framework
zu nennen, das in der aktuellen Version enthalten ist.
Bei Eclipse PDT handelt es sich um die bekannte Entwicklungsumgebung mit
dem neuen PDT Plug-in. Hiermit erhalten Sie eine leistungsfähige, plattform-
unabhängige Entwicklungsumgebung, die kostenlos genutzt werden darf. Sie
bietet viele interessante Features wie zum Beispiel dient- und serverseitiges
Debuggen.
Neben Probekapiteln aus anderen Büchern ist auch das komplette Buch PHP
PEAR von Carsten Möhrke enthalten, in dem Sie umfangreiche Informationen
zum PEAR-Framework finden.
409
e-bol.net
e-bol.net
Index
A auslesen 350
getExpiryTime 348
Access Control List 115 Account-Authentication 250 ACL 115 Action Controller 30 Active Recordset 88 AJAX 306 Amazon 220 APC 137, 150 ASIN 225, 227 Assertion 121 Atom 209, 214 Authentifikation 124 dateibasierend 128 datenbankbasiert 124 AuthSub 250 AWS 220 isExpired 348 isSecure 348 isSession 348 setzen 350 D Datenbank 59 DELETE 77 DISTINCT 82 GROUPBY 82 HAVING 82 INSERT 76 JOIN 83 Konsistenz 103 LIMIT 81 Performance 110
B Prepared Statements 66
Profiling 110
Basename 168 Benutzerauthentifizierung 124 Bildersuche 242 Bogenmaß 326 Bootstrap-File 27 Breitengrad 362 Bugs 16 UPDATE 77 Datenbankunabhängigkeit 59 Datum 395 Ausgabe 397 prüfen 157, 407 rechnen mit 402 Vergleich von 404 Datum korrigieren 379
c Datumsangaben lokalisieren 378
Deklarative referentielle Integrität 103
Cache Einträge löschen 153 Garbage Collection 140 Caching 137 Dateien 147 Debugging 142 Gültigkeitsdauer 139 komplette Seiten 141 MVC 142 CC-Nummern 157 Chain 166 Changelog 16 Controller 25 Cookies 346 DIN-Datum 378 Dokumentation 15 Blogs 15 Wiki 15 DPI 308 DR1 103 E E-Mail-Adresse validieren 157 E-Mails 279 abholen 288 Anhänge 283
411
e-bol.net
Index
Betreff 280 Body 280 CC 281 Content-Disposition 284 Content-Type 284 Dringlichkeit 282 Expunge 298,301 Header 281 Header auslesen 292 HTML 282 Kopie 281 kopieren 304 löschen 298 Ordner 302 Papierkorb 302 Retum-Path 281 SMTP 286 Subject 280 Unique-IDs 299 verschieben 304 verschlüsseln 288 versenden 280 Exception Handling 21 Expunge 298 H Helper 49 hexadezimale Zahlen 160 htaccess 18 HTTP 335 Authentifizierung 341 Cookies 346 Datei-Download 345 Server-Antworten 342 SSL 352 Uploads 340 HTTP-Client 336 1 IIS 28 Installation 17 Internationalisierung 369 IP-Adressen validieren 163 Issue Tracker 16 J
F JOIN 83
JSON 306
Feeds 209 finden 209 generieren 218 Filtern vonDaten 165 Flickr 232 Fließen, Maßeinheiten 386 Fluent Interface 23 Fluid Interface 23 Formulare validieren 175 Front Controller 29 K Kaskadierendes Löschen 109 Kochen, Maßeinheiten 386 Konfiguration 194 Kreditkartennummern 157 L Längengrad 362 lastlnsertld 75
G Laufweite 316
Locale 369
getPost 39 Glyphe 316 GMT 396 Google 245 Calendar 255 Spreadsheets 271 Logfiles 184 Lokalisierung 369 von Zahlen 373 Art m:n-Verknüpfung XE 108 Maildir 288
412
e-bol.net
Index
Mailing-Listen 15
Maßeinheiten 385
konvertieren von 388
rechnen mit 389
mbox 288
Mehrsprachigkeit 381
Memcached 150
MIME-Type 340
mod_rewrite 28
Model 26
Model View Controller -> MVC
Module 33
MVC 25
____call 43
Action Controller 30
assign 36
Beispiel 50
Caching 142
canSendHeaders 48
Controller 25
Controller Benunnung 32
Datenübergabe 36
dispatch 30
Error Handling 42
errorAction 45
ErrorController 45
escape 37
Exception Handling 30
Exceptions 30
Fortgeschrittene Techniken 47
forward 44
Front Controller 29
getParam 39
getPost 39
getRequest 38
getResponse 46
getStaticHelper 49
GET-Werte 38
Header 48
init 47
initView 36
mehrere Controller 32
Model 41
Module 33
Moved Permanently 45
Moved Temporarily 45
Parameter 39
Plug-ins 49
postDispatch 49
POST-Werte 38
preDispatch 49
redirect 45
Request-Objekt 38
setControllerDirectory 30, 31, 33
setParam 44
throwExceptions 30, 45
Übergabe von Werten 38
useDefaultControllerAlways 42
Verarbeitungsschritte 3 7
Verzeichnisstruktur 2 7
View 34
View unterdrücken 30
ViewRenderer 48
MX-Eintrag 158
N
Natural Key 90
Negotiation 370
News-Suche 240
o
One-Time-Use-Token 251
P
Papierkorb 302
PDF 308
Bilder 330
Clipping 331
CMYK 320
Datei 312
drawLine 323
einlesen 333
Ellipse zeichnen 327
Farben 320
Fließtexte 315
Graustufen 321
Kreis zeichnen 326
laden 333
Meta-Informationen 331
Polygon zeichnen 328
Rechteck 324
RGB 320
Schriftarten 313
Schriftschnitt 314
Seiten hinzufugen 309
Text ausgeben 311
413
e-bol.net
Index
TrueType-Font 313 Zeichnen 322 PECL 151 Performance 110 php.ini 17 Plug-ins 49 preDispatch 49 u Übersetzen 371 Unique-IDs 299 URL-Rewriting 28 useDefaultControllerAlways 42 UTC 396
R V
Realm 128 RealPath 170 Rechteverwaltung 115 referentielle Integrität 103 Ressourcen 116 REST 361 Client 362 Server 363 RewriteBase 29 Rewrite-Engine 28 Rewrite-Rule 28 RGB 320 Rollback 73 Rollen 116 RSS 209,211 Validierung 154 Vererbung von Rechten 118 Verzeichnisstruktur 27 View 25, 34 Speicherort 35 Übergabe von Werten 36 View-Helper 54 Viskosität, Maßeinheiten 386 w Währung Namen festlegen 391 Währungsdarstellung 3 89 Webinare 16 Websuche 236
s Whitespaces 171
Schaltjahr 407 Sequenzen 60, 75 Session beenden 132 Save-Handler 133 Schutz von Daten 132 Time-out 132 Sessions 129 SMTP after IMAP 287 SMTP after POP 287 SPL 24 Spreadsheets 271 SQLite 152 Standard Programmers Library 24 X xapi 247 XML-RPC 355 Client 359 Server 356 Y Yahoo! 236 Yahool-Maps 361 z Zeit 395 Ausgabe 397
rechnen mit 402
Tabellenkalkulation 271 Tags entfernen 172 to Lower 170 Transaktionen 73 Vergleich von 404 Zeitangaben lokalisieren 378 Zeitzonen 396
414
e-bol.net
Index
Zenc.Acl Frontend Output 140
isAllowed 118 Frontend Page 141
Zend Platform 152 Frontends 139
Zend.Acl 115 lifetime 139
add 118 load 148
addRole 117 Memchached 150
allow 118 MVC 142, 144
assert 121 remove 154
bekannte Rechte 123 Serialisierung 139
Bootstrap-File 123 start 138, 140
deny 118 Tags 153
getRoleld 117 write_control 139
Manipulieren von Rechten 123 Zend_Client_Http
remove 123 Authentifizierung 341
removeAll 123 Zend_Config 194
removeAllow 123 Arrays 195
removeDeny 123 Default-Werte 196
removeRole 123 INI-Dateien 197
removeRoleAll 123 Konstante 199
Vererbung 118 Sektionen 199
Verfeinerung 120 SimpleXML 201
Zend.Auth 124, 127 XML-Dateien 200
authenticate 127 Zend_Console_Getopt 203
dateibasiert 128 Argumente 207
Datenbank 124 Cluster 204
MVC 129 Flag 203
setCredentialColumn 125 getRemainingArgs 207
setldentityColumn 125 getUserMessage 206
setTableName 125 Hilfe 207
Zend.Cache 137 setHelp 207
APC 150 Zend_Controller_Action 30
ausschalten 139 getResponse 46
automatiC-deaningfactor 140 Zend_Controller_Action_Exception 42
automatic-Serialization 139 Zend_Controller_Dispatcher_Exception
Backend File 149 42
Backends 149 Zend_Controller_Front 27, 29
cacheByDefault 144 getlnstance 29
cachedEntity 146 setControllerDirectory 31, 33
cachedFunctions 145 setParam 30
caching 139 Zend_Controller_Request_Http 38
call 146 Zend_Currency 389
debug_header 142 Caching 394
Einträge löschen 153 getCurrencyList 394
end 138 getName 393
factory 138 getRegionList 394
Frontend Class 146 getSymbol 393
Frontend Core 149 name 391
Frontend File 147 setFormat 391
Frontend Function 144 toCurrency 390
415
e-bol.net
Index
Währungen 390 INSERT 76
Zend_Date 395 JOIN 83
add 402 lastlnsertld 75
checkLeapYear 407 HmitPage 81
compare 405 Optionen 61
compareDate 406 PDO-Optionen 62
compareTime 406 Platzhalter 65
get 398 Platzhalter, benannte 65
getArpa 401 prepare 66
getDate 401 quote 63
getGmtOffset 396 quoteColumnAs 64
getMonth 402 quoteldentißer 64
getTime 401 quotelnto 63
getTimezone 396 quoteTableAs 64
isDate 407 rollBack 73
isEarlier 407 rowCount 68
isLater 407 SELECT 78
isLeapYear 407 Sequenzen 75
isToday 407 setFetchMode 69
isTomorrow 407 Sortierung 82
isYesterday 407 Transaktionen 73
now 395 unterstützte Datenbanken 61
set 400 UPDATE 77
setLocale 395 WHERE 80
setTimezone 396 Zend_Db_Expr 94
sub 402 Zend_Db_Profile
Zeitzonen 396 getLastQueryProßle 112
Zend_Db 59, 65 getQueryProßle 112
Aggregat-Funktionen 79 Zend_Db_Profiler 110
Aliasnamen 64 getProßler 110
auslesen von Daten 69 getTotalElapsedSecs 112
beginTransaction 73 getTotalNumQueries 112
Binding 67 setFilterElapsedSecs 113
bindParam 67 Zend_Db_Select 78
Case-Folding 62 Zend_Db_Table 88
DELETE 77 Abhängigkeiten 100
DISTINCT 82 alternative Syntax 108
execute 66 Einfügen von Daten 93
factory 6Q fetchAll 97
fetchAll 70 fetchRow 97
fetchAssoc 70 findDependentRowset 106
fetchCol 71 insert 93, 94
fetchColumn 71 Kaskadierendes Löschen 109
Fetch-Mode 69 LIMIT 97
fetchObject 71 Löschen von Daten 95
fetchOne 72 Natural Key 90
fetchPairs 72 onDelete 103
GROUP BY 82 onUpdate 103
HAVING 82 Primärschlüssel 90
416
e-bol.net
Index
save 98
SELECT 95
Sequenz 91
setFromArray 98
SetupTableName 89
Tabellenname 89
Zend_Db_Table_Row 95
Zend_Db_Table_Rowset 95
Zend_Feed 209
Atom 214
Attribute auslesen 211
Elemente auslesen 210
generieren von Feeds 218
import 210
importArray 218
RSS 211
Zend_Feed P Feeds
Zend_Filter 165
Absoluter Pfad 170
alphanumerische Zeichen 167
Basename 168
Buchstaben filtern 168
Chain 166
eigene Filter 174
Entitäten 169
Großbuchstaben 171
HTML-Tags 172
Integer- Werte 170
Kleinbuchstaben 170
RealPath 170
setEncoding 170
Whitespaces 171
Ziffern 169
Zend_Filter_Input 175
Escape-Filter 179
Fehlermeldungen 179
getEscaped 178
getMessages 183
getMissing 183
getUnescaped 178
getUnknown 183
haslnvalid 183
hasUnknown 183
setDefaultEscapeFilter 179
Wildcard 176
Zend_Gdata 245
Account-Authentication 250
Allgemeines 246
Authentifikation 246
AuthSub 250
AuthSubRevokeToken 253
Calendar 255
CAPTCHA 248
delete 270
Erstellungsdatum 265
Event-Query 262, 265
eventStatus 260
Exception Handling 254
Feed 256
Geburtstage 269
gelöschte Termine 260
getAuthSubSessionToken 252
getAuthSubTokenUri 251
getCalendarEventEntry 270
getCalendarListFeed 261
getCaptchaToken 248, 250
getCaptchaUrl 248, 250
getCustom 273
getHttpClient 247, 249
getQueryUrl 267
getSpreadsheetFeed 271
insertEvent 267
insertRow 277
Kalendereintrag anlegen 265
Kalendereintragauslesen 270
Kalendereintrag löschen 270
Kalendereinträge ändern 269
magische Methoden 256
newContent 266
newTitle 266
One-Time-Use-Token 251
regelmäßige Termine 269
save 270
setProjection 262
setPublishedMax 265
setPublishedMin 265
setSpreadsheetQuery 276
setUpdatedMax 265
setUpdatedMin 265
setVisibility 262
Sortierreihenfolge 265
Spreadsheets auslesen 271
Structured Query 276
Suche 265
Tabellenblatt auslesen 273
Tabellenfelder auslesen 273
Tabellenzeile aktualisieren 278
Tabellenzeile löschen 277
417
e-bol.net
Index
Tabellenzeilen einfügen 277
Termin anlegen 265
updateRow 278
Veränderungsdatum 265
verschiedene Kalender 261
Volltextsuche 265
Zugriff auf Elemente 256
Zend_Gdata_Entry 256
Zend_Gdata_Feed 256
Zend.Http 335
Zend_Http_Client 336
Adapter 351
addCookie 350
Cookie setzen 350
Cookies 346
Datei-Download 345
getCookies 347
getHeader 345
getHeaders 345
getHeadersAsString 345
isError 343
isRedirect 343
isSuccessfull 343
Port 337
Proxy 351
Redirect 338
request 336
Server-Antworten 342
setAuth 341
setConfig 338
setCookieJar 347
setFileUpload 340
setParameterGet 339
setParameterPost 339
setUri 338
Uploads 340
User-Agent 338
Zend_Http_CookieJar 346
Zend_Http_Response 336
Zend_Json 306
decode 307
encode 306
Zend_Locale 369, 376
checkDate 380
getßrowser 370
getDate 378
getDateFormat 378
getFloat 376
getHttpCharset 371
getlnteger 376
getLanguage 370
getLanguageTranslation 373
getQuestion 373
getTranslationList 371
toFloat 374
tolnteger 376
toNumber 374
Zahlen 373
Zend_Log 184
addFilter 190
addWriter 186
CSV-Format 192
Datenbank 186
eigene Items 194
eigener Log-Level 189
Einträge filtern 190
Einträge formatieren 191
Einträge verwerfen 188
Filtern nach Priorität 191
Formatter 192
log 189
Loglevel 185
Mock-Writer 188
setEventltem 194
Stream-Writer 186
suppress 190
Writer 186
XML-Format 192
Zend_Mail 279
addBcc 281
addCc 281
addHeader 281
addTo 280
Anhänge 283
Anzahl der E-Mails 289
Attachments 283
Bcc 281
Betreff 280
Body 280
CC 281
Content-Disposition 284
Content-Type 284
copyMessage 305
countMessages 289
createAttachment 283
Dringlichkeit 282
E-Mails abholen 288
E-Mails löschen 298
418
e-bol.net
Index
E-Mails versenden 280
Expunge 298,301
Flag entfernen 302
Flags, IMAP 300
getContent 293
getCurrentFolder 304
getFolders 302
getHeader 292
getHeaders 293
getLocalName 302
getMessage 289
getNumberByUniqueld 299
getPart 293
getUniqueld 299
hasFlag 300
Header 281
Header auslesen 292
HTML-E-Mails 282
/MAP 288
IMAP, erweiterte Möglichkeiten 300
isMultipart 293
Kopie 281
kopieren von E-Mails 304
Ordner 302
Papierkorb 302
POP3 288
Recent-Flag 301
removeMessage 298
setBodyHtml 282
setBodyText 280
setFlags 300
setRetumPath 281
setSubject 280
setType 284
SMTP-Server 286
Subject 280
undelete 299
Unique-IDs 299
verschieben von E-Mails 304
Verschlüsseln 288
Zeichensatz 280
Zend_Measure 385
add 389
compare 389
convertTo 388
equals 389
getType 387
setValue 387
sub 389
Zend.Pdf 308
Bilder 330
Clipping 331
CMYK 320
Datei 312
DPI 308
drawCircle 326
drawEllipse 327
drawlmage 330
drawLine 323
drawPolygon 328
drawRectangle 324
drawText 311
einlesen 333
Ellipse 327
Farben 320
Fließtexte 315
fontWithName 311, 313
fontWithPath 313
Füllfarbe 321
Graustufen 321
imageWithPath 330
Kreis 326
laden 333
Linienfarbe 321
load 333
mehrzeilige Texte 315
Meta-Informationen 331
OpenType-Font 313
Polygon 328
Rechteck 324
render 312
RGB 320
save 312
Schriftart 311
Schriftarten 313
Schriftschnitt 314
Seiten hinzufugen 309
setFillColor 321
setFont 311
setLineColor 321
setLineDashingPattem 322
setLineWidth 322
setStyle 311
Stil 310
Stile 309
Text ausgeben 311
TrueType-Font 313
Zeichnen 322
419
e-bol.net
Index
Zend_Rest 361
get 362
MVC 365
post 362
Zend_Rest_Client
setUri 362
Zend_Rest_Server 363
addFunction 364
Fehlerbehandlung 367
setClass 364
Zend_Service_Amazon 220
Antworten 228
ASIN 225
DetailPageURL 225
itemLookup 228
ItemSearch 221
Kategorien 222
Manufacturer 227
Offers 231
Response-Group 228
Rückgabewert 225
Searchlndex 221
Zend_Service_Flickr 232
firstResultPosition 233
Image-Objekt 234
Optionen 233
Seitenweises blättern 232
tagSearch 232
totalResultsAvailable 233
userSearch 232
Zend_Service_Yahoo 236
Bildersuche 242
imageSearch 242
News-Suche 240
searchNews 240
webSearch 236
Websuche 236
Zend_Session 129
expireSession Cookie 132
forgetMe 132
Namespaces 129
rememberMe 132
Save-Handler 133
Session starten 130
setSaveHandler 133
start 130
Time-out 132
Zend_Translate 381
Adapter 381
addTranslation 383
CSV-Dateien 384
Datenquellen 381
Delimiter 385
getMessagelds 384
Gettext 381
isAvailable 384
isTranslated 384
Mastersprache 383
Separator 385
setLocale 384
Zend_Uri 352
URI analysieren 354
Zend_Validate 154
alphanumerische Daten 156
Arrays 163
Datum 157
E-Mail-Adresse 157
Fehlermeldungen 155
Fließkomma 160
getMessages 154
Größer als 160
hexadezimale Zahlen 160
Hostnames 161
Integer-Werte 163
IP-Adressen 163
isValid 154
Kleiner als 164
Kreditkartennummern 157
regulärer Ausdruck 164
setMessage 155
String-Länge 165
Texte 156
Varaible 164
Zahlen 156
Ziffernfolge 157
Zend_View 34, 36
escape 37
Zend_View_Exception 34
Zend_XmlRpc 355
Fehlerbehandlung 358
setClass 357
system-Methoden 357
Zend_XmlRpc_Client 359
getLastRequest 361
ZFForum 16
ZFTutorials 16
Ziffern prüfen 157
420