Php5 Und My SQL In 14 Tagen.pdf - Ausbildung-Elektrotechnik
Php5 Und My SQL In 14 Tagen.pdf - Ausbildung-Elektrotechnik
Php5 Und My SQL In 14 Tagen.pdf - Ausbildung-Elektrotechnik
Erfolgreiche ePaper selbst erstellen
Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.
PHP5 und <strong>My</strong><strong>SQL</strong> in <strong>14</strong> <strong>Tagen</strong>
Unser Online-Tipp<br />
für noch mehr Wissen ...<br />
... aktuelles Fachwissen rund<br />
um die Uhr — zum Probelesen,<br />
Downloaden oder auch auf Papier.<br />
www.<strong>In</strong>formIT.de
PHP5 HP5 und<br />
und<br />
<strong>My</strong>SQ ySQ y<strong>SQL</strong> ySQ
Bibliografische <strong>In</strong>formation Der Deutschen Bibliothek<br />
Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie;<br />
detaillierte bibliografische Daten sind im <strong>In</strong>ternet über abrufbar.<br />
Die <strong>In</strong>formationen in diesem Produkt werden ohne Rücksicht auf einen<br />
eventuellen Patentschutz veröffentlicht.<br />
Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt.<br />
Bei der Zusammenstellung von Texten und Abbildungen wurde mit größter<br />
Sorgfalt vorgegangen.<br />
Trotzdem können Fehler nicht vollständig ausgeschlossen werden.<br />
Verlag, Herausgeber und Autoren können für fehlerhafte Angaben<br />
und deren Folgen weder eine juristische Verantwortung noch<br />
irgendeine Haftung übernehmen.<br />
Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und<br />
Herausgeber dankbar.<br />
Alle Rechte vorbehalten, auch die der fotomechanischen<br />
Wiedergabe und der Speicherung in elektronischen Medien.<br />
Die gewerbliche Nutzung der in diesem Produkt gezeigten<br />
Modelle und Arbeiten ist nicht zulässig.<br />
Fast alle Hardware- und Software-Bezeichnungen, die in diesem Buch<br />
erwähnt werden, sind gleichzeitig auch eingetragene Marken<br />
oder sollten als solche betrachtet werden.<br />
Umwelthinweis:<br />
Dieses Buch wurde auf chlorfrei gebleichtem Papier gedruckt.<br />
10 9 8 7 6 5 4 3 2 1<br />
06 05 04<br />
ISBN 3-8272-63<strong>14</strong>-X<br />
© 2004 by Markt+Technik Verlag,<br />
ein Imprint der Pearson Education Deutschland GmbH,<br />
Martin-Kollar-Straße 10–12, D–81829 München/Germany<br />
Alle Rechte vorbehalten<br />
Lektorat: Boris Karnikowski, bkarnikowski@pearson.de<br />
Herstellung: Philipp Burkart, pburkart@pearson.de<br />
Korrektur: Haide Fiebeler-Krause, Berlin<br />
Satz: reemers publishing services gmbh, Krefeld, (www.reemers.de)<br />
Coverkonzept: independent Medien-Design, München<br />
Coverlayout: Sabine Krohberger<br />
Druck und Verarbeitung: Bercker, Kevelaer<br />
Printed in Germany
<strong>In</strong>haltsverzeichnis<br />
Vorwort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15<br />
Für wen das Buch und die Reihe gedacht sind . . . . . . . . . 15<br />
Unsere Zielgruppe als Leser . . . . . . . . . . . . . . . . . . . . . . . . 16<br />
PHP und ... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16<br />
Ein paar Worte zum Autor . . . . . . . . . . . . . . . . . . . . . . . . . 17<br />
<strong>In</strong> diesem Buch verwendete Konventionen . . . . . . . . . . . . 18<br />
Woche 1 – Wochenvorschau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19<br />
Tag 1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21<br />
1.1 Die Geschichte von PHP . . . . . . . . . . . . . . . . . . . . . . . . . . 22<br />
PHP/FI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22<br />
PHP3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22<br />
PHP 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23<br />
PHP5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23<br />
1.2 PHP auf einen Blick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24<br />
Ein wenig Vorbereitung . . . . . . . . . . . . . . . . . . . . . . . . . . . 24<br />
Mit PHP spielen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />
Ein paar wichtige Techniken . . . . . . . . . . . . . . . . . . . . . . . 27<br />
1.3 Einen Webserver bauen . . . . . . . . . . . . . . . . . . . . . . . . . . . 29<br />
Vorbemerkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29<br />
WAMP vorbereiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29<br />
1.4 Apache installieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30<br />
<strong>In</strong>stallationsstart. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31<br />
Erste Schritte. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31<br />
Apache testen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35<br />
Weitere Einstellungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35<br />
Apache automatisch starten . . . . . . . . . . . . . . . . . . . . . . . . 36<br />
1.5 PHP5 installieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37<br />
1.6 PHP5 konfigurieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37<br />
Die Datei php.ini . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38<br />
Wichtige Konfigurationsschritte . . . . . . . . . . . . . . . . . . . . . 38<br />
5
<strong>In</strong>haltsverzeichnis<br />
6<br />
1.7 PHP5 testen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39<br />
Konfiguration des Webservers für PHP5 . . . . . . . . . . . . . . 40<br />
PHP-Skripte ausführen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41<br />
Wenn es nicht funktioniert. . . . . . . . . . . . . . . . . . . . . . . . . 42<br />
1.8 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43<br />
Tag 2 Erste Schritte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45<br />
2.1 Einfache HTML-Seiten . . . . . . . . . . . . . . . . . . . . . . . . . . . 46<br />
HTML-Refresh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47<br />
Tabellen und Bilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49<br />
Formulare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52<br />
2.2 PHP einbetten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53<br />
Wie PHP den Code erkennt. . . . . . . . . . . . . . . . . . . . . . . . 53<br />
2.3 Ausgaben erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55<br />
Die Ausgabe mit echo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55<br />
Variablen ausgeben. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56<br />
Ausgabe von großen Textmengen . . . . . . . . . . . . . . . . . . . 57<br />
Vielfältige Formatierungen mit print und Verwandten. . . 58<br />
2.4 Professionelles Programmieren . . . . . . . . . . . . . . . . . . . . . 62<br />
Nicht nur für die Nachwelt: Kommentare. . . . . . . . . . . . . 62<br />
Benennungsregeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65<br />
2.5 Webserver und Browser. . . . . . . . . . . . . . . . . . . . . . . . . . . . 69<br />
Prinzip des Seitenabrufs . . . . . . . . . . . . . . . . . . . . . . . . . . . 70<br />
HTTP auf einen Blick . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71<br />
2.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72<br />
Tag 3 Daten verarbeiten. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73<br />
3.1 Variablen und Literale . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74<br />
3.2 Konstanten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76<br />
Konstanten definieren und nutzen. . . . . . . . . . . . . . . . . . . 76<br />
3.3 Rechnen und Vergleichen mit Ausdrücken . . . . . . . . . . . . 78<br />
Ausdrücke und Operatoren. . . . . . . . . . . . . . . . . . . . . . . . . 78<br />
3.4 Allgemeine Aussagen zu Zeichenketten. . . . . . . . . . . . . . . 83<br />
Datentyp und Größe. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83<br />
Umgang mit Sonderzeichen. . . . . . . . . . . . . . . . . . . . . . . . 84<br />
Zeichenketten erkennen und bestimmen . . . . . . . . . . . . . 85<br />
Zeichenkettenoperationen . . . . . . . . . . . . . . . . . . . . . . . . . 86
<strong>In</strong>haltsverzeichnis<br />
3.5 Die Zeichenkettenfunktionen . . . . . . . . . . . . . . . . . . . . . . 86<br />
Suchen und Ersetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87<br />
Ermitteln von Eigenschaften einer Zeichenkette . . . . . . . 90<br />
Teilzeichenketten und Behandlung einzelner Zeichen . . 90<br />
HTML-abhängige Funktionen. . . . . . . . . . . . . . . . . . . . . . 92<br />
3.6 Reguläre Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95<br />
Einführung in die Welt der regulären Ausdrücke . . . . . . . 95<br />
Anwendungsbeispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98<br />
Typische Suchmuster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103<br />
3.7 Datums- und Zeitfunktionen . . . . . . . . . . . . . . . . . . . . . . . 110<br />
Der Timestamp und die Serverzeit . . . . . . . . . . . . . . . . . . 110<br />
Rechnen mit Datums- und Zeitwerten . . . . . . . . . . . . . . . 110<br />
Tricks mit JavaScript: Lokale Zeit ermitteln . . . . . . . . . . . 117<br />
3.8 Formatieren und Ausgeben. . . . . . . . . . . . . . . . . . . . . . . . . 119<br />
Zahlen formatieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119<br />
Umwandeln, Anpassen und Ausgeben . . . . . . . . . . . . . . . . 120<br />
3.9 Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124<br />
Zeichenketten-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . 125<br />
Funktionen für reguläre Ausdrücke . . . . . . . . . . . . . . . . . . 129<br />
Referenz Ausgabe- und Datumsfunktionen . . . . . . . . . . . . 129<br />
3.10 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130<br />
Tag 4 Programmieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131<br />
4.1 Verzweigungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132<br />
Einfache Verzweigungen mit if . . . . . . . . . . . . . . . . . . . . . 132<br />
Alternative Zweige mit else. . . . . . . . . . . . . . . . . . . . . . . . . 136<br />
Mehrfachverzweigungen mit switch . . . . . . . . . . . . . . . . . 137<br />
4.2 Schleifen erstellen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . <strong>14</strong>1<br />
Gemeinsamkeiten aller Schleifenanweisungen. . . . . . . . . <strong>14</strong>1<br />
Die Zählschleife for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . <strong>14</strong>2<br />
Die Universalschleifen do/while und while . . . . . . . . . . . . <strong>14</strong>8<br />
4.3 Benutzerdefinierte Funktionen. . . . . . . . . . . . . . . . . . . . . . 150<br />
Funktionen definieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150<br />
Parameter der Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . 153<br />
Referenzen auf Funktionen und Parameter. . . . . . . . . . . . 159<br />
Statische Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160<br />
Globale Variablen und Konstanten . . . . . . . . . . . . . . . . . . 162<br />
Rekursive Funktionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164<br />
Variable Funktionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166<br />
7
<strong>In</strong>haltsverzeichnis<br />
8<br />
4.4 Modularisierung von Skripten . . . . . . . . . . . . . . . . . . . . . . 168<br />
Module einbinden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168<br />
<strong>In</strong>formationen über Module ermitteln. . . . . . . . . . . . . . . . 172<br />
4.5 Fehlerbehandlung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173<br />
Konventionelle Fehlerbehandlung . . . . . . . . . . . . . . . . . . 174<br />
Fehlerbehandlung mit PHP5 . . . . . . . . . . . . . . . . . . . . . . . 178<br />
4.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180<br />
Tag 5 Daten mit Arrays verarbeiten. . . . . . . . . . . . . . . . . . . . . . . . . . . . 181<br />
5.1 Datenfelder im Einsatz: Arrays. . . . . . . . . . . . . . . . . . . . . . 182<br />
5.2 Arrays erstellen und befüllen . . . . . . . . . . . . . . . . . . . . . . . 182<br />
Ein einfaches Array erstellen . . . . . . . . . . . . . . . . . . . . . . . 183<br />
Die Anzahl der Elemente ermitteln . . . . . . . . . . . . . . . . . . 184<br />
Den <strong>In</strong>dex manipulieren . . . . . . . . . . . . . . . . . . . . . . . . . . 184<br />
Schlüssel statt <strong>In</strong>dizes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185<br />
Arraywerte schneller zuweisen . . . . . . . . . . . . . . . . . . . . . . 186<br />
5.3 Arrays manipulieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187<br />
Werte entfernen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187<br />
Der Arrayzeiger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188<br />
Arrayfunktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191<br />
Arraydaten manipulieren . . . . . . . . . . . . . . . . . . . . . . . . . . 193<br />
5.4 Arrays ausgeben. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193<br />
Arrays zählbar ausgeben: for . . . . . . . . . . . . . . . . . . . . . . . . 193<br />
Beliebige Arrays mit foreach durchlaufen . . . . . . . . . . . . . 194<br />
Einfache Arrays mit while, each, list ausgeben . . . . . . . . . 195<br />
Array mit array_walk durchlaufen . . . . . . . . . . . . . . . . . . . 196<br />
Fehlersuche mit print_r . . . . . . . . . . . . . . . . . . . . . . . . . . . 196<br />
5.5 Referenz der Arrayfunktionen . . . . . . . . . . . . . . . . . . . . . . 198<br />
5.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202<br />
Tag 6 Objektorientierte Programmierung . . . . . . . . . . . . . . . . . . . . . . 203<br />
6.1 Warum objektorientiert programmieren?. . . . . . . . . . . . . . 205<br />
6.2 Syntax der Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207<br />
Eine Klasse definieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207<br />
Ein Objekt erzeugen und benutzen. . . . . . . . . . . . . . . . . . 208<br />
Eine Klasse erweitern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208<br />
Zugriffskontrolle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
<strong>In</strong>haltsverzeichnis<br />
6.3 Schnittstellen zur Außenwelt . . . . . . . . . . . . . . . . . . . . . . . 220<br />
Schnittstellen (<strong>In</strong>terfaces) . . . . . . . . . . . . . . . . . . . . . . . . . . 221<br />
<strong>In</strong>formationen über Klassen und Objekte . . . . . . . . . . . . . 223<br />
Eigene Fehlerklassen erstellen . . . . . . . . . . . . . . . . . . . . . . 224<br />
Spezielle Zugriffsmethoden für Klassen. . . . . . . . . . . . . . . 227<br />
6.4 Analyse und Kontrolle von Objekten . . . . . . . . . . . . . . . . . 232<br />
__METHOD__ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233<br />
Zeichenkettenform: __toString() . . . . . . . . . . . . . . . . . . . . 234<br />
Abstammung von Klassen und Objekten . . . . . . . . . . . . . . 235<br />
6.5 Referenz der OOP-Funktionen . . . . . . . . . . . . . . . . . . . . . 239<br />
OOP-Schlüsselwörter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239<br />
OOP-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240<br />
6.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242<br />
Tag 7 Das Dateisystem entdecken. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243<br />
7.1 Dateizugriff organisieren. . . . . . . . . . . . . . . . . . . . . . . . . . . 244<br />
Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244<br />
Einsatzfälle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245<br />
7.2 Praktischer Dateizugriff . . . . . . . . . . . . . . . . . . . . . . . . . . . 246<br />
Prinzipien des Dateizugriffs . . . . . . . . . . . . . . . . . . . . . . . . 246<br />
Wichtige Konstanten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249<br />
Nachrichtenquelle für eine News-Seite . . . . . . . . . . . . . . . 250<br />
Beliebige Code-Dateien ausgeben . . . . . . . . . . . . . . . . . . . 254<br />
Protokolldatei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255<br />
7.3 Verzeichniszugriff . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257<br />
<strong>In</strong>halt eines Verzeichnisses anzeigen . . . . . . . . . . . . . . . . . 257<br />
Das Verzeichnisobjekt dir und verwandte Funktionen . . . 260<br />
Rekursive Dateiliste. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263<br />
Verzeichniszugriff mit Iteratoren . . . . . . . . . . . . . . . . . . . . 265<br />
7.4 Funktions-Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268<br />
Datei-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268<br />
Prozess-Funktionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272<br />
7.5 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273<br />
9
<strong>In</strong>haltsverzeichnis<br />
Woche 2 – Wochenvorschau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275<br />
Tag 8 Formular- und Seitenmanagement. . . . . . . . . . . . . . . . . . . . . . . 277<br />
8.1 Grundlagen in HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278<br />
Viel HTML – wenig PHP. . . . . . . . . . . . . . . . . . . . . . . . . . 278<br />
8.2 Auswerten der Daten aus Formularen . . . . . . . . . . . . . . . . 280<br />
Grundlegende Schritte . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280<br />
Auswertung von Formularen . . . . . . . . . . . . . . . . . . . . . . . 283<br />
Textfelder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285<br />
Optionsfelder und Kontrollkästchen . . . . . . . . . . . . . . . . . 287<br />
Dropdown-Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295<br />
8.3 Professionelle Formulare und »Sticky Forms« . . . . . . . . . . 301<br />
Ausfüllhinweise und Feldvorgaben . . . . . . . . . . . . . . . . . . 302<br />
Ausfüllhilfen mit JavaScript . . . . . . . . . . . . . . . . . . . . . . . . 303<br />
Fehlerangaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309<br />
Sticky Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315<br />
Mehrseitige Formulare mit versteckten Feldern . . . . . . . . 325<br />
8.4 Dateien hochladen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334<br />
Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334<br />
Prinzip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335<br />
Konfigurationsmöglichkeiten . . . . . . . . . . . . . . . . . . . . . . . 340<br />
8.5 Von der Seite zum Projekt . . . . . . . . . . . . . . . . . . . . . . . . . 341<br />
Die HTTP-Methode GET . . . . . . . . . . . . . . . . . . . . . . . . . 341<br />
Daten per URL übermitteln . . . . . . . . . . . . . . . . . . . . . . . . 344<br />
Sicherheitsprobleme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346<br />
8.6 Cookies und Sessions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349<br />
Cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349<br />
Session-Verwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358<br />
8.7 Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365<br />
Wichtige Systemarrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365<br />
Server- und Umgebungsvariablen . . . . . . . . . . . . . . . . . . . 365<br />
Session-Verwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368<br />
8.8 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372<br />
Tag 9 Professionelle Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . 373<br />
9.1 Mehrsprachige Webseiten . . . . . . . . . . . . . . . . . . . . . . . . . 374<br />
Browserdaten erkennen . . . . . . . . . . . . . . . . . . . . . . . . . . . 374<br />
Lokalisierung und Formatierung von Zeichen . . . . . . . . . 376<br />
10
<strong>In</strong>haltsverzeichnis<br />
9.2 Dynamisch Bilder erzeugen . . . . . . . . . . . . . . . . . . . . . . . . 379<br />
Prinzip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379<br />
Einführung in die Grafikbibliothek GD2 . . . . . . . . . . . . . 381<br />
Anwendungsbeispiel »Dynamischer Werbebanner« . . . . . 386<br />
9.3 Code röntgen: Die Reflection-API . . . . . . . . . . . . . . . . . . . 395<br />
Die Reflection-API als Objektmodell . . . . . . . . . . . . . . . . . 395<br />
Die Reflection-Klassen im Detail . . . . . . . . . . . . . . . . . . . . 397<br />
9.4 Funktions-Referenz GD2 . . . . . . . . . . . . . . . . . . . . . . . . . . 407<br />
9.5 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413<br />
Tag 10 Kommunikation per HTTP, FTP und E-Mail. . . . . . . . . . . . . . 415<br />
10.1 Konzepte in PHP5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416<br />
Prinzip der Streams und Wrapper . . . . . . . . . . . . . . . . . . . 416<br />
Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418<br />
10.2 Streams und Wrapper anwenden . . . . . . . . . . . . . . . . . . . . 419<br />
Daten von einer fremden Website beschaffen . . . . . . . . . . 419<br />
Daten komprimiert speichern. . . . . . . . . . . . . . . . . . . . . . . 420<br />
10.3 Filter verwenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422<br />
10.4 Die Stream-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . 425<br />
Eigene Wrapper und Filter. . . . . . . . . . . . . . . . . . . . . . . . . 425<br />
Anwendung spezifischer Stream-Funktionen . . . . . . . . . . 425<br />
10.5 E-Mail versenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426<br />
Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427<br />
Vorbereitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429<br />
Praktische Umsetzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430<br />
10.6 Funktions-Referenz Stream-Funktionen . . . . . . . . . . . . . . 440<br />
10.7 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 442<br />
Tag 11 Datenbankprogrammierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443<br />
11.1 Prinzip der Datenbankprogrammierung . . . . . . . . . . . . . . 444<br />
11.2 Die universelle Abfragesprache <strong>SQL</strong> . . . . . . . . . . . . . . . . . 444<br />
Was ist <strong>SQL</strong>? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445<br />
Tabellen und Abfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445<br />
Tabellen anlegen und füllen . . . . . . . . . . . . . . . . . . . . . . . 448<br />
Aktualisieren und Löschen von Daten . . . . . . . . . . . . . . . . 452<br />
Fortgeschrittene Abfragen mit SELECT . . . . . . . . . . . . . . 453<br />
Verknüpfungen zwischen Tabellen . . . . . . . . . . . . . . . . . . 457<br />
Fortgeschrittene <strong>SQL</strong>-Techniken. . . . . . . . . . . . . . . . . . . . 459<br />
11
<strong>In</strong>haltsverzeichnis<br />
12<br />
11.3 Der <strong>My</strong><strong>SQL</strong>-Dialekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461<br />
Grobe Abweichungen vom <strong>SQL</strong>92-Standard . . . . . . . . . . 461<br />
Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 462<br />
Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 464<br />
11.4 Erste Schritte mit <strong>My</strong><strong>SQL</strong> und <strong>My</strong><strong>SQL</strong>i . . . . . . . . . . . . . . 477<br />
<strong>My</strong><strong>SQL</strong>i vorbereiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477<br />
Verbindung testen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 478<br />
Mit der Datenbank arbeiten . . . . . . . . . . . . . . . . . . . . . . . . 479<br />
11.5 Referenz <strong>My</strong><strong>SQL</strong>i. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491<br />
11.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 496<br />
Tag 12 Die integrierte Datenbank <strong>SQL</strong>ite . . . . . . . . . . . . . . . . . . . . . . . 497<br />
12.1 Hintergrund. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 498<br />
12.2 Vor- und Nachteile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499<br />
Wann <strong>SQL</strong>ite vorteilhaft ist . . . . . . . . . . . . . . . . . . . . . . . . 499<br />
Wann <strong>SQL</strong>ite nachteilig ist . . . . . . . . . . . . . . . . . . . . . . . . 499<br />
12.3 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 500<br />
Eine einfache Beispielanwendung . . . . . . . . . . . . . . . . . . . 500<br />
Eine Benutzerverwaltung mit <strong>SQL</strong>ite . . . . . . . . . . . . . . . . 501<br />
Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505<br />
12.4 Referenz <strong>SQL</strong>ite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506<br />
12.5 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 508<br />
Tag 13 Datenbanklösungen mit <strong>My</strong><strong>SQL</strong> . . . . . . . . . . . . . . . . . . . . . . . . 509<br />
13.1 Bibliotheks-Verwaltung mit <strong>My</strong><strong>SQL</strong>. . . . . . . . . . . . . . . . . . 510<br />
Schrittfolge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510<br />
Weitere Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510<br />
Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510<br />
13.2 Vorbereitung der Datenbank. . . . . . . . . . . . . . . . . . . . . . . . 512<br />
Datenbank anlegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 512<br />
Tabellen anlegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513<br />
13.3 Die Seiten des Projekts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 516<br />
Vorbereitungen – das Template-System. . . . . . . . . . . . . . . 516<br />
Funktionsweise des Template-Systems. . . . . . . . . . . . . . . . 517<br />
Der Code der Seiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 523<br />
Die Geschäftslogik des Ausleihvorgangs . . . . . . . . . . . . . . 537<br />
13.4 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 546<br />
13.5 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 547
<strong>In</strong>haltsverzeichnis<br />
Tag <strong>14</strong> XML und Webservices. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 549<br />
<strong>14</strong>.1 Vorbemerkungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 550<br />
XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 550<br />
XSLT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 553<br />
XPath . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 562<br />
Ein konkretes XML-Format: RSS. . . . . . . . . . . . . . . . . . . . 568<br />
<strong>14</strong>.2 Einführung in die libxml2 . . . . . . . . . . . . . . . . . . . . . . . . . 575<br />
Neu in PHP5. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 575<br />
Die neuen DOM-Funktionen . . . . . . . . . . . . . . . . . . . . . . 576<br />
<strong>14</strong>.3 SimpleXML. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 581<br />
Unterschiede zur DOM-Schnittstelle . . . . . . . . . . . . . . . . 581<br />
Ein Schritt weiter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 583<br />
XML-Namensräume . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 588<br />
<strong>14</strong>.4 SOAP-Webservices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 593<br />
Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 593<br />
Der Amazon-Webservice: ein Überblick . . . . . . . . . . . . . . 597<br />
Webservices konsumieren . . . . . . . . . . . . . . . . . . . . . . . . . 607<br />
<strong>14</strong>.5 Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 620<br />
Referenz SimpleXML. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 620<br />
Referenz DOM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 621<br />
<strong>14</strong>.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 626<br />
Anhang A Antworten auf die Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . 627<br />
Stichwortverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 643<br />
13
Vorwort<br />
Sie möchten PHP lernen? Kein Problem, nehmen Sie sich ein Buch, ein paar<br />
Tage Zeit und schon können Sie PHP?! Leider ist das nicht ganz so, denn PHP ist<br />
keine isolierte Sprache auf der einsamen <strong>In</strong>sel der glücklichen Programmierer,<br />
sondern in ein komplexes System aus Standards, anderen Sprachen und Protokollen<br />
eingebunden. Um PHP erfolgreich einsetzen zu können, müssen Sie sehr viel<br />
mehr beherrschen und wissen, als der reine Sprachkern erfordert.<br />
Der erste Tag soll deshalb dazu dienen, die Begriffe und Abhängigkeiten zu sortieren,<br />
Sie mit den wichtigsten Techniken vertraut zu machen und so die Grundlagen<br />
für schnelle Lernfortschritte zu legen.<br />
Sie erfahren hier,<br />
an welche Zielgruppe sich dieses Buch wendet,<br />
wie dieses Buch aufgebaut ist und wie Sie es benutzen sollten, um maximalen<br />
Nutzen daraus zu ziehen,<br />
wie sich PHP entwickelt hat und warum manches so ist, wie es sich heute darstellt,<br />
wie das PHP in die Welt der Webserver und deren Protokolle und Techniken<br />
eingebunden ist,<br />
wo Sie im <strong>In</strong>ternet mehr Quellen finden und wie Sie Hilfe bei praktischen<br />
Problemen erhalten.<br />
Für wen das Buch und die Reihe gedacht sind<br />
Das vorliegende Buch ist Teil der sehr erfolgreichen »<strong>14</strong>-Tage«-Reihe. Warum<br />
<strong>14</strong> Tage? An wen wurde dabei als Zielgruppe gedacht?<br />
15
16<br />
Vorwort<br />
Unsere Zielgruppe als Leser<br />
Das Buch wendet sich an Leser, die bisher mit anderen Skriptsprachen oder auch<br />
nur mit HTML gearbeitet haben oder sich in der <strong>Ausbildung</strong> befinden. Elementare<br />
Grundlagen der Programmierung sind hilfreich, vieles lässt sich aber auch gut<br />
aus dem Zusammenhang erschließen.<br />
Dieses Buch ist so ausgelegt, dass Sie jedes Kapitel an einem Tag durcharbeiten<br />
können. Nach nur <strong>14</strong> <strong>Tagen</strong> haben Sie dann alle wichtigen Funktionen kennen<br />
gelernt und können selbst PHP-Programme schreiben. Der Weg zum professionellen<br />
Programmierer ist freilich weit.<br />
Die ersten Erfolge und die steile Lernkurve sollten Sie ermutigen, sich fortan zügig<br />
in spezifische Probleme einzuarbeiten und dort die Feinheiten der jeweiligen<br />
Funktion zu verstehen. Dieses Buch kann und will nicht jedes Detail behandeln.<br />
Eine umfassende Darstellung würde gut den zehnfachen Umfang erfordern. Um<br />
in einer überschaubaren Zeit zu greifbaren Ergebnissen zu gelangen, müssen Sie<br />
sich auf die entscheidenden 20% konzentrieren, mit denen Sie 80% aller Alltagsaufgaben<br />
meistern können.<br />
Um einen intensiven Lerneffekt zu erzielen, finden Sie am Ende jedes Tages<br />
einige Fragen. Beantworten Sie diese und schlagen Sie bei Schwierigkeiten nochmals<br />
nach. Lassen Sie den Tag immer Revue passieren und wiederholen sie selbstständig<br />
Abschnitte, die Ihnen besonders kompliziert erschienen. Die <strong>14</strong> Tage sind<br />
kein Zwang, sondern helfen vor allem bei der zielgerichteten Erarbeitung der Aufgabenstellung.<br />
PHP und ...<br />
PHP als Skriptsprache zur Webserverprogrammierung steht nicht isoliert da. Tatsächlich<br />
können Sie PHP niemals erfolgreich einsetzen, wenn Sie sich nicht mit<br />
den Technologien auseinander setzen, die rund um den Webserver benutzt werden.<br />
An erster Stelle steht natürlich HTML. Ohne HTML-Kenntnisse werden Sie kein<br />
einziges brauchbares PHP-Programm erstellen. Damit eng verbunden ist Java-<br />
Script, das HTML an einigen Stellen etwas unter die Arme greift. Ohne JavaScript<br />
kommen Sie aus, bleiben aber immer in der Amateur-Liga.<br />
Gleiches gilt für Datenbanken und die Datenbankabfragesprache <strong>SQL</strong>. Es gibt<br />
viele kleine und gute Programme, die auf eine Datenbankanbindung verzichten.
Für wen das Buch und die Reihe gedacht sind<br />
Praktisch jedes größere Projekt baut jedoch darauf auf, und es sind signifikante<br />
Vorteile, die den Einsatz von Datenbanksystemen in so breiter Front vorangetrieben<br />
haben. <strong>SQL</strong> spielt deshalb in diesem Buch eine bedeutende Rolle. <strong>In</strong> engem<br />
Zusammenhang damit steht XML – eingesetzt zum Datenaustausch und zur<br />
Schaffung von universellen Datenschnittstellen. Auch dieses Thema nimmt breiten<br />
Raum ein und zwingt so auch den Leser zur aktiven Auseinandersetzung<br />
damit.<br />
Zwischen Browser und Webserver pendeln die Daten nicht im luftleeren Raum.<br />
Das Web basiert auf Protokollen, die den Verkehr regeln – allen voran HTTP. Da<br />
Sie als PHP-Entwickler die Kontrolle über den Webserver haben, nehmen Sie die<br />
Rolle des Verkehrspolizisten ein. <strong>Und</strong> an dieser Stelle müssen Sie zwangsläufig<br />
HTTP kennen. Damit eng verbunden sind die anderen im <strong>In</strong>ternet verwendeten<br />
Protokolle wie POP3 und SMTP für E-Mail oder FTP für Dateiübertragungen.<br />
Ein paar Worte zum Autor<br />
Jörg Krause, Jahrgang 1964, wohnt mit Familie in Berlin. Er<br />
arbeitet als freier Autor, Systemprogrammierer und Consultant.<br />
Seine Arbeitsschwerpunkte sind die<br />
Programmierung von <strong>In</strong>ternetapplikationen und Datenbanken<br />
mit PHP, ASP, C#, XML/XSL, HTML, MS <strong>SQL</strong>-<br />
Server, <strong>My</strong><strong>SQL</strong>,<br />
Programmierung von Windows-Applikationen mit .NET-<br />
Technologie,<br />
Consulting für Start-Ups und »Old Economy«,<br />
Seminare über Webserverprogrammierung (HTML, ASP, PHP) ,<br />
und journalistische Arbeiten.<br />
Sie finden außerdem regelmäßig Artikel von ihm in Fachzeitschriften wie iX, dotnet-<br />
oder PHP-Magazin und können seine Vorträge auf Fachkonferenzen erleben.<br />
Des Weiteren veröffentlichte er mehr als 30 Fachbücher zu Themen wie Windows<br />
2000, Windows XP, Microsoft Active Server Pages, ASP.NET, C#, VB.NET, PHP<br />
sowie Electronic Commerce und Online Marketing.<br />
17
18<br />
Vorwort<br />
<strong>In</strong>teressierten Lesern steht der Autor für professionelle Softwareentwicklung und<br />
Programmierung zur Verfügung. Dies betrifft sowohl Web- als auch Windows-<br />
Applikationen. Professionelles Know-how finden Sie über Beratung, Coaching,<br />
Training, Schulung und Consulting.<br />
<strong>In</strong> diesem Buch verwendete Konventionen<br />
Dieses Buch enthält spezielle Icons, mit denen wichtige Konzepte und <strong>In</strong>formationen herausgestrichen<br />
werden sollen.<br />
Ein Hinweis enthält interessante <strong>In</strong>formationen zum behandelten Thema.<br />
Ein Tipp gibt Ihnen Ratschläge oder zeigt Ihnen einfachere Wege zur Lösung<br />
eines Problems auf.<br />
Ein Achtungszeichen weist Sie auf mögliche Probleme hin und hilft Ihnen,<br />
schwierigen Situationen aus dem Wege zu gehen.<br />
<strong>In</strong> Listings zeigt Ihnen dieses Zeichen, dass die laufende Zeile nur aus satztechnischen<br />
Gründen umbrochen wurde. Sie müssen hier also keinen Return setzen.
Tag g 1 Einführung 21<br />
Tag g 2 Erste Schritte 45<br />
Tag g 3 Daten verarbeiten 73<br />
Tag g 4 Programmieren 131<br />
Tag g 5 Daten mit Arrays verarbeiten 181<br />
Tag g 6 Objektorientierte Programmierung 203<br />
Tag g 7 Das Dateisystem entdecken 243<br />
Tag g 8 Formular- und Seitenmanagement 277<br />
Tag g 9 Professionelle Programmierung 373<br />
Tag g g 10 10 10 Kommunikation per HTTP, FTP und E-Mail 415<br />
Tag g 11 11 11 Datenbankprogrammierung 443<br />
Tag g 12 12 Die integrierte Datenbank <strong>SQL</strong>ite 497<br />
Tag g 13 13 Datenbanklösungen mit <strong>My</strong><strong>SQL</strong> 509<br />
Tag g <strong>14</strong> <strong>14</strong> XML und Webservices 549<br />
W O<br />
C HE<br />
W OC<br />
H E
Einführung<br />
1
22<br />
Einführung<br />
1.1 Die Geschichte von PHP<br />
Dieser Abschnitt zeigt einen kurzen Abriss der Geschichte von PHP, die zwar erst<br />
kurz aber dafür umso beeindruckender ist.<br />
PHP/FI<br />
1995 entwickelte der damals erst 17-jährige Däne Rasmus Lerdorf unter dem<br />
Namen PHP/FI einen Satz von Perl-Skripten zur Erfassung der Zugriffe auf seine<br />
Website. Diese so genannten »Personal Home Page (Tools)/Forms <strong>In</strong>terpreter«<br />
setzte er später noch einmal in der Sprache C um. Das führte zu mehr Leistung<br />
und höherer Geschwindigkeit, als dies mit den damaligen Perl-Werkzeugen möglich<br />
war. PHP/FI kannte Variablen, konnte Formularinhalte interpretieren und<br />
besaß eine Perl-ähnliche Syntax, die allerdings noch relativ inkonsistent war.<br />
Lerdorf entschied sich, den Quellcode von PHP/FI im Sinne der Debian Free<br />
Software Guidelines freizugeben. Damit war der Weg für die einfache Entwicklung<br />
dynamischer Websites frei.<br />
Im November 1997 wurde PHP/FI 2.0 offiziell freigegeben; nach eigenen Angaben<br />
kam die Software auf ca. 50.000 Sites zum Einsatz. Obwohl bereits mehrere<br />
Entwickler am Quellcode mitarbeiteten, leistete nach wie vor Lerdorf den Löwenanteil<br />
der Arbeit. PHP/FI 2.0 wurde kurz darauf durch die erste Alphaversion von<br />
PHP3 abgelöst.<br />
PHP3<br />
Im gleichen Jahr starteten Andi Gutmans und Zeev Suraski eine Kooperation mit<br />
Rasmus Lerdorf. Auf der Basis von PHP/FI 2.0 aufbauend kündigten sie PHP 3.0<br />
als offiziellen Nachfolger an, denn die bestehende Leistungsfähigkeit von PHP/FI<br />
2.0 hatte sich vor allem im Bereich von eCommerce-Applikationen als nicht<br />
ausreichend erwiesen. Die neue Sprache sollte unter einem neuen Namen veröffentlicht<br />
werden; vor allem deswegen, um die im alten Namen implizierte eingeschränkte<br />
Nutzung aufzugeben. Man einigte sich auf PHP, das – ganz im Stile des<br />
GNU-Projektes – ein rekursives Akronym für »PHP Hypertext Preprocessor« darstellt.<br />
PHP Version 3.0 wurde im Juni 1998 nach einer neunmonatigen öffentlichen<br />
Testphase offiziell freigegeben.
Die Geschichte von PHP<br />
PHP3 überzeugte vor allem durch seine Erweiterungsmöglichkeiten, die solide<br />
<strong>In</strong>frastruktur, die Möglichkeit einer objektorientierten Syntax sowie die Unterstützung<br />
der gängigsten Datenbanken. Dutzende von Entwicklern sahen diese Stärken<br />
und beteiligten sich mit neuen Modulen an PHP3. Möglicherweise war gerade die<br />
Zusammenarbeit von vielen Entwicklern einer der Gründe für den gewaltigen<br />
Erfolg von PHP. Ende 1998 wurde PHP von einigen Zehntausend Benutzern verwendet.<br />
Die <strong>In</strong>stallationen von PHP deckten dabei schätzungsweise 10% aller<br />
Websites im Netz ab.<br />
PHP 4<br />
Bereits kurz nach dem offiziellen Release von PHP3 begannen Gutmans und<br />
Suraski, den Kern von PHP umzuschreiben. Die Leistung komplexer Applikationen<br />
sollte gesteigert werden und der Basiscode sollte modularer werden. Mitte<br />
1999 wurde die neue PHP-Engine unter dem Namen »Zend« (gebildet aus Teilen<br />
der Vornamen Zeev und Andi) erstmalig eingeführt, der auch Namensgeber der<br />
Firma der beiden ist: Zend Ltd. (www.zend.com). Ein Jahr später konnte PHP4.0<br />
offiziell freigegeben werden. Basierend auf der »Zend«-Engine glänzte diese Version<br />
neben ihrer stark verbesserten Leistung vor allem durch ihre Unterstützung<br />
verschiedenster Webserver, HTTP-Sessions, Ausgabepufferung und vielen neuen<br />
Sprachkonstrukten.<br />
PHP 4 wird weltweit von unzähligen Entwicklern erfolgreich eingesetzt. Mit mehreren<br />
Millionen <strong>In</strong>stallationen (schätzungsweise 20% aller Websites) ist PHP<br />
damit eine der erfolgreichsten Entwicklungen im dynamischen <strong>In</strong>ternet.<br />
PHP5<br />
Die Arbeit an der Spezifikation und der Verbesserung der »Zend«-Engine als Basis<br />
für die neuen Leistungsmerkmale von PHP5 ist der nächste Schritt. Obwohl<br />
PHP 4 bereits optimal an die Aufgabe »Webserverprogrammierung« angepasst ist<br />
und vielen Entwicklern ein verlässliches Werkzeug in die Hand gibt, blieben Kritikpunkte<br />
bestehen. Vor allem die Profilager, wo mit C++ und Java programmiert<br />
wird, bemängelten die eingeschränkten Fähigkeiten der Sprache im Bereich<br />
Objektorientierung (OOP). Eine weitere wichtige Funktion war die Möglichkeit,<br />
einen Applikationsserver aufzubauen. Skriptprogramme laufen normalerweise nur<br />
auf <strong>In</strong>itiative des Benutzers ab, der über seinen Browser ein Programm startet.<br />
Applikationsserver können selbstständig Programme agieren lassen. Diese Forderung<br />
wurde mit PHP5 nicht erfüllt.<br />
23
24<br />
Einführung<br />
Die OOP-Fähigkeiten sind mit der Version 5 deutlich verbessert worden. Sie werden<br />
sich damit an gleich zwei <strong>Tagen</strong> intensiv beschäftigen können. Von einem<br />
Applikationsserver ist allerdings nach wie vor weit und breit nichts zu sehen. Dies<br />
wird »Skripter« freuen, denen die Einfachheit des Systems am Herzen liegt, und<br />
das Profilager zu Recht an Java, C# und C++ festhalten lassen.<br />
PHP5 ist ein gutes, professionelles und leistungsfähiges Tool geworden, mit dem<br />
sich kleine und mittlere Projekte schnell und effizient erstellen lassen. Diesen<br />
Markt sollte man vor Augen haben und verstehen, dass PHP5 keine ernst zu nehmende<br />
Konkurrenz für Java und ASP.NET ist, auch wenn diese Tatsache einige<br />
Vertreter der PHP-Community partout nicht verstehen wollen.<br />
1.2 PHP auf einen Blick<br />
Wenn Sie neugierig sind und darauf brennen, schnell in PHP einzusteigen, sollten<br />
Sie sich nicht zurückhalten lassen. Es ist durchaus hilfreich, schon mal ein paar<br />
Skripte laufen zu lassen, bevor die ganzen theoretischen Schritte durchgegangen<br />
werden.<br />
Dieser Abschnitt setzt voraus, dass Sie bereits Zugriff auf einen PHP-Server haben.<br />
Falls Sie die lokale <strong>In</strong>stallation, die am zweiten Tag behandelt wird, noch nicht<br />
vorziehen möchten und auch keinen Provider haben, der Skripte ausführt, gehen<br />
Sie auf den Webserver des Autors: www.php5.comzept.de.<br />
Dort können Sie die hier gezeigten Skripte eingeben und ausführen. Aus Sicherheitsgründen<br />
werden aber andere als die hier gezeigten Funktionen nicht akzeptiert.<br />
Ein wenig Vorbereitung<br />
Um Skriptdateien ausführen zu können, brauchen Sie einen Webserver. Wenn<br />
Sie sich damit auskennen, nutzen Sie den Ihnen bekannten Webserver und legen<br />
Sie ein virtuelles Verzeichnis mit dem Namen »MuT« an, in das Sie dann alle<br />
Skripte speichern. Rufen Sie die fertigen Skripte nun wie folgt auf:<br />
http://localhost/MuT/
PHP auf einen Blick<br />
Statt steht der Name des Skripts, beispielsweise listing1.php. Die<br />
Dateierweiterung benötigt der Webserver, um die Datei an den PHP-<strong>In</strong>terpreter<br />
weiterzureichen. Genauer wird dies am zweiten Tag erläutert.<br />
Wenn Sie die Codes des Programms sehen oder der Browser das Skript zum Herunterladen<br />
anbietet, ist die gewählte Dateierweiterung .php unbekannt oder PHP<br />
ist nicht oder falsch installiert. Zur Lösung dürfen Sie schon mal zum zweiten Tag<br />
blättern und schmulen.<br />
Mit PHP spielen<br />
PHP wird in HTML eingebettet. Das heißt, Sie erstellen primär eine einfache<br />
HTML-Seite und bringen darin PHP unter. Der erste Schritt besteht deshalb in<br />
der Beschaffung einer geeigneten HTML-Datei:<br />
Listing 1.1: Basis aller Versuche: Eine einfache HTML-Datei (html1.php)<br />
<br />
<br />
Listing 1<br />
<br />
<br />
Unser erster Test<br />
<br />
<br />
Speichern Sie diesen Text unter html1.php im Übungsverzeichnis des Webservers.<br />
Führen Sie ihn dann aus, indem Sie im Browser folgende Zeile eingeben:<br />
http://localhost/MuT/html1.php<br />
Wenn es nicht funktioniert, probieren Sie statt localhost die lokale IP-Adresse<br />
127.0.0.1. Wenn der Webservers nicht lokal (auf der Entwicklungsmaschine) läuft,<br />
müssen Sie den Namen oder die IP-Adresse anstellte von localhost angeben. Am<br />
zweiten Tag werden ein paar Tipps vorgestellt, wie man ein Entwicklungssystem<br />
geschickt aufbaut.<br />
Nun geht es aber wirklich los! Das erste Skript diente nur als Test für die Funktion<br />
des Webservers und von PHP. Es enthielt freilich noch keinen PHP-Code. Eine<br />
kleine Erweiterung soll nun auch PHP ins Spiel bringen:<br />
25
26<br />
Einführung<br />
Listing 1.2: PHP erzeugt Text<br />
<br />
<br />
Listing 2<br />
<br />
<br />
Unser erster Test<br />
<br />
<br />
<br />
Hier sind Sie nun gleich mit mehreren Techniken konfrontiert worden, die elementar<br />
für die PHP-Programmierung sind. Auch wenn es nur vier neue Zeilen<br />
sind, passiert hier jedoch Erstaunliches. Typisch für die Programmierung ist es,<br />
dass fast jedes Zeichen eine Bedeutung hat. Programmierer arbeiten sehr effizient,<br />
deshalb wird viel abgekürzt und viel mit Symbolen gearbeitet. Zeilenweise<br />
betrachtet sieht dies nun folgendermaßen aus:<br />
PHP auf einen Blick<br />
?><br />
Damit alles seine Ordnung hat, wird der PHP-Abschnitt korrekt beendet. Der<br />
Rest ist nur HTML und wird unverändert vom Webserver ausgegeben.<br />
Programmierung mit PHP ist nun eigentlich ganz einfach. Es kommt darauf an,<br />
die richtigen Funktionen und Befehle in der richtigen Reihenfolge aufzuschreiben.<br />
Damit es Spaß macht, bietet PHP über 2.000 solcher Funktionen in über<br />
100 Bibliotheken und dazu noch reichlich Sprachbefehle und Operatoren. <strong>In</strong>sofern<br />
ist PHP nun doch nicht so ganz einfach...<br />
Ein paar wichtige Techniken<br />
Das Beispiel zeigte freilich nur wenig. Etwas mehr Systematik bietet dieser<br />
Abschnitt. Zuerst sollten Sie lernen, sich über Zeilenumbrüche weniger Gedanken<br />
zu machen. Folgender Code ist dem bereits gezeigten völlig gleichwertig 1 :<br />
<br />
<br />
<strong>In</strong> einem Skript können beliebig viele solcher PHP-Blöcke stehen, die von oben<br />
nach unten abgearbeitet werden. Wie es gezeigt wurde ist es nicht immer clever,<br />
immerhin braucht auch die Erkennung der Zeichen einige Zeit<br />
(nichts, was Sie jemals spüren oder messen könnten, aber richtige Programmierer<br />
macht es schon nervös, wenn der Server ständig ein paar Nanosekunden lang sinnlose<br />
Dinge tut).<br />
Woran Sie sich hier gewöhnen müssen, sind die öffnenden und schließenden<br />
Sequenzen und die abschließenden Semikola.<br />
Manche Befehle wirken über einen bestimmten Bereich hinweg bzw. fassen<br />
mehrere Zeilen zusammen. <strong>In</strong> solchen Fällen werden geschweifte Klammern verwendet.<br />
Das sieht dann folgendermaßen aus:<br />
}<br />
?><br />
28<br />
Einführung<br />
print($text);<br />
PHP wird deshalb der Gruppe der so genannten Klammersprachen zugeordnet, zu<br />
der auch Java und alle C-Versionen zählen. Die Klammer ersetzt übrigens das<br />
Semikolon, aber das haben Sie sicher bereits bemerkt. Eine wichtige Technik ist<br />
das Einrücken. Das ist dem PHP-<strong>In</strong>terpreter natürlich wieder egal, es dient nur der<br />
Verbesserung der Lesbarkeit. Tun Sie sich selbst und allen späteren Entwicklern<br />
des Programms den Gefallen, wirklich konsequent einzurücken. Die meisten Editoren<br />
unterstützen das in der einen oder anderen Weise.<br />
Weil gerade die Rede von anderen Entwicklern war: Kommentare im Quelltext<br />
sind ein gutes Mittel, sich als mitteilsam zu erweisen und die Lesbarkeit weiter zu<br />
verbessern. Dazu setzen Sie wieder spezielle Zeichen ein:<br />
<br />
Die beiden Schrägstriche leiten einen Kommentar ein, der am nächsten Zeilenumbruch<br />
endet. Alternativ können mehrzeilige Kommentare mit /* Kommentar */<br />
geschrieben werden. Da gibt es aber einige Fallen und deshalb wird diesem<br />
Thema am dritten Tag ein eigener Abschnitt gewidmet.<br />
Eine nähere Betrachtung ist die Funktion print wert. Funktionen haben Parameter<br />
und deshalb immer runde Klammern am Ende. Wenn man mal keine Daten<br />
übergeben will, bleiben die Klammern leer, aber sie fallen niemals weg. Ob man<br />
Daten direkt (Fachsprache: als Literal) oder über eine Variable übergibt, spielt selten<br />
eine Rolle. Es gibt aber Fälle, wo dies wichtig ist. Auch dazu wird der dritte<br />
Tag handfeste <strong>In</strong>formationen zu bieten haben. Damit die Sache in der Praxis nicht<br />
langweilig wird (wer kommt schon mit 2.000 Funktionen aus?), können Sie auch<br />
eigene Funktionen definieren.<br />
Auch wenn es so ähnlich aussieht, if ist keine Funktion, sondern eine Sprachanweisung.<br />
Die Klammern fassen einen komplexeren Ausdruck zusammen. Sprachanweisungen<br />
sehen sehr unterschiedlich aus und bedürfen einer eingehenden<br />
Betrachtung. Tag 6 widmet sich ganz diesem Thema, ebenso wie den selbst erstellten<br />
Funktionen.
1.3 Einen Webserver bauen<br />
Einen Webserver bauen<br />
Bevor die Arbeit mit PHP5 beginnen kann, benötigen Sie eine Entwicklungsumgebung.<br />
Diese besteht aus einem Editor, in dem Quelltexte erfasst und bearbeitet<br />
werden, einem Webserver, PHP5 ausführen kann, und einem Browser, der die<br />
Skripte vom Webserver abruft. Für die Bearbeitung von Datenbanken wird noch<br />
ein Datenbanksystem benötigt.<br />
Vorbemerkungen<br />
PHP5 ist fest in der Linux-Welt etabliert. <strong>In</strong>zwischen existiert jedoch auch eine<br />
sehr stabile Version für Windows, die den Einsatz auch auf Produktionssystemen<br />
erlaubt. Für die Entwicklung zählten jedoch schon früher auch andere Kriterien,<br />
wie beispielsweise ein guter Editor. Diese sind nach wie vor eher in der Windows-<br />
Welt zu finden.<br />
Nach Untersuchungen einiger kommerzieller Editor-Hersteller werden ca. 90%<br />
aller Skripte auf Windows entwickelt. Das Hosting – also der eigentliche Produktionsbetrieb<br />
der fertigen Applikation – findet dann mit ähnlicher Dominanz unter<br />
Linux statt.<br />
Der Aufbau eines Entwicklungssystems unter Linux ist deshalb in den meisten Fällen<br />
kein Thema, Aufwand und Nutzen stehen in einem sehr ungünstigen Verhältnis<br />
zueinander. An dieser Stelle soll deshalb nur der Aufbau eines so genannten<br />
WAMP-Systems demonstriert werden, wie es in den allermeisten Fällen zum Einsatz<br />
kommen dürfte.<br />
WAMP vorbereiten<br />
Alle nötigen Daten finden Sie auf der CD zum Buch. Wenn Sie die allerneuesten<br />
Versionen möchten, müssen Sie diese entsprechend aus dem <strong>In</strong>ternet herunterladen.<br />
Benötigt werden:<br />
PHP5 – Binärdistribution für Windows<br />
PHP5 PECL-Module<br />
Apache 2 – Binärdistribution für Windows<br />
29
30<br />
Einführung<br />
Auf die Datenbank soll an dieser Stelle vorerst verzichten werden. <strong>In</strong> den entsprechenden<br />
Kapiteln wird entweder das integrierte <strong>SQL</strong>ite benutzt, das keine weiteren<br />
<strong>In</strong>stallationsschritte verlangt, oder <strong>My</strong><strong>SQL</strong>, dem ein eigener Tag gewidmet ist.<br />
Im <strong>In</strong>ternet finden Sie die entsprechenden Dateien auf folgenden Webseiten:<br />
PHP5: www.php.net/downloads.php<br />
Nutzen Sie hier die Option »PHP5.0.0 zip package« unter »Windows Binaries«.<br />
Außerdem ist die »Collection of PECL modules for PHP5.0.0« interessant,<br />
eine Sammlung weiterer Module, von denen einige gebraucht werden<br />
könnten. PHP5 selbst ist ca. 7,5 MB groß, die Module brauchen etwa 930 KB.<br />
Apache: httpd.apache.org/download.cgi<br />
Wählen Sie hier die Option »Win32 Binary (MSI <strong>In</strong>staller)«. Der Dateiname<br />
hat in etwa die Form »apache_2.0.50-win32-x86-no_ssl.msi«. Es handelt sich<br />
also um die Version 2.0.50 für Win32-Systeme, verpackt als Windows-<strong>In</strong>staller-<br />
Paket (MSI). Die Größe beträgt ca. 6 MB. Wenn Sie auf dem Webserver<br />
genau diese Version finden, können Sie sie auch von der CD nehmen.<br />
Windows-<strong>In</strong>staller-Pakete sind mit Windows XP eingeführt worden und<br />
laufen sofort auf Windows XP Pro, XP Home und Windows Server 2003.<br />
Sie laufen auch auf einem aktualisierten Windows 98 und Me. Falls<br />
MSI als Dateierweiterung nicht bekannt ist, müssen Sie sich über die<br />
Microsoft-Download-Seite ein entsprechendes Update beschaffen.<br />
Benötigt wird mindestens Microsoft <strong>In</strong>staller 1.2. Gehen Sie bei Bedarf<br />
auf folgende Website: www.microsoft.com/downloads/release.asp?Release<br />
ID=32831 (Win 9X) bzw. www.microsoft.com/downloads/release.asp?Re<br />
leaseID=32832 (NT4, 2000).<br />
Nach dem die <strong>In</strong>stallationspakete vorliegen, beginnt die <strong>In</strong>stallation. <strong>In</strong>stallieren<br />
Sie zuerst den Webserver, testen Sie ihn und fahren Sie dann mit PHP5 fort.<br />
1.4 Apache installieren<br />
Dank des <strong>In</strong>stallers ist die <strong>In</strong>stallation ganz einfach. Die Schritte werden nachfolgend<br />
gezeigt und anhand der Bemerkungen können Sie die nötigen Angaben<br />
machen. Starten Sie nun den <strong>In</strong>staller und überspringen Sie den Startbildschirm<br />
des Assistenten mit NEXT.
<strong>In</strong>stallationsstart<br />
Apache installieren<br />
Nach dem Start der <strong>In</strong>stallation müssen Sie die Lizenzbedingungen bestätigen<br />
und eine <strong>In</strong>formationsseite mit allgemeinen Hinweisen über sich ergehen lassen.<br />
Gehen Sie jeweils mit NEXT weiter, bis Sie zur Seite SERVER INFORMATION<br />
gelangt sind.<br />
Erste Schritte<br />
Abbildung 1.1:<br />
Auch bei GPL-<br />
Projekten sind die<br />
Lizenzbestimmungen<br />
anzuerkennen<br />
Die erste erforderliche Eingabe konfiguriert den Server. Webserver sind immer<br />
Teil einer Domain, wenn Sie im <strong>In</strong>ter- oder <strong>In</strong>tranet laufen, beispielsweise<br />
php.net. Sie haben immer einen bestimmten Namen, der meist »www« lautet. Der<br />
Webserver der PHP Group heißt deshalb www.php.net. Es ist möglich, dass Sie<br />
lokal über keine Domain verfügen, weil ihr Rechner direkt am <strong>In</strong>ternet hängt, Sie<br />
also keine eigenen Namensdienste betreiben. Die Angabe der richtigen Domain<br />
ist jedoch notwendig, damit der Browser den Server später auch findet.<br />
Für einen lokal betriebenen Webserver nimmt man meist den Namen localhost,<br />
der an die interne Loopback-Adresse 127.0.0.1 gebunden ist. Diese Adresse stellt<br />
sicher, dass Anfragen nicht über Router ans <strong>In</strong>ternet oder andere Computer im<br />
Netzwerk weitergeleitet werden. Meist ist die entsprechende Verknüpfung zwi-<br />
31
32<br />
Einführung<br />
schen localhost und 127.0.0.1 schon vorhanden. Um dies zu prüfen, öffnen Sie die<br />
Datei hosts (ohne Dateierweiterung) im Verzeichnis c:\windows\system32\<br />
drivers\etc. Ist die Datei leer, wird folgende Zeile eingefügt:<br />
127.0.0.1 localhost<br />
Achten Sie beim Speichern darauf, dass der Editor keine Dateierweiterung<br />
anhängt. Auf dem folgenden Bildschirm im <strong>In</strong>stallationsassistenten ist nun folgendes<br />
anzugeben:<br />
Domain: localhost<br />
Servername: localhost<br />
E-Mail des Administrators: Ihre E-Mail (wird nicht benutzt)<br />
Option FOR ALL USERS, ...<br />
Die Option FOR ALL USERS, ... (für alle Benutzer) ist grundsätzlich richtig, wenn<br />
Sie keine anderen Webserver wie die <strong>In</strong>ternetinformationsdienste auf dem Computer<br />
installiert haben. Apache benutzt standardmäßig Port 80 (dies ist der Port,<br />
den der Browser benutzt, wenn man nichts angibt). Dieser Port sollte natürlich frei<br />
sein. Haben Sie keinen anderen Webserver installiert, ist dieser Port frei.<br />
Abbildung 1.2:<br />
Einstellungen für<br />
ein lokales System
Apache installieren<br />
Setzen Sie mit NEXT fort. Im nächsten Schritt werden Sie gefragt, ob sie die Standardinstallation<br />
oder eine benutzerdefinierte wünschen. Wählen Sie CUSTOM<br />
(Benutzerdefiniert).<br />
Abbildung 1.3:<br />
Benutzerdefinierte<br />
<strong>In</strong>stallation –<br />
verhindert unnötige<br />
Module<br />
Sie können nun auf der nächsten Seite einige Optionen abwählen, die für PHP5<br />
nicht benötigt werden. Falls Sie sich intensiver mit Apache auseinandersetzen<br />
möchten, belassen Sie die Dokumentation im <strong>In</strong>stallationspaket. Allerdings sind<br />
die Texte auch online verfügbar. Mit einer schnellen <strong>In</strong>ternetverbindung können<br />
Sie online schneller arbeiten (siehe Abbildung 1.4).<br />
Im nächsten Schritt müssen Sie den Start der <strong>In</strong>stallation nur noch bestätigen, den<br />
Rest erledigt der <strong>In</strong>staller dann automatisch (siehe Abbildung 1.5).<br />
Falls Fehler auftreten, werden diese in Konsolenfenstern angezeigt. Am Ende<br />
erhalten Sie eine Erfolgsmeldung. Nun kann der Webserver getestet werden.<br />
Ein erneuter Start des <strong>In</strong>stallers erlaubt die Reparatur oder Vervollständigung<br />
einer bestehenden <strong>In</strong>stallation, die nicht komplett installiert wurde<br />
oder aus anderen Gründen nicht läuft.<br />
33
34<br />
Einführung<br />
Abbildung 1.4:<br />
Was nicht benötigt<br />
wird, fliegt<br />
raus<br />
Abbildung 1.5:<br />
Während der<br />
<strong>In</strong>stallation
Apache testen<br />
Apache installieren<br />
Der erste Test ist sehr einfach. Öffnen Sie Ihren Browser und geben Sie folgende<br />
Adresse ein:<br />
http://localhost<br />
Weitere Einstellungen<br />
Der Apache Webserver wird über eine Konfigurationsdatei mit dem Namen<br />
httpd.conf verwaltet. Diese ist über START | ALLE PROGRAMME | APACHE HTTP<br />
SERVER 2.0.50 | CONFIGURE APACHE SERVER zu finden.<br />
Prüfen Sie beispielsweise Ihre Einstellungen, die Sie während der <strong>In</strong>stallation vorgenommen<br />
haben, indem Sie nach einer Zeile suchen, die mit SERVERNAME<br />
beginnt. Dort sollte folgendes stehen:<br />
ServerName localhost:80<br />
Abbildung 1.6:<br />
Der Webserver<br />
funktioniert<br />
35
36<br />
Einführung<br />
Jetzt ist noch interessant zu wissen, wo die Dateien abgelegt werden, die der Webserver<br />
anbietet. Jeder Webserver hat ein Stammverzeichnis, das benutzt wird,<br />
wenn die blanke Adresse angegeben wird. Beim Apache-Webserver heißt dieses<br />
Verzeichnis htdocs und liegt unter<br />
C:\Programme\Apache Group\Apache2\htdocs<br />
Apache automatisch starten<br />
Es ist sinnvoll, wenn der Webserver nach dem Start des Betriebssystems sofort zur<br />
Verfügung steht. Bei manueller Arbeitsweise können Sie Apache über entsprechende<br />
Aufrufe starten und stoppen. Über START | ALLE PROGRAMME | APACHE<br />
HTTP SERVER 2.0.50 | CONTROL APACHE SERVER finden Sie die Optionen<br />
START, STOP und RESTART.<br />
Abbildung 1.7:<br />
Dienst-Verwaltung für den<br />
Apache Webserver
PHP5 installieren<br />
Unter Win9X fügen Sie den Startaufruf in den Autostart-Ordner des Startmenüs<br />
ein. Auf XP und 2000 wird Apache automatisch als Dienst installiert und startet<br />
mit dem Betriebssystem. Se können dies kontrollieren, indem Sie den Dienst-<br />
Manager öffnen. Gehen Sie (unter XP) dazu im STARTMENÜ auf ARBEITSPLATZ,<br />
klicken Sie mit der rechten Maustaste und wählen Sie dann VERWALTEN. Alternativ<br />
finden Sie die Option auch in der Systemsteuerung unter VERWALTUNG |<br />
COMPUTERVERWALTUNG. Es öffnet sich eine Managementkonsole. Dort suchen<br />
Sie den Zweig DIENSTE UND ANWENDUNGEN und darin DIENSTE. <strong>In</strong> der Dienstliste<br />
ist der Eintrag APACHE2 zu finden. Öffnen Sie diesen mit einem Doppelklick<br />
(siehe Abbildung 1.7).<br />
Wenn Sie möchten, dass der Dienst immer automatisch startet, stellen Sie den<br />
STARTTYP entsprechend ein.<br />
Läuft alles, geht die <strong>In</strong>stallation nun mit PHP5 weiter.<br />
1.5 PHP5 installieren<br />
PHP5 kommt als ZIP-Datei daher und muss nur ausgepackt werden. Dazu wählen<br />
Sie ein passendes Verzeichnis, am besten im Zweig Ihres Apache-Webservers:<br />
C:\Programme\Apache Group\php5<br />
Entpacken Sie alle Dateien dorthinein. Denken Sie daran, dass Winzip beim Entpacken<br />
evtl. den Namen der Datei in den Pfad einbaut. Unter dem Verzeichnis<br />
php5 sollten sofort die entsprechenden Arbeitsdateien folgen, nicht der von Winzip<br />
erzeugte Ordner, der dem Namen der gepackten Datei entspricht.<br />
Entpacken Sie nun noch die PECL-Datei. Sie können alle enthaltenen Module<br />
nach folgendem Pfad kopieren:<br />
C:\Programme\Apache Group\php5\ext<br />
Hier liegen bereits die standardmäßig in PHP5 eingebundenen Module.<br />
1.6 PHP5 konfigurieren<br />
PHP5 wird – ähnlich wie der Apache Webserver – über eine Konfigurationsdatei<br />
mit dem Namen php.ini konfiguriert. Nach der <strong>In</strong>stallation gibt es diese noch<br />
nicht. Stattdessen finden Sie zwei Musterdateien:<br />
37
38<br />
Einführung<br />
php.ini-dist<br />
Dies ist die Datei mit allen Standardoptionen.<br />
php.ini-recommended<br />
Eine Datei mit der empfohlenen Konfiguration.<br />
Erstellen Sie nun eine Kopie von php.ini-dist und nennen diese php.ini. Lassen<br />
Sie die Originaldatei auf jeden Fall unverändert, damit Sie die ursprüngliche Konfiguration<br />
im Fehlerfall wiederherstellen können.<br />
Die Datei php.ini<br />
Die Datei php.ini ist in mehrere Abschnitte unterteilt, die jeweils bestimmte Funktionen<br />
konfigurieren. Die Abschnitte beginnen immer mit einem als Sektion ausgeführten<br />
Block, der etwa folgendermaßen aussieht:<br />
[Session]<br />
Darin befinden sich mehrere Optionen, denen jeweils ein Wert zugewiesen wird.<br />
Vielen Optionen wird lediglich ein »On« oder »Off« zugeordnet, um die betreffende<br />
Funktion ein- oder auszuschalten:<br />
asp_tags = Off<br />
Anderen werden bestimmte Werte zugeordnet:<br />
output_buffering = 4096<br />
Einige Optionen sind auch »ausgeblendet«, indem ein Kommentarzeichen (das ist<br />
das Semikolon) davor gestellt ist:<br />
;extension=php_curl.dll<br />
Wenn man diese Option aktivieren möchte, entfernt man einfach das Semikolon.<br />
Stört eine Option, setzt man ein Semikolon davor.<br />
Wichtige Konfigurationsschritte<br />
Eigentlich muss man bei PHP5 nicht viel konfigurieren. Einige Optionen sind<br />
dennoch eine nähere Betrachtung wert, damit später alles funktioniert.<br />
Zuerst sollten Sie PHP5 mitteilen, wo die Erweiterungen zu finden sind, die eventuell<br />
benutzt werden sollen. Die entsprechende Option heißt folgendermaßen:
extension_dir = "./"<br />
Tragen Sie hier den Pfad zum ext-Verzeichnis ein:<br />
extension_dir = "C:\Programme\Apache Group\PHP5\ext"<br />
PHP5 testen<br />
Nun können Sie die Erweiterungen selbst freigeben, die Sie benutzen möchten.<br />
Dazu suchen Sie die Liste mit den extension-Optionen. Nehmen Sie, wenn der<br />
Eintrag bereits vorhanden ist, das Semikolon weg. Um ein PECL-Modul zu<br />
benutzten, fügen Sie den entsprechenden Eintrag hinzu. Für dieses Buch sind folgende<br />
Einträge sinnvoll:<br />
extension = php_gd2.dll<br />
extension = php_mysqli.dll<br />
extension = php_soap.dll<br />
Sie werden früher oder später weitere benötigen; ergänzen Sie dann die Liste entsprechend.<br />
Eine sinnvolle Funktion ist das Hochladen von Dateien. Zur Konfiguration dienen<br />
zwei Optionen:<br />
upload_tmp_dir = ?C:\Windows\temp?<br />
upload_max_filesize = 8M<br />
Die erste teilt PHP mit, in welchem Pfad temporäre Dateien abzulegen sind. Standardmäßig<br />
ist diese Option nicht aktiviert. Beachten Sie, dass der Webserver in diesem<br />
Pfad Schreibrechte benötigt. Die zweite Option stellt die Größe der Dateien<br />
auf maximal 8 MB ein, der Standardwert 2 MB ist meist zu klein.<br />
Die Verwaltung von Benutzersitzungen spielt in PHP5 eine große Rolle. Damit<br />
später alles funktioniert, ist auch hier eine entsprechende Änderung erforderlich:<br />
session.save_path = "C:\Windows\temp"<br />
Die Option ist meist auskommentiert.<br />
Dies sind freilich nur einige Optionen, die geändert werden können. An entsprechender<br />
Stelle wird im Buch immer wieder auf diese php.ini zugegriffen.<br />
1.7 PHP5 testen<br />
Um nun mit PHP5 arbeiten zu können, muss dem Webserver noch mitgeteilt werden,<br />
wann und wie PHP-Skripte auszuführen sind. Standardmäßig ist ein Webser-<br />
39
40<br />
Einführung<br />
ver ein primitives Stück Software. Er erkennt Anfragen mit dem Protokoll HTTP<br />
auf Port 80 (falls so konfiguriert) und führt diese aus. Das heißt, er erkennt mindestens<br />
die HTTP-Kommandos GET (Anfordern einer Ressource, beispielsweise<br />
einer Datei) und POST (Anfordern einer Ressource, der zugleich Formulardaten<br />
übergeben werden). GET führt standardmäßig dazu, dass der Webserver auf der<br />
Festplatte nach der entsprechenden Datei sucht und diese an den Browser sendet.<br />
Damit PHP5 ins Spiel kommt, wird eine spezielle Dateierweiterung benutzt.<br />
Theoretisch ist die Wahl freigestellt, eine »sinnvolle« Benennung ist jedoch unbedingt<br />
erforderlich. Für PHP5 ist dies .php, falls keine andere Version auf demselben<br />
Server läuft. Haben Sie parallel noch PHP 4 laufen, ist .php5 eine gute<br />
Ergänzung. Alle Skripte in diesem Buch tragen die Dateierweiterung .php und lassen<br />
sich nur dann unverändert einsetzen, wenn dies auch im Apache entsprechend<br />
konfiguriert wird.<br />
Konfiguration des Webservers für PHP5<br />
Die Verknüpfung der Dateierweiterung findet wieder in der httpd.conf statt. Letztlich<br />
wird nur definiert, dass Dateien mit der Endung .php nicht direkt ausgeliefert,<br />
sondern zuvor an die ausführbare <strong>In</strong>stanz von PHP geschickt werden.<br />
Der Apache-Webserver kann PHP5 in zwei Arten bedienen – als CGI (Common<br />
Gateway <strong>In</strong>terface) und als Apache-Modul (mod_php). Es ist für ein Entwicklungssystem<br />
dringend zu empfehlen, CGI zu benutzen. CGI ist eine sehr alte und vergleichsweise<br />
langsame Schnittstelle. Auf einem Entwicklungssystem, wo immer<br />
nur ein Browser läuft und damit kaum parallele Zugriffe stattfinden, gibt es jedoch<br />
keinen Unterschied in der Zugriffsgeschwindigkeit. CGI bietet dafür einen durchaus<br />
ernst zu nehmenden Vorteil. Da bei jedem Aufruf die entsprechende <strong>In</strong>stanz<br />
gestartet wird, die die Anfrage ausführt, werden auch eventuell vorhandene Änderungen<br />
an der Konfiguration sofort erkannt. Da gerade in der ersten Zeit häufig<br />
mit den Konfigurationen gespielt wird, wäre ein ständiges Stoppen und Starten des<br />
Servers suboptimal. Denn läuft PHP als Modul, wird es nur einmal gestartet und<br />
bleibt für jeden folgenden Aufruf aktiv. Änderungen an der PHP-Konfiguration<br />
wirken sich damit nicht sofort aus, da diese nur beim Start gelesen wird.<br />
Änderungen an der httpd.conf erfordern immer einen Neustart des Webservers<br />
– also des Apache2-Dienstes im Besonderen. Das Betriebssystem<br />
selbst muss niemals neu gestartet werden.
PHP5 testen<br />
Tragen Sie nun folgenden Text in die httpd.conf ein (beachten Sie, dass der Skript-<br />
Alias hier nur php lautet, <strong>In</strong>stallationen die PHP 4 und PHP5 parallel betreiben,<br />
sollten hier einen anderen Namen benutzen):<br />
ScriptAlias /php/ "C:/Programme/Apache Group/php5/"<br />
AddType application/x-httpd-php .php<br />
Action application/x-httpd-php "/php/php-cgi.exe"<br />
Änderungen wirken sich erst aus, wenn der Apache-Dienst neu gestartet wird. Die<br />
drei Zeilen verknüpfen die Dateierweiterung .php mit der ausführbaren Datei phpcgi.exe,<br />
die Teil der PHP5-<strong>In</strong>stallation ist. Passen Sie die Pfade an, wenn Sie bei<br />
den vorhergehenden Schritten nicht exakt den Vorschlägen gefolgt sind.<br />
PHP-Skripte ausführen<br />
Nun ist es an der Zeit, einen ersten Test vorzunehmen. PHP5 stellt eine Funktion<br />
bereit, die über die gesamte Konfiguration Auskunft gibt: phpinfo. Diese wird<br />
benutzt, um das allerkleinste PHP-Skript zu schreiben:<br />
Listing 1.3: info.php – <strong>In</strong>formationen über PHP5 ermitteln<br />
<br />
Speichern Sie diesen Text unter dem Namen info.php unter htdocs ab. Rufen Sie<br />
dann im Browser folgende Adresse auf:<br />
http://localhost/info.php<br />
Sie sollten dann in etwa folgende Seite sehen (siehe Abbildung 1.8).<br />
Wenn Sie die Seite weiter nach unten scrollen, finden Sie Angaben zu den installierten<br />
Modulen, konfigurierten Umgebungs- und Servervariablen und andere<br />
Daten über Ihr PHP5. Wenn Sie Änderungen an der Datei php.ini vornehmen,<br />
beispielsweise Erweiterungen hinzufügen, erscheinen hier Angaben über jedes<br />
einzelne Modul. Sie können so sehr schnell kontrollieren, ob die Einstellungen<br />
den gewünschten Effekt haben. Stimmt alles, kann es jetzt mit der Programmierung<br />
richtig losgehen.<br />
41
42<br />
Einführung<br />
Wenn es nicht funktioniert<br />
Abbildung 1.8:<br />
PHP5 läuft auf<br />
einem Apache-<br />
Webserver<br />
Auch wenn die <strong>In</strong>stallation verhältnismäßig trivial ist, können Fehler passieren.<br />
Jeder Computer ist anders konfiguriert und manchmal klappt es einfach nicht. Ein<br />
paar typische Fehler zeigt die folgende Liste:<br />
Fehlermeldung der Art »Socket-Fehler« oder »Adresse 0.0.0.0:80 nicht verfügbar«<br />
während der <strong>In</strong>stallation des Webservers.<br />
Sie haben einen weiteren Webserver (vermutlich IIS) laufen, der Port 80 ist so<br />
bereits belegt. Weisen Sie dem IIS eine andere IP-Adresse, einen anderen Port<br />
zu oder deaktivieren Sie ihn ganz. Starten Sie die Apache-<strong>In</strong>stallation erneut<br />
mit der Option REPAIR.<br />
Der Browser zeigt die Fehlermeldung 400 (Bad Request, Falsche Anforderung).<br />
Die Verknüpfung zwischen .php und PHP ist in der httpd.conf falsch eingerichtet.<br />
Vermutlich stimmt ein Pfad nicht. Versuchen Sie auch nochmals, den
Kontrollfragen<br />
Webserver neu zu starten: START | ALLE PROGRAMME | APACHE HTTP-SER-<br />
VER 2.0.50 | CONTROL APACHE SERVER | RESTART.<br />
Alternativ können Sie das Fehlerprotokoll untersuchen. Öffnen Sie START |<br />
ALLE PROGRAMME | APACHE HTTP-SERVER 2.0.50 | REVIEW SERVER LOG<br />
FILES | REVIEW ERROR LOG. Dort finden Sie hilfreiche Hinweise zu den Vorgängen<br />
im Webserver, beispielsweise folgendes:<br />
[Mon Jul 19 16:03:41 2004] [error] [client 127.0.0.1] script not found<br />
or unable to stat: C:/Programme/Apache Group/php5php-cgi.exe<br />
Hier fehlt in der Konfiguration hinter php5 offensichtlich ein Pfadtrennzeichen.<br />
Statt der <strong>In</strong>fo-Seite wird der <strong>In</strong>halt der Datei angezeigt.<br />
Auch hier gelangte die Datei nicht bis zu PHP, Apache hat sie einfach als Ressource<br />
behandelt und ausgeliefert. Vermutlich ist der Eintrag in der httpd.conf<br />
nicht vorhanden oder nach dem Eintragen erfolgt kein Neustart.<br />
1.8 Kontrollfragen<br />
1. Wann wurde PHP das erste Mal unter diesem Namen bekannt?<br />
2. Was bedeutet »PHP«?<br />
3. Worauf ist der Name »Zend« zurückzuführen?<br />
4. Anhand welcher Begrenzungszeichen werden PHP-Fragmente in HTML-Code<br />
erkannt?<br />
5. Welches Protokoll wird zur Übertragung von HTML-Seiten vom Server zum<br />
Browser verwendet?<br />
6. Welcher Webserver kann auf allen Betriebssystemen zur Entwicklung eingesetzt<br />
werden?<br />
7. Wofür steht der Begriff WAMP?<br />
8. Welche Bedeutung hat die Adresse »http://localhost«?<br />
9. Mit welcher Funktion kann PHP5 ausführlich getestet werden?<br />
43
Erste Schritte<br />
2
46<br />
Erste Schritte<br />
Die ersten Schritte mit PHP beginnen immer mit HTML. Denn es ist das Ziel<br />
jeder PHP-Anwendung, HTML-Seiten zu erzeugen. Darauf aufbauend wird das<br />
Einbetten von PHP in HTML behandelt und es werden elementare Programmiertechniken<br />
vorgestellt, die in den folgenden <strong>Tagen</strong> angewendet werden. Als Abschluss<br />
soll ein Blick auf die Arbeitsweise von HTTP das Zusammenspiel von<br />
Webserver und Browser näher bringen.<br />
2.1 Einfache HTML-Seiten<br />
Ihre Entwicklungsumgebung sollte also in der Lage sein, HTML-Seiten über<br />
einen URL-Aufruf auszuliefern. Das folgende Skript kann als Start benutzt werden:<br />
Listing 2.1: Basis aller Seiten – eine HTML-Struktur<br />
<br />
<br />
Kein Titel<br />
<br />
<br />
PHP beginnt hier...<br />
<br />
<br />
Diese Seite sollte in einem Verzeichnis des Webservers liegen, das mit dem Browser<br />
aufgerufen werden kann, beispielsweise http://localhost/php5Mut/html1.html.<br />
Diese Adresse wird im Browser eingegeben, dann sollte sie erscheinen. Erst wenn<br />
dies funktioniert, lohnt es, mit PHP5 weiterzumachen.<br />
Viele Fehler bei der Entwicklung von PHP entstehen, weil schlechtes oder nicht<br />
lauffähiges HTML erzeugt wird. Browser tolerieren zwar falsches HTML, reagieren<br />
also nicht mit einer Fehlermeldung. Sie zeigen aber im Zweifelsfall gar nichts<br />
oder völlig unsinniges an. Nicht immer ist ein falsches PHP-Skript daran schuld.<br />
Wenn beispielsweise vergessen wurde, das -Tag zu schließen, nimmt der<br />
Browser den gesamten Text als Titel und die Seite selbst bleibt weiß. Solche Fehler<br />
offenbart ein Blick in den Quelltext, beim <strong>In</strong>ternet Explorer am einfachsten<br />
über das Kontextmenü (rechte Maustaste) – QUELLTEXT ANZEIGEN.
HTML-Refresh<br />
Einfache HTML-Seiten<br />
Bevor Sie mit PHP anfangen, sollten Sie ihre HTML-Kenntnisse überprüfen. Dieser<br />
Abschnitt zeigt die wichtigsten Elemente einer HTML-Seite.<br />
Grundstruktur<br />
Jede HTML-Seite hat folgende Grundstruktur:<br />
Listing 2.2: Grundaufbau einer HTML-Seite<br />
<br />
<br />
Titel der Seite<br />
<br />
<br />
<br />
<br />
Alles, was auf der Seite ausgegeben werden soll, muss zwischen den -Tags<br />
stehen. Im Kopfbereich () stehen der Seitentitel, JavaScript-Codes und Verweise<br />
auf Stildateien (CSS).<br />
JavaScript einbinden<br />
Um JavaScript zu nutzen, ist folgendes Tag vorgesehen:<br />
<br />
function <strong>My</strong>Function()<br />
{<br />
alert("Tu was");<br />
}<br />
<br />
Stildateien (Stylesheets) einbinden<br />
Stildateien werden über das Tag eingebunden:<br />
<br />
47
48<br />
Erste Schritte<br />
Das Attribut href verweist auf den relativen, variablen Pfad zu der Stildatei, während<br />
das Attribut type auf den Typ festgelegt ist.<br />
Sollen die Stile direkt eingebettet werden, wird dagegen das Tag verwendet:<br />
<br />
*<br />
{<br />
color:black;<br />
font-weight:normal;<br />
text-decoration:none;<br />
font-size:9pt;<br />
font-family:Verdana;<br />
}<br />
<br />
Das Sternchen legt die gewählte Formatierung als global fest.<br />
Die wichtigsten Formatier- und Auszeichnungselemente<br />
Die folgende Tabelle zeigt in einer Übersicht die wichtigsten Elemente, die in alltäglichen<br />
Seiten (und in diesem Buch) verwendet werden:<br />
Tag Wichtige Attribute Container? Beschreibung<br />
, ja Fett<br />
, ja Kursiv<br />
ja Unterstrichen<br />
ja Durchgestrichen<br />
ja Wie im Text formatiert<br />
ja Hoch gestellt<br />
ja Tiefer gestellt<br />
wahlweise Absatz<br />
Tabelle 2.1: Wichtige Formatier- und Auszeichnungselemente
Tag Wichtige Attribute Container? Beschreibung<br />
Einfache HTML-Seiten<br />
clear="left|right" nein Zeilenumbruch, das Attribut<br />
clear bestimmt das Verhalten<br />
angrenzender Blöcke<br />
src, width, height,<br />
hspace, vspace<br />
Wenn keine Attribute benannt sind, können dennoch immer die Standardattribute<br />
benutzt werden:<br />
style<br />
Enthält eingebettete Stildefinitionen, beispielsweise color:red;<br />
class<br />
Verweist auf eine Klasse, die in Stildefinitionen der Seite erstellt wurde.<br />
id<br />
Verweist auf eine ID, die in Stildefinitionen der Seite erstellt wurde.<br />
Tabellen und Bilder<br />
Tabellen und Bilder sind auf fast jeder Seite zu finden. Tabellen dienen vor allem<br />
der Formatierung. Die Grundstruktur einer Tabelle basiert auf folgenden Tags:<br />
<br />
Diese Tags umschließen die Tabelle.<br />
nein Fügt ein Bild ein, src bestimmt<br />
den Pfad zum Bild<br />
type ja Liste mit Zeichen als Aufzählungszeichen<br />
type ja Liste mit Zahlen als Aufzählungszeichen<br />
ja Listelement für und <br />
ja Absatz, frei formatierbar<br />
ja Zeichenblock, frei formatierbar<br />
Tabelle 2.1: Wichtige Formatier- und Auszeichnungselemente (Forts.)<br />
49
50<br />
Erste Schritte<br />
...<br />
Es folgen Tags für die Definition einer Reihe.<br />
<strong>In</strong>halt<br />
<strong>In</strong>nerhalb der Reihe stehen diese Tags für jede Zeile.<br />
Kopf<br />
Kopfelemente werden mit einem eigenen Tag definiert. Der <strong>In</strong>halt erscheint<br />
fett und zentriert, wenn dies nicht von Stilen überschrieben wird.<br />
Eine einfache Tabelle mit drei mal drei Zellen sieht nun folgendermaßen aus:<br />
Listing 2.3: Einfache Tabelle mit Überschrift<br />
<br />
<br />
NameAnschriftTelefon<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Das Attribut border sorgt für einen einfachen Rand. Meist wird hier 0 eingesetzt,<br />
damit die Tabelle nur das Raster für andere Elemente liefert, selbst aber unsichtbar<br />
bleibt.<br />
Wichtige Tabellenattribute<br />
Abbildung 2.1:<br />
Eine einfache Tabelle<br />
Die folgenden Tabellen zeigen die wichtigsten Attribute für die Tags und<br />
.
Attribut Beispiel Bedeutung<br />
border border="1" Rand in Pixel<br />
cellspacing cellspacing="3" Abstand der Zellen voneinander<br />
Einfache HTML-Seiten<br />
cellpadding cellpadding="4" Abstand des <strong>In</strong>halts zum Zellenrand<br />
width width="100%" Breite, entweder in Pixel oder % vom Browserfenster<br />
oder der umschließenden Tabelle<br />
height height="477" Höhe, entweder in Pixel oder % vom Browserfenster<br />
oder der umschließenden Tabelle<br />
Tabelle 2.2: Einige Attribute für <br />
Attribut Beispiel Bedeutung<br />
bgcolor bgcolor="red" Hintergrundfarbe der Zelle<br />
width width="50" Breite der Zelle, kann nicht kleiner sein als der<br />
<strong>In</strong>halt<br />
height height="20" Höhe der Zelle, kann nicht kleiner sein als der<br />
<strong>In</strong>halt<br />
colspan colspan="3" Zelle dehnt sich über drei Spalten aus<br />
rowspan rowspan="2" Zelle dehnt sich über zwei Zeilen aus<br />
Tabelle 2.3: Einige Attribute für <br />
Vor allem der Umgang mit colspan und rowspan ist nicht ganz einfach. Dabei<br />
muss beachtet werden, dass die Gesamtzahl der Zellen pro Reihe immer gleich ist,<br />
damit sich eine konstante Anzahl von Spalten bildet. Andernfalls zerfällt die<br />
Tabelle. Das folgende Bild zeigt, wie eine Verknüpfung von Zellen mit<br />
colspan="2" bzw. rowspan="2" aussieht:<br />
Abbildung 2.2:<br />
Verknüpfte Zellen mit rowspan und colspan<br />
51
52<br />
Erste Schritte<br />
Der Code für diese Tabelle sieht nun folgendermaßen aus:<br />
Listing 2.4: Tabelle mit verknüpften Zellen<br />
<br />
<br />
NameAnschriftTelefon<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<strong>In</strong> der zweiten und dritten Zeile sind nur zwei -Elemente enthalten, weil sich<br />
durch die Verknüpfungen die erforderliche Anzahl von drei Zellen ergibt. Das<br />
heißt, die Angabe steht für eine Zelle, während für zwei<br />
Zellen steht. <strong>In</strong> die folgende Zeile ragt die mit rowspan verknüpfte Zelle der zweiten<br />
Zeile hinein, deshalb kann (und muss) die dritte Zelle hier entfallen.<br />
Formulare<br />
Formulare sind der dritte große Komplex in HTML. PHP bietet eine weitläufige<br />
Unterstützung für die Verarbeitung von Formulardaten. Deshalb ist eine gute<br />
Kenntnis der Formularelemente unerlässlich.<br />
Ein Formular erstellen<br />
Ein Formular entsteht, indem Formularelemente neben normalem HTML innerhalb<br />
des Tags gestellt wird.<br />
Die Formularelemente<br />
Die folgende Tabelle zeigt alle Formularelemente und die wichtigsten Attribute:
Element type-Attribut Attribute Nutzen, Anwendung<br />
input text value, size, name Textfeld, einzeilig<br />
button value, name Schaltfläche<br />
submit value, name Absendeschaltfläche<br />
PHP einbetten<br />
reset value, name Rücksetzschaltfläche, leert das Formular<br />
file name Dateihochladen<br />
checkbox value, name, checked Kontrollkästchen<br />
radio value, name, checked Optionsfeld<br />
select - size, multiple Listbox, Klappfeld<br />
textarea - Großes Textfeld, statischer Text<br />
steht zwischen den Tags (Container)<br />
Die Benutzung wird an anhand vieler Beispiele im Buch dokumentiert, sodass<br />
eine ausführliche Darstellung an dieser Stelle nicht sinnvoll erscheint. Sollte<br />
Ihnen das eine oder andere Tag gänzlich unbekannt erscheinen, konsultieren Sie<br />
die entsprechende Fachliteratur zum Thema HTML.<br />
2.2 PHP einbetten<br />
Bislang wurde der Text in den HTML-Seiten statisch ausgegeben. Nach diesen<br />
Grundlagen ist es nun an der Zeit, PHP5 zum Leben zu erwecken. Die folgenden<br />
Beispiele zeigen, wie das mit PHP funktioniert. Die genaue Arbeitsweise wird im<br />
Laufe des Tages noch genauer erläutert. Probieren Sie die Beispiele erst aus.<br />
Wie PHP den Code erkennt<br />
Wenn der Browser eine PHP-Seite beim Webserver anfordert, erkennt dieser an<br />
der Dateierweiterung, dass PHP für die Verarbeitung zuständig ist. Der Webserver<br />
übergibt dem PHP-Modul die <strong>In</strong>formation, welche Seite verarbeitet werden soll.<br />
53
54<br />
Erste Schritte<br />
Das Modul lädt diese Seite dann von der Festplatte und liest den <strong>In</strong>halt Zeile für<br />
Zeile ein. <strong>In</strong> einem mehrstufigen Prozess entstehen daraus Zwischencodes, die<br />
interpretiert werden. Auf die genauen Zusammenhänge wird noch genauer eingegangen.<br />
Bei diesem Vorgang sucht das PHP-Modul gezielt nach bestimmten PHP-<br />
Codes. Der erste ist die Eröffnung eines Code-Blocks:<br />
<br />
Die Syntax lehnt an XML an und würde im XML-Dialekt als so genannte Prozessanweisung<br />
verstanden werden. Baut man Seiten mit XHTML (der XML-Darstellung<br />
von HTML), so stört der eingebettet PHP-Code die Syntax der Seite nicht<br />
und erlaubt weiterhin die Mischung von PHP und (X)HTML. Es gibt noch andere<br />
Erkennungszeichen, die PHP zulässt. Das Gezeigte ist jedoch das einzige praxistaugliche<br />
und wird deshalb exklusiv vorgestellt.<br />
Wie viele solcher Blöcke in der Seite stehen, spielt kaum ein Rolle. Man kann<br />
zwar theoretische Erwägungen darüber anstellen, ob die mehrfache Eröffnung von<br />
Code-Blöcken auf der Seite Leistung kostet, in der Praxis ist das aber kaum relevant,<br />
weil viele andere Teile der Verarbeitungsmaschine einen weitaus gewichtigeren<br />
Einfluss nehmen.<br />
Jeder Text, der außerhalb der Blöcke steht, wird unverändert ausgegeben. Dies<br />
betrifft normalerweise den HTML-Anteil. Da die Verarbeitung von oben nach<br />
unten erfolgt, wird die Seite genau so gesendet, wie sie aufgebaut ist. Es ist nun<br />
Sache der eingebetteten Codes, an den richtigen Stellen vernünftige Ausgaben zu<br />
erzeugen.<br />
Listing 2.5: PhpStart1.php: Eine einfache Ausgabe<br />
<br />
<br />
PhpStart1<br />
<br />
<br />
<br />
<br />
Ausgaben erzeugen<br />
<strong>In</strong>nerhalb der Code-Blöcke folgt nun PHP-Anweisung auf PHP-Anweisung. Dies<br />
kann sehr verschieden aussehen, weil PHP im Umgang mit Code sehr flexibel ist.<br />
Zur besseren Lesbarkeit schreibt man in der Regel immer genau eine Anweisung<br />
pro Zeile oder teilt sehr lange Konstrukte sogar auf. Mehrere Anweisungen auf<br />
einer Zeile gehört zu den Dingen, die man als Profi unbedingt vermeidet.<br />
2.3 Ausgaben erzeugen<br />
Auch einfachste PHP-Skripte geben Daten aus. Die erzeugten Zeichen werden an<br />
der Stelle in die HTML-Ausgabe eingebaut, wo der entsprechende Ausgabebefehl<br />
steht. PHP kennt hierfür mehrere Techniken:<br />
Die Ausgabeanweisung echo<br />
Die Ausgabefunktion print und einige spezialisierte Versionen, wie printf<br />
Die verkürzte, direkte Ausgabe allein stehender Variablen<br />
Die Ausgabe mit echo<br />
Die Anweisung echo gibt die übergebenen Argument direkt aus. Im Gegensatz zu<br />
Funktionen wie print müssen hier keinen Klammern gesetzt werden. Solange es<br />
nur ein einziges Argument gibt, darf dieses aber in runde Klammern gestellt werden.<br />
Diese Schreibweise ist jedoch unüblich und sollte auch deshalb vermieden<br />
werden, um die Natur als Sprachanweisung im Code herauszuheben.<br />
Außerdem verkraftet echo beliebig viele Argumente, die durch Kommata getrennt<br />
werden. Listing 2.6 zeigt die möglichen Varianten:<br />
Listing 2.6: EchoVariants.php – Ausgaben mit der Anweisung echo<br />
<br />
Freilich wird in der Praxis nicht nur eine einfache Zeichenfolge ausgegeben, sondern<br />
auch das Ergebnis einer Berechnung oder der <strong>In</strong>halt einer Variable.<br />
55
56<br />
Erste Schritte<br />
Variablen ausgeben<br />
Variablen sind benannte Speicherstellen für Daten. Am vierten Tag wird dieses<br />
Thema ausführlich behandelt. Einstweilen sei nur auf die grundlegende Syntax<br />
hingewiesen: $text ist eine Variable mit dem Namen »text«. Für PHP ist sie<br />
erkennbar durch das vorangestellte $-Zeichen.<br />
Eine solche explizite Erkennungsmöglichkeit erlaubt es, Variablen auch mitten in<br />
anderem Text zu erkennen. Das funktioniert tatsächlich, sodass Sie auch folgendes<br />
schreiben können:<br />
Listing 2.7: EchoVariable1.php: Variable in einer Zeichenfolge erkennen<br />
<br />
Die Variable $version wird anhand des $-Zeichens erkannt. Damit das Ende korrekt<br />
gefunden wird, muss entweder ein Leerzeichen folgen oder die ganze Konstruktion<br />
in geschweifte Klammern gepackt werden:<br />
Listing 2.8: EchoVariable2.php: Variable in einer Zeichenfolge schützen<br />
<br />
Solche Ausgaben sind sehr häufig und typisch in PHP. Die Möglichkeit, Variablen<br />
in Zeichenketten einzubetten, ist deshalb außerordentlich hilfreich. Sie spart jede<br />
Menge Verkettungen und Aufreihungen von Parametern und macht Ihre Programme<br />
lesbarer und einfacher.<br />
Abbildung 2.3:<br />
Ausgabe mit echo<br />
Nun kann es manchmal vorkommen, dass dieses Verhalten nicht gewünscht ist.<br />
Sie haben es sicher schon bemerkt: Zeichenfolgen werden in Anführungszeichen<br />
gepackt. Statt der gezeigten doppelten sind in PHP auch einfache zulässig. Werden<br />
diese verwendet, versucht PHP nicht mehr, Variablen zu erkennen und auszuwerten.<br />
Das folgende Beispiel zeigt den Effekt:
Ausgaben erzeugen<br />
Listing 2.9: EchoVariable3.php – So werden Variablen nicht ausgewertet<br />
<br />
<strong>In</strong> der Bildschirmausgabe erscheint der Text unverändert:<br />
Abbildung 2.4:<br />
Variablen werden in einfachen<br />
Anführungszeichen nicht erkannt<br />
Ausgabe von großen Textmengen<br />
Die Möglichkeit, PHP und HTML zu vermischen, ist ein schneller und einfacher<br />
Weg dynamische Webseiten zu erstellen. Leider führt diese Vermischung zu sehr<br />
schlecht lesbaren Programmen. Solange Sie mit Trivialskripten wie in diesem<br />
Kapitel arbeiten, fällt das nicht auf. Später jedoch – wenn richtige Projekte an der<br />
Tagesordnung sind – wird der Code schnell unleserlich. Damit schleichen sich<br />
Fehler ein. Für andere Entwickler, die mit dem Code arbeiten sollen, wird es teilweise<br />
unmöglich oder zumindest zeitraubend, sich einzuarbeiten.<br />
Vor diesem Hintergrund entstand eine ansatzweise aus der Sprache Perl übernommene<br />
Technik, längere Textblöcke auszugeben – »heredoc« genannt. Dabei werden<br />
der Anfang und das Ende in einer speziellen Weise gekennzeichnet. PHP liest<br />
einen solchen Block, der auch viele Zeilen umfassen kann, als eine geschlossene<br />
Zeichenfolge. Die bereits gezeigte Erkennung von Variablen und die Kennzeichnung<br />
derselben mit geschweiften Klammern steht auch hier zur Verfügung. Das<br />
folgende Beispiel zeigt die Anwendung:<br />
Listing 2.10: EchoHeredoc.php – Ausgabe von Textblöcken innerhalb von PHP-Code<br />
58<br />
Erste Schritte<br />
echo "$text";<br />
?><br />
Der Abschnitt, der ausgegeben werden soll, beginnt mit
Ausgaben erzeugen<br />
aber im Gegensatz zu echo immer wie eine Funktion. Funktionen geben Daten<br />
zurück, wenn sie in Ausdrücken verwendet werden. Auf diese Feinheiten wird in<br />
den nächsten <strong>Tagen</strong> noch genau eingegangen. An dieser Stelle ist es nur wichtig<br />
zu wissen, dass Sie mit print Daten ausgeben können. Der Aufruf gibt, wenn die<br />
Ausgabe gelang, den Wahrheitswert TRUE (Wahr) zurück. Meist wird dies jedoch<br />
nicht ausgewertet und deshalb erscheint print in einem sehr einfachen Kontext:<br />
Listing 2.11: Print.php – Einfache Ausgabe von Daten mit print<br />
<br />
Der Aufruf ist nur mit genau einem Argument erlaubt, eine Verkettung mit Kommata<br />
wie bei echo funktioniert nicht. Der Einsatz von print ist dann vorzuziehen,<br />
wenn der Kontext Rückgabewerte erwartet. An den entsprechenden Stellen im<br />
Buch wird darauf hingewiesen, ansonsten wird durchgehend echo verwendet.<br />
Formatierung von Ausgaben<br />
PHP und HTML sind im Zusammenspiel sehr leistungsfähig. Ein typischer<br />
Anwendungsfall besteht im dynamischen Erzeugen von HTML-Tags. Stellen Sie<br />
sich vor, eine Navigation aufzubauen, bestehend aus mehreren Hyperlinks, die<br />
etwa folgendermaßen aussehen:<br />
Seite 1<br />
Drei Elemente dieses Tags könnten variabel sein: Die Attribute href und target und<br />
der <strong>In</strong>halt des Tags. Folgende Variante ist funktionsfähig, aber nicht gut lesbar:<br />
<br />
Die Funktion printf ist hier ausgesprochen hilfreich, denn sie erlaubt die Erstellung<br />
einer Formatieranweisung und die getrennte Übergabe von Parametern. Dies<br />
59
60<br />
Erste Schritte<br />
erhält die Lesbarkeit des HTML-Tags (oder können Sie die erste echo-Anweisung<br />
des Beispiels auf Anhieb lesen?)<br />
Listing 2.12: Printf.php: So werden dynamische HTML-Konstrukte lesbar<br />
Ausgaben erzeugen<br />
F steht für ein Füllzeichen, entweder ein Leerzeichen oder eine »0« (Null).<br />
Andere Zeichen müssen mit einem Apostroph eingeleitet werden. Es folgt – ebenfalls<br />
optional – ein Minuszeichen, wenn die Füllzeichen linksbündig aufgefüllt<br />
werden sollen. Ohne Angabe wird rechts aufgefüllt.<br />
Werden Zahlen ausgegeben, kann mit der weiteren Angabe D noch bestimmt werden,<br />
wie viele Dezimalstellen erzeugt werden. Nebenbei rundet printf im<br />
Bedarfsfall mathematisch korrekt.<br />
Formatcode Bedeutung und weitere Parameter<br />
b Zahlen werden binär ausgegeben (als Folge von »0« und »1«) (b = binary)<br />
c Eine Zahl als Parameter wird als Zeichencode interpretiert (c = character)<br />
d Zahlen werden als Dezimalwert mit Vorzeichen ausgegeben (d = decimal)<br />
u Zahlen werden als Dezimalwert ohne Vorzeichen ausgegeben (u = unsigned)<br />
f Zahlen werden als Gleitkommawert ausgegeben (f = float)<br />
o Zahlen werden als Oktalzahl (Basis 8) ausgegeben (o = octal)<br />
s Zeichenfolgen (s = string)<br />
x und X Zahlen werden hexadezimal ausgegeben, x erzeugt kleine Buchstaben, X<br />
große (x = heXadecimal)<br />
% Gibt das Prozentzeichen selbst aus<br />
Tabelle 2.4: Formatierte Ausgabe<br />
Mit diesen Angaben lassen sich schon recht ansprechende Ergebnisse erzeugen,<br />
beispielsweise für die Generierung von HTML-konformen Farbangaben:<br />
Listing 2.13: Printf2.php – Erzeugen HTML-konformer Farben aus Dezimalzahlen<br />
62<br />
Erste Schritte<br />
$r, $g, $b);<br />
?><br />
Da HTML die Angabe von RGB-Farbwerten mit Hexadezimalzahlen erwartet,<br />
bietet sich die Option $X an, wie im letzten Beispiel gezeigt. Der erzeugte HTML-<br />
Code sieht nun folgendermaßen aus:<br />
Farbausgabe mit printf<br />
CC, 66 und 99 sind die zu den Dezimalzahlen 204, 102 und 153 passenden Hex-<br />
Werte.<br />
2.4 Professionelles Programmieren<br />
Nach den ersten Fertigkeiten, die das Skript überhaupt erstmal zur Ausgabe von<br />
Daten bewegen, sollten Sie sich mit bestimmten Programmierstilen vertraut<br />
machen. Diese Techniken und Methoden helfen, später den Überblick zu behalten<br />
und besseren Code zu schreiben. Der Begriff »besser« bedeutet hier: schneller<br />
lesbar, einfacher wartbar, zuverlässiger, stabiler, schneller.<br />
Code auf möglichst kleinem Raum unterzubringen und der verbreitete<br />
Stolz, ein Problem mit noch weniger Zeilen gelöst zu haben als irgendjemand<br />
anderes, ist unprofessionell und dumm, die künstliche Verkomplizierung<br />
von Code ist amateurhaftes Gehabe.<br />
Nicht nur für die Nachwelt: Kommentare<br />
Kommentare sind ebenso wichtig wie guter Code, oder andersherum, kein guter<br />
Code ohne gute Kommentare. Dabei sollten Kommentare nie beschreiben, wie<br />
der Code arbeitet, sondern was er tut. Denn gute Entwickler können sehr wohl<br />
fremden Code lesen und nachvollziehen. Jeden Gedankengang des ursprünglichen<br />
Programmierers nachzuvollziehen und den Zusammenhang zu anderen,<br />
nicht offensichtlich erkennbaren Programmteilen herzustellen, ist ungleich aufwändiger.
Warum Sie Kommentare schreiben sollten<br />
Professionelles Programmieren<br />
Kommentare sollten Sie unbedingt auch dann schreiben, wenn Sie nur für sich<br />
selbst entwickeln. Denn einige Wochen oder Monate später fällt es auch erfahrenen<br />
Entwicklern schwer, sich noch an die eigenen Überlegungen zu erinnern.<br />
Einfache Wartungsaufgaben, die Kunden verständlicherweise einfordern, geraten<br />
so zu einer störenden Mammutaufgabe, die zudem unpassenderweise immer<br />
parallel zu einem neuen, spannenden Projekt anfallen.<br />
Nicht zuletzt sind Kommentare auch dann hilfreich, wenn man Code nur für den<br />
Eigenbedarf entwickelt. Später, wenn Sie mehr Erfahrung haben, lesen Sie Ihren<br />
Code anders und können ihn schneller und professioneller überarbeiten.<br />
Mit Hilfe externer Werkzeuge können aus entsprechend aufgebauten Kommentaren<br />
automatisch Quellcode-Dokumentationen erstellt werden. Dies ist hilfreich,<br />
wenn Bibliotheken verkauft werden sollen oder andere Entwickler Teile des Projekts<br />
übernehmen.<br />
Kommentare in PHP5<br />
PHP5 kennt alle Kommentarzeichen, die schon seit der ersten Version mit dabei<br />
sind. Änderungen gab es hier nicht. Der Stil ist weitgehend aus der Programmiersprache<br />
C übernommen worden. Man unterscheidet Kommentarzeichen, die nur<br />
für eine Zeile gelten und wo die Kommentare am Zeilenende aufhören und solche<br />
Kommentare, die mit einem speziellen Endzeichen beendet werden müssen.<br />
Einfache Zeilenkommentare werden folgendermaßen geschrieben:<br />
<br />
Die beiden Schrägstriche leiten den Kommentartext ein, alles weitere bis zum<br />
nächsten Zeilenumbruch wird vom PHP-Parser ignoriert.<br />
Alternativ ist auch ein Doppelkreuz als einleitendes Kommentarzeichen möglich,<br />
die Anwendung ist jedoch bestenfalls die »zweite Wahl«:<br />
<br />
63
64<br />
Erste Schritte<br />
Die Wirkung ist identisch mit der ersten Variante. Beide können auch mitten auf<br />
der Zeile beginnen und gelten dann ab dort:<br />
<br />
Der Parser führt den PHP-Code am Zeilenanfang aus und ignoriert den Rest der<br />
Zeile. Die Schreibweise mit den Schrägstrichen entspricht der in C üblichen und<br />
ist weiter verbreitet. Sie sollten aus stilistischen Gründen entweder generell nur<br />
eine Version verwenden oder eine ganz klare Vereinbarung (mit sich selbst) treffen,<br />
unter welchen Umständen welche Kommentarzeichen verwendet werden.<br />
Neben den einzeiligen gibt es auch mehrzeilige Kommentare. Diese beginnen<br />
immer mit den Zeichen /* und enden mit */. Sie werden oft eingesetzt, um an<br />
den Anfang der Seite einen längeren Abschnitt mit <strong>In</strong>formationen über Programm<br />
und Autor zu setzen. Ebenso dienen Sie häufig zum so genannten »Auskommentieren«.<br />
Dabei werden zu Testzwecken ganze Programmteile als Kommentar markiert<br />
und so versteckt. <strong>In</strong>differente Fehler lassen sich damit besser einkreisen. Aber<br />
es gibt kleine Fallen beim Umgang mit Kommentaren. Betrachten Sie als (negatives)<br />
Beispiel den folgenden Code:<br />
<br />
Wenn Sie diesen Abschnitt komplett auskommentieren möchten, müssten Sie folgendes<br />
schreiben:<br />
<br />
Das funktioniert freilich nicht, weil der erste Kommentar am Ende der zweiten<br />
Zeile aufhört und die zweite Zeile ($d usw.) ausgeführt wird. Deshalb sind zwei<br />
verschiedene Kommentarzeichen sehr hilfreich, denn hätte man gleich den Zeilenkommentar<br />
verwendet, hätten die Kommentarzeichen ihre Wirkung nicht verfehlt:
Benennungsregeln<br />
Professionelles Programmieren<br />
Neben den Kommentaren gibt es auch Regeln für die Benennung von Variablen,<br />
Klassen, Methoden und Funktionen. Dies gilt nicht nur für die Schreibweise, sondern<br />
auch die Art und Weise der Namensvergabe. Lesbare, sinnvolle und eindeutige<br />
Namen sind nicht immer einfach zu finden, zeichnen jedoch guten und<br />
professionellen Code aus.<br />
Regeln aus der PEAR-Welt<br />
Fest zu PHP gehört eine große Sammlung an Bibliotheken und Skripten, die immer<br />
wiederkehrende Aufgaben erledigen: PEAR (PHP Extension and Application Repository).<br />
Da PEAR-Code weitgehend in PHP geschrieben wird und viele Hundert<br />
Entwickler dazu beisteuern, wurde dafür eine Vorgabe entworfen, welchen Stil der<br />
Code haben muss, damit alle anderen damit problemlos arbeiten können. Diese<br />
Vorgaben sind sehr sinnvoll und sollten – mangels anderer Richtlinien – für alle<br />
mittleren und größeren PHP-Projekte gelten. Kleine Testskripte und andere »Einzeiler«<br />
profitieren davon freilich nur bedingt, sodass man es hier etwas lockerer<br />
sehen kann. <strong>In</strong> der PEAR-Welt heißen diese Vorgaben »Coding Standards«.<br />
PHP Coding Standards<br />
PHP ist eine so genannte Klammersprache, Blöcke zusammengehörigen Codes<br />
werden dabei durch geschweifte Klammern markiert. Es liegt in der Natur der<br />
Sache, dass diese Blöcke oft tief verschachtelt werden. Um zusammengehörende<br />
Klammern halbwegs lesbar zu behalten, werden die innen liegenden Codes eingerückt.<br />
Der Coding Standard schreibt eine Einrücktiefe von vier Zeichen und die<br />
Verwendung von Leerzeichen vor. Editoren sollten so eingestellt werden, dass sie<br />
bei Druck auf die (TAB)-Taste vier Leerzeichen erzeugen.<br />
65
66<br />
Erste Schritte<br />
Auch die Zeilenlänge hat Einfluss auf die Lesbarkeit. Mehr als 85 Zeichen sind<br />
schlecht darstellbar, wenn in modernen Entwicklungsumgebungen noch Werkzeugkästen<br />
und Symbolleisten auf dem Bildschirm platziert werden müssen. PHP<br />
ist sehr tolerant in Bezug auf Zeilenumbrüche, deshalb ist eine »Verteilung« von<br />
Code auf mehrere Zeilen besser als endlose Codeschlangen.<br />
Die Bedeutung der Blöcke mit geschweiften Klammern wurde bereits angesprochen.<br />
Auch deren Schreibweise ist vorgegeben. Sie sollten überdies immer gesetzt<br />
werden, auch wenn dies im konkreten Kontext nicht zwingend erforderlich ist. Ein<br />
Beispiel zeigt dies:<br />
<br />
Hier wird die Ausgabe von $a nur dann erfolgen, wenn die Bedingung erfüllt ist.<br />
Möglicherweise wollen Sie zu Testzwecken auch die andere Variable $b ausgeben.<br />
Im Eifer des Gefechts steht dann folgendes im Skript:<br />
<br />
Das ist zwar nett eingerückt, nur interessiert das PHP kaum. Ohne Klammern<br />
wirkt if nur auf eine einzige Anweisung. Deshalb würde der erste Versuch besser<br />
folgendermaßen aussehen:<br />
<br />
Die Klammern umschließen den Block eindeutig und eine Erweiterung ist unproblematisch.<br />
Das Beispiel zeigt auch, dass die schließende Klammer eine Zeile für<br />
sich allein einnimmt. Noch besser lesbar, aber weniger verbreitet, ist es, auch die<br />
öffnende Klammer allein auf eine Zeile zu setzen. Dies betont die Verschachtelungsebene<br />
noch intensiver:<br />
Professionelles Programmieren<br />
echo $a;<br />
}<br />
else<br />
{<br />
// Tu nichts!<br />
}<br />
?><br />
Falls ein Block endet und aus technischen Gründen keine Klammer möglich ist,<br />
fügen Sie einfach eine Leerzeile hinzu. Dies ist beispielsweise beim switch-Befehl<br />
möglich:<br />
<br />
Jedes break wird hier von einer Leerzeile abgeschlossen.<br />
Funktionsaufrufe – egal ob zu internen oder selbst definierten Funktionen – werden<br />
immer folgendermaßen geschrieben:<br />
$a = funktion($parameter, $parameter2);<br />
Dabei gilt, dass vor und nach dem Gleichheitszeichen jeweils ein Leerzeichen<br />
steht. Die runde Klammer nach dem Funktionsnamen folgt ohne Leerzeichen<br />
und nach jedem Komma in der Parameterliste steht wiederum ein Leerzeichen.<br />
Das schließende Semikolon folgt wieder ohne Pause.<br />
Manchmal kommt es vor, dass sehr viele Zuweisungen dieser Art hintereinander<br />
stehen. Dann kann man diese nach den Gleichheitszeichen ausrichten, um die<br />
Lesbarkeit weiter zu verbessern:<br />
67
68<br />
Erste Schritte<br />
$antonia_name = funktion($parameter, $parameter2);<br />
$bert = funktion($parameter3);<br />
Zu den Standards gehört übrigens auch, sich bei der Markierung von Code-Blöcken<br />
auf zu beschränken und keine der anderen Varianten zu benutzen.<br />
Zuletzt noch ein Tipp für den Kopf eines Skripts, der <strong>In</strong>formationen zum Programm,<br />
Autor und der Lizenz enthalten sollte:<br />
<br />
Die erste Zeile steuert die Anzeige im Unix-Editor VIM, was vermutlich nur eine<br />
Minderheit wirklich tangiert.<br />
Namenskonventionen<br />
Ob Sie deutsch oder englisch schreiben, bleibt Ihnen überlassen. Hier sollte das<br />
potenzielle Zielpublikum beachtet werden. Wer seine Skripte im <strong>In</strong>ternet anbietet,<br />
sollte keine deutschen Namen verwenden. Für den Eigenbedarf ist es völlig in<br />
Ordnung und weit besser, als unpassende oder falsche englische Begriffe zu verwenden,<br />
was eher peinlich ist.
Webserver und Browser<br />
Variablen- und Funktionsnamen unterliegen keiner zufälligen Namensgebung.<br />
Sie sollten bedenken, dass Variablen immer Werte enthalten, also Zustände darstellen.<br />
Sie werden deshalb mit Nomen bezeichnet: $name, $laenge, $content usw.<br />
Die Namenskonventionen sind strenger als die von PHP selbst geforderten<br />
Eigenschaften der Namen, wie sie am nächsten Tag beschrieben<br />
werden. Es sind also »freiwillige« Vereinbarungen, die helfen, für Menschen<br />
lesbaren Code zu erzeugen.<br />
Funktionen führen dagegen Code aus, sie »tun« also etwas. Folgerichtig nutzen<br />
Sie Verben zur Benennung, gegebenenfalls um ein Objekt ergänzt: $save(),<br />
$load() oder auch $ladeDaten(). Namensteile werden durch Großbuchstaben<br />
getrennt, wobei der erste Buchstabe klein ist. Ist die Zuordnung zu einem Modul<br />
(Package in der PEAR-Welt genannt) wichtig, wird der Modulname davor gestellt<br />
und durch Unterstrich getrennt: HTML_getData().<br />
Klassennamen beschreiben den Sinn der Klasse: Log, HTML_Error_Resolver. Sie<br />
beginnen immer mit einem Großbuchstaben und trennen Wörter mit Unterstrichen.<br />
<strong>In</strong> der Regel handelt es sich um Nomen bzw. um substantivierte Verben.<br />
Klassenmitglieder unterscheiden sich nicht von den allgemeinen Regeln, Methoden<br />
entsprechen Funktionen und Eigenschaften einfachen Variablen, da sie<br />
innerhalb der Klasse dieselbe Aufgabe übernehmen. Es ist üblich, private Mitglieder<br />
von Klassen mit einem führenden Unterstrich zu benennen: $_content. Die<br />
Regel stammt aus den PHP 4-Zeiten und ist nicht zwingend bei PHP5, weil es<br />
einen speziellen Modifizierer gibt: private. Wie Sie es verwenden, sollten Sie pro<br />
Projekt selbst festlegen und diese Regel dann konsequent durchhalten.<br />
Konstanten werden immer in Großbuchstaben geschrieben. Auch hier gilt: Sind<br />
sie Teil eines Moduls, wird der Modulname davor gesetzt und durch Unterstrich<br />
getrennt: DB_INFO, XML_ERROR, WIDTH, HEIGHT.<br />
2.5 Webserver und Browser<br />
Webserver und Browser sind die beiden Programme, die bei der Bereitstellung<br />
und Nutzung von Webseiten eine herausragende Rolle einnehmen. Sie verständigen<br />
sich mit Hilfe des Protokolls HTTP miteinander. Dieser Abschnitt führt kurz<br />
in Grundlagen und Prinzipien ein – Handwerkszeug für künftige PHP-Entwickler.<br />
69
70<br />
Erste Schritte<br />
Prinzip des Seitenabrufs<br />
Zwischen Browser und Webserver läuft ein streng reglementiertes Protokoll:<br />
HTTP. Die Abkürzung steht für Hypertext Transfer Protocol und sagt bereits, wozu<br />
es dient: dem Übertragen von durch Hyperlinks verknüpften Seiten. Bevor jedoch<br />
die erste Seite im Browser erscheint, passiert einiges auf dem Weg zum und natürlich<br />
im Server.<br />
Der ganze Ablauf startet mit der Eingabe der Adresse der Seite im Browser. Der<br />
Browser nutzt nun Betriebssystemfunktionen, um eine Verbindung mit dem<br />
Server herzustellen. Der erste Schritt besteht in der Auflösung der Adresse. <strong>In</strong>tern<br />
wird auf Datenebene die Protokollkombination TCP/IP benutzt. Sender und<br />
Empfänger einer Botschaft benötigen hier eine eindeutige IP-Adresse der Art<br />
134.15.211.65. Weil sich derartige Adressen schlecht merken (und vermarkten) lassen,<br />
wurde das Domain Name System (DNS) entwickelt. Es basiert auf dem<br />
gleichnamigen DNS-Protokoll und verwendet DNS-Server, die Zuordnungen zwischen<br />
Adressen und Domain-Namen speichern. Diese Server bilden im <strong>In</strong>ternet<br />
eine Hierarchie, wobei der Ausgangspunkt von etwas mehr als einem Dutzend<br />
Root-Server gebildet wird. Diese Server lösen die Toplevel-Namen, wie »com«<br />
oder »de« auf. Erhält der Browser also eine ihm unbekannte Domain, fragt er den<br />
nächsten erreichbaren Name-Server nach der Adresse. Dies ist der Server, den der<br />
Provider beim Aufbau der Verbindung ins <strong>In</strong>ternet zugewiesen hat. Große Firmen<br />
betreiben manchmal auch eigene Name-Server. Der Name-Server wird freilich<br />
nicht alle Millionen Adressen und deren IP-Nummern kennen. Also fragt er einen<br />
der Root-Server an, ob diesem eine verwaltende <strong>In</strong>stanz für das benötigte Toplevel<br />
bekannt ist. Der Root-Server verweist auf einen der Landes-Server oder den Verwalter<br />
der generischen Level. Endet die Adresse auf »de« ist nic.de zuständig,<br />
betrieben durch das DENIC in Karlsruhe.<br />
War die vollständige Adresse www.comzept.de, wird nun beim DENIC nach dem<br />
Betreiber der Domain »comzept.de« gefragt. Diese Anfrage verweist auf einen weiteren<br />
Name-Server, nämlich dem für comzept.de zuständigen. Dies ist entweder<br />
ein Server in der betreffenden Firma oder bei deren Provider. Dieser Name-Server<br />
(der vierte in der Kette), hat nun seinerseits nur eine kleine Liste der Server-Adressen<br />
und deren Namen zu verwalten, im Beispiel also www und dazu die passenden<br />
IP-Adresse. Diese <strong>In</strong>formation wird über das DNS-Protokoll dem zuerst anfragenden<br />
Name-Server übermittel und gelangt von dort zum Browser. Der Browser<br />
besitzt seinerseits eine eindeutige Adresse, die vom Provider beim Verbindungsaufbau<br />
vergeben wurde. <strong>In</strong> Netzen mit festen IP-Adressen läuft meist ein Dienst, der
Webserver und Browser<br />
die Adressevergabe steuert oder die Angaben sind auf der Netzwerkkarte des Computers<br />
fest hinterlegt.<br />
Das allein reicht jedoch noch nicht, um Daten zu übertragen. Denn auf dem Server<br />
und meist auch auf dem Client laufen unzählige Dienste, die mit der aktuellen<br />
Anforderung nichts zu tun haben. Um bestimmte Dienste gezielt erreichen zu<br />
können, werden so genannte Ports verwendet. Webserver haben im Allgemeinen<br />
(Standard) den Port 80. Mail-Server (SMTP) haben den Port 25 und POP3-Server<br />
erreicht man über Port 110. Der Browser hat auch einen Port, der jedoch nicht fest<br />
zugeordnet ist sondern dynamisch aus dem Adresspool oberhalb 1.024 vergeben<br />
wird, beispielsweise 4.768. IP-Adresse und Port bilden zusammen einen so genannten<br />
Socket. Zwischen zwei solchen Sockets spielt sich dann die Kommunikation<br />
ab. Während IP für die Adresszuordnung sorgt, wird der Datenstrom per TCP<br />
gesteuert. Hier findet die Verpackung der Daten in Pakete und die Fehlerprüfung<br />
statt.<br />
Die Daten, die der Browser nun sendet, werden ihrerseits wieder in ein höheres<br />
Protokoll verpackt: HTTP. Dies sind lesbare Codes, die bestimmte Aktionen am<br />
Server steuern oder im Browser zur Darstellung der Seiten führen.<br />
HTTP auf einen Blick<br />
Was der Browser sendet, sieht bei der ersten Anforderung in etwa folgendermaßen<br />
aus:<br />
GET info.php<br />
GET ist das Kommando, danach folgt die Ressource, die mit GET angefordert<br />
werden soll. Der Webserver erkennt das Kommando und weiß nun, dass er die<br />
Datei laden und ausgeben soll. Wurde die Dateierweiterung verknüpft, kann sich<br />
noch ein Programm dazwischen schieben und die Datei verarbeiten, bevor sie<br />
gesendet wird. Die Erweiterung .php übergibt die Daten an PHP und dort wird die<br />
Datei geladen, nach den Skript-Tags durchsucht, der Code untersucht und ausgeführt<br />
und eine fertige HTML-Seite erstellt. Diese wird wieder an den Webserver<br />
zurückgegeben und der sendet sie dann in Beantwortung der Anforderung aus:<br />
HTTP/1.1 200 OK<br />
Content-Length: 46783<br />
<br />
<br />
<br />
71
72<br />
Erste Schritte<br />
Die Antwort besteht aus einem Hinweis auf das Protokoll (1.0 oder 1.1), einem Statuscode<br />
(200 steht für OK, 404 für nicht gefunden) und verschiedenen Kopfzeilen,<br />
die Zusatzinformationen liefern. Diese Kopfzeilen umfassen mehr als im Beispiel<br />
gezeigt, beispielsweise auch Angaben über den Server, Cookies usw. Im Beispiel<br />
wurde lediglich die Länge der Seite übertragen. Nach einer Leerzeile beginnt<br />
dann der <strong>In</strong>halt der Datei.<br />
Wenn eine HTML-Seite nun drei Bilder enthält, dann wird für jedes Bild eine<br />
eigene GET-Anforderung erstellt und gesendet. Der Webserver wird meist keine<br />
Verknüpfung für Bilder haben und diese als Ressource direkt von der Festplatte<br />
laden und senden. Binärdaten werden ebenso angehängt wie alle anderen<br />
<strong>In</strong>formationen – freilich so codiert, dass nichts verloren geht. Damit nicht jedes<br />
Mal wieder die gesamte Kette von Name-Servern abgefragt wird, speichern diese<br />
das letzte Ergebnis einige Zeit, sodass der gesamte Ablauf stark beschleunig wird.<br />
Neben GET kann der Browser auch POST verwenden und seinerseits Daten an<br />
die Anforderung anhängen. Dies wird genutzt, um Daten aus Formularen zu übertragen.<br />
Diese <strong>In</strong>formationen werden PHP ebenfalls bereitgestellt, sodass sie in<br />
Skripten ausgewertet werden können.<br />
2.6 Kontrollfragen<br />
1. Wie geben Sie mehrzeilige Texte aus PHP-Code heraus am besten aus?<br />
2. Welche Funktion eignet sich zur Ausgabe hexadezimaler Farbangaben?<br />
3. Welche Kommentarform wird am besten verwendet, wenn hinter einer Codezeile<br />
ein kurzer, auf die Zeile bezogener Kommentar stehen soll? Mögliche Varianten<br />
sind: /* */, // oder #.
Daten verarbeiten<br />
3
74<br />
Daten verarbeiten<br />
Elementarer Bestandteil eines jeden Programms ist die Möglichkeit, Daten zu<br />
erfassen, zu speichern und damit vielfältige Operationen anzustellen. PHP bietet<br />
dafür alle auch aus anderen Sprachen bekannten Syntaxformen und -varianten.<br />
3.1 Variablen und Literale<br />
Variablen nehmen unter einem weitgehend freiwählbaren Namen Daten auf. Sie<br />
lassen sich jederzeit verändern und können in Ausdrücken als Ersatz für literale<br />
Konstrukte eingesetzt werden. PHP erkennt Variablen an einem vorangestellten<br />
$-Zeichen. Der Name muss bestimmten Regeln gehorchen, damit er gültig ist:<br />
Namen dürfen nur Buchstaben, Zahlen und den Unterstrich enthalten.<br />
Das erste Zeichen des Namens muss ein Buchstabe oder der Unterstrich sein.<br />
Groß- und Kleinschreibung wird unterschieden.<br />
Das impliziert, dass Leerzeichen nicht erlaubt und Umlaute tabu sind.<br />
PHP und Datentypen<br />
PHP und Datentypen sind ein besonderes Kapitel, leider kein besonders erfreuliches.<br />
Es ist typisches Merkmal von einfachen Skriptsprachen, mit den Datentypen<br />
sehr locker umzugehen. Das heißt, es wird dem Benutzer wenig Gelegenheit gegeben,<br />
selbst festzulegen, ob der <strong>In</strong>halt einer Variable eine Zahl oder eine Zeichenfolge<br />
oder etwas anderes ist. PHP stellt dies anhand bestimmter Kriterien selbst fest<br />
und wechselt den Typ auch mal auf halber Strecke.<br />
Das ist nicht immer von Vorteil, auch wenn Sie am Anfang weniger einrichten<br />
und festlegen müssen als bei anderen Sprachen. Es führt jedoch zu Problemen,<br />
wenn Ihre Anwendung so programmiert ist, dass ein bestimmter Typ erforderlich<br />
ist und dies gleichzeitig nicht ständig absichert. Es kommt dann zu so genannten<br />
Seiteneffekten, die schwer nachvollziehbare Fehlerquellen sind.<br />
Nichtsdestotrotz kennt PHP natürlich den intern und automatisch festgelegten<br />
Typ, den eine Variable haben kann. Neben Variablen ist auch die Art des Rückgabewerts<br />
einer Funktion in dieser Weise festgelegt (auch diesen können Sie nicht<br />
explizit erzwingen). Die automatische Ermittlung erfolgt im Rahmen des Zuweisungsprozesses:
$zahl = 23;<br />
Variablen und Literale<br />
Hier wird der Variablen $zahl der Wert 23 zugewiesen. PHP erkennt keine Anführungszeichen,<br />
wie sie für Zeichenfolgen typisch sind und auch keinen Dezimalpunkt,<br />
wie er bei Gleitkommazahlen auftritt. Also handelt es sich um eine Ganzzahl,<br />
als <strong>In</strong>teger bezeichnet.<br />
Ganzzahlen lassen sich auch oktal und hexadezimal darstellen. Oktale Zahlen<br />
haben die Zahlenbasis 8 und die Ziffern 0 bis 7. Sie werden erkannt, wenn eine<br />
führende Null davor geschrieben wird:<br />
$zahl = 040;<br />
Hexadezimalzahlen haben die Zahlenbasis 16 und verwenden als Ziffernzeichen<br />
für die Zeichen 0 bis 9 und A bis F. Die Kennzeichnung erfolgt durch das Präfix<br />
»0x«:<br />
$zahl = 0xFF;<br />
Zeichenkettenliterale werden folgendermaßen geschrieben:<br />
$zeichen = "Zahl 23";<br />
Diese Anweisung legt die Zeichenfolge »Zahl 23« in die Variable $zeichen. Hier<br />
erkennt PHP den Datentyp an den Anführungszeichen. Ob Sie einfache oder doppelte<br />
verwenden, ist für den Typ ohne Bedeutung. Folgendes können Sie deshalb<br />
auch schreiben:<br />
$zeichen = 'Zahl 23';<br />
Gleitkommazahlen (Teil des Zahlenraums der reellen Zahlen) haben Kommastellen,<br />
ein Merkmal ist also der Dezimalpunkt. Denken Sie daran, dass in Programmiersprachen<br />
immer die englische Schreibweise verwendet wird:<br />
$wert = 13.04;<br />
Große Zahlen können durch eine Exponentialdarstellung abgebildet werden.<br />
Dabei wird der Wert als Konstruktion der Art 3·104 dargestellt. <strong>In</strong> PHP wird diese<br />
Form folgendermaßen geschrieben:<br />
$wert = 3E4;<br />
Das »E« entspricht also »zehn hoch«. An dieser Stelle sei auch erwähnt, dass die<br />
Genauigkeit durch die Rechenbreite des Computers bedingt nicht unendlich ist<br />
und auch nicht genau dem reellen Zahlenraum entspricht. Scheinbar verrechnet<br />
sich PHP bei sehr vielen Stellen deshalb manchmal geringfügig.<br />
75
Literale<br />
76<br />
Daten verarbeiten<br />
Die rechte Seite der Zuweisungen, wie ?23?, 13.04 oder 3E4 werden als Literale<br />
bezeichnet – es sind konstante Werte mit einem definierten Aufbau. PHP erkennt<br />
bestimmte Literale und erzeugt daraus entsprechende Werte.<br />
Hinweise zur Definition<br />
<strong>In</strong> der Standardeinstellung von PHP5 wird erwartet, dass Variablen vor der ersten<br />
Verwendung deklariert werden. Damit ist gemeint, dass mindestens einmal ein<br />
Wert zugewiesen wurde und PHP damit den Datentyp feststellen konnte. Auch<br />
wenn dies trivial klingt: Man kann PHP so einstellen, dass dieser Zwang zur Deklaration<br />
nicht erforderlich ist und sich damit eine Fehlerquelle einfangen. Sie sollten<br />
es deshalb immer bei der Regel belassen, jede Variable zu deklarieren. Haben<br />
Sie nämlich versehentlich einen Tippfehler gemacht oder Groß- und Kleinschreibung<br />
verwechselt, weist eine Fehlermeldung auf diesen Umstand hin. Ohne diese<br />
würde Ihr Programm wegen der automatischen Typerkennung und -umwandlung<br />
möglicherweise dennoch funktionieren – bis bestimmte Daten auftreten, bei<br />
denen es aus dem Tritt kommt. Solche sporadisch auftretenden Fehler sind nur<br />
sehr schwer zu finden.<br />
3.2 Konstanten<br />
Oft werden Variablen eingesetzt, um einen Wert zur Konfiguration einer Anwendung<br />
zu bestimmen. Diese Werte ändern sich während der Abarbeitung des Programms<br />
nicht. PHP bietet mit Konstanten eine bessere Form. Konstanten können,<br />
nachdem sie einmal einen Wert erhalten haben, nicht mehr verändert werden.<br />
Konstanten definieren und nutzen<br />
Zuständig für die Erzeugung einer Konstanten ist die Funktion define. Benötigt<br />
wird der Name der Konstanten als Zeichenfolge und der zuzuweisende Wert.<br />
define('BREITE', 640);
Konstanten<br />
<strong>In</strong> Ausdrücken werden Konstanten ebenso wie Variablen verwendet. Sie werden<br />
jedoch ohne führendes $-Zeichen geschrieben und können deshalb nicht in Zeichenfolgen<br />
mit doppelten Anführungszeichen erkannt werden.<br />
Listing 3.1: define.php – Definition und Ausgabe einer Konstanten<br />
<br />
Umgang mit Konstanten<br />
Abbildung 3.1:<br />
Ausgabe einer Konstanten<br />
Da Konstanten nur einmalig definiert werden können, kann es zu Problemen<br />
kommen, wenn Konfigurationen von Programmteilen mehrfach erfolgen. Das<br />
kann notwendig sein, um Module unabhängig voneinander anzuwenden. Jedes<br />
Modul soll beispielsweise in der Lage sein, sich seine benötigten Konstanten selbst<br />
zu definieren, wenn keine übergeordnete <strong>In</strong>stanz dies bereits getan hat. Diese Art<br />
zu programmieren ergibt größtmögliche Sicherheit – das Modul bleibt in jedem<br />
Kontext stabil.<br />
Zur Abfrage einer bereits erfolgten Definition stellt PHP eine weitere Funktion zur<br />
Verfügung: defined. Der Rückgabewert kann Wahr (TRUE) oder Falsch (FALSE)<br />
sein, je nachdem, ob die als Parameter angegebene Konstante bereits existierte.<br />
Die Anwendung ist nur mit einer if-Anweisung sinnvoll, wie folgendes Beispiel<br />
zeigt:<br />
<br />
Wie schon bei der Definition der Konstanten wird auch hier der Name, nicht<br />
jedoch die Konstante selbst angegeben. Das ist logisch, weil diese ja möglicherweise<br />
noch gar nicht existiert.<br />
77
78<br />
Daten verarbeiten<br />
3.3 Rechnen und Vergleichen mit Ausdrücken<br />
Ganz nebenbei wurde der Begriff »Ausdruck« bereits erwähnt. Es handelt sich<br />
dabei immer um ein Konstrukt, das genau einen definierten Wert zurückgibt. Folgende<br />
Beispiele stellen gültige Ausdrücke in PHP dar:<br />
17 + 4<br />
"Heute ist " . "Mittwoch"<br />
3E4 == 30000<br />
Der erste Ausdruck gibt 21 zurück; PHP erkennt zwei <strong>In</strong>teger-Werte und führt eine<br />
Addition aus. Der zweite ergibt die Zeichenfolge »Heute ist Mittwoch«. Der Punkt<br />
ist ein Operator zur Verbindung von Zeichen. Der dritte Ausdruck vergleicht zwei<br />
Zahlen, die beiden Gleichheitszeichen sind ein so genannter Boolescher Operator.<br />
<strong>In</strong> diesem Fall sind die beiden Zahlen gleich und der Ausdruck gibt »Wahr«<br />
(TRUE) zurück.<br />
Ausdrücke und Operatoren<br />
Ausdrücke entstehen durch die Verbindung von Variablen, Konstanten, Literalen<br />
und Operatoren. Den Operatoren kommt also eine sehr große Bedeutung zu. Es<br />
gibt sehr viele davon, die für die unterschiedlichsten Zwecke eingesetzt werden<br />
können.<br />
Ausdrücke<br />
Als Ausdruck werden alle Kombinationen aus Literalen, Variablen, Konstanten<br />
oder Funktionsaufrufen und Operatoren bezeichnet, die einen konkreten skalaren<br />
Wert zurückgeben. Dabei ist es erst einmal nicht relevant, welchen Datentyp dieser<br />
Wert hat. Typische Ausdrücke sind:<br />
$a + 4<br />
45 – 56 * 99<br />
$zahl1--<br />
$string = $buchstabe . " " . $ende<br />
Die Benutzung erfolgt entweder beim Funktionsaufruf, als Zuweisung (Ziel ist<br />
eine Variable) oder im Kontext anderer Ausdrücke.
Rechnen mit Operatoren<br />
Zum Rechnen werden folgende Operatoren verwendet:<br />
Zeichen Operandenzahl Anwendung Bedeutung<br />
+ 2 23 + $zahl Addition<br />
- 2 $wert – 17 Subtraktion<br />
Rechnen und Vergleichen mit Ausdrücken<br />
- 1 -345 Negatives Vorzeichen<br />
* 2 12 * 11 Multiplikation<br />
/ 2 100 / 10 Division<br />
% 2 6 % 3 Rest einer Ganzzahl-Division (Modulus<br />
genannt)<br />
() 1 (Ausdruck) 13 * (4 + 7) Klammern zur Bildung von Teilausdrücken<br />
++ 1 $ziffer++ Erhöhung um 1, <strong>In</strong>krement<br />
-- 1 --$zahl Verringerung um 1, Dekrement<br />
Tabelle 3.1: Arithmetische Operatoren<br />
Der Modulus-Operator kommt sehr häufig zum Einsatz, auch wenn der Rest einer<br />
Division im Alltagsleben kaum eine Rolle spielt. Viele Divisionen ergeben jedoch<br />
gebrochene Zahlen und dies ist bei der Ausgabe nicht immer praktisch. So müssen<br />
Sie vielleicht in einer Tabelle jede dritte Zeile einfärben. Aber wie erkennt man<br />
jede dritte Zeile? Haben Sie einen Wert, der die Zeilen zählt, teilen Sie diesen einfach<br />
durch Drei. Immer wenn der Rest der Ganzzahl-Division gleich 0 ist, wurde<br />
eine dritte Zeile erreicht.<br />
<strong>In</strong>teressant sind auch die <strong>In</strong>krement- und Dekrementoperatoren. Mit diesen wird<br />
der Wert in einer Variablen um eins erhöht bzw. verringert. <strong>In</strong>nerhalb von Ausdrücken<br />
angewendet stellt sich die Frage, ob die Veränderung vor oder nach der Entnahme<br />
des Wertes erfolgen soll. Gesteuert wird das durch die Platzierung des<br />
Operators. Das folgende Beispiel zeigt dies:<br />
79
80<br />
Daten verarbeiten<br />
Listing 3.2: Operator<strong>In</strong>cdec.php – Werte um eins erhöhen oder verringern<br />
<br />
Die Ausgabe zeigt die Wirkung:<br />
Abbildung 3.2:<br />
Ausgabe von Listing 3.2<br />
Bei der Berechnung der Summe wird 20 ausgegeben, weil die Erhöhung der Variable<br />
$digit erst nach der Auswertung des Ausdrucks erfolgt. Bei der Ausgabe der<br />
Variablen $number erfolgt die Veränderung dagegen vorher, deshalb erscheint 11<br />
(statt 12).<br />
Zeichenfolgen verketten<br />
Es wurde bereits angedeutet, dass Zeichenfolgen mit einem eigenen Operator verkettet<br />
werden, dem Punkt ».«. Hier gibt es keine weiteren Besonderheiten. Bei der<br />
Anwendung ist jedoch zu beachten, dass PHP erforderlichenfalls eine Typumwandlung<br />
vornimmt, wie das folgende Beispiel zeigt:<br />
Listing 3.3: TypeStringpoint.php – PHP erkennt hier alle Variablen als Zeichenfolgen<br />
<br />
Dieses Skript gibt nicht 20 aus, sondern »137«. Das liegt daran, dass der Punkt nur<br />
Zeichenfolgen verbinden kann. Damit das funktioniert, wandelt PHP die beiden<br />
Variablen entsprechend um; aus der Zahl 13 wird die Zeichenfolge »13« usw. Dies
Rechnen und Vergleichen mit Ausdrücken<br />
geschieht hier übrigens nur für diesen Ausdruck, danach stehen die Variablen<br />
unverändert als Zahlen zur Verfügung. Im Hinblick auf die Programmsicherheit<br />
und Stabilität ist dieses Verhalten kritisch. Sie sollten hier besondere Sorgfalt walten<br />
lassen.<br />
Kombinationsoperatoren<br />
Sie kennen bereits die Zuweisung mit dem Gleichheitszeichen. Alle dualen Operatoren,<br />
also solche mit zwei Operanden (+, –, *, /, %), können mit der Zuweisung<br />
kombiniert werden. Das spart lediglich Tipparbeit und hat keinen Effekt auf den<br />
Programmfluss. Die folgende klassische Operation kann verkürzt werden:<br />
$a = $a + 5;<br />
Hiermit wird der <strong>In</strong>halt von $a um fünf erhöht. Kürzer geht es mit einem Kombinationsoperator:<br />
$a += 5;<br />
Freilich geht das nicht immer. Folgende Operation ist nicht direkt verkürzbar:<br />
$a = 17 - $a;<br />
Vergleiche und Boolesches<br />
Zu den typischen Operationen gehören auch Vergleiche (größer, kleiner, gleich)<br />
und Boolesche Operationen (logische Ausdrücke). Der Name Boolesch stammt<br />
von George Boole, der die Grundlagen der logischen Algebra entwickelte, und<br />
wird deshalb immer groß geschrieben. Ausdrücke, die sich logisch auswerten lassen,<br />
geben immer einen Wahrheitswert zurück, also entweder »Wahr«, in PHP als<br />
TRUE ausgedrückt oder »Falsch«, wofür in PHP die Konstante FALSE steht.<br />
PHP erkennt zwar logische Ausdrücke und kann bei Vergleichen entsprechend<br />
damit umgehen, nutzt aber intern Zahlen zur Darstellung. Das führt dazu, dass<br />
andere Ausdrucksarten fast immer auch in einen Wahrheitswert gewandelt werden,<br />
wenn dies vom Kontext her erforderlich ist. Das führt manchmal zu schweren<br />
Programmfehlern. Achten Sie deshalb unbedingt auf eine saubere Konstruktion<br />
der Ausdrücke.<br />
81
82<br />
Daten verarbeiten<br />
Die Vergleichsoperatoren finden Sie in der folgenden Tabelle:<br />
Operator Bedeutung<br />
< Kleiner als<br />
> Größer als<br />
= Größer als oder Gleich<br />
== Gleich, wobei der Datentyp beider Operanden vor dem Vergleich so<br />
umgewandelt wird, dass er gleich ist.<br />
=== Gleich, wobei der Datentyp nicht verändert wird und identisch sein muss,<br />
damit der Ausdruck Wahr wird<br />
!= Ungleich als Umkehroperation zu ==<br />
!== Ungleich als Umkehroperation zu ===<br />
(a1)?(a2):(a3) Trinärer Operator, der den Ausdruck a1 auswertet und, wenn dieser Wahr<br />
zurückgibt, a2 ausführt, ansonsten a3. Alle drei Bestandteile sind erforderlich,<br />
die Klammern sind optional.<br />
Tabelle 3.2: Vergleichsoperatoren<br />
Wenn man nun dergestalt Ausdrücke entwickelt, ist auch eine Kombination von<br />
Teilausdrücken erforderlich, um komplexere Strukturen abzubilden. Dazu werden<br />
die logischen Operatoren eingesetzt, meist in Verbindung mit Klammern, um<br />
die Rangfolge zu bestimmen.<br />
Operator Bedeutung<br />
|| Oder. Wahr, wenn einer der beiden Operanden Wahr ist.<br />
&& <strong>Und</strong>. Wahr, wenn beide Operanden Wahr sind.<br />
! Nicht, nur ein Operand möglich. Wahr, wenn der Operand Falsch ist.<br />
or Oder. Wahr, wenn einer der beiden Operanden Wahr ist.<br />
Tabelle 3.3: Logische (Boolesche) Operatoren
Operator Bedeutung<br />
and <strong>Und</strong>. Wahr, wenn beide Operanden Wahr sind.<br />
Allgemeine Aussagen zu Zeichenketten<br />
xor Exklusives Oder. Wahr, wenn einer der Operanden Wahr ist, jedoch<br />
nicht beide (ausschließendes Oder).<br />
Tabelle 3.3: Logische (Boolesche) Operatoren (Forts.)<br />
Scheinbar sind die Operatoren || und or bzw. && und and identisch. Dem ist<br />
jedoch nicht so, denn jeder Operator hat eine definierte Rangfolge. Das heißt,<br />
wenn Sie Ihre Ausdrücke ohne Klammern bauen, muss der <strong>In</strong>terpreter beim Auflösen<br />
eine bestimmte Reihenfolge einhalten. Diese ist nicht einfach von links nach<br />
rechts, sondern berücksichtigt bestimmte typische Gegebenheiten.<br />
Betrachten Sie den folgenden Ausdruck:<br />
echo 4 + 5 * 6;<br />
PHP berechnet hier – mathematisch korrekt – das Ergebnis 34. Denn wegen der<br />
Regel »Punkt- vor Strichrechnung« wird zuerst der rechte Teilausdruck ausgewertet.<br />
Soll dies umgangen werden, sind Klammern erforderlich. Nun ist eine so allgemeine<br />
Rechenregel jedem geläufig. Bei anderen Operatoren muss der Designer<br />
der Sprache eine Reihenfolge festlegen. Mehr <strong>In</strong>formationen zur Rangfolge (Assoziativität)<br />
der Operatoren werden im Zusammenhang mit dem if-Befehl (Siehe<br />
Abschnitt »Einfache Verzweigungen mit if«).<br />
3.4 Allgemeine Aussagen zu Zeichenketten<br />
Zeichenketten oder Zeichenfolgen sind ein elementarer Datentyp in PHP. Die<br />
Ausgabe von Text nach HTML erfolgt immer als Zeichenkette. Aus diesem Grund<br />
sind Operationen mit Zeichenketten elementar und Sie sollten sich mit den<br />
Möglichkeiten von PHP ebenso auseinandersetzen wie mit den vielfältigen Einsatzfällen.<br />
Datentyp und Größe<br />
Als Datentyp unterscheidet PHP nicht zwischen Zeichenketten und einzelnen<br />
Zeichen. Es gilt deshalb generell, dass ein einzelnes Zeichen wie eine Zeichen-<br />
83
84<br />
Daten verarbeiten<br />
kette mit der Länge 1 behandelt werden kann. Der Datentyp selbst wird als<br />
»string« bezeichnet. Zur Umwandlung aus anderen Typen wird entsprechend der<br />
Operator (string) verwendet:<br />
$string = (string) 11;<br />
Zeichenketten können bis zu 2 Milliarden Zeichen enthalten (2 GByte), dies ist<br />
jedoch ein eher theoretischer Wert, da das Laufzeitverhalten unter sehr großen<br />
Datenmengen massiv leidet. Der Umfang typischer HTML-Seiten von einigen<br />
Dutzend KByte kann jedoch problemlos verarbeitet werden.<br />
Zeichenketten haben keine definierte (vorgegebene) Länge oder ein spezielles<br />
Endzeichen, wie dies bei C der Fall ist. Für die Abfrage der aktuellen Länge gibt es<br />
eine passende Funktion.<br />
Umgang mit Sonderzeichen<br />
Zeichenketten selbst stehen in ihrer literalen Form in Anführungszeichen. Wenn<br />
Sie nun ein solches Zeichen selbst ausgeben möchten, muss es maskiert werden.<br />
Die Maskierung erfolgt durch ein spezielles Maskierungszeichen, das in PHP wie<br />
in fast allen anderen Sprachen der Backslash »\« ist. Dem Backslash kommt deshalb<br />
eine besondere Bedeutung zu. Das folgende Beispiel zeigt, wie Anführungszeichen<br />
für ein HTML-Tag erzeugt werden:<br />
""<br />
Das kann vermieden werden (weil es schlecht lesbar ist), indem das jeweils korrespondierende<br />
Anführungszeichen genutzt wird:<br />
''<br />
Damit verliert man aber unter Umständen die Fähigkeit zur Variablenerkennung.<br />
Eine andere Aufgabe des Backslash besteht darin, Sonderzeichen zu erzeugen, die<br />
sich über die Tastatur nicht eingeben lassen, weil sie im Editor eine besondere<br />
Aufgabe haben. Das betrifft vor allem Zeilenumbrüche und Tabulatoren. Weitere<br />
Sonderzeichen, die PHP erkennt und nutzt, zeigt die folgende Tabelle:
Zeichen Bedeutung<br />
Allgemeine Aussagen zu Zeichenketten<br />
\n Zeilenvorschub, entspricht dem Code 0A (hexadezimal, in PHP 0x0A<br />
geschrieben)<br />
\r Wagenrücklauf, entspricht dem Code 0D (hexadezimal, in PHP 0x0D<br />
geschrieben)<br />
\t Tabulator, Code 9<br />
\\ Der Backslash selbst<br />
\$ Das $-Zeichen, wenn $ in dem gegebenen Zusammenhang eine besondere<br />
Bedeutung hat<br />
\« Das doppelte Anführungszeichen, wenn es in einem Literal aus doppelten<br />
Anführungszeichen steht<br />
\' Das einfache Anführungszeichen, wenn es in einem Literal aus einfachen<br />
Anführungszeichen steht<br />
\40 Ein Zeichen mit dem Code 040 im oktalen Zahlensystem (Basis 8, 040 = Leerzeichen)<br />
\x20 Ein Zeichen mit dem Code 0x20 im hexadezimalen Zahlensystem (auch das<br />
Leerzeichen)<br />
Tabelle 3.4: Sonderzeichen in Zeichenketten<br />
Beachten Sie, dass die Zeilenumbrüche unter Windows und Unix unterschiedlich<br />
sind. Während Unix nur \n einsetzt, wird unter Windows<br />
\n\r geschrieben. Dies ist zu beachten, wenn Datenausgaben betriebssystemunabhängig<br />
erfolgen sollen.<br />
Zeichenketten erkennen und bestimmen<br />
Wenn Sie feststellen möchten, ob eine Variable tatsächlich eine Zeichenkette enthält,<br />
nutzen Sie die Funktion is_string:<br />
is_string($var);<br />
Der Rückgabewert ist Wahr (TRUE) oder Falsch (FALSE). Alternativ kann auch gettype<br />
benutzt werden, hier wird bei einer Zeichenkette »string« zurückgegeben.<br />
85
86<br />
Daten verarbeiten<br />
Um die Typumwandlung festzulegen, wird entweder der Umwandlungsoperator<br />
(string) oder die Funktion settype benutzt:<br />
$string = settype($var, "string");<br />
Zeichenkettenoperationen<br />
Mit Zeichenketten kann man allerlei anstellen. Typische Operationen sind:<br />
Vergleichen, Suchen und Ersetzen<br />
Ermitteln der Zeichenzahl und anderer Eigenschaften<br />
Auswahl einer Teilzeichenkette und ähnliche Operationen<br />
Behandlung einzelner Zeichen<br />
Ermitteln von bestimmten Zeichen mit besonderer Bedeutung<br />
Umwandeln in einen anderen Datentyp und HTML-abhängige Funktionen<br />
Sprach- oder landesabhängiges Formatieren für die Ausgabe<br />
Zum Suchen und Ersetzen gibt es gleich zwei Möglichkeiten. Einmal kann mit<br />
Hilfe eines einfachen Vergleichsmusters gearbeitet werden, das eine exakte Übereinstimmung<br />
bedingt. Zum anderen gibt es universelle Suchmuster, so genannte<br />
reguläre Ausdrücke. Letztere werden in Abschnitt 3.6 »Reguläre Ausdrücke« ab<br />
Seite 95 behandelt.<br />
3.5 Die Zeichenkettenfunktionen<br />
Die praktische Anwendung der Zeichenkettenfunktionen in diesem Abschnitt<br />
zeigt die Einsatzbandbreite. Es geht in PHP – dies werden Sie später noch deutlicher<br />
sehen – mehr um das Auffinden der passenden Funktionen als um Probleme<br />
bei der Anwendung. Leider sind die Benennungsregeln bestenfalls als konfus zu<br />
bezeichnen. Sie zeigen eine der Mankos freier Software: Viele unkoordinierte Entwicklungsschritte<br />
und mangelnde Dominanz einer Ordnungsinstanz führen zu<br />
Chaos. So wird bei Zeichenketten oft zwischen Groß- und Kleinschreibung unterschieden.<br />
Ältere PHP-Funktionen verwenden zur Kennzeichnung der Verhaltensweise<br />
die Zeichen »case« (von »case insensitive«), neuere dagegen nur »i« (von<br />
»insensitive«). Einige Funktionen haben den Präfix »str_«, andere nur »str«, bei
Die Zeichenkettenfunktionen<br />
anderen gibt es gar keinen Hinweis im Namen darauf, dass sie zur Gruppe der Zeichenkettenfunktionen<br />
gehören.<br />
Suchen und Ersetzen<br />
Beim Suchen und Ersetzen geht es darum, Daten, die beispielsweise aus einer<br />
Datenbank oder einem Formular empfangen wurden, auf bestimmte Merkmale<br />
hin zu untersuchen. PHP bietet hier gleich mehrere Funktionen an.<br />
Suchen einer Zeichenfolge in einer anderen<br />
Angenommen, Sie haben eine Folge von Zeichen, beispielsweise einen Text aus<br />
einem Formular, und möchten ermitteln, ob sich darin ein bestimmtes Wort<br />
befindet. Dann wird die Funktion strpos eingesetzt. Sie liefert die Position der<br />
Fundstelle:<br />
Listing 3.4: strpos.php – Ermitteln einer Zeichenfolge in einer anderen<br />
<br />
Die Ausgabe zeigt, dass der Text »langer« an 13. Stelle gefunden wurde:<br />
Abbildung 3.3:<br />
13 bedeutet das <strong>14</strong>. Zeichen, weil die Zählung mit 0 beginnt<br />
Man nennt eine solche Zählweise »nullbasiert«, das heißt, das erste Zeichen hat<br />
den <strong>In</strong>dex 0. Ist der Text nicht enthalten, wird FALSE zurückgegeben. Das ist zwar<br />
für PHP typisch aber dennoch gewöhnungsbedürftig: Viele eingebaute Funktionen<br />
können den Datentyp bei der Rückgabe wechseln. Da generell keine Möglichkeit<br />
besteht, Datentypen zu erzwingen, erscheint dies konsequent. Es<br />
erleichtert nur nicht unbedingt die Programmierung. Denn Sie müssen in Ihrem<br />
Programm vor der weiteren Verarbeitung des Ergebnisses an die Reaktionen auf<br />
verschiedene Datentypen denken. strpos beispielsweise gibt eine ganze Zahl<br />
zurück (integer), wenn die Zeichenfolge gefunden wurde, oder einen Booleschen<br />
Wert (boolean), wenn die Suche erfolglos war. Durch die bereits beschriebene<br />
87
88<br />
Daten verarbeiten<br />
automatische Typumwandlung fällt dies nicht weiter auf, dennoch ist das Verhalten<br />
ein Quell sporadisch auftretender Programmfehler.<br />
Tabelle 3.8 können Sie entnehmen, dass es noch zwei andere Varianten von<br />
strpos gibt. strrpos sucht die Fundstelle vom Ende her (was gleichbedeutend mit<br />
der letzten Fundstelle ist, wenn man von vorn sucht) während stripos bei der<br />
Suche nicht auf Groß- und Kleinschreibung achtet. Probieren Sie diese selbst aus,<br />
indem Sie die Funktion und die Testdaten in Listing 3.4 austauschen.<br />
Ähnlichkeiten zwischen Zeichenfolgen feststellen<br />
Gibt ein Benutzer Daten ein, ist ein exakter Vergleich nicht immer angebracht.<br />
Auch ähnliche Angaben können brauchbar sein. PHP bietet auch hier einige<br />
Funktionen, die einen Vergleich erlauben.<br />
Vergleichsfunktionen ermitteln typischerweise drei Zustände und geben einen von<br />
drei Werten zurück:<br />
0 bedeutet, dass die beiden Zeichenfolgen identisch sind.<br />
1 bedeutet, dass die erste Zeichenfolge größer als die zweite eingestuft wird.<br />
-1 bedeutet, dass die erste Zeichenfolge kleiner als die zweite eingestuft wird.<br />
Normalerweise erfolgt ein solcher Vergleich auf »binärer« Basis. Bei Zeichen wird<br />
also die Reihenfolge der Zeichen im Zeichensatz herangezogen. So hat der Buchstabe<br />
»A« den Code 65, während »a« den Wert 97 hat. Deshalb werden Großbuchstaben<br />
als »kleiner« als Kleinbuchstaben eingestuft, was nicht immer den<br />
Erwartungen entspricht. Ein zweites Merkmal derartiger Vergleiche ist die Vorgehensweise<br />
bei mehreren Zeichen: Der Algorithmus prüft Zeichen für Zeichen, bis<br />
er einen Unterschied findet. Der Vergleich der Zeichenfolge »11« mit »2« führt<br />
bereits beim ersten Zeichen zu einem Unterschied. »1« wird mit »2« verglichen;<br />
die »2« ist natürlich größer und deshalb produzieren derartige Funktionen als<br />
Ergebnis: »2« ist größer als »11«. Auch dies dürfte nur selten den Erwartungen entsprechen.<br />
Binäre Vergleiche sind im Alltag also weniger gefragt.<br />
PHP bietet zwei Lösungen für das Problem an: Zum einen kann die Unterscheidung<br />
von Groß- und Kleinschreibung verhindert werden. Die Funktionen strcasecmp,<br />
strnatcasecmp und strncasecmp setzen »a« gleich »A«, der Teilname »case«<br />
deutet darauf hin. Der Teilname »nat« verhindert dagegen die binäre Suche und<br />
versucht eine so genannte natürliche Suche. Hierbei werden Zahlen extrahiert und<br />
als Zahl, nicht als Zeichen behandelt, wobei 11 dann größer als 2 ist.
Listing 3.5: StrNatCmp.php – Vergleichen von Zeichenketten<br />
Die Zeichenkettenfunktionen<br />
<br />
Das Ergebnis zeigt, dass die Standardfunktion strcmp (binär, abhängig von Großund<br />
Kleinschreibung) $text1 als größer einstuft, was an dem Vergleich der »2« zur<br />
korrespondierenden »1« liegt. Mit strnatcmp sieht es anders aus. Hier wird der<br />
Zahlenanteil extrahiert und – soweit der Rest übereinstimmt – als Zahl verglichen.<br />
Deshalb ist $text1 nun kleiner als $text2.<br />
Die Vergleichsfunktionen werden auch eingesetzt, um Sortiervorgänge auszuführen.<br />
Sortieralgorithmen greifen auf die Ergebnisse eines einzelnen Vergleichs zwischen<br />
zwei Werten zurück. Das Einsortieren in die Ergebnisliste erfolgt auf Basis<br />
der Rückgabewerte 0, 1 oder -1. Diese Anwendung wird im Zusammenhang mit<br />
Arrays am achten Tag noch gezeigt.<br />
Ersetzen von Zeichen<br />
Abbildung 3.4:<br />
Natürlich oder nicht: Verschiedene Vergleichsergebnisse<br />
Ebenso wichtig wie das Suchen von Mustern ist das Ersetzen der Fundstelle durch<br />
anderen Text. PHP stellt zwei Funktionen zur Verfügung: str_replace berücksichtigt<br />
beim Suchvorgang Groß- und Kleinschreibung, während str_ireplace<br />
dies nicht tut.<br />
Listing 3.6: StrReplace.php: Ersetzen eines Teils einer Zeichenkette<br />
<br />
89
90<br />
Daten verarbeiten<br />
Hier werden gleich drei Parameter benötigt: Der erste gibt die zu suchenden Zeichen<br />
an, der zweite bestimmt die Ersatzzeichen. Als Ersatz kann auch eine leere<br />
Zeichenkette angegeben werden, womit die zu suchenden Zeichen entfernt werden.<br />
Der dritte Parameter gibt den Text an, der durchsucht werden soll.<br />
Komfortablere Such- und Ersetzungsmöglichkeiten bieten übrigens reguläre Ausdrücke,<br />
die zum Standardrepertoire eines jeden Softwareentwicklers gehören<br />
sollten. <strong>In</strong>formationen dazu werden in Abschnitt 3.6 »Reguläre Ausdrücke« ab<br />
Seite 95 vermittelt.<br />
Ermitteln von Eigenschaften einer Zeichenkette<br />
Bei der Beurteilung des <strong>In</strong>halts einer Zeichenfolge kommt es natürlich auch auf<br />
die allgemeinen Eigenschaften an. Besonders häufig wird die Länge benötigt, also<br />
die Anzahl der Zeichen. Bei der Auswertung von Formulardaten können so<br />
Pflichtfelder leicht überprüft werden – die Anzahl der Zeichen muss einfach größer<br />
als 0 sein.<br />
Listing 3.7: strlen.php – Anzahl der Zeichen einer Zeichenkette<br />
<br />
Im Gegensatz zum nullbasierten <strong>In</strong>dex wird hier die wirkliche Länge ermittelt.<br />
Daran müssen Sie denken, wenn die Ergebnisse der Funktion strlen zusammen<br />
mit der Auswahl von Teilzeichenketten oder der Positionsbestimmung (strpos)<br />
benutzt werden.<br />
Abbildung 3.5:<br />
Anzahl der Zeichen in einer Zeichenkettenvariablen<br />
Teilzeichenketten und Behandlung einzelner Zeichen<br />
Bei der Behandlung einzelner Zeichen werden Sie mit zwei Dingen in Berührung<br />
kommen. Zum einen wird gelegentlich mit dem Zeichencode gearbeitet, das<br />
heißt, Sie haben den Code eines Zeichens aus dem verwendeten Zeichensatz und
Die Zeichenkettenfunktionen<br />
benötigen dazu das Zeichen. Dazu gehört auch die entsprechende Rückumwandlung.<br />
Zum anderen benötigen Sie noch eine Technik zum gezielten Zugriff auf<br />
einzelne Zeichen.<br />
Umgang mit Zeichencodes<br />
Vor dem Zugriff auf Zeichencodes steht natürlich die Kenntnis derselben. PHP<br />
kann mit seinen Zeichenkettenfunktionen nur den erweiterten ASCII-Zeichensatz<br />
(Codes 0 bis 255) darstellen, dies entspricht ISO-8859-1.<br />
Abbildung 3.6:<br />
Die Zeichentabelle<br />
des Zeichensatzes<br />
ISO-8859-1<br />
Entsprechend dieser Tabelle führt chr(65) zum Zeichen »A« und ord("_") zum<br />
Code 128. Dies entspricht bei Zeichencodes oberhalb 127 nicht der oft verwendeten<br />
Unicode-Kodierung, die »breitere« Zeichen verwendet (16 statt 8 Bit) und<br />
auch asiatische Schriften verarbeiten kann. Kritisch ist das lediglich, wenn Sie versuchen,<br />
dezimale HTML-Entitäten zu erstellen, beispielsweise €. Dieses<br />
Zeichen entspricht der Entität € und stellte das Eurozeichen dar. Leider<br />
kann PHP mit dem Code 8364 nichts anfangen und umgekehrt kann nicht garantiert<br />
werden, dass ein Browser bei € auch wirklich 1 das €-Symbol anzeigt.<br />
1 Solange das Betriebssystem den Zeichensatz ISO-8859-1 oder einen darauf aufbauenden verwendet, kann es<br />
die Zeichen schon dekodieren und der Browser wird sie dann auch darstellen. Da der Zeichensatz aber in<br />
jeder Sprache etwas variiert, ist das Ergebnis mehr oder weniger unvorhersehbar.<br />
91
92<br />
Daten verarbeiten<br />
Zugriff auf einzelne Zeichen<br />
Am Anfang wurde bereits gezeigt, dass jedes Zeichen in einer Zeichenfolge einen<br />
<strong>In</strong>dex hat, beginnend mit 0. Dieser <strong>In</strong>dex kann auch zur gezielten Auswahl genau<br />
eines Zeichens benutzt werden:<br />
Listing 3.8: chars1.php – Auswahl eines einzelnen Zeichens<br />
<br />
Die Schreibweise mit den geschweiften Klammern ($field{3}) ist etwas gewöhnungsbedürftig,<br />
erweist sich aber als gut lesbar.<br />
Wenn die Auswahl nicht nur ein Zeichen, sondern gleich mehrere betrifft, sind wieder<br />
spezielle Funktionen gefragt. substr wählt einfach eine Anzahl Zeichen aus,<br />
wobei die verschiedenen Parametervarianten dies sehr flexibel ermöglichen. Der<br />
erste Parameter gibt die Zeichenfolge an, aus der ein Teil ausgewählt werden soll.<br />
Der zweite die Startposition, ab der ausgewählt werden soll. Ist dieser Wert negativ,<br />
beginnt die Zählung von hinten. Der dritte – optionale – Parameter gibt die Anzahl<br />
der Zeichen an, von der Startposition beginnend immer nach rechts zählend.<br />
Das folgende Beispiel nutzt diese Funktion und einige andere, um die Werte der<br />
Parameter zu bestimmen.<br />
Listing 3.9: Substr.php – Einen Teil einer Zeichenkette ermitteln<br />
<br />
Die Nutzung mehrere Zeichenkettenfunktionen zusammen in einer Anweisung ist<br />
sehr typisch für die PHP-Programmierung. Das Beispiel gibt das Wort »Muster« aus.<br />
HTML-abhängige Funktionen<br />
Der Sinn und Zweck nahezu jeden PHP-Programms ist die Erzeugung von<br />
HTML. Entsprechend umfangreich ist auch hier das Angebot an Funktionen.
Die Zeichenkettenfunktionen<br />
Häufiges Problem ist die korrekte Ausgabe von Text. Daten aus Datenbanken enthalten<br />
meist nicht die für HTML nötigen Entitäten, also beispielsweise »ä«<br />
statt »ä«. Das folgende Beispiel zeigt, wie eine solche Umwandlung durchgeführt<br />
werden kann:<br />
Listing 3.10: HtmlEntity.php: Sonderzeichen in HTML-Entitäten umwandeln<br />
94<br />
Daten verarbeiten<br />
?><br />
<br />
Die Funktion kennt noch zwei weitere Parameter. Der dritte gibt ein alternatives<br />
Zeichen für den Umbruch an, der vierte kann auf 1 gesetzt werden, um einen<br />
Umbruch auch mitten im Wort zuzulassen.<br />
Abbildung 3.7:<br />
Umbruchsteuerung zur Formatierung der Ausgabe<br />
Das Problem mit dem Standardzeilenumbruch \n in einer Datenquelle tritt häufiger<br />
auf. Statt wäre die Verwendung des Umbruch-Tags oft besser. Auch<br />
dies ist in PHP schnell gelöst: nl2br nimmt diese Umwandlung vor.<br />
Listing 3.12: nl2br.php – Umwandlung von Zeilenumbrüchen in -Tags<br />
<br />
<br />
<br />
Die folgende Abbildung zeigt den Unterschied. Die Breite des Rahmens wird<br />
durch den Stil bestimmt (Attribut style des HTML-Tags ):<br />
Abbildung 3.8:<br />
Das linke Bild ist<br />
ohne nl2br entstanden,<br />
das<br />
rechte mit.<br />
Noch eine interessante Anwendung von Zeichenkettenfunktionen betrifft die Zerlegung<br />
von Zeichenfolgen. Telefonnummern oder Bankleitzahlen sind beispielsweise<br />
besser lesbar, wenn man sie mit Leerzeichen gruppiert. Die Funktion<br />
chunk_split ist dafür bestens geeignet. Ähnlich wie wordwrap wird nach einer definierten<br />
Anzahl von Zeichen ein Trennzeichen eingefügt. Auch hier ist dies standardmäßig<br />
der Zeilenumbruch.
Listing 3.13: ChunkSplit.php – Formatierung einer Bankleitzahl<br />
Reguläre Ausdrücke<br />
<br />
Die Zeichenfolge wird hier von links beginnend in Abschnitte gleicher Länge<br />
geteilt. Der letzte Abschnitt kann freilich kleiner sein, was in diesem Fall aber<br />
gewollt ist. An jeden Abschnitt wird das Leerzeichen angehängt, das durch den<br />
dritten Parameter bestimmt wird.<br />
Das Beispiel gibt »100 700 24« aus.<br />
3.6 Reguläre Ausdrücke<br />
Reguläre Ausdrücke beschreiben Suchmuster. So einfach dies klingt, ist es indes<br />
nicht. Denn die Suche nach Zeichen in einem Text kann komplexen Regeln<br />
gehorchen. Nicht alle Arten von Suchen sind mit regulären Ausdrücken abbildbar.<br />
Derartige Ausdrücke können im Prinzip nur statischen Text beschreiben, nicht<br />
jedoch Berechnungen anstellen. Die Suche nach einer Zahl, einer bestimmten<br />
Buchstabenfolge oder einer Zeichenart ist einfach. Eine Prüfsumme oder einen<br />
Zahlenbereich kann man damit nicht erfassen.<br />
Vor allem bei der Prüfung von Formularfeldern, die ein Benutzer ausgefüllt hat,<br />
laufen reguläre Ausdrücke zur Höchstform auf. Typisch sind Suchmuster, die<br />
E-Mail-Adressen, einen URL oder Zahlen erkennen.<br />
Einführung in die Welt der regulären Ausdrücke<br />
Die ersten Schritte mit regulären Ausdrücken sind nicht einfach. Hat man aber<br />
erstmal eine gewisse Systematik erkannt, ist es nicht unmöglich, auch schwierige<br />
Aufgaben zu lösen. Auch wenn reguläre Ausdrücke sehr »unleserlich« aussehen<br />
können, eine einfache Variante ist oft ausreichend.<br />
Das Grundprinzip<br />
Betrachten Sie folgenden Satz:<br />
95
96<br />
Daten verarbeiten<br />
Eine wichtige Programmiersprache ist PHP.<br />
Sie möchten in diesem Satz – an beliebiger Stelle – nach der Zeichenfolge »PHP«<br />
suchen. Der passende Ausdruck dazu lautet:<br />
PHP<br />
Freilich kann man das mit einer einfache Suche mittels Zeichenkettenfunktionen<br />
auch. Angenommen, Sie wollen nach den Zeichen »PHP« oder »Perl« suchen.<br />
Wenn Sie Zeichenkettenfunktionen verwenden, benötigen Sie dazu zwei Anfragen.<br />
Ein regulärer Ausdruck kennt dafür einen Operator:<br />
PHP|Perl<br />
Die erste wichtige Aussage lautet also: Zum Suchen nach beliebigen konkreten<br />
Zeichen oder Zeichenfolgen werden diese einfach aufgeschrieben. Die zweite<br />
wichtige Aussage: Suchmuster lassen sich miteinander verknüpfen, um komplexere<br />
Abfragen zu ermöglichen.<br />
Muster durch Platzhalter und Zeichenklassen<br />
Nun ist das Suchen nach fest vorgegebenen Mustern recht einfach. Spannender ist<br />
es, die Zeichen durch Platzhalter zu ersetzen. Der wichtigste Platzhalter ersetzt ein<br />
beliebiges Zeichen – der Punkt (.):<br />
P.P<br />
Dieses Muster findet PAP, PBP usw. und natürlich auch PHP.<br />
Außer »alle« oder »ein bestimmtes« Zeichen kann man beliebige Platzhalter selbst<br />
konstruieren. Dazu werden so genannte Zeichenklassen eingesetzt, gekennzeichnet<br />
durch eckige Klammern. Diese Gebilde stehen für genau ein Zeichen, das den<br />
Kriterien entsprechen muss:<br />
[PH]HP<br />
Dieses Muster steht für PHP oder HHP. Der <strong>In</strong>halt der Zeichenklasse kann negiert<br />
werden, das heißt, alle Zeichen außer den angegebenen sind zulässig:<br />
[^PH]HP<br />
Das letzte Muster erkennt – neben vielen anderen – AHP, BHP usw. nicht jedoch<br />
PHP und HHP.<br />
Weil bestimmte Zeichenklassen sehr häufig benötigt werden, gibt es einige vordefinierte<br />
Abkürzungen dafür. Die folgende Tabelle zeigt diese:
Zeichen Bedeutung<br />
\t Tabulator, entspricht dem ASCII-Wert 0x9<br />
\n Zeilenumbruch (Newline), entspricht dem ASCII-Wert 0xC<br />
Wiederholungen definieren<br />
Reguläre Ausdrücke<br />
\r Wagenrücklauf (Carriage Return), entspricht dem ASCII-Wert 0xA<br />
\xHH Ein beliebiges Zeichen, definiert durch seinen hexadezimalen Wert HH<br />
\d Eine Ziffer<br />
\D Keine Ziffer (alles außer Zifferzeichen)<br />
\s Jedes unsichtbare Zeichen (Leerzeichen, Tabulator usw.)<br />
\S Alles außer unsichtbare Zeichen<br />
\w Jedes Wortzeichen (Zahl, Ziffer, Unterstrich)<br />
\W Kein Wortzeichen<br />
\b Jedes für Wortgrenzen verwendete Zeichen (Leerzeichen, <strong>In</strong>terpunktion)<br />
\B Kein für Wortgrenzen verwendetes Zeichen<br />
\A Erstes Zeichen der Sequenz (absolut, ohne Rücksicht auf andere Schalter)<br />
\Z Letztes Zeichen der Sequenz oder Zeile (absolut, ohne Rücksicht auf andere<br />
Schalter)<br />
\z Letztes Zeichen der Sequenz (absolut, ohne Rücksicht auf andere Schalter)<br />
Tabelle 3.5: Vordefinierte Zeichen und Zeichenklassen<br />
Damit man mit Zeichen und Zeichenklassen flexibel umgehen kann, lässt sich ein<br />
Wiederholungsoperator anhängen. Dieser Zeigt an, wie oft das Zeichen vorkommen<br />
soll:<br />
{n, m}<br />
Dabei steht das n für die Mindestzahl und das m für die Maximalzahl. Beide Werte<br />
sind optional; {4,} steht beispielsweise für mindestens vier bis unendlich viele Zeichen,<br />
{,6} für keines bis höchstens sechs Zeichen. Für drei Kombinationen gibt es<br />
eine verkürzte Schreibweise, die häufig zum Einsatz kommt:<br />
97
98<br />
Daten verarbeiten<br />
*<br />
Steht für keines oder beliebig viele Zeichen, entspricht also {0,}.<br />
?<br />
Steht für keines oder genau ein Zeichen, entspricht also {0,1}.<br />
+<br />
Steht für ein oder mehr Zeichen, entspricht also {1,}.<br />
Die Kombination {1,1} muss nicht angegeben werden, dies ist der Standardfall.<br />
Um die Zeichenposition innerhalb der zu durchsuchenden Zeichenkette festlegen<br />
zu können, sind weitere Sonderzeichen erforderlich:<br />
^<br />
Muster muss am Beginn der Zeichenkette anfangen.<br />
$<br />
Muster muss am Ende der Zeichenkette aufhören.<br />
Da nun bereits einige Zeichen mit Sonderfunktionen belegt sind, braucht man<br />
noch ein Aufhebungszeichen, wozu der Backslash \ eingesetzt wird. Suchen Sie<br />
also nach einem Punkt, schreiben Sie \. .<br />
Gruppierungen<br />
Nicht nur einzelne Zeichen, sondern auch Kombinationen lassen sich mit den<br />
Wiederholungsoperatoren +, *, ? und {n,m} verwenden. Gruppen entstehen durch<br />
runde Klammern:<br />
(PHP)+<br />
Neben der Funktion der Gruppierung dienen die Klammern auch dazu, Teilmuster<br />
zurückzugeben. <strong>In</strong> PHP entsteht am Ende nicht nur die Aussage »Muster<br />
gefunden«, sondern auch ein Array mit den Teilmustern, die durch Gruppen definiert<br />
wurden.<br />
Anwendungsbeispiele<br />
Bevor Sie mit regulären Ausdrücken beginnen, sollten Sie sich ein kleines Skript<br />
bauen, das Eingabe und Test vereinfacht. Das folgende Listing zeigt eine einfache<br />
Version. Dabei lernen Sie auch gleich einige PHP-Funktionen kennen, die mit
Reguläre Ausdrücke<br />
regulären Ausdrücken umgehen können. Sie beginnen immer mit dem Präfix<br />
preg_ 2 :<br />
Listing 3.<strong>14</strong>: RegexTest.php – Ein Testskript für reguläre Ausdrücke<br />
100<br />
Daten verarbeiten<br />
{<br />
foreach ($val as $subkey => $subval)<br />
{<br />
echo " Teilgruppe<br />
$subkey => $subval";<br />
}<br />
} else {<br />
echo "$val";<br />
}<br />
}<br />
}<br />
} else {<br />
echo "Keine Übereinstimmung gefunden";<br />
}<br />
} else {<br />
echo "Kein Muster angegeben.";<br />
}<br />
?><br />
Kern des Skripts ist die Funktion preg_match_all. Sie sucht alle Vorkommen des<br />
angegebenen Musters, bleibt also nicht stehen, wenn eine erste Übereinstimmung<br />
gefunden wurde. Für Testzwecke ist dies sehr hilfreich. Wird dagegen nur nach<br />
einem (dem ersten) Vorkommen gesucht, reicht preg_match aus.<br />
Neben dem einfachen Suchen kann man natürlich auch Suchen und Ersetzen,<br />
das heißt, gefundene Teilmuster gezielt austauschen.<br />
Suchen und Ersetzen<br />
Zum Suchen und Ersetzen stehen die Funktionen preg_replace und<br />
preg_replace_callback bereit. Jede Fundstelle kann hier durch eine Ersatzzeichenkette<br />
ausgetauscht werden. preg_replace_callback erledigt das auch für komplexe<br />
Aufgaben, denn hier wird für jede Fundstelle eine so genannte<br />
Rückruffunktion aufgerufen, in der man zusätzliche Berechnungen anstellen<br />
kann. Das bringt viel Dynamik in den Ersetzungsvorgang.<br />
Das folgende Beispiel zeigt die Anwendung. Ein Text soll nach einem Muster, hier<br />
der Zeichenfolge ~#~ durchsucht werden. Das Nummernzeichen # soll dabei<br />
durch eine fortlaufende Ziffer ersetzt werden. Mit preg_replace_callback ist das<br />
sehr einfach zu realisieren:
Reguläre Ausdrücke<br />
Listing 3.15: RegexReplace.php – Ersetzen mit Hilfe einer Rückruffunktion<br />
<br />
Der Funktionsaufruf preg_replace_callback enthält den Namen der Rückruffunktion<br />
replaceNumbers, die für jede Fundstelle aufgerufen wird, im Beispiel also vier<br />
Mal. <strong>In</strong> der Funktion zählt eine statische Variable (static) die Aufrufe mit und<br />
gibt den aktuellen Wert, beginnend mit 1, zurück. Der Rückgabewert wird zum<br />
Ersetzen der Zeichenfolge benutzt. Statische Variablen behalten ihren Wert und<br />
führen die Zuweisung nur beim ersten Aufruf aus.<br />
Für die Ausgabe werden außerdem noch die normalen Zeilenumbrüche, wie sie<br />
bei der Heredoc-Notation entstehen, in HTML-typische -Tags konvertiert.<br />
Abbildung 3.9:<br />
Ausgabe des Skripts mit<br />
dynamisch ersetzten Ziffern<br />
Noch einfacher ist preg_replace, wo anstatt der Rückruffunktion nur eine konstante<br />
Zeichenkette angegeben wird. Allerdings kann man auch hier etwas Dynamik<br />
bekommen, indem auf Gruppen zugegriffen wird. Zulässig ist nämlich die<br />
Angabe von Referenzen auf Gruppen des Ausdrucks. Wie bereits in der Einführung<br />
beschrieben, werden Gruppen durch runde Klammern gebildet. Auf diese<br />
kann mit einer speziellen Referenzsyntax zugegriffen werden.<br />
Angenommen, Sie suchen nach einem bestimmten Datumsformat, beispielsweise<br />
der Art 8/9/2004 (amerikanisches Format) in einem Text. Sie möchten nun an die-<br />
101
102<br />
Daten verarbeiten<br />
sen Stellen die deutsche Version 9.8.2004 sehen. Dies ist mit preg_replace in<br />
einem Schritt erledigt.<br />
Listing 3.16: RegexReplace2.php: Ersetzen mit Zugriff auf Teilmuster<br />
<br />
Das Muster untersucht den Text auf Datumsformate hin. \d steht für eine Ziffer,<br />
die im Beispiel für den Monat (erstes Teilmuster) ein oder zwei Mal vorkommen<br />
darf: \d{1,2}. Die Jahreszahl muss immer vierstellig sein. Wichtig an dem gezeigten<br />
Muster sind die drei runden Klammerpaare, die dafür sorgen, dass die erkannten<br />
Teile in separaten Variablen abgelegt werden. Die Schrägstriche werden damit<br />
zwar zur Erkennung herangezogen, fallen aber aus den Teilmustern heraus. <strong>In</strong> der<br />
Ersatzzeichenkette kann nun auf die Teilmuster mit der Syntax \\n zugegriffen<br />
werden, wobei n einfach die Nummer der öffnenden Klammer ist. \\2 bezeichnet<br />
also die zweite Klammer, mithin der Tag im amerikanischen Datumsformat. Die<br />
Punkte in der Ersatzzeichenkette werden wie normale Zeichen behandelt.<br />
Reguläre Ausdrücke können weit komplexer sein. Schauen Sie sich folgendes<br />
Suchmuster an:<br />
(?
Reguläre Ausdrücke<br />
( Zweites Teilmuster (nicht unterdrückt)<br />
[^,]* Suche alles (*) außer dem Komma (^ negiert hier)<br />
)<br />
( Drittes Teilmuster (nicht unterdrückt)<br />
,\1 Komma mit folgendem Text, der dem ersten nicht<br />
unterdrückten Teilmuster (zweite Klammer)<br />
entspricht – dies ist das Duplikat<br />
)+ Davon mindestens 1 oder beliebig viele<br />
(? Viertes Teilmuster (unterdrückt, ?-Zeichen)<br />
= Positiv (=) vorausschauen<br />
,|$ Vollständigkeit untersuchen, es muss entweder ein<br />
weiteres Komma oder (|) das Musterende ($) folgen<br />
)<br />
Alles klar? Die Beispiele zeigen, wie stark reguläre Ausdrücke in der Praxis sind.<br />
Sie zeigen auch, dass reguläre Ausdrücke nicht trivial sind. Es kommt also weniger<br />
auf die Anwendung der PHP-Funktionen an. Problematischer ist das Finden der<br />
passenden Ausdrücke. Der folgende Abschnitt stellt deshalb einige häufig benutzte<br />
Ausdrücke vor – zum Lernen und natürlich zum Anwenden.<br />
Typische Suchmuster<br />
<strong>In</strong> diesem Abschnitt werden einige häufig benötigte Suchmuster vorgestellt und<br />
kurz erläutert. Einerseits können Sie so reguläre Ausdrücke schnell anwenden,<br />
andererseits lernen, vergleichbare Muster selbst zu entwerfen. Die Muster werden<br />
nicht im Kontext eines PHP-Skripts dargestellt, da es sich hier lediglich um den<br />
immer wieder gleichen Aufruf von preg_match, preg_match_all oder preg_replace<br />
handeln würde.<br />
Wenn Sie im <strong>In</strong>ternet auf die Suche nach regulären Ausdrücken gehen,<br />
sollten Sie daran denken, dass es sehr viele Regex-Maschinen gibt, die<br />
keineswegs zueinander vollkommen kompatibel sind. PHP verwendet<br />
eine weitgehend (aber nicht 100-prozentig) Perl-kompatible Methode.<br />
JavaScript, Unix-Shells, .NET, VBScript und andere Systeme weichen<br />
davon teilweise ab. Das Grundprinzip ist zwar allen gleich, aber die Syntax<br />
variiert ebenso wie die Standardeinstellungen (beispielsweise ist Perl<br />
standardmäßig »gierig«, und man unterdrückt dies explizit, während<br />
.NET »ungierig« ist und man die Option explizit einschalten muss). Sie<br />
müssen deshalb fremde Muster immer erst sorgfältig testen und eine<br />
103
104<br />
Daten verarbeiten<br />
scheinbare Fehlfunktion ist nicht immer dem Anbieter anzulasten, es sei<br />
denn, das Muster wurde explizit für PHP geschrieben.<br />
Vorbemerkungen<br />
Alle folgenden Beispiele verwenden als Begrenzungszeichen die Tilde (~). Wenn<br />
in Ihrem Suchmuster nach der Tilde gesucht werden soll, müssen Sie ein anderes<br />
Begrenzungszeichen nutzen, das nicht im Muster selbst vorkommt.<br />
Sie finden jeweils das Muster auf einen Blick und danach zeilenweise mit Erläuterungen.<br />
Sie finden auf der Buch-CD das Programm RegexExamples.php, das die<br />
Beispiele enthält und ein Formular anbietet, mit dem sich schnelle<br />
Tests ausführen lassen. Für die Ausführung ist JavaScript erforderlich.<br />
HTML-Tags suchen<br />
Tags zu suchen ist eine sehr häufige Aufgabe. Immer wieder müssen Daten umund<br />
aufbereitet werden. Das folgende einfache Beispiel zeigt das Prinzip:<br />
HTML-Tag suchen<br />
~]*>(.*?)~i<br />
~<br />
]* Öffnendes b-Tag, alles außer >-Zeichen<br />
> Bis zum >-Zeichen<br />
(.*?) Beliebige Zeichen<br />
Bis zum schließenden Tag<br />
~<br />
i Groß- und Kleinschreibung ist egal<br />
Beliebige Tags suchen<br />
~]*>(.*?)~iu<br />
~<br />
]*> Ende des Tags, beliebige Zeichen<br />
innerhalb des Tags
für Attribute<br />
(.*?) Beliebige Zeichen innerhalb des Tags<br />
Der Tagname muss sich im schließenden Tag<br />
wiederholen (eine Referenz)<br />
~iu Groß-/Klein egal, Gierigkeit aus<br />
Umgang mit Leerzeichen<br />
Reguläre Ausdrücke<br />
Leerzeichen umfassen im Allgemeinen alle beim Druck nicht sichtbaren Zeichen,<br />
also neben dem eigentlichen Leerschritt auch Tabulatoren und Zeilenumbrüche.<br />
Reguläre Ausdrücke verwenden dazu das Ersatzzeichen \s.<br />
Führende Leerzeichen<br />
~^\s+~<br />
~<br />
^ Zeichen muss am Beginn erscheinen<br />
\s+ Ein oder mehr Leerzeichen<br />
~<br />
Abschließende Leerzeichen<br />
~\s+$~<br />
~<br />
\s+ Ein oder mehr Leerzeichen<br />
$ Unmittelbar vor dem Ende des Textes<br />
~<br />
IP-Adressen<br />
Ebenso wie mit HTML-Tags hat man in der Webprogrammierung häufiger mit IP-<br />
Adressen zu tun. Drei Versionen sollen die möglichen Lösungsansätze zeigen:<br />
IP-Adressen (Schwache Version mit Gruppierung)<br />
Die Version erkennt zwar die Gruppen, prüft aber nicht den Sinn der Zahlen.<br />
IP-Nummern sind Bytes, habe Dezimal also einen Wertebereich zwischen 0<br />
und 255. Das folgende Muster erkennt jedoch auch 980.378.275.455 noch als<br />
gültig an:<br />
~\b(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\b~<br />
~<br />
105
106<br />
Daten verarbeiten<br />
\b Beliebige Wortgrenze<br />
(\d{1,3}) Erste Gruppe: 1 bis 3 Ziffern<br />
\. Gefolgt von einem Punkt<br />
(\d{1,3})\. Zweite Gruppe mit Punkt<br />
(\d{1,3})\. Dritte Gruppe mit Punkt<br />
(\d{1,3}) Letzte Gruppe<br />
\b~ gefolgt von einer Wortgrenze<br />
IP-Adressen (schwache Version ohne Gruppierung)<br />
Dieses Muster entspricht dem vorhergehenden, es verzichtet jedoch auf die<br />
Gruppierung der Teiladressen. Es ist vorzuziehen, wenn eine Auswertung der<br />
einzelnen Blöcke nicht erforderlich ist.<br />
~\b(?:\d{1,3}\.){3}\d{1,3}\b~<br />
IP-Adressen (starke Version, mit Gruppierung)<br />
Diese Version versucht etwas mehr Logik in die Sache zu bringen und erkennt<br />
nur korrekte Nummernkreise. Hier ist freilich fraglich, ob der Aufwand lohnt,<br />
ein Zugriff auf die Teilmuster des letzten Beispiels mit PHP und eine simple<br />
Prüfung (
Gleitkommazahlen (englisches Format, ganzer Ausdruck)<br />
~^((?:[-]|[.]|[-.]|[0-9])[0-9]*)(?:(?:\.)([0-9]+))?$~<br />
Reguläre Ausdrücke<br />
~<br />
^ Muss am Textanfang beginnen<br />
( Teilmuster<br />
(?: Keine Aufnahme als Teilmuster<br />
[-] Minuszeichen<br />
| Oder<br />
[.] Punkt<br />
| Oder<br />
[-.] Kombination aus beidem<br />
|Oder<br />
[0-9] Ziffer<br />
)<br />
[0-9]* Gefolgt von 0 oder mehr Ziffern<br />
) Bilden die erste Gruppe<br />
(?: Unterdrückte Teilgruppe<br />
(?:\.) Beginnt mit Punkt, der ebenso bei der<br />
Ausgabe unterdrückt wird<br />
([0-9]+) Eine oder mehrere Ziffern<br />
)? Gruppe ein oder kein Mal<br />
$ Der Vergleichstext muss nun enden<br />
~<br />
Gleitkommazahlen (englisches Format mit Exponenten, beispielsweise 23e4)<br />
Die folgende Variante funktioniert ähnlich, bindet jedoch zusätzlich noch die<br />
Buchstaben e oder E mit ein, um den Exponenten abzutrennen.<br />
~[-+]?([0-9]*\.)?[0-9]+([eE][-+]?[0-9]+)?~<br />
Ganzzahlen mit optional Vorzeichen (alle Vorkommen)<br />
Dieses Muster ermittelt alle Vorkommen ganzer Zahlen in einem Text.<br />
~[-+]?\b\d+\b~<br />
~<br />
[-+]? Optionales Vorzeichen<br />
\b Wortgrenze<br />
\d+ Ziffernfolge (eine oder mehrere Ziffern)<br />
\b Wortgrenze<br />
~<br />
107
108<br />
Daten verarbeiten<br />
Hexzahlen im PHP-Format (z.B. 0xFF)<br />
Das folgende Beispiel funktioniert ähnlich, setzt jedoch das für Hex-Literale<br />
typische Präfix 0x davor. Die zweite Variante ist identisch, nutzt aber den<br />
Schalter i statt einer expliziten Angabe der Buchstaben in Groß- und Kleinschreibung.<br />
~\b0[xX][0-9a-fA-F]+\b~<br />
~\b0[X][0-9A-F]+\b~i<br />
Datumsformate prüfen<br />
Die folgenden Beispiele zeigen, wie mit Datumsformaten umgegangen werden<br />
kann. Die Ausdrücke sind recht einfach, sodass sie sich leicht anpassen oder erweitern<br />
lassen.<br />
Datumsformat mm/dd/yyyy (Jahr nur 19XX und 20XX)<br />
~(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])<br />
[- /.](19|20)\d\d~<br />
~<br />
(0[1-9]|1[012]) Monate 01-09 und 10, 11, 12<br />
[- /.] Erlaubte Trennzeichen<br />
(<br />
0[1-9] Tage 01 bis 09<br />
| oder<br />
[12][0-9] 10 bis 29 (1 oder 2 und 0 bis 9)<br />
| oder<br />
3[01] 30 oder 31<br />
Abbildung 3.11:<br />
Das Muster findet<br />
alle ganzen<br />
Zahlen in einem<br />
Text (Bild vom<br />
Testprogramm)
Reguläre Ausdrücke<br />
)<br />
[- /.] Erlaubte Trennzeichen<br />
(19|20)\d\d 19nn oder 20nn, nn = jede Ziffer<br />
~<br />
Datumsformat yyyy-mm-dd (Jahr nur 19XX und 20XX)<br />
Analog zum vorherigen Beispiel, aber mit anderer Reihenfolge.<br />
~(19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12]<br />
[09]|3[01])~<br />
Wörter suchen oder auf Merkmale hin analysieren<br />
Sucht die Schlüsselwörter print, printf, echo, print_r<br />
Der Ausdruck ist recht einfach, die zulässigen Wörter sind lediglich durch |<br />
(oder) verknüpft.<br />
~\b(print|echo|print_r|printf)\b~<br />
E-Mail-Adresse (einfache Version)<br />
Bereits etwas anspruchsvoller, aber bei weitem noch nicht so perfekt wie möglich<br />
ist diese Prüfung einer E-Mail-Adresse.<br />
~^([_a-z0-9-]+(\.[_a-z0-9-]+)*)@([a-z0-9-]+(?:\.[a-z0-9-]+)*)$~i<br />
~<br />
^ Prüfung ab Textanfang<br />
( Teilmuster für den Namen<br />
[_a-z0-9-]+ Beginnt mit _, -, Buchstaben, Zahlen<br />
(\. Gefolgt von einem Punkt<br />
[_a-z0-9-]+ <strong>Und</strong> _, -, Buchstaben oder Zahlen<br />
)* Diese Kombination ist optional<br />
)<br />
@ Das @-Zeichen<br />
( Teilmuster für die Domain<br />
[a-z0-9-]+ Zulässige Zeichen, ein oder mehr Mal<br />
(?:\.[a-z0-9-]+) Toplevel, durch Punkt getrennt, das<br />
?: am Anfang unterdrückt die Gruppe<br />
*) Das Teilmuster kann sich wiederholen<br />
$ <strong>Und</strong> der Textende ist auch Musterende<br />
~i Groß-/Klein ist wieder egal<br />
109
110<br />
Daten verarbeiten<br />
3.7 Datums- und Zeitfunktionen<br />
PHP verfügt über vielfältige Datums- und Zeitfunktionen, die außerdem durch ein<br />
Kalendermodul ergänzt werden. Datumsberechnungen werden damit vereinfacht<br />
und helfen dabei, nutzerfreundliche Websites aufzubauen. <strong>In</strong> diesem Abschnitt<br />
werden verschiedene erweiterte Techniken der Datenausgabe vorgestellt. Dazu<br />
gehört der Umgang mit Datums- und Zeitfunktionen sowie deren Formatierung.<br />
Aber auch die vielfältigen Möglichkeiten, Zahlen und Währungsangaben zu verarbeiten,<br />
werden behandelt.<br />
Der Timestamp und die Serverzeit<br />
Basis aller Zeitberechnungen in PHP bildet der Unix-Timestamp (Zeitstempel).<br />
Dies ist ein sekundengenauer Zähler, der den Zeitraum seit dem 1.1.1970 zählt.<br />
Dargestellt wird er im 32-Bit-Zahlenraum (<strong>In</strong>teger), sodass er Datumswerte bis<br />
2037 enthalten kann. Solange man sich innerhalb dieses Zeitraumes bewegt, sind<br />
die internen Funktionen außerordentlich wirkungsvoll und einfach. Daten, die<br />
davor oder danach berechnet werden sollen, bedürfen einer etwas eingehenderen<br />
Betrachtung.<br />
Wenn Sie mit Zeitwerten arbeiten, die die aktuelle Zeit reflektieren, müssen Sie<br />
beachten, dass der Server möglicherweise nicht in Ihrer Zeitzone oder der der<br />
künftigen Nutzer steht. Manche Provider stellen ihre Server auch nicht auf die<br />
Zeitzone ein, in der die Maschine physisch steht, sondern auf UTC (Universal<br />
Time Conversion, früher als GMT, Greenwich Mean Time bezeichnet). Für die<br />
Abfrage der lokalen Zeit des Browser benötigen Sie JavaScript; PHP allein hilft<br />
hier nicht wirklich weiter. Lesen Sie mehr dazu im Abschnitt »Tricks mit Java-<br />
Script: Lokale Zeit ermitteln« ab Seite 117.<br />
Rechnen mit Datums- und Zeitwerten<br />
Um unter PHP mit Datums- und Zeitwerten rechnen zu können, müssen Sie<br />
zuerst über einen Zeitstempel verfügen – entweder den aktuellen (Jetzt-Zeit) oder<br />
den für einen bestimmten Zeitpunkt. Für beides gibt es entsprechende Funktionen.
Datums- und Zeitfunktionen<br />
Die aktuelle Zeit wird mit time ermittelt. Eine bestimmte Zeit wird dagegen mit<br />
mktime (mk = engl. make; machen, erzeugen) erzeugt. Das folgende Beispiel zeigt<br />
dies und gibt die Zeitstempel im »Rohformat« aus.<br />
Listing 3.17: time.php – Der aktuelle Zeitstempel<br />
<br />
Berechnungen mit Zeitwerten sind auf Basis des Zeitstempels sehr einfach. Da es<br />
sich um ein Maß in Sekunden handelt, muss zur Berechnung eines anderen<br />
Datums lediglich die nötige Anzahl Sekunden abgezogen oder hinzugefügt werden.<br />
Dabei helfen folgende Umrechnungen:<br />
1 Minute = 60 Sekunden<br />
1 Stunde = 3.600 Sekunden<br />
1 Tag = 86.400 Sekunden<br />
1 Woche = 604.800 Sekunden<br />
Monate sind naturgemäß unterschiedlich lang. Um also die Länge eines Monats<br />
zu ermitteln, muss bekannt sein, in welchem Jahr er liegt (auch wenn dies nur<br />
beim Februar zu unterschiedlichen Ergebnissen führen kann).<br />
Ausgabe formatierter Zeit- und Datumswerte<br />
Nachdem das benötigte Datum nun ermittelt wurde, muss es meist formatiert werden.<br />
Mit den typischen Zeitstempeln kann in der Regel kein Benutzer etwas anfangen.<br />
Zur Speicherungen in Datenbanken sind sie dagegen gut geeignet. Vor allem<br />
wenn die spätere Formatierung flexibel angewendet werden soll, ist das Vorhalten<br />
der Datumswerte in Form von Zeitstempeln optimal.<br />
Zur Formatierung bietet PHP gleich zwei Funktionen an: date und strftime.<br />
Während date ausschließlich englisch »spricht«, kennt strftime auch eine Lokalisierung.<br />
Die Dopplung der Funktionen ist historisch bedingt und erschwert leider<br />
den Umgang mit PHP etwas, weil derartige <strong>In</strong>konsistenzen die Lernkurve nicht<br />
eben ebnen. Es bleibt in der Praxis nichts weiter übrig, als mit beiden Funktionen<br />
parallel zu arbeiten.<br />
111
112<br />
Daten verarbeiten<br />
Die folgende Tabelle stellt die beiden Funktionen und deren Formatparameter<br />
gegenüber.<br />
date strftime Beschreibung<br />
a %p Erzeugt den Schriftzug »am« oder »pm«<br />
A %r Erzeugt den Schriftzug »AM« oder »PM«<br />
B Zeigt die Swatch-<strong>In</strong>ternetzeit an, eine Zeitform, die den Tag ohne<br />
Zeitzonen in 1.000 Einheiten teilt<br />
d %d Gibt den Tag im Monat an; zwei Ziffern mit führender Null zwischen<br />
01 und 31 werden ausgegeben<br />
%e Gibt den Tag im Monat mit führendem Leerzeichen bei einstelligen<br />
Werten an<br />
D %a Erzeugt den Tag der Woche mit drei Buchstaben, beispielsweise<br />
»Fri«<br />
F %B Ausgeschriebener Monatsname, beispielsweise »January«<br />
h Stunde im 12-Stunden-Format zwischen 01 und 12 mit führender<br />
Null<br />
H Stunde im 24-Stunden-Format zwischen 00 und 23 mit führender<br />
Null<br />
g %I Stunde im 12-Stunden-Format ohne führende Null, also zwischen<br />
1 und 12<br />
G %H Stunde im 24-Stunden-Format ohne führende Null, also zwischen<br />
00 und 23<br />
i Minuten 00 bis 59 mit führender Null<br />
%M Minuten 0 bis 59<br />
I 1 in der Sommerzeit, sonst 0.<br />
j Tag des Monats ohne führende Null: 1 bis 31 sind mögliche<br />
Werte.<br />
l %A Ausgeschriebener Tag der Woche: »Friday«<br />
Tabelle 3.6: Formatzeichen der Datumsformatierfunktion date und strftime
date strftime Beschreibung<br />
L Boolescher Wert für das Schaltjahr: 0 oder 1<br />
Datums- und Zeitfunktionen<br />
m %m Monat des Jahres von 01 bis 12, also mit führender Null<br />
n Monat des Jahres ohne führende Null: 1 bis 12<br />
M %b Monat als Abkürzung mit drei Buchstaben: »Jan«<br />
O Zeitdifferenz zu UTC in Stunden als Differenzzeichenkette, beispielsweise<br />
»+0400«<br />
r Datum und Zeit im RFC 822-Format. Folgendes Format wird verwendet:<br />
»Mon, 19 Mar 2004 12:05:00 +0100«. Die RFC 822 definiert<br />
Datumsformate, wie sie in E-Mails verwendet werden.<br />
s %S Sekunden 00 bis 59 mit führender Null<br />
S Suffix der englischen Ordnungszahlen: »th«, »nd« usw.<br />
t Tage des Monats: 28 bis 31. Verwechseln Sie dies nicht mit der<br />
Nummer des Tages.<br />
T Zeitzoneneinstellung des Computers, beispielsweise »EST« oder<br />
»MDT«<br />
U Sekunden der UNIX-Epoche (seit 1.1.1970). Entspricht der Ausgabe<br />
der Funktion time.<br />
w %w Tag der Woche, beginnend mit 0 (Sonntag) bis 6 (Samstag)<br />
W ISO-8601 Wochennummer des Jahres (ab erste Woche mit einem<br />
Montag drin)<br />
Y %Y Jahr im vierstelligen Format »2001«<br />
y %y Jahr im zweistelligen Format »01«<br />
z %j Tag im Jahr: 0 bis 365<br />
Z %Z Offset der Zeitzonen gegen UTC in Sekunden von -43 200 bis<br />
43 200 (86 400 Sekunden entspricht 1 Tag).<br />
%G Das Jahr wie %Y, aber nur bei vollen Wochen. Angefangene<br />
Wochen zählen im vorhergehenden oder im folgenden Jahr<br />
Tabelle 3.6: Formatzeichen der Datumsformatierfunktion date und strftime (Forts.)<br />
113
1<strong>14</strong><br />
Daten verarbeiten<br />
date strftime Beschreibung<br />
Beachten Sie, dass die Funktion date nur englische Namen für<br />
Wochentage oder Monate erzeugen kann. Benutzen Sie in lokalisierten<br />
Umgebungen besser immer strftime.<br />
Das Prinzip der Formatzeichen ist relativ einfach. Man stellt sich dazu eine Zeichenkette<br />
zusammen, die alle benötigten Formatzeichen enthält. Für strftime<br />
sieht das dann beispielsweise folgendermaßen aus:<br />
%d.%m.%Y<br />
%g Wie %G, aber kurzes Jahresformat wie %y<br />
%x Zeit nach lokalen Systemeinstellungen<br />
%X Datum nach lokalen Systemeinstellungen<br />
%c Zeit + Datum nach lokalen Systemeinstellungen<br />
%D Entsprechung der Folge »%m/%d/%y«<br />
%U Wochennummer, beginnt mit Sonntag<br />
%V Kalenderwoche nach ISO 8601<br />
%W Wochennummer, beginnt mit Montag<br />
%R Komplette Zeit im 24-Stunden-Format<br />
%C Jahrhundert (2003 gibt »20« zurück)<br />
%t Tabulator<br />
%n Zeilenvorschub<br />
%% Prozentzeichen<br />
Tabelle 3.6: Formatzeichen der Datumsformatierfunktion date und strftime (Forts.)<br />
Nun ist der Funktion noch ein Datumswert zu übergeben. Aus diesem werden die<br />
benötigten Datumsfragmente dann extrahiert. Bei strftime kommt noch hinzu,<br />
dass die Lokalisierung vorher eingestellt werden kann, um die Namen von Monaten<br />
oder Wochentagen für eine bestimmte Sprache zu erzeugen. Das folgende<br />
Beispiel zeigt, wie das geht:
Listing 3.18: DateStrftime.php – Lokalisierte Datumsformatierung<br />
(hier: Windows-Version)<br />
Datums- und Zeitfunktionen<br />
setlocale(LC_TIME, "ge");<br />
echo strftime("%A, %d. %B %Y", time());<br />
Die Funktionen date und strftime geben jeweils die formatierte Zeichenkette<br />
zurück, sodass anschließend die Ausgabe mit echo oder print erfolgen kann. Die<br />
Angabe der Lokalisierung mit setlocale ist dagegen etwas tückisch, denn die<br />
benötigten Angaben werden dem Betriebssystem entnommen und hier gibt es<br />
Unterschiede zwischen der Windows- und der Linux-Version. Mehr dazu finden<br />
Sie im Abschnitt »Lokalisierung und Formatierung von Zeichen« ab Seite 376.<br />
Jetzt muss man natürlich feststellen, auf welcher Plattform man gerade das Skript<br />
laufen lässt. Ein Plattformwechsel ist durchaus typisch, weil die meisten Entwickler<br />
ihre Skripte auf Windows entwickeln und beim Provider unter Linux im Produktionsbetrieb<br />
laufen lassen. Dazu kann die Konstante PHP_OS eingesetzt werden:<br />
Listing 3.19: DateStrftimeExt.php: Datumsausgaben betriebsystemunabhängig steuern<br />
$sl = TRUE;<br />
if (PHP_OS == "WINNT")<br />
{<br />
$sl = setlocale(LC_TIME, "German_Germany");<br />
}<br />
else<br />
{<br />
$sl = setlocale(LC_TIME, "de_DE");<br />
}<br />
if ($sl)<br />
{<br />
echo strftime("%A, %d. %B %Y", time());<br />
}<br />
else<br />
{<br />
echo "Lokalisierung konnte nicht gesetzt werden";<br />
}<br />
setlocale gibt entweder die aktuelle Einstellung zurück, wenn die neue<br />
akzeptiert werden konnte, oder FALSE, wenn die Zuweisung misslang.<br />
Auf dieser Rückgabe beruht die im letzten Skript gezeigte Ausgabesteuerung.<br />
115
116<br />
Daten verarbeiten<br />
Die Konstante PHP_OS gibt auf Windows-Systemen »WINNT« zurück, unabhängig<br />
davon, ob es sich um Windows NT, 2000 oder XP handelt. Auf Linux-Systemen<br />
steht »Linux« drin, auf FreeBSD beispielsweise »FreeBSD«. Um auch ältere Windows-Systeme<br />
zu erfassen, ist folgende Abfrage noch robuster:<br />
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')<br />
{<br />
echo 'Dieser Server läuft unter Windows!';<br />
}<br />
else<br />
{<br />
echo 'Dieser Server läuft nicht unter Windows!';<br />
}<br />
Beachten Sie, dass die Abfrage des Betriebssystems nichts mit dem System<br />
zu tun hat, das der Benutzer der Seite nutzt bzw. auf dem der Browser<br />
läuft.<br />
Abschließend noch ein Wort zu der stillschweigend genutzten Konstanten<br />
LC_TIME. Damit wird setlocale mitgeteilt, dass die Einstellung nur für Zeit- und<br />
Datumswerte erfolgen soll. PHP kennt auch andere Funktionen, deren Ausgaben<br />
sich lokalisieren lassen. Diese verlangen andere Konstanten. Mit LC_ALL wird die<br />
Lokalisierung global umgestellt. Im Abschnitt 9.1 »Mehrsprachige Webseiten« ab<br />
Seite 374 finden Sie mehr <strong>In</strong>formationen dazu.<br />
Datumsfragmente mit idate<br />
Wenn man nur einen bestimmten Teil aus einem Zeitstempel benötigt, ist idate<br />
eine interessante Funktion, die mit PHP5 neu eingeführt wurde. Die folgende<br />
Tabelle zeigt, welche Formatanweisungen möglich sind:<br />
Format Beschreibung<br />
B Swatch <strong>In</strong>ternet Time<br />
d Tag des Monats<br />
h Stunde (12:00 Format)<br />
H Stunde (24:00 Format)<br />
Tabelle 3.7: Formatanweisungen der Funktion idate
Format Beschreibung<br />
i Minute<br />
I 1, wenn Sommerzeit, sonst 0<br />
L 1, wenn Schaltjahr, sonst 0<br />
m Nummer des Monats<br />
s Sekunden<br />
t Tag im Monat<br />
Datums- und Zeitfunktionen<br />
U Sekunden seit Beginn der Unix-Epoche, entspricht der von time erzeugten<br />
Ausgabe.<br />
w Tag der Woche, wobei 0 der Sonntag ist.<br />
W ISO-8601-Wochennummer. Die erste Woche beginnt am ersten Montag<br />
im Jahr.<br />
y Jahr (1 oder 2 Ziffern, 2005 wird als »5« zurückgegeben)<br />
Y Jahr (4 Ziffern)<br />
Z Tag im Jahr<br />
Z Offset der Zeitzone in Sekunden<br />
Tabelle 3.7: Formatanweisungen der Funktion idate (Forts.)<br />
Die Funktion gibt immer einen Zahlenwert zurück, was zwangsläufig bedeutet,<br />
dass immer nur eine der Formatanweisungen benutzt werden kann:<br />
$jahr = idate('Y');<br />
Der zweite, optionale Parameter bestimmt über einen Zeitstempel die zugrunde<br />
liegende Zeitangabe. Beachten Sie bei den zweistelligen Jahreszahlen, dass auch<br />
diese als Zahl zurückgegeben werden. Das Jahr 2004 wird also mit dem Parameter<br />
»y« als Zahl 4, nicht als Zeichenkette »04« erscheinen.<br />
Tricks mit JavaScript: Lokale Zeit ermitteln<br />
Um die lokale Zeit eines Benutzers zu ermitteln, benötigen Sie JavaScript. Meist<br />
ist es ausreichend, diese <strong>In</strong>formation nur einmal, gleich beim ersten Seitenaufruf,<br />
117
118<br />
Daten verarbeiten<br />
zu ermitteln. Es bietet sich an, dazu das Refresh-Tag von HTML einzusetzen, das<br />
einen erneuten Aufruf der Seite erzwingt. Der Benutzer muss deshalb nicht eingreifen,<br />
ein Formular senden oder einen Link klicken. Gleichzeitig können Sie<br />
feststellen, ob JavaScript überhaupt aktiviert ist.<br />
Das Ganze basiert auf einem einfachen Trick. Zuerst wird mit PHP festgestellt, ob<br />
eine Zeitinformation bereits vorliegt. Das ist beim ersten Aufruf der Seite natürlich<br />
nicht der Fall. Dann wird eine JavaScript-Sequenz erzeugt, die die lokale Zeit<br />
abfragt. Es ist hilfreich, dazu ein Formular einzusetzen und das Formular dann per<br />
JavaScript senden zu lassen.<br />
Listing 3.20: datejavascript.php – Lokale Zeit des Browsers ermitteln<br />
Formatieren und Ausgeben<br />
Hier wird einmal abgefragt, ob ein Formular oder eine Seite gesendet wurde. Beim<br />
ersten Aufruf der Seite über den Browser kann es sich nie um ein Formular handeln.<br />
Die Variable $_SERVER['REQUEST_METHOD'] enthält diese <strong>In</strong>formation. Der<br />
Umgang mit derartigen <strong>In</strong>formationen wird später noch ausführlich behandelt.<br />
Der linke Teil der Abfrage umfasst die Erkennung der gesendete Zeitinformation.<br />
Verpackt wird diese in einem versteckten Feld im Formular mit dem Namen<br />
»timeForm«. Der Rest ist reines JavaScript. Bei ersten Aufruf wird der Skriptblock<br />
erzeugt und dann sofort ausgeführt. Dies führt zum Senden des Formulars, was<br />
PHP wiederum wie beschrieben erkennt.<br />
Ist JavaScript nicht vorhanden, verbleibt die Seite im ersten Zustand, entsprechend<br />
ist die Variable $_POST['timeField'] niemals gesetzt. Darauf zielen die Abfragen<br />
mit der Funktion isset ab, die testet, ob eine Variable initialisiert wurde.<br />
3.8 Formatieren und Ausgeben<br />
Neben Datumsangaben sind meist Zeichenketten und Zahlen zu formatieren. Vor<br />
allem mehrsprachige Webseiten profitieren von den Möglichkeiten, die PHP bietet.<br />
Zahlen formatieren<br />
Eine wichtige Formatierfunktion ist number_format. Damit lassen sich Zahlen –<br />
intern immer im englischen Format vorliegend – in das im deutschsprachigen<br />
Sprachraum übliche Format umwandeln. Das folgende Skript zeigt die Anwendung:<br />
Listing 3.21: NumberFormat.php: Zahlen für eine deutsche Website formatieren<br />
$z1 = 100000;<br />
$z2 = 2.67;<br />
$z3 = $z1 / $z2;<br />
echo "$z3 ";<br />
echo number_format($z3, 2, ',', '.');<br />
Die vier Parameter der Funktion number_format haben folgende Bedeutung:<br />
1. Zahl, die formatiert werden soll.<br />
2. Anzahl der Dezimalstellen, auf die gerundet werden soll.<br />
119
120<br />
Daten verarbeiten<br />
3. Zeichen für das Dezimaltrennzeichen (deutsch: Komma, englisch: Punkt).<br />
4. Zeichen für das Tausendertrennzeichen (optional, deutsch: Punkt, englisch:<br />
Komma).<br />
Abbildung 3.12:<br />
Unformatierte (oben) und formatierte (unten) Zahl im Vergleich<br />
Umwandeln, Anpassen und Ausgeben<br />
Mehr Möglichkeiten der Formatierung bieten die rund um printf etablierten<br />
Funktionen, die teilweise aus der C-Welt übernommen wurden:<br />
printf<br />
Die Funktion formatiert einen oder mehrere Werte und gibt das Resultat wie<br />
print sofort aus.<br />
sprintf<br />
Die Funktion formatiert einen oder mehrere Werte und gibt das Resultat als<br />
Zeichenkette zurück.<br />
vprintf<br />
Die Funktion formatiert einen oder mehrere Werte aus einem Array und gibt<br />
das Resultat wie print sofort aus.<br />
vsprintf<br />
Die Funktion formatiert einen oder mehrere Werte aus einem Array und gibt<br />
das Resultat als Zeichenkette zurück.<br />
sscanf<br />
Hiermit lässt sich eine Zeichenkette anhand vorhandener Funktionsmuster<br />
durchsuchen und die Fundstellen werden in Variablen abgelegt.<br />
fscanf<br />
Funktioniert wie sscanf, als Eingabe wird aber eine Datei erwartet.<br />
Der Trick bei der Nutzung besteht auch hier, ähnlich wie bei strftime, in der<br />
Nutzung von Formatangaben. Die Eingabewerte werden dann so verändert, dass<br />
sie den Formatangaben entsprechen. Alle hier vorgestellten Funktionen verwenden<br />
denselben Satz an Formaten, der nachfolgend vorgestellt wird.
Die Formatstruktur<br />
Formatieren und Ausgeben<br />
Jede Formatanweisung wird einem Datenwert zugeordnet. Sie beginnt immer mit<br />
dem Prozentzeichen als Sonderzeichen. Soll ein Prozentzeichen ausgegeben werden,<br />
schreibt man %%. Nach dem Prozentzeichen folgen (optional) verschiedene<br />
Formathinweise, die abhängig von der Datenart sind. Zwischen dem Prozentzeichen<br />
und den Formatinstruktionen kann optional ein Verweis auf einen bestimmten<br />
Datenwert stehen, bestehend aus der Argumentnummer und einem Backslash.<br />
Alle Funktionen erlauben die Angabe mehrerer Datenwerte, entweder als Parameterkette<br />
oder als Array. Der Verweis auf einen bestimmten Parameter erhöht die<br />
Flexibilität.<br />
Am Ende steht dann eine <strong>In</strong>struktion, die die Art der Formatierung bestimmt:<br />
b<br />
Das Argument wird als ganze Zahl betrachtet und als Binär-Wert ausgegeben.<br />
c<br />
Das Argument wird als ganze Zahl betrachtet und das entsprechende ASCII-<br />
Zeichen wird ausgegeben.<br />
d<br />
Das Argument wird als ganze Zahl betrachtet und ein Dezimalwert (mit Vorzeichen)<br />
wird ausgegeben.<br />
u<br />
Das Argument wird als ganze Zahl betrachtet und ein Dezimalwert (ohne Vorzeichen)<br />
wird ausgegeben.<br />
f<br />
Das Argument wird als float angesehen und eine Fließkomma-Zahl wird ausgegeben.<br />
o<br />
Das Argument wird als ganze Zahl betrachtet und als Oktalzahl ausgegeben.<br />
s<br />
Das Argument wird als Zeichenkette betrachtet und unverändert ausgegeben.<br />
x<br />
Das Argument wird als ganze Zahl betrachtet und als Hexadezimal-Wert ausgegeben<br />
(mit Kleinbuchstaben für die Hex-Zeichen »a« bis »f«).<br />
121
122<br />
Daten verarbeiten<br />
X<br />
Das Argument wird als ganze Zahl betrachtet und als Hexadezimal-Wert ausgegeben<br />
(mit Großbuchstaben für die Hex-Zeichen »A« bis »F«).<br />
Alles zusammen sieht dann beispielsweise folgendermaßen aus:<br />
%3\s<br />
Diese Zeichenfolge gibt das dritte Argument (3\) als Zeichenkette (s) aus.<br />
%d<br />
Diese Zeichenfolge gibt das in der Liste nächste Argument als Dezimalzahl<br />
aus.<br />
%082d<br />
Auch dies formatiert eine Dezimalzahl. Die drei Zeichen haben hier folgende<br />
Bedeutung:<br />
1. Füllwert, entweder ein Leerzeichen, die Zahl 0 oder ein beliebiges, von<br />
einem Apostroph ’ eingeleitetes Zeichen<br />
2. Die Anzahl der Stellen vor dem Dezimaltrennzeichen, auf die aufgefüllt<br />
wird.<br />
3. Die Anzahl der Stellen nach dem Dezimaltrennzeichen.<br />
%4\’_-10s<br />
Dieses Format formatiert eine Zeichenkette linksbündig (das macht das<br />
Minuszeichen, rechtsbündig ist der Standard), die das vierte Argument der<br />
Parameterkette (4\) nutzt. Zum Auffüllen wird der Unterstrich benutzt (’_), der<br />
die Zeichenkette auf zehn (10) Zeichen auffüllt. »s« definiert den Datenwert<br />
als Zeichenkette.<br />
Die Beispiele und ihre Wirkung können Sie im folgenden Listing sehen:<br />
Listing 3.22: FormatPrintf.php – Verschiedene Formatierungen<br />
$s1 = "Markt+Technik";<br />
$s2 = 34.90;<br />
echo "$z3 ";<br />
printf("Drittes Argument, Zeichenkette: %3\$s <br />
Nächstes Argument, Zahl: %d <br />
Aufgefüllte 4. Zeichenkette: %4\$'_-10s <br />
Nächstes Argument, Zahl: %08.2f ",<br />
$s2,<br />
$s2,
Formatieren und Ausgeben<br />
$s1,<br />
'16%');<br />
Bei der Auswahl der passenden Werte gilt im Beispiel folgende Zuordnung:<br />
\3 und \4 deuten klar auf den passenden Wert hier: \3 erhält $s1, \4 das Literal<br />
'16%'.<br />
Die übrigen Formate haben keine Präferenzen zu einem spezifischen Wert<br />
und suchen deshalb die Argumente vom ersten beginnend ab: $d erhält $s2<br />
und das letzte Format den zweiten Wert, der ebenfalls $s2 enthält.<br />
Für die Generierung von Farbwerten in HTML lässt sich das Formatzeichen »X«<br />
sehr gut nutzen. Wichtig ist hier, die Anzahl der Stellen mit 2 vorzugeben.<br />
Andernfalls würden kleine Werte (unter 16) lediglich eine Stelle belegen und der<br />
Farbcode damit nicht korrekt aufgebaut werden. Ein Füllzeichen muss nicht angegeben<br />
werden, für Zahlen ist die 0 das voreingestellte Zeichen.<br />
Listing 3.23: FormatSPrintf.php – Formatierung von HTML-Farbwerten<br />
$r = 134;<br />
$g = 211;<br />
$b = 56;<br />
$code = sprintf("#%2X%2X%2X", $r, $g, $b);<br />
echo
124<br />
Daten verarbeiten<br />
Werte erkennen<br />
Die Funktionen sscanf und fscanf drehen den Vorgang um. Erwartet wird eine<br />
Zeichenkette, die bestimmte Wertfragmente enthält. Daraus wird dann der Wert<br />
extrahiert und an eine bereitgestellte Variable übergeben. Das letzte Beispiel eignet<br />
sich besonders gut zur »Umkehrung«:<br />
Listing 3.24: FormatScanf.php – Daten aus einer Zeichenkette extrahieren<br />
$r = 134;<br />
$g = 211;<br />
$b = 56;<br />
$code = sprintf("#%2X%2X%2X", $r, $g, $b);<br />
echo "Die Farbwerte für $code sind: ";<br />
list($rr, $gg, $bb) = sscanf($code, "#%2X%2X%2X");<br />
echo "R = $rr, G = $gg, B = $bb";<br />
Die Ausgabe zeigt, dass alle Farbwerte korrekt erkannt wurden. Trickreich ist hier<br />
die Übernahme des von sscanf erzeugten Arrays mit list in die drei erwarteten<br />
Variablen. Mehr <strong>In</strong>formationen zu Array folgen im Abschnitt 5.1 »Datenfelder im<br />
Einsatz: Arrays« ab Seite 182.<br />
Allerdings kann sscanf nicht erkennen, wenn die gesuchte Zeichenfolge in einer<br />
anderen eingebettet ist. Dafür kommen nur reguläre Ausdrücke in Frage, die mehr<br />
Formatangaben erlauben, dafür aber ungleich komplexer sind.<br />
3.9 Referenz<br />
Abbildung 3.<strong>14</strong>:<br />
Die einzelnen dezimalen Farbwerte<br />
wurden aus der hexadezimalen<br />
Folge extrahiert<br />
Dieser Abschnitt enthält eine Übersicht aller in diesem Kapitel behandelten Funktionen<br />
in tabellarischer Form.
Zeichenketten-Funktionen<br />
Referenz<br />
<strong>In</strong>sgesamt kennt PHP5 knapp 90 Zeichenkettenfunktionen. Es ist sinnvoll, sich<br />
dieser schieren Fülle strukturiert zu nähern. Zuerst einmal kann man Gruppen bilden,<br />
um die Funktionsnamen den bereits erwähnten typischen Operationen zuzuordnen.<br />
Sie finden dann weitaus schneller die passende Funktion.<br />
Funktion Bedeutung<br />
strpos Sucht eine Zeichenfolge in einer anderen und gibt die Position der<br />
Fundstelle zurück. Berücksichtigt Groß- und Kleinschreibung.<br />
stripos Sucht eine Zeichenfolge in einer anderen und gibt die Position der<br />
Fundstelle zurück. Beachtet Groß- und Kleinschreibung nicht.<br />
strrpos Sucht das letzte Auftreten einer Zeichenfolge und gibt die Position<br />
der Fundstelle zurück. Berücksichtigt Groß- und Kleinschreibung.<br />
strchr Sucht ein einzelnes Zeichen in einer Zeichenfolge und gibt den Rest<br />
der Zeichenkette ab der Fundstelle zurück.<br />
strrchr Sucht das letzte Auftreten eines Zeichens in einer Zeichenfolge und<br />
gibt den Rest der Zeichenkette ab der Fundstelle zurück.<br />
strstr Sucht eine Zeichenfolge in einer anderen und gibt den Rest der Zeichenfolge<br />
ab der Fundstelle zurück. Berücksichtigt Groß- und Kleinschreibung.<br />
stristr Sucht eine Zeichenfolge in einer anderen und gibt den Rest der Zeichenfolge<br />
ab der Fundstelle zurück. Beachtet Groß- und Kleinschreibung<br />
nicht.<br />
strcmp Vergleicht zwei Zeichenfolgen.<br />
strspn Ermittelt die Anzahl der Zeichen, die bei zwei zu vergleichenden<br />
Zeichenfolgen übereinstimmen.<br />
strcspn Ermittelt die Anzahl der Zeichen, die bei zwei zu vergleichenden<br />
Zeichenfolgen nicht übereinstimmen.<br />
strcasecmp Vergleicht zwei Zeichenfolgen auf binärer Basis. Beachtet Groß- und<br />
Kleinschreibung.<br />
Tabelle 3.8: Zeichenkettenfunktionen zum Vergleichen, Suchen und Ersetzen<br />
125
126<br />
Daten verarbeiten<br />
Funktion Bedeutung<br />
strnatcasecmp Vergleicht zwei Zeichenfolgen auf binärer Basis und berücksichtigt<br />
die »natürliche« Erscheinungsform. Beachtet Groß- und Kleinschreibung.<br />
strnatcmp Vergleicht zwei Zeichenfolgen auf binärer Basis und berücksichtigt<br />
die »natürliche« Erscheinungsform. Beachtet Groß- und Kleinschreibung<br />
nicht.<br />
strncasecmp Vergleicht nur eine gegebene Anzahl von Zeichen zweier Zeichenfolgen<br />
und beachtet Groß- und Kleinschreibung dabei nicht.<br />
strncmp Vergleicht nur eine gegebene Anzahl von Zeichen zweier Zeichenfolgen<br />
und beachtet Groß- und Kleinschreibung dabei.<br />
str_replace Ersetzt Zeichen in einer Zeichenfolge durch andere Zeichen oder<br />
löscht sie<br />
str_ireplace Wie str_replace, beachtet aber Groß- und Kleinschreibung nicht.<br />
substr_replace Wie str_replace, jedoch kann der Suchbereich eingeschränkt werden.<br />
Tabelle 3.8: Zeichenkettenfunktionen zum Vergleichen, Suchen und Ersetzen (Forts.)<br />
Funktion Bedeutung<br />
strlen Anzahl der Zeichen<br />
crc32 Prüfsumme über eine Zeichenfolge<br />
count_chars Häufigkeitsanalyse über in der Zeichenfolge enthaltenen Zeichen<br />
str_word_count Häufigkeitsanalyse über die Wörter eines Textes<br />
md5 MD5-Hash (Message Digest 5, eine Art Prüfsumme) einer Zeichenfolge<br />
sha1 SHA1-Hash, eine andere Art Prüfsumme<br />
similar_text Diese Funktion berechnet die Ähnlichkeit zweier Zeichenketten als<br />
Prozentwert.<br />
Tabelle 3.9: Zeichenkettenfunktionen zum Ermitteln von Eigenschaften
Funktion Bedeutung<br />
soundex Berechnet die Lautähnlichkeit von Zeichenkette<br />
Referenz<br />
levenshtein Berechnet die Lautähnlichkeit nach dem Levenshtein-Algorithmus<br />
metaphone Lautähnlichkeit analog zu saindex, aber unter Berücksichtigung der<br />
Grundregeln der englichen Sprache.<br />
substr_count Ermittelt, wie oft eine Zeichenfolge in einer anderen steckt<br />
Tabelle 3.9: Zeichenkettenfunktionen zum Ermitteln von Eigenschaften (Forts.)<br />
Funktion Bedeutung<br />
substr Gibt einen Teil einer Zeichenkette zurück.<br />
trim, chop Entfernt Leerzeichen und Zeilenumbrüche vom Anfang und vom<br />
Ende.<br />
ltrim Entfernt Leerzeichen und Zeilenumbrüche vom Anfang.<br />
rtrim Entfernt Leerzeichen und Zeilenumbrüche vom Ende.<br />
ucfirst Macht den ersten Buchstaben einer Zeichenkette zum Großbuchstaben.<br />
ucwords Macht den ersten Buchstaben jedes Wortes zum Großbuchstaben.<br />
strtolower Verwandelt alle Buchstaben in Kleinbuchstaben.<br />
strtoupper Verwandelt alle Buchstaben in Großbuchstaben.<br />
wordwrap Bricht längeren Text durch Einfügen von Zeilenumbrüchen um.<br />
strtr Tauscht einzelne Zeichen auf Basis einer Austauschtabelle aus.<br />
strtok Zerlegt eine Zeichenfolge durch sukzessive Aufrufe in Teile.<br />
strrev Dreht eine Zeichenfolge um.<br />
tr_shuffle Bringt die Zeichen mittels Zufallsgenerator durcheinander.<br />
strip_tags Entfernt HTML-Tags aus einer Zeichenfolge.<br />
chunk_split Teilt eine Zeichenkette in Abschnitte gleicher Größe.<br />
Tabelle 3.10: Zeichenkettenfunktionen zur Auswahl einer Teilzeichenkette und andere<br />
Umwandlungsoperationen<br />
127
128<br />
Daten verarbeiten<br />
Funktion Bedeutung<br />
str_rot13 Führt eine primitive Kodierung nach ROT13 aus.<br />
str_repeat Wiederholt eine Zeichenkette mehrfach und gibt die resultierende<br />
Zeichenfolge zurück.<br />
str_pad Füllt eine Zeichenfolge links oder rechts mit Füllzeichen auf.<br />
Tabelle 3.10: Zeichenkettenfunktionen zur Auswahl einer Teilzeichenkette und andere<br />
Umwandlungsoperationen (Forts.)<br />
Funktion Bedeutung<br />
addslashes,<br />
addcslashes<br />
stripslashes,<br />
stripcslashes<br />
Fügt Backslashes hinzu.<br />
Entfernt Backslashes.<br />
Tabelle 3.11: Zeichenkettenfunktionen zur Behandlung einzelner Zeichen<br />
Funktion Bedeutung<br />
chr Gibt das dem Zeichencode entsprechende Zeichen zurück.<br />
ord Gibt den Zeichencode zum angegebenen Zeichen zurück.<br />
Tabelle 3.12: Zeichenkettenfunktionen zum Ermitteln von bestimmten Zeichen mit besonderer<br />
Bedeutung<br />
Funktion Bedeutung<br />
bin2hex Wandelt binäre Daten in ihre hexadezimale Form um<br />
html_entity_decode Wandelt HTML-Entitäten in Zeichen um<br />
html_entities Wandelt alle Sonderzeichen in HTML-Entitäten um<br />
html_specialchars Wandelt nur XML-Spezialzeichen in HTML-Entitäten um<br />
Tabelle 3.13: Zeichenkettenfunktionen zum Umwandeln in einen anderen Datentyp und<br />
HTML-abhängige Funktionen
Funktion Bedeutung<br />
setlocale Setzt eine bestimmte Lokalisierung<br />
number_format Formatiert Zahlen<br />
Funktionen für reguläre Ausdrücke<br />
Referenz Ausgabe- und Datumsfunktionen<br />
Referenz<br />
money_format Formatiert Währungsangaben, ist jedoch nur auf Linux verfügbar<br />
localeconv Ermittelt die aktuellen Formatierbedingungen für Zahlen<br />
Tabelle 3.<strong>14</strong>: Zeichenkettenfunktionen zum sprach- oder landesabhängigen Formatieren<br />
Funktion Bedeutung<br />
preg_match Ermittelt ein zutreffendes Suchmuster<br />
preg_match_all Ermittelt alle zutreffendes Suchmuster<br />
preg_grep Durchsucht ein Array nach einem Suchmuster und filtert Fundstellen<br />
preg_split Teilt eine Zeichenkette an einem Suchmuster und gibt jede Fundstelle<br />
al Element eines Arrays zurück<br />
Tabelle 3.15: Funktionen zum Suchen und Ersetzen mittels regulärer Ausdrücke<br />
Funktion Bedeutung<br />
date Formatiert eine Datumsangabe.<br />
strftime Formatiert eine Datumsangabe in Abhängigkeit von der Lokalisierung.<br />
printf Formatiert beliebige Werte und gibt sie sofort aus.<br />
sprintf Formatiert beliebige Werte und gibt sie als Zeichenkette zurück.<br />
Tabelle 3.16: Funktionen zum Erzeugen, Erkennen und Formatieren von Datums-, Zahlen-<br />
und Zeichenkettenwerten<br />
129
130<br />
Daten verarbeiten<br />
Funktion Bedeutung<br />
vprintf Formatiert beliebige Werte aus einem Array und gibt sie sofort aus.<br />
vsprintf Formatiert beliebige Werte aus einem Array und gibt sie als Zeichenkette<br />
zurück.<br />
sscanf Extrahiert nach Formatangaben Werte aus einer Zeichenkette.<br />
fscanf Extrahiert nach Formatangaben Werte aus einer Datei.<br />
Tabelle 3.16: Funktionen zum Erzeugen, Erkennen und Formatieren von Datums-, Zahlen-<br />
und Zeichenkettenwerten (Forts.)<br />
3.10 Kontrollfragen<br />
1. Was ist ein Literal?<br />
2. Wie werden Konstanten definiert?<br />
3. Wozu werden reguläre Ausdrücke eingesetzt?<br />
4. Schreiben Sie ein Skript, dass anhand mehrere Parameter ein span-Tag mit korrekter<br />
hexadezimaler Angabe der Vordergrund- und Hintergrundfarbe mit Hilfe<br />
des style-Attributes erstellt und ausgibt. Tipp: Verwenden Sie printf.
Programmieren<br />
4
132<br />
Programmieren<br />
Jetzt geht es richtig los. Zur richtigen Programmierung benötigen Sie zwangsläufig<br />
Anweisungen, die den Programmfluss steuern. Dieses Kapitel stellt alle Befehle<br />
vor, die dies in PHP erledigen. Wer bereits mit PHP 4 vertraut ist, kann diesen Tag<br />
überspringen, denn PHP5 bietet hier nichts Neues. Alle Flussbefehle, die im<br />
Zusammenhang mit den neuen objektorientierten Eigenschaften eingeführt wurden,<br />
werden am Tag 8 behandelt.<br />
4.1 Verzweigungen<br />
Verzweigungen steuern den Programmfluss. PHP lehnt sich hier stark an die Syntax<br />
der Programmiersprache C an. Auf der gleich Basis sind auch die Befehle von<br />
Sprachen wie JavaScript, Java, C++ und C# entstanden.<br />
Einfache Verzweigungen mit if<br />
Der Befehl if dient der Programmierung einer einfachen Verzweigung. Damit<br />
können Programmteile in Abhängigkeit von einer Bedingung ausgeführt werden.<br />
Als Bedingung verwendet man einen Ausdruck, der einen Booleschen Wert<br />
zurückgibt – also entweder Wahr (TRUE) oder Falsch (FALSE). Zur Verknüpfung<br />
von solchen Ausdrücken gibt es verschiedene Operatoren, die in der Einführung<br />
bereits vorgestellt wurden.<br />
Aufbau von Ausdrücken<br />
Da PHP praktisch über ein sehr schwaches Typkonzept verfügt, werden oft intern<br />
Typumwandlungen vorgenommen. Da Bedingungen Boolesche Werte benötigen,<br />
konvertiert PHP5 hier eventuell vorhandene andere Datentypen einfach. Das entsprechende<br />
Verhalten sollten Sie kennen, um den gewünschten Effekt auch tatsächlich<br />
zu erzielen.<br />
Ausdrücke bestehen generell aus zwei Arten von Operatoren. So genannte Vergleichsoperatoren<br />
vergleichen zwei Werte, entweder zwei Variablen oder eine<br />
Variable und eine Konstante. Zu den Vergleichsoperatoren gehören >, =,
Verzweigungen<br />
Denken Sie daran, dass der Operator »gleich« aus zwei Gleichheitszeichen<br />
besteht!<br />
$a > 3<br />
45 <br />
Bei der Erstellung von logischen Ausdrücken ist es wichtig zu wissen, dass diese<br />
eine so genannte Assoziativität besitzen, die die Rangfolge bei der Verarbeitung<br />
bestimmt. Wenn diese intern festgelegte Reihenfolge nicht passt, setzen Sie einfach<br />
Klammern.<br />
Assoziativität Rangfolge Operator<br />
1. (höchster Rang) Links ,<br />
2. Links or<br />
3. Links xor<br />
Tabelle 4.1: Rangfolge und Assoziativität von Operatoren<br />
133
134<br />
Programmieren<br />
Assoziativität Rangfolge Operator<br />
4. Links and<br />
5. Rechts print<br />
6. Rechts = += -= *= /= .= %= &= |= ^= =<br />
7. Links ? :<br />
8. Links ||<br />
9. Links &&<br />
10. Links |<br />
11. Links ^<br />
12. Links &<br />
13. Keine == != === !==<br />
<strong>14</strong>. Keine < >=<br />
15. Links ><br />
16. Links + – .<br />
17. Links * / %<br />
18. Rechts ! ~ ++ -- (int) (float) (string) (array) (object) @<br />
19. Rechts [<br />
20. (niedrigster Rang) Keine new<br />
Tabelle 4.1: Rangfolge und Assoziativität von Operatoren (Forts.)<br />
<strong>In</strong> der Praxis bedeutet die Rangfolge »Links«, dass beispielsweise im folgenden<br />
Ausdruck der linke Teil zuerst ausgeführt wird, weil der Operator || Links-assoziativ<br />
ist:<br />
$b == 8 || $c == $d<br />
Wird dagegen eine Typumwandlung mit (float) durchgeführt, wird der Ausdruck<br />
rechts vom Operator zuerst ausgeführt:<br />
(float) $a = 3
Verzweigungen<br />
Weil auch der Operator = Rechts-assoziativ ist, wird beim letzten Beispiel die Auswertung<br />
mit der 3 begonnen, dann erfolgt die Zuweisung an die Variable $a und<br />
erst dann wird $a in eine Gleitkommazahl umgewandelt.<br />
Keine Assoziativität bei den Operatoren == und deren Verwandten heißt, dass es<br />
für das Ergebnis keinen Unterschied ergibt, ob ein Teilausdruck links oder rechts<br />
steht. Dies kann ausgenutzt werden, um einen typischen Fehler zu vermeiden, der<br />
häufig passiert: Die Verwechslung von = und ==. Schreiben Sie nämlich in einem<br />
if-Befehl nur ein einfaches Gleichheitszeichen, so wird PHP als Ergebnis der<br />
Zuweisung den <strong>In</strong>halt zurückgeben und dann diesen Wert automatisch konvertieren.<br />
Sie erhalten also keine Fehlermeldung, wohl aber einen falschen Programmfluss.<br />
Betrachten Sie folgenden Code:<br />
$a = 23;<br />
if ($a = 23)<br />
{<br />
//<br />
}<br />
Hier wird der Variablen $a in der Bedingung ein zweites Mal der Wert 23 zugewiesen<br />
und dann dem if-Befehl als Ergebnis des Ausdrucks übergeben. PHP konvertiert<br />
nun die 23 in TRUE (0 entspräche FALSE, alles andere ist TRUE). Um derartige<br />
Tippfehler zu vermeiden, vertauscht man die Operanden:<br />
$a = 23;<br />
if (23 = $a)<br />
{<br />
//<br />
}<br />
Würde korrekt 23 == $a geschrieben werden, funktionierte der Ausdruck wie<br />
erwartet, denn bei == spielt die Rangfolge keine Rolle. 23 = $a misslingt allerdings,<br />
weil zuerst der rechte Ausdruck ausgewertet wird (gelingt noch) und dann der<br />
linke, was die Zuweisung einer Variablen zu einem Literal bedeuten würde – ein<br />
klarer Fall für einen Syntaxfehler.<br />
Eng verbunden mit der Assoziativität ist auch eine Optimierungseigenschaft von<br />
PHP. Manche Ausdrücke müssen nämlich nicht vollständig ausgewertet werden:<br />
$a = 3;<br />
if ($a == 3 || $b == 7)<br />
Bei dieser Verknüpfung erkennt PHP zuerst einen Oder-verknüpften Ausdruck.<br />
Wenn der linke Zweig ($a == 3) wahr ist, dann wird der gesamte Ausdruck zwangs-<br />
135
136<br />
Programmieren<br />
läufig wahr sein. Es ist also nicht notwendig, den rechten Teil abzuarbeiten, denn<br />
dies würde nie etwas am Ergebnis ändern. Das spart Rechenzeit und verbessert das<br />
Laufzeitverhalten.<br />
Anstatt einer Variablen kann jedoch auch eine Funktion aufgerufen werden:<br />
if (TestFunction1() == 3 || TestFunction2() == 7)<br />
Nun kann es im Programmkontext außerdem erforderlich sein, dass beide Testfunktionen<br />
tatsächlich aufgerufen werden, beispielsweise weil sie anderweitige für<br />
den Programmfluss erforderliche Aktionen ausführen, unabhängig vom jeweiligen<br />
Rückgabewert. Die Optimierung würde aber immer dann die Ausführung von<br />
TestFunction2 verhindern, wenn TestFunction1 den Wert TRUE zurückgibt.<br />
Es gibt zwei Lösungen für das Problem. Zum einen ist diese Art der Programmierung<br />
prinzipiell kein guter Stil. Man spricht von so genannten Nebeneffekten, d.h.<br />
zwei nicht in direktem Zusammenhang stehende Programmteile beeinflussen sich<br />
in Abhängigkeit von konkreten Daten gegenseitig. Das führt in der Praxis zu<br />
schwer beherrschbaren und kaum nachvollziehbaren Programmfehlern. Die erste<br />
Lösung besteht also darin, diese Art der Programmierung zu vermeiden:<br />
$result1 = TestFunction1();<br />
$result2 = TestFunction2();<br />
if ($result1 == 3 || $result2 == 7)<br />
Hier wird sichergestellt, dass beide Testfunktionen aufgerufen werden. Anschließend<br />
findet die Überprüfung statt, wobei es keine Rolle spielt, ob mit oder ohne<br />
Optimierung gearbeitet wird.<br />
Die zweite Lösung heißt PHP und verwendet Operatoren, die die Optimierung<br />
einfach unterdrücken. Dies ist immer dann angebracht, wenn Nebeneffekte ausgeschlossen<br />
werden sollen und die Abfrage der Teilausdrücke nur unnötige Schreibarbeit<br />
verursachen würde.<br />
Alternative Zweige mit else<br />
Der Block, der hinter if folgt, wird immer dann ausgeführt, wenn die Bedingung<br />
Wahr ist. Oft sind jedoch Aktionen auszuführen, wenn die Bedingung nicht erfüllt<br />
ist. Dazu wird ein alternativer Zweig geschrieben, der mit dem Schlüsselwort else<br />
einzuleiten ist. Auch hier gilt die Regel, dass mehrere Befehle nach else als Block<br />
zusammengefasst werden müssen.
Listing 4.2: Else.php – Alternativer Verarbeitungszweig<br />
Verzweigungen<br />
<br />
Zweige mit if und else können beliebig geschachtelt werden, um umfangreiche<br />
Bedingungen zu testen.<br />
Mehrfachverzweigungen mit switch<br />
Wenn sich in einem Skript mehrere Auswertungen mit if aneinanderreihen, kann<br />
man schnell die Übersicht verlieren. Da man auch deutlich häufiger auf Gleichheit<br />
testet als mit anderen Operatoren, gibt einen speziellen Befehl dafür: switch.<br />
switch verhält sich analog zu C, fällt allerdings immer von Zweig zu Zweig durch,<br />
wobei jeder Zweig durch das Schlüsselwort case angezeigt wird. Jeder Zweig muss<br />
deshalb explizit mit dem Schlüsselwort break abgeschlossen werden. Der Code<br />
hinter den einzelnen case-Zweigen muss nicht mit einem Blockkennzeichen<br />
umschlossen werden, weil hier immer alles bis zum nächsten break oder dem<br />
Ende von switch abgearbeitet wird.<br />
Ein einfaches switch<br />
Das folgende Skript zeigt eine einfache Anwendung von switch:<br />
Listing 4.3: Switch.php – Mehrfachverzweigungen sind sehr kompakt<br />
$hour = 13;<br />
echo "Tagesabschnitt für Stunde $hour: ";<br />
switch ($hour)<br />
{<br />
case 1:<br />
137
}<br />
138<br />
Programmieren<br />
echo "Nachts";<br />
break;<br />
case 7:<br />
echo "Morgens";<br />
break;<br />
case 9:<br />
echo "Vormittags";<br />
break;<br />
case 13:<br />
echo "Mittags";<br />
break;<br />
case 16:<br />
echo "Nachmittags";<br />
break;<br />
default:<br />
echo "Unbekannte Stunde";<br />
Die Testvariable ($hour) wird in den Kopf der Anweisung geschrieben. PHP vergleicht<br />
diese dann mit jedem Wert der case-Zweige und führt den Zweig aus, für<br />
den Gleichheit der Werte festgestellt wurde. Der Vergleich nutzt die Stärke des ==-<br />
Operators, nicht von ===. Das bedeutet, dass case "13" anstatt case 13 im gezeigten<br />
Beispiel als Gleich erkannt werden würde.<br />
Die break-Anweisungen sind erforderlich, damit der betroffene Zweig wieder verlassen<br />
wird, andernfalls wird einfach der Code des nächsten Zweiges ausgeführt,<br />
ohne dass dort eine erneute Prüfung stattfindet.<br />
Falls kein Vergleich zutrifft, wird ein default-Zweig gesucht und – wenn vorhanden<br />
– ausgeführt. Ist ein solcher Zweig nicht vorhanden, wird mit dem Code nach<br />
dem switch-Block fortgesetzt.<br />
Falls die Prüfung mehrerer Werte denselben Code erfordert, kann man case-<br />
Zweige aneinander setzen, wie es das folgende Beispiel zeigt:<br />
Listing 4.4: switchcase2.php – Mehrfache case-Zweige<br />
$hour = 13;<br />
echo "Tagesabschnitt für Stunde $hour: ";<br />
switch ($hour)<br />
{<br />
case 1:<br />
case 2:
}<br />
case 3:<br />
case 4:<br />
case 5:<br />
echo "Nachts";<br />
break;<br />
case 6:<br />
case 7:<br />
case 8:<br />
echo "Morgens";<br />
break;<br />
case 9:<br />
case 10:<br />
case 11:<br />
echo "Vormittags";<br />
break;<br />
case 12:<br />
case 13:<br />
case <strong>14</strong>:<br />
echo "Mittags";<br />
break;<br />
case 15:<br />
case 16:<br />
case 17:<br />
echo "Nachmittags";<br />
break;<br />
default:<br />
echo "Unbekannte Stunde";<br />
Verzweigungen<br />
Beachten Sie hier, dass die Aufeinanderfolge von case nicht bedeutet, dass die<br />
Codeausführung am nächsten Zweig fortgesetzt wird, wie es bei fehlendem break<br />
der Fall ist. Betrachten Sie die folgende Sequenz:<br />
case 15:<br />
case 16:<br />
case 17:<br />
Mit if könnte man dafür folgendes schreiben:<br />
if ($hour == 15 or $hour == 16 or $hour == 17)<br />
Der gesamte Block verhält sich darüber hinaus wie ein allein stehendes case.<br />
139
<strong>14</strong>0<br />
Programmieren<br />
Komplexe Ausdrücke mit switch auswerten<br />
Mit switch lassen sich in PHP auch komplexe Ausdrücke auswerten. Das unterscheidet<br />
PHP von einigen anderen Sprachen, die nur strenge Vergleiche konstanter<br />
Werte zulassen. Mit der Möglichkeit, als Vergleichswerte im case-Zweig auch<br />
Ausdrücke einsetzen zu können, kann man switch deutlich flexibler verwenden.<br />
Hier zeigt sich einer der wenigen Vorteile eines <strong>In</strong>terpreters gegenüber kompilierenden<br />
Sprachen.<br />
<strong>In</strong>tern vergleicht PHP freilich auch hier nur auf der Basis eines Booleschen Ausdrucks.<br />
Das Resultat eines Zweiges muss mit einem entsprechenden Gegenstück<br />
verknüpft werden. Dazu setzt man einfach im Kopf eine der Konstanten TRUE oder<br />
FALSE ein und in den case-Zweigen die Ausdrücke:<br />
Listing 4.5: switchcaseplus.php – Variable Nutzung Boolescher Ausdrücke<br />
$hour = 13;<br />
echo "Tagesabschnitt für Stunde $hour: ";<br />
switch (TRUE)<br />
{<br />
case $hour >= 0 and $hour < 5 or $hour > 22:<br />
echo "Nachts";<br />
break;<br />
case $hour >= 5 and $hour < 8:<br />
echo "Morgens";<br />
break;<br />
case $hour >= 8 and $hour < 11:<br />
echo "Vormittags";<br />
break;<br />
case $hour >= 11 and $hour < 13:<br />
echo "Mittags";<br />
break;<br />
case $hour >= 13 and $hour < 17:<br />
echo "Nachmittags";<br />
break;<br />
case $hour >= 17 and $hour < 22:<br />
echo "Abends";<br />
break;<br />
}<br />
PHP löst hier intern zuerst die Booleschen Ausdrücke auf und ermittelt den Wert<br />
TRUE oder FALSE. Dann vergleicht er TRUE==TRUE oder TRUE==FALSE und springt in
Schleifen erstellen<br />
den ersten passenden Zweig. Man muss hier aufpassen, dass immer nur ein Zweig<br />
gefunden wird, auch wenn ein danach liegender ebenso zutreffend wäre. switch<br />
kann immer nur einen Zweig ausführen. Das break wegzulassen ist keine Lösung,<br />
denn dann fällt der Programmablauf durch alle folgenden Zweige bis zum nächsten<br />
break durch. Die Auswertung eines weiteren case-Zweiges erfolgt nicht.<br />
4.2 Schleifen erstellen<br />
PHP verfügt über die in allen gängigen Programmiersprachen verfügbaren Schleifenanweisungen:<br />
do<br />
while<br />
for<br />
do und while sind eng miteinander verwandt und bilden universelle Schleifen mit<br />
einer Abbruchbedingung (do) bzw. einer Eintrittsbedingung (while). for ist eine<br />
Zählschleife, die numerische Werte abzählen kann.<br />
Gemeinsamkeiten aller Schleifenanweisungen<br />
Alle Schleifen weisen einige Gemeinsamkeiten auf, die sehr praktisch sind. Standardmäßig<br />
werden Schleifen solange durchlaufen, bis die Auswertung der Prüfbedingung<br />
einen erneuten Durchlauf verhindert. Dies kann bei falscher<br />
Programmierung zu Endlosschleifen führen. Endlosschleifen sind meist unerwünscht.<br />
Mit PHP sind solche Programmierfehler zwar lästig, aber nicht wirklich<br />
kritisch. Denn standardmäßig verfügt PHP über eine Skriptlaufzeit von 30 Sekunden.<br />
Ist das Skript dann nicht beendet – egal ob es an einem Fehler hängt oder in<br />
einer Schleife feststeckt –, wird es abgebrochen.<br />
Der Schleifenablauf lässt sich aber nicht nur allein durch die Bedingung kontrollieren.<br />
Alle Schleifen verfügen über zwei spezielle Schlüsselwörter zur Steuerung,<br />
die grundsätzlich nur zusammen mit if oder (seltener) switch eingesetzt werden:<br />
break<br />
continue<br />
<strong>14</strong>1
<strong>14</strong>2<br />
Programmieren<br />
Mit Hilfe von break wird die Schleife sofort und ohne weitere Prüfung verlassen.<br />
Natürlich muss man davor eine Bedingung schreiben:<br />
if ($hour == 12) break;<br />
Andernfalls würde die Schleife immer abgebrochen werden, unabhängig von der<br />
eigentlichen Schleifenbedingung, was ziemlich sinnlos ist.<br />
Die Anweisung continue dagegen erzwingt eine sofortige Prüfung der Schleifenbedingung<br />
und springt dazu zum Schleifenkopf (bzw. bei do zum Ende). Der Code,<br />
der innerhalb der Schleife nach dem continue steht, wird nicht ausgeführt. Natürlich<br />
muss man auch hier eine Bedingung schreiben:<br />
if ($hour < 12) continue;<br />
Damit werden alle Schleifenkonstrukte sehr flexibel. Zusammen mit der recht einfachen<br />
Definition ergeben sich starke und universelle Anweisungen.<br />
Die Zählschleife for<br />
for bildet eine Zählschleife – im weitesten Sinne. Aufbau und Bedingungen sind<br />
sehr flexibel einsetzbar. <strong>In</strong>sgesamt besteht der Kopf der for-Schleife aus drei,<br />
jeweils optional verwendbaren, Abschnitten:<br />
1. <strong>In</strong>itialisierung<br />
2. Laufbedingung<br />
3. Iterationsanweisung<br />
Alle drei Abschnitte ermöglichen die Angabe mehrere Ausdrücke. Wie der Name<br />
»Laufbedingung« bereits andeutet, muss die Bedingung Wahr ergeben, also beim<br />
Eintritt in die Schleife TRUE ergeben. Ist das nicht mehr der Fall, wird die Schleife<br />
beendet. Nach jedem Schleifendurchlauf wird die Iterationsanweisung ausgeführt,<br />
dann erfolgt die Prüfung der Laufbedingung. Die <strong>In</strong>itialisierung wird nur beim<br />
Schleifeneintritt ausgeführt.<br />
Die grundsätzliche Syntax sieht folgendermaßen aus:<br />
for (<strong>In</strong>itialisierung; Laufbedingung; Iterationsanweisung)<br />
{<br />
// Code in der Schleife<br />
}
Schleifen erstellen<br />
Um nun mehrere Ausdrücke in einen Abschnitt zu packen, werden diese durch<br />
Kommata getrennt:<br />
for ($i = 0, $k = 2; $k < 5; $i++, $k++)<br />
Beachten Sie hier, dass es sich wirklich um Ausdrücke handeln muss,<br />
die etwas zurückgeben (was genau, ist erstmal egal). Die Anweisung echo<br />
?Hallo for? gibt nichts zurück, sondern produziert eine Ausgabe. Sie<br />
würde deshalb nicht im Kopf der for-Schleife funktionieren.<br />
Solche Konstruktionen sind aber eigentlich sowieso keine gute Idee, weil sie die<br />
Lesbarkeit erschweren und schnell Programmierfehler hervorrufen. Eigentlich ist<br />
for nämlich eine Zählschleife und nicht dazu gedacht, Ausgaben oder Code des<br />
Schleifenkörpers im Kopf aufzunehmen.<br />
Typische Einsatzfälle<br />
Wie bereits erwähnt, ist der Einsatz mit zählbaren Werten typisch für die for-<br />
Schleife. Im einfachsten (und häufigsten) Fall sieht das dann folgendermaßen aus:<br />
Listing 4.6: forloop.php – Die einfachste for-Schleife<br />
$rows = 7;<br />
echo '';<br />
for ($i = 0; $i < $rows; $i++)<br />
{<br />
echo "$i ...";<br />
}<br />
echo '';<br />
Es ist dabei sehr typisch, als Startwert 0 zu wählen, und dann die Anzahl der<br />
Durchläufe mit der Formel $laufwert < ENDWERT zu bestimmen. Die gezeigte<br />
Schleife produziert wie erwartet sieben Durchläufe und zählt dabei von null bis<br />
sechs. Der naive Ansatz wäre sicher, hier von eins bis sieben zu zählen, zumal viele<br />
Ausgaben dies auch erwarten. <strong>In</strong>tern verwendet PHP jedoch – wie viele andere<br />
Sprachen auch – so genannte null-basierte <strong>In</strong>dizes. Egal ob Arrays, Datenbankabfragen<br />
oder andere zählbare Quellen verwendet werden, fast immer hat der erste<br />
Wert der Reihe den <strong>In</strong>dex 0. Wenn Sie es sich von vornherein angewöhnen, Zählschleifen<br />
null-basiert zu bauen, wird vieles leichter.<br />
<strong>14</strong>3
<strong>14</strong>4<br />
Programmieren<br />
Abbildung 4.1:<br />
Tabelle, mit einer einfachen Zählschleife erzeugt<br />
Die Trennung von Logik und Benutzerschnittstelle gehört zu den elementaren<br />
Richtlinien der praktischen <strong>In</strong>formationsverarbeitung. PHP bietet hier nur eine<br />
rudimentäre Unterstützung, aber auch im Kleinen lohnt es, das Programmdesign<br />
nach diesem Prinzip auszurichten. Wenn Sie im gezeigten Beispiel tatsächlich<br />
eine Ausgabe der Zählwerte wünschen und diese Ausgabe mit 1 beginnen soll,<br />
ändern sie nicht den Schleifenkopf. Fügen Sie stattdessen eine Berechnung allein<br />
für die Ausgabe ein:<br />
echo "" . ($i + 1) . " ...";<br />
Es wäre übrigens ein echter Programmierfehler, folgendes zu schreiben, wenn die<br />
Iterationsanweisung im Schleifenkopf weiter bestehen bleibt:<br />
echo "" . ($i++) . " ...";<br />
Diese Version würde den Wert für die Ausgabe zwar um eins erhöhen, zugleich<br />
aber auch den <strong>In</strong>halt der Laufvariablen verändern. Da diese aber ohnehin bei<br />
jedem Durchgang erhöht wird, würde nun jeder Durchlauf deren Wert um zwei<br />
erhöhen. Da sich die Abbruchbedingung nicht geändert hat, wird nur die Hälfte<br />
der Zeilen ausgegeben.<br />
Aus der Beobachtung des Effekts sollten Sie nicht eine tolle Möglichkeit der nachträglichen<br />
Manipulation der Schleifenvariable erkennen. Dies geht zwar, ist aber<br />
einfach nur schlechter Stil. Die Veränderung der Schleifenvariablen ist ein so<br />
genannter Seiteneffekt, der schwer verständlich ist, wenn jemand anderes den<br />
Code liest1 . Seiteneffekte sind schlecht. Vermeiden Sie es unbedingt, die Schleifenvariable<br />
während des Durchlaufs zu manipulieren.<br />
Prüfen Sie sorgfältig, ob die Iterationsanweisung wirklich dazu führt,<br />
dass die Laufbedingung ungültig wird. Andernfalls haben Sie eine Endlosschleife.<br />
Fügen Sie gegebenenfalls ein break mit einer weiteren<br />
Bedingungen ein, um einen Notausstieg zu haben.<br />
1 Jemand anderes sind auch Sie selbst, sechs Monate später!
Schleifen erstellen<br />
Wenn Sie nun Ausgabewerte und Zählwerte gleichzeitig benötigen, bietet es sich<br />
an, den Kopf zu erweitern. Sie vermeiden so strikt die Manipulation der Schleifenvariablen<br />
und profitieren dennoch von der kompakten Schreibweise der for-<br />
Schleife. Das folgende Beispiel ist die vermutlich einfachste und primitivste<br />
Demonstration dieser Technik, die jemals gefunden wurde:<br />
Listing 4.7: forloop2.php – Trennung von Zählung (Logik) und Ausgabe (Benutzerschnittstelle)<br />
$rows = 7;<br />
echo '';<br />
for ($i = 0, $j = 1; $i < $rows; $i++, $j++)<br />
{<br />
echo "$j ...";<br />
}<br />
echo '';<br />
Das Beispiel deklariert eine zweite Variable $j, die nur der Ausgabe dient. Diese<br />
lässt sich gefahrlos manipulieren, weil sie keinen Einfluss auf die Laufbedingungen<br />
hat und damit keine Seiteneffekte auslösen kann.<br />
Auch die Ausgabe ließe sich zu guter Letzt noch in den Schleifenkopf verlegen:<br />
Listing 4.8: forloop3.php – Ausgaben im Kopf einer Schleife<br />
$rows = 7;<br />
echo '';<br />
for ($i = 0, $j = 1;<br />
$i < $rows;<br />
$i++, print "$j ...", $j++)<br />
{<br />
}<br />
echo '';<br />
Grundsätzlich sollten sich solche Konstrukte auf seltene, einfache Fälle beschränken.<br />
Sie sind schwer lesbar und Erweiterungen verlangen grobe Änderungen, was<br />
eine zusätzliche Fehlerquelle bedeutet.<br />
Beachten Sie außerdem, dass der Befehl echo hier nicht einsetzbar ist. for erwartet<br />
Ausdrücke und echo hat keinen Rückgabewert, was ein Merkmal eines Ausdrucks<br />
ist. Deshalb wird hier print eingesetzt.<br />
<strong>14</strong>5
<strong>14</strong>6<br />
Programmieren<br />
Noch ein anderes Problem macht den Einsatz der letzten Variante weniger empfehlenswert.<br />
Die Reihenfolge der Parameter im Kopf im Abschnitt »Iterationsanweisung«<br />
ist wichtig. Betrachten Sie beispielsweise Folgendes:<br />
$i++, $j++, print "$j ..."<br />
Diese Schleife würde nun mit der Ausgabe des Wertes 2 beginnen, denn der Startwert<br />
für $j ist 1 und die erste Erhöhung $j++ findet vor der Ausgabe statt! Das ist<br />
nicht unbedingt sofort erkennbar und deshalb eine weitere, unnötige Fehlerquelle.<br />
Geschachtelte Schleifen<br />
Wie alle Schleifen lässt sich auch for verschachteln. Hier sollte beachtet werden,<br />
dass sich die Laufvariablen auf keinen Fall gegenseitig beeinflussen. Außerdem<br />
multipliziert sich die Anzahl der Durchläufe zur Anzahl der Gesamtdurchläufe.<br />
Hat die äußere Schleife acht Durchläufe und die innere ebenfalls, sind es insgesamt<br />
64. Das klingt trivial, aber bei mehreren Verschachtelungsebenen erreicht<br />
man schnell Skriptlaufzeiten, die deutlich spürbar sind. Zeitkritische Abfragen<br />
haben in Schleifen sowieso nichts verloren, aber in verschachtelten Schleifen können<br />
auch triviale Berechnungen problematisch werden. Denken Sie beispielsweise<br />
an den Aufbau eines Farbrades für RGB-Farben. Statt ein GIF vorzubereiten<br />
möchten Sie es in PHP »life« erstellen. Das wären dann 256 x 256 x 256 Farben;<br />
leicht mit drei Schleifen erzeugbar. Diese drei Schleifen führen zu 16.777.216<br />
Ausgaben. Abgesehen davon, dass die Darstellung wenig hilfreich ist, führt allein<br />
eine Ausgabezeit von 5 ms pro Durchlauf zu einer Skriptlaufzeit von fast 1 Tag!<br />
Speziell für die Problematik Farbrad benötigt man aber so viele Farben nicht. Kein<br />
System kann alle Farben wirklich darstellen. Für das Web benutzt man 216 so<br />
genannte websichere Farben. Diese sind so definiert, dass als hexadezimale Farbwerte<br />
nur die Zahlen 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF zum Einsatz kommen.<br />
Folgende Lösung nutzt auch verschachtelte Schleifen, führt aber einige Berechnungen<br />
außerhalb durch, um so mit wenigen Durchläufen zum Ergebnis zu kommen:<br />
Listing 4.9: forllopnested.php – Erzeugen eines Farbwählers mit websicheren Farben<br />
$r = 0;<br />
$g = 0;<br />
$b = 0;<br />
// Farben ermitteln
Schleifen erstellen<br />
$WebSafe = array (0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF);<br />
echo '';<br />
for($y = 0; $y < 12; $y++)<br />
{<br />
$b = $WebSafe[$y % 6];<br />
echo '';<br />
for ($x = 0; $x < 18; $x++)<br />
{<br />
$g = $WebSafe[$x % 6];<br />
$r = ($y < 6) ? $WebSafe[$x / 6] : $WebSafe[($x / 6) + 3];<br />
printf('',<br />
$r, $g, $b);<br />
}<br />
echo "";<br />
}<br />
echo '';<br />
Das Skript nutzt ein Array (Datenfeld, siehe dazu Kapitel 5, Arrays, ab Seite 181),<br />
das die möglichen Basiswerte enthält. Dann wird die Farbfläche als Feld mit<br />
18 mal 12 Rechtecken definiert. Die äußere Schleife zählt die Reihen (12), die<br />
innere die Spalten (18). Dies erzeugt insgesamt 216 Durchläufe. Aus dem Wertefeld<br />
muss dann nur noch der richtige Wert entnommen werden, was durch einfache<br />
Modulus-Berechnungen erfolgt und den gewünschten Schachteleffekt<br />
erzeugt, der ähnliche Farben nebeneinander platziert.<br />
Abbildung 4.2:<br />
forloopnested.php: Farbwähler mit sortierten Farben<br />
Die Ausgabe der Farben erfolgt durch Einfärben des Hintergrundes der erzeugten<br />
Tabellenzellen. Die passenden RGB-Werte erzeugt die printf-Funktion, die mit<br />
der Formatanweisung %2X aus einer Dezimalzahl den passenden zweistelligen<br />
Hexwert erzeugt.<br />
<strong>14</strong>7
<strong>14</strong>8<br />
Programmieren<br />
Die Universalschleifen do/while und while<br />
Die Anweisungen do/while und while werden so lange durchlaufen, wie die<br />
Bedingung Wahr (TRUE) ist. Als Bedingung wird ein beliebiger Boolescher Ausdruck<br />
eingesetzt. Die grundsätzliche Syntax sieht folgendermaßen aus:<br />
while (Bedingung)<br />
{<br />
// Code, der immer wieder ausgeführt wird<br />
}<br />
do funktioniert ebenso, nur erfolgt die Prüfung erst am Ende:<br />
do<br />
{<br />
// Code, der immer wieder ausgeführt wird<br />
} while (Bedingung)<br />
do wird immer dann eingesetzt, wenn sichergestellt werden muss, dass der <strong>In</strong>halt<br />
mindestens einmal durchlaufen wird. Das ist der Fall, weil die Prüfung – wie der<br />
Schleifenkörper suggeriert – erst am Ende erfolgt. Dies ist freilich nicht mehr der<br />
Fall, wenn vorher ein break eingesetzt wird.<br />
Mit dem Training durch die for-Schleifen wird der Einsatz von while sehr einfach.<br />
Das folgende Beispiel zeigt eine fortgeschrittene Version des Farbwählers,<br />
diesmal nicht mit blockweiser, sondern fortlaufender Farbanordnung:<br />
Listing 4.10: while.php – Verschachtelte while-Schleifen zum Aufbau eines Farbwählers<br />
$r = 0;<br />
$g = 0;<br />
$b = 0;<br />
$ContR = array (0xCC, 0x66, 0x00, 0xFF, 0x99, 0x33);<br />
$ContG = array (0xFF, 0xCC, 0x99, 0x66, 0x33, 0x00);<br />
$ContG = array_merge($ContG, array_reverse($ContG));<br />
$ContB = array_merge($ContG, array_reverse($ContG));<br />
echo '';<br />
$y = 0;<br />
while($y < 12)<br />
{<br />
$g = $ContG[$y++];<br />
echo '';<br />
$x = 0;<br />
while($x < 18)
{<br />
$r = $ContR[($x / 6) + (($y < 6) ? 0 : 3)];<br />
$b = $ContB[$x++];<br />
printf('',<br />
$r, $g, $b);<br />
}<br />
echo "";<br />
}<br />
echo '';<br />
Schleifen erstellen<br />
Die Funktionen array, array_merge und array_reverse werden im Kapitel 3<br />
Arrays ab Seite 73 vorgestellt. Sie bauen wieder passende Datenfelder aus den<br />
Farbwerten 0x00, 0x33, 0x66, 0x99, 0xCC und 0xFF auf. Die beiden while-Schleifen<br />
durchlaufen dann die Werte 0 – 11 (12 Reihen) bzw. 0 – 17 (18 Spalten) und<br />
erzeugen so das Muster. Die Ausgabe findet mit einer printf-Funktion statt, die<br />
die Darstellung der von HTML benötigten RGB-Werte übernimmt. Die Erhöhung<br />
der Schleifenwerte findet in der Abfrage der Arrays statt:<br />
$g = $ContG[$y++];<br />
Das ist sehr kompakt und einfach. Wenn Sie es besser lesbar mögen, kann man<br />
auch folgendes schreiben:<br />
$g = $ContG[$y];<br />
$y++ ;<br />
Falls Sie $y anders als zur Zählung verwenden, müssen Sie die Erhöhung (oder<br />
jede andere Änderung) ohnehin so platzieren, dass die Änderung erst am Ende der<br />
Schleife erfolgt.<br />
Abbildung 4.3:<br />
Verschachtelte Schleifen wurden zum Erzeugen<br />
dieser Farbfläche benutzt<br />
<strong>14</strong>9
150<br />
Programmieren<br />
4.3 Benutzerdefinierte Funktionen<br />
Mit Hilfe des Schlüsselwortes function lassen sich eigene Funktionen definieren.<br />
Da man einer Funktion Parameter übergeben kann, gibt es die Möglichkeit, erforderliche<br />
und optionale Parameter zu deklarieren. Da Parameter auch wegfallen<br />
oder in beliebiger Menge eingesetzt werden können, gibt es noch eine Reihe von<br />
Hilfsfunktionen in PHP, die dem Umgang mit Parametern dienen. Funktionsdefinitionen,<br />
wie sie hier vorgestellt werden, finden auch Eingang in die Klassendefinition.<br />
Dort heißen sie dann Methoden. Die hier vorgestellten Techniken sind also<br />
recht allgemeingültig.<br />
Funktionen definieren<br />
Funktionen kennen Sie bereits – praktisch besteht PHP aus Hunderten Funktionen.<br />
Diesen eingebauten Funktionen kann man leicht eigene hinzufügen und<br />
im Skript verwenden. Das ist praktisch, um größere Skripte modular aufzubauen,<br />
Code leichter wieder verwenden zu können und ein und denselben Ablauf mit<br />
verschiedenen Daten auszuführen.<br />
Die Funktionsdefinition<br />
Funktionen werden durch das Schlüsselwort function definiert. Die Definition<br />
umfasst einen Funktionskopf, der Name und Parameter definiert. Diese so<br />
genannte Signatur der Funktion bestimmt, wie die Funktion später aufgerufen<br />
wird. Im einfachsten Fall wird nichts übergeben, wie das folgende Beispiel zeigt:<br />
Listing 4.11: functionsimple.php – Eine einfache Funktionsdefinition<br />
function PrintTableRow()<br />
{<br />
echo 'Reihe';<br />
}<br />
echo '';<br />
for ($i = 0; $i < 3; $i++)<br />
{<br />
PrintTableRow();<br />
}<br />
echo '';
Benutzerdefinierte Funktionen<br />
Der Funktionskopf ist hier in seiner einfachsten Form zu sehen:<br />
function PrintTableRow()<br />
Da keine Parameter verwendet werden, schreibt man nur zwei runden Klammern<br />
zum Abschluss. Schlüsselwort und Name sind ebenso obligatorisch. Der Name<br />
sollte so gewählt werden, dass ein Verb am Anfang steht, denn Funktionen führen<br />
Aktionen aus, die man üblicherweise mit Verben bezeichnet.<br />
Wie das Beispiel zeigt, eignen sich Funktionen zum Einsatz an allen Stellen im<br />
Skript, also auch in Schleifen.<br />
Die Reihenfolge der Definition spielt keine Rolle. PHP5 beherrscht die so<br />
genannte späte Bindung, bei der die Aufrufadressen der Funktionen erst am Ende<br />
der Seitenverarbeitung festgelegt werden. Deshalb funktioniert auch Folgendes:<br />
echo '';<br />
for ($i = 0; $i < 3; $i++)<br />
{<br />
PrintTableRow();<br />
}<br />
echo '';<br />
function PrintTableRow()<br />
{<br />
echo 'Reihe';<br />
}<br />
Wenn in der Funktion Ausgaben erzeugt werden, erscheinen diese im Rahmen<br />
der Ausführung auf der Seite an der Stelle, wo die Ausgabe beim Aufruf der Funktion<br />
gerade war. Das Beispiel nutzt diesen Effekt bereits.<br />
Rückgabewerte<br />
Abbildung 4.4:<br />
Ausgabe einer Tabelle mit einer Funktion<br />
Besser ist es, die Ausgabe von Werten nicht der Funktion zu überlassen. Der Aufrufer<br />
sollte (wieder im Sinne eines besseren Programmierstils) letztlich die Kontrolle<br />
über die Ausgaben behalten. Wenn Funktionen Werte erzeugen, können Sie<br />
diese mit Hilfe des Schlüsselwortes return zurückgeben.<br />
151
152<br />
Programmieren<br />
Eine Unterscheidung zwischen Prozeduren (kein Rückgabewert) und<br />
Funktionen (mit Rückgabewert), wie in BASIC (Visual Basic, VBA,<br />
VBScript), gibt es in PHP nicht. Es ist völlig dem Entwickler überlassen,<br />
ohne weitere Definition mit oder ohne return zu arbeiten. Lediglich der<br />
Aufruf muss entsprechend angepasst werden.<br />
Das letzte Beispiel sieht etwas besser aus, wenn es folgendermaßen definiert wird:<br />
Listing 4.12: functionreturn.php – Eine Funktion mit Datenrückgabe<br />
function PrintTableRow()<br />
{<br />
return 'Reihe';<br />
}<br />
echo '';<br />
for ($i = 0; $i < 3; $i++)<br />
{<br />
echo PrintTableRow();<br />
}<br />
echo '';<br />
Es ist nun dem Aufrufer überlassen, mit echo die sofortige Ausgabe zu veranlassen<br />
oder die erzeugte Zeichenkette vorher noch zu verarbeiten. Letzteres wäre möglich,<br />
wenn der Wert direkt einer Variablen übergeben wird:<br />
$row = PrintTableRow();<br />
Wenn nun die Anzahl der Zellen in der erzeugten Reihe variabel sein soll, müsste<br />
man für jede Variante eine neue Funktion definieren. Besser ist es, hier mit Parametern<br />
zu arbeiten.<br />
Eine Funktion kann immer nur einen Wert zurückgeben. Auch wenn Arrays noch<br />
nicht vorgestellt wurden, sei hier ein kleiner Ausflug präsentiert. Wenn Sie<br />
mehrere Werte zurückgeben müssen, erstellen Sie ein Array:<br />
function RetArray()<br />
{<br />
$result = array(45, 26, <strong>14</strong>);<br />
return $result;<br />
}
Benutzerdefinierte Funktionen<br />
Beim Aufruf weisen Sie die Rückgabewerte mit Hilfe der Funktion list wieder<br />
einzelnen Variablen zu:<br />
list($var1, $var2, $var3) = RetArray();<br />
$var1 enthält nun 45, $var2 den Wert 26 usw.<br />
Parameter der Funktionen<br />
Parameter sind Werte, die der Aufrufer an die Funktion übergibt, um deren Verhalten<br />
zu steuern. PHP5 kann pro Funktion mit beliebig vielen Parametern umgehen.<br />
Die Anzahl ist nicht nur insgesamt frei gestellt, sondern kann abweichend von<br />
der Definition schwanken. Dies ist tückisch (wenn auch recht bequem), denn der<br />
Aufruf mit einer zu großen Anzahl Parameter führt nicht zwingend zu einer Fehlermeldung.<br />
Da PHP typlos arbeitet und der Datentyp der Parameter ignoriert<br />
wird, ist die Fehlerquote hier aber ohnehin recht hoch. Der Umgang mit Parametern<br />
wird deshalb etwas detaillierter betrachtet.<br />
Einfache Parameter<br />
Einfache Parameter werden deklariert, indem man diese als Variablen im Kopf der<br />
Funktion aufschreibt:<br />
function PrintTableRow($cells)<br />
Diese Funktion hat einen Parameter. <strong>In</strong>nerhalb des Funktionskörpers kann über<br />
die Variable $cells darauf zugegriffen werden. Mehrere Parameter werden durch<br />
Kommata getrennt, so wie bei den eingebauten Funktionen:<br />
function PrintTableRow($bgcolor, $cells)<br />
Das folgende Beispiel erzeugt eine Reihe mit beliebig vielen Zellen, prüft die Verwendung<br />
der Parameter und akzeptiert außerdem noch eine Farbe für den Reihenhintergrund:<br />
Listing 4.13: functionparam1.php – Funktion mit Parametern aufrufen<br />
function PrintTableRow($bgcolor, $cells)<br />
{<br />
$cellstring = '';<br />
for ($i = 0; $i < $cells; $i++)<br />
{<br />
153
154<br />
Programmieren<br />
$cellstring .= " $i ";<br />
}<br />
$row =
Benutzerdefinierte Funktionen<br />
return $row;<br />
}<br />
echo '';<br />
echo PrintTableRow('gray');<br />
echo PrintTableRow('red');<br />
echo PrintTableRow('gold');<br />
echo '';<br />
Wenn der zweite Parameter fehlt, wird hier automatisch die Zahl 6 eingesetzt.<br />
Optionale Parameter dürfen nur am Ende der Parameterauflistung stehen. Das ist<br />
logisch, denn PHP kann die Position nur durch Abzählen feststellen. Da PHP<br />
keine Datentypen erkennt, ist eine Auswahl von Parametern schwer zu definieren.<br />
Folgende Definition ist noch zulässig:<br />
function PrintTableRow($bgcolor = 'gray', $cells = 6)<br />
Beim Aufruf sind aber nicht alle Varianten möglich. Ohne Parameter ist die Sache<br />
eindeutig:<br />
PrintTableRow()<br />
Mit beiden Parameter funktioniert es natürlich auch. Aber bei der Angabe nur<br />
eines Parameters versagt der Aufruf. Das folgende Beispiel funktioniert nicht wie<br />
erwartet:<br />
PrintTableRow(4)<br />
PHP5 kann nicht erkennen, dass hier die Anzahl der Zellen gemeint war und<br />
offensichtlich die Standardhintergrundfarbe zum Einsatz kommen soll. Auch mit<br />
den passenden Datentypen ist dies nicht möglich, weil sich die Parametertypen ja<br />
nicht zwingend unterscheiden müssen.<br />
Unzulässig ist auch folgende Definition:<br />
function PrintTableRow($bgcolor = 'gray', $cells)<br />
PHP5 kann hier beim Aufruf nicht erkennen, welcher Parameter weggelassen werden<br />
soll.<br />
Abschließend sei noch bemerkt, dass der Standardwert eine Konstante oder ein<br />
Literal sein muss. Variablen und Ausdrücke sind nicht erlaubt. Deshalb ist die folgende<br />
Variante unzulässig:<br />
function PrintTableRow($bgcolor = $gray, $cells = 3 + 3)<br />
155
156<br />
Programmieren<br />
Beliebige Parameteranzahl<br />
Die Lösung des Dilemmas liegt in der Akzeptanz einer beliebigen Parameteranzahl.<br />
Dazu wird der Kopf der Funktion einfach leer gelassen – wie in der ersten<br />
Variante. Der Aufrufer kann dennoch beliebig viele Parameter angeben. Daneben<br />
ist die nachfolgend vorgestellte Angabe von vielen Parametern auch besser, als<br />
diese in endlosen Schlangen aufzuschreiben. Man findet in der professionellen<br />
Programmierung selten Funktionen mit sechs oder mehr Parametern. Die<br />
dadurch erreichte Steuerbarkeit einer Funktion ist derart umfassend, dass wohl<br />
meist ein Designfehler vorliegt. Komplexere Dinge erledigt man mit Klassen<br />
(siehe Tag 6).<br />
Für den Umgang mit variabler Parameteranzahl stellt PHP5 einige spezielle Funktionen<br />
zur Verfügung:<br />
func_num_args<br />
Diese Funktion, aufgerufen innerhalb einer benutzerdefinierten Funktion,<br />
ermittelt die tatsächliche Anzahl der Parameter, die übergeben wurden.<br />
func_get_args<br />
Hiermit wird ein Array der Parameter erstellt. Der erste Parameter hat den<br />
<strong>In</strong>dex 0. So erhält man Zugriff auf alle Parameter.<br />
func_get_arc<br />
Alternativ zum Array kann jeder Parameter auch direkt über eine Funktion<br />
adressiert werden. Als Argument wird der null-basierte <strong>In</strong>dex verwendet.<br />
Es ist empfehlenswert, Funktionen immer so zu schreiben, dass zuerst die eingehenden<br />
Parameter geprüft werden. Es gehört zu den grundlegenden Regeln der<br />
Programmierung, dass Eingabedaten nie vertraut werden darf. Würden alle Programme<br />
so erstellt werden, gäbe es kaum noch Programmierfehler, denn die<br />
eigentlichen Algorithmen lassen sich meist gut testen.<br />
Listing 4.15: functionparams.php – <strong>In</strong>telligente Funktion mit variabler Parameterzahl<br />
function BuildRow()<br />
{<br />
$cells = 4;<br />
$Params = func_num_args();<br />
if ($Params == 0)<br />
{<br />
return "";<br />
}
$row = '';<br />
for ($i = 0; $i < $Params; $i++)<br />
{<br />
$row .= '' . func_get_arg($i) . '';<br />
}<br />
for ($k = $i; $k < $cells; $k++)<br />
{<br />
$row .= ' ';<br />
}<br />
return "$row";<br />
}<br />
echo '';<br />
echo BuildRow('PLZ', 'Vorwahl', 'Ort', 'Aktiv?');<br />
echo BuildRow('12683', '030', 'Berlin');<br />
echo BuildRow('89315', '089', 'München', 'X');<br />
echo '';<br />
Benutzerdefinierte Funktionen<br />
Diese Funktion prüft zuerst, ob Parameter vorliegen. Ist das nicht der Fall, wird<br />
eine leere Reihe erzeugt:<br />
<br />
Ohne diese Abfrage würde eine Reihe ohne Zelle erzeugt werden, was falsch ist:<br />
<br />
Diese Funktion reagiert also auch bei falscher Parameterzahl korrekt. Will man<br />
diesen Zustand (keine Parameter) vermeiden, so sollte man einen Laufzeitfehler<br />
erzeugen oder eine entsprechende Ausschrift.<br />
Stehen nun Parameter zur Verfügung, wird zuerst deren Anzahl ermittelt:<br />
$Params = func_num_args();<br />
Mit dieser Anzahl wird die Schleife bedient, die die Zellen erzeugt. Zu jeder Zelle<br />
wird ein Parameter als Zelleninhalt abgerufen:<br />
$row .= '' . func_get_arg($i) . '';<br />
Abbildung 4.6:<br />
Ausgabe einer einfachen Tabelle mit benutzerdefinierter<br />
Funktion<br />
157
158<br />
Programmieren<br />
HTML-Tabellen benötigen immer eine konstante Anzahl Tabellenzellen pro<br />
Reihe. Entweder man fügt für fehlende Zellen colspan-Attribute ein oder füllt die<br />
Zellen auf. Das Auffüllen geschieht hier mit einer weiteren Schleife, die auf dem<br />
letzten Wert der ersten Schleife aufbaut. Diese wird dann durchlaufen, bis die<br />
Anzahl der Zellen jeweils der maximalen Anzahl entspricht, die in der Variablen<br />
$cells definiert wurde.<br />
Der Aufruf ist nun recht flexibel, kann jedoch noch verbessert werden. So ist es<br />
möglich, feste mit optionalen Parametern zu kombinieren. Das zweite Beispiel<br />
legt einen Booleschen Parameter fest, der darüber entscheidet, ob Kopf- oder<br />
<strong>In</strong>haltszellen erzeugt werden. Ein weiterer fester Parameter bestimmt die maximale<br />
Anzahl. Die Prüfung wird so erweitert, dass bei Unterschreiten der Anzahl<br />
eine spezielle Ausgabe erzeugt wird.<br />
Listing 4.16: functionparamsplus.php – Verbesserte Funktion mit variabler Parameterzahl<br />
function BuildRow($bHead, $iCells)<br />
{<br />
$cells = $iCells;<br />
$Params = func_num_args();<br />
$sTag = $bHead ? 'th' : 'td';<br />
if (($Params - 2)
Benutzerdefinierte Funktionen<br />
Die Anzahl der Parameter, die func_num_args ermittelt, bezieht hier die beiden feste<br />
am Anfang der Liste mit ein. Deshalb muss an zwei Stellen (Prüfung und Auffüllen)<br />
im Skript die Anzahl um zwei verringert werden. Ansonsten gibt es kaum Unterschiede,<br />
abgesehen vom Aufruf, wo nun zwei Pflichtargumente erforderlich sind.<br />
Referenzen auf Funktionen und Parameter<br />
Wie in den letzten Abschnitten gezeigt, kann man mit Parametern Daten an eine<br />
Funktion übergeben und deren Wiederverwendungswert damit erhöhen. Die<br />
Rückgabe nur eines Wertes mit return verschafft zusätzliche Flexibilität. Das ist<br />
jedoch nicht immer ausreichend. Sollen von einer Funktion viele Werte geändert<br />
werden, so kann dies auch über die Parameter erfolgen. Um die dazu verwendete<br />
Technik zu verstehen, muss man sich an ein wenig Theorie wagen.<br />
Prinzip der Parameterübergabe<br />
Abbildung 4.7:<br />
Ausgabe einer Tabelle mit eigener Funktion<br />
Einfache Skriptsprachen wie PHP arbeiten meist direkt mit Variablen. Wird der<br />
Wert einer Variablen einer anderen Zugewiesen, findet tatsächlich eine Übertragung<br />
des Wertes statt. Variablen sind die Namen von Speicherbereichen im<br />
Hauptspeicher des Computers. Die folgende Zuweisung überträgt nur den Wert<br />
von einem Platz an einen anderen:<br />
$a = $b;<br />
Änderungen von $b zu einem späteren Zeitpunkt wirken sich nicht auf $a aus. Da<br />
so viele Daten durch den Speicher geschaufelt werden, ist dies nicht optimal.<br />
Moderne Sprachen wie C++ oder C# arbeiten deshalb anders. C++ bietet ein Zeigerkonzept,<br />
bei dem statt mit den Werten nur mit den Speicheradressen (so<br />
genannte Zeiger) gearbeitet wird. C# verwendet intern immer Zeiger, dem Benutzer<br />
werden diese aber nicht zugänglich gemacht und erst beim Schreiben wird der<br />
syntaktisch sichtbare Zusammenhang hergestellt und der Wert kopiert. Wegen der<br />
Verwaltung dieser Vorgänge spricht man von verwaltetem Code. C++ profitiert<br />
von Zeigern durch deutlich höhere Geschwindigkeit, durch den direkten Speicherzugriff<br />
ist es jedoch fehleranfällig. C# ist nicht ganz so schnell, aber schneller<br />
159
160<br />
Programmieren<br />
als andere Sprachen und erzeugt sehr stabilen Code. PHP zu guter Letzt<br />
beherrscht weder Zeiger noch kennt es verwalteten Code. Man kann aber mit<br />
Referenzen arbeiten, die den Zusammenhang zwischen der ursprünglichen Speicherstelle<br />
und einem neuen Namen erhalten.<br />
Übergibt man nun einer Funktion statt des Wertes eine Referenz auf eine Variablen,<br />
wirken sich Änderungen an den Parametern in der Funktion auf den Aufrufwert aus.<br />
Listing 4.17: functionreference.php – Ändern der aufrufenden Variablen<br />
function BuildRow(&$text)<br />
{<br />
$text = "$text";<br />
}<br />
echo '';<br />
$t = 'Reihe';<br />
BuildRow($t);<br />
echo $t;<br />
echo $t;<br />
echo $t;<br />
echo '';<br />
Die Referenzierung wird definiert, indem dem Parameter das &-Zeichen vorangestellt<br />
wird. Damit bestimmt die Definition, ob referenziert wird oder nicht. Leider<br />
kann auch in PHP5 das Prinzip auf den Kopf gestellt und die Referenzierung dem<br />
Aufrufer überlassen werden:<br />
BuildRow(&$t);<br />
Es ist deshalb gefährlich, Parameter zu verändern, wenn dies nicht ausdrücklich<br />
erwünscht ist. Neben der Prüfung der Parameter heißt dies in der Praxis, dass<br />
zuerst alle Werte in lokale Variablen kopiert und dann weiter verwendet werden.<br />
Zum Thema Variablen gibt es darüber hinaus noch einige weitere Besonderheiten<br />
im Zusammenhang mit Funktionen. Sie werden in den folgenden beiden<br />
Abschnitten vorgestellt.<br />
Statische Variablen<br />
Statische Variablen erlauben es, den Wert einer Variablen von Funktionsaufruf zu<br />
Funktionsaufruf zu erhalten. Das Beispiel mit dem Tabellenreihengenerator<br />
könnte so mit einer Zählfunktion erweitert werden. Als Schlüsselwort wird static<br />
eingesetzt.
Benutzerdefinierte Funktionen<br />
Listing 4.18: functionstatic.php – Verwendung statischer Variablen in Funktionen<br />
function BuildRow($bRowNumber, $bHead, $iCells)<br />
{<br />
static $rowNumber = 1;<br />
$cells = $iCells;<br />
$Params = func_num_args();<br />
$sTag = $bHead ? 'th' : 'td';<br />
if (($Params - 2)
162<br />
Programmieren<br />
PHP5 führt diese Zeile nur beim ersten Aufruf aus. Danach gilt die Variable als<br />
bekannt und die 1 wird nicht erneut zugewiesen. Damit kann der Wert bei jedem<br />
Aufruf verändert werden:<br />
$rowNumber++;<br />
Bei der Ausgabe ergibt sich daraus dann eine fortlaufende Zählung der Tabellenreihen:<br />
Die hier vorgestellte Methode mit statischen Variablen hat – trotz gleichen<br />
Namens – nichts mit den statischen Mitgliedern von Klassen zu<br />
tun. Der Name wurde bereits vor langer Zeit in PHP benutzt, in der an<br />
die mit PHP5 eingeführten objektorientierten Konzepte noch nicht zu<br />
denken war.<br />
Globale Variablen und Konstanten<br />
PHP5 kennt, abgesehen von den Schutzmechanismen für Variablen innerhalb<br />
von Klassen, nur globale Variablen. Die Sichtbarkeit beschränkt sich auf das<br />
Basisskript, innerhalb von Funktionen sind globale Variablen normalerweise nicht<br />
sichtbar. <strong>In</strong> der Regel helfen Parameter, den Zugriff auf Werte zu erlauben. Wenn<br />
nichts Grundlegendes dagegen spricht, sind Parameter der beste Weg. Außerdem<br />
verfügt PHP5 über einige so genannte »Super-Arrays«, die wichtige Werte aus Formularen<br />
oder Cookies enthalten und die auch in Funktionen abgerufen werden<br />
können.<br />
Globale Variablen in Funktionen nutzen<br />
Abbildung 4.8:<br />
Tabelle, gebaut mit einer zählende Spalte mit statischen<br />
Funktionsvariablen<br />
Dennoch besteht manchmal der Wunsch, Variablen aus der obersten Skriptebene<br />
sichtbar zu machen, ohne dafür Parameter einzusetzen. Um das zu erledigen, können<br />
Sie sich den Zugriff mit dem Schlüsselwort global verschaffen. Dies ist<br />
zugleich auch ein Vollzugriff auf die Variable, das heißt, Änderungen innerhalb<br />
der Funktion wirken sich direkt auf das Original aus.
Benutzerdefinierte Funktionen<br />
Listing 4.19: functionglobal.php – Globale Variablen in Funktionen nutzen<br />
$cells = 6;<br />
function PrintTableRow($bgcolor)<br />
{<br />
global $cells;<br />
$cellstring = '';<br />
for ($i = 0; $i < $cells; $i++)<br />
{<br />
$cellstring .= " $i ";<br />
}<br />
$row =
164<br />
Programmieren<br />
$row =
Benutzerdefinierte Funktionen<br />
Da Stapel eine begrenzte Kapazität haben, muss man das Programm so gestalten,<br />
dass nicht endlos Aufrufe möglich sind, auch wenn die aktuellen Daten dies verlangen.<br />
<strong>In</strong> PHP 4 war die Stapelgröße strikt auf 254 beschränkt. PHP5 kann offensichtlich<br />
dynamisch Speicher anfordern und verkraftet ca. 6.000 rekursive Aufrufe.<br />
Eine genaue Definition gibt es nicht, die Anzahl hängt vom Speicherverbrauch<br />
des Skripts ab. Allerdings muss man beachten, dass Hunderte Funktionsaufrufe zu<br />
erheblichen Laufzeiten führen und die genannte Größe mehr als ausreichend ist.<br />
Sollten Sie mehr rekursive Aufrufe benötigen, liegt vermutlich ein Designfehler<br />
im Programm vor.<br />
Eine gute Demonstrationsmöglichkeit bieten immer wieder mathematische Funktionen.<br />
Die folgende Funktion ist sicher jedem vertraut:<br />
f(x) = xy PHP verfügt dafür über eine passende Funktion. Dennoch könnte man dies auch<br />
rekursiv lösen, indem der folgende Algorithmus angewendet wird:<br />
f(x) = x * x (y-1)<br />
Letzteres wird solange ausgeführt, bis y = 1 und damit x (y-1) = 1 ist, denn mit x × 1 =<br />
x ist der Weg beendet.<br />
Listing 4.21: functionrekursion.php – Potenzrechnung mit rekursiven Aufrufen<br />
function Power($base, $exp)<br />
{<br />
if ($exp > 0)<br />
{<br />
return $base * power($base, $exp - 1);<br />
}<br />
return 1;<br />
}<br />
echo Power(7, 2);<br />
Das Beispiel gibt mit den Testwerten 7 und 2 natürlich 49 aus (72 ). <strong>In</strong>teressanter ist<br />
der Ablauf der Funktion. Das schrittweise Verfolgen kann sehr hilfreich sein:<br />
1. Der erste Aufruf füllt die Parameter mit den Werten 7 und 2.<br />
2. Nach der Prüfung 2 > 0 (Wahr) erfolgt die erste Berechnung:<br />
7 * (Nächster Aufruf mit 7 und 1)<br />
165
166<br />
Programmieren<br />
3. Prüfung, danach Selbstaufruf mit den neuen Parametern:<br />
7 * (Nächster Aufruf mit 7 und 0)<br />
4. Die Prüfung ist nun nicht mehr Wahr, der Rückgabewert wird mit 1 festgelegt.<br />
5. Der zweite rekursive Aufruf wird ausgeführt, der Rückgabewert ist 7:<br />
7 * 1<br />
6. Der erste rekursive Aufruf wird ausgeführt, der Rückgabewert ist 49:<br />
7 * 7<br />
7. Es sind keine Stapelwerte mehr da und der letzte Rückgabewert geht ans<br />
Hauptprogramm: 49.<br />
Die Rücksprungprüfung im Beispiel wird übrigens durch die if-Anweisung erledigt.<br />
Eine solche Prüfung ist unbedingt erforderlich. Andere rekursive Lösungen<br />
verwenden auch for oder foreach, die irgendwann keine definierten Startwerte<br />
mehr erhalten und deshalb den im Schleifenkörper liegenden rekursiven Aufruf<br />
nicht mehr ausführen.<br />
Variable Funktionen<br />
Neben dem normalen Funktionsaufruf kennt PHP auch dynamische Funktionen,<br />
ähnlichen den dynamischen Variablen. Dabei wird der Name einer Funktion<br />
einer Variablen zugewiesen und diese dann mit der Funktionssyntax aufgerufen.<br />
Der Vorteil dabei: Der Name der Funktion wird wie eine Zeichenkette behandelt<br />
werden.<br />
Das folgende Skript zeigt mehrere Versionen für Definition und Aufruf:<br />
Listing 4.22: functiondynamic.php – Dynamische Funktionen sind sehr flexibel<br />
function MakeB($text)<br />
{<br />
return "$text";<br />
}<br />
function MakeI($text)<br />
{<br />
return "$text";<br />
}<br />
function MakeU($text)
Benutzerdefinierte Funktionen<br />
{<br />
return "$text";<br />
}<br />
$tag = 'I';<br />
$base = 'Make';<br />
$func = $base.$tag;<br />
echo 'Kursiv: ' . $func('Test') . '';<br />
$func = $base.'U';<br />
echo 'Unterstrichen: ' . $func('Test') . '';<br />
$func = "{$base}B";<br />
echo "Fett: {$func('Test')}";<br />
Im Beispiel werden drei Funktionen definiert, die sich nur durch ihr Suffix unterscheiden.<br />
Sie bauen einen als Parameter übergebenen Text jeweils in ein HTML-<br />
Tag ein. Der Funktionsname wird nun dynamisch zusammengesetzt:<br />
$func = $base.$tag;<br />
Der Aufruf erfolgt dann unter Zuhilfenahme der Funktionsschreibweise, angewendet<br />
auf diese Variable:<br />
$func('Test')<br />
PHP kann dies an den runden Klammern erkennen. Der eigentliche Effekt besteht<br />
darin, dass man einen an zentraler Stelle platzierten Funktionsaufruf situationsabhängig<br />
modifizieren kann. Dies kann unter Umständen ein Skript erheblich vereinfachen.<br />
Die Lesbarkeit wird dadurch aber kaum gesteigert. Anfänger dürften<br />
regelmäßig Schwierigkeiten haben, derartige Programme sofort zu durchschauen.<br />
Noch ein weiterer Effekt macht den Einsatz lohnenswert. Die Auflösung von Variablen<br />
in Zeichenketten wird sehr oft eingesetzt. Will man Rückgabewerte von<br />
Funktionen direkt ausgeben, bleibt normalerweise nur eine Zeichenverkettung:<br />
echo 'Kursiv: ' . $func('Test') . '';<br />
Viele Anführungszeichen, viele Punkte, wenig Lesbarkeit; derartige Konstrukte<br />
sind ebenso häufig wie lästig. Eleganter sieht der folgende Aufbau aus:<br />
echo "Fett: {$func('Test')}";<br />
Die geschweiften Klammern sind zwingend erforderlich, um der Auflösungskomponenten<br />
die korrekten Grenzen zu zeigen.<br />
Abbildung 4.9:<br />
Ausgaben, erzeugt mit dynamischen Funktionsaufrufen<br />
167
168<br />
Programmieren<br />
Die dynamischen Funktionen kann man auch mit internen Namen verwenden,<br />
wie das folgende Beispiel zeigt:<br />
Listing 4.23: functiondynintern.php – <strong>In</strong>terne Funktionen dynamisch aufrufen<br />
$pf = 'sprintf';<br />
$dt = 'date';<br />
echo "Ausgabe: {$pf('Datum: %s', $dt('d.M.Y'))} ";<br />
Hier werden die Funktionen sprintf und date (Datumsausgabe) verwendet und<br />
die Ausgabe in eine Zeichenkette integriert. Die Ausgabe ist wenig spektakulär,<br />
aber die dadurch erreichte Flexibilität reicht hart an selbst modifizierenden Code<br />
heran.<br />
Sprachkonstrukte und Anweisungen, wie beispielsweise echo oder<br />
include, lassen sich nicht mit der dynamischen Funktionssyntax verwenden,<br />
weil es eben keine Funktionen sind.<br />
4.4 Modularisierung von Skripten<br />
Schon bei der Erstellung der ersten Projekte wird der Wunsch aufkommen, einmal<br />
mühevoll fertig gestellte Programme wieder verwenden zu können. PHP<br />
kennt als äußeres Strukturierungskriterium nur Dateien. Will man also Teile eines<br />
anderen Programms wieder verwenden, muss man diese in eigenen Dateien ablegen.<br />
Für das Einbindung solcher Module werden spezielle Funktionen bereitgestellt.<br />
Module einbinden<br />
Abbildung 4.10:<br />
Ausgabe, komplett mit dynamischen Funktionen erzeugt<br />
PHP5 kennt insgesamt vier Anweisungen, die Module aus Dateien aufrufen:
include<br />
Modularisierung von Skripten<br />
require<br />
Beide Anweisungen öffnen eine angegebene Datei und binden den enthaltenen<br />
Code so ein, also ob er an dieser Stelle geschrieben wäre.<br />
include_once<br />
require_once<br />
Auch diese beiden Anweisungen öffnen eine angegebene Datei und binden<br />
den enthaltenen Code so ein, also ob er an dieser Stelle geschrieben wäre. Sie<br />
werden jedoch nur ein einziges Mal ausgeführt, auch wenn sie mehrfach aufgerufen<br />
werden.<br />
Der Unterschied zwischen include und require besteht in der Reaktion auf Fehler.<br />
Das liegt an den verschiedenen Verarbeitungszeitpunkten. require bindet erst<br />
die Datei ein und führt dann das Skript aus. Fehlt das Modul (Datei nicht gefunden),<br />
erscheint sofort ein fataler Fehler und die Ausführung bricht ab. include<br />
wird dagegen erst ausgeführt, wenn der Programmfluss am Befehl angekommen<br />
ist. Eine fehlende Datei führt dann lediglich zu einer Warnung.<br />
Bezüglich der reinen Verarbeitung der <strong>In</strong>halte verhalten sich beide Anweisung<br />
identisch. Im folgenden Text wird deshalb keine Trennung mehr vorgenommen<br />
und nur mit include gearbeitet.<br />
Module und HTML<br />
Jedes Modul wird separat verarbeitet. Das bedeutet, dass PHP erstmal eine HTML-<br />
Datei erwartet. Steht PHP drin, muss dieses in die üblichen Markierungen eingeschlossen werden.<br />
Betrachten Sie zuerst die Anwendung:<br />
Listing 4.24: include.php – Nutzung von Moduldateien<br />
<br />
<br />
<br />
<br />
<br />
<br />
169
170<br />
Programmieren<br />
<br />
<br />
<br />
Der PHP-Block erscheint hier in der üblichen Form. Völlig unabhängig von der<br />
Verwendung steht jedes Modul für sich und deklariert seinen PHP-Bereich – wenn<br />
vorhanden – unabhängig davon.<br />
Listing 4.25: header.inc.php – Variablen der übergeordneten <strong>In</strong>stanz verwenden<br />
<br />
Das erste Modul, für den Kopfbereich, übernimmt die Variable $title. Das ist ohne<br />
weiteres möglich, da sich der Text so verhält, als wäre er an der Stelle der include-<br />
Anweisung geschrieben worden. Das Schlüsselwort global oder vergleichbare<br />
Techniken sind nicht erforderlich.<br />
Listing 4.26: footer.inc.php – Der Fußbereich, ganz ohne PHP<br />
(C) 2004 Markt+Technik<br />
Der Fußbereich zeigt, dass sich die Anweisungen auch bestens zum Einbindung<br />
von reinem HTML eignen. Dies verschafft möglicherweise eine bessere Flexibilität<br />
als die Verwendung der Heredoc-Syntax, wenn größere Textblöcke erforderlich<br />
sind.<br />
Module finden<br />
Im letzten Beispiel wurde der Pfad zu den Modulen – sie waren im Unterverzeichnis<br />
/include platziert – direkt angegeben. Möglicherweise haben Sie eine Sammlung<br />
solcher Module an zentraler Stelle auf dem Server liegen. Dann wäre die<br />
Angabe des Pfades immer und immer wieder sehr lästig. Deshalb kann man einen<br />
speziellen Suchpfad angeben, der von PHP benutzt wird, wenn im aktuellen Ver-
Modularisierung von Skripten<br />
zeichnis die entsprechende Datei nicht aufzufinden war. Der Wert kann entweder<br />
in der php.ini konfiguriert oder mit ini_set angegeben werden.<br />
Listing 4.27: includeset.php – Pfad zu den Modulen über Suchpfade finden<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Der Parameter, der gesetzt werden muss, heißt include_path. Der Pfad kann absolut<br />
oder relativ zu aktuellen Skriptposition sein. Damit man von der Verschiebung<br />
des Skripts unabhängig ist, sollten auf Produktionssystemen absolute Pfade eingesetzt<br />
werden – im Gegensatz zur sonst üblichen Praxis, nur relative Pfade zu verwenden.<br />
Mehrfachverwendung verhindern<br />
Wenn innerhalb eines Moduls eine Funktions- oder Klassendefinition steht, ist die<br />
mehrfache Verwendung innerhalb eines Skripts fatal. Denn eine mehrfache Definition<br />
– worauf es hinausläuft – ist nicht zulässig. Ein Laufzeitfehler wäre die<br />
Folge. <strong>In</strong> solchen Fällen verwendet man include_once. Die Anweisung stellt<br />
sicher, dass der <strong>In</strong>halt nur beim ersten Mal ausgeführt wird. Viele große Projekt<br />
verwenden nämlich verschachtelte Zugriffe auf die Module und andere Module<br />
haben möglicherweise einen bestimmten Code bereits eingebunden. Mangels<br />
anderer Konzepte sind solche Mehrfachverwendungen in PHP durchaus üblich.<br />
Aus informationstechnischer Sicht ist include_once zwar eher als »dirty hack« zu<br />
betrachten, aber es passt zu PHP als primitiver Sprache.<br />
171
172<br />
Programmieren<br />
Automatisches Einbinden mit __autoload<br />
Die Funktion __autoload ist auf globaler Ebene zu definieren. Sie wird immer<br />
dann aufgerufen, wenn eine unbekannte Klasse instanziiert wird. Bei großen Projekten<br />
spart diese Vorgehensweise unter Umständen das Laden großer Bibliotheken,<br />
falls einige Klasse daraus nur sporadisch benötigt werden. Praktisch sieht<br />
das folgendermaßen aus:<br />
function __autoload($classname)<br />
{<br />
include_once("{$classname}.inc.php");<br />
}<br />
$instance = new LibraryClass();<br />
Bei der Ausführung wird folgendes ausgeführt:<br />
include_once("LibraryClass.inc.php");<br />
Ohne weitere objektorientierte Techniken wird man davon allerdings nicht profitieren<br />
können.<br />
<strong>In</strong>formationen über Module ermitteln<br />
Hat man vollends den Überblick über die gerade eingebundenen Module verloren,<br />
bringen ein paar Hilfsfunktionen Licht in den Dschungel:<br />
get_included_files<br />
get_required_files<br />
Beide Funktionen geben jeweils ein Array der Dateien zurück, die im Skript eingeschlossen<br />
wurden. Um eine vollständige Übersicht zu erhalten, müssen Sie die<br />
Funktion am Ende des Skripts aufrufen, andernfalls werden nur die bis zu diesem<br />
Zeitpunkt eingeschlossenen Module ermittelt. Das Array enthält an erster Stelle<br />
immer auch das eigentliche Hauptskript. Beide Funktionen geben übrigens alle<br />
durch require oder include eingebundene Module an, sind also praktisch völlig<br />
identisch. Vermutlich ist dies ein Bug, denn die Namen suggerieren etwas anderes.<br />
Wenn Sie noch keinen Umgang mit Arrays beherrschen, können Sie die folgende<br />
Ausgabe verwenden, um das Ergebnis zu sehen:<br />
<br />
<br />
ini_set('include_path', 'include');<br />
$title = "Testseite";<br />
?><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Die Ausgabe erscheint gut lesbar, wenn man sie in -Tags einbaut. Dies eignet<br />
sich vor allem zur schnellen Fehlersuche:<br />
4.5 Fehlerbehandlung<br />
Abbildung 4.11:<br />
Ausgabe der Liste<br />
der eingeschlossenen<br />
Dateien<br />
PHP5 führt einige neue Methoden der Behandlung von Laufzeitfehlern ein.<br />
Damit rückt die Sprache ein wenig in Richtung moderner Sprachen wie Java oder<br />
C. Allerdings gelang den Entwicklern nur eine halbherzige Umsetzung, was den<br />
Wert wieder etwas in Frage stellt. Nichtsdestotrotz lohnt es sich, die neuen Möglichkeiten<br />
auszuprobieren und einzusetzen.<br />
173
174<br />
Programmieren<br />
Aber auch die konventionelle Technik der Fehlerbehandlung ist nicht völlig<br />
außen vor. <strong>In</strong> der Kombination aller Varianten ist der Umgang mit unvermeidlichen<br />
Fehlern recht brauchbar.<br />
Konventionelle Fehlerbehandlung<br />
Das folgende Skript prüft, ob ein Name in einer Liste von Namen vorhanden ist.<br />
Die Details der Verarbeitung sind hier völlig uninteressant, es geht nur um das<br />
Prinzip. Die Funktion SearchName sucht einen Namen und gibt TRUE zurück,<br />
wenn die Suche erfolgreich war.<br />
Listing 4.29: errorcontrolcon.php – Fehlerbehandlung ohne spezielle Sprachmittel<br />
function SearchName($Name)<br />
{<br />
if (strlen($Name) < 3)<br />
{<br />
return false;<br />
}<br />
$Names = array ('Joerg', 'Uwe', 'Clemens', 'Lukas');<br />
if (in_array($Name, $Names))<br />
{<br />
return true;<br />
}<br />
else<br />
{<br />
return false;<br />
}<br />
}<br />
$name = 'Clemens';<br />
if (SearchName($name))<br />
{<br />
echo 'Name gefunden';<br />
}<br />
else<br />
{<br />
echo 'Name nicht vorhanden';<br />
}<br />
Das Skript ist prinzipiell korrekt geschrieben. Die Funktion folgt den üblichen<br />
Regeln und prüft vor der Verarbeitung, ob sinnvolle Daten vorliegen. Im Beispiel
Fehlerbehandlung<br />
wird eine Mindestlänge von drei Zeichen für den Namen erwartet. Der Rückgabewert<br />
ist das eigentliche Problem: FALSE kann sowohl auf einen Datenfehler als<br />
auch auf eine erfolglose Suche hindeuten. Genau hier setzen üblicherweise Fehlerbehandlungen<br />
an. Falsche Daten sollten einen Laufzeitfehler erzeugen, während<br />
eine misslungene Suche lediglich die passenden Rückgabewerte erzeugt.<br />
Manche Programme behelfen sich mit Hilfslösungen, indem sie Zahlenwerte<br />
zurückgeben und dann -1, 0 und 1 usw. definieren. Das ist nicht schön, denn die<br />
Zustände »gefunden« und »nicht gefunden« sind eindeutig Boolescher Natur.<br />
Laufzeitfehler selbst erzeugen – der klassische Weg<br />
Der erste Ansatz ist bereits in PHP 4 möglich. Hierzu wird zuerst eine Funktion<br />
deklariert, die Laufzeitfehler auffängt und damit eine gezielte Reaktion ermöglicht.<br />
Die Funktion set_error_handler definiert diese Verzweigung. Ist das erfolgt,<br />
wird mit trigger_error im Bedarfsfall der Fehler erzeugt.<br />
Listing 4.30: errorcontroltrigger.php – Benutzerdefinierte Fehlerverwaltung<br />
set_error_handler('SearchError');<br />
function SearchError($errno, $errstr, $errfile, $errline)<br />
{<br />
switch ($errno)<br />
{<br />
case E_USER_ERROR:<br />
echo "Fehler: $errstr";<br />
exit;<br />
default:<br />
echo "$errstr on Line $errline in file $errfile";<br />
}<br />
}<br />
function SearchName ($Name)<br />
{<br />
if (strlen($Name) < 3)<br />
{<br />
trigger_error('Name zu kurz', E_USER_ERROR);<br />
}<br />
$Names = array ('Joerg', 'Uwe', 'Clemens', 'Lukas');<br />
if (in_array($Name, $Names))<br />
{<br />
return true;<br />
}<br />
175
176<br />
Programmieren<br />
else<br />
{<br />
return false;<br />
}<br />
}<br />
$name = 'xx';<br />
if (SearchName($name))<br />
{<br />
echo 'Name gefunden';<br />
}<br />
else<br />
{<br />
echo 'Name nicht vorhanden';<br />
}<br />
Das Skript definiert zuerst eine so genannte Rückruffunktion, die aufgerufen wird,<br />
wenn ein Fehler auftritt:<br />
set_error_handler('SearchError');<br />
Dann muss freilich die Funktion selbst auch definiert werden. Sie hat einen definierten<br />
Aufbau, denn PHP erwartet, dass bestimmte Parameter übergeben werden<br />
können:<br />
function SearchError($errno, $errstr, $errfile, $errline)<br />
Die Parameter haben von links nach rechts folgende festgeschriebene Bedeutung:<br />
Fehlernummer: Eine der internen Fehlernummern.<br />
Fehlertext: Ein Text, der den Fehler beschreibt.<br />
Datei: Die Datei, in der der Fehler ausgelöst wurde. Diese Angabe beachtet<br />
mit include oder require eingebundene Module.<br />
Zeile: Die Zeile, auf der der Laufzeitfehler in der entsprechenden Datei ausgelöst<br />
wurde.<br />
Wie Sie diese Angaben nun verwenden, hängt ganz von der Applikation ab. Wichtig<br />
ist auch, auf die verschiedenen Fehlerklassen reagieren zu können. Sie sind der<br />
folgenden Tabelle zu entnehmen.
Wert Konstante Beschreibung<br />
Fehlerbehandlung<br />
1 E_ERROR Dies zeigt Fehler an, die nicht behoben werden können.<br />
Die Ausführung des Skripts wird abgebrochen.<br />
2 E_WARNING Warnungen während der Laufzeit des Skripts. Führen nicht<br />
zum Abbruch.<br />
4 E_PARSE Parser-Fehler während der Übersetzung. Das Skript startet<br />
gar nicht erst.<br />
8 E_NOTICE Benachrichtigung während der Laufzeit. Wird oft unterdrückt,<br />
was aber keine gute Idee ist.<br />
16 E_CORE_ERROR Fatale Fehler, ähnlich E_ERROR, aber woanders erzeugt.<br />
32 E_CORE_WARNING Warnungen, ähnlich E_WARNING, aber woanders erzeugt.<br />
64 E_COMPILE_ERROR Fataler Fehler zur Übersetzungszeit, erzeugt von der Zend-<br />
Engine.<br />
128 E_COMPILE_WARNING Warnungen zur Übersetzungszeit, erzeugt von der Zend-<br />
Engine.<br />
256 E_USER_ERROR Benutzerdefinierte Fehlermeldungen, erzeugt mit<br />
trigger_error.<br />
512 E_USER_WARNING Benutzerdefinierte Warnung, erzeugt mit trigger_error.<br />
1024 E_USER_NOTICE Benutzerdefinierte Benachrichtigung, erzeugt mit<br />
trigger_error.<br />
2047 E_ALL Alle Fehler und Warnungen die unterstützt werden, mit<br />
Ausnahme von E_STRICT.<br />
2048 E_STRICT Benachrichtigungen des Laufzeitsystems mit Vorschlägen<br />
für Änderungen des Programmcodes, die eine bessere <strong>In</strong>teroperabilität<br />
und Kompatibilität Ihres Codes gewährleisten.<br />
Wurde mit PHP5 eingeführt.<br />
Tabelle 4.2: Fehlercodes in PHP5<br />
Wie das letzte Beispiel zeigte, eignet sich für den eigenen Code E_USER_ERROR.<br />
Weitere Konstanten kann man selbst definieren, diese sollte dann einfach Werte<br />
größer als 65.535 enthalten, damit keine Konflikte auch in künftigen Versionen<br />
177
178<br />
Programmieren<br />
auftreten. Im Übrigen ist noch zu beachten, dass die Werte Bitfelder darstellen<br />
und immer Zweier-Potenzen sind, damit man die Werte kombinieren kann.<br />
Fehlerbehandlung mit PHP5<br />
Mit PHP5 wurde eine neue Art der Fehlerbehandlung eingeführt, die auf einer<br />
neuen Kontrollstruktur basiert: try/catch. Die Idee dahinter: Man definiert einen<br />
Block, umschlossen von try, in dem mit dem Auftreten von Laufzeitfehlern<br />
gerechnet wird. Um diese selbst zu erzeugen, wird die Anweisung throw verwendet.<br />
Der so ausgelöste Fehler muss nun noch zentral abgefangen und verarbeitet<br />
werden, quasi das Gegenstück zur Rückruffunktion muss her. Dies erledigt catch.<br />
Das mag alles etwas abstrakt und unnütz aufwändig klingen. Es führt jedoch dazu,<br />
dass Applikation und Fehlerbehandlung getrennt werden. Damit wird ein Skript<br />
leichter wartbar, lesbarer und letztendlich stabiler. Die hier gezeigten Trivialbeispiele<br />
wären freilich einfacher zu schreiben, aber in der Praxis sind 5.000 Zeilen<br />
für ein Skript durchaus üblich, und dann ist die Suche nach einem Fehler nicht<br />
mehr trivial, auch für Profis nicht.<br />
Fehlerbehandlung mit try/catch verwenden<br />
Das folgende Skript nutzt die Kombination aus try/catch und throw, um die<br />
besprochene Trennung von Auswertung und Fehler zu realisieren:<br />
Listing 4.31: errorcontroltrycatch.php – Kontrolle über Laufzeitfehler mit try/catch<br />
function SearchName ($Name)<br />
{<br />
if (strlen($Name) < 3)<br />
{<br />
throw new Exception('Name zu kurz');<br />
}<br />
$Names = array ('Joerg', 'Uwe', 'Clemens', 'Lukas');<br />
if (in_array($Name, $Names))<br />
{<br />
return true;<br />
}<br />
else<br />
{<br />
return false;
Fehlerbehandlung<br />
}<br />
}<br />
$name = 'Jo';<br />
try<br />
{<br />
if (SearchName($name))<br />
{<br />
echo 'Name gefunden';<br />
}<br />
else<br />
{<br />
echo 'Name nicht vorhanden';<br />
}<br />
}<br />
catch (Exception $ex)<br />
{<br />
echo $ex->getMessage();<br />
}<br />
Eine Rückruffunktion wird nun nicht mehr benötigt. Es genügt, den Block, in<br />
dem Fehler auftreten können, in try zu verpacken:<br />
try<br />
{<br />
///<br />
}<br />
Im Beispiel ist es die Funktion SearchName, die Probleme machen, also Fehler<br />
»werfen« kann. Der Begriff »werfen« hat hier Methode, denn »geworfen« wird der<br />
Fehler mit throw (engl. werfen). <strong>In</strong>teressanterweise sind die Fehler in PHP5, die<br />
ausgelöst werden, keine Konstanten mehr, sondern Klassen. Da Klassen erst am<br />
nächsten Tag behandelt werden, nehmen Sie die Syntax hier erstmal kommentarlos<br />
hin:<br />
throw new Exception('Name zu kurz');<br />
Technisch wird hier eine <strong>In</strong>stanz mit new erzeugt und dann übergeben. <strong>In</strong>teressanter<br />
ist, was daraus im catch-Zweig wird. Hinter einem try können ein oder<br />
mehrere catch-Zweige stehen. Jeder Zweig prüft nun, welche Art von Fehler hier<br />
ankommt. Im Beispiel wurde dazu nur die Basisklasse Exception verwendet, die<br />
PHP5 bereitstellt:<br />
catch (Exception $ex)<br />
179
180<br />
Programmieren<br />
Die Variable $ex, die hier entsteht, erlaubt den Zugriff auf das Objekt, das new<br />
erzeugt hat, als der Fehler mit throw abgeschickt wurde. Darin steht – wenig überraschend<br />
– der Fehlertext:<br />
echo $ex->getMessage();<br />
Der Aufruf zeigt den Zugriff auf eine Methode der Klasse, getMessage(). Genaueres<br />
dazu finden Sie am Ende des folgenden Tages, wenn es die dann vermittelten<br />
Grundlagen der objektorientierten Programmierung erlauben, tiefer in die<br />
Geheimnisse der Fehlerbehandlung in PHP5 einzusteigen.<br />
Laufzeitfehler, die das interne Laufzeitsystem erzeugt, lassen sich übrigens<br />
mit try/catch nicht abfangen. Hierzu kann nur auf die bereits<br />
beschriebenen »konventionellen« Techniken verwiesen werden.<br />
4.6 Kontrollfragen<br />
1. Schreiben Sie ein Skript, das beliebige Zahlen zwischen 0 und 100 auf ganze<br />
Zehner rundet.<br />
2. Können mit switch-Anweisungen auch Vergleiche mit beliebigen Booleschen<br />
Ausdrücken erfolgen?<br />
3. Welche Schleife wird benutzt, wenn sichergestellt werden muss, dass der Schleifenkörper<br />
mindestens einmal durchlaufen wird?<br />
4. Zu Testzwecken kann es erforderlich sein, eine Schleifenbedingung so zu formulieren,<br />
dass eine Endlosschleife entsteht. Mit welchen Anweisungen kann ein<br />
»Notausstieg« programmiert werden?
Daten mit Arrays<br />
verarbeiten<br />
5
182<br />
Daten mit Arrays verarbeiten<br />
Sehr praktisch ist das Zusammenfassen ähnlicher Daten unter einem gemeinsamen<br />
Namen. Solche Gebilde werden als Arrays (Datenfelder) bezeichnet. PHP<br />
bietet hier eine großartige Unterstützung mit vielen Funktionen an.<br />
5.1 Datenfelder im Einsatz: Arrays<br />
Arrays werden sehr oft benötigt. Ein sehr wichtiger Einsatzfall ist im Zusammenhang<br />
mit Datenbanken zu finden. Abfragen an Datenbanken liefern sehr oft die<br />
Daten als Arrays aus. Mit Arrays kann man dann sehr flexibel umgehen. Typische<br />
Funktionen, die direkt und mit wenig Aufwand ausgeführt werden können, sind:<br />
Ausgaben in Schleifen<br />
Sortieren<br />
Zusammenfügen<br />
Suchen<br />
Für all diese Aufgaben bietet PHP spezielle Funktionen und Sprachkonstrukte.<br />
Das führt zwangsläufig zu einer großen Anzahl von Möglichkeiten und Varianten,<br />
was den Umgang mit Arrays nicht unbedingt vereinfacht. Etwas Systematik kann<br />
da nicht schaden. Die folgenden drei Abschnitte entsprechen deshalb in etwa dem<br />
Lebenszyklus eines Arrays:<br />
1. Das Array erstellen und befüllen<br />
2. Das Array manipulieren<br />
3. Das Array ausgeben<br />
5.2 Arrays erstellen und befüllen<br />
Arrays sind Sammlungen ähnlicher Daten unter einem gemeinsamen Namen. So<br />
könnte man eine Liste von Ländercodes erstellen:<br />
de<br />
en<br />
ca
fr<br />
it<br />
Arrays erstellen und befüllen<br />
es<br />
Will man damit in einem Programm umgehen, wäre die Anlage immer neuer<br />
Variablen wenig hilfreich:<br />
$lang1 = "de"<br />
$lang2 = "en"<br />
Stattdessen schreibt man alle Werte in eine Array-Variable und greift über einen so<br />
genannten <strong>In</strong>dex auf die <strong>In</strong>halte zu.<br />
Die Aussage »ähnliche Daten« ist übrigens in PHP nicht ganz ernst zu<br />
nehmen. Mangels strenger Typkontrolle kann man beliebige Daten in<br />
einem Array zusammenpacken.<br />
Ein einfaches Array erstellen<br />
Um ein einfaches Array zu erstellen, benötigt man eine Angabe, die PHP mitteilt,<br />
dass es sich um ein solches handelt. Das folgende Beispiel zeigt dies:<br />
$lang[] = "de";<br />
$lang[] = "en";<br />
$lang[] = "es";<br />
$lang[] = "fr";<br />
$lang[] = "it";<br />
PHP erzeugt hier nur eine Variable mit dem Namen $lang, die insgesamt fünf<br />
Werte enthält. Im Gegensatz zu normalen Variablen überschreiben sich die<br />
Zuweisungen nicht gegenseitig. Das Geheimnis liegt in den eckigen Klammern.<br />
<strong>In</strong>tern passiert ein klein wenig mehr, denn PHP muss die Werte unterscheiden<br />
können. Dazu wird ein numerischer <strong>In</strong>dex vergeben, also eine Zahl, mit der jeder<br />
einzelne Wert adressiert werden kann. Ohne weitere Angaben beginnt PHP mit<br />
dem <strong>In</strong>dex 0 – man spricht dann von null-basierten <strong>In</strong>dizes – und zählt fortlaufend<br />
in ganzen Zahlen weiter. Auf diesem Weg kann man auch gezielt jeden Wert<br />
abrufen:<br />
183
184<br />
Daten mit Arrays verarbeiten<br />
Listing 5.1: arrayindex1.php – Ein einfaches Array erzeugen und darauf zugreifen<br />
$lang[] = "de";<br />
$lang[] = "en";<br />
$lang[] = "es";<br />
$lang[] = "fr";<br />
$lang[] = "it";<br />
echo "<strong>In</strong>dex Nr. 3 enthält den Wert: {$lang[3]}";<br />
Die Schreibweise bei der Ausgabe wurde übrigens nur mit geschweiften Klammern<br />
ergänzt, um die Variablenauflösung in der Zeichenkette mit jeder beliebigen<br />
Arrayart zu ermöglichen. Die »Grundform« sieht folgendermaßen aus:<br />
$lang[3]<br />
Das Beispiel gibt »fr« aus, der vierte Wert im Array. Da mit 0 begonnen wird zu<br />
zählen, führt der <strong>In</strong>dex 3 zum gewünschten Ergebnis.<br />
Die Anzahl der Elemente ermitteln<br />
Um die Anzahl der Elemente zu ermitteln, stellt PHP5 die Funktion count zur<br />
Verfügung:<br />
Listing 5.2: arraycount.php – Anzahl der Elemente ermitteln<br />
$lang[] = "de";<br />
$lang[] = "en";<br />
$lang[] = "es";<br />
$lang[] = "fr";<br />
$lang[] = "it";<br />
echo 'Im Array sind ' . count($lang) . ' Werte';<br />
Die Ausgabe entspricht hier der tatsächlichen Anzahl, also 5 im Beispiel (nicht 4,<br />
nach dem Motto »null-basiert«).<br />
Den <strong>In</strong>dex manipulieren<br />
Der <strong>In</strong>dex muss in PHP nicht fortlaufend sein. Sie können bei der Zuweisung<br />
»wild« Werte vergeben:
Listing 5.3: arrayindex2.php – Freie Vergabe von <strong>In</strong>dizes<br />
$lang[4] = "de";<br />
$lang[5] = "en";<br />
$lang[17] = "es";<br />
$lang[3] = "fr";<br />
$lang[11] = "it";<br />
echo "<strong>In</strong>dex Nr. 11 enthält den Wert: {$lang[11]}";<br />
Auch dieses Array gibt als Anzahl 5 aus:<br />
echo count($lang);<br />
Arrays erstellen und befüllen<br />
Das Array wird also nicht mit den fehlenden Werten aufgefüllt. Tatsächlich spielt<br />
der <strong>In</strong>dex keine Rolle für die interne Verwaltung. Hauptsache, PHP kann die <strong>In</strong>dizes<br />
unterscheiden.<br />
Wenn Sie die Angabe zeitweilig weglassen, setzt PHP immer mit dem höchsten<br />
vergebenen <strong>In</strong>dex plus Eins fort. Betrachten Sie dazu folgende Sequenz:<br />
$lang[4] = "de";<br />
$lang[23] = "en";<br />
$lang[17] = "es";<br />
$lang[] = "fr";<br />
Der Wert »fr« wird hier mit dem <strong>In</strong>dex 24 gespeichert, nicht mit 18, denn der bislang<br />
höchste vergebene Wert war 23.<br />
Schlüssel statt <strong>In</strong>dizes<br />
Das führt schnell zu der Überlegung, ob es nicht möglich wäre, statt der numerischen<br />
<strong>In</strong>dizes auch Zeichenketten zu verwenden. PHP zeigt sich tolerant und<br />
erlaubt dies jederzeit und in jeder Form:<br />
Listing 5.4: arrayindexhash.php – Sprechende <strong>In</strong>dizes verwenden<br />
$lang['de'] = "Deutschland";<br />
$lang['en'] = "England";<br />
$lang['es'] = "Spanien";<br />
$lang['fr'] = "Frankreich";<br />
$lang['ir'] = "Italien";<br />
echo "Das Land mit dem Code 'es' heißt: {$lang['es']}";<br />
185
186<br />
Daten mit Arrays verarbeiten<br />
<strong>In</strong> der <strong>In</strong>formatik werden solche Schlüssel/Wert-Paare auch als Hashes oder Dictionaries<br />
bezeichnet. Der Fantasie sind bei der Namensvergabe keine Grenzen<br />
gesetzt, sprechende oder logische Namen sind sinnvoll, sehr lange Ungetüme fehlerträchtig.<br />
Auf jeden Fall wird nur dann von numerischen <strong>In</strong>dizes abgewichen,<br />
wenn es die Lesbarkeit und Verständlichkeit des Skripts deutlich erhöht oder<br />
zusätzliche <strong>In</strong>formationen in den Schlüsseln gespeichert werden.<br />
Vergessen Sie nie die Anführungszeichen bei der Angabe der Schlüssel.<br />
PHP mag die direkte Angabe $lang[de] anstatt $lang['de'] tolerieren; dies<br />
ist jedoch nur auf einen Trick zurückzuführen. Ist eine Konstante (was<br />
de darstellt) nicht definiert, wird diese in eine Zeichenkette umgewandelt,<br />
was dann vermutlich dem gewünschten Effekt entspricht. Ist die<br />
Konstante jedoch zufällig vorhanden, wird der falsche Schlüssel verwendet.<br />
Entspricht der Wert einem Schlüsselwort aus PHP, wird ein Fehler<br />
erzeugt.<br />
Arraywerte schneller zuweisen<br />
Die Zuweisung von Werten, die nicht automatisch generiert werden, kann in der<br />
beschriebenen Form sehr mühselig sein. Deshalb gibt es ein spezielles Schlüsselwort,<br />
das dies einfacher erledigt. Daraus entsteht dasselbe Array wie bei der bereits<br />
gezeigten Version, die Nutzung von array spart lediglich Tipparbeit:<br />
Listing 5.5: arrayindexarray.php – Arrays schneller anlegen<br />
$lang = array('de', 'en', 'es', 'fr', 'it');<br />
echo "<strong>In</strong>dex Nr. 3 enthält den Wert: {$lang[3]}";<br />
Um nun Schlüssel oder andere als die automatischen <strong>In</strong>dizes verwenden zu können,<br />
muss die Syntax etwas erweitert werden:<br />
Listing 5.6: arrayindex2array.php – Array mit unregelmäßigen <strong>In</strong>dizes<br />
$lang = array(4 => 'de',<br />
5 => 'en',<br />
17 => 'es',<br />
3 => 'fr',<br />
11 => 'it');<br />
echo "<strong>In</strong>dex Nr. 11 enthält den Wert: {$lang[11]}";
Arrays manipulieren<br />
Der Operator => wird hier benutzt, um Schlüssel bzw. <strong>In</strong>dizes und Werte zu trennen.<br />
Es ist nahe liegend, dass dies auch mit Zeichenketten funktioniert:<br />
Listing 5.7: arrayhasharray.php – Hash, ein Array mit Schlüsselwerten als <strong>In</strong>dizes<br />
$lang = array('de' => "Deutschlang",<br />
'en' => "England",<br />
'es' => "Spanien",<br />
'fr' => "Frankreich",<br />
'ir' => "Italien");<br />
echo "Das Land mit dem Code 'es' heißt: {$lang['es']}";<br />
Die »Ausdehnung« der Definition über mehrere Zeilen dient der besseren Lesbarkeit.<br />
Dies sollten Sie sich unbedingt angewöhnen. Es geht auch nicht darum, hier<br />
gegenüber der ersten Variante Zeilen zu sparen. Kompakter, schlecht lesbarer<br />
Code ist immer eine dumme Idee. Die obige Schreibweise ist leichter zu erfassen<br />
und bei großer Datenzahl schneller geschrieben. Weniger Zeichen zu schreiben<br />
heißt immer auch, die Fehlerquote zu verringern.<br />
5.3 Arrays manipulieren<br />
Nachdem die grundsätzlichen Wege, Daten in ein Array zu bekommen, keine<br />
Hürde mehr darstellen, soll mit den Werten gearbeitet werden.<br />
Werte entfernen<br />
Gelegentlich stört ein Wert – er muss entfernt werden. Ebenso wie eine einfache<br />
Variable kann jeder Teil eines Arrays mit unset entfernt werden:<br />
Listing 5.8: arrayunsethash.php – Entfernen eines Elements aus einem Array<br />
$lang =array('de' => "Deutschlang",<br />
'en' => "England",<br />
'es' => "Spanien",<br />
'fr' => "Frankreich",<br />
'ir' => "Italien");<br />
unset($lang['en']);<br />
echo "Das Land mit dem Code 'en' heißt: {$lang['en']}";<br />
187
188<br />
Daten mit Arrays verarbeiten<br />
Das Beispiel führt zu einer Fehlermeldung:<br />
Der fehlende Wert kann jederzeit mit der am Anfang beschriebenen Syntax wieder<br />
zugewiesen werden.<br />
Der Arrayzeiger<br />
PHP führt intern einen Zeiger für Arrays, der die gezielte Auswahl von Elementen<br />
erlaubt. Dies ist eine bereits mit PHP3 eingeführte Technik, die aber nichts an<br />
ihrer Attraktivität verloren hat. Viele der neuen Arrayfunktionen, von denen auch<br />
mit PHP5 wieder weitere hinzukamen, nutzen diese Zeiger nicht. Einfache Arrays<br />
lassen sich mit den alten Funktionen dennoch gut verarbeiten. Zur Auswahl steht<br />
eine ganze Palette:<br />
reset($array);<br />
Der Zeiger wird auf das erste Element zurückgesetzt.<br />
Abbildung 5.1:<br />
Der Fehlerfall<br />
zeigte den Erfolg<br />
der Aktion, das<br />
Element wurde<br />
aus dem Array<br />
entfernt<br />
current($array);<br />
Die Funktion gibt das Element zurück, auf das der Zeiger zurzeit zeigt.<br />
next($array);<br />
Die Funktion verschiebt den Zeiger zum nächsten Element. Sie gibt false<br />
zurück, wenn kein Element mehr da ist, ansonsten den betreffenden Elementwert.<br />
prev($array);<br />
Die Funktion verschiebt den Zeiger zum vorhergehenden Element. Die Funktion<br />
gibt false zurück, wenn der Zeiger bereits auf dem ersten Element stand,<br />
ansonsten den betreffenden Elementwert.<br />
key($array);<br />
Die Funktion gibt den Schlüssel oder <strong>In</strong>dex des aktuellen Elements zurück.
Arrays manipulieren<br />
Einige Beispiele, die diese Funktionen nutzen, finden Sie im Abschnitt 5.4 »Arrays<br />
ausgeben« ab Seite 193. Darüber hinaus ist der Einsatz immer dann interessant,<br />
wenn die <strong>In</strong>dizes nicht sequenziell vergeben wurden und einfache Zählschleifen<br />
deshalb nicht in Frage kommen. Für das zuletzt bereits verwendete assoziative<br />
Array sind die Funktionen recht praktisch:<br />
$lang = array('de' => "Deutschland",<br />
'en' => "England",<br />
'es' => "Spanien",<br />
'fr' => "Frankreich",<br />
'ir' => "Italien");<br />
reset($lang);<br />
Listing 5.9: arraynextprev.php – Zugriff auf Array-Elemente mit Zeigerfunktionen<br />
do<br />
{<br />
echo current($lang) . ' hat den Code ' . key($lang);<br />
echo '';<br />
} while(next($lang));<br />
Die Ausgabe zeigt, dass der Zugriff sequenziell in der Reihenfolge der Definition<br />
erfolgt:<br />
Abbildung 5.2:<br />
Sequenzielle Ausgabe eines Arrays mit Zeigerfunktionen<br />
Das Beispiel sieht toll aus und funktioniert auch. Es hat jedoch eine böse Falle, die<br />
nicht offensichtlich ist. Sehen Sie sich zum Vergleich das folgende Array an:<br />
$lang = array('de' => "Deutschland",<br />
'en' => "England",<br />
'es' => "0",<br />
'fr' => "Frankreich",<br />
'it' => "Italien");<br />
Statt dem Wort »Spanien« wurde die Ziffer »0« eingesetzt – nach wie vor als Zeichenkette<br />
wohlgemerkt. Diesmal wird folgende Ausgabe produziert:<br />
189
190<br />
Daten mit Arrays verarbeiten<br />
Abbildung 5.3:<br />
Sequenzielle Ausgabe mit Problemen<br />
Was ist passiert? PHPs Typlosigkeit hat hier einen Streich gespielt. Die Funktion<br />
next gibt nämlich nicht true oder false, sondern den Elementwert oder false<br />
zurück. Der Elementwert ist beim dritten Element jedoch »0«. Da die Prüfung<br />
mit while auf false erfolgt, konvertiert PHP den Wert stillschweigend in sein Boolesches<br />
Äquivalent, und da ist 0 == false, was planmäßig zum Abbruch führt. Die<br />
Korrektur ist einfach:<br />
Listing 5.10: arraynextprevkorr.php – Korrigierter Zugriff mit next<br />
$lang = array('de' => "Deutschland",<br />
'en' => "England",<br />
'es' => "0",<br />
'fr' => "Frankreich",<br />
'it' => "Italien");<br />
reset($lang);<br />
do<br />
{<br />
echo current($lang) . ' hat den Code ' . key($lang);<br />
echo '';<br />
} while(next($lang)!==FALSE);<br />
Der Effekt besteht in der korrekten Auswertung in der while-Bedingung. Mit dem<br />
Operator !== wird auch der ursprüngliche Typ geprüft und der ist auf der rechten<br />
Seite Boolesch, auf der linken jedoch nicht. Nun kann es sein, dass man den<br />
Abbruch durchaus will. Dann setzt man als Wert des Arrays nicht eine Zeichenkette<br />
ein, sondern den passenden Vergleichswert, TRUE oder FALSE. Folgendes Array<br />
bricht wieder ab, diesmal ist es jedoch gewollt und logisch:<br />
$lang = array('de' => "Deutschland",<br />
'en' => "England",<br />
'es' => FALSE,<br />
'fr' => "Frankreich",<br />
'it' => "Italien");<br />
Auch der Zugriff auf key ist tückisch. Beginnt das Array mit dem <strong>In</strong>dex 0 – das ist<br />
der Standardfall – gibt key eben zuerst 0 zurück. Durchläuft man mit einer<br />
Schleife die <strong>In</strong>dizes, ist schon der erste Wert implizit FALSE. Die Operatoren ===<br />
und !== sind nett, ersetzen aber eine gewisse Typstrenge nur mangelhaft. So einfach<br />
wie PHP manchmal ist, so bösartig kann es im Detail werden.
Arrayfunktionen<br />
Arrays manipulieren<br />
Einige der ersten Arrayfunktionen, mit denen Sie praktisch Bekanntschaft<br />
machen, werden die Sortierfunktionen sein. Sortiert wird in der Programmierung<br />
immer wieder und in allen Varianten. PHP verfügt hier über eine inflationäre<br />
Funktionssammlung.<br />
Die einfachste Sortierfunktion heißt sort. Sie sortiert alphanumerisch aufwärts,<br />
das heißt, zuerst kommen die Zahlen, dann Satzzeichen, dann Buchstaben. Dies<br />
entspricht dem Verlauf der ASCII-Werte der Zeichen. Es gibt weitere Sortierfunktionen,<br />
die ein anderes Verhalten aufweisen. Dazu später mehr.<br />
$lang[] = "fr";<br />
$lang[] = "es";<br />
$lang[] = "it";<br />
$lang[] = "de";<br />
$lang[] = "en";<br />
sort($lang);<br />
Listing 5.11: arraysort.php – Sortierung eines einfache Arrays<br />
reset($lang);<br />
do<br />
{<br />
echo current($lang) . '';<br />
} while (next($lang) !== FALSE);<br />
Das Ergebnis entspricht den Erwartungen:<br />
Abbildung 5.4:<br />
Erfolgreiche Sortierung eines einfachen Arrays<br />
Bei assoziativen Arrays ist das nicht ganz so einfach. Das folgende Beispiel zeigt<br />
einen interessanten Effekt:<br />
$lang =array('de' => "Deutschland",<br />
'en' => "England",<br />
'es' => "Spanien",<br />
'fr' => "Frankreich",<br />
191
sort($lang);<br />
192<br />
Daten mit Arrays verarbeiten<br />
'ir' => "Italien");<br />
Listing 5.12: arrayhashsorterr.php – Sieht gut aus, funktioniert aber nicht (siehe Text)<br />
reset($lang);<br />
do<br />
{<br />
echo current($lang) . ' hat den Code ' . key($lang);<br />
echo '';<br />
} while(next($lang));<br />
Die Ausgabe ist wenig hilfreich:<br />
Für solche Fälle kennt PHP eine spezielle Sortierung: asort. Denn sort sortiert<br />
nur Werte, ignoriert die Schlüssel und legt die dann fehlenden <strong>In</strong>dizes nach dem<br />
üblichen Schema neu an: aufsteigend mit 0 beginnend.<br />
asort($lang);<br />
Abbildung 5.5:<br />
Sortierung ohne Schlüssel: sort ist sehr primitiv<br />
Listing 5.13: arrayhashasort.php: Ausschnitt aus dem korrigierten Listing<br />
Ersetzt man im letzten Listung sort durch asort, funktioniert wieder alles:<br />
Abbildung 5.6:<br />
Korrekte Sortierung, auch mit <strong>In</strong>dizes<br />
Eine Liste aller elementaren Sortierfunktionen finden Sie am Ende des Kapitels in<br />
der Kurzreferenz.
Arraydaten manipulieren<br />
Arrays ausgeben<br />
Richtig spannend wird es, wenn man die vielen Array-Funktionen, die PHP5 bietet,<br />
zum Einsatz bringen kann. Vielfach wird erst dadurch die Verarbeitung von<br />
Daten richtig effizient. Alle Funktionen vorzustellen, führt hier zu weit, weil allein<br />
die schiere Masse eine ausführliche Darstellung nur auf breitem Raum erlaubt.<br />
Die Referenz am Ende des Kapitels bietet eine ausreichende Hilfestellung bei der<br />
Suche nach der passenden Funktion.<br />
5.4 Arrays ausgeben<br />
Arrays kann man auf vielen Wegen ausgeben. Was konkret zum Einsatz kommt,<br />
hängt von der Situation und oft auch von den Daten selbst ab.<br />
Arrays zählbar ausgeben: for<br />
Die Anweisung for ist immer dann vorteilhaft, wenn man eine konkrete Anzahl<br />
Elemente eines indizierten Arrays ausgeben möchte. Mit for tun sich assoziative<br />
und verschachtelte Arrays recht schwer, denn diese Arrays kennen die nötigen<br />
numerischen <strong>In</strong>dizes nicht.<br />
$lang[] = array('de', "Deutschland");<br />
$lang[] = array('en', "England");<br />
$lang[] = array('es', "Spanien");<br />
$lang[] = array('fr', "Frankreich");<br />
$lang[] = array('it', "Italien");<br />
for($i = 0; $i < count($lang); $i++)<br />
Listing 5.<strong>14</strong>: arrayoutfor.php – Ausgabe der Elemente eines Arrays mit for<br />
{<br />
}<br />
$larray = $lang[$i];<br />
echo "Das Kürzel {$larray[0]} steht für {$larray[1]}";<br />
Das eine Zählschleife einen Start- und einen Endwert benötigt, wird hier die<br />
Funktion count benutzt, um den Endwert zu ermitteln. Als Startwert wird 0 eingesetzt.<br />
Die Formel $Laufwert < $Endwert ist typisch, da der Endwert durch die<br />
193
194<br />
Daten mit Arrays verarbeiten<br />
Anzahl bestimmt wird, während der Laufwert bei 0 beginnt (null-basiertes Array).<br />
Deshalb wird als Endwert die Anzahl minus Eins benutzt.<br />
Beliebige Arrays mit foreach durchlaufen<br />
Das Problem der <strong>In</strong>dizes hat foreach nicht. Wie der Name bereits suggeriert, wird<br />
hier jedes Element eines Arrays durchlaufen, unabhängig von der Anzahl und Art<br />
der Elemente.<br />
$lang[] = array('de', "Deutschland");<br />
$lang[] = array('en', "England");<br />
$lang[] = array('es', "Spanien");<br />
$lang[] = array('fr', "Frankreich");<br />
$lang[] = array('it', "Italien");<br />
foreach ($lang as $larray)<br />
Listing 5.15: arrayoutforeach.php – Flexibel, schnell, einfach: foreach zur Arrayausgabe<br />
{<br />
echo "Das Kürzel {$larray[0]} steht für {$larray[1]}";<br />
}<br />
Die Syntax der Anweisung ist recht einfach. Der erste Wert ist der Name des auszugebenden<br />
Arrays, danach folgt das Schlüsselwort as. Ab hier gibt es zwei Varianten.<br />
Die einfachste nennt einfach eine Variable ($larray im Beispiel), der der<br />
jeweils aktuelle Wert übergeben wird. Es ist dann Sache des Schleifencodes, dieses<br />
Element gegebenenfalls weiter zu zerlegen. Alternativ kann man auch zwei Variablen<br />
angeben, die Schlüssel und Wert des Elements eines assoziativen Arrays aufnehmen.<br />
Beachten Sie im folgenden Beispiel die andere Art der Definition und<br />
die Angabe der Kopfparameter für foreach:<br />
Listing 5.16: arrayoutforeachas.php – Ausgabe eines assoziativen Arrays<br />
$lang['de'] = "Deutschland";<br />
$lang['en'] = "England";<br />
$lang['es'] = "Spanien";<br />
$lang['fr'] = "Frankreich";<br />
Abbildung 5.7:<br />
Ausgabe eines Arrays mit for
Arrays ausgeben<br />
$lang['it'] = "Italien";<br />
foreach ($lang as $iso => $name)<br />
{<br />
echo "Das Kürzel {$iso} steht für {$name}";<br />
}<br />
Die Ausgabe entspricht Abbildung 5.7. Der Code erscheint aber besser lesbar. <strong>In</strong><br />
den allermeisten Fällen wird man assoziative Arrays mit foreach ausgeben.<br />
Einfache Arrays mit while, each, list ausgeben<br />
Die Anweisungen while, each und list sind bereits seit den ersten PHP-Versionen<br />
zur Arraybehandlung im Einsatz. Arbeitet man mit einfachen Arrays, bieten sie<br />
eine erstaunliche Flexibilität.<br />
$lang['de'] = "Deutschland";<br />
$lang['en'] = "England";<br />
$lang['es'] = "Spanien";<br />
$lang['fr'] = "Frankreich";<br />
$lang['it'] = "Italien";<br />
while (list($iso, $name) = each ($lang))<br />
Listing 5.17: arrayoutwhile.php – Anstatt foreach kann man auch mit while arbeiten<br />
{<br />
echo "Das Kürzel {$iso} steht für {$name}";<br />
}<br />
Auf den ersten Blick erscheint dieser Ersatz für foreach unnötig kompliziert. Allerdings<br />
ist die Anzahl der Elemente für list nicht begrenzt, was den Einsatz auch<br />
auf kompliziertere Konstrukte ausdehnen kann. Der Ablauf dieses Beispiels sieht<br />
folgendermaßen aus:<br />
1. Die while-Schleife startet mit dem ersten Aufruf von each<br />
2. each ruft den ersten Wert ab und übergibt ihn an list<br />
3. list zerlegt das Element und packt die Werte in die Variablen<br />
4. Die Schleife wird durchlaufen<br />
5. Der Vorgang beginnt wieder bei 2. Wenn each nichts mehr findet, gibt es<br />
FALSE zurück. Darauf reagiert auch list mit FALSE und der gesamte Ausdruck<br />
wird FALSE, was while zum Abbruch der Schleife veranlasst.<br />
Die Ausgabe entspricht Abbildung 5.7.<br />
195
196<br />
Daten mit Arrays verarbeiten<br />
Array mit array_walk durchlaufen<br />
Eine der wichtigsten Arrayfunktionen ist array_walk. Die Funktion ruft für jedes<br />
Arrayelement eine so genannte Rückruffunktion auf. Damit kann man praktische<br />
alles mit einem Array anstellen, was erforderlich ist. Das folgende Beispiel zeigt, wie<br />
ein Array ausgegeben werden kann, wobei sich die Daten vielfältig behandeln lassen:<br />
function CountryList($element)<br />
{<br />
list($iso, $name) = $element;<br />
echo "Der Code für $name ist: $iso";<br />
}<br />
$lang[] = array('de', "Deutschland");<br />
$lang[] = array('en', "England");<br />
$lang[] = array('es', "Spanien");<br />
$lang[] = array('fr', "Frankreich");<br />
$lang[] = array('it', "Italien");<br />
array_walk($lang, 'CountryList');<br />
Listing 5.18: arraywalk.php – Array mit benutzerdefinierter Funktion durchlaufen<br />
Die Funktion array_walk sorgt dafür, dass die Elemente nacheinander an die<br />
benutzerdefinierte Funktion CountryList übergeben werden. Die weitere Verarbeitung<br />
ist nur ein Beispiel. Es handelt sich hier um ein Array, dessen Elemente<br />
wiederum Arrays sind. Da list zur Übertragung der Elemente in Variablen<br />
benutzt wird, sind numerische <strong>In</strong>dizes erforderlich. Das folgende Array erfüllt<br />
diese Forderungen:<br />
array('de', 'Deutschland')<br />
Der interne Aufbau ist:<br />
[0] => 'de'<br />
[1] => 'Deutschland'<br />
Für derartige Ausgaben ist übrigens die Funktion print_r sehr hilfreich.<br />
Fehlersuche mit print_r<br />
Zur Fehlersuche braucht man oft einen schnellen Überblick über den aktuellen<br />
Aufbau eines Arrays. Die Funktion print_r erledigt das. Für die reine Program-
Arrays ausgeben<br />
mierung ist der Einsatz kaum zu gebrauchen, die Ausgabe ist eher technischer<br />
Natur und kann nicht formatiert werden.<br />
Listing 5.19: print_r.php – Ausgabe eines Arrays zur Fehlersuche<br />
<br />
<br />
<br />
Die Funktion verwendet außerdem einfache Zeilenumbrüche (\n) zur Formatierung.<br />
Um die Darstellung in HTML zu ermöglichen, müssen diese entweder in<br />
umgewandelt werden oder die Ausgabe steht zwischen -Tags.<br />
Abbildung 5.8:<br />
Ausgabe eines Arrays mit print_r zur <strong>In</strong>haltsanalyse<br />
197
198<br />
Daten mit Arrays verarbeiten<br />
Mehr Analysemöglichkeiten für Skripte werden im Abschnitt 9.3 »Code röntgen:<br />
Die Reflection-API« ab Seite 395 vorgestellt. Außerdem sei auch auf die neue<br />
Funktion var_dump verwiesen, die sehr brauchbare Darstellungen komplexer<br />
Arrays erzeugt.<br />
5.5 Referenz der Arrayfunktionen<br />
Funktion Bedeutung<br />
array Erstellt ein neues Array aus konstanten Werten.<br />
array_change_key_case Liefert das Array mit geänderter Groß- und Kleinschreibung<br />
der Schlüssel.<br />
array_chunk Zerlegt ein Array in zwei Teile und gibt beide Teile als<br />
Arrays zurück.<br />
array_combine Verbindet ein einfaches Array mit Schlüsseln und eines mit<br />
Werten zu einem assoziativen Array.<br />
array_count_values Ermittelt die Häufung von Werten in einem Array.<br />
array_diff Ermittelt die Unterschiede mehrerer Arrays anhand der<br />
Werte.<br />
array_diff_assoc Ermittelt die Unterschiede mehrerer Arrays anhand der<br />
<strong>In</strong>dizes.<br />
array_diff_uassoc<br />
array_udiff_assoc<br />
Ermittelt die Unterschiede mehrerer Arrays anhand der<br />
<strong>In</strong>dizes durch Nutzung einer benutzerdefinierten Rückruffunktion.<br />
array_fill Füllt das Array mit bestimmten konstanten Werten unter<br />
Angabe des Startindex.<br />
array_filter Filtert ein Array unter Nutzung einer Rückruffunktion.<br />
array_flip Vertauscht Schlüssel und Werte.<br />
array_intersec Ermittelt die Schnittmenge mehrerer Arrays anhand der<br />
<strong>In</strong>dizes.<br />
Tabelle 5.1: Arrayfunktionen in PHP5, alphabetisch sortiert
Funktion Bedeutung<br />
Referenz der Arrayfunktionen<br />
array_intersec_assoc Ermittelt die Schnittmenge mehrerer Arrays anhand der<br />
Schlüssel.<br />
array_keys Gibt die Schlüssel eines assoziativen Arrays als einfaches<br />
Array zurück.<br />
array_map Ruft für jedes Arrayelement eine benutzerdefinierte Rückruffunktion<br />
auf und akzeptiert für diese ein Array, das die<br />
Parameter definiert.<br />
array_merge Verbindet zwei oder mehr Arrays miteinander.<br />
array_merge_recursive Verbindet zwei oder mehr Arrays miteinander, wobei identische<br />
Schlüsselwerte sich überschreiben, während numerische<br />
<strong>In</strong>dizes ignoriert und die Werte angehängt werden.<br />
array_multisort Sortiert mehrdimensionale Arrays.<br />
array_pad Füllt ein Array mit Werten zu einer bestimmten Größe auf.<br />
array_pop Liefert das oberste Element eines Arrays und setzt den Arrayzeiger<br />
zurück.<br />
array_push Fügt ein Element als neues oberstes Element einem Array<br />
hinzu.<br />
array_rand Liefert zufällig ausgewählte Elemente eines Arrays.<br />
array_reduce Verdichtet die Werte eines Arrays zu einem Wert mittels<br />
benutzerdefinierter Rückruffunktion.<br />
array_reverse Vertauscht die Elemente eines Arrays, sodass das letzte Element<br />
nach der Operation das erste ist.<br />
array_shift Liefert das erste Element eines Arrays und setzt den Arrayzeiger<br />
zurück.<br />
array_slice Extrahiert einen Teil aus einem Array und gibt diesen<br />
zurück.<br />
array_splice Entfernt einen Teil eines Arrays und ersetzt ihn durch ein<br />
anderes Array.<br />
Tabelle 5.1: Arrayfunktionen in PHP5, alphabetisch sortiert (Forts.)<br />
199
200<br />
Daten mit Arrays verarbeiten<br />
Funktion Bedeutung<br />
array_sum Die Summe aller numerischen Werte des Arrays.<br />
array_udiff Ermittelt die Unterschiede mehrerer Arrays anhand der<br />
Werte durch Nutzung einer benutzerdefinierten Rückruffunktion.<br />
array_unique Entfernt doppelte vorkommende Werte aus einem Array.<br />
array_unshift Fügt ein Element als erstes Element eines Arrays ein.<br />
array_values Liefert die Werte eines assoziativen Arrays als einfaches<br />
Array.<br />
array_walk Ruft für jeden Wert eine Rückruffunktion auf.<br />
arsort Sortiert assoziative Arrays absteigend.<br />
asort Sortiert assoziative Arrays aufsteigend.<br />
compact Erstellt ein Array aus skalaren Variablen.<br />
count Ermittelt die Anzahl der Werte insgesamt.<br />
current Gibt das aktuelle Element zurück, wenn mit Arrayzeigern<br />
gearbeitet wird.<br />
each Gibt das aktuelle Element zurück, wenn mit Arrayzeigern<br />
gearbeitet wird und setzt den Zeiger eins weiter<br />
end Setzt den Arrayzeiger auf das letzte Element im Array.<br />
extract Exportiert die Werte eines Arrays als skalare Variablen.<br />
in_array Ermittelt, ob ein Wert in einem Array enthalten ist.<br />
key Gibt den aktuellen Schlüssel zurück.<br />
krsort Sortiert assoziative Arrays absteigend nach den Schlüsseln.<br />
ksort Sortiert assoziative Arrays aufsteigend nach den Schlüsseln.<br />
list Übernimmt alle Unterelemente des aktuellen Elements in<br />
Variablen.<br />
Tabelle 5.1: Arrayfunktionen in PHP5, alphabetisch sortiert (Forts.)
Funktion Bedeutung<br />
Referenz der Arrayfunktionen<br />
natcasesort Sortiert »natürlich« aufsteigend ohne Rücksicht auf Groß-<br />
und Kleinschreibung.<br />
natsort Sortiert »natürlich« aufsteigend.<br />
next Gibt das aktuelle Element zurück, wenn mit Arrayzeigern<br />
gearbeitet wird und setzt den Zeiger eins weiter.<br />
pos Gibt die aktuelle Position des Arrayzeigers zurück.<br />
prev Gibt das aktuelle Element zurück, wenn mit Arrayzeigern<br />
gearbeitet wird und setzt den Zeiger eins zurück.<br />
range Erstellt ein Array mit definierten numerischen Werten.<br />
reset Setzt den Arrayzeiger auf die erste Position im Array.<br />
rsort Sortiert einfache Arrays absteigend.<br />
shuffle Sortiert die Werte eines Arrays mit Zufallsgenerator.<br />
sizeof Ein anderer Name für count.<br />
sort Sortiert einfache Arrays aufsteigend.<br />
uasort Sortiert assoziative Arrays durch benutzerdefinierte Funktion.<br />
uksort Sortiert assoziative Arrays nach dem Schlüssel durch benutzerdefinierte<br />
Funktion.<br />
usort Sortiert einfache Arrays durch benutzerdefinierte Funktion.<br />
Tabelle 5.1: Arrayfunktionen in PHP5, alphabetisch sortiert (Forts.)<br />
201
202<br />
Daten mit Arrays verarbeiten<br />
5.6 Kontrollfragen<br />
1. Worin unterscheiden sich normale von assoziativen Arrays?<br />
2. Wie kann ein Element eines Array gezielt entfernt werden?<br />
3. Schreiben Sie ein Skript, dass Arrays nutzt, um Namen von Mitarbeitern zu speichern.<br />
4. Erweitern Sie das Skript, sodass zu jedem Mitarbeiter im selben Array zusätzliche<br />
<strong>In</strong>formationen gespeichert werden. Tipp: Nutzen Sie verschachtelte assoziative<br />
Arrays dafür.
Objektorientierte<br />
Programmierung<br />
6
204<br />
Objektorientierte Programmierung<br />
PHP5 hat in seiner Entwicklung in Bezug auf die objektorientierte Programmierung<br />
einen großen Sprung vorwärts gemacht. Auch wenn PHP keine im klassischen<br />
Sinne objektorientierte Sprache ist, sind erstmals professionelle Softwareentwicklungstechniken<br />
in gewissem Umfang möglich.<br />
Bei den Programmiersprachen gibt es verschiedene Modelle, wie Syntax und<br />
Semantik aufgebaut sind. Alle Sprachen produzieren letztlich Code, der von<br />
einem Prozessor verarbeitet werden muss. Prozessoren sind so genannte Von-Neumann-Maschinen,<br />
die Befehlsinstruktionen immer sequenziell verarbeiten, wobei<br />
sie vereinfacht einen Zyklus aus Speicheradresse aussenden, Speicher auslesen,<br />
Befehl empfangen, Befehl verarbeiten und Ergebnis ablegen durchlaufen. Die ersten<br />
Programmiersprachen folgten diesem Schema und waren so aufgebaut, dass<br />
primär Befehl für Befehl abgearbeitet wird. Vom lateinischen Wort für Befehlen –<br />
imperare – kommt auch der Name dieser Sprachen: Imperative Programmiersprachen.<br />
Typische Vertreter sind BASIC und C.<br />
Im Laufe der Zeit wurde Code immer komplexer und aufwändiger. Es entstanden<br />
viele andere Versionen, von denen die objektorientierte weiteste Verbreitung fand.<br />
Objektorientierte Sprachen fassen Code zu Objekten zusammen, die aus Eigenschaften<br />
und Methoden bestehen. Wie in der Natur werden Daten und Daten<br />
verarbeitende Funktionen zusammengefasst. Dies erleichtert die Wiederverwendbarkeit<br />
und Organisation von Code erheblich und erlaubt erst größere Applikationen.<br />
Typische Vertreter sind Java, C++, Delphi, C# und alle anderen .NET-<br />
Sprachen.<br />
Neben der Unterscheidung der Art der Programmiersprache ist auch die Konstruktion<br />
des Sprachumsetzers wichtig. Denn der Prozessor versteht nur Maschinencode,<br />
weshalb ein Übersetzungsvorgang stattfinden muss. Die ersten Sprachen<br />
waren <strong>In</strong>terpretersprachen. Ein entsprechendes Programm übersetzte Befehl für<br />
Befehl in Maschinencode. Weil dies in Schleifen uneffektiv ist – jeder Befehl wird<br />
wieder und wieder übersetzt – entstanden so genannte Compiler. Diese übertragen<br />
in einem Lauf erst alle Codes in die Maschinensprache und das Betriebssystem<br />
lässt diesen Code dann ablaufen. Der Vorgang ist freilich aufwändiger und komplexer<br />
und war deshalb immer »richtigen« Programmiersprachen vorbehalten, die<br />
mit umfangreichen Entwicklungsumgebungen und vielen Hilfswerkzeugen ausgestattet<br />
waren. Die ersten BASIC-Versionen in Homecomputern der 80er Jahre<br />
waren immer <strong>In</strong>terpretersprachen, weil nicht genug Speicher zum Ablegen der<br />
übersetzten Codes vorhanden war. C und C++ sind typische Compilersprachen.<br />
Java und die .NET-Sprachen sind auch Compilersprachen, nutzen aber einen speziellen<br />
Zwischencode, der eine zweifache Übersetzung erfordert. Dies erlaubt eine<br />
stärkere Kontrolle des Codes durch eine so genannte Laufzeitschicht und macht
Warum objektorientiert programmieren?<br />
die Sprachen systemunabhängiger. Aus den <strong>In</strong>terpretersprachen entwickelten sich<br />
die Skriptsprachen, die mit reduziertem Sprachumfang und einfachster Verarbeitung<br />
kleine Programmieraufgaben erledigen und geringe Ansprüche an Entwicklungsumgebung<br />
und das Know-how des Entwicklers stellen. Hier sind die<br />
typischen Vertreter VBScript, JavaScript, Perl und auch die ersten Versionen von<br />
PHP.<br />
PHP ist in der Version 5, wie inzwischen auch Perl, ein Zwitter. Denn zum einen<br />
ist es eine Skriptsprache, die per <strong>In</strong>terpreter verarbeitet wird. Sie ist prinzipiell<br />
imperativ strukturiert. Objektorientierte Sprachelemente sind vorhanden, deren<br />
Verwendung wird aber nicht erzwungen, wie das bei »echten« objektorientierten<br />
Sprachen der Fall ist. Die Verarbeitung basiert auf so genannten Token. Der <strong>In</strong>terpreter<br />
zerlegt den Code also erst in einen eigenen Zwischencode – quasi ein dem<br />
Kompilieren ähnelnder Vorgang – und führt diesen Code dann interpretativ aus.<br />
Das ist effektiver als bei einem reinen <strong>In</strong>terpreter und vermeidet die Komplexität<br />
eines echten Compilers. PHP selbst ist übrigens zu großen Teilen in C geschrieben,<br />
was vielleicht als ein Hinweis auf die Leistungsfähigkeit und Einsatzbreite der<br />
Sprachen wichtig zu wissen ist.<br />
6.1 Warum objektorientiert programmieren?<br />
Die Behandlung von Objekten wurde in PHP5 grundlegend überarbeitet. Dies<br />
war von der Community vehement gefordert worden, weil große Projekte von vielen<br />
verfügbaren Bibliotheken leben und derartige Codesammlungen ohne objektorientierte<br />
Mittel kaum beherrschbar sind. Mit der zunehmenden Bedeutung von<br />
PHP waren die Bibliothekenentwickler schnell an die Grenzen des bisherigen<br />
Modells gestoßen.<br />
Unabhängig davon profitieren auch kleine Projekte von objektorientierter Programmierung,<br />
weil sie Code lesbarer und wartbarer macht. Nachteilig ist der etwas<br />
höhere Schreibaufwand, der Anfänger oft davon abhält, sich damit auseinander zu<br />
setzen.<br />
Prinzipien und Paradigmen<br />
Bei der objektorientierten Programmierung werden reale Sachverhalte in eine<br />
Modellumgebung übertragen. Es ist also letztlich eine Technik zur Abstraktion.<br />
205
206<br />
Objektorientierte Programmierung<br />
Bei imperativen Sprachen ist diese Abstraktion allein auf die Fähigkeit des Entwicklers<br />
abgestellt. Objektorientierte Sprachen bieten hier eine explizite Unterstützung<br />
durch Sprachelemente. Der Formalismus, der dazu benutzt wird, ist nicht<br />
unbedingt trivial, hilft aber durch seine Strenge, besseren 1 Code zu entwickeln.<br />
Die folgende Darstellung ist so weit vereinfacht, wie es zum Verständnis<br />
von PHP5 erforderlich ist. Objektorientierte Techniken, die PHP5 nicht<br />
unterstützt, werden nicht weiter betrachtet.<br />
Das objektorientierte Modell nutzt folgende Begriffe:<br />
Objekte und Klassen<br />
Beziehungen und Eigenschaften von Objekten und Klassen<br />
Kommunikation mit und zwischen Objekten<br />
Objekte sind Abstraktionen von im Problembereich existierenden Einheiten, die<br />
für das System relevante <strong>In</strong>formationen zusammenfassen oder mit denen im System<br />
zusammengearbeitet wird. Ein solches Objekt besitzt statische und dynamische<br />
Eigenschaften, die den Zustand des Objekts beschreiben, und Dienste, die<br />
ein Objekt ausführen kann bzw. anbietet, abgebildet durch Methoden.<br />
Objekte mit gleichen Eigenschaften und gleichen Methoden werden zu Klassen<br />
zusammengefasst. Ein Objekt ist also Exemplar einer Klasse. Anders gedeutet ist<br />
eine Klasse eine Vorlage, nach der gleichgeartete Objekte erzeugt werden. Die<br />
Ableitung eines Objekts aus einer Klasse wird in der Literatur häufig als <strong>In</strong>stanziieren<br />
bezeichnet. Auch wenn dies auf einer falschen Übersetzung des englischen<br />
Begriffs »instance« basiert, ist der Begriff inzwischen etabliert (ähnlich wie beim<br />
»Handy«).<br />
Klassen können Hierarchien bilden. Ausgehend von einer Basisklasse »erben«<br />
untergeordnete Strukturen deren Eigenschaften und Methoden, verändern einige<br />
davon oder ergänzen weitere. Aus einfachen Klassen entstehen so immer komplexere<br />
Gebilde. Das hat Vorteile bei der Wiederverwendbarkeit und Wartbarkeit von<br />
Code. Ändert man Details einer Basisklasse, ändern sich die abgeleiteten Klassen<br />
automatisch mit, was Eingriffe in Code oft drastisch reduziert – entsprechend clever<br />
entworfene Modelle vorausgesetzt. Letzteres ist übrigens der Grund, warum<br />
Anfänger davon kaum profitieren. Ohne lange Erfahrung werden Modelle falsch<br />
oder unbrauchbar entworfen und statt des erhofften Designvorteils entsteht Chaos<br />
und Konfusion.<br />
1 Besser war im letzten Kapitel bereits als: »Schneller, Wartbarer, Lesbarer« definiert worden.
Syntax der Klassen<br />
Wenn Klassen Abhängigkeiten haben, so haben deren Objekte diese auch.<br />
Objekte können einander enthalten und Teil eines anderen sein – ebenso wie es<br />
die Klassenhierarchie vorschreibt. Da Objekte alles enthalten, was Code zum<br />
Ablauf braucht – Daten in Form von Eigenschaften und verarbeitbare Anweisungen<br />
in Form von Methoden – führen sie gewissermaßen ein Eigenleben.<br />
Ein reines objektorientiertes System kennt übriges ausschließlich Objekte. Das ist<br />
ein Paradigma der objektorientierten Sprachen – sie erzwingen eine derartige Programmierweise.<br />
Das heißt in der Praxis, dass auch die einfachste Ausgabe (wie mit<br />
echo) das Anlegen wenigstens einer Klasse erfordert. Soweit geht PHP5 nicht –<br />
imperative und objektorientierte Elemente können parallel verwendet werden.<br />
6.2 Syntax der Klassen<br />
Bevor so richtig objektorientiert gearbeitet werden kann, muss wenigstens eine<br />
Klasse definiert werden. Dieser Abschnitt führt in die grundlegenden Prinzipien<br />
der objektorientierten Programmierung ein.<br />
Eine Klasse definieren<br />
Objektorientierte Programmierung beginnt immer mit der Definition einer Klasse.<br />
<strong>In</strong> PHP5 erledigt dies das in vielen Sprachen verwendete Schlüsselwort class.<br />
Listing 6.1: Class<strong>In</strong>tro1.php (erster Teil) – Definition einer Klasse<br />
208<br />
Objektorientierte Programmierung<br />
Definiert wurde hier eine Klasse mit dem Namen FontElement. Später sollen daraus<br />
Objekte erzeugt werden, die -Tags erzeugen und ausgeben, vielfältig<br />
und flexibel formatiert, ohne dass HTML-Code geschrieben werden soll.<br />
Sie enthält drei Eigenschaften: $face, $size und $color, die mit Standardwerten<br />
belegt werden. Das Schlüsselwort public vor den Variablennamen deklariert sie als<br />
öffentliche Mitglieder. Dazu und zu Alternativen später mehr. Neben den Eigenschaften<br />
wurde auch eine Methode definiert: Output. Dies ist die einzige Aktion,<br />
die ausgeführt werden kann.<br />
Das Programm ist soweit zwar fehlerfrei lauffähig, tut jedoch gar nichts. Die Klasse<br />
erlaubt die Erzeugung von Objekten, dies ist aber hier noch nicht passiert.<br />
Ein Objekt erzeugen und benutzen<br />
Objekte können Aktionen ausführen. Dazu muss man erst über ein solches verfügen.<br />
<strong>In</strong> PHP5 erledigt dies das Schlüsselwort new:<br />
Listing 6.2: Class<strong>In</strong>tro1.php (zweiter Teil): Definition einer Klasse<br />
$myfont = new FontElement();<br />
echo 'Formatierte Ausgabe: ';<br />
$myfont->Output('Mustertext');<br />
Das neue Objekt wird in einer Variablen ($myfont) gespeichert. new erzeugt es und<br />
dazu wird ihm der Name der Klasse mitgeteilt (FontElement). Das neue Objekt<br />
kann nun sofort benutzt werden. Auf Eigenschaften und Methoden wird über die<br />
Verweissyntax -> zugegriffen.<br />
Freilich ist hier noch kein Vorteil gegenüber einem einfachen Funktionsaufruf zu<br />
erkennen, wie er am Tag 7 behandelt wurde. Tatsächlich bietet das objektorientierte<br />
Modell viel mehr.<br />
Eine Klasse erweitern<br />
Zuerst soll die Klasse erweitert werden. Dazu wird sie aber nicht einfach umgeschrieben,<br />
denn möglicherweise ist sie in dieser Form bereits woanders im Projekt<br />
im Einsatz. Besser ist es, von der Klasse zu erben und darauf aufbauend eine erweiterte<br />
Version zu erzeugen. Das erfolgt in PHP5 mit dem Schlüsselwort extends.
Syntax der Klassen<br />
Bei der Vorüberlegung, welche Struktur entstehen soll, ist leicht zu erkennen, dass<br />
das -Tag keine brauchbare Basis darstellt. Besser wäre es, erstmal über ein<br />
allgemeines »HTML-Element« zu verfügen und daraus speziellere Elemente<br />
abzuleiten. Ein solches Basiselement könnte nun folgendermaßen aussehen:<br />
Listing 6.3: Class<strong>In</strong>tro2.php – Eine flexible Basisklasse für HTML-Elemente<br />
210<br />
Objektorientierte Programmierung<br />
$element = new Element();<br />
$element->name = 'font';<br />
$element->style = 'font-size:22pt; font-weight:bold; color:red;';<br />
$element->selfClosed = FALSE;<br />
$element->Output('Mustertext');<br />
?><br />
Die Basisklasse ist offensichtlich in der Lage, alle Arten von Elementen zu erzeugen.<br />
Auf dieser Grundlage ist es nun leicht, spezialisiertere Klassen zu entwerfen,<br />
die den Aufwand zur Erzeugung neuer Elemente reduzieren.<br />
Listing 6.4: Class<strong>In</strong>tro3.php – FontElement profitiert von den Fähigkeiten von Element<br />
class FontElement extends Element<br />
{<br />
function __construct()<br />
{<br />
$this->name = 'font';<br />
$this->selfClosed = FALSE;<br />
}<br />
}<br />
$element = new FontElement();<br />
$element->style = 'font-size:22pt; font-weight:bold; color:red;';<br />
$element->Output('Mustertext');<br />
Der einzige Unterschied zur Klasse Element ist die Einführung eines so genannten<br />
Konstruktors, der bestimmte Eigenschaften fixiert.<br />
Konstruktoren und Destruktoren<br />
Immer wenn ein neues Objekt entsteht, also in dem Augenblick, wenn new ausgeführt<br />
wird, beginnt das Leben des Objekts in einem undefinierten Zustand. Sie<br />
haben bereits gesehen, wie das Objekt durch das Setzen der Eigenschaften nutzbar<br />
wird. Oft wird jedoch der undefinierte Zustand nie benötigt, sondern das Objekt<br />
soll von vornherein bestimmte Grundeigenschaften enthalten. Der Vorgang des<br />
Erzeugens und Definierens kann daher zusammengefasst werden. Das passiert<br />
durch die Einführung einer speziellen Methode, die im Augenblick der Erzeugung<br />
automatisch aufgerufen wird: des Konstruktors.<br />
Ein Konstruktor entsteht in PHP5 durch den reservierten Namen __construct.<br />
(Achtung! Das sind zwei Unterstriche vor dem Wort.) Im letzten Beispiel wurde
Syntax der Klassen<br />
dies benutzt, um dem Objekt vom Typ FontElement gleich die richtigen Eigenschaften<br />
mitzugeben.<br />
Ein Objekt existiert, wie alle anderen Variablen auch, bis zum Ende des Skripts<br />
oder bis es gezielt zerstört, also auf NULL gesetzt oder mit unset vernichtet wird.<br />
Normalerweise ergeben sich daraus keine Konsequenzen, PHP kümmert sich um<br />
das Aufräumen des belegten Speichers. Es gibt jedoch Anwendungsfälle, in denen<br />
externe Programme an der Kommunikation beteiligt sind, Datenbanken oder<br />
Dateien beispielsweise. Nun wäre es fatal, wenn ein Objekt eine Verbindung zur<br />
Datenbank herstellt und dann zerstört wird, während die Verbindung offen bleibt.<br />
Es entstehen verwaiste Verbindungen – Zombies. Verfügt eine Datenbank nur<br />
über primitive Kontrolltechniken oder eine begrenzte Anzahl von erlaubten Verbindungen,<br />
führt dies früher oder später zu Fehlern, die zudem sporadisch und<br />
schwer nachvollziehbar auftreten. Um das Verhalten am Ende der Existenz eines<br />
Objekts zu kontrollieren, werden Destruktoren eingesetzt. Sie werden unmittelbar<br />
vor der endgültigen Vernichtung aufgerufen.<br />
Ein Destruktor entsteht in PHP5 durch den reservierten Namen __destruct.<br />
(Noch einmal – das sind zwei Unterstriche vor dem Wort.)<br />
Verhalten der Konstruktoren und Destruktoren bei der Vererbung<br />
Bei der Vererbung wird die Sache etwas komplizierter. Denn jede Klasse in der<br />
Hierarchie kann einen eigenen Konstruktor 2 haben. Wenn nun ein Objekt erzeugt<br />
wird, könnten sich die Effekte der Konstruktoren überlagern. Deshalb führt PHP5<br />
nur den letzten Konstruktor aus. Hätte im letzten Beispiel auch die Klasse Element<br />
einen Konstruktor, so wäre dieser beim Aufruf von FontElement nicht ausgeführt<br />
worden. Es bleibt dem Entwickler überlassen, den Aufruf des Konstruktors der<br />
Elternklasse explizit zu erzwingen:<br />
function __construct()<br />
{<br />
parent::__construct();<br />
}<br />
Das reservierte Wort parent verweist auf die Klasse, von der mit extends geerbt<br />
wurde. Der Konstruktor wird über seinen Namen aufgerufen. Hinter parent darf<br />
nur mit dem statischen Verweisoperator :: gearbeitet werden, weil zum Zeitpunkt<br />
des Konstruktoraufrufs das Objekt noch nicht existiert und deshalb die Definition<br />
2 Für Destruktoren gilt das ebenso, auch wenn diese nicht immer explizit erwähnt werden.<br />
211
212<br />
Objektorientierte Programmierung<br />
direkt benutzt wird. Mehr zu statischen Mitgliedern und dem neuen Verweisoperator<br />
folgt im nächsten Abschnitt.<br />
Konstruktoren können Parameter wie jede andere Methode besitzen. Die Angabe<br />
erfolgt zusammen mit dem new-Operator hinter dem Namen der Klasse:<br />
$object = new SuperFontElement('color:red');<br />
Die Verwendung von Parametern ist meist sinnvoll, um dem künftigen Objekt<br />
zusätzlich individuelle Eigenschaften zu verpassen.<br />
Statische Mitglieder und Konstanten<br />
Statische Mitglieder von Klassen existieren nicht im Kontext des Objekts, sondern<br />
der Klasse. Damit teilen sich alle Objekte einer Klasse diese Mitglieder, ganz<br />
gleich ob es sich dabei um Eigenschaften oder Methoden handelt.<br />
Es sind viele Einsatzfälle denkbar:<br />
Nutzen Sie statische Mitglieder für Aufgaben, die keinen Bezug zu den spezifischen<br />
Daten eines Objekts haben, beispielsweise Umrechnungen.<br />
Statische Mitglieder erlauben die Implementierung von Verweiszählern, wie<br />
beispielsweise Eigenschaften, die allen Objekten gleich sind.<br />
Statische Mitglieder erlauben den direkten Aufruf ohne vorherige <strong>In</strong>stanziierung.<br />
Dies ist sinnvoll, wenn man ohnehin nur ein Objekt benötigt.<br />
Ein Zähler, der die Anzahl der bereits erzeugten Objekte ermittelt, kann beispielsweise<br />
folgendermaßen implementiert werden:<br />
Listing 6.5: ClassStatic.php – Die Anzahl von Objektinstanzen ermitteln<br />
Syntax der Klassen<br />
}<br />
}<br />
$obj1 = new ElementCounter();<br />
$obj2 = new ElementCounter();<br />
$obj3 = new ElementCounter();<br />
printf('Es wurden %s Objekte erzeugt.', $obj3->GetObjectNumber());<br />
?><br />
Der Trick besteht hier im Schlüsselwort static, das die Variable $counter in den<br />
Klassenkontext überführt. Diese Variable existiert für alle Objekte nur einmal.<br />
Wird nun ein neues Objekt erzeugt, wird der Wert im Konstruktor um Eins<br />
erhöht. Beachten Sie hier, dass der Verweis mit $this nicht gelingen kann, weil es<br />
kein Objekt- sondern ein Klassenverweis ist, der benötigt wird. Für letzteren wird<br />
der Klassenname benutzt, gefolgt vom statischen Verweisoperator :: und dem vollständigen<br />
Namen der Variablen, inklusive dem $-Zeichen. Das gilt sowohl für<br />
Zugriffe innerhalb der Klasse, wie im Beispiel gezeigt, als auch für externe. Dabei<br />
gelingen externe Zugriffe nur, wenn kein oder der Modifizierer public verwendet<br />
wird.<br />
Ein sehr typisches Designelement in der objektorientierten Programmierung sind<br />
so genannte Singleton-Klassen. Dies sind Klassen, die nur ein einziges Objekt<br />
erzeugen dürfen. Oft sind auch in objektorientierten Ansätzen nämlich nicht Dutzende<br />
Objekte im Spiel. Ein imperativer Ansatz würde jedoch das Konzept der<br />
Wiederverwendbarkeit stören. Der Ausweg sind Singleton-Klassen. Um dies zu<br />
sichern, benötigt man zwei Techniken. Zum einem muss der Konstruktor »versteckt«<br />
werden. Dies erfolgt mit dem Schlüsselwort private. So wird verhindert,<br />
dass der verwendende Code fälschlicherweise mit new weitere Objekte erzeugt.<br />
Stattdessen wird eine statische Methode angeboten, die die <strong>In</strong>stanziierung übernimmt<br />
und dabei prüft, ob bereits ein Objekt existiert. Der erneute Aufruf von new<br />
referenziert dann immer wieder dasselbe Objekt.<br />
Listing 6.6: ClassSingleton.php – Volle Kontrolle über die Objekterzeugung<br />
2<strong>14</strong><br />
{<br />
Objektorientierte Programmierung<br />
Singleton::$instance = new Singleton();<br />
}<br />
return Singleton::$instance;<br />
}<br />
function setText($text)<br />
{<br />
$this->text = $text;<br />
}<br />
function getText()<br />
{<br />
return $this->text;<br />
}<br />
}<br />
class Hello<br />
{<br />
function __construct()<br />
{<br />
$single = Singleton::instance();<br />
$single->setText('Hallo Welt!');<br />
}<br />
}<br />
class Goodbye<br />
{<br />
function __construct()<br />
{<br />
$single = Singleton::instance();<br />
$single->setText('<strong>Und</strong> tschüß...');<br />
}<br />
}<br />
$single = Singleton::instance();<br />
echo ( $single->getText().'' );<br />
$hello = new Hello();<br />
echo ( $single->getText().'' );<br />
$hello = new Goodbye();<br />
echo ( $single->getText().'' );<br />
?><br />
Das Beispiel nutzt in jeder Klasse, die Singleton verwendet, immer wieder dasselbe<br />
Objekt, weil eine erneute Erzeugung im Rahmen eines einfachen Methodenaufrufs<br />
völlig sinnlos wäre – man hätte am Ende nur eine Anzahl verwaister Objekte<br />
im Speicher. <strong>In</strong> der Praxis wird diese Technik benutzt, um Ressourcen schonend
Syntax der Klassen<br />
zu programmieren. Verbindungen zu Ressourcen, die nur einmalig vorhanden<br />
sind, wie beispielsweise Dateien, werden über Singleton-Klassen verwaltet.<br />
Konstanten in Objekten<br />
Konstanten können auch in Objekten verwendet werden. Vor PHP5 waren Konstanten<br />
immer global, was den Einsatz etwas problematisch machte, weil leicht<br />
Namenskonflikte auftraten. Da sich Konstanten von Objekt zu Objekt nicht<br />
ändern, verhalten sie sich wie statische Mitglieder und werden auch genau wie<br />
diese verwendet. Der einzige Unterschied besteht darin, dass sich der <strong>In</strong>halt zur<br />
Laufzeit nicht verändern lässt.<br />
Im Gegensatz zu den globalen Konstanten, die mit define erzeugt werden, erfolgt<br />
die Vereinbarung in Klassen mit dem Schlüsselwort const:<br />
const WIDTH = 800;<br />
Da Konstanten zwangsläufig in allen <strong>In</strong>stanzen identisch sind, verhalten sie sich<br />
wie statische Mitglieder. Der einzige Unterschied besteht darin, dass sie nicht<br />
geschrieben werden können – außer bei der Definition. Diese Definition erfolgt<br />
auf Ebene der Klasse, wo auch alle anderen Eigenschaften, Methoden und Variablen<br />
definiert werden.<br />
Zugriffskontrolle<br />
Meins, deins, für alle: private, public, protected<br />
Generell sind in PHP5 alle Variablen global. Definitionen innerhalb einer Funktion<br />
sind dort lokal. Zum Übergang benutzt man das Schlüsselwort global. <strong>In</strong> der<br />
objektorientierten Programmierung ist das bei weitem nicht genug. PHP5 bietet<br />
hier gleich drei neue Schlüsselwörter für den Variablenschutz:<br />
public<br />
Der Standardwert hat nun sein eigenes Schlüsselwort. Derart deklarierte Variablen<br />
oder Methoden sind im gesamten Skript sichtbar. Aus Gründen der<br />
Abwärtskompatibilität kann die Angabe entfallen.<br />
private<br />
Die so deklarierte Variable oder Methode ist nur innerhalb der Klasse sichtbar,<br />
abgeleitete Klassen oder Aufrufe von Objekten können nicht zugreifen.<br />
215
216<br />
Objektorientierte Programmierung<br />
protected<br />
Dieses Schlüsselwort macht eine Variable oder Methode in den eigenen und<br />
abgeleiteten Klassen sichtbar, aber nicht darüber hinaus (mehr als private und<br />
weniger als public als Kurzformel).<br />
Wenn Sie sich überlegen, wie die Deklaration konkret erfolgen soll, versuchen Sie<br />
zuerst so restriktiv wie möglich zu deklarieren, also alles private zu machen. Dann<br />
ändern Sie gezielt die Werte, die unbedingt woanders im Zugriff sein müssen.<br />
Generell ist es jedoch ein gute Idee, den Zugriffsweg zu kontrollieren. So könnte<br />
man eine Klasse mit einer öffentlichen Eigenschaft immer folgendermaßen erstellen:<br />
class Test<br />
{<br />
public $sTest;<br />
}<br />
Besser ist es, den Weg der Daten in die Variablen und hinaus zu kontrollieren:<br />
class Test<br />
{<br />
private $sTest;<br />
public GetTestString()<br />
{<br />
return $this->sTest;<br />
}<br />
public SetTestString($st)<br />
{<br />
$this->sTest = $st;<br />
}<br />
}<br />
Es ist nun leicht, in den Zugriffsmethoden Prüfungen und ein ordentliches Fehlermanagement<br />
einzubauen, was ein öffentlicher Zugriff auf eine Mitgliedsvariable<br />
nicht erlaubt.<br />
Finale Klassen und Methoden: final<br />
Dass sich Klassen leicht vererben lassen, wurde anhand des Schlüsselworts extends<br />
bereits gezeigt. Manchmal soll dies aber nicht so sein, entweder für eine Klasse als<br />
solche oder auch nur für einzelne Methoden. Denn manche Methoden sind für<br />
die Funktion der Objekte von elementarer Bedeutung. Gelingt der Schutz mit
Syntax der Klassen<br />
private nicht, weil der Zugriff von außen anderweitig benötigt wird, muss das<br />
Überschreiben verhindert werden. Dies erledigt das Schlüsselwort final. Von<br />
einer so gekennzeichneten Klasse kann nicht geerbt werden, bei »finalen« Methoden<br />
ist das Überschreiben verboten.<br />
Aufforderung zur Implementierung: abstract<br />
Werden Klassen oder Methoden als abstract gekennzeichnet, wird der Benutzer<br />
explizit dazu aufgefordert, hier eigenen Code zu schreiben. Praktisch ist dies das<br />
Gegenteil zu final – statt dem ausdrücklichen Verbot folgt nun das ausdrückliche<br />
Gebot. Der Schreiber der Klassen gibt damit Struktur, Namen und Aufbau vor,<br />
nicht jedoch die konkrete Implementierung, weil dies möglicherweise von der<br />
Anwendung abhängt.<br />
Eine Ableitung von Objekten von abstrakten Klassen ist nicht möglich. Es muss<br />
deshalb immer eine Implementierung erfolgen. Das gilt auch für abstrakte Methoden.<br />
Es ist jedoch möglich, eine Klasse als abstrakt zu definieren und einige der<br />
Methoden bereits voll auszuformulieren. Das folgende Beispiel zeigt dies. Während<br />
eine <strong>In</strong>stanziierung der Klasse Senior nicht gelingt, ist diese mit Junior möglich.<br />
Auch der Aufruf der in der abstrakten Klasse Senior (als nicht abstrakt)<br />
definierten Methode GetText gelingt wie erwartet:<br />
Listing 6.7: ClassAbstract.php – Abstrakte Klassen können nur über extends benutzt werden<br />
<br />
217
218<br />
Objektorientierte Programmierung<br />
Um den Benutzer der Klasse zu zwingen, auch die Methode GetText selbst zu<br />
schreiben, wird diese ebenfalls als abstract definiert und der <strong>In</strong>halt entfernt:<br />
abstract class Senior<br />
{<br />
protected $text = 'Hallo Senior!';<br />
abstract function GetText();<br />
}<br />
PHP5 reagiert mit zwei typischen Fehlermeldungen, wenn die Zuordnung hier<br />
nicht stimmt. Der erste Fall tritt auf, wenn eine abstrakte Methode Code enthält:<br />
Abbildung 6.1:<br />
Es wurde versucht, eine abstrakte Methode zu definieren<br />
Der zweite Fall tritt auf, wenn vergessen wurde, eine abstrakte Methode in einer<br />
abgeleiteten Klasse zu implementieren:<br />
Abbildung 6.2:<br />
Eine abstrakte Methode wurde nicht implementiert<br />
An dieser Stelle sei bereits auf Schnittstellen – <strong>In</strong>terfaces genannt – verwiesen die<br />
eine ähnliche Rolle wie abstrakte Klassen übernehmen. Mehr dazu finden Sie im<br />
Abschnitt 6.3 »Schnittstellen zur Außenwelt«.<br />
Klonen erlaubt: clone und die Methode __clone()<br />
Klonen ist ein typischer Vorgang in der objektorientierten Welt. Bislang wurde in<br />
PHP immer eine vollständige Kopie eines Objekts übergeben, wenn die Zuweisung<br />
an eine andere Variable erfolgte. Damit sind natürlich auch Verweise, die das<br />
ursprüngliche Objekt hatte, Teil der Kopie. Das kann gewollt sein, meist ist es<br />
jedoch so, dass man eher mit einem einzigen Verweis, beispielsweise auf eine<br />
Datenbank oder Datei, arbeiten will, unabhängig von der Anzahl der Objekte. Die<br />
folgende Version erlaubt mehr Kontrolle über den Kopiervorgang:<br />
$clone = clone $object;
Syntax der Klassen<br />
Dies entspricht dem Aufruf der Methode __clone() des Quellobjekts. Sie können<br />
nun diese Methode (mit exakt diesem Namen) anlegen und den Kopiervorgang<br />
kontrollieren. Oder Sie können den internen Mechanismus nutzen, der alle<br />
Eigenschaften des Quellobjekts kopiert.<br />
Betrachten Sie zuerst folgenden Code:<br />
Listing 6.8: ClassClone.php – Klassen und direkte Verwendung von Objektkopien<br />
class CloneClass<br />
{<br />
var $id;<br />
}<br />
$o1 = new CloneClass();<br />
$o1->id = "1";<br />
$o2 = $o1;<br />
$o2->id = "2";<br />
echo 'ID: ' . $o1->id;<br />
Die Ausgabe zeigt, dass die beiden Objekte $o1 und $o2 auf dasselbe Basisobjekt<br />
verweisen:<br />
ID: 2<br />
Anders sieht es aus, wenn die Zuweisung den clone-Befehl nutzt:<br />
Listing 6.9: CloneClass2.php – Der Klon enthält eine Kopie aller Eigenschaften<br />
class CloneClass<br />
{<br />
var $id;<br />
}<br />
$o1 = new CloneClass();<br />
$o1->id = "1";<br />
$o2 = clone $o1;<br />
$o2->id = "2";<br />
echo 'ID: ' . $o1->id;<br />
Dann wird ein unabhängiges Objekt instanziiert und die Zuweisung zu $o1 wirkt<br />
sich in $o2 nicht mehr aus:<br />
ID: 1<br />
Das Verhalten der Klonierung kann mit __clone() beeinflusst werden. Diese<br />
Methode existiert intern und kopiert alle Eigenschaften. Man kann sie jedoch<br />
überschreiben und dann entscheiden, welche Eigenschaften zu kopieren sind.<br />
219
220<br />
Objektorientierte Programmierung<br />
Listing 6.10: CloneClass3.php – Beeinflussung des Klonvorgangs über __clone()<br />
class CloneClass<br />
{<br />
var $x;<br />
var $y;<br />
function __clone()<br />
{<br />
$this->y = 300;<br />
}<br />
}<br />
$o1 = new CloneClass();<br />
$o1->x = "100";<br />
$o1->y = "100";<br />
$o2 = clone $o1;<br />
echo "XY: {$o2->x} x {$o2->y}";<br />
Hier wird die Methode __clone() benutzt, um die Übertragung der Eigenschaften<br />
zu beeinflussen.<br />
Der in frühen Betaversionen kolportierte Zugriff über $that auf das zu<br />
klonende Objekt war seit der RC1 nicht mehr verfügbar. Abgesehen von<br />
der mangelnden Flexibilität erscheint der Verlust kruder Syntax verschmerzbar.<br />
<strong>In</strong>sgesamt bleibt der Eindruck, dass ein wichtiges OOP-<br />
Feature miserabel implementiert wurde.<br />
6.3 Schnittstellen zur Außenwelt<br />
Nicht jeder schreibt PHP-Skripte nur für sich selbst. Entwickler von Bibliotheken<br />
– ganz gleich ob für die Öffentlichkeit oder das eigene Team – benötigen ganz spezielle<br />
Techniken, um lesbaren, klar strukturierten und wieder verwendbaren Code<br />
zu schreiben. Schnittstellen (<strong>In</strong>terfaces) sind eine sehr gute Methode dafür. Sie<br />
sind neu in PHP5 eingeführt.
Schnittstellen (<strong>In</strong>terfaces)<br />
Schnittstellen zur Außenwelt<br />
Schnittstellen bieten eine gute Möglichkeit, die Verwendung eigener Klassen zu<br />
kontrollieren. Ähnlich wie abstrakte Klassen bieten <strong>In</strong>terfaces nur eine Definition<br />
der Struktur an, sie enthalten jedoch keinen Code. Dem potenziellen Nutzer einer<br />
Bibliothek bieten sie die Chance, einen »gefilterten« Blick auf die Klassen zu werfen,<br />
die die Bibliothek anbietet. Es kann nämlich aus technischen Gründen erforderlich<br />
sein, bestimmte Teile der Bibliothek als öffentlich zu kennzeichnen.<br />
Wenn nun in einem anderen Teil des Projekts Ableitungen von Klassen erfolgen,<br />
so sollte sich die Verwendung an bestimmte Regeln halten. Aus der Tatsache, dass<br />
Methoden oder Eigenschaften öffentlich sind, folgt nicht zwingend, dass diese zur<br />
Implementierung geeignet sind. Die <strong>In</strong>formation darüber, was wirklich »öffentlich«<br />
im Sinne der freien Verwendung ist, definiert eine Schnittstelle. Der Name<br />
ist Programm – es handelt sich um eine Vereinbarungsschnittstelle zwischen dem<br />
ursprünglichen Entwickler der Klasse und dem Entwickler, der die Klasse später<br />
verwendet. Zielgruppe sind also vor allem Programmierer, die Bibliotheken schreiben,<br />
welche in Projekten angepasst und modifiziert werden sollen.<br />
Schlüsselwörter<br />
Wenn Sie kleinere Projekte erstellen und keinen Code anderen Entwicklern<br />
zur Verfügung stellen, ist die Verwendung von Schnittstellen<br />
meist nicht angebracht. Die Tatsache, dass PHP5 diese Funktionen bietet,<br />
soll nicht dazu verleiten, sie unbedingt nutzen zu müssen.<br />
Die Definition einer Schnittstelle wird mit dem Schlüsselwort interface eingeleitet.<br />
Es darf keine Eigenschaften enthalten und von allen Methoden darf nur der<br />
»Kopf« geschrieben werden; direkt abgeschlossen mit einem Semikolon, statt der<br />
geschweiften Klammern.<br />
Bei der Implementierung wird wie bei der Klassenvererbung vorgegangen, anstatt<br />
extends wird jedoch das Schlüsselwort implements verwendet.<br />
Herkunft und Mitgliedschaft<br />
Beim Umgang mit komplexen Klassenhierarchien ist es oft notwendig, bei einem<br />
Objekt zu ermitteln, von welcher Klasse es abstammt. Im Beispiel der HTML-Element-Klassen<br />
lässt sich leicht eine solche Hierarchie entwickeln:<br />
221
Element<br />
StylableElement<br />
ContainerElement<br />
222<br />
Objektorientierte Programmierung<br />
FontElement<br />
Die oberste Ebene definiert nur Elemente als solche, beispielsweise mit den<br />
Eigenschaften Tag-Name und ID. Danach folgen weitere, speziellere Eigenschaften<br />
bis hin zu einem konkreten HTML-Element (FontElement). Bei der Untersuchung,<br />
ob ein Objekt nun ein Element aus der Hierarchie ist, wäre eine<br />
Erkennung sehr aufwändig, weil es Dutzende HTML-Elemente gibt, die in verschiedenen<br />
Stufen der Hierarchie stehen. Gleichzeitig stellt sich die Frage, was an<br />
diesem Element öffentlich verwendet werden kann. Die Vereinbarung darüber<br />
trifft man mit Hilfe einer Schnittstelle, beispielsweise IElement genannt.<br />
Listing 6.11: Class<strong>In</strong>terface.php – Schnittstellen definieren und implementieren<br />
Schnittstellen zur Außenwelt<br />
echo " ist ein Element";<br />
}<br />
?><br />
Das Beispiel definiert zuerst eine einfache Schnittstelle, die die Grundstruktur<br />
aller Elemente enthält. Anschließend wird die erste Ebene der Implementierung<br />
vorgenommen. Diese Klasse ist als abstrakt gekennzeichnet, weil ein »Element«<br />
ohne weitere Definition seiner Eigenschaften unsinnig ist. Der Benutzer wird also<br />
gezwungen, seine eigenen Elemente darauf aufbauend zu implementieren. Das<br />
letzte Element in der Kette (die Zwischenstufen StylableElement und Container-<br />
Element wurden hier zur Vereinfachung weggelassen) ist dann als final markiert,<br />
weil es keine Varianten von gibt. Der Konstruktor legt die typischste Eigenschaft<br />
des Elements fest, den Namen. Anschließend prüft das Skript, ob das Element<br />
von einer bestimmten Schnittstelle abstammt:<br />
if ($font instanceof IElement)<br />
Ohne die Schnittstelle müsste man hier auf alle Element-Klassen prüfen, was<br />
sicher deutlich aufwändiger ist. Außerdem kann man der Schnittstelle per Reflektion<br />
(siehe dazu Abschnitt 9.3 »Code röntgen: Die Reflection-API«. Die zulässigen<br />
öffentlichen Methoden entlocken, was die Gestaltung der abstrakten Basisklassen<br />
vereinfacht.<br />
Der Operator instanceof funktioniert freilich auch mit Klassen, er zeigt lediglich<br />
an, dass ein Objekt von einer bestimmten Klasse oder Schnittstelle abstammt, ohne<br />
Rücksicht auf die Vererbungskette. Alternativ kann die Funktion is_a verwendet<br />
werden, die jedoch nicht so gut lesbaren Code ergibt:<br />
if (is_a($font, 'IElement'))<br />
<strong>In</strong>formationen über Klassen und Objekte<br />
Typ-<strong>In</strong>formationen<br />
PHP5 ist, wie alle Vorgängerversionen, seiner Tradition als Skriptsprache treu<br />
geblieben. Dazu gehört neben anderen Merkmalen auch der Verzicht auf typisierte<br />
Variablen. PHP5 legt intern selbst fest, welchen Datentyp eine Variable<br />
annimmt oder eine Funktion zurückgibt. Es gibt zwar die bereits behandelten<br />
Umwandlungsfunktionen, letztlich besteht aber kein Typzwang, wie er in »richtigen«<br />
Programmiersprachen üblich ist.<br />
223
224<br />
Objektorientierte Programmierung<br />
Bei kleineren Projekten ist der fehlende Zwang zur Deklaration (und Einhaltung)<br />
des Typs meist zu tolerieren, weil sich Fehler mit überschaubarem Aufwand finden<br />
lassen. Werden jedoch größere Bibliotheken entworfen, ist die Fehlersuche<br />
ungleich schwerer und oft erst mit Hilfe spezieller Testapplikationen zu bewerkstelligen.<br />
An dieser Stelle fehlt dann ein Typzwang, der zumindest elementare<br />
Zuweisungs- und Konvertierfehler bereits auf der Parserebene verhindert.<br />
Da die Programmierung in objektorientierter Art und Weise mit den hier vorgestellten<br />
neuen Möglichkeiten von PHP5 zunimmt, wird sich der fehlende Typzwang<br />
umso drastischer auf die Qualität des Codes auswirken – im negativen<br />
Sinne. Um das ein wenig zu umgehen, wird die Typisierung quasi über die Hintertür<br />
und nur für selbst definierte Klassen eingeführt. Erstaunlich inkonsequent,<br />
wenn man bedenkt, dass nun dafür eine Syntax zur Verfügung steht, gleichwohl<br />
aber nicht für eingebaute Typen verwendet werden darf.<br />
Einzig für eigene als Klasse definierte Typen besteht die Möglichkeit, bei der<br />
Angabe von Parametern einen »Typ-Hinweis« mitzugeben. Dies unterstützt die<br />
Nutzbarkeit von Objekten, die aus einer Hierarchie entstammen.<br />
Eigene Fehlerklassen erstellen<br />
Bereits im letzten Kapitel war die neue Fehlerbehandlung ein Thema. Richtig leistungsfähig<br />
wird man damit aber erst, wenn man eigene Fehlerklassen erstellen<br />
kann. Dazu sind freilich Kenntnisse der objektorientierten Programmierung erforderlich,<br />
weshalb dieser Teil erst jetzt folgt.<br />
Eigene Fehlerklassen basieren immer auf einer Vererbung der internen Fehlerklasse<br />
Exception. Dies verschafft der neuen Klasse einen Satz an Basismethoden,<br />
die sehr hilfreich sind.<br />
Zuerst ist ein Blick auf die eingebauten Methoden hilfreich, wie ihn das folgende<br />
Beispiel ermöglicht:<br />
Listing 6.12: TryCatchBasis.php – Die Leistungen der Exception-Klasse testen<br />
try<br />
{<br />
throw new Exception('Das ist ein provozierter Fehler', 12345);<br />
}<br />
catch (Exception $e)<br />
{
}<br />
Schnittstellen zur Außenwelt<br />
echo ('Fehlermeldung: ' . $e->getMessage() . '' );<br />
echo ('Fehlercode: ' . $e->getCode() . '' );<br />
echo ('Skriptname: ' . $e->getFile() . '' );<br />
echo ('Zeilennnummer: ' . $e->getLine() . '' );<br />
Die Ausgabe zeigt, dass die wichtigsten <strong>In</strong>formationen vorliegen, erzeugt durch die<br />
folgenden Methoden:<br />
getMessage<br />
Die eigentliche Fehlermeldung, am Ort der <strong>In</strong>stanziierung übergeben.<br />
getCode<br />
Der Fehlercode, frei wählbar und hilfreich, um weiterführende <strong>In</strong>formationen<br />
abzurufen.<br />
getFile<br />
Die Datei, in der der Fehler auftrat. Das ist das aktuelle Skript oder eine mit<br />
include oder require eingebundene Datei.<br />
getLine<br />
Die Zeile, in der throw ausgelöst wurde. Dies ist hilfreich bei der Fehlersuche,<br />
wenn man clever genug ist, mögliche Tests und throw eng beieinander zu<br />
halten.<br />
Listing 6.13: TryCatchExceptionClass.php – Eigene Exception-Implementierung<br />
226<br />
Objektorientierte Programmierung<br />
}<br />
}<br />
function divide($by)<br />
{<br />
if ($by == 0)<br />
{<br />
throw new DivideByZeroError (' Division durch Null');<br />
}<br />
return 1 / $by;<br />
}<br />
$test = 0;<br />
try<br />
{<br />
echo (divide($test));<br />
}<br />
catch (DivideByZeroError $ex)<br />
{<br />
echo $ex->getCode() . ' ' . $ex->getMessage();<br />
}<br />
?><br />
Um eigene Fehlerklassen zu schreiben, müssen Sie lediglich von der Basisklasse<br />
Exception ableiten:<br />
class DivideByZeroError extends Exception<br />
Es steht Ihnen dann frei, das Verhalten der Basisklasse zu modifizieren. Im Beispiel<br />
wird der Konstruktor überschrieben und der Aufruf des Konstruktors der<br />
Basisklasse mit eigenen Fehlertexten ergänzt:<br />
function __construct($message)<br />
{<br />
parent::__construct(self::PREFIX.$message, self::CODE);<br />
}<br />
Beliebt ist auch das Überschreiben von __toString. Diese Methode liefert eine<br />
Zeichenkettenentsprechung der Klasse und verkürzt die Ausgabe der Meldung.<br />
Beachten Sie außerdem die Syntax des Aufrufs des Konstruktors der Basisklasse mit<br />
parent::.<br />
Die Anwendung ist einfach. Haben Sie im Code einen Fehler festgestellt, wird<br />
eine <strong>In</strong>stanz der Fehlerklasse mit new erzeugt und mittels throw ausgelöst:<br />
throw new DivideByZeroError (' Division durch Null');
Schnittstellen zur Außenwelt<br />
Es ist jetzt Aufgabe des Hauptprogramms, den Block von Code mittels try zu<br />
umschließen, wo der Fehler auftreten könnte:<br />
try<br />
{<br />
echo (divide($test));<br />
}<br />
Dann ist für jeden denkbaren Fehler (im Beispiel kann nur ein Fehler ausgelöst<br />
werden) der passende catch-Zweig anzugeben:<br />
catch (DivideByZeroError $ex)<br />
<strong>In</strong>nerhalb des catch-Blocks kann dann auf den Fehler reagiert werden, beispielsweise<br />
durch Ausgabe einer Fehlermeldung:<br />
echo $ex->getCode() . ' ' . $ex->getMessage();<br />
Die Ausgabe zeigt, wie die modifizierten Daten der selbst definierten Fehlerklasse<br />
arbeiten:<br />
Im nächsten Abschnitt zu __get und __set (siehe Seite 229) finden Sie eine weitere<br />
Anwendung.<br />
Spezielle Zugriffsmethoden für Klassen<br />
Einige spezielle Zugriffsmethoden für Klassen erlauben sehr flexible und kompakte<br />
Lösungen.<br />
Der universelle Methodenaufruf mit __call()<br />
Abbildung 6.4:<br />
Spezifische Fehlermeldung, mittels try/catch verwaltet<br />
Eine Funktion mit dem reservierten Namen __call wird immer dann aufgerufen,<br />
wenn keine andere Funktion mit dem benutzten Namen existiert. Als Parameter<br />
werden der Name der versuchsweise aufgerufenen Funktion und ein Array mit den<br />
verwendeten Parametern übergeben. Theoretisch könnte man alle Methoden – bis<br />
auf den Konstruktor – hier bündeln. Praktisch dürfte es sich eher um Ausnahmen<br />
handeln, weil die exzessive Anwendung die Lesbarkeit beeinträchtigt.<br />
227
228<br />
Objektorientierte Programmierung<br />
Das folgende Beispiel zeigt die Anwendung. Es speichert Namen und gibt diese<br />
über Methodenaufrufe in verschiedenen Versionen wieder aus:<br />
Listing 6.<strong>14</strong>: ClassCall.php – Methodenaufrufe über die Sammelstelle __call()<br />
<br />
Das Beispiel zeigt, dass Code durchaus kompakter werden kann. Voraussetzung ist<br />
jedoch, dass der <strong>In</strong>halt der so zusammengefassten Methoden überschaubar ist. Bei<br />
den hier zur Demonstration eingesetzten Einzeilern ist dies sicher ideal.<br />
Abbildung 6.5:<br />
Ausgabe des Skripts ClassCall.php
Schnittstellen zur Außenwelt<br />
Der universelle Eigenschaftenaufruf mit __get() und __set()<br />
<strong>In</strong> ganz engem Zusammenhang mit dem universellen Methodenaufruf steht die<br />
Bildung von Eigenschaften. Echte Eigenschaften gab es bislang in PHP nicht, die<br />
Bereitstellung von öffentlichen Variablen reicht dazu allein nicht aus. Eigenschaften<br />
erlauben es nämlich, die Daten, die hinein geschrieben werden, bei der<br />
Zuweisung oder Rückgabe zu kontrollieren. Ebenso kann vor dem Abruf der<br />
Daten eingegriffen werden. Im Sinne einer sicheren Programmierung ist es nun<br />
möglich, den Datenfluss besser zu kontrollieren. Allerdings stellt PHP keinen dedizierten<br />
Weg zur Bildung von Eigenschaften bereit, wie dies andere Sprachen tun,<br />
sondern nur einen universellen über die Pseudoeigenschaftsmethoden __get<br />
(Lesen) und __set (Schreiben).<br />
Das folgende Beispiel zeigt eine Anwendung, in der verhindert wird, dass der Aufrufer<br />
versehentlich den Namen löscht und – falls dies trotzdem passiert ist – daran<br />
gehindert wird einen leeren Namen zu lesen. Diese Klasse ist quasi »gentlemanlike«<br />
programmiert. Sie akzeptiert alles und liefert dennoch nur sinnvolle Werte<br />
zurück. Damit eventuelle Fehlermeldungen auch einen gesicherten Weg aus der<br />
Klasse herausfinden, werden zwei eigene Exception-Klassen benutzt und die Fehler<br />
mit try/catch abgefangen:<br />
Listing 6.15: ClassProperties.php – Kontrolle über den Datenfluss mit Eigenschaften<br />
230<br />
{<br />
}<br />
Objektorientierte Programmierung<br />
switch ($property)<br />
{<br />
case 'Name':<br />
if (preg_match('~^[A-Z][a-z]{3,}$~', $this->name))<br />
{<br />
return $this->name;<br />
}<br />
else<br />
{<br />
throw new NameException('Name hat falsches Format: '<br />
. $this->name);<br />
}<br />
break;<br />
case 'Age':<br />
if (preg_match('~^\d{1,2}$~', $this->age))<br />
{<br />
return $this->age;<br />
}<br />
else<br />
{<br />
return 0;<br />
}<br />
break;<br />
}<br />
public function __set($property, $value)<br />
{<br />
switch ($property)<br />
{<br />
case 'Name':<br />
if (preg_match('~^[A-Z][a-z]{3,}$~', $value))<br />
{<br />
$this->name = $value;<br />
}<br />
else<br />
{<br />
throw new NameException('Name hat falsches Format: '<br />
. $value);<br />
}<br />
break;
Schnittstellen zur Außenwelt<br />
case 'Age':<br />
if (preg_match('~^\d{1,2}$~', $value))<br />
{<br />
$this->age = $value;<br />
}<br />
else<br />
{<br />
throw new AgeException('Alter hat falsches Format: '<br />
. $value);<br />
}<br />
break;<br />
}<br />
}<br />
}<br />
$m1 = new Member();<br />
try<br />
{<br />
$m1->Name = 'Mustermann';<br />
$m1->Age = 43;<br />
echo "{$m1->Name} ist {$m1->Age} alt.";<br />
}<br />
catch (NameException $nex)<br />
{<br />
echo $nex->getMessage();<br />
}<br />
catch (AgeException $aex)<br />
{<br />
echo $aex->getMessage();<br />
}<br />
?><br />
Die beiden Exception-Klassen unterscheiden sich nicht vom Original, sie werden<br />
lediglich benutzt, um die Fehlermeldungen selbst mittels catch auseinander<br />
zuhalten. Entsprechend bescheiden sieht die Definition aus:<br />
class AgeException extends Exception<br />
{<br />
}<br />
Die eigentliche Klasse definiert für die Eigenschaften sowohl __get als auch __set.<br />
Mittels einer switch-Anweisung werden zwei Eigenschaften gebildet, name und<br />
age. <strong>In</strong> beiden Fällen werden die Daten mittels regulärer Ausdrücke geprüft. Entsprechen<br />
sie nicht den erwarteten Wertebereichen, wird mittels throw new eine<br />
231
232<br />
Objektorientierte Programmierung<br />
spezifische Ausnahme ausgelöst. Die fest programmierten Daten des Musterskripts<br />
funktionieren. Die folgende Zeile sorgt für die Ausgabe:<br />
echo "{$m1->Name} ist {$m1->Age} alt.";<br />
Diese Zeile wird nicht erreicht, wenn die Daten nicht den Erwartungen entsprechen.<br />
Beträgt beispielsweise die Altersangabe 112, wird bei der Zuweisung (__set)<br />
die Ausnahme AgeException ausgelöst:<br />
throw new AgeException('Alter hat falsches Format: ' . $value);<br />
Der try-Zweig bricht dann sofort ab und PHP sucht nach einem passenden catch:<br />
catch (AgeException $aex)<br />
Hier kann nun eine Fehlerbehandlung stattfinden. Im einfachsten Fall besteht<br />
diese aus einer schlichten Ausgabe der Fehlermeldung:<br />
echo $aex->getMessage();<br />
Abbildung 6.6:<br />
Ausgabe, wenn die Daten gültig waren<br />
Abbildung 6.7:<br />
Ausgabe, wenn die Daten fehlerhaft sind<br />
Auch wenn die Eigenschaftsmethoden __get und __set auf den ersten<br />
Blick unsinnig erscheinen, entfalten sie jedoch im Zusammenspiel mit<br />
anderen neuen OOP-Techniken eine hohe Leistungsfähigkeit. Dies<br />
unterstützt vor allem die sichere, saubere Programmierung und damit<br />
stabilere Software.<br />
6.4 Analyse und Kontrolle von Objekten<br />
Der Analyse und Kontrolle von Objekten kommt vor allem bei der Fehlersuche<br />
eine große Bedeutung zu. Darüber hinaus kann man Bibliotheken universeller<br />
und sicherer aufbauen, wenn die entsprechenden Techniken zum Einsatz kommen,<br />
die in diesem Abschnitt beschrieben werden.
__METHOD__<br />
Analyse und Kontrolle von Objekten<br />
Die Konstante – gedacht als Erweiterung der bereits in PHP3 und 4 benutzten<br />
__LINE__ und __FILE__ – enthält den Namen der aktuellen Methode. Vor allem<br />
zur Fehlersuche ist die Angabe durchaus geeignet.<br />
Beachten Sie, dass es sich in allen Fällen um zwei Unterstriche vor und<br />
nach dem Namen handelt.<br />
Listing 6.16: ClassMETHOD.php – <strong>In</strong>formationen über die Methoden einer Klasse<br />
class BaseClass<br />
{<br />
public $x;<br />
public $y;<br />
public function SetX($x)<br />
{<br />
$this->x = $x;<br />
echo __METHOD__ . '';<br />
}<br />
public function SetY($y)<br />
{<br />
$this->y = $y;<br />
echo __METHOD__ . '';<br />
}<br />
}<br />
$o1 = new BaseClass();<br />
$o1->SetX(100);<br />
$o1->SetY(100);<br />
echo "XY: {$o1->x} x {$o1->y}";<br />
Die Ausgabe klärt darüber auf, wann welche Methode aufgerufen wurde:<br />
Abbildung 6.8:<br />
Ausgabe der aktuellen Methode mit __METHOD__<br />
233
234<br />
Objektorientierte Programmierung<br />
Zeichenkettenform: __toString()<br />
Generell gibt es von jedem Objekt eine Zeichenkettenform. PHP5 erstellt diese<br />
automatisch, wenn der Kontext es verlangt. Für die explizite Definition gibt es die<br />
Methode __toString(). Betrachten Sie zuerst den Standardfall:<br />
Listing 6.17: ClassToString.php – Ein einfaches echo gibt die Zeichenkettenform aus<br />
class BaseClass<br />
{<br />
public $x;<br />
public $y;<br />
public function SetX($x)<br />
{<br />
$this->x = $x;<br />
}<br />
public function SetY($y)<br />
{<br />
$this->y = $y;<br />
}<br />
}<br />
$o1 = new BaseClass();<br />
$o1->SetX(100);<br />
$o1->SetY(100);<br />
echo $o1;<br />
Die interne Form folgt etwa dem folgenden Muster:<br />
Abbildung 6.9:<br />
Zeichenkettendarstellung eines Objekts<br />
Das ist meist wenig hilfreich, weshalb die Möglichkeit besteht, das Verhalten zu<br />
ändern. Dazu wird eine öffentliche Methode __toString definiert, deren Rückgabewert<br />
die Ausgabe bestimmt.<br />
class BaseClass<br />
{<br />
public $x;<br />
public $y;<br />
public function SetX($x)<br />
{<br />
$this->x = $x;
}<br />
public function SetY($y)<br />
{<br />
$this->y = $y;<br />
}<br />
public function __toString()<br />
{<br />
return 'Object BaseClass';<br />
}<br />
Analyse und Kontrolle von Objekten<br />
Listing 6.18: ClassToStringO.php – Überschriebene __toString()-Methode<br />
}<br />
$o1 = new BaseClass();<br />
$o1->SetX(100);<br />
$o1->SetY(100);<br />
echo $o1;<br />
Mit der nun erfolgten Ausgabe kann man im Zweifelsfall etwas mehr anfangen.<br />
Beachten Sie wieder, dass es sich bei der Methode __toString() um<br />
zwei Unterstriche vor dem Namen handelt.<br />
Abstammung von Klassen und Objekten<br />
Die Abstammung von Klassen und Objekten ist immer dann interessant, wenn die<br />
Erstellung dynamisch erfolgt (Factory-Klassen) oder fremder Code benutzt wird.<br />
Wovon ein Objekt abstammt<br />
Abbildung 6.10:<br />
Kundenspezifische Zeichenkettenentsprechung<br />
Verschiedene Funktionen können in PHP benutzt werden, um die Abstammung<br />
eines Objekts von einer Klasse festzustellen. Das mag auf den ersten Blick trivial<br />
erscheinen, weil man ja beim Schreiben des Codes genau weiß, wovon ein Objekt<br />
abstammt. Bei großen Bibliotheken, fremdem Code oder komplexen Hierarchien<br />
235
236<br />
Objektorientierte Programmierung<br />
ist das nicht immer der Fall. Betrachten Sie zuerst ein triviales Beispiel, das das<br />
Schlüsselwort instanceof nutzt:<br />
Listing 6.19: Class<strong>In</strong>stanceOf.php – Abstammung eines Objekts mit instanceof klären<br />
class BaseClass<br />
{<br />
public $x;<br />
public $y;<br />
public function SetX($x)<br />
{<br />
$this->x = $x;<br />
}<br />
public function SetY($y)<br />
{<br />
$this->y = $y;<br />
}<br />
}<br />
$o1 = new BaseClass();<br />
$o1->SetX(100);<br />
$o1->SetY(100);<br />
if ($o1 instanceof BaseClass)<br />
{<br />
echo 'Object $o1 stammt von BaseClass';<br />
}<br />
else<br />
{<br />
echo 'Object $o1 stammt nicht von BaseClass';<br />
}<br />
Hat man eine Hierarchie, lässt sich die Frage nach der Abstammung klären:<br />
Listing 6.20: Class<strong>In</strong>stanceOfExt.php – Die Abstammung eines Objekts wird geklärt<br />
class BaseClass<br />
{<br />
protected $x;<br />
protected $y;<br />
public function SetX($x)<br />
{<br />
$this->x = $x;<br />
}<br />
public function SetY($y)
Analyse und Kontrolle von Objekten<br />
{<br />
$this->y = $y;<br />
}<br />
}<br />
class DerivedClass extends BaseClass<br />
{<br />
protected $x;<br />
protected $y;<br />
public function GetXY()<br />
{<br />
return "{$this->x}x{$this->y}";<br />
}<br />
}<br />
$o1 = new DerivedClass();<br />
$o1->SetX(100);<br />
$o1->SetY(100);<br />
if ($o1 instanceof BaseClass)<br />
{<br />
echo 'Object $o1 stammt von BaseClass';<br />
}<br />
else<br />
{<br />
echo 'Object $o1 stammt nicht von BaseClass';<br />
}<br />
Die Ausgabe beider Skripte ist identisch und zeigt im Fall des zweiten Beispiels,<br />
dass ein Objekt immer auch von der Basisklasse abstammt:<br />
Alternativ zu instanceof kann auch die Funktion is_a genutzt werden:<br />
is_a($object, 'ClassName')<br />
Die Lesbarkeit ist, nicht zuletzt wegen des wenig transparenten Namens, etwas eingeschränkt.<br />
Dafür funktioniert die letzte Variante auch mit PHP 4, während<br />
instanceof erst mit PHP5 eingeführt wurde.<br />
Abstammungshierarchie von Objekten<br />
Abbildung 6.11:<br />
Jedes Objekt ist immer eine <strong>In</strong>stanz auch der Basisklasse<br />
Um die Abstammungshierarchie von Objekten festzustellen, eignet sich die Funktion<br />
is_subclass_of.<br />
237
238<br />
Objektorientierte Programmierung<br />
Listing 6.21: ClassSubclassOf.php – Feststellen, ob eine Klasse von einer anderen<br />
abstammt<br />
class BaseClass<br />
{<br />
protected $x;<br />
protected $y;<br />
public function SetX($x)<br />
{<br />
$this->x = $x;<br />
}<br />
public function SetY($y)<br />
{<br />
$this->y = $y;<br />
}<br />
}<br />
class DerivedClass extends BaseClass<br />
{<br />
protected $x;<br />
protected $y;<br />
public function GetXY()<br />
{<br />
return "{$this->x}x{$this->y}";<br />
}<br />
}<br />
$o1 = new DerivedClass();<br />
$o1->SetX(100);<br />
$o1->SetY(100);<br />
if (is_subclass_of($o1, 'BaseClass'))<br />
{<br />
echo 'DerivedClass stammt von BaseClass';<br />
}<br />
else<br />
{<br />
echo 'DerivedClass stammt nicht von BaseClass';<br />
}<br />
Im Beispiel ist der folgende Ausdruck wahr:<br />
if (is_subclass_of($o1, 'BaseClass'))<br />
Dagegen wäre der folgende Ausdruck falsch:
if (is_subclass_of($o1, 'DerivedClass'))<br />
Referenz der OOP-Funktionen<br />
Damit kann man, im Gegensatz zu instanceof, die tatsächliche Hierarchie feststellen<br />
und nicht nur eine globale Zugehörigkeit.<br />
Test- und <strong>In</strong>formationsmethoden<br />
Weitere Testmethoden für Objekte und Klassen finden Sie in der Kurzreferenz am<br />
Ende des Kapitels. Vor allem bei der Fehlersuche und zur Analyse fremden Codes<br />
sind die Testmethoden interessant. Als Programmiermittel dürften sie sich weniger<br />
anbieten.<br />
6.5 Referenz der OOP-Funktionen<br />
OOP-Schlüsselwörter<br />
Schlüsselwort Beschreibung<br />
class Deklariert eine Klasse.<br />
var Deklariert eine öffentliche Mitgliedsvariable (veraltet, PHP 4-Syntax).<br />
new Legt eine neue <strong>In</strong>stanz eines Objekts an.<br />
extends Erweitert eine Klasse.<br />
interface Deklariert eine Schnittstelle.<br />
implements Implementiert eine Schnittstelle.<br />
private Deklariert ein Mitglied einer Klasse als privat. Es ist damit für Aufrufer<br />
der Klasse nicht sichtbar.<br />
public Deklariert ein Mitglied einer Klasse als öffentlich. Es ist damit für alle<br />
Aufrufer der Klasse sichtbar. Dies ist der Standardwert, das heißt, ohne<br />
Angabe des Schlüsselwortes sind alle Mitglieder öffentlich.<br />
protected Deklariert ein Mitglied einer Klasse als geschützt. Es ist damit für Aufrufer<br />
der Klasse nicht sichtbar, kann jedoch in direkt abgeleiteten Klassen<br />
benutzt werden.<br />
239
OOP-Funktionen<br />
240<br />
Objektorientierte Programmierung<br />
Schlüsselwort Beschreibung<br />
parent Erlaubt den statischen Zugriff auf die Basisklasse.<br />
Self Erlaubt den statischen Zugriff auf die eigene Klasse.<br />
$this Pseudovariable zum Zugriff auf die <strong>In</strong>stanzform der eigenen Klasse.<br />
const Deklariert eine Konstante im Kontext der Klasse.<br />
try Leitet einen Block ein, der der Ausnahmebehandlung unterliegt<br />
catch Leitet einen Block ein, der eine spezifische Ausnahme behandelt<br />
throw Generiert eine Ausnahme<br />
Funktion Beschreibung<br />
call_user_method Ruft eine benutzerdefinierte Methode auf, verwendet jedoch<br />
dazu die Funktionssyntax und nicht den direkten Aufruf mittels<br />
des Operators ->. Dies kann sinnvoll sein, wenn der Programmkontext<br />
explizit einen Funktionsaufruf verlangt oder<br />
einer der Parameter dynamisch verändert werden soll.<br />
call_user_method_array Ruft eine benutzerdefinierte Methode auf, verwendet jedoch<br />
dazu die Funktionssyntax und nicht den direkten Aufruf mittels<br />
des Operators ->. Außerdem kann ein Array mit den Parametern<br />
übergeben werden, die die Methode erwartet.<br />
class_exists Prüft, ob eine bestimmte Klasse deklariert wurde. Sinnvoll<br />
beispielsweise nach dem dynamischen Einbinden von<br />
Modulen.<br />
get_class_methods Gibt ein Array der Methoden einer Klasse zurück. Dient vor<br />
allem Analysezwecken.<br />
get_class_vars Gibt ein Array der Eigenschaften einer Klasse zurück. Dient<br />
vor allem Analysezwecken.<br />
get_declared_classes Gibt ein Array mit allen deklarierten Klassen in PHP5 zurück.<br />
Dies umfasst sowohl die eingebauten als auch die im Augenblick<br />
der Abfrage selbst definierten.
Funktion Beschreibung<br />
Referenz der OOP-Funktionen<br />
get_object_vars Ermittelt die Eigenschaften eines Objekts. Dies ist gut zur<br />
Analyse des Zustands, wenn nicht völlig klar, wie und wo das<br />
Objekt instanziiert wurde.<br />
get_parent_class Gibt die Klasse zurück, von der eine abgeleitete Klasse<br />
abstammt.<br />
is_a Gibt TRUE zurück, wenn das Objekt von der angegebenen<br />
Klasse abstammt.<br />
is_subclass_of Gibt TRUE zurück, wenn ein Objekt von der angegebenen<br />
Basisklasse abstammt.<br />
method_exists Gibt TRUE zurück, wenn die Methode existiert.<br />
instanceof Gibt TRUE zurück, wenn das Objekt von einer Klasse<br />
abstammt.<br />
__toString() Verändert beim Überschreiben in einer Klasse die Zeichenkettenform<br />
des Objekts. Die Zeichenkettenform wird immer<br />
dann benutzt, wenn die Ausgabe eines Objekts direkt mit<br />
echo oder print erfolgt oder der Kontext des Codes eine Zeichenkettenform<br />
verlangt.<br />
__call() Ruft dynamisch Methoden auf. Die so deklarierte Funktion<br />
wird immer dann aufgerufen, wenn PHP in der betreffenden<br />
Klasse keine Methode des verlangten Namens findet.<br />
__get() Ruft dynamisch Eigenschaften zum Lesen auf. Die so deklarierte<br />
Funktion wird immer dann aufgerufen, wenn PHP in<br />
der betreffenden Klasse keine Methode des verlangten<br />
Namens findet. Wenn man mit __set eine Eigenschaft definiert<br />
und mit __get nicht, erhält man eine »Nur-Schreib«-<br />
Eigenschaft.<br />
__set() Ruft dynamisch Eigenschaften zum Schreiben auf. Die so<br />
deklarierte Funktion wird immer dann aufgerufen, wenn PHP<br />
in der betreffenden Klasse keine Methode des verlangten<br />
Namens findet. Wenn man mit __get eine Eigenschaft definiert<br />
und mit __set nicht, erhält man eine »Nur-Lese«-Eigenschaft.<br />
241
242<br />
Objektorientierte Programmierung<br />
Funktion Beschreibung<br />
__construct() Reservierter Name für den Konstruktor einer Klasse. Der Konstruktor<br />
wird aufgerufen, bevor das Objekt erzeugt wird. Er<br />
wird vor allem verwendet, um einen definierten Zustand zu<br />
erzeugen. Auslöser ist der Aufruf des Schlüsselwortes new.<br />
__destruct() Reservierter Name für den Destruktor einer Klasse. Der Destruktor<br />
wird aufgerufen unmittelbar bevor das Objekt zerstört<br />
wird. Er wird vor allem verwendet, um mit dem Objekt verbundene<br />
Ressourcen zu bereinigen.<br />
6.6 Kontrollfragen<br />
1. Schreiben Sie ein Skript, dass die Namen von Mitarbeitern in einer eigens dafür<br />
entwickelten Klasse speichert.<br />
2. Erklären Sie den Unterschied zwischen private, public und protected.<br />
3. Sie haben nur eine einzige Klasse in Ihrem Skript. Ist die Anwendung des Schlüsselwortes<br />
protected sinnvoll? Begründen Sie die Antwort.<br />
4. Welchen Vorteil bietet die Verwendung von __get und __set anstatt des direkten<br />
Zugriffs auf öffentliche Eigenschaften, die mit public $name gekennzeichnet<br />
sind?<br />
5. Wie schreiben Sie eine Klasse, deren Objekte beim Aufruf von new einen definierten<br />
Anfangszustand unabhängig von Parametern erhalten soll?<br />
6. Wie schreiben Sie eine Klasse, von der nur eine <strong>In</strong>stanz erzeugt werden darf? Wie<br />
nennt man dieses Entwurfsmuster?
Das Dateisystem<br />
entdecken<br />
7
244<br />
Das Dateisystem entdecken<br />
7.1 Dateizugriff organisieren<br />
Das Dateisystem verwaltet Dateien und Ordner. PHP verfügt über einen umfassenden<br />
Satz an Funktionen zum Lesen, Schreiben und Erzeugen von Dateien und<br />
Verzeichnissen. Die Anwendung ist unkritisch, wenn man sich mit einigen grundlegenden<br />
Techniken angefreundet hat.<br />
Grundlagen<br />
Der Begriff Dateisystem wird in PHP übrigens etwas weiter gefasst und<br />
kann sich durchaus auf andere Server ausdehnen, die über HTTP oder<br />
FTP benutzt werden.<br />
Beim Umgang mit Datei- und Verzeichnisfunktionen sind einige Dinge wichtig.<br />
Zum einen ist immer wieder von so genannten Handles die Rede. Dahinter verbergen<br />
sich Variablen, die einen Zeiger auf eine Datei oder ein Verzeichnis enthalten.<br />
Das Handle wird von jeder Funktion benutzt, um eine ganze bestimmte Datei<br />
aufzurufen oder Verbindung zu einem Verzeichnis herzustellen. <strong>In</strong>tern handelt es<br />
sich dabei um eine so genannte Ressource. Der prinzipielle Ablauf des Dateizugriffs1<br />
erfordert fast immer folgenden Ablauf:<br />
1. Anfordern der Ressource<br />
2. Erstellen des Handles mit dem Verweis auf die Ressource<br />
3. Benutzen des Handles zum Zugriff auf die Ressource<br />
4. Schließen der Ressource, damit andere darauf zugreifen können<br />
5. Vernichten des Handles (optional, meist automatisch am Skript-Ende erledigt)<br />
Ein ähnlicher Ablauf wird auch für Datenbanken benutzt. <strong>In</strong> allen Fällen heißen<br />
die im ersten und im vierten Schritt benötigten Funktionen typischerweise<br />
xxx_open und xxx_close, wobei xxx die spezifische Art der Ressource näher<br />
beschreibt. Bei den Dateifunktionen von PHP5 steht dafür meist »f« oder »file«.<br />
Wichtig ist auch, dass die Ressourcen meist exklusiv geöffnet werden. Das heißt,<br />
wenn ein Benutzer eine Datei liest oder schreibt, kann dies gleichzeitig kein anderer<br />
tun. Das ist unproblematisch, wenn immer wieder mit neuen Dateien gearbeitet<br />
wird, wie bei der Sitzungsverwaltung. Greifen aber alle Benutzer auf dieselbe<br />
1 Soweit nicht explizit etwas anderes erwähnt wird, gelten diese Ausführung auch für den Verzeichniszugriff.
Dateizugriff organisieren<br />
Datei zu , kann es entweder zu Programmfehlern (»Zugriff verweigert«) oder zu<br />
langen Wartezeiten kommen. Dateien als Datenspeicher sind deshalb nur<br />
begrenzt einsatzfähig. Stoßen sie an ihre Grenzen, kommen Datenbanken ins<br />
Spiel, die fein granulierter sperren können (mal von <strong>SQL</strong>ite abgesehen, dass<br />
Dateien zum Speichern nutzt).<br />
Der Zugriff über das Schema »Handle à Ressource« ist nicht allein auf Dateien<br />
beschränkt, auch entfernte Server sind über die Protokolle http:// oder ftp://<br />
erreichbar. Das erweitert den Einsatzspielraum gewaltig, weil man sich über die<br />
eigentlichen Protokolle kaum Gedanken machen muss. Beim HTTP ist lediglich<br />
zu beachten, dass die Laufzeiten eines Skripts erheblich länger sein können, weil<br />
erst eine Verbindung aufgebaut werden muss. Das impliziert auch, dass diese Verbindung<br />
fehlschlagen kann, was zusätzliche Fehlerabfragen erfordert. Daneben ist<br />
auch zu beachten, dass meist nur lesend auf andere Server zugegriffen werden<br />
kann.<br />
Einsatzfälle<br />
Einsatzfälle für den Datei- und Verzeichniszugriff gibt es ungeheuer viele. Hier<br />
sollen nur ein paar Anregungen gegeben werden:<br />
Abspeichern von Formulardaten<br />
Datei-Explorer auf dem Server<br />
Nachrichtenquelle für die News-Seite<br />
Speicher fürs Gästebuch<br />
Steuerung des Hochladens von Dateien<br />
<strong>In</strong>formationsspeicher für ein Content Management System<br />
Template-System-Steuerung<br />
Protokolldateien erzeugen<br />
Das ist sicher nur ein kleiner Ausschnitt. Anhand vieler kleiner Beispiele wird im Folgenden<br />
eine Übersicht über die Anwendung der wichtigsten Funktionen gegeben.<br />
245
246<br />
Das Dateisystem entdecken<br />
7.2 Praktischer Dateizugriff<br />
Der Dateizugriff ist mit PHP relativ einfach. Eine breite Palette von Funktionen<br />
steht zur Verfügung, um alle erdenklichen Zugriffe zu erledigen. Dabei werden<br />
immer wieder einige Basistechniken benutzt, die Sie kennen sollten.<br />
Prinzipien des Dateizugriffs<br />
Es gibt mehrere Prinzipien beim Dateizugriff, die nicht nur für PHP gelten. Diese<br />
sollten Sie kennen, damit die Wirkungsweise der verschiedenen Funktionen transparent<br />
wird.<br />
Dateizugriff<br />
Grundsätzlich kann man zwei Gruppen von Funktionen unterscheiden, die PHP<br />
beim Dateizugriff einsetzt:<br />
Funktionen mit direktem Dateizugriff<br />
Funktionen mit Datei-Handle auf eine geöffnete Datei<br />
Funktionen mit direktem Dateizugriff benötigen einen Pfad. Sie lesen oder schreiben<br />
die Daten und geben die Datei danach wieder frei. Ein späterer Zugriff benötigt<br />
erneut diese Pfadangabe.<br />
Anders funktionieren die zahlreichen Funktionen mit einem so genannten Datei-<br />
Handle. Dabei wird mit einer Funktion ein Verweis auf die geöffnete Datei<br />
erstellt, das Handle. <strong>In</strong>tern ist dies eine Ressource. Andere Funktionen benutzten<br />
dann nur noch dieses Handle – gespeichert in einer Variablen – zum flexiblen<br />
Zugriff. Am Ende der Zugriffe wird die Verbindung geschlossen, die Datei freigegeben<br />
und das Handle weggeworfen. Vor allem bei wiederholten Zugriffen in<br />
Schleifen ist dieses Verfahren einfacher und schneller.<br />
Universeller Quellzugriff<br />
PHP beherrscht das Konzept des Wrapper-Zugriffs (manchmal wird das auch als<br />
Moniker bezeichnet). Dabei gilt als Dateipfad jede den Prinzipien einer URI (Uniform<br />
Resource Identifier) entsprechende Form als zulässig. Der prinzipielle (vereinfachte)<br />
Aufbau sieht etwa folgendermaßen aus:
wrapper://pfad/pfad/pfad/datei.extension<br />
Praktischer Dateizugriff<br />
Der Standard-Wrapper für Dateien heißt file:///. Er kann entfallen, weil PHP<br />
ohne Angabe davon ausgeht, dass es sich um eine lokale Datei handelt. Gültige<br />
lokale Pfadangaben sind beispielsweise:<br />
/pfad/pfad2/datei.ext<br />
Dies kennzeichnet einen absoluten Pfad auf dem aktuellen Laufwerk.<br />
pfadrelativ/datei.ext<br />
Dies kennzeichnet einen relativen Pfad, berechnet vom Ausführungsort des<br />
Skripts an.<br />
C:\pfad\datei.ext oder C:/pfad/datei.ext<br />
Pfad mit Laufwerk unter Windows. Die Art der Schrägstriche spielt in PHP<br />
keine Rolle.<br />
\\sambasrv\sharedpath\local\datei.ext<br />
Ein Pfad zu einem gemeinsamen Laufwerk auf einem Windows- oder Samba-<br />
Server<br />
file:///pfad/datei.ext oder file://c|pfad/datei.ext<br />
Vollständige Angabe eines Pfades mit dem Wrapper. Beachten Sie, dass dieser<br />
Wrapper mit absoluten Unix-Pfaden arbeitet, wenn der Pfad seinerseits mit /<br />
beginnt, was oft zu Pfadangaben wie file:///pfad führt..<br />
Neben dem Dateizugriff sind folgende Wrapper erlaubt:<br />
http:// und https://<br />
Hier erfolgt der Zugriff über HTTP auf einen remoten Server. https:// nutzt<br />
eine verschlüsselte Verbindung. PHP arbeitet mit dem Standard HTTP 1.0<br />
und löst eine GET-Anfrage aus. Grundsätzlich kann die Datei auf dem entfernten<br />
Server nur gelesen werden.<br />
Wenn der Zugriff durch Kennwort geschützt ist, kann folgende Syntax verwendet<br />
werden, um per Skript einen Zugang zu bekommen:<br />
http://benutzername:kennwort@www.adresse.de<br />
ftp:// und ftps://<br />
Hier erfolgt der Zugriff über FTP auf einen remoten Server. ftps:// nutzt eine<br />
verschlüsselte Verbindung, wenn der Server dies unterstützt (sehr selten) und<br />
benötigt eine PHP-Version, die SSL unterstützt. PHP kann die Datei auf dem<br />
entfernten Server lesen und schreiben.<br />
247
248<br />
Das Dateisystem entdecken<br />
Wenn der Zugriff durch Kennwort geschützt ist, kann folgende Syntax verwendet<br />
werden, um per Skript einen Zugang zu bekommen:<br />
ftp://benutzername:kennwort@www.adresse.de<br />
php://<br />
PHP verwendet intern bestimmte Kanäle (so genannte Streams) für den<br />
Zugriff auf Datenquellen. Der php://-Wrapper gestattet den Zugriff darauf. Die<br />
folgende Tabelle zeigt die möglichen Kanäle:<br />
Kanal Bedeutung<br />
stdin Standardeingabe des Betriebssystems<br />
stdout Standardausgabe des Betriebssystems<br />
stderr Fehlerausgabe des Betriebssystems<br />
output Zugriff auf den Ausgabepuffer, der von print oder echo verwendet wird<br />
input Zugriff auf den Eingabepuffer, der unverarbeitete Daten aus POST-Anfragen<br />
enthält (Formulardaten)<br />
filter Filter für Funktionen, die keine derartige Parametrisierung von Hause aus<br />
unterstützten.<br />
compress.zlib:// oder compress.bzip2://<br />
Zugriff auf komprimierte Dateien, die beim Lesen automatisch entpackt<br />
werden.<br />
Zugriff auf Dateiinhalte<br />
Eine andere Technik ist mit dem Zugriff auf den <strong>In</strong>halt der Datei verbunden.<br />
Wenn man eine Datei liest, gibt es auch hier wieder mehrere Varianten:<br />
Die gesamte Datei lesen oder schreiben<br />
Die Datei zeilenweise lesen oder schreiben<br />
Die Datei zeichenweise lesen oder schreiben<br />
Das Lesen der gesamten Datei erfolgt entweder als Zeichenkette oder als Array.<br />
Die Benutzung eines Arrays setzt voraus, dass es eine Möglichkeit gibt, den <strong>In</strong>halt<br />
in Elemente zu trennen. Das gilt in der Regel nur für Dateiinhalte, die Text
Praktischer Dateizugriff<br />
umfassen, der mit Zeilenumbrüchen arbeitet. Binäre Dateiinhalte (das sind auch<br />
solche mit proprietären Formaten wie MS Word oder Excel) lassen sich damit<br />
nicht verarbeiten. Beim Schreiben einer kompletten Datei muss beachtet werden,<br />
dass von der Zielapplikation eventuell erwartete Zeilenumbrüche erzeugt werden.<br />
Beim zeilenweisen Lesen wird davon ausgegangen, dass die Daten auch tatsächlich<br />
in Zeilen angeordnet sind. Dann bieten sich Funktionen an, die das Erreichen<br />
eines Zeileumbruchs anzeigen und damit eine bestimmte Aktion auslösen.<br />
Binäre Daten kennen keine Zeilenumbrüche und können – ebenso wie natürlich<br />
Textdateien – zeichenweise gelesen werden. Dazu wird ein so genannter Dateizeiger<br />
eingesetzt. Der Dateizeiger zeigt auf ein konkretes Zeichen, das als nächstes<br />
gelesen bzw. nach dessen Position geschrieben wird. Typischerweise wird während<br />
der Abfrage von Zeichen in einer Schleife permanent überprüft, ob das Dateiende<br />
bereits erreicht wurde.<br />
Wichtige Konstanten<br />
PHP liefert einige wichtige Konstanten, die den Umgang mit Pfadangaben und<br />
Dateinamen vereinfachen:<br />
DIRECTORY_SEPARATOR<br />
Der Verzeichnistrenner. Unter Windows ist dies der Doppelpunkt.<br />
PATH_SEPARATOR<br />
Der Pfadtrenner. Unter Windows ist dies der Backslash, unter Unix der Schrägstrich.<br />
GLOB_BRACE<br />
GLOB_ONLYDIR<br />
GLOB_MARK<br />
GLOB_NOSORT<br />
GLOB_NOCHECK<br />
GLOB_NOESCAPE<br />
Konstanten, die das Verhalten der glob-Funktion steuern.<br />
PATHINFO_DIRNAME<br />
PATHINFO_BASENAME<br />
PATHINFO_EXTENSION<br />
Konstanten, die das Verhalten der pathinfo-Funktion steuern.<br />
249
250<br />
Das Dateisystem entdecken<br />
FILE_USE_INCLUDE_PATH<br />
FILE_APPEND<br />
FILE_IGNORE_NEW_LINES<br />
FILE_SKIP_EMPTY_LINES<br />
Konstanten, die das Verhalten verschiedener Datei-Funktionen steuern.<br />
Nachrichtenquelle für eine News-Seite<br />
Als erstes Beispiel soll eine Nachrichtenquelle für eine News-Seite mit Hilfe der<br />
Dateifunktionen programmiert werden. Die Nachrichten werden in einer Textdatei<br />
abgelegt. Sie sind dort zeilenweise angeordnet, das heißt, jede Nachricht<br />
steht auf einer Zeile. Zuerst ein Blick auf die Textdatei:<br />
Listing 7.1: news.txt (im Verzeichnis /data) – Die Nachrichten des Tages im Textformat<br />
Das neue Buch "PHP5 und <strong>My</strong>sQL in <strong>14</strong> <strong>Tagen</strong>" ist erschienen.<br />
PHP für Profis und Agenturen: www.phptemple.de!<br />
PHP erneut erfolgreichste Skriptsprache im Web.<br />
Auf der Website sollen diese Nachrichten untereinander ausgegeben werden:<br />
Listing 7.2: filenewsreader.php – Ausgabe von <strong>In</strong>formationen aus einer Textdatei<br />
$fh = fopen('data/news.txt', 'r');<br />
if (is_resource($fh))<br />
{<br />
while ($line = fgets($fh))<br />
{<br />
echo
Praktischer Dateizugriff<br />
{<br />
echo "Datei nicht gefunden";<br />
}<br />
Dieses Skript nutzt zuerst fopen, um das Datei-Handle zu erhalten. Angegeben<br />
werden muss der Pfad zur Datei und ein Parameter, der die Art des Zugriffs<br />
bestimmt (siehe unten). Dann wird geprüft, ob tatsächlich eine Ressource zurückgegeben<br />
wurde, was is_resource erledigt. <strong>In</strong> der Schleife wird dann Zeile für Zeile<br />
mit fgets gelesen, wobei die Funktion den <strong>In</strong>halt der Zeile zurückgibt. Sind keine<br />
Daten mehr vorhanden, wird FALSE zurückgegeben und die Schleife ist beendet.<br />
Anschließend ist der Zugriff mit fclose zu beenden.<br />
Die Funktion fopen ist enorm wichtig und wird nachfolgend genauer vorgestellt.<br />
Dateizugriff mit der Funktion fopen<br />
Abbildung 7.1:<br />
Daten aus einer Datei lesen und formatiert ausgeben<br />
Der Dateizugriff mit fopen ist elementar. Die Funktion ist deshalb unbedingt eine<br />
nähere Betrachtung wert. Der grundsätzliche Aufbau kann dem folgenden Syntaxdiagramm<br />
entnommen werden:<br />
fopen (string pfad, string mode [, int incl [, resource ctx]])<br />
Lediglich die ersten beiden Parameter sind Pflichtangaben. <strong>In</strong>teressant ist dabei<br />
der zweite, eine Zeichenkette, die folgende Bedeutung hat:<br />
251
252<br />
Das Dateisystem entdecken<br />
Modus Beschreibung<br />
r Nur lesen, der Dateizeiger wird an den Anfang positioniert<br />
r+ Lesen und Schreiben, der Dateizeiger wird an den Anfang positioniert<br />
w Nur Schreiben. Der Dateizeiger wird an den Anfang gesetzt und eine vorhandene<br />
Datei auf die Länge 0 gesetzt. Existiert die Datei nicht, wird sie erzeugt.<br />
w+ Lesen und Schreiben. Der Dateizeiger wird an den Anfang gesetzt und eine<br />
vorhandene Datei auf die Länge 0 gesetzt. Existiert die Datei nicht, wird sie<br />
erzeugt.<br />
a Nur Schreiben. Der Dateizeiger wird an das Ende gesetzt, zu schreibende<br />
Daten werden angehängt. Existiert die Datei nicht, wird sie erzeugt.<br />
a+ Lesen und Schreiben. Der Dateizeiger wird an das Ende gesetzt, zu schreibende<br />
Daten werden angehängt. Existiert die Datei nicht, wird sie erzeugt.<br />
x Erzeugen und Schreiben einer lokalen Datei. Wrapper außer file:/// können<br />
nicht verwendet werden. Wenn die Datei bereits existiert, gibt fopen FALSE<br />
zurück.<br />
x+ Erzeugen und Öffnen zum Lesen und Schreiben einer lokalen Datei. Wrapper<br />
außer file:/// können nicht verwendet werden. Wenn die Datei bereits existiert,<br />
gibt fopen FALSE zurück.<br />
Tabelle 7.1: Modi für fopen<br />
Die einzelnen Dateisysteme gehen unterschiedlich mit dem Zeilenumbruch um.<br />
<strong>In</strong>tern ist dieser durch ein Sonderzeichen definiert. Dabei gilt folgende Regel:<br />
\n: Zeilenumbruch unter Unix<br />
\r\n: Zeilenumbruch unter Windows<br />
\r: Zeilenumbruch unter MacOS<br />
PHP versucht möglichst flexibel damit umzugehen, damit unter Windows erstellte<br />
Dateien auch auf Linux laufen und umgekehrt. Da es dennoch Probleme mit<br />
anderen Applikationen geben kann, besteht die Möglichkeit ein »Transfer«-Flag<br />
zu definieren, das PHP zur fortlaufenden Konvertierung veranlasst. Das Flag t<br />
wird beim Öffnen dem Modus nachgestellt. Der Transfer funktioniert nur unter<br />
Windows und veranlasst PHP, Dateien mit Unix-Zeilenumbrüchen in solche mit<br />
Windows-Zeilenumbrüchen zu verwandeln. Damit das funktioniert, muss das t
Praktischer Dateizugriff<br />
and einen der anderen Modifizierer vor dem optionalen +-Zeichen angehängt werden,<br />
beispielsweise wt oder at+. Alternativ dazu kann man auch b (auf allen Systemen)<br />
verwenden, um explizit mit Binärdateien zu arbeiten, wo jegliche Zugriffe<br />
auf die Daten zu unterbleiben haben (rb, wb oder ab beispielsweise).<br />
Wenn Sie bezüglich der Flags unsicher sind, verwenden Sie immer die<br />
Kombination mit b. Es ist immer besser, die Daten in ihrer ursprünglichen<br />
Form zu belassen, als unqualifizierte Änderungen anzubringen.<br />
Der dritte Parameter ist optional. Er wird entweder auf TRUE oder FALSE gesetzt,<br />
wobei FALSE der Standard ist. Die Angabe von TRUE veranlasst die Funktion beim<br />
Öffnen der Datei den Suchpfad für <strong>In</strong>clude-Dateien mit einzubeziehen, wenn die<br />
Datei (bei relativen Pfadangaben) im aktuellen Verzeichnis nicht gefunden werden<br />
konnte.<br />
Der vierte Parameter ist nur sinnvoll einsetzbar, wenn der verwendete Wrapper<br />
(http://, ftp:// oder php://) zusätzliche Angaben verlangt. Diese werden dann hier<br />
platziert. Das erforderliche Format kann mit stream_context_create erstellt werden.<br />
Diese Funktion selbst akzeptiert wiederum ein Array. Das folgende Code-<br />
Fragment zeigt, wie die Anwendung erfolgt:<br />
<br />
Für HTTP sind folgende Optionen vorgesehen:<br />
253
254<br />
Das Dateisystem entdecken<br />
Option Beschreibung<br />
method Methode, beispielsweise GET oder POST.<br />
header Die Kopfzeilen (siehe Beispiel).<br />
user_agent <strong>In</strong>formation darüber, wie sich das Skript identifiziert. Man kann hier beispielsweise<br />
den Namen eines Browsers eintragen.<br />
content Daten, die an POST-Anforderungen angehängt werden.<br />
proxy URI eines Proxy-Servers, beispielsweise tcp://proxy.zielserver.de:9900<br />
request_fulluri TRUE oder FALSE, wobei die Angabe TRUE in der HTTP-Anforderung die<br />
vollen Angabe der Zieladresse erzwingt (nicht üblich).<br />
Tabelle 7.2: Kontext-Optionen für HTTP<br />
Weitere Optionen der anderen Wrapper können der Online-Dokumentation entnommen<br />
werden.<br />
Beliebige Code-Dateien ausgeben<br />
Im nächsten Beispiel werden PHP-Quelltexte angezeigt. Für diesen einfachen Fall<br />
zeigt sich die Datei einfach selbst an.<br />
Listing 7.3: filefile.php – Ausgaben einer Datei mit Zeilennummern<br />
$path = basename($_SERVER['PHP_SELF']);<br />
$file = @file($path);<br />
if (is_array($file))<br />
{<br />
echo '';<br />
for ($i = 0; $i < count($file); $i++)<br />
{<br />
printf('%04d: %s', $i, htmlspecialchars($file[$i]));<br />
}<br />
echo '';<br />
}<br />
Das Skript ermittelt zuerst den eigenen Namen aus der Servervariablen<br />
$_SERVER['PHP_SELF'] mit der Funktion basename. Dann wird die Funktion file<br />
benutzt, um den <strong>In</strong>halt der Datei in ein Array zu überführen. Sicherheitshalber
Praktischer Dateizugriff<br />
wird noch mit is_array geprüft, ob dies auch gelang. Dann wird die Schleife vom<br />
ersten bis zum letzten Arrayelement durchlaufen. Da Zeilennummern ausgegeben<br />
werden sollen, bietet sich hier for an, womit gleich eine Zählvariable zur Verfügung<br />
steht.<br />
Die Funktion htmlspecialchars verhindert, dass die HTML-Zeichen und &<br />
bei der Ausgabe ausgeführt werden. Zuletzt wird die Ausgabe noch mit printf formatiert,<br />
wodurch die Zeilennummern auf gleiche Breite gebracht werden.<br />
Protokolldatei<br />
Das Schreiben einer Datei ist ebenso einfach wie das Lesen, entsprechende<br />
Zugriffsrechte vorausgesetzt. Eine Protokolldatei zu schreiben, ist ein guter Test<br />
für ein einfaches Skript. Benötigt werden der Zugriff auf eine Datei mit fopen und<br />
die Schreibfunktion fwrite. Außerdem sind noch einige Daten zu beschaffen, die<br />
geschrieben werden sollen:<br />
Das aktuellen Datum und eine Zeitangabe (strftime)<br />
<strong>In</strong>formationen über den Browser (Servervariablen aus $_SERVER):<br />
Abbildung 7.2:<br />
Ausgabe einer<br />
Datei mit Zeilennummern<br />
HTTP_USER_AGENT<br />
Ermittelt die Kennung des Browsers, der gerade das Skript ausführt<br />
HTTP_ACCEPT_LANGUAGE<br />
Ermittelt <strong>In</strong>formationen über die Spracheinstellungen des Browsers<br />
255
256<br />
Das Dateisystem entdecken<br />
REMOTE_ADDR<br />
Die IP-Adresse, mit der der Rechner des Nutzers mit dem <strong>In</strong>ternet verbunden<br />
ist<br />
Die praktische Umsetzung zeigt das folgende Skript:<br />
Listing 7.4: fileprotocol.php – Daten über den aktuellen Benutzer speichern<br />
$dt = strftime('%d.%m.%Y %H:%M:%S', time());<br />
$bi = $_SERVER['HTTP_USER_AGENT'];<br />
$al = $_SERVER['HTTP_ACCEPT_LANGUAGE'];<br />
$ra = $_SERVER['REMOTE_ADDR'];<br />
$fh = fopen('data/protocol.txt', 'a');<br />
fwrite ($fh, sprintf("%s '%s' '%s' %s\n", $dt, $bi, $al, $ra));<br />
fclose($fh);<br />
$log = file_get_contents('data/protocol.txt');<br />
echo
7.3 Verzeichniszugriff<br />
Verzeichniszugriff<br />
Der Verzeichniszugriff geht eng zusammen mit dem Dateizugriff. Die Funktionen<br />
lassen sich gut kombinieren und für umfangreiche dateiorientierte Projekte nutzen.<br />
<strong>In</strong>halt eines Verzeichnisses anzeigen<br />
Um den <strong>In</strong>halt eines Verzeichnisses anzuzeigen, ist glob ein gute Funktion. Die<br />
Angabe von Platzhaltern erlaubt eine höhere Flexibilität, als die reinen Verzeichnisfunktionen<br />
bieten.<br />
Einfache Dateiliste<br />
Eine einfache Dateiliste lässt sich sehr leicht mit der Funktion glob erstellen. Das<br />
folgende Skript zeigt alle PHP-Skripte an, die mit den Buchstaben »e« oder »f«<br />
beginnen:<br />
Listing 7.5: fileglob.php – Gefilterte Dateiliste<br />
$path = basename($_SERVER['PHP_SELF']);<br />
$files = glob("{[ef]*.php}", GLOB_BRACE);<br />
if (is_array($files))<br />
{<br />
foreach ($files as $filename)<br />
{<br />
echo "$filename";<br />
}<br />
}<br />
Die Funktion glob kann mit einigen Schaltern gesteuert werden, die in der folgenden<br />
Tabelle erklärt werden:<br />
Schalter Funktion<br />
GLOB_BRACE Die Platzhalter verwenden Aufzählungssymbolik: {*.txt,*.php} usw.<br />
GLOB_ONLYDIR Es werden nur Verzeichnisse erkannt<br />
Tabelle 7.3: Schalter für die Funktion glob<br />
257
258<br />
Das Dateisystem entdecken<br />
Schalter Funktion<br />
GLOB_MARK Fügt einen Schrägstrich an alle erkannten Einträge an<br />
GLOB_NOSORT Verhindert die Sortierung (Standard ist ein alphabetische Sortierung)<br />
GLOB_NOCHECK Gibt das Suchmuster zurück, wenn keine Dateien gefunden<br />
wurden<br />
GLOB_NOESCAPE Meta-Zeichen (Verzeichnistrennzeichen) werden nicht mit einem<br />
Backslash markiert (unter Windows unbedingt sinnvoll)<br />
Tabelle 7.3: Schalter für die Funktion glob (Forts.)<br />
Mehrere Schalter können über eine einfache Oder-Verknüpfung | kombiniert<br />
werden:<br />
GLOB_BRACE | GLOB_ONLYDIR<br />
Die Platzhalterzeichen erlauben folgende Angaben:<br />
{Platzhalter,Platzhalter}<br />
Eine Serie von Platzhaltern, die ODER-verknüpft sind, werden durch Kommata<br />
getrennt in geschweifte Klammern gesetzt. Dies funktioniert nur, wenn<br />
der Schalter GLOB_BRACE verwendet wird.<br />
*<br />
Keines oder eine beliebige Anzahl Zeichen.<br />
?<br />
Genau ein beliebiges Zeichen.<br />
[]<br />
Genau ein Zeichen aus einer Zeichengruppe, die durch die Angabe in der<br />
Klammer bestimmt wird. Dies kann eine Aufzählung aus Zeichen sein, beispielsweise:<br />
[aef]<br />
Steht für die Buchstaben »a« oder »e« oder »f«.<br />
[a-f]<br />
Steht für die Buchstaben »a«, »b«, »c«, »d«, »e« oder »f«.<br />
[0-9]<br />
Steht für die Zahlen 0 bis 9.
Verzeichniszugriff<br />
Der gesamte Ausdruck in der Klammer kann negiert werden, indem das Zeichen<br />
»!« vorangestellt wird:<br />
[!eEfF]<br />
Alle Zeichen außer »e«, »E«, »f« und »F«<br />
Dieser Ausdruck muss in geschweiften Klammern stehen und funktioniert nur,<br />
wenn der Schalter GLOB_BRACE verwendet wird.<br />
<strong>In</strong>sgesamt betrachtet ist glob schnell und einfach einzusetzen. Reicht die Konstruktion<br />
der Suchmuster jedoch nicht aus, muss man auf reguläre Ausdrücke ausweichen.<br />
Abbildung 7.4:<br />
Gefilterte Dateiliste mit der glob-Funktion<br />
Manchmal soll nur festgestellt werden, ob eine Datei einem bestimmten Muster<br />
entspricht. Dieselbe Syntax wie bei glob kann mit fnmatch verwendet werden. Um<br />
eine Dateiliste zu durchsuchen, wird das dir-Objekt bemüht und dann mit<br />
fnmatch untersucht.<br />
259
260<br />
Das Dateisystem entdecken<br />
Das Verzeichnisobjekt dir und verwandte Funktionen<br />
PHP kennt eine Klasse dir, aus der sich ein Verzeichnisobjekt erstellen lässt. Die<br />
prinzipielle Anwendung zeigt das folgende Skript. Dazu wird auch gleich die<br />
Funktion fnmatch definiert. Diese mit PHP 4.3 eingeführte Funktion steht leider<br />
auch mit PHP5 nicht unter Windows zur Verfügung, was unverständlich ist, weil<br />
die ebenso dem Unix-System entstammende Variante glob vorhanden ist. Um das<br />
Skript universell zu machen, wird eine fnmatch-Variante selbst definiert.<br />
Listing 7.6: filefnmatch.php – Verwendung von fnmatch und alternative Definition<br />
if (!function_exists('fnmatch'))<br />
{<br />
function fnmatch($pattern, $file)<br />
{<br />
for($i=0; $i
}<br />
foreach ($letter_set as $letter)<br />
{<br />
if(fnmatch($letter.substr($pattern, $k+1),<br />
substr($file, $i)))<br />
{<br />
return TRUE;<br />
}<br />
}<br />
return false;<br />
}<br />
if($pattern[$i] == "?")<br />
{<br />
continue;<br />
}<br />
if($pattern[$i] != $file[$i])<br />
{<br />
return FALSE;<br />
}<br />
}<br />
return TRUE;<br />
Verzeichniszugriff<br />
}<br />
$path = $_SERVER['SCRIPT_FILENAME'];<br />
$self = dirname($path);<br />
$dir = dir($self);<br />
if (is_object($dir))<br />
{<br />
$dir->rewind();<br />
while ($file = $dir->read())<br />
{<br />
if (fnmatch("[ef]*.php", $file))<br />
{<br />
echo "$file";<br />
}<br />
}<br />
$dir->close();<br />
}<br />
Betrachten Sie zuerst nur den unteren Teil, der die Klasse dir verwendet. Hier<br />
wird zuerst der physikalische Pfad zur aktuellen Datei ermittelt, weil das Verzeichnis<br />
gelesen werden soll, in dem das Skript selbst liegt.<br />
$path = $_SERVER['SCRIPT_FILENAME'];<br />
261
262<br />
Das Dateisystem entdecken<br />
Die Servervariable SCRIPT_FILENAME wird hier verwendet, weil sie den kompletten<br />
Pfad enthält und als einzige Variable sowohl unter dem IIS- als auch Apache-Webserver<br />
zur Verfügung steht. Es gibt zwar viele vermeintliche Alternativen,<br />
aber die funktionieren nie auf allen Systemen. Aus der Pfadangabe wird dann das<br />
aktuelle Verzeichnis gewonnen:<br />
$self = dirname($path);<br />
Nun wird eine <strong>In</strong>stanz des Verzeichnisobjekts erstellt:<br />
$dir = dir($self);<br />
Nach einer Prüfung, ob es wirklich gelang, das Objekt zu erzeugen, wird die<br />
interne Liste der Dateien auf den Anfang gesetzt:<br />
$dir->rewind();<br />
Dann wird in einer Schleife Eintrag für Eintrag gelesen:<br />
while ($file = $dir->read())<br />
Abbildung 7.5:<br />
Gefilterte Dateiliste mit fnmatch
Verzeichniszugriff<br />
Die Methode read gibt FALSE zurück, wenn keine Einträge mehr da sind. <strong>In</strong>nerhalb<br />
der Schleife wird dann jeder Dateiname mit fnmatch untersucht:<br />
if (fnmatch("[ef]*.php", $file))<br />
Die Funktion fnmatch, ob eingebaut oder selbst definiert, durchsucht nun den<br />
Dateinamen entsprechend dem angegebenen Suchmuster Zeichen für Zeichen<br />
und gibt einen Booleschen Wert zurück (siehe Abbildung 7.5).<br />
Zum Schluss sollen noch die Möglichkeiten, die dir bietet, auf einen Blick gezeigt<br />
werden:<br />
Eigenschaft Beschreibung<br />
path Pfad, der zum Erzeugen des Objekts benutzt wurde<br />
handle Verzeichnis-Handle, das andere Funktionen benutzen können<br />
Tabelle 7.4: Eigenschaften der Klasse dir<br />
Eigenschaft Beschreibung<br />
read Liest den nächsten Eintrag aus dem Verzeichnis und gibt entweder<br />
eine Zeichenkette oder FALSE zurück.<br />
rewind Setzt den Lesezeiger wieder auf den ersten Eintrag zurück.<br />
close Schließt das Handle zum Verzeichnis.<br />
Tabelle 7.5: Methoden der Klasse dir<br />
Die Benutzung des Handles ist dann interessant, wenn – aus welchen Gründen<br />
auch immer – mit den anderen Verzeichnisfunktionen gearbeitet werden soll.<br />
Diese Funktionen sind nicht objektorientiert, sondern werden ebenso wie die<br />
Dateifunktionen benutzt. Eine Auflistung finden Sie in der Kurzreferenz am Ende<br />
des Kapitels.<br />
Rekursive Dateiliste<br />
Das folgende Beispiel erweitert die Ausgabe der Dateiliste auf eine beliebige<br />
Anzahl von Unterverzeichnissen. Die Technik der Rekursion bietet eine einfache<br />
Lösung dafür:<br />
263
264<br />
Das Dateisystem entdecken<br />
Listing 7.7: filerecursiveglob.php – Rekursive Version, um untergeordnete Verzeichnisse<br />
zu lesen<br />
function rglob($sDir, $sPattern, $nFlags = NULL)<br />
{<br />
$sDir = escapeshellcmd($sDir);<br />
$aFiles = glob("$sDir/$sPattern", $nFlags);<br />
foreach (glob("$sDir/*", GLOB_ONLYDIR) as $sSubDir)<br />
{<br />
$aSubFiles = rglob($sSubDir, $sPattern, $nFlags);<br />
if (is_array($aSubFiles))<br />
{<br />
$aFiles = array_merge($aFiles, $aSubFiles);<br />
}<br />
}<br />
return $aFiles;<br />
}<br />
$path = dirname(getcwd() . '/php5MuT');<br />
echo "Liste der PHP-Skripte in $path:";<br />
$aFiles = rglob($path, '{*.txt,a*.php}', GLOB_BRACE);<br />
foreach ($aFiles as $filepath)<br />
{<br />
echo dirname(realpath($filepath)) . ' : ';<br />
echo basename($filepath) . '';<br />
}<br />
Auch dieses Skript bietet wieder eine reiche Verwendung von Dateifunktionen.<br />
Zuerst wird der Pfad ermittelt. Im Beispiel wird das aktuelle Arbeitsverzeichnis<br />
(getcwd) und dort das Unterverzeichnis »php5MuT« genommen. Eine tiefere Verzeichnisstruktur<br />
kann Probleme bereiten, da die Laufzeit des Skripts bei tiefer<br />
Verschachtelung erheblich ist:<br />
$path = dirname(getcwd() . '/php5MuT');<br />
Nun wird die rekursiv programmierte Funktion rglob vorgestellt. Sie gibt ein Array<br />
mit allen Dateien zurück, die dem Suchmuster entsprechen, wobei alle Dateien in<br />
allen untergeordneten Verzeichnissen mit eingeschlossen werden. Bevor die<br />
Details der rekursiven Funktion diskutiert werden, ist ein Blick auf die Ausgabe<br />
interessant. glob liefert komplette Dateipfade. Um diese zu trennen wird zuerst der<br />
vollständige Pfad mit realpath ermittelt, dann mit dirname der Pfad extrahiert. Für<br />
basename, genutzt zum Abtrennen des Dateinamens, reicht die Nennung des<br />
Pfades ohne Veränderungen.
Verzeichniszugriff<br />
Die Funktion rglob selbst basiert im wesentlichen auf glob. Zuerst wird die aktuelle<br />
Dateiliste gemäß dem gewählten Suchmuster erstellt:<br />
$aFiles = glob("$sDir/$sPattern", $nFlags);<br />
Das Muster folgt dabei den üblichen Platzhalterregeln. Zusammen mit der Konstanten<br />
GLOB_BRACE kann außerdem eine Kette von Platzhalter angegeben<br />
werden. Im Beispiel sieht dieses Muster folgendermaßen aus:<br />
{*.txt,a*.php}<br />
Dieses Muster betrifft alle Dateien mit der Endung »txt« und alle Dateien, die mit<br />
dem Buchstaben »a« beginnen und auf »php« enden. Nach der Erstellung der<br />
Dateiliste werden die untergeordneten Verzeichnisse gesucht:<br />
glob("$sDir/*", GLOB_ONLYDIR)<br />
Hier sorgt der Schalter GLOB_ONLYDIR für das gewünschte Resultat. Um die Dateien<br />
innerhalb des gefundenen Verzeichnisses zu lesen, erfolgt als erste Maßnahme<br />
innerhalb der foreach-Schleife der rekursive Aufruf. Die Ergebnisse werden dann<br />
mit array_merge an das bestehende Array angehängt. Damit leere Verzeichnisse<br />
nicht stören, wird noch eine Abfrage mit is_array davor geschaltet.<br />
Verzeichniszugriff mit Iteratoren<br />
Iteratoren durchlaufen Auflistungen, vorzugsweise mit foreach. Das Konzept<br />
wurde neu in PHP5 eingeführt und ist Teil der so genannten Standard PHP<br />
Library. Alle Iteratoren implementieren die Schnittstelle Iterator. Dies führt zu<br />
den folgenden Methoden:<br />
current()<br />
Die Methode gibt das aktuelle Element der Auflistung zurück.<br />
next()<br />
Die Methode setzt einen Schritt in der Auflistung weiter.<br />
valid()<br />
Die Methode gibt TRUE zurück, wenn ein weiteres Element beim vorhergehenden<br />
Aufruf von next gefunden wurde, sonst FALSE.<br />
rewind()<br />
Mit dieser Methode wird der Zeiger wieder an den Anfang gesetzt.<br />
265
266<br />
Das Dateisystem entdecken<br />
Einige Iterator-Klassen bieten eine Reihe weiterer Methoden an, die den Zugriff<br />
auf spezifischen Eigenschaften der Elemente der Auflistung ermöglichen.<br />
Verzeichnisse werden mit <strong>In</strong>stanzen des DirectoryIterators verarbeitet. Folgende<br />
Methoden sind zusätzlich zu den Standardmethoden verfügbar:<br />
Methode Beschreibung<br />
fileATime Zeitpunkt des letzten Zugriffs.<br />
fileCTime Zeitpunkt der letzten Änderung.<br />
fileGroup Gruppe, der diese Datei zugeordnet ist.<br />
file<strong>In</strong>ode <strong>In</strong>ode dieser Datei (nur Unix).<br />
fileOwner Eigentümer dieser Datei.<br />
filePerms Zugriffsrechte an dieser Datei.<br />
fileSize Die Dateigröße.<br />
fileType Der Dateityp.<br />
getFileName Der Dateiname.<br />
getPath Pfad zu dieser Datei.<br />
hasMore Dieser Eintrag hat weitere Einträge.<br />
isDir TRUE, wenn der Eintrag ein Verzeichnis ist.<br />
isDot TRUE, wenn der Eintrag das Verzeichnis ».« oder »..« ist.<br />
isExecutable Die Datei ist ausführbar.<br />
isFile Dieser Eintrag ist eine Datei.<br />
isLink Dieser Eintrag ist ein Link (nur Unix-Links).<br />
isReadable Dieser Eintrag ist lesbar.<br />
isWritable Dieser Eintrag ist schreibbar.<br />
Tabelle 7.6: Methoden der Klasse DirectoryIterator
Das folgende Beispiel zeigt, wie der Iterator verwendet wird:<br />
Listing 7.8: dirIterator.php – Verzeichnisauflistung mittels SPL-Iterator<br />
Verzeichniszugriff<br />
<br />
<br />
* { font-family: Verdana; font-size:12pt}<br />
.dir { background-color:silver; margin-left:5px}<br />
.file { background-color:white; margin-left:15px}<br />
<br />
<br />
268<br />
Das Dateisystem entdecken<br />
7.4 Funktions-Referenz<br />
Datei-Funktionen<br />
Funktion Bedeutung<br />
basename Dateiname in einer vollständigen Pfadangabe.<br />
chgrp Ändert die Gruppenzugehörigkeit eines Dateieintrags (nur Unix).<br />
chmod Ändert den Zugriffsmodus eines Dateieintrags.<br />
chown Ändert den Eigentümer eines Dateieintrags (nur Unix * ).<br />
clearstatcache Löscht den Status-Cache mit den letzten Dateiinformationen.<br />
copy Kopiert eine Datei oder benennt sie um.<br />
delete Löscht eine Datei (ein Alias für unlink).<br />
dirname Verzeichnisname in einer vollständigen Pfadangabe.<br />
disk_free_space Ermittelt den freien Speicherplatz eines Datenträgers.<br />
disk_total_space Ermittelt den gesamten Speicherplatz eines Datenträgers<br />
fclose Schließt einen Dateizeiger.<br />
feof Testet einen Dateizeiger auf das Dateiende.<br />
fflush Überträgt den gesamten <strong>In</strong>halt in den Ausgabepuffer.<br />
fgetc Liest ein Zeichen aus einer Datei an der Position des<br />
Dateizeigers.<br />
fgetcsv Holt eine Zeile aus einer Datei und ermittelt CSV ** -Felder .<br />
fgets Holt die nächste Zeile ohne weitere Verarbeitung.<br />
fgetss Holt die nächste Zeile und entfernt alle HTML-Tags.<br />
Tabelle 7.7: Funktions-Referenz für Datei-Funktionen<br />
* Windows bietet dies auch, aber PHP unterstützt dies nicht auf Windows.<br />
** CSV = Comma Separated Values, allgemein: Dateien mit Feldtrennzeichen, beispielsweise Kommata, Semikola<br />
oder Tabulatoren.
Funktion Bedeutung<br />
file_exists Prüft, ob eine Datei existiert.<br />
file_get_contents Liest die gesamte Datei in eine Zeichenkette.<br />
file_put_contents Schreibt eine Zeichenkette in eine Datei.<br />
file Liest die gesamte Datei in ein Array.<br />
fileatime Ermittelt den Zeitpunkt des letzten Dateizugriffs.<br />
Funktions-Referenz<br />
filectime Ermittelt den Zeitpunkt der letzten Änderung des Dateizeigers<br />
<strong>In</strong>ode.<br />
filegroup Ermittelt die Gruppenzugehörigkeit einer Datei (nur Unix).<br />
fileinode Ermittelt den <strong>In</strong>ode einer Datei (nur Unix).<br />
filemtime Ermittelt den Zeitpunkt der letzten Änderung der Datei.<br />
fileowner Ermittelt den Eigentümer einer Datei (nur Unix).<br />
fileperms Ermittelt die Dateizugriffsregeln.<br />
filesize Ermittelt die Dateigröße.<br />
filetype Ermittelt den Dateitype.<br />
flock Blockiert eine Datei für exklusiven Zugriff.<br />
fnmatch Prüft einen Dateinamen gegen ein bestimmtes Muster.<br />
fopen Öffnet eine Datei oder ein URL.<br />
fpassthru Liefert alle ausstehenden Daten an einen Dateizeiger.<br />
fputs Schreibt Daten in eine Datei, angegeben durch ein Dateihandle.<br />
fread Liest eine Datei binär.<br />
fscanf Liest aus einer Datei und interpretiert den <strong>In</strong>halt gemäß dem<br />
angegebenen Suchmuster.<br />
fseek Setzt den Dateizeiger.<br />
fstat Ermittelt Statusinformationen über eine Datei.<br />
Tabelle 7.7: Funktions-Referenz für Datei-Funktionen (Forts.)<br />
269
270<br />
Das Dateisystem entdecken<br />
Funktion Bedeutung<br />
ftell Ermittelt die Position des Dateizeigers.<br />
ftruncate Schneidet eine Datei an der angegebenen Position ab.<br />
fwrite Schreibt Binärdaten in eine Datei.<br />
glob Sucht Pfadnamen anhand eines Suchmusters.<br />
is_dir Ermittelt, ob die Pfadangabe ein Verzeichnisname ist.<br />
is_executable Ermittelt, ob die Pfadangabe eine ausführbare Datei darstellt.<br />
is_file Ermittelt, ob die Pfadangabe eine Datei bezeichnet.<br />
is_link Ermittelt, ob die Pfadangabe ein symbolischer Link ist (nur Unix).<br />
is_readable Ermittelt, ob die Pfadangabe auf eine lesbare Datei zeigt.<br />
is_uploaded_file Ermittelt, ob die Pfadangabe auf eine per HTTP-Upload hochgeladene<br />
Datei zeigt.<br />
is_writable Ermittelt, ob die Pfadangabe auf eine beschreibbare Datei zeigt .<br />
link Erzeugt eine Verknüpfung (nur Unix * ).<br />
linkinfo Liefert <strong>In</strong>formationen über einen Dateilink.<br />
lstat Liefert <strong>In</strong>formationen über einen symbolischen Link.<br />
move_uploaded_file Verschiebt eine hochgeladene Datei in ihr finales Verzeichnis.<br />
parse_ini_file Untersucht eine Konfigurationsdatei.<br />
pathinfo Ermittelt <strong>In</strong>formationen über eine Pfadangabe.<br />
readfile Liest eine Datei und gibt den <strong>In</strong>halt sofort aus.<br />
readlink Liest das Ziel eines symbolischen Links (nur Unix).<br />
realpath Ermittelt den tatsächlichen vollständigen Pfad aus einer relativen<br />
Angabe.<br />
rename Benennt ein Verzeichnis oder eine Datei um.<br />
Tabelle 7.7: Funktions-Referenz für Datei-Funktionen (Forts.)<br />
* Windows kennt auch Verknüpfungen, die jedoch anders realisiert sind und von PHP nicht als Links erkannt<br />
werden.
Funktion Bedeutung<br />
Funktions-Referenz<br />
rewind Setzt die Position des Dateizeigers zurück auf den Anfang.<br />
stat Ermittelt Statusinformationen über eine Datei und speichert<br />
diese. Spätere Anforderungen rufen die zwischengespeicherten<br />
Daten ab, bis der Puffer mit clearstatcache gelöscht wird.<br />
symlink Erzeugt einen neuen symbolischen Link (nur Unix).<br />
tempnam Erzeugt einen temporären Dateinamen.<br />
tmpfile Erzeugt einen temporären Dateinamen und die entsprechende<br />
Datei.<br />
touch Ändert die letzte Zugriffszeit einer Datei.<br />
umask Ändert die Umask einer Datei (nur Unix).<br />
unlink Löscht eine Datei (auch Windows).<br />
Tabelle 7.7: Funktions-Referenz für Datei-Funktionen (Forts.)<br />
Funktion Bedeutung<br />
chdir Ändert das aktuelle Verzeichnis, aus dem gelesen wird.<br />
chroot Ändert das Stammverzeichnis.<br />
closedir Schließt das Verzeichnis-Handle.<br />
dir <strong>In</strong>stanziiert die Verzeichnisklasse (siehe Text).<br />
dirname Ermittelt den Verzeichnisnamen in einer vollständigen Pfadangabe.<br />
getcwd Ermittelt das aktuelle Arbeitsverzeichnis.<br />
mkdir Erzeugt ein Verzeichnis.<br />
opendir Öffnet ein Handle auf ein Verzeichnis prozedural.<br />
readdir Liest einen Eintrag aus einem Verzeichnis-Handle.<br />
rewinddir Setzt den Zeiger vor dem Lesen der Einträge auf den Anfang<br />
zurück.<br />
Tabelle 7.8: Funktions-Referenz für Verzeichnis-Funktionen<br />
271
272<br />
Das Dateisystem entdecken<br />
Funktion Bedeutung<br />
rmdir Entfernt ein Verzeichnis.<br />
scandir Liest Dateien eines bestimmten Pfades.<br />
Tabelle 7.8: Funktions-Referenz für Verzeichnis-Funktionen (Forts.)<br />
Prozess-Funktionen<br />
Funktion Bedeutung<br />
escapeshellarg Behandelt eine Zeichenkette so, dass sie als Argument für Shell-<br />
Aufrufe verwenden werden kann, indem die Anführungszeichen<br />
maskiert werden.<br />
escapeshellcmd Maskiert Shell-Zeichen so, dass sie nicht zum Missbrauch bei der<br />
Ausführung von Kommandos verwendet werden können.<br />
exec Führt ein externes Programm aus. Die Funktion erzeugt keine Ausgabe<br />
und gibt lediglich die letzte Zeile der Antwort des Programms<br />
zurück.<br />
passthru Führt ein externes Programm aus und zeigt die Ausgabe an.<br />
pclose Schließt ein Prozesshandle (einfache Form).<br />
popen Öffnet einen Prozess und erzeugt das passende Prozesshandle (einfache<br />
Form).<br />
proc_close Schließt ein Prozesshandle.<br />
proc_get_status Ermittelt Statusinformationen über den Prozess. Die Rückgabe<br />
erfolgt als Array, dessen Elemente Angaben wie das verwendete<br />
Kommando (command), die Prozess-ID (pid) oder den Rückgabecode<br />
(exitcode) enthalten.<br />
proc_nice Ändert die Priorität des Prozesses (nicht auf Windows verfügbar).<br />
proc_open Öffnet einen Prozess und erzeugt das passende Prozesshandle.<br />
proc_terminate Beendet einen Prozess.
Funktion Bedeutung<br />
7.5 Kontrollfragen<br />
1. Auf welche Datenquellen können die Dateifunktionen zugreifen?<br />
Kontrollfragen<br />
shell_exec Führt ein Kommando über die Shell (Eingabeaufforderung) aus.<br />
Alternativ kann auch der »Backtick«-Operator ’kommando’ verwendet<br />
werden.<br />
system Führt ein externes Programm aus und zeigt die Ausgabe an.<br />
2. Warum muss eine Datei nach der Benutzung wieder geschlossen werden?<br />
3. Schreiben Sie ein Skript, dass eine beliebige Datei aus dem aktuellen Verzeichnis<br />
im Quelltext anzeigt, wobei der Benutzer die Datei selbst wählen kann. Tipp:<br />
Benutzen Sie HTML-Links und ermitteln<br />
Sie den übergebenen Namen mittels $_GET[’name’].<br />
4. Warum ist das in der letzten Übung verlangte Prinzip auf einer öffentlichen<br />
Website nicht unmodifiziert einsetzbar? Tipp: Denken Sie an mögliche Sicherheitsprobleme.<br />
273
Tag g 1 Einführung 21<br />
Tag g 2 Erste Schritte 45<br />
Tag g 3 Daten verarbeiten 73<br />
Tag g 4 Programmieren 131<br />
Tag g 5 Daten mit Arrays verarbeiten 181<br />
Tag g 6 Objektorientierte Programmierung 203<br />
Tag g 7 Das Dateisystem entdecken 243<br />
Tag g 8 Formular- und Seitenmanagement 277<br />
Tag g 9 Professionelle Programmierung 373<br />
Tag g g 10 10 10 Kommunikation per HTTP, FTP und E-Mail 415<br />
Tag g 11 11 11 Datenbankprogrammierung 443<br />
Tag g 12 12 Die integrierte Datenbank <strong>SQL</strong>ite 497<br />
Tag g 13 13 Datenbanklösungen mit <strong>My</strong><strong>SQL</strong> 509<br />
Tag g <strong>14</strong> <strong>14</strong> XML und Webservices 549<br />
W O<br />
C HE<br />
W OC<br />
H E
Formular- und<br />
Seitenmanagement<br />
8
278<br />
Formular- und Seitenmanagement<br />
8.1 Grundlagen in HTML<br />
Formulare sind das wichtigste Element einer Website. Kaum eine Seite kann praktisch<br />
ohne Eingabeelemente betrieben werden, denn nur darüber kann der Nutzer<br />
wirklich Kontakt mit Ihnen aufnehmen.<br />
Viel HTML – wenig PHP<br />
Formulare haben viel mit HTML und wenig mit PHP zu tun. Das ist eigentlich<br />
eine der herausragenden Stärken von PHP, denn für den Programmierer ist wenig<br />
zu tun, damit man bequem an die Daten herankommt. Freilich müssen Sie sicher<br />
im Umgang mit den entsprechenden HTML-Tags sein. Springen Sie zum nächsten<br />
Abschnitt, wenn es keine Fragen mehr im Umgang mit , <br />
und gibt. Besteht doch noch die eine oder andere Unsicherheit, lesen Sie<br />
unbedingt diesen Abschnitt.<br />
Die HTML-Formularelemente kurz vorgestellt<br />
Dieses Buch soll kein HTML-Buch ersetzen. Eine kompakte Darstellung auf den<br />
folgenden Seiten ist jedoch hilfreich, vor allem wenn man alternativ vor einem<br />
1000-Seiten-Schinken eines HTML-Gurus sitzt.<br />
Zuerst eine prinzipielle Aussage: Ein Formular entsteht, indem man die entsprechenden<br />
Elemente in ein -Tag packt. <strong>In</strong> den vorangegangenen Kapiteln<br />
wurde dies bereits häufig getan.<br />
Nun müssen Sie alle Tags kennen, mit denen HTML-Formulare erstellt werden<br />
können. Die folgende Tabelle fasst diese zusammen:<br />
Element Elementtyp<br />
type="..."<br />
Beschreibung Wichtige Attribute<br />
input text Einzeiliges Eingabefeld size, value, name<br />
checkbox Kontrollkästchen value, checked, name<br />
radio Optionsschaltfläche value, checked, name<br />
submit Sendeschaltfläche value, name<br />
Tabelle 8.1: Formular-Elemente in HTML
Element Elementtyp<br />
type="..."<br />
reset Schaltfläche zum Rücksetzen<br />
Grundlagen in HTML<br />
value, name<br />
password Verdecktes Eingabefeld size, value, name<br />
hidden Unsichtbares Feld value, name<br />
button Schaltfläche value, name<br />
image Bild, ersetzt submit und ist<br />
damit wie eine Sendeschaltfläche<br />
file Eingabefeld und Schalter<br />
zum Hochladen von Dateien<br />
src, name,<br />
Bildattribute<br />
name, accept<br />
select Dropdown-Liste multiple, name, size<br />
option Element der Dropdown-Liste<br />
(nur Sub-Element)<br />
value<br />
textarea Mehrzeiliges Textfeld name, rows, cols<br />
Tabelle 8.1: Formular-Elemente in HTML (Forts.)<br />
Die Attribute sind für die Steuerung des Verhaltens erforderlich und haben die in<br />
der folgenden Tabelle gezeigte Bedeutung:<br />
Attributname Beschreibung<br />
name Name des Feldes. Unter diesem Namen wird es in PHP erreicht.<br />
size Breite des Feldes, bei select ist dies die Anzahl der sichtbaren Zeilen.<br />
value Der Wert des Felds. Kann aus Sicherheitsgründen bei type="file" und<br />
type="password" nicht gesetzt werden.<br />
multiple Erlaubt die Mehrauswahl in Listen (select)<br />
rows Anzahl der Zeilen bei textarea.<br />
cols Anzahl der Spalten bei textarea.<br />
Beschreibung Wichtige Attribute<br />
Tabelle 8.2: Attribute der Formular-Elemente (nur formularspezifische wurden aufgeführt)<br />
279
280<br />
Formular- und Seitenmanagement<br />
Daneben können alle Elemente natürlich mit den Standardattributen wie class,<br />
style, id usw. bestückt werden.<br />
8.2 Auswerten der Daten aus Formularen<br />
Die Nutzung von Formularen erfolgt immer in einer zweiteiligen Form. Zum<br />
einen wird ein Formular benötigt, das die Namen der Felder und den potenziellen<br />
<strong>In</strong>halt festlegt. Zum anderen wird ein PHP-Skript erstellt, das die Daten auswertet<br />
und beispielsweise in eine Datenbank schreibt. Die Aufnahme der Daten und die<br />
Auswertung umfasst immer einige elementare Schritte.<br />
Grundlegende Schritte<br />
Vor der ersten Auswertung muss klar sein, wohin ein Formular gesendet werden<br />
soll. Dies ist, bestimmt durch das Attribut action des -Tags, natürlich der<br />
Name eines PHP-Skripts.<br />
Die einfachste Form ist der Selbstaufruf. Dabei ist das Skript, das das Formular<br />
definiert, selbst das Ziel. Es ist sinnvoll, das Ziel am Anfang des Skripts zu definieren:<br />
$action = $_SERVER['PHP_SELF'];<br />
Die Variable $action wird dann im Formular verwendet, beispielsweise bei einer<br />
mit print oder echo erzeugten Ausgabe direkt, wie nachfolgend gezeigt:<br />
echo
…<br />
<br />
Den Zustand des Formulars erkennen<br />
Auswerten der Daten aus Formularen<br />
Es gibt prinzipiell zwei Zustände, in denen ein Skript aufgerufen werden kann,<br />
bestimmt durch die verwendete HTTP-Methode. Um Formulardaten zu versenden,<br />
wird POST verwendet. Um ein Skript direkt auszuführen, wird GET verwendet.<br />
Damit kann man unterscheiden, ob das Skript das erste Mal zur Anzeige des<br />
Formulars aufgerufen wurde, oder ob der Benutzer es bereits ausgefüllt und wieder<br />
abgesendet hat.<br />
Der erste Schritt bei der Auswertung besteht also darin, überhaupt zu erkennen, ob<br />
das Formular einer Auswertung bedarf. Das folgende Beispiel zeigt, wie die Servervariable<br />
REQUEST_METHOD dazu verwendet wird:<br />
Listing 8.1: formmethod.php – Erkennen der verwendeten HTTP-Methode<br />
<br />
282<br />
Formular- und Seitenmanagement<br />
Die Herkunft erkennen<br />
Abbildung 8.2:<br />
Anzeige des Formulars, nachdem es mit der Schaltfläche abgesendet<br />
wurde<br />
Manchmal ist es wichtig zu wissen, woher ein Formular kommt. Entweder aus<br />
Sicherheitsgründen oder zur Organisation des Zusammenhangs der einzelnen<br />
Skripte einer Applikation muss auf den so genannten »Referer« zugegriffen werden.<br />
Auch hierfür steht eine Servervariable zur Verfügung: HTTP_REFERER. Die<br />
Abfrage ist meist nur sinnvoll, wenn das Formular bereits abgesendet wurde, weil<br />
der erste Aufruf eines Skripts auch durch direkte Eingabe der Adresse im Browser<br />
erfolgen kann. <strong>In</strong> diesem Fall existiert jedoch kein Referer und die Variable ist<br />
leer.<br />
Das folgende Skript kontrolliert den Selbstaufruf und sichert sich dagegen ab, von<br />
einer anderen Quelle aus aufgerufen zu werden.<br />
Listing 8.2: formmethodreferer.php – Die Herkunft eines Aufrufs ermitteln<br />
}<br />
?><br />
284<br />
Formular- und Seitenmanagement<br />
Zustandsabfrage für Felder<br />
Über den Datentyp müssen Sie sich bei Formularen keine Gedanken machen.<br />
HTML kennt nur Zeichenketten und als solche kommen alle Daten im Skript an.<br />
Umwandlungen müssen dann bei Bedarf explizit vorgenommen werden. Wichtiger<br />
bei der ersten Auswertung ist die Erkennung leerer Felder. PHP erzeugt leere<br />
Arrayelemente für unausgefüllte Felder. Man kann deshalb immer überprüfen, ob<br />
ein spezifisches Element auch vorhanden ist und die Abfrage danach gestalten.<br />
Am einfachsten erfolgt die Auswertung mit empty. Im Skript sieht das dann folgendermaßen<br />
aus:<br />
Listing 8.3: formisset.php – Prüfung, ob ein Feld ausgefüllt wurde<br />
Auswerten der Daten aus Formularen<br />
<br />
<br />
<br />
<br />
<br />
286<br />
Formular- und Seitenmanagement<br />
{<br />
if (!empty($_POST['Loginname']))<br />
{<br />
echo "LoginName: {$_POST['Loginname']}";<br />
}<br />
if (!empty($_POST['Subject']))<br />
{<br />
$rawsubject = $_POST['Subject'];<br />
$htmsubject = nl2br($rawsubject);<br />
echo
Auswerten der Daten aus Formularen<br />
<br />
<br />
<br />
Das Skript verwendet die übliche Auswertung mit empty zum Erkennen leerer Felder.<br />
Wie die Daten dann aufbereitet werden, hängt von der Anwendung ab. Im<br />
Beispiel werden die Daten in beiden Varianten ausgegeben.<br />
Wenn Sie die Felddaten innerhalb von -Tags anzeigen, dann werden<br />
die integrierten Zeilenumbrüche ausgegeben. <strong>In</strong> einem solchen<br />
Fall wäre die zusätzliche Angabe von fatal, weil sich die Umbrüche<br />
verdoppeln würden.<br />
Optionsfelder und Kontrollkästchen<br />
Abbildung 8.5:<br />
Verarbeitung von Zeilenumbrüchen<br />
aus einem<br />
Textfeld<br />
Ebenso wichtig wie die übrigen Feldarten sind auch Optionsfelder und Kontrollkästchen.<br />
Beide sind eng miteinander verwandt und werden ähnlich eingesetzt:<br />
Optionsfelder (Radiobutton) werden zu Gruppen zusammengefasst und erlauben<br />
die Auswahl genau einer Option (daher der Name 1 ) aus einer Liste<br />
1 Der englische Name rührt von der Anzeigeform her: Die runden Optionsfelder sehen aus wie runde Knöpfe<br />
an alten Radios.<br />
287
288<br />
Formular- und Seitenmanagement<br />
Kontrollkästchen (Checkbox) sind nicht miteinander verbunden und lassen<br />
eine Mehrfachauswahl zu, beispielsweise zur Konfiguration oder Kontrolle<br />
(daher der Name 2 ).<br />
Optionsfelder<br />
Optionsfelder werden gruppiert, indem allen Feldern derselbe Name gegeben<br />
wird. Da immer nur ein Feld ausgewählt werden kann, übermittel der Browser zu<br />
dem gemeinsamen Namen den Wert, den man dem Feld vorher zugeordnet hat.<br />
Der Wert wird mit dem Attribut value festgelegt. Im $_POST-Array taucht dann<br />
genau ein Wert auf.<br />
Es ist möglich, dass kein Feld ausgewählt wurde. Erst wenn der Benutzer<br />
ein Feld einmal angeklickt hat, kann er den Zustand »nichts ausgewählt«<br />
nicht wieder herstellen. Es ist prinzipiell eine schlechte Benutzerführung,<br />
einen Zustand vorzugeben, der nicht wieder hergestellt<br />
werden kann. Sie sollten deshalb unbedingt den Anfangszustand dediziert<br />
setzen.<br />
Das folgende Beispiel zeigt eine Auswahl von mehreren Werten und setzt den<br />
Anfangszustand durch das Attribut checked auf den ersten Wert.<br />
Kontrollkästchen<br />
Jedes Kontrollkästchen setzt seinen eigenen Wert, weshalb jedes auch einen eigenen<br />
Namen erhält. Der Wert ist standardmäßig entweder leer oder »on« (aktiviert).<br />
Wenn Sie einen anderen Wert benötigen, wird das Attribute value benutzt.<br />
Ebenso wie bei den Optionsfeldern dient das Attribute checked dazu, das Kontrollkästchen<br />
bereits beim ersten Aufruf aktiviert erscheinen zu lassen.<br />
Beispiel für Optionsfelder und Kontrollkästchen<br />
Das folgende Beispiel zeigt, wie die <strong>In</strong>formationen aus Optionsfeldern und Kontrollkästchen<br />
verwendet werden. Beachten Sie vor allem die Benennung der Felder<br />
in HTML:<br />
2 Der englische Name rührt auch hier von der Anzeigeform her: Kontrollkästchen lassen sich ankreuzen oder<br />
auswählen (engl. »to check«).
Auswerten der Daten aus Formularen<br />
Listing 8.5: formcheckradio.php – Umgang mit Kontrollkästchen und Optionsfeldern<br />
<br />
290<br />
Formular- und Seitenmanagement<br />
Welche Sprachen kennen Sie?<br />
<br />
Perl <br />
VBScript<br />
<br />
PHP <br />
C# <br />
C++ <br />
Delphi<br />
<br />
Fortran<br />
<br />
<br />
<br />
<br />
<br />
Wie beurteilen Sie PHP?<br />
<br />
<br />
1 <br />
2 <br />
3 <br />
4 <br />
5 <br />
6 <br />
<br />
<br />
<br />
<br />
<br />
<br />
Dieses Beispiel zeigt bereits einige wichtige Techniken beim Umgang mit Formularelementen.<br />
Vor allem die Auswertung einer großen Anzahl von Elementen<br />
bereitet mitunter Schwierigkeiten.<br />
Beim Optionsfeld wurde der Name »PHPNote« für alle Elemente gewählt. <strong>In</strong><br />
PHP ist die Auswertung relativ einfach, denn es genügt die Abfrage des Arrayelements<br />
$_POST['PHPNote']. Im Beispiel wurde durch die Angabe des Attributes checked<br />
beim Formularelement eine Erstauswahl erzwungen. Damit ist sichergestellt,<br />
dass dieses Element immer einen Eintrag im Array erzeugt, denn der Benutzer<br />
kann Optionsfelder nicht abwählen. Die folgende Bedingung kann deshalb, theoretisch,<br />
nie FALSE sein:
if (!empty($_POST['PHPNote']))<br />
Auswerten der Daten aus Formularen<br />
Sie wurde dennoch im Skript belassen, um zu zeigen, wie man mit einer leeren<br />
Auswahl umgehen muss.<br />
Die Auswertung der Kontrollkästchen bereitet unter Umständen größere Probleme.<br />
Die Namen lassen sich natürlich alle einzeln auswerten. Bei Änderungen<br />
am Code ist die Fehlerquote aber relativ hoch. Das Skript reduziert die Abhängigkeit<br />
von den Namen auf eine einzige Stelle. Zuerst wird abgefragt, ob überhaupt<br />
Felder ausgefüllt wurden:<br />
if (count($_POST) > 0)<br />
Dann werden alle Felder mit einer Schleife durchlaufen. Darin tauchen natürlich<br />
auch die auf, die nicht zu den Kontrollkästchen gehören:<br />
foreach ($_POST as $name => $value)<br />
Jetzt werden die benötigten Felder mit einer switch-Anweisung extrahiert. Wurde<br />
ein passendes Feld gefunden, wird der Wert ermittelt. Die Zahl 1 ist nur dann vorhanden,<br />
wenn das entsprechende Kontrollkästchen aktiviert wurde. Standardmäßig<br />
wird hier »on« übermittelt, man muss die 1 explizit mit dem HTML-Attribut<br />
value vereinbaren.<br />
if ($value == 1)<br />
{<br />
$result[] = $name;<br />
}<br />
Wenn das entsprechende Kontrollkästchen aktiviert war, wird der Name in das<br />
Array $result geschrieben. Der Trick mit dem Array dient der schnellen Generierung<br />
einer kommaseparierten Liste. Dies erledigt am Ende, bei der Ausgabe die<br />
implode-Funktion:<br />
implode($result, ',')<br />
Gegenüber dem systematischen Anhängen erkannter Einträge an die Zeichenkette,<br />
beispielsweise nach dem Schema $result .= $value . ',', vermeidet man<br />
damit das abschließende Komma (siehe Abbildung 8.6).<br />
Der Umgang mit einer großen Anzahl von Kontrollkästchen und ähnlicher Elemente<br />
verlangt in der Praxis noch nach weiteren Lösungen. Das erste Beispiel in<br />
diesem Abschnitt soll deshalb noch etwas verbessert werden. Um die Anzahl der<br />
Elemente flexibel zu halten, wäre auch eine dynamische Generierung der Kontrollkästchen<br />
wünschenswert. Dazu werden Feldnamen und Typen in einem Array<br />
festgelegt. Die Namen enthalten ein Präfix, das die spätere Selektierung erleichtert.<br />
Die Präfixe können etwa diesem Schema folgen:<br />
291
292<br />
Formular- und Seitenmanagement<br />
Kontrollkästchen: chk<br />
Optionsfelder: rdb<br />
Textfelder: txt<br />
Textarea: txa<br />
Dropdown-Listen: drp<br />
Schaltflächen: btn<br />
Das folgende Skript zeigt, wie das praktisch aussieht:<br />
Abbildung 8.6:<br />
Das Formular in Aktion<br />
Listing 8.6: formcheckauto.php – Dynamisch Listen von Feldern ersetzen<br />
$action = $_SERVER['PHP_SELF'];<br />
$checkboxes = array (<br />
'chkPerl' => array('Perl', 'Perl', '1', FALSE),<br />
'chkPHP' => array('PHP', 'PHP', '1', TRUE),<br />
'chkVBS' => array('VBScript', 'Visual Basic Script', '1', TRUE),<br />
'chkCSharp' => array('C#', 'C# (.NET)', '1', FALSE),<br />
'chkCPlusPlus' => array('C++', 'C++ (VSC++)', '1', FALSE),<br />
'chkDelphi' => array('Delphi', 'Delphi (Pascal)', '1', FALSE),<br />
'chkFortran' => array('Fortan', 'Fortran', '1', FALSE));<br />
if ($_SERVER['REQUEST_METHOD'] == 'POST')<br />
{<br />
foreach ($_POST as $fName => $fValue)<br />
{<br />
if (substr($fName, 0, 3) == 'chk')<br />
{
Auswerten der Daten aus Formularen<br />
echo <br />
<br />
<br />
<br />
<br />
<br />
<br />
Hier wird zuerst ein Array definiert, das alle Daten enthält, die zur Konstruktion<br />
der Kontrollkästchen erforderlich sind. Die Struktur zeigt ein verschachteltes<br />
Array. Die erste Ebene enthält als Schlüssel den künftigen Namen des Felds:<br />
$checkboxes = array ('chkPerl' => …<br />
293
294<br />
Formular- und Seitenmanagement<br />
Jedes Kontrollkästchen bekommt dann einen Satz von Eigenschaften als weiteres<br />
Array:<br />
… array('Perl', 'Perl', '1', FALSE)<br />
Die Bedeutung der Elemente ist willkürlich gewählt. Im Beispiel gilt folgendes<br />
(nach den <strong>In</strong>dizes):<br />
[0]: Kurzname<br />
[1]: Langname oder Beschreibung<br />
[2]: Wert, der gesendet wird, wenn das Kontrollkästchen aktiviert ist<br />
[3]: Boolescher Wert, der den Zustand »checked« beim ersten Aufruf bestimmt<br />
Die Generierung der Elemente lässt sich sehr praktisch mit printf erreichen. Hier<br />
wird – angeordnet in einer Schleife – der <strong>In</strong>halt des Arrays als Parameter an die<br />
Stellen im Feld übergeben, wo sie benötigt werden:<br />
printf('<br />
%s (%s)',<br />
$boxName,<br />
($boxData[3]) ? 'checked' : '',<br />
$boxData[2],<br />
$boxData[0],<br />
$boxData[1]<br />
);<br />
Die Variable $boxData repräsentiert hier das innere Array aus der Definition. Die<br />
Auswahl der Elemente erfolgt über die beschriebenen <strong>In</strong>dizes.<br />
Bei der Auswertung wird die Tatsache ausgenutzt, dass die Feldnamen mit »chk«<br />
beginnen. Für die Auswahl sorgt eine if-Anweisung:<br />
if (substr($fName, 0, 3) == 'chk')<br />
Damit funktioniert die ganze Konstruktion auch, wenn weitere Formularelemente<br />
im Spiel sind, vorausgesetzt man hält sich konsequent an das Präfix-Schema.<br />
Das hier vorgestellte Schema der automatischen Feldgenerierung wurde<br />
exemplarisch für Kontrollkästchen gezeigt. Es gilt in ähnlicher Form für<br />
alle Feldelemente und sollte generell zur dynamischen Generierung<br />
eingesetzt werden.
Dropdown-Listen<br />
Auswerten der Daten aus Formularen<br />
Zu Dropdown-Listen pflegen Benutzer wie Webmaster eine gewisse Hassliebe. Sie<br />
bieten den Vorteil, sehr viele vorbereitete <strong>In</strong>formationen in sehr kompakter Form<br />
anzeigen zu können. Formulare mit vielen Dropdown-Listen verbergen aber auch<br />
<strong>In</strong>formationen vor dem Benutzer; sie zwingen dazu, die Liste aufzuklappen, zu<br />
scrollen und dann eine Auswahl zu treffen. Statt eines Klicks sind oft zwei oder<br />
mehr erforderlich. Dies ist unter Umständen lästig. <strong>In</strong> der Praxis muss man einen<br />
guten Kompromiss zwischen dem Volumen des Gesamtformulars und dem sichtbaren<br />
<strong>In</strong>formationsvolumen wahren. Listen mit Hunderten Einträgen sind schwer<br />
beherrschbar und Listen mit nur zwei Einträgen besser durch Optionsfelder darstellbar.<br />
Die Wahrheit für die optimale Anzahl der Elemente liegt irgendwo zwischen<br />
drei und zwanzig Optionen.<br />
Aufbau der Dropdown-Listen<br />
Der prinzipielle Aufbau folgt folgendem Muster:<br />
<br />
Angezeigter Text<br />
<br />
Die Anzahl der Option-Tags ist nicht begrenzt, sollte aber in den bereits erwähnten<br />
Grenzen liegen, um den Benutzer nicht mit unnützen Klicks zu quälen.<br />
Standardattribute<br />
Die Attribute des Select-Tags verlangen eine genauere Untersuchung, denn die<br />
Angaben können sich auch auf den PHP-Code auswirken, der zur Auswertung<br />
benötigt wird. Am einfachsten ist name, das wie bei allen anderen Tags zur Generierung<br />
des Eintrags im $_POST-Array führt. Für die Anzeige entscheidend ist size.<br />
Hiermit legen Sie fest, wie viele Elemente gleichzeitig sichtbar sind. Der Standardwert<br />
»1« erzeugt eine echte Klappliste, bei der nur der erste Eintrag sichtbar ist.<br />
Jeder andere Wert größer als 1 erzeugt eine Listbox, die die bestimmte Anzahl Elemente<br />
anzeigt. Sind mehr Optionen vorhanden, wird ein Rollbalken erzeugt. Sind<br />
weniger Werte vorhanden, bleiben die Zeilen der Listbox leer (was sehr unprofessionell<br />
aussieht).<br />
295
296<br />
Formular- und Seitenmanagement<br />
Listing 8.7: formselect.php – Aufbau und Auswertung einer Dropdown-Liste<br />
$action = $_SERVER['PHP_SELF'];<br />
if ($_SERVER['REQUEST_METHOD'] == 'POST')<br />
{<br />
if (!empty($_POST['Language']))<br />
{<br />
echo "Ihre Auswahl: {$_POST['Language']}";<br />
}<br />
}<br />
else<br />
{<br />
echo 'Erster Aufruf der Seite';<br />
}<br />
?><br />
Mehrfachauswahl<br />
Auswerten der Daten aus Formularen<br />
Abbildung 8.7:<br />
Die Dropdown-Liste in Aktion<br />
Die Dropdown-Liste kann mit der Angabe des Attributes multiple dazu veranlasst<br />
werden, mehrere Werte gleichzeitig auswählbar zu machen. Wie die Mehrfachauswahl<br />
erfolgt, hängt vom Betriebssystem ab. Unter Windows ist dazu die Steuerungs-Taste<br />
(Strg) zu drücken, während Einträge mit der Maus gewählt werden.<br />
Da das Element nach wie vor nur einen Namen besitzt, stellt sich die Frage, wie<br />
mehrere Werte in PHP ankommen.<br />
Der Trick besteht darin, PHP die Möglichkeit der Auswahl mehrerer Werte anzuzeigen.<br />
Dazu wird dem Namen im HTML-Element einfach das Arraykennzeichen<br />
[] angehängt. PHP verpackt die Werte dann in ein Array, aus dem sie sich leicht<br />
wieder entnehmen lassen. Das letzte Beispiel wird dazu im HTML-Teil leicht verändert<br />
und im PHP-Abschnitt um die Ausgabe des Arrays erweitert. Außerdem<br />
wird die bereits bei den Kontrollkästchen und Optionsfeldern gezeigte Technik<br />
der automatischen Generierung gezeigt, die auch bei Optionsfeldern gute Dienste<br />
tut:<br />
Listing 8.8: formselectauto.php – Mehrfachauswahl von Listen<br />
298<br />
Formular- und Seitenmanagement<br />
if (is_array($_POST['Language']))<br />
{<br />
echo "Auswahl: " . implode($_POST['Language'], ', ');<br />
}<br />
}<br />
else<br />
{<br />
echo 'Erster Aufruf der Seite';<br />
}<br />
?><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Dieses Skript enthält zum einen wieder die Generierung der Optionen über eine<br />
als Array angelegte Definition, ähnlich wie bei den Kontrollkästchen gezeigt. Das<br />
Array enthält die nötigen Angaben zum Options-Wert (value) und dem Text, der<br />
dem Benutzer angezeigt wird. Der dritte Wert wird hier noch nicht verwendet; das<br />
nächste Beispiel nimmt darauf Bezug. Die Ausgabe des Arrays erfolgt mit einer<br />
foreach-Schleife, in der eine printf-Funktion die -Tags sachgerecht<br />
erzeugt. Wichtig ist bei der Definition des Select-Tags, dass hinter dem Namen die<br />
eckigen Klammern stehen, wenn das Attribut multiple benutzt wird:<br />
Auswerten der Daten aus Formularen<br />
Im PHP-Teil geht es nun anders zur Sache. Es ist sinnvoll, statt auf ein leeres Feld<br />
auf ein Array zu prüfen:<br />
if (is_array($_POST['Language']))<br />
Das Array wird auch erzeugt, wenn nur ein Element angeklickt wurde. Danach<br />
steht natürlich jede Array-Funktion zur Weiterverarbeitung zur Verfügung. Im<br />
Beispiel wird eine kommaseparierte Liste mit implode erzeugt.<br />
Die Größe der Liste wurde übrigens auf 5 gesetzt (size="5"), sodass immer fünf<br />
Werte sichtbar sind. Bei einer Mehrfachauswahl ist dies unbedingt zu empfehlen,<br />
weil die Bedienung so einfacher ist als bei einer echten Klappliste.<br />
Vorauswahl<br />
Abbildung 8.8:<br />
Mehrfachauswahl einer automatisch<br />
erzeugten Box<br />
Auch mit Listen ist die Vorauswahl möglich und sinnvoll. Dazu wird das -<br />
Tag um das Attribut selected erweitert. Das letzte Beispiel enthielt bereits einen<br />
Booleschen Wert (der dritte Wert im Array $select), der die Vorauswahl steuern<br />
sollte. Eine Erweiterung der printf-Funktion zeigt, wie es geht:<br />
Listing 8.9: formselectautoselect.php – Vorauswahl per Skript steuern<br />
300<br />
Formular- und Seitenmanagement<br />
{<br />
if (is_array($_POST['Language']))<br />
{<br />
echo "Auswahl: " . implode($_POST['Language'], ', ');<br />
}<br />
}<br />
else<br />
{<br />
echo 'Erster Aufruf der Seite';<br />
}<br />
?><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Der Boolesche Wert wird genutzt, um direkt das entsprechende Attribut zu erzeugen:<br />
$option[2] ? 'selected' : ''<br />
Zur besseren Übersicht wurden außerdem die Parameter der printf-Funktion<br />
nummeriert:<br />
printf('%2$s', …
Professionelle Formulare und »Sticky Forms«<br />
Probleme mit klassisch programmierten Formularen<br />
Freilich ist die letzte Lösung nicht optimal, weil bei einem erneuten Aufruf der<br />
Seite immer wieder der Ursprungszustand wiederhergestellt wird. Besser ist es,<br />
wenn der Anfangs- und der Arbeitszustand getrennt gesteuert werden können.<br />
Denn der Benutzer erwartet bei Fehleingaben und ähnlichen Problemen, dass<br />
seine Werte erhalten bleiben. Man spricht bei solchen Formularen von so genannten<br />
»Sticky Forms« oder »klebrigen Formularen«, die so heißen, weil sie die letzten<br />
Werte behalten. Weil dies eine sehr wichtige Technik ist, wurde ihr ein<br />
eigener Abschnitt gewidmet.<br />
8.3 Professionelle Formulare und »Sticky Forms«<br />
Professionelle Formulare verlangen nach mehr Funktionen als bislang gezeigt<br />
wurde. Die Auswertung der eingegebenen Daten in PHP ist nur eine Seite der<br />
Medaille. Die Programmierung muss auch eine gute Benutzerführung erlauben.<br />
Dazu gehört, dass dem Benutzer<br />
sinnvolle Ausfüllhinweise gegeben werden,<br />
hilfreiche Ausfüllhilfen mit clientseitigen Funktionen angeboten werden,<br />
er auf Fehler bei der Eingabe hingewiesen wird und<br />
Abbildung 8.9:<br />
Vorauswahl beim ersten Aufruf der Seite<br />
der <strong>In</strong>halt des Formulars beim erneuten Laden nach Fehlern erhalten bleibt.<br />
Sinnvolle Ausfüllhinweise sind Teil des Designs. Zu den typischen Vorgaben<br />
gehört die Markierung von Pflichtfeldern und <strong>In</strong>formationen über die in Textfeldern<br />
zu platzierenden Daten. Ausfüllhilfen werden meist in JavaScript erstellt und<br />
betreffen beispielsweise die Prüfung von Feldinhalten, das Vorausfüllen auf der<br />
301
302<br />
Formular- und Seitenmanagement<br />
Grundlage anderer Daten oder die Formatierung von Eingaben. Sie helfen, die<br />
Fehlerquote bei der Eingabe zu senken, und vereinfachen die serverseitige Prüfung<br />
in PHP. Hinweise auf Eingabefehler sind in letzter Konsequenz immer serverseitig<br />
zu klären. Dazu gehören aussagekräftige Fehlermeldungen, Hinweise auf<br />
den Ort des Fehlers und Wege, diesen zu beseitigen. Nicht zuletzt ist all dies mit<br />
der Technik der »Sticky Forms« zu kombinieren, denn der fatalste Entwurfsfehler<br />
einer Seite ist ein Formular, das minutenlang mühevoll ausgefüllt wurde und nach<br />
dem Senden und erneuten Laden mit einer Fehlermeldung und ohne die Daten<br />
erscheint.<br />
Neben diesen Techniken ist auch die Größe eines Formulars von Bedeutung.<br />
Wenn sehr viele Felder erforderlich sind, muss eventuell über ein mehrseitiges<br />
Formular nachgedacht werden.<br />
Ausfüllhinweise und Feldvorgaben<br />
Tabellen haben beim Bau von Formularen eine große Bedeutung. Sie dienen vor<br />
allem der eindeutigen Platzierung von Feldbeschriftungen und Feldelementen.<br />
Das Formular bauen<br />
Eine der elementaren Techniken beim Formularbau betrifft den Einsatz von<br />
Tabellen. Damit Feldbeschriftungen und Felder vernünftig angeordnet erscheinen,<br />
benutzt man zu ihrer Positionierung eine feste Struktur in Form einer<br />
Tabelle nach folgendem Schema:<br />
Text Feld<br />
Text Feld<br />
...<br />
<strong>In</strong> HTML sieht das dann folgendermaßen aus:<br />
<br />
<br />
Kennwort:<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Hinweistexte platzieren<br />
Professionelle Formulare und »Sticky Forms«<br />
Als nächstes sollten Hinweistexte platziert werden, um den Benutzer durch das<br />
Formular zu führen. Dies erfolgt meist rechts vom Feld, sodass man bei der<br />
Tabelle mit einem dreispaltigen Layout arbeitet. Ist das Formular nicht sehr hoch,<br />
die Felder aber recht breit, kann man den Text auch unter die Feldnamen oder<br />
unter die Felder selbst stellen. Er sollte sich dann aber optisch abheben, beispielsweise<br />
durch eine andere Farbe und eventuell durch eine verkleinerte Schrifthöhe.<br />
Wenn man nun bereits Eingabehilfen in Form von Erklärungen platziert, wären<br />
auch aktive Hilfen interessant. Dabei hilft JavaScript.<br />
Ausfüllhilfen mit JavaScript<br />
Abbildung 8.10:<br />
Das perfekte Formular mit einer Tabelle erstellt<br />
Abbildung 8.11:<br />
Zum Vergleich: Unprofessionelles Formular ohne Tabelle<br />
Es ist grundsätzlich falsch, auf JavaScript zu verzichten. Es ist ebenso grundsätzlich<br />
falsch, allein darauf zu vertrauen. Erst die Kombination aus JavaScript (client-<br />
303
304<br />
Formular- und Seitenmanagement<br />
seitig) und PHP (serverseitig) erlaubt wirklich gute und professionelle Lösungen.<br />
Es ist sicher so, dass dieses Buch sich dem Thema PHP widmet. Bei der Formularverarbeitung<br />
hat JavaScript aber eine ganz bedeutende Stellung und der Verzicht<br />
auf eine clientseitige Unterstützung ist einfach nur dumm. Glauben Sie nicht Leuten,<br />
die die Verwendung von JavaScript verteufeln – die haben das Web nicht verstanden.<br />
<strong>In</strong>teraktion zwischen JavaScript und PHP<br />
Die komplette Betrachtung um JavaScript füllt gleichfalls Bücher. Dieser<br />
Abschnitt zeigt lediglich das Zusammenspiel mit PHP. <strong>In</strong>formationen über den<br />
genauen Umgang mit allen Möglichkeiten, die JavaScript bietet, sind anderen<br />
Quellen vorbehalten. Prinzipiell gibt es zwei Wege der <strong>In</strong>teraktion:<br />
PHP steuert JavaScript durch dynamische Code-Erstellung<br />
Abbildung 8.12:<br />
Platzierung von<br />
Hinweistexten<br />
und Eingabehilfen<br />
JavaScript erzeugt Werte und übergibt sie an PHP<br />
Da JavaScript im Browser abläuft und PHP auf dem Server, muss man sich Gedanken<br />
über die Schnittstelle machen. Damit PHP die Reaktion von JavaScript beeinflussen<br />
kann, ist es möglich, den JavaScript-Code dynamisch zu erstellen. Ist die<br />
Seite aber erstmal an den Browser abgesendet worden, funktioniert das nicht mehr.<br />
Es ist deshalb eine mehr oder wenig einseitige Steuerung. <strong>In</strong>teressant ist es dennoch,<br />
denn Sie sollten Ihren JavaScript-Code so steuern, dass Sie auf den Erst- und<br />
Zweitaufruf des Formulars gegebenenfalls unterschiedlich reagieren. Das ist wichtig,<br />
weil JavaScript von Hause aus nicht unterscheiden kann, aufgrund welcher<br />
HTTP-Anforderung die Seite gesendet wurde. Das folgende Beispiel erzeugt eine<br />
Anzeigebox in JavaScript, deren <strong>In</strong>halt von der HTTP-Methode gesteuert wird,<br />
indem der Anzeigetext in PHP dynamisch ausgewählt wurde:
Professionelle Formulare und »Sticky Forms«<br />
Listing 8.10: formjavascriptalert.php – JavaScript per PHP steuern<br />
<br />
<br />
Formulare<br />
<br />
function Box(text)<br />
{<br />
alert(text);<br />
document.logon.submit();<br />
}<br />
<br />
<br />
<br />
<br />
306<br />
Formular- und Seitenmanagement<br />
Die Funktion erzeugt eine Ausgabebox (alert), die vor allem zur Demonstration,<br />
Fehlersuche und für einfache Meldungen an den Benutzer geeignet ist. Entscheidender<br />
ist die zweite Zeile:<br />
document.logon.submit();<br />
Hiermit wird das Formular dazu veranlasst, sich abzusenden. Das entspricht dem<br />
Anklicken einer »Submit«-Schaltfläche. Es spielt dabei keine Rolle, ob eine solche<br />
vorhanden ist oder nicht. <strong>In</strong> PHP können Sie übrigens nicht direkt unterscheiden,<br />
ob der Benutzer auf »Submit« geklickt hat oder das Skript das Absenden übernahm.<br />
JavaScript arbeitet mit dem so genannten Document Object Model (DOM)<br />
der HTML-Seite. Dieses erlaubt eine objektorientierte Darstellung. document verweist<br />
auf das Dokument. Eines der darin definierten Elemente heißt »logon« – der<br />
Name des Formulars. Der wurde hier willkürlich folgendermaßen festgelegt:<br />
Clientseitig auf Eingaben reagieren<br />
Professionelle Formulare und »Sticky Forms«<br />
Da es in JavaScript einen Zugriff auf das Formular gibt, kann auch auf alle Felder<br />
zugegriffen werden. Das ist hilfreich, damit Formulare mit offensichtlichen Fehlern<br />
gar nicht erst gesendet werden. Das folgende Beispiel zeigt, wie bei einem<br />
Feld geprüft wird, ob ein Text darin steht. Diese Prüfung ist für Pflichtfelder sinnvoll.<br />
JavaScript spart das Zurücksenden des Formulars zum Server und damit dem<br />
Benutzer Zeit und Bandbreite. Natürlich muss jede clientseitige Prüfung mit einer<br />
serverseitigen kombiniert werden, damit die Bedienung auch dann möglich ist,<br />
wenn JavaScript nicht aktiviert ist.<br />
Listing 8.11: formjavascriptmand.php – Eingabeprüfungen mit JavaScript<br />
<br />
<br />
Formulare<br />
<br />
function Check()<br />
{<br />
var ok = true;<br />
if (document.logon.Loginname.value == '')<br />
{<br />
alert ('Login Name wurde nicht angegeben');<br />
ok = false;<br />
}<br />
if (document.logon.Password.value == '')<br />
{<br />
alert ('Kennwort wurde nicht angegeben');<br />
ok = false;<br />
}<br />
if (ok)<br />
{<br />
document.logon.submit();<br />
}<br />
Abbildung 8.13:<br />
Diese Box erscheint beim<br />
ersten (links) und beim zweiten<br />
(und folgenden) Klick<br />
(rechts)auf die Schaltfläche<br />
307
308<br />
Formular- und Seitenmanagement<br />
}<br />
<br />
<br />
<br />
<br />
if (document.logon.Loginname.value == '')<br />
Professionelle Formulare und »Sticky Forms«<br />
document.logon verweist wieder auf das Formular, Loginname ist der Name des<br />
Feldes und value eine Eigenschaft, die den momentanen Wert zurückgibt. Der<br />
Rest ist Standardprogrammierung und wird nur durch die eigene Fantasie<br />
begrenzt.<br />
Der PHP-Abschnitt wiederholt diese Prüfung, weil Sie nie darauf vertrauen dürfen,<br />
dass jeder Browser auch tatsächlich JavaScript ausführt.<br />
Was nun noch fehlt, ist eine vernünftige Anzeige der Fehlertexte im Formular.<br />
Das ist ein Fall für CSS und natürlich JavaScript. Dabei muss man als Mittel zur<br />
Fehlerausgabe nicht nur JavaScript sehen. Auch die in PHP erzeugten Fehlerinformationen<br />
werden hier eingebunden.<br />
Fehlerangaben<br />
Fehlerangaben sind ein wichtiges <strong>In</strong>strument der Benutzerführung. Dazu gibt es<br />
mehrere Techniken, die die Kombination aus JavaScript und PHP erfordern.<br />
Eine wichtige Frage ist die Platzierung der Fehlertexte. Eine optimale Benutzerführung<br />
verlangt, dass ein direkter Bezug zwischen Fehlerquelle und Fehlertext<br />
hergestellt werden muss. Dazu ist die Fehlerausgabe direkt neben (oder unter)<br />
dem Feld zu platzieren, das den Fehler verursachte. An zentraler Stelle – vorzugsweise<br />
oberhalb des Formulars (damit der Fehler ohne Rollen sichtbar ist) – wird<br />
eine Zusammenfassung aller Fehler gegeben. Wichtig ist auch, alle Fehler gleichzeitig<br />
zur Anzeige zu bringen und damit zu verhindern, dass der Benutzer sich mit<br />
dem Trial-and-Error-Prinzip durch die Reaktionen des Formulars klickt. Falls das<br />
Layout keinen Platz für detaillierte Meldungen neben den Feldern liefert, kann<br />
man mit Fußnoten arbeiten. Dabei wird eine Fehlernummer neben dem betroffenen<br />
Feld eingeblendet und unterhalb des Formulars eine ausführliche Erläuterung<br />
geboten.<br />
Techniken mit Style-Definitionen<br />
Eine Problematik, die fast jedes Formular betrifft, ist die Platzierung der Fehlertexte.<br />
Generell benötigen Fehlertexte Platz. Wird dieser von vornherein freigehalten,<br />
wirkt ein Formular möglicherweise recht »locker«, was dem Layout der Seite<br />
widersprechen kann. Nimmt man auf Fehler dagegen keine Rücksicht und blendet<br />
sie einfach in einem fertigen Layout ein, kann die Seite »zerfallen«.<br />
309
310<br />
Formular- und Seitenmanagement<br />
Was genau zu machen ist, hängt von der konkreten Situation ab. Häufig bietet es<br />
sich an, den Platz der Fehlermeldung zu reservieren, indem man den Text schon<br />
beim ersten Aufruf des Formulars auf die Seite schreibt. Dort wird er mit der Stil-<br />
Anweisung visibility:hidden unsichtbar gemacht. Dieser Stil wird in erster <strong>In</strong>stanz<br />
durch JavaScript oder – wenn JavaScript nicht funktioniert – durch PHP ersetzt.<br />
Das folgende Skript zeigt, wie das funktioniert.<br />
Listing 8.12: formjavascriptdisplay.php – Nutzung von CSS für die Fehlerausgabe<br />
<br />
<br />
Formulare<br />
<br />
function Check()<br />
{<br />
var ok = true;<br />
if (document.logon.Loginname.value == '')<br />
{<br />
document.getElementById('errName').style.visibility <br />
= "visible";<br />
ok = false;<br />
}<br />
if (document.logon.Password.value == '')<br />
{<br />
document.getElementById('errPass').style.visibility <br />
= "visible";<br />
ok = false;<br />
}<br />
if (ok)<br />
{<br />
document.logon.submit();<br />
}<br />
document.logon.submit();<br />
}<br />
<br />
<br />
<br />
Professionelle Formulare und »Sticky Forms«<br />
{<br />
$displayName = 'visible';<br />
}<br />
else<br />
{<br />
$displayName = 'hidden';<br />
}<br />
if (empty($_POST['Password']))<br />
{<br />
$displayPass = 'visible';<br />
}<br />
else<br />
{<br />
$displayPass = 'hidden';<br />
}<br />
}<br />
else<br />
{<br />
$displayName = 'hidden';<br />
$displayPass = 'hidden';<br />
}<br />
?><br />
312<br />
Formular- und Seitenmanagement<br />
style="color:red;visibility:"><br />
Kennwort wurde nicht angegeben<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Dieses Skript erledigt alle dynamischen Darstellaufgaben hervorragend. Betrachten<br />
Sie zuerst den PHP-Abschnitt. Darin werden zwei Variablen festgelegt, die die<br />
Vorauswahl der Sichtbarkeit der Fehlertexte festlegen, $displayName und $displayPass.<br />
Beim ersten Aufruf, REQUEST_METHOD ist gleich GET, werden<br />
natürlich keine Fehler angezeigt. Schaut man in den generierten HTML-Quelltext,<br />
sehen die Span-Tags, die die Fehlertexte umschließen, folgendermaßen aus:<br />
<br />
Neben der Steuerung der Sichtbarkeit mit visibility:hidden wird dem Tag auch das<br />
id-Attribut mitgegeben. Dies ist notwendig, um per JavaScript darauf zugreifen zu<br />
können. Die JavaScript-Prüfung ist dann auch der erste Versuch, den Fehlerzustand<br />
zu erkennen. Wie bereits beim letzten Beispiel gezeigt, führt ein Klick auf<br />
die Schaltfläche zu einer Prüffunktion:<br />
onClick="Check();"<br />
Diese wiederum greift in der schon bekannten Weise auf die Felder zu und überprüft<br />
deren <strong>In</strong>halt. Ist eines der Fehler leer, wird nun einfach der Fehlertext sichtbar<br />
gemacht, indem die Voreinstellung überschrieben wird:<br />
document.getElementById('errName').style.visibility = "visible";<br />
getElementById ist eine Methode, die das Element mit dem betreffenden Namen<br />
beschafft und bereitstellt. Für die Nutzung dieser Methode wurde das id-Attribut<br />
benötigt. Von dem betroffenen Element wird nun die Style-Auflistung ermittelt<br />
(.style) und aus dieser wiederum das Attribut .visibility. Dann ist nur noch der entsprechende<br />
Wert zu setzen, der den Angaben entspricht, wie man sie direkt mit<br />
HTML machen würde (»hidden« oder »visible«).<br />
War die Prüfung erfolgreich, wird das Formular gesendet.
JavaScript geht nicht...<br />
Professionelle Formulare und »Sticky Forms«<br />
Wenn JavaScript ganz abgeschaltet ist, funktioniert die letzte Variante noch nicht<br />
zufrieden stellend, weil das Absenden der Form überhaupt nicht stattfindet. Eine<br />
kleine Modifikation hilft, das Problem zu umgehen.<br />
Listing 8.13: formjavascriptdisplaymod.php – Komplexe Verarbeitung von Eingaben mit<br />
JavaScript und CSS<br />
<br />
<br />
Formulare<br />
<br />
function Check()<br />
{<br />
var ok = true;<br />
if (document.logon.Loginname.value == '')<br />
{<br />
document.getElementById('errName').style.visibility<br />
= "visible";<br />
ok = false;<br />
}<br />
if (document.logon.Password.value == '')<br />
{<br />
document.getElementById('errPass').style.visibility<br />
= "visible";<br />
ok = false;<br />
}<br />
return ok;<br />
}<br />
<br />
<br />
<br />
3<strong>14</strong><br />
Formular- und Seitenmanagement<br />
{<br />
$displayName = 'hidden';<br />
}<br />
if (empty($_POST['Password']))<br />
{<br />
$displayPass = 'visible';<br />
}<br />
else<br />
{<br />
$displayPass = 'hidden';<br />
}<br />
}<br />
else<br />
{<br />
$displayName = 'hidden';<br />
$displayPass = 'hidden';<br />
}<br />
?><br />
Professionelle Formulare und »Sticky Forms«<br />
<br />
<br />
<br />
<br />
Hier wurde nun die Schaltfläche wieder durch die originäre Variante type="submit"<br />
ersetzt. Ohne JavaScript wird das onClick-Ereignis nicht erkannt und das Formular<br />
sofort gesendet. Die Prüfung in PHP wird dann aktiviert und die Variable<br />
entsprechend gesetzt:<br />
$displayName = 'visible';<br />
Die JavaScript-Funktion Check wurde auch ein wenig geändert. Sie gibt nun<br />
einen Booleschen Wert zurück, der das Ergebnis der Prüfung bestimmt:<br />
return ok;<br />
Dieses Ergebnis wird mit einem weiteren return an das Klickereignis übergeben<br />
und bestimmt dessen Rückgabewert. Ist der Wert false, wird der Klick nicht ausgeführt,<br />
ist er true, wird er ausgeführt.<br />
<strong>In</strong> der Kombination der Maßnahmen erhält man das (fast) perfekte Formular:<br />
Ist JavaScript an, wird die Prüfung im Client durchgeführt<br />
Ohne JavaScript funktioniert es auch, dann kommt PHP zum Zuge<br />
Fehlermeldungen werden in jedem Fall dynamisch erzeugt und der Platz ist<br />
immer reserviert<br />
Was jetzt noch fehlt ist der Erhalt der Werte im Fehlerfall. Dazu wird eine als<br />
»Sticky Forms« bezeichnete Technik verwendet.<br />
Sticky Forms<br />
Sticky Forms sind eigentlich nur ein simpler Programmierstil. Formulare gewinnen<br />
dadurch jedoch signifikant an Professionalität und Benutzerfreundlichkeit.<br />
Das Prinzip ist einfach. Man nutzt die im POST-Zyklus erkannten Werte, um die<br />
value-Attribute der Felder mit den alten Werten zu belegen.<br />
Die JavaScript-Abschnitte aus den letzten Beispielen wurden in diesem Abschnitt<br />
nicht erneut in die Beispiele eingebaut, weil bei einer rein clientseitigen Prüfung<br />
die Werte ohnehin erhalten bleiben und die hier beschriebenen Techniken nicht<br />
315
316<br />
Formular- und Seitenmanagement<br />
sinnvoll einsetzbar sind. Für ein optimales »Fallback«, also die volle Funktion<br />
auch ohne JavaScript, sind jedoch beide Maßnahmen erforderlich.<br />
Textfelder<br />
Bei Eingabefeldern für Text ist dies besonders einfach. Das letzte Beispiel lässt sich<br />
leicht erweitern, um im Fehlerfall nun auch die <strong>In</strong>halte zu erhalten.<br />
Listing 8.<strong>14</strong>: formstickytext.php – Fehlerprüfung mit Werterhalt in allen Feldern<br />
Professionelle Formulare und »Sticky Forms«<br />
}<br />
?><br />
318<br />
Formular- und Seitenmanagement<br />
Die Kombination aus dynamischer Fehlererzeugung und Werterhaltung macht<br />
aus einem einfachen HTML-Formular durch den Einsatz von PHP und JavaScript<br />
eine hochwertige, benutzerfreundliche Lösung.<br />
Textarea-Tags werden übrigens ebenso behandelt, nur wird hier statt des Attributes<br />
value der Wert zwischen die Tags geschrieben:<br />
<br />
Optionsfelder und Kontrollkästchen<br />
Optionsfelder und Kontrollkästchen unterscheiden sich kaum von der Behandlung<br />
der Textfelder. Statt des Wertes wird hier das Attribut checked gesetzt. Wenn man<br />
sich für die dynamische Erzeugung der Felder per Skript entschieden hat, ist die<br />
Umsetzung nicht schwer. Das folgende Beispiel zeigt, wie es geht:<br />
Listing 8.15: formstickycheckauto.php – Startwerte festlegen und Auswahlzustand bei<br />
einer dynamisch erzeugten Auswahl von Kontrollkästchen erhalten<br />
Professionelle Formulare und »Sticky Forms«<br />
}<br />
$action = $_SERVER['PHP_SELF'];<br />
$checkboxes = array (<br />
'chkPerl' => array('Perl', 'Perl', '1', FALSE),<br />
'chkPHP' => array('PHP', 'PHP', '1', TRUE),<br />
'chkVBS' => array('VBScript', 'Visual Basic Script', '1', TRUE),<br />
'chkCSharp' => array('C#', 'C# (.NET)', '1', FALSE),<br />
'chkCPlusPlus' => array('C++', 'C++ (VSC++)', '1', FALSE),<br />
'chkDelphi' => array('Delphi', 'Delphi (Pascal)', '1', FALSE),<br />
'chkFortran' => array('Fortan', 'Fortran', '1', FALSE));<br />
if ($_SERVER['REQUEST_METHOD'] == 'POST')<br />
{<br />
foreach ($_POST as $fName => $fValue)<br />
{<br />
if (substr($fName, 0, 3) == 'chk')<br />
{<br />
echo
320<br />
Formular- und Seitenmanagement<br />
$boxData[0],<br />
$boxData[1]<br />
);<br />
}<br />
?><br />
<br />
<br />
<br />
<br />
<br />
<br />
Das Geheimnis liegt hier im Aufruf von IsChecked innerhalb der Ausgabefunktion<br />
printf:<br />
IsChecked($boxName, $boxData)<br />
Übergeben werden der Name der aktuellen Box und die Daten, die im Array $box-<br />
Data stehen. Diese Daten bestimmen das Aussehen des jeweiligen Kontrollkästchens.<br />
Die eigentliche Arbeit wird nun in IsChecked erledigt. Beim Erstaufruf (GET)<br />
wird die Vorgabe aus dem Array benutzt, um eine Voreinstellung zu erreichen.<br />
return ($boxData[3]) ? 'checked' : '';<br />
Im POST-Zyklus wird dagegen der Zustand der gesendeten Felder mit dem gerade<br />
in Bearbeitung befindlichen verglichen:<br />
if (!empty($_POST[$boxName]))<br />
Ist da ein Wert drin, dann gilt das Feld als gesetzt und die Zeichenkette »checked«<br />
wird zurückgegeben.<br />
Optionsfelder werden mit demselben Attribut gesetzt, nur muss man hier die Mehrfachauswahl<br />
verhindern, was die Sache aber im Prinzip noch weiter vereinfacht.<br />
Listfelder<br />
Listen aller Art sind etwas schwerer zu beherrschen. Der Erhalt des Wertes erfolgt<br />
hier über das Setzen von selected-Attributen in den Option-Tags. Da dies jedes Tag<br />
betreffen kann, muss man quasi über eine ausreichende Anzahl Variablen für jede<br />
Option verfügen.
Professionelle Formulare und »Sticky Forms«<br />
Listing 8.16: formstickyselect.php – Einfache Listbox, die den gewählten Wert erhält<br />
<br />
322<br />
Formular- und Seitenmanagement<br />
<br />
Professionelle Formulare und »Sticky Forms«<br />
Listing 8.17: formstickyselectmulti.php – Mehrfachauswahl einer Liste mit automatischem<br />
Erhalt der Werte<br />
<br />
324<br />
Formular- und Seitenmanagement<br />
<br />
<br />
Professionelle Formulare und »Sticky Forms«<br />
Mehrseitige Formulare mit versteckten Feldern<br />
Mehrseitige Formulare sind immer dann sinnvoll, wenn die Anzahl der Felder zu<br />
groß ist, um ohne heftiges Umherscrollen gelesen werden zu können. Nun stellt<br />
sich natürlich die Frage, wie man von einer Seite zu nächsten gelangt, ohne dass<br />
die Daten der vorhergehenden Seiten verloren gehen. Es gibt hier mehrere<br />
Lösungsansätze, die je nach Situation den einen oder anderen Vorteil bringen.<br />
Prinzipien und Forderungen<br />
Vor der Erstellung der Formulare muss man sich klar machen, dass ein Zurückblättern<br />
vom Browser mehr oder weniger wirkungsvoll verhindert wird. Es ist wichtig,<br />
dass die Fehlerprüfungen auf jeder Seite stattfinden und nicht erst am Ende,<br />
weil ein gezielter Rückschritt sehr aufwändig zu programmieren ist und dem<br />
Benutzer keinen wirklichen Vorteil bringt.<br />
Das Zurückblättern ist ohnehin eine aufwändige Angelegenheit. Für den Benutzer<br />
ist es unter Umständen jedoch durchaus hilfreich, wenn er sich in einem mehrseitigen<br />
Formular frei bewegen kann. Typischerweise werden dann oberhalb des Formulars<br />
Fortschrittsanzeigen platziert, die Auskunft über den aktuellen Stand<br />
geben. So etwas ist vertrauensbildend und sorgt für einen unverkrampften Umgang<br />
mit den Seiten, was mithin den Erfolg steigert, dass ein Benutzern auch am Ende<br />
ankommt.<br />
Wie bereits bei den bisherigen Beispielen, macht erst eine geschickte Kombination<br />
aus Styles, JavaScript, HTML und PHP wirklich das aus, was ein Benutzer als<br />
brauchbar empfindet. Wie es geht, soll anhand eines längeren Beispiels erläutert<br />
werden. Es basiert im Wesentlichen auf der Nutzung versteckter Felder.<br />
Versteckte Felder<br />
HTML erlaubt die Verwendung von Feldern mit dem Typ »hidden«. Eine Anzeigeform<br />
hat dieser Typ nicht, das Feld ist versteckt. Im Quelltext der Seite bleibt es<br />
freilich sichtbar, aus Sicherheitsgründen ist der Einsatz sinnlos. Versteckte Felder<br />
erlauben es aber, die aktuell erfassten Werte mitzunehmen und damit auf der<br />
nächsten Seite im Kontext der Seite zu sehen, ohne dass dies zusätzlichen Programmieraufwand<br />
verursacht. Denn die <strong>In</strong>halte versteckter Felder sind so wie<br />
jedes andere Formularfeld Teil des $_POST-Arrays.<br />
325
326<br />
Formular- und Seitenmanagement<br />
Die Definition in HTML sieht folgendermaßen aus:<br />
Professionelle Formulare und »Sticky Forms«<br />
{<br />
color: red;<br />
background-color:#ddddff;<br />
text-align:center;<br />
font-size: <strong>14</strong>pt;<br />
}<br />
.inactiveNumber<br />
{<br />
color: #cccccc;<br />
background-color:#ddddff;<br />
text-align:center;<br />
font-size: <strong>14</strong>pt;<br />
}<br />
.activeTable<br />
{<br />
display:visible;<br />
height:100px;<br />
width:500px;<br />
}<br />
.inactiveTable<br />
{<br />
display:none<br />
}<br />
<br />
<br />
<br />
328<br />
Formular- und Seitenmanagement<br />
}<br />
if (!empty($_POST['next']))<br />
{<br />
$currentPage++;<br />
if ($currentPage == 4)<br />
{<br />
$nextDisabled = 'disabled';<br />
}<br />
}<br />
$fldName = GetField('Name');<br />
$fldAddress = GetField('Address');<br />
$fldZip = GetField('Zip');<br />
$fldCity = GetField('City');<br />
$fldConditions = GetField('Conditions');<br />
$fldLogonName = GetField('LogonName');<br />
$fldPassword = GetField('Password');<br />
$fldNews = GetField('News');<br />
if (!empty($_POST['Send']))<br />
{<br />
$currentPage = 0;<br />
echo "Vielen Dank für das Ausfüllen des Formulars";<br />
exit;<br />
}<br />
?><br />
<br />
<br />
Professionelle Formulare und »Sticky Forms«<br />
Name<br />
<br />
330<br />
Formular- und Seitenmanagement<br />
/><br />
<br />
Type 2 type="submit"<br />
name="prev" value="Zurück"/><br />
<br />
<br />
Professionelle Formulare und »Sticky Forms«<br />
Das ist sicher nicht mehr ganz so kompakt wie die anderen Beispiele. Deshalb soll<br />
der Blick auf den Ablauf vorangestellt werden. Die folgenden Abbildungen zeigen<br />
die vier Seiten in Aktion.<br />
Abbildung 8.16:<br />
Schritt1 umfasst<br />
die Anmeldedaten<br />
Abbildung 8.17:<br />
Schritt 2 enthält<br />
nur die Bestätigung<br />
Abbildung 8.18:<br />
Schritt 3 erlaubt<br />
die Erfassung von<br />
Anmeldename<br />
und Kennwort<br />
Abbildung 8.19:<br />
<strong>In</strong> Schritt 4 kann<br />
noch ein Newsletter<br />
bestellt werden<br />
331
332<br />
Formular- und Seitenmanagement<br />
Das Skript verwendet Stildefinitionen zur Kontrolle der Anzeige. Da sich die<br />
Tabellen mit den Formulardaten immer an derselben Stelle befinden sollen, wird<br />
zum Ausblenden der nicht benötigten Teile das Attribut display verwendet. Das<br />
bereits benutzte visibility kann Elemente zwar auch unsichtbar machen, diese nehmen<br />
aber weiterhin den im sichtbaren Zustand okkupierten Platz ein. Bei der Ausgabe<br />
der Fehlermeldungen war das gewollt. Hier dürfte es eher störend sein,<br />
weshalb mit display mehr erreicht werden kann. Um die Umschaltung zu vereinfachen,<br />
werden innerhalb des Style-Tags am Anfang CSS-Klassen definiert, die die<br />
nötigen Einstellungen enthalten. Die aktive Tabelle erhält folgenden Stil:<br />
display:visible;<br />
Die inaktive (unsichtbare) Tabelle wird mit dem folgenden Code vom Bildschirm<br />
verbannt:<br />
display:none;<br />
Auch der Balken, der oberhalb des Formulars den Fortschritt anzeigt, nutzt Stile.<br />
Hier wird ebenfalls nur die Klasse umgeschaltet, die passenden Definitionen<br />
wurden entsprechend vorbereitet. Dann wird der Wert für die aktuelle Seite in<br />
$currentPage ermittelt und bei der betreffenden Tabelle auf »activeNumber«<br />
umgeschaltet:<br />
<br />
Die Variable $currentPage spielt ohnehin eine herausragende Rolle, denn sie steuert<br />
den aktuellen Zustand des Formulars. Beim ersten Aufruf ist sie undefiniert<br />
und wird in einen Anfangszustand versetzt:<br />
$currentPage = empty($_POST['currentPage']) ? 1<br />
: (int) $_POST['currentPage'];<br />
Hier wird geprüft, ob das Formular bereits abgesendet wurde. Wenn das der Fall<br />
ist, steht die aktuelle Seite in $_POST['currentPage'], andernfalls wird hier der<br />
Startwert 1 eingesetzt. Damit das Formular nun ständig den Wert mitnehmen<br />
kann, kommt wie angekündigt ein verstecktes Feld zum Einsatz:<br />
Professionelle Formulare und »Sticky Forms«<br />
Position 1 steht, ist die Schaltfläche (Zurück) inaktiv (es geht nicht weiter zurück)<br />
und auf Position 4 die Schaltfläche (Weiter). Beispielhaft soll hier der Vorgang für<br />
eine Schaltfläche erläutert werden. Zuerst ein Blick auf die Definition:<br />
334<br />
Formular- und Seitenmanagement<br />
8.4 Dateien hochladen<br />
Dateien hochladen gehört zu den Formularfunktionen. <strong>In</strong> der Praxis bereitet dies<br />
gelegentlich Schwierigkeiten, weil mehrere Prozesse zusammenspielen müssen.<br />
Dieser Abschnitt klärt über Hintergründe, Anwendung und praktische Beispiele<br />
auf.<br />
Grundlagen<br />
Um Dateien hochladen zu können, müssen diese vom Browser speziell verpackt<br />
werden. Prinzipiell können per HTTP nur Text-Daten übertragen werden. Da<br />
Dateien im allgemeinen Binärdaten sind (HTML-Felder enthalten dagegen nur<br />
Text), müssen sie entsprechend kodiert werden. Um Kodierung und Dekodierung<br />
muss man sich keine Gedanken machen. Alle Browser beherrschen das Kodieren<br />
ebenso, wie PHP mit dem Dekodieren keine Probleme hat und dies intern erledigt.<br />
Der Vorgang basiert auf der Nutzung der Methode POST und der üblichen<br />
Formularübertragung.<br />
PHP unterstützt auch Dateiuploads nach der PUT-Methode, die beispielsweise<br />
vom Netscape Composer und dem W3C Amaya benutzt<br />
wird. <strong>In</strong> der Praxis hat dies wenig Bedeutung, weil die meisten Provider<br />
FTP und neuerdings auch WebDAV anbieten, was einfacher und transparenter<br />
ist und keine Skriptsteuerung verlangt.<br />
Das Formular vorbereiten<br />
Um ein Formular für das Hochladen von Dateien benutzen zu können, muss es<br />
zwei Dinge enthalten:<br />
Die Kodierungsanweisung im Form-Tag<br />
Ein spezielles Feld mit dem Attribut type="file"<br />
Das Form-Tag sieht nun folgendermaßen aus:<br />
<br />
Das Attribut enctype ist hier entscheidende Teil. Nun muss noch das Feld eingebaut<br />
werden, dass die Auswahl der Datei lokal ermöglicht.
Dateien hochladen<br />
Ein Feld mit dem Attribut type="file" unterliegt aus Sicherheitsgründen<br />
bestimmten Einschränkungen. So kann die Beschriftung nicht geändert<br />
werden. Die Schaltfläche zum Durchsuchen ist immer mit dem Text<br />
(Durchsuchen) oder – in der englischen Version – mit (Browse) beschriftet.<br />
Das ist einsichtig, weil man mit anderen Beschriftungen (»Gewinnen<br />
Sie 1 Million €«) den Benutzer zu einem ungewollten Klick<br />
verleitet könnte. Im Zusammenhang damit kann auch der Wert (Attribut<br />
value) nicht vom Programm gesetzt werden. Denn sonst könnte man<br />
eine Vorauswahl hineinschreiben und die Aktion dann mit JavaScript<br />
auslösen, sodass die Website lokale Dateien systematisch beschafft. Deshalb<br />
fällt das Thema »Sticky Forms« hier auch aus.<br />
Nach dem Hochladen<br />
Die empfangenen Dateien stellt PHP nicht wie die übrigen Felder als Variablen<br />
zur Verfügung, sondern speichert sie in einem temporären Verzeichnis ab. Die<br />
beim Hochladen erzeugten Variablen enthalten nur <strong>In</strong>formationen über die<br />
Datei, wie beispielsweise den Dateinamen und die Größe. Das Skript, das die<br />
Daten empfängt, muss die Dateien aus dem temporären Speicher in das finale<br />
Verzeichnis kopieren.<br />
Das Kopieren kann entweder mit der Funktion copy oder mit move_uploaded_files<br />
ausgeführt werden. Die Funktion move_uploaded_files kann ausschließlich die<br />
temporären Dateien kopieren. Das ist sicherer als copy, weil ein offen programmiertes<br />
Skript mit freier Wahl der Pfade eventuell missbraucht werden könnte, um<br />
vorhandene Dateien aus einem anderen Verzeichnis zu kopieren und so die Kontrolle<br />
über den Server zu erlangen.<br />
Prinzip<br />
Das folgende Beispiel zeigt eine einfache Anwendung. Zur Erfolgskontrolle wird<br />
das Verzeichnis, in dem die hochgeladenen Dateien landen, anschließend ausgegeben.<br />
Listing 8.19: formuploadsimple.php – Formular zum Hochladen von Dateien<br />
336<br />
Formular- und Seitenmanagement<br />
$err = "Kein Fehler ($num)";<br />
switch ($num)<br />
{<br />
case UPLOAD_ERR_OK:<br />
break;<br />
case UPLOAD_ERR_INI_SIZE:<br />
$err = "Die in der php.ini festgelegte<br />
Größe wurde überschritten";<br />
break;<br />
case UPLOAD_ERR_FORM_SIZE:<br />
$err = "Die im Formular festgelegte Größe<br />
wurde überschritten";<br />
break;<br />
case UPLOAD_ERR_PARTIAL:<br />
$err = "Es wurde nur ein Teil der Datei<br />
hochgeladen (Abbruch)";<br />
break;<br />
case UPLOAD_ERR_NO_FILE:<br />
$err = "Es wurde keine Datei hochgeladen";<br />
break;<br />
}<br />
return $err;<br />
}<br />
$action = $_SERVER['PHP_SELF'];<br />
if ($_SERVER['REQUEST_METHOD'] == 'POST')<br />
{<br />
if (is_array($_FILES))<br />
{<br />
$tmp = $_FILES['<strong>My</strong>File']['tmp_name'];<br />
$target = "upload/{$_FILES['<strong>My</strong>File']['name']}";<br />
$error = GetFileError($_FILES['<strong>My</strong>File']['error']);<br />
echo "Dateien wurden hochgeladen:";<br />
echo
}<br />
Dateien hochladen<br />
if (move_uploaded_file($tmp, $target))<br />
{<br />
echo "Datei übertragen. Aktueller <strong>In</strong>halt des<br />
Verzeichnisses:";<br />
foreach (glob(dirname($target).'/*') as $file)<br />
{<br />
printf('%1$s', $file);<br />
}<br />
}<br />
else<br />
{<br />
echo "Keine Datei übertragen";<br />
}<br />
}<br />
else<br />
{<br />
echo "Bitte Datei hochladen (100 KB maximal)";<br />
}<br />
?><br />
338<br />
Formular- und Seitenmanagement<br />
eine Überschreitung der Größe zu einem Fehler und die Generierung der temporären<br />
Datei wird unterbunden. Trotz erfolgreicher Übertragung wird zumindest<br />
verhindert, dass jemand böswillig den Server zumüllt.<br />
Der spannende Teil ist die Auswertung des Hochladevorgangs. PHP erzeugt ein<br />
Array mit dem Namen $_FILES, das alle <strong>In</strong>formationen über die Dateien enthält.<br />
Dabei wird ein Eintrag für jedes mit type="file" gekennzeichnetes Feld erzeugt.<br />
Dieser Eintrag enthält wiederum ein Array, dessen Schlüssel folgende Bedeutung<br />
haben:<br />
Schlüssel Bedeutung<br />
name Ursprünglicher Namen der Datei auf der Festplatte des Benutzers<br />
type Der MIME-Typ, der die Art der Datei beschreibt, beispielsweise »image/<br />
gif« für ein GIF-Bild<br />
size Die Größe der Datei in Byte<br />
tmp_name Der Name der Datei im temporären Verzeichnis<br />
error Der Fehlercode, der beim Hochladen erzeugt wurde (0 = Fehlerfrei)<br />
Tabelle 8.3: Bedeutung der Schlüssel des Arrays, die zu einer Datei erzeugt werden<br />
Mit diesen Angaben kann man praktisch alles über die Datei erfahren und die weitere<br />
Vorgehensweise steuern. Der Name des Hauptschlüssels im Array $_FILES<br />
wird durch den Namen des Felds bestimmt:<br />
<br />
Der Arrayschlüssel lautet also »<strong>My</strong>File«. Für die Übertragung der Datei in das<br />
finale Verzeichnis benötigt man nun zwei Angaben. Zuerst wird der temporäre<br />
Name als Quelle ermittelt:<br />
$tmp = $_FILES['<strong>My</strong>File']['tmp_name'];<br />
Dann der Zielname, erstellt aus einem frei wählbaren Verzeichnis und dem<br />
ursprünglichen Namen:<br />
$target = "upload/{$_FILES['<strong>My</strong>File']['name']}";<br />
Nun wird der Kopiervorgang ausgelöst:<br />
if (move_uploaded_file($tmp, $target))
Dateien hochladen<br />
Die Funktion gibt FALSE zurück, wenn es sich nicht um eine hochgeladene Datei<br />
handelt. Ist die Datei dagegen bereits vorhanden, wird sie einfach überschrieben.<br />
Das nächste Beispiel behandelt mehr solcher »Feinheiten«.<br />
Hier wird im Anschluss lediglich noch der aktuelle <strong>In</strong>halt des Verzeichnisses angezeigt:<br />
foreach (glob(dirname($target).'/*') as $file)<br />
Das Kapitel zu Dateifunktionen geht ausführlich auf die eingesetzten Funktionen<br />
glob und dirname ein.<br />
Die Dateien werden außerdem als Link angeboten:<br />
printf('%1$s', $file);<br />
Nach ein paar Ladevorgängen sieht die Ausgabe dann ungefähr wie im folgenden<br />
Bild aus:<br />
Typische Probleme<br />
Abbildung 8.20:<br />
Hochladen von Dateien mit Auswertung<br />
und Dateiliste<br />
Es gibt einige Probleme, die in der Praxis auftreten können und die bisher nicht<br />
beachtet wurden:<br />
Laden mehrere Benutzer gleichnamige Dateien hoch, überschreiben sich<br />
diese gegenseitig<br />
339
340<br />
Formular- und Seitenmanagement<br />
Will ein Benutzer mehrere Dateien hochladen, ist das sehr umständlich<br />
Es besteht keine Einschränkung des Dateityps; ein Benutzer könnte auch ausführbare<br />
Dateien hochladen<br />
Die Größenbegrenzung ist mit ein paar Tricks zu umgehen<br />
Es gibt Probleme mit exotischen Zeichen in Dateinamen<br />
An dieser Stelle sollen nur einige Lösungsansätze aufgezeigt werden. Um ein<br />
Überschreiben zu vermeiden, wird die Funktion file_exists eingesetzt. Wie<br />
dann darauf reagiert wird, hängt von der Applikation ab. Eine andere Lösung<br />
besteht darin, jedem Benutzer ein eigenes Verzeichnis zu geben, aber dann müsste<br />
man ein Skript implementieren, das eine Benutzeranmeldung umfasst. Ist diese<br />
ohnehin vorhanden, sind persönliche Verzeichnisse optimal.<br />
Für den Dateityp bietet es sich an, ein Array mit zulässigen Typen zu erstellen.<br />
Typisch sind folgende Werte:<br />
Bilder: image/gif, image/jpg, image/jpeg, image/png, image/x-png<br />
Text und HTML: text/txt, text/html<br />
Binärdaten: application/octet-stream<br />
Sounddaten: audio/basic<br />
Videos: video/mpeg<br />
Prüfen Sie dann mit der Funktion in_array, ob der gesendete Typ in der Liste der<br />
erlaubten Werte enthalten ist.<br />
Für einen zuverlässigen Schutz gegen sehr große Dateien finden Sie im Folgenden<br />
Abschnitt zu den Konfigurationsmöglichkeiten mehr <strong>In</strong>formationen.<br />
Probleme mit exotischen (beispielsweise asiatischen) Zeichen in Dateinamen<br />
kann man in den Griff bekommen, indem das iconv-Modul geladen und zur<br />
Umwandlung benutzt wird.<br />
Konfigurationsmöglichkeiten<br />
Zahlreiche Konfigurationsmöglichkeiten bietet die Datei php.ini. Wenn Sie keinen<br />
Zugriff darauf haben, bietet sich die Funktion ini_set an, mit der sich die<br />
Einstellungen zeitweilig ändern lassen.
Konfigurationsmöglichkeiten mit der Datei php.ini<br />
Von der Seite zum Projekt<br />
<strong>In</strong> der Datei php.ini sind mehrere Optionen versteckt, die das Hochladeverhalten<br />
steuern:<br />
Option Beschreibung<br />
file_uploads Boolescher Wert, der bestimmt, ob überhaupt ein Hochladen<br />
erlaubt ist. »0« verhindert das Hochladen, der Standardwert<br />
»1« erlaubt es.<br />
upload_max_filesize Maximale Größe in Byte, die Dateien haben dürfen. Statt Bytes<br />
kann auch eine Kombination mit »K« (beispielsweise »50K«)<br />
für Kilobyte oder »M« (beispielsweise »1M«) für Megabyte<br />
benutzt werden. Der Standardwert beträgt »2M« (2 Megabyte).<br />
upload_tmp_dir Verzeichnis für die temporären Dateien. Hier müssen Schreibrechte<br />
bestehen, sonst geht gar nichts.<br />
post_max_size Maximale Größe in Byte, die der gesamte Umfang des Formulars<br />
haben darf. Der Wert muss größer als upload_max_filesize<br />
sein, damit keine weitere Einschränkung besteht.<br />
Tabelle 8.4: Optionen zur Konfiguration des Hochladeverhaltens<br />
8.5 Von der Seite zum Projekt<br />
Bislang waren immer nur einzelne Seiten zu erstellen. Praktisch besteht jedoch<br />
jede Website aus mehreren Seiten. Die Verbindung mit Hyperlinks ist Sache von<br />
HTML und dürfte kaum Probleme bereiten. Spannender ist es, wie Daten von<br />
Seite zu Seite übertragen werden.<br />
Die HTTP-Methode GET<br />
Die HTTP-Methode GET wird immer dann benutzt, wenn der Benutzer auf<br />
einen Link klickt. Außer dem einfachen Aufruf der nächsten Seite verfügt GET<br />
jedoch auch über Möglichkeiten, Daten mitzugeben. Damit wird der Zusammenhalt<br />
zwischen den Seiten gesteuert.<br />
341
342<br />
Formular- und Seitenmanagement<br />
Bevor es richtig losgeht, sollten Sie den Aufbau des URL komplett kennen. Hier<br />
ein Muster:<br />
http://www.comzept.de/pfad/index.php?val1=wert&val%202=wert#hash<br />
Die einzelnen Teile haben nun folgende Bedeutung:<br />
URL-Teil Bedeutung<br />
http Protokoll<br />
:// Trennzeichen<br />
www Servername (kann entfallen)<br />
comzept Domain<br />
de Toplevel-Domain<br />
/pfad Pfad (kann entfallen)<br />
/index.php Datei, die aufgerufen wird (Ressource)<br />
Alle weiteren Angaben sind optional:<br />
? Trennzeichen zur Abtrennung der Parameter<br />
val1=wert Parameterpaar<br />
val1 Parametername<br />
wert Parameterwert<br />
& Trennzeichen zwischen Parametern<br />
val%202=wert Weiteres Parameterpaar, kodiert<br />
val%202 Parametername, kodiert<br />
wert Parameterwert<br />
# Abtrennung des Hash-Zeichens<br />
hash Hash-Wert als Sprungziel (Sprungsmarken werden mit gebildet)<br />
Tabelle 8.5: Aufbau eines URL
Einen URL untersuchen<br />
Von der Seite zum Projekt<br />
Liegt ein URL im Skript vor und Sie benötigen spezielle Angaben daraus, hilft die<br />
Funktion parse_url:<br />
Listing 8.20: getparseurl.php – <strong>In</strong>formationen über den Aufbau eines URL ermitteln<br />
<br />
Die Ausgabe des von parse_url erzeugten Arrays zeigt die wichtigsten Komponenten:<br />
Der Rest dieses Abschnitts rankt sich mehr oder minder intensiv um das als<br />
»Query« bezeichnete Element.<br />
Kodierung des URL<br />
Abbildung 8.21:<br />
Teile eines URL<br />
Da einige Zeichen eine Sonderfunktion erfüllen, ist eine Kodierung der URL-<br />
Daten erforderlich. Das betrifft vor allem den <strong>In</strong>halt der im Muster als »wert«<br />
gekennzeichneten Bereiche. Die übliche Kodierung ersetzt Sonderzeichen durch<br />
ihren Hexwert. So wird ein Leerzeichen durch %20 ersetzt. »%« leitet den Code<br />
ein, 20 steht für dezimal 32 und dies ist der ASCII-Wert des Leerzeichens.<br />
Damit es nicht so schwierig ist, kennt PHP die Funktionen url_encode und<br />
url_decode. Dabei ist die Rückumwandlung meist nicht erforderlich, weil dies<br />
PHP intern erledigt. Der folgende Abschnitt zeigt die praktische Anwendung.<br />
343
344<br />
Formular- und Seitenmanagement<br />
Die Länge eines URL ist auf ca. 2.000 Zeichen begrenzt. Es ist wichtig,<br />
das zu beachten, weil überhängende Werte einfach abgeschnitten werden.<br />
Für große Datenmengen ist GET deshalb nicht geeignet. Weichen<br />
Sie dann auf POST aus, wie im vorhergehenden Abschnitt beschrieben.<br />
Die im letzten Beispiel als »Query« bezeichnet Folge kann damit dekodiert und<br />
weiter untersucht werden:<br />
Listing 8.21: getparseurldecode.php – »Händische« Zerlegung eines URL<br />
$url = "http://www.comzept.de/pfad/<br />
index.php?val1=wert&val%202=wert#hash";<br />
$p = parse_url($url);<br />
foreach ($p as $name => $content)<br />
{<br />
if ($name == 'query')<br />
{<br />
$pairs = explode('&', urldecode($content));<br />
foreach ($pairs as $values)<br />
{<br />
foreach (explode('=', $values) as $w => $v)<br />
{<br />
echo "$w = '$v'";<br />
}<br />
}<br />
}<br />
else<br />
{<br />
echo "$name: $content";<br />
}<br />
}<br />
Die systematische Zerlegung erfolgt hier anhand der Trennzeichen mit explode<br />
und die Ausgabe mit foreach-Schleifen. Dies war nur als kleine Übung zum<br />
Warmwerden gedacht, wie es einfacher geht, folgt gleich.<br />
Daten per URL übermitteln<br />
Um Daten per URL übermitteln zu können, werden an den Link entsprechende<br />
Paare aus Namen und Werten angehängt. Damit ergibt sich ein ähnliches Schema<br />
wie bei den POST-Daten aus einem Formular. Folgerichtig stellt PHP die Daten
Von der Seite zum Projekt<br />
auch wieder zur Verfügung, und zwar als Array mit dem Namen $_GET. Dekodierung<br />
und Splittung der Werte aus dem Query-Fragment wird automatisch erledigt.<br />
Das funktioniert freilich nur, wenn der Benutzer auch auf einen Link klickt und<br />
die Auswertung im aufgerufenen Skript stattfindet. Um einen als Zeichenkette vorliegenden<br />
Link zu zerlegen, muss man wie im letzten Beispiel gezeigt vorgehen.<br />
Listing 8.22: get_page1.php: Übergabe von Werten per URL<br />
346<br />
Formular- und Seitenmanagement<br />
?><br />
Von der Seite zum Projekt<br />
leicht erkennen. <strong>In</strong> solchen Fällen wird immer wieder auf die Startseite der Applikation<br />
zurückgesprungen. Eine Möglichkeit, die Kodierung vorzunehmen, sind so<br />
genannte Hashes. Dazu werden die vorbereiteten Daten in eine gesonderte Datei<br />
geschrieben und per include eingebunden. Eine solche Datei könnte am Beispiel<br />
der Navigation folgendermaßen aussehen:<br />
Listing 8.25: get_includenav.php – Definition der Basisdaten<br />
<br />
Vor dem Absenden an die Folgeseite werden diese Daten nun mit Hilfe der Funktion<br />
md5 zu einem Hash verpackt.<br />
Hashfunktionen liefern eine Art Prüfsumme über Daten, die generell<br />
nicht wieder in den ursprünglichen Wert umgewandelt werden (sie enthalten<br />
meist viel weniger Daten als die Datenquelle hatte). MD5 steht<br />
für Message Digest Version 5 und ist sehr weit verbreitet.<br />
Listing 8.26: get_navigationmd5.php – Linkgenerierung mit Kodierung<br />
348<br />
Formular- und Seitenmanagement<br />
<br />
Impressum<br />
<br />
Produkte<br />
<br />
AGBs<br />
<br />
Anmeldung<br />
<br />
Nun muss auf der Zielseite der Code wieder aufgelöst werden. Dazu kodiert man<br />
die Werte aus dem Array $titles erneut und vergleicht sie mit den gesendeten<br />
Hashcodes:<br />
Listing 8.27: get_navigationtargetmd5.php – Mit MD5 kodierte Links<br />
<br />
<br />
Bla bla bla, ...<br />
Der entscheidende Punkt ist hier der Vergleich zwischen dem am Ziel berechnet<br />
Hash (linker Term) und dem per GET übertragenen:<br />
if (md5($name) == $t)<br />
Der URL der Seite sieht nun folgendermaßen aus:<br />
get_navigationtargetmd5.php?t=3d87a30682721340bdfe22c3a1dfae28<br />
Dahinter kann auch ein Profi nicht mehr Ansatzweise vermuten, welche Art von<br />
Daten übertragen werden könnte. Bedenkt man, dass die Datenquelle in der Praxis<br />
keine einfachen Zeichenketten sind, sondern durchaus schon an sich kryptische<br />
Parameter, dann ist das Verfahren sehr sicher.
Grenzen des Verfahrens<br />
Cookies und Sessions<br />
Man stößt freilich auf eine andere Grenze des Verfahrens. Wenn die zu übertragenden<br />
Daten nicht statisch sind, also bereits beim Laden der Seite feststehen,<br />
kann man Hashes nicht einsetzen. Kryptografische Verfahren wären zwar möglich,<br />
erhöhen aber den Aufwand erheblich. Die entstehenden URLs sind sehr lang und<br />
der Rechenaufwand ist teilweise erheblich. Man kann Daten jedoch noch eleganter<br />
transportieren: Mit Sessions und passend dafür entworfenen Cookies.<br />
8.6 Cookies und Sessions<br />
Cookies und Sessions sind untrennbar miteinander verbunden, weshalb sie auch<br />
hier zusammen behandelt werden. Sie werden zwar noch sehen, dass Sessions<br />
auch ohne Cookies funktionieren, aber Cookies sind als Grundlage unerlässlich.<br />
Dieser Abschnitt zeigt, was Cookies und Sessions überhaupt sind, wozu sie dienen<br />
und wie man sie praktisch programmiert.<br />
Cookies<br />
Cookies sind Daten, die der Browser im Auftrag eines Servers ablegt und immer<br />
dann wieder ausliefert, wenn der Benutzer vom selben Server erneut Daten abruft.<br />
Die entscheidende Aussage dabei ist, dass der Browser darüber entscheidet, was er<br />
an den Server zurück sendet und ob überhaupt Daten gespeichert werden. Alle<br />
modernen Browser verfügen über Möglichkeiten, Cookies abzuschalten.<br />
An sich sind Cookies ungefährlich. Sie können lediglich Name/Wert-Paare enthalten<br />
und entsprechen damit dem bereits behandelten Schema der POST- und<br />
GET-Daten. Sie sind halt ein weiterer Weg, Daten von Seite zu Seite mitzunehmen.<br />
Hintergrund<br />
Cookies genießen leider einen recht schlechten Ruf, was an einigen missbräuchlichen<br />
Einsätzen liegt. Generell verfügen Cookies über ein Verfallsdatum. Dem<br />
Browser wird darin mitgeteilt, für welchen Zeitraum er das Cookie behalten und<br />
wieder ausliefern soll. Ein Sonderfall stellen so genannten Sessioncookies dar, die<br />
349
350<br />
Formular- und Seitenmanagement<br />
am Ende der Benutzersitzung verfallen – im Allgemeinen dann, wenn der Browser<br />
geschlossen wird.<br />
Viele Benutzer akzeptieren inzwischen nur noch Sessioncookies, weshalb<br />
deren Anwendung unkritisch ist. Auf reguläre Cookies sollte man<br />
sich als Webentwickler besser nicht verlassen.<br />
Die Entwicklung der Cookies geht auf das Protokoll HTTP zurück. HTTP gilt als<br />
so genanntes status- oder zustandsloses Protokoll. Der Browser initiiert die Verbindung<br />
und fordert vom Server eine Ressource (HTML, PHP-Skript, Bild, ...) an.<br />
Der Server liefert die Ressource aus und beide Seiten beenden den Vorgang. Ob<br />
die nächste Anfrage vom selben Browser oder einem anderen kommt, kann nicht<br />
sicher festgestellt werden. Die IP-Nummern der Browser sind nämlich meist dynamisch<br />
und wechseln bei manchen großen Providern auch während der Benutzersitzung.<br />
Die Firma Netscape hatte schon sehr früh die Idee, dass der Server eine kleine<br />
<strong>In</strong>formationsdatei zusammen mit den Daten sendet, die der Browser speichert.<br />
Die Daten enthalten eine Identifikationsnummer. Der Browser sendet bei der<br />
nächsten Anfrage an den Server dies Nummer wieder zurück. Daran kann der Server<br />
(bzw. das Programm, das dort abläuft) dann erkennen, um welchen Browser es<br />
sich handelt. Das Cookie war geboren.<br />
Den schlechten Ruf erhielten Cookies, weil die Wiedererkennung pro Ressource<br />
passiert. Der Server verpackt die Cookies nämlich im Kopf (Header) jeder HTTP-<br />
Antwort. Jedes Element einer Seite wird durch eine Anforderung geholt und mit<br />
der Antwort geliefert. Eine Seite enthält neben dem eigentlichen <strong>In</strong>halt auch Bilder<br />
oder Flash-Objekte. Diese werden alle mit einzelnen HTTP-Anforderungen<br />
beschafft. Dabei kann es sein, dass ein Bild, das als Werbebanner auf der Seite<br />
steht, von einem anderen Server geholt wird, als die eigentliche Seite. Derartige<br />
Ad-Server (Werbefirmen betreiben die) liefern nun für viele Seiten die Banner<br />
und damit auch Cookies aus. Natürlich gelangen die Cookies wieder zurück und<br />
so kann man leicht Bewegungsprofile erstellen, die den Weg des Benutzers über<br />
verschiedene ans Netzwerk angeschlossene Seiten aufzeichnen. Der Benutzer<br />
selbst bleibt zwar anonym, aber für eine kritische Presse hat es dennoch gereicht<br />
und flugs war der Ruf der Cookies versaut. Der Übeltäter, der als erster auf die Idee<br />
kam, war übrigens die Firma Doubleclick, die dieses Geschäft auch heute noch<br />
(neben vielen anderen) erfolgreich betreibt.
Cookies und Sessions<br />
Nichtsdestotrotz sind Cookies ein wertvolles und zudem leicht beherrschbares<br />
<strong>In</strong>strument des Webprogrammierers und deshalb sollten Sie sich damit unbedingt<br />
auseinandersetzen.<br />
Cookies in PHP<br />
Am Anfang wurde bereits erwähnt, dass Cookies ähnliche Name/Wert-Paare enthalten<br />
wie die POST- oder GET-Werte. Folgerichtig stehen auch Cookies als<br />
Array mit dem Namen $_COOKIE zur Verfügung. Die Erzeugung übernimmt<br />
dagegen eine spezielle Funktionen: setcookie. Die Parameter dieser Funktion<br />
reflektieren den <strong>In</strong>halt des Cookies:<br />
name<br />
Dies ist der einzige Pflichtparameter (alle anderen sind optional) und er enthält<br />
den Namen des Cookies. Beim Abruf empfangener Cookies ist dies der<br />
Schlüssel des Arrays.<br />
value<br />
Dies ist der Wert, der ins Cookie gesetzt wird. Die Angabe erfordert eine Zeichenkette.<br />
Wenn der Wert leer bleibt, wird das Cookie gelöscht. Das Cookie<br />
wird auch gelöscht, wenn das Verfallsdatum in der Vergangenheit liegt, weshalb<br />
meist beide Angaben zum Löschen kombiniert werden.<br />
expire<br />
Dies ist das Verfallsdatum als Unix-Zeitstempel. Auch diese Angabe ist optional.<br />
Wird nichts angegeben, entsteht ein Sessioncookie.<br />
path<br />
Ein relativer Pfad innerhalb der Applikation, damit nicht immer alle Cookies<br />
zurückgesendet werden. Selten benutzt.<br />
domain<br />
Schränkt die Domain ein, wenn der Server über Subdomains verfügt. Wenn<br />
das Cookie von www.comzept.de kommt, wird es auch nur dahin wieder<br />
zurückgesendet. Soll es auch auf www1.comzept.de landen, muss die Domain<br />
auf comzept.de eingeschränkt werden.<br />
secure<br />
Wird der Wert auf 1 gesetzt, wird das Cookie nur gesendet, wenn eine sichere<br />
Verbindung besteht. Der Standardwert ist 0.<br />
351
352<br />
Formular- und Seitenmanagement<br />
Die setcookie-Funktion kodiert Daten automatisch nach dem URL-<br />
Schema. Wenn Sie das nicht wünschen, setzen Sie setrawcookie ein.<br />
Abgesehen von der Kodierung sind beide Funktionen identisch.<br />
Beschränkungen<br />
Cookies sind auch wegen grundsätzlicher Beschränkungen nicht über längere<br />
Zeiträume zuverlässig. Diese Grenzen sind in der Spezifikation festgelegt:<br />
Der Browser muss nur 300 Cookies speichern<br />
Ein Cookie darf maximal 4 KByte groß sein, inklusive des Namens<br />
Jede Domain darf nur 20 Cookies senden<br />
Wird eine der Grenzen erreicht, werden die im Kontext ältesten Cookies gelöscht.<br />
Zu große Cookies werden gekürzt.<br />
Personalisierung mit Cookies<br />
Cookies lassen sich zur Personalisierung einsetzen. Das folgende Beispiel setzt<br />
Schriftart und -größe einer Website nach Benutzervorgaben. Wenn der Benutzer<br />
später wiederkommt, findet er die letzte Einstellung wieder vor. Die Daten werden<br />
in einem Cookie gespeichert.<br />
Listing 8.28: cookiestandard.php – Cookies setzen und wieder auslesen<br />
Cookies und Sessions<br />
$size = 3;<br />
}<br />
?><br />
Ihre Auswahl für Schriftart und -größe:<br />
354<br />
Formular- und Seitenmanagement<br />
Damit ist das Skript in dieser Phase praktisch beendet. Die Cookies sind jetzt im<br />
Browser angekommen. Das wirkt sich auf die Einstellungen nicht sofort aus, sondern<br />
erst beim nächsten (zweiten) Abruf der Seite durch den Browser. Nun sendet<br />
der Browser die Daten zurück und das $_COOKIE-Array ist gefüllt:<br />
if (!empty($_COOKIE['face']))<br />
Dann werden die beiden Cookies zurückgeholt:<br />
$face = $_COOKIE['face'];<br />
$size = $_COOKIE['size'];<br />
Mit einem Font-Tag wird dann noch die Ausgabe kontrolliert:<br />
<br />
Ihre Auswahl für Schriftart und -größe:<br />
Cookies und Sessions<br />
-- Ihre Auswahl --<br />
Verdana<br />
Arial<br />
Tahoma<br />
Times Roman<br />
<br />
<br />
-- Ihre Auswahl --<br />
1<br />
2<br />
3<br />
4<br />
5<br />
6<br />
7<br />
<br />
<br />
<br />
<br />
<br />
356<br />
Formular- und Seitenmanagement<br />
Das Skript ist nun schon sehr weit entwickelt. Was als nächste Aufgabe ansteht,<br />
wäre die Kodierung der Cookie-Werte.<br />
Andere Daten in Cookies speichern<br />
Cookies können nur Zeichenketten speichern. PHP kann andere Skalare leicht<br />
umwandeln. Arrays sind dagegen schwerer zu verpacken. Eine gute Idee ist die<br />
Verwendung von serialize. Diese Funktion erstellt eine Zeichenkettenform auch<br />
aus komplexen Variablen. Das folgende Beispiel verpackt die Daten aus dem letzten<br />
Skript in ein Array und muss deshalb nur noch ein Cookie senden. Das erhöht<br />
– nebenbei – durchaus die Chance, dass es auch wiederkommt.<br />
Listing 8.30: cookieserialize.php – Cookies zum Speichern verpackter Arrays verwenden<br />
(das verwendete Formular entspricht exakt dem letzten Listing 8.29)<br />
Cookies und Sessions<br />
$super = array_merge($cookies, $_POST);<br />
if (count($super) > 1)<br />
{<br />
$face = $super['face'];<br />
$size = $super['size'];<br />
}<br />
else<br />
{<br />
$face = 'Courier New';<br />
$size = 3;<br />
}<br />
?><br />
Zuerst werden die Daten, die das Cookie speichern soll, in ein Array gepackt:<br />
$array = array('face' => $_POST['face'],<br />
'size' => $_POST['size']);<br />
Dann wird das Array serialisiert und gesendet:<br />
setcookie('font', serialize($array), $expires);<br />
Beim Auspacken des Cookies geht man dann genau umgekehrt vor:<br />
$cookies = unserialize($_COOKIE['font']);<br />
Das entstehende Array entspricht genau dem ursprünglich verpackten und kann<br />
sofort verwendet werden:<br />
$super = array_merge($cookies, $_POST);<br />
Der Rest ist gegenüber der letzten Version unverändert.<br />
Als Letztes ist ein Blick auf die Daten interessant, die PHP beim Serialisieren<br />
erzeugt:<br />
a:2:{s:4:"face";s:5:"Arial";s:4:"size";s:1:"3";}<br />
Aus diesen Daten kann PHP alles entnehmen, was zur Regenerierung der Daten<br />
erforderlich ist.<br />
Cookies und Sicherheit<br />
Cookies sind nicht zuverlässig und nicht sicher. Es ist generell ein sehr schlechte<br />
Idee, Benutzernamen und Kennwörter in Cookies zu speichern. Bemächtigt sich<br />
jemand eines Computers, auf dem Kennwörter in Cookies gespeichert sind,<br />
könnte er sich damit unter einem fremden Namen anmelden. Außerdem liegen<br />
357
358<br />
Formular- und Seitenmanagement<br />
die Cookies in allen Browsern im Klartext vor, sodass »Schnüffelprogramme«<br />
intime Daten finden könnten.<br />
Am besten ist es, wenn man nur IDs speichert und alle anderen Daten konsequent<br />
auf dem Server belässt. Genau das ist die Aufgabe der Session-Verwaltung.<br />
Session-Verwaltung<br />
Bei der Session-Verwaltung geht es im weitesten Sinne darum, zu einem bestimmten<br />
Benutzer gehörende Daten während der gesamten Sitzung zu speichern und<br />
die Zuordnung aufrecht zu erhalten. Cookies sind ursprünglich zu diesem Zweck<br />
entworfen worden. Nun darf man die Dinge nicht durcheinander bringen. Es ist<br />
nicht der Sinn des Cookies, diese Daten zu speichern. Wie bereits gezeigt wurde,<br />
sind Cookies nicht wirklich sicher. Die Session-Verwaltung macht es sich zur Aufgabe,<br />
zum einen jeden technisch möglichen Weg zum Erhalt des Zustands zu nutzen<br />
und außerdem die Speicherung der eigentlichen Daten als integrierte Aufgabe<br />
zu übernehmen.<br />
Datenspeichermethoden<br />
Werden Daten zu einem Benutzer gespeichert, muss zuerst die Frage gestellt werden,<br />
wo diese abgelegt werden. Speichern ist ein weitläufiger Begriff. Grundsätzlich<br />
bietet sich folgendes an:<br />
Dateisystem<br />
Datenbank<br />
Computer-Speicher<br />
Andere Server<br />
PHP verwendet davon standardmäßig das Dateisystem. Es ist auf Unix-Systemen<br />
mit Apache Webserver und PHP-Modul möglich, auch den Computer-Speicher<br />
zu nutzen. Der Preis für höhere Leistung ist die <strong>In</strong>kompatibilität der Skripte mit<br />
anderen Systemen. Datenbanken sind schnell, elegant und meist optimal, das<br />
Modul zum Speichern aber muss selbst implementiert werden. Andere Server sind<br />
eher Notlösungen, um Cluster zu ermöglichen, wenn keine Datenbank vorhanden<br />
ist. Apropos Cluster: Sind mehrere Webserver zur Lastverteilung zusammengeschlossen,<br />
so ist nicht unbedingt sicherzustellen, dass Abrufe ein und desselben<br />
Browsers immer beim gleichen Server landen. Wenn nun die Sitzungsdaten auf
Cookies und Sessions<br />
der einen Maschine auf der Festplatte liegen und der nächste Start des Skripts von<br />
einer anderen erfolgt, geht der durch die Session-Verwaltung erreichte Zusammenhang<br />
wieder verloren. Alle größeren Systeme nutzen deshalb Datenbanken.<br />
Damit aber nicht schon bei den ersten Versuchen eine Datenbank benötigt wird,<br />
nutzt PHP ohne weitere Einstellungen das Dateisystem.<br />
Methoden zur Übertragung der Session-ID<br />
Die Session-ID spielt eine herausragende Rolle bei der Session-Verwaltung. Sie<br />
sorgt dafür, dass der Benutzer beim erneuten Abruf wieder erkannt wird. Nur die<br />
Session-ID wird letztlich auf seinem Computer gespeichert. Die Daten verlassen<br />
niemals den Server. Der Erhalt der Session-ID ist also wesentlich für die Funktion.<br />
Damit das funktioniert, muss diese mit dem Code der HTML-Seite zum Browser<br />
und von dort wieder zurück gelangen. Es gibt drei Wege, die in PHP5 dafür zur<br />
Verfügung stehen:<br />
1. GET<br />
2. POST<br />
3. Cookies<br />
Cookies sind übrigens eigens zu diesem Zweck erfunden worden und PHP5<br />
nimmt diese als Standard. Nur wenn Cookies – aus welchen Gründen auch immer<br />
– nicht benutzt werden sollen, kommen andere Techniken zum Zuge. Letztlich<br />
geht es immer nur darum, die Session-ID hin und her zu transportieren.<br />
Wird GET verwendet, erfolgt die Verpackung im URL, als einfacher GET-Parameter:<br />
http://www.comzept.de/test.php?sid=ASD934DE42BC906876AF73F10932B<br />
Es ist dann natürlich erforderlich, dafür zu sorgen, dass dieser Parameter in jeden<br />
internen Seitenaufruf eingebaut wird. PHP erledigt dies intern sehr zuverlässig,<br />
wenn es sich um einfaches HTML oder komplette Links in JavaScript handelt.<br />
Bei POST muss die Session-ID Teil des Formulars werden. Dazu wird ein verstecktes<br />
Feld eingebaut:<br />
<br />
<br />
<br />
359
360<br />
Formular- und Seitenmanagement<br />
Die Session-Verwaltung in PHP5 erledigt nun mehrere Dinge intern:<br />
Verwendung von Sessioncookies, wenn möglich<br />
Übergang auf GET und POST, wenn erforderlich oder explizit verlangt<br />
Speicherung von Variablen im Kontext einer Sitzung in Dateien<br />
Kontrolle der Sitzungsdauer<br />
Wenn Sie die Daten nicht in Dateien, sondern anders speichern möchten, müssen<br />
die entsprechenden Behandlungsfunktionen manuell ausprogrammiert werden.<br />
PHP5 stellt dafür einen Schnittstelle über Rückruffunktionen bereit, die dies recht<br />
einfach macht. Für »normale« Applikationen reicht es jedoch, die interne Verwaltung<br />
zu verwenden und so schnell von Session-Variablen zu profitieren.<br />
Vorbereitung<br />
Wie bereits erwähnt, speichert PHP5 standardmäßig die Sitzungsdaten in Dateien.<br />
Als Ziel wird das temporäre Verzeichnis benutzt. <strong>In</strong> der Auslieferungsversion der<br />
php.ini wird der Unix-Dateipfad verwendet. Da 90% aller Entwicklungsumgebungen<br />
unter Windows laufen, kommt es regelmäßig zu einer Fehlermeldung beim<br />
ersten Start der Session-Verwaltung.<br />
Wenn dieser Fehler auftritt, öffnen Sie die für Ihre <strong>In</strong>stallation zuständige Datei<br />
php.ini. Wenn Sie nicht wissen, wo diese liegt, erstellen Sie ein Skript, in dem die<br />
Funktion phpinfo aufgerufen wird. Im Kopf der Seite steht die benötigte Angabe:<br />
<strong>In</strong> dieser Datei suchen Sie nun nach folgender Zeile:<br />
session.save_path = "/tmp"<br />
Abbildung 8.25:<br />
Fehler, die Session-Verwaltung<br />
konnte die Daten<br />
nicht speichern<br />
Abbildung 8.26:<br />
So ermitteln Sie<br />
die richtige<br />
php.ini
Cookies und Sessions<br />
Ändern Sie die Zeile so, dass sie auf ein existierendes Verzeichnis zeigt:<br />
session.save_path = "C:\Windows\Temp"<br />
Noch ein Problem ergibt sich, wenn Skripte aus dem <strong>In</strong>ternet übernommen werden,<br />
die Funktionen wie session_register verwenden. Es ist mit PHP5 nicht<br />
empfehlenswert, diese Funktionen einzusetzen, weil aus Sicherheitsgründen<br />
einige damit verbundene Automatismen ausgeschaltet wurden. Das erschwert den<br />
Umgang. Einfacher ist es, das bereits mehrfach verwendete Schema mit dem globalen<br />
Array zu nutzen und das für die Session-Verwaltung zuständige Array<br />
$_SESSION zu verwenden.<br />
Anwendung<br />
Um die Session-Verwaltung zu verwenden, genügt es, am Anfang des Skripts folgende<br />
Funktion aufrufen:<br />
session_start();<br />
Nun sind noch die Variablen festzulegen, die als Teil der Session-Verwaltung<br />
gespeichert werden sollen. Dazu dient das $_SESSION-Array:<br />
$_SESSION['VariablenName'] = $Variable;<br />
Springt nun der Benutzer auf eine Folgeseite, steht dort, nachdem erneut<br />
session_start aufgerufen wurde, das Array mitsamt <strong>In</strong>halt wieder zur Verfügung.<br />
Das folgende Beispiel zeigt, wie es in der Praxis funktioniert. Es realisiert eine einfache<br />
Benutzeranmeldung. Nach erfolgreicher Anmeldung sollen alle Seiten<br />
zugänglich sein:<br />
Listing 8.31: sessionlogon.php – Anmeldeseite mit Speicherung der Anmeldedaten in<br />
einer Session-Variablen<br />
362<br />
Formular- und Seitenmanagement<br />
{<br />
$links .= sprintf('%2$s',<br />
$target,<br />
$title);<br />
}<br />
return $links;<br />
}<br />
function CheckLogon()<br />
{<br />
global $names;<br />
if ($_SERVER['REQUEST_METHOD'] != 'POST') return FALSE;<br />
foreach ($names as $Logon => $Password)<br />
{<br />
echo '#';<br />
if (!empty($_POST['Logon']) && $_POST['Logon'] == $Logon<br />
&&<br />
!empty($_POST['Password'])<br />
&& $_POST['Password'] == $Password)<br />
{<br />
$_SESSION['LogonName'] = $Logon;<br />
return TRUE;<br />
}<br />
}<br />
return FALSE;<br />
}<br />
if ($_SERVER['REQUEST_METHOD'] == 'POST')<br />
{<br />
CheckLogon();<br />
}<br />
?><br />
Cookies und Sessions<br />
Kennwort:<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Der erste Prozess, der erkannt werden muss, ist das Absenden des Formulars. Nur<br />
dann kann der Benutzer eine Anmeldung versucht haben:<br />
if ($_SERVER['REQUEST_METHOD'] == 'POST')<br />
Ist das der Fall, wird die Funktion zum Prüfen der Anmeldedaten ausgeführt:<br />
CheckLogon();<br />
Diese Funktion sollte in praktischen Anwendungen durch eine spezifischeren<br />
Variante ersetzt werden. Hier wird nur gegen die Daten eines fest programmierten<br />
Arrays geprüft. Ist diese Prüfung erfolgreich, wird der Anmeldename in die Session<br />
geschrieben:<br />
$_SESSION['LogonName'] = $Logon;<br />
Dieser Vorgang ist sicher, weil der Benutzer keine Möglichkeit hat, den <strong>In</strong>halt dieses<br />
Arrays zu manipulieren. Beim Erzeugen der Links wird nun die erfolgreiche<br />
Anmeldung zur Anzeigesteuerung benutzt. Es ist nun wichtig, dass auf jeder beliebigen<br />
Folgeseite die <strong>In</strong>formation darüber vorliegt, ob die Anmeldung erfolgte. Das<br />
Skript sessionpages.php zeigt, wie dies aussehen kann:<br />
Listing 8.32: sessionpages.php – Zugriff auf die Session-Variablen<br />
364<br />
Formular- und Seitenmanagement<br />
$$name = $value;<br />
}<br />
}<br />
ReCreateVariables();<br />
$title = empty($_GET['title']) ? 'Startseite' : $_GET['title'];<br />
?><br />
<br />
Sie sind angemeldet als: <br />
Mit dem Aufruf von session_start stehen die gespeicherten Variablen im<br />
$_SESSION-Array wieder bereit. Sie können nun direkt darauf zugreifen. Falls es<br />
einfacher oder praktikabler ist, alle Variablen wieder als globale verfügbar zu<br />
machen. Die Funktion ReCreateVariables() erledigt das. Dazu durchläuft man das<br />
Array mit foreach:<br />
foreach($_SESSION as $name => $value)<br />
Nun wird über die Syntax der variablen Variablennamen die aktuelle lokale Variable<br />
$name zur globalen Variablen erklärt:<br />
global $$name;<br />
Dieser nun global deklarierten Variablen wird der entsprechende Wert zugewiesen:<br />
$$name = $value;<br />
Wichtig ist hier nur, die zwei $$-Zeichen statt des normalerweise verwendeten einzelnen<br />
anzugeben, damit der Text in $name den Namen der Variable bestimmt,<br />
nicht die Variable selbst (indirekter Zugriff).<br />
Wenn nun im $_SESSION-Array ein Eintrag mit dem Namen LogonName existiert,<br />
steht dieser als Variable zur Verfügung, sodass folgendes funktioniert:<br />
<br />
Die Referenz Session-Verwaltung zeigt weitere Session-Funktionen. Deren Anwendung<br />
unterliegt teilweise Restriktionen der konkreten PHP-<strong>In</strong>stallation. Der<br />
hier beschriebene Weg mit dem $_SESSION-Array ist nicht die einzige Möglichkeit,<br />
aber er funktioniert garantiert immer.
8.7 Referenz<br />
Wichtige Systemarrays<br />
Name Beschreibung<br />
Server- und Umgebungsvariablen<br />
Referenz<br />
$_GET Variablen einer GET-Anforderung, die als Teil des URL übertragen wurden.<br />
$_POST Variablen einer POST-Anforderung, die als Teil eines Formulars übertragen<br />
wurden.<br />
$_REQUEST Alle Variablen, egal ob per GET oder POST übertragen.<br />
$_COOKIES Auflistung aller definierten und empfangenen Cookies der letzten<br />
Anforderung.<br />
$_SESSION Auflistung aller Sitzungsvariablen und deren Werte. Dieses Array ist nur<br />
nutzbar, wenn die Sitzungsverwaltung aktiviert wurde.<br />
$_FILES <strong>In</strong>formationen über zuletzt hochgeladene Dateien, deren Größe, MIME-<br />
Typ, Name und temporärer Name. Dieses Array ist nur gefüllt, wenn Formulare<br />
mit der entsprechenden Kodierung gesendet werden.<br />
Tabelle 8.6: Systemarrays in PHP5<br />
Name der Variable Beschreibung<br />
ALL_HTTP Alle HTTP-Header, die vom Client zum Server gesendet<br />
wurden. Das Ergebnis sind Header, die mit HTTP_<br />
beginnen.<br />
ALL_RAW Alle HTTP-Header, die vom Client zum Server gesendet<br />
wurden. Im Ergebnis werden Header gesendet, die<br />
kein Präfix haben.<br />
APPL_MD_PATH Gibt den Pfad zur Metabasis der Applikation an (nur<br />
IIS/Windows).<br />
Tabelle 8.7: Server- und Umgebungsvariablen<br />
365
366<br />
Formular- und Seitenmanagement<br />
Name der Variable Beschreibung<br />
APPL_PHYSICAL_PATH Gibt den physischen Pfad zur Metabasis der Applikation<br />
an (nur IIS/Windows).<br />
AUTH_NAME Name des Nutzers bei Eingabe in das Kennwortfeld des<br />
Browsers.<br />
AUTH_PASSWORD Das Kennwort einer Autorisierung, wenn es im Kennwortfeld<br />
des Browsers eingegeben wurde (nur Windows).<br />
AUTH_TYPE Art der Autorisierung, wenn Nutzer Zugriff auf ein<br />
geschütztes Dokument haben möchten (nur Windows).<br />
CERT_COOKIE Eindeutige ID eines Clientzertifikats.<br />
CERT_FLAGS Flag des Clientzertifikats, Bit 0 ist 1, wenn das Clientzertifikat<br />
vorhanden ist, Bit 1 ist 1, wenn das Clientzertifikat<br />
nicht überprüft wurde.<br />
CERT_ISSUER Das Issuer-(Herausgeber)-Feld des Clientzertifikats.<br />
CERT_KEYSIZE Bitzahl bei einer SSL-Verbindung.<br />
CERT_SECRETKEYSIZE Anzahl der Bits eines privaten Zertifikatschlüssels.<br />
CERT_SERIALNUMBER Die Seriennummer des Zertifikats.<br />
CERT_SERVER_ISSUER Das Issuer-(Herausgeber)-Feld des Serverzertifikats<br />
(Issuer-Feld).<br />
CERT_SERVER_SUBJECT Beschreibung des Zertifikats (Server).<br />
CERT_SUBJECT Beschreibung des Zertifikats (Client).<br />
CONTENT_LENGTH Länge des zu sendenden <strong>In</strong>halts.<br />
CONTENT_TYPE Art des <strong>In</strong>halts (MIME-Type).<br />
DATE_GMT Datum/Uhrzeit des Servers in Zone GMT.<br />
DATE_LOCAL Datum/Uhrzeit des Servers.<br />
DOCUMENT_NAME Name des ausführenden Dokuments.<br />
Tabelle 8.7: Server- und Umgebungsvariablen (Forts.)
Name der Variable Beschreibung<br />
DOCUMENT_ROOT Pfad zum Dokument ohne Dateiname.<br />
GATEWAY_INTERFACE Art des <strong>In</strong>terfaces, das der Server benutzt.<br />
Referenz<br />
HTTP_ACCEPT Enthält die MIME-Typen, die der Browser akzeptieren<br />
kann und will.<br />
HTTP_COOKIE Die Cookie-Daten, wenn Cookies gesendet wurden.<br />
HTTP_REFERER Die letzte Adresse, von welcher der Browser kam.<br />
Wurde die Seite direkt aufgerufen, ist der Wert leer.<br />
HTTP_USER_AGENT Kennung des Browsers, beispielsweise Mozilla/4.0<br />
(compatible; MSIE 5.0; Windows NT). Der Wert kann<br />
auf vielen Systemen manipuliert werden und ist deshalb<br />
mit Vorsicht zu betrachten.<br />
HTTPS Ist ON, wenn der Server SSL benutzt.<br />
HTTPS_KEYSIZE Schlüssellänge der HTTPS-Verbindung (40bit,<br />
128bit...).<br />
HTTPS_SECRETKEYSIZE Schlüssellänge bei privaten Zertifikaten.<br />
HTTPS_SERVER_ISSUER Issuer-Feld des Serverzertifikats bei sicherer Übertragung.<br />
HTTPS_SERVER_SUBJECT Eine Beschreibung des Servers.<br />
INSTANCE_ID ID-Nummer der <strong>In</strong>stanz (nur IIS).<br />
INSTANCE_META_PATH Der Metabasispfad (nur IIS).<br />
LOCAL_ADDR Die in der Anforderung benutzte Serveradresse.<br />
LOGON_USER Das lokale Benutzerkonto.<br />
PATH_INFO Pfadinformation für den Client.<br />
PATH_TRANSLATED Übertragung der Pfadinformation ins physische Format.<br />
QUERY_STRING <strong>In</strong>halt des Querystrings (Parameter-URL).<br />
REMOTE_ADDR Die IP-Adresse des Nutzers.<br />
Tabelle 8.7: Server- und Umgebungsvariablen (Forts.)<br />
367
368<br />
Formular- und Seitenmanagement<br />
Name der Variable Beschreibung<br />
REMOTE_HOST Der Name des Computers des Nutzers.<br />
REQUEST_METHOD Die Methode der Datenübertragung eines Formulars.<br />
Kann GET, PUT oder POST sein.<br />
REQUEST_URI URI der Anforderung.<br />
SCRIPT_FILENAME Name eines Skripts.<br />
SCRIPT_NAME Name eines Skripts.<br />
SERVER_NAME Der Hostname des Servers, eine DNS- oder IP-Adresse.<br />
SERVER_PORT Port, der vom Server benutzt wird (normalerweise 80).<br />
SERVER_PORT_SECURE Port, der bei sicherer Übertragung benutzt wird<br />
(Standard: 443).<br />
SERVER_PROTOCOL Das verwendete Protokoll und die Version (beispielsweise<br />
HTTP1.1).<br />
SERVER_SIGNATURE Signatur des Servers (einstellbare <strong>In</strong>fo).<br />
SERVER_SOFTWARE Der Name und die Version der auf dem Server laufenden<br />
Software.<br />
Tabelle 8.7: Server- und Umgebungsvariablen (Forts.)<br />
Der Abruf erfolgt immer mit $_SERVER['NAME']. Beachten Sie, dass nicht auf allen<br />
Servern alle Variablen zur Verfügung stehen und dass sich die <strong>In</strong>halte teilweise<br />
geringfügig unterscheiden, beispielsweise bei Pfadangaben.<br />
Session-Verwaltung<br />
Funktion Beschreibung<br />
session_cache_expire Die aktuelle Cache-Verfallszeit.<br />
session_cache_limiter Art der Cacheverwaltung.<br />
session_decode Dekodiert die Daten einer Session.<br />
Tabelle 8.8: Funktionen der Session-Verwaltung
Funktion Beschreibung<br />
session_destroy Beendet die Session und zerstört alle Daten.<br />
session_encode Kodiert die Daten der aktuellen Session.<br />
session_get_cookie_params Liefert die Session-Cookie Parameter.<br />
session_id Setzt oder ermittelt die aktuelle Session-ID.<br />
Referenz<br />
session_is_registered Überprüft, ob eine globale Variable in einer Session<br />
bereits registriert ist.<br />
session_module_name Ermittelt oder setzt das aktuelle Session-Modul.<br />
session_name Ermittelt oder setzt den Namen der aktuellen Session.<br />
session_regenerate_id Erzeugt eine neue Session-ID, ohne die Session dabei<br />
zu beenden.<br />
session_register Registriert eine Variable als Session-Variable.<br />
session_save_path Ermittelt oder setzt den aktuellen Speicherpfad der<br />
Session (zum temporären Verzeichnis).<br />
session_set_cookie_params Setzt die Parameter für das Session-Cookie.<br />
session_set_save_handler Setzt benutzerdefinierte Rückruffunktionen.<br />
session_start <strong>In</strong>itialisiert eine Session.<br />
session_unregister Nimmt eine Variable aus den Session-Variablen wieder<br />
raus.<br />
session_unset Löscht alle Session-Variablen.<br />
session_write_close Speichert alle Session-Daten und beendet die Session.<br />
Tabelle 8.8: Funktionen der Session-Verwaltung (Forts.)<br />
<strong>In</strong>itialisierungseintrag Beschreibung<br />
session.name Der Name der Session. Dieser Wert wird für den Url-Parameter<br />
und das Cookie verwendet. Der Standardwert ist<br />
PHPSESSID.<br />
Tabelle 8.9: Konfiguration der Session-Verwaltung<br />
369
370<br />
Formular- und Seitenmanagement<br />
<strong>In</strong>itialisierungseintrag Beschreibung<br />
session.auto_start Wenn aktiviert, wird bei einer neuen Anfrage eine neue<br />
Session gestartet, nicht erst beim Aufruf von session_start.<br />
Standardmäßig deaktiviert (0), zum Aktivieren auf 1 setzen.<br />
session.serialize_handler Name der Methode, mit der die Daten serialisiert werden.<br />
Standardwert ist »php«, Alternative ist »wddx«.<br />
session.gc_probability Regelt die Wahrscheinlichkeit, mit der die Aufräumroutine<br />
alte Session-Daten beseitigt. Standardwert ist 1.<br />
session.gc_divisor Regelt die Wahrscheinlichkeit, mit der die Aufräumroutine<br />
alte Session-Daten beseitigt, im Verbund mit<br />
»gc_probability«. Die finale Wahrscheinlichkeit beträgt<br />
gc_probability/gc_divisor. Der Standardwert ist 100, dass<br />
heißt die Aufräumroutine startet mit einer Wahrscheinlichkeit<br />
von 1%.<br />
session.gc_maxlifetime Lebensdauer einer Session in Sekunden. Der Standardwert<br />
beträgt <strong>14</strong>40 (24 Minuten).<br />
session.referer_check Zeichenkette, die im Referer (Quelle des HTTP-Abrufs)<br />
enthalten sein muss. Wenn aktiviert, verhindert man damit<br />
den Einsprung in eine Session von außerhalb. Standardmäßig<br />
nicht verwendet.<br />
session.entropy_file Pfad einer Quelldatei, die beim Erzeugen der Session-ID<br />
benutzt wird. Standardmäßig nicht verwendet.<br />
session.entropy_length Anzahl der Bytes, die aus der Entropie-Datei gelesen werden.<br />
Standardwert ist 0.<br />
session.use_cookies Erlaubt Cookies. Standardwert ist 1 (aktiviert).<br />
session.use_only_cookies Erzwingt (!) Cookies. Cookies werden auch so verwendet,<br />
aber mit dieser Option kann man alternative Wege aus<br />
Kompatibilitätsgründen verhindern. Standardmäßig nicht<br />
aktiviert (0).<br />
session.cookie_lifetime Lebensdauer des Cookies. Standardwert ist 0, was typische<br />
Session-Cookies erzeugt.<br />
session.cookie_path Pfad-Angabe des Session-Cookies.<br />
Tabelle 8.9: Konfiguration der Session-Verwaltung (Forts.)
<strong>In</strong>itialisierungseintrag Beschreibung<br />
session.cookie_domain Domain-Angabe des Session-Cookies.<br />
Referenz<br />
session.cookie_secure Erzwingt die Verwendung sicherer Verbindungen für das<br />
Cookie.<br />
session.cache_limiter Methode der Cacheverwaltung. Standardwert ist »nocache«<br />
(nicht verwendet).<br />
session.cache_expire Verfallszeitpunkt des Cache in Minuten. Standardwert ist<br />
180.<br />
session.use_trans_sid Aktiviert die transparente SID-Umsetzung, bei der versteckte<br />
Felder und URL-Erweiterung zur Übergabe der<br />
Session-ID verwendet werden.<br />
session.bug_compat_42 Erzeugt eine Warnung, wenn versucht wird, mit einem<br />
Trick globale Variablen mit Session-Daten zu erzeugen,<br />
obwohl dies durch andere Einstellungen verboten ist und<br />
zusätzlich bug_compat_warn aktiviert wurde.<br />
session.bug_compat_warn Erzeugt eine Warnung, wenn versucht wird, mit einem<br />
Trick globale Variablen mit Session-Daten zu erzeugen,<br />
obwohl dies durch andere Einstellungen verboten ist.<br />
session.hash_function Prüfsummenfunktion für die Session-ID. 0 = MD5<br />
(128 Bit), 1 = SHA-1 (160 Bit). Standard ist MD5.<br />
session.hash_bits_per_<br />
character<br />
Gespeicherte Bits pro Zeichen in der Session-ID. Kann 4, 5<br />
oder 6 sein. 4 ist der Standardwert.<br />
url_rewriter.tags Tags, die zur Aufnahme der Session-ID umgeschrieben<br />
werden dürfen.<br />
Tabelle 8.9: Konfiguration der Session-Verwaltung (Forts.)<br />
371
372<br />
Formular- und Seitenmanagement<br />
8.8 Kontrollfragen<br />
1. Warum sollten unbedingt immer »Sticky Forms« verwendet werden?<br />
2. Welche Funktionen unterstützen das Hochladen von Dateien? Was ist beim Aufbau<br />
des Formulars zu beachten?<br />
3. Welche Methoden verwenden Sessions, um die Session-Daten zu speichern?<br />
4. Wie können Cookies missbraucht werden?
Professionelle<br />
Programmierung<br />
9
374<br />
Professionelle Programmierung<br />
9.1 Mehrsprachige Webseiten<br />
Immer häufiger wird gefordert, Webseiten mehrsprachig anzubieten. Es ist gerade<br />
für kleine Unternehmen ein Segen, Kunden überall auf der Welt ansprechen zu<br />
können. Mit Deutsch allein kommt man dabei aber nicht weit, mit Englisch wiederum<br />
kann man hierzulande nicht viel erreichen. Es ist deshalb oft notwendig,<br />
Websites in mehreren Sprachen anzubieten. Damit der Aufwand nicht ins Unermessliche<br />
steigt, gibt es verschiedene Techniken, Gestaltung und <strong>In</strong>halt zu trennen,<br />
sodass die aufwändige Grafik nur einmal existiert, die variablen <strong>In</strong>halte<br />
dagegen getrennt gehalten werden.<br />
Vor der Auswahl der Sprache steht jedoch die Erkennung der Daten durch den<br />
Benutzer. Dies erfolgt durch Auswertung der vom Browser übermittelten Daten.<br />
Browserdaten erkennen<br />
Der Browser überträgt verschiedene <strong>In</strong>formationen an den Server, die sich dann in<br />
PHP-Skripten auswerten lassen. Neben Betriebssystem, Name des Browsers und<br />
verschiedenen Angaben zu akzeptierten Daten gehört auch die bevorzugte Sprache<br />
dazu.<br />
Sprache im Browser einrichten<br />
Jeder Benutzer sollte in seinem Browser die bevorzugte Sprache einrichten, damit<br />
Skripte, wie die nachfolgend vorgestellten, auch funktionieren. Die entsprechenden<br />
Menükommandos zeigt die folgende Liste:<br />
Netscape/Mozilla: Bearbeiten | Einstellungen | Navigator | Sprachen<br />
<strong>In</strong>ternet Explorer: Extras | <strong>In</strong>ternetoptionen | Allgemein | Sprachen<br />
Opera: Datei | Einstellungen | Sprachen<br />
Tango: Sprache | Spracheinstellungen<br />
Lynx 2.7: O (Options) | G (lanGuage)<br />
Nach dieser Angabe ist natürlich interessant zu wissen, was davon in PHP5<br />
ankommt. Benutzt wird eine Servervariable aus dem Array $_SERVER:<br />
HTTP_ACCEPT_LANGUAGE.
Mehrsprachige Webseiten<br />
Diese enthält eine Liste der akzeptierten Sprachen mit einer Angabe der Wertigkeit.<br />
Die erste Sprache ist immer die bevorzugte, der Rest teilt sich mit abnehmender<br />
Wichtung in Relation zu 1. Das »q« steht für »Quality«. Die in der letzten<br />
Abbildung gezeigte Angabe sendet der Browser folgendermaßen:<br />
de,en-us;q=0.7,fr;q=0.3<br />
Das bedeutet, die Hauptsprache ist Deutsch, Englisch wird mit 0,7 gegenüber<br />
Französisch mit 0,3 bevorzugt, wobei 1 = ideal wäre. Die meisten Browser legen<br />
die Werte selbst fest, sodass hier wenig Wahl besteht. Tatsächlich wäre es auch<br />
möglich, folgende Angabe zu machen:<br />
de,en-us;q=0.8,e-gb;q=0.7<br />
Hier wird mitgeteilt, dass Deutsch ideal ist, amerikanisches Englisch akzeptiert<br />
wird und alternativ auch britisches Englisch gern gelesen wird.<br />
Sie sollten als Programmierer nicht darauf vertrauen, dass irgendwer<br />
mehr als die Standardsprache halbwegs sinnvoll eingestellt hat. Die folgenden<br />
Anwendungen werten deshalb auch nur diesen Teil aus.<br />
Sprache auswerten<br />
Abbildung 9.1:<br />
Spracheinstellungen im <strong>In</strong>ternet<br />
Explorer<br />
Das Auswerten der Sprache ist nun recht einfach. Man muss nur die Servervariable<br />
abfragen und den ersten Teil, bis zum Komma, abtrennen:<br />
375
376<br />
Professionelle Programmierung<br />
Listing 9.1: browserlang.php – Sprachakzeptanz abfragen und Hauptsprache ermitteln<br />
<br />
Das Skript nutzt Zeichenkettenfunktionen, um den Anfang der Sprachinformation<br />
und damit die Hauptsprache herauszufinden. Für die Auswertung der übrigen<br />
<strong>In</strong>formationen bietet sich der Zugriff auf reguläre Ausdrücke oder die Zerlegung<br />
mit explode an.<br />
Steht die Sprache fest, muss nur noch entsprechend darauf reagiert werden.<br />
Lokalisierung und Formatierung von Zeichen<br />
Abbildung 9.2:<br />
Sprachinfo und extrahierte<br />
Hauptsprache<br />
Die Lokalisierung basiert meist auf der Anwendung der Funktion setlocale, die<br />
das Verhalten anderer Funktionen beeinflusst. Allerdings ist der Umgang damit<br />
recht tückisch. Leider hat sich dies mit PHP5 nicht gebessert, was umso unverständlicher<br />
ist, als dass neuere Programmierumgebungen wie .NET auch unter Windows<br />
den gängigen Standards folgen und es keinen Grund gibt, dies nicht zu adaptieren.<br />
Generell gilt folgende Regel für Linux-Systeme:<br />
Das Basisformat ist »Sprache_Land«, also beispielsweise »de_DE« oder<br />
»de_AT«<br />
Braucht man bestimmte Sonderzeichen einer Sprache, die bei der Ausgabe<br />
angezeigt werden sollen, kann man eine Codeseite angeben: »kr_KR.949«.<br />
Windows-Systeme erwarten statt der Ländercodes Sprachnamen, abgekürzt oder<br />
ausgeschrieben:<br />
Das Basisformat ist »Germany« oder »ge« bzw. »German_Germany« oder<br />
»German_Austria«. Es ist also in den allermeisten Fällen erforderlich, das vom<br />
Browser gesendete Sprachkürzel »de« in die passende Form umzuwandeln.
Die Funktion setlocale anwenden<br />
Mehrsprachige Webseiten<br />
Alle Formate, die printf und verwandte Funktionen benutzen, reagieren auf die<br />
Einstellungen mit setlocale. Damit ist es möglich, die korrekten Formatierungen<br />
für Zahlen zu erhalten, also Komma als Dezimaltrennzeichen in deutschen Texten<br />
usw. Außerdem lassen sich Zeitangaben bei der Ausgabe mit strftime in die<br />
passende Sprache setzen.<br />
Für die Formatierung von Zahlen muss setlocale entweder mit der Konstanten<br />
LC_ALL oder LC_NUMERIC bedacht werden:<br />
setlocale(LC_NUMERIC, "German_Germany");<br />
Die Angabe muss vor der ersten Ausgabe mit einer der printf-Funktionen erfolgen.<br />
Um die Rückstellung der vorherigen Angabe zu ermöglichen, können Sie den<br />
Rückgabewert von setlocale abfragen.<br />
Die folgende Tabelle zeigt alle Konstanten, die setlocale benutzt:<br />
Konstante Bedeutung Betroffene Funktion<br />
LC_ALL Beeinflusst alle Formate printf<br />
LC_COLLATE Beeinflusst Zeichenkettenvergleiche strcoll<br />
LC_CTYPE Klassifizierung von Zeichen strtoupper<br />
LC_MONETARY Betrifft Währungsangaben localeconv<br />
LC_NUMERIC Betrifft Zahlenangaben localeconv<br />
LC_TIME Beeinflusst Datums- und Zeitangaben strftime<br />
Tabelle 9.1: Konstanten, die setlocale benutzt<br />
strcoll entspricht der Funktion strcmp mit dem Unterschied, dass die<br />
Lokalisierung berücksichtig wird. strcmp ignoriert diese.<br />
Die Anwendung der Funktion kann auch auf Bedingungen des Betriebssystems<br />
abgestimmt werden. Wenn der zweite Parameter leer ist (nicht fehlend, sondern<br />
eine leere Zeichenkette), dann wird versucht, eine Umgebungsvariable zu lesen,<br />
deren Namen der Konstanten entspricht. Der folgende Ausdruck liest die Umgebungsvariable<br />
LC_NUMERIC und setzt setlocale entsprechend:<br />
377
378<br />
Professionelle Programmierung<br />
setlocale(LC_NUMERIC, "");<br />
Clever ist es auch, eine Fallbackmöglichkeit zu schaffen. Wie anfangs bereits<br />
beschrieben, sind auf den verschiedenen Betriebssystemen die Lokalisierungszeichenfolgen<br />
zu beachten. Deshalb kann an dieser Stelle auch ein Array angegeben<br />
werden:<br />
setlocale(LC_NUMERIC, array("de", "de_DE", "German_Germany"));<br />
Es gibt natürlich auch die Möglichkeit, hier die Sprache zu wechseln, um unter<br />
allen Umständen ein reproduzierbares Ergebnis zu erzielen.<br />
Das Format der aktuellen Lokalisierung ermitteln<br />
Um festzustellen, wie PHP5 intern arbeitet und wie sich die Parameter der Lokalisierung<br />
auswirken, gibt es die Funktion localeconv. Sie gibt ein Array zurück, das<br />
die entsprechenden Angaben enthält. Die folgende Abbildung zeigt die Ausgabe<br />
für die Lokalisierung Deutsch:<br />
Abbildung 9.3:<br />
Ausgabe der Lokalisierungszeichen<br />
für<br />
Deutsch/Deutschland<br />
(links) und Deutsch/<br />
Schweiz (rechts)<br />
Die Angaben können hilfreich sein, um den Platzbedarf von Zeichenketten zu<br />
berechnen oder die Wirkung der Lokalisierung zu überwachen. Um die Tabellen<br />
zu erstellen, wurde folgendes Skript verwendet:
Listing 9.2: localeconv.php – Lokalisierungsangaben ausgeben<br />
Dynamisch Bilder erzeugen<br />
<br />
<br />
<br />
9.2 Dynamisch Bilder erzeugen<br />
Das dynamische Erzeugen von Bildern gehört mit zu den am häufigsten diskutierten<br />
Möglichkeiten von PHP. Die mit PHP5 verfügbare neue Grafikbibliothek<br />
GD2 stellt neue Funktionen bereit, um professionell wirkende Grafiken dynamisch<br />
zu erstellen.<br />
Prinzip<br />
Um das Prinzip der Erzeugung dynamischer Bilder zu verstehen, muss man sich<br />
noch mal den Ablauf des Seitenaufbaus im Browser in Erinnerung rufen. Nach<br />
dem Laden der HTML-Seite analysiert der Browser den <strong>In</strong>halt und beginnt mit<br />
der Darstellung. Findet er ein Bild, leitet er für die angegebene Bildquelle eine<br />
weitere asynchrone 1 HTTP-Anforderung ein. Der dann zurückgegebene binäre<br />
Datenstrom wird wiederum interpretiert und, ins entsprechende Bildformat<br />
zurückgewandelt, zur Darstellung verwendet. Bei dem Vorgang an sich ist es dem<br />
Browser egal, woher das Bild kommt. Er erwartet letztlich nur einen simplen<br />
Datenstrom. Den kann man erzeugen, indem man auf dem Server ein Bild ablegt<br />
und dem Webserver die Auslieferung überlässt. Es ist aber auch möglich, die<br />
Arbeit von einem Skript erledigen zu lassen, das die Daten fachgerecht generiert.<br />
Als Quelle wird dann einfach ein PHP-Skript genommen:<br />
<br />
1 Deswegen erscheinen die Bilder manchmal erst viel später.<br />
379
380<br />
Professionelle Programmierung<br />
Dem Skript kann man selbstverständlich auch GET-Parameter übergeben, um das<br />
Verhalten zu steuern:<br />
<br />
Aufbau eines bilderzeugenden Skripts<br />
Für ein bilderzeugendes Skript muss man mehrere Dinge beachten:<br />
Es dürfen ausschließlich Bilddaten erzeugt werden. Schon ein einziges Leerzeichen<br />
zerstört das Bild.<br />
Es müssen die passenden Kopfzeilen erzeugt werden, damit der Browser weiß,<br />
welcher Bildtyp zu den Binärdaten passt.<br />
Das Skript sollte relativ schnell und effizient sein, sonst verzögert sich der Bildaufbau<br />
erheblich.<br />
Die passenden Kopfzeilen werden mit der Funktion header erzeugt. Die Ausgaben<br />
selbst können wie üblich mit printf oder echo gesendet werden. Für header wird<br />
die Angabe des <strong>In</strong>haltstyps (Content-type) benötigt. Der konkrete Typ richtet sich<br />
nach der Bildart. Möglich sind folgende Werte:<br />
image/gif<br />
image/jpg<br />
image/png<br />
image/wbmp (für WAP)<br />
Der Aufruf der Funktion sieht dann beispielsweise für ein PNG-Bild folgendermaßen<br />
aus:<br />
header ('Content-type: image/png');<br />
Es ist übrigens gut möglich, zusätzlich noch Cookies an das Bild anzuhängen,<br />
was oft bei Werbebannern gemacht wird. Werbebanner sind<br />
letztlich auch nur Bilder (mal von Flash-Bannern abgesehen) und lassen<br />
sich sehr gut dynamisch erzeugen.<br />
Typische Probleme<br />
Gleich vorab soll auf einige typische Probleme hingewiesen werden. Da der Browser<br />
ein Bild erwatet, wird er eine Fehlerausgabe des Skripts nicht interpretieren
Dynamisch Bilder erzeugen<br />
können. Der <strong>In</strong>ternet Explorer zeigt beispielsweise ein Ersatzbild mit einem roten<br />
Kreuz – aber eben keine Fehlermeldung. Hierfür gibt es mehrere Lösungen:<br />
Erzeugen Sie im Fehlerfall andere Kopfzeilen, die die Ausgabe von Text erlauben.<br />
Geben Sie ein (vorher korrekt programmiertes) Ersatzbild aus, das den Fehler<br />
anzeigt.<br />
Verwenden Sie try/catch und den @-Operator, um Fehler abzufangen und die<br />
Fehlertexte in eine Protokolldatei zu schreiben.<br />
Allen Varianten gemeinsam ist, dass die Fehlerausgabe mehr Aufwand verursacht<br />
als bei normalen Skripten. Der erreichte Effekt einer dynamischen Bildausgabe ist<br />
allerdings oft diesen Mehraufwand wert.<br />
Einführung in die Grafikbibliothek GD2<br />
Vor den ersten Versuchen mit der Grafikbibliothek GD2 sollten Sie testen, ob die<br />
Erweiterung aktiviert wurde und zur Verfügung steht. Der Aufruf von phpinfo ist<br />
der ideale Platz dafür.<br />
Prinzip der Bilderzeugung<br />
Die Bilderzeugung erfolgt in zwei Stufen. Zuerst muss eine Zeichenfläche erstellt<br />
werden, auf der anschließend alle Zeichen- und Textoperationen stattfinden.<br />
Dazu dient die Funktion imagecreate:<br />
$img = imagecreate(480, 80);<br />
Abbildung 9.4:<br />
GD2 wurde erfolgreich<br />
aktiviert<br />
381
382<br />
Professionelle Programmierung<br />
Der Bildtyp, also GIF oder PNG, muss hier noch nicht festgelegt werden. Das Zeichenmodul<br />
arbeitet generell mit einem internen Format, aus dem alle anderen<br />
Formate verlustfrei erstellt werden können.<br />
Der Rückgabewert $img enthält nun ein Handle auf die interne Abbildung des Bildes.<br />
Alle folgenden Funktionen nutzen dieses Handle zum Zugriff.<br />
Sind alle Bildoperationen fertig, wird das Bild erzeugt und an den Browser gesendet.<br />
Dazu dienen die Funktion imagegif, imagepng, imagejpeg usw.:<br />
imagegif($img);<br />
Der Suffix bestimmt dabei den Bildtyp, der sich je nach Applikation unterscheiden<br />
kann.<br />
Alternativ zur Erzeugung eines neuen Bilds kann die Bilddatei auf einem bereits<br />
existierenden Bild basieren. Dazu dienen die Funktionen imagecreatefromgif,<br />
imagecreatefrompng, imagecreatefromjpeg usw.<br />
$img = imagecreatefromgif('pfad/zur/datei.gif');<br />
Die Bildgröße wird dabei zwangsläufig von der Quelldatei bestimmt. Liegt das Bild<br />
vor, kann man nun entweder Text darauf schreiben oder mit Bildfunktionen<br />
malen. Die Vielfalt der Funktionen lässt eine ausführliche Betrachtung in diesem<br />
Rahmen nicht zu, deshalb sollen zwei Beispiele das Prinzip demonstrieren.<br />
Textausgabe<br />
Die Textausgabe ist deshalb ein schwieriges Thema, weil der Text auf der Fläche<br />
platziert werden muss, was nicht einfach ist, wenn die Menge nicht passt. Hier<br />
muss gegebenenfalls gerechnet werden. Ausgangspunkt ist die Funktion<br />
imagettfbbox, die die voraussichtlichen Maße des Textes in Abhängigkeit vom<br />
Font (Schriftart) und der verlangten Größe ermittelt. Die Funktion gibt ein<br />
Array zurück, dessen Elemente die entsprechenden Angaben enthalten:<br />
<strong>In</strong>dex Beschreibung<br />
0 Links oben, X<br />
1 Links unten, Y<br />
2 Rechts unten, X<br />
Tabelle 9.2: Bedeutung der Elemente des mit imagettfbbox erzeugten Arrays
<strong>In</strong>dex Beschreibung<br />
3 Rechts unten, Y<br />
4 Oben rechts, X<br />
5 Oben rechts, Y<br />
6 Oben links, X<br />
7 Oben links, Y<br />
Dynamisch Bilder erzeugen<br />
Tabelle 9.2: Bedeutung der Elemente des mit imagettfbbox erzeugten Arrays (Forts.)<br />
Die Funktion kann einen Winkel einbeziehen, um den der Text gedreht<br />
erscheint. Die Koordinaten, die den Platzbedarf angeben, sind jedoch immer absolut<br />
in den Ecken eines virtuellen Rechtecks, das im Fall einer Drehung entsprechend<br />
groß ist, um den Text aufzunehmen.<br />
Aus den Daten lässt sich nun entnehmen, ob der Text passt und man kann entsprechend<br />
reagieren, um den Text gegebenenfalls passend zu machen. Stimmt alles,<br />
erfolgt die eigentliche Ausgabe mit imagettftext. Benötigt werden folgende Angaben:<br />
Position der linken, oberen Ecke in zwei Werten für x und y.<br />
Winkel, um den der Text gedreht erscheint.<br />
Schriftart, in Form eines Pfades zu einer TrueType-Datei.<br />
Schrifthöhe in Punkt (1 Punkt entspricht 1/72«).<br />
Die Farbe, die benutzt werden soll.<br />
Der Text, der dargestellt wird.<br />
Die Schriftart wird als Pfad zu einer TrueType-Datei angegeben. Achten Sie hier<br />
darauf, dass nur Dateien verwendet werden, zu denen auch die erforderlichen<br />
Rechte vorhanden sind. Das Herauskopieren aus anderen Anwendungen funktioniert<br />
zwar meist, ist aber nur selten legal. Frei Fonts sind im <strong>In</strong>ternet leicht zu finden,<br />
wenngleich diese auch eine schwankende Qualität haben. Wer seinen Server<br />
unter Windows betreibt hat es da leichter, denn er kann die typischen Windows-<br />
Fonts nutzen, beispielsweise Verdana oder Tahoma.<br />
Die Farbe wird als RGB-Wert erwartet, der vorher in der Farbtabelle der Bildbasis<br />
registriert werden muss. Dabei geht die GD2-Bibliothek so vor, dass die erste Farbzuweisung<br />
immer die Hintergrundfarbe festlegt, während alle Folgenden der Farb-<br />
383
384<br />
Professionelle Programmierung<br />
tabelle zugeordnet werden. Jede Zuweisung gibt ein Handle auf die Farbe zurück<br />
und dieses Handle ist zu verwenden. Im folgenden Fragment ist $img das Handle<br />
auf das Bild und $handle_black das Handle auf die Farbe (nach der Ausführung).<br />
Die definierte Farbe ist schwarz:<br />
$handle_black = imagecolorallocate($img, 0, 0, 0);<br />
Die drei Werte stehen für die Farbkanäle Rot, Grün und Blau. Erlaubt sind Angaben<br />
zwischen 0 (kein Anteil der Farbe) und 255 (voller Anteil).<br />
Um hexadezimale Angaben zu verwenden, bietet sich die Angabe von<br />
Hex-Literalen der Art 0xFF an.<br />
Das folgende Beispiel erzeugt einen Text auf einer grauen Grundfläche:<br />
Listing 9.3: imagettftext.php – Text auf einer Bildfläche ausgeben<br />
<br />
Das Skript wendet sehr direkt die zuvor beschriebenen Funktionen an. Es erzeugt<br />
die folgende Ausgabe:<br />
Abbildung 9.5:<br />
Dynamisch<br />
erzeugtes Bild<br />
Soweit das Skript nicht unmittelbar nach der Ausgabe endet, ist es empfehlenswert,<br />
den belegten Speicherplatz sofort freizugeben. Bedenken Sie, dass Bilder<br />
erheblichen Speicherplatz in Anspruch nehmen können. Das sehr einfache Bild<br />
des letzten Beispiels benötigt etwa 115 KByte.
Bildfunktionen<br />
Dynamisch Bilder erzeugen<br />
Ähnlich wie bei Text geht es auch bei den Bildfunktionen. Typische Operationen<br />
umfassen hier das Zeichnen von Rechtecken, Kreisen, Linien oder Punkten. Teil<br />
der Bildfunktionen ist aber auch das Verarbeiten anderer Bilder, das Filtern, Verzerren,<br />
Kopieren, Zerschneiden usw.<br />
Das folgende Beispiel zeichnet olympische Ringe (es ist während der Sommerolympiade<br />
2004 in Athen entstanden):<br />
Listing 9.4: imagerings.php – Dynamisches Zeichnen olympischer Ringe<br />
386<br />
Professionelle Programmierung<br />
{<br />
for ($i = 0; $i < 15; $i++)<br />
{<br />
imagearc($img, $point->x, $point->y, <br />
Circle::RAD+$i, Circle::RAD+$i, <br />
0, 360, $point->color);<br />
}<br />
imagearc($img, $point->x, $point->y, <br />
Circle::RAD+$i, Circle::RAD+$i, <br />
0, 360, $white);<br />
}<br />
header ('Content-type: image/gif');<br />
imagegif($img);<br />
?><br />
Das Skript ist keineswegs perfekt, aber es zeigt das Prinzip und die Möglichkeiten.<br />
Vor allem die korrekte Verschlingung der Ringe wäre weitaus aufwändiger. Die<br />
Anwendung der Klasse Circle demonstriert dennoch, wie die Zeichenfunktionen<br />
von objektorientierten Ansätzen profitieren können. Die Darstellung der Kreise als<br />
Objekte vereinfacht das Skript erheblich und trägt wesentlich zur Lesbarkeit bei.<br />
Abbildung 9.6:<br />
Vereinfachte Version der olympischen Ringe,<br />
mit GD2 erstellt<br />
Wenn Sie das Skript laufen lassen, können sie die farbige Version erleben, die der<br />
Druck nur mangelhaft wiedergeben kann.<br />
Anwendungsbeispiel »Dynamischer Werbebanner«<br />
Das folgende Beispiel zeigt die Anwendung der GD2-Bibliothek anhand einer<br />
Klasse, die dynamisch rechteckige Bereiche erstellt und mit Bildern, Farben und<br />
Texten füllt. Anhand von weiteren Parametern kann man daraus beispielsweise<br />
dynamisch Banner erstellen, deren <strong>In</strong>halt je nach Situation und Benutzer variiert.
Nutzung<br />
Dynamisch Bilder erzeugen<br />
Die Klasse erwartet die Werte als GET-Parameter. Dieser Teil lässt sich freilich<br />
leicht anpassen. Die folgende Tabelle zeigt Namen und Bedeutung der Parameter:<br />
Parameter Bedeutung<br />
W Breite<br />
H Höhe<br />
A Ausrichtung, beispielsweise »left«<br />
C Farbe des Textes<br />
FF Font, der verwendet werden soll<br />
FS Schrifthöhe in Punkt<br />
BG Pfad zu einem Hintergrundbild. Das Bild bestimmt die Größe des Banners,<br />
sodass die Werte H und W ignoriert werden<br />
BC Hintergrundfarbe, wird nur akzeptiert, wenn kein Hintergrundbild angegeben<br />
wird<br />
B Randbreite in Pixel<br />
TXT Der Text, der auf dem Banner angezeigt wird<br />
Tabelle 9.3: Bedeutung der Parameter zur Steuerung der Grafikklasse<br />
Quellcode der Klasse<br />
Die Klasse nutzt konsequent die neuen Möglichkeiten von PHP5 und eignet sich<br />
ideal für den Einbau in eigene Projekte. Ein kleines Testskript folgt am Ende.<br />
Listing 9.5: gd2lib.inc.php – Hilfsklasse zum Erstellen von Bannern<br />
388<br />
Professionelle Programmierung<br />
public function __construct($msg)<br />
{<br />
parent::__construct($msg);<br />
}<br />
}<br />
class LSImage<br />
{<br />
private $intWidth;<br />
private $intHeight;<br />
private $strAlign;<br />
private $strColor;<br />
private $strFontFace;<br />
private $strFontSize;<br />
private $intBorder;<br />
private $strBackColor;<br />
private $strBackImage;<br />
private $strText;<br />
private $strType;<br />
private $im;<br />
private $dx;<br />
private $dy;<br />
const FONTPATH = 'fonts/';<br />
public function __construct($strSetType = 'png')<br />
{<br />
set_error_handler(array($this, 'throwError'));<br />
$this->strType = $strSetType;<br />
$arrURI = $_GET;<br />
if ($arrURI['LSF'] = 'image')<br />
{<br />
if (isset($arrURI['BG']))<br />
{<br />
$this->strBackImage = $arrURI['BG'];<br />
}<br />
else<br />
{<br />
$this->strBackColor= $arrURI['BC'];<br />
}<br />
$this->intWidth = $arrURI['W'];<br />
$this->intHeight = $arrURI['H'];<br />
$this->strAlign = $arrURI['A'];
}<br />
}<br />
Dynamisch Bilder erzeugen<br />
$this->strColor = $arrURI['C'];<br />
$this->strFontFace = self::FONTPATH <br />
. strtolower($arrURI['FF']);<br />
if (substr($this->strFontFace, -4) != '.ttf')<br />
$this->strFontFace .= '.ttf';<br />
if (!file_exists ($this->strFontFace))<br />
$this->strFontFace = self::FONTPATH . 'verdana.ttf';<br />
$this->strFontSize = $arrURI['FS'];<br />
$this->intBorder = $arrURI['B'];<br />
$this->strText=stripslashes(urldecode($arrURI['TXT']));<br />
private function throwError($errno, $err, $line, $file)<br />
{<br />
throw new NoticeException("$errno, $err, $line, $file");<br />
}<br />
private function getcolor($strColor)<br />
{<br />
$r = hexdec(substr($strColor, 0, 2));<br />
$g = hexdec(substr($strColor, 2, 2));<br />
$b = hexdec(substr($strColor, 4, 2));<br />
return imagecolorallocate($this->im, $r, $g, $b);<br />
}<br />
private function CheckBackgroundSize()<br />
{<br />
if ($this->intWidth != imagesx($this->im) <br />
or $this->intHeight != imagesy($this->im))<br />
{<br />
return true;<br />
} else {<br />
return false;<br />
}<br />
}<br />
private function create()<br />
{<br />
$pi = pathinfo($this->strBackImage);<br />
$extension = isset($pi['extension']) ? $pi['extension'] : '';<br />
switch ($extension)<br />
{<br />
389
390<br />
Professionelle Programmierung<br />
case 'jpg':<br />
case 'jpeg':<br />
$this->im = imagecreatefromjpeg($this->strBackImage);<br />
break;<br />
case 'gif':<br />
$this->im = imagecreatefromgif($this->strBackImage);<br />
break;<br />
case 'png':<br />
$this->im = imagecreatefrompng($this->strBackImage);<br />
break;<br />
default:<br />
$this->im = imagecreate($this->intWidth, <br />
$this->intHeight);<br />
}<br />
if ($this->im === FALSE)<br />
{<br />
throw new ImageException('Kein Hintergrund und <br />
keine Bildgröße angegeben');<br />
}<br />
if ($this->CheckBackgroundSize())<br />
{<br />
$bi = imagecreate($this->intWidth, $this->intHeight);<br />
if (function_exists('imagecopyresampled'))<br />
imagecopyresampled($bi, $this->im, 0, 0, 0, 0, <br />
imagesx($bi),<br />
imagesy($bi), <br />
imagesx($this->im), <br />
imagesy($this->im));<br />
else<br />
imagecopyresized($bi, $this->im, 0, 0, 0, 0, <br />
imagesx($bi), <br />
imagesy($bi), <br />
imagesx($this->im), <br />
imagesy($this->im));<br />
$this->im = $bi;<br />
}<br />
$this->getcolor($this->strBackColor);<br />
$arr = imagettfbbox($this->strFontSize, 0, <br />
$this->strFontFace, $this->strText); //<br />
if ($arr === FALSE)<br />
{<br />
throw new ImagingException('TTF wird nicht unterstützt');
Dynamisch Bilder erzeugen<br />
}<br />
else<br />
{<br />
$this->dy = $this->intHeight - <br />
(($this->intHeight / 2) - (abs($arr[7]) / 2));<br />
$this->dy = $this->dy < 0 ? $this->intHeight : $this->dy;<br />
switch (strtolower($this->strAlign))<br />
{<br />
case 'center':<br />
case 'middle':<br />
$this->dx = ($this->intWidth / 2) - ($arr[2] / 2);<br />
break;<br />
case 'right':<br />
$this->dx = $this->intWidth - $arr[2];<br />
break;<br />
case 'left':<br />
default:<br />
$this->dx = 0;<br />
break;<br />
}<br />
}<br />
$this->dx += $this->intBorder;<br />
for ($i = 0; $i < $this->intBorder; $i++)<br />
{<br />
imagerectangle($this->im, $i, $i, <br />
$this->intWidth-$i-1, <br />
$this->intHeight-$i-1, <br />
$this->getColor($this->strBackColor));<br />
}<br />
}<br />
public function image()<br />
{<br />
try<br />
{<br />
$this->create();<br />
header( "Content-type: image/" . $this->strType);<br />
$fg = $this->getcolor($this->strColor);<br />
imagettftext($this->im, (int) $this->strFontSize, 0, <br />
$this->dx, $this->dy, $fg, <br />
$this->strFontFace, $this->strText);<br />
call_user_func('Image' . $this->strType, $this->im);<br />
ImageDestroy($this->im);<br />
391
}<br />
392<br />
}<br />
Professionelle Programmierung<br />
}<br />
catch (ImagingException $ex)<br />
{<br />
echo $ex->getMessage();<br />
}<br />
$objImg = new LSImage;<br />
$objImg->image();<br />
?><br />
Das Skript beginnt mit der Erstellung zweier Fehlerklassen, damit die Fehlerbehandlung<br />
über die neue Ausnahmesteuerung mit try/catch erfolgen kann. Die<br />
erste Klasse soll lediglich Warnungen erfassen:<br />
class NoticeException extends Exception<br />
Auslöser ist die Umleitung der klassischen Laufzeitfehler im Konstruktor:<br />
set_error_handler(array($this, 'throwError'));<br />
Tritt ein Fehler auf, wird die Methode throwError aufgerufen, die ihrerseits den<br />
Fehler in eine Ausnahme verwandelt:<br />
private function throwError($errno, $err, $line, $file)<br />
{<br />
throw new NoticeException("$errno, $err, $line, $file");<br />
}<br />
Das eigentliche Bilderzeugungsprogramm beginnt im Konstruktor mit der Übernahme<br />
der GET-Parameter in lokale Eigenschaften. Danach muss das Hauptprogramm<br />
die Methode image aufrufen, um das Bild zu erstellen. Die Bilderzeugung<br />
ist in einem try-Block untergebracht, um Ausnahmen abfangen zu können. Im<br />
Fehlerfall werden dann andere Header erzeugt, was die Fehlersuche vereinfacht.<br />
Die Ausführung der Bilderzeugung erfolgt in der privaten Methode create:<br />
$this->create();<br />
Gelingt die Bilderzeugung, wird der Text auf dem Bild platziert. Hier könnte man<br />
Cache-Techniken ansetzen, um die mit create erzeugten Daten zu erhalten und<br />
die aufwändige Erstellungsprozedur zu vermeiden.<br />
Die Ausgabe vollzieht sich in mehreren Schritten. Zuerst wird die Vordergrundfarbe<br />
ermittelt:<br />
$fg = $this->getcolor($this->strColor);
Dynamisch Bilder erzeugen<br />
Dann wird anhand der vorausberechneten Größen der Text im verlangten True-<br />
Type-Font geschrieben:<br />
imagettftext($this->im, (int) $this->strFontSize, 0,<br />
$this->dx, $this->dy, $fg,<br />
$this->strFontFace, $this->strText);<br />
Dann wird die Kopfzeile für den Browser erzeugt, die den Bildtyp bestimmt:<br />
header( "Content-type: image/" . $this->strType);<br />
Passend dazu wird der entsprechende Typ erzeugt und gesendet:<br />
call_user_func('Image' . $this->strType, $this->im);<br />
Der Funktionsaufruf »baut« aus dem Wort Image und dem Typ den Funktionsnamen<br />
zusammen, also beispielsweise ImagePng. Anschließend wird das Bild zerstört,<br />
um den belegten Speicher sofort freizugeben:<br />
ImageDestroy($this->im);<br />
Der eigentliche Bildaufbau findet in create statt. Es wird zunächst ermittelt, ob der<br />
Pfad eines Hintergrundbildes vorliegt:<br />
$pi = pathinfo($this->strBackImage);<br />
$extension = isset($pi['extension']) ? $pi['extension'] : '';<br />
Je nach Bildtyp erfolgt dann im switch-Zweig der Aufbau der Zeichenfläche aus<br />
dem Hintergrundbild. Das Hintergrundbild bestimmt bis hierher die Bildgröße:<br />
$this->im = imagecreatefromgif($this->strBackImage);<br />
Nur wenn kein Hintergrundbild existiert, werden die Größen- und Breitenparameter<br />
übernommen:<br />
$this->im = imagecreate($this->intWidth, $this->intHeight);<br />
Alle folgenden Funktionen beziehen sich dann auf das Handle in der Eigenschaft<br />
$this->im. Stimmen Hintergrundbild und verlangte Bildgröße nicht überein, wird<br />
das Hintergrundbild auf die erforderliche Größe skaliert. Dazu wird folgende<br />
Funktion verwendet:<br />
imagecopyresampled($bi, $this->im, 0, 0, 0, 0,<br />
imagesx($bi), imagesy($bi),<br />
imagesx($this->im), imagesy($this->im));<br />
Die Basis für die Platzierung des Textes ist die Berechnung der erforderlichen<br />
Größe:<br />
$arr = imagettfbbox($this->strFontSize, 0, $this->strFontFace,<br />
$this->strText);<br />
393
394<br />
Professionelle Programmierung<br />
Je nach Ausrichtung wird dann der Startpunkt basierend auf den von imagettfbox<br />
zurückgegebenen Werten berechnet. Für zentrierte Texte beispielsweise wird ausgehend<br />
von den Mittelpunkten die Verschiebung nach folgender Formel ermittelt:<br />
$this->dx = ($this->intWidth / 2) - ($arr[2] / 2);<br />
Für den Rand erfolgt die Generierung von Rechtecken, die dem Bild überlagert<br />
werden. Die folgende Funktion erledigt dies auch für Randbreiten > 1, wenn der<br />
Aufruf in einer Schleife erfolgt und mit der Schleifenvariablen $i gesteuert wird:<br />
imagerectangle($this->im, $i, $i,<br />
$this->intWidth-$i-1,<br />
$this->intHeight-$i-1,<br />
$this->getColor($this->strBackColor));<br />
Die Randfarbe wird von der Hintergrundfarbe bestimmt. Der Einsatz ist vor allem<br />
im Zusammenhang mit Hintergrundbildern sinnvoll.<br />
Beispielaufruf<br />
Das folgende Skript enthält ein IMG-Tag, das einen passenden Aufruf enthält:<br />
Listing 9.6: gd2libTest.php – Test der Bibliothek aus einer HTML-Datei heraus<br />
<br />
Beachten Sie hier, dass der Text gegebenenfalls URL-kodiert werden muss. Die<br />
Bibliothek sorgt für die entsprechende Dekodierung.<br />
Abbildung 9.7:<br />
Banner, der mit<br />
der Bibliothek<br />
erzeugt wurde.<br />
Der Text kann als<br />
Parameter ausgetauscht<br />
werden.
9.3 Code röntgen: Die Reflection-API<br />
Code röntgen: Die Reflection-API<br />
PHP5 verfügt über eine Programmierschnittstelle, die die Analyse von Code<br />
erlaubt. Dies ist sinnvoll, um aus anderen Programmen heraus die Eigenschaften<br />
und den Aufbau von Klassen und Bibliotheken zu ermitteln. Hilfreich können<br />
diese Techniken zum einen für die Entwickler von Editoren sein, zum anderen<br />
aber auch für Bibliotheksprogrammierer, die flexibel auf veränderte Bedingungen<br />
reagieren möchten und damit weniger Probleme mit Updates, Versionen und<br />
Varianten haben. Ein dritter und sehr wichtiger Komplex ist die Entwicklung von<br />
automatischen Code-Dokumentierern. Dies sind Programme, die Code analysieren<br />
und daraus Quelltextdokumentationen erstellen.<br />
Die Reflection-API als Objektmodell<br />
Der Zugriff auf die Reflection-<strong>In</strong>formationen erfolgt über ein Objektmodell, das<br />
folgenden Aufbau hat:<br />
<br />
Das Prinzip ist einfach. Um <strong>In</strong>formationen über alle Funktionen eines Skripts<br />
herauszufinden, wird eine <strong>In</strong>stanz der Klasse ReflectionFunction erzeugt und<br />
benutzt. Die Basisklasse Reflection stellt Hilfsfunktionen zur Verfügung, um die<br />
ermittelten Daten auslesen zu können.<br />
Listing 9.7: ReflectionBase.php – Die Struktur der internen Klasse Exception ermitteln<br />
<br />
<br />
<br />
395
396<br />
Professionelle Programmierung<br />
Diese Zeile erzeugt folgende Ausgabe:<br />
Abbildung 9.8:<br />
Ausgabe der Reflection-<br />
Abfrage einer Klasse<br />
Praktisch ist die Ausgabe eine Kopie der Definition, wobei alle theoretisch möglichen<br />
Mitglieder des abgefragten Objekts – hier also einer Klasse – angezeigt wer-
Code röntgen: Die Reflection-API<br />
den. Die gezeigte Klasse hat also keine Konstanten, keine statische Eigenschaften,<br />
keine statische Methoden, sechs normale Eigenschaften und neun Methoden.<br />
Die Reflection-Klassen im Detail<br />
Der folgende Abschnitt zeigt die zur Verfügung stehenden Klassen, deren Definition<br />
und einfache Anwendungsbeispiele.<br />
<strong>In</strong>formationen über Funktionen ermitteln<br />
Mit ReflectionFunction werden Funktionen analysiert. Die folgende Tabelle zeigt<br />
die zur Verfügung stehenden Methoden, die jeweils konkrete <strong>In</strong>formationen über<br />
die Funktion zurückgeben.<br />
Methode Bedeutung<br />
__construct(string name) Konstruktor, erwartet den Namen einer Funktion<br />
string getName() Gibt den Namen der Funktion zurück<br />
bool is<strong>In</strong>ternal() Wahr, wenn dies eine eingebaute Funktion ist<br />
bool isUserDefined() Wahr, wenn dies eine benutzerdefinierte Funktion ist<br />
string getFileName() Den Namen der Datei, wo diese Methode definiert ist<br />
int getStartLine() Den Namen der Zeile, wo die Definition beginnt<br />
int getEndLine() Den Namen der Zeile, wo die Definition endet<br />
string getDocComment() Kommentare, die die Funktion dokumentieren<br />
array getStaticVariables() Array der statischen Variablen<br />
mixed invoke(mixed* args) Führt die Funktion mit bestimmten Argumenten aus<br />
string toString() Zeichenkettendarstellung der Funktion<br />
bool returnsReference() Wahr, wenn der Rückgabewert eine Referenz ist<br />
Reflection_Parameter[]<br />
getParameters()<br />
Die Parameter der Funktion<br />
Tabelle 9.4: Methoden der Klasse ReflectionFunction<br />
397
398<br />
Professionelle Programmierung<br />
Um eine Funktion zu untersuchen, muss natürlich erst ein Objekt der Klasse<br />
erzeugt werden.<br />
<strong>In</strong>formationen über Parameter ermitteln<br />
Da Funktionen oft Parameter haben, besteht ein enger Zusammenhang zwischen<br />
ReflectionFunction und ReflectionParameter. Das folgende Beispiel zeigt beide<br />
Klassen in der Anwendung:<br />
Listing 9.8: ReflectionFunction.php – Analyse einer Funktion mit Parametern<br />
/**<br />
* Eine einfache Zählfunktion<br />
*<br />
* @return int<br />
*/<br />
function counter($debug, $multiplier = 6)<br />
{<br />
static $c = 1;<br />
echo $debug;<br />
return $c++ * $multiplier;<br />
}<br />
$func= new ReflectionFunction('counter');<br />
printf(<br />
"===> Die %s Function'%s'\n".<br />
" deklariert in %s\n".<br />
" von Zeile %d bis %d\n",<br />
$func->is<strong>In</strong>ternal() ? 'interne' : 'benutzerdefinierte',<br />
$func->getName(),<br />
$func->getFileName(),<br />
$func->getStartLine(),<br />
$func->getEndline()<br />
);<br />
printf("---> Dokumentation:\n %s\n",<br />
var_export($func->getDocComment(), 1));<br />
if ($statics = $func->getStaticVariables())<br />
{<br />
printf("---> Statische Variablen: %s\n",<br />
var_export($statics, 1));<br />
}<br />
foreach ($func->getParameters() as $name => $param)
Code röntgen: Die Reflection-API<br />
{<br />
printf("Parameter '%s' %s [%s]\n",<br />
$name,<br />
$param->getName(),<br />
$param->isPassedByReference() ? '&' : '');<br />
}<br />
printf("\n\n---> Aufrufergebnisse: ");<br />
var_dump($func->invoke(array('Test', 2)));<br />
»Reflektiert« wird hier die Funktion counter. Das Ergebnis zeigt alle erforderlichen<br />
Details, um die Funktion zu verstehen:<br />
Vor allem Programme, die Dokumentationen automatisch erstellen, können<br />
davon erheblich profitieren.<br />
Daten einer Klasse ermitteln<br />
Mit Hilfe der Klasse ReflectionClass lassen sich andere Klassen – eingebaute oder<br />
eigene – analysieren. Das folgende Skript versucht diese Analyse möglichst umfassend.<br />
Es verwendet einige der bereitgestellten Methoden:<br />
Listing 9.9: ReflectClass.php – Ermittlung von Daten einer Klasse<br />
interface Serializable<br />
{ // ...<br />
}<br />
class Object<br />
Abbildung 9.9:<br />
Ergebnis der Analyse einer benutzerdefinierten<br />
Funktion<br />
399
400<br />
Professionelle Programmierung<br />
{ // ...<br />
}<br />
class Counter extends Object implements Serializable<br />
{<br />
const START= 0;<br />
private static $c= Counter::START;<br />
public function count()<br />
{<br />
return self::$c++;<br />
}<br />
public static function ReflectMe($config)<br />
{<br />
$class = new Reflection_Class($config);<br />
printf(<br />
"===> Die %s%s%s %s '%s' [extends %s]\n".<br />
" deklariert in %s\n".<br />
" von Zeile %d bis %d\n".<br />
" mit dem Modifikatoren %d [%s]\n",<br />
$class->is<strong>In</strong>ternal() ? 'interne' : 'benutzerdefinierte',<br />
$class->isAbstract() ? ' abstrakte' : '',<br />
$class->isFinal() ? ' finale' : '',<br />
$class->is<strong>In</strong>terface() ? 'Schnittstelle' : 'Klasse',<br />
$class->getName(),<br />
var_export($class->getParentClass(), 1),<br />
$class->getFileName(),<br />
$class->getStartLine(),<br />
$class->getEndline(),<br />
$class->getModifiers(),<br />
implode(' ',<br />
Reflection::getModifierNames($class->getModifiers()))<br />
);<br />
printf("---> Dokumentation:\n %s\n",<br />
var_export($class->getDocComment(), 1));<br />
printf("---> Implementiert:\n %s\n",<br />
var_export($class->get<strong>In</strong>terfaces(), 1));<br />
printf("---> Konstanten: %s\n",<br />
var_export($class->getConstants(), 1));<br />
printf("---> Eigenschaften: %s\n",<br />
var_export($class->getProperties(), 1));<br />
printf("---> Methoden: %s\n",<br />
var_export($class->getMethods(), 1));
}<br />
if ($class->is<strong>In</strong>stantiable())<br />
{<br />
$counter = $class->new<strong>In</strong>stance();<br />
echo '---> $counter ist <strong>In</strong>stanz? ';<br />
var_dump($class->is<strong>In</strong>stance($counter));<br />
echo '---> new Object() ist <strong>In</strong>stanz? ';<br />
var_dump($class->is<strong>In</strong>stance(new Object()));<br />
}<br />
Code röntgen: Die Reflection-API<br />
}<br />
Counter::ReflectMe('Counter');<br />
Die Funktion new<strong>In</strong>stance kann mit einer variablen Anzahl von Argumenten aufgerufen<br />
werden, die durch den Konstruktor der analysierten Klasse bestimmt werden.<br />
Es wird eine Warnung ausgegeben, wenn die <strong>In</strong>stanziierung nicht korrekt<br />
erfolgt.<br />
Folgende Sequenz ermittelt, ob ein Objekt eine <strong>In</strong>stanz einer bestimmten Klasse<br />
ist:<br />
$class= new Reflection_Class('Foo');<br />
$class->is<strong>In</strong>stance($arg)<br />
Diese <strong>In</strong>formation kann auch folgendermaßen gewonnen werden:<br />
if ($arg instanceof Foo)<br />
<strong>In</strong>formation über Methoden einer Klassen ermitteln<br />
Die <strong>In</strong>formationen über eine Klasse enthalten bereits die Namen der Methoden.<br />
Das folgende Beispiel zeigt die Anwendung:<br />
Listing 9.10: ReflectMethod.php – Daten über eine Methode ermitteln<br />
class Counter<br />
{<br />
private static $c= 0;<br />
/**<br />
* Zähler erhöhen<br />
*<br />
* @final<br />
* @static<br />
* @access public<br />
* @return int<br />
401
402<br />
Professionelle Programmierung<br />
*/<br />
final public static function increment()<br />
{<br />
self::$c++;<br />
return self::$c;<br />
}<br />
}<br />
// Create an instance of the Reflection_Method class<br />
$method= new ReflectionMethod('Counter', 'increment');<br />
// Print out basic information<br />
Abbildung 9.10:<br />
<strong>In</strong>formationen über eine Klasse und<br />
deren Mitglieder
Code röntgen: Die Reflection-API<br />
printf(<br />
"===> Die %s%s%s%s%s%s%s Methode '%s' (welche %s ist)\n".<br />
" Deklariert in %s\n".<br />
" von Zeile %d bis %d\n".<br />
" hat die Modifikatoren %d[%s]\n",<br />
$method->is<strong>In</strong>ternal() ? 'interne' : 'benutzerdefinierte',<br />
$method->isAbstract() ? ' abstrakte' : '',<br />
$method->isFinal() ? ' finale' : '',<br />
$method->isPublic() ? ' öffentliche' : '',<br />
$method->isPrivate() ? ' private' : '',<br />
$method->isProtected() ? ' geschützte' : '',<br />
$method->isStatic() ? ' statische' : '',<br />
$method->getName(),<br />
$method->isConstructor() ? 'ein Konstruktor' <br />
: 'eine reguläre Methode',<br />
$method->getFileName(),<br />
$method->getStartLine(),<br />
$method->getEndline(),<br />
$method->getModifiers(),<br />
implode(' ', <br />
Reflection::getModifierNames($method->getModifiers())));<br />
printf("---> Dokumentation:\n %s\n", var_export($method->getDocComment(),<br />
1));<br />
if ($statics= $method->getStaticVariables())<br />
{<br />
printf("---> Statische Variables: %s\n", var_export($statics, 1));<br />
}<br />
printf("---> Aufrufergebnisse: ");<br />
var_dump($method->invoke(NULL));<br />
Das Aufrufen einer privaten oder geschützten (protected) Methode führt auch<br />
über Reflektion zu einem Fehler. Das ist durchaus unverständlich, weil ein derartiger<br />
Schutz vor allem ungewollte Zugriffen verhindert. Der explizite Einsatz einer<br />
Reflektionstechnik benötigt jedoch einen derartigen Schutz nicht. Der Zugriff auf<br />
Methoden verlangt als erstes Argument eine <strong>In</strong>stanz der Klasse. Bei statischen Aufrufen<br />
gibt es diese <strong>In</strong>stanz nicht, deshalb wird hier der Wert NULL angegeben.<br />
Klassen haben neben Methoden meist auch Eigenschaften. Neben der einfachen<br />
Liste, die bereits die Reflektion der Klasse erbrachte, können auch Detaildaten<br />
ermittelt werden. Das folgende Beispiel zeigt, wie mit ReflectionProperty gearbeitet<br />
wird.<br />
403
404<br />
Professionelle Programmierung<br />
Abbildung 9.11:<br />
<strong>In</strong>formationen über eine Methode<br />
Listing 9.11: ReflectMethodProps.php – Eigenschaften einer Klasse<br />
class PropTest<br />
{<br />
public $number = 5;<br />
}<br />
$prop= new ReflectionProperty('PropTest', 'number');<br />
printf(<br />
"===> Die %s%s%s%s Eigenschaft '%s'<br />
(welche %s deklariert wurde)\n".<br />
" hat die folgenden Modifizierer %s\n",<br />
$prop->isPublic() ? ' öffentlich' : '',<br />
$prop->isPrivate() ? ' privat' : '',<br />
$prop->isProtected() ? ' geschüttz' : '',<br />
$prop->isStatic() ? ' statisch' : '',<br />
$prop->getName(),<br />
$prop->isDefault() ? 'zur Kompilierzeit' : 'zur Laufzeit',<br />
var_export(Reflection::getModifierNames( <br />
$prop>getModifiers()),<br />
1)<br />
);<br />
$obj= new PropTest();<br />
print("---> Der Wert ist: ");<br />
var_dump($prop->getValue($obj));<br />
$prop->setValue($obj, 10);<br />
print("---> Setze den Wert 10, der Wert ist jetzt: ");<br />
var_dump($prop->getValue($obj));<br />
var_dump($obj);<br />
Das Aufrufen einer privaten oder geschützten (protected) Methode führt auch<br />
über Reflektion zu einem Fehler.
Daten über PHP-Erweiterungen ermitteln<br />
Code röntgen: Die Reflection-API<br />
Mit ReflectionExtension lassen sich <strong>In</strong>formationen über Erweiterungen ermitteln.<br />
Das ist manchmal hilfreich, um die Kompatibilität eines Skripts zu sichern<br />
oder die Konfiguration eines entfernten Computers, beispielsweise beim Provider,<br />
zu untersuchen.<br />
Zuerst müssen alle vorhandenen Erweiterungen ermittelt werden, was mit dem folgenden<br />
Beispiel demonstriert wird.<br />
Listing 9.12: ReflectGetExtension.php – Eine Erweiterung untersuchen<br />
$aExtensions = get_loaded_extensions();<br />
foreach ($aExtensions as $num => $name)<br />
{<br />
echo "$num. $name ";<br />
}<br />
Hat man einmal die Namen, kann eine weitere Untersuchung mit Reflection-<br />
Extension erfolgen.<br />
Die einzelnen Daten können sehr umfangreich werden; die im folgenden Beispiel<br />
gezeigte umfassende Darstellung einer Erweiterung dient mehr der Demonstration<br />
der Möglichkeiten. <strong>In</strong> der Praxis dürfte die gezielte Abfrage einer bestimmten<br />
Teilinformation sinnvoller sein. Um konkret festzustellen, ob eine Erweiterung<br />
vorhanden ist, wird am besten in_array eingesetzt und dann die Ausgabe gestartet:<br />
Listing 9.13: ReflectGetExtensionDetails.php – Details von PHP-Erweiterungen ermitteln<br />
$aExtensions = get_loaded_extensions();<br />
if (in_array('<strong>SQL</strong>ite', $aExtensions))<br />
{<br />
echo "Erweiterung 'sqlite' existiert";<br />
$ext= new ReflectionExtension('sqlite');<br />
Abbildung 9.12:<br />
Aufruf einer Methode über Reflection<br />
405
}<br />
406<br />
Professionelle Programmierung<br />
Abbildung 9.13:<br />
Liste der geladenen Erweiterungen<br />
einer typischen PHP-<strong>In</strong>stallation<br />
printf(<br />
"Name : %s\n".<br />
"Version : %s\n".<br />
"Funktionen : [%d] %s\n".<br />
"Konstanten : [%d] %s\n".<br />
"INI-Einträge : [%d] %s\n",<br />
$ext->getName(),<br />
$ext->getVersion() ? $ext->getVersion() : 'NO_VERSION',<br />
sizeof($ext->getFunctions()),<br />
var_export($ext->getFunctions(), 1),<br />
sizeof($ext->getConstants()),<br />
var_export($ext->getConstants(), 1),<br />
sizeof($ext->getINIEntries()),<br />
var_export($ext->getINIEntries(), 1)<br />
);<br />
Die Ausgabe zeigt die Klassen der Erweiterung. Mit den bereits vorgestellten Techniken<br />
der Reflektion kann man so schnell alle Details ermitteln und damit Editoren<br />
oder Debugger steuern, Code robuster gestalten oder einfach nur lernen.<br />
Damit stehen alle wichtigen Techniken zur Verfügung, Code per Skript zu analysieren,<br />
zu dokumentieren und fremde Bibliotheken im Kontext korrekt zu verarbeiten.
9.4 Funktions-Referenz GD2<br />
Funktion Bedeutung<br />
Funktions-Referenz GD2<br />
exif_imagetype Ermittelt Daten über ein vorhandenes Bild.<br />
Abbildung 9.<strong>14</strong>:<br />
Ausschnitt aus der<br />
Beschreibung<br />
einer einzigen<br />
Extension<br />
(<strong>SQL</strong>ite)<br />
exif_read_data Liest eingebettet EXIF-Daten aus einer JPG- oder TIF-<br />
Datei. Solche Daten werden beispielsweise von Digitalkameras<br />
erzeugt.<br />
exif_thumbnail Extrahiert in JPG- oder TIF-Dateien eingebettete Thumbnail<br />
* -Dateien.<br />
gd_info Beschafft <strong>In</strong>formationen über die Bibliothek selbst.<br />
Tabelle 9.5: Funktions-Referenz für GD2-Funktionen<br />
* Verkleinertes Abbild der Hauptdatei.<br />
407
408<br />
Professionelle Programmierung<br />
Funktion Bedeutung<br />
getimagesize Ermittelt die Ausmaße einer GIF-, JPEG-, PNG- oder<br />
SWF-Grafik-Datei.<br />
image_type_to_mime_type Ermittelt die MIME-Typen für getimagesize,<br />
exif_read_data, exif_thumbnail, exif_imagetype.<br />
image2wbmp Gibt das Bild im BMP-Format an den Browser aus.<br />
imagealphablending Setzt den Modus für Alpha-Blending.<br />
imageantialias Schaltet das Antialiasing ein oder aus.<br />
imagearc Zeichnet eine Teil-Ellipse.<br />
imagechar Stellt ein Zeichen mit horizontaler Ausrichtung dar.<br />
imagecharup Zeichnet einen vertikal ausgerichteten Charakter.<br />
imagecolorallocate Bestimmt die Farbe einer Grafik.<br />
imagecolorallocatealpha Bestimmt die Farbe unter Verwendung eines Alpha-<br />
Werts.<br />
imagecolorat Ermittelt den Farbwert eines Bildpunktes.<br />
imagecolorclosest Ermittelt den Farbwert-<strong>In</strong>dex, der den angegebenen<br />
Farben am nächsten liegt.<br />
imagecolorclosestalpha Gibt den <strong>In</strong>dex der Farbe in der Farbtabelle, die dem<br />
angegebenen Wert am Nächsten liegt.<br />
imagecolorclosesthwb Gibt den <strong>In</strong>dex der Farbe in der Farbtabelle, die dem<br />
angegebenen Wert am Nächsten liegt. Als Werte werden<br />
Helligkeit, Sättigung und Kontrast verwendet.<br />
imagecolordeallocate Löscht eine Farbdefinition.<br />
imagecolorexact Ermittelt den <strong>In</strong>dex-Wert der angegebenen Farbe.<br />
imagecolorexactalpha <strong>In</strong>dex einer Farbe.<br />
imagecolormatch Versucht die Werte der Farbtabelle in Übereinstimmung<br />
mit den tatsächlichen Farben zu bringen.<br />
Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.)
Funktion Bedeutung<br />
Funktions-Referenz GD2<br />
imagecolorresolve Ermittelt den <strong>In</strong>dex-Wert der angegebenen Farbe oder<br />
die nächstmögliche Alternative dazu.<br />
imagecolorresolvealpha Ermittelt den <strong>In</strong>dex einer Farbe oder – wenn die Farbe<br />
nicht existiert – die bestmögliche Entsprechung.<br />
imagecolorset Setzt die Farbe für den angegebenen Paletten-<strong>In</strong>dex.<br />
imagecolorsforindex Ermittelt die Farbwerte einer angegebenen Farb-Palette.<br />
imagecolorstotal Ermittelt die Anzahl der definierten Farben eines Bildes.<br />
imagecolortransparent Definiert eine Farbe als transparent<br />
imagecopy Kopiert einen Bildausschnitt<br />
imagecopymerge Kopiert einen Bildausschnitt und verbindet mit einem<br />
anderen.<br />
imagecopymergegray Kopiert einen Bildausschnitt und verbindet mit einem<br />
anderen mit Graustufen.<br />
imagecopyresampled Kopiert einen Bildausschnitt und verbindet mit einem<br />
anderen mit erneuter Kompression.<br />
imagecopyresized Kopiert einen Bildausschnitt und verbindet mit einem<br />
anderen mit Größenänderung.<br />
imagecreate Erzeugt ein neues Bild.<br />
imagecreatefromgd2 Erzeugt ein neues Bild aus GD2-Rohdaten.<br />
imagecreatefromgd2part Erzeugt ein neues Bild aus GD2-Rohdaten und Fragmenten<br />
eines Bilds.<br />
imagecreatefromgd Erzeugt ein neues Bild aus GD-Rohdaten.<br />
imagecreatefromgif Erzeugt ein neues Bild aus GIF-Daten.<br />
imagecreatefromjpeg Erzeugt ein neues Bild aus JPG-Daten.<br />
imagecreatefrompng Erzeugt ein neues Bild aus PNG-Daten.<br />
imagecreatefromstring Erzeugt ein neues Bild aus einem Stream.<br />
Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.)<br />
409
410<br />
Professionelle Programmierung<br />
Funktion Bedeutung<br />
imagecreatefromwbmp Erzeugt ein neues Bild aus WBMP-Daten.<br />
imagecreatefromxbm Erzeugt ein neues Bild aus XBM-Daten.<br />
imagecreatefromxpm Erzeugt ein neues Bild aus XPM-Daten.<br />
imagecreatetruecolor Erzeugt ein neues Bild mit voller Farbunterstützung.<br />
imagedashedline Zeichnen einer gestrichelten Linie.<br />
imagedestroy Löscht ein Bild.<br />
imageellipse Zeichnete eine Ellipse oder einen Kreis.<br />
imagefill Füllen mit Farbe (»flood fill«).<br />
imagefilledarc Zeichnet einen gefüllten elliptischen Kreisbogen.<br />
imagefilledellipse Zeichnet eine gefüllte Ellipse.<br />
imagefilledpolygon Zeichnet ein gefülltes Vieleck (Polygon).<br />
imagefilledrectangle Zeichnet ein gefülltes Rechteck.<br />
imagefilltoborder Flächen-Farbfüllung (»flood fill«) mit einer angegebenen<br />
Farbe bis zum nächsten Rand.<br />
imagefontheight Ermittelt die Font-Höhe.<br />
imagefontwidth Ermittelt die Font-Breite.<br />
imageftbbox Ergibt Daten über die Fläche, die ein TrueType-Text in<br />
Anspruch nimmt.<br />
imagefttext Schreibt Text in das Bild.<br />
imagegammacorrect Anwendung einer Gamma-Korrektur auf ein GD-Bild.<br />
imagegd2 Ausgabe im internen GD2-Format.<br />
imagegd Ausgabe im internen GD-Format.<br />
imagegif Ausgabe eines Bildes an den Browser oder in eine Datei .<br />
Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.)
Funktion Bedeutung<br />
Funktions-Referenz GD2<br />
imageinterlace Schaltet die <strong>In</strong>terlaced-Darstellung eines Bildes an oder<br />
aus.<br />
imageistruecolor Ermittelt, ob das Bild Vollfarbunterstützung hat.<br />
imagejpeg Ausgabe des Bildes im Browser oder als Datei als JPG.<br />
imagelayereffect Setzt Alpha-Blending mit Layer-Effekten.<br />
imageline Zeichnen einer Linie.<br />
imageloadfont Lädt einen neuen Font.<br />
imagepalettecopy Kopiert eine Farbpalette.<br />
imagepng Ausgabe des Bildes im Browser oder als Datei als PNG.<br />
imagepolygon Zeichnen eines Vielecks (Polygons).<br />
imagepsbbox Ermittelt die Ausmaße des Rechtecks, das für die Ausgabe<br />
eines Textes unter Verwendung eines PostScript-Fonts<br />
(Typ 1) notwendig ist.<br />
imagepscopyfont Erstellt eine Kopie eines bereits geladenen Fonts für weitere<br />
Veränderungen.<br />
imagepsencodefont Ändert die Vektor-Beschreibung eines Fonts.<br />
imagepsextendfont Vergrößert oder komprimiert einen Font.<br />
imagepsfreefont Gibt den durch einen Typ 1 PostScript-Font belegten<br />
Speicher wieder frei.<br />
imagepsloadfont Lädt einen Typ 1 PostScript-Font aus einer Datei.<br />
imagepsslantfont Setzt einen Font schräg.<br />
imagepstext Ausgabe eines Textes auf einem Bild unter Verwendung<br />
von Typ 1 PostScript-Fonts.<br />
imagerectangle Zeichnet ein Rechteck.<br />
imagerotate Rotiert ein Bild um einen Winkel.<br />
imagesavealpha Speichert Alpha-Kanal-<strong>In</strong>formationen.<br />
Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.)<br />
411
412<br />
Professionelle Programmierung<br />
Funktion Bedeutung<br />
imagesetbrush Setzt ein Bild, das als Füllung für Linien dient.<br />
imagesetpixel Setzt ein einzelnes Pixel.<br />
imagesetstyle Setzt einen Stil für Linien.<br />
imagesetthickness Setzt die Breite für Linien und andere Figuren.<br />
imagesettile Setzt ein kachelbares Bild für Füllungen.<br />
imagestring Zeichnet eine horizontalen Zeichenfolge.<br />
imagestringup Zeichnet eine vertikale Zeichenfolge.<br />
imagesx Ermittelt die Bild-Breite.<br />
imagesy Ermittelt die Bild-Höhe.<br />
imagetruecolortopalette Konvertiert eine Vollfarbpalette in ein Palettenbild.<br />
imagettfbbox Ermittelt die Rahmenmaße für die Ausgabe eines Textes<br />
im TrueType-Format.<br />
imagettftext Erzeugt TTF-Text im Bild.<br />
imagetypes Gibt die von der aktuell verwendeten PHP-Version unterstützten<br />
Grafik-Formate zurück.<br />
imagewbmp Gibt das Bild im WBMP-Format aus.<br />
iptcembed Bettet binäre IPTC-<strong>In</strong>formationen ins Bild ein.<br />
iptcparse Holt binäre IPTC-<strong>In</strong>formationen aus einem Bild ein.<br />
<strong>In</strong>formationen zu IPTC sind unter http://www.iptc.org/ zu<br />
finden.<br />
jpeg2wbmp Konvertiert JPEG nach WBMP.<br />
png2wbmp Konvertiert PNG nach WBMP.<br />
read_exif_data Liest die EXIF Header-<strong>In</strong>fos einer JPEG-Grafik<br />
Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.)
9.5 Kontrollfragen<br />
Kontrollfragen<br />
1. Welche Servervariable wird benutzt, um die bevorzugte Sprache des Benutzers zu<br />
ermitteln?<br />
2. Welcher Header wird in HTTP benötigt, um dem Browser anzuzeigen, dass ein<br />
Bild gesendet wird?<br />
3. Schreiben Sie ein Skript, dass dezimale Farbangaben wie 255, 192, 128 in hexadezimale<br />
Zeichenfolgen der Art »#FFCC99« umrechnet und umgekehrt.<br />
4. Wozu dient das Werkzeug Reflection?<br />
413
Kommunikation per<br />
HTTP, FTP und<br />
E-Mail<br />
10
416<br />
Kommunikation per HTTP, FTP und E-Mail<br />
10.1 Konzepte in PHP5<br />
Bedingt durch die Vielzahl möglicher Datenquellen kommt der Server-Server-<br />
Kommunikation immer stärkere Bedeutung zu. PHP5 stellt eine ganze Reihe von<br />
Methoden zum Zugriff auf andere Server zur Verfügung, einiges davon ist völlig<br />
neu. Damit ergeben sich spannende Möglichkeiten für die Nutzung von PHP5.<br />
Einige Anregungen finden Sie hier:<br />
Holen von News-Daten von einem anderen Server (HTTP)<br />
Senden von Status-<strong>In</strong>formationen an einen anderen Server (HTTP)<br />
Versenden von Formularinhalten per E-Mail (SMTP)<br />
Aktualisierung von <strong>In</strong>halten auf einem Download-Server (FTP)<br />
<strong>In</strong> Klammern finden Sie die jeweils bevorzugten Protokolle.<br />
Prinzip der Streams und Wrapper<br />
PHP5 nutzt ein einheitliches Konzept zur Organisation des Zugriffs auf Daten, so<br />
genannte Wrapper. Dies sind protokollähnliche Bezeichner (im Englischen oft als<br />
Moniker, Spitznamen, bezeichnet):<br />
wrapper://dateiname<br />
Ein Wrapper gilt als Standard: file://, diese Angabe kann entfallen. Das vereinfacht<br />
den Zugriff auf lokalen Dateien, die besonders häufig benötigt werden. Wrapper<br />
finden immer dann Anwendung, wenn Daten von einer Datenquelle geholt oder<br />
in eine Datenquelle geschrieben werden. Das gilt beispielsweise für sämtliche<br />
Dateifunktionen. Statt also für neue Zugriffsarten (per HTTP oder FTP) immer<br />
neue Bibliotheken zu verwenden, bietet PHP5 einen vereinheitlichten Weg des<br />
Zugriffs an. Damit die Verwaltung intern funktioniert, werden ergänzend so<br />
genannte Streams eingesetzt. Diese bietet elementare Zugriffsfunktionen wie Öffnen,<br />
Lesen, Schreiben, Schließen. Wenn man eigene Wrapper entwickeln will –<br />
auch dies ist Teil des universellen Konzepts – programmiert man praktisch diese<br />
Stream-Funktion für einen spezifischen Fall.
Die eingebauten Wrapper<br />
Konzepte in PHP5<br />
PHP5 kommt mit einigen eingebauten Wrappern daher, die für die meisten Zwecke<br />
ausreichend sind. Konkret sind dies:<br />
file:///<br />
Die Angabe file:// kann entfallen, sodass normale Pfadangaben weiterhin nutzbar<br />
sind. Als mögliche Varianten sind relative Pfade (/pfad/name.ext), absolute<br />
Pfade (D:\pfad\name.ext oder /pfad/pfad/datei.ext) oder Netzwerkshares (\\server\freigabe\name)<br />
erlaubt. Wenn die Angabe mit file:// erfolgt und unter Windows<br />
Laufwerkbuchstaben adressiert werden, sind dies mit | abzutrennen.<br />
http://, https://<br />
Um auf andere Webserver zuzugreifen, werden diese Wrapper benutzt. https://<br />
baut eine verschlüsselte Verbindung auf. Der URL wird in der übliche Form<br />
angegeben. Um Name und Kennwort für eine geschützte Verbindung anzugeben,<br />
wird die Form http://:@ benutzt.<br />
ftp://, ftps://<br />
Um auf andere Server per FTP zuzugreifen, werden diese Wrapper benutzt.<br />
ftps:// baut eine verschlüsselte Verbindung auf. Um Name und Kennwort für<br />
eine geschützte Verbindung anzugeben, wird die Form ftp://:@<br />
benutzt.<br />
php://<br />
Um auf die internen Ein- und Ausgabekanäle des Betriebssystems zuzugreifen,<br />
bietet PHP einen speziellen Wrapper an. Zulässige Kanalnamen sind »stdin«<br />
(Eingabe, beispielsweise Tastaturabfragen am Systemprompt), »stdout« (Ausgabe),<br />
»stderr« (Fehlerausgaben), »output« (Ausgabepuffer, der von echo, print<br />
usw. verwendet wird), »input« (Rohdaten aus einem Formular bei POST-<br />
Anforderungen und »filter« (benutzerspezifische oder eingebaute Filter, die<br />
Daten während der Übertragung verändern).<br />
compress.zlib://, compress.bzip2://<br />
Komprimiert oder dekomprimiert die Daten während der Übertragung nach<br />
dem ZIP-Verfahren. Beim Schreiben in eine Datei entstehen GZ-Dateien,<br />
keine Archive.<br />
417
Filter<br />
418<br />
Kommunikation per HTTP, FTP und E-Mail<br />
Filter werden im Stream eingebunden, um – für den Aufrufer transparent – Daten<br />
während der Übertragung zu ändern. Der Vorteil liegt in der Anwendung. Der<br />
Benutzer eines Filters arbeitet weiter wie gewohnt mit Standardfunktionen wie<br />
fopen und fread. Dabei gibt er lediglich an, einen bestimmten Filter benutzen zu<br />
wollen. Wie dieser funktioniert, eingebunden wird und wie dieser konkret mit den<br />
Daten interagiert, bleibt völlig im Hintergrund. Eingebaute Filter können folgende<br />
Aktionen ausführen (zuvor der Name des Filters):<br />
string.rot13<br />
Eine ROT13-Kodierung, bei der die Buchstaben um 13 Stellen im Alphabet<br />
verschoben werden, um eine sehr schwache Verschlüsselung zu erreichen.<br />
string.toupper<br />
Verwandelt alle Zeichen in Großbuchstaben.<br />
string.tolower<br />
Verwandelt alle Zeichen in Kleinbuchstaben.<br />
string.strip_tags<br />
Entfernt alle Tags aus dem Datenstrom.<br />
convert.base64-encode<br />
Konvertiert die Daten in das Base64-Format.<br />
convert.base64-decode<br />
Konvertiert die Base64-Daten aus dem Base64-Format in eine lesbare Form.<br />
convert.quoted-printable-encode<br />
Konvertiert die Daten in das Quoted-Printable-Format (in E-Mails benutzt).<br />
convert.quoted-printable-decode<br />
Konvertiert die Quoted-Printable-Daten aus dem Quoted-Printable-Format in<br />
eine lesbare Form.<br />
Damit die Filter wirklich universell sind, kann man auch bei der Angabe von<br />
php://filter wiederum Pfade angeben, die ihrerseits die Wrapper http:// oder ftp://<br />
verwenden.
10.2 Streams und Wrapper anwenden<br />
Streams und Wrapper anwenden<br />
Die Anwendung der Wrapper ist relativ einfach. Eigentlich müssen Sie lediglich<br />
mit den Dateifunktionen umgehen können, die bereits ausführlich vorgestellt<br />
wurden.<br />
Daten von einer fremden Website beschaffen<br />
Das folgende Skript holt sich Daten von einer fremden Website. Es liest die Daten<br />
ein und ermittelt die <strong>In</strong>halte bestimmter META-Tags, wenn diese vorhanden sind.<br />
Das klingt weitaus aufregender, als es ist:<br />
Listing 10.1: wrapperhttp.php – Zugriff mit Standardfunktionen auf Webserver<br />
<br />
Der eigentliche Aufwand – also das Herstellen der Verbindung – erledigt der Wrapper.<br />
Die Filterung und Ausgabe derw META-Tags erfolgt mit Hilfe der Funktion<br />
get_meta_tags. Die Anwendung unterscheidet sich nicht von der beim Lesen normaler<br />
Dateien. Allerdings kann die Ausführzeit des Skripts etwas länger sein, da<br />
einige Zeit für den Verbindungsaufbau und die Übertragung der Seite benötigt wird.<br />
Abbildung 10.1:<br />
Ausgabe der META-Tags der<br />
Website www.apache.org<br />
Sie sollten außerdem auf eine Fehlerbehandlung achten, da <strong>In</strong>ternet-<br />
Verbindungen aus den verschiedensten Gründen fehlschlagen können.<br />
419
420<br />
Kommunikation per HTTP, FTP und E-Mail<br />
Das folgende Skript liest den <strong>In</strong>halt einer Webseite direkt ein und speichert die<br />
Seite (ohne Bilder oder andere Ressourcen) lokal ab:<br />
Listing 10.2: wrapperhttppage.php – Zugriff mit Dateifunktionen<br />
<br />
Auch hier wird lediglich eine Standardfunktion, file_get_contents, benutzt. Zur<br />
Kontrolle wird noch die Dateigröße angezeigt. Zum Testzeitpunkt waren dies<br />
stolze 12 KByte.<br />
Daten komprimiert speichern<br />
Weitaus kompakter geht es, wenn man die Daten komprimiert speichern kann.<br />
Das folgende Beispiel verwendet einen weiteren Wrapper beim Speichern der<br />
Datei, der das erledigt:<br />
Listing 10.3: wrapperhttpcompress.php – Abspeichern einer Datei in komprimierter<br />
Form<br />
Streams und Wrapper anwenden<br />
Das Ergebnis unterscheidet sich vor allem im Platzverbrauch von der vorherigen<br />
Version. Die Startseite der Apache Group belegt nun nur noch 3,6 KByte. Eine<br />
verbesserte Version liest die Datei wieder aus und zeigt sie zeilenweise an. Dabei<br />
wird wiederum ein Wrapper benutzt, der auf die abgespeicherte Version zugreift.<br />
Listing 10.4: wrappedecompress.php – Komprimierte Datei erzeugen und dekomprimieren<br />
<br />
Hier wird eine weitere Dateifunktion, file, benutzt und diesmal reagiert derselbe<br />
Wrapper in genau umgekehrter Weise. Da die Daten gelesen werden, erwartet er<br />
komprimierte Daten und diese werden folgerichtig wiederhergestellt. Der eigentliche<br />
Vorgang des Komprimierens und Dekomprimierens ist für den Anwender<br />
transparent – man muss weder bei der Benutzung noch bei der Wahl der Funktionen<br />
darauf Rücksicht nehmen.<br />
Damit ist zum Thema Wrapper eigentlich schon fast alles gesagt. Der Fantasie<br />
sind beim Anwenden kaum Grenzen gesetzt. Vor allem aber kann man komplizierte<br />
Zugriffe mit den Socket-Funktionen meist vermeiden.<br />
421
422<br />
Kommunikation per HTTP, FTP und E-Mail<br />
10.3 Filter verwenden<br />
Das folgende Skript holt sich wie bereits beim vorhergehenden Beispiel Daten von<br />
einer fremden Website. Es liest im Gegensatz dazu nur den <strong>In</strong>halt der Seite, also<br />
ohne HTML-Tags. Dies kann beispielsweise für eine <strong>In</strong>dizierung sinnvoll sein.<br />
Anstatt also die Ausgabe mit htmlspecialchars sichtbar zu machen, soll nun ein<br />
Filter die Arbeit erledigen.<br />
Wie bereits angedeutet, basieren Filter auf der Wrapper-Syntax und haben folgende<br />
Struktur:<br />
php://filter:/=<br />
Aus den verfügbaren Filtern kann man nun wählen. string.strip_tags ist dabei von<br />
der Funktion mit strip_tags vergleichbar, spart jedoch den expliziten Aufruf dieser<br />
Funktion.<br />
Listing 10.5: wrapperfilter.php – Filtern von Daten während des Lesevorgangs<br />
}<br />
?><br />
Filter verwenden<br />
$file = "data/$url.gz";<br />
$f = fopen("$file", 'w');<br />
fwrite($f, $page);<br />
fclose($f);<br />
echo 'Dateigröße: ' . filesize($file) . ' Byte';<br />
echo '';<br />
$farray =<br />
file("php://filter/read=string.strip_tags/resource=$file");<br />
foreach($farray as $num => $content)<br />
{<br />
if (strlen(trim($content)) == 0) continue;<br />
printf('%03d: %s', $num, $content);<br />
}<br />
Die einzige Ergänzung zu den vorhergehenden Beispielen besteht in der Angabe<br />
des Filters:<br />
file("php://filter/read=string.strip_tags/resource=$file")<br />
Die Syntax wird transparent, wenn man das zuvor gezeigte Muster zu Rate zieht.<br />
php://filter leitet die Sequenz ein. Danach folgen bis zu drei Aktionen, die entsprechende<br />
Parameter verlangen:<br />
resource=<br />
Diese Angabe ist zwingend erforderlich. Nutzen Sie für die Angabe von<br />
wieder die bereits bekannten Wrapper.<br />
read<br />
Geben Sie an, welche Filteraktion beim Lesen ausgeführt werden soll. Der<br />
Abschnitt »Filter« auf Seite 418 zeigte die möglichen Filter bereits.<br />
write<br />
Geben Sie an, welche Filteraktion beim Schreiben ausgeführt werden soll.<br />
Der Abschnitt »Filter« auf Seite 418 zeigte die möglichen Filter bereits.<br />
Es bietet sich nun an, alle Möglichkeiten miteinander zu kombinieren, was tatsächlich<br />
funktioniert.<br />
Listing 10.6: wrapperfiltercompress.php – Filtern und Komprimieren in einem Schritt<br />
424<br />
Kommunikation per HTTP, FTP und E-Mail<br />
if (strlen($page) > 0)<br />
{<br />
$file = "data/$url.gz";<br />
$f = fopen("compress.zlib://$file", 'w');<br />
fwrite($f, $page);<br />
fclose($f);<br />
echo 'Dateigröße: ' . filesize($file) . ' Byte';<br />
echo '';<br />
$farray = file("php://filter/read=string.strip_tags/<br />
resource=compress.zlib://$file");<br />
foreach($farray as $num => $content)<br />
{<br />
if (strlen(trim($content)) == 0) continue;<br />
printf('%03d: %s', $num, $content);<br />
}<br />
}<br />
?><br />
Der Aufruf des Filters sieht nun folgendermaßen aus:<br />
php://filter/read=string.strip_tags/resource=compress.zlib://$file<br />
Der Wrapper compress.zlib:// wurde in die resource-Aktion eingebaut. Die Ausgabe<br />
zeigt die Wirkung des Filters und zugleich die erzeugte Dateigröße an:<br />
Abbildung 10.3:<br />
Komprimieren und Filtern in einem Schritt<br />
Damit sind die eingebauten Anwendungen erschöpft. <strong>In</strong> der Praxis dürfte das für<br />
die meisten Fälle ausreichen.
10.4 Die Stream-Funktionen<br />
Die Stream-Funktionen<br />
Generell kann man mit den Stream-Funktionen, auf denen Wrapper und Filter<br />
basieren, sehr viel mehr anstellen. Sie sind aber recht komplex in der Anwendung.<br />
Sie bieten einen generellen und vereinheitlichten Weg für den Umgang mit<br />
Datenübertragungen.<br />
Eigene Wrapper und Filter<br />
Es gibt allerdings noch die Möglichkeit, eigene Wrapper und natürlich auch<br />
eigene Filter zu schreiben. Diese tauchen dann unter eigenem Namen auf. Dies<br />
ist jedoch ein deutlich größerer Aufwand und lohnt sich vermutlich nur für Bibliotheksentwickler.<br />
Die Darstellung sprengt auch den Rahmen dieses Buches. Wenn<br />
Sie eigene Versuche in dieser Richtung unternehmen möchten, müssen Sie für<br />
einen eigenen Wrapper eine Klasse schreiben, die auf den Stream-Funktionen aufbaut,<br />
also stream_open zum Öffnen, stream_read zum Lesen und stream_close<br />
zum Schließen, um nur einige zu nennen. Diese Klasse wird dann mit<br />
stream_register_wrapper in Verbindung mit einem eigenen Moniker registriert.<br />
Auch eigene Filter lassen sich entwickeln. Dies funktioniert etwas anders. Hier<br />
müssen Sie eine Klasse erstellen, die von php_user_filter erbt. Dort ist dann die<br />
Methode filter zu schreiben. Mit Hilfe der bereits für Wrapper benutzten Stream-<br />
Funktionen erfolgt dann der Zugriff auf den Datenstrom, der beim Durchlaufen<br />
der filter-Methode verändert werden kann.<br />
Anwendung spezifischer Stream-Funktionen<br />
Zusätzlich zu den vorgestellten Techniken gibt es hier noch den Begriff des<br />
Stream-Kontexts. Dies sind Parametersammlungen, die einer Stream-verarbeitenden<br />
<strong>In</strong>stanz übergeben werden, um das Verhalten global zu beeinflussen.<br />
Beim Zugriff mittels http:// (als Beispiel) werden Sie sicher schnell bemerken, dass<br />
sich bestimmte Seiten nicht ohne weiteres benutzen lassen. Möglicherweise fehlen<br />
einige Kopfzeilen der HTTP-Anforderung, wie beispielsweise die akzeptierte<br />
Sprache.<br />
Die meisten Dateifunktionen erlauben es, einen so genannten Kontext-Parameter<br />
anzugeben.<br />
425
426<br />
Kommunikation per HTTP, FTP und E-Mail<br />
Listing 10.7: streamcontext.php – Parameter für Wrapper<br />
<br />
Das Skript übermittelt an den Wrapper http zwei <strong>In</strong>formationen: Die Methode für<br />
den Abruf ist GET (dies ist auch die Standardmethode) und die akzeptierte<br />
Sprache. Im Beispiel wird die Startseite von Google abgerufen. Nutzt man<br />
www.google.com (nicht »de«), versucht Google die Sprache dem Parameter Acceptlanguage<br />
zu entnehmen und schaltet auf die entsprechende Landesversion um.<br />
Damit lassen sich Webseiten recht komfortabel steuern und nutzen.<br />
Beachten Sie jedoch unbedingt die Urheberrechte und täuschen Sie<br />
niemals fremde <strong>In</strong>halt vor. Vereinbaren Sie besser Kooperationen mit<br />
den Anbietern der Datenquellen und profitieren Sie so von den unbegrenzten<br />
Möglichkeiten des Webs.<br />
10.5 E-Mail versenden<br />
Abbildung 10.4:<br />
Google auf<br />
Deutsch – Statt<br />
per Browser über<br />
einen parametrisierten<br />
Wrapper<br />
aufgerufen<br />
Das Versenden von E-Mail gehört zu den häufiger benötigten Aufgaben. Es dient<br />
auf fast jeder Website dazu, die Nutzerkommunikation zu verbessern. PHP5 unter-
E-Mail versenden<br />
stützt E-Mail auf vielfältige Weise, angefangen von der einfachen mail-Funktion,<br />
über Socket-Programmierung für die Kommunikation per POP3 bis hin zu ganzen<br />
Bibliotheken für IMAP-Postfächer.<br />
Grundlagen<br />
Auch wenn das einfache Versenden von E-Mail verhältnismäßig leicht zu programmieren<br />
ist, sollten Sie einige elementare Grundlagen beherrschen. Dies hilft,<br />
bei größeren Projekten den richtigen Ansatz zu finden und schneller zu brauchbaren<br />
Ergebnissen zu kommen. Bedenken Sie auch, dass eine Server-zu-Server-<br />
Kommunikation immer auch fremde Maschinen beeinflusst. Dem sauberen Protokollfluss<br />
kommt deshalb eine große Bedeutung zu.<br />
Generell werden in der E-Mail-Kommunikation sowohl verschiedene Protokolle<br />
als auch Kodierungsverfahren verwendet. Als Transportprotokolle kommen zum<br />
Einsatz:<br />
SMTP<br />
SMTP (Simple Mail Transfer Protocol) dient der Weiterleitung von E-Mail von<br />
einem Server zu einem anderen. Es dient auch dazu, die erstmalig auf einem<br />
Client erzeugte E-Mail an den ersten Server der Übertragungskette weiterzuleiten.<br />
POP3<br />
POP3 (Post Office Protocol, Version 3) ruft E-Mail von einem Server ab, um<br />
diese auf einem Client dem Benutzer zur Verfügung zu stellen.<br />
IMAP4<br />
IMAP4 (<strong>In</strong>ternet Message Access Protocol, Version 4) ist ein Protokoll zum<br />
Empfang von E-Mail (wie POP3) und zur Verwaltung der E-Mails eines<br />
Clients auf einem Server. Das vollständige Herunterladen, wie bei POP3 erforderlich,<br />
ist nicht notwendig.<br />
Im <strong>In</strong>ternet kommen praktisch nur SMTP und POP3 zum Einsatz. Clients wie<br />
Outlook benutzen beide Protokolle, SMTP zum Senden und POP3 zum Empfangen.<br />
Als Kodierungsverfahren stehen folgende zur Auswahl:<br />
UUEncode<br />
Der Name steht für Unix-to-Unix-Encode, ein Kodierverfahren aus der Unix-<br />
Welt. Das Verfahren ist inzwischen relativ selten anzutreffen, war in der<br />
427
428<br />
Kommunikation per HTTP, FTP und E-Mail<br />
Anfangszeit des <strong>In</strong>ternet jedoch das am weitesten verbreitete. Viele Clients<br />
beherrschen es heute noch.<br />
Vorteile bietet es bei der Kodierung von binären Daten über Transportsysteme,<br />
die nur 7-Bit-Zeichen verarbeiten können. Dateien werden um ca. 40% vergrößert.<br />
PHP5 verfügt über Funktionen zur Kodierung und Dekodierung dieses Verfahrens.<br />
Quoted Printable<br />
Diese Verfahren wird häufiger benutzt. Es lässt den Text praktisch unverändert<br />
und kodiert nur Sonderzeichen, beispielsweise wird das Leerzeichen zu =20.<br />
Das Verfahren eignet sich nur für die Kodierung von Text, nicht für Binärdaten<br />
für Anhänge.<br />
PHP5 verfügt über Funktionen zur Kodierung und Dekodierung dieses Verfahrens.<br />
Base64<br />
Ein anderes Verfahren, dessen Schwerpunkt auf Dateianhängen liegt. Es<br />
kodiert ebenfalls auf 7-Bit und vergrößert die Daten dabei um 37%. Es wird<br />
heute am häufigsten eingesetzt, weil es das Standardverfahren für Anhänge ist,<br />
die nach MIME (siehe unten) verpackt werden.<br />
PHP5 verfügt über Funktionen zur Kodierung und Dekodierung dieses Verfahrens.<br />
BinHex<br />
Ein Verfahren aus der Apple-Welt, das sich außerhalb dieser nicht durchsetzen<br />
konnte. Gute Clients sollten es beherrschen. Das Verfahren komprimiert,<br />
sodass die ursprüngliche Größe trotz der Umsetzung auf 7-Bit oft unterschritten<br />
wird.<br />
PHP5 verfügt nicht über Funktionen zur Kodierung und Dekodierung dieses<br />
Verfahrens.<br />
MIME<br />
Ein globaler Standard (Multipurpose <strong>In</strong>ternet Mail Extensions), der nicht selbst<br />
ein reines Kodierverfahren beschreibt, sondern eine vollständige Anweisung<br />
zum Aufbau kompletter Nachrichten ist. Zur Kodieren von Binärdaten wird<br />
das bereits erwähnt Base64 empfohlen. MIME beschreibt den Aufbau der<br />
E-Mail einschließlich der Art der Verpackung von Text, Anhängen, <strong>In</strong>formationszeilen<br />
(Kopfzeilen) usw.
Vorbereitung<br />
E-Mail versenden<br />
Wenn Sie auf einem Entwicklungssystem die E-Mail-Funktion ausprobieren wollen,<br />
müssen Sie über einen SMTP-Server verfügen. Der SMTP-Server Ihres <strong>In</strong>ternet-Providers<br />
ist vermutlich nicht nutzbar, weil externe Zugriffe eine Autorisierung<br />
verlangen, um den Missbrauch durch SPAM-Versender zu vermeiden. Dies ist mit<br />
zusätzlichem Aufwand verbunden. Meist verlangen die SMTP-Server, dass unmittelbar<br />
zuvor ein autorisierter POP3-Zugriff erfolgt.<br />
Als Lösung können Sie lokal einen eigenen SMTP-Server betreiben. Hier gibt es<br />
mehrere Varianten. Windows 2000 Professional, Server, Windows Server 2003 und<br />
Windows XP Professional werden mit den <strong>In</strong>ternet-<strong>In</strong>formationsdiensten geliefert,<br />
die einen SMTP-Server enthalten. Der Server ist sehr einfach zu konfigurieren<br />
und harmoniert bestens mit PHP. Die Windows 9X/Me- und XP Home-Varianten<br />
sind leider außen vor. Hier muss man freie Programme bemühen, die es zuhauf<br />
gibt. Linux kommt ebenfalls mit dem integrierten Sendmail-Programm. Hier ist<br />
die Konfiguration etwas aufwändiger, außerdem erwartet PHP bei der Nutzung<br />
von Sendmail, dass es selbst auf Linux läuft.<br />
Steht der SMTP-Server, muss PHP noch ein wenig dafür konfiguriert werden.<br />
Wenn Sie einen SMTP-Server für Windows benötigen, weil Sie mit XP<br />
Home oder Windows 9x/Me arbeiten, schauen Sie sich den ArGoSoft<br />
Mail Server an:<br />
http://www.argosoft.com/applications/mailserver/<br />
Läuft er lokal, ist als Name des SMTP-Servers localhost anzugeben.<br />
Konfiguration für Windows<br />
<strong>In</strong> der php.ini sind einige Parameter zu setzen:<br />
SMTP = localhost<br />
smtp_port = 25<br />
Der erste, SMTP, bezeichnet die Adresse des SMTP-Server. Geben Sie den Netzwerknamen,<br />
die IP-Adresse oder einen vollqualifizierten Domänennamen ein. Der<br />
zweite, smtp_port, bezeichnet den Port, an dem der SMTP-Server läuft. 25 ist der<br />
Standardport.<br />
429
430<br />
Kommunikation per HTTP, FTP und E-Mail<br />
Dann ist noch die Absenderadresse zu setzen:<br />
sendmail_from = webmaster@domain.net<br />
Ohne diese Angabe wird die Funktion mail nicht arbeiten.<br />
Konfiguration für Linux<br />
Unter Linux erledigt nicht PHP in Kombination mit SMTP die Arbeit, sondern<br />
alleine Sendmail. Damit entfallen die für Windows erforderlichen Konfigurationsschritte.<br />
Dafür müssen Sie Sendmail konfigurieren, wozu der folgende Parameter<br />
dient:<br />
sendmail_path = -t –i<br />
Welche Parameter hier nötig und möglich sind, ist der Dokumentation zu Sendmail<br />
zu entnehmen. Sie hängen außerdem von der konkreten <strong>In</strong>stallation ab.<br />
Praktische Umsetzung<br />
Das Versenden von E-Mail mit PHP ist sehr einfach. Voraussetzung ist allerdings<br />
eine <strong>In</strong>stallation, in der PHP einen SMTP-Server erreichen kann, wie zuvor<br />
beschrieben. Wird beim Provider gehostet, ist dies in der Regel gewährleistet.<br />
Einfache E-Mails versenden<br />
Eine einfache E-Mail zu versenden basiert auf nur einer einzigen Funktion: mail.<br />
Die Funktion kennt mehrere Parameter:<br />
to<br />
Eine Zeichenkette, die die Mail-Adresse angibt, an die gesendet werden soll.<br />
Sie können mehrere Adressen angeben und durch Kommata trennen. Beachten<br />
Sie aber, dass die Empfänger dann alle Adressen sehen können. Besser ist<br />
es, Blind Carbon Copy zu verwenden (siehe unten).<br />
subject<br />
Eine Zeichenkette, die als Betreff benutzt wird.<br />
message<br />
Eine Zeichenkette, die die eigentliche Nachricht darstellt.
E-Mail versenden<br />
header<br />
Zusätzliche benutzerdefinierte Kopfzeilen, die das Verhalten der E-Mail<br />
beeinflussen. Hier können Angaben zur Priorität, Cc (Carbon Copy), Bcc<br />
(Blind Carbon Copy) usw. eingetragen werden. Die Kopfzeilen müssen im<br />
Rohformat geschrieben werden. Die Angabe des Parameters ist optional.<br />
parameters<br />
Diese Option ist nur anwendbar, wenn PHP auf Linux läuft und zum Versenden<br />
Sendmail. Geben Sie dann Parameter für Sendmail an. Es ist möglich, die<br />
Verwendung dieser Option in der Datei php.ini zu sperren. Deshalb kann der<br />
Einsatz bei einem Massenhoster nicht garantiert werden.<br />
Die Funktion gibt einen Booleschen Wert zurück, der den Erfolg repräsentiert.<br />
Außerdem wird eine Fehlermeldung angezeigt, wenn etwas schief läuft. Da Fehlermeldungen<br />
meist stören und die E-Mail-Übertragung in der Praxis durchaus<br />
ihre Tücken hat, ist ein Abfangen und Auswerten der Fehler unerlässlich. Das folgende<br />
Skript zeigt, wie das aussieht:<br />
Listing 10.8: sendmail.php – E-Mail versenden und auf mögliche Fehler reagieren<br />
432<br />
Kommunikation per HTTP, FTP und E-Mail<br />
}<br />
?><br />
Folgendes ist zu beachten. Wenn Sie mit Windows arbeiten und den Windows-<br />
SMTP-Server verwenden, muss die Angabe »From:« in der Datei php.ini eingestellt<br />
werden. Um dies zu vereinfachen, wird der Parameter im Skript dynamisch<br />
gesetzt:<br />
ini_set('sendmail_from', 'test@test.net');<br />
Außerdem wird das Abfangen des Fehlers programmiert. Dazu ist zuerst die Fehlerverfolgung<br />
in PHP5 einzuschalten:<br />
ini_set('track_errors', 1);<br />
Beim Senden wird der mail-Funktion ein @ zum Unterdrücken des Fehlers vorangestellt:<br />
$success = @mail($to, $subject, $message, $from);<br />
Die Variable $success enthält nun im Fehlerfall FALSE. Darauf kann entsprechend<br />
reagiert werden. Trat ein Fehler auf, wird die interne Fehlervariable<br />
$php_errormsg benutzt:<br />
echo $php_errormsg;<br />
Programmierung mit Kopfzeilen<br />
Abbildung 10.5:<br />
Fehlerausgabe des Programms:<br />
Hier war der<br />
Mail-Server falsch konfiguriert<br />
Um weitere Verwendungsmöglichkeiten zu erkunden, müssen Sie sich mit den<br />
Kopfzeilen beschäftigen. Diese werden als vierter Parameter in der folgenden<br />
Form angegeben:<br />
: \r\n<br />
Dabei ist durch den Namen, beispielsweise »From« zu ersetzen und<br />
durch die von dieser Kopfzeile verlangten Parameter:<br />
From: test@absender.de\r\n<br />
\r\n bezeichnet einen Zeilenumbruch, der in der E-Mail zum Abtrennen der<br />
Kopfzeilen benötigt wird.
E-Mail versenden<br />
Die Zeilenendezeichen \r\n werden unter Windows verwendet. Wenn<br />
der SMTP-Server unter Unix läuft, ist meist \n ausreichend, manchmal<br />
erforderlich. Probieren Sie Ihr Skript sorgfältig aus, bevor Sie es ausliefern.<br />
Die folgende Tabelle zeigt einige typische Kopfzeilen, die Sie verwenden können:<br />
Name Parameterbeispiel Bedeutung<br />
To Empfänger, kann auch eine Liste sein<br />
From test@test.de Absender. Die Angabe in spitzen<br />
Klammern ist optional. Die Klammern<br />
selbst müssen hier geschrieben werden,<br />
wenn die Angabe erfolgt.<br />
Cc test@test.de, weiter@weiter.de Kopie an. Liste von Empfängern, die<br />
die E-Mail auch erhalten und die<br />
sichtbar ist (Cc = Carbon copy).<br />
Trennung durch Kommata.<br />
Bcc test@test.de, weiter@weiter.de Kopie an. Liste von Empfängern, die<br />
die E-Mail auch erhalten und die<br />
nicht sichtbar ist (Bcc = Blind carbon<br />
copy). Trennung durch Kommata.<br />
Date Mon, 16 Aug 2004 11:29:28 +02 Datum, das beim Empfänger als das<br />
Absendedatum angezeigt wird. Nutzen<br />
Sie am einfachsten date('r').<br />
Reply-To Gewünschte Antwortadresse. Fehlt die<br />
Angabe, wir »To« verwendet.<br />
X-Mailer PHP-Mail Version 5 Ein Hinweis, welches Programm zum<br />
Versenden benutzt wurde.<br />
X-Priority<br />
X-MSMail-<br />
Priority<br />
MIME-<br />
Version<br />
1<br />
High<br />
Priorität (niedrig, hoch, …) in der allgemeinen<br />
Variante und in einer für<br />
Outlook/Outlook Express<br />
1.0 Version der MIME-Kodierung<br />
Tabelle 10.1: Namen und Parameter typischer Kopfzeilen für E-Mails<br />
433
434<br />
Kommunikation per HTTP, FTP und E-Mail<br />
Name Parameterbeispiel Bedeutung<br />
Content-type multipart/alternative <strong>In</strong>haltstyp nach MIME-Kriterien<br />
Content-<br />
Transfer-<br />
Encoding<br />
Content-<br />
Disposition<br />
Dies ist nur eine kleine Auswahl der Möglichkeiten. Sie reicht aber für die häufigsten<br />
Fälle aus:<br />
Versenden von HTML-Mails<br />
Versenden von Anhängen<br />
Anhänge versenden<br />
base64 Kodierungsverfahren für Anhänge<br />
attachment;\n\tfilename=anhang.txt<br />
Anhänge und HTML werden in ähnlicher Weise versendet. Die Kodierung folgt<br />
dem MIME-Standard. Dabei wird der <strong>In</strong>halt der Nachricht in mehrere Blöcke aufgeteilt,<br />
die jeweils eigene Kopfzeilen haben und zusätzlich durch spezielle Grenzen<br />
voneinander getrennt sind. Die Kopfzeilen helfen dem Client, die Nachricht<br />
wieder zusammenzubauen. Binäre Daten, wie beispielsweise Bilder, werden meist<br />
Base64-kodiert, damit sie auch über 7-Bit-Übertragungssysteme geliefert werden<br />
können. Beachten Sie, dass sich Nachrichten dabei um bis zu 30% vergrößern<br />
können.<br />
Zuerst ein Beispiel: eine Klasse, die Anhänge versenden kann:<br />
Listing 10.9: sendattachments.php – Anhänge zusammen mit E-Mail versenden<br />
}<br />
E-Mail versenden<br />
$types = array(<br />
".jpg" => "image/jpeg",<br />
".jpeg" => "image/jpeg",<br />
".png" => "image/png" );<br />
$filename = basename($filename);<br />
$ext = strtolower(substr($filename, strrpos($filename, ".")));<br />
return $types[$ext];<br />
public function sendMail($to, $from, $headers, $subject,<br />
$message, $attachments="")<br />
{<br />
$bound = uniqid(time()."_");<br />
$header = "From: $from\r\n";<br />
$content = '';<br />
foreach($headers as $head => $value)<br />
$header .= "$head: $value\r\n";<br />
if(is_array($attachments))<br />
{<br />
$header .= "MIME-Version: 1.0\r\n";<br />
$header .= "Content-Type: multipart/mixed;<br />
boundary=\"$bound\"\r\n";<br />
$content .= "--$bound\r\n";<br />
$content .= "Content-Type: text/html;<br />
charset=\"iso-8859-1\"\r\n";<br />
$content .= "Content-Transfer-Encoding:<br />
8bit\r\n\r\n$message\r\n\r\n";<br />
foreach($attachments as $attch)<br />
{<br />
if($fp = fopen($attch, "rb"))<br />
{<br />
$bn = basename($attch);<br />
$content .= "--$bound\r\n" ;<br />
$content .= "Content-Type: ".$this->getMime($attch).";<br />
name=\"$bn\"\r\n" ;<br />
$content .= "Content-Transfer-Encoding: base64\r\n";<br />
$content .= "Content-Disposition: inline;<br />
filename=\"$bn\"\r\n\r\n" ;<br />
$content .= chunk_split(base64_encode(fread($fp,<br />
filesize($attch))))."\r\n";<br />
435
436<br />
}<br />
Kommunikation per HTTP, FTP und E-Mail<br />
fclose($fp);<br />
}<br />
else<br />
{<br />
throw new MailException("Fehler, kann Anhang $attch<br />
nicht öffnen.");<br />
}<br />
}<br />
$content .= "--$bound--\r\n";<br />
}<br />
else<br />
{<br />
$content .= "Content-Type: text/html;<br />
charset=\"iso-8859-1\"\r\n";<br />
$content .= "Content-Transfer-Encoding:<br />
8bit\r\n\r\n$message\r\n\r\n";<br />
}<br />
ini_set('track_errors', 1);<br />
if (!@mail($to, $subject, $content, $header))<br />
{<br />
throw new MailException($php_errormsg);<br />
}<br />
}<br />
$sm = new SendHtmlMail();<br />
$to = 'joerg@krause.net';<br />
$from = 'joerg@krause.net';<br />
$headers = array('X-Mailer:' => 'PHP '.phpversion());<br />
$msg = 'Test für Anhänge';<br />
$subject = 'Test Nr. 5';<br />
$attachment = array('data/Telefon.jpg', 'data/7650Nokia.jpg');<br />
try<br />
{<br />
$sm->sendMail($to, $from, $headers, $subject, $msg, $attachment);<br />
echo "An: $to, Von: $fromKopfzeilen: ";<br />
print_r($headers);<br />
}<br />
catch (MailException $ex)<br />
{<br />
die($ex->getMessage());<br />
}<br />
?>
E-Mail versenden<br />
Das Skript verwendet die neuen objektorientierten Möglichkeiten in PHP5. So<br />
wird eine eigene Ausnahmenklasse MailException definiert, um Ausnahmen<br />
gezielt abfangen zu können:<br />
class MailException extends Exception<br />
Die Klasse ist noch recht einfach. Als Anhänge sind nur Bilder zulässig, deren<br />
MIME-Typ die private Methode getMime ermittelt:<br />
private function getMime($filename)<br />
Für JPEG-Bilder ist der MIME-Typ beispielsweise »image/jpeg«. Der MIME-<br />
Standard verlangt die Vereinbarung einer eindeutigen Zeichenkette zur Trennung<br />
von Datenblöcken. Eine sichere Methode ist die Nutzung der folgenden Funktionen:<br />
$bound = uniqid(time()."_");<br />
Daraus entstehen Zeichenfolgen wie beispielsweise »1092655372_4120990c6010f«.<br />
Nach der Generierung der Kopfzeilen, die hier bequemerweise als Array übergeben<br />
werden, sind die Anhänge als Nachrichtenblöcke aufbereitet dem Text der<br />
E-Mail anzufügen. Zuerst wird dabei geprüft, ob Anhänge vorliegen. Die Methode<br />
erwartet diese als ein Array aus Dateinamen:<br />
if(is_array($attachments))<br />
Ist das der Fall, soll MIME verwendet werden. Diese Angabe gehört in den Header:<br />
$header .= "MIME-Version: 1.0\r\n";<br />
Dazu muss mitgeteilt werden, dass mehrere Blöcke verschiedenen <strong>In</strong>halts (mixed)<br />
folgen:<br />
$header .= "Content-Type: multipart/mixed; boundary=\"$bound\"\r\n";<br />
<strong>In</strong> dieser Zeile wird auch die Blockgrenze mit Hilfe des Parameters boundary festgelegt.<br />
Der <strong>In</strong>halt beginnt nun mit einem solchen Abgrenzungszeichen. Die beiden<br />
Minuszeichen leiten die Grenze ein und sind zwingend erforderlich:<br />
$content .= "--$bound\r\n" ;<br />
Vor die Anhänge wird der Text der Nachricht gestellt. Die Klasse ist in der Lage,<br />
auch gleich HTML-E-Mails zu versenden:<br />
$content .= "Content-Type: text/html; charset=\"iso-8859-1\"\r\n";<br />
437
438<br />
Kommunikation per HTTP, FTP und E-Mail<br />
Wenn Sie Text versenden möchten, nutzen Sie den MIME-Typ »text/text« anstatt<br />
»text/html«. Der Zeichensatz erlaubt die Verwendung des üblichen westeuropäischen<br />
Standardzeichensatzes für Windows, sodass auch Umlaute übertragen werden.<br />
Dann folgt die Kodierung und die Nachricht selbst:<br />
$content .= "Content-Transfer-Encoding: 8bit\r\n\r\n<br />
$message\r\n\r\n";<br />
Hier wird nichts zusätzlich kodiert, da es sich nur um ASCII handelt. Das Übertragungsmedium<br />
wird aber angewiesen, nach Möglichkeit mit 8 Bit zu übertragen,<br />
damit die erweiterten Zeichen des Zeichensatzes erhalten bleiben.<br />
Nun beginnt der Zusammenbau der Anhänge. Jeder Anhang wird einzeln erstellt.<br />
foreach($attachments as $attch)<br />
Zuerst wird die Datei im Binärmodus geöffnet:<br />
if($fp = fopen($attch, "rb"))<br />
Dann wird der Basisname ermittelt. Diese Angabe wird übertragen, während der<br />
lokale Pfad, aus dem die Dateien gelesen werden, dem Empfänger nicht mitgeteilt<br />
wird:<br />
$bn = basename($attch)<br />
Der Anhang wird dann wieder mit einem Grenzzeichen eingeleitet:<br />
$content .= "--$bound\r\n" ;<br />
Es folgen drei Kopfzeilen, die den Anhang beschreiben. Sie sehen nach der Verarbeitung<br />
folgendermaßen aus:<br />
Content-Type: image/jpg; name="telefon.jpg"<br />
Content-Transfer-Encoding: base64<br />
Content-Disposition: inline; filename="telefon.jpg";<br />
Nun muss der Anhang noch gemäß den MIME-Kriterien zerlegt und kodiert werden.<br />
Die folgenden Funktionen erledigen dies:<br />
chunk_split(base64_encode(fread($fp, filesize($attch))))<br />
Lesen Sie den Ablauf von innen nach außen:<br />
1. fread liest die Datei komplett, die Dateigröße wird dabei mit filesize ermittelt
E-Mail versenden<br />
2. base64_encode kodiert die Datei mit dem Base64-Verfahren (wie in der Kopfzeile<br />
gefordert)<br />
3. chunk_split zerlegt den Datenstrom in Blöcke, die verhindern, dass Mail-Server<br />
störende Leerzeilen oder Umbrüche einfügen<br />
Am Ende aller Anhänge wird eine weitere Grenze eingefügt, die mit zwei Minuszeichen<br />
abschließt und so dem Mailclient mitteilt, dass keine weiteren Anhänge<br />
folgen:<br />
$content .= "--$bound--\r\n";<br />
Der Rest ist trivial. Die E-Mail wird versendet und der Erfolg mit einer if-Anweisung<br />
überwacht:<br />
if (!@mail($to, $subject, $content, $header))<br />
Trat ein Fehler auf, wird eine Ausnahme ausgelöst:<br />
throw new MailException($php_errormsg);<br />
Das Hauptprogramm definiert die nötigen Angaben zum Betrieb der Klasse. Die<br />
einzige öffentliche Methode sendMail macht die Verwendung extrem einfach.<br />
Abbildung 10.6:<br />
E-Mail mit zwei<br />
Anhängen in<br />
einem E-Mail-<br />
Client, mit der<br />
Klasse SendHtml-<br />
Mail versendet<br />
Um HTML zu versenden, muss der Nachrichtenkörper lediglich eine gültige<br />
HTML-Seite enthalten, also wenigstens mit den Tags umschlossen<br />
sein.<br />
Abbildung 10.7:<br />
HTML-Mail, mit<br />
demselben Programm<br />
versendet<br />
439
Andere Lösungen<br />
440<br />
Kommunikation per HTTP, FTP und E-Mail<br />
Das Versenden von HTML oder Anhängen wird von Hunderten im <strong>In</strong>ternet kursierenden<br />
Klassen und Funktionssammlungen unterstützt. Die vorliegende Variante<br />
ist insofern dennoch interessant, weil sie ausreichend kompakt ist, um<br />
komplett verstanden zu werden. Außerdem nutzt sie die neuen Funktionen in<br />
PHP5, was nahezu alle anderen Lösungen nicht tun. Sie ist außerdem hinreichend<br />
alltagstauglich und leicht erweiterbar, wenn es erforderlich sein sollte.<br />
10.6 Funktions-Referenz Stream-Funktionen<br />
Funktion Bedeutung<br />
stream_context_create Erzeugt ein Kontext-Handle, das andere Funktionen<br />
nutzen, um auf so konfigurierte Streams zurückzugreifen.<br />
Der Kontext-Parameter ist neu in vielen Dateifunktionen<br />
in PHP5 hinzugekommen.<br />
stream_context_get_default Ermittelt den aktuellen Kontext eines Streams.<br />
stream_context_get_options Ermittelt gesetzte Optionen für Kontexte, Streams oder<br />
Wrapper.<br />
stream_context_set_option Setzt Optionen für Kontexte, Streams oder Wrapper.<br />
stream_context_set_params Setzte Parameter für Kontexte, Streams oder Wrapper.<br />
stream_copy_to_stream Kopiert Daten von einem Stream zu einem anderen.<br />
stream_filter_append Fügt einem Stream ein Filter hinzu. Die Filteraktion<br />
wird nach dem Übertragen der Daten ausgeführt.<br />
stream_filter_prepend Fügt ein Filter vor einem Stream an. Die Filteraktion<br />
wird vor dem Übertragen der Daten ausgeführt.<br />
stream_filter_register Registriert einen benutzerspezifischen Filter. Das Filter<br />
kann durch eine Klasse implementiert werden, die von<br />
php_user_filter abgeleitet wird.<br />
Tabelle 10.2: Funktions-Referenz für Stream-Funktionen
Funktion Bedeutung<br />
Funktions-Referenz Stream-Funktionen<br />
stream_get_contents Liest ausstehende Daten eines Streams in eine Zeichenkette.<br />
stream_get_filters Ermittelt eine Liste von registrierten Filtern.<br />
stream_get_line Liest eine Zeile aus einem Stream bis zu einem vereinbarten<br />
Trennzeichen.<br />
stream_get_meta_data Ermittelt Kopfzeilen und Metadaten aus einem Stream.<br />
stream_get_transports Ermittelt eine Liste von Sockets, die Transportaufgaben<br />
auf niedriger Ebene übernehmen.<br />
stream_get_wrappers Ermittelt eine Liste registrierter Streams.<br />
stream_register_wrapper Alias für stream_wrapper_register.<br />
stream_select Äquivalent für den select-Systemaufruf.<br />
stream_set_blocking Setzt blocking/non-blocking-Mode für einen Stream.<br />
stream_set_timeout Setzt die Abbruchzeit für einen Stream.<br />
stream_set_write_buffer Setzt Dateipufferung für einen Stream.<br />
stream_socket_accept Akzeptiert eine Socket-Verbindung für den Stream. Die<br />
Verbindung muss mit stream_socket_server erstellt<br />
worden sein.<br />
stream_socket_client Eröffnet eine Socket-Verbindung ins <strong>In</strong>ternet oder zu<br />
einer Unix-Domain.<br />
stream_socket_enable_crypto Schaltet die Verschlüsselung für eine bestehende Verbindung<br />
ein oder aus.<br />
stream_socket_get_name Ermittelt den Namen eines lokalen oder remoten<br />
Sockets.<br />
stream_socket_recvfrom Empfängt Daten von einem Socket, unabhängig davon,<br />
ob die Verbindung noch besteht oder nicht.<br />
stream_socket_sendto Sendet eine Nachricht an einen Socket, unabhängig<br />
davon, ob die Verbindung noch besteht oder nicht.<br />
Tabelle 10.2: Funktions-Referenz für Stream-Funktionen (Forts.)<br />
441
442<br />
Kommunikation per HTTP, FTP und E-Mail<br />
Funktion Bedeutung<br />
stream_socket_server Erstellt eine Socket-Verbindung ins <strong>In</strong>ternet oder zu<br />
einer Unix-Domain.<br />
stream_wrapper_register Registriert einen benutzerspezifischen Wrapper, der als<br />
PHP-Klasse implementiert sein muss.<br />
Tabelle 10.2: Funktions-Referenz für Stream-Funktionen (Forts.)<br />
10.7 Kontrollfragen<br />
1. Welche Aufgabe erfüllen Wrapper?<br />
2. Was muss ein fremder Entwickler beachten, wenn er einen von Ihnen entwickeltes<br />
Filter verwendet?<br />
3. Was bedeuten die Kopfzeilen Cc: und Bcc: in einer E-Mail?<br />
4. Welches Protokoll nutzt die mail-Funktion zur Übertragung der Daten?
Datenbankprogrammierung<br />
11
444<br />
Datenbankprogrammierung<br />
11.1 Prinzip der Datenbankprogrammierung<br />
Die Datenbankprogrammierung ist für fast alle Webseiten von elementarer Bedeutung.<br />
Es geht dabei immer wieder darum, anfallende Daten zustands- und seitenunabhängig<br />
zu erfassen und wieder bereitzustellen. Der Datenbankserver arbeitet<br />
– auch wenn das entsprechende Programm auf derselben Maschine wie der Webserver<br />
läuft – unabhängig von PHP oder Apache.<br />
Um eine Datenbank zu nutzen, sind deshalb mehrere Schritte erforderlich, die<br />
den Einstieg nicht einfach machen (wenn man es kann, mag dies primitiv klingen):<br />
Aufsetzen oder Beschaffen eines Datenbankservers<br />
Aktivierung eines Client-Moduls in PHP, das mit dem Server kommunizieren<br />
kann<br />
Herstellen einer Verbindung zum Datenbankserver am Beginn des Skripts,<br />
meist unter Angabe der IP-Adresse oder des Netzwerknamens<br />
Herstellen einer Verbindung zu einer konkreten Datenbank, die der Datenbankserver<br />
verwaltet<br />
Aufbau einer Abfrageverbindung, um <strong>SQL</strong>-Kommandos an den Datenbankserver<br />
zu senden<br />
Auswerten der Antwort und Aufbereitung der Daten, um diese für die Ausgabe<br />
oder für weitere Abfragen verwenden zu können<br />
<strong>In</strong>sgesamt ist also einiger Aufwand zu treiben. Dafür hat man ein System, das mit<br />
drei Datensätzen ebenso gut zurechtkommt wie mit 3 Millionen. Außerdem lassen<br />
sich auch komplexe Daten mit vielfältigen Verknüpfungen verwalten.<br />
Einiger Lernaufwand ist dennoch nötig. Während Sie das Aufsetzen und Verwalten<br />
des Datenbankservers an den Provider oder einen Administrator abschieben<br />
können, bleibt die Last der Programmierung allein beim Entwickler der Website.<br />
Unerlässlich sind deshalb <strong>SQL</strong>-Kenntnisse.<br />
11.2 Die universelle Abfragesprache <strong>SQL</strong><br />
Dieser Abschnitt führt ganz allgemein in <strong>SQL</strong> ein. Dabei wird, soweit nichts anderes<br />
explizit erwähnt wird, sowohl <strong>My</strong><strong>SQL</strong> als auch <strong>SQL</strong>ite unterstützt. Das heißt,
Die universelle Abfragesprache <strong>SQL</strong><br />
die entsprechende Abfragen funktionieren in beiden Fällen. Auf die Besonderheiten<br />
der beiden Systeme wird in eigenen Abschnitten eingegangen.<br />
Wenn Sie <strong>SQL</strong> bereits kennen, nutzen Sie diesen Abschnitt zum Auffrischen und<br />
Wiederholen. <strong>SQL</strong> ist im Zusammenhang mit der PHP-Programmierung enorm<br />
wichtig und sollte unbedingt beherrscht werden.<br />
Was ist <strong>SQL</strong>?<br />
<strong>SQL</strong>, am besten englisch ausgesprochen (»ess-kju-ell«), steht für Structured Query<br />
Language – Strukturierte Abfragesprache. <strong>SQL</strong> wird verwendet, um mit einer<br />
Datenbank zu kommunizieren. Nach der Definition der Normungsinstitution<br />
ANSI (American National Standards <strong>In</strong>stitute) ist <strong>SQL</strong> der verbindliche <strong>In</strong>dustriestandard<br />
für die Abfrage relationaler Datenbankmanagementsysteme (RDBMS).<br />
<strong>SQL</strong>-Anweisungen bestimmen, wie eine Abfrage oder Befehl aussehen soll, den<br />
das System empfängt und ausführt. Der Standard erlaubt es, mit einem Basisbefehlssatz<br />
nahezu alle RDBMS zu bedienen. Abweichend von den Basisbefehlen<br />
verfügen alle am Markt operierenden System – egal ob kommerziell oder Open<br />
Source – über mehr oder wenige sinnvolle und natürlich zu allen anderen inkompatible<br />
Erweiterungen. <strong>My</strong><strong>SQL</strong> ist hierin ebenso wenig eine Ausnahme wie<br />
<strong>SQL</strong>ite, Oracle, MS <strong>SQL</strong>, Sybase oder Access. Der Basisbefehlssatz, den man<br />
unbedingt beherrschen muss, wird jedoch weitgehend identisch unterstützt. Dazu<br />
gehören SELECT (Abfragen), UPDATE (Aktualisieren), DELETE (Löschen),<br />
INSERT (Einfügen) und DROP (Entfernen).<br />
Tabellen und Abfragen<br />
Relationale Datenbanken halten Daten in Tabellen. Tabellen werden durch einen<br />
eindeutigen Namen identifiziert, der in den Abfragebefehlen anzugeben ist.<br />
Tabellen bestehen aus Spalten, die jeweils einen ganz bestimmten Datentyp aufnehmen<br />
können. Sie bestehen auch aus Reihen, in denen zusammengehörende<br />
Daten gespeichert sind. Eine einfache Tabelle mit Daten kann in etwa folgendermaßen<br />
dargestellt werden:<br />
445
446<br />
Datenbankprogrammierung<br />
Stadt Bundesland Tief Hoch<br />
Berlin Berlin 5 13<br />
München Bayern 2 16<br />
Hamburg Hamburg 7 12<br />
Frankfurt Hessen 7 11<br />
Tabelle 11.1: Eine sehr einfache <strong>SQL</strong>-Tabelle mit Wetterdaten (Tabellenname: Wetter)<br />
Die Tabelle abfragen: SELECT<br />
Grundsätzlich lässt sich jede Datenbank vollständig über <strong>SQL</strong>-Befehle bedienen.<br />
Gerade am Anfang wird das eher weniger der Fall sein. Alle Datenbankprogramme<br />
bieten mehr oder weniger elegante dialogorientierte Oberflächen, die zumindest<br />
das Anlegen der Tabellen und das Eingeben der ersten Daten erlauben, ohne die<br />
Anweisungen selbst zu beherrschen.<br />
Bei Abfragen sieht es anders aus. Für viele kommerzielle Datenbanken gibt es so<br />
genannte Query Builder und einige freie Programme mit entsprechender Funktion<br />
sind auch verfügbar. Derartige Tools verhindern jedoch, dass man <strong>SQL</strong> wirklich<br />
lernt. Das ist fatal, denn früher oder später stößt jedes dieser Programme an<br />
seine Grenzen. Das sind aber selten die Grenzen von <strong>SQL</strong>. Die Beschäftigung mit<br />
der Abfragesyntax ist deshalb unbedingt zu empfehlen.<br />
Der prinzipielle Aufbau einer Abfrage folgt folgendem Schema:<br />
SELECT Spaltename1 [, Spaltenname2, ...]<br />
FROM Tabelle<br />
[WHERE Bedingungen]<br />
Die eckigen Klammern deuten optionale Angaben an. Die Anweisungen sollten<br />
außerdem, auch wenn sie hier aus Gründen der Lesbarkeit mehrzeilig erscheinen,<br />
als eine Zeichenkette gelesen werden. Auf die oben gezeigte Tabelle »Wetter«<br />
bezogen, sieht eine konkrete Abfrage beispielsweise folgendermaßen aus:<br />
SELECT Stadt, Bundesland FROM Wetter WHERE Tief > 5<br />
Entfällt die mit WHERE eingeleitete Bedingung, werden alle Reihen der Tabelle<br />
zurückgegeben. Die Auswahl der Spalten bestimmt die Liste unmittelbar hinter<br />
SELECT. Hier kann alternativ auch das Sternchen (*) angegeben werden, um alle<br />
Spalten zu holen.
Die universelle Abfragesprache <strong>SQL</strong><br />
SELECT * FROM ist die häufigste Abfrage – zumindest in der Literatur. <strong>In</strong><br />
der Praxis sollte dies nicht so sein, weil die Abfrage von Spalten, die<br />
nicht benötigt werden, unnütz Zeit kostet. Die Darstellung auf dem<br />
Papier lässt sich aber verkürzen und die Angaben sind weniger spezifisch,<br />
was zur Erklärung manchmal hilfreich ist.<br />
Hat eine Abfrage mit SELECT Erfolg, besteht die Antwort aus einem oder mehreren<br />
so genannten Datensätzen. Jeder Datensatz kann wiederum ein oder mehrere Felder<br />
enthalten. Der Datensatz ist quasi eine konkrete Version einer Reihe der<br />
Datenbank, ergänzt um berechnete Spalten oder reduziert auf ausgewählte Spalten.<br />
Nur wenn die Abfrage aus dem einfachen * besteht, entspricht ein Datensatz<br />
einer Reihe.<br />
Bedingungen formulieren: WHERE<br />
Die WHERE-Bedingung ist sehr wichtig. Sie folgt denselben Prinzipien wie if in<br />
PHP5, das heißt, es muss sich ein Boolescher Ausdruck formulieren lassen. Dazu<br />
gehören in erster Linie die Vergleichsoperatoren:<br />
><br />
Größer als; meist nur mit numerischen Werten sinnvoll<br />
<<br />
Kleiner als; meist nur mit numerischen Werten sinnvoll<br />
>=<br />
Größer als oder gleich; meist nur mit numerischen Werten sinnvoll<br />
448<br />
Datenbankprogrammierung<br />
Neben diesen kennt <strong>SQL</strong> aber auch einige sehr spezielle Operatoren, die in PHP<br />
nicht vorhanden sind:<br />
LIKE<br />
Dieser Operator erlaubt eine Ähnlichkeitssuche mit Platzhaltern. <strong>SQL</strong> verwendet<br />
sehr untypische Platzhalter: % steht für jedes beliebige Zeichen in beliebiger<br />
Anzahl und _ für genau ein beliebiges Zeichen.<br />
Beispiel:<br />
SELECT * FROM Wetter WHERE Stadt LIKE 'B%'<br />
Diese Anweisung gibt alle Städte zurück, die mit dem Buchstaben »B« beginnen.<br />
IN<br />
<strong>SQL</strong> verwendet zur Referenz auf Datensätze oft deren Schlüsselnummern<br />
(darauf wird noch genauer eingegangen). Listen solcher Referenzen kann man<br />
mit IN verwenden. Aber auch einfache Zahlenvergleich sind gut möglich.<br />
Beispiel:<br />
SELECT * FROM Wetter WHERE Tief IN (1,3,5,7)<br />
Dieser Befehl fragt alle ungeraden Tiefsttemperaturwerte aller Datensätze ab.<br />
BETWEEN<br />
Hiermit lassen sich Wertebereiche abfragen.<br />
Beispiel:<br />
SELECT * FROM Wetter WHERE Hoch BETWEEN 10 AND 15<br />
Zeichenketten müssen in <strong>SQL</strong> mit einfachen Anführungszeichen umschlossen<br />
werden. Die meisten Systeme verkraften zwar auch doppelte, die Zusammenarbeit<br />
mit PHP profitiert aber erheblich von einer einheitlichen Schreibweise, weshalb<br />
einfache Anführungszeichen zum Standard gehören sollten.<br />
Die SELECT-Anweisung kann weitaus mehr. Dazu gehören Befehle zum Sortieren,<br />
Filtern, Verknüpfen mit anderen Tabellen usw. Um dies ausprobieren zu können,<br />
brauchen Sie jedoch mehr Tabellen, die erstmal erzeugt und befüllt sein müssen.<br />
Tabellen anlegen und füllen<br />
Werden Tabellen per Code erzeugt, braucht man eine spezielle Anweisung dafür.<br />
Dieser Abschnitt zeigt, wie Tabellen erzeugt und gefüllt werden.
Tabelle erzeugen: CREATE TABLE<br />
Die universelle Abfragesprache <strong>SQL</strong><br />
<strong>In</strong> <strong>SQL</strong> heißt diese CREATE TABLE. Die grundlegende Syntax lautet:<br />
CREATE TABLE Tabellenname<br />
(SpaltenName DatenTyp [Einschränkung],<br />
[SpaltenName DatenTyp [Einschränkung], ...]<br />
)<br />
Die Angabe der Einschränkung bestimmt nähere Eigenschaften der Spalte, abhängig<br />
vom gewählten Datentyp. Die Angabe ist optional, weil alle Datentypen mit<br />
bestimmten Basiseigenschaften ausgestattet sind. Der Spaltenname ist frei wählbar.<br />
Die meisten Systeme verkraften sogar Leerzeichen (keine gute Idee), wenn der<br />
Name in Anführungszeichen steht.<br />
Der Datentyp einer Spalte<br />
Der Datentyp ist eine sehr wichtige Eigenschaft einer Spalte. <strong>SQL</strong> ist prinzipiell<br />
typstreng und überwacht die Einhaltung der Daten in den Feldern umfassend. <strong>In</strong><br />
der Praxis wird dies etwas aufgeweicht betrachtet. So konvertiert <strong>SQL</strong>ite intern<br />
alles so lange, bis es passt, und akzeptiert meist alles. <strong>My</strong><strong>SQL</strong> hält die Typen streng<br />
ein, konvertiert aber auch weit reichend, und die Daten »passend« zu machen.<br />
Andere Datenbanken sind strenger und reagieren mit Fehlermeldungen auf falsche<br />
Typen. Generell geht <strong>SQL</strong> aber nie so locker mit Datentypen um wie PHP.<br />
<strong>SQL</strong> verfügt über eine Vielzahl von Funktionen, die speziell für Zeichenketten<br />
oder Zahlen gedacht sind. Der falsche Datentyp für die zu berechnende Spalte<br />
führt dann in jedem Fall zu einer Fehlermeldung.<br />
Die Angabe des Datentyps erfolgt durch ein aussagekräftiges Schlüsselwort und<br />
optional durch Parameter. Die folgende Tabelle zeigt die wichtigsten Angaben, die<br />
von fast allen <strong>SQL</strong>-Datenbanken akzeptiert werden.<br />
Datentyp Beschreibung<br />
CHAR(Anzahl) Feld mit fester Anzahl von Zeichen.<br />
VARCHAR(Maximum) Feld mit variabler Anzahl Zeichen, begrenzt auf ein Maximum.<br />
INT(Breite) Ganzzahlfeld mit der durch Breite angegebenen Anzahl<br />
Stellen<br />
449
450<br />
Datenbankprogrammierung<br />
Datentyp Beschreibung<br />
DATE Ein Datumsfeld (nur die Angabe des Datums ist möglich).<br />
DATETIME Ein Datums- und Zeitfeld (enthält Datums- und Zeitinformationen).<br />
FLOAT(Breite, Dezimal) Gleitkommazahl mit Breite Stellen und Dezimal Nachkommastellen.<br />
TEXT Längere Textdaten (VARCHAR und CHAR sind auf<br />
255 Zeichen begrenzt).<br />
BLOB Größere Binärdaten (BLOB = Binary Large Objects).<br />
Die meisten Datenbanken bieten vordefinierte Variationen von INT für verschiedene<br />
Mengen, beispielsweise BIGINT oder TINYINT in <strong>My</strong><strong>SQL</strong>.<br />
Tabellen erzeugen<br />
Das folgende Beispiel zeigt, wie die am Anfang benutzte Tabelle erzeugt wird:<br />
CREATE TABLE Wetter<br />
(Stadt VARCHAR(100),<br />
Bundesland VARCHAR(30),<br />
Tief INT(2),<br />
Hoch INT(2))<br />
Generell gilt die Regel, dass die Datentypen so fein und begrenzend wie möglich<br />
ausgelegt werden sollten. So dürfte es in Mitteleuropa kaum vorkommen, dass dreistellige<br />
Temperaturen auftreten. Deshalb wird INT(2) geschrieben. Es gibt kein<br />
Bundesland, das mehr als 30 Zeichen für den Namen benötigt (Mecklenburg-Vorpommern<br />
ist der längste Name mit genau 22 Zeichen). Beachten Sie, dass die<br />
Temperaturangabe -11°C in einem INT-Feld auch nur zwei Stellen benötigt, weil<br />
das Vorzeichen separat gespeichert wird.<br />
Derartige Beschränkungen verhindern zumindest teilweise, dass unsinnige Daten<br />
in die Datenbank gelangen. Sie führen freilich, wenn es dennoch versucht wird,<br />
zu Fehlermeldungen im Skript, die gesondert behandelt werden müssen. <strong>In</strong>konsistente<br />
Datenbestände sind jedoch weitaus schlimmer als Fehler bei der Eingabe,<br />
die frühzeitig auf das Problem hinweisen.
Einschränkungen<br />
Die universelle Abfragesprache <strong>SQL</strong><br />
Bei der Definition der Syntax für CREATE TABLE war von Einschränkungen die<br />
Rede. Dies sind Angaben, die den möglichen <strong>In</strong>halt unabhängig vom Datentyp<br />
bestimmen. Drei solcher Zusatzangaben sind besonders wichtig:<br />
UNIQUE<br />
Hiermit wird bestimmt, dass alle Werte in der Spalte nur ein Mal vorkommen<br />
dürfen, das heißt, sie müssen eindeutig (engl. unique) sein.<br />
PRIMARY KEY<br />
Bestimmt, dass diese Teile den Primärschlüssel stellt. Zur Bedeutung der<br />
Schlüssel folgt noch eine detaillierte Betrachtung. PRIMARY KEY impliziert<br />
immer UNIQUE.<br />
AUTO_INCREMENT<br />
Nicht offizieller <strong>SQL</strong>-Standard aber dennoch von fast allen Datenbank unterstützt<br />
sind Felder, deren Werte automatisch erzeugt werden, ohne dass dies<br />
beim Einfügen angegeben werden muss.<br />
NULL, NOT NULL<br />
NULL erklärt, dass das betreffende Feld beim Schreiben der Daten leer bleiben<br />
darf, NOT NULL bestimmt, dass dies nie der Fall sein darf.<br />
DEFAULT<br />
Bestimmt einen Standardwert, wenn bei der Eingabe der Daten kein Wert<br />
angegeben oder das Feld überhaupt nicht spezifiziert wurde.<br />
Es gibt weitaus mehr Einschränkungen als die gezeigten, allerdings werden die<br />
meisten weder von <strong>My</strong><strong>SQL</strong> noch von <strong>SQL</strong>ite unterstützt, weshalb die Ausführungen<br />
auf die elementaren Angaben beschränkt werden.<br />
Daten einfügen<br />
Nachdem die Tabelle existiert, kann sie mit Daten gefüllt werden. Dazu wird die<br />
Anweisung INSERT benutzt. INSERT verlangt die Angabe des Tabellennamens, einer<br />
Liste der Spalten, die mit Daten bestückt werden, und eine Liste der Werte, die<br />
hineingeschrieben werden. Die Angabe der Spalten kann entfallen, wenn alle<br />
beschrieben werden. Eine weitere Wetterangabe in der Mustertabelle könnte folgendermaßen<br />
eingefügt werden:<br />
451
452<br />
Datenbankprogrammierung<br />
INSERT INTO Wetter<br />
(Stadt, Bundesland, Hoch, Tief)<br />
VALUES<br />
('Dresden', 'Sachsen', 17, 8)<br />
Beachten Sie, dass Zeichenketten in einfachen Anführungszeichen stehen, numerische<br />
Werte jedoch nicht. Die Anzahl der Werte hinter VALUES muss exakt der<br />
Anzahl der Spalten entsprechen, damit die Zuordnung stimmt. Spalten, die hier<br />
nicht auftreten, werden entsprechend den Einschränkungsregeln befüllt, also mit<br />
Standardwerten (DEFAULT), automatischen Werten (AUTO_INCREMENT) oder NULL-<br />
Werten (NULL).<br />
Aktualisieren und Löschen von Daten<br />
Nachdem die Daten nun in der Tabelle sind und auch gelesen werden können,<br />
sind Lösch- und Aktualisierungsvorgänge an der Tagesordnung.<br />
Aktualisierung von Daten: UPDATE<br />
Die Änderung der <strong>In</strong>halte wird mit der Anweisung UPDATE vorgenommen. Die<br />
grundlegende Syntax folgt folgendem Schema:<br />
UPDATE TabellenName<br />
SET SpaltenName = Wert [, SpaltenName = Wert, ...]<br />
[WHERE Bedingung]<br />
Die Bedingung entspricht weitgehend den bei SELECT möglichen Angaben. Ohne<br />
Bedingung werden immer alle Spalten geändert. Die Zuweisung neuer Werte<br />
kann mit Konstanten oder Berechnungen, auch mit Referenzen auf andere Spalten<br />
erfolgen.<br />
Im Beispiel lässt sich die Temperatur für eine bestimmte Stadt folgendermaßen<br />
ändern:<br />
UPDATE Wetter SET Hoch = 17, Tief = 23 WHERE Stadt = 'Berlin'<br />
Man kann auch alle Temperaturen einfach erhöhen:<br />
UPDATE Wetter SET Hoch = Hoch + 1
Löschen von Daten: DELETE<br />
Die universelle Abfragesprache <strong>SQL</strong><br />
Dem Löschen dient die Anweisung DELETE. Die Anwendung entspricht dem<br />
Schema der bisher vorgestellten Befehle:<br />
DELETE FROM TabellenName [WHERE Bedingung]<br />
Auch wenn die Bedingung optional ist, die Angabe dürfte in den allermeisten Fällen<br />
unbedingt erforderlich sein – sonst ist die Tabelle nämlich leer.<br />
Für die Bedingung kann wieder ein Ausdruck nach dem bei SELECT gezeigten<br />
Schema benutzt werden. Beachten Sie, dass <strong>SQL</strong> kein »<strong>Und</strong>o/Rückgängig« etc.<br />
kennt – was weg ist, ist endgültig weg.<br />
Löschen und Leeren von Tabellen: DROP und TRUNCATE<br />
Um eine Tabelle komplett wieder loszuwerden, nutzen Sie die Anweisung DROP:<br />
DROP TabellenName<br />
Ebenso einfach geht das Leeren, das alle Daten entfernt, aber die Tabelle selbst<br />
erhält:<br />
TRUNCATE TabellenName<br />
Gegenüber der Anweisung DELETE FROM TabellenName (ohne Bedingung) ist, TRUN-<br />
CATE schneller. Der Effekt ist derselbe – alles ist weg.<br />
Fortgeschrittene Abfragen mit SELECT<br />
SELECT allein ist ungeheuer mächtig. Der korrekte Umgang damit verlangt aber<br />
einiges an Hintergrundwissen. Dennoch kommt man sehr schnell nicht mehr<br />
ohne die Basisabfragen aus. Dieser Abschnitt zeigt die Anwendung von SELECT auf<br />
dem Niveau, wie es auf durchschnittlich komplexen PHP-Seiten durchaus zur<br />
Anwendung kommen kann. Wenn Sie noch weiter lernen möchten, ist der Zugriff<br />
auf spezielle Literatur erforderlich.<br />
SELECT komplett betrachtet<br />
Mit SELECT werden Datenbanken abgefragt. Dabei ist FROM die einzige Angabe, die<br />
zwingend erforderlich ist. Das bereits vorgestellte WHERE ist optional. <strong>In</strong>sgesamt sind<br />
es fünf derartige Operatoren, die zum Einsatz kommen können:<br />
453
454<br />
Datenbankprogrammierung<br />
FROM<br />
Bestimmt die Tabelle (oder mehrere Tabellen), aus denen gelesen wird.<br />
WHERE<br />
Schränkt die Auswahl durch eine global wirkende Bedingung ein.<br />
GROUP BY<br />
Gruppiert Ergebnisse, sodass sie mit anderen zusammengefasst werden können.<br />
HAVING<br />
Wendet Auswahleinschränkungen auf Gruppen an.<br />
ORDER BY<br />
Sortiert die Ergebnisse.<br />
Bei der Auswahl der Spalten kann zudem bestimmt werden, ob alle oder nur eindeutige<br />
Ergebnisse zurückgegeben werden sollen:<br />
ALL<br />
DISTINCT<br />
Die Angabe ALL entfällt in den allermeisten Fällen, weil dies der Standardwert ist.<br />
Für die Abfrage eindeutiger Werte schreiben Sie beispielsweise:<br />
SELECT DISTINCT Bundesland FROM Wetter<br />
Dies verhindert, dass die mehrfach in der Tabelle auftretenden Bundesländer auch<br />
mehrfach ausgegeben werden.<br />
GROUP BY gruppiert Ergebnisse und wenn Einschränkungen bei der Auswahl der<br />
Gruppen gefragt sind, dann ist HAVING die erste Wahl. Nach HAVING können dieselben<br />
Booleschen Ausdrücke stehen wie nach WHERE. Mehr dazu weiter unten.<br />
Aggregierende Funktionen<br />
Aggregierende Funktionen führen Berechnungen mit der durch SELECT erstellten<br />
Auswahl von Daten aus. Die häufigste und einfachste ist COUNT – hiermit wird<br />
schlicht die Anzahl der Reihen ermittelt. Wichtig sind folgende Funktionen:<br />
COUNT, COUNT(*)<br />
Hiermit wird die Anzahl der ausgewählten oder (*) aller Reihen der Abfrage<br />
ermittelt.
Die universelle Abfragesprache <strong>SQL</strong><br />
MAX, MIN<br />
Enthält die betroffene Spalte Werte, die eine Ordnung bilden, kann hiermit<br />
der größte bzw. kleinste Wert ermittelt werden.<br />
SUM<br />
Für numerische Spalten ermittelt diese Funktion die Summe.<br />
AVG<br />
Für numerische Spalten ermittelt diese Funktion den Durchschnitt.<br />
Beachten Sie, dass die Aggregatfunktionen keine Liste von Daten erzeugen, sondern<br />
skalare Werte. Dies muss bei der Auswertung mit PHP-Funktionen berücksichtigt<br />
werden. Bei den PHP-Beispielen wird darauf nochmals explizit eingegangen.<br />
Die Anwendung in <strong>SQL</strong> sieht folgendermaßen aus:<br />
SELECT AVG(Hoch) FROM Wetter<br />
Damit erhält man die durchschnittliche Tageshöchsttemperatur aus der Wetter-<br />
Tabelle. Selbstverständlich sind auch hier WHERE-Bedingungen eine gute Idee:<br />
SELECT AVG(Hoch) FROM Wetter WHERE Bundesland = 'Bayern'<br />
Nun erhalten Sie die durchschnittliche Tageshöchsttemperatur aller bayerischen<br />
Städte.<br />
Gruppierungen<br />
Gruppierungen mit GROUP BY wurden bereits kurz erwähnt. Der Sinn ist weniger<br />
der damit erreichbare Sortiereffekt, sondern die Möglichkeit, die Aggregatfunktionen<br />
auf die Gruppen anzuwenden.<br />
SELECT MAX(Hoch), Bundesland<br />
FROM Wetter<br />
GROUP BY Bundesland<br />
HAVING Bundesland LIKE 'B%'<br />
Diese Abfrage ermittelt die höchste Tagestemperatur aller Bundesländer, die mit<br />
»B« beginnen. Die Einschränkung mit HAVING ist freilich optional, meist genügt<br />
die Gruppierung alleine. Wichtig ist, dass die Liste der Spalten hinter GROUP BY in<br />
der Liste der Spalten hinter SELECT enthalten sein muss. Gruppierung und Ausgabe<br />
korrespondieren immer. Die Aggregatfunktionen sind davon nicht betroffen,<br />
deshalb kann die Funktion MAX im Beispiel hinzugefügt werden.<br />
455
456<br />
Datenbankprogrammierung<br />
Ergebnisse sortieren<br />
Tabellen sind in <strong>SQL</strong> niemals sortiert oder geordnet. Allein die Abfrage entscheidet,<br />
wie die Daten auszugeben sind. Um die entsprechende Kontrolle zu bekommen,<br />
wird ans Ende der Anweisung ORDER BY angehängt. Zwei zusätzliche<br />
Klauseln bestimmen die Richtung:<br />
ASC<br />
Dies steht für »ascending«, also aufsteigend.<br />
DESC<br />
Dies steht für »descending«, also absteigend.<br />
Hinter ORDER BY lassen sich mehrere Spalten angeben, um uneindeutige Sortierungen<br />
nach nur einer Spalte zu vermeiden:<br />
SELECT * FROM Wetter ORDER BY Bundesland, Stadt ASC<br />
Das konkrete Sortierverhalten kann <strong>SQL</strong> nicht bestimmen. Dazu bietet jedes<br />
Datenbanksystem verschiedene Einstellmöglichkeiten, die nicht standardisiert<br />
sind. Dies betrifft beispielsweise die Einordnung von Umlauten ins Alphabet.<br />
Die Klausel ASC kann auch entfallen, denn die aufsteigende Sortierung ist der<br />
Standardfall. Dies gilt aber nur, wenn ORDER BY angegeben wurde. Ohne dieses<br />
Schlüsselwort wird unsortiert ausgegeben.<br />
Funktionen<br />
<strong>SQL</strong> verfügt über eine reiche Funktionspalette, unter anderem:<br />
Mathematische Funktionen<br />
Zeichenkettenverarbeitung<br />
Datumsfunktionen<br />
Systemfunktionen<br />
Leider ist der Standard hier kaum beachtet worden. Deshalb gibt es teilweise drastische<br />
Unterschiede in Ausstattung und Syntax bei verschiedenen Datenbanken.<br />
Der Abschnitt 11.4 »Erste Schritte mit <strong>My</strong><strong>SQL</strong> und <strong>My</strong><strong>SQL</strong>i« ab Seite 477 zeigt<br />
die konkreten Funktionen für <strong>My</strong><strong>SQL</strong> und im Kapitel Die integrierte Datenbank<br />
<strong>SQL</strong>ite ab Seite 497 finden Sie entsprechende Auflistung für <strong>SQL</strong>ite.
Verknüpfungen zwischen Tabellen<br />
Die universelle Abfragesprache <strong>SQL</strong><br />
Der Name »Relationale Datenbanken« enthält einen Hinweis auf eine ganz spezifische<br />
Eigenschaft – die Relationen oder Beziehungen. Richtig wertvoll wird der<br />
Umgang mit Datenbanken erst, wenn man zwischen Tabellen derartige Beziehungen<br />
aufbaut.<br />
Das ist in fast allen Anwendung auch notwendig, um die Datenhaltung effektiv und<br />
nicht redundant zu halten. Wenn Sie die gut gefüllte Wetter-Tabelle sehen, werden<br />
Sie bemerken, dass dieselben Bundesländer immer wieder aufgeführt sind. Ändert<br />
man nun den Namen, beispielsweise wegen eines Schreibfehlers, dann müsste man<br />
alle betreffenden Felder ändern. Hat man dabei schon einen Fehler, beispielsweise<br />
aus früheren Einträgen, wird die Datenbank inkonsistent. Deshalb versucht man,<br />
redundante Daten zu vermeiden. Dazu wird eine zweite Tabelle erstellt und mit<br />
der ersten verknüpft. Damit das Verknüpfen funktioniert, erhält außerdem jede<br />
Tabellen einen Primärschlüssel, der jeden Datensatz eindeutig kennzeichnet.<br />
Normalisierungen<br />
Folgende neue Tabelle ist also für die Wetter-Anwendung erforderlich:<br />
ID Name<br />
1 Berlin<br />
2 Bayern<br />
3 Hamburg<br />
4 Hessen<br />
Tabelle 11.2: Tabelle für Bundesländer (Ausschnitt)<br />
Nun wird die Wetter-Tabelle so geändert, dass nur noch mit Relationen gearbeitet<br />
wird:<br />
ID Stadt BundeslandID Hoch Tief<br />
1 Berlin 1 7 13<br />
2 München 2 8 16<br />
Tabelle 11.3: Tabelle für Wetterdaten (Ausschnitt)<br />
457
458<br />
Datenbankprogrammierung<br />
ID Stadt BundeslandID Hoch Tief<br />
3 Regensburg 2 9 15<br />
4 Altötting 2 8 15<br />
Tabelle 11.3: Tabelle für Wetterdaten (Ausschnitt) (Forts.)<br />
Die Verknüpfung ist nun noch zu definieren. Je nach Datenbank ist dies eine einfache<br />
Festlegung oder ein fest programmierter Wert, der von der Datenbank überwacht<br />
wird. <strong>My</strong><strong>SQL</strong> und <strong>SQL</strong>ite überwachen nichts und deshalb genügt die<br />
Angabe der Verknüpfung bei der Abfrage, was durch Vergleich der beiden betroffenen<br />
Felder geschieht:<br />
Bundesland.ID = Wetter.BundeslandID<br />
Damit das in einer Anweisung gelingt, muss man etwas beachten. So kann jedem<br />
Spaltennamen immer der Tabellenname vorangestellt werden, getrennt durch<br />
einen Punkt. Das ist meist notwendig, weil sich die Spaltennamen gleichen können<br />
und der <strong>SQL</strong>-Server dann nicht mehr weiß, welche Spalte gemeint ist.<br />
Das Aufbrechen einer Tabellen in mehrere zur Vermeidung von Redundanzen<br />
wird als Normalisierung bezeichnet. Es gibt mehrere Stufen der Normalisierung,<br />
für deren vollständige Darstellung jedoch mehr Platz erforderlich ist. An dieser<br />
Stelle sei auf entsprechende Spezialliteratur zu Datenbanken verwiesen.<br />
Prinzipien verknüpfter Abfragen<br />
Verknüpfte Abfragen können auf mehreren Wegen erstellt werden. Der einfachste<br />
weg ist ein so genannter »<strong>In</strong>ner Join«, bei dem ein Kreuzprodukt aus beiden Tabellen<br />
erstellt wird:<br />
SELECT * FROM Wetter, Bundesland<br />
Das führt dazu, dass jede Zeile der einen Tabelle mit jeder der anderen Tabelle<br />
»gekreuzt« wird. 100 Wetterdaten für 16 Bundesländer ergeben dann 1.600 Reihen.<br />
Damit kann in der Praxis niemand etwas anfangen. Also ist die benötigte<br />
Verknüpfung anzugeben:<br />
SELECT * FROM Wetter, Bundesland <br />
WHERE Bundesland.ID = Wetter.BundeslandID<br />
Nun werden die Datensätze aus der ersten Tabelle mit den passenden aus der<br />
zweiten verknüpft. Da jeder Wettereintrag nur in einem Bundesland platziert sein
Die universelle Abfragesprache <strong>SQL</strong><br />
kann, sind es nun nur noch die benötigten 100 Datensätze. Im Unterschied zur<br />
einfachen Abfrage der ersten Tabelle steht nun aber auch der Name des Bundeslandes<br />
zur Verfügung. Korrekter (im Sinne von: effizienter) ist folgende Version:<br />
SELECT W.Stadt, W.Hoch, W.Tief, B.Name <br />
FROM Wetter W, Bundesland B <br />
WHERE B.ID = W.BundeslandID<br />
Damit ist die mit der ersten Abfrage erreichte Ausgabe wieder da, allerdings diesmal<br />
basierend auf einer verbesserten Datenstruktur.<br />
Zum »<strong>In</strong>ner Join« kann eine alternative Syntax verwendet werden:<br />
SELECT W.Stadt, W.Hoch, W.Tief, B.Name <br />
FROM Wetter W INNER JOIN Bundesland B <br />
ON B.ID = W.BundeslandID<br />
Weitere Bedingungen, die in der ersten Variante mit AND angehängt werden, müssten<br />
hier in einer zusätzlichen WHERE-Bedingung stehen. Der »<strong>In</strong>ner Join« bezieht<br />
NULL-Felder nicht mit ein. Es kann nämlich sein, dass zu einer Stadt noch keine<br />
Zuordnung des Bundeslandes erfolgte. Was passiert mit solchen Feldern? Grundsätzlich<br />
ist NULL in <strong>SQL</strong> kein unmöglicher Zustand. Es gibt nun mehrere Varianten:<br />
Die linke Tabelle (links vom JOIN) enthält NULL-Werte und die entsprechenden<br />
Zeilen sollen trotzdem verarbeitet werden.<br />
<strong>In</strong> diesem Fall wird als Schlüsselwort LEFT JOIN gewählt.<br />
Die rechte Tabelle (rechts vom JOIN) enthält NULL-Werte und die entsprechenden<br />
Zeilen sollen trotzdem verarbeitet werden.<br />
<strong>In</strong> diesem Fall wird als Schlüsselwort RIGHT JOIN gewählt.<br />
Freilich kann man auch die Tabellen vertauschen und die Schlüsselwörter genau<br />
umgekehrt einsetzen.<br />
Es gibt weitere JOIN-Varianten, die hier jedoch zu weit führen und die – leider –<br />
auch nicht alle von <strong>My</strong><strong>SQL</strong> unterstützt werden.<br />
Fortgeschrittene <strong>SQL</strong>-Techniken<br />
Einige fortgeschrittene <strong>SQL</strong>-Techniken seien an dieser Stelle kurz erwähnt. Sie<br />
sind leicht zu verstehen und einzusetzen. Das Handbuch zu <strong>My</strong><strong>SQL</strong> gibt hier ausführlich<br />
Auskunft.<br />
459
Primärschlüssel<br />
460<br />
Datenbankprogrammierung<br />
Bislang wurde einfach angenommen, dass die Tabellen einen Schlüssel haben.<br />
Generell ist dies keine Option, sondern ein Primärschlüssel ist erforderlich, damit<br />
die Datenbank die Datensätze unterscheiden kann. Praktischerweise definiert man<br />
meist eine Spalte als INT (oder größer, beispielsweise BIGINT) und versieht sie mit<br />
dem Attribut AUTO_INCREMENT. <strong>My</strong><strong>SQL</strong> legt die benötigten Schlüssel dann automatisch<br />
an. Andere Tabellen können dann Referenzen dazu nutzen. Ohne Primärschlüssel<br />
könnte die Datenbank die Datensätze nicht eindeutig unterscheiden und<br />
Abfragen wären nicht ausführbar.<br />
Alternativ zur automatischen Vergabe ist freilich auch die Nutzung anderer Spalten<br />
mit eindeutigen Werten möglich, ebenso wie sich der Primärschlüssel aus<br />
mehreren Spalten zusammensetzen kann, um eindeutig zu sein.<br />
Unterabfragen<br />
Manchmal benötigt man die Ergebnisse einer Abfrage für eine andere. <strong>SQL</strong> kann<br />
diese Verknüpfung direkt ausführen, also ohne PHP dazwischen. Man spricht von<br />
so genannten Sub-Selects:<br />
SELECT * FROM Wetter<br />
WHERE BundeslandID<br />
NOT IN<br />
(SELECT ID FROM Bundesland WHERE NAME LIKE 'B%')<br />
Zuerst wird hier die innere Abfrage ausgeführt. Sie ergibt eine Liste der Primärschlüssel<br />
der Tabelle Bundesland für alle Länder, die mit »B« beginnen. Diese<br />
Liste (beispielsweise 1,2,4,8) wird für den IN-Operator benutzt. Dann wird die<br />
Abfrage für alle Bundesländer ausgeführt, die nicht (NOT IN) mit »B« beginnen.<br />
Sub-Selects sind vor allem bei verknüpften Tabellen häufiger im Einsatz.<br />
Sub-Selects sind meist langsamer als JOIN und äquivalent verwendbar. Es gibt aber<br />
auch Fälle, in denen sie sich nicht gegenseitig ersetzen können. <strong>In</strong> der Praxis<br />
braucht man beides.<br />
UNION<br />
Mit UNION lassen sich zwei Abfragen kombinieren. Das Resultat können zwei verschiedene,<br />
aber auch zwei gleichartig strukturierte Tabellen sein. Die Kombina-
Der <strong>My</strong><strong>SQL</strong>-Dialekt<br />
tion von Abfragen hebt einige Beschränkungen der JOIN-Verfahren auf. Das<br />
Schlüsselwort muss immer zwischen zwei gültigen SELECT-Abfragen stehen:<br />
SELECT * FROM Wetter UNION SELECT * FROM Stadt<br />
Beim programmtechnischen Zugriff mittels PHP5 müssen spezielle Abfragemethoden<br />
verwendet werden, um die unterschiedlich strukturierten Tabellen auslesen<br />
zu können.<br />
11.3 Der <strong>My</strong><strong>SQL</strong>-Dialekt<br />
Dieser Abschnitt zeigt die Besonderheiten, Funktionen und Eigenarten von<br />
<strong>My</strong><strong>SQL</strong> 4 und teilweise auch <strong>My</strong><strong>SQL</strong> 5. Soweit <strong>My</strong><strong>SQL</strong> sich an das im vorhergehenden<br />
Abschnitt beschriebene Standard-<strong>SQL</strong> hält, wird dies nicht nochmals wiederholt.<br />
Grobe Abweichungen vom <strong>SQL</strong>92-Standard<br />
Die folgende Liste zeigt in loser Folge einige der wichtigsten Erweiterungen,<br />
Funktionen und Einschränkungen in <strong>My</strong><strong>SQL</strong>. Die meisten Angaben gelten für<br />
<strong>My</strong><strong>SQL</strong> ab Version 4.1, <strong>My</strong><strong>SQL</strong> 5 zeichnet sich demgegenüber vor allem durch<br />
höhere Stabilität, Geschwindigkeit und (nach der finalen Version) Zuverlässigkeit<br />
aus.<br />
CREATE TABLE t2 LIKE t1<br />
Erzeugt eine neue Tabelle t2, die exakt denselben Aufbau wie t1 hat.<br />
INSERT ON DUPLICATE KEY UPDATE<br />
Fügt Daten ein, bzw. aktualisiert, wenn der Primärschlüssel bereits existiert.<br />
Das ist eine elegante Kombination aus INSERT und UPDATE.<br />
Erweiterungen bei den GROUP BY-Funktionen: STD, BIT_OR, BIT_AND,<br />
GROUP_CONCAT(SpaltenName SEPARATOR ',')<br />
Die neue Aggregat-Funktion GROUP_CONCAT, kann die Werte einer Abfrage als<br />
Zeichenkette zusammenfassen. Es kann ein Trennzeichen ähnlich wie in<br />
PHPs join-Funktion angegeben werden.<br />
LIKE kann auch auf numerische Spalten angewendet werden.<br />
461
462<br />
Datenbankprogrammierung<br />
Der SELECT-Befehl ist um zwei Schlüsselwörter erweitert worden: INTO OUTFILE<br />
und STRAIGHT_JOIN.<br />
Neu sind die Befehle OPTIMIZE TABLE und SHOW.<br />
GROUP BY muss nicht alle Spalten enthalten, die ausgegeben werden.<br />
Zusätzlich zu den Operatoren OR und AND können die Symbole || und &&<br />
genutzt werden. Für die Verknüpfung von Zeichenketten muss anstatt || die<br />
Funktion CONCAT verwendet werden.<br />
Der Operator % kann als Synonym für MOD eingesetzt werden.<br />
Logische Operatoren können auch auf der linken Seite eines SELECT-Befehls<br />
geschrieben werden:<br />
SELECT spalte1 >= 100 FROM table<br />
Der Abruf der letzten automatisch generierten ID erfolgt mit der Funktion<br />
LAST_INSERT_ID().<br />
Diverse neue Funktionen, unter anderem versteht <strong>My</strong><strong>SQL</strong> reguläre Ausdrücke<br />
mit REGEXP und RLIKE (siehe Liste im nächsten Abschnitt).<br />
Neue Befehle: REPLACE ersetzt UPDATE und INSERT. Ist der Datensatz vorhanden<br />
(identifiziert am Primärschlüssel), wird INSERT ausgeführt, andernfalls UPDATE.<br />
FLUSH löscht Statusinformationen.<br />
Datentypen<br />
Die Datentypen sind in <strong>My</strong><strong>SQL</strong> sehr umfangreich. Die folgende Tabelle zeigt<br />
alles, was geht, unabhängig davon, ob es dem <strong>SQL</strong>92-Standard entspricht oder<br />
nicht.<br />
Datentyp Beschreibung<br />
TINYINT [(M)] [UNSIGNED] [Z] Kleine Ganzzahlen, von 0 bis 255 oder -128 bis 127<br />
SMALLINT [(M)] [UNSIGNED] [Z] Ganzzahlen, entweder von 0 bis 65.535 oder von<br />
-32.768 bis +32.767<br />
MEDIUMINT [(M)] [UNSIGNED] [Z] Ganzzahlen, entweder von 0 bis 16.777.215 oder<br />
von -8.388.608 bis +8.388.607<br />
Tabelle 11.4: Datentypen in <strong>My</strong><strong>SQL</strong> (Z steht für ZEROFILL, M für die Stellenzahl)
Datentyp Beschreibung<br />
INT [(M)] [UNSIGNED] [Z]<br />
INTEGER<br />
Der <strong>My</strong><strong>SQL</strong>-Dialekt<br />
Ganzzahlen, entweder von 0 bis 4.294.967.295 oder<br />
von -2.<strong>14</strong>7.283.648 bis +2.<strong>14</strong>7.283.647<br />
BIGINT [(M)] [UNSIGNED] [Z] Ganzzahlen, entweder von 0 bis<br />
18.446.744.073.709.551.615 oder von<br />
-9.223.372.036.854.775.808 bis<br />
+9.223.372.036.854.775.807<br />
FLOAT(precision) [Z] Fließkommazahl, immer vorzeichenbehaftet.<br />
precision kann 4 oder 8 sein; 4 steht für einfache<br />
Genauigkeit, 8 für doppelte.<br />
FLOAT[(M,D)] [Z] Fließkommazahl, deren Wertebereich von<br />
-3,402823466 38 bis -1,175494351- 38 reicht und die 0<br />
sowie den Wertebereich von +1,175494351 -38 bis<br />
+3,402823466 38 umfasst.<br />
DOUBLE[(M,D)] [Z]<br />
DOUBLEPRECISION[(M,D)] [Z]<br />
REAL[(M,D)] [Z]<br />
DECIMAL(M,D) [Z]<br />
NUMERIC<br />
Fließkommazahl, deren Wertebereich von<br />
-1,7976931348623157 308 bis -2,22507385850720<strong>14</strong> -308 ,<br />
und von -2,22507385850720<strong>14</strong> -308 bis<br />
+1,7976931348623157 308 reicht, inklusive der 0.<br />
Ungepackte Fließkommazahl, immer vorzeichenbehaftet.<br />
Zahlen werden als Zeichenkette gespeichert,<br />
jede Ziffer steht in einem Byte. Das Komma, Vorzeichen<br />
usw. belegen jeweils ein Byte.<br />
DATE Datum im Format »YYYY-MM-DD«. Der Wertebereich<br />
geht vom 1.1.1000 bis zum 31.12.9999.<br />
DATETIME Datum und Zeit im Format<br />
»YYYY-MM-DD hh:mm:ss«.<br />
TIMESTAMP[(M)] Zeitstempel, Wertebereich von 1.1.1970 bis zum<br />
31.12.2036. Für die Angabe des Anzeigebereiches<br />
M gilt:<br />
<strong>14</strong>: YYYYMMDDhhmmss<br />
12: YYMMDDhhmmss<br />
8: YYYYMMDD<br />
6: YYMMDD<br />
Tabelle 11.4: Datentypen in <strong>My</strong><strong>SQL</strong> (Z steht für ZEROFILL, M für die Stellenzahl) (Forts.)<br />
463
Funktionen<br />
464<br />
Datenbankprogrammierung<br />
Datentyp Beschreibung<br />
TIME Zeit im Wertebereich von -838:59:59 bis +838:59:59<br />
mit dem Ausgabeformat »hh:mm:ss«.<br />
YEAR Jahr, Wertebereich 1901 bis 2155, Ausgabe YYYY.<br />
CHAR(M) [BINARY] Zeichenkette fester Länge M. Wertebereich 1 bis<br />
255. Leerzeichen am Ende werden automatisch für<br />
die Ausgabe entfernt. Sortieren und Selektieren<br />
berücksichtigt Groß- und Kleinschreibung nicht,<br />
wenn Sie nicht BINARY verwenden.<br />
VARCHAR(M) [BINARY] Zeichenkette variabler Länge, maximal M. Wertebereich<br />
1 bis 255. Leerzeichen am Ende werden<br />
automatisch für die Ausgabe entfernt. Sortieren und<br />
Selektieren berücksichtigt Groß- und Kleinschreibung<br />
nicht, wenn Sie nicht BINARY verwenden.<br />
TINYBLOB, TINYTEXT BLOB oder TEXT mit maximal 255 Byte<br />
BLOB, TEXT BLOB oder TEXT mit maximal 65.535 Byte<br />
MEDIUMBLOB, MEDIUMTEXT BLOB oder TEXT mit maximal 16.777.215 Byte<br />
LONGBLOB, LONGTEXT BLOB oder TEXT mit maximal 4.294.967.295 Byte<br />
ENUM('wert1', 'wert2', ...,) Aufzählung. Ein Feld dieses Typs kann nur eine<br />
Zeichenkette enthalten, die einem Objekt der Aufzählung<br />
entspricht.<br />
SET('wert1', 'wert2' , ...,) Wie ENUM, kann aber mehrere Werte aus der Liste<br />
enthalten.<br />
Tabelle 11.4: Datentypen in <strong>My</strong><strong>SQL</strong> (Z steht für ZEROFILL, M für die Stellenzahl) (Forts.)<br />
Funktionen sind schlecht standardisiert. Jede Datenbank kocht ihr eigenes Süppchen.<br />
<strong>My</strong><strong>SQL</strong> ist da keine Ausnahme. Deshalb finden Sie an dieser Stelle alle<br />
Funktionen der aktuellen Version mit entsprechenden Einsatzhinweisen.
Funktionen für mathematische Berechnungen<br />
Funktion Beschreibung<br />
ABS(x) Absoluter Betrag der Zahl x.<br />
ACOS(x) Der Arkuskosinus der Zahl x.<br />
ASIN(num) Der Arcussinus der Zahl x.<br />
ATAN(num) Der Arkustangens der Zahl x.<br />
Der <strong>My</strong><strong>SQL</strong>-Dialekt<br />
ATN2(num1, num2) Der Arkustangens (Winkel) zwischen num1 und num2.<br />
BIT_COUNT(num) Die Anzahl der Bits, die in einer Zahl 1 sind.<br />
CEILING(x) Die kleinste Ganzzahl größer oder gleich dem Ausdruck<br />
COS(x) Der Kosinus der Zahl x.<br />
COT(x) Der Kotangens der Zahl x.<br />
CRC32(expr) Die zyklische Prüfsumme eines Ausdrucks als 32-Bit-Wert.<br />
DEGREES(x) Eine Umrechnung von Radiant in Grad.<br />
num1 DIV num2 Eine Ganzzahldivision, funktioniert auch mit sehr großen<br />
Zahlen (BIGINT).<br />
EXP(x) Die Potenz zur Basis e.<br />
FLOOR(x) Die größte Ganzzahl kleiner oder gleich dem angegebenen<br />
numerischen Ausdruck.<br />
GREATEST(x1,x2,..) Gibt den größten Wert der Liste zurück.<br />
LEAST(x1,x2,...) Gibt den kleinsten Wert der Liste zurück.<br />
LN(x) Der natürliche Logarithmus der Zahl x.<br />
LOG(x), LOG(b, x) Der natürliche Logarithmus (ohne b) oder Logarithmus<br />
zur Basis b.<br />
LOG2(x) Der Logarithmus zur Basis 2 der Zahl x.<br />
LOG10(x) Der dekadische Logarithmus der Zahl x.<br />
Tabelle 11.5: Mathematische Funktionen<br />
465
466<br />
Datenbankprogrammierung<br />
Funktion Beschreibung<br />
MOD(n, m)<br />
n % m<br />
n MOD m<br />
PI() Die Konstante π<br />
POWER(x,y) Potenzrechnung (x hoch y).<br />
Funktionen zur Steuerung des Kontrollflusses<br />
Modulus, das ist der Rest einer Ganzzahldivision. Alle drei<br />
Schreibweisen sind völlig identisch im Verhalten.<br />
RADIANS(x) Umrechnung von Grad in Radiant der Zahl x.<br />
RAND(x) Ermittelt eine Zufallszahl, x ist dabei optional und<br />
bestimmt, wenn angegeben, den Startwert.<br />
ROUND(x, d) Rundet Werte mathematisch, die Angabe der Stellen d ist<br />
optional, ohne Angabe wird auf ganze Zahlen gerundet<br />
SIGN(x) Das Vorzeichen der Zahl x, gibt -1, 0 oder 1 zurück.<br />
SIN(x) Der Sinus der Zahl x.<br />
SQRT(x) Die Quadratwurzel der Zahl x.<br />
TAN(x) Der Tangens der Zahl x.<br />
TRUNCATE(x, d) Gibt die Zahl x, gekürzt auf d Dezimalstellen, zurück.<br />
Tabelle 11.5: Mathematische Funktionen (Forts.)<br />
Funktion Beschreibung<br />
ISNULL(expr) Wertet den Ausdruck expr aus und gibt 1 zurück, wenn der<br />
Ausdruck NULL ist, sonst 0.<br />
IFNULL(expr1, expr2) Wertet den Ausdruck expr1 aus und gibt expr2 zurück,<br />
wenn der Ausdruck NULL ist, sonst expr1<br />
IF(expr1,expr2,expr3) Wenn der Ausdruck expr1 Wahr ist, wird expr2 zurückgegeben,<br />
sonst expr3<br />
Tabelle 11.6: Diese Funktionen können die Auswahl Bedingungsabhängig ändern
Funktion Beschreibung<br />
CASE val<br />
WHEN cval THEN result<br />
[WHEN cval THEN<br />
result]<br />
[ELSE result]<br />
END<br />
Funktionen für aggregierende Berechnungen<br />
Der <strong>My</strong><strong>SQL</strong>-Dialekt<br />
Eine Mehrfachverzweigung, die etwa dem switch in PHP<br />
entspricht. Der Wert val wird mit cval verglichen und bei<br />
Gleichheit wird result zurückgegeben. Ansonsten wird mit<br />
dem nächsten Zweig fortgesetzt.<br />
NULLIF(expr1, expr2) Gibt NULL zurück, wenn expr1 gleich expr2 ist.<br />
Tabelle 11.6: Diese Funktionen können die Auswahl Bedingungsabhängig ändern (Forts.)<br />
Funktion Beschreibung<br />
AVG(Ausdruck) Der Durchschnitt der Felder.<br />
COUNT(Ausdruck) Die Anzahl der Felder.<br />
COUNT(DISTINCT Ausdruck) Die Anzahl eindeutiger Felder.<br />
COUNT (*) Repräsentiert die Anzahl aller Datensätze einer Tabelle.<br />
GROUP_CONCAT(Ausdruck) Verbundene Zeichenkette aller Teilfelder.<br />
SUM(Ausdruck) Die Summe der Felder (Addition).<br />
MAX(Ausdruck) Das Feld mit dem größten Wert bestimmt das Ergebnis.<br />
MIN(Ausdruck) Das Feld mit dem kleinsten Wert bestimmt das Ergebnis.<br />
STD(Ausdruck)<br />
STDDEV(Ausdruck)<br />
Statistische Standardabweichung aller Werte der Liste<br />
BIT_OR(Ausdruck) Führt ein bitweises Oder aus.<br />
BIT_AND(Ausdruck) Führt ein bitweises <strong>Und</strong> aus.<br />
BIT_XOR(Ausdruck) Führt ein bitweises Exklusives Oder aus.<br />
Tabelle 11.7: Aggregat-Funktionen<br />
467
468<br />
Datenbankprogrammierung<br />
Funktionen für Systeminformationen<br />
Funktion Beschreibung<br />
BENCHMARK(count, expr) Die Funktion führt einen Ausdruck expr so oft aus, wie count<br />
angibt. Bei der Ausführung auf Kommandozeile wird die<br />
gesamte Ausführungszeit ausgegeben. Man kann damit die<br />
Effizienz seiner Ausdrücke testen.<br />
CHARSET(str) Ermittelt, in welchem Zeichensatz eine Zeichenfolge<br />
definiert ist.<br />
COERCIBILITY(str) Ermittelt die Zugehörigkeit einer Zeichenfolge zu einem<br />
Vergleichszeichensatz. Gibt einen Wert zwischen 0 und 3<br />
zurück, wobei 0 explizite Vergleichbarkeit (höchster Rang),<br />
1 keine Vergleichbarkeit, 2 implizite Vergleichbarkeit und 3<br />
übergehende Vergleichbarkeit bedeutet.<br />
COLLATION(str) Ermittelt den Vergleichszeichensatz, der die Sortierkriterien<br />
bestimmt.<br />
CONNECTION_ID() Nummer des Threads, der die aktuelle Verbindung verarbeitet.<br />
CURRENT_USER() Aktueller <strong>My</strong><strong>SQL</strong>-Nutzername mit Rechnernamen.<br />
DATABASE() Gibt den Namen der aktuellen Datenbank aus.<br />
USER() SYSTEM_USER()<br />
SESSION_USER()<br />
Aktueller <strong>My</strong><strong>SQL</strong>-Nutzername<br />
FORMAT(n, d) Formatiert eine Zahl n mit Kommata als Tausendergruppensymbol<br />
und Punkt als Dezimaltrennzeichen mit d Dezimalstellen.<br />
FOUND_ROWS() Anzahl der Reihen, so wie die Abfrage ohne LIMIT erfolgt<br />
wäre, auch wenn LIMIT benutzt wurde.<br />
LAST_INSERT_ID() Gibt den zuletzt erzeugten Wert einer AUTO_INCREMENT-Spalte<br />
zurück.<br />
VERSION() Gibt die Versionsnummer des <strong>My</strong><strong>SQL</strong>-Server an.<br />
Tabelle 11.8: Die System- und <strong>In</strong>formationsfunktionen
Funktion Beschreibung<br />
Verschlüsselungsfunktionen<br />
Der <strong>My</strong><strong>SQL</strong>-Dialekt<br />
GET_LOCK(str, to) Erzeugt eine Verriegelung (Lock) mit dem Namen str und<br />
dem Zeitüberschreitungswert to.<br />
RELEASE_LOCK(str) Gibt die Verriegelung str wieder frei.<br />
IS_FREE_LOCK(str) Prüft, ob die Verriegelung str frei ist.<br />
INET_ATON(expr) Die Numerische Entsprechung einer als Zeichenkette angegebenen<br />
IP.<br />
INET_NTOA(expr) Die IP-Nummer in Punktschreibweise aus einer Zahl.<br />
UUID() Universal Unique Identifier für RPC-Verbindungen.<br />
Tabelle 11.8: Die System- und <strong>In</strong>formationsfunktionen (Forts.)<br />
Funktion Beschreibung<br />
PASSWORD(str) Erzeugt ein Kennwort zur Zeichenkette str.<br />
ENCRYPT(str, seed) Erzeugt ein Kennwort zur Zeichenkette str und mit dem<br />
Startwert seed. Nutzt das Unix-Kommando crypt. Unter<br />
Windows wird NULL zurückgegeben.<br />
ENCODE(str, pass) Einfaches Verschlüsselungsverfahren auf Basis des Kennworts<br />
pass. Zum Speichern sollten BLOB-Spalten verwendet<br />
werden.<br />
DECODE(str, pass)<br />
SHA(str)<br />
SHA1(str)<br />
Erstellt einen Hashwert nach SHA bzw. SHA1.<br />
MD5(str) Erstellt einen Hashwert nach MD5.<br />
DES_ENCRYPT(str, key) Triple-DES-Ver- und Entschlüsselung. Kann nur genutzt<br />
werden, wenn <strong>My</strong><strong>SQL</strong> mit SSL-Unterstützung kompiliert<br />
wurde.<br />
DES_DECRYPT(str, key)<br />
Tabelle 11.9: Verschlüsselungsfunktionen<br />
469
470<br />
Datenbankprogrammierung<br />
Funktion Beschreibung<br />
AES_ENCRYPT(str, key) AES-(Advanced Encryption Standard)-Ver- und Entschlüsselung.<br />
Kann nur genutzt werden, wenn <strong>My</strong><strong>SQL</strong> mit SSL-<br />
Unterstützung kompiliert wurde.<br />
AES_DECRYPT(str, key)<br />
Tabelle 11.9: Verschlüsselungsfunktionen (Forts.)<br />
Zeichenkettenfunktionen<br />
Funktion Beschreibung<br />
ASCII(str) Gibt den ASCII-Code des Zeichens str zurück. Hat str<br />
mehr als ein Zeichen, wird nur das erste Zeichen überprüft.<br />
CHAR(n,...) Wandelt die Zahlen n in die entsprechenden ASCII-Zeichen<br />
um. Mehrere Argumente werden zu einer Zeichenkette<br />
kombiniert.<br />
CHAR_LENGTH(str)<br />
CHARACTER_LENGTH(str)<br />
Die echte Zeichenlänge einer Zeichenkette. Manche Zeichen<br />
bestehen aus mehreren Bytes, sodass LENGTH die falsche<br />
Länge liefert, während CHAR_LENGTH die erwartete<br />
Zahl liefert.<br />
COMPRESS(str) Komprimiert eine Zeichenfolgen nach dem ZIP-Verfahren.<br />
CONCAT(str,...) Verknüpft alle Argumente zu einer Zeichenkette. Wenn<br />
eines der Argumente NULL ist, wird NULL zurückgegeben.<br />
ELT(n, str1, str2,...) Gibt die durch n bezeichnete Zeichenkette zurück: str1,<br />
wenn n=1 usw.<br />
EXPORT_SET(bit, on, off,<br />
sep, number)<br />
Tabelle 11.10: Zeichenkettenfunktionen<br />
Exportiert einen Bitwert in eine Zeichenfolge, in der jedes<br />
Zeichen ein Bit repräsentiert. Das Zeichen on bestimmt<br />
Bits mit dem Wert 1, off steht für 0, sep bestimmt ein<br />
Trennzeichen und number die Anzahl der Bits, die aus<br />
dem Wert bit ausgewertet werden sollen.
Funktion Beschreibung<br />
Der <strong>My</strong><strong>SQL</strong>-Dialekt<br />
FIELD(str,str1,str2..) Gibt die Position von str in str1, str2 usw. zurück: Wenn<br />
str2=str, wird 2 zurückgegeben.<br />
FIND_IN_SET(str, list) Gibt die Position von str in der Liste list zurück. Die Liste<br />
besteht aus kommaseparierten Werten.<br />
INSERT(str,st,len,new) Fügt len Zeichen der Zeichenkette new an der Stelle st der<br />
Zeichenkette str ein.<br />
INSTR(str, sub) Entspricht LOCATE, nur die Argumente sind vertauscht.<br />
LCASE, LOWER(str) Wandelt in Kleinbuchstaben um.<br />
LEFT(str, len) Gibt len Zeichen vom linken Ende der Zeichenkette str<br />
zurück.<br />
LENGTH(str) Länge der Zeichenketten str.<br />
LOAD_FILE(name) Lädt eine Datei und gibt den <strong>In</strong>halt als Zeichenkette<br />
zurück.<br />
LOCATE(sub, str)<br />
POSITION(sub IN str)<br />
Bestimmt die Position der Zeichenkette sub in der Zeichenkette<br />
str.<br />
LPAD(str,len,pad) Fügt pad links an str an, gibt jedoch nur len Zeichen<br />
zurück.<br />
LTRIM(str) Entfernt Leerzeichen vom linken Ende.<br />
MAKE_SET(bits,list) Wählt die Elemente der Liste list anhand der gesetzten<br />
Bits in bits aus.<br />
MID(str, pos, len) Gibt len Zeichen von Position pos an der Zeichenkette str<br />
zurück.<br />
QUOTE() Fügt Anführungszeichen hinzu und markiert solche<br />
im Text mit einem Backslash. NULL-Werte werden als<br />
Zeichenfolge "NULL" zurückgegeben.<br />
REPEAT(str, count) Wiederholt die Zeichenkette str count mal.<br />
REPLACE(str,from,to) Ersetzt alle Vorkommen von from in der Zeichenkette str<br />
durch to.<br />
Tabelle 11.10: Zeichenkettenfunktionen (Forts.)<br />
471
472<br />
Datenbankprogrammierung<br />
Funktion Beschreibung<br />
REVERSE(str) Dreht eine Zeichenkette um.<br />
RIGHT(str, len) Gibt len Zeichen vom rechten Ende der Zeichenkette str<br />
zurück.<br />
RPAD(str,len,pad) Fügt pad rechts an str an, gibt jedoch nur len Zeichen<br />
zurück.<br />
RTRIM(str) Entfernt Leerzeichen vom rechten Ende.<br />
SOUNDEX(str) Gibt die Lautfolge für str zurück.<br />
SPACE(n) Gibt n Leerzeichen zurück.<br />
SUBSTRING(str FROM len<br />
SUBSTRING(str,pos,len)<br />
SUBSTRING(str FROM pos<br />
FOR len)<br />
Andere Schreibweisen für RIGHT und MID.<br />
SUBSTRING(str, pos) Gibt Teile von str ab Position pos zurück.<br />
SUBSTRING_INDEX(str,<br />
delimiter, count)<br />
TRIM<br />
BOTH|LEADING|TRAILING<br />
rem FROM str<br />
Gibt den linken Teil einer Zeichenkette zurück, nachdem<br />
count mal das Zeichen delimiter aufgetreten ist.<br />
BOTH entspricht TRIM, LEADING entspricht LTRIM, TRAILING<br />
entspricht RTRIM, FROM ist optional, rem ist optional und<br />
steht für das zu entfernende Zeichen, str wird bearbeitet.<br />
TRIM(str) Entfernt Leerzeichen von beiden Enden der Zeichenkette<br />
str.<br />
UCASE, UPPER(str) Wandelt Zeichen in Großbuchstaben um.<br />
UNCOMPRESS(str) Hebt ein Komprimierung wieder auf (siehe auch<br />
COMPRESS).<br />
Tabelle 11.10: Zeichenkettenfunktionen (Forts.)
Zahlenformatierungen<br />
Funktion Beschreibung<br />
Datums- und Zeitfunktionen<br />
Der <strong>My</strong><strong>SQL</strong>-Dialekt<br />
CONV(n,from,to) Konvertiert Zahlen zwischen verschiedenen Zahlenbasen.<br />
Zurückgegeben wird immer eine Zeichenkette mit der ermittelten<br />
Zahl. n ist die Zahl, from die ursprüngliche Zahlenbasis, to<br />
die Zielbasis.<br />
BIN(n) Gibt eine Zahl n als Zeichenkette im Binärformat zurück,<br />
BIN(7) ergibt beispielsweise »111«.<br />
BIT_LENGTH(n) Gibt die Länge einer Zeichenkette in Bits zurück.<br />
OCT(n) Gibt eine Zahl n als Zeichenkette im Oktalformat zurück.<br />
HEX(n) Gibt eine Zahl n als Zeichenkette im Hexadezimalformat<br />
zurück.<br />
UNHEX() Umkehrfunktion zu HEX<br />
Tabelle 11.11: Zeichenkettenfunktionen für Zahlen<br />
Funktion Beschreibung<br />
DAYOFWEEK(date) Der Tag der Woche, 1 ist Sonntag (1 – 7).<br />
WEEKDAY(date) Der Tag der Woche, 0 ist Montag (0 – 6).<br />
DAYOFMONTH(date) Der Tag des Monats (1 – 31).<br />
DAYOFYEAR(date) Der Tag des Jahres (1 – 366).<br />
MONTH(date) Der Monat (1 – 12).<br />
DAYNAME(date) Der Wochentag (englisch, ausgeschrieben).<br />
MONTHNAME(date) Der Monat (englisch, ausgeschrieben).<br />
QUARTER(date) Das Quartal (1 – 4).<br />
Tabelle 11.12: Datums- und Zeitfunktionen<br />
473
WEEK(date)<br />
WEEK(date, first)<br />
474<br />
Datenbankprogrammierung<br />
Funktion Beschreibung<br />
YEAR(date) Das Jahr (1000 – 9999).<br />
HOUR(time) Die Stunde (0 – 23).<br />
MINUTE(time) Die Minute (0 – 59).<br />
SECOND(time) Die Sekunde (0 – 59).<br />
Die Woche im Jahr (0 – 52). Das optionale Argument<br />
first bestimmt, welcher Wochentag als Beginn gezählt<br />
wird. 0 entspricht dem Sonntag.<br />
PERIOD_ADD(p,n) Addiert n Monate zur Periode p. Die Periode wird im Format<br />
YYYYMM oder YYMM erwartet. Zurückgegeben wird<br />
immer die Langform YYYYMM.<br />
PERIOD_DIFF(p1,p2) Gibt die Differenz in Monaten zwischen p1 und p2<br />
zurück<br />
TO_DAYS(date) Die Anzahl der Tage seit dem Jahr 0.<br />
FROM_DAYS(dn) Ermittelt ein Datum aus der Tageszahl dn.<br />
CURDATE()<br />
CURRENT_DATE<br />
CURTIME()<br />
CURRENT_TIME<br />
NOW()<br />
SYSDATE()<br />
CURRENT_TIMESTAMP<br />
Das aktuelle Datum (Systemzeit des Servers).<br />
Die aktuelle Zeit (Systemzeit des Servers)<br />
Datum und Uhrzeit (Systemzeit des Servers)<br />
UNIX_TIMESTAMP Unix Timestamp (Sekunden in GMT seit den 1.1.1970, 0<br />
Uhr). Die Funktion wird auch von der Windows-Version<br />
unterstützt.<br />
FROM_UNIXTIME(stp) Gibt ein Datum entsprechend dem Unix Timestamp stp<br />
zurück.<br />
FROM_UNIXTIME(stp,<br />
format)<br />
Tabelle 11.12: Datums- und Zeitfunktionen (Forts.)<br />
Gibt ein Datum entsprechend dem Unix Timestamp stp<br />
zurück. Das Datum ist entsprechend format formatiert.
Funktion Beschreibung<br />
Datumsformatierungen<br />
Der <strong>My</strong><strong>SQL</strong>-Dialekt<br />
SEC_TO_TIME(sec) Rechnet die Angabe in Sekunden in das Format<br />
HH:MM:SS um.<br />
TIME_TO_SEC(time) Rechnet eine Zeitangabe in Sekunden um.<br />
Tabelle 11.12: Datums- und Zeitfunktionen (Forts.)<br />
Typ-Konstante Bedeutung Formatangabe<br />
SECOND Sekunde ss<br />
MINUTE Minute mm<br />
HOUR Stunde hh<br />
DAY Tag DD<br />
MONTH Monat MM<br />
YEAR Jahr YY<br />
MINUTE_SECONDS Minute und Sekunde mm:ss<br />
HOUR_MINUTE Stunde und Minute hh:mm<br />
DAY_HOUR Tag und Stunde DD hh<br />
YEAR_MONTH Jahr und Monat YY-MM<br />
HOUR_SECOND Stunde, Minute, Sekunde hh:mm:ss<br />
DAY_MINUTE Tag, Stunde, Minute DD hh:mm<br />
DAY_SECONDS Tag, Stunde, Minute, Sekunde DD hh:mm:ss<br />
Tabelle 11.13: Zeittypen für Datumsberechnungen<br />
475
476<br />
Datenbankprogrammierung<br />
Funktion Berechnung<br />
DATE_FORMAT(date, format) Formatiert den Wert date mit dem Format format, dessen<br />
Elemente wurden in der vorherigen Tabelle bereits<br />
beschrieben.<br />
TIME_FORMAT(time, format) Formatiert den Wert time mit dem Format format, dessen<br />
Elemente wurden in der vorherigen Tabelle bereits<br />
beschrieben.<br />
Tabelle 11.<strong>14</strong>: Datumsformatierungen<br />
Code Bedeutung (Wertebereich) Code Bedeutung (Wertebereich)<br />
%M Monatsname (January – December) %k Stunde (0 – 23) ohne führende Null<br />
%W Wochenname (Monday – Sunday) %h Stunde (01 – 12) mit führender<br />
Null<br />
%D Monat mit engl. Suffix<br />
(1 st , 2 nd , 3 rd usw.)<br />
%I Stunde (1 – 12) ohne führende Null<br />
%Y Jahr mit 4 Stellen %l Minuten (0 – 59)<br />
%y Jahr mit 2 Stellen %i Minuten (00 – 59) mit führender<br />
Null<br />
%a Abgekürzter Wochentag<br />
(Mon – Sun)<br />
%n Zeit, 12-Stunden-Format: hh:mm:ss<br />
AM|PM<br />
%d Tag des Monats (00 – 31) %T Zeit, 24-Stunden-Format: hh:mm:ss<br />
%e Tag des Monats (0 – 31) %S Sekunde (00 – 59) mit führender<br />
Null<br />
%m Monat (00 – 12) mit führender Null %s Sekunde (0 – 59)<br />
%c Monat (0 – 12) ohne führende Null %p AM oder PM<br />
%b Abgekürzter Monatsname<br />
(Jan–Dec)<br />
%w Wochentag<br />
(0=Sonntag, 6=Samstag)<br />
%j Tag des Jahres (000 – 366) %U Woche, Sonntag ist der erste Tag<br />
der Woche (00 – 52)<br />
Tabelle 11.15: Platzhalter für Datumsformatierungen
Erste Schritte mit <strong>My</strong><strong>SQL</strong> und <strong>My</strong><strong>SQL</strong>i<br />
Code Bedeutung (Wertebereich) Code Bedeutung (Wertebereich)<br />
%H Stunde (00 – 23) mit führender<br />
Null<br />
%% Prozentzeichen<br />
Tabelle 11.15: Platzhalter für Datumsformatierungen (Forts.)<br />
11.4 Erste Schritte mit <strong>My</strong><strong>SQL</strong> und <strong>My</strong><strong>SQL</strong>i<br />
<strong>My</strong><strong>SQL</strong>i (<strong>My</strong><strong>SQL</strong> improved) ist eine neue Erweiterung von PHP zur besseren<br />
Unterstützung von <strong>My</strong><strong>SQL</strong>. Im Bundle mit dem neuen, objektorientierten Ansatz<br />
und der neuen <strong>My</strong><strong>SQL</strong>-Version 4 ergibt sich ein außerordentlich leistungsfähiges<br />
System.<br />
<strong>My</strong><strong>SQL</strong>i vorbereiten<br />
Seit PHP5 gehört die <strong>My</strong><strong>SQL</strong>-Unterstützung aus rechtlichen Gründen nicht mehr<br />
zum integrierten Paket. <strong>In</strong> PHP 4 waren die <strong>My</strong><strong>SQL</strong>-Module noch fest in den<br />
Kern kompiliert. Mit der Version 5 ist nun alles wieder wie bereits bei PHP3 – man<br />
muss die <strong>My</strong><strong>SQL</strong>-Dateien zusätzlich einbinden. Auf dem Entwicklungssystem<br />
unter Windows erfolgt dies durch das Auskommentieren der entsprechenden Zeile<br />
in der Datei php.ini:<br />
extension=php_mysql.dll<br />
%u Woche, Montag ist der erste Tag der<br />
Woche (00 – 52)<br />
Dann haben Sie noch die Wahl, statt der alten <strong>My</strong><strong>SQL</strong>-Module die neuen, objektorientierten<br />
<strong>My</strong><strong>SQL</strong>i-Dateien zu nutzen:<br />
extension=php_mysqli.dll<br />
<strong>In</strong> diesem Buch werden die neuen Module vorgestellt. Über weite Strecken ist<br />
Syntax und Nutzung praktisch identisch, sodass eine Umstellung nicht schwer ist.<br />
477
478<br />
Datenbankprogrammierung<br />
Verbindung testen<br />
Nach der <strong>In</strong>stallation von <strong>My</strong><strong>SQL</strong> steht ein kurzer Test an, ob alles geklappt hat.<br />
Unter XP muss dazu der entsprechende Dienst gestartet sein, unter Linux der<br />
<strong>My</strong><strong>SQL</strong>-Daemon.<br />
Dann kommt folgendes Skript zum Zuge, um alles zu testen:<br />
Listing 11.1: mysqliconnect.php – Ein erster Versuch mit <strong>My</strong><strong>SQL</strong> und PHP<br />
$mysqli = new mysqli("localhost", "root", "", "test");<br />
$query = $mysqli->query("SELECT version() AS version");<br />
$result = $query->fetch_assoc();<br />
echo "Wir arbeiten mit <strong>My</strong><strong>SQL</strong> Version {$result['version']}";<br />
$mysqli->Close();<br />
Das Skript geht davon aus, dass das Standardkennwort (leer) und der Benutzername<br />
(»root«) nicht geändert wurden und dass die Datenbank auf derselben<br />
Maschine wie der Webserver läuft (»localhost«). Haben Sie eine andere <strong>In</strong>stallation,<br />
müssen Sie die Daten in der ersten Zeile entsprechend anpassen.<br />
Die Klasse mysqli1 stellt nun den Zugriff auf die neuen Funktionen bereit. Im Beispiel<br />
führt die <strong>In</strong>stanziierung des Objekts in $mysqli auch gleich zum Öffnen der<br />
Verbindung. Die <strong>SQL</strong>-Abfrage ermittelt dann die Versionsnummer des Datenbankservers:<br />
SELECT version() AS version<br />
Die Methode zur Abfrage der Datenbank heißt query:<br />
$mysqli->query()<br />
Wenn Ergebnisse entstehen, kann man diese mit fetch_assoc in ein assoziatives<br />
Array überführen:<br />
$result = $query->fetch_assoc();<br />
Der Zugriff auf das Array entspricht wieder einfachem PHP, als Schlüsselname<br />
taucht der Alias der <strong>SQL</strong>-Abfrage auf.<br />
Hat alles geklappt, wird die Verbindung wieder geschlossen. Die Ausgabe sollte<br />
nun in etwa wie nachfolgend gezeigt aussehen:<br />
1 Es gibt auch nach wie vor einen prozeduralen Zugriff, der hier nicht weiter betrachtet wird.
Erste Schritte mit <strong>My</strong><strong>SQL</strong> und <strong>My</strong><strong>SQL</strong>i<br />
Nun kann man voll loslegen und praktisch mit der Datenbank arbeiten.<br />
Die folgenden Skripte wurden mit <strong>My</strong><strong>SQL</strong> 4.1 getestet. Die einwandfreie<br />
Funktionsweise von <strong>My</strong><strong>SQL</strong> 5 kann derzeit nicht garantiert werden.<br />
Außerdem ist mindestens die Version PHP5.1 für die <strong>My</strong><strong>SQL</strong>i-<br />
Erweiterungen zu empfehlen. Die erste Final, PHP5.0.0, versagte bei<br />
einigen Abfragen.<br />
Genereller Datenbankzugriff<br />
Für die folgenden Skript wird immer wieder dieselbe Methode zum Datenbankzugriff<br />
verwendet und als <strong>In</strong>clude-Datei eingebunden. Dies erleichtert Änderungen:<br />
Listing 11.2: mysqli.inc.php – <strong>In</strong>halt der <strong>In</strong>clude-Datei, noch ohne Fehlermanagement<br />
<br />
Die Variable $mysqli wird in allen Skripten dieses Kapitels verwendet. Das Einbinden<br />
erfolgt über folgende Zeile:<br />
include("mysqli.inc.php");<br />
Diese Anweisung wird nicht in jedem Listing immer wieder abgebildet.<br />
Mit der Datenbank arbeiten<br />
Abbildung 11.1:<br />
Alles neu: PHP5 mit <strong>My</strong><strong>SQL</strong>i und<br />
<strong>My</strong><strong>SQL</strong> 5.0-alpha<br />
Um mit der Datenbank arbeiten zu können, werden zuerst Tabellen benötigt.<br />
Diese können Sie entweder mit einem Werkzeug oder per Skript anlegen. Als<br />
Werkzeug kommen unter Windows das <strong>My</strong><strong>SQL</strong>CC (<strong>My</strong><strong>SQL</strong> Control Center)<br />
oder php<strong>My</strong>Admin in Frage. Dieser Abschnitt zeigt alle nötigen Schritte in Skriptform.<br />
479
480<br />
Datenbankprogrammierung<br />
Tabellen vorbereiten<br />
Zuerst werden die passenden Tabellen benötigt. <strong>My</strong><strong>SQL</strong> verfügt über eine sehr<br />
gute Funktion beim Anlegen von Tabellen. Es kann prüfen, ob die Tabelle bereits<br />
existiert. Damit muss man sich erstmal nicht darum kümmern, ob das Skript<br />
bereits aufgerufen wurde.<br />
Die beiden Tabellen, die hier benötigt werden, enthalten die Bundesländer und<br />
Wetterdaten. Sie haben folgende Definitionen:<br />
Listing 11.3: Definition der Wetter-Tabelle<br />
CREATE TABLE IF NOT EXISTS wetter (<br />
ID bigint(20) NOT NULL auto_increment,<br />
Stadt varchar(100) NOT NULL default '',<br />
Hoch int(2) NOT NULL default '0',<br />
Tief int(2) NOT NULL default '0',<br />
BundeslandID bigint(20) default '0',<br />
PRIMARY KEY (ID),<br />
KEY Stadt<strong>In</strong>dex (Stadt)<br />
);<br />
Listing 11.4: Definition der Tabelle der Bundesländer<br />
CREATE TABLE IF NOT EXISTS bundesland (<br />
ID int(2) NOT NULL auto_increment,<br />
Name varchar(30) NOT NULL default '',<br />
PRIMARY KEY (ID)<br />
);<br />
Das folgende PHP-Skript nutzt diese Anweisungen, um die Tabellen anzulegen.<br />
Die Angabe von IF NOT EXISTS verhindert, dass die Tabellen erneut erzeugt werden,<br />
wenn sie bereits existieren.<br />
Listing 11.5: mysqlicreatetables.php – Die benötigten Datentabellen erzeugen<br />
$tables['Wetter'] =
Erste Schritte mit <strong>My</strong><strong>SQL</strong> und <strong>My</strong><strong>SQL</strong>i<br />
PRIMARY KEY (ID),<br />
KEY Stadt<strong>In</strong>dex (Stadt)<br />
);<br />
TABLE1;<br />
$tables['Bundesland'] =
482<br />
Datenbankprogrammierung<br />
echo "Fehler: {$mysqli->error}";<br />
Provozieren Sie einen Fehler, indem Sie die Syntax der <strong>SQL</strong>-Anweisung ein<br />
wenig »unqualifiziert« verändern. Sie erhalten dann beispielsweise folgende Ausgabe:<br />
Die Tabellen füllen<br />
Es gibt mehrere Wege, Tabellen zu füllen:<br />
Formulare werden verwendet – dann gibt der Benutzer die Daten von Hand<br />
ein.<br />
Abfrage einer anderen Datenbank oder Tabelle.<br />
Durch das Auslesen von Textdateien.<br />
Durch den Import von XML.<br />
Für die Generierung der Bundesländer bietet es sich an, eine Textdatei zu verwenden,<br />
die die Namen enthält. Das ist einfacher als alle INSERT-Anweisungen aufzuschreiben.<br />
Die nötigen Techniken wurden alle bereits behandelt. Zuerst die<br />
Textdatei:<br />
Listing 11.6: bl.txt im Verzeichnis /data – Textdatei mit Bundesländern<br />
'Baden-Württemberg'<br />
'Bayern'<br />
'Berlin'<br />
'Brandenburg'<br />
'Bremen'<br />
'Hamburg'<br />
'Hessen'<br />
'Mecklenburg-Vorpommern'<br />
'Niedersachsen'<br />
'Nordrhein-Westfalen'<br />
'Rheinland-Pfalz'<br />
'Saarland'<br />
'Sachsen'<br />
Abbildung 11.3:<br />
Ausgabe mit<br />
Fehlermeldung
'Sachsen-Anhalt'<br />
'Schleswig-Holstein '<br />
'Thüringen'<br />
Erste Schritte mit <strong>My</strong><strong>SQL</strong> und <strong>My</strong><strong>SQL</strong>i<br />
Auch für ein paar Wetterdaten wurde eine Textdatei vorbereitet. Da hier mehrere<br />
Spalten existieren, wurde ein Trennzeichen definiert, das Komma. Die erste Zeile<br />
enthält die Feldnamen:<br />
Listing 11.7: wetter.txt im Verzeichnis /data – Textdatei mit Wetterdaten<br />
Stadt,Hoch,Tief,BundeslandID<br />
'Berlin',22,12,3<br />
'Hamburg',20,10,6<br />
'Stuttgart',24,18,1<br />
'München',23,17,2<br />
'Regensburg',23,19,2<br />
'Dresden',19,12,13<br />
'Leipzig',19,12,13<br />
'Wittenberge',19,12,4<br />
'Angermünde',18,12,4<br />
'Frankfurt/Oder',19,13,4<br />
'Cottbus',22,17,4<br />
'Hof',19,<strong>14</strong>,2<br />
'Nürnberg',19,11,2<br />
'Würzburg',19,16,2<br />
'Augsburg',22,18,2<br />
Die einfachen Anführungszeichen in den Datendateien vereinfachen<br />
die Verarbeitung erheblich. Dies ist hier vor allem gemacht worden, um<br />
die ersten Skripte überschaubar zu halten. <strong>In</strong> der Praxis gibt es freilich<br />
Lösungen, die beliebige Daten korrekt einfügen.<br />
Das folgende Skript zeigt eine mögliche Lösung:<br />
Listing 11.8: mysqliinsert.php: Programmgesteuert Datensätze einfügen<br />
$path = 'data';<br />
$imports = array('Wetter' => 'wetter.txt',<br />
'Bundesland' => 'bl.txt');<br />
if (is_object($mysqli))<br />
{<br />
foreach ($imports as $table => $file)<br />
483
484<br />
{<br />
Datenbankprogrammierung<br />
$content = file("$path/$file");<br />
$fields = array_shift($content);<br />
$mysqli->query("TRUNCATE $table");<br />
foreach ($content as $line)<br />
{<br />
$sql = "INSERT INTO $table ($fields) VALUES ($line)";<br />
$result = $mysqli->query($sql);<br />
if ($result === FALSE)<br />
{<br />
echo "Konnte Anweisung nicht ausführen";<br />
echo "Fehler: {$mysqli->error}";<br />
}<br />
else<br />
{<br />
echo "Anweisung ausgeführt: $sql";<br />
}<br />
}<br />
}<br />
}<br />
Dieses Skript beginnt mit der Definition eines Arrays, das die Tabellennamen und<br />
Dateinamen enthält und miteinander verknüpft:<br />
$imports = array('Wetter' => 'wetter.txt', 'Bundesland' => 'bl.txt');<br />
Der Vorteil der vorgestellten Lösung liegt in ihrer leichten Erweiterbarkeit. Um<br />
weitere Tabelle mit Daten zu beschicken, muss man lediglich dieses Array erweitern<br />
und natürlich die Daten bereitstellen.<br />
Dann durchläuft die erste foreach-Schleife alle Elemente dieses Arrays:<br />
foreach ($imports as $table => $file)<br />
So erhält man Tabellen- und Dateinamen. Dann werden die Daten in ein weiteres<br />
Array überführt, wozu die Funktion file hervorragend geeignet ist:<br />
$content = file("$path/$file");<br />
Die erste Zeile enthält die Feldnamen. Das erste Element eines Arrays lässt sich<br />
mit array_shift extrahieren, sodass in $fields die Feldliste steht:<br />
$fields = array_shift($content);
Erste Schritte mit <strong>My</strong><strong>SQL</strong> und <strong>My</strong><strong>SQL</strong>i<br />
Dann wird die jeweils zu bearbeitende Tabelle gelöscht, um zu verhindern, dass<br />
bei mehrfachem Aufruf des Skripts die Daten doppelt erscheinen:<br />
$mysqli->query("TRUNCATE $table");<br />
Hier ist etwas Vorsicht angebracht! Wenn Sie mit anderen Skripten der<br />
Wetter-Tabelle weitere Daten hinzugefügt haben, gehen diese durch<br />
den Aufruf von TRUNCATE unwiderruflich verloren.<br />
Für den Rest der Datei (array_shift extrahiert nicht nur, sondern entfernt auch<br />
gleich die erste Zeile) werden nun die Zeilen gelesen und zu INSERT-Anweisungen<br />
verarbeitet:<br />
foreach ($content as $line)<br />
$sql = "INSERT INTO $table ($fields) VALUES ($line)";<br />
Der Aufbau einer <strong>SQL</strong>-Anweisung in einer eigenen Variable ist sinnvoll, um das<br />
dynamisch konstruierte Gebilde leicht überwachen zu können. Die fertige Anweisung<br />
wird dann an den <strong>SQL</strong>-Server gesendet:<br />
$result = $mysqli->query($sql);<br />
Zuletzt folgt noch die bereits bekannte Fehlerausgabe.<br />
Mit den so erstellten Tabellen kann nun gearbeitet werden. Die folgenden<br />
Abschnitte behandeln die bereits im Einführungsteil zu <strong>SQL</strong> präsentierten Abfragen<br />
im praktischen Kontext eines PHP-Skripts.<br />
Einfache Abfragen<br />
Die Abfragetechnik in PHP folgt immer ein und demselben Schema:<br />
1. Verbinden mit der Datenbank.<br />
2. Senden der Abfrage und Erhalt des Ergebnisobjekts.<br />
3. Überführen des Ergebnisobjekts in ein Array.<br />
4. Ausgaben oder Verarbeiten des Arrays.<br />
<strong>In</strong> selteneren Fällen werden nur einzelne Daten abgefragt und ohne Arrays gearbeitet.<br />
Aufgrund der starken Arrayfunktionen in PHP ist die Nutzung jedoch meist<br />
angebracht.<br />
485
486<br />
Datenbankprogrammierung<br />
Listing 11.9: mysqlselectfrom.php – Einfache Abfrage mit Ausgabe<br />
if (is_object($mysqli))<br />
{<br />
$sql = "SELECT Stadt, Hoch, Tief FROM Wetter";<br />
$result = $mysqli->query($sql);<br />
while ($rs = $result->fetch_assoc())<br />
{<br />
echo
Komplexe Abfragen<br />
Erste Schritte mit <strong>My</strong><strong>SQL</strong> und <strong>My</strong><strong>SQL</strong>i<br />
Abbildung 11.4:<br />
Ausgabe einer Datenbanktabelle in formatiertem<br />
HTML<br />
Als nächstes sollen die durchschnittlichen Höchst- und Tiefsttemperaturen ermittelt<br />
werden. Dazu eignet sich die Aggregat-Funktion AVG:<br />
Listing 11.10: mysqlselectavgfrom.php – Durchschnittswerte ermitteln<br />
$sql = "SELECT AVG(Hoch) AS MittelHoch, <br />
AVG(Tief) AS MittelTief FROM Wetter";<br />
$result = $mysqli->query($sql);<br />
$data = $result->fetch_object();<br />
echo "Die mittlere Höchsttemperatur <br />
beträgt {$data->MittelHoch} °C";<br />
echo "Die mittlere Tiefsttemperatur <br />
beträgt {$data->MittelTief} °C";<br />
487
488<br />
Datenbankprogrammierung<br />
Eingesetzt wird hier die Methode fetch_object. Sie gibt den aktuellen Datensatz<br />
als Objekt zurück, oder FALSE, falls keine Daten mehr da sind. Da hier nur skalare<br />
Werte abgefragt werden, ist eine Schleife nicht erforderlich.<br />
Das Objekt enthält eine Eigenschaft für jedes Feld, der Name entspricht auch dem<br />
Feldnamen:<br />
$data->MittelHoch<br />
Im Beispiel wurden die Namen gegenüber den originalen Spaltennamen noch<br />
durch den Alias-Operator AS geändert. Das ist sinnvoll, wenn die Spaltennamen<br />
selbst auch abgefragt werden sollen.<br />
Abbildung 11.5:<br />
Berechnung der Durchschnittstemperaturen<br />
Das folgende Beispiel zeigt, wie gleichzeitig die höchsten und niedrigsten Werte<br />
ermittelt werden.<br />
Listing 11.11: mysqlselectmimaxfrom.php – Minimale und maximale Temperaturen<br />
$sql = "SELECT AVG(Hoch) AS MittelHoch, <br />
AVG(Tief) AS MittelTief, <br />
MIN(Tief) AS Tief, <br />
MAX(Hoch) AS Hoch <br />
FROM Wetter";<br />
$result = $mysqli->query($sql);<br />
$data = $result->fetch_object();<br />
echo "Die mittlere Höchsttemperatur <br />
beträgt {$data->MittelHoch} °C";<br />
echo "Die mittlere Tiefsttemperatur <br />
beträgt {$data->MittelTief} °C";<br />
echo "Die höchste Höchsttemperatur beträgt {$data->Hoch} °C";<br />
echo "Die niedrigste Tiefsttemperatur beträgt {$data->Tief} °C";<br />
Das Prinzip der Abfrage entspricht hier dem vorhergehenden Beispiel.<br />
Abbildung 11.6:<br />
Der höchste und niedrigste Wert werden<br />
ermittelt
Erste Schritte mit <strong>My</strong><strong>SQL</strong> und <strong>My</strong><strong>SQL</strong>i<br />
Bei der Ausgabe fällt auf, dass die Kommastellen wenig praxistauglich sind. Bevor<br />
Sie jetzt auf die Idee kommen, dafür printf oder number_format einzusetzen, ist<br />
ein Blick in die Funktionssammlung von <strong>My</strong><strong>SQL</strong> interessant. Hier wird man bei<br />
ROUND fündig:<br />
Listing 11.12: mysqlselectroundfrom.php – Rundung der Ausgabewerte vor der Ausgabe<br />
$sql = "SELECT ROUND(AVG(Hoch),2) AS MittelHoch, <br />
ROUND(AVG(Tief),2) AS MittelTief<br />
FROM Wetter";<br />
$result = $mysqli->query($sql);<br />
$data = $result->fetch_object();<br />
echo "Die mittlere Höchsttemperatur <br />
beträgt {$data->MittelHoch} °C";<br />
echo "Die mittlere Tiefsttemperatur <br />
beträgt {$data->MittelTief} °C";<br />
Diese Ausgabe ist schon eher überzeugend, allerdings muss <strong>My</strong><strong>SQL</strong> vorerst bei der<br />
sprachabhängigen Darstellung passen:<br />
Verknüpfungen abfragen<br />
Abbildung 11.7:<br />
Gerundete Temperaturwerte<br />
Als nächstes soll wieder eine Ausgabe der Temperaturen der einzelnen Städte<br />
erfolgen, jedoch mit der Angabe des jeweiligen Bundeslandes.<br />
Listing 11.13: mysqlselectjoin1.php: Zusatzinformationen aus verknüpfter Tabelle holen<br />
$sql = "SELECT Stadt, Hoch, Tief, Name AS Bundesland <br />
FROM Wetter W JOIN Bundesland B <br />
ON W.BundeslandID = B.ID";<br />
$result = $mysqli->query($sql);<br />
while($rs = $result->fetch_object())<br />
{<br />
echo "{$rs->Stadt} ({$rs->Bundesland}):";<br />
echo "↑ {$rs->Hoch}°C, ↓ {$rs->Tief}°C";<br />
}<br />
Die Abfrageform in PHP ändert sich hier nicht – die gesamte Arbeit erledigt die<br />
<strong>SQL</strong>-Anweisung. Die Ausgabe zeigt ein für den geringen Aufwand durchaus<br />
489
490<br />
Datenbankprogrammierung<br />
respektables Ergebnis (die Pfeile werden durch die Entitäten ↑ und ↓<br />
erzeugt):<br />
Abbildung 11.8:<br />
<strong>In</strong>formationen aus zwei Tabellen: Städte und Bundesländer<br />
Etwas kniffliger wird es, wenn die Verknüpfung mit Aggregierungen verbunden<br />
werden soll. So könnte man die mittleren Temperaturen in einem Bundesland<br />
abfragen. Dazu werden die Städte aus demselben Bundesland gruppiert und aus<br />
den Werten einer Gruppe der Durchschnittswert berechnet.<br />
Listing 11.<strong>14</strong>: mysqlselectjoingroup.php – Mittlere Temperaturen nach Bundesland<br />
$sql = "SELECT ROUND(AVG(Hoch),2) AS MittelHoch, <br />
ROUND(AVG(Tief),2) AS MittelTief, <br />
Name AS Bundesland
Referenz <strong>My</strong><strong>SQL</strong>i<br />
FROM Wetter W JOIN Bundesland B <br />
ON W.BundeslandID = B.ID <br />
GROUP BY W.BundeslandID <br />
";<br />
$result = $mysqli->query($sql);<br />
while($rs = $result->fetch_object())<br />
{<br />
echo "{$rs->Bundesland}:";<br />
echo "↑ {$rs->MittelHoch}°C,";<br />
echo "↓ {$rs->MittelTief}°C";<br />
}<br />
Der Trick besteht hier in der Anwendung von GROUP BY. Erst nach der Gruppierung<br />
werden die Aggregat-Funktionen zur Berechnung des Durchschnitts angewendet.<br />
11.5 Referenz <strong>My</strong><strong>SQL</strong>i<br />
Alle Methoden werden auf einer <strong>In</strong>stanz der Klasse mysqli oder auf einem Resultatobjekt<br />
ausgeführt, beispielsweise:<br />
$mi = new mysqli('localhost', 'root', '', 'test');<br />
$mi->commit();<br />
Alternativ ist immer auch der direkte prozedurale Aufruf möglich:<br />
mysqli_commit();<br />
Abbildung 11.9:<br />
Mittelwerte der Temperaturen pro Bundesland<br />
491
492<br />
Datenbankprogrammierung<br />
Eigenschaft Beschreibung<br />
affected_rows Anzahl der Datensätze, die von der letzten Anweisung betroffen<br />
waren (nur für UPDATE/INSERT/REPLACE, nicht jedoch für<br />
SELECT).<br />
errno Die Nummer des Fehlercodes.<br />
error Eine Beschreibung des Fehlers (englisch).<br />
field_count Anzahl der Spalten, die die letzte Abfrage zurückgegeben hat.<br />
host_info <strong>In</strong>formationen über den Server und die Art der Verbindung, beispielsweise<br />
»localhost via TCP/IP«.<br />
info <strong>In</strong>formationen über die letzte Abfrage.<br />
insert_id Die letzte durch INSERT in einem AUTO_INCREMENT-Feld erzeugte<br />
ID.<br />
protocol_version Die Version des <strong>My</strong><strong>SQL</strong>-Protokolls (aktuell: 10).<br />
sqlstate Status einer vorher gesendeten Abfrage.<br />
thread_id ID des Threads in dem die Abfrage abgearbeitet wird .<br />
thread_safe Ermittelt, ob Threadsicherheit besteht.<br />
warning_count Anzahl der Warnungen, die die letzte Abfrage auslösten.<br />
Tabelle 11.16: Eigenschaften des <strong>My</strong><strong>SQL</strong>i-Objekts<br />
Methode Beschreibung<br />
autocommit Schaltet die automatische Bestätigung von Transaktionen ein<br />
oder aus.<br />
change_user Ändert den Benutzer für die aktuelle Verbindung.<br />
character_set_name Gibt den aktuellen Zeichensatz zurück.<br />
close Schließt die Verbindung.<br />
commit Bestätigt die aktuelle Transaktion.<br />
Tabelle 11.17: Methoden des <strong>My</strong><strong>SQL</strong>i-Objekts
Methode Beschreibung<br />
Referenz <strong>My</strong><strong>SQL</strong>i<br />
connect Öffnet eine neue Verbindung zum <strong>My</strong><strong>SQL</strong>-Server.<br />
get_client_info Gibt <strong>In</strong>formationen über die verwendet <strong>My</strong><strong>SQL</strong>-Version<br />
zurück.<br />
get_client_version Gibt <strong>In</strong>formationen über den <strong>My</strong><strong>SQL</strong>-Client zurück.<br />
get_host_info Gibt <strong>In</strong>formationen über den Server und die Verbindung<br />
zurück.<br />
init Vorbereiten eines <strong>My</strong><strong>SQL</strong>i-Objekts für spätere Verwendung.<br />
info Gibt die automatisch erstellte ID der letzten Abfrage zurück.<br />
kill Versucht den von <strong>My</strong><strong>SQL</strong> belegten Thread zu beenden.<br />
multi_query Sendet eine Abfrage an die Datenbank.<br />
more_results Ermittelt, ob weitere Ergebnissätze von Mehrfachabfragen vorhanden<br />
sind.<br />
next_result Nächste Ergebnissätze von Mehrfachabfragen abholen.<br />
options Setzt verschiedene Optionen.<br />
ping Sendet einen Ping zur Kontrolle der Verbindung an den Server.<br />
prepare Kann eine Abfrage vorbereiten. Vorbereitete Abfragen sind<br />
schneller bei wiederholter Ausführung.<br />
query Eine Abfrage direkt an den Server senden.<br />
real_connect Kann eine Verbindung öffnen.<br />
real_query Kann eine Abfrage ausführen.<br />
rollback Kann eine Transaktion rückabwickeln.<br />
select_db Wird eine andere Datenbank als Standard auswählen. Entspricht<br />
dem <strong>SQL</strong>-Befehl USE.<br />
send_query Sendet eine Abfrage an die Datenbank.<br />
sqlstate <strong>SQL</strong> Status und Fehlercodes einer vorhergehenden Abfrage<br />
ermitteln.<br />
Tabelle 11.17: Methoden des <strong>My</strong><strong>SQL</strong>i-Objekts (Forts.)<br />
493
494<br />
Datenbankprogrammierung<br />
Methode Beschreibung<br />
ssl_set Kann eine gesicherte SSL-Verbindung aufbauen.<br />
stat Der aktuellen Status des Systems.<br />
stmt_init <strong>In</strong>itialisiert eine Abfrage und gibt ein Objekt zurück, mit dem<br />
diese Abfrage gesteuert werden kann. Tabelle 11.18 und Tabelle<br />
11.19 zeigen die Eigenschaften und Methoden dieses Objekts.<br />
thread_safe Ermittelt, ob Threadsicherheit gegeben ist oder nicht.<br />
use_result Bereitet einen Ergebnissatz zur Verwendung vor.<br />
Tabelle 11.17: Methoden des <strong>My</strong><strong>SQL</strong>i-Objekts (Forts.)<br />
Eigenschaft Beschreibung<br />
affected_rows Anzahl der von der Anweisung betroffenen Datensätze. Damit<br />
kann man den Erfolg von UPDATE oder DELETE überwachen.<br />
errno Der letzte Fehlercode.<br />
error Eine Beschreibung des Fehlers (englisch).<br />
param_count Die Anzahl der Parameter.<br />
sqlstate Ermittelt <strong>SQL</strong> Status und Fehlercodes einer vorhergehenden<br />
Abfrage.<br />
ssl_set Kann eine gesicherte SSL-Verbindung aufbauen.<br />
Tabelle 11.18: Eigenschaften des Anweisungs-Objekts<br />
Methode Beschreibung<br />
bind_param Erstellt Parameter für die Anweisung.<br />
bind_result Erstellt ein Ergebnisobjekt.<br />
close Schließt die Verbindung.<br />
data_seek Setzt den Ergebnissatzzeiger auf einen bestimmten Datensatz.<br />
Tabelle 11.19: Methoden des Anweisungs-Objekts
Methode Beschreibung<br />
execute Führt die Anweisung aus.<br />
Referenz <strong>My</strong><strong>SQL</strong>i<br />
fetch Holt den Ergebnissatz mit verschiedenen Optionen.<br />
fetch_result Holt den Ergebnissatz.<br />
get_metadata Holt globale <strong>In</strong>formationen zur Anweisung.<br />
prepare Bereitet die Anweisung vor (vorab Kompilierung).<br />
send_long_data Sendet große Datenpakete.<br />
store_result Speichert die Ergebnisse zwischen.<br />
Tabelle 11.19: Methoden des Anweisungs-Objekts (Forts.)<br />
Eigenschaft Beschreibung<br />
current_field Ermittelt das aktuelle Feld des Ergebnissatzes.<br />
field_count Ermittelt die Anzahl der Felder.<br />
length Ermittelt die Länge (Breite) eines Feldes.<br />
num_rows Ermittelt die Anzahl der Reihen im Ergebnissatz.<br />
Tabelle 11.20: Eigenschaften des Ergebnis-Objekts<br />
Methode Beschreibung<br />
close Schließt die Verbindung.<br />
data_seek Setzt den Ergebnissatzzeiger auf eine bestimmte Reihe.<br />
fetch_field_direct Holt direkt ein bestimmtes Feld.<br />
fetch_field Holt das nächste Feld einer Liste.<br />
fetch_fields Holt Felder als Array.<br />
fetch_lengths Ermittelt die Breite des aktuellen Feldes.<br />
fetch_object Holt die Reihe als Objekt. Felder sind nun Eigenschaften.<br />
Tabelle 11.21: Methoden des Ergebnis-Objekts<br />
495
496<br />
Datenbankprogrammierung<br />
Methode Beschreibung<br />
fetch_row Holt die Reihe als numerisches (einfaches) Array.<br />
fetch_assoc Holt die Reihe als assoziatives Array. Die Schlüssel sind die Spaltennamen.<br />
field_seek Setzt den Zeiger auf ein spezifisches Feld.<br />
Tabelle 11.21: Methoden des Ergebnis-Objekts (Forts.)<br />
11.6 Kontrollfragen<br />
1. Warum sollte die neue Bibliothek <strong>My</strong><strong>SQL</strong>i anstatt der alten eingesetzt werden?<br />
2. Was versteht man unter Normalisierung?<br />
3. Sie müssen bei der Ausgabe von Daten aus einer <strong>My</strong><strong>SQL</strong>-Datenbank Datumsformate<br />
anpassen und \n in umwandeln. Wie gehen Sie vor?<br />
4. Wie würden Sie eine Abfrage formulieren, bei der aus zwei Tabellen Daten<br />
zugleich entnommen werden müssen? Welche Voraussetzungen müssen die<br />
Tabellen erfüllen?
Die integrierte<br />
Datenbank <strong>SQL</strong>ite<br />
12
498<br />
Die integrierte Datenbank <strong>SQL</strong>ite<br />
Nicht immer muss es <strong>My</strong><strong>SQL</strong> sein. Statt einer ausgewachsenen Datenbank reicht<br />
oft ein einfacheres System. Die Lücke zwischen einfachen Textdateien als Datenspeicher<br />
und einem relationalen Datenbankmanagementsystem schließt in PHP5<br />
das <strong>SQL</strong>ite-Modul.<br />
12.1 Hintergrund<br />
<strong>SQL</strong>ite ist fest in PHP integriert. Ein weiteres Modul oder Treiber sind nicht erforderlich.<br />
Sie können deshalb immer davon ausgehen, dass jede PHP5-<strong>In</strong>stallation<br />
wenigstens <strong>SQL</strong>ite bereitstellt. Wenn kleine Datenbankprojekte erstellt werden,<br />
die auf Funktionen großer Datenbankmanagementsysteme nicht angewiesen sind,<br />
ist <strong>SQL</strong>ite völlig ausreichend. Allerdings muss man sich auch der Grenzen bewusst<br />
sein, um gegebenenfalls rechtzeitig wechseln zu können.<br />
<strong>SQL</strong>ite ist eine einfache Datenbank-Lösung, die als »Public Domain« entwickelt<br />
wurde. Dies ist eine sehr freie Art der Softwareverteilung, bei der völlig auf Rechte<br />
und Lizenzen verzichtet wird. Der Code wird jedermann ohne Beschränkungen<br />
zur Verfügung gestellt.<br />
Der Entwickler ist D. Richard Hipp der Firma Hipp, Wyrick & Company, <strong>In</strong>c. Die<br />
neueste Version wird unter folgender Adresse bereitgestellt:<br />
http://www.hwaci.com/sw/sqlite<br />
<strong>SQL</strong>ite soll dabei kein Ersatz für <strong>My</strong><strong>SQL</strong> oder Postgre<strong>SQL</strong> sein. Nur einfachste<br />
Anwendungen werden auf größere Datenbank verzichten können. Wenn Sie<br />
bereits einige Erfahrung mit Datenbanken haben und Oracle oder MS <strong>SQL</strong> Server<br />
kennen, wird Ihnen <strong>My</strong><strong>SQL</strong> möglicherweise primitiv vorgekommen sein. Dass<br />
es noch deutlich einfacher geht, zeigt <strong>SQL</strong>ite. Dafür profitiert auch <strong>SQL</strong>ite von<br />
der Vereinfachung. Zugriffe sind extrem schnell und die Speicherung der Daten<br />
nutzt den Speicher effektiv. Einige Benchmarks versprechen etwa doppelte<br />
Geschwindigkeit gegenüber dem bereits recht flotten <strong>My</strong><strong>SQL</strong>.<br />
Einige der typischen Aufgaben, denen <strong>SQL</strong>ite ohne weiteres gewachsen ist, sind:<br />
Konfigurationsdaten einer Applikation speichern.<br />
Serialisierte Objekte zur Mitnahme von Seite zu Seite speichern.<br />
Speicherung von Anmeldeinformationen.<br />
Ein Gästebuch.<br />
Eine News-Site, auf der aktuelle Nachrichten zu finden sind.
Vor- und Nachteile<br />
Ein komplettes Content Management System oder größere Foren dürften allein<br />
aufgrund der komplexeren <strong>SQL</strong>-Abfragen mit <strong>My</strong><strong>SQL</strong> besser bedient sein.<br />
12.2 Vor- und Nachteile<br />
Bevor Sie intensiv in <strong>SQL</strong>ite einsteigen, sollten Sie einige technische Hintergründe<br />
und deren Auswirkungen auf Projekte kennen.<br />
Wann <strong>SQL</strong>ite vorteilhaft ist<br />
<strong>SQL</strong>ite bietet sich an, wenn einfache Projekte völlig unabhängig von der PHP5-<br />
Konfiguration laufen müssen. Die Zugriffe sollten vorrangig lesend erfolgen.<br />
Außerdem sollte PHP5 nur auf einem einzigen System laufen, nicht im Cluster<br />
oder mit Lastverteilung. Der Anspruch an die Sicherheit der Daten sollte eher<br />
gering sein oder eine häufige Datensicherung ist vorhanden.<br />
Vorteilhaft ist <strong>SQL</strong>ite deshalb, weil sie vielen Entwicklern mit wenig Datenbankerfahrung<br />
die Chance bietet, ohne großen Aufwand eine datenbankgestützte Applikation<br />
zu bauen. Denn mit der <strong>In</strong>tegration reduziert sich die Lernkurve auf PHP<br />
und sehr wenige <strong>SQL</strong>-Befehle selbst. Eine kleinere Einstiegbarriere erlaubt dann<br />
vielleicht den Ersatz uneffektiver Bastellösungen durch eine halbwegs saubere Programmierung,<br />
was insgesamt schon als Vorteil betrachtet werden kann.<br />
Wann <strong>SQL</strong>ite nachteilig ist<br />
<strong>SQL</strong>ite speichert die gesamte Datenbank in einer einzigen Datei. Schreibzugriffe<br />
von einer Benutzersitzung aus führen dazu, dass die gesamten Datei kurzzeitig<br />
gesperrt wird. Das führt bei häufigen Schreibzugriffen sehr schnell dazu, dass<br />
PHP5 ständig auf die erneute Freigabe der Datenbank wartet und dies verschlechtert<br />
drastisch die Antwortzeiten des Systems.<br />
Die Speicherung von Sitzungsdaten in <strong>SQL</strong>ite ist deshalb eher nachteilig. Sollte<br />
das Dateisystem feststellen, dass die Datenbankdatei korrupt ist, wird man alle Sitzungsdaten<br />
aller Sitzungen verlieren. Beim bisherigen System beträfe dies nur<br />
eine Sitzung. Außerdem verfügt <strong>SQL</strong>ite über keine »Selbstheilungskräfte«, wie sie<br />
andere DBMS kennen. John Lim von PHPEverywhere und Sterling Hughes<br />
499
500<br />
Die integrierte Datenbank <strong>SQL</strong>ite<br />
haben einige Benchmarks erstellt, die klar beweisen, das <strong>SQL</strong>ite bei schreibintensiven<br />
Aufgaben wie der Sitzungsverwaltung deutlich langsamer ist als die eingebaute<br />
Sitzungsverwaltung oder andere Datenbanklösungen.<br />
Allerdings muss auch gesagt werden, dass die in den Tests erreichten Zugriffszahlen<br />
weit höher liegen, als die meisten Webseiten jemals erreichen. <strong>In</strong>sofern sind<br />
derartige Aussagen immer auch akademischer Natur.<br />
12.3 Einführung<br />
Die Implementierung von <strong>SQL</strong>ite folgt weitgehend den Vorgaben der C-Implementierung<br />
1 . Wo immer es an Dokumentation mangelt, kann man also gut auf die<br />
Originalausgabe der Datenbank zurückgreifen.<br />
Eine einfache Beispielanwendung<br />
Ein einfaches Beispiel soll zeigen, wie der Zugriff prinzipiell aussieht. Das Skript<br />
geht davon aus, dass ein Verzeichnis data unterhalb des Speicherorts des Skripts<br />
existiert, wo die Datenbankdatei erzeugt werden kann:<br />
Listing 12.1: <strong>SQL</strong>iteCreate.php – Erzeugen einer Datenbank und einer Tabelle<br />
Einführung<br />
)";<br />
if ( !sqlite_query($sql, $db) )<br />
die(sqlite_last_error($db).': '. <br />
sqlite_error_string(sqlite_last_error($db)));<br />
echo ( "Datenbank $sqliteDb erfolgreich angelegt." );<br />
sqlite_close($db);<br />
?><br />
Dieses Skript erzeugt eine neue <strong>SQL</strong>ite-Datenbank und darin eine einfache<br />
Tabelle mit vier Spalten: id, login, password und email zur Implementierung einer<br />
Benutzeranmeldung.<br />
Eine Benutzerverwaltung mit <strong>SQL</strong>ite<br />
Das folgende Listing zeigt ein weiteres Skript auf einen Blick, eine Erläuterung der<br />
wichtigen Passagen folgt danach. Es handelt sich um die noch rudimentäre Benutzerverwaltung,<br />
die die bereits erzeugte Tabelle mit zwei Datensätzen befüllt und<br />
einen Anmeldevorgang simuliert:<br />
Listing 12.2: SQliteSelect.php – Primitive Benutzerverwaltung mit <strong>SQL</strong>ite<br />
502<br />
Die integrierte Datenbank <strong>SQL</strong>ite<br />
{<br />
foreach ($users as $user)<br />
{<br />
$sql = "INSERT INTO users (login, password, email) <br />
VALUES <br />
( <br />
'{$user['login']}', <br />
'{$user['password']}', <br />
'{$user['email']}' <br />
)";<br />
echo "$sql";<br />
if (!sqlite_query($db, $sql))<br />
{<br />
die ("Fehler: " . sqlite_last_error($db).': '. <br />
sqlite_error_string(sqlite_last_error($db)));<br />
}<br />
}<br />
echo 'Daten erfasst';<br />
}<br />
else<br />
{<br />
echo 'Daten bereits vorhanden';<br />
}<br />
sqlite_close($db);<br />
?><br />
Abfrage der Datenbank<br />
Einführung<br />
echo "{$row['id']} Name: {$row['login']} <br />
E-Mail: {$row['email']}";<br />
}<br />
if ($_SERVER['REQUEST_METHOD']=='POST' && $_POST['action']=='test')<br />
{<br />
$pw_encrypted = md5($_POST['logpass']);<br />
$sql = "SELECT * FROM users WHERE login='{$_POST['logname']}' <br />
AND password='$pw_encrypted'";<br />
$result = sqlite_query($db, $sql);<br />
echo sqlite_error_string(sqlite_last_error($db));<br />
if (sqlite_num_rows($result) == 1)<br />
{<br />
echo 'Anmeldung erfolgreich';<br />
}<br />
else<br />
{<br />
echo 'Benutzername oder Kennwort konnten <br />
nicht gefunden werden.';<br />
}<br />
}<br />
?><br />
Test der Anmeldung<br />
504<br />
Die integrierte Datenbank <strong>SQL</strong>ite<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Um Kennwortdaten in der Datenbank zu schützen, werden die Zeichenfolgen als<br />
MD5-Hash abgelegt. Dies ist eine irreversible Abbildung beliebiger Daten. Um<br />
später das Kennwort verifizieren zu können, wird die Eingabe erneut mit MD5<br />
berechnet und die kodierten Werte werden verglichen.<br />
Das Skript ist relativ primitiv, es erfasst nur zwei Musterdatensätze, die hart kodiert<br />
sind. Um festzustellen, ob die Daten schon vorhanden sind, erfolgt zuerst eine einfache<br />
Abfrage:<br />
$result = sqlite_query($db, "SELECT * FROM users");<br />
Dann wird ermittelt, wie viele Daten vorliegen:<br />
$num = sqlite_num_rows($result);<br />
Die Funktion sqlite_num_rows ermittelt die Anzahl der von der in $result gespeicherten<br />
Abfrage zurückgegebenen Datensätze.<br />
Sind keine Datensätze vorhanden, werden die als Array vorliegenden Daten mittels<br />
INSERT gespeichert. Geht dabei etwas schief, enthält sqlite_last_error($db) den<br />
letzte Fehlercode. Um den passenden Text anzuzeigen, was meist hilfreicher ist,<br />
wird eine weitere Funktion benötigt:<br />
sqlite_error_string(sqlite_last_error($db))<br />
Für die Ausgabe von Datensätzen kann der <strong>In</strong>halt zeilenweisen einem Array zugeführt<br />
werden. Auch hier dient der Verweis auf den Ergebnissatz, wiederum in<br />
$result abgelegt, als Datenquelle:<br />
while ($row = sqlite_fetch_array($result, <strong>SQL</strong>ITE_ASSOC))
Einführung<br />
Der Vergleich des eingegebenen Kennworts mit dem gespeicherten erfolgt in<br />
einer weiteren Abfrage. Damit dieser Programmteil nur ausgeführt wird, wenn ein<br />
Formular gesendet wurde, wird eine Servervariable verwendet:<br />
$_SERVER['REQUEST_METHOD']=='POST'<br />
Das Kennwort muss wieder mit MD5 verpackt werden:<br />
$pw_encrypted = md5($_POST['logpass']);<br />
Dieser Wert wird dann für die Abfrage verwendet. Der Rest ist bekannt – es folgt<br />
eine Abfrage und diese wird mit sqlite_num_rows untersucht. Nur wenn genau ein<br />
Datensatz gefunden wurde, war die Prüfung erfolgreich.<br />
Am Ende des Skripts sollten Sie nicht vergessen, die Verbindung zu schließen,<br />
damit <strong>SQL</strong>ite die Datei freigeben kann, in der die Datenbank gespeichert ist.<br />
sqlite_close($db);<br />
Ausblick<br />
<strong>SQL</strong>ite kann Daten auch im Speicher des Computers halten, was für temporäre<br />
Daten sehr vorteilhaft ist. Unter Linux konnten dafür bislang die Shared-Memory-<br />
Funktionen eingesetzt werden, die jedoch unter Windows nicht zur Verfügung<br />
standen, was echte Cross-Plattform-Entwicklung natürlich verhindert. Hier bietet<br />
<strong>SQL</strong>ite nun eine einfache und elegante Lösung. Natürlich hat <strong>SQL</strong>ite seine Grenzen.<br />
Bei sehr vielen konkurrierenden Zugriffen führt die Sperrung der gesamten<br />
Datenbankdatei dazu, dass neu eintreffende Abfragen verzögert werden. Das kann<br />
praktisch bis zum Deadlock führen – dem Skriptstillstand. Allerdings ist das<br />
Gespann Dateisystem-Speicher-<strong>SQL</strong>ite äußerst leistungsfähig und die allergrößte<br />
Masse aller Sites erreicht niemals die kritische Benutzerzahl.<br />
Spannend sind auch die Möglichkeiten, <strong>SQL</strong>ite beliebige Funktionen hinzuzufügen,<br />
die innerhalb von <strong>SQL</strong>-Abfragen benutzt werden können. Damit kann man<br />
sehr effizient die Defizite der Datenbank ausgleichen und erreicht eine mithin<br />
höhere Leistungsfähigkeit als bei größeren Datenbanksystemen. Es ist durchaus<br />
denkbar, dass auch anspruchsvolle Lösungen für geringen oder mittlere Last mit<br />
<strong>SQL</strong>ite programmiert werden.<br />
505
506<br />
Die integrierte Datenbank <strong>SQL</strong>ite<br />
12.4 Referenz <strong>SQL</strong>ite<br />
Funktion und Parameter Beschreibung<br />
sqlite_array_query Führt eine Abfrage aus und gibt ein Array mit den Ergebnissen<br />
zurück.<br />
sqlite_busy_timeout Maximale Wartezeit auf ein Kommando. Der Standardwert<br />
beträgt 60 Sekunden, die Angabe muss in Millisekunden<br />
erfolgen.<br />
sqlite_changes Anzahl der Spalten, die das letzte Kommando verändert<br />
hat.<br />
sqlite_close Schließt die Verbindung.<br />
sqlite_column Holt eine Spalte anhand des angegebenen <strong>In</strong>dizes aus der<br />
aktuellen Ergebnisreihe.<br />
sqlite_create_aggregate Nutzt eine PHP-Funktion zur Verarbeitung von Daten aus<br />
einer Tabelle.<br />
sqlite_create_function Definiert eine PHP-Funktion callback unter dem Namen<br />
function zur Verwendung in künftigen <strong>SQL</strong>-Befehlen.<br />
sqlite_current Holt einen Ergebnissatz aus dem Ergebnisarray zurück.<br />
sqlite_error_string Zeigt eine Fehlermeldung an. Siehe auch<br />
sqlite_last_error.<br />
sqlite_escape_string Markiert in <strong>SQL</strong>ite unzulässige Zeichen.<br />
sqlite_fetch_array Holt die nächste Reihe aus dem Ergebnissatz.<br />
sqlite_fetch_single Holt die erste Spalte aus dem Ergebnissatz.<br />
sqlite_fetch_string Alias für sqlite_fetch_single.<br />
sqlite_field_name Name eines Feldes<br />
sqlite_has_more TRUE, wenn weitere Reihen verfügbar sind.<br />
sqlite_last_error Letzte Fehlernummer.<br />
sqlite_last_insert_rowed Letzte ID, die in einer INTEGER PRIMARY KEY Spalte eingefügt<br />
wurde.
Funktion und Parameter Beschreibung<br />
Referenz <strong>SQL</strong>ite<br />
sqlite_libencoding Zeichenkodierung der Bibliothek, beispielsweise ISO-<br />
8859-1.<br />
sqlite_libversion Die Versionsnummer der Bibliothek.<br />
sqlite_next Sucht einen Ergebnisdatensatz zur Ausgabe, gibt aber<br />
selbst nichts aus. Wird zur Schleifensteuerung verwendet.<br />
sqlite_num_fields Anzahl der Felder im Ergebnis.<br />
sqlite_num_rows Anzahl der Reihen im Ergebnis.<br />
sqlite_open Öffnet die Datenbank und erzeugt die Datei, wenn sie<br />
nicht existiert, mit dem angegebenen Zugriffsmode (Unix-<br />
Dateiparameter, beispielsweise 0666 (oktal)).<br />
sqlite_popen Wie sqlite_open, aber persistent.<br />
sqlite_query Fragt die Datenbank ab und gibt ein Handle auf die Ergebnisse<br />
zurück.<br />
sqlite_rewind Setzt den Zeiger auf die erste Reihe. Wird für die Schleifensteuerung<br />
verwendet.<br />
sqlite_seek Setzt den Zeiger auf die angegebene Reihe. Wird für die<br />
Schleifensteuerung verwendet.<br />
sqlite_udf_decode_binary Dekodiert Binärdaten, bevor diese an eine benutzerdefinierte<br />
Funktion gesendet werden.<br />
sqlite_udf_encode_binary Kodiert Binärdaten, bevor diese von einer benutzerdefinierte<br />
Funktion zurückgenommen werden.<br />
sqlite_unbuffered_query Wie sqlite_query, aber das Ergebnis wird nicht sofort<br />
komplett geholt, sondern erst, wenn es andere Funktionen<br />
anfordern. Schneller bei einfachen sequenziellen Lesezugriffen.<br />
507
508<br />
Die integrierte Datenbank <strong>SQL</strong>ite<br />
12.5 Kontrollfragen<br />
1. Was müssen Sie tun, um <strong>SQL</strong>ite benutzt zu können?<br />
2. Welche Datentypen kennt <strong>SQL</strong>ite?<br />
3. Sie möchten mehrere Tausend Datensätze für eine hoch frequentierte Website<br />
verwalten. Ist <strong>SQL</strong>ite dafür die beste Wahl? Begründen Sie die Antwort?<br />
4. Welche Aufgabe hat der Befehl VACUUM?
Datenbanklösungen<br />
mit <strong>My</strong><strong>SQL</strong><br />
13
510<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
13.1 Bibliotheks-Verwaltung mit <strong>My</strong><strong>SQL</strong><br />
Dieses Kapitel widmet sich einem Praxisprojekt: Der Verwaltung einer Bibliothek.<br />
Dabei soll, neben der einfachen Auflistung von Büchern auch deren Position in<br />
einem Regalsystem erfasst werden.<br />
Schrittfolge<br />
Um das Programm komplett zu erstellen, werden zwei Schritte erforderlich:<br />
Entwicklung einer Datenbankstruktur<br />
Aufbau der Oberfläche<br />
Programmierung der Geschäftslogik<br />
Die Trennung von Oberfläche und Logik erfolgt durch ein primitives Template-<br />
System, einer Verwaltung von Vorlagen. Dies erleichtert die Erweiterung der<br />
Funktionalität erheblich.<br />
Weitere Aufgaben<br />
Das Programm ist ein guter Start in Richtung komplexer Datenbankanwendungen.<br />
Es ist zugleich Grundlage des nächsten Kapitels, indem die Daten der Bücher<br />
eleganter beschafft werden. Das eigentliche Problem bei der Nutzung des Programms<br />
ist nämlich nicht dessen Erstellung, sondern dessen Nutzung. Das Erfassen<br />
aller Buchdaten ist rech mühselig. Glücklicherweise bietet Amazon einen<br />
Webservice, der die Beschaffung der Daten erleichtert. Vorerst wird jedoch – auch<br />
zur Korrektur später automatisch erfasster Daten – ein Eingabeformular benutzt.<br />
Funktionen<br />
Das Programm soll grundsätzlich folgende Funktionen anbieten:<br />
Erfassung von Büchern<br />
Suchen nach Titel, Autor und ISBN<br />
Erfassung der Regalposition und optional einer Anzahl vorhandener<br />
Exemplare
Bibliotheks-Verwaltung mit <strong>My</strong><strong>SQL</strong><br />
Erfassung von Lesern, an die die Bücher ausgeliehen werden<br />
Verwaltung der Leihvorgänge<br />
Da ein Benutzer ein Buch immer nur einmal ausleihen kann, sind nur sehr einfache<br />
Beziehungen erforderlich. Allerdings kann jeder Benutzer mehrere verschiedene<br />
Bücher ausleihen, was zu folgenden Tabellen führt:<br />
Tabelle »Books« für die Erfassung der Bücher<br />
Tabelle »Reader« für die Leser, die Bücher ausleihen können<br />
Tabelle »LendOut« für die Leihvorgänge<br />
Der Vorteil bei der Ausleihtabelle besteht im Aufbau einer Historie, das heißt, hier<br />
kann man schnell nach der Häufigkeit und Dauer der Ausleihe suchen.<br />
Nebenbei bemerkt eignet sich das Programm auch für eine private Bibliothek.<br />
Wer mit seinen Kumpels und Kollegen einen Buchpool bildet und sich gegenseitig<br />
Bücher ausleiht, kann hier verwalten, welches Exemplar gerade wo unterwegs<br />
ist.<br />
Für die eigentliche Funktionalität muss außerdem festgelegt werden, welche Seiten<br />
und Formulare erforderlich sind. Dabei sind zwei »Betriebsarten« zu beachten:<br />
Ausleih- und Verwaltungsmodus. Im Ausleihmodus sollen Leser oder der<br />
Bibliothekar suchen und Ausleihvorgänge steuern können, im Verwaltungsmodus<br />
können neue Bücher erfasst oder alte entfernt werden. Benötigt werden dazu folgende<br />
Seiten:<br />
Startseite mit Auswahl der Betriebsart<br />
Seite zum Suchen von Büchern<br />
Seite zum Steuern des Ausleih- und Rücknamevorgangs<br />
Seite zum Erfassen, Anzeigen und Löschen von Benutzern<br />
Seite zum Erfassen, Anzeigen und Löschen von Büchern<br />
Auf eine Verwaltung der Bediener durch eigene Anmeldenamen und Kennwörter<br />
soll hier – vor allem aus Platzgründen – verzichten werden. Die dazu benötigten<br />
Techniken unterscheiden sich nicht grundlegend von den gezeigten und die<br />
Umsetzung sollte nach der Absolvierung des Kapitels nicht schwer fallen.<br />
511
512<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
13.2 Vorbereitung der Datenbank<br />
Für den Entwurf der Datenbank müssen Sie sich zuerst über eine Struktur der<br />
Tabellen im Klaren sein. Die drei benötigten Tabellen wurden bereits erwähnt.<br />
Der Aufbau wird so gewählt, dass alle Daten im passenden Datenformat gespeichert<br />
werden können.<br />
Das Anlegen kann mit einem Werkzeug erfolgen. Im Beispiel wurde das <strong>My</strong><strong>SQL</strong><br />
Control Center verwendet. Für dieses Projekt wurde lokal eine eigene Datenbank<br />
angelegt. Wenn Sie ausschließlich auf dem Server eines Providers arbeiten und<br />
dort nur eine fertige Datenbank verwenden können, müssen Sie diesen Schritt auslassen<br />
und bei der Nutzung der Skripte den Namen der Datenbank anpassen. Wie<br />
das zu erfolgen hat, wird im entsprechenden Code-Abschnitt erläutert.<br />
Datenbank anlegen<br />
Falls noch nicht geschehen, legen Sie die für die Datenbank-Übungen erforderliche<br />
Datenbank »marktundtechnik« an. Sie können auch jeden anderen Namen<br />
nehmen, müssen dann aber die Skripte entsprechend anpassen. Dies ist nicht sehr<br />
schwer, weil es eine zentrale Konfigurationsdatei für die Datenbankparameter gibt.<br />
Datenbank mit <strong>SQL</strong><br />
Wenn Sie mit <strong>SQL</strong> arbeiten, geben Sie am Prompt der <strong>My</strong><strong>SQL</strong>-Konsole folgendes<br />
ein:<br />
CREATE DATABASE marktundtechnik;<br />
Weitere Parameter werden für diese Projekt nicht benötigt.<br />
Datenbank mit <strong>My</strong><strong>SQL</strong> Control Center anlegen<br />
Um die Datenbank mit <strong>My</strong><strong>SQL</strong> Control Center anzulegen, starten Sie das Programm.<br />
Beim ersten Mal müssen Sie den Server angeben (vermutlich »localhost«,<br />
wenn es lokal läuft) und gegebenenfalls Anmeldename und Kennwort. Nach einer<br />
frischen <strong>In</strong>stallation ist der Anmeldename »root« und das Kennwort ist leer. Im<br />
Zweig Datenbanken des Servers klicken Sie mit der rechten Maustaste und wählen<br />
dann im Kontextmenü den Eintrag Neue Datenbank.
Vorbereitung der Datenbank<br />
Danach erscheint ein Eingabefeld, wo der Name der Datenbank eingetragen wird.<br />
Weitere Optionen sind nicht erforderlich.<br />
Tabellen anlegen<br />
Nun werden die drei benötigten Tabellen angelegt. Zuerst wird wieder der dazu<br />
erforderliche <strong>SQL</strong>-Code vorgestellt. Danach wird der entsprechende Dialog im<br />
Control Center gezeigt. Die Tabellen weisen keine Besonderheiten auf, wenngleich<br />
einige Dinge zu beachten sind.<br />
Jede Tabelle enthält ein Feld id, das als Typ BIGINT und AUTO_INCREMENT definiert<br />
wurde. Dieses Feld ist zugleich der Primärschlüssel der Tabelle (PRIMARY KEY).<br />
Des Weiteren wurde bei einigen Feldern explizit NOT NULL angegeben, um zu verhindern,<br />
dass beim Anlegen der Datensätze Spalten leer bleiben. Felder, nach<br />
denen gesucht werden könnte, wurden außerdem mit einem <strong>In</strong>dex bedacht.<br />
Tabellen mit <strong>SQL</strong> anlegen<br />
Das folgende Listing zeigt die <strong>SQL</strong>-Anweisungen, mit denen die drei Tabellen<br />
erzeugt werden können:<br />
Listing 13.1: <strong>SQL</strong>-Anweisungen zum Erstellen der Tabellen<br />
#<br />
# Tabellenstruktur für Tabelle `books`<br />
#<br />
CREATE TABLE books (<br />
id bigint(20) NOT NULL auto_increment,<br />
title varchar(100) NOT NULL default '',<br />
subtitle varchar(255) default '',<br />
isbn varchar(10) NOT NULL default '',<br />
author varchar(100) default '',<br />
number int(11) NOT NULL default '1',<br />
Abbildung 13.1:<br />
Eine neue Datenbank mit <strong>My</strong><strong>SQL</strong><br />
Control Center anlegen<br />
513
5<strong>14</strong><br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
shelf varchar(15) default '',<br />
PRIMARY KEY (id),<br />
KEY idx_title (title)<br />
) ENGINE=<strong>My</strong>ISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;<br />
# --------------------------------------------------------<br />
#<br />
# Tabellenstruktur für Tabelle `lendout`<br />
#<br />
CREATE TABLE lendout (<br />
id bigint(20) NOT NULL auto_increment,<br />
books_id bigint(20) NOT NULL default '0',<br />
reader_id bigint(20) NOT NULL default '0',<br />
lendingdate datetime NOT NULL default '0000-00-00 00:00:00',<br />
lendingperiod timestamp NOT NULL,<br />
PRIMARY KEY (id)<br />
) ENGINE=<strong>My</strong>ISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;<br />
# --------------------------------------------------------<br />
#<br />
# Tabellenstruktur für Tabelle `reader`<br />
#<br />
CREATE TABLE reader (<br />
id bigint(20) NOT NULL auto_increment,<br />
name varchar(100) NOT NULL default '',<br />
surname varchar(100) NOT NULL default '',<br />
address varchar(150) NOT NULL default '',<br />
zip varchar(5) NOT NULL default '',<br />
city varchar(100) NOT NULL default '',<br />
active tinyint(1) NOT NULL default '0',<br />
created datetime NOT NULL default '0000-00-00 00:00:00',<br />
PRIMARY KEY (id),<br />
KEY idx_name (name),<br />
KEY idx_surname (surname)<br />
) ENGINE=<strong>My</strong>ISAM DEFAULT CHARSET=latin1;<br />
Beachten Sie, dass die Anweisungen nur Tabellen erzeugen. Um das Skript erneut<br />
anzuwenden, müssen eventuell vorhandene Tabellen mit DROP TABLE entfernt werden.
Tabellen mit <strong>My</strong><strong>SQL</strong> Control Center anlegen<br />
Vorbereitung der Datenbank<br />
Zuerst wird die Buchtabelle erzeugt. Neben Titel, Autor und ISBN werden auch<br />
die Regalnummer und die Anzahl der Exemplare erfasst.<br />
Wenn im Control Center eine entsprechende Zeile der Tabelle ausgewählt wird,<br />
lassen sich die Parameter, beispielsweise die Anzahl der Zeichen beim Datentyp<br />
VARCHAR, erfassen.<br />
Die Kundentabelle weist keine Besonderheiten auf:<br />
Als letztes folgt die Ausleihtabelle:<br />
Abbildung 13.2:<br />
Struktur der Tabelle<br />
»Books«<br />
Abbildung 13.3:<br />
Die Tabelle »Reader« zur<br />
Erfassung der Bibliotheksnutzer<br />
Abbildung 13.4:<br />
Struktur der Tabelle<br />
»LendOut«<br />
Nach diesem Schritt steht die Datenbank bereit, um Daten aufzunehmen und später<br />
natürlich Abfragen zu beantworten.<br />
515
516<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
13.3 Die Seiten des Projekts<br />
Am Anfang wurde bereits eine Übersicht über die benötigten Formulare gezeigt.<br />
Diese werden hier nacheinander in der genannten Reihenfolge vorgestellt.<br />
Vorbereitungen – das Template-System<br />
Wie bereits kurz erwähnt, sollen Geschäftslogik und Design weitgehend getrennt<br />
werden. Dazu wird ein kleines Template-System entwickelt, das die Verwaltung<br />
der Seiten übernimmt. Das Prinzip ist recht einfach. Eine zentrale <strong>In</strong>stanz,<br />
index.php, verwaltet alle Seiten. Diesem Skript wird mitgeteilt, welche Vorlage<br />
(Template) aufgerufen werden soll. Die Vorlage besteht immer aus zwei Teilen:<br />
Dem PHP-Code und dem HTML-Code. Damit beide interagieren können, gibt es<br />
ein paar Steuerzeichen, die im HTML eingebaut werden und die in PHP aufzulösen<br />
sind.<br />
Der Sinn der Übung besteht nicht darin, ein professionelles Template-System wie<br />
phpTemple (http://www.phptemple.de) oder Smarty (http://smarty.php.net) zu<br />
ersetzen, sondern die prinzipielle Arbeitsweise und den Nutzen transparent werden<br />
zu lassen. Die Technik lässt sich leicht auf andere Projekte übertragen. Hat<br />
man erstmal das Gefühl dafür, wann ein solches System Vorteile bringt, fällt es<br />
leichter, sich für den Einarbeitungsaufwand in ein kommerzielles Produkt zu entscheiden.<br />
Die Steuerzeichen des Template-Systems<br />
Die wichtigste Funktion besteht in der Datenausgabe. Dazu werden Variablen<br />
erforderlich. Diese sehen – der Einfachheit halber – genauso aus, wie in PHP.<br />
Allerdings kann man sie direkt ins HTML schreiben:<br />
{$variable}<br />
Die geschweiften Klammern verhindern Konflikte mit Währungsangaben mit Dollarzeichen.<br />
Zum Ausgeben von Tabellen braucht man eine Konstruktion, die Datenwiederholungen<br />
erzeugt. <strong>In</strong> PHP sind dies Schleifen. <strong>In</strong> HTML wird einfach ein Kommentar<br />
eingefügt, den das Skript verarbeiten kann (Editoren ignorieren diesen<br />
Kommentar):
Die Seiten des Projekts<br />
<br />
<br />
<br />
<br />
${readers:name}<br />
<br />
<br />
${readers:surname}<br />
<br />
<br />
<br />
<br />
Das Programm wiederholt jetzt das Tag mit sämtlichen darin befindlichen Codes<br />
so oft, wie das Array readers dies ermöglicht. Das Array muss assoziativ sein. Der<br />
<strong>In</strong>dex mit dem Namen surname wird über ${readers:surname} abgerufen. Technisch<br />
werden dazu reguläre Ausdrücke und simple Ersetzungsfunktionen benutzt.<br />
Funktionsweise des Template-Systems<br />
Das eingesetzte Template-System beschränkt sich auf eine einzige Klasse, die in<br />
der Datei engine.inc.php definiert ist. Sie dient dazu, die HTML-Vorlagen zu<br />
erkennen, Variablen zu suchen und zu ersetzen und gegebenenfalls Blöcke mit<br />
sich wiederholenden Daten zu bilden. Das Resultat wird direkt ausgegeben.<br />
Damit die Steuerung funktioniert, ist folgender Einsatz vorgesehen:<br />
$engine = include_once('engine.inc.php');<br />
Diese Zeile schließt die Klasse ein, erzeugt eine <strong>In</strong>stanz und gibt sie wieder<br />
zurück. Dadurch hat die Klasse sowohl Zugriff auf die globalen Variablen, deren<br />
Werte benutzt werden können, als auch die Möglichkeit, sich selbst dem aufrufenden<br />
Code bereitzustellen.<br />
Der aufrufende Code muss nun den Namen des Templates zuweisen:<br />
$engine->Html = 'Start.html';<br />
Alternativ kann dies entfallen; dann wird versucht, die Daten aus der GET-Variablen<br />
TEMPLATE zu übernehmen. Die Steuerung kann damit vom Code in das<br />
Template selbst verlagert werden:<br />
Zur Startseite<br />
517
518<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
Wenn ein expliziter Code-Block erforderlich ist, muss man diesen lediglich wie<br />
die Vorlage benennen und ihm die Dateierweiterung .php geben. Damit das funktioniert,<br />
wurde die Startseite index.php folgendermaßen definiert:<br />
Listing 13.2: index.php – Universelle Startseite für das Template-System<br />
Die Seiten des Projekts<br />
Zwei private Variablen speichern den Namen der Vorlage und optional inkludierten<br />
und implizit ausgeführten Code (ebenso als Dateiname übergeben). Der<br />
Zugriff erfolgt später über die __set-Methode und den Konstruktor:<br />
private $templateCode;<br />
private $templateHtml;<br />
Die Steuerung der Vorlage erfolgt durch eingebettete Kommentare, die von dem<br />
folgenden regulären Ausdruck erkannt werden:<br />
const COMMENT = '/
520<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
$html = file_get_contents($this->templateHtml);<br />
}<br />
if (strlen($this->templateCode) > 0)<br />
{<br />
include_once($this->templateCode);<br />
}<br />
Nun werden die Wiederholungen aufgelöst. Dazu werden zuerst alle Blöcke<br />
ermittelt, die mit der Kommentarsyntax umschlossen sind:<br />
$matches = array();<br />
$blocksFound = preg_match_all(self::COMMENT, $html,<br />
$matches, PREG_OFFSET_CAPTURE);<br />
if ($blocksFound)<br />
{<br />
$page = '';<br />
Der reguläre Ausdruck produziert ein Array in $matches, dessen Elemente folgende<br />
Bedeutung haben:<br />
0: Array der Kommentaranfänge<br />
1: TAGs<br />
2: Variablen<br />
3: Ende-Offset<br />
Liegt ein solches Array vor, beginnt die eigentliche Analyse. Anhand der Blockdaten<br />
wird mit Hilfe von schnellen Zeichenkettenfunktionen der umschlossene Teil<br />
ermittelt, weil diese Daten gegebenenfalls wiederholt werden müssen. Das passiert<br />
innerhalb der for-Schleife praktisch für jeden Block:<br />
if (is_array($matches))<br />
{<br />
$startBlock = $lastStartBlock = 0;<br />
for ($i = 0; $i < count($matches[1]); $i++)<br />
{<br />
$token = strtoupper($matches[1][$i][0]);<br />
$offsetComment = (int)$matches[0][$i][1];<br />
$offsetContent = ((int)$matches[3][$i][1]) + 1;<br />
$variableName = $matches[2][$i][0];<br />
Die Auflösung der Daten erfolgt in zwei Schritten. Beim Start des Blocks werden<br />
die Anfangspositionen gespeichert (REPEAT), beim Ende werden die Daten zur
Die Seiten des Projekts<br />
Wiederholung benutzt (END). Die eigentliche Arbeit erledigt die Methode repeat-<br />
Block, die nachfolgend erläutert wird:<br />
switch ($token)<br />
{<br />
case 'REPEAT':<br />
$startBlock = $offsetContent;<br />
$startComment = $offsetComment;<br />
break;<br />
case 'END':<br />
$block = substr($html, $startBlock, <br />
$offsetComment - $startBlock);<br />
$block = $this->repeatBlock($block, $variableName);<br />
$len = $startComment - $lastStartBlock;<br />
$page .= substr($html, $lastStartBlock, $len).$block;<br />
$lastStartBlock = $offsetContent;<br />
break;<br />
}<br />
Die wiederholten Daten werden der fertigen Seite, die in $page gespeichert wird,<br />
hinzugefügt:<br />
}<br />
$page .= substr($html, $offsetContent);<br />
}<br />
Zum Schluss werden noch die übrig gebliebenen Variablen ersetzt:<br />
echo ($this->varReplace($page));<br />
}<br />
else<br />
{<br />
echo 'Nicht gefunden';<br />
}<br />
}<br />
Wie bereits angedeutet, kommt repeatBlock eine große Bedeutung zu. Der<br />
Methode wird der Code eines Blockes und der Name einer Variablen übergeben.<br />
Ist diese Variable ein Array, wird der Block für jedes Array-Element einmal ausgegeben<br />
und die Daten des jeweiligen Elements werden benutzt, um die Blockvariablen<br />
zu ersetzen. Letzteres findet in einer eigenen Methode statt, arrayReplace.<br />
Damit der Zugriff auf globale Variablen gelingt, wird der Name der Variablen als<br />
dynamische Variable ($$-Syntax) benutzt:<br />
521
522<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
private function repeatBlock($block, $varName)<br />
{<br />
global $$varName;<br />
$result = '';<br />
if (is_array($$varName))<br />
{<br />
$this->index = 0;<br />
foreach ($$varName as $value)<br />
{<br />
$result .= $this->arrayReplace($block);<br />
$this->index++;<br />
}<br />
}<br />
else<br />
{<br />
$result = $block;<br />
}<br />
return $result;<br />
}<br />
arrayReplace nutzt einen regulären Ausdruck, um die Variablen in der Vorlage zu<br />
finden und zu ersetzen. Das eigentliche Ersetzen passiert in der Rückruffunktion<br />
replaceArray, die ihrerseits Zugriff auf den globalen Adressraum hat:<br />
private function arrayReplace($block)<br />
{<br />
$block = preg_replace_callback('/\{\$([^:}]*):(.+)\}/U', <br />
array($this, 'replaceArray'), $block);<br />
return $block;<br />
}<br />
private $index = 0;<br />
private function replaceArray($repl)<br />
{<br />
global $$repl[2];<br />
$arr = $$repl[2];<br />
return $arr[$this->index];<br />
}<br />
Vergleichbar arbeitet die Ersetzung der normalen skalaren Variablen, wo ebenso<br />
eine Rückruffunktion benutzt wird. Auch hier geht es nur um einen vereinfachten<br />
Zugriff auf den globalen Adressraum:
Die Seiten des Projekts<br />
private function varReplace($block)<br />
{<br />
$block = preg_replace_callback('/\{\$(.+)\}/U', <br />
array($this, 'replaceSkalar'), $block);<br />
return $block;<br />
}<br />
private function replaceSkalar($repl)<br />
{<br />
global $$repl[1];<br />
return $$repl[1];<br />
}<br />
Der Rest der Datei steht außerhalb der Klassendefinition. Er erzeugt eine <strong>In</strong>stanz<br />
der Klasse und gibt das entsprechende Objekt als Ergebnis der Ausführung zurück.<br />
Damit kann die include-Funktion (oder include_once) als Quelle einer Zuweisung<br />
benutzt werden. Zuvor wird noch versucht, den Namen des Templates aus<br />
der GET-Variablen TEMPLATE zu entnehmen, wobei dieser Schritt optional ist,<br />
das heißt, er schlägt nicht fehl, wenn die Angabe fehlt:<br />
$__template = isset($_GET['TEMPLATE']) ? $_GET['TEMPLATE'] : '';<br />
return new SimpleTemplate($__template);<br />
?><br />
Das vorgestellte Prinzip ist einfach und schnell und bietet einigen Raum für Verbesserungen.<br />
Es zeigt vor allem, wie effektiv mit regulären Ausdrücken und der<br />
neuen objektorientierten Syntax von PHP5 gearbeitet werden kann.<br />
Der Code der Seiten<br />
Die folgenden Skripte zeigen eine kurze Übersicht über die Vorlagen, also den<br />
HTML-Code der Seiten (befreit von gestalterischen Elementen, um die Lesbarkeit<br />
zu verbessern) und die Variablen, die die dynamischen Daten enthalten.<br />
Startseite mit Auswahl der Betriebsart<br />
Die Startseite ist relativ einfach, denn es sind nur folgende Zustände zu unterscheiden:<br />
Ausleihen oder Rücknehmen eines Buches (Betriebszustand)<br />
Anlegen oder Sperren von Kunden (Betriebszustand)<br />
Anlegen oder Herausnehmen von Büchern (Administrationszustand)<br />
523
524<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
Das vorliegende Beispiel ist vergleichsweise rudimentär und unterscheidet nicht<br />
explizit zwischen Betriebs- und Administrationszustand. Dies ist jedoch leicht zu<br />
implementieren. Solange eine solche Bibliotheksverwaltung lokal im <strong>In</strong>tranet<br />
läuft, ist die Unterscheidung möglicherweise nicht erforderlich. Eine öffentliche<br />
Seite müsste um entsprechende Sicherheitsfunktionen ergänzt werden.<br />
Da keine weitere Logik erforderlich ist, besteht diese Seite lediglich aus einigen<br />
Links auf die anderen Vorlagen:<br />
Listing 13.3: start.html – Startseite der Bibliotheksverwaltung<br />
<br />
<br />
Bibliotheksverwaltung<br />
Willkommen in der Bibliotheksverwaltung mit PHP5<br />
Funktionsauswahl<br />
Kundenverwaltung<br />
<br />
<br />
Bücherverwaltung<br />
<br />
<br />
Heute ist der {$today}.<br />
<br />
<br />
PHP-Code wird hier kaum benötigt, lediglich eine Variable zur Anzeige des<br />
Datums ist zu definieren:<br />
Listing 13.4: start.php – Erzeugen einer deutschen, systemunabhängigen Datumsanzeige<br />
Die Seiten des Projekts<br />
Seite zum Steuern des Ausleih- und Rücknahmevorgangs<br />
Die Seite zum Steuern des Ausleih- und Rücknahmevorgangs benötigt folgende<br />
elementare Funktionen:<br />
Suchen des Kunden, wenn nicht vorhanden, Sprung zur Kundenerwaltung<br />
Suche des Buches (wird vorausgesetzt, dass nur vorhandene Bücher genutzt<br />
werden)<br />
Ausleihe oder Rücknahmevorgang<br />
Hier wird auf jeden Fall der Datenbankzugriff benötigt, der in einer <strong>In</strong>clude-Datei<br />
abgelegt wird, die auch den anderen Modulen zur Verfügung steht. <strong>In</strong> dieser Datei<br />
wird ein <strong>My</strong><strong>SQL</strong>i-Objekt erzeugt, das in allen folgenden Codes den Zugriff auf die<br />
Datenbank erlaubt:<br />
Listing 13.5: database.inc.php – <strong>In</strong>clude-Datei für den Datenbankzugriff<br />
<br />
Seite zum Erfassen, Anzeigen und Löschen von Kunden<br />
Die Seite umfasst folgende Funktionen:<br />
Liste aller Kunden anzeigen<br />
Anzeige von Details<br />
Ändern bestehender Kunden<br />
Anlegen neuer Kunden<br />
Setzen einer Sperre (anstatt eines endgültigen Löschens)<br />
Die HTML-Vorlage ist sehr direkt aufgebaut und enthält mehrere Tabellen, die<br />
jeweils die Darstellung einer Funktion steuern:<br />
525
526<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
Listing 13.6: kunden.html – Vorlage für die Verwaltung der Kunden<br />
<br />
<br />
Bibliotheksverwaltung<br />
Startseite<br />
|<br />
Ausleihe<br />
|<br />
Bücherverwaltung<br />
Kundenverwaltung<br />
<strong>In</strong>formationen<br />
<br />
Kundenliste<br />
<br />
<br />
Status<br />
<br />
<br />
Name<br />
<br />
<br />
Aktion<br />
<br />
<br />
<br />
<br />
{$reader:state}<br />
<br />
<br />
{$reader:name}, {$reader:surname}<br />
<br />
<br />
<br />
Details ansehen<br />
<br />
<br />
<br />
<br />
<br />
<br />
Die Seiten des Projekts<br />
Details für Kunde {$detail_name}<br />
<br />
<br />
Name, Vorname:<br />
<br />
<br />
{$detail_name}, {$detail_surname}<br />
<br />
<br />
<br />
<br />
Anschrift:<br />
<br />
<br />
{$detail_zip} {$detail_city}<br />
<br />
<br />
<br />
<br />
Ändern<br />
<br />
<br />
<br />
<br />
Kundendaten<br />
<br />
<br />
Daten:<br />
<br />
<br />
,<br />
<br />
<br />
<br />
<br />
<br />
PLZ Ort:<br />
<br />
<br />
<br />
527
528<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
<br />
<br />
<br />
<br />
<br />
Status<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Neuer Kunde<br />
<br />
<br />
<br />
Kundendaten<br />
<br />
<br />
Daten:<br />
<br />
<br />
,<br />
<br />
<br />
<br />
<br />
<br />
PLZ Ort:<br />
<br />
<br />
Die Seiten des Projekts<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Besonderheiten gibt es hier nicht. Beachten Sie den Aufruf der anderen Vorlagen<br />
im Menü:<br />
index.php?TEMPLATE=ausleihe<br />
Die Steuerung des Codes erfolgt durch versteckte Felder in den Formularen:<br />
<br />
<br />
Dabei werden auch dynamische Daten ({$detail_id}) übergeben, wodurch Vorlage<br />
und Code miteinander in Verbindung stehen. Die Vorlage verwendet außerdem<br />
eine #IF-Anweisung, um einen Teil der Seite nur bei Bedarf anzuzeigen.<br />
Weitaus aufschlussreicher ist der PHP-Code, der mit dieser Vorlage verknüpft ist:<br />
Listing 13.7: kunden.php – Steuerung der Vorlage mittels Datenbankabfragen<br />
530<br />
}<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
'{$_POST['reader_surname']}', <br />
'{$_POST['reader_zip']}', <br />
'{$_POST['reader_city']}', <br />
1, CURDATE())");<br />
break;<br />
case 'change':<br />
$state = isset($_POST['reader_state']) ? 1 : 0;<br />
$mysqli->query("UPDATE reader SET <br />
name='{$_POST['reader_name']}', <br />
surname='{$_POST['reader_surname']}', <br />
zip='{$_POST['reader_zip']}', <br />
city='{$_POST['reader_city']}', <br />
active=$state <br />
WHERE id = {$_POST['id']}");<br />
break;<br />
}<br />
// Liste fuellen<br />
$query = $mysqli->query("SELECT id, name, surname,<br />
IF(active=0,'Gesperrt','Aktiv') AS state FROM reader");<br />
while($arr = $query->fetch_assoc())<br />
{<br />
$reader[] = $arr;<br />
}<br />
// Details abrufen, wenn gefordert<br />
if (isset($_GET['id']))<br />
{<br />
$query = $mysqli->query("SELECT * FROM reader WHERE id =<br />
{$_GET['id']}");<br />
$detail = $query->fetch_assoc();<br />
$detail_id = $detail['id'];<br />
$detail_name = $detail['name'];<br />
$detail_surname = $detail['surname'];<br />
$detail_zip = $detail['zip'];<br />
$detail_city = $detail['city'];<br />
$detail_state = ($detail['active']==1) ? 'checked' : '';<br />
}<br />
?><br />
Am Anfang des Codes werden die beiden Formulare ausgewertet, erst danach<br />
erfolgt der Aufbau der Liste für die Ausgabe. Auf diese Weise wird sichergestellt,<br />
dass die soeben erfolgten Änderungen sofort in der Anzeige reflektiert werden.
Die Seiten des Projekts<br />
Unbedingt erforderlich ist auch der Einschluss der Vorbereitung der Datenbankabfrage:<br />
include('database.inc.php');<br />
Die Aktionen werden nur ausgewertet, wenn ein Formular abgesendet wurde.<br />
Dazu wird die Servervariable REQUEST_METHOD auf den Wert »POST« hin untersucht:<br />
if ($_SERVER['REQUEST_METHOD']=='POST' && isset($_POST['action']))<br />
Anschließend werden die passenden INSERT- bzw. UPDATE-Befehle gebildet, um die<br />
Daten in der Datenbank zu verändern. Als Quelle dienen die Felder der Formulare.<br />
Sind alle Kommandos abgearbeitet, werden die aktuellen Daten aus der Datenbank<br />
gelesen:<br />
$query = $mysqli->query("SELECT id, name, surname, <br />
IF(active=0,'Gesperrt','Aktiv') AS state FROM reader");<br />
Der SELECT-Befehl sorgt gleich für die korrekte Aufbereitung der Daten für die Ausgabe.<br />
Das Feld active der Tabelle reader kann die Werte 0 oder 1 enthalten. Für<br />
die Anzeige werden diese Werte mit der <strong>My</strong><strong>SQL</strong>-Funktion IF in die Zeichenketten<br />
»Gesperrt« bzw. »Aktiv« umgewandelt. Es ist generell eine gute Idee, fest verdrahtete<br />
Berechnungen in der Datenbank erledigen zu lassen, und damit den<br />
lokalen Code zu entlasten. Nur dann, wenn die Berechnungen selbst dynamisch<br />
sind und möglicherweise häufig verändert werden oder wenn sie sehr komplex<br />
sind, ist PHP besser geeignet.<br />
Die fertige Abfrage muss nun noch in ein Array überführt werden, damit die Template-Engine<br />
die Daten in einer Schleife ausgeben kann:<br />
while($arr = $query->fetch_assoc())<br />
{<br />
$reader[] = $arr;<br />
}<br />
Damit kann dann innerhalb des Abschnitts folgende<br />
Syntax zum Abruf der Felder benutzt werden:<br />
{$reader:name}<br />
Für das Füllen der Detaildaten und der Formulare wird keine Schleife benötigt.<br />
Die erforderlichen Variablen werden deshalb in skalarer Form abgerufen (nach<br />
einer erneuten SELECT-Anweisung):<br />
531
532<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
$detail_id = $detail['id'];<br />
<strong>In</strong> der Vorlage wird dieser Wert beispielsweise benutzt, um die aktuelle Auswahl<br />
im Formular zu halten:<br />
<br />
Die folgende Abbildung zeigt, wie die Seite in Aktion aussieht:<br />
Abbildung 13.5:<br />
Die Kundenverwaltung der Bibliothek<br />
Seite zum Erfassen, Anzeigen und Löschen von Büchern<br />
Die Seite zur Verwaltung der Bücher ist praktisch gleichartig zur Verwaltung der<br />
Kunden aufgebaut. Lediglich die Daten unterscheiden sich und damit Anzahl und<br />
Benennung der Spalten der Tabellen.
Zuerst wird wieder die Vorlage gezeigt:<br />
Listing 13.8: buecher.html – Die Vorlage zur Verwaltung der Bücher<br />
Die Seiten des Projekts<br />
<br />
<br />
Bibliotheksverwaltung<br />
Startseite<br />
|<br />
Ausleihe<br />
|<br />
Kundenverwaltung<br />
Bücherverwaltung<br />
<strong>In</strong>formationen<br />
<br />
Bücherliste<br />
<br />
<br />
Anzahl<br />
<br />
<br />
ISBN<br />
<br />
<br />
Titel<br />
<br />
<br />
Aktion<br />
<br />
<br />
<br />
<br />
<br />
{$books:number}<br />
<br />
<br />
{$books:isbn}<br />
<br />
<br />
{$books:title}<br />
<br />
<br />
<br />
533
534<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
Details<br />
<br />
<br />
<br />
<br />
<br />
Neues Buch erfassen<br />
Ändern / Erfassen<br />
<br />
<br />
<br />
<br />
Buchdaten<br />
<br />
<br />
Titel:<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Untertitel:<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Autor:<br />
<br />
<br />
<br />
<br />
<br />
Die Seiten des Projekts<br />
<br />
ISBN:<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Anzahl<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Ausgeliehen<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Die Vorlage ist bewusst etwas anders als die für die Kunden verwendete aufgebaut.<br />
Detailanzeige, Änderung und Erfassung sind in einem Formular zusammengefasst,<br />
dessen Funktion sich damit dynamisch ändert. Ein verstecktes Feld, dessen<br />
Wert über eine Variable gesteuert wird, macht es möglich:<br />
<br />
535
536<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
Auch die Beschriftung der Sendeschaltfläche ist dynamisch:<br />
<br />
Die eigentliche Arbeit erledigt der Code-Teil:<br />
Listing 13.9: buecher.php – Logik zur Verwaltung der Bücher<br />
Die Seiten des Projekts<br />
break;<br />
}<br />
}<br />
$query = $mysqli->query("SELECT * FROM books");<br />
while($arr = $query->fetch_assoc())<br />
{<br />
$books[] = $arr;<br />
}<br />
// Details abrufen, wenn gefordert<br />
if (isset($_GET['id']))<br />
{<br />
$query = $mysqli->query("SELECT *, <br />
(SELECT COUNT(*) FROM lendout WHERE books_id = b.id) AS lend <br />
FROM books b WHERE id = {$_GET['id']}");<br />
$detail = $query->fetch_assoc();<br />
$detail_id = $detail['id'];<br />
$detail_title = $detail['title'];<br />
$detail_subtitle= $detail['subtitle'];<br />
$detail_author = $detail['author'];<br />
$detail_number = $detail['number'];<br />
$detail_isbn = $detail['isbn'];<br />
$detail_lend = $detail['lend'];<br />
}<br />
?><br />
Der erste Teil steuert die Umschaltung des Formulars. Danach folgen das Einfügen<br />
(INSERT) und Aktualisieren (UPDATE) der Datenbank. Im letzten Teil werden<br />
wieder die frisch aktualisierten Daten abgefragt, damit stets die aktuelle Liste zu<br />
sehen ist. Eine Besonderheit stellt die letzte Abfrage dar, die auf einem so genannten<br />
Sub-Select basiert. (Achtung! Dazu ist mindestens <strong>My</strong><strong>SQL</strong> 4 erforderlich.)<br />
Diese Unterabfrage sichert den Zugriff auf die Tabelle lendout, um die Anzahl der<br />
bereits ausgeliehenen Bücher zu erhalten.<br />
Die Geschäftslogik des Ausleihvorgangs<br />
Die Logik des Ausleihvorgangs muss folgende Aufgaben erfüllen:<br />
Suchen des Kunden, beispielsweise anhand der Kundennummer<br />
Suchen des Buches, beispielsweise anhand der ISBN<br />
»Buchen« des Ausleihvorgangs oder der Rücknahme eines Buches<br />
537
538<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
Außerdem sind noch einige Verwaltungsfunktionen denkbar, die hier nicht vollständig<br />
ausgeführt wurden:<br />
Auflistung komplett ausgeliehener Bücher<br />
Auflistung nicht pünktlich zurückgegebener Bücher<br />
Auflistung säumiger Kunden<br />
Die Vorlage der Ausleihseite<br />
Abbildung 13.6:<br />
Die Bücherseite mit Details über ein Buch<br />
Auch die Ausleihseite besteht aus einer Vorlage und einer Code-Seite. Die Vorlage<br />
ist vergleichsweise schlicht. Auch hier werden wieder mehrere Formulare eingesetzt,<br />
um die Daten zu sammeln. Die Daten (Kunde, Buch) werden in Sitzungsvariablen<br />
gespeichert, damit sie bis zum Ende des Vorgangs erhalten bleiben. Der<br />
Teil der Seite, der die eigentliche Buchung ausführt, wird erst sichtbar, wenn alle<br />
Daten vorliegen.
Listing 13.10: ausleihe.html – Vorlage für die Ausleihseite<br />
Die Seiten des Projekts<br />
<br />
<br />
Bibliotheksverwaltung<br />
Startseite<br />
|<br />
Kundenverwaltung<br />
|<br />
Bücherverwaltung<br />
Kunden<br />
<br />
<br />
<br />
Kunden suchen<br />
<br />
<br />
Name <br />
<br />
<br />
<br />
<br />
Buch suchen<br />
<br />
<br />
ISBN <br />
<br />
<br />
<br />
<br />
<br />
<br />
Ausleihe- oder Rücknahme<br />
Ausleih- oder Rücknahmeinformationen:<br />
<br />
Kundenname: {$reader_name}<br />
Kundennummer: {$reader_id}<br />
Buchtitel: {$book_title}<br />
ISBN: {$book_isbn}<br />
Es sind noch {$available} Bücher verfügbar.<br />
<br />
<br />
539
540<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Übersicht aller Vorgänge des Kunden<br />
<br />
Buch ID<br />
Ausleihdatum<br />
Fälligkeit<br />
Aktion<br />
<br />
<br />
<br />
{$history:books_id}<br />
{$history:lendingdate}<br />
{$history:due}<br />
<br />
Rückgabe<br />
<br />
<br />
<br />
<br />
<br />
Fehler: {$reader_error}{$book_error}<br />
<br />
<br />
<br />
Diese Seite nutzt die Wiederholungs-Anweisungen zum Aufbau und definiert<br />
einen Sprung auf die Ausleihseite.
Der Code der Ausleihseite<br />
Die Seiten des Projekts<br />
Listing 13.11: ausleihe.php – Die Steuerung der Vorlage nutzt intensiv die Datenbank<br />
542<br />
}<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
$arr = $result->fetch_assoc();<br />
$reader_name = $arr['name'];<br />
$reader_id = $arr['id'];<br />
}<br />
else<br />
{<br />
$reader_error = 'Auswahl des Kunden war<br />
nicht eindeutig';<br />
$error = true;<br />
}<br />
break;<br />
case 'book':<br />
$result = $mysqli->query("SELECT * FROM books <br />
WHERE title LIKE '%{$_POST['book']}%' <br />
OR isbn = '{$_POST['book']}'");<br />
if ($result->num_rows == 1)<br />
{<br />
$arr = $result->fetch_assoc();<br />
$book_title = $arr['title'];<br />
$book_id = $arr['id'];<br />
$book_isbn = $arr['isbn'];<br />
$number = $arr['number'];<br />
}<br />
else<br />
{<br />
$book_error = 'Auswahl des Buches war<br />
nicht eindeutig';<br />
$error = true;<br />
}<br />
break;<br />
}<br />
if (!isset($error))<br />
{<br />
$result = $mysqli->query("SELECT COUNT(*) AS lent <br />
FROM lendout <br />
WHERE books_id=$book_id AND active=1");<br />
$books_to_lend = $result->fetch_object();<br />
$available = $number - $books_to_lend->lent;<br />
$disabled = ($available
}<br />
?><br />
Die Seiten des Projekts<br />
$_SESSION['book_title'] = $book_title;<br />
$_SESSION['book_id'] = $book_id;<br />
$_SESSION['book_isbn'] = $book_isbn;<br />
$_SESSION['number'] = $number;<br />
// Ermittle die Liste aller bisherigen Buchungen fuer diesen Benutzer<br />
$sql = "SELECT id, books_id, <br />
DATE_FORMAT(lendingdate, '%d.%m.%Y') AS lendingdate, <br />
IF(TO_DAYS(CURDATE())-TO_DAYS(lendingdate) <br />
> lendingperiod, 'Überfällig', <br />
CONCAT('Noch ', 30 - (TO_DAYS(CURDATE()) <br />
- TO_DAYS(lendingdate)), ' Tage') ) AS due <br />
FROM lendout <br />
WHERE reader_id=$reader_id <br />
AND active=1";<br />
$result = $mysqli->query($sql);<br />
if ($result != false)<br />
{<br />
while($arr = $result->fetch_assoc())<br />
{<br />
$history[] = $arr;<br />
}<br />
}<br />
$noerror = true;<br />
Der Code basiert wieder auf dem bereits mehrfach verwendeten Prinzip der Übertragung<br />
eines Kommandos, dessen Erkennen in einem switch-Zweig und der<br />
Steuerung der entsprechenden <strong>SQL</strong>-Anweisungen auf Basis der Auswahl. Freilich<br />
ist bei der Ausleihe (und Rückgabe) etwas mehr zu tun, da nun alle drei Tabellen<br />
an der Aktion beteiligt sind.<br />
Da Kunden und Bücher getrennt gesucht werden, werden die bereits gefundenen<br />
Werte in Sitzungsvariablen festgehalten. Damit das Sitzungsmanagement funktioniert,<br />
wird es am Beginn des Skripts gestartet:<br />
session_start();<br />
Dann werden die bestehenden Sitzungsvariablen wiedergeholt, soweit vorhanden.<br />
Die Fehlervariable $error dient der Steuerung des entsprechenden #IF-Blockes.<br />
Der <strong>In</strong>halt eines solchen Blockes wird ausgeführt, wenn die Variable existiert. Um<br />
unter allen Umständen zu verhindern, dass die Variable ohne Vorliegen eines<br />
Fehlergrunds einen Wert enthält, wird sie zunächst zurückgesetzt:<br />
unset($error);<br />
543
544<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
Dann werden die entsprechenden Abfragen an die Datenbank gesendet. Beim<br />
Einfügen wird die zulässige Ausleihzeit des Buches fest auf 30 Tage gesetzt. Die<br />
Tabelle nutzt außerdem die Spalte active, um festzuhalten, welche Datensätze<br />
aktiv sind. Um eine Historie des Benutzerverhaltens zu erhalten, werden die<br />
Datensätze bei Rückgabe des Buches nicht gelöscht, sondern der Wert in der<br />
Spalte active wird lediglich auf 0 gesetzt:<br />
UPDATE lendout SET active = 0 WHERE id = {$_GET['id']}<br />
Die Anzeige der noch verfügbaren Bücher nutzt dieselbe Spalte, was die Abfrage<br />
sehr einfach macht:<br />
SELECT COUNT(*) AS lent FROM lendout <br />
WHERE books_id=$book_id AND active=1<br />
Etwas komplexer ist die Abfrage der Liste der Ausleihvorgänge (Historie). Hier werden<br />
Datumsfunktionen von <strong>My</strong><strong>SQL</strong> benutzt, um die Anzahl der noch verbleibenden<br />
Ausleihtage festzustellen und gegebenenfalls eine Meldung anzuzeigen, wenn<br />
ein Buch überfällig ist. Damit kann verhindert werden, dass säumige Nutzer weitere<br />
Bücher ausleihen.<br />
SELECT id, books_id,<br />
DATE_FORMAT formatiert das interne Datumsformat für die Ausgabe im Deutschen<br />
Format:<br />
DATE_FORMAT(lendingdate, '%d.%m.%Y') AS lendingdate,<br />
Die IF-Funktion ermittelt die Datumsdifferenz. Dazu wird mit TO_DAYS ein Tageszähler<br />
ermittelt und mit dem aktuellen Datum (CURDATE) verglichen. Als Vergleichswert<br />
wird die individuelle Ausleihdauer (in lendingperiod) herangezogen,<br />
sodass die Funktion auf vereinbarte Verlängerungen korrekt reagieren kann. Die<br />
IF-Funktion gibt bei wahrer Bedingung den ersten Teil zurück (hier das Wort<br />
»Überfällig«), andernfalls einen Text, der mittels CONCAT zusammengesetzt wird.<br />
IF(TO_DAYS(CURDATE())-TO_DAYS(lendingdate) > lendingperiod, <br />
'Überfällig',<br />
CONCAT erlaubt die Angabe mehrerer Parameter, die zu einer Zeichenkette zusammengefasst<br />
werden, unabhängig vom Datentyp. Der Name der damit neu erzeugten<br />
Spalte wird als due (fällig) bezeichnet:<br />
CONCAT('Noch ',<br />
30 - (TO_DAYS(CURDATE())-TO_DAYS(lendingdate)),<br />
' Tage') ) AS due
Die Seiten des Projekts<br />
Als Auswahlkriterium gilt logischerweise der ausgewählte Nutzer. Außerdem werden<br />
nur offene Leihvorgänge angezeigt:<br />
FROM lendout <br />
WHERE reader_id=$reader_id <br />
AND active=1<br />
Die Übergabe der Abfrageergebnisse an das Array $history korrespondiert wieder<br />
mit der Vorlage:<br />
while($arr = $result->fetch_assoc())<br />
{<br />
$history[] = $arr;<br />
}<br />
<strong>In</strong> der Vorlage wird beispielsweise die Fälligkeit folgendermaßen ausgegeben:<br />
{$history:due}<br />
Wie das fertig aussieht, kann der nächsten Abbildung entnommen werden:<br />
Abbildung 13.7:<br />
Die Ausleihseite in Aktion<br />
545
13.4 Ausblick<br />
546<br />
Datenbanklösungen mit <strong>My</strong><strong>SQL</strong><br />
Die vorgestellte Bibliothek zeigte zwei entscheidende Techniken:<br />
Funktionsweise eines rudimentären Template-Systems<br />
Praktische Anwendung der <strong>My</strong><strong>SQL</strong>i-Bibliothek<br />
Freilich bleibt noch einiges zu tun, um ein solches System praxistauglich zu<br />
machen. Dazu gehört zuerst sicher eine vernünftige Gestaltung, die mit einer<br />
Reihe von Maßnahmen einhergeht. Zuerst sollte eine zentrale Datei erstellt werden,<br />
die CSS-(Style)-Definitionen enthält und in allen Vorlagen Verwendung findet.<br />
Außerdem fehlen noch einige grundlegende Funktionen:<br />
Benutzerverwaltung und Absicherung gegen unbefugten Zugriff.<br />
Regalverwaltung. Es gibt zwar bereits eine Spalte shelf, aber diese wird nicht<br />
benutzt. Für Benutzer könnte sich daran eine Suchfunktion anschließen, über<br />
die die Bücher schnell gefunden werden.<br />
Fehlermanagement. Datenbank- und Bedienfehler führen meist zum Programmabbruch<br />
Online-Zugriff. Die derzeitige Lösung ist praktisch nur intranet-tauglich. Für<br />
eine auch online betreibbare Bibliothek sind noch einige Ergänzungen<br />
(Benutzeranmeldung, Benutzerdienste, Sicherheitsfunktionen) einzubauen.<br />
Hilfen bei der Erfassung von Buchdaten.<br />
Zumindest der letzte Punkt kann eine echte Erleichterung sein. Dazu finden Sie<br />
im nächsten Kapitel XML und Webservices ein entsprechendes Projekt, das den<br />
Amazon-Webservice nutzt, um vollständige bibliografische Daten zu einem Buch<br />
auf Grundlage der ISBN zu beschaffen. So ist auch der Katalog schnell gefüllt.<br />
Nicht zuletzt kann auch die Umstellung auf ein kommerzielles Templatesystem<br />
wie phpTemple ein Schritt hin zu einer schnell erstellten und leicht zu wartenden<br />
Lösung sein, weil hier beispielsweise die <strong>SQL</strong>-Abfragen unverändert übernommen<br />
werden können. Auf PHP kann weitgehend verzichtet werden, weil einige Makrofunktionen<br />
– gesteuert über XML-Tags – die Arbeit erledigen.
13.5 Kontrollfragen<br />
1. Wozu werden Template-Systeme eingesetzt?<br />
Kontrollfragen<br />
2. Erweitern Sie die Applikation, sodass zu jedem Leser der Bibliothek mehrere Telefonnummern<br />
gespeichert werden können. Wie gehen Sie vor?<br />
3. Formulieren Sie eine <strong>SQL</strong>-Abfrage, die die nötigen Daten zur Erstellung von<br />
Mahnschreiben erzeugt, um säumige Leser auf die bereits abgelaufene Rückgabefrist<br />
hinzuweisen.<br />
547
XML und<br />
Webservices<br />
<strong>14</strong>
550<br />
XML und Webservices<br />
XML ist inzwischen für viele Anwendungen eine fest etablierte Technologie. Mit<br />
PHP5 fanden auch neue, vereinheitlichte Bibliotheken Verwendung, die die Verarbeitung<br />
und Erzeugung von XML noch einfacher machen.<br />
<strong>14</strong>.1 Vorbemerkungen<br />
Dieser Abschnitt klärt in knapper Form über einige Grundlagen auf, die Sie unbedingt<br />
beherrschen sollten, bevor Sie praktisch mit XML arbeiten.<br />
XML<br />
XML ist eine Auszeichnungssprache für Dokumente, die strukturierte <strong>In</strong>formationen<br />
enthalten. Derartige <strong>In</strong>formationen enthalten beliebigen <strong>In</strong>halt, also Text,<br />
Bilder usw. und Angaben darüber, was dieser <strong>In</strong>halt bedeutet; eine Art Semantik<br />
des <strong>In</strong>halts. <strong>In</strong> einem XML-Dokument wird eine Überschrift nicht deshalb zur<br />
Überschrift, weil Sie in einer bestimmten Weise erscheint, sondern weil sie explizit<br />
als Überschrift markiert wurde. Überhaupt spielt die Formatierung keine Rolle<br />
und ist auch technisch nicht möglich. Erst das Ausgabegerät kann die <strong>In</strong>formationen<br />
anhand ihrer Struktur in eine für den Menschen (oder andere Maschinen)<br />
sinnvoll formatierte Form überführen.<br />
XML ist eine so genannte Markup-Sprache (XML = Extensible Markup Language).<br />
Sie dient der Erstellung von Strukturen für Dokumente. XML ist ein Standard,<br />
der die Art der Strukturierung beschreibt.<br />
Was ist ein Dokument?<br />
Die Anzahl der Applikationen, die XML bereits nutzen, unterstützen oder sogar<br />
darauf angewiesen sind, wächst drastisch. XML ist längst im Alltag angekommen<br />
und keineswegs mehr ein Exot unter den Datenformaten. Webprogrammierer, vor<br />
allem mit einem Hintergrund im grafischen Bereich, werden das vielleicht noch<br />
nicht so empfunden haben. Wer jedoch mit Datenformaten hantiert, kommt<br />
inzwischen fast unweigerlich mit XML in Berührung. Allerdings ist XML kein Allheilmittel<br />
und auch kein Ersatz für relationale Datenbanken. Es ist eine sinnvolle<br />
Ergänzung, die vor allem den systemübergreifenden Datenaustausch stark vereinfacht.
Vorbemerkungen<br />
XML-Dokumente sind die Basis dieses Datenaustauschs. Letztlich ist XML immer<br />
wieder eine Datei, gleich ob sie als solche im Dateisystem abgelegt oder als Teil<br />
einer HTTP-Anforderung gesendet wird. Der Sinn eines solchen Dokuments<br />
ergibt sich aus der gewählten Strukturbeschreibung. Ein XML-Dokument kann<br />
deshalb Webseiten beschreiben (XHTML), Vektorgrafiken enthalten (SVG),<br />
mathematische Formeln darstellen (MathML) oder sogar Rechnungen beinhalten<br />
(EMC). Es gibt Tausende derartige vordefinierte Formate und weitere lassen sich<br />
jederzeit mit bestimmten Werkzeugen (und entsprechenden Fachkenntnissen)<br />
entwickeln. XML selbst ist also nur eine Art Metasprache, die eine Vorschrift zur<br />
Definition von Datenformaten darstellt. XML-Dokumente entstehen, indem sie<br />
dieser globalen Vorschrift genügen, egal ob die konkrete Struktur im Einzelfall<br />
sinnvoll ist oder nicht.<br />
XML versus HTML<br />
XML und HTML haben nur oberflächlich etwas gemeinsam. <strong>In</strong> HTML ist sowohl<br />
der Satz an Formatanweisungen als auch deren Semantik festgelegt. Das Tag <br />
bezeichnet seinen <strong>In</strong>halt als fett und es wird erwartet, dass ein Ausgabegerät dies<br />
auch so darstellt. Das Tag ist dagegen bedeutungslos und wird ignoriert.<br />
Zugleich wird ein Ausgabegerät (der Browser) definiert und damit das Verhalten<br />
der mit HTML markierten Dokumente fixiert.<br />
XML ist universeller und legt solche Regeln nicht fest. Die Ähnlichkeit hat ihren<br />
Ursprung in den gemeinsamen Wurzeln. HTML wurde auf der Basis der Urform<br />
aller Auszeichnungssprachen, SGML, entworfen und letztlich ist XML eine vereinfachte<br />
Form von SGML. Daher rührt die Ähnlichkeit.<br />
XML definiert niemals selbst eine Semantik oder ein Satz von Tags. Dies ist Aufgabe<br />
der anwendenden <strong>In</strong>stanz. Ein Sinn aus einem XML-Dokument ergibt sich<br />
erst, wenn eine bestimmte, konkrete Auszeichnungssprache, den Regel von XML<br />
folgend, entworfen wurde. <strong>In</strong> diesem Abschnitt wird noch die Variante RSS vorgestellt,<br />
die der Erstellung von Dokumenten dient, die Einträge in einem Weblog<br />
beschreiben. Für HTML gibt es auch eine Entsprechung in XML, XHTML<br />
genannt.<br />
Um nun die Semantik (den Sinn) in ein Dokument zu bekommen, benötigt man<br />
eine <strong>In</strong>stanz, die in der Lage ist, XML zu interpretieren. Einen Sinn kann man freilich<br />
nur erkennen, wenn man das Ziel der Darstellung kennt. Ohne ein Programm,<br />
das eine Weiterverarbeitung vornimmt, bleibt XML Selbstzweck. Deshalb kommt<br />
diesem Verarbeitungsprozess, nicht zuletzt in PHP, eine große Bedeutung zu.<br />
551
Warum XML?<br />
552<br />
XML und Webservices<br />
Aus der einleitenden Darstellung wird sich nicht zwingend ableiten lassen, warum<br />
XML nun eine so große Bedeutung zukommt. XML wurde für reichhaltig strukturierte<br />
Dokumente entworfen, die ein hohes Maß an Automation bei der Verarbeitung<br />
erfordern.<br />
Weder HTML noch SGML können dies in dieser exzessiven Form leisten. HTML<br />
scheitert an der Vermischung von Struktur und Semantik, was die Nutzbarkeit einschränkt1<br />
. SGML ist universell, zugleich aber sehr viel komplizierter und mit vielen<br />
Nebeneffekten durch die komplexe Syntax belegt. Programme, die SGML gut<br />
verarbeiten, sind sehr teuer. Viele Anwendungen benötigen eine derartige Komplexität<br />
nicht, weshalb sich SGML außerhalb der Druckindustrie nie durchgesetzt<br />
hat.<br />
XML ist einfacher, klar strukturiert und leicht zu lesen. Programme, die XML verarbeiten<br />
– so genannte Parser – sind leicht zu erstellen und zu perfektionieren.<br />
Praktisch existieren sie für alle Sprachen, Plattformen und Systeme in vollkommen<br />
ausgereifter Form. Im Verbund mit der technischen Unterstützung wird XML<br />
praktisch einsetzbar – deshalb ist XML so wichtig.<br />
Wie XML definiert ist<br />
XML wird durch eine Reihe von Dokumenten definiert, die alle im Web zugänglich<br />
sind. Sie sind teilweise äußerst abstrakt und sehr technisch, im Alltag also<br />
wenig hilfreich. Ein Blick hinein kann dennoch nicht schaden, um ein Gefühl für<br />
den Hintergrund zu bekommen. Wer selbst XML-Formate entwerfen möchte,<br />
muss diese Dokumente tatsächlich verstanden haben. Hier folgt eine Auswahl:<br />
Extensible Markup Language (XML) 1.0 (http://www.w3.org/TR/WD-xml)<br />
Definiert die Syntax von XML, sozusagen die Mutter aller Dokumente.<br />
XML Pointer Language (XPointer, http://www.w3.org/TR/1998/WD-xptr-<br />
19980303) and XML Linking Language (Xlink, http://www.w3.org/TR/1998/<br />
WD-xlink-19980303)<br />
Beide Standards definieren Wege, um Links (Verknüpfungen) zwischen<br />
Datenquellen zu definieren. Sie sind quasi der Ersatz des HTML-Tags in<br />
der XML-Welt. XPointer beschreibt, wie eine Ressource adressiert wird (wo<br />
1 Für seinen ureigenen Zweck, nämlich Webseiten, war dies jedoch sehr sinnvoll.
Vorbemerkungen<br />
man sie findet), während XLink die Beziehungen zwischen zwei oder mehr<br />
Ressourcen definiert.<br />
Extensible Style Language (XSL) (http://www.w3.org/TR/WD-xsl)<br />
Dieser Standard definiert die globale Methodik der Formatierung und Transformation<br />
(letzteres im wichtigen Teilstandard XSLT). Damit wird für XML<br />
die Brücke zur realen Welt der Ausgabegeräte geschaffen. Eine einführende<br />
Beschreibung folgt im nächsten Abschnitt.<br />
XPath und XQuery<br />
Für die gezielte Auswahl von Teilstrukturen aus einem XML-Dokument dient<br />
die Abfragesprache XPath. XQuery dient ähnlichen Zwecken und ist bei sehr<br />
datenorientierten Strukturen von Vorteil, da es Ähnlichkeiten mit <strong>SQL</strong> aufweist.<br />
XPath ist bereits seit einiger Zeit etabliert, XQuery noch in der Entwicklung<br />
begriffen.<br />
DTD und Schema<br />
Vor der Struktur steht die Strukturdefinition. Eine DTD (Document Type Definition)<br />
definiert, welche Tags wie von anderen abhängen. Leider ist die DTD<br />
selbst nicht in einem XML-Format definiert, was die automatisierte Verarbeitung<br />
etwas erschwert. Das neuere XML Schema ist dagegen selbst ein XML-<br />
Dialekt. Im Gegensatz zur DTD weist es einen starke Unterstützung für Strukturen<br />
auf, wie sie Datenbank nutzen. So sind beispielsweise viele elementare<br />
Datentypen definiert.<br />
Für die Arbeit mit XML und PHP sollten Sie mit XML, XSLT und in Grundzügen<br />
mit XPath und DTD vertraut sein. Das ist mitnichten trivial, aber der Einsatz lohnt<br />
sich.<br />
XSLT<br />
XSLT ist die Transformationssprache für XML-Dokumente. Mit Hilfe von XSLT<br />
kann beispielsweise ein Teil eines XML-Datenpaketes in HTML umgewandelt<br />
werden, damit der Browser es anzeigen kann. XSLT ist nicht einfach, aber<br />
unglaublich mächtig.<br />
Einführung<br />
XSLT erlaubt eine weitgehend eigene Kontrolle der weiteren Verarbeitungsschritte<br />
gegenüber dem, was fertige Programm anbieten. XSLT erlaubt es außer-<br />
553
554<br />
XML und Webservices<br />
dem, fertige Vokabulare einfach zu übernehmen und an eigene Bedingungen<br />
anzupassen. Damit wird der Datenaustausch weiter vereinfacht und verbessert.<br />
Neben der Transformation von XML nach XML ist vor allem, die Umwandlung<br />
von XML nach HTML und von XML nach Text von Bedeutung. XSLT erzeugt in<br />
der Regel eine frei formatierte Datei. Dies ist meist XML, muss jedoch nicht. Als<br />
Eingabe muss jedoch immer XML vorliegen. XSLT nutzt zur Auswahl von Knoten<br />
die Abfragesprache XPath, womit sich der Kreis zu den anderen Standards wieder<br />
schließt. Sie müssen also mindestens XPath und XSLT beherrschen, um praktisch<br />
Umwandlungsprogramme schreiben zu können. PHP wird hierbei zur reinen<br />
Hilfsschicht degradiert, es dient lediglich dem Laden der Dateien von der Festplatte<br />
(oder von einem Server) und dem Rückschreiben der Ergebnisse.<br />
Eine kompakte Einführung in XSLT<br />
XSLT ist eine so genannte funktionale Programmiersprache. Das Prinzip unterscheidet<br />
sich grundlegend von den objektorientierten oder imperativen Sprachen,<br />
wie beispielsweise PHP. Der Programmfluss selbst wird in erster Linie durch Automatismen<br />
initiiert, in zweiter Linie dann durch Regeln. Regeln definieren Sie, um<br />
bestimmte Effekte beim Auftreten von bestimmten Daten zu erreichen. Vorteil<br />
derartiger Systeme ist die weitgehende – bei XSLT per Definition die vollkommene<br />
– Befreiung von Seiteneffekten. Wenn eine Regel gilt, dann wird diese und<br />
nur diese ausgeführt und dies in immer der gleichen Art und Weise. Dazu gehört<br />
auch, dass Variablen zwar verfügbar sind, beispielsweise um einer Regel einen<br />
Wert zu übergeben, ihren <strong>In</strong>halt aber nachträglich nicht ändern können. Sie verhalten<br />
sich also eher wie die Konstanten in PHP, abgesehen davon, dass der <strong>In</strong>halt<br />
dynamisch definiert werden kann. Nachträgliche Änderungen könnten Seiteneffekte<br />
erzeugen, was nicht erlaubt ist.<br />
Dennoch kann man damit erstaunlich effektiv programmieren und verblüffende<br />
Resultate erzielen. Nicht immer ist XSLT die perfekte Sprache. Richtig leistungsfähig<br />
wird sie erst in Kombination mit einer modernen objektorientierten Sprache,<br />
die hinreichende imperative Merkmale aufweist. Es ist nahe liegend, Transformation<br />
und Programm mit PHP in einen Kontext zu überführen. Zuvor sind jedoch<br />
wenigstens elementare Kenntnisse von XSLT notwendig.
Die Basisregeln in XSLT<br />
Vorbemerkungen<br />
XSLT basiert auf XML, weshalb jede Datei durch die entsprechende Deklaration<br />
eingeleitet wird. Dann folgt das Wurzelelement . Das W3C empfiehlt<br />
als Standardnamensraum xsl; diese Angabe ist aber im Prinzip freiwillig. Es<br />
ist jedoch empfehlenswert, generell den Standardnamensraum zu verwenden.<br />
Daraus ergibt sich folgendes Grundgerüst für XSLT:<br />
Listing <strong>14</strong>.1: Ein leeres XSLT-Programm-Fragment<br />
<br />
<br />
<br />
Durch die Erweiterung des Attributes xmlns wird der Namensraumalias xsl festgelegt.<br />
Zwischen den Wurzelelementen wird nun das Regelwerk aufgebaut. Eine<br />
zentrale Rolle spielt das Element . Templates (dt. Vorlagen) bilden<br />
die Stufen der eigentlichen Transformation. Dabei gibt es zwei Arten von Templates.<br />
Zum einen können sie durch eine XPath-Anweisung in ihrer Zuständigkeit<br />
programmiert werden. Die folgende Regel zeigt, wie jedes Element zu<br />
einer Ausgabe im Ausgabedatenstrom führt:<br />
<br />
NAME<br />
<br />
Eine andere Methode ist der Aufruf benannter Vorlagen, dazu später mehr. Der<br />
<strong>In</strong>halt des Elements findet hier freilich noch keine Berücksichtigung. Text kann,<br />
wie gezeigt, direkt ausgegeben werden. Beachten Sie dabei, dass es sich auch hier<br />
um wohlgeformtes XML handeln muss; HTML muss also gegebenenfalls den<br />
Regeln von XHTML 1.0 entsprechend modifiziert werden.<br />
Wenn Sie eine Vorlage mit benennen, können<br />
Sie diese folgendermaßen aufrufen:<br />
<br />
Soll explizit Text ausgegeben werden, der mit dem verwendeten XML-Editor 2<br />
nicht darstellbar ist, muss das -Element eingesetzt werden. Das ist<br />
eigentlich – nach der Spezifikation – immer notwendig. Die direkte Angabe von<br />
Text oder Tags ist eine Vereinfachung.<br />
2 Schreibt man XSLT mit einem einfachen Editor, trifft dies nur bedingt zu.<br />
555
556<br />
XML und Webservices<br />
<br />
Hier folgt ein Zeilenumbruch: 0x0A<br />
<br />
Wo Text ist, sind Kommentare nicht weit. Diese entsprechen, XML-konform, den<br />
aus HTML bekannten und werden nicht in den Ausgabedatenstrom übernommen:<br />
<br />
Vorlagen werden meist verschachtelt angewendet. Das folgende Beispiel zeigt das<br />
Grundgerüst einer HTML-Seite, wie sie mit XSLT erzeugt wird:<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Zuerst erkennt der XSLT-Prozessor hier, dass die Vorlage das Wurzelelement der<br />
XML-Quelle verarbeitet. Dann wird das Grundgerüst der HTML-Seite erstellt.<br />
<strong>In</strong>nerhalb des Body-Tags wird versucht, alle übrigen Elemente durch Aufruf der<br />
passenden Vorlagen zu verarbeiten. Dass keine Parameter<br />
hat, ist ein spezieller Fall. Er setzt voraus, dass alle Elemente irgendwo auf eine<br />
passende Vorlage stoßen, wobei der Prozessor den besten Treffer auswählt und diesen<br />
– und nur diesen – ausführt.<br />
Allerdings besitzt der Prozessor eine Fallback-Funktion. Wenn kein Template<br />
zutrifft, wird der <strong>In</strong>halt des aktuellen Tags genommen und als gültiger Ausgabewert<br />
betrachtet. Voraussetzung ist aber, dass wenigstens an einer Stelle<br />
steht, um die Ausgabe auszulösen.<br />
Sollen <strong>In</strong>halte von Tags gezielt ausgegeben werden, was sicher der häufigste Weg<br />
ist, findet Verwendung. Das Attribut select wählt den <strong>In</strong>halt des<br />
durch eine XPath-Anweisung ermittelten Knotens und die gesamte Anweisung gibt<br />
diesen als Zeichenkette aus.<br />
<br />
<br />
<br />
Beim Einsatz innerhalb einer Vorlage bezieht sich der Pfad, den select akzeptiert,<br />
auf den übergebenen Knoten, ist also relativ. Sie können aber absolute Angaben
Vorbemerkungen<br />
verwenden. Der allein stehende Punkt reflektiert in XPath den aktuellen Knoten,<br />
im Beispiel also den <strong>In</strong>halt des Tags . Auf eben diesem Wege werden auch<br />
Attribute gelesen. Das folgende Beispiel sucht nach Elementen vom Typ und<br />
gibt den <strong>In</strong>halt des Attributes href aus:<br />
<br />
<br />
<br />
Der direkte Zugriff mit einer absoluten XPath-Anweisung wäre a/@href. Sie können<br />
auf den Parameter eines Attributes auch direkt zugreifen. Ein -Tag<br />
wird folgendermaßen in transformiert:<br />
<br />
<br />
<br />
Die Schreibweise mit den geschweiften Klammern ist immer dann angebracht,<br />
wenn der Einsatz eines Tags aufgrund der Syntax nicht möglich ist. Andererseits ist<br />
es mit und möglich, beliebige Tags indirekt zu<br />
erzeugen.<br />
Mit XSLT programmieren<br />
Bei XSLT spricht man von einer funktionalen Programmiersprache. Zum Programmieren<br />
gehören jedoch nicht nur Regeln, wie sie in XSLT durch die Vorlagen<br />
gebildet werden, sondern auch Programmanweisungen.<br />
Zuerst soll eine einfache Verzweigung mit vorgestellt werden:<br />
<br />
Dieses Verzeichnis enthält Dateien<br />
<br />
Der Test kann verschiedene Operatoren und Funktionen verwenden, um Knoten<br />
nach allerhand Kriterien zu untersuchen. Eine Else-Anweisung gibt es übrigens<br />
nicht, hierfür ist die Mehrfachverzweigung gedacht:<br />
<br />
<br />
Archiv<br />
<br />
<br />
Compressed<br />
557
558<br />
XML und Webservices<br />
<br />
<br />
Hidden<br />
<br />
<br />
Unknown<br />
<br />
<br />
Wollen Sie Listen von bestimmten Tags an einer Stelle ausgeben, ist <br />
sehr praktisch, was ähnlich wie das foreach von PHP funktioniert. for-Schleifen<br />
im Sinne imperativer Programmierung gibt es jedoch nicht, weil veränderliche<br />
Zustände nicht erlaubt sind.<br />
<br />
<br />
<br />
Die -Anweisung gibt, wie auch, jeweils einen aktuellen<br />
Knoten für jedes Element aus, das gefunden wurde. Deshalb funktioniert<br />
auch hier der verkürzte Zugriff auf den <strong>In</strong>halt mit dem Punkt-Alias.<br />
Von <strong>In</strong>teresse ist oft auch eine Sortiermöglichkeit. Sie können dazu innerhalb<br />
einer Schleife mit eine Anweisung platzieren, die das zuverlässig erledigt:<br />
<br />
<br />
<br />
<br />
Das Element ist übrigens auch in anwendbar.<br />
Es versteht freilich einige Attribute mehr, mit denen die Steuerung der Sortierung<br />
erfolgen kann.<br />
Was Sie in XSLT nicht finden, sind die Anweisungen »for« und »while«. Beide<br />
benötigen Variable, deren Zustand sich ändert. Das ist in funktionalen Sprachen<br />
nicht möglich, weswegen die Anweisungen nicht sinnvoll sind. Komplexere<br />
Schleifen werden durch Rekursion (Selbstaufruf einer Vorlage) und Parameter<br />
ermöglicht. Oft ist dies kompakter als vergleichsweise imperative Programme, für<br />
PHP-Programmierer aber möglicherweise ungewohnt.<br />
Variablen sind dennoch verwendbar, sie verhalten sich aber ähnlich den Konstanten<br />
in anderen Sprachen. Sie können Werte, Knoten oder Knotenbäume speichern:
Variable können natürlich auch komplexere <strong>In</strong>halte aufnehmen:<br />
Vorbemerkungen<br />
<br />
<br />
TRUE<br />
<br />
<br />
FALSE<br />
<br />
<br />
Sie sehen im letzten Beispiel auch die Verwendung von XPath-Funktionen. Variablen<br />
gelten nur innerhalb der Vorlage oder der Schleife, in der sie definiert<br />
wurden. Dieses Verhalten ist nicht modifizierbar, das heißt, es gibt keine Modifikatoren<br />
wie public oder private, wie sie bei den objektorientierten Funktionen von<br />
PHP anwendbar sind.<br />
Ähnlich wie Variablen werden auch Parameter verwendet. Damit können Sie<br />
einer Vorlage verschiedene Werte übergeben, damit diese sich je nach Art des Aufrufs<br />
unterschiedlich verhält. Der Aufruf sieht folgendermaßen aus (am Beispiel<br />
einer benannten Vorlage):<br />
<br />
no<br />
<br />
<strong>In</strong>nerhalb der Vorlage werden die übergebenen Parameter dann so verwendet:<br />
<br />
<br />
...<br />
Das select-Attribut in bestimmt einen Standardwert, wenn der Parameter<br />
nicht übergeben wurde.<br />
XSLT praktisch verwenden<br />
XSLT verfügt über einige komplexere Anweisungen, die für größere Projekte von<br />
Bedeutung sind. Dazu gehört zum Erzeugen fortlaufender Nummern<br />
oder Buchstabenfolgen.<br />
Oft ist auch der Umgang mit ganzen Knoten notwendig, statt dem Textinhalt des<br />
Knotens. Dann findet Verwendung. Sollen Knoten und Attribute<br />
559
560<br />
XML und Webservices<br />
kopiert werden, können mehrere Anweisungen mit zusammengefasst<br />
werden.<br />
Das folgende Beispiel kopiert ein XML-Dokument vollständig in ein anderes:<br />
<br />
<br />
<br />
<br />
<br />
<br />
Wenn Sie sich die Frage stellen, warum ein Dokument ausgerechnet mit XSLT<br />
unverändert kopiert werden sollte, ist ein Blick auf angebracht. Mit<br />
dieser Anweisung kann die Kodierung des Ausgabestromes gesteuert werden. Das<br />
Tag steht immer am Anfang des Dokumentes. Wenn Ihr XML-Dokument UTF-8<br />
kodiert ist, können Sie es mit der Kopiervorlage des letzten Beispiels leicht in ein<br />
anderes Format bringen, nebenbei auch ins HTML 4.0-Format:<br />
<br />
Sie können weitere XSLT-Dateien mit Hilfe der Anweisungen und<br />
importieren. Die erste Funktion kann ein Dokument so einfügen,<br />
als wäre der <strong>In</strong>halt an dieser Stelle geschrieben worden. Der Import hat eine<br />
geringe Priorität. Stehen vorhandene und importierte Regeln miteinander im Konflikt,<br />
unterliegen die importierten.<br />
Die XSLT-Funktionen<br />
Transformationen laufen oft in Abhängigkeit von konkreten Daten ab. XSLT stellt<br />
deshalb einige elementare Funktionen zur Verfügung, die meist in select- und test-<br />
Attributen benutzt werden.<br />
Funktion Beschreibung<br />
boolean()<br />
number()<br />
string()<br />
Typumwandlungen ausführen. Manche Funktionen brauchen<br />
einen bestimmten Typ als Parameter.<br />
format_number() Formatiert Zahlen für die Ausgabe.<br />
Tabelle <strong>14</strong>.1: XSLT-Funktionen
Funktion Beschreibung<br />
ceiling()<br />
floor()<br />
round()<br />
Vorbemerkungen<br />
Zahlenberechnungen und Runden. Die Funktionsweise<br />
entspricht den Funktion ceil, floor und round in PHP.<br />
concat() Verbindet Zeichenketten, ähnlich der Kombination mit<br />
dem Punkt-Operator in PHP.<br />
contains()<br />
starts-with()<br />
Ermittelt, ob eine Zeichenkette bestimmte Zeichen enthält<br />
bzw. ob sie mit einer bestimmten Zeichenfolge beginnt.<br />
normalize_whitespace() Kontrolliert den Umgang mit Leerzeichen.<br />
string-length() Ermittelt die Anzahl Zeichen einer Zeichenkette.<br />
substring()<br />
substring-before()<br />
substring-after()<br />
translate() Tauscht Zeichen aus.<br />
count()<br />
sum()<br />
generate-id()<br />
local-name()<br />
name()<br />
false()<br />
true()<br />
not()<br />
current()<br />
last()<br />
position()<br />
Ermittelt Teile einer Zeichenkette, entweder nach der Position<br />
oder in Abhängigkeit von einem Schlüsselzeichen<br />
Zählt Knoten und summiert Werte aus Knotenlisten.<br />
Funktionen zum Umgang mit Knoten, der Erzeugung von<br />
Identifikatoren usw.<br />
Diese Funktionen können Boolesche Werte für Boolesche<br />
Ausdrücke beschaffen. Dies ist erforderlich, weil innerhalb<br />
von Ausdrücken kein Zugriff auf Schlüsselwörter (wie true)<br />
besteht.<br />
Bestimmt die Position des Zeigers in Knotenlisten .<br />
document() Realisiert den Dateizugriff, lädt ein anderes Modul.<br />
key()<br />
id()<br />
Einen Knoten mit bestimmten Eigenschaften finden.<br />
element-available() Eine Parserfunktion suchen und mitteilen, ob der Parser<br />
diese unterstützt.<br />
Tabelle <strong>14</strong>.1: XSLT-Funktionen (Forts.)<br />
561
562<br />
XML und Webservices<br />
Die Funktionen finden auch bei der Knotenselektion mit XPath Verwendung.<br />
Mehr dazu im nächsten Abschnitt.<br />
XPath<br />
XPath ist eine Abfragesprache, die direkt oder über XSLT zum Einsatz kommt, um<br />
Knoten oder Knotenlisten zu ermitteln.<br />
Daten mit XPath suchen<br />
Mit Hilfe einer Transformation wurde im letzten Abschnitt bereits eine Auswahl<br />
aus XML-Daten vorgenommen. Immer dann, wenn auf Knoten oder <strong>In</strong>halte mit<br />
match="" oder select="" zugegriffen wurde, kam bereits XPath zum Einsatz. Diese<br />
einfache Form reicht in der Praxis nur selten aus. Der vollständige Name für<br />
XPath lautet XML Path Language 1.0.<br />
Um in der hierarchischen Struktur eines XML-Dokuments gezielt Knoten adressieren<br />
zu können, wird XPath eingesetzt. Ohne eine solche Sprache würden Dokumente<br />
immer sequenziell durchlaufen werden, was wenig praxistauglich ist.<br />
Gerade bei Webanwendungen, die oft vielen Benutzern einen Ausschnitt aus<br />
einer großen Datenmenge zur Verfügung stellen, ist die schnelle Auswahl eminent<br />
wichtig. Die Abfrage durch Beschreibung eines Pfades und verzichtet dabei auf<br />
Schleifen oder andere zyklische Elemente. Damit ist die Konstruktion zur Laufzeit<br />
und in Abhängigkeit vom aktuellen Auftreten von Knoten möglich. Wie der Name<br />
der Sprache andeutet, ähnelt die Auswahl von Knoten den Pfadangaben im Dateisystem<br />
eines Betriebssystems. Das ist nahe liegend, weil auch dort Daten hierarchisch<br />
angeordnet sind. Eine typische XPath-Anweisung könnte also folgendermaßen<br />
aussehen:<br />
eintrag/name/vorname<br />
Sie adressiert einen Knoten , der Kind von ist, was wiederum<br />
Kind von sein muss:<br />
<br />
<br />
<br />
Es gibt verschiedene Knotentypen in XML. XPath muss diese adressieren können.<br />
Konkret unterschieden werden die in der folgenden Tabelle dargestellten Typen:
Tabelle <strong>14</strong>.2: Knotentypen, die mit XPath adressiert werden können<br />
Grundlagen für die Entwicklung von Ausdrücken<br />
Vorbemerkungen<br />
XPath-Knoten Darstellung Kurzform<br />
Wurzelknoten /<br />
Elementknoten ElementName (keine Kurzform)<br />
Kindknoten child:: (kann entfallen)<br />
Attributknoten attribute::AttributName @AttributName<br />
Textknoten self::node() .<br />
Elternknoten parent::node() ..<br />
Prozessinformation Nicht darstellbar<br />
Namensraum alias:<br />
XPath basiert auf Ausdrücken, die den Weg zu einem Knoten beschreiben. Der<br />
Weg kann – ebenso wie beim Dateisystem – durch absolute oder relative Pfadangaben<br />
beschrieben werden. Absolute Angaben beginnen immer an der Dokumentenwurzel.<br />
Wenn der Ausdruck einen Pfad über mehrere Knoten hinweg beschreibt,<br />
werden die Elemente durch Schrägstriche getrennt:<br />
dirlist/directory/file<br />
Jedes dieser Elemente wird allgemein als Lokalisierungsschritt bezeichnet. Die<br />
eben gezeigte und häufig verwendete Darstellung durch einen Knotennamen ist<br />
eine verkürzte Form. Tatsächlich kann jeder Schritt aus drei Teilen bestehen:<br />
1. Achsenbezeichner<br />
2. Knotentest<br />
3. Prädikate<br />
Der Achsenbezeichner modifiziert die Auswahl des Knotens auf der Grundlage<br />
seiner Position im Baum. Als Trennzeichen zwischen dem Achsenbezeichner und<br />
dem nächsten Teil des Ausdrucks werden zwei Doppelpunkte geschrieben "::".<br />
Dies wird im nächsten Abschnitt noch weiter erläutert.<br />
563
564<br />
XML und Webservices<br />
Der Knotentest beschreibt den Knoten selbst, beispielsweise eine direkte Auswahl<br />
durch Nennung des Tagnamens. Die Prädikate stehen in eckigen Klammern und<br />
werden meist zur Auswahl von Attributen verwendet. <strong>In</strong>sgesamt ergibt sich beispielsweise<br />
folgender Ausdruck:<br />
child::directory[attribute::hasfiles='true']<br />
child:: ist der Achsenbezeichner, hier wird also beginnend von der aktuellen Position<br />
das nächste Kindelement gesucht. Dann wird das Element selbst benannt:<br />
directory. Es wird also das nächste Kindelement mit dem Namen <br />
gesucht. Das Prädikat schränkt die Suche weiter ein; hier auf das Vorhandensein<br />
eines Attributes hasfile mit dem Parameter 'true'3 .<br />
Um solche Ausdrücke nun entwickeln zu können, ist in erster Linie eine Kenntnis<br />
der Achsenbezeichner notwendig.<br />
Die XPath-Achsenbezeichner<br />
Achsenbezeichner können einen oder mehrere Knoten auswählen. Die konkrete<br />
Auswahl hängt vom aktuellen Knoten ab. Wenn ein Dokument sequenziell durchlaufen<br />
wird, können die Bezeichner mehrere Knoten selektieren. Die folgende<br />
Tabelle zeigt alle Achsenbezeichner für Elemente auf einen Blick.<br />
Elemente sind hier Tag-Namen, also keine Attribute und keine Namensraumbezeichnungen:<br />
Achsenname Suchrichtung Beschreibung<br />
self – Der aktuelle Knoten.<br />
child vor Die Kinder des Knotens.<br />
parent Die Eltern des Knotens.<br />
descendant vor Alle Nachfahren (Kinder und Kindeskinder) .<br />
descendant-or-self vor Alle Nachfahren und der Knoten selbst.<br />
ancestor rück Alle Vorfahren (Eltern und deren Eltern).<br />
ancestor-or-self rück Alle Vorfahren und der Knoten selbst.<br />
3 Die einfachen Anführungszeichen innerhalb der doppelten gehören bei XPath dazu.
Achsenname Suchrichtung Beschreibung<br />
Tabelle <strong>14</strong>.3: Achsenbezeichner für Element-Knoten<br />
Vorbemerkungen<br />
following vor Alle folgenden Knoten im Dokument, die<br />
nicht direkte Nachfahren sind.<br />
following-sibling vor Alle folgenden Geschwister.<br />
preceding rück Alle vorhergehenden Knoten, die nicht<br />
Eltern sind.<br />
preceding-sibling rück Alle vorhergehenden Geschwister.<br />
Neben den Achsenbezeichnern für Elemente gibt es noch zwei spezielle: attribute<br />
zur Auswahl von Attributen und namespace zur Lokalisierung von Namensräumen.<br />
Es bietet sich an dieser Stelle an, die Wirkung der Achsenbezeichner mit<br />
einem Testprogramm zu lernen. Damit alle erdenklichen Kombinationen auch<br />
getestet werden können, wird eine XML-Datei entworfen, die entsprechende Achsen<br />
auch aufweist:<br />
Listing <strong>14</strong>.2: axischeck.xml – Testdatei zum Testen von Achsenzugriffen<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Diese Datei dient im folgenden Beispiel als Basis für die ersten Programmierversuche<br />
mit PHP und XPath. Sie ist so aufgebaut, dass aus den Elementnamen die<br />
565
566<br />
XML und Webservices<br />
Position im Baum ablesbar ist, was für das Verständnis der XPath-Abfragen sinnvoll<br />
ist.<br />
Mit PHP5 XPath-Ausdrücke verarbeiten<br />
Lesen Sie zunächst das folgende Listing:<br />
Listing <strong>14</strong>.3: xpathcheck.php – XPath-Achsenbezeichner ausprobieren<br />
{<br />
}<br />
Vorbemerkungen<br />
echo '';<br />
echo 'Element-Beziehungen, ausgehend von <br />
"Ebene1_2"';<br />
foreach ($this->expressions as $expression)<br />
{<br />
$this->nodes = <br />
$this->xp->query("//Ebene1_2/$expression::*");<br />
echo "$expression";<br />
echo "";<br />
$this->PrintNodeList();<br />
echo "";<br />
}<br />
echo '';<br />
}<br />
$x = new XPathCheck('data/axischeck.xml');<br />
$x->ShowNodeList();<br />
?><br />
Führt man diese Schritte für alle XPath-Standardanweisungen aus, bezogen auf<br />
den Knoten als Startknoten, ergibt sich folgende Abbildung:<br />
Abbildung <strong>14</strong>.1:<br />
Ausgabe der Knotenlisten<br />
für alle<br />
typischen Achsenbezeichner<br />
Der Ausdruck selektiert lediglich die Achse (achsenbezeichner::*), wobei das *<br />
zur Auswahl aller Elemente der Achse führt. <strong>In</strong> der Praxis könnten sich hier Elementnamen<br />
anschließen, um die Auswahl einzuschränken, sowie eines oder<br />
mehrere Attribute, die auftreten oder bestimmte Parameter aufweisen müssen.<br />
567
568<br />
XML und Webservices<br />
Ein konkretes XML-Format: RSS<br />
RSS dient dem Austauschen von Nachrichten und <strong>In</strong>halten über Nachrichtenseiten.<br />
Auch große Nachrichtendienste wie Wired oder Slashdot bieten <strong>In</strong>formationen<br />
zur Übernahme auf anderen Seiten an. Eine private Seite kann dies<br />
deutlich aufwerten. Das Prinzip ist dabei einfach. Der RSS-Feed (ein XML-Fragment)<br />
wird von der Spenderseite abgeholt. Enthalten ist der Nachrichtentext in<br />
einer Art Zusammenfassung und ein Link zurück zum Spender, der zum Volltext<br />
führt. Davon profitieren beide Seiten. Der Spender erhält Zugriffe durch Nutzer<br />
fremder Seiten und der Empfänger hat ständig aktuelle <strong>In</strong>halte zu bieten, ohne<br />
dafür irgendwelchen Aufwand treiben zu müssen.<br />
RSS kann bis zu einzelnen Nachrichten und Stichwörtern herunter gebrochen<br />
werden, nur die letzten Änderungen oder Aktualisierungen enthalten oder auch<br />
zum Verfolgen von Einträgen in einer Änderungsliste dienen. Der Einsatzzweck<br />
ist keineswegs auf Nachrichten beschränkt, die aus Sport oder Politik stammen,<br />
sondern kann ebenso die <strong>In</strong>formationen einer Serverüberwachung umfassen.<br />
RSS-fähige Programme werden als Aggregatoren bezeichnet, das sind quasi Datensammler<br />
der Weblog-Gemeinschaft. Weblogs wiederum zeichnen sich selbst oft<br />
durch die Fähigkeit aus, <strong>In</strong>halte im RSS-Format bereitstellen zu können. Auf diese<br />
Art und Weise werden Weblogs gleichzeitig zu Spendern und Empfängern – ein<br />
wilder Kreislauf aus Nachrichtenaustauschprogrammen entsteht.<br />
Rückblick<br />
RSS umfasst allerdings weit mehr als ein einfaches XML-Format zum Nachrichtenaustausch.<br />
Ein Blick zurück ist hilfreich, um die Zusammenhänge zu verstehen<br />
und die richtige Strategie zur Programmierung eigener RSS-Programme zu finden.<br />
Die erste Version war RSS 0.90, entworfen von Netscape als Format zum Aufbau<br />
von Portalen, die sich die wichtigsten aktuellen Nachrichten von großen Nachrichtenanbietern<br />
holen. Diese Version war äußerst komplex, was einer schnellen<br />
Verbreitung eher abträglich ist. Daraus entstand in kurzer Zeit RSS 0.91, eine vereinfachte<br />
Ausgabe. Netscape verlor schnell das <strong>In</strong>teresse am Betrieb von Portalen<br />
und die Fa. UserLand Software übernahm die weitere Entwicklung als Basis ihrer<br />
kommerziellen Weblog-Produkte. <strong>In</strong> der Zwischenzeit spaltete sich eine weitere,<br />
nicht kommerzielle Gruppe ab und entwarf ein weiteres Format, basierend auf<br />
dem alten 0.90, bevor es die Vereinfachungen der 0.91 erfuhr. Dieses Format, sei-
Vorbemerkungen<br />
nerseits mit dem ebenso sehr komplexen RDF verwandt, wurde als RSS 1.0<br />
bezeichnet. UserLand war in diesen Prozess nicht involviert worden und torpediert<br />
seitdem die Entwicklung mit einer Reihe eigener Versionen, die aus der 0.91-<br />
Reihe fortgeführt wurden. Auf diesem Wege entstanden RSS 0.92, RSS 0.93 und<br />
RSS 0.94. Weil die 1.0 bereits vergeben war, heißt die finale UserLand-Version<br />
RSS 2.0. Womit das Chaos perfekt ist.<br />
Welche Version man verwenden sollte<br />
Es gibt mittlerweile sieben Versionen. Normalerweise macht eine derartige Versionspolitik<br />
ein Produkt nachhaltig kaputt. RSS hat sich dennoch rasend verbreitet,<br />
vermutlich aufgrund des anhaltenden Bedarfs am automatischen <strong>In</strong>formationsaustausch.<br />
Die folgende Tabelle hilft, die richtige Version zu wählen:<br />
Version Ursprung Hinweis Status Empfehlung<br />
0.90 Netscape Obsolet<br />
wegen 1.0<br />
0.91 UserLand Besonders<br />
einfach<br />
0.92,<br />
0.93,<br />
0.94<br />
1.0 RSS-<br />
DEV<br />
Working<br />
Group<br />
UserLand Umfassender<br />
als 0.91<br />
RDF-basiert,<br />
erweiterbar,<br />
offener<br />
Standard<br />
2.0 UserLand 0.9X<br />
kompatibel,<br />
erweiterbar<br />
Offiziell obsolet<br />
wegen 2.0,<br />
aber noch sehr<br />
populär<br />
Obsolet<br />
wegen 2.0<br />
Tabelle <strong>14</strong>.4: Übersicht über die RSS-Versionen<br />
Nicht mehr verwenden.<br />
Leicht zu verwenden für einfache<br />
Ansprüche, interessant,<br />
wenn spätere Migration auf 2.0<br />
geplant ist.<br />
Nicht mehr verwenden, besser<br />
auf 2.0 setzen.<br />
Stabil, aktuell Wird verwendet, wenn auch<br />
mit RDF-Quellen gearbeitet<br />
wird.<br />
Stabil, aktuell Generell empfehlenswert.<br />
569
570<br />
XML und Webservices<br />
Wie RSS praktisch aussieht<br />
Für den Einsatz von RSS auf einer mit PHP programmierten Webseite braucht<br />
man zuerst ein Programm, das RSS-Feeds anderer Seiten liest. Eigene Angebote<br />
setzen immerhin auch eigene Nachrichten voraus, und die sind ungleich schwerer<br />
zu produzieren. Das Lesen der Datenquelle ist einfach – sie ist unter einer spezifischen<br />
URL zu erreichen. Auf Webseiten kann man diese meist hinter dem Bildchen<br />
zu finden.<br />
Das folgende Beispiel stammt von xml.com und ist in RSS 0.91 erstellt:<br />
Listing <strong>14</strong>.4: Ein RSS 0.91-Feed, gefunden auf xml.com<br />
<br />
<br />
XML.com<br />
http://www.xml.com/<br />
XML.com features a rich mix of information and services<br />
for the XML community.<br />
en-us<br />
<br />
Normalizing XML, Part 2<br />
<br />
http://www.xml.com/pub/a/2002/12/04/normalizing.html<br />
<br />
<strong>In</strong> this second and final look at applying relational<br />
normalization techniques to W3C XML Schema data modeling, Will<br />
Provost discusses when not to normalize, the scope of uniqueness<br />
and the fourth and fifth normal forms.<br />
<br />
<br />
The .NET Schema Object Model<br />
<br />
http://www.xml.com/pub/a/2002/12/04/som.html<br />
<br />
Priya Lakshminarayanan describes in detail the use of<br />
the .NET Schema Object Model for programmatic manipulation of W3C<br />
XML Schemas.<br />
<br />
<br />
SVG's Past and Promising Future<br />
Vorbemerkungen<br />
http://www.xml.com/pub/a/2002/12/04/svg.html<br />
<br />
<strong>In</strong> this month's SVG column, Antoine Quint looks back<br />
at SVG's journey through 2002 and looks forward to 2003.<br />
<br />
<br />
<br />
Die Datenquelle präsentiert einen Kanal (Thema), der einen Titel, den Link<br />
zurück zur Quelle und eine Beschreibung enthält. Optional kann eine Sprache<br />
angegeben werden, gefolgt von einer Reihe konkreter Einträge, die jeweils wieder<br />
einen Titel, den Rücklink und eine Beschreibung enthalten.<br />
<strong>In</strong> RSS 1.0 sieht exakt derselbe <strong>In</strong>halt etwas anders aus:<br />
Listing <strong>14</strong>.5: RSS1.0-Feed, identisch mit dem <strong>In</strong>halt aus Listing <strong>14</strong>.4<br />
<br />
<br />
XML.com<br />
http://www.xml.com/<br />
XML.com features a rich mix of information and services<br />
for the XML community.<br />
en-us<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Normalizing XML, Part 2<br />
<br />
http://www.xml.com/pub/a/2002/12/04/normalizing.html<br />
571
572<br />
XML und Webservices<br />
<br />
<strong>In</strong> this second and final look at applying relational<br />
normalization techniques to W3C XML Schema data modeling, Will<br />
Provost discusses when not to normalize, the scope of uniqueness<br />
and the fourth and fifth normal forms.<br />
Will Provost<br />
2002-12-04<br />
<br />
<br />
The .NET Schema Object Model<br />
http://www.xml.com/pub/a/2002/12/04/som.html<br />
Priya Lakshminarayanan describes in detail the use of<br />
the .NET Schema Object Model for programmatic manipulation of W3C<br />
XML Schemas.<br />
Priya Lakshminarayanan<br />
2002-12-04<br />
<br />
<br />
SVG's Past and Promising Future<br />
http://www.xml.com/pub/a/2002/12/04/svg.html<br />
<strong>In</strong> this month's SVG column, Antoine Quint looks back at<br />
SVG's journey through 2002 and looks forward to 2003.<br />
Antoine Quint<br />
2002-12-04<br />
<br />
<br />
Die <strong>In</strong>formationen sind hier ein wenig ausführlicher. So gehören auch der Name<br />
des Autors und ein Datum dazu. Das Datum ist sehr sinnvoll, wenn man aus einer<br />
großen Anzahl von Nachrichten nur die aktuellsten anzeigen möchte. Die Struktur<br />
ist dennoch weitgehend der früherer RSS-Versionen angepasst. Wer kompatible<br />
Software schreiben möchte, die mit Datenquellen beider Arten umgehen<br />
kann (das ist heute leider notwendig, weil beide Versionen gleichermaßen benutzt<br />
werden), kann sich mit folgenden »Umrechnungsregeln« zwischen 1.0 und 0.91<br />
behelfen (aus der Sicht von 1.0):<br />
1. Das Stammelement ist rdf:RDF statt rss. Beim Lesen mit PHP ist es nicht zwingend<br />
erforderlich, dies zu prüfen – man kann der Quelle durchaus vertrauen.<br />
2. RSS 1.0 verwendet den Namensraum http://purl.org/rss/1.0/ als Standardnamensraum.<br />
Die RDF-spezifischen Elemente sind durch den eigenen RDF-
Vorbemerkungen<br />
Namensraum http://www.w3.org/1999/02/22-rdf-syntax-ns# abgetrennt. Die<br />
Spezifika von RDF kann man aber getrost ignorieren für einen einfachen<br />
Nachrichtenleser. Metadaten (Autor, Datum) befinden sich im Namensraum<br />
des so genannten Dublin Core4 http://purl.org/dc/elements/1.1/.<br />
Wenn Sie nun mit einem Parser arbeiten, der Namensräume nur beschränkt<br />
verarbeiten kann – wie SimpleXML in PHP5 – dann ignorieren Sie das einfach.<br />
<strong>In</strong>nerhalb eines vollständigen RDF-basierten RSS 1.0-Feed dürften Elementnamenskonflikte<br />
kaum auftreten. Der blanke Leseprozess ist also<br />
unkritisch. Erzeugen lassen sich RSS-Quellen damit freilich nicht. Sie müssen<br />
lediglich vertrauen, dass bei der Definition der Quelle die Standardaliase nicht<br />
verändert wurden. Ein Parser, der Namensräume nicht kennt, wird den Elementnamen<br />
als dc:creator lesen, nicht als creator. Solange Sie von einem fixen<br />
Alias dc ausgehen, ist das in Ordnung. Tauscht das jemand gegen dublc aus,<br />
versagt das Skript. Programme mit vollständiger Namensraumunterstützung<br />
würden eine solche Änderung tolerieren. Letztere können auch erkennen, welche<br />
Version benutzt wurde und ihre Strategie beim Verarbeiten anpassen.<br />
3. Ein bedeutender Unterschied ist die Tatsache, dass das -Element außerhalb<br />
des -Elements steht, bei RSS 0.91 war es genau umgekehrt. Zur<br />
Verarbeitung beider Versionen muss man damit zwangsläufig Teile des Codes<br />
doppelt schreiben. Nebenbei bemerkt: RSS 0.90 (außerhalb) und RSS 2.0<br />
(innerhalb) zeugen von leichten Designzweifeln bei den Erfindern.<br />
4. Das zusätzliche -Element innerhalb des -Elements kann ignoriert<br />
werden. Es bedient lediglich native RDF-Parser.<br />
RSS 2.0 wurde bereits mehrfach angesprochen. Auch hierfür ist ein Blick auf denselben<br />
<strong>In</strong>halt im RSS 2.0-Format empfehlenswert:<br />
<br />
<br />
XML.com<br />
http://www.xml.com/<br />
XML.com features a rich mix of information and services<br />
for the XML community.<br />
en-us<br />
<br />
Normalizing XML, Part 2<br />
4 Der Name geht zurück auf die Dublin Core Metadata <strong>In</strong>itiative, eine Organisation, die Beschreibungsformate<br />
für Metadaten von Dokumenten entwirft (siehe http://dublincore.org).<br />
573
574<br />
XML und Webservices<br />
<br />
http://www.xml.com/pub/a/2002/12/04/normalizing.html<br />
<br />
<strong>In</strong> this second and final look at applying relational<br />
normalization techniques to W3C XML Schema data modeling, Will<br />
Provost discusses when not to normalize, the scope of uniqueness<br />
and the fourth and fifth normal forms.<br />
Will Provost<br />
2002-12-04<br />
<br />
<br />
The .NET Schema Object Model<br />
<br />
http://www.xml.com/pub/a/2002/12/04/som.html<br />
<br />
Priya Lakshminarayanan describes in detail the use of<br />
the .NET Schema Object Model for programmatic manipulation of W3C<br />
XML Schemas.<br />
Priya Lakshminarayanan<br />
2002-12-04<br />
<br />
<br />
SVG's Past and Promising Future<br />
<br />
http://www.xml.com/pub/a/2002/12/04/svg.html<br />
<br />
<strong>In</strong> this month's SVG column, Antoine Quint looks back<br />
at SVG's journey through 2002 and looks forward to 2003.<br />
<br />
Antoine Quint<br />
2002-12-04<br />
<br />
<br />
<br />
RSS 2.0 verwendet Namenräume analog zu RSS 1.0, aber es wird kein RDF verwendet.<br />
Ebenso wie bei RDF 0.91 gibt es keinen Standardnamensraum und die <strong>In</strong>halt<br />
sind innerhalb des -Elements. Code, der flexibel mit RSS 0.91 und 1.0<br />
umgehen kann, wird mit nur geringem Aufwand auch RSS 2.0 lesen können.
<strong>14</strong>.2 Einführung in die libxml2<br />
Einführung in die libxml2<br />
Generell ist XML ein Thema für Website-Entwickler, denn es gibt vielfältige<br />
Anwendungen:<br />
Webservices (basieren weitgehend auf XML)<br />
Datenim- und -export aus und zu unbekannten oder inkompatiblen Datenquellen<br />
Aufbereitung von XHTML für verschiedene Medien<br />
Bereitstellung oder Konsumierung von RSS-Kanälen<br />
<strong>In</strong> PHP 4 war nur der SAX-Parser Expat fester Teil des Pakets. Alle anderen Erweiterungen<br />
waren entweder nicht immer vorhanden oder noch im Entwicklungsstadium<br />
(und sind es bis heute). Applikationen, die XML nutzen, waren damit etwas<br />
eingeschränkt, denn nicht alle Provider und Webhoster boten die nötige Unterstützung.<br />
PHP DOM wurde zwar heiß diskutiert und oft als Wunderwaffe der XML-<br />
Entwickler gepriesen, dümpelte aber mangels Provider-Unterstützung eher vor<br />
sich hin. Wichtige Applikationen wie das Templatesystem phpTemple (http://<br />
www.phptemple.de) sind jedoch auf XML angewiesen, um hohe Performance auch<br />
bei anspruchsvollen Seiten zu ermöglichen.<br />
Neu in PHP5<br />
Anstatt des Sammelsuriums von teilweise unfertigen und inkompatiblen Bibliotheken<br />
(XSLT von Ginger Alliance, Expat, XML DOM) wurde nun auf das sehr<br />
weit entwickelten Projekt Gnome XML Parser libxml gesetzt. Die Vorgängerversion<br />
war bereits die Basis für die XML DOM-Erweiterungen. Vielleicht haben Sie<br />
bereits mit den DOM-Erweiterungen gearbeitet und herausgefunden, dass diese<br />
von den drei Bibliotheken der schwächste Teil waren. Dies als die herausragende<br />
XML-Neuerung in PHP5 anzupreisen erscheint nicht einsichtig. Allerdings lag die<br />
schlechte Qualität nicht an der verwendeten Bibliothek, sondern an den Mängeln<br />
der <strong>In</strong>tegration in PHP. Dieses Problem wurde durch den Wechsel der Entwickler<br />
und Verbesserungen in der Struktur der Programmierung beseitigt.<br />
Die Gnome-Bibliothek liefert einen der schnellsten Parser, die es gibt. Sie ist relativ<br />
jung und die Entwickler konnten von den Fehlern der anderen Parser lernen.<br />
<strong>Und</strong> nicht zuletzt werden viele in der XML-Welt dringend benötigte Standards<br />
unterstützt:<br />
575
576<br />
XML und Webservices<br />
SAX- und DOM-Programmierschnittstellen<br />
Validierung mit DTDs und XML Schema<br />
XSLT, XPath, XPointer und Xinclude<br />
<strong>In</strong>tegrierte Parser für HTML und Docbook-XML<br />
XMLSec (XML Security Library)<br />
<strong>In</strong>sgesamt gewinnt PHP dadurch deutlich und es lohnt sich unbedingt, sich mit<br />
der Bibliothek und den neuen Möglichkeiten intensiver auseinander zu setzen.<br />
Für viele Projekte gibt es dazu auch keine Alternative, weil die weitere Unterstützung<br />
der alten Bibliotheken nicht geplant ist. Andererseits bleiben die Funktionsschnittstellen<br />
erhalten, um eine Abwärtskompatibilität zu gewährleisten. Es ist also<br />
nicht zwingend erforderlich, alle XML-basierten Skripte nun umzuschreiben. Bis<br />
auf wenige Ausnahmen sollten alle Programme unverändert laufen. Es lohnt sich<br />
dennoch, den vorhandenen Code zu untersuchen, weil sich eventuell bessere oder<br />
schnellere Möglichkeiten bieten und der eine oder andere »Work-Around« oder<br />
»Dirty-Hack« sich nun sauber programmieren lässt.<br />
Die neuen DOM-Funktionen<br />
Die Kernfunktionen der DOM-Erweiterung sind inzwischen weitgehend stabil aus<br />
Sicht der Entwicklung. Das ist sehr wichtig, weil ständige Änderungen an den Bibliotheken<br />
den Einsatz in ernsthaften Projekten arg gefährden. Hier hatte PHP bislang<br />
das Nachsehen gegenüber .NET und Java, wo es eine sehr solide und lange<br />
Zeit stabile Basisplattform gab und gibt.<br />
HTML verarbeiten<br />
Webseiten bestehen aus HTML und HTML ist verwandt mit XML. Es sollte also<br />
möglich sein, HTML mit einem XML-Parser zu verarbeiten. Weit gefehlt, denn<br />
tatsächlich stammt HTML von der alten Auszeichnungssprache SGML ab, aus der<br />
auch XML entwickelt wurde. XML definiert eine strengere Syntax und erlaubt<br />
damit schnellere und stabilere Parser. Prinzipiell kann also ein XML-Parser einfache<br />
HTML-Seiten nicht verarbeitet, bevor diese nicht »XML-tauglich« gemacht<br />
werden. Auch dazu gibt es einen entsprechenden Standard: XHTML. <strong>In</strong> der Praxis<br />
ist dies jedoch nicht immer einfach, weil man dazu spezielle Programme braucht,<br />
die die Umwandlung vornehmen – oder PHP5.
Einführung in die libxml2<br />
Das folgende Beispiel zeigt ein typisches, aus XML-Sicht nicht »wohlgeformtes«<br />
XML-Dokument:<br />
Listing <strong>14</strong>.6: XmlDomBadHtml.html – Ein einfaches HTML-Dokument, nicht wohlgeformt<br />
<br />
<br />
<br />
XML<br />
<br />
<br />
HTML mit DOM verarbeiten<br />
<br />
Diese Seite enthält nach XML-Regeln nicht wohlgeformte Tags.<br />
<br />
<br />
<br />
Red<br />
Blue<br />
Green<br />
<br />
<br />
<br />
<br />
Dem neuen DOM-Parser ist dies ziemlich egal, er liest diese HTML-Seite problemlos<br />
ein. Das folgende Skript verarbeitet die Seite und agiert dabei mit Knoten,<br />
Knotenauflistungen, Elementen und Attributen:<br />
Listing <strong>14</strong>.7: XmlDomHtml.php – Laden und Verarbeiten von nicht wohlgeformtem<br />
HTML<br />
getElementsByTagName('head');<br />
$body = $doc->getElementsByTagName('body');<br />
function getTitle ($head)<br />
{<br />
foreach ( $head as $header )<br />
{<br />
if ( $header->tagName == 'title' )<br />
577
}<br />
578<br />
}<br />
XML und Webservices<br />
echo ("Page Title: ".$header->textContent. <br />
"\n");<br />
function parseBody($body)<br />
{<br />
$bodyTag = $body->item(0);<br />
foreach ($bodyTag->childNodes as $element)<br />
{<br />
$content = htmlspecialchars( <br />
utf8_decode($element->textContent));<br />
switch ($element->tagName)<br />
{<br />
case 'h2':<br />
echo "Header 2: $content\n";<br />
break;<br />
case 'p':<br />
echo "Paragraph: $content\n";<br />
break;<br />
case 'form':<br />
foreach ($element->childNodes as $input)<br />
{<br />
if ($input->nodeType != XML_ELEMENT_NODE) continue;<br />
if ($input->tagName == 'select')<br />
parseSelect($input);<br />
}<br />
break;<br />
default:<br />
echo $content->tagName.'';<br />
break;<br />
}<br />
}<br />
}<br />
function parseSelect($select)<br />
{<br />
echo "Select:\n\n";<br />
$options = $select->childNodes;<br />
foreach ($options as $option)<br />
{<br />
$content = htmlspecialchars(
}<br />
utf8_decode($option->textContent));<br />
echo "{$content}";<br />
if ($option->hasAttribute('selected'))<br />
{<br />
echo '
580<br />
XML und Webservices<br />
rung. Zusätzlich ist noch darauf zu achten, dass das Dokument gegebenenfalls<br />
UTF-8-kodiert ist. UTF-8 ist das Standardzeichenformat für XML. Für die Ausgabe<br />
im Beispiel wird außerdem noch HTML-Code sichtbar gemacht:<br />
$content = htmlspecialchars(utf8_decode($element->textContent));<br />
Die weitere Verarbeitung richtet sich nach dem gefundenen Elementnamen:<br />
switch ($element->tagName)<br />
Wenn man weitere Untersuchungen anstellt ist es wichtig zu wissen, welchen Typ<br />
ein Knoten hat. Denn die von der Knotenliste extrahierten Knoten sind nicht<br />
immer Elemente, sondern können auch Kommentare, Zeichen, Namensräume,<br />
Prozessanweisungen usw. sein:<br />
if ($input->nodeType != XML_ELEMENT_NODE) continue;<br />
<strong>In</strong>formationen über die verwendbaren Konstanten finden Sie in der<br />
Kurzreferenz am Ende des Kapitels.<br />
<strong>In</strong> der Funktion parseSelect ist noch eine weitere Methode zu finden, die hier dazu<br />
dient, die Existenz eines Attributes zu ermitteln:<br />
if ($option->hasAttribute('selected'))<br />
Die Abbildung zeigt, wie das Skript arbeitet:<br />
Abbildung <strong>14</strong>.2:<br />
Verarbeitung<br />
einer HTML-Seite<br />
mit dem DOM-<br />
Modul<br />
Das DOM-Modul ist äußerst leistungsfähig und konnte hier nur oberflächlich<br />
angerissen werden. Für größere XML-Projekte ist es unbedingt zu empfehlen und<br />
die hier gezeigten Techniken lassen sich in jeder Richtung ausbauen. <strong>In</strong>formieren<br />
Sie sich in der Kurzreferenz über die verfügbaren Methoden und Eigenschaften,<br />
um einen schnellen Einstieg zu finden.
<strong>14</strong>.3 SimpleXML<br />
SimpleXML<br />
Neben der DOM-Erweiterung, die auf libxml basiert, fand eine zweite XML-<br />
Schnittstelle Eingang in PHP5: SimpleXML. Diese Bibliothek ist bekannt als<br />
»Object Mapping XML API«.<br />
Unterschiede zur DOM-Schnittstelle<br />
DOM (Document Object Model) erzeugt aus einem XML-Dokument einen vollständigen<br />
Baum von Objekten. Der Zugriff in einer Applikation ist sehr direkt<br />
möglich, dafür ist der Speicherverbrauch erheblich, weil immer die gesamte Struktur<br />
im Speicher steht. Ein vollständiges Abbild eines Dokuments als DOM ist<br />
nicht trivial – auch in der Verarbeitung. SimpleXML nutzt ebenso eine objektorientierte<br />
Sicht, vereinfacht diese jedoch drastisch. Statt der »offiziellen« DOM-API,<br />
die alle Programmiersprachen mit DOM-Unterstützung anbieten, überführt SimpleXML<br />
die Daten direkt in eine einfache, an PHP angepasste Objektstruktur.<br />
Als Ausgangspunkt für erste Versuche soll folgendes Dokument dienen:<br />
Listing <strong>14</strong>.8: data/dbconfig.xml – Eine einfache XML-Datei zum Testen<br />
<br />
<br />
joerg@krause.net<br />
<br />
localhost<br />
<strong>My</strong><strong>SQL</strong><br />
dbuser<br />
geheim<br />
simpletest<br />
<br />
<br />
Ein Parser, der dies verarbeitet, könnten nun folgendermaßen aussehen:<br />
Listing <strong>14</strong>.9: SimpleXML1.php – Einfachste Verarbeitung von XML mit SimpleXML<br />
582<br />
XML und Webservices<br />
echo ('');<br />
print_r($config);<br />
echo ('');<br />
?><br />
Die print_r-Funktion zeigt die vollständige Objektstruktur an, wie sie von Simple-<br />
XML erkannt wurde:<br />
Abbildung <strong>14</strong>.3:<br />
Objektstruktur eines<br />
XML-Dokuments<br />
Das folgende Skript nutzt diese Struktur, um auf eine <strong>My</strong><strong>SQL</strong>-Datenbank zuzugreifen.<br />
Dazu werden die Daten aus der XML-Datei zur Konfiguration benutzt:<br />
Listing <strong>14</strong>.10: SimpleXMLConfig.php – Nutzung von XML zur Konfiguration<br />
database;<br />
try<br />
{<br />
if (!@mysql_connect($db->host,$db->user,$db->pass))<br />
{<br />
throw new Exception('Database Connect Error'.mysql_error());<br />
}<br />
}<br />
catch (Exception $e)<br />
{<br />
echo 'Error: ' . $e->GetMessage();<br />
echo '';<br />
echo 'Sende E-Mail an: ' . $config->email;<br />
}<br />
?>
SimpleXML<br />
Wie der Name verspricht, ist SimpleXML tatsächlich sehr einfach. Parsen und<br />
Nutzen ist praktisch unmittelbar miteinander verbunden und eignet sich hervorragend<br />
für kleinere Sequenzen, beispielsweise Konfigurationsdateien.<br />
Ein Schritt weiter<br />
XML ist großartig und äußerst vielseitig einsetzbar. Der erste Abschnitt zeigte<br />
bereits, dass es mit den neuen XML-Funktionen in PHP5 nicht schwer ist, damit<br />
umzugehen. PHP 4 verlangte hier einiges mehr an Programmierung stellte damit<br />
eine zu große Hürde dar, um auch einfache Problemstellungen mit XML zu lösen.<br />
Prinzipiell gibt es drei Wege, um auf XML zuzugreifen. Die meisten Programmierumgebungen<br />
nutzen alle drei, SAX, DOM und XSLT:<br />
SAX (Simple API for XML) bietet eine ereignisbasierte Verarbeitung und verlangt,<br />
dass die Elemente manuell gelesen werden, indem das aktuell verarbeitete<br />
auf einem Stapelspeicher abgelegt und von da wieder entnommen wird.<br />
DOM (Document Object Model) ist sehr flexibel und verständlich, verlangt<br />
jedoch reichlich Ressourcen. Außerdem ist der Aufwand bei sehr trivialen Aufgabenstellungen<br />
recht hoch.<br />
XSLT (XSL Transformation) ist eine funktionale Programmiersprache zum<br />
Transformieren von XML in anderes XML oder in Text oder HTML. Es ist<br />
sehr mächtig, aber auch sehr gewöhnungsbedürftig.<br />
SimpleXML ist eine neue und allein stehende Erweiterung in PHP5 und dient<br />
dazu, den Zugriff auf XML-Dokumente zu erledigen. Dabei geht es in erster Linie<br />
darum, die Daten in eine interne Datenstruktur zu übertragen, die sich besser weiterverarbeiten<br />
lässt. Dazu gehören beispielsweise Arrays oder Datenbanken. XML<br />
dient also keineswegs dazu, intern nur noch damit zu arbeiten, sondern soll vor<br />
allem den vollkommen plattformneutralen Datenaustausch gewährleisten. Nebenbei<br />
kann man seine Daten nun auch in einem der vielen XML-Editoren bearbeiten,<br />
was recht bequem sein kann. Mit einem PHP-Array können eben nur sehr<br />
wenige andere Systeme irgendwas anfangen.<br />
Besonders leistungsfähig ist SimpleXML, wenn man gezielt auf die Attribute eines<br />
Elements oder dessen <strong>In</strong>halt zugreifen möchte. Andere, komplexere Zugriffsmethoden<br />
verlangen andere Erweiterungen. Der Namensteil »Simple« kann hier<br />
wörtlich genommen werden. Damit wird die Erweiterung aber richtig praxistauglich,<br />
denn die Lernkurve ist verhältnismäßig flach.<br />
583
584<br />
XML und Webservices<br />
<strong>In</strong> diesem Abschnitt wird gezeigt, wie mit SimpleXML eine XML-Datei gelesen<br />
und verarbeitet wird. Dazu wird der <strong>In</strong>halt teilweise extrahiert und eine Abfrage<br />
mit der Abfragesprache XPath realisiert. Als Beispielformat soll das beliebte RSS<br />
dienen, das bereits am Anfang des Kapitels kurz vorgestellt wurde. RSS wird zum<br />
Datenaustausch der beliebten Weblogs benutzt. Hier wird die einfachste Version<br />
von RSS verwendet, nicht die komplexere »Muttersprache« RDF, die einiges<br />
mehr an XML-Techniken nutzt und die meisten Anwendungen massiv überfordert.<br />
Wer mag, findet im Web reichlich Quellen zu RDF. Auf jeden Fall müssen<br />
Sie mit XML-Namensräumen und XPath vertraut sein, um alle folgenden Skripte<br />
lesen zu können.<br />
XML Lesen<br />
Als Ausgangspunkt der ersten Versuche mit SimpleXML soll die folgende RSS-<br />
Datei dienen:<br />
<br />
<br />
<br />
PHP: Hypertext Preprocessor<br />
http://www.php.net/<br />
<br />
The PHP scripting language web site<br />
<br />
<br />
<br />
PHP 5.0.0 Beta 4 Released<br />
http://www.php.net/downloads.php<br />
PHP 5.0 Beta 4 has been released. The third beta of PHP<br />
is also scheduled to be the last one (barring unexpected<br />
surprises).<br />
<br />
<br />
PHP Template Project<br />
http://www.phptemple.de<br />
<br />
A design oriented template system for PHP is announced by Comzept<br />
Systemhaus GmbH. It compiles dynamic HTML into native PHP.<br />
<br />
<br />
Listing <strong>14</strong>.11: rssfeed091.xml – Eine erste RSS-Datei als Datenbasis<br />
SimpleXML<br />
Um mit dieser Datei arbeiten zu können, wird in SimpleXML ein entsprechendes<br />
Objekt erzeugt:<br />
$rss = simplexml_load_file('/data/rssfeed091.xml');<br />
Danach steht das Objekt in der Variablen $rss zur Verfügung, vorausgesetzt die<br />
Datei liegt im Verzeichnis /data und heißt rss091.xml. Diese Angaben entsprechen<br />
den Daten auf der Buch-CD.<br />
Auf die Elemente (Tags) kann nun mit der üblichen Objektsyntax zugegriffen werden,<br />
so als wären es Eigenschaften:<br />
echo $rss->channel->title;<br />
Das vollständige Skript sieht folgendermaßen aus:<br />
Listing <strong>14</strong>.12: SimpleXMLRss091.php – Einfachster Zugriff auf eine RSS-Quelle<br />
channel->link}\">{$rss->channel->link})";<br />
?><br />
Mit der Musterdatei sollte nun folgendes ausgegeben werden:<br />
Abbildung <strong>14</strong>.4:<br />
Ausgabe der Channel-Daten<br />
einer RSS-Quelle<br />
Falls ein Element auf einer Stufe in der Hierarchie mehr als einmal vorkommt,<br />
legt PHP diese Daten in einem Array an. Im Beispiel finden Sie zwar nur ein Element<br />
, darunter aber zwei Element . Der Zugriff darauf sieht dann<br />
folgendermaßen aus:<br />
Wie üblich kann zum Zugriff auf alle Elemente eines Arrays die Anweisung<br />
foreach zum Einsatz kommen:<br />
channel->link}\">{$rss->channel->link})";<br />
echo "";<br />
585
586<br />
XML und Webservices<br />
foreach ($rss->item as $item)<br />
{<br />
echo "{$item->title} ";<br />
}<br />
?><br />
Listing <strong>14</strong>.13: SimpleXMLRss091b.php – Ausgabe mehrerer Elemente<br />
Die folgende Abbildung zeigt, wie die Titel der RSS-Quelle erscheinen:<br />
Nach den Elementen sind nun die Attribute an der Reihe. Diese stehen ebenfalls<br />
als Arrayelemente zur Verfügung und können über ihre Namen erreicht werden:<br />
Listing <strong>14</strong>.<strong>14</strong>: SimpleXMLRssVersion.php (Ausschnitt) – Ermittlung der Version aus<br />
dem Attribute version des Stammelements<br />
echo $rss['version'];<br />
Damit kann man in der Praxis schon eine Menge erreichen, denn Sie können nun<br />
XML lesen und mit den bereits bekannten Mitteln in PHP weiterverarbeiten. Das<br />
reicht nicht immer aus, macht aber die ersten Schritte sehr einfach.<br />
Andere XML-Funktionen, wie Kommentare oder Prozessanweisungen, lassen sich<br />
mit dieser Technik freilich nicht nutzen. Sie können diese Entitäten nicht erreichen.<br />
Essentielle <strong>In</strong>formationen werden auf diesem Wege jedoch selten übermittelt,<br />
sodass SimpleXML durchaus seine Daseinsberechtigung hat.<br />
SimpleXML-Methoden<br />
Abbildung <strong>14</strong>.5:<br />
Ausgabe aller Elemente einer<br />
Auflistung<br />
Der Zugriff auf Attribute gestaltet sich bei den Objekten der tiefer liegenden Struktur<br />
ebenso. Hier können Sie jederzeit die Arraysyntax ansetzen. Wenn Sie dagegen<br />
eine Auflistung der Attribute benötigen, beispielsweise um mit foreach zugreifen<br />
zu können, nutzen Sie die Methode attributes():
SimpleXML<br />
foreach($rss->item[0]->attributes() as $a => $b)<br />
{<br />
echo $a,'="',$b,"\"\n";<br />
}<br />
Der erste Parameter ergibt dabei immer den Namen des Attributes, der zweite den<br />
<strong>In</strong>halt.<br />
Hat man komplexe Strukturen, sind Vereinfachungen hilfreich. Eine Methode<br />
hilft dabei, einen Zweig von Knoten gesondert weiterzuverarbeiten: children().<br />
Zurückgegeben wird ein Objekt, das ähnlich wie das Stammobjekt aufgebaut ist<br />
und nur die Kindelemente enthält.<br />
Selbstverständlich kann mit SimpleXml der <strong>In</strong>halt der Knoten geändert werden.<br />
Um die geänderte Version wieder schreiben zu können, ist die Methode AsXml()<br />
hilfreich. Die Methode gibt den <strong>In</strong>halt als Zeichenkette zurück. Die Speicherung<br />
in einer Datei müssen Sie dagegen selbst organisieren.<br />
Hilfreich ist – für umfassendere XML-Zugriffe – das Laden von Objekten, die<br />
mit den regulären DOM-Erweiterungen erzeugt wurden. Die Methode<br />
simplexml_import_dom dient diesem Zweck. Der Abschnitt über DOM zeigt die<br />
Anwendung.<br />
Komplexere Abfragen mit XPath gestalten<br />
Der Zugriff auf eine tief liegende Ebene oder eine Parametrisierung misslingt mit<br />
dem direkten Objektzugriff meist oder wird so kompliziert, dass die ganze Vereinfachung,<br />
die erreicht wurde, wieder aufgebraucht ist. Will man mehr, kommt<br />
XPath zum Einsatz.<br />
Der folgende Code findet alle Texte, die innerhalb aller -Element stehen:<br />
Listing <strong>14</strong>.15: SimpleXMLXpath.php – Abfrage mit XPath<br />
xpath('//title') as $title)<br />
{<br />
echo "$title ";<br />
}<br />
?><br />
587
588<br />
XML und Webservices<br />
Die xpath-Methode durchsucht das SimpleXML-Objekt und gibt ein Array mit<br />
den Fundstellen zurück. Der Parameter enthält die XPath-Anweisung, die recht<br />
komplex werden kann. Die beiden Schrägstriche sagen: »Suche Elemente mit<br />
dem Namen title an jeder beliebigen Stelle im Dokument«. Im Beispiel heißt<br />
das, dass sowohl die Titel des als auch der -Elemente gefunden<br />
werden.<br />
Sollen dagegen nur die -Elemente gefunden werden, die sich innerhalb<br />
von befinden, wäre folgende Anweisung einzusetzen:<br />
//item/title<br />
Spätestens hier wäre der Zugriff über das Objektmodell nur noch mit sehr viel Programmierung<br />
zu erreichen und außerdem unglaublich langsam. XPath ist dagegen<br />
sehr schnell.<br />
XML-Namensräume<br />
SimpleXML erledigt den XML-Zugriff leicht und einfach. Das Lesen von RSS 1.0-<br />
Quellen wird so für jeden beherrschbar. Allerdings benutzt RSS 1.0 auch XML-<br />
Namensräume, was die Sache wieder etwas komplizierter werden lässt. Da<br />
Namensräume generell eine herausragende Rolle in XML übernehmen, ist eine<br />
Auseinandersetzung damit unerlässlich.<br />
Der Namensraum eines XML-Elements ist immer durch einen URL definiert. Im<br />
Dokument wird aus Gründen der Lesbarkeit ein Alias benutzt, der dann zu folgenden<br />
Elementformen führt:<br />
<br />
Abbildung <strong>14</strong>.6:<br />
Abfrage mit XPath: Schnell und flexibel<br />
my: ist dabei der Alias für einen Namensraum. Treffen mehrere XML-Dialekte aufeinander,<br />
kann man diese mit Hilfe der Namensräume auseinander halten. Den<br />
Alias wählt man im Zieldokument so, dass er konfliktfrei aufgelöst werden kann.<br />
Mit Hilfe von Namensräumen können Sie leicht zwischen dem RSS-Element<br />
und dem HTML-Element mit demselben Namen unterscheiden.
SimpleXML<br />
Aus Sicht des XML-Zugriffs mit SimpleXML werden die Dinge nun plötzlich<br />
komplizierter. Denn ein Zugriff über das Objektmodell wird mit $rss->my:title<br />
nicht gelingen. Wird nur ->title geschrieben, verfügt der Prozessor jedoch über<br />
keine <strong>In</strong>formationen, welches -Element denn nun gemeint ist. Schreibt<br />
man die vollständigen Namensräume mit auf, stehen nun zwei Arten von -<br />
Elementen zur Auswahl:<br />
{http://www.w3.org/1999/xhtml}:title<br />
{http://purl.org/rss/1.0}:title<br />
Der gesamte Name wird als »qualifizierter Name« (qualified name) bezeichnet.<br />
Der Alias ändert nichts an der Bezeichnung. Wie bereits erwähnt, dient dies lediglich<br />
der Lesbarkeit. Es vereinfacht aber auch den Zugriff ein wenig. Es gibt keine<br />
korrekten Aliasnamen im Sinne einer Vorgabe oder Norm, sondern lediglich Vorschläge,<br />
welche Namen ohne den konfliktfrei gewählt werden sollten. Vergessen<br />
Sie jedoch nicht, dass der Alias primär der Konfliktauflösung dient und deshalb<br />
bewusst Änderungen unterliegt. Typische Aliase für die beiden erwähnten Namen<br />
sind die folgenden:<br />
<br />
<br />
Der URL, der den Namensraum definiert, führt nicht dazu, dass die verarbeitende<br />
Software online auf <strong>In</strong>formationen zugreift. Auch muss die<br />
verlinkte Seite nicht wirklich existieren. Die Angabe sichert lediglich die<br />
weltweite Eindeutigkeit und sollte deshalb den Namen der Firmen-<br />
Domain enthalten.<br />
Um den Umgang von SimpleXML mit Namensräumen kennen zu lernen, wird<br />
die bereits bekannte RSS-Datei mit einem solchen ausgestattet, was der Übergang<br />
zur Version 1.0 erledigt:<br />
<br />
<br />
<br />
PHP: Hypertext Preprocessor<br />
http://www.php.net/<br />
The PHP scripting language web<br />
589
590<br />
XML und Webservices<br />
site<br />
<br />
<br />
PHP 5.0.0 Beta 3 Released<br />
http://www.php.net/downloads.php<br />
<br />
PHP 5.0 Beta 3 has been released. The third beta of<br />
PHP is also scheduled to be the last one (barring<br />
unexpected surprises).<br />
<br />
2004-01-02<br />
<br />
<br />
PHP Community Site Project Announced<br />
http://shiflett.org/archive/19<br />
<br />
Members of the PHP community are seeking volunteers to<br />
help develop the first web site that is created both by<br />
the community and for<br />
the community.<br />
<br />
2003-12-18<br />
<br />
<br />
Listing <strong>14</strong>.16: rssfeed100.xml – RSS-Datei mit Namensräumen<br />
(explizite Aliase rdf und dc)<br />
Dieses Dokument enthält drei Namensräume, davon sind im Kopf zwei explizit<br />
definiert und den Aliasnamen rdf und dc zugeordnet. Die folgende Syntax zeigt,<br />
wie der Alias definiert wird:<br />
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#<br />
Die Definition wird immer als Attribut xmlns im Wurzelelement des den Namensraum<br />
benutzenden Dokumentteils geschrieben. Man kann die Definition auch in<br />
jedem einzelnen betroffenen Element wiederholen. Nach dem Doppelpunkt folgt<br />
der gewählte Alias und danach der eigentlichen Namensraum als vollständiger<br />
URL.<br />
Die Elemente , und zeigen die Anwendung.
SimpleXML<br />
RDF ist die »Mutterdefinition« von RSS und steht für Resource Description<br />
Format. Dies ist eine sehr komplexe Form der Beschreibung von<br />
verlinkten Dokumenten. Unter http://www.w3.org/RDF/ finden Sie eine<br />
eingehende technische Beschreibung.<br />
Eine Definition im gezeigten Dokument hat keinen Alias:<br />
xmlns="http://purl.org/rss/1.0/"<br />
Das ist der Standardnamensraum, der für alle Elemente gilt, die keinen Alias aufweisen<br />
(der dritte im Bunde der oben erwähnten). RSS 1.0 verlangt diese Angabe,<br />
während die erste öffentliche Version 0.91 ausdrücklich keinen Namensraum verlangt,<br />
was freilich zu praktischen Problemen beim Datenaustausch führen kann.<br />
Mehr dazu finden Sie im Abschnitt »Ein konkretes XML-Format: RSS« ab Seite<br />
568.<br />
Namensräume mit SimpleXML verwenden<br />
Die Suche von Elementen mit bestimmten Namensräumen verlangt nach einem<br />
neuen Satz von Methoden, über den SimpleXML nicht verfügt. <strong>In</strong> der Praxis versagt<br />
die Anwendung dennoch nicht, denn SimpleXML ignoriert die Namensräume<br />
einfach. Damit sind auch Dateien mit Namensraumaliasen lesbar,<br />
zumindest solange die interne Vereinfachung nicht zu Namenskonflikten führt.<br />
XML Namensräume und XPath<br />
SimpleXML ignoriert die Namensräume jedoch nicht völlig. Es existiert lediglich<br />
keine dedizierte Syntax für den Zugriff. Mit XPath sieht es anders aus. Hier kann<br />
der Namensraum bei der Abfrage durchaus angegeben werden. Freilich ist dies<br />
keine Leistung von SimpleXML, sondern vom intern verwendeten XPath-Modul.<br />
Damit die Übergabe der Namensräume auch mit dem Standardnamensraum<br />
funktioniert, muss wenigstens dieser registriert werden. Dann funktioniert auch die<br />
Auflösung der übrigen Namensräume, wie es das nächste Beispiel weiter unten<br />
zeigt.<br />
Um alle Titel aus den Elementen zu finden, muss der Namensraum-<br />
Alias registriert werden. Der vollständige Namensraum kann beispielsweise folgendermaßen<br />
aussehen:<br />
http://purl.org/rss/1.0/<br />
591
592<br />
XML und Webservices<br />
Der Alias ist frei wählbar, aber der RSS-Standard empfiehlt für das vorliegende Beispiel<br />
die Verwendung von »rss«. Die XPath-Abfrage sollte dann folgendermaßen<br />
aussehen:<br />
//rss:item/rss:title<br />
Leider gibt es keinen Weg, in XPath einen Standardnamensraum zu definieren,<br />
wie es in XML selbst möglich ist. Das ist in der Praxis aber nur selten problematisch.<br />
Wichtig ist es, an den Namensraum bei der Abfrage zu denken, auch wenn<br />
im Dokument ein Standardnamensraum verwendet wird und der Alias nicht in<br />
allen Tags auftaucht.<br />
Das folgende Listing zeigt die fertige Lösung:<br />
Listing <strong>14</strong>.17: simplens.php – Abfrage eines RSS-Feed mit Namensräumen<br />
registerXPathNamespace('rss', 'http://purl.org/rss/1.0/');<br />
$titles = $s->xpath('//rss:item[ <br />
starts-with(dc:date, "2004-01-")]/rss:title');<br />
foreach ($titles as $title)<br />
{<br />
print "$title\n";<br />
}<br />
?><br />
Die erste Zeile entspricht den letzten Beispielen. Dann folgt die Registrierung des<br />
Namensraums:<br />
$s->registerXPathNamespace('rss', 'http://purl.org/rss/1.0/');<br />
Die Abfrage selbst greift außerdem demonstrativ auf XSLT-Funktionen zurück,<br />
um die Ergebnisse einzuschränken. Die Funktion starts-with prüft, ob eine Zeichenkette<br />
mit einer bestimmten Zeichenfolge beginnt. Der Alias dc deutet darauf<br />
hin, dass die Daten der Namensraumdefinition aus den Dublin Core Metadata<br />
entstammen, die Datumswerte spezifiziert.<br />
Abbildung <strong>14</strong>.7:<br />
Auswahl des Titels aus dem RSS-Feed
Andere Funktionen<br />
SOAP-Webservices<br />
Wie bereits erwähnt, ist es möglich, Attribute und Elementinhalte auszutauschen,<br />
indem ein neuer Wert zugewiesen wird. Der geänderte <strong>In</strong>halt des Dokuments<br />
kann komplett als Zeichenkette zurückgegeben werden, wozu die Methode<br />
AsXml() auf den Wurzelknoten angewendet wird.<br />
<strong>In</strong>sgesamt ist SimpleXML ein feines und kleines Modul, das den Zugriff auf XML<br />
stark vereinfacht und ohne tief gehende Kenntnisse leicht bedienbar ist. Große<br />
Projekte können davon weniger profitieren, weil die Einschränkungen programmiertechnisch<br />
kaum zum umschiffen sind. Hier können Sie jedoch mit dem zweiten<br />
XML-Modul, der libxml2, nahtlos ansetzen.<br />
<strong>14</strong>.4 SOAP-Webservices<br />
Webservices sind bereits seit einiger Zeit heftig umworben und immer wieder<br />
Titelthema von Entwicklerkonferenzen und Produktankündigungen. Eine breite<br />
Nutzung ist indes nicht auszumachen. Die ersten Anfänge sind trotzdem ermutigend<br />
und die technischen Vorteile sind so gravierend, dass eine Beschäftigung mit<br />
den Möglichkeiten unbedingt lohnt.<br />
Grundlagen<br />
PHP5 stellt mit dem SOAP-Erweiterungen ein integriertes Paket zur Verfügung,<br />
mit dem sich Webservices anbieten und nutzen lassen. Damit ist der Einsatz derart<br />
einfach geworden, dass man sich deutlich mehr Gedanken um mögliche Anwendungen<br />
als um deren Realisierung machen muss.<br />
Gedanken<br />
Webservices sind im Grund genommen nur entfernte Methodenaufrufe. Auf<br />
irgendeinem Server läuft ein Programm, das öffentliche Methoden zur Verfügung<br />
stellt. Werden diese – mit oder ohne Parameter – aufgerufen, gibt eine spezielle<br />
Methode Daten zurück oder führt Aktionen aus. Das Ganze nennt man dann<br />
einen Webservice (oder Web-Dienst oder XML-Webdienst), wenn sich der Anbieter<br />
des Dienstes an bestimmte Standards hält. Das ist sehr sinnvoll, denn damit öff-<br />
593
594<br />
XML und Webservices<br />
net er sich einer Palette von Clients, die denselben Standard benutzen.<br />
Prominente Beispiele solcher Anbieter sind Amazon und Google, deren Dienste<br />
»Bücher kaufen« und »Suchen« so anderen Servern zur Verfügung gestellt werden.<br />
Dabei umfasst die Definition »Webservice« bereits sehr viel, lediglich die<br />
Ausformulierung der Abfragen muss bekannt gemacht werden. Da entsprechende<br />
Clients für alle Betriebssysteme und Programmierumgebungen zur Verfügung stehen,<br />
muss sich der Anbieter keine Gedanken über die Nutzbarkeit seines Angebots<br />
machen. Entsprechend zahlreich sollten Anbieter und Nutzer sein – theoretisch.<br />
<strong>In</strong> der Praxis setzen sich Webservices recht zögerlich durch. Vermutlich hat dies<br />
keine technischen Ursachen, sondern deutet eher auf mentale Hemmnisse hin.<br />
Webservices sind primär für die Server-Server-Kommunikation geschaffen. Auch<br />
bei der Abwicklung von automatisierten Abfragen in der Server-Client-Kommunikation<br />
sind Webservices stark. Das massiv clientgeprägte Web mit einem nicht<br />
Webservice-tauglichen Browser steht dem natürlich entgegen. Es existieren<br />
schlicht keine Strukturen in Unternehmen und deren IT-Abteilungen, in der Kategorie<br />
»Dienst« zu denken. Dabei ist eigentlich alles ganz einfach.<br />
Prinzipien<br />
Die Kommunikation zwischen Server und Server oder zwischen Client und Server<br />
benutzt zwei bereits etablierte Techniken:<br />
1. HTTP oder SMTP<br />
HTTP und SMTP sind die möglichen Transportprotokolle, die die Daten<br />
übertragen. Meist kommt nur HTTP zum Einsatz. Das bedeutet, dass Aufrufe<br />
von Webservices problemlos Firewalls überwinden. Der Anbieter muss seine<br />
massiven Sicherheitseinrichtungen also kein bisschen öffnen, was äußerst<br />
attraktiv ist.<br />
SMTP ist kaum in Benutzung, würde jedoch auch interessant sein, weil es eine<br />
asynchrone Kommunikation zulässt. Man stelle sich vor, ein Client, beispielsweise<br />
ein Handy, sendet eine Anfrage an einen solchen Dienst über die MMS-<br />
Funktion. Der Dienstanbieter setzt dies auf SMTP um und der Webservice-<br />
Anbieter beantwortet die Anforderung auf gleichem Wege. Der Dienstanbieter<br />
erstellt aus der Antwort wiederum eine MMS und schon kann man billig interaktiv<br />
kommunizieren – ohne wie bei WAP ständig online zu sein. Liest hier<br />
eigentlich jemand von Vodafone mit?
SOAP-Webservices<br />
2. XML<br />
Die Nachrichten selbst – also die Parameterdaten einer Anfrage und die Ergebnisse<br />
– werden mittels SOAP (Simple Object Access Protocol) kodiert. Dies ist<br />
ein XML-Dialekt. Damit ist die Weiterverarbeitung schnell und einfach, denn<br />
XML-Parser stehen immer und überall zur Verfügung. Die Dienstinformationen<br />
selbst, also die Namen der Methoden und die Datentypen der Parameter<br />
werden über eine <strong>In</strong>formationsdatei im Format WSDL (Web Services Description<br />
Language) angeboten, wobei es sich hier selbstverständlich wiederum um<br />
XML handelt.<br />
Auf der Grundlage der Basistechnologien HTTP und XML ist es für Hersteller<br />
nicht schwer, Webservices in Programmiersprachen und Betriebssysteme zu integrieren.<br />
Jeder neuere Website, die mit PHP5, Java oder ASP.NET in den letzten<br />
drei bis fünf Jahren entstanden ist, steht die Technologie praktisch zur Verfügung.<br />
Technisch läuft das Ganze dann so ab, dass zuerst der Dienst selbst programmiert<br />
wird, meist eine Klasse mit einer Anzahl öffentlicher Methoden, die den Benutzern<br />
bereitgestellt werden. Zu diesem Angebot wird dann die Beschreibungsdatei<br />
im WSDL-Format erstellt. Dann wird der SOAP-Server der Programmierumgebung<br />
damit beauftragt, Anfragen anzunehmen, an die Methoden weiterzuleiten<br />
und deren Beantwortung zu überwachen. Die Außenkommunikation wird, wenn<br />
sie über HTTP abgewickelt wird, vom Webserver erledigt, der selbst nichts von<br />
SOAP oder XML wissen muss. Für ihn sind es lediglich HTTP-Anforderungen,<br />
deren Körper (Body) statt HTML-Seiten SOAP-Nachrichten enthält.<br />
Technische Grundlagen<br />
Um nicht völlig im Dunkeln zu tappen, was den technischen Ablauf betrifft, sollten<br />
Sie sich kurz mit den technischen Grundlagen vertraut machen. Die hier vorliegende<br />
Beschreibung ist stark vereinfacht, reicht aber für das Verständnis der<br />
ersten Beispiele aus.<br />
An allererster Stelle steht immer SOAP. SOAP realisiert den eigentlichen Funktionsaufruf.<br />
Die SOAP-Nachricht besteht aus drei Teilen:<br />
Umschlag (SOAP Envelope)<br />
Kopf (SOAP Header)<br />
<strong>In</strong>halt (SOAP Body)<br />
595
596<br />
XML und Webservices<br />
Der <strong>In</strong>halt kann in einer Nachricht in mehrere Blöcke verteilt sein. <strong>In</strong> XML sieht<br />
das Ganze dann folgendermaßen aus:<br />
Listing <strong>14</strong>.18: Rumpf einer SOAP-Nachricht (ohne Daten)<br />
<br />
<br />
<br />
<br />
<br />
<br />
Zusätzlich zur eigenen Namensraumdefinition werden meist noch XML-Schema<br />
für die Datentypen und Schema-<strong>In</strong>stance vereinbart. Als Namensraumaliase kommen<br />
die Abkürzungen »xsd« und »xsi« zum Einsatz.<br />
Was auch immer im <strong>In</strong>halt der SOAP-Body-Tags steht, hängt bereits von der konkreten<br />
Anwendung ab. Der Aufbau ist zwar sehr streng, die Benennung der Tags<br />
und die Struktur variieren jedoch. Damit sich die beiden Kommunikationspartner<br />
über diese Struktur einig werden – und zwar automatisch – kommt WSDL zum<br />
Einsatz.<br />
WSDL steht für Web Services Description Language. Dies ist eine vom Anbieter zu<br />
erstellende XML-Datei, die auf Anforderung des Clients gesendet wird. Sie muss<br />
genaue <strong>In</strong>formationen über folgende Dienstmerkmale enthalten:<br />
Kommunikationsport, also den URL, unter dem der Dienst bereitsteht<br />
Bindung, also die Verknüpfung zwischen Dienst und Ein- und Ausgabeoperationen<br />
Porttypen, eine Definition der Kommunikationsrichtung der zulässigen Operationen<br />
Nachrichten, die Namen der von den Ports angebotenen Methoden und deren<br />
Parameter- und Rückgabedefinitionen<br />
Datentypen, also die konkret von einem Parameter oder Rückgabewert benutzten<br />
Datentypen<br />
Bei den Datentypen wird zur Beschreibung XML-Schema benutzt. Zulässig sind<br />
sowohl die in XML-Schema definierten elementaren Typen (string, integer) als<br />
auch so genannte Komplextypen, die benutzerdefinierte Datensammlungen<br />
beschreiben.
SOAP-Webservices<br />
Mit diesen <strong>In</strong>formationen ist der Client nun in der Lage, die vom Server bereitgestellten<br />
Methoden so umzusetzen, dass dem Programmierer tatsächlich eine Klasse<br />
mit den ursprünglich definierten Methoden vorgehalten wird. Das ist die eigentliche<br />
Faszination: Während auf der einen Seite der Dienst mit PHP und den SOAP-<br />
Erweiterungen benutzt wird, als Betriebssystem Unix läuft und als Datenbank<br />
<strong>My</strong><strong>SQL</strong> benutzt wird, steht dem Client möglicherweise ein Windows XP und ein<br />
in Access geschriebene Benutzeroberfläche zur Verfügung. <strong>Und</strong> wenn der Server<br />
später auf das Gespann Java/Sun Solaris wechselt, sollte der Client davon nichts<br />
mitbekommen. Das ist die schöne neue Welt der Webservices.<br />
Alle Theorie ist jedoch langweilig, wenn man nichts praktisch unternimmt. Deshalb<br />
schließt dieses Kapitel mit zwei kleinen Projekten ab. Eines konsumiert einen<br />
im <strong>In</strong>ternet öffentlich angebotenen Webservice, das andere bietet selbst einen solchen<br />
an.<br />
Der Amazon-Webservice: ein Überblick<br />
Dieser Abschnitt stellt die Amazon-Webservices kurz vor und gibt einen ersten<br />
Einblick in mögliche Anwendungen. Dies erhebt keinerlei Anspruch auf Vollständigkeit.<br />
Die Definition der Dienste: WSDL<br />
Die folgende WSDL-Datei stellt den Zustand und das Angebot des Dienstes Mitte<br />
2004 dar. Auf die Datei wird im folgenden Bezug genommen. Man kann ihr<br />
sowohl die Struktur der aufrufbaren Funktionen als auch die Datentypen der Parameter<br />
und Rückgabewerte entnehmen:<br />
Listing <strong>14</strong>.19: WSDL-Datei des Amazon-Webservices (stark gekürzt)<br />
<br />
<br />
598<br />
XML und Webservices<br />
targetNamespace="http://soap.amazon.com"><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
SOAP-Webservices<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
600<br />
XML und Webservices<br />
minOccurs="0"/><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
SOAP-Webservices<br />
type="typens:SimilarProductsArray"<br />
minOccurs="0"/><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
601
602<br />
XML und Webservices<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Lesen Sie diese Datei am besten von unten nach oben. <strong>In</strong> dieser Reihenfolge<br />
bauen nämlich die <strong>In</strong>formationen aufeinander auf. Beachten Sie auch, dass die<br />
Daten, um hier überhaupt druckbar zu sein, sehr stark gekürzt wurden (auch wenn<br />
es nicht so aussieht). Praktisch sind nur die <strong>In</strong>formationen enthalten, die im Folgenden<br />
direkt benötigt werden – für die ISBN-Suche mit AsinSearchRequest. Aber<br />
wie liest man nun solch eine Datei?<br />
Alles beginnt bei der Deklaration eines Dienstes und der Festlegung seines<br />
Namens:<br />
<br />
Der Dienst definiert einen Port und eine Bindung:<br />
<br />
Die Bindung definiert die Namen der Funktionen (Operation genannt) und deren<br />
Kodierung und Namensräume. Für AsinSearchRequest sieht das folgendermaßen<br />
aus:<br />
SOAP-Webservices<br />
Der Port-Typ definiert dieselbe Funktion und nennt dazu die konkrete Ein- und<br />
Ausgabefunktion, die getrennt definiert werden:<br />
<br />
Der SOAP-Client baut aus diesen Angaben dann einen normalen PHP-Funktionsaufruf<br />
zusammen. Der Port nennt für die in der Bindung vereinbarte Operation<br />
die Namen der Funktionen für beide Transportrichtungen, input ist die Abfrage,<br />
output die Rückgabe:<br />
<br />
<br />
<br />
<br />
Diese Funktionen wiederum erscheinen als Nachrichten und definieren die<br />
Datentypen, die benutzt werden. Der Typ AsinRequest wird für die Anfrage<br />
benutzt:<br />
<br />
<br />
<br />
Der Typ Product<strong>In</strong>fo wird bei der Antwort verwendet:<br />
<br />
<br />
<br />
Beides sind offensichtlich keine elementaren Typen, weshalb sich weiter oben die<br />
Typdefinition im XML-Schema-Stil anschließt. Komplexe Typen setzen sich,<br />
gegebenenfalls über mehrere Schritte, aus einfachen Typen zusammen. Die Definition<br />
eines komplexen Typs wird folgendermaßen eingeleitet:<br />
<br />
Darunter folgen entweder weitere komplexe Typen oder – in letzter Konsequenz –<br />
elementare:<br />
<br />
Erscheint der Zusatz minOccurs="0", ist das Feld optional:<br />
<br />
Freilich kann hier auch eine bestimmte Mindest- oder Maximalanzahl festgelegt<br />
werden. XML-Schema erlaubt eine sehr feine Definition von Datentypen.<br />
603
604<br />
XML und Webservices<br />
Ein Datentyp für die Antwort Product<strong>In</strong>fo, Details, enthält beispielsweise einen<br />
Verweis auf DetailsArray:<br />
<br />
Dieses ist als SOAP-Array (Achtung! Basisdatentyp) definiert, das Elemente des<br />
Typs Details (Achtung! benutzerdefinierter Komplextyp) enthält:<br />
<br />
Details seinerseits enthält nun nur noch Basistypen (wie string), wie der folgende<br />
Ausschnitt zeigt:<br />
<br />
Es können jedoch auch weitere komplexe Typen folgen, die andernorts in der<br />
WSDL-Datei definiert sind (möglicherweise aber nicht abgedruckt, weil sonst das<br />
halbe Buch voll WSDL-Code wäre):<br />
<br />
Mit diesen <strong>In</strong>formationen ist der SOAP-Client nun in der Lage, die Daten für eine<br />
Anfrage zu erstellen und aus der Antwort die Daten wieder zu entnehmen. Eine<br />
korrekte Anfrage, wie sie das nachfolgend vorgestellte Beispiel erzeugt, sieht folgendermaßen<br />
aus:<br />
Listing <strong>14</strong>.20: Anfrage an Amazon: Aufruf der Funktion AsinSearchRequest<br />
<br />
<br />
<br />
<br />
<br />
3827264553<br />
activeserverpa0e<br />
heavy<br />
XXXXXXXXXXX
SOAP-Webservices<br />
de<br />
<br />
<br />
<br />
<br />
Man kann hier sehr gut erkennen, wie im Body der Nachricht die Anfrage getreu<br />
der Datentyp-Definition aufgebaut ist. Erkennbar ist aber auch, dass mehr als 50%<br />
der Daten keine realen Daten sind. Der so genannte Overhead einer SOAP-Verbindung<br />
ist bei vielen kurzen Anfragen erheblich.<br />
Etwas umfassender fällt die Antwort aus (hier die »lite«-Version, ohne Kundenrezensionen):<br />
Listing <strong>14</strong>.21: Eine SOAP-Antwort (auf exakt die zuvor gezeigte Anfrage)<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
http://www.amazon.de/exec/obidos/ASIN/3827264553/<br />
activeserverpa0e?dev-t=D3D53F5TPPE230%26<br />
camp=2025%26link_code=sp1<br />
<br />
3827264553<br />
<br />
.NET Windows Forms in 21 <strong>Tagen</strong> . Oberflächen programmieren<br />
<br />
Book<br />
<br />
605
606<br />
XML und Webservices<br />
Chris Payne<br />
<br />
15. Januar 2003<br />
<br />
Markt+Technik<br />
<br />
<br />
http://images-eu.amazon.com/images/P/3827264553.03.THUMBZZZ.jpg<br />
<br />
<br />
http://images-eu.amazon.com/images/P/3827264553.03.MZZZZZZZ.jpg<br />
<br />
<br />
http://images-eu.amazon.com/images/P/3827264553.03.LZZZZZZZ.jpg<br />
<br />
EUR 46,68<br />
EUR 49,95<br />
EUR 12,70<br />
<br />
<br />
<br />
<br />
<br />
<br />
Man kann gut erkennen, dass XML ideal für die Übertragung komplexer hierarchischer<br />
Daten geeignet ist. Die Rückübertragung nach PHP ist nun wieder Sache<br />
des SOAP-Clients. Dieser erstellt übrigens ein Objekt daraus. Angeschaut mit<br />
var_dump sieht es folgendermaßen aus (wieder exakt dieselbe Anfrage).<br />
Der Zugriff auf die Daten ist nun recht einfach. Man muss lediglich wissen, wo die<br />
Daten die man benötigt, in der Hierarchie stehen. Man muss außerdem natürlich<br />
auf Arrays, <strong>In</strong>dizes usw. achten, um gegebenenfalls mit Schleifen arbeiten zu können.<br />
Um das zu erfahren, ist jedoch das Lesen der Rohdaten und der WSDL-<br />
Dateien erforderlich, falls sich niemand die Mühe gemacht hat, eine umfassende<br />
verbale Beschreibung mitzuliefern.
Webservices konsumieren<br />
SOAP-Webservices<br />
Um einen Webservice zu konsumieren, benötigen Sie zuerst einen solchen Dienst<br />
und dessen Dienstbeschreibung. Steht das bereit, erledigen PHP5 und dessen<br />
SOAP-Erweiterungen die ganze Arbeit.<br />
Als Beispiel soll die im letzten Kapitel erstellte Bibliotheks-Anwendung erweitert<br />
werden. Das Erfassen von möglicherweise hunderten Büchern ist recht mühevoll.<br />
Wenn der Server ohnehin online ist, bietet es sich an, die Daten von einem Buchhändler<br />
zu beschaffen. Dankbares Opfer wird in diesem Fall Amazon, da dort ein<br />
entsprechender Dienst angeboten wird.<br />
Die Daten beschaffen<br />
Abbildung <strong>14</strong>.8:<br />
Das vom SOAP-Client aus der zuvor<br />
gezeigten Antwort erzeugte Objekt<br />
Der Start beginnt auf der folgenden Website (ab hier in Englisch):<br />
http://www.amazon.com/webservices<br />
Melden Sie sich zuerst bei Amazon an, um die kostenlose Zugangsberechtigung<br />
zu erhalten. Dazu sind lediglich eine E-Mail-Adresse und ein Kennwort erforderlich,<br />
das selbst gewählt werden kann. Optional laden Sie dort zuerst des SDK (Entwicklerpaket)<br />
herunter. Das Paket hat den Namen kit.zip und ist auch auf der CD<br />
zum Buch unter /Bibliothek/SOAP zu finden (Version vom August 2004). Das<br />
607
608<br />
XML und Webservices<br />
SDK enthält Beispielprogramme, Hilfen und Hinweise, auch für PHP. Das enthaltene<br />
Programm nutzt jedoch die für PHP 4 entwickelte Version, die auf dem<br />
PEAR-Paket NuSOAP basiert. Die in diesem Buch gezeigte Version wurde dagegen<br />
vom Autor explizit für PHP5 geschrieben und soweit vereinfacht, dass ein<br />
schneller Einstieg gelingt.<br />
Versuchsaufbau und Vorbereitung von PHP5<br />
Die SOAP-Erweiterung in PHP5 steht unter Windows als php_soap.dll zur Verfügung,<br />
deren Aktivierung wie üblich in der php.ini erfolgt. Unter Linux wird PHP5<br />
mit dem Schalter --with-soap kompiliert. Ein Blick in die Anzeige der Funktion<br />
phpinfo zeigt, ob es geklappt hat:<br />
Dann ist noch zu überprüfen, ob die Konfigurationsparameter in der Datei php.ini eingetragen<br />
sind. Wenn der folgende Abschnitt nicht zu finden ist, fügen Sie ihn hinzu:<br />
[soap]<br />
soap.wsdl_cache_enabled=1<br />
soap.wsdl_cache_dir="c:\windows\temp"<br />
soap.wsdl_cache_ttl=86400<br />
Achten Sie auf den Pfad zum Verzeichnis temporärer Daten (das Beispiel gilt für<br />
Windows).<br />
Ziel des SOAP-Projekts<br />
Abbildung <strong>14</strong>.9:<br />
Die SOAP-Erweiterungen<br />
wurden erfolgreich<br />
aktiviert<br />
Ziel der Anwendung ist die Vereinfachung der Datenerfassung bei der Aufnahme<br />
von Büchern in die im vorigen Kapitel vorgestellte Bibliotheksverwaltung. Dabei<br />
soll die Angabe der ISBN genügen, alle übrigen Daten sollen aus der Amazon-<br />
Datenbank geholt werden. Dazu gehört beispielsweise auch ein Bild des Buches,<br />
was sonst sicher nur mühevoll zu beschaffen wäre. Wie das am Ende aussehen soll,<br />
zeigt die Abbildung<strong>14</strong>.10.
SOAP-Webservices<br />
Dies spart vor allem viel Zeit bei der Erfassung, vermeidet aber auch lästige Tippfehler.<br />
Die hier benutzten Daten stellen nur einen kleinen Teil der Möglichkeiten<br />
dar, die Amazon bietet.<br />
Erweiterung der Anwendung<br />
Abbildung <strong>14</strong>.10:<br />
Erfassung von<br />
Büchern: Alle<br />
Daten außer der<br />
ISBN liefert der<br />
Webservice<br />
Die Erweiterung der Anwendung umfasst nur zwei Schritte:<br />
1. Aufbau eines Moduls, das den Webservice abfragt<br />
2. Erweiterung der bisherigen Bibliotheksverwaltung<br />
Das Modul wird natürlich PHP5-typisch als Klasse implementiert. Sie kann dann<br />
auch von anderen Anwendungen benutzt werden. Diese ist allerdings – im Gegensatz<br />
zu vielen fertigen Lösungen im <strong>In</strong>ternet – bewusst minimalistisch programmiert<br />
worden, um die mindestens notwendigen Funktionen demonstrieren zu<br />
können, ohne dabei erschlagend viele Code zu benutzen.<br />
609
610<br />
XML und Webservices<br />
Listing <strong>14</strong>.22: soapclient.inc.php – Eine primitive Klasse zur Benutzung des Amazon-<br />
Webservice<br />
SOAP-Webservices<br />
}<br />
return isset($result) ? $result : '';<br />
}<br />
}<br />
?><br />
Die Klasse definiert einige <strong>In</strong>formationen, die der Dienst benötigt, als Konstanten:<br />
const LANG = 'de'<br />
Die Sprache des Katalogs, der abgefragt werden soll. Wenn Sie deutsche<br />
Bücher bearbeiten, muss die Sprache »de« sein.<br />
const MODE = 'heavy'<br />
Der Modus, hier kann »lite« für kurze Ergebnisse oder »heavy« für ausführliche<br />
stehen. Untersuchen Sie zuerst »lite« und nutzen Sie »heavy« nur dann,<br />
wenn Sie die benötigten Angaben andernfalls nicht finden.<br />
const TAG = 'activeserverpa0e'<br />
Der Token eines Affiliate-Programms. Diese Angabe ist optional.<br />
const WSDL_FILE =<br />
'http://soap.amazon.com/schemas2/AmazonWebServices.wsdl'<br />
Die Adresse, unter der der Webservice zur Verfügung steht.<br />
const TOKEN = 'IHR_AMAZON_TOKEN'<br />
Der Token, den Sie bei der Anmeldung von Amazon erhalten haben.<br />
Zuerst wird immer eine <strong>In</strong>stanz des SOAP-Clients benötigt:<br />
$this->Client = new SoapClient(self::WSDL_FILE, <br />
array('trace' => 1));<br />
Der zweite Parameter, array('trace' => 1), kann entfallen, wenn die Anwendung<br />
stabil läuft und der Zugriff auf die originalen SOAP-Nachrichten nicht mehr benötigt<br />
wird. Sie können Methoden wie __getLastResponse() nur benutzen, wenn mit<br />
trace die Speicherung der Daten eingeschaltet wurde. Dies ist allerdings mit mehr<br />
Speicher und damit höherer Serverbelastung verbunden und sollte deshalb nur<br />
während der Aufbauphase benutzt werden.<br />
Die eigentliche Abfrage wird in der Methode searchISBN ausgeführt:<br />
public function SearchISBN($Keyword)<br />
Dort wird zunächst ein Array aus den Parametern zusammengestellt. Die Namen<br />
der Schlüsselfelder müssen den Anforderungen in der WSDL-Datei entsprechen,<br />
andernfalls reagiert der Amazon-Server mit einer Fehlermeldung. Stehen alle<br />
Daten bereit, erfolgt die Abfrage:<br />
611
612<br />
XML und Webservices<br />
$result = $this->Client->AsinSearchRequest($params);<br />
Der Name AsinSearchRequest ist wiederum in der WSDL-Datei als mögliche<br />
Dienstfunktion festgelegt. Der Einbau in einen try-Block fängt Fehler ab. Da es<br />
sich hier um eine <strong>In</strong>ternetverbindung handelt, kann diese natürlich fehlschlagen<br />
und dann hilft der Block bei der Fehlerverarbeitung. Wenn Sie außerhalb wieder<br />
mit try/catch arbeiten, sollten Sie mögliche Fehler mit throw new weiterreichen<br />
und gegebenenfalls eigene Fehlerklassen für Ihre Zwecke entwerfen. Im Beispiel<br />
ist die Fehlerverwaltung dem angesprochenen minimalistischen Prinzip zum<br />
Opfer gefallen. Für andere Fehler ersetzen Sie SoapFault durch Exception:<br />
catch (SoapFault $ex)<br />
Am Ende sind die ermittelten Daten nur noch zurückzugeben:<br />
return isset($result) ? $result : '';<br />
Die hier zurückgegebenen Daten sind ein Objekt, das in seinen Eigenschaften<br />
weitere Objekte und Arrays enthält. Der Aufbau entspricht der Struktur der in der<br />
WSDL-Datei für die spezifische Anfrage verwendeten Rückgabedaten.<br />
Einbau in das Bibliotheksskript<br />
Der Einbau in das Bibliotheksskript ist relativ einfach. Die Funktionalität wurde<br />
dabei etwas erweitert und in der Vorlage musste Platz für die zusätzlichen Daten<br />
geschaffen werden. Zuerst ein Blick auf die erweiterte Vorlage:<br />
Listing <strong>14</strong>.23: buecher.html: Die Vorlage zur Organisation der Buchdatenbank<br />
<br />
<br />
Bibliotheksverwaltung<br />
Startseite<br />
|<br />
Ausleihe<br />
|<br />
Kundenverwaltung<br />
Bücherverwaltung<br />
<strong>In</strong>formationen<br />
<br />
Bücherliste<br />
<br />
SOAP-Webservices<br />
Anzahl<br />
<br />
<br />
ISBN<br />
<br />
<br />
Titel<br />
<br />
<br />
Aktion<br />
<br />
<br />
<br />
<br />
<br />
{$books:number}<br />
<br />
<br />
{$books:isbn}<br />
<br />
<br />
{$books:title}<br />
<br />
<br />
Details<br />
Löschen<br />
<br />
<br />
<br />
<br />
<br />
Neues Buch erfassen<br />
Ändern / Erfassen<br />
<br />
<br />
<br />
<br />
Buchdaten<br />
<br />
<br />
613
6<strong>14</strong><br />
XML und Webservices<br />
Titel:<br />
<br />
<br />
<br />
<br />
<br />
<br />
Verlag: {$detail_publisher}<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Untertitel:<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Autor:<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
ISBN:<br />
<br />
<br />
<br />
<br />
SOAP-Webservices<br />
<br />
<br />
Anzahl<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Ausgeliehen<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Neu – gegenüber der im letzten Kapitel vorgestellten Variante – ist die Ausgabe<br />
der Zusatzdaten:<br />
<br />
<br />
Verlag: {$detail_publisher}<br />
<br />
<br />
<br />
<br />
<br />
<br />
615
616<br />
XML und Webservices<br />
Dies ist optional, weil die Daten nicht zwingend vorliegen. Deshalb erfolgt der<br />
Einbau in IF-Abschnitte.<br />
Der PHP-Code zu dieser Vorlage ist ebenfalls nur wenig umfassender als die letzte<br />
Version:<br />
Listing <strong>14</strong>.24: buecher.php – Steuerung der Vorlage mit allen Datenbankzugriffen<br />
}<br />
SOAP-Webservices<br />
= utf8_decode($data->Details[0]->Authors[0]);<br />
$detail_isbn = $_POST['book_isbn'];<br />
$detail_title <br />
= utf8_decode($data->Details[0]->ProductName);<br />
$detail_image <br />
= utf8_decode($data->Details[0]->ImageUrlMedium);<br />
$detail_publisher <br />
= utf8_decode($data->Details[0]->Manufacturer);<br />
}<br />
else<br />
{<br />
$detail_subtitle = $_POST['book_subtitle'];<br />
$detail_author = $_POST['book_author'];<br />
$detail_isbn = $_POST['book_isbn'];<br />
$detail_title = $_POST['book_title'];<br />
}<br />
$detail_number = empty($_POST['book_number']) ? 1 :<br />
(int) $_POST['book_number'];<br />
$mysqli->query("INSERT INTO books <br />
(title, subtitle, author, isbn, number)<br />
VALUES ('{$detail_title}', <br />
'{$detail_subtitle}',<br />
'{$detail_author}', <br />
'{$detail_isbn}',<br />
{$detail_number}<br />
)");<br />
break;<br />
case 'change':<br />
$mysqli->query("UPDATE books SET <br />
title='{$_POST['book_title']}', <br />
subtitle='{$_POST['book_subtitle']}', <br />
author='{$_POST['book_author']}', <br />
isbn='{$_POST['book_isbn']}', <br />
number={$_POST['book_number']}, <br />
WHERE id = {$_POST['id']}");<br />
break;<br />
}<br />
// Liste fuellen<br />
$query = $mysqli->query("SELECT * FROM books");<br />
while($arr = $query->fetch_assoc())<br />
{<br />
617
618<br />
XML und Webservices<br />
$books[] = $arr;<br />
}<br />
// Details abrufen, wenn gefordert<br />
if (isset($_GET['id']))<br />
{<br />
$query = $mysqli->query("SELECT *, (SELECT COUNT(*) <br />
FROM lendout <br />
WHERE books_id = b.id) AS lend <br />
FROM books b <br />
WHERE id = {$_GET['id']}");<br />
$detail = $query->fetch_assoc();<br />
$detail_id = $detail['id'];<br />
$detail_title = $detail['title'];<br />
$detail_subtitle= $detail['subtitle'];<br />
$detail_author = $detail['author'];<br />
$detail_number = $detail['number'];<br />
$detail_isbn = $detail['isbn'];<br />
$detail_lend = $detail['lend'];<br />
}<br />
?><br />
Hier sollen nur die Aspekte erläutert werden, die gegenüber der letzten Version<br />
neu sind. Zuerst muss natürlich das SOAP-Modul eingebunden werden:<br />
include('soapclient.inc.php');<br />
Die Abfrage der Amazon-Datenbank erfolgt, wenn ein neues Buch erfasst wurde<br />
und keine Daten außer der ISBN-Nummer im Formular angegeben wurden:<br />
if ((empty($_POST['book_author']) <br />
|| empty($_POST['book_title'])) && isset($_POST['book_isbn']))<br />
Für die Abfrage müssen die Trennzeichen aus der ISBN entfernt werden, andernfalls<br />
funktioniert AsinSearchRequest nicht:<br />
$asin = str_replace('-', '', $_POST['book_isbn']);<br />
Dann wird eine <strong>In</strong>stanz der SOAP-Klasse erstellt:<br />
$ws = new AmazonSearch();<br />
<strong>In</strong> diesem Augenblick wird bereits die WSDL-Datei gelesen. Da die Speicherzeit<br />
im Cache 1 Tag beträgt, muss man beim ersten Abruf und nach jedem weiteren<br />
Tag hier mit einer etwas längeren Wartezeit rechnen. Die Größe beträgt ca.<br />
52 KByte.
SOAP-Webservices<br />
Dann erfolgt die Abfrage, dies ist immer »life«, benötigt eine <strong>In</strong>ternetverbindung<br />
und dauert fast immer eine oder mehrere Sekunden (auch bei DSL):<br />
$data = $ws->SearchISBN($asin);<br />
Die Daten werden nun entsprechend der erwarteten Struktur entnommen, beispielsweise<br />
der erste Autor:<br />
$detail_author = utf8_decode($data->Details[0]->Authors[0]);<br />
Beachten Sie hier, dass die Daten UTF-8-kodiert sind. Dies ist XML-Standard.<br />
PHP liefert die passende Funktion zum Dekodieren mit: utf8_decode.<br />
Der Aufruf $data->Details[0]->Authors[0] sollte Ihnen keine Rätsel bereiten:<br />
$data Das Datenobjekt<br />
->Details Die Eigenschaft Details (ergibt ein Array)<br />
[0] Das erste Element des Arrays, dies ist wieder<br />
ein Objekt, diesmal vom Typ Details<br />
->Authors Darin ist eine Eigenschaft Authors definiert<br />
[0] Die Eigenschaft enthält ein Array, dessen erstes<br />
Element benutzt wird<br />
Das das erste Element direkt benutzt werden kann, ist der WSDL-Beschreibung<br />
oder dem Antwortpaket zu entnehmen. Der Datentyp für einen Autor ist »string«,<br />
also eine simple Zeichenkette. Die übrigen Abrufe folgen demselben Schema,<br />
wobei beispielsweise der Titel keine Unterteilung in ein Array mehr besitzt:<br />
utf8_decode($data->Details[0]->ProductName)<br />
Fazit<br />
Damit ist eigentlich alles gesagt. Die Amazon-Webservices sind äußerst komplex<br />
und für jeden Entwickler eine Herausforderung. Es lohnt sich jedoch, denn man<br />
kann mit den Affiliate-Programmen oder den zShops Geld verdienen. Der automatisierte<br />
Zugriff erlaubt einen personalarmen Betrieb eines solchen Shops und<br />
damit werden auch kleine Einnahmen attraktiv, weil sie nicht sofort wieder von<br />
Kosten aufgefressen werden. Wer übrigens eine Bibliothek betreibt, der kann<br />
direkt Kosten sparen, denn er muss nun künftig eingehende Bücher, die ohne elektronische<br />
Datensätze ankommen, nicht mehr von Hand erfassen. Mit Hilfe eines<br />
kleinen, preiswerten Barcode-Scanners lässt sich die ISBN vom Buchrücken<br />
abnehmen und schon sind die kompletten Daten von Amazon geholt und in der<br />
Datenbank. Was will man mehr?<br />
619
620<br />
XML und Webservices<br />
<strong>14</strong>.5 Referenz<br />
Die Referenz zeigt die in diesem Kapitel behandelten XML-Module SimpleXML<br />
und DOM.<br />
Referenz SimpleXML<br />
Funktion Beschreibung<br />
simplexml_import_dom Die Funktion importiert ein SimpleXML-Objekt aus<br />
einem DOM-Knoten und gibt das SimpleXML-Objekt<br />
zurück.<br />
simplexml_export_dom Exportiert ein SimpleXML-Objekt als DOM-Knoten.<br />
simplexml_load_file Lädt eine XML-Datei und gibt ein SimpleXML-Objekt<br />
zurück.<br />
simplexml_load_string Lädt XML aus einer Zeichenkette und gibt ein Simple-<br />
XML-Objekt zurück.<br />
Tabelle <strong>14</strong>.5: Funktionen, die SimpleXML-Objekte erzeugen oder verarbeiten<br />
Methode Beschreibung<br />
AsXML Exportiert den <strong>In</strong>halt als XML-Zeichenkette.<br />
attributes Die Liste der Attribute eines Elements.<br />
children Die Aufzählung der Kindelemente eines Elements.<br />
xpath Führt eine XPath-Abfrage aus.<br />
registerXPathNamespace Registriert einen Namensraumalias für die Verwendung in<br />
XPath-Ausdrücken<br />
Tabelle <strong>14</strong>.6: Methoden des SimpleXML-Objekts
Referenz DOM<br />
Klasse Bedeutung<br />
DomAttr Repräsentiert ein Attribut.<br />
DomCData Repräsentiert einen CDATA-Abschnitt.<br />
DomComment Repräsentiert einen Kommentar.<br />
DomDocument Die Basisklasse, das Dokument selbst.<br />
Referenz<br />
DomDocumentType Die DOCTYPE-Deklaration des Dokuments.<br />
DomElement Repräsentiert ein Element.<br />
DomEntity Repräsentiert eine Entität, beispielsweise &.<br />
DomEntityReference Repräsentiert eine Verweis-Entität (auf eine externe<br />
Datei).<br />
DomNode Allgemeine Knotenklasse (kann alles außer DomDocument<br />
sein).<br />
DomProcessing<strong>In</strong>struction Repräsentiert eine Prozessanweisung, beispielsweise<br />
<br />
DomText Frei stehender Text zwischen Elementen.<br />
DomXPath Erlaubt Zugriff per XPath-Abfrage auf das Dokument.<br />
Tabelle <strong>14</strong>.7: Liste der Klassen, die im DOM-Modul definiert sind<br />
Methode Bedeutung<br />
createAttributeNS Erzeugt ein Attribut mit Namensraum.<br />
createAttribute Erzeugt ein Attribut.<br />
createElementNS Erzeugt ein Element mit Namensraum.<br />
createElement Erzeugt ein neues Element.<br />
createTextNode Erzeugt einen Textknoten.<br />
Tabelle <strong>14</strong>.8: Methoden der Klasse DomDocument<br />
621
622<br />
XML und Webservices<br />
Methode Bedeutung<br />
createDocumentFragment Erzeugt ein Fragment neuer Knoten.<br />
createComment Erzeugt einen Kommentarknoten .<br />
createCDATASection Erzeugt einen CDATA-Abschnitt.<br />
createProcessing<strong>In</strong>struction Erzeugt eine Prozessanweisung.<br />
createAttribute Erzeugt ein Attribut.<br />
createEntityReferenz Erzeugt einen Verweis.<br />
getElementsByTagName Gibt eine Liste aller Elemente mit dem gegebenen<br />
Namen zurück.<br />
getElementsByTagNameNS Gibt eine Liste aller Elemente mit dem gegebenen<br />
Namen in einem bestimmten Namensraum zurück.<br />
getElementById Gibt ein Element mit einem bestimmten ID-Attribut<br />
zurück.<br />
renameNode Benennt einen Knoten um.<br />
load Lädt ein Dokument aus einem Stream (beispielsweise<br />
eine Datei).<br />
save Speichert das Dokument.<br />
loadXML Lädt XML aus einer Zeichenkette.<br />
saveXML Speichert XML als Zeichenkette.<br />
loadHTML Lädt eine HTML-Datei.<br />
saveHTML Speichert eine HTML-Datei.<br />
validate Prüft gegen eine DTD.<br />
validateSchema Prüft gegen ein Schema (XSD).<br />
validateRelaxNG Prüft gegen ein RelaxNG-Schema.<br />
insertBefore Fügt vor einem anderen Knoten ein.<br />
replaceChild Ersetzt ein Kindelement.<br />
Tabelle <strong>14</strong>.8: Methoden der Klasse DomDocument (Forts.)
Methode Bedeutung<br />
removeChild Entfernt ein Kindelement.<br />
appendChild Hängt ein Kindelement an.<br />
hasChildNodes Prüft, ob Kindelemente vorhanden sind.<br />
cloneNode Klont einen Knoten.<br />
hasAttributes Ermittelt, ob ein Element Attribute hat.<br />
isSameNode Vergleicht zwei Knoten auf Identität.<br />
Referenz<br />
isDefaultNamespace Prüft, ob der Knoten im Standardnamensraum ist.<br />
isEqualNode Vergleicht zwei Knoten auf Gleichheit.<br />
Tabelle <strong>14</strong>.8: Methoden der Klasse DomDocument (Forts.)<br />
Die Methode getElementById gibt Element-Objekte vom Typ DomElement zurück.<br />
Die Methoden getElementsByTagName und getElementsByTagNameNS geben Auflistungen<br />
mit Elementen des Typs DomNode zurück (interne Klasse DomNodeList).<br />
Methode Bedeutung<br />
getAttribute Ermittelt ein Attribut.<br />
setAttribute Erzeugt und definiert ein Attribut.<br />
removeAttribute Entfernt das Attribut.<br />
getAttributeNS Ermittelt ein Attribut in einem bestimmten Namensraum.<br />
setAttributeNS Erzeugt und definiert ein Attribut in einem bestimmten Namensraum.<br />
removeAttributeNS Entfernt das Attribut in einem bestimmten Namensraum.<br />
getAttributeNode Ermittelt das Attribut als Objekt vom Typ DomAttr.<br />
setAttributeNode Setzt ein Attribut auf Basis eines DomAttr-Objekts.<br />
setIdAttribute Setzt das Attribut mit den Namen ID.<br />
Tabelle <strong>14</strong>.9: Methoden der Klasse DomElement<br />
623
624<br />
XML und Webservices<br />
Methode Bedeutung<br />
setIdAttributeNS Setzt das Attribut mit den Namen ID in einem bestimmten<br />
Namensraum.<br />
isSameNode Vergleicht zwei Attribute auf Identität.<br />
isDefaultNamespace Prüft, ob der Attribute im Standardnamensraum ist.<br />
isEqualNode Vergleicht zwei Attribute auf Gleichheit.<br />
Tabelle <strong>14</strong>.9: Methoden der Klasse DomElement (Forts.)<br />
Die Methode getAttributeNode gibt ein Objekt vom Typ DomAttr zurück.<br />
Eigenschaft Bedeutung<br />
textContent <strong>In</strong>halt des Elements als Text (ohne Kindelemente).<br />
nodeType Siehe DomNode. Ist bei Elementen immer XML_ELEMENT_NODE.<br />
childNodes Auflistung von Kindelementen als DomNodeList, eine aufzählbare<br />
Liste von DomNode-Objekten.<br />
tagName Name des Elements<br />
Tabelle <strong>14</strong>.10: Eigenschaften der Klasse DomElement<br />
Methode Bedeutung<br />
isId Das Attribut ist ein ID-Attribut.<br />
insertBefore Fügt ein Attribut vor dem aktuellen ein.<br />
isSameNode Vergleicht zwei Attribute auf Identität.<br />
isDefaultNamespace Prüft, ob das Attribut im Standardnamensraum ist.<br />
isEqualNode Vergleicht zwei Attribute auf Gleichheit.<br />
Tabelle <strong>14</strong>.11: Methoden der Klasse DomAttribute
Eigenschaft Bedeutung<br />
value Wert des Attributes.<br />
name Name des Attributes.<br />
Referenz<br />
specified Gibt TRUE zurück, wenn das Attribut seinen aktuellen Wert<br />
aus dem Dokument erhielt und nicht erst durch spätere<br />
Änderung.<br />
ownerElement Gibt das DomElement zurück, zu dem dieses Attribut gehört.<br />
Tabelle <strong>14</strong>.12: Methoden der Klasse DomAttribute<br />
Name der Konstanten Bedeutung<br />
XML_ELEMENT_NODE Element<br />
XML_ATTRIBUTE_NODE Attribut<br />
XML_TEXT_NODE Textknoten<br />
XML_CDATA_SECTION_NODE CDATA-Abschnitt<br />
XML_ENTITY_REF_NODE Entity-Verweis<br />
XML_ENTITY_NODE Entity-Knoten<br />
XML_PI_NODE Prozessanweisung<br />
XML_COMMENT_NODE Kommentar<br />
XML_DOCUMENT_NODE Dokument<br />
XML_DOCUMENT_TYPE_NODE Document Type Deklaration<br />
XML_DOCUMENT_FRAG_NODE Dokumentfragment<br />
XML_NOTATION_NODE Notation (in einer DTD)<br />
XML_GLOBAL_NAMESPACE Globaler Namensraum<br />
XML_LOCAL_NAMESPACE Lokaler Namensraum<br />
XML_HTML_DOCUMENT_NODE Dokument im HTML-Modus<br />
Tabelle <strong>14</strong>.13: Konstanten des DOM-Moduls<br />
625
626<br />
XML und Webservices<br />
Name der Konstanten Bedeutung<br />
XML_DTD_NODE Document Type Definition<br />
XML_ELEMENT_DECL_NODE Element-Deklaration einer DTD<br />
XML_ATTRIBUTE_DECL_NODE Attribut-Deklaration einer DTD<br />
XML_ENTITY_DECL_NODE Entitäts-Deklaration einer DTD<br />
XML_NAMESPACE_DECL_NODE Namensraum-Deklaration<br />
XML_ATTRIBUTE_CDATA CDATA-Teil eines Attributes<br />
XML_ATTRIBUTE_ID ID-Attribut<br />
XML_ATTRIBUTE_IDREF IDREF-Attribut<br />
XML_ATTRIBUTE_IDREFS Kollektion von IDREF-Attributen<br />
XML_ATTRIBUTE_ENTITY Entität in einem Attribut<br />
XML_ATTRIBUTE_NMTOKEN NMTOKEN<br />
XML_ATTRIBUTE_NMTOKENS Aufzählung von NMTOKEN Elementen<br />
XML_ATTRIBUTE_ENUMERATION Attribut-Aufzählung<br />
XML_ATTRIBUTE_NOTATION Attribut-Notation<br />
Tabelle <strong>14</strong>.13: Konstanten des DOM-Moduls (Forts.)<br />
<strong>14</strong>.6 Kontrollfragen<br />
1. Welche Technologien sind wichtig, wenn mit XML gearbeitet wird?<br />
2. Welche Methode der Verarbeitung ist bei einem ca. 12 MByte großen XML-<br />
Dokument wahrscheinlich die beste, SAX oder DOM? Begründen Sie die Antwort.<br />
3. Nennen Sie typische Erscheinungsformen eines so genannten Knotens in einem<br />
XML-Dokument.<br />
4. Welche Bedeutung hat die DTD (Document Type Definition)?
Antworten auf die<br />
Kontrollfragen<br />
15
1. Tag<br />
628<br />
Antworten auf die Kontrollfragen<br />
1. Wann wurde PHP das erste Mal unter diesem Namen bekannt?<br />
A 1995, als PHP/FI. Damals war es noch einen Sammlung von Perl-Skripten.<br />
2. Was bedeutet »PHP«?<br />
A Dies ist ein so genanntes rekursives Acronym: PHP Hypertext Preprocessor.<br />
3. Worauf ist der Name »Zend« zurückzuführen?<br />
A Ein Kunstwort aus Namensteilen der Gründer der Firma Zend, Ze von<br />
Zeev Suraski und nd aus Andi Gutmans. Zend stellt den Sprachkern für<br />
PHP bereit und vertreibt außerdem kommerzielle Werkzeuge für PHP, wie<br />
beispielsweise eine IDE, Verschlüsselungs- und Cachetools.<br />
4. Anhand welcher Begrenzungszeichen werden PHP-Fragmente in HTML-Code<br />
erkannt?<br />
A . Andere Varianten werden nicht empfohlen. De Kurzform<br />
zum Einbetten von Variablen sollte nur in Ausnahmefällen<br />
benutzt werden.<br />
5. Welches Protokoll wird zur Übertragung von HTML-Seiten vom Server zum<br />
Browser verwendet?<br />
A HTTP – Hypertext Transfer Protocol.<br />
6. Welcher Webserver kann auf allen Betriebssystemen zur Entwicklung eingesetzt<br />
werden?<br />
A Der Apache-Webserver ist auf allen Plattformen zu Hause.<br />
7. Wofür steht der Begriff WAMP?<br />
A W = Windows, A = Apache, M = <strong>My</strong><strong>SQL</strong>, P = PHP. Die beliebteste Kombination<br />
für PHP-Entwickler, wird von weit über 90% der Anwender<br />
benutzt.<br />
8. Welche Bedeutung hat die Adresse »http://localhost«?<br />
A Verweis auf den lokalen Webserver (!). Der Name weicht meist vom<br />
Namen des lokalen Computers ab. Die zugehörigen IP-Adresse ist<br />
127.0.0.1.
9. Mit welcher Funktion kann PHP5 ausführlich getestet werden?<br />
2. Tag<br />
A phpinfo() heißt die Funktion. Folgendes Skript zeigt, wie die Funktion<br />
genutzt wird:<br />
2. Tag<br />
<br />
1. Wie geben Sie mehrzeilige Texte aus PHP-Code heraus am besten aus?<br />
A Mit der Heredoc-Syntax werden mehrzeilige Texte aus PHP-Code heraus<br />
ausgegeben:<br />
<br />
2. Welche Funktion eignet sich zur Ausgabe hexadezimaler Farbangaben?<br />
A printf könnte hilfreich sein. Die Option %2X erzeugt zweistellige hexadezimale<br />
Zahlen. Als Eingabewert dienen alle Zahlenliterale, also beispielsweise<br />
32 (Dezimal) oder 0x20 (Hexadezimal) oder 040 (Oktal).<br />
3. Welche Kommentarform wird am besten verwendet, wenn hinter einer Codezeile<br />
ein kurzer, auf die Zeile bezogener Kommentar stehen soll? Mögliche Varianten<br />
sind: /* */, // oder #.<br />
A Es sollte immer // verwendet werden. # ist unüblich und /* scheitert, wenn<br />
umschließende mehrzeilige Kommentare benutzt werden, um den Block<br />
auszukommentieren.<br />
629
3. Tag<br />
1. Was ist ein Literal?<br />
630<br />
Antworten auf die Kontrollfragen<br />
A Jede Form von im Quellcode direkt geschriebener konstanter Werte, also<br />
beispielsweise 0x22, 345.66, "string" usw.<br />
2. Wie werden Konstanten definiert?<br />
A Konstanten werden mit der Funktion define erstellt.<br />
3. Wozu werden reguläre Ausdrücke eingesetzt?<br />
A Reguläre Ausdrücke werden zum Suchen und Ersetzen benutzt, wenn die<br />
Suchmuster komplex oder in sich variabel sind.<br />
4. Schreiben Sie ein Skript, dass anhand mehrerer Parameter ein span-Tag mit korrekter<br />
hexadezimaler Angabe der Vordergrund- und Hintergrundfarbe mit Hilfe<br />
des style-Attributes erstellt und ausgibt. Tipp: Verwenden Sie printf.<br />
A printf ist aufgrund der Parameter gut geeignet, um Tags übersichtlich zu<br />
erstellen.<br />
4. Tag<br />
Ausgegeben werden die Zahlen : 0, 30, 70 und 100.<br />
2. Können mit switch-Anweisungen auch Vergleiche mit beliebigen Booleschen<br />
Ausdrücken erfolgen?<br />
4. Tag<br />
A Ja, denn PHP behandelt die Ausdrücke erst zur Laufzeit und erledigt dann<br />
den Vergleich. Wenn man im Verzweigungskopf den Wert TRUE schreibt,<br />
müssen die Ausdrücke auch TRUE oder FALSE zurückgeben, sodass ein Test<br />
auf Gleichheit funktioniert. Wichtig ist jedoch darauf zu achten, dass<br />
immer nur ein (oder kein) Zweig die Bedingung zum Zeitpunkt der<br />
Abfrage erfüllt, sonst ist die Reaktion nicht korrekt.<br />
switch(TRUE)<br />
{<br />
case $test < 9 && $test > 4:<br />
// Reaktion<br />
break;<br />
}<br />
3. Welche Schleife wird benutzt, wenn sichergestellt werden muss, dass der Schleifenkörper<br />
mindestens einmal durchlaufen wird?<br />
A Die do-Schleife. Da der Test erst am Ende mit Hilfe der while-Bedingung<br />
erfolgt, wird der Körper garantiert einmal durchlaufen, auch wenn die<br />
Bedingung dann nicht erfüllt ist und die Schleife sofort wieder verlassen<br />
wird.<br />
4. Zu Testzwecken kann es erforderlich sein, eine Schleifenbedingung so zu formulieren,<br />
dass eine Endlosschleife entsteht. Mit welchen Anweisungen kann ein<br />
»Notausstieg« programmiert werden?<br />
A Die Bedingung für den Ausstieg wird mit if erstellt, der Ausstieg erfolgt<br />
dann mit break. switch ist nicht geeignet, weil die Zweige selbst mit break<br />
enden und ein weiteres break nicht mehr ausgeführt wird.<br />
631
5. Tag<br />
632<br />
Antworten auf die Kontrollfragen<br />
1. Worin unterscheiden sich normale von assoziativen Arrays?<br />
A Assoziative Arrays verwenden Schlüsselwerte, die meist als Zeichenketten<br />
vorliegen, zur Adressierung der Elemente, beispielsweise $arr[?name?].<br />
»Normale« Arrays haben dagegen immer Zahlen, über die Elemente<br />
erreicht werden, beispielsweise $arr[8].<br />
2. Wie kann ein Element eines Array gezielt entfernt werden?<br />
A Mit unset kann ein Element entfernt werden. PHP organisiert die Schlüsselwerte<br />
einfacher Arrays danach nicht neu. Sie müssen das selbst erledigen,<br />
wozu die Funktion array_values dient:<br />
<br />
A Die Ausgabe der <strong>In</strong>dizes lautet 0, 1, 2. Ohne array_values wäre es 0, 1, 3.<br />
3. Schreiben Sie ein Skript, dass Arrays nutzt um Namen von Mitarbeitern zu speichern.<br />
Erweitern Sie das Skript, sodass zu jedem Mitarbeiter im selben Array<br />
zusätzliche <strong>In</strong>formationen gespeichert werden. Tipp: Nutzen Sie verschachtelte<br />
assoziative Arrays dafür.<br />
A Sie können innerhalb einer array-Definition für den Wert ein weiteres<br />
Array verwenden. Als Schlüssel sind dagegen nur Zahlen oder Zeichenketten<br />
erlaubt. Beachten Sie jedoch, dass mehr als drei Ebenen nur schwer<br />
handhabbar sind und meist auf einen Designfehler beim Entwurf des<br />
Datenmodells hindeuten.<br />
6. Tag<br />
foreach ($ma as $name => $details)<br />
{<br />
echo "$name => $details[0], $details[1]";<br />
echo '';<br />
}<br />
?><br />
1. Schreiben Sie ein Skript, dass die Namen von Mitarbeitern in einer eigens dafür<br />
entwickelten Klasse speichert.<br />
6. Tag<br />
A Sie sollten auch bei derart einfachen Aufgabenstellungen grundsätzlich<br />
versuchen, die elementaren OOP-Techniken zu verwenden. Dazu gehört<br />
der Schutz der Daten in privaten Mitgliedern und der Zugriff über __get<br />
bzw. __set und die Verwendung eines sinnvollen Konstruktors.<br />
634<br />
Antworten auf die Kontrollfragen<br />
return $this->_room;<br />
}<br />
}<br />
}<br />
$ma = array(new Employee('Bernd Muster', '0172/654321', '215'),<br />
new Employee('Katja Muster', '0172/123456', '948'));<br />
foreach ($ma as $em)<br />
{<br />
echo "{$em->name} => {$em->telephon}, {$em->room}";<br />
echo '';<br />
}<br />
?><br />
2. Erklären Sie den Unterschied zwischen private, public und protected.<br />
A Mitglieder, die in einer Klasse als private gekennzeichnet sind, können<br />
nur von Methoden der Klasse selbst benutzt werden. Mitglieder, die in<br />
einer Klasse als public gekennzeichnet sind, können von Methoden oder<br />
Funktionen des gesamten Skripts benutzt werden. Mitglieder, die in einer<br />
Klasse als protected gekennzeichnet sind, können nur von Methoden der<br />
Klasse selbst und solchen einer von dieser Klasse abgeleiten Klasse benutzt<br />
werden.<br />
3. Sie haben nur eine einzige Klasse in Ihrem Skript. Ist die Anwendung des Schlüsselwortes<br />
protected sinnvoll? Begründen Sie die Antwort.<br />
A Nein, die Anwendung ist nicht sinnvoll. Sie können das so gekennzeichnete<br />
Mitglied nicht aus Ihrem Skript erreichen und für die Benutzung<br />
innerhalb der Klasse wäre private ausreichend.<br />
4. Welchen Vorteil bietet die Verwendung von __get und __set anstatt des direkten<br />
Zugriffs auf öffentliche Eigenschaften, die mit public $name gekennzeichnet<br />
sind?<br />
A Die Pseudo-Eigenschaften können den Weg der Daten in die Eigenschaft<br />
hinein und wieder heraus kontrollieren. Man kann so sicherstellen, dass<br />
niemals ungültige Daten im Objekt gespeichert sind. Man kann auch<br />
sicherstellen, dass niemals ungültige Daten das Objekt verlassen. Beides<br />
trägt zur Codesicherheit bei. Außerdem kann eine Klasse weitaus mehr<br />
Eigenschaften bereitstellen, als Mitgliedsvariablen vorhanden sind. Die<br />
interne Form der Datenspeicherung kann also anderen Optimierungskriterien<br />
unterliegen als die Darstellung der Daten nach außen.
5. Wie schreiben Sie eine Klasse, deren Objekte beim Aufruf von new einen definierten<br />
Anfangszustand unabhängig von Parametern erhalten soll?<br />
7. Tag<br />
A Sollen die Daten des Anfangszustands nicht variabel sein, bietet sich der<br />
Einsatz von Konstanten an. Im Konstruktor werden die Konstanten dann<br />
den Eigenschaften zugewiesen. Dies erleichtert die Wartung der Klasse.<br />
6. Wie schreiben Sie eine Klasse, von der nur eine <strong>In</strong>stanz erzeugt werden darf? Wie<br />
nennt man dieses Entwurfsmuster?<br />
A Das Muster wird Singleton genannt. Man deklariert den Konstruktor privat,<br />
um den wiederholten Aufruf zu verhindern. Ersatzweise schafft man<br />
eine Methode, in der mittels new eine <strong>In</strong>stanz erzeugt und zurückgegeben<br />
wird. Die Methode speichert die erzeugte <strong>In</strong>stanz in einer Variablen und<br />
prüft beim erneuten Aufruf, ob die <strong>In</strong>stanz bereits existiert. Ist das der Fall,<br />
wird die bereits existente Version zurückgegeben, die zugleich die einzige<br />
<strong>In</strong>stanz der Klasse ist.<br />
7. Tag<br />
1. Auf welche Datenquellen können die Dateifunktionen zugreifen?<br />
A Dank des universellen Stream-Konzeptes können Dateifunktionen aus<br />
beliebigen Datenquellen zurückgreifen. Bereits fertig implementiert ist der<br />
Zugriff auf Dateien des Dateisystems, Ein-/Ausgabekanäle des Betriebssystems,<br />
Webserver per HTTP und FTP-Server. Für andere Datenquellen lassen<br />
sich benutzerdefinierte Wrapper erstellen.<br />
2. Warum muss eine Datei nach der Benutzung wieder geschlossen werden?<br />
A Der schreibende Zugriff auf Dateien ist exklusiv, damit sich die Änderungen<br />
nicht unbemerkt überschreiben. Um künftige Zugriffe nicht zu verzögern,<br />
sollten Dateien schnell wieder geschlossen werden. Bedenken Sie,<br />
dass Webserver Multiuser-Zugriffe gestatten, ein Skript also gleichzeitig<br />
mehrfach starten kann.<br />
635
636<br />
Antworten auf die Kontrollfragen<br />
3. Schreiben Sie ein Skript, dass eine beliebige Datei aus dem aktuellen Verzeichnis<br />
im Quelltext anzeigt, wobei der Benutzer die Datei selbst wählen kann. Tipp:<br />
Benutzen Sie HTML-Links und ermitteln<br />
Sie den übergebenen Namen mittels $_GET[’name’].<br />
A Zuerst benötigt man eine Dateiliste. Hier ist das Verzeichnisobjekt dir<br />
oder der DirectoryIterator der SPL eine gute Wahl.<br />
4. Warum ist das in der letzten Übung verlangte Prinzip auf einer öffentlichen<br />
Website nicht unmodifiziert einsetzbar? Tipp: Denken Sie an mögliche Sicherheitsprobleme.<br />
A Der GET-Parameter lässt sich im Browser leicht manipulieren. Da die<br />
meisten Dateifunktionen auch Pfade mit relativen Angaben wie ../../../ verarbeiten<br />
können, lassen sich so leicht Dateien auslesen, die nicht zum<br />
freien Zugriff gedacht sind. Auch wenn das Betriebssystem meist verhindert,<br />
dass Systemdateien gelesen werden können, reicht oft die Auswahl<br />
einer <strong>In</strong>clude-Datei oder anderer PHP-Skripte, um an Kennwörter oder<br />
Systeminformationen zu gelangen.<br />
Zur Lösung arbeitet man mit Hashwerten oder ID-Nummern, sodass im<br />
Link nur Werte stehen, die keinen Rückschluss auf die Datei zulassen.<br />
Den Dateinamen einfach als MD5-Hash abzulegen ist freilich keine<br />
Lösung, denn Profis erkennen derartige Hashes sehr schnell und können<br />
sie ebenso schnell auch erzeugen.<br />
8. Tag<br />
1. Warum sollten unbedingt immer »Sticky Forms« verwendet werden?<br />
A Es ist ausgesprochen lästig für den Benutzer, wenn er nach einem Fehler<br />
die mühevoll eingegebenen Daten verliert. Formulare sind ohnehin die<br />
Stelle mit der höchsten Abbruchquote. Schlecht gemachte Formulare steigern<br />
den Misserfolg der Site enorm.<br />
<strong>In</strong>zwischen gilt der Verzicht auf »Sticky Forms« auch einfach nur als<br />
unprofessionell.
2. Welche Funktionen unterstützen das Hochladen von Dateien? Was ist beim Aufbau<br />
des Formulars zu beachten?<br />
9. Tag<br />
A Die wichtigste Funktion ist move_uploaded_file, eingesetzt zum Transport<br />
der Dateien zum endgültigen Ziel. Beim Formular ist zu beachten, dass<br />
das Attribut enctype im Form-Tag angegeben wird, damit der Browser die<br />
Daten korrekt verpackt.<br />
3. Welche Methoden verwenden Sessions, um die Session-Daten zu speichern?<br />
A Zum Speichern werden Dateien oder Datenbanken benutzt, seltener der<br />
Systemspeichern. PHP5 unterstützt standardmäßig Dateien. Beachten Sie,<br />
dass die Sessiondaten, die im Sessioncookie gespeichert werden, lediglich<br />
eine Referenznummer auf die im Webserver oder einer Datenbank gespeicherten<br />
Daten enthalten.<br />
4. Wie können Cookies missbraucht werden?<br />
A Cookies können mit jeder Anforderung gesendet werden. Da die Objekte<br />
einer Website nicht vom selben Server stammen müssen, können beim<br />
Laden einer Website auch Cookies verschiedener Server übermittelt werden.<br />
Besucht derselbe Benutzer eine andere Website, wobei ein Objekt<br />
von demselben Server stammt wie beim Besuch der vorhergehenden Site,<br />
erhält der Server das Cookie von der zweiten Site wieder zurück. Damit<br />
kann man Bewegungsprofile erstellen, die den Weg des Benutzers durchs<br />
Web markieren. Firmen wie Doubleclick nutzen dies als Geschäftsmodell,<br />
um kundenspezifische Werbebanner zu schalten.<br />
9. Tag<br />
1. Welche Servervariable wird benutzt, um die bevorzugte Sprache des Benutzers zu<br />
ermitteln?<br />
A HTTP_ACCEPT_LANGUAGE kann verwendet werden. Zur Abfrage<br />
schreiben Sie:<br />
$lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];<br />
637
638<br />
Antworten auf die Kontrollfragen<br />
2. Welcher Header wird in HTTP benötigt, um dem Browser anzuzeigen, dass ein<br />
Bild gesendet wird?<br />
A Benötigt wird Content-type:. Als Parameter ist image/ anzugeben,<br />
wobei der Typ dem Bildformat entspricht:<br />
Content-type: image/gif<br />
3. Schreiben Sie ein Skript, dass dezimale Farbangaben wie 255, 192, 128 in hexadezimale<br />
Zeichenfolgen der Art »#FFCC99« umrechnet und umgekehrt.<br />
A Nutzen Sie die Funktion printf dafür. Folgende Zeile muss nur noch in<br />
eine Funktion eingebaut werden:<br />
printf('#%2X%2X%2X', 255, 192, 128);<br />
4. Wozu dient das Werkzeug Reflection?<br />
A Mittels Reflection kann Code analysiert werden.<br />
10. Tag<br />
1. Welche Aufgabe erfüllen Wrapper?<br />
A Wrapper dienen dazu, benutzerspezifische Ein-/Ausgabekanäle zu schaffen,<br />
sodass beispielsweise die normalen Dateifunktionen darauf zugreifen<br />
können.<br />
2. Was muss ein fremder Entwickler beachten, wenn er einen von Ihnen entwickeltes<br />
Filter verwendet?<br />
A Nichts – Filter sind für die Benutzung transparent.<br />
3. Was bedeuten die Kopfzeilen Cc: und Bcc: in einer E-Mail?<br />
A Cc: (Carbon Copy, Durchschlag) sendet eine Kopie der E-Mail an den<br />
angegebenen Empfänger, wobei alle anderen Empfänger dessen Adresse<br />
sehen. Bcc: dagegen sendet ebenso eine Kopie, andere Empfänger können<br />
dies aber nicht sehen (Blind Carbon Copy, Blinddurchschlag).<br />
4. Welches Protokoll nutzt die mail-Funktion zur Übertragung der Daten?<br />
A Diese Funktion überträgt die Daten mittels SMTP zum Server.
11. Tag<br />
11. Tag<br />
1. Warum sollte die neue Bibliothek <strong>My</strong><strong>SQL</strong>i anstatt der alten eingesetzt werden?<br />
A <strong>My</strong><strong>SQL</strong>i ist auf die Datenbank <strong>My</strong><strong>SQL</strong> 4 abgestimmt. Außerdem unterstützt<br />
das neue Modul die objektorientierte Programmierung.<br />
2. Was versteht man unter Normalisierung?<br />
A Stark vereinfacht die Vermeidung redundanter Datenhaltung in einer relationalen<br />
Datenbank.<br />
3. Sie müssen bei der Ausgabe von Daten aus einer <strong>My</strong><strong>SQL</strong>-Datenbank Datumsformate<br />
anpassen und \n in umwandeln. Wie gehen Sie vor?<br />
A <strong>My</strong><strong>SQL</strong> verfügt über einen reichen Vorrat an Zeichenkettenfunktionen.<br />
Suchen Sie eine Funktion zum Suchen und Ersetzen, die dies erledigt.<br />
Dies ist meist schneller als nl2br im PHP-Skript.<br />
4. Wie würden Sie eine Abfrage formulieren, bei der aus zwei Tabellen Daten<br />
zugleich entnommen werden müssen? Welche Voraussetzungen müssen die<br />
Tabellen erfüllen.<br />
A Dies ist ein so genannter JOIN:<br />
12. Tag<br />
SELECT t1.feldname, t2.feldname<br />
FROM tabelle1 t1 INNER JOIN tabelle2 t2 ON t1.id = t2.id<br />
Die Schreibweise tabelle1 t1 definiert einen Aliasnamen für tabelle1.<br />
Damit lassen sich gleichnamige Felder auseinander halten, ohne die möglicherweise<br />
langen Tabellennamen schreiben zu müssen. t1.id = t2.id verknüpft<br />
die beiden Tabellen miteinander, indem die Primärschlüssel<br />
verglichen werden. t1.feldname, t2.feldname ruft dann aus jeder Tabelle<br />
eine bestimmte Spalte ab.<br />
1. Was müssen Sie tun, um <strong>SQL</strong>ite benutzt zu können?<br />
A Nichts, <strong>SQL</strong>ite gehört zum Standardumfang von PHP5.<br />
639
640<br />
Antworten auf die Kontrollfragen<br />
2. Welche Datentypen kennt <strong>SQL</strong>ite?<br />
A Zahlen und Zeichen, alle anderen Datentypnamen sind nur aus Kompatibilitätsgründen<br />
im Umfang reservierter Namen. <strong>SQL</strong>ite prüft sonst keine<br />
Daten sondern behandelt diese immer als Zeichenketten.<br />
3. Sie möchten mehrere Tausend Datensätze für eine hoch frequentierte Website<br />
verwalten. Ist <strong>SQL</strong>ite dafür die beste Wahl? Begründen Sie die Antwort?<br />
A <strong>SQL</strong>ite ist dafür nicht geeignet, weil die Verwaltung der Datenbank auf<br />
Dateiebene stattfindet. Während des Zugriffs auf eine Tabelle ist die Datei<br />
gesperrt, was konkurrierende Zugriffe unmöglich macht. Bei hoher Last<br />
wird SQlite deshalb sehr langsam.<br />
4. Welche Aufgabe hat der Befehl VACUUM?<br />
A Der Befehl verdichtet die Datenbank nach Löschvorgängen. Die separate<br />
Optimierung verhindert, dass einfache Löschvorgänge zu lange dauern.<br />
13. Tag<br />
1. Wozu werden Template-Systeme eingesetzt?<br />
A Template-Systeme dienen dazu, Code und Layout zu trennen.<br />
2. Erweitern Sie die Applikation, sodass zu jedem Leser der Bibliothek mehrere Telefonnummern<br />
gespeichert werden können. Wie gehen Sie vor?<br />
A Legen Sie eine weitere Tabelle in der Datenbank an. Erweitern Sie die<br />
Tabelle der Leser um einen ID-Spalte. Verknüpfen Sie die Spalte mit der<br />
neuen Telefon-Tabelle und einer dort befindlichen Korrespondenz-Spalte.<br />
Sie benötigen also mindestens drei Spalten in der Telefon-Tabelle: id (Primärschlüssel),<br />
telephon-id (Verknüpfung, gleiche Werte können freilich<br />
mehrfach auftreten, wenn ein Kunde mehrere Telefonnummern hat), telephon<br />
(die eigentlichen Daten).<br />
3. Formulieren Sie eine <strong>SQL</strong>-Abfrage, die die nötigen Daten zur Erstellung von<br />
Mahnschreiben erzeugt, um säumige Leser auf die bereits abgelaufene Rückgabefrist<br />
hinzuweisen.<br />
A Der folgende Ausdruck (Teil der Abfrage) wird TRUE, wenn die Ausleihfrist<br />
abgelaufen ist:<br />
TO_DAYS(CURDATE())-TO_DAYS(lendingdate) > lendingperiod
<strong>14</strong>. Tag<br />
1. Welche Technologien sind wichtig, wenn mit XML gearbeitet wird?<br />
<strong>14</strong>. Tag<br />
A Neben XML benötigen Sie eine Definitionssprache, also DTD oder XML-<br />
Schema. Außerdem zur Verarbeitung XSLT und XPath.<br />
2. Welche Methode der Verarbeitung ist bei einem ca. 12 MByte großen XML-<br />
Dokument wahrscheinlich die beste, SAX oder DOM? Begründen Sie die Antwort.<br />
A SAX ist besser. Die sequenzielle ereignisbasierte Verarbeitung ist optimal<br />
bei großen Datenmengen. DOM würde das Dokument als Ganzes versuchen<br />
im Speicher zu halten und dabei sehr viel Speicherplatz benötigen,<br />
sodass der Zugriff sehr langsam wird.<br />
3. Nennen Sie typische Erscheinungsformen eines so genannten Knotens in einem<br />
XML-Dokument.<br />
A Neben Elementen sind auch Kommentare, Attribute, CDATA-Abschnitte<br />
und Prozessanweisungen Teil eines XML-Dokuments. Da das gesamte<br />
Dokument als Baum dargestellt werden kann, dessen Verzweigungen Knoten<br />
sind, können Knoten praktisch jeden Teil des Dokuments darstellen.<br />
4. Welche Bedeutung hat die DTD (Document Type Definition)?<br />
A Die DTD definiert die in einem XML-Dokument zulässigen Elemente<br />
(deren Namen) und deren Attribute sowie die Abhängigkeiten zwischen<br />
diesen. Dazu gehört beispielsweise, welche Elemente in welcher Zahl Kindelemente<br />
anderer sein dürfen. Ohne DTD kann man lediglich prüfen, ob<br />
ein XML-Dokument wohlgeformt ist. Mit DTD kann man prüfen, ob es<br />
außerdem gültig ist. Jedes XML-Dokument sollte eine DTD haben oder<br />
alternativ das neuere XML-Schema, das einem vergleichbaren Zweck<br />
dient.<br />
641
Stichwortverzeichnis<br />
Symbols<br />
$_COOKIE 351<br />
$_REQUEST 355<br />
-> 208<br />
__autoload 172<br />
__call 227<br />
__clone 218<br />
__construct 210<br />
__destruct 211<br />
__FILE__ 233<br />
__get 229<br />
__LINE__ 233<br />
__METHOD__ 233<br />
__set 229<br />
__toString 226, 234<br />
_subclass_of 237<br />
A<br />
Abfragesyntax 446<br />
Abstammung 235<br />
abstract 217<br />
Achsenbezeichner 564<br />
Addition 79<br />
Ähnlichkeiten 88<br />
aggregierende Funktionen (<strong>SQL</strong>) 454<br />
Algebra, logische 81<br />
ALL (<strong>SQL</strong>) 454<br />
alternative Zweige 136<br />
Anführungszeichen 85<br />
Anhänge versenden 434<br />
Apache<br />
– installieren 30<br />
– testen 35<br />
ArGoSoft Mail Server 429<br />
Array 182<br />
– Assoziative 191<br />
– erstellen 183<br />
– Funktionen 198<br />
– Werte 186<br />
– Zeiger 188<br />
array_walk 196<br />
Arraydaten 193<br />
ASC (<strong>SQL</strong>) 456<br />
ASCII-Zeichensatz 91<br />
Assoziativität 133<br />
attributes (SimpleXML) 586<br />
Attributknoten 563<br />
Ausdrücke 78<br />
Ausgaben 55<br />
– formatieren 59<br />
AUTO_INCREMENT (<strong>SQL</strong>) 451<br />
AVG (<strong>SQL</strong>) 455<br />
B<br />
Backslash 84<br />
Bad Request 42<br />
Base64 428<br />
basename 254<br />
Benennungsregeln 65<br />
Benutzerverwaltung (mit <strong>SQL</strong>ite) 501<br />
BETWEEN (<strong>SQL</strong>) 448<br />
Bilder 49<br />
– Funktionen 385<br />
– Zeugung 381<br />
BinHex 428<br />
Boolesches 81<br />
break 137, <strong>14</strong>1<br />
Browserdaten erkennen 374<br />
C<br />
case 137<br />
catch 178<br />
childNodes 579<br />
children (SimpleXML) 587<br />
chunk_split 94<br />
class 207<br />
643
Stichwortverzeichnis<br />
clone 218<br />
Coding Standards 65<br />
continue <strong>14</strong>1<br />
Cookies 349<br />
– Beschränkungen 352<br />
– Personalisierung 352<br />
copy 335<br />
count 184<br />
COUNT (<strong>SQL</strong>) 454<br />
CREATE TABLE (<strong>SQL</strong>) 449<br />
current 188<br />
current (Iterator) 265, 265<br />
D<br />
D. Richard Hipp 498<br />
date 111<br />
Dateien hochladen 334<br />
Dateisystem 244<br />
Dateizugriff 246<br />
Datenspeichermethoden 358<br />
Datentypen 74, 83<br />
Datentypen (<strong>SQL</strong>) 449<br />
Datum 110<br />
default 138<br />
DEFAULT (<strong>SQL</strong>) 451<br />
define 76<br />
defined 77<br />
Dekrement 79<br />
DELETE (<strong>SQL</strong>) 453<br />
DESC (<strong>SQL</strong>) 456<br />
Destruktoren 210<br />
dir 260<br />
DirectoryIterator 266<br />
DISTINCT (<strong>SQL</strong>) 454<br />
Division 79<br />
DNS 70<br />
do <strong>14</strong>8<br />
DOM 576<br />
– domAttr (Klasse) 624<br />
– Funktionen 576<br />
– Konstanten 625<br />
Domain Name System siehe DNS<br />
DomAttr 624<br />
DomDocument 579<br />
– getAttributeNode 624<br />
644<br />
– getElementsById 623<br />
– getElementsByTagName 623<br />
DomXPath 566<br />
DROP (<strong>SQL</strong>) 453<br />
Dropdown-Listen 295<br />
DTD 553<br />
dynamisch Bilder erzeugen 379<br />
dynamischer Werbebanner 386<br />
E<br />
each 195<br />
echo 55<br />
Eigenschaftenaufruf 229<br />
Elementknoten 563<br />
else 136<br />
Elternknoten 563<br />
E-Mail 426<br />
– versenden 430<br />
empty 287<br />
Entitäten 93<br />
Ersetzen 87<br />
Exception 224<br />
exklusives Oder 83<br />
Extensible Markup Language siehe XML<br />
F<br />
Factory-Klassen 235<br />
Farbrad <strong>14</strong>6<br />
fclose 251<br />
Fehler (PHP) 42<br />
– Behandlung 173<br />
– Codes 177<br />
– Klassen erstellen 224<br />
– Nummer 176<br />
– Text 176<br />
fgets 251<br />
file_get_contents 420<br />
Filter 422, 440<br />
final 216<br />
fnmatch 260<br />
fopen 251<br />
for <strong>14</strong>2<br />
foreach 194<br />
Formatieren 119<br />
– Zahlen 119
Formatregel 60<br />
Formulare 52<br />
– bauen 302<br />
fscanf 120<br />
func_get_arc 156<br />
func_get_args 156<br />
func_num_args 156<br />
function 150<br />
Funktionen 150<br />
– Referenzen 159<br />
– Rekursive 164<br />
– Variable 166<br />
Funktionsdefinition 150<br />
G<br />
GD2 379<br />
Geschichte 22<br />
GET 341<br />
get_included_files 172<br />
get_meta_tags 419<br />
get_required_files 172<br />
getElementsByTagName 579<br />
getMessage 225<br />
gleich 82<br />
glob 257<br />
global 162<br />
globale Variablen 162<br />
Grafikbibliothek 379<br />
Grafiken 379<br />
Größe (Datentyp) 83<br />
größer als 82<br />
Große Textmengen 57<br />
GROUP BY (<strong>SQL</strong>) 454<br />
Gruppierungen 98<br />
Gruppierungen (<strong>SQL</strong>) 455<br />
Gutmans, Andi 22<br />
H<br />
Handle 244<br />
Hashes 186<br />
HAVING (<strong>SQL</strong>) 454<br />
header 380<br />
heredoc 57<br />
Herkunft erkennen 282<br />
Hinweistexte platzieren 303<br />
hochladen 334<br />
HTML 46<br />
– Formularelemente 278<br />
– Mails 434<br />
htmlentities 93<br />
HTTP 71<br />
HTTP_REFERER 282<br />
httpd.conf 40<br />
I<br />
iconv-Modul 340<br />
idate 116<br />
if 132<br />
imagecreate 381<br />
imagecreatefromgif 382<br />
imagecreatefromjpeg 382<br />
imagecreatefrompng 382<br />
imagegif 382<br />
imagettfbbox 382<br />
imagettftext 383<br />
IMAP4 427<br />
implements 221<br />
IN (<strong>SQL</strong>) 448<br />
include 169<br />
include_once 169<br />
<strong>In</strong>krement 79<br />
<strong>In</strong>ner Join 458<br />
INSERT INTO (<strong>SQL</strong>) 451<br />
instanceof 236<br />
<strong>In</strong>terfaces 221<br />
is_a 237<br />
is_resource 251<br />
ISO-8859-1 91<br />
J<br />
JavaScript 47, 303<br />
– lokale Zeit 117<br />
JOIN (<strong>SQL</strong>) 459<br />
K<br />
key 188<br />
Kindknoten 563<br />
Klasse 207<br />
kleiner als 82<br />
Stichwortverzeichnis<br />
645
Stichwortverzeichnis<br />
Kombinationsoperatoren 81<br />
Kommentare 62<br />
Konfigurationsschritte 38<br />
Konstanten 76<br />
Konstruktoren 210<br />
Kontext 440<br />
Kontrollkästchen 287<br />
Krause, Jörg 17<br />
L<br />
Laufzeitfehler 175<br />
LC_TIME 116<br />
LEFT JOIN (<strong>SQL</strong>) 459<br />
libxml2 575<br />
LIKE (<strong>SQL</strong>) 448<br />
links-assoziativ 134<br />
list 195<br />
Literale 74<br />
loadHtmlFile 579<br />
localeconv 378<br />
Lokalisierung 376<br />
M<br />
Mail 426<br />
MAX (<strong>SQL</strong>) 455<br />
MAX_FILE_SIZE 337<br />
MD5 347<br />
Mehrfachauswahl 297, 322<br />
Mehrfachverzweigungen 137<br />
mehrseitige Formulare 325<br />
mehrsprachige Webseiten 374<br />
Message Digest Version 5 347<br />
MIME 428<br />
MIN (<strong>SQL</strong>) 455<br />
mktime 111<br />
Modularisierung 168<br />
Module einbinden 168<br />
Modulus 79<br />
Moniker 416<br />
move_uploaded_files 335<br />
MSI <strong>In</strong>staller 30<br />
multipart/form-data 334<br />
Multiplikation 79<br />
Multipurpose <strong>In</strong>ternet Mail Extensions<br />
siehe MIME<br />
646<br />
<strong>My</strong><strong>SQL</strong><br />
– Aggregat-Funktionen 467<br />
– Beispielprojekt 510<br />
– Control Center 513<br />
– Datentypen 462<br />
– Datumsformatierungen 475<br />
– Datumsfunktionen 473<br />
– Dialekt 461<br />
– Funktionen 464<br />
– Kontrollflussfunktionen 466<br />
– Mathematische Funktionen 465<br />
– Systemfunktionen 468<br />
– Verschlüsselungsfunktionen 469<br />
– Zahlenformatierungen 473<br />
– Zeichenkettenfunktionen 470<br />
– Zeitfunktionen 473<br />
<strong>My</strong><strong>SQL</strong>i 477<br />
– Einfache Abfragen 485<br />
– Komplexe Abfragen 487<br />
– Referenz 491<br />
– Testen 478<br />
– Verknüpfungen abfragen 489<br />
N<br />
Namenskonventionen 68<br />
new 208, 471, 471<br />
next 188<br />
next (Iterator) 265<br />
Nicht 82<br />
nl2br 94, 285<br />
nodeType 580<br />
Normalisierungen 457<br />
NOT NULL (<strong>SQL</strong>) 451<br />
NULL (<strong>SQL</strong>) 451<br />
number_format 119<br />
O<br />
Objekt erzeugen 208<br />
Objektorientierte Programmierung 204<br />
oder 82<br />
Operatoren 78<br />
Optionsfelder 288<br />
ORDER BY (<strong>SQL</strong>) 454, 456
P<br />
Paradigmen 205<br />
Parameter 153<br />
– Anzahl, beliebige 155<br />
– optionale 154<br />
– Übergabe 159<br />
parent 211<br />
parse_url 343<br />
PEAR 65<br />
PECL-Module 29<br />
Personal Home Page (Tools)/Forms<br />
<strong>In</strong>terpreter 22<br />
PHP<br />
– Blöcke 27<br />
– einbetten 53<br />
– Skripte ausführen 41<br />
PHP5 23<br />
– installieren 37<br />
– konfigurieren 37<br />
PHP Hypertext Preprocessor 22<br />
php.ini 38<br />
PHP_OS 116<br />
PHP_SELF 254<br />
PHP4 23<br />
phpTemple 516, 575<br />
Platzhalter 96<br />
POP3 427<br />
preg_match 100<br />
preg_match_all 100<br />
preg_replace 100<br />
preg_replace_callback 100<br />
prev 188<br />
Primärschlüssel 460<br />
PRIMARY KEY (<strong>SQL</strong>) 451<br />
print 58<br />
print_r 196<br />
printf 60, 120<br />
– Formatstruktur 121<br />
Prinzip des Seitenabrufs 70<br />
private 215<br />
professionelle Programmierung 62, 373<br />
protected 216<br />
Protokolldatei 255<br />
public 215<br />
PUT 334<br />
Q<br />
Query Builder 446<br />
Quoted Printable 428<br />
R<br />
Rangfolge 133<br />
Rasmus Lerdorf 22<br />
RDF 569<br />
Rechnen 79<br />
rechts-assoziativ 135<br />
Reflection::export 395<br />
Reflection-API 395<br />
ReflectionClass 399<br />
ReflectionExtension 405<br />
ReflectionFunction 397<br />
Reflection-<strong>In</strong>formationen 395<br />
Reflection-Klassen 397<br />
ReflectionMethod 402<br />
ReflectionProperty 403<br />
Regex-Maschinen 103<br />
registerXPathNamespace 592<br />
reguläre Ausdrücke 95<br />
rekursive Dateiliste 263<br />
rekursive Funktionen 164<br />
REQUEST_METHOD 281<br />
require 169<br />
require_once 169<br />
reset 188<br />
Ressource 244<br />
rewind (Iterator) 265<br />
RIGHT JOIN (<strong>SQL</strong>) 459<br />
Root-Server 70<br />
RSS 568<br />
Rückgabewerte 151<br />
S<br />
Schema 553<br />
Schleifen <strong>14</strong>1<br />
Schnittstellen 221<br />
Seitenmanagement 277<br />
SELECT (<strong>SQL</strong>) 446<br />
serialize 356<br />
Stichwortverzeichnis<br />
647
Stichwortverzeichnis<br />
Servervariablen 365<br />
Serverzeit 110<br />
Sessions<br />
– Cookies 350<br />
– Verwaltung 358<br />
set_error_handler 175<br />
setcookie 352<br />
setlocale 115, 376<br />
setrawcookie 352<br />
Sicherheitsprobleme 346<br />
SimpleXML 581<br />
– Referenz 620<br />
simplexml_import_dom 587<br />
simplexml_load_file 585, 620<br />
simplexml_load_string 620<br />
Singleton 213<br />
Smarty 516<br />
SMTP 427<br />
SOAP 593, 595<br />
– Body 595<br />
– Envelope 595<br />
– Header 595<br />
– Projekt 608<br />
Socket-Verbindung 441<br />
Sonderzeichen 84f.<br />
sort 191<br />
Sortieren (<strong>SQL</strong>) 456<br />
Sortierfunktion 191<br />
SPAM-Versender 429<br />
Sprache 374<br />
sprintf 120<br />
<strong>SQL</strong> 445<br />
– Aggregate Funktionen 467<br />
<strong>SQL</strong>92 461<br />
<strong>SQL</strong>ite 498<br />
– Beispiel 500<br />
– Referenz 506<br />
sqlite_close 505<br />
sqlite_error_string 504<br />
sqlite_fetch_array 504<br />
sqlite_last_error 504<br />
sqlite_num_rows 504<br />
sqlite_query 504<br />
sscanf 120<br />
648<br />
Standard-Wrapper 247<br />
static 213<br />
statische Mitglieder 212<br />
statische Variablen 160<br />
Sticky Forms 301, 315<br />
str_ireplace 89<br />
str_replace 89<br />
strcmp 89<br />
stream_context_create 253<br />
Streams 416<br />
– Funktionen 425, 440<br />
strftime 111<br />
strip_tags 422<br />
stripos 88<br />
strlen 90<br />
strnatcasecmp 88<br />
strncasecmp 88<br />
strpos 87<br />
strrpos 88<br />
Structured Query Language siehe <strong>SQL</strong><br />
Sub-Select 460<br />
substr 92<br />
Subtraktion 79<br />
Suchen 87<br />
Suchmuster 103<br />
SUM (<strong>SQL</strong>) 455<br />
Suraski, Zeev 22<br />
switch 137<br />
T<br />
Tabellen 49, 445<br />
Tabulator 85<br />
Teilausdrücke 79<br />
Teilzeichenketten 90<br />
Template-System 516<br />
Textfelder 285<br />
Textknoten 563<br />
throw 178<br />
Timestamp 110<br />
trigger_error 175<br />
trinärer Operator 82<br />
TRUNCATE (<strong>SQL</strong>) 453<br />
try 178<br />
Typ-<strong>In</strong>formationen 223
U<br />
Umgebungsvariablen 365<br />
und 82<br />
ungleich 82<br />
UNIQUE (<strong>SQL</strong>) 451<br />
Unix-Timestamp 110<br />
unserialize 357<br />
unset 187<br />
Unterabfragen 460<br />
UPDATE (<strong>SQL</strong>) 452<br />
url_decode 343<br />
url_encode 343<br />
UserLand 569<br />
UTC 110<br />
utf8_decode 580<br />
UUEncode 427<br />
V<br />
valid (Iterator) 265<br />
variable Funktionen 166<br />
Variablen 74<br />
– Erkennung 84<br />
– globale 162<br />
– statische 160<br />
Vererbung 211<br />
Vergleiche 81<br />
verketten 80<br />
Verknüpfungen (<strong>SQL</strong>) 457<br />
Verzeichnisobjekt dir 260<br />
Verzeichniszugriff 257<br />
Verzweigungen 132<br />
Vorauswahl 299<br />
Vorbereitung 24<br />
Vorzeichen 79<br />
vprintf 120<br />
vsprintf 120<br />
W<br />
Wagenrücklauf 85<br />
WAMP 29<br />
WebDAV 334<br />
Webserver bauen 29<br />
Webservices 593<br />
– konsumieren 607<br />
WHERE (<strong>SQL</strong>) 447<br />
while <strong>14</strong>8<br />
wordwrap 93<br />
Wrapper 416<br />
– eigene 425<br />
Wrapper-Zugriff 246<br />
WSDL 597<br />
wsdl_cache_dir 608<br />
wsdl_cache_enabled 608<br />
wsdl_cache_ttl 608<br />
Wurzelknoten 563<br />
Stichwortverzeichnis<br />
X<br />
XML 550<br />
– Definition 552<br />
– Namensräume (SimpleXML) 588<br />
– Path Language 1.0. 562<br />
– Pointer Language 552<br />
XPath 562<br />
– Achsenbezeichner 564<br />
– ancestor 564<br />
– Ausdrücke 563<br />
– Ausdrücke in PHP5 566<br />
– child 564<br />
– descendant 564<br />
– following 565<br />
– parent 564<br />
– preceding 565<br />
– self 564<br />
xpath (SimpleXML) 588<br />
XPath-Knoten 563<br />
XQuery 553<br />
xsl:apply-template 556<br />
xsl:call-template 559<br />
xsl:copy-of 559<br />
xsl:for-each 558<br />
xsl:if 557<br />
xsl:include 560<br />
xsl:number 559<br />
xsl:output 560<br />
xsl:sort 558<br />
xsl:template 555<br />
xsl:text 555<br />
xsl:value-of 556<br />
649
Stichwortverzeichnis<br />
XSLT 553<br />
– Basisregeln 555<br />
– Funktionen 560<br />
Z<br />
Zeichen 90<br />
Zeichencodes 91<br />
Zeichenfolgen verketten 80<br />
Zeichenketten 83<br />
– erkennen 85<br />
– Funktionen 86<br />
650<br />
– Operationen 86<br />
Zeichenklassen 96<br />
Zeilenvorschub 85<br />
Zeit 110<br />
Zend-Engine 23<br />
Zielgruppe 16<br />
Zugriffskontrolle 215<br />
Zugriffsmethoden 227