30.06.2013 Aufrufe

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

MEHR ANZEIGEN
WENIGER ANZEIGEN

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 &#8364;. Dieses<br />

Zeichen entspricht der Entität &euro; 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 &#128; 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 »&auml;«<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 "&nbsp;&nbsp;&nbsp;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&nbsp;...";<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) . "&nbsp;...";<br />

Es wäre übrigens ein echter Programmierfehler, folgendes zu schreiben, wenn die<br />

Iterationsanweisung im Schleifenkopf weiter bestehen bleibt:<br />

echo "" . ($i++) . "&nbsp;...";<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&nbsp;...";<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&nbsp;...", $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&nbsp;..."<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 .= '&nbsp;';<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 ('&nbsp;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 ('&nbsp;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&ouml;&szlig;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&ouml;&szlig;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 "&uarr; {$rs->Hoch}°C, &darr; {$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 &uarr; und &darr;<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 "&uarr; {$rs->MittelHoch}°C,";<br />

echo "&darr; {$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 />

&nbsp;<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&uuml;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&uuml;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&uuml;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 />

&Auml;ndern<br />

<br />

<br />

<br />

<br />

Kundendaten<br />

<br />

<br />

Daten:<br />

<br />

<br />

,<br />

<br />

<br />

<br />

<br />

<br />

PLZ&nbsp;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&nbsp;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&uuml;cherverwaltung<br />

<strong>In</strong>formationen<br />

<br />

B&uuml;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 />

&Auml;ndern&nbsp;/&nbsp;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&uuml;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&uuml;cknahme<br />

Ausleih- oder R&uuml;cknahmeinformationen:<br />

<br />

Kundenname:&nbsp;{$reader_name}<br />

Kundennummer: {$reader_id}<br />

Buchtitel: {$book_title}<br />

ISBN: {$book_isbn}<br />

Es sind noch {$available} B&uuml;cher verf&uuml;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 />

&Uuml;bersicht aller Vorg&auml;nge des Kunden<br />

<br />

Buch ID<br />

Ausleihdatum<br />

F&auml;lligkeit<br />

Aktion<br />

<br />

<br />

<br />

{$history:books_id}<br />

{$history:lendingdate}<br />

{$history:due}<br />

<br />

R&uuml;ckgabe<br />

<br />

<br />

<br />

<br />

<br />

Fehler:&nbsp;{$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&uuml;cherverwaltung<br />

<strong>In</strong>formationen<br />

<br />

B&uuml;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&ouml;schen<br />

<br />

<br />

<br />

<br />

<br />

Neues Buch erfassen<br />

&Auml;ndern&nbsp;/&nbsp;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:&nbsp;{$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:&nbsp;{$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 &amp;.<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

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!