649

Click here to load reader

382726314 X Php5 In 14 Tagen (Ddt)

Embed Size (px)

Citation preview

Page 1: 382726314 X Php5 In 14 Tagen (Ddt)

PHP5 in 14 Tagen

Page 2: 382726314 X Php5 In 14 Tagen (Ddt)

Unser Online-Tippfür noch mehr Wissen ...

... aktuelles Fachwissen rund um die Uhr — zum Probelesen,

Downloaden oder auch auf Papier.

www.InformIT.de

Page 3: 382726314 X Php5 In 14 Tagen (Ddt)

PHPHPHPHPPPP5555J Ö R G K R A U S E

Markt+Technik Verlag
© Copyright-Hinweis
Copyright Daten, Texte, Design und Grafiken dieses eBooks, sowie die eventuell angebotenen eBook-Zusatzdaten sind urheberrechtlich geschützt. Dieses eBook stellen wir lediglich als persönliche Einzelplatz-Lizenz zur Verfügung! Jede andere Verwendung dieses eBooks oder zugehöriger Materialien und Informationen, einschliesslich der Reproduktion, der Weitergabe, des Weitervertriebs, der Platzierung im Internet, in Intranets, in Extranets, der Veränderung, des Weiterverkaufs und der Veröffentlichung bedarf der schriftlichen Genehmigung des Verlags. Bei Fragen zu diesem Thema wenden Sie sich bitte an: [email protected] Zusatzdaten Möglicherweise liegt dem gedruckten Buch eine CD-ROM mit Zusatzdaten bei. Die Zurverfügungstellung dieser Daten auf unseren Websites ist eine freiwillige Leistung des Verlags. Der Rechtsweg ist ausgeschlossen.
Page 4: 382726314 X Php5 In 14 Tagen (Ddt)

Bibliografische Information Der Deutschen Bibliothek

Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über <http://dnb.ddb.de> abrufbar.

Die Informationen in diesem Produkt werden ohne Rücksicht auf eineneventuellen Patentschutz veröffentlicht.Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt.Bei der Zusammenstellung von Texten und Abbildungen wurde mit größter Sorgfalt vorgegangen.Trotzdem können Fehler nicht vollständig ausgeschlossen werden.Verlag, Herausgeber und Autoren können für fehlerhafte Angabenund deren Folgen weder eine juristische Verantwortung nochirgendeine Haftung übernehmen.Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag undHerausgeber dankbar.

Alle Rechte vorbehalten, auch die der fotomechanischenWiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigtenModelle und Arbeiten ist nicht zulässig.

Fast alle Hardware- und Software-Bezeichnungen, die in diesem Bucherwähnt werden, sind gleichzeitig auch eingetragene Markenoder sollten als solche betrachtet werden.

Umwelthinweis:Dieses Buch wurde auf chlorfrei gebleichtem Papier gedruckt.

10 9 8 7 6 5 4 3 2 1

06 05 04

ISBN 3-8272-6314-X

© 2004 by Markt+Technik Verlag,ein Imprint der Pearson Education Deutschland GmbH,Martin-Kollar-Straße 10–12, D–81829 München/GermanyAlle Rechte vorbehaltenLektorat: Boris Karnikowski, [email protected]: Philipp Burkart, [email protected] Korrektur: Haide Fiebeler-Krause, BerlinSatz: reemers publishing services gmbh, Krefeld, (www.reemers.de)Coverkonzept: independent Medien-Design, MünchenCoverlayout: Sabine KrohbergerDruck und Verarbeitung: Bercker, KevelaerPrinted in Germany

Page 5: 382726314 X Php5 In 14 Tagen (Ddt)

InhaltsverzeichnisVorwort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

Für wen das Buch und die Reihe gedacht sind . . . . . . . . . 15Unsere Zielgruppe als Leser . . . . . . . . . . . . . . . . . . . . . . . . 16PHP und ... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16Ein paar Worte zum Autor . . . . . . . . . . . . . . . . . . . . . . . . . 17In diesem Buch verwendete Konventionen . . . . . . . . . . . . 18

Woche 1 – Wochenvorschau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

Tag 1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211.1 Die Geschichte von PHP . . . . . . . . . . . . . . . . . . . . . . . . . . 22

PHP/FI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22PHP3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22PHP4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23PHP5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

1.2 PHP auf einen Blick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24Ein wenig Vorbereitung . . . . . . . . . . . . . . . . . . . . . . . . . . . 24Mit PHP spielen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25Ein paar wichtige Techniken . . . . . . . . . . . . . . . . . . . . . . . 27

1.3 Einen Webserver bauen . . . . . . . . . . . . . . . . . . . . . . . . . . . 29Vorbemerkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29WAMP vorbereiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

1.4 Apache installieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30Installationsstart. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31Erste Schritte. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31Apache testen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35Weitere Einstellungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35Apache automatisch starten . . . . . . . . . . . . . . . . . . . . . . . . 36

5

1.5 PHP5 installieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371.6 PHP5 konfigurieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

Die Datei php.ini . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38Wichtige Konfigurationsschritte . . . . . . . . . . . . . . . . . . . . . 38

Page 6: 382726314 X Php5 In 14 Tagen (Ddt)

Inhaltsverzeichnis

1.7 PHP5 testen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39Konfiguration des Webservers für PHP5 . . . . . . . . . . . . . . 40PHP-Skripte ausführen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41Wenn es nicht funktioniert . . . . . . . . . . . . . . . . . . . . . . . . . 42

1.8 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

Tag 2 Erste Schritte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452.1 Einfache HTML-Seiten . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

HTML-Refresh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47Tabellen und Bilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49Formulare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

2.2 PHP einbetten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53Wie PHP den Code erkennt . . . . . . . . . . . . . . . . . . . . . . . . 53

2.3 Ausgaben erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55Die Ausgabe mit echo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55Variablen ausgeben. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56Ausgabe von großen Textmengen . . . . . . . . . . . . . . . . . . . 57Vielfältige Formatierungen mit print und Verwandten. . . 58

2.4 Professionelles Programmieren . . . . . . . . . . . . . . . . . . . . . 62Nicht nur für die Nachwelt: Kommentare. . . . . . . . . . . . . 62Benennungsregeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

2.5 Webserver und Browser. . . . . . . . . . . . . . . . . . . . . . . . . . . . 69Prinzip des Seitenabrufs . . . . . . . . . . . . . . . . . . . . . . . . . . . 70HTTP auf einen Blick . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71

2.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72

Tag 3 Daten verarbeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 733.1 Variablen und Literale . . . . . . . . . . . . . . . . . . . . . . . . . . . . 743.2 Konstanten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

Konstanten definieren und nutzen. . . . . . . . . . . . . . . . . . . 763.3 Rechnen und Vergleichen mit Ausdrücken . . . . . . . . . . . . 78

Ausdrücke und Operatoren. . . . . . . . . . . . . . . . . . . . . . . . . 783.4 Allgemeine Aussagen zu Zeichenketten. . . . . . . . . . . . . . . 83

6

Datentyp und Größe. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83Umgang mit Sonderzeichen. . . . . . . . . . . . . . . . . . . . . . . . 84Zeichenketten erkennen und bestimmen . . . . . . . . . . . . . 85Zeichenkettenoperationen . . . . . . . . . . . . . . . . . . . . . . . . . 86

Page 7: 382726314 X Php5 In 14 Tagen (Ddt)

Inhaltsverzeichnis

3.5 Die Zeichenkettenfunktionen . . . . . . . . . . . . . . . . . . . . . . 86Suchen und Ersetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87Ermitteln von Eigenschaften einer Zeichenkette . . . . . . . 90Teilzeichenketten und Behandlung einzelner Zeichen . . 90HTML-abhängige Funktionen. . . . . . . . . . . . . . . . . . . . . . 92

3.6 Reguläre Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95Einführung in die Welt der regulären Ausdrücke . . . . . . . 95Anwendungsbeispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98Typische Suchmuster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103

3.7 Datums- und Zeitfunktionen . . . . . . . . . . . . . . . . . . . . . . . 110Der Timestamp und die Serverzeit . . . . . . . . . . . . . . . . . . 110Rechnen mit Datums- und Zeitwerten . . . . . . . . . . . . . . . 110Tricks mit JavaScript: Lokale Zeit ermitteln . . . . . . . . . . . 117

3.8 Formatieren und Ausgeben. . . . . . . . . . . . . . . . . . . . . . . . . 119Zahlen formatieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119Umwandeln, Anpassen und Ausgeben . . . . . . . . . . . . . . . . 120

3.9 Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124Zeichenketten-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . 125Funktionen für reguläre Ausdrücke . . . . . . . . . . . . . . . . . . 129Referenz Ausgabe- und Datumsfunktionen . . . . . . . . . . . . 129

3.10 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130

Tag 4 Programmieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1314.1 Verzweigungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132

Einfache Verzweigungen mit if . . . . . . . . . . . . . . . . . . . . . 132Alternative Zweige mit else. . . . . . . . . . . . . . . . . . . . . . . . . 136Mehrfachverzweigungen mit switch . . . . . . . . . . . . . . . . . 137

4.2 Schleifen erstellen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141Gemeinsamkeiten aller Schleifenanweisungen. . . . . . . . . 141Die Zählschleife for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142Die Universalschleifen do/while und while . . . . . . . . . . . . 148

4.3 Benutzerdefinierte Funktionen. . . . . . . . . . . . . . . . . . . . . . 150Funktionen definieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150Parameter der Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . 153

7

Referenzen auf Funktionen und Parameter. . . . . . . . . . . . 159Statische Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160Globale Variablen und Konstanten . . . . . . . . . . . . . . . . . . 162Rekursive Funktionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164Variable Funktionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166

Page 8: 382726314 X Php5 In 14 Tagen (Ddt)

Inhaltsverzeichnis

4.4 Modularisierung von Skripten . . . . . . . . . . . . . . . . . . . . . . 168Module einbinden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168Informationen über Module ermitteln. . . . . . . . . . . . . . . . 172

4.5 Fehlerbehandlung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173Konventionelle Fehlerbehandlung . . . . . . . . . . . . . . . . . . 174Fehlerbehandlung mit PHP5 . . . . . . . . . . . . . . . . . . . . . . . 178

4.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180

Tag 5 Daten mit Arrays verarbeiten. . . . . . . . . . . . . . . . . . . . . . . . . . . . 1815.1 Datenfelder im Einsatz: Arrays . . . . . . . . . . . . . . . . . . . . . . 1825.2 Arrays erstellen und befüllen . . . . . . . . . . . . . . . . . . . . . . . 182

Ein einfaches Array erstellen . . . . . . . . . . . . . . . . . . . . . . . 183Die Anzahl der Elemente ermitteln . . . . . . . . . . . . . . . . . . 184Den Index manipulieren . . . . . . . . . . . . . . . . . . . . . . . . . . 184Schlüssel statt Indizes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185Arraywerte schneller zuweisen . . . . . . . . . . . . . . . . . . . . . . 186

5.3 Arrays manipulieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187Werte entfernen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187Der Arrayzeiger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188Arrayfunktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191Arraydaten manipulieren . . . . . . . . . . . . . . . . . . . . . . . . . . 193

5.4 Arrays ausgeben. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193Arrays zählbar ausgeben: for . . . . . . . . . . . . . . . . . . . . . . . . 193Beliebige Arrays mit foreach durchlaufen . . . . . . . . . . . . . 194Einfache Arrays mit while, each, list ausgeben . . . . . . . . . 195Array mit array_walk durchlaufen . . . . . . . . . . . . . . . . . . . 196Fehlersuche mit print_r . . . . . . . . . . . . . . . . . . . . . . . . . . . 196

5.5 Referenz der Arrayfunktionen . . . . . . . . . . . . . . . . . . . . . . 1985.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202

Tag 6 Objektorientierte Programmierung . . . . . . . . . . . . . . . . . . . . . . 2036.1 Warum objektorientiert programmieren?. . . . . . . . . . . . . . 2056.2 Syntax der Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207

8

Eine Klasse definieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207Ein Objekt erzeugen und benutzen. . . . . . . . . . . . . . . . . . 208Eine Klasse erweitern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208Zugriffskontrolle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215

Page 9: 382726314 X Php5 In 14 Tagen (Ddt)

Inhaltsverzeichnis

6.3 Schnittstellen zur Außenwelt . . . . . . . . . . . . . . . . . . . . . . . 220Schnittstellen (Interfaces) . . . . . . . . . . . . . . . . . . . . . . . . . . 221Informationen über Klassen und Objekte . . . . . . . . . . . . . 223Eigene Fehlerklassen erstellen . . . . . . . . . . . . . . . . . . . . . . 224Spezielle Zugriffsmethoden für Klassen. . . . . . . . . . . . . . . 227

6.4 Analyse und Kontrolle von Objekten . . . . . . . . . . . . . . . . . 232__METHOD__ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233Zeichenkettenform: __toString() . . . . . . . . . . . . . . . . . . . . 234Abstammung von Klassen und Objekten . . . . . . . . . . . . . . 235

6.5 Referenz der OOP-Funktionen . . . . . . . . . . . . . . . . . . . . . 239OOP-Schlüsselwörter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239OOP-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240

6.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242

Tag 7 Das Dateisystem entdecken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2437.1 Dateizugriff organisieren. . . . . . . . . . . . . . . . . . . . . . . . . . . 244

Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244Einsatzfälle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245

7.2 Praktischer Dateizugriff . . . . . . . . . . . . . . . . . . . . . . . . . . . 246Prinzipien des Dateizugriffs . . . . . . . . . . . . . . . . . . . . . . . . 246Wichtige Konstanten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249Nachrichtenquelle für eine News-Seite . . . . . . . . . . . . . . . 250Beliebige Code-Dateien ausgeben . . . . . . . . . . . . . . . . . . . 254Protokolldatei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255

7.3 Verzeichniszugriff . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257Inhalt eines Verzeichnisses anzeigen . . . . . . . . . . . . . . . . . 257Das Verzeichnisobjekt dir und verwandte Funktionen . . . 260Rekursive Dateiliste. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263Verzeichniszugriff mit Iteratoren . . . . . . . . . . . . . . . . . . . . 265

7.4 Funktions-Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268Datei-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268Prozess-Funktionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272

7.5 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273

9

Page 10: 382726314 X Php5 In 14 Tagen (Ddt)

Inhaltsverzeichnis

Woche 2 – Wochenvorschau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275

Tag 8 Formular- und Seitenmanagement . . . . . . . . . . . . . . . . . . . . . . . 2778.1 Grundlagen in HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278

Viel HTML – wenig PHP. . . . . . . . . . . . . . . . . . . . . . . . . . 2788.2 Auswerten der Daten aus Formularen . . . . . . . . . . . . . . . . 280

Grundlegende Schritte . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280Auswertung von Formularen . . . . . . . . . . . . . . . . . . . . . . . 283Textfelder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285Optionsfelder und Kontrollkästchen . . . . . . . . . . . . . . . . . 287Dropdown-Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295

8.3 Professionelle Formulare und »Sticky Forms« . . . . . . . . . . 301Ausfüllhinweise und Feldvorgaben . . . . . . . . . . . . . . . . . . 302Ausfüllhilfen mit JavaScript . . . . . . . . . . . . . . . . . . . . . . . . 303Fehlerangaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309Sticky Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315Mehrseitige Formulare mit versteckten Feldern . . . . . . . . 325

8.4 Dateien hochladen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334Prinzip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335Konfigurationsmöglichkeiten . . . . . . . . . . . . . . . . . . . . . . . 340

8.5 Von der Seite zum Projekt . . . . . . . . . . . . . . . . . . . . . . . . . 341Die HTTP-Methode GET . . . . . . . . . . . . . . . . . . . . . . . . . 341Daten per URL übermitteln . . . . . . . . . . . . . . . . . . . . . . . . 344Sicherheitsprobleme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346

8.6 Cookies und Sessions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349Cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349Session-Verwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358

8.7 Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365Wichtige Systemarrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365Server- und Umgebungsvariablen . . . . . . . . . . . . . . . . . . . 365Session-Verwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368

8.8 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372

10

Tag 9 Professionelle Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . 3739.1 Mehrsprachige Webseiten . . . . . . . . . . . . . . . . . . . . . . . . . 374

Browserdaten erkennen . . . . . . . . . . . . . . . . . . . . . . . . . . . 374Lokalisierung und Formatierung von Zeichen . . . . . . . . . 376

Page 11: 382726314 X Php5 In 14 Tagen (Ddt)

Inhaltsverzeichnis

9.2 Dynamisch Bilder erzeugen . . . . . . . . . . . . . . . . . . . . . . . . 379Prinzip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379Einführung in die Grafikbibliothek GD2 . . . . . . . . . . . . . 381Anwendungsbeispiel »Dynamischer Werbebanner« . . . . . 386

9.3 Code röntgen: Die Reflection-API . . . . . . . . . . . . . . . . . . . 395Die Reflection-API als Objektmodell . . . . . . . . . . . . . . . . . 395Die Reflection-Klassen im Detail . . . . . . . . . . . . . . . . . . . . 397

9.4 Funktions-Referenz GD2 . . . . . . . . . . . . . . . . . . . . . . . . . . 4079.5 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413

Tag 10 Kommunikation per HTTP, FTP und E-Mail . . . . . . . . . . . . . . 41510.1 Konzepte in PHP5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416

Prinzip der Streams und Wrapper . . . . . . . . . . . . . . . . . . . 416Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418

10.2 Streams und Wrapper anwenden . . . . . . . . . . . . . . . . . . . . 419Daten von einer fremden Website beschaffen . . . . . . . . . . 419Daten komprimiert speichern. . . . . . . . . . . . . . . . . . . . . . . 420

10.3 Filter verwenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42210.4 Die Stream-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . 425

Eigene Wrapper und Filter. . . . . . . . . . . . . . . . . . . . . . . . . 425Anwendung spezifischer Stream-Funktionen . . . . . . . . . . 425

10.5 E-Mail versenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427Vorbereitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429Praktische Umsetzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430

10.6 Funktions-Referenz Stream-Funktionen . . . . . . . . . . . . . . 44010.7 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 442

Tag 11 Datenbankprogrammierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44311.1 Prinzip der Datenbankprogrammierung . . . . . . . . . . . . . . 44411.2 Die universelle Abfragesprache SQL . . . . . . . . . . . . . . . . . 444

Was ist SQL? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445Tabellen und Abfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445

11

Tabellen anlegen und füllen . . . . . . . . . . . . . . . . . . . . . . . 448Aktualisieren und Löschen von Daten . . . . . . . . . . . . . . . . 452Fortgeschrittene Abfragen mit SELECT . . . . . . . . . . . . . . 453Verknüpfungen zwischen Tabellen . . . . . . . . . . . . . . . . . . 457Fortgeschrittene SQL-Techniken. . . . . . . . . . . . . . . . . . . . 459

Page 12: 382726314 X Php5 In 14 Tagen (Ddt)

Inhaltsverzeichnis

11.3 Der MySQL-Dialekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461Grobe Abweichungen vom SQL92-Standard . . . . . . . . . . 461Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 462Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 464

11.4 Erste Schritte mit MySQL und MySQLi . . . . . . . . . . . . . . 477MySQLi vorbereiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477Verbindung testen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 478Mit der Datenbank arbeiten . . . . . . . . . . . . . . . . . . . . . . . . 479

11.5 Referenz MySQLi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49111.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 496

Tag 12 Die integrierte Datenbank SQLite . . . . . . . . . . . . . . . . . . . . . . . 49712.1 Hintergrund. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49812.2 Vor- und Nachteile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499

Wann SQLite vorteilhaft ist . . . . . . . . . . . . . . . . . . . . . . . . 499Wann SQLite nachteilig ist . . . . . . . . . . . . . . . . . . . . . . . . 499

12.3 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 500Eine einfache Beispielanwendung . . . . . . . . . . . . . . . . . . . 500Eine Benutzerverwaltung mit SQLite . . . . . . . . . . . . . . . . 501Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505

12.4 Referenz SQLite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50612.5 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 508

Tag 13 Datenbanklösungen mit MySQL . . . . . . . . . . . . . . . . . . . . . . . . 50913.1 Bibliotheks-Verwaltung mit MySQL. . . . . . . . . . . . . . . . . . 510

Schrittfolge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510Weitere Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510

13.2 Vorbereitung der Datenbank. . . . . . . . . . . . . . . . . . . . . . . . 512Datenbank anlegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 512Tabellen anlegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513

13.3 Die Seiten des Projekts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 516Vorbereitungen – das Template-System. . . . . . . . . . . . . . . 516

12

Funktionsweise des Template-Systems. . . . . . . . . . . . . . . . 517Der Code der Seiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 523Die Geschäftslogik des Ausleihvorgangs . . . . . . . . . . . . . . 537

13.4 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54613.5 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 547

Page 13: 382726314 X Php5 In 14 Tagen (Ddt)

Inhaltsverzeichnis

Tag 14 XML und Webservices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54914.1 Vorbemerkungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 550

XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 550XSLT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 553XPath . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 562Ein konkretes XML-Format: RSS. . . . . . . . . . . . . . . . . . . . 568

14.2 Einführung in die libxml2 . . . . . . . . . . . . . . . . . . . . . . . . . 575Neu in PHP5. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 575Die neuen DOM-Funktionen . . . . . . . . . . . . . . . . . . . . . . 576

14.3 SimpleXML. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 581Unterschiede zur DOM-Schnittstelle . . . . . . . . . . . . . . . . 581Ein Schritt weiter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 583XML-Namensräume . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 588

14.4 SOAP-Webservices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 593Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 593Der Amazon-Webservice: ein Überblick . . . . . . . . . . . . . . 597Webservices konsumieren . . . . . . . . . . . . . . . . . . . . . . . . . 607

14.5 Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 620Referenz SimpleXML. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 620Referenz DOM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 621

14.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 626

Anhang A Antworten auf die Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . 627

Stichwortverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 643

13

Page 14: 382726314 X Php5 In 14 Tagen (Ddt)
Page 15: 382726314 X Php5 In 14 Tagen (Ddt)

Vorwort

Sie möchten PHP lernen? Kein Problem, nehmen Sie sich ein Buch, ein paarTage Zeit und schon können Sie PHP?! Leider ist das nicht ganz so, denn PHP istkeine isolierte Sprache auf der einsamen Insel der glücklichen Programmierer,sondern in ein komplexes System aus Standards, anderen Sprachen und Protokol-len eingebunden. Um PHP erfolgreich einsetzen zu können, müssen Sie sehr vielmehr beherrschen und wissen, als der reine Sprachkern erfordert.

Der erste Tag soll deshalb dazu dienen, die Begriffe und Abhängigkeiten zu sortie-ren, Sie mit den wichtigsten Techniken vertraut zu machen und so die Grund-lagen für schnelle Lernfortschritte zu legen.

Sie erfahren hier,

� an welche Zielgruppe sich dieses Buch wendet,

� wie dieses Buch aufgebaut ist und wie Sie es benutzen sollten, um maximalenNutzen daraus zu ziehen,

� wie sich PHP entwickelt hat und warum manches so ist, wie es sich heute dar-stellt,

� wie das PHP in die Welt der Webserver und deren Protokolle und Technikeneingebunden ist,

� wo Sie im Internet mehr Quellen finden und wie Sie Hilfe bei praktischenProblemen erhalten.

Für wen das Buch und die Reihe gedacht sind

Das vorliegende Buch ist Teil der sehr erfolgreichen »14-Tage«-Reihe. Warum

15

14 Tage? An wen wurde dabei als Zielgruppe gedacht?

Page 16: 382726314 X Php5 In 14 Tagen (Ddt)

Vorwort

Unsere Zielgruppe als Leser

Das Buch wendet sich an Leser, die bisher mit anderen Skriptsprachen oder auchnur mit HTML gearbeitet haben oder sich in der Ausbildung befinden. Elemen-tare Grundlagen der Programmierung sind hilfreich, vieles lässt sich aber auch gutaus dem Zusammenhang erschließen.

Dieses Buch ist so ausgelegt, dass Sie jedes Kapitel an einem Tag durcharbeitenkönnen. Nach nur 14 Tagen haben Sie dann alle wichtigen Funktionen kennengelernt und können selbst PHP-Programme schreiben. Der Weg zum professionel-len Programmierer ist freilich weit.

Die ersten Erfolge und die steile Lernkurve sollten Sie ermutigen, sich fortan zügigin spezifische Probleme einzuarbeiten und dort die Feinheiten der jeweiligenFunktion zu verstehen. Dieses Buch kann und will nicht jedes Detail behandeln.Eine umfassende Darstellung würde gut den zehnfachen Umfang erfordern. Umin einer überschaubaren Zeit zu greifbaren Ergebnissen zu gelangen, müssen Siesich auf die entscheidenden 20% konzentrieren, mit denen Sie 80% aller Alltags-aufgaben meistern können.

Um einen intensiven Lerneffekt zu erzielen, finden Sie am Ende jedes Tageseinige Fragen. Beantworten Sie diese und schlagen Sie bei Schwierigkeiten noch-mals nach. Lassen Sie den Tag immer Revue passieren und wiederholen sie selbst-ständig Abschnitte, die Ihnen besonders kompliziert erschienen. Die 14 Tage sindkein Zwang, sondern helfen vor allem bei der zielgerichteten Erarbeitung der Auf-gabenstellung.

PHP und ...

PHP als Skriptsprache zur Webserverprogrammierung steht nicht isoliert da. Tat-sächlich können Sie PHP niemals erfolgreich einsetzen, wenn Sie sich nicht mitden Technologien auseinander setzen, die rund um den Webserver benutzt wer-den.

An erster Stelle steht natürlich HTML. Ohne HTML-Kenntnisse werden Sie kein

16

einziges brauchbares PHP-Programm erstellen. Damit eng verbunden ist Java-Script, das HTML an einigen Stellen etwas unter die Arme greift. Ohne JavaScriptkommen Sie aus, bleiben aber immer in der Amateur-Liga.

Gleiches gilt für Datenbanken und die Datenbankabfragesprache SQL. Es gibtviele kleine und gute Programme, die auf eine Datenbankanbindung verzichten.

Page 17: 382726314 X Php5 In 14 Tagen (Ddt)

Für wen das Buch und die Reihe gedacht sind

Praktisch jedes größere Projekt baut jedoch darauf auf, und es sind signifikanteVorteile, die den Einsatz von Datenbanksystemen in so breiter Front vorangetrie-ben haben. SQL spielt deshalb in diesem Buch eine bedeutende Rolle. In engemZusammenhang damit steht XML – eingesetzt zum Datenaustausch und zurSchaffung von universellen Datenschnittstellen. Auch dieses Thema nimmt brei-ten Raum ein und zwingt so auch den Leser zur aktiven Auseinandersetzungdamit.

Zwischen Browser und Webserver pendeln die Daten nicht im luftleeren Raum.Das Web basiert auf Protokollen, die den Verkehr regeln – allen voran HTTP. DaSie als PHP-Entwickler die Kontrolle über den Webserver haben, nehmen Sie dieRolle des Verkehrspolizisten ein. Und an dieser Stelle müssen Sie zwangsläufigHTTP kennen. Damit eng verbunden sind die anderen im Internet verwendetenProtokolle wie POP3 und SMTP für E-Mail oder FTP für Dateiübertragungen.

Ein paar Worte zum Autor

Jörg Krause, Jahrgang 1964, wohnt mit Familie in Berlin. Erarbeitet als freier Autor, Systemprogrammierer und Consul-tant. Seine Arbeitsschwerpunkte sind die

� Programmierung von Internetapplikationen und Daten-banken mit PHP, ASP, C#, XML/XSL, HTML, MS SQL-Server, MySQL,

� Programmierung von Windows-Applikationen mit .NET-Technologie,

� Consulting für Start-Ups und »Old Economy«,

� Seminare über Webserverprogrammierung (HTML, ASP, PHP) ,

� und journalistische Arbeiten.

Sie finden außerdem regelmäßig Artikel von ihm in Fachzeitschriften wie iX, dot-net- oder PHP-Magazin und können seine Vorträge auf Fachkonferenzen erleben.

17

Des Weiteren veröffentlichte er mehr als 30 Fachbücher zu Themen wie Windows2000, Windows XP, Microsoft Active Server Pages, ASP.NET, C#, VB.NET, PHPsowie Electronic Commerce und Online Marketing.

Page 18: 382726314 X Php5 In 14 Tagen (Ddt)

Vorwort

Interessierten Lesern steht der Autor für professionelle Softwareentwicklung undProgrammierung zur Verfügung. Dies betrifft sowohl Web- als auch Windows-Applikationen. Professionelles Know-how finden Sie über Beratung, Coaching,Training, Schulung und Consulting.

In diesem Buch verwendete Konventionen

Dieses Buch enthält spezielle Icons, mit denen wichtige Konzepte und Informationen her-ausgestrichen werden sollen.

Ein Hinweis enthält interessante Informationen zum behandelten Thema.

Ein Tipp gibt Ihnen Ratschläge oder zeigt Ihnen einfachere Wege zur Lösungeines Problems auf.

Ein Achtungszeichen weist Sie auf mögliche Probleme hin und hilft Ihnen,schwierigen Situationen aus dem Wege zu gehen.

� In Listings zeigt Ihnen dieses Zeichen, dass die laufende Zeile nur aus satztechni-schen Gründen umbrochen wurde. Sie müssen hier also keinen Return setzen.

18

Page 19: 382726314 X Php5 In 14 Tagen (Ddt)

WOCH

TTTTaaaag g g g 1111 Einführung 21

TTTTaaaag g g g 2222 Erste Schritte 45

TTTTaaaag g g g 3333 Daten verarbeiten 73

WOCHE

ETTTTaaaag g g g 4444 Programmieren 131

TTTTaaaag g g g 5555 Daten mit Arrays verarbeiten 181

TTTTaaaag g g g 6666 Objektorientierte Programmierung 203

TTTTaaaag g g g 7777 Das Dateisystem entdecken 243

TTTTaaaag g g g 8888 Formular- und Seitenmanagement 277

TTTTaaaag g g g 9999 Professionelle Programmierung 373

TTTTaaaag g g g 10101010 Kommunikation per HTTP, FTP und E-Mail 415

TTTTaaaag g g g 11111111 Datenbankprogrammierung 443

TTTTaaaag g g g 12121212 Die integrierte Datenbank SQLite 497

TTTTaaaag g g g 13131313 Datenbanklösungen mit MySQL 509

TTTTaaaag g g g 14141414 XML und Webservices 549

Page 20: 382726314 X Php5 In 14 Tagen (Ddt)
Page 21: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung

1

Page 22: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung

1.1 Die Geschichte von PHP

Dieser Abschnitt zeigt einen kurzen Abriss der Geschichte von PHP, die zwar erstkurz aber dafür umso beeindruckender ist.

PHP/FI

1995 entwickelte der damals erst 17-jährige Däne Rasmus Lerdorf unter demNamen PHP/FI einen Satz von Perl-Skripten zur Erfassung der Zugriffe auf seineWebsite. Diese so genannten »Personal Home Page (Tools)/Forms Interpreter«setzte er später noch einmal in der Sprache C um. Das führte zu mehr Leistungund höherer Geschwindigkeit, als dies mit den damaligen Perl-Werkzeugen mög-lich war. PHP/FI kannte Variablen, konnte Formularinhalte interpretieren undbesaß eine Perl-ähnliche Syntax, die allerdings noch relativ inkonsistent war.

Lerdorf entschied sich, den Quellcode von PHP/FI im Sinne der Debian FreeSoftware Guidelines freizugeben. Damit war der Weg für die einfache Entwick-lung dynamischer Websites frei.

Im November 1997 wurde PHP/FI 2.0 offiziell freigegeben; nach eigenen Anga-ben kam die Software auf ca. 50.000 Sites zum Einsatz. Obwohl bereits mehrereEntwickler am Quellcode mitarbeiteten, leistete nach wie vor Lerdorf den Löwen-anteil der Arbeit. PHP/FI 2.0 wurde kurz darauf durch die erste Alphaversion vonPHP3 abgelöst.

PHP3

Im gleichen Jahr starteten Andi Gutmans und Zeev Suraski eine Kooperation mitRasmus Lerdorf. Auf der Basis von PHP/FI 2.0 aufbauend kündigten sie PHP 3.0als offiziellen Nachfolger an, denn die bestehende Leistungsfähigkeit von PHP/FI2.0 hatte sich vor allem im Bereich von eCommerce-Applikationen als nichtausreichend erwiesen. Die neue Sprache sollte unter einem neuen Namen veröf-fentlicht werden; vor allem deswegen, um die im alten Namen implizierte einge-

22

schränkte Nutzung aufzugeben. Man einigte sich auf PHP, das – ganz im Stile desGNU-Projektes – ein rekursives Akronym für »PHP Hypertext Preprocessor« dar-stellt. PHP Version 3.0 wurde im Juni 1998 nach einer neunmonatigen öffentli-chen Testphase offiziell freigegeben.

Page 23: 382726314 X Php5 In 14 Tagen (Ddt)

Die Geschichte von PHP

PHP3 überzeugte vor allem durch seine Erweiterungsmöglichkeiten, die solideInfrastruktur, die Möglichkeit einer objektorientierten Syntax sowie die Unterstüt-zung der gängigsten Datenbanken. Dutzende von Entwicklern sahen diese Stärkenund beteiligten sich mit neuen Modulen an PHP3. Möglicherweise war gerade dieZusammenarbeit von vielen Entwicklern einer der Gründe für den gewaltigenErfolg von PHP. Ende 1998 wurde PHP von einigen Zehntausend Benutzern ver-wendet. Die Installationen von PHP deckten dabei schätzungsweise 10% allerWebsites im Netz ab.

PHP4

Bereits kurz nach dem offiziellen Release von PHP3 begannen Gutmans undSuraski, den Kern von PHP umzuschreiben. Die Leistung komplexer Applika-tionen sollte gesteigert werden und der Basiscode sollte modularer werden. Mitte1999 wurde die neue PHP-Engine unter dem Namen »Zend« (gebildet aus Teilender Vornamen Zeev und Andi) erstmalig eingeführt, der auch Namensgeber derFirma der beiden ist: Zend Ltd. (www.zend.com). Ein Jahr später konnte PHP4.0offiziell freigegeben werden. Basierend auf der »Zend«-Engine glänzte diese Ver-sion neben ihrer stark verbesserten Leistung vor allem durch ihre Unterstützungverschiedenster Webserver, HTTP-Sessions, Ausgabepufferung und vielen neuenSprachkonstrukten.

PHP 4 wird weltweit von unzähligen Entwicklern erfolgreich eingesetzt. Mit meh-reren Millionen Installationen (schätzungsweise 20% aller Websites) ist PHPdamit eine der erfolgreichsten Entwicklungen im dynamischen Internet.

PHP5

Die Arbeit an der Spezifikation und der Verbesserung der »Zend«-Engine als Basisfür die neuen Leistungsmerkmale von PHP5 ist der nächste Schritt. ObwohlPHP 4 bereits optimal an die Aufgabe »Webserverprogrammierung« angepasst istund vielen Entwicklern ein verlässliches Werkzeug in die Hand gibt, blieben Kri-tikpunkte bestehen. Vor allem die Profilager, wo mit C++ und Java programmiert

23

wird, bemängelten die eingeschränkten Fähigkeiten der Sprache im BereichObjektorientierung (OOP). Eine weitere wichtige Funktion war die Möglichkeit,einen Applikationsserver aufzubauen. Skriptprogramme laufen normalerweise nurauf Initiative des Benutzers ab, der über seinen Browser ein Programm startet.Applikationsserver können selbstständig Programme agieren lassen. Diese Forde-rung wurde mit PHP5 nicht erfüllt.

Page 24: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung

Die OOP-Fähigkeiten sind mit der Version 5 deutlich verbessert worden. Sie wer-den sich damit an gleich zwei Tagen intensiv beschäftigen können. Von einemApplikationsserver ist allerdings nach wie vor weit und breit nichts zu sehen. Dieswird »Skripter« freuen, denen die Einfachheit des Systems am Herzen liegt, unddas Profilager zu Recht an Java, C# und C++ festhalten lassen.

PHP5 ist ein gutes, professionelles und leistungsfähiges Tool geworden, mit demsich kleine und mittlere Projekte schnell und effizient erstellen lassen. DiesenMarkt sollte man vor Augen haben und verstehen, dass PHP5 keine ernst zu neh-mende Konkurrenz für Java und ASP.NET ist, auch wenn diese Tatsache einigeVertreter der PHP-Community partout nicht verstehen wollen.

1.2 PHP auf einen Blick

Wenn Sie neugierig sind und darauf brennen, schnell in PHP einzusteigen, solltenSie sich nicht zurückhalten lassen. Es ist durchaus hilfreich, schon mal ein paarSkripte laufen zu lassen, bevor die ganzen theoretischen Schritte durchgegangenwerden.

Dieser Abschnitt setzt voraus, dass Sie bereits Zugriff auf einen PHP-Server haben.Falls Sie die lokale Installation, die am zweiten Tag behandelt wird, noch nichtvorziehen möchten und auch keinen Provider haben, der Skripte ausführt, gehenSie auf den Webserver des Autors: www.php5.comzept.de.

Dort können Sie die hier gezeigten Skripte eingeben und ausführen. Aus Sicher-heitsgründen werden aber andere als die hier gezeigten Funktionen nicht akzep-tiert.

Ein wenig Vorbereitung

Um Skriptdateien ausführen zu können, brauchen Sie einen Webserver. WennSie sich damit auskennen, nutzen Sie den Ihnen bekannten Webserver und legen

24

Sie ein virtuelles Verzeichnis mit dem Namen »MuT« an, in das Sie dann alleSkripte speichern. Rufen Sie die fertigen Skripte nun wie folgt auf:

http://localhost/MuT/<scriptname>

Page 25: 382726314 X Php5 In 14 Tagen (Ddt)

PHP auf einen Blick

Statt <scriptname> steht der Name des Skripts, beispielsweise listing1.php. DieDateierweiterung benötigt der Webserver, um die Datei an den PHP-Interpreterweiterzureichen. Genauer wird dies am zweiten Tag erläutert.

Wenn Sie die Codes des Programms sehen oder der Browser das Skript zum Her-unterladen anbietet, ist die gewählte Dateierweiterung .php unbekannt oder PHPist nicht oder falsch installiert. Zur Lösung dürfen Sie schon mal zum zweiten Tagblättern und schmulen.

Mit PHP spielen

PHP wird in HTML eingebettet. Das heißt, Sie erstellen primär eine einfacheHTML-Seite und bringen darin PHP unter. Der erste Schritt besteht deshalb inder Beschaffung einer geeigneten HTML-Datei:

Listing 1.1: Basis aller Versuche: Eine einfache HTML-Datei (html1.php)

<html> <head> <title>Listing 1</title> </head> <body> <h1>Unser erster Test</h1> </body></html>

Speichern Sie diesen Text unter html1.php im Übungsverzeichnis des Webservers.Führen Sie ihn dann aus, indem Sie im Browser folgende Zeile eingeben:

http://localhost/MuT/html1.php

Wenn es nicht funktioniert, probieren Sie statt localhost die lokale IP-Adresse127.0.0.1. Wenn der Webservers nicht lokal (auf der Entwicklungsmaschine) läuft,müssen Sie den Namen oder die IP-Adresse anstellte von localhost angeben. Amzweiten Tag werden ein paar Tipps vorgestellt, wie man ein Entwicklungssystemgeschickt aufbaut.

25

Nun geht es aber wirklich los! Das erste Skript diente nur als Test für die Funktiondes Webservers und von PHP. Es enthielt freilich noch keinen PHP-Code. Einekleine Erweiterung soll nun auch PHP ins Spiel bringen:

Page 26: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung

Listing 1.2: PHP erzeugt Text

<html> <head> <title>Listing 2</title> </head> <body> <h1>Unser erster Test</h1> <?php $text = "Hallo PHP5"; print($text); ?> </body></html>

Hier sind Sie nun gleich mit mehreren Techniken konfrontiert worden, die ele-mentar für die PHP-Programmierung sind. Auch wenn es nur vier neue Zeilensind, passiert hier jedoch Erstaunliches. Typisch für die Programmierung ist es,dass fast jedes Zeichen eine Bedeutung hat. Programmierer arbeiten sehr effizient,deshalb wird viel abgekürzt und viel mit Symbolen gearbeitet. Zeilenweisebetrachtet sieht dies nun folgendermaßen aus:

� <?phpHier geht es los: Diese Zeichenfolge leitet einen Programmteil ein, der vonPHP verarbeitet werden soll. Auf der nächsten Zeile steht nun Programmcode.

� $text = "Hallo PHP5";Hier passieren mehrere Dinge. Zum einen wird eine Variable erzeugt. Sie hatden Namen »text« und speichert die Zeichenfolge »Hallo PHP5«. Das $-Zei-chen sagt PHP, dass es sich um eine Variable handelt, es ist aber nicht Teil desNamens. Der Name ist willkürlich gewählt und adressiert eine Speicherstelle,wo PHP die Daten ablegt.

Das Semikolon am Ende ist auch ganz wichtig und teilt PHP mit, dass dieserBefehl nun zu Ende ist. Der Zeilenwechsel selbst ist PHP ziemlich egal (meis-tens jedenfalls), das Programm liest sich aber so besser.

� print($text);

26

Hier ist auch wieder richtig viel los: »print« ist eine Funktion, die PHP bereitstellt und die Daten ausgibt; in diesem Fall die Variable $text und damit derenInhalt. Ausgeben heißt bei PHP »an dieser Stelle ins HTML einfügen«. Denndie eigentliche Ausgabe zum Browser – das Senden der Daten – erledigt derWebserver.

Page 27: 382726314 X Php5 In 14 Tagen (Ddt)

PHP auf einen Blick

� ?>Damit alles seine Ordnung hat, wird der PHP-Abschnitt korrekt beendet. DerRest ist nur HTML und wird unverändert vom Webserver ausgegeben.

Programmierung mit PHP ist nun eigentlich ganz einfach. Es kommt darauf an,die richtigen Funktionen und Befehle in der richtigen Reihenfolge aufzuschrei-ben. Damit es Spaß macht, bietet PHP über 2.000 solcher Funktionen in über100 Bibliotheken und dazu noch reichlich Sprachbefehle und Operatoren. Inso-fern ist PHP nun doch nicht so ganz einfach...

Ein paar wichtige Techniken

Das Beispiel zeigte freilich nur wenig. Etwas mehr Systematik bietet dieserAbschnitt. Zuerst sollten Sie lernen, sich über Zeilenumbrüche weniger Gedan-ken zu machen. Folgender Code ist dem bereits gezeigten völlig gleichwertig1:

<?php $text = "Hallo PHP5"; ?><?php print($text); ?>

In einem Skript können beliebig viele solcher PHP-Blöcke stehen, die von obennach unten abgearbeitet werden. Wie es gezeigt wurde ist es nicht immer clever,immerhin braucht auch die Erkennung der Zeichen <?php und ?> einige Zeit(nichts, was Sie jemals spüren oder messen könnten, aber richtige Programmierermacht es schon nervös, wenn der Server ständig ein paar Nanosekunden lang sinn-lose Dinge tut).

Woran Sie sich hier gewöhnen müssen, sind die öffnenden und schließendenSequenzen und die abschließenden Semikola.

Manche Befehle wirken über einen bestimmten Bereich hinweg bzw. fassenmehrere Zeilen zusammen. In solchen Fällen werden geschweifte Klammern ver-wendet. Das sieht dann folgendermaßen aus:

<?php$a = 5;$b = 5;if ($a == $b)

27

{ $text = "a und b sind gleich";

1 Um Papier zu sparen, wird der langweilige HTML-Teil hier nicht jedes Mal mitgedruckt

Page 28: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung

print($text);}?>

PHP wird deshalb der Gruppe der so genannten Klammersprachen zugeordnet, zuder auch Java und alle C-Versionen zählen. Die Klammer ersetzt übrigens dasSemikolon, aber das haben Sie sicher bereits bemerkt. Eine wichtige Technik istdas Einrücken. Das ist dem PHP-Interpreter natürlich wieder egal, es dient nur derVerbesserung der Lesbarkeit. Tun Sie sich selbst und allen späteren Entwicklerndes Programms den Gefallen, wirklich konsequent einzurücken. Die meisten Edi-toren unterstützen das in der einen oder anderen Weise.

Weil gerade die Rede von anderen Entwicklern war: Kommentare im Quelltextsind ein gutes Mittel, sich als mitteilsam zu erweisen und die Lesbarkeit weiter zuverbessern. Dazu setzen Sie wieder spezielle Zeichen ein:

<?php// $a und $b wurden bereits woanders definiertif ($a == $b){ $text = "a und b sind gleich"; print($text);}?>

Die beiden Schrägstriche leiten einen Kommentar ein, der am nächsten Zeilen-umbruch endet. Alternativ können mehrzeilige Kommentare mit /* Kommentar */geschrieben werden. Da gibt es aber einige Fallen und deshalb wird diesemThema am dritten Tag ein eigener Abschnitt gewidmet.

Eine nähere Betrachtung ist die Funktion print wert. Funktionen haben Parame-ter und deshalb immer runde Klammern am Ende. Wenn man mal keine Datenübergeben will, bleiben die Klammern leer, aber sie fallen niemals weg. Ob manDaten direkt (Fachsprache: als Literal) oder über eine Variable übergibt, spielt sel-ten eine Rolle. Es gibt aber Fälle, wo dies wichtig ist. Auch dazu wird der dritteTag handfeste Informationen zu bieten haben. Damit die Sache in der Praxis nichtlangweilig wird (wer kommt schon mit 2.000 Funktionen aus?), können Sie aucheigene Funktionen definieren.

28

Auch wenn es so ähnlich aussieht, if ist keine Funktion, sondern eine Sprachan-weisung. Die Klammern fassen einen komplexeren Ausdruck zusammen. Sprach-anweisungen sehen sehr unterschiedlich aus und bedürfen einer eingehendenBetrachtung. Tag 6 widmet sich ganz diesem Thema, ebenso wie den selbst erstell-ten Funktionen.

Page 29: 382726314 X Php5 In 14 Tagen (Ddt)

Einen Webserver bauen

1.3 Einen Webserver bauen

Bevor die Arbeit mit PHP5 beginnen kann, benötigen Sie eine Entwicklungsum-gebung. Diese besteht aus einem Editor, in dem Quelltexte erfasst und bearbeitetwerden, einem Webserver, PHP5 ausführen kann, und einem Browser, der dieSkripte vom Webserver abruft. Für die Bearbeitung von Datenbanken wird nochein Datenbanksystem benötigt.

Vorbemerkungen

PHP5 ist fest in der Linux-Welt etabliert. Inzwischen existiert jedoch auch einesehr stabile Version für Windows, die den Einsatz auch auf Produktionssystemenerlaubt. Für die Entwicklung zählten jedoch schon früher auch andere Kriterien,wie beispielsweise ein guter Editor. Diese sind nach wie vor eher in der Windows-Welt zu finden.

Nach Untersuchungen einiger kommerzieller Editor-Hersteller werden ca. 90%aller Skripte auf Windows entwickelt. Das Hosting – also der eigentliche Produkti-onsbetrieb der fertigen Applikation – findet dann mit ähnlicher Dominanz unterLinux statt.

Der Aufbau eines Entwicklungssystems unter Linux ist deshalb in den meisten Fäl-len kein Thema, Aufwand und Nutzen stehen in einem sehr ungünstigen Verhält-nis zueinander. An dieser Stelle soll deshalb nur der Aufbau eines so genanntenWAMP-Systems demonstriert werden, wie es in den allermeisten Fällen zum Ein-satz kommen dürfte.

WAMP vorbereiten

Alle nötigen Daten finden Sie auf der CD zum Buch. Wenn Sie die allerneuestenVersionen möchten, müssen Sie diese entsprechend aus dem Internet herunterla-den. Benötigt werden:

29

� PHP5 – Binärdistribution für Windows

� PHP5 PECL-Module

� Apache 2 – Binärdistribution für Windows

Page 30: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung

Auf die Datenbank soll an dieser Stelle vorerst verzichten werden. In den entspre-chenden Kapiteln wird entweder das integrierte SQLite benutzt, das keine weite-ren Installationsschritte verlangt, oder MySQL, dem ein eigener Tag gewidmet ist.

Im Internet finden Sie die entsprechenden Dateien auf folgenden Webseiten:

� PHP5: www.php.net/downloads.phpNutzen Sie hier die Option »PHP5.0.0 zip package« unter »Windows Bina-ries«. Außerdem ist die »Collection of PECL modules for PHP5.0.0« interes-sant, eine Sammlung weiterer Module, von denen einige gebraucht werdenkönnten. PHP5 selbst ist ca. 7,5 MB groß, die Module brauchen etwa 930 KB.

� Apache: httpd.apache.org/download.cgiWählen Sie hier die Option »Win32 Binary (MSI Installer)«. Der Dateinamehat in etwa die Form »apache_2.0.50-win32-x86-no_ssl.msi«. Es handelt sichalso um die Version 2.0.50 für Win32-Systeme, verpackt als Windows-Installer-Paket (MSI). Die Größe beträgt ca. 6 MB. Wenn Sie auf dem Webservergenau diese Version finden, können Sie sie auch von der CD nehmen.

Windows-Installer-Pakete sind mit Windows XP eingeführt worden undlaufen sofort auf Windows XP Pro, XP Home und Windows Server 2003.Sie laufen auch auf einem aktualisierten Windows 98 und Me. FallsMSI als Dateierweiterung nicht bekannt ist, müssen Sie sich über dieMicrosoft-Download-Seite ein entsprechendes Update beschaffen.Benötigt wird mindestens Microsoft Installer 1.2. Gehen Sie bei Bedarfauf folgende Website: www.microsoft.com/downloads/release.asp?ReleaseID=32831 (Win 9X) bzw. www.microsoft.com/downloads/release.asp?ReleaseID=32832 (NT4, 2000).

Nach dem die Installationspakete vorliegen, beginnt die Installation. InstallierenSie zuerst den Webserver, testen Sie ihn und fahren Sie dann mit PHP5 fort.

1.4 Apache installieren

30

Dank des Installers ist die Installation ganz einfach. Die Schritte werden nachfol-gend gezeigt und anhand der Bemerkungen können Sie die nötigen Angabenmachen. Starten Sie nun den Installer und überspringen Sie den Startbildschirmdes Assistenten mit NEXT.

Page 31: 382726314 X Php5 In 14 Tagen (Ddt)

Apache installieren

Installationsstart

Nach dem Start der Installation müssen Sie die Lizenzbedingungen bestätigenund eine Informationsseite mit allgemeinen Hinweisen über sich ergehen lassen.Gehen Sie jeweils mit NEXT weiter, bis Sie zur Seite SERVER INFORMATIONgelangt sind.

Erste Schritte

Die erste erforderliche Eingabe konfiguriert den Server. Webserver sind immerTeil einer Domain, wenn Sie im Inter- oder Intranet laufen, beispielsweisephp.net. Sie haben immer einen bestimmten Namen, der meist »www« lautet. DerWebserver der PHP Group heißt deshalb www.php.net. Es ist möglich, dass Sielokal über keine Domain verfügen, weil ihr Rechner direkt am Internet hängt, Siealso keine eigenen Namensdienste betreiben. Die Angabe der richtigen Domain

Abbildung 1.1: Auch bei GPL-Projekten sind die Lizenzbestimmun-gen anzuerkennen

31

ist jedoch notwendig, damit der Browser den Server später auch findet.

Für einen lokal betriebenen Webserver nimmt man meist den Namen localhost,der an die interne Loopback-Adresse 127.0.0.1 gebunden ist. Diese Adresse stelltsicher, dass Anfragen nicht über Router ans Internet oder andere Computer imNetzwerk weitergeleitet werden. Meist ist die entsprechende Verknüpfung zwi-

Page 32: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung

schen localhost und 127.0.0.1 schon vorhanden. Um dies zu prüfen, öffnen Sie dieDatei hosts (ohne Dateierweiterung) im Verzeichnis c:\windows\system32\drivers\etc. Ist die Datei leer, wird folgende Zeile eingefügt:

127.0.0.1 localhost

Achten Sie beim Speichern darauf, dass der Editor keine Dateierweiterunganhängt. Auf dem folgenden Bildschirm im Installationsassistenten ist nun folgen-des anzugeben:

� Domain: localhost

� Servername: localhost

� E-Mail des Administrators: Ihre E-Mail (wird nicht benutzt)

� Option FOR ALL USERS, ...

Die Option FOR ALL USERS, ... (für alle Benutzer) ist grundsätzlich richtig, wennSie keine anderen Webserver wie die Internetinformationsdienste auf dem Com-puter installiert haben. Apache benutzt standardmäßig Port 80 (dies ist der Port,den der Browser benutzt, wenn man nichts angibt). Dieser Port sollte natürlich freisein. Haben Sie keinen anderen Webserver installiert, ist dieser Port frei.

32

Abbildung 1.2: Einstellungen für ein lokales System

Page 33: 382726314 X Php5 In 14 Tagen (Ddt)

Apache installieren

Setzen Sie mit NEXT fort. Im nächsten Schritt werden Sie gefragt, ob sie die Stan-dardinstallation oder eine benutzerdefinierte wünschen. Wählen Sie CUSTOM(Benutzerdefiniert).

Sie können nun auf der nächsten Seite einige Optionen abwählen, die für PHP5nicht benötigt werden. Falls Sie sich intensiver mit Apache auseinandersetzenmöchten, belassen Sie die Dokumentation im Installationspaket. Allerdings sinddie Texte auch online verfügbar. Mit einer schnellen Internetverbindung könnenSie online schneller arbeiten (siehe Abbildung 1.4).

Im nächsten Schritt müssen Sie den Start der Installation nur noch bestätigen, denRest erledigt der Installer dann automatisch (siehe Abbildung 1.5).

Falls Fehler auftreten, werden diese in Konsolenfenstern angezeigt. Am Endeerhalten Sie eine Erfolgsmeldung. Nun kann der Webserver getestet werden.

Ein erneuter Start des Installers erlaubt die Reparatur oder Vervollständi-

Abbildung 1.3: Benutzerdefinierte Installation – verhindert unnö-tige Module

33

gung einer bestehenden Installation, die nicht komplett installiert wurdeoder aus anderen Gründen nicht läuft.

Page 34: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung

Abbildung 1.4: Was nicht benö-tigt wird, fliegt raus

Abbildung 1.5: Während der

34

Installation

Page 35: 382726314 X Php5 In 14 Tagen (Ddt)

Apache installieren

Apache testen

Der erste Test ist sehr einfach. Öffnen Sie Ihren Browser und geben Sie folgendeAdresse ein:

http://localhost

Weitere Einstellungen

Der Apache Webserver wird über eine Konfigurationsdatei mit dem Namenhttpd.conf verwaltet. Diese ist über START | ALLE PROGRAMME | APACHE HTTPSERVER 2.0.50 | CONFIGURE APACHE SERVER zu finden.

Abbildung 1.6: Der Webserver funktioniert

35

Prüfen Sie beispielsweise Ihre Einstellungen, die Sie während der Installation vor-genommen haben, indem Sie nach einer Zeile suchen, die mit SERVERNAMEbeginnt. Dort sollte folgendes stehen:

ServerName localhost:80

Page 36: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung

Jetzt ist noch interessant zu wissen, wo die Dateien abgelegt werden, die der Web-server anbietet. Jeder Webserver hat ein Stammverzeichnis, das benutzt wird,wenn die blanke Adresse angegeben wird. Beim Apache-Webserver heißt diesesVerzeichnis htdocs und liegt unter

C:\Programme\Apache Group\Apache2\htdocs

Apache automatisch starten

Es ist sinnvoll, wenn der Webserver nach dem Start des Betriebssystems sofort zurVerfügung steht. Bei manueller Arbeitsweise können Sie Apache über entspre-chende Aufrufe starten und stoppen. Über START | ALLE PROGRAMME | APACHEHTTP SERVER 2.0.50 | CONTROL APACHE SERVER finden Sie die OptionenSTART, STOP und RESTART.

Abbildung 1.7:

36

Dienst-Verwaltung für den Apache Webserver

Page 37: 382726314 X Php5 In 14 Tagen (Ddt)

PHP5 installieren

Unter Win9X fügen Sie den Startaufruf in den Autostart-Ordner des Startmenüsein. Auf XP und 2000 wird Apache automatisch als Dienst installiert und startetmit dem Betriebssystem. Se können dies kontrollieren, indem Sie den Dienst-Manager öffnen. Gehen Sie (unter XP) dazu im STARTMENÜ auf ARBEITSPLATZ,klicken Sie mit der rechten Maustaste und wählen Sie dann VERWALTEN. Alterna-tiv finden Sie die Option auch in der Systemsteuerung unter VERWALTUNG |COMPUTERVERWALTUNG. Es öffnet sich eine Managementkonsole. Dort suchenSie den Zweig DIENSTE UND ANWENDUNGEN und darin DIENSTE. In der Dienst-liste ist der Eintrag APACHE2 zu finden. Öffnen Sie diesen mit einem Doppelklick(siehe Abbildung 1.7).

Wenn Sie möchten, dass der Dienst immer automatisch startet, stellen Sie denSTARTTYP entsprechend ein.

Läuft alles, geht die Installation nun mit PHP5 weiter.

1.5 PHP5 installieren

PHP5 kommt als ZIP-Datei daher und muss nur ausgepackt werden. Dazu wählenSie ein passendes Verzeichnis, am besten im Zweig Ihres Apache-Webservers:

C:\Programme\Apache Group\php5

Entpacken Sie alle Dateien dorthinein. Denken Sie daran, dass Winzip beim Ent-packen evtl. den Namen der Datei in den Pfad einbaut. Unter dem Verzeichnisphp5 sollten sofort die entsprechenden Arbeitsdateien folgen, nicht der von Win-zip erzeugte Ordner, der dem Namen der gepackten Datei entspricht.

Entpacken Sie nun noch die PECL-Datei. Sie können alle enthaltenen Modulenach folgendem Pfad kopieren:

C:\Programme\Apache Group\php5\ext

Hier liegen bereits die standardmäßig in PHP5 eingebundenen Module.

37

1.6 PHP5 konfigurieren

PHP5 wird – ähnlich wie der Apache Webserver – über eine Konfigurationsdateimit dem Namen php.ini konfiguriert. Nach der Installation gibt es diese nochnicht. Stattdessen finden Sie zwei Musterdateien:

Page 38: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung

� php.ini-distDies ist die Datei mit allen Standardoptionen.

� php.ini-recommendedEine Datei mit der empfohlenen Konfiguration.

Erstellen Sie nun eine Kopie von php.ini-dist und nennen diese php.ini. LassenSie die Originaldatei auf jeden Fall unverändert, damit Sie die ursprüngliche Kon-figuration im Fehlerfall wiederherstellen können.

Die Datei php.ini

Die Datei php.ini ist in mehrere Abschnitte unterteilt, die jeweils bestimmte Funk-tionen konfigurieren. Die Abschnitte beginnen immer mit einem als Sektion aus-geführten Block, der etwa folgendermaßen aussieht:

[Session]

Darin befinden sich mehrere Optionen, denen jeweils ein Wert zugewiesen wird.Vielen Optionen wird lediglich ein »On« oder »Off« zugeordnet, um die betref-fende Funktion ein- oder auszuschalten:

asp_tags = Off

Anderen werden bestimmte Werte zugeordnet:

output_buffering = 4096

Einige Optionen sind auch »ausgeblendet«, indem ein Kommentarzeichen (das istdas Semikolon) davor gestellt ist:

;extension=php_curl.dll

Wenn man diese Option aktivieren möchte, entfernt man einfach das Semikolon.Stört eine Option, setzt man ein Semikolon davor.

Wichtige Konfigurationsschritte

38

Eigentlich muss man bei PHP5 nicht viel konfigurieren. Einige Optionen sinddennoch eine nähere Betrachtung wert, damit später alles funktioniert.

Zuerst sollten Sie PHP5 mitteilen, wo die Erweiterungen zu finden sind, die even-tuell benutzt werden sollen. Die entsprechende Option heißt folgendermaßen:

Page 39: 382726314 X Php5 In 14 Tagen (Ddt)

PHP5 testen

extension_dir = "./"

Tragen Sie hier den Pfad zum ext-Verzeichnis ein:

extension_dir = "C:\Programme\Apache Group\PHP5\ext"

Nun können Sie die Erweiterungen selbst freigeben, die Sie benutzen möchten.Dazu suchen Sie die Liste mit den extension-Optionen. Nehmen Sie, wenn derEintrag bereits vorhanden ist, das Semikolon weg. Um ein PECL-Modul zubenutzten, fügen Sie den entsprechenden Eintrag hinzu. Für dieses Buch sind fol-gende Einträge sinnvoll:

extension = php_gd2.dllextension = php_mysqli.dllextension = php_soap.dll

Sie werden früher oder später weitere benötigen; ergänzen Sie dann die Liste ent-sprechend.

Eine sinnvolle Funktion ist das Hochladen von Dateien. Zur Konfiguration dienenzwei Optionen:

upload_tmp_dir = "C:\Windows\temp"upload_max_filesize = 8M

Die erste teilt PHP mit, in welchem Pfad temporäre Dateien abzulegen sind. Stan-dardmäßig ist diese Option nicht aktiviert. Beachten Sie, dass der Webserver in die-sem Pfad Schreibrechte benötigt. Die zweite Option stellt die Größe der Dateienauf maximal 8 MB ein, der Standardwert 2 MB ist meist zu klein.

Die Verwaltung von Benutzersitzungen spielt in PHP5 eine große Rolle. Damitspäter alles funktioniert, ist auch hier eine entsprechende Änderung erforderlich:

session.save_path = "C:\Windows\temp"

Die Option ist meist auskommentiert.

Dies sind freilich nur einige Optionen, die geändert werden können. An entspre-chender Stelle wird im Buch immer wieder auf diese php.ini zugegriffen.

39

1.7 PHP5 testen

Um nun mit PHP5 arbeiten zu können, muss dem Webserver noch mitgeteilt wer-den, wann und wie PHP-Skripte auszuführen sind. Standardmäßig ist ein Webser-

Page 40: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung

ver ein primitives Stück Software. Er erkennt Anfragen mit dem Protokoll HTTPauf Port 80 (falls so konfiguriert) und führt diese aus. Das heißt, er erkennt mindes-tens die HTTP-Kommandos GET (Anfordern einer Ressource, beispielsweiseeiner Datei) und POST (Anfordern einer Ressource, der zugleich Formulardatenübergeben werden). GET führt standardmäßig dazu, dass der Webserver auf derFestplatte nach der entsprechenden Datei sucht und diese an den Browser sendet.

Damit PHP5 ins Spiel kommt, wird eine spezielle Dateierweiterung benutzt.Theoretisch ist die Wahl freigestellt, eine »sinnvolle« Benennung ist jedoch unbe-dingt erforderlich. Für PHP5 ist dies .php, falls keine andere Version auf demsel-ben Server läuft. Haben Sie parallel noch PHP 4 laufen, ist .php5 eine guteErgänzung. Alle Skripte in diesem Buch tragen die Dateierweiterung .php und las-sen sich nur dann unverändert einsetzen, wenn dies auch im Apache entsprechendkonfiguriert wird.

Konfiguration des Webservers für PHP5

Die Verknüpfung der Dateierweiterung findet wieder in der httpd.conf statt. Letzt-lich wird nur definiert, dass Dateien mit der Endung .php nicht direkt ausgeliefert,sondern zuvor an die ausführbare Instanz von PHP geschickt werden.

Der Apache-Webserver kann PHP5 in zwei Arten bedienen – als CGI (CommonGateway Interface) und als Apache-Modul (mod_php). Es ist für ein Entwicklungs-system dringend zu empfehlen, CGI zu benutzen. CGI ist eine sehr alte und ver-gleichsweise langsame Schnittstelle. Auf einem Entwicklungssystem, wo immernur ein Browser läuft und damit kaum parallele Zugriffe stattfinden, gibt es jedochkeinen Unterschied in der Zugriffsgeschwindigkeit. CGI bietet dafür einen durch-aus ernst zu nehmenden Vorteil. Da bei jedem Aufruf die entsprechende Instanzgestartet wird, die die Anfrage ausführt, werden auch eventuell vorhandene Ände-rungen an der Konfiguration sofort erkannt. Da gerade in der ersten Zeit häufigmit den Konfigurationen gespielt wird, wäre ein ständiges Stoppen und Starten desServers suboptimal. Denn läuft PHP als Modul, wird es nur einmal gestartet undbleibt für jeden folgenden Aufruf aktiv. Änderungen an der PHP-Konfigurationwirken sich damit nicht sofort aus, da diese nur beim Start gelesen wird.

40

Änderungen an der httpd.conf erfordern immer einen Neustart des Web-servers – also des Apache2-Dienstes im Besonderen. Das Betriebssystemselbst muss niemals neu gestartet werden.

Page 41: 382726314 X Php5 In 14 Tagen (Ddt)

PHP5 testen

Tragen Sie nun folgenden Text in die httpd.conf ein (beachten Sie, dass der Skript-Alias hier nur php lautet, Installationen die PHP 4 und PHP5 parallel betreiben,sollten hier einen anderen Namen benutzen):

ScriptAlias /php/ "C:/Programme/Apache Group/php5/"AddType application/x-httpd-php .phpAction application/x-httpd-php "/php/php-cgi.exe"

Änderungen wirken sich erst aus, wenn der Apache-Dienst neu gestartet wird. Diedrei Zeilen verknüpfen die Dateierweiterung .php mit der ausführbaren Datei php-cgi.exe, die Teil der PHP5-Installation ist. Passen Sie die Pfade an, wenn Sie beiden vorhergehenden Schritten nicht exakt den Vorschlägen gefolgt sind.

PHP-Skripte ausführen

Nun ist es an der Zeit, einen ersten Test vorzunehmen. PHP5 stellt eine Funktionbereit, die über die gesamte Konfiguration Auskunft gibt: phpinfo. Diese wirdbenutzt, um das allerkleinste PHP-Skript zu schreiben:

Listing 1.3: info.php – Informationen über PHP5 ermitteln

<?phpphpinfo();?>

Speichern Sie diesen Text unter dem Namen info.php unter htdocs ab. Rufen Siedann im Browser folgende Adresse auf:

http://localhost/info.php

Sie sollten dann in etwa folgende Seite sehen (siehe Abbildung 1.8).

Wenn Sie die Seite weiter nach unten scrollen, finden Sie Angaben zu den instal-lierten Modulen, konfigurierten Umgebungs- und Servervariablen und andereDaten über Ihr PHP5. Wenn Sie Änderungen an der Datei php.ini vornehmen,beispielsweise Erweiterungen hinzufügen, erscheinen hier Angaben über jedeseinzelne Modul. Sie können so sehr schnell kontrollieren, ob die Einstellungen

41

den gewünschten Effekt haben. Stimmt alles, kann es jetzt mit der Programmie-rung richtig losgehen.

Page 42: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung

Wenn es nicht funktioniert

Auch wenn die Installation verhältnismäßig trivial ist, können Fehler passieren.Jeder Computer ist anders konfiguriert und manchmal klappt es einfach nicht. Einpaar typische Fehler zeigt die folgende Liste:

� Fehlermeldung der Art »Socket-Fehler« oder »Adresse 0.0.0.0:80 nicht verfüg-bar« während der Installation des Webservers.

Sie haben einen weiteren Webserver (vermutlich IIS) laufen, der Port 80 ist sobereits belegt. Weisen Sie dem IIS eine andere IP-Adresse, einen anderen Portzu oder deaktivieren Sie ihn ganz. Starten Sie die Apache-Installation erneutmit der Option REPAIR.

Abbildung 1.8: PHP5 läuft auf einem Apache-Webserver

42

� Der Browser zeigt die Fehlermeldung 400 (Bad Request, Falsche Anforde-rung).

Die Verknüpfung zwischen .php und PHP ist in der httpd.conf falsch einge-richtet. Vermutlich stimmt ein Pfad nicht. Versuchen Sie auch nochmals, den

Page 43: 382726314 X Php5 In 14 Tagen (Ddt)

Kontrollfragen

Webserver neu zu starten: START | ALLE PROGRAMME | APACHE HTTP-SER-VER 2.0.50 | CONTROL APACHE SERVER | RESTART.

Alternativ können Sie das Fehlerprotokoll untersuchen. Öffnen Sie START |ALLE PROGRAMME | APACHE HTTP-SERVER 2.0.50 | REVIEW SERVER LOGFILES | REVIEW ERROR LOG. Dort finden Sie hilfreiche Hinweise zu den Vor-gängen im Webserver, beispielsweise folgendes:

[Mon Jul 19 16:03:41 2004] [error] [client 127.0.0.1] script not found or unable to stat: C:/Programme/Apache Group/php5php-cgi.exe

Hier fehlt in der Konfiguration hinter php5 offensichtlich ein Pfadtrennzei-chen.

� Statt der Info-Seite wird der Inhalt der Datei angezeigt.

Auch hier gelangte die Datei nicht bis zu PHP, Apache hat sie einfach als Res-source behandelt und ausgeliefert. Vermutlich ist der Eintrag in der httpd.confnicht vorhanden oder nach dem Eintragen erfolgt kein Neustart.

1.8 Kontrollfragen

1. Wann wurde PHP das erste Mal unter diesem Namen bekannt?

2. Was bedeutet »PHP«?

3. Worauf ist der Name »Zend« zurückzuführen?

4. Anhand welcher Begrenzungszeichen werden PHP-Fragmente in HTML-Codeerkannt?

5. Welches Protokoll wird zur Übertragung von HTML-Seiten vom Server zumBrowser verwendet?

6. Welcher Webserver kann auf allen Betriebssystemen zur Entwicklung eingesetztwerden?

7. Wofür steht der Begriff WAMP?

43

8. Welche Bedeutung hat die Adresse »http://localhost«?

9. Mit welcher Funktion kann PHP5 ausführlich getestet werden?

Page 44: 382726314 X Php5 In 14 Tagen (Ddt)
Page 45: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte

2

Page 46: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte

Die ersten Schritte mit PHP beginnen immer mit HTML. Denn es ist das Zieljeder PHP-Anwendung, HTML-Seiten zu erzeugen. Darauf aufbauend wird dasEinbetten von PHP in HTML behandelt und es werden elementare Programmier-techniken vorgestellt, die in den folgenden Tagen angewendet werden. Als Ab-schluss soll ein Blick auf die Arbeitsweise von HTTP das Zusammenspiel vonWebserver und Browser näher bringen.

2.1 Einfache HTML-Seiten

Ihre Entwicklungsumgebung sollte also in der Lage sein, HTML-Seiten übereinen URL-Aufruf auszuliefern. Das folgende Skript kann als Start benutzt werden:

Listing 2.1: Basis aller Seiten – eine HTML-Struktur

<html><head> <title>Kein Titel</title></head><body> PHP beginnt hier...</body></html>

Diese Seite sollte in einem Verzeichnis des Webservers liegen, das mit dem Brow-ser aufgerufen werden kann, beispielsweise http://localhost/php5Mut/html1.html.Diese Adresse wird im Browser eingegeben, dann sollte sie erscheinen. Erst wenndies funktioniert, lohnt es, mit PHP5 weiterzumachen.

Viele Fehler bei der Entwicklung von PHP entstehen, weil schlechtes oder nichtlauffähiges HTML erzeugt wird. Browser tolerieren zwar falsches HTML, reagie-ren also nicht mit einer Fehlermeldung. Sie zeigen aber im Zweifelsfall gar nichtsoder völlig unsinniges an. Nicht immer ist ein falsches PHP-Skript daran schuld.Wenn beispielsweise vergessen wurde, das <title>-Tag zu schließen, nimmt derBrowser den gesamten Text als Titel und die Seite selbst bleibt weiß. Solche Feh-

46

ler offenbart ein Blick in den Quelltext, beim Internet Explorer am einfachstenüber das Kontextmenü (rechte Maustaste) – QUELLTEXT ANZEIGEN.

Page 47: 382726314 X Php5 In 14 Tagen (Ddt)

Einfache HTML-Seiten

HTML-Refresh

Bevor Sie mit PHP anfangen, sollten Sie ihre HTML-Kenntnisse überprüfen. Die-ser Abschnitt zeigt die wichtigsten Elemente einer HTML-Seite.

Grundstruktur

Jede HTML-Seite hat folgende Grundstruktur:

Listing 2.2: Grundaufbau einer HTML-Seite

<html><head> <title>Titel der Seite</title></head><body></body></html>

Alles, was auf der Seite ausgegeben werden soll, muss zwischen den <body>-Tagsstehen. Im Kopfbereich (<head>) stehen der Seitentitel, JavaScript-Codes und Ver-weise auf Stildateien (CSS).

JavaScript einbinden

Um JavaScript zu nutzen, ist folgendes Tag vorgesehen:

<script language="JavaScript">function MyFunction(){ alert("Tu was");}</script>

Stildateien (Stylesheets) einbinden

47

Stildateien werden über das Tag <link> eingebunden:

<link href="stile/comzept.css" type="text/css"/>

Page 48: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte

Das Attribut href verweist auf den relativen, variablen Pfad zu der Stildatei, wäh-rend das Attribut type auf den Typ festgelegt ist.

Sollen die Stile direkt eingebettet werden, wird dagegen das Tag <style> verwen-det:

<style>*{ color:black; font-weight:normal; text-decoration:none; font-size:9pt; font-family:Verdana;}</style>

Das Sternchen legt die gewählte Formatierung als global fest.

Die wichtigsten Formatier- und Auszeichnungselemente

Die folgende Tabelle zeigt in einer Übersicht die wichtigsten Elemente, die in all-täglichen Seiten (und in diesem Buch) verwendet werden:

Tag Wichtige Attribute Container? Beschreibung

<b>, <strong> ja Fett

<i>, <em> ja Kursiv

<u> ja Unterstrichen

<strike> ja Durchgestrichen

<pre> ja Wie im Text formatiert

<sup> ja Hoch gestellt

48

<sub> ja Tiefer gestellt

<p> wahlweise Absatz

Tabelle 2.1: Wichtige Formatier- und Auszeichnungselemente

Page 49: 382726314 X Php5 In 14 Tagen (Ddt)

Einfache HTML-Seiten

Wenn keine Attribute benannt sind, können dennoch immer die Standardattributebenutzt werden:

� styleEnthält eingebettete Stildefinitionen, beispielsweise color:red;

� classVerweist auf eine Klasse, die in Stildefinitionen der Seite erstellt wurde.

� idVerweist auf eine ID, die in Stildefinitionen der Seite erstellt wurde.

Tabellen und Bilder

Tabellen und Bilder sind auf fast jeder Seite zu finden. Tabellen dienen vor allem

<br> clear="left|right" nein Zeilenumbruch, das Attribut clear bestimmt das Verhalten angrenzender Blöcke

<img> src, width, height, hspace, vspace

nein Fügt ein Bild ein, src bestimmt den Pfad zum Bild

<ul> type ja Liste mit Zeichen als Aufzäh-lungszeichen

<ol> type ja Liste mit Zahlen als Aufzäh-lungszeichen

<li> ja Listelement für <ul> und <ol>

<div> ja Absatz, frei formatierbar

<span> ja Zeichenblock, frei formatierbar

Tag Wichtige Attribute Container? Beschreibung

Tabelle 2.1: Wichtige Formatier- und Auszeichnungselemente (Forts.)

49

der Formatierung. Die Grundstruktur einer Tabelle basiert auf folgenden Tags:

� <table></table>Diese Tags umschließen die Tabelle.

Page 50: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte

� <tr>...</tr>Es folgen Tags für die Definition einer Reihe.

� <td>Inhalt</td>Innerhalb der Reihe stehen diese Tags für jede Zeile.

� <th>Kopf</th>Kopfelemente werden mit einem eigenen Tag definiert. Der Inhalt erscheintfett und zentriert, wenn dies nicht von Stilen überschrieben wird.

Eine einfache Tabelle mit drei mal drei Zellen sieht nun folgendermaßen aus:

Listing 2.3: Einfache Tabelle mit Überschrift

<table border="1"> <tr> <th>Name</th><th>Anschrift</th><th>Telefon</th> </tr> <tr> <td></td><td></td><td></td> </tr> <tr> <td></td><td></td><td></td> </tr></table>

Das Attribut border sorgt für einen einfachen Rand. Meist wird hier 0 eingesetzt,damit die Tabelle nur das Raster für andere Elemente liefert, selbst aber unsichtbarbleibt.

Wichtige Tabellenattribute

Abbildung 2.1: Eine einfache Tabelle

50

Die folgenden Tabellen zeigen die wichtigsten Attribute für die Tags <table> und<td>.

Page 51: 382726314 X Php5 In 14 Tagen (Ddt)

Einfache HTML-Seiten

Vor allem der Umgang mit colspan und rowspan ist nicht ganz einfach. Dabeimuss beachtet werden, dass die Gesamtzahl der Zellen pro Reihe immer gleich ist,damit sich eine konstante Anzahl von Spalten bildet. Andernfalls zerfällt dieTabelle. Das folgende Bild zeigt, wie eine Verknüpfung von Zellen mitcolspan="2" bzw. rowspan="2" aussieht:

Attribut Beispiel Bedeutung

border border="1" Rand in Pixel

cellspacing cellspacing="3" Abstand der Zellen voneinander

cellpadding cellpadding="4" Abstand des Inhalts zum Zellenrand

width width="100%" Breite, entweder in Pixel oder % vom Browserfenster oder der umschließenden Tabelle

height height="477" Höhe, entweder in Pixel oder % vom Browserfenster oder der umschließenden Tabelle

Tabelle 2.2: Einige Attribute für <table>

Attribut Beispiel Bedeutung

bgcolor bgcolor="red" Hintergrundfarbe der Zelle

width width="50" Breite der Zelle, kann nicht kleiner sein als der Inhalt

height height="20" Höhe der Zelle, kann nicht kleiner sein als der Inhalt

colspan colspan="3" Zelle dehnt sich über drei Spalten aus

rowspan rowspan="2" Zelle dehnt sich über zwei Zeilen aus

Tabelle 2.3: Einige Attribute für <td>

51

Abbildung 2.2: Verknüpfte Zellen mit rowspan und colspan

Page 52: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte

Der Code für diese Tabelle sieht nun folgendermaßen aus:

Listing 2.4: Tabelle mit verknüpften Zellen

<table border="1"> <tr> <th>Name</th><th>Anschrift</th><th>Telefon</th> </tr> <tr> <td colSpan="2"></td> <td rowspan="2"></td> </tr> <tr> <td></td> <td></td> </tr></table>

In der zweiten und dritten Zeile sind nur zwei <td>-Elemente enthalten, weil sichdurch die Verknüpfungen die erforderliche Anzahl von drei Zellen ergibt. Dasheißt, die Angabe <td> steht für eine Zelle, während <td colspan="2"> für zweiZellen steht. In die folgende Zeile ragt die mit rowspan verknüpfte Zelle der zwei-ten Zeile hinein, deshalb kann (und muss) die dritte Zelle hier entfallen.

Formulare

Formulare sind der dritte große Komplex in HTML. PHP bietet eine weitläufigeUnterstützung für die Verarbeitung von Formulardaten. Deshalb ist eine guteKenntnis der Formularelemente unerlässlich.

Ein Formular erstellen

Ein Formular entsteht, indem Formularelemente neben normalem HTML inner-halb des Tags <form> gestellt wird.

52

Die Formularelemente

Die folgende Tabelle zeigt alle Formularelemente und die wichtigsten Attribute:

Page 53: 382726314 X Php5 In 14 Tagen (Ddt)

PHP einbetten

Die Benutzung wird an anhand vieler Beispiele im Buch dokumentiert, sodasseine ausführliche Darstellung an dieser Stelle nicht sinnvoll erscheint. SollteIhnen das eine oder andere Tag gänzlich unbekannt erscheinen, konsultieren Siedie entsprechende Fachliteratur zum Thema HTML.

2.2 PHP einbetten

Bislang wurde der Text in den HTML-Seiten statisch ausgegeben. Nach diesenGrundlagen ist es nun an der Zeit, PHP5 zum Leben zu erwecken. Die folgendenBeispiele zeigen, wie das mit PHP funktioniert. Die genaue Arbeitsweise wird imLaufe des Tages noch genauer erläutert. Probieren Sie die Beispiele erst aus.

Element type-Attribut Attribute Nutzen, Anwendung

input text value, size, name Textfeld, einzeilig

button value, name Schaltfläche

submit value, name Absendeschaltfläche

reset value, name Rücksetzschaltfläche, leert das For-mular

file name Dateihochladen

checkbox value, name, checked Kontrollkästchen

radio value, name, checked Optionsfeld

select - size, multiple Listbox, Klappfeld

textarea - Großes Textfeld, statischer Text steht zwischen den Tags (Container)

53

Wie PHP den Code erkennt

Wenn der Browser eine PHP-Seite beim Webserver anfordert, erkennt dieser ander Dateierweiterung, dass PHP für die Verarbeitung zuständig ist. Der Webserverübergibt dem PHP-Modul die Information, welche Seite verarbeitet werden soll.

Page 54: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte

Das Modul lädt diese Seite dann von der Festplatte und liest den Inhalt Zeile fürZeile ein. In einem mehrstufigen Prozess entstehen daraus Zwischencodes, dieinterpretiert werden. Auf die genauen Zusammenhänge wird noch genauer einge-gangen. Bei diesem Vorgang sucht das PHP-Modul gezielt nach bestimmten PHP-Codes. Der erste ist die Eröffnung eines Code-Blocks:

<?php

Der zweite wichtige Code ist das Ende eine Code-Blocks:

?>

Die Syntax lehnt an XML an und würde im XML-Dialekt als so genannte Prozess-anweisung verstanden werden. Baut man Seiten mit XHTML (der XML-Darstel-lung von HTML), so stört der eingebettet PHP-Code die Syntax der Seite nichtund erlaubt weiterhin die Mischung von PHP und (X)HTML. Es gibt noch andereErkennungszeichen, die PHP zulässt. Das Gezeigte ist jedoch das einzige praxist-augliche und wird deshalb exklusiv vorgestellt.

Wie viele solcher Blöcke in der Seite stehen, spielt kaum ein Rolle. Man kannzwar theoretische Erwägungen darüber anstellen, ob die mehrfache Eröffnung vonCode-Blöcken auf der Seite Leistung kostet, in der Praxis ist das aber kaum rele-vant, weil viele andere Teile der Verarbeitungsmaschine einen weitaus gewichtige-ren Einfluss nehmen.

Jeder Text, der außerhalb der Blöcke steht, wird unverändert ausgegeben. Diesbetrifft normalerweise den HTML-Anteil. Da die Verarbeitung von oben nachunten erfolgt, wird die Seite genau so gesendet, wie sie aufgebaut ist. Es ist nunSache der eingebetteten Codes, an den richtigen Stellen vernünftige Ausgaben zuerzeugen.

Listing 2.5: PhpStart1.php: Eine einfache Ausgabe

<html><head> <title>PhpStart1</title></head><body>

54

<?php echo "PHP beginnt hier..."; ?></body></html>

Page 55: 382726314 X Php5 In 14 Tagen (Ddt)

Ausgaben erzeugen

Innerhalb der Code-Blöcke folgt nun PHP-Anweisung auf PHP-Anweisung. Dieskann sehr verschieden aussehen, weil PHP im Umgang mit Code sehr flexibel ist.Zur besseren Lesbarkeit schreibt man in der Regel immer genau eine Anweisungpro Zeile oder teilt sehr lange Konstrukte sogar auf. Mehrere Anweisungen aufeiner Zeile gehört zu den Dingen, die man als Profi unbedingt vermeidet.

2.3 Ausgaben erzeugen

Auch einfachste PHP-Skripte geben Daten aus. Die erzeugten Zeichen werden ander Stelle in die HTML-Ausgabe eingebaut, wo der entsprechende Ausgabebefehlsteht. PHP kennt hierfür mehrere Techniken:

� Die Ausgabeanweisung echo

� Die Ausgabefunktion print und einige spezialisierte Versionen, wie printf

� Die verkürzte, direkte Ausgabe allein stehender Variablen

Die Ausgabe mit echo

Die Anweisung echo gibt die übergebenen Argument direkt aus. Im Gegensatz zuFunktionen wie print müssen hier keinen Klammern gesetzt werden. Solange esnur ein einziges Argument gibt, darf dieses aber in runde Klammern gestellt wer-den. Diese Schreibweise ist jedoch unüblich und sollte auch deshalb vermiedenwerden, um die Natur als Sprachanweisung im Code herauszuheben.

Außerdem verkraftet echo beliebig viele Argumente, die durch Kommata getrenntwerden. Listing 2.6 zeigt die möglichen Varianten:

Listing 2.6: EchoVariants.php – Ausgaben mit der Anweisung echo

<?phpecho("Hallo PHP5");echo "<br />";

55

echo "Mit ", "mehreren ", "Argumenten";?>

Freilich wird in der Praxis nicht nur eine einfache Zeichenfolge ausgegeben, son-dern auch das Ergebnis einer Berechnung oder der Inhalt einer Variable.

Page 56: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte

Variablen ausgeben

Variablen sind benannte Speicherstellen für Daten. Am vierten Tag wird diesesThema ausführlich behandelt. Einstweilen sei nur auf die grundlegende Syntaxhingewiesen: $text ist eine Variable mit dem Namen »text«. Für PHP ist sieerkennbar durch das vorangestellte $-Zeichen.

Eine solche explizite Erkennungsmöglichkeit erlaubt es, Variablen auch mitten inanderem Text zu erkennen. Das funktioniert tatsächlich, sodass Sie auch folgendesschreiben können:

Listing 2.7: EchoVariable1.php: Variable in einer Zeichenfolge erkennen

<?php$version = 5;echo "Hallo PHP Version $version";?>

Die Variable $version wird anhand des $-Zeichens erkannt. Damit das Ende kor-rekt gefunden wird, muss entweder ein Leerzeichen folgen oder die ganze Kon-struktion in geschweifte Klammern gepackt werden:

Listing 2.8: EchoVariable2.php: Variable in einer Zeichenfolge schützen

<?php$version = 5;echo "Hallo PHP Version {$version}";?>

Solche Ausgaben sind sehr häufig und typisch in PHP. Die Möglichkeit, Variablenin Zeichenketten einzubetten, ist deshalb außerordentlich hilfreich. Sie spart jedeMenge Verkettungen und Aufreihungen von Parametern und macht Ihre Pro-gramme lesbarer und einfacher.

Abbildung 2.3: Ausgabe mit echo

56

Nun kann es manchmal vorkommen, dass dieses Verhalten nicht gewünscht ist.Sie haben es sicher schon bemerkt: Zeichenfolgen werden in Anführungszeichengepackt. Statt der gezeigten doppelten sind in PHP auch einfache zulässig. Wer-den diese verwendet, versucht PHP nicht mehr, Variablen zu erkennen und auszu-werten. Das folgende Beispiel zeigt den Effekt:

Page 57: 382726314 X Php5 In 14 Tagen (Ddt)

Ausgaben erzeugen

Listing 2.9: EchoVariable3.php – So werden Variablen nicht ausgewertet

<?php$version = 5;echo 'Hallo PHP Version $version';?>

In der Bildschirmausgabe erscheint der Text unverändert:

Ausgabe von großen Textmengen

Die Möglichkeit, PHP und HTML zu vermischen, ist ein schneller und einfacherWeg dynamische Webseiten zu erstellen. Leider führt diese Vermischung zu sehrschlecht lesbaren Programmen. Solange Sie mit Trivialskripten wie in diesemKapitel arbeiten, fällt das nicht auf. Später jedoch – wenn richtige Projekte an derTagesordnung sind – wird der Code schnell unleserlich. Damit schleichen sichFehler ein. Für andere Entwickler, die mit dem Code arbeiten sollen, wird es teil-weise unmöglich oder zumindest zeitraubend, sich einzuarbeiten.

Vor diesem Hintergrund entstand eine ansatzweise aus der Sprache Perl übernom-mene Technik, längere Textblöcke auszugeben – »heredoc« genannt. Dabei wer-den der Anfang und das Ende in einer speziellen Weise gekennzeichnet. PHP liesteinen solchen Block, der auch viele Zeilen umfassen kann, als eine geschlosseneZeichenfolge. Die bereits gezeigte Erkennung von Variablen und die Kennzeich-nung derselben mit geschweiften Klammern steht auch hier zur Verfügung. Dasfolgende Beispiel zeigt die Anwendung:

Listing 2.10: EchoHeredoc.php – Ausgabe von Textblöcken innerhalb von PHP-Code

<?php$version = 5;$text = <<<BLOCK1

Abbildung 2.4: Variablen werden in einfachen Anführungszeichen nicht erkannt

57

<h3>Wir lernen PHP in 14 Tagen</h3> Dieses Buch vermittelt PHP{$version} in nur 14 Tagen, von Grund auf und verständlich.BLOCK1;

Page 58: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte

echo "$text";?>

Der Abschnitt, der ausgegeben werden soll, beginnt mit <<<NAME, wobei »NAME«eine frei wählbare Zeichenfolge ist. Er endet, wenn dieser Name allein stehend aufeiner Zeile erscheint. Wichtig ist dabei: Es werden alle Zeichen auf der Zeile aus-gewertet, der Abschnittsname muss deshalb am Anfang der Zeile stehen – ohneirgendwelche Leerzeichen und Tabulatorschritte davor. Der folgende Fehlererscheint, wenn das Endkennzeichen des Abschnitts nicht erkannt wurde:

Parse error: parse error, unexpected $end in D:\Inetpub\wwwroot\buecher.site\php5MuT\EchoHeredoc.php on line 18

Bei $end handelt es sich um einen Hinweis auf einen so genanntenToken. Token stellen Code-Fragmente bei der internen Verarbeitungdar. Die Angaben sind hilfreich bei der Fehlersuche. In diesem konkre-ten Fall wurde das Ende des Dokuments erreicht, obwohl dies aufgrundder vorangegangenen Codes nicht zu erwarten war.

Variablen werden erkannt, wenn Sie in der im letzten Abschnitt gezeigten Weiseerkennbar sind.

Die geschweiften Klammern sollten Sie sich gleich zu Beginn angewöhnen, auchwenn sie bei einfachen Beispielen nicht zwingend notwendig sind. Es spart späterÄrger mit komplexeren Ausdrücken, die PHP nicht mehr ohne sie erkennen mag.

Vielfältige Formatierungen mit print und Verwandten

Bei der Ausgabe von Daten stehen Sie oft vor dem Problem, dass diese nicht so vor-

Abbildung 2.5: Ausgabe von HTML mit Here-doc-Syntax aus PHP-Code heraus

58

liegen, wie es die Anzeige erfordert. »Formatierung« heißt das Zauberwort, mitdem diese dann auf den richtigen Weg gebracht werden müssen.

Zuerst noch mal eine Ausgabemöglichkeit, ohne weitere Veränderungen am Para-meter, die bereits am ersten Tag kurz benutzt wurde: print. Dies ist – von derinternen Programmierung her – zwar auch eine Sprachanweisung, verhält sich

Page 59: 382726314 X Php5 In 14 Tagen (Ddt)

Ausgaben erzeugen

aber im Gegensatz zu echo immer wie eine Funktion. Funktionen geben Datenzurück, wenn sie in Ausdrücken verwendet werden. Auf diese Feinheiten wird inden nächsten Tagen noch genau eingegangen. An dieser Stelle ist es nur wichtigzu wissen, dass Sie mit print Daten ausgeben können. Der Aufruf gibt, wenn dieAusgabe gelang, den Wahrheitswert TRUE (Wahr) zurück. Meist wird dies jedochnicht ausgewertet und deshalb erscheint print in einem sehr einfachen Kontext:

Listing 2.11: Print.php – Einfache Ausgabe von Daten mit print

<?php$version = 5;print ("Hallo PHP Version {$version}");?>

Der Aufruf ist nur mit genau einem Argument erlaubt, eine Verkettung mit Kom-mata wie bei echo funktioniert nicht. Der Einsatz von print ist dann vorzuziehen,wenn der Kontext Rückgabewerte erwartet. An den entsprechenden Stellen imBuch wird darauf hingewiesen, ansonsten wird durchgehend echo verwendet.

Formatierung von Ausgaben

PHP und HTML sind im Zusammenspiel sehr leistungsfähig. Ein typischerAnwendungsfall besteht im dynamischen Erzeugen von HTML-Tags. Stellen Siesich vor, eine Navigation aufzubauen, bestehend aus mehreren Hyperlinks, dieetwa folgendermaßen aussehen:

<a href="ziel_1.php" target="_self">Seite 1</a>

Drei Elemente dieses Tags könnten variabel sein: Die Attribute href und target undder Inhalt des Tags. Folgende Variante ist funktionsfähig, aber nicht gut lesbar:

<?$href = "ziel_1.php";$target = "_self";$content = "Seite 1";echo '<a href="', $href, '" target="', $target, '">';echo $content;

59

echo '</a>';?>

Die Funktion printf ist hier ausgesprochen hilfreich, denn sie erlaubt die Erstel-lung einer Formatieranweisung und die getrennte Übergabe von Parametern. Dies

Page 60: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte

erhält die Lesbarkeit des HTML-Tags (oder können Sie die erste echo-Anweisungdes Beispiels auf Anhieb lesen?)

Listing 2.12: Printf.php: So werden dynamische HTML-Konstrukte lesbar

<?php$href = "ziel_1.php";$target = "_self";$content = "Seite 1";printf('<a href="%1$s" target="%2$s">%3$s (%1$s)</a>', $href, $target, $content);?>

Die merkwürdigen Zeichenfolgen wie %1$s werden als Formatanweisungenbezeichnet. Das %-Zeichen leitet sie ein und die nachfolgende Ziffer bestimmt,welches der folgenden Argumente als Datenquelle genommen werden soll. %1nimmt sich also den Inhalt der Variablen $href. Nach der Zahl folgt eine Format-regel. $s steht für die Ausgabe einer Zeichenkette.

Eine genauere Betrachtung der Parameter erfordert etwas mehr Lernaufwand.printf gehört mit zu den komplexeren Ausgabemethoden. Die Mühe lohnt sichbereits ganz am Anfang, denn der Programmierung der Benutzerschnittstelle mussimmer große Aufmerksamkeit gewidmet werden.

Die Formatregeln der Funktion printf genauer betrachtet

Jede einzelne Formatregel hat folgenden Aufbau:

%N$?

Dabei steht der Buchstabe N für eine Zahl, die die Nummer des Argumentsangibt. Nach dem Trennzeichen $ folgt ein weiterer Buchstabe, der bestimmt, wie

Abbildung 2.6: Professionell dynamisch erzeugter Link

60

die Daten formatiert werden. Dazwischen können, je nach Aufgabenstellung, wei-tere Formatierzeichen folgen:

%N$F-D?

Page 61: 382726314 X Php5 In 14 Tagen (Ddt)

Ausgaben erzeugen

F steht für ein Füllzeichen, entweder ein Leerzeichen oder eine »0« (Null).Andere Zeichen müssen mit einem Apostroph eingeleitet werden. Es folgt – eben-falls optional – ein Minuszeichen, wenn die Füllzeichen linksbündig aufgefülltwerden sollen. Ohne Angabe wird rechts aufgefüllt.

Werden Zahlen ausgegeben, kann mit der weiteren Angabe D noch bestimmt wer-den, wie viele Dezimalstellen erzeugt werden. Nebenbei rundet printf imBedarfsfall mathematisch korrekt.

Mit diesen Angaben lassen sich schon recht ansprechende Ergebnisse erzeugen,beispielsweise für die Generierung von HTML-konformen Farbangaben:

Listing 2.13: Printf2.php – Erzeugen HTML-konformer Farben aus Dezimalzahlen

Formatcode Bedeutung und weitere Parameter

b Zahlen werden binär ausgegeben (als Folge von »0« und »1«) (b = binary)

c Eine Zahl als Parameter wird als Zeichencode interpretiert (c = character)

d Zahlen werden als Dezimalwert mit Vorzeichen ausgegeben (d = decimal)

u Zahlen werden als Dezimalwert ohne Vorzeichen ausgegeben (u = unsig-ned)

f Zahlen werden als Gleitkommawert ausgegeben (f = float)

o Zahlen werden als Oktalzahl (Basis 8) ausgegeben (o = octal)

s Zeichenfolgen (s = string)

x und X Zahlen werden hexadezimal ausgegeben, x erzeugt kleine Buchstaben, X große (x = heXadecimal)

% Gibt das Prozentzeichen selbst aus

Tabelle 2.4: Formatierte Ausgabe

61

<?php$r = 204;$g = 102;$b = 153;printf('<font color="#%1$X%2$X%3$X">Farbausgabe mit printf</font>',

Page 62: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte

$r, $g, $b);?>

Da HTML die Angabe von RGB-Farbwerten mit Hexadezimalzahlen erwartet,bietet sich die Option $X an, wie im letzten Beispiel gezeigt. Der erzeugte HTML-Code sieht nun folgendermaßen aus:

<font color="#CC6699">Farbausgabe mit printf</font>

CC, 66 und 99 sind die zu den Dezimalzahlen 204, 102 und 153 passenden Hex-Werte.

2.4 Professionelles Programmieren

Nach den ersten Fertigkeiten, die das Skript überhaupt erstmal zur Ausgabe vonDaten bewegen, sollten Sie sich mit bestimmten Programmierstilen vertrautmachen. Diese Techniken und Methoden helfen, später den Überblick zu behal-ten und besseren Code zu schreiben. Der Begriff »besser« bedeutet hier: schnellerlesbar, einfacher wartbar, zuverlässiger, stabiler, schneller.

Code auf möglichst kleinem Raum unterzubringen und der verbreiteteStolz, ein Problem mit noch weniger Zeilen gelöst zu haben als irgend-jemand anderes, ist unprofessionell und dumm, die künstliche Verkom-plizierung von Code ist amateurhaftes Gehabe.

Nicht nur für die Nachwelt: Kommentare

Kommentare sind ebenso wichtig wie guter Code, oder andersherum, kein guterCode ohne gute Kommentare. Dabei sollten Kommentare nie beschreiben, wieder Code arbeitet, sondern was er tut. Denn gute Entwickler können sehr wohlfremden Code lesen und nachvollziehen. Jeden Gedankengang des ursprüngli-chen Programmierers nachzuvollziehen und den Zusammenhang zu anderen,nicht offensichtlich erkennbaren Programmteilen herzustellen, ist ungleich auf-

62

wändiger.

Page 63: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelles Programmieren

Warum Sie Kommentare schreiben sollten

Kommentare sollten Sie unbedingt auch dann schreiben, wenn Sie nur für sichselbst entwickeln. Denn einige Wochen oder Monate später fällt es auch erfahre-nen Entwicklern schwer, sich noch an die eigenen Überlegungen zu erinnern.Einfache Wartungsaufgaben, die Kunden verständlicherweise einfordern, geratenso zu einer störenden Mammutaufgabe, die zudem unpassenderweise immerparallel zu einem neuen, spannenden Projekt anfallen.

Nicht zuletzt sind Kommentare auch dann hilfreich, wenn man Code nur für denEigenbedarf entwickelt. Später, wenn Sie mehr Erfahrung haben, lesen Sie IhrenCode anders und können ihn schneller und professioneller überarbeiten.

Mit Hilfe externer Werkzeuge können aus entsprechend aufgebauten Kommenta-ren automatisch Quellcode-Dokumentationen erstellt werden. Dies ist hilfreich,wenn Bibliotheken verkauft werden sollen oder andere Entwickler Teile des Pro-jekts übernehmen.

Kommentare in PHP5

PHP5 kennt alle Kommentarzeichen, die schon seit der ersten Version mit dabeisind. Änderungen gab es hier nicht. Der Stil ist weitgehend aus der Programmier-sprache C übernommen worden. Man unterscheidet Kommentarzeichen, die nurfür eine Zeile gelten und wo die Kommentare am Zeilenende aufhören und sol-che Kommentare, die mit einem speziellen Endzeichen beendet werden müssen.

Einfache Zeilenkommentare werden folgendermaßen geschrieben:

<?php// Hier beginnt der zweite Teil der Formularauswertung?>

Die beiden Schrägstriche leiten den Kommentartext ein, alles weitere bis zumnächsten Zeilenumbruch wird vom PHP-Parser ignoriert.

Alternativ ist auch ein Doppelkreuz als einleitendes Kommentarzeichen möglich,die Anwendung ist jedoch bestenfalls die »zweite Wahl«:

63

<?php# Hier beginnt der zweite Teil der Formularauswertung?>

Page 64: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte

Die Wirkung ist identisch mit der ersten Variante. Beide können auch mitten aufder Zeile beginnen und gelten dann ab dort:

<?phpecho "Ausgabe"; // Nur zum Test?>

Der Parser führt den PHP-Code am Zeilenanfang aus und ignoriert den Rest derZeile. Die Schreibweise mit den Schrägstrichen entspricht der in C üblichen undist weiter verbreitet. Sie sollten aus stilistischen Gründen entweder generell nureine Version verwenden oder eine ganz klare Vereinbarung (mit sich selbst) tref-fen, unter welchen Umständen welche Kommentarzeichen verwendet werden.

Neben den einzeiligen gibt es auch mehrzeilige Kommentare. Diese beginnenimmer mit den Zeichen /* und enden mit */. Sie werden oft eingesetzt, um anden Anfang der Seite einen längeren Abschnitt mit Informationen über Programmund Autor zu setzen. Ebenso dienen Sie häufig zum so genannten »Auskommen-tieren«. Dabei werden zu Testzwecken ganze Programmteile als Kommentar mar-kiert und so versteckt. Indifferente Fehler lassen sich damit besser einkreisen. Aberes gibt kleine Fallen beim Umgang mit Kommentaren. Betrachten Sie als (negati-ves) Beispiel den folgenden Code:

<?php$c = sin($a) * cos($b); /* Berechnung Term C15 */$d = $c * PI; /* Berechnung Term C16 */?>

Wenn Sie diesen Abschnitt komplett auskommentieren möchten, müssten Sie fol-gendes schreiben:

<?php/*$c = sin($a) * cos($b); /* Berechnung Term C15 */$d = $c * PI; /* Berechnung Term C16 */*/?>

Das funktioniert freilich nicht, weil der erste Kommentar am Ende der zweitenZeile aufhört und die zweite Zeile ($d usw.) ausgeführt wird. Deshalb sind zwei

64

verschiedene Kommentarzeichen sehr hilfreich, denn hätte man gleich den Zei-lenkommentar verwendet, hätten die Kommentarzeichen ihre Wirkung nicht ver-fehlt:

Page 65: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelles Programmieren

<?php/*$c = sin($a) * cos($b); // Berechnung Term C15$d = $c * PI; // Berechnung Term C16*/?>

Benennungsregeln

Neben den Kommentaren gibt es auch Regeln für die Benennung von Variablen,Klassen, Methoden und Funktionen. Dies gilt nicht nur für die Schreibweise, son-dern auch die Art und Weise der Namensvergabe. Lesbare, sinnvolle und eindeu-tige Namen sind nicht immer einfach zu finden, zeichnen jedoch guten undprofessionellen Code aus.

Regeln aus der PEAR-Welt

Fest zu PHP gehört eine große Sammlung an Bibliotheken und Skripten, die immerwiederkehrende Aufgaben erledigen: PEAR (PHP Extension and Application Repo-sitory). Da PEAR-Code weitgehend in PHP geschrieben wird und viele HundertEntwickler dazu beisteuern, wurde dafür eine Vorgabe entworfen, welchen Stil derCode haben muss, damit alle anderen damit problemlos arbeiten können. DieseVorgaben sind sehr sinnvoll und sollten – mangels anderer Richtlinien – für allemittleren und größeren PHP-Projekte gelten. Kleine Testskripte und andere »Ein-zeiler« profitieren davon freilich nur bedingt, sodass man es hier etwas lockerersehen kann. In der PEAR-Welt heißen diese Vorgaben »Coding Standards«.

PHP Coding Standards

PHP ist eine so genannte Klammersprache, Blöcke zusammengehörigen Codeswerden dabei durch geschweifte Klammern markiert. Es liegt in der Natur derSache, dass diese Blöcke oft tief verschachtelt werden. Um zusammengehörendeKlammern halbwegs lesbar zu behalten, werden die innen liegenden Codes einge-

65

rückt. Der Coding Standard schreibt eine Einrücktiefe von vier Zeichen und dieVerwendung von Leerzeichen vor. Editoren sollten so eingestellt werden, dass siebei Druck auf die (TAB)-Taste vier Leerzeichen erzeugen.

Page 66: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte

Auch die Zeilenlänge hat Einfluss auf die Lesbarkeit. Mehr als 85 Zeichen sindschlecht darstellbar, wenn in modernen Entwicklungsumgebungen noch Werk-zeugkästen und Symbolleisten auf dem Bildschirm platziert werden müssen. PHPist sehr tolerant in Bezug auf Zeilenumbrüche, deshalb ist eine »Verteilung« vonCode auf mehrere Zeilen besser als endlose Codeschlangen.

Die Bedeutung der Blöcke mit geschweiften Klammern wurde bereits angespro-chen. Auch deren Schreibweise ist vorgegeben. Sie sollten überdies immer gesetztwerden, auch wenn dies im konkreten Kontext nicht zwingend erforderlich ist. EinBeispiel zeigt dies:

<?phpif ($a < $b) echo $a;?>

Hier wird die Ausgabe von $a nur dann erfolgen, wenn die Bedingung erfüllt ist.Möglicherweise wollen Sie zu Testzwecken auch die andere Variable $b ausge-ben. Im Eifer des Gefechts steht dann folgendes im Skript:

<?phpif ($a < $b) echo $a; echo $b;?>

Das ist zwar nett eingerückt, nur interessiert das PHP kaum. Ohne Klammernwirkt if nur auf eine einzige Anweisung. Deshalb würde der erste Versuch besserfolgendermaßen aussehen:

<?phpif ($a < $b) { echo $a;}?>

Die Klammern umschließen den Block eindeutig und eine Erweiterung ist unpro-blematisch. Das Beispiel zeigt auch, dass die schließende Klammer eine Zeile fürsich allein einnimmt. Noch besser lesbar, aber weniger verbreitet, ist es, auch die

66

öffnende Klammer allein auf eine Zeile zu setzen. Dies betont die Verschachte-lungsebene noch intensiver:

<?phpif ($a < $b){

Page 67: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelles Programmieren

echo $a;} else { // Tu nichts!}?>

Falls ein Block endet und aus technischen Gründen keine Klammer möglich ist,fügen Sie einfach eine Leerzeile hinzu. Dies ist beispielsweise beim switch-Befehlmöglich:

<?phpswitch (condition) {case 1: action1; break;

case 2: action2; break;

default: defaultaction(); break;

}?>

Jedes break wird hier von einer Leerzeile abgeschlossen.

Funktionsaufrufe – egal ob zu internen oder selbst definierten Funktionen – wer-den immer folgendermaßen geschrieben:

$a = funktion($parameter, $parameter2);

Dabei gilt, dass vor und nach dem Gleichheitszeichen jeweils ein Leerzeichensteht. Die runde Klammer nach dem Funktionsnamen folgt ohne Leerzeichenund nach jedem Komma in der Parameterliste steht wiederum ein Leerzeichen.

67

Das schließende Semikolon folgt wieder ohne Pause.

Manchmal kommt es vor, dass sehr viele Zuweisungen dieser Art hintereinanderstehen. Dann kann man diese nach den Gleichheitszeichen ausrichten, um dieLesbarkeit weiter zu verbessern:

Page 68: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte

$antonia_name = funktion($parameter, $parameter2);$bert = funktion($parameter3);

Zu den Standards gehört übrigens auch, sich bei der Markierung von Code-Blö-cken auf <?php ... ?> zu beschränken und keine der anderen Varianten zu benut-zen.

Zuletzt noch ein Tipp für den Kopf eines Skripts, der Informationen zum Pro-gramm, Autor und der Lizenz enthalten sollte:

<?php/* vim: set expandtab tabstop=4 shiftwidth=4: */// +---------------------------------------------------------------+// | PHP version 4 |// +---------------------------------------------------------------+// | Copyright (c) 1997-2003 The PHP Group |// +---------------------------------------------------------------+// | This source file is subject to version 2.0 of the PHP license,|// | that is bundled with this package in the file LICENSE, and is |// | available through the world-wide-web at |// | http://www.php.net/license/2_02.txt. |// | If you did not receive a copy of the PHP license …unable to |// | obtain it through the world-wide-web, please send a note to |// | [email protected] so we can mail you a copy immediately. |// +---------------------------------------------------------------+// | Authors: Original Author <[email protected]> |// | Your Name <[email protected]> |// +---------------------------------------------------------------+//// $Id$?>

Die erste Zeile steuert die Anzeige im Unix-Editor VIM, was vermutlich nur eineMinderheit wirklich tangiert.

Namenskonventionen

Ob Sie deutsch oder englisch schreiben, bleibt Ihnen überlassen. Hier sollte das

68

potenzielle Zielpublikum beachtet werden. Wer seine Skripte im Internet anbie-tet, sollte keine deutschen Namen verwenden. Für den Eigenbedarf ist es völlig inOrdnung und weit besser, als unpassende oder falsche englische Begriffe zu ver-wenden, was eher peinlich ist.

Page 69: 382726314 X Php5 In 14 Tagen (Ddt)

Webserver und Browser

Variablen- und Funktionsnamen unterliegen keiner zufälligen Namensgebung.Sie sollten bedenken, dass Variablen immer Werte enthalten, also Zustände dar-stellen. Sie werden deshalb mit Nomen bezeichnet: $name, $laenge, $content usw.

Die Namenskonventionen sind strenger als die von PHP selbst geforder-ten Eigenschaften der Namen, wie sie am nächsten Tag beschriebenwerden. Es sind also »freiwillige« Vereinbarungen, die helfen, für Men-schen lesbaren Code zu erzeugen.

Funktionen führen dagegen Code aus, sie »tun« also etwas. Folgerichtig nutzenSie Verben zur Benennung, gegebenenfalls um ein Objekt ergänzt: $save(),$load() oder auch $ladeDaten(). Namensteile werden durch Großbuchstabengetrennt, wobei der erste Buchstabe klein ist. Ist die Zuordnung zu einem Modul(Package in der PEAR-Welt genannt) wichtig, wird der Modulname davor gestelltund durch Unterstrich getrennt: HTML_getData().

Klassennamen beschreiben den Sinn der Klasse: Log, HTML_Error_Resolver. Siebeginnen immer mit einem Großbuchstaben und trennen Wörter mit Unterstri-chen. In der Regel handelt es sich um Nomen bzw. um substantivierte Verben.

Klassenmitglieder unterscheiden sich nicht von den allgemeinen Regeln, Metho-den entsprechen Funktionen und Eigenschaften einfachen Variablen, da sieinnerhalb der Klasse dieselbe Aufgabe übernehmen. Es ist üblich, private Mitglie-der von Klassen mit einem führenden Unterstrich zu benennen: $_content. DieRegel stammt aus den PHP 4-Zeiten und ist nicht zwingend bei PHP5, weil eseinen speziellen Modifizierer gibt: private. Wie Sie es verwenden, sollten Sie proProjekt selbst festlegen und diese Regel dann konsequent durchhalten.

Konstanten werden immer in Großbuchstaben geschrieben. Auch hier gilt: Sindsie Teil eines Moduls, wird der Modulname davor gesetzt und durch Unterstrichgetrennt: DB_INFO, XML_ERROR, WIDTH, HEIGHT.

2.5 Webserver und Browser

69

Webserver und Browser sind die beiden Programme, die bei der Bereitstellungund Nutzung von Webseiten eine herausragende Rolle einnehmen. Sie verständi-gen sich mit Hilfe des Protokolls HTTP miteinander. Dieser Abschnitt führt kurzin Grundlagen und Prinzipien ein – Handwerkszeug für künftige PHP-Entwickler.

Page 70: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte

Prinzip des Seitenabrufs

Zwischen Browser und Webserver läuft ein streng reglementiertes Protokoll:HTTP. Die Abkürzung steht für Hypertext Transfer Protocol und sagt bereits, wozues dient: dem Übertragen von durch Hyperlinks verknüpften Seiten. Bevor jedochdie erste Seite im Browser erscheint, passiert einiges auf dem Weg zum und natür-lich im Server.

Der ganze Ablauf startet mit der Eingabe der Adresse der Seite im Browser. DerBrowser nutzt nun Betriebssystemfunktionen, um eine Verbindung mit demServer herzustellen. Der erste Schritt besteht in der Auflösung der Adresse. Internwird auf Datenebene die Protokollkombination TCP/IP benutzt. Sender undEmpfänger einer Botschaft benötigen hier eine eindeutige IP-Adresse der Art134.15.211.65. Weil sich derartige Adressen schlecht merken (und vermarkten) las-sen, wurde das Domain Name System (DNS) entwickelt. Es basiert auf demgleichnamigen DNS-Protokoll und verwendet DNS-Server, die Zuordnungen zwi-schen Adressen und Domain-Namen speichern. Diese Server bilden im Interneteine Hierarchie, wobei der Ausgangspunkt von etwas mehr als einem DutzendRoot-Server gebildet wird. Diese Server lösen die Toplevel-Namen, wie »com«oder »de« auf. Erhält der Browser also eine ihm unbekannte Domain, fragt er dennächsten erreichbaren Name-Server nach der Adresse. Dies ist der Server, den derProvider beim Aufbau der Verbindung ins Internet zugewiesen hat. Große Firmenbetreiben manchmal auch eigene Name-Server. Der Name-Server wird freilichnicht alle Millionen Adressen und deren IP-Nummern kennen. Also fragt er einender Root-Server an, ob diesem eine verwaltende Instanz für das benötigte Toplevelbekannt ist. Der Root-Server verweist auf einen der Landes-Server oder den Ver-walter der generischen Level. Endet die Adresse auf »de« ist nic.de zuständig,betrieben durch das DENIC in Karlsruhe.

War die vollständige Adresse www.comzept.de, wird nun beim DENIC nach demBetreiber der Domain »comzept.de« gefragt. Diese Anfrage verweist auf einen wei-teren Name-Server, nämlich dem für comzept.de zuständigen. Dies ist entwederein Server in der betreffenden Firma oder bei deren Provider. Dieser Name-Server(der vierte in der Kette), hat nun seinerseits nur eine kleine Liste der Server-Adres-sen und deren Namen zu verwalten, im Beispiel also www und dazu die passenden

70

IP-Adresse. Diese Information wird über das DNS-Protokoll dem zuerst anfragen-den Name-Server übermittel und gelangt von dort zum Browser. Der Browserbesitzt seinerseits eine eindeutige Adresse, die vom Provider beim Verbindungsauf-bau vergeben wurde. In Netzen mit festen IP-Adressen läuft meist ein Dienst, der

Page 71: 382726314 X Php5 In 14 Tagen (Ddt)

Webserver und Browser

die Adressevergabe steuert oder die Angaben sind auf der Netzwerkkarte des Com-puters fest hinterlegt.

Das allein reicht jedoch noch nicht, um Daten zu übertragen. Denn auf dem Ser-ver und meist auch auf dem Client laufen unzählige Dienste, die mit der aktuellenAnforderung nichts zu tun haben. Um bestimmte Dienste gezielt erreichen zukönnen, werden so genannte Ports verwendet. Webserver haben im Allgemeinen(Standard) den Port 80. Mail-Server (SMTP) haben den Port 25 und POP3-Servererreicht man über Port 110. Der Browser hat auch einen Port, der jedoch nicht festzugeordnet ist sondern dynamisch aus dem Adresspool oberhalb 1.024 vergebenwird, beispielsweise 4.768. IP-Adresse und Port bilden zusammen einen so genann-ten Socket. Zwischen zwei solchen Sockets spielt sich dann die Kommunikationab. Während IP für die Adresszuordnung sorgt, wird der Datenstrom per TCPgesteuert. Hier findet die Verpackung der Daten in Pakete und die Fehlerprüfungstatt.

Die Daten, die der Browser nun sendet, werden ihrerseits wieder in ein höheresProtokoll verpackt: HTTP. Dies sind lesbare Codes, die bestimmte Aktionen amServer steuern oder im Browser zur Darstellung der Seiten führen.

HTTP auf einen Blick

Was der Browser sendet, sieht bei der ersten Anforderung in etwa folgendermaßenaus:

GET info.php

GET ist das Kommando, danach folgt die Ressource, die mit GET angefordertwerden soll. Der Webserver erkennt das Kommando und weiß nun, dass er dieDatei laden und ausgeben soll. Wurde die Dateierweiterung verknüpft, kann sichnoch ein Programm dazwischen schieben und die Datei verarbeiten, bevor siegesendet wird. Die Erweiterung .php übergibt die Daten an PHP und dort wird dieDatei geladen, nach den Skript-Tags durchsucht, der Code untersucht und ausge-führt und eine fertige HTML-Seite erstellt. Diese wird wieder an den Webserverzurückgegeben und der sendet sie dann in Beantwortung der Anforderung aus:

71

HTTP/1.1 200 OKContent-Length: 46783<html> <!-- Hier folgt die Seite --></html>

Page 72: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte

Die Antwort besteht aus einem Hinweis auf das Protokoll (1.0 oder 1.1), einem Sta-tuscode (200 steht für OK, 404 für nicht gefunden) und verschiedenen Kopfzeilen,die Zusatzinformationen liefern. Diese Kopfzeilen umfassen mehr als im Beispielgezeigt, beispielsweise auch Angaben über den Server, Cookies usw. Im Beispielwurde lediglich die Länge der Seite übertragen. Nach einer Leerzeile beginntdann der Inhalt der Datei.

Wenn eine HTML-Seite nun drei Bilder enthält, dann wird für jedes Bild eineeigene GET-Anforderung erstellt und gesendet. Der Webserver wird meist keineVerknüpfung für Bilder haben und diese als Ressource direkt von der Festplatteladen und senden. Binärdaten werden ebenso angehängt wie alle anderenInformationen – freilich so codiert, dass nichts verloren geht. Damit nicht jedesMal wieder die gesamte Kette von Name-Servern abgefragt wird, speichern diesedas letzte Ergebnis einige Zeit, sodass der gesamte Ablauf stark beschleunig wird.

Neben GET kann der Browser auch POST verwenden und seinerseits Daten andie Anforderung anhängen. Dies wird genutzt, um Daten aus Formularen zu über-tragen. Diese Informationen werden PHP ebenfalls bereitgestellt, sodass sie inSkripten ausgewertet werden können.

2.6 Kontrollfragen

1. Wie geben Sie mehrzeilige Texte aus PHP-Code heraus am besten aus?

2. Welche Funktion eignet sich zur Ausgabe hexadezimaler Farbangaben?

3. Welche Kommentarform wird am besten verwendet, wenn hinter einer Codezeileein kurzer, auf die Zeile bezogener Kommentar stehen soll? Mögliche Variantensind: /* */, // oder #.

72

Page 73: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

3

Page 74: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

Elementarer Bestandteil eines jeden Programms ist die Möglichkeit, Daten zuerfassen, zu speichern und damit vielfältige Operationen anzustellen. PHP bietetdafür alle auch aus anderen Sprachen bekannten Syntaxformen und -varianten.

3.1 Variablen und Literale

Variablen nehmen unter einem weitgehend freiwählbaren Namen Daten auf. Sielassen sich jederzeit verändern und können in Ausdrücken als Ersatz für literaleKonstrukte eingesetzt werden. PHP erkennt Variablen an einem vorangestellten$-Zeichen. Der Name muss bestimmten Regeln gehorchen, damit er gültig ist:

� Namen dürfen nur Buchstaben, Zahlen und den Unterstrich enthalten.

� Das erste Zeichen des Namens muss ein Buchstabe oder der Unterstrich sein.

� Groß- und Kleinschreibung wird unterschieden.

Das impliziert, dass Leerzeichen nicht erlaubt und Umlaute tabu sind.

PHP und Datentypen

PHP und Datentypen sind ein besonderes Kapitel, leider kein besonders erfreuli-ches. Es ist typisches Merkmal von einfachen Skriptsprachen, mit den Datentypensehr locker umzugehen. Das heißt, es wird dem Benutzer wenig Gelegenheit gege-ben, selbst festzulegen, ob der Inhalt einer Variable eine Zahl oder eine Zeichen-folge oder etwas anderes ist. PHP stellt dies anhand bestimmter Kriterien selbst festund wechselt den Typ auch mal auf halber Strecke.

Das ist nicht immer von Vorteil, auch wenn Sie am Anfang weniger einrichtenund festlegen müssen als bei anderen Sprachen. Es führt jedoch zu Problemen,wenn Ihre Anwendung so programmiert ist, dass ein bestimmter Typ erforderlichist und dies gleichzeitig nicht ständig absichert. Es kommt dann zu so genanntenSeiteneffekten, die schwer nachvollziehbare Fehlerquellen sind.

Nichtsdestotrotz kennt PHP natürlich den intern und automatisch festgelegten

74

Typ, den eine Variable haben kann. Neben Variablen ist auch die Art des Rück-gabewerts einer Funktion in dieser Weise festgelegt (auch diesen können Sie nichtexplizit erzwingen). Die automatische Ermittlung erfolgt im Rahmen des Zuwei-sungsprozesses:

Page 75: 382726314 X Php5 In 14 Tagen (Ddt)

Variablen und Literale

$zahl = 23;

Hier wird der Variablen $zahl der Wert 23 zugewiesen. PHP erkennt keine Anfüh-rungszeichen, wie sie für Zeichenfolgen typisch sind und auch keinen Dezimal-punkt, wie er bei Gleitkommazahlen auftritt. Also handelt es sich um eine Ganzzahl,als Integer bezeichnet.

Ganzzahlen lassen sich auch oktal und hexadezimal darstellen. Oktale Zahlenhaben die Zahlenbasis 8 und die Ziffern 0 bis 7. Sie werden erkannt, wenn eineführende Null davor geschrieben wird:

$zahl = 040;

Hexadezimalzahlen haben die Zahlenbasis 16 und verwenden als Ziffernzeichenfür die Zeichen 0 bis 9 und A bis F. Die Kennzeichnung erfolgt durch das Präfix»0x«:

$zahl = 0xFF;

Zeichenkettenliterale werden folgendermaßen geschrieben:

$zeichen = "Zahl 23";

Diese Anweisung legt die Zeichenfolge »Zahl 23« in die Variable $zeichen. Hiererkennt PHP den Datentyp an den Anführungszeichen. Ob Sie einfache oder dop-pelte verwenden, ist für den Typ ohne Bedeutung. Folgendes können Sie deshalbauch schreiben:

$zeichen = 'Zahl 23';

Gleitkommazahlen (Teil des Zahlenraums der reellen Zahlen) haben Kommastel-len, ein Merkmal ist also der Dezimalpunkt. Denken Sie daran, dass in Program-miersprachen immer die englische Schreibweise verwendet wird:

$wert = 13.04;

Große Zahlen können durch eine Exponentialdarstellung abgebildet werden.Dabei wird der Wert als Konstruktion der Art 3·104 dargestellt. In PHP wird dieseForm folgendermaßen geschrieben:

$wert = 3E4;

75

Das »E« entspricht also »zehn hoch«. An dieser Stelle sei auch erwähnt, dass dieGenauigkeit durch die Rechenbreite des Computers bedingt nicht unendlich istund auch nicht genau dem reellen Zahlenraum entspricht. Scheinbar verrechnetsich PHP bei sehr vielen Stellen deshalb manchmal geringfügig.

Page 76: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

Literale

Die rechte Seite der Zuweisungen, wie ?23?, 13.04 oder 3E4 werden als Literalebezeichnet – es sind konstante Werte mit einem definierten Aufbau. PHP erkenntbestimmte Literale und erzeugt daraus entsprechende Werte.

Hinweise zur Definition

In der Standardeinstellung von PHP5 wird erwartet, dass Variablen vor der erstenVerwendung deklariert werden. Damit ist gemeint, dass mindestens einmal einWert zugewiesen wurde und PHP damit den Datentyp feststellen konnte. Auchwenn dies trivial klingt: Man kann PHP so einstellen, dass dieser Zwang zur Dekla-ration nicht erforderlich ist und sich damit eine Fehlerquelle einfangen. Sie soll-ten es deshalb immer bei der Regel belassen, jede Variable zu deklarieren. HabenSie nämlich versehentlich einen Tippfehler gemacht oder Groß- und Kleinschrei-bung verwechselt, weist eine Fehlermeldung auf diesen Umstand hin. Ohne diesewürde Ihr Programm wegen der automatischen Typerkennung und -umwandlungmöglicherweise dennoch funktionieren – bis bestimmte Daten auftreten, beidenen es aus dem Tritt kommt. Solche sporadisch auftretenden Fehler sind nursehr schwer zu finden.

3.2 Konstanten

Oft werden Variablen eingesetzt, um einen Wert zur Konfiguration einer Anwen-dung zu bestimmen. Diese Werte ändern sich während der Abarbeitung des Pro-gramms nicht. PHP bietet mit Konstanten eine bessere Form. Konstanten können,nachdem sie einmal einen Wert erhalten haben, nicht mehr verändert werden.

Konstanten definieren und nutzen

Zuständig für die Erzeugung einer Konstanten ist die Funktion define. Benötigt

76

wird der Name der Konstanten als Zeichenfolge und der zuzuweisende Wert.

define('BREITE', 640);

Page 77: 382726314 X Php5 In 14 Tagen (Ddt)

Konstanten

In Ausdrücken werden Konstanten ebenso wie Variablen verwendet. Sie werdenjedoch ohne führendes $-Zeichen geschrieben und können deshalb nicht in Zei-chenfolgen mit doppelten Anführungszeichen erkannt werden.

Listing 3.1: define.php – Definition und Ausgabe einer Konstanten

<?phpdefine('BREITE', 640);echo "Die Breite ist : ", BREITE;?>

Umgang mit Konstanten

Da Konstanten nur einmalig definiert werden können, kann es zu Problemenkommen, wenn Konfigurationen von Programmteilen mehrfach erfolgen. Daskann notwendig sein, um Module unabhängig voneinander anzuwenden. JedesModul soll beispielsweise in der Lage sein, sich seine benötigten Konstanten selbstzu definieren, wenn keine übergeordnete Instanz dies bereits getan hat. Diese Artzu programmieren ergibt größtmögliche Sicherheit – das Modul bleibt in jedemKontext stabil.

Zur Abfrage einer bereits erfolgten Definition stellt PHP eine weitere Funktion zurVerfügung: defined. Der Rückgabewert kann Wahr (TRUE) oder Falsch (FALSE)sein, je nachdem, ob die als Parameter angegebene Konstante bereits existierte.Die Anwendung ist nur mit einer if-Anweisung sinnvoll, wie folgendes Beispielzeigt:

<?phpif (!defined('BREITE')){ define ('BREITE', 640);}?>

Abbildung 3.1: Ausgabe einer Konstanten

77

Wie schon bei der Definition der Konstanten wird auch hier der Name, nichtjedoch die Konstante selbst angegeben. Das ist logisch, weil diese ja möglicher-weise noch gar nicht existiert.

Page 78: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

3.3 Rechnen und Vergleichen mit Ausdrücken

Ganz nebenbei wurde der Begriff »Ausdruck« bereits erwähnt. Es handelt sichdabei immer um ein Konstrukt, das genau einen definierten Wert zurückgibt. Fol-gende Beispiele stellen gültige Ausdrücke in PHP dar:

17 + 4"Heute ist " . "Mittwoch"3E4 == 30000

Der erste Ausdruck gibt 21 zurück; PHP erkennt zwei Integer-Werte und führt eineAddition aus. Der zweite ergibt die Zeichenfolge »Heute ist Mittwoch«. Der Punktist ein Operator zur Verbindung von Zeichen. Der dritte Ausdruck vergleicht zweiZahlen, die beiden Gleichheitszeichen sind ein so genannter Boolescher Opera-tor. In diesem Fall sind die beiden Zahlen gleich und der Ausdruck gibt »Wahr«(TRUE) zurück.

Ausdrücke und Operatoren

Ausdrücke entstehen durch die Verbindung von Variablen, Konstanten, Literalenund Operatoren. Den Operatoren kommt also eine sehr große Bedeutung zu. Esgibt sehr viele davon, die für die unterschiedlichsten Zwecke eingesetzt werdenkönnen.

Ausdrücke

Als Ausdruck werden alle Kombinationen aus Literalen, Variablen, Konstantenoder Funktionsaufrufen und Operatoren bezeichnet, die einen konkreten skalarenWert zurückgeben. Dabei ist es erst einmal nicht relevant, welchen Datentyp die-ser Wert hat. Typische Ausdrücke sind:

$a + 445 – 56 * 99$zahl1--

78

$string = $buchstabe . " " . $ende

Die Benutzung erfolgt entweder beim Funktionsaufruf, als Zuweisung (Ziel isteine Variable) oder im Kontext anderer Ausdrücke.

Page 79: 382726314 X Php5 In 14 Tagen (Ddt)

Rechnen und Vergleichen mit Ausdrücken

Rechnen mit Operatoren

Zum Rechnen werden folgende Operatoren verwendet:

Der Modulus-Operator kommt sehr häufig zum Einsatz, auch wenn der Rest einerDivision im Alltagsleben kaum eine Rolle spielt. Viele Divisionen ergeben jedochgebrochene Zahlen und dies ist bei der Ausgabe nicht immer praktisch. So müssenSie vielleicht in einer Tabelle jede dritte Zeile einfärben. Aber wie erkennt manjede dritte Zeile? Haben Sie einen Wert, der die Zeilen zählt, teilen Sie diesen ein-fach durch Drei. Immer wenn der Rest der Ganzzahl-Division gleich 0 ist, wurdeeine dritte Zeile erreicht.

Interessant sind auch die Inkrement- und Dekrementoperatoren. Mit diesen wirdder Wert in einer Variablen um eins erhöht bzw. verringert. Innerhalb von Ausdrü-cken angewendet stellt sich die Frage, ob die Veränderung vor oder nach der Ent-nahme des Wertes erfolgen soll. Gesteuert wird das durch die Platzierung des

Zeichen Operandenzahl Anwendung Bedeutung

+ 2 23 + $zahl Addition

- 2 $wert – 17 Subtraktion

- 1 -345 Negatives Vorzeichen

* 2 12 * 11 Multiplikation

/ 2 100 / 10 Division

% 2 6 % 3 Rest einer Ganzzahl-Division (Modulus genannt)

() 1 (Ausdruck) 13 * (4 + 7) Klammern zur Bildung von Teilausdrücken

++ 1 $ziffer++ Erhöhung um 1, Inkrement

-- 1 --$zahl Verringerung um 1, Dekrement

Tabelle 3.1: Arithmetische Operatoren

79

Operators. Das folgende Beispiel zeigt dies:

Page 80: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

Listing 3.2: OperatorIncdec.php – Werte um eins erhöhen oder verringern

<?php$number = 12;$digit = 8;echo "Summe: ", $number + $digit++;echo "<br>";echo "<br>Digit: ", $digit;echo "<br>Number: ", --$number;?>

Die Ausgabe zeigt die Wirkung:

Bei der Berechnung der Summe wird 20 ausgegeben, weil die Erhöhung der Vari-able $digit erst nach der Auswertung des Ausdrucks erfolgt. Bei der Ausgabe derVariablen $number erfolgt die Veränderung dagegen vorher, deshalb erscheint 11(statt 12).

Zeichenfolgen verketten

Es wurde bereits angedeutet, dass Zeichenfolgen mit einem eigenen Operator ver-kettet werden, dem Punkt ».«. Hier gibt es keine weiteren Besonderheiten. Bei derAnwendung ist jedoch zu beachten, dass PHP erforderlichenfalls eine Typum-wandlung vornimmt, wie das folgende Beispiel zeigt:

Listing 3.3: TypeStringpoint.php – PHP erkennt hier alle Variablen als Zeichenfolgen

<?php$a = 13;$b = 7;

Abbildung 3.2: Ausgabe von Listing 3.2

80

echo $a . $b;?>

Dieses Skript gibt nicht 20 aus, sondern »137«. Das liegt daran, dass der Punkt nurZeichenfolgen verbinden kann. Damit das funktioniert, wandelt PHP die beidenVariablen entsprechend um; aus der Zahl 13 wird die Zeichenfolge »13« usw. Dies

Page 81: 382726314 X Php5 In 14 Tagen (Ddt)

Rechnen und Vergleichen mit Ausdrücken

geschieht hier übrigens nur für diesen Ausdruck, danach stehen die Variablenunverändert als Zahlen zur Verfügung. Im Hinblick auf die Programmsicherheitund Stabilität ist dieses Verhalten kritisch. Sie sollten hier besondere Sorgfalt wal-ten lassen.

Kombinationsoperatoren

Sie kennen bereits die Zuweisung mit dem Gleichheitszeichen. Alle dualen Ope-ratoren, also solche mit zwei Operanden (+, –, *, /, %), können mit der Zuweisungkombiniert werden. Das spart lediglich Tipparbeit und hat keinen Effekt auf denProgrammfluss. Die folgende klassische Operation kann verkürzt werden:

$a = $a + 5;

Hiermit wird der Inhalt von $a um fünf erhöht. Kürzer geht es mit einem Kombi-nationsoperator:

$a += 5;

Freilich geht das nicht immer. Folgende Operation ist nicht direkt verkürzbar:

$a = 17 - $a;

Vergleiche und Boolesches

Zu den typischen Operationen gehören auch Vergleiche (größer, kleiner, gleich)und Boolesche Operationen (logische Ausdrücke). Der Name Boolesch stammtvon George Boole, der die Grundlagen der logischen Algebra entwickelte, undwird deshalb immer groß geschrieben. Ausdrücke, die sich logisch auswerten las-sen, geben immer einen Wahrheitswert zurück, also entweder »Wahr«, in PHP alsTRUE ausgedrückt oder »Falsch«, wofür in PHP die Konstante FALSE steht.

PHP erkennt zwar logische Ausdrücke und kann bei Vergleichen entsprechenddamit umgehen, nutzt aber intern Zahlen zur Darstellung. Das führt dazu, dassandere Ausdrucksarten fast immer auch in einen Wahrheitswert gewandelt wer-den, wenn dies vom Kontext her erforderlich ist. Das führt manchmal zu schwerenProgrammfehlern. Achten Sie deshalb unbedingt auf eine saubere Konstruktion

81

der Ausdrücke.

Page 82: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

Die Vergleichsoperatoren finden Sie in der folgenden Tabelle:

Wenn man nun dergestalt Ausdrücke entwickelt, ist auch eine Kombination vonTeilausdrücken erforderlich, um komplexere Strukturen abzubilden. Dazu wer-den die logischen Operatoren eingesetzt, meist in Verbindung mit Klammern, umdie Rangfolge zu bestimmen.

Operator Bedeutung

< Kleiner als

> Größer als

<= Kleiner als oder Gleich

>= Größer als oder Gleich

== Gleich, wobei der Datentyp beider Operanden vor dem Vergleich so umgewandelt wird, dass er gleich ist.

=== Gleich, wobei der Datentyp nicht verändert wird und identisch sein muss, damit der Ausdruck Wahr wird

!= Ungleich als Umkehroperation zu ==

!== Ungleich als Umkehroperation zu ===

(a1)?(a2):(a3) Trinärer Operator, der den Ausdruck a1 auswertet und, wenn dieser Wahr zurückgibt, a2 ausführt, ansonsten a3. Alle drei Bestandteile sind erforder-lich, die Klammern sind optional.

Tabelle 3.2: Vergleichsoperatoren

Operator Bedeutung

|| Oder. Wahr, wenn einer der beiden Operanden Wahr ist.

&& Und. Wahr, wenn beide Operanden Wahr sind.

82

! Nicht, nur ein Operand möglich. Wahr, wenn der Operand Falsch ist.

or Oder. Wahr, wenn einer der beiden Operanden Wahr ist.

Tabelle 3.3: Logische (Boolesche) Operatoren

Page 83: 382726314 X Php5 In 14 Tagen (Ddt)

Allgemeine Aussagen zu Zeichenketten

Scheinbar sind die Operatoren || und or bzw. && und and identisch. Dem istjedoch nicht so, denn jeder Operator hat eine definierte Rangfolge. Das heißt,wenn Sie Ihre Ausdrücke ohne Klammern bauen, muss der Interpreter beim Auf-lösen eine bestimmte Reihenfolge einhalten. Diese ist nicht einfach von links nachrechts, sondern berücksichtigt bestimmte typische Gegebenheiten.

Betrachten Sie den folgenden Ausdruck:

echo 4 + 5 * 6;

PHP berechnet hier – mathematisch korrekt – das Ergebnis 34. Denn wegen derRegel »Punkt- vor Strichrechnung« wird zuerst der rechte Teilausdruck ausgewer-tet. Soll dies umgangen werden, sind Klammern erforderlich. Nun ist eine so allge-meine Rechenregel jedem geläufig. Bei anderen Operatoren muss der Designerder Sprache eine Reihenfolge festlegen. Mehr Informationen zur Rangfolge (Asso-ziativität) der Operatoren werden im Zusammenhang mit dem if-Befehl (SieheAbschnitt »Einfache Verzweigungen mit if«).

3.4 Allgemeine Aussagen zu Zeichenketten

Zeichenketten oder Zeichenfolgen sind ein elementarer Datentyp in PHP. DieAusgabe von Text nach HTML erfolgt immer als Zeichenkette. Aus diesem Grundsind Operationen mit Zeichenketten elementar und Sie sollten sich mit denMöglichkeiten von PHP ebenso auseinandersetzen wie mit den vielfältigen Ein-satzfällen.

and Und. Wahr, wenn beide Operanden Wahr sind.

xor Exklusives Oder. Wahr, wenn einer der Operanden Wahr ist, jedoch nicht beide (ausschließendes Oder).

Operator Bedeutung

Tabelle 3.3: Logische (Boolesche) Operatoren (Forts.)

83

Datentyp und Größe

Als Datentyp unterscheidet PHP nicht zwischen Zeichenketten und einzelnenZeichen. Es gilt deshalb generell, dass ein einzelnes Zeichen wie eine Zeichen-

Page 84: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

kette mit der Länge 1 behandelt werden kann. Der Datentyp selbst wird als»string« bezeichnet. Zur Umwandlung aus anderen Typen wird entsprechend derOperator (string) verwendet:

$string = (string) 11;

Zeichenketten können bis zu 2 Milliarden Zeichen enthalten (2 GByte), dies istjedoch ein eher theoretischer Wert, da das Laufzeitverhalten unter sehr großenDatenmengen massiv leidet. Der Umfang typischer HTML-Seiten von einigenDutzend KByte kann jedoch problemlos verarbeitet werden.

Zeichenketten haben keine definierte (vorgegebene) Länge oder ein speziellesEndzeichen, wie dies bei C der Fall ist. Für die Abfrage der aktuellen Länge gibt eseine passende Funktion.

Umgang mit Sonderzeichen

Zeichenketten selbst stehen in ihrer literalen Form in Anführungszeichen. WennSie nun ein solches Zeichen selbst ausgeben möchten, muss es maskiert werden.Die Maskierung erfolgt durch ein spezielles Maskierungszeichen, das in PHP wiein fast allen anderen Sprachen der Backslash »\« ist. Dem Backslash kommt des-halb eine besondere Bedeutung zu. Das folgende Beispiel zeigt, wie Anführungs-zeichen für ein HTML-Tag erzeugt werden:

"<img src=\"bild1.gif\" width=\"100\" height=\"150\" />"

Das kann vermieden werden (weil es schlecht lesbar ist), indem das jeweils korres-pondierende Anführungszeichen genutzt wird:

'<img src="bild1.gif" width="100" height="150" />'

Damit verliert man aber unter Umständen die Fähigkeit zur Variablenerkennung.

Eine andere Aufgabe des Backslash besteht darin, Sonderzeichen zu erzeugen, diesich über die Tastatur nicht eingeben lassen, weil sie im Editor eine besondereAufgabe haben. Das betrifft vor allem Zeilenumbrüche und Tabulatoren. WeitereSonderzeichen, die PHP erkennt und nutzt, zeigt die folgende Tabelle:

84

Page 85: 382726314 X Php5 In 14 Tagen (Ddt)

Allgemeine Aussagen zu Zeichenketten

Beachten Sie, dass die Zeilenumbrüche unter Windows und Unix unter-schiedlich sind. Während Unix nur \n einsetzt, wird unter Windows\n\r geschrieben. Dies ist zu beachten, wenn Datenausgaben betriebs-systemunabhängig erfolgen sollen.

Zeichenketten erkennen und bestimmen

Wenn Sie feststellen möchten, ob eine Variable tatsächlich eine Zeichenkette ent-hält, nutzen Sie die Funktion is_string:

Zeichen Bedeutung

\n Zeilenvorschub, entspricht dem Code 0A (hexadezimal, in PHP 0x0A geschrieben)

\r Wagenrücklauf, entspricht dem Code 0D (hexadezimal, in PHP 0x0D geschrieben)

\t Tabulator, Code 9

\\ Der Backslash selbst

\$ Das $-Zeichen, wenn $ in dem gegebenen Zusammenhang eine besondere Bedeutung hat

\« Das doppelte Anführungszeichen, wenn es in einem Literal aus doppelten Anführungszeichen steht

\' Das einfache Anführungszeichen, wenn es in einem Literal aus einfachen Anführungszeichen steht

\40 Ein Zeichen mit dem Code 040 im oktalen Zahlensystem (Basis 8, 040 = Leer-zeichen)

\x20 Ein Zeichen mit dem Code 0x20 im hexadezimalen Zahlensystem (auch das Leerzeichen)

Tabelle 3.4: Sonderzeichen in Zeichenketten

85

is_string($var);

Der Rückgabewert ist Wahr (TRUE) oder Falsch (FALSE). Alternativ kann auch get-type benutzt werden, hier wird bei einer Zeichenkette »string« zurückgegeben.

Page 86: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

Um die Typumwandlung festzulegen, wird entweder der Umwandlungsoperator(string) oder die Funktion settype benutzt:

$string = settype($var, "string");

Zeichenkettenoperationen

Mit Zeichenketten kann man allerlei anstellen. Typische Operationen sind:

� Vergleichen, Suchen und Ersetzen

� Ermitteln der Zeichenzahl und anderer Eigenschaften

� Auswahl einer Teilzeichenkette und ähnliche Operationen

� Behandlung einzelner Zeichen

� Ermitteln von bestimmten Zeichen mit besonderer Bedeutung

� Umwandeln in einen anderen Datentyp und HTML-abhängige Funktionen

� Sprach- oder landesabhängiges Formatieren für die Ausgabe

Zum Suchen und Ersetzen gibt es gleich zwei Möglichkeiten. Einmal kann mitHilfe eines einfachen Vergleichsmusters gearbeitet werden, das eine exakte Über-einstimmung bedingt. Zum anderen gibt es universelle Suchmuster, so genanntereguläre Ausdrücke. Letztere werden in Abschnitt 3.6 »Reguläre Ausdrücke« abSeite 95 behandelt.

3.5 Die Zeichenkettenfunktionen

Die praktische Anwendung der Zeichenkettenfunktionen in diesem Abschnittzeigt die Einsatzbandbreite. Es geht in PHP – dies werden Sie später noch deutli-cher sehen – mehr um das Auffinden der passenden Funktionen als um Problemebei der Anwendung. Leider sind die Benennungsregeln bestenfalls als konfus zubezeichnen. Sie zeigen eine der Mankos freier Software: Viele unkoordinierte Ent-

86

wicklungsschritte und mangelnde Dominanz einer Ordnungsinstanz führen zuChaos. So wird bei Zeichenketten oft zwischen Groß- und Kleinschreibung unter-schieden. Ältere PHP-Funktionen verwenden zur Kennzeichnung der Verhaltens-weise die Zeichen »case« (von »case insensitive«), neuere dagegen nur »i« (von»insensitive«). Einige Funktionen haben den Präfix »str_«, andere nur »str«, bei

Page 87: 382726314 X Php5 In 14 Tagen (Ddt)

Die Zeichenkettenfunktionen

anderen gibt es gar keinen Hinweis im Namen darauf, dass sie zur Gruppe der Zei-chenkettenfunktionen gehören.

Suchen und Ersetzen

Beim Suchen und Ersetzen geht es darum, Daten, die beispielsweise aus einerDatenbank oder einem Formular empfangen wurden, auf bestimmte Merkmalehin zu untersuchen. PHP bietet hier gleich mehrere Funktionen an.

Suchen einer Zeichenfolge in einer anderen

Angenommen, Sie haben eine Folge von Zeichen, beispielsweise einen Text auseinem Formular, und möchten ermitteln, ob sich darin ein bestimmtes Wortbefindet. Dann wird die Funktion strpos eingesetzt. Sie liefert die Position derFundstelle:

Listing 3.4: strpos.php – Ermitteln einer Zeichenfolge in einer anderen

<?php$text = "Dies ist ein langer Text";$pos = strpos($text, "langer");echo "Position : $pos";?>

Die Ausgabe zeigt, dass der Text »langer« an 13. Stelle gefunden wurde:

Man nennt eine solche Zählweise »nullbasiert«, das heißt, das erste Zeichen hatden Index 0. Ist der Text nicht enthalten, wird FALSE zurückgegeben. Das ist zwarfür PHP typisch aber dennoch gewöhnungsbedürftig: Viele eingebaute Funk-tionen können den Datentyp bei der Rückgabe wechseln. Da generell keine Mög-lichkeit besteht, Datentypen zu erzwingen, erscheint dies konsequent. Es

Abbildung 3.3: 13 bedeutet das 14. Zeichen, weil die Zählung mit 0 beginnt

87

erleichtert nur nicht unbedingt die Programmierung. Denn Sie müssen in IhremProgramm vor der weiteren Verarbeitung des Ergebnisses an die Reaktionen aufverschiedene Datentypen denken. strpos beispielsweise gibt eine ganze Zahlzurück (integer), wenn die Zeichenfolge gefunden wurde, oder einen BooleschenWert (boolean), wenn die Suche erfolglos war. Durch die bereits beschriebene

Page 88: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

automatische Typumwandlung fällt dies nicht weiter auf, dennoch ist das Verhal-ten ein Quell sporadisch auftretender Programmfehler.

Tabelle 3.8 können Sie entnehmen, dass es noch zwei andere Varianten vonstrpos gibt. strrpos sucht die Fundstelle vom Ende her (was gleichbedeutend mitder letzten Fundstelle ist, wenn man von vorn sucht) während stripos bei derSuche nicht auf Groß- und Kleinschreibung achtet. Probieren Sie diese selbst aus,indem Sie die Funktion und die Testdaten in Listing 3.4 austauschen.

Ähnlichkeiten zwischen Zeichenfolgen feststellen

Gibt ein Benutzer Daten ein, ist ein exakter Vergleich nicht immer angebracht.Auch ähnliche Angaben können brauchbar sein. PHP bietet auch hier einigeFunktionen, die einen Vergleich erlauben.

Vergleichsfunktionen ermitteln typischerweise drei Zustände und geben einen vondrei Werten zurück:

� 0 bedeutet, dass die beiden Zeichenfolgen identisch sind.

� 1 bedeutet, dass die erste Zeichenfolge größer als die zweite eingestuft wird.

� -1 bedeutet, dass die erste Zeichenfolge kleiner als die zweite eingestuft wird.

Normalerweise erfolgt ein solcher Vergleich auf »binärer« Basis. Bei Zeichen wirdalso die Reihenfolge der Zeichen im Zeichensatz herangezogen. So hat der Buch-stabe »A« den Code 65, während »a« den Wert 97 hat. Deshalb werden Großbuch-staben als »kleiner« als Kleinbuchstaben eingestuft, was nicht immer denErwartungen entspricht. Ein zweites Merkmal derartiger Vergleiche ist die Vorge-hensweise bei mehreren Zeichen: Der Algorithmus prüft Zeichen für Zeichen, biser einen Unterschied findet. Der Vergleich der Zeichenfolge »11« mit »2« führtbereits beim ersten Zeichen zu einem Unterschied. »1« wird mit »2« verglichen;die »2« ist natürlich größer und deshalb produzieren derartige Funktionen alsErgebnis: »2« ist größer als »11«. Auch dies dürfte nur selten den Erwartungen ent-sprechen. Binäre Vergleiche sind im Alltag also weniger gefragt.

PHP bietet zwei Lösungen für das Problem an: Zum einen kann die Unterschei-dung von Groß- und Kleinschreibung verhindert werden. Die Funktionen strcase-

88

cmp, strnatcasecmp und strncasecmp setzen »a« gleich »A«, der Teilname »case«deutet darauf hin. Der Teilname »nat« verhindert dagegen die binäre Suche undversucht eine so genannte natürliche Suche. Hierbei werden Zahlen extrahiert undals Zahl, nicht als Zeichen behandelt, wobei 11 dann größer als 2 ist.

Page 89: 382726314 X Php5 In 14 Tagen (Ddt)

Die Zeichenkettenfunktionen

Listing 3.5: StrNatCmp.php – Vergleichen von Zeichenketten

<?php$text1 = "Bild_2";$text2 = "Bild_11";echo "<br>Normal : " . strcmp($text1, $text2);echo "<br>Natürlich : " . strnatcmp($text1, $text2);?>

Das Ergebnis zeigt, dass die Standardfunktion strcmp (binär, abhängig von Groß-und Kleinschreibung) $text1 als größer einstuft, was an dem Vergleich der »2« zurkorrespondierenden »1« liegt. Mit strnatcmp sieht es anders aus. Hier wird derZahlenanteil extrahiert und – soweit der Rest übereinstimmt – als Zahl verglichen.Deshalb ist $text1 nun kleiner als $text2.

Die Vergleichsfunktionen werden auch eingesetzt, um Sortiervorgänge auszufüh-ren. Sortieralgorithmen greifen auf die Ergebnisse eines einzelnen Vergleichs zwi-schen zwei Werten zurück. Das Einsortieren in die Ergebnisliste erfolgt auf Basisder Rückgabewerte 0, 1 oder -1. Diese Anwendung wird im Zusammenhang mitArrays am achten Tag noch gezeigt.

Ersetzen von Zeichen

Ebenso wichtig wie das Suchen von Mustern ist das Ersetzen der Fundstelle durchanderen Text. PHP stellt zwei Funktionen zur Verfügung: str_replace berück-sichtigt beim Suchvorgang Groß- und Kleinschreibung, während str_ireplacedies nicht tut.

Listing 3.6: StrReplace.php: Ersetzen eines Teils einer Zeichenkette

<?php$text1 = "Dies ist Bild_2";

Abbildung 3.4: Natürlich oder nicht: Verschiedene Vergleichsergebnisse

89

$text2 = "Bild_11";$text3 = str_replace("Bild_2", $text2, $text1);echo $text3;?>

Page 90: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

Hier werden gleich drei Parameter benötigt: Der erste gibt die zu suchenden Zei-chen an, der zweite bestimmt die Ersatzzeichen. Als Ersatz kann auch eine leereZeichenkette angegeben werden, womit die zu suchenden Zeichen entfernt wer-den. Der dritte Parameter gibt den Text an, der durchsucht werden soll.

Komfortablere Such- und Ersetzungsmöglichkeiten bieten übrigens reguläre Aus-drücke, die zum Standardrepertoire eines jeden Softwareentwicklers gehörensollten. Informationen dazu werden in Abschnitt 3.6 »Reguläre Ausdrücke« abSeite 95 vermittelt.

Ermitteln von Eigenschaften einer Zeichenkette

Bei der Beurteilung des Inhalts einer Zeichenfolge kommt es natürlich auch aufdie allgemeinen Eigenschaften an. Besonders häufig wird die Länge benötigt, alsodie Anzahl der Zeichen. Bei der Auswertung von Formulardaten können soPflichtfelder leicht überprüft werden – die Anzahl der Zeichen muss einfach grö-ßer als 0 sein.

Listing 3.7: strlen.php – Anzahl der Zeichen einer Zeichenkette

<?php$field = "Dies ist Bild_2";$len = strlen($field);echo "Das Feld hat $len Zeichen.";?>

Im Gegensatz zum nullbasierten Index wird hier die wirkliche Länge ermittelt.Daran müssen Sie denken, wenn die Ergebnisse der Funktion strlen zusammenmit der Auswahl von Teilzeichenketten oder der Positionsbestimmung (strpos)benutzt werden.

Abbildung 3.5: Anzahl der Zeichen in einer Zeichenkettenvariablen

90

Teilzeichenketten und Behandlung einzelner Zeichen

Bei der Behandlung einzelner Zeichen werden Sie mit zwei Dingen in Berührungkommen. Zum einen wird gelegentlich mit dem Zeichencode gearbeitet, dasheißt, Sie haben den Code eines Zeichens aus dem verwendeten Zeichensatz und

Page 91: 382726314 X Php5 In 14 Tagen (Ddt)

Die Zeichenkettenfunktionen

benötigen dazu das Zeichen. Dazu gehört auch die entsprechende Rückumwand-lung. Zum anderen benötigen Sie noch eine Technik zum gezielten Zugriff aufeinzelne Zeichen.

Umgang mit Zeichencodes

Vor dem Zugriff auf Zeichencodes steht natürlich die Kenntnis derselben. PHPkann mit seinen Zeichenkettenfunktionen nur den erweiterten ASCII-Zeichensatz(Codes 0 bis 255) darstellen, dies entspricht ISO-8859-1.

Entsprechend dieser Tabelle führt chr(65) zum Zeichen »A« und ord("_") zumCode 128. Dies entspricht bei Zeichencodes oberhalb 127 nicht der oft verwende-ten Unicode-Kodierung, die »breitere« Zeichen verwendet (16 statt 8 Bit) undauch asiatische Schriften verarbeiten kann. Kritisch ist das lediglich, wenn Sie ver-suchen, dezimale HTML-Entitäten zu erstellen, beispielsweise &#8364;. DiesesZeichen entspricht der Entität &euro; und stellte das Eurozeichen dar. Leiderkann PHP mit dem Code 8364 nichts anfangen und umgekehrt kann nicht garan-tiert werden, dass ein Browser bei &#128; auch wirklich1 das €-Symbol anzeigt.

Abbildung 3.6: Die Zeichenta-belle des Zeichen-satzes ISO-8859-1

91

1 Solange das Betriebssystem den Zeichensatz ISO-8859-1 oder einen darauf aufbauenden verwendet, kann esdie Zeichen schon dekodieren und der Browser wird sie dann auch darstellen. Da der Zeichensatz aber injeder Sprache etwas variiert, ist das Ergebnis mehr oder weniger unvorhersehbar.

Page 92: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

Zugriff auf einzelne Zeichen

Am Anfang wurde bereits gezeigt, dass jedes Zeichen in einer Zeichenfolge einenIndex hat, beginnend mit 0. Dieser Index kann auch zur gezielten Auswahl genaueines Zeichens benutzt werden:

Listing 3.8: chars1.php – Auswahl eines einzelnen Zeichens

<?php$field = "Dies ist Bild_2";$len = strlen($field);echo "Zeichen 3 = ", $field{3};?>

Die Schreibweise mit den geschweiften Klammern ($field{3}) ist etwas gewöh-nungsbedürftig, erweist sich aber als gut lesbar.

Wenn die Auswahl nicht nur ein Zeichen, sondern gleich mehrere betrifft, sind wie-der spezielle Funktionen gefragt. substr wählt einfach eine Anzahl Zeichen aus,wobei die verschiedenen Parametervarianten dies sehr flexibel ermöglichen. Dererste Parameter gibt die Zeichenfolge an, aus der ein Teil ausgewählt werden soll.Der zweite die Startposition, ab der ausgewählt werden soll. Ist dieser Wert negativ,beginnt die Zählung von hinten. Der dritte – optionale – Parameter gibt die Anzahlder Zeichen an, von der Startposition beginnend immer nach rechts zählend.

Das folgende Beispiel nutzt diese Funktion und einige andere, um die Werte derParameter zu bestimmen.

Listing 3.9: Substr.php – Einen Teil einer Zeichenkette ermitteln

<?php$text = "Dies ist ein Mustertext";echo substr($text, strpos($text, "M"), strlen("muster"));?>

Die Nutzung mehrere Zeichenkettenfunktionen zusammen in einer Anweisung istsehr typisch für die PHP-Programmierung. Das Beispiel gibt das Wort »Muster« aus.

92

HTML-abhängige Funktionen

Der Sinn und Zweck nahezu jeden PHP-Programms ist die Erzeugung vonHTML. Entsprechend umfangreich ist auch hier das Angebot an Funktionen.

Page 93: 382726314 X Php5 In 14 Tagen (Ddt)

Die Zeichenkettenfunktionen

Häufiges Problem ist die korrekte Ausgabe von Text. Daten aus Datenbanken ent-halten meist nicht die für HTML nötigen Entitäten, also beispielsweise »&auml;«statt »ä«. Das folgende Beispiel zeigt, wie eine solche Umwandlung durchgeführtwerden kann:

Listing 3.10: HtmlEntity.php: Sonderzeichen in HTML-Entitäten umwandeln

<?php$text = "Die Umlaute 'äöü', 'ÄÖÜ' und Zeichen wie '_' korrekt ausgeben.";echo htmlentities($text, ENT_NOQUOTES, "ISO-8859-1");?>

Von den drei Parametern der Funktion htmlentities sind zwei optional, lediglichder Text zum Umwandeln muss angegeben werden. Der zweite Parameter kanneine von drei Konstanten angeben, die folgende Bedeutungen haben:

� ENT_COMPAT

Es werden nur doppelte Anführungszeichen konvertiert (" in &quot;).

� ENT_QUOTES

Es werden doppelte und einfache Anführungszeichen konvertiert.

� ENT_NOQUOTES

Die Anführungszeichen bleiben unverändert.

Der dritte Parameter bestimmt den Zeichensatz und ist ebenfalls optional. OhneAngabe wird ISO-8859-1 verwendet.

Zur Aufbereitung von Daten gehört auch die Formatierung von Text für die Aus-gabe in Bezug auf Länge und Darstellung. Soll nur eine bestimmte Anzahl vonZeichen pro Zeile angezeigt werden, ist wordwrap eine Hilfe. Die Funktion verhin-dert, dass der Umbruch willkürlich mitten im Wort erfolgt. Als Umbruchzeichenwird standardmäßig \n verwendet, weshalb das folgende Beispiel das HTML-Tag<pre> einsetzt, um den Umbruch sichtbar werden zu lassen.

Listing 3.11: WordWrap.php – Wortweises umbrechen von längeren Texten

93

<pre style="border:1px blue solid; width:100px; padding:2px"><?php$text = "Die ist ein längerer Text, der in einem kleinen Fenster erscheinen soll";echo wordwrap($text, 20);

Page 94: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

?></pre>

Die Funktion kennt noch zwei weitere Parameter. Der dritte gibt ein alternativesZeichen für den Umbruch an, der vierte kann auf 1 gesetzt werden, um einenUmbruch auch mitten im Wort zuzulassen.

Das Problem mit dem Standardzeilenumbruch \n in einer Datenquelle tritt häufi-ger auf. Statt <pre> wäre die Verwendung des Umbruch-Tags <br> oft besser. Auchdies ist in PHP schnell gelöst: nl2br nimmt diese Umwandlung vor.

Listing 3.12: nl2br.php – Umwandlung von Zeilenumbrüchen in <br />-Tags

<div style="border:1px blue solid; width:220px; padding:2px"><?php$text = "Die ist ein längerer Text, der in einem kleinen Fenster erscheinen soll";echo nl2br(wordwrap($text, 20));?></div>

Die folgende Abbildung zeigt den Unterschied. Die Breite des Rahmens wirddurch den Stil bestimmt (Attribut style des HTML-Tags <div>):

Noch eine interessante Anwendung von Zeichenkettenfunktionen betrifft die Zer-legung von Zeichenfolgen. Telefonnummern oder Bankleitzahlen sind beispiels-

Abbildung 3.7: Umbruchsteuerung zur Formatierung der Ausgabe

Abbildung 3.8: Das linke Bild ist ohne nl2br ent-standen, das rechte mit.

94

weise besser lesbar, wenn man sie mit Leerzeichen gruppiert. Die Funktionchunk_split ist dafür bestens geeignet. Ähnlich wie wordwrap wird nach einer defi-nierten Anzahl von Zeichen ein Trennzeichen eingefügt. Auch hier ist dies stan-dardmäßig der Zeilenumbruch.

Page 95: 382726314 X Php5 In 14 Tagen (Ddt)

Reguläre Ausdrücke

Listing 3.13: ChunkSplit.php – Formatierung einer Bankleitzahl

<?php$text = "10070024";echo chunk_split($text, 3, " ");?>

Die Zeichenfolge wird hier von links beginnend in Abschnitte gleicher Längegeteilt. Der letzte Abschnitt kann freilich kleiner sein, was in diesem Fall abergewollt ist. An jeden Abschnitt wird das Leerzeichen angehängt, das durch dendritten Parameter bestimmt wird.

Das Beispiel gibt »100 700 24« aus.

3.6 Reguläre Ausdrücke

Reguläre Ausdrücke beschreiben Suchmuster. So einfach dies klingt, ist es indesnicht. Denn die Suche nach Zeichen in einem Text kann komplexen Regelngehorchen. Nicht alle Arten von Suchen sind mit regulären Ausdrücken abbildbar.Derartige Ausdrücke können im Prinzip nur statischen Text beschreiben, nichtjedoch Berechnungen anstellen. Die Suche nach einer Zahl, einer bestimmtenBuchstabenfolge oder einer Zeichenart ist einfach. Eine Prüfsumme oder einenZahlenbereich kann man damit nicht erfassen.

Vor allem bei der Prüfung von Formularfeldern, die ein Benutzer ausgefüllt hat,laufen reguläre Ausdrücke zur Höchstform auf. Typisch sind Suchmuster, dieE-Mail-Adressen, einen URL oder Zahlen erkennen.

Einführung in die Welt der regulären Ausdrücke

Die ersten Schritte mit regulären Ausdrücken sind nicht einfach. Hat man abererstmal eine gewisse Systematik erkannt, ist es nicht unmöglich, auch schwierigeAufgaben zu lösen. Auch wenn reguläre Ausdrücke sehr »unleserlich« aussehen

95

können, eine einfache Variante ist oft ausreichend.

Das Grundprinzip

Betrachten Sie folgenden Satz:

Page 96: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

Eine wichtige Programmiersprache ist PHP.

Sie möchten in diesem Satz – an beliebiger Stelle – nach der Zeichenfolge »PHP«suchen. Der passende Ausdruck dazu lautet:

PHP

Freilich kann man das mit einer einfache Suche mittels Zeichenkettenfunktionenauch. Angenommen, Sie wollen nach den Zeichen »PHP« oder »Perl« suchen.Wenn Sie Zeichenkettenfunktionen verwenden, benötigen Sie dazu zwei Anfra-gen. Ein regulärer Ausdruck kennt dafür einen Operator:

PHP|Perl

Die erste wichtige Aussage lautet also: Zum Suchen nach beliebigen konkretenZeichen oder Zeichenfolgen werden diese einfach aufgeschrieben. Die zweitewichtige Aussage: Suchmuster lassen sich miteinander verknüpfen, um komple-xere Abfragen zu ermöglichen.

Muster durch Platzhalter und Zeichenklassen

Nun ist das Suchen nach fest vorgegebenen Mustern recht einfach. Spannender istes, die Zeichen durch Platzhalter zu ersetzen. Der wichtigste Platzhalter ersetzt einbeliebiges Zeichen – der Punkt (.):

P.P

Dieses Muster findet PAP, PBP usw. und natürlich auch PHP.

Außer »alle« oder »ein bestimmtes« Zeichen kann man beliebige Platzhalter selbstkonstruieren. Dazu werden so genannte Zeichenklassen eingesetzt, gekennzeich-net durch eckige Klammern. Diese Gebilde stehen für genau ein Zeichen, das denKriterien entsprechen muss:

[PH]HP

Dieses Muster steht für PHP oder HHP. Der Inhalt der Zeichenklasse kann negiertwerden, das heißt, alle Zeichen außer den angegebenen sind zulässig:

[^PH]HP

96

Das letzte Muster erkennt – neben vielen anderen – AHP, BHP usw. nicht jedochPHP und HHP.

Weil bestimmte Zeichenklassen sehr häufig benötigt werden, gibt es einige vorde-finierte Abkürzungen dafür. Die folgende Tabelle zeigt diese:

Page 97: 382726314 X Php5 In 14 Tagen (Ddt)

Reguläre Ausdrücke

Wiederholungen definieren

Damit man mit Zeichen und Zeichenklassen flexibel umgehen kann, lässt sich einWiederholungsoperator anhängen. Dieser Zeigt an, wie oft das Zeichen vorkom-men soll:

Zeichen Bedeutung

\t Tabulator, entspricht dem ASCII-Wert 0x9

\n Zeilenumbruch (Newline), entspricht dem ASCII-Wert 0xC

\r Wagenrücklauf (Carriage Return), entspricht dem ASCII-Wert 0xA

\xHH Ein beliebiges Zeichen, definiert durch seinen hexadezimalen Wert HH

\d Eine Ziffer

\D Keine Ziffer (alles außer Zifferzeichen)

\s Jedes unsichtbare Zeichen (Leerzeichen, Tabulator usw.)

\S Alles außer unsichtbare Zeichen

\w Jedes Wortzeichen (Zahl, Ziffer, Unterstrich)

\W Kein Wortzeichen

\b Jedes für Wortgrenzen verwendete Zeichen (Leerzeichen, Interpunktion)

\B Kein für Wortgrenzen verwendetes Zeichen

\A Erstes Zeichen der Sequenz (absolut, ohne Rücksicht auf andere Schalter)

\Z Letztes Zeichen der Sequenz oder Zeile (absolut, ohne Rücksicht auf andere Schalter)

\z Letztes Zeichen der Sequenz (absolut, ohne Rücksicht auf andere Schalter)

Tabelle 3.5: Vordefinierte Zeichen und Zeichenklassen

97

{n, m}

Dabei steht das n für die Mindestzahl und das m für die Maximalzahl. Beide Wertesind optional; {4,} steht beispielsweise für mindestens vier bis unendlich viele Zei-chen, {,6} für keines bis höchstens sechs Zeichen. Für drei Kombinationen gibt eseine verkürzte Schreibweise, die häufig zum Einsatz kommt:

Page 98: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

� *Steht für keines oder beliebig viele Zeichen, entspricht also {0,}.

� ?Steht für keines oder genau ein Zeichen, entspricht also {0,1}.

� +Steht für ein oder mehr Zeichen, entspricht also {1,}.

Die Kombination {1,1} muss nicht angegeben werden, dies ist der Standardfall.

Um die Zeichenposition innerhalb der zu durchsuchenden Zeichenkette festlegenzu können, sind weitere Sonderzeichen erforderlich:

� ^Muster muss am Beginn der Zeichenkette anfangen.

� $Muster muss am Ende der Zeichenkette aufhören.

Da nun bereits einige Zeichen mit Sonderfunktionen belegt sind, braucht mannoch ein Aufhebungszeichen, wozu der Backslash \ eingesetzt wird. Suchen Siealso nach einem Punkt, schreiben Sie \. .

Gruppierungen

Nicht nur einzelne Zeichen, sondern auch Kombinationen lassen sich mit denWiederholungsoperatoren +, *, ? und {n,m} verwenden. Gruppen entstehen durchrunde Klammern:

(PHP)+

Neben der Funktion der Gruppierung dienen die Klammern auch dazu, Teilmus-ter zurückzugeben. In PHP entsteht am Ende nicht nur die Aussage »Mustergefunden«, sondern auch ein Array mit den Teilmustern, die durch Gruppen defi-niert wurden.

Anwendungsbeispiele

98

Bevor Sie mit regulären Ausdrücken beginnen, sollten Sie sich ein kleines Skriptbauen, das Eingabe und Test vereinfacht. Das folgende Listing zeigt eine einfacheVersion. Dabei lernen Sie auch gleich einige PHP-Funktionen kennen, die mit

Page 99: 382726314 X Php5 In 14 Tagen (Ddt)

Reguläre Ausdrücke

regulären Ausdrücken umgehen können. Sie beginnen immer mit dem Präfixpreg_2:

Listing 3.14: RegexTest.php – Ein Testskript für reguläre Ausdrücke

<?php$expression = isset($_POST['expression']) ? $_POST['expression']:'';$pattern = isset($_POST['pattern']) ? $_POST['pattern'] : '';echo <<<FORM <form action="{$_SERVER['PHP_SELF']}" method="post"> <table> <tr> <td>Ausdruck</td> <td><input type="text" name="expression" value="$expression"/></td> </tr> <tr> <td>Muster</td> <td><input type="text" name="pattern" value="$pattern"/></td> </tr> <tr> <td></td> <td><input type="submit" value="Test"/></td> </tr> </table> </form>FORM;if (!empty($expression)){ if (preg_match_all("~$pattern~", $expression, $result)) { echo "Das Muster ist im Ausdruck gefunden worden.<p/>"; if (count($result) > 0) { echo "Folgende Teilmuster gefunden:<br/>"; foreach ($result as $key => $val)

99

{ echo "<br/>Gruppe $key :<br/>"; if (is_array($val))

2 »Preg« steht für »Perl Regular Expressions«. Das Modul, auf dem die Funktionen in PHP basieren, hat sei-nen Ursprung in der Programmiersprache Perl.

Page 100: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

{ foreach ($val as $subkey => $subval) { echo "&nbsp;&nbsp;&nbsp;Teilgruppe $subkey => $subval"; } } else { echo "$val<br/>"; } } } } else { echo "Keine Übereinstimmung gefunden"; }} else { echo "Kein Muster angegeben.";}?>

Kern des Skripts ist die Funktion preg_match_all. Sie sucht alle Vorkommen desangegebenen Musters, bleibt also nicht stehen, wenn eine erste Übereinstimmunggefunden wurde. Für Testzwecke ist dies sehr hilfreich. Wird dagegen nur nacheinem (dem ersten) Vorkommen gesucht, reicht preg_match aus.

Neben dem einfachen Suchen kann man natürlich auch Suchen und Ersetzen,das heißt, gefundene Teilmuster gezielt austauschen.

Suchen und Ersetzen

Zum Suchen und Ersetzen stehen die Funktionen preg_replace undpreg_replace_callback bereit. Jede Fundstelle kann hier durch eine Ersatzzei-chenkette ausgetauscht werden. preg_replace_callback erledigt das auch für kom-plexe Aufgaben, denn hier wird für jede Fundstelle eine so genannteRückruffunktion aufgerufen, in der man zusätzliche Berechnungen anstellenkann. Das bringt viel Dynamik in den Ersetzungsvorgang.

Das folgende Beispiel zeigt die Anwendung. Ein Text soll nach einem Muster, hier

100

der Zeichenfolge ~#~ durchsucht werden. Das Nummernzeichen # soll dabeidurch eine fortlaufende Ziffer ersetzt werden. Mit preg_replace_callback ist dassehr einfach zu realisieren:

Page 101: 382726314 X Php5 In 14 Tagen (Ddt)

Reguläre Ausdrücke

Listing 3.15: RegexReplace.php – Ersetzen mit Hilfe einer Rückruffunktion

<?php$text = <<<TEXT~#~. Heute ist PHP5 erschienen!~#~. Das neue Buch zu PHP5 ist auch fast fertig.~#~. PHP5 enth&auml;lt viele neue Funktionen.~#~. Regul&auml;re Ausdr&uuml;cke gab es schon bei PHP4.TEXT;function replaceNumbers($text){ static $number = 1; return $number++;}$result = preg_replace_callback('|~#~|', 'replaceNumbers', $text);echo nl2br($result);?>

Der Funktionsaufruf preg_replace_callback enthält den Namen der Rückruffunk-tion replaceNumbers, die für jede Fundstelle aufgerufen wird, im Beispiel also vierMal. In der Funktion zählt eine statische Variable (static) die Aufrufe mit undgibt den aktuellen Wert, beginnend mit 1, zurück. Der Rückgabewert wird zumErsetzen der Zeichenfolge benutzt. Statische Variablen behalten ihren Wert undführen die Zuweisung nur beim ersten Aufruf aus.

Für die Ausgabe werden außerdem noch die normalen Zeilenumbrüche, wie siebei der Heredoc-Notation entstehen, in HTML-typische <br>-Tags konvertiert.

Noch einfacher ist preg_replace, wo anstatt der Rückruffunktion nur eine kon-stante Zeichenkette angegeben wird. Allerdings kann man auch hier etwas Dyna-mik bekommen, indem auf Gruppen zugegriffen wird. Zulässig ist nämlich die

Abbildung 3.9: Ausgabe des Skripts mit dynamisch ersetzten Ziffern

101

Angabe von Referenzen auf Gruppen des Ausdrucks. Wie bereits in der Einfüh-rung beschrieben, werden Gruppen durch runde Klammern gebildet. Auf diesekann mit einer speziellen Referenzsyntax zugegriffen werden.

Angenommen, Sie suchen nach einem bestimmten Datumsformat, beispielsweiseder Art 8/9/2004 (amerikanisches Format) in einem Text. Sie möchten nun an die-

Page 102: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

sen Stellen die deutsche Version 9.8.2004 sehen. Dies ist mit preg_replace ineinem Schritt erledigt.

Listing 3.16: RegexReplace2.php: Ersetzen mit Zugriff auf Teilmuster

<?php$text = <<<TEXTDieser Text enth&auml;lt ist am 9/8/2004 geschrieben worden,das Kapitel dazu aber schon am 4/17/2004. TEXT;$result = preg_replace('~(\d{1,2})/(\d{1,2})/(\d{4})~', � '\\2.\\1.\\3', $text);echo $result;?>

Das Muster untersucht den Text auf Datumsformate hin. \d steht für eine Ziffer,die im Beispiel für den Monat (erstes Teilmuster) ein oder zwei Mal vorkommendarf: \d{1,2}. Die Jahreszahl muss immer vierstellig sein. Wichtig an dem gezeig-ten Muster sind die drei runden Klammerpaare, die dafür sorgen, dass die erkann-ten Teile in separaten Variablen abgelegt werden. Die Schrägstriche werden damitzwar zur Erkennung herangezogen, fallen aber aus den Teilmustern heraus. In derErsatzzeichenkette kann nun auf die Teilmuster mit der Syntax \\n zugegriffenwerden, wobei n einfach die Nummer der öffnenden Klammer ist. \\2 bezeichnetalso die zweite Klammer, mithin der Tag im amerikanischen Datumsformat. DiePunkte in der Ersatzzeichenkette werden wie normale Zeichen behandelt.

Reguläre Ausdrücke können weit komplexer sein. Schauen Sie sich folgendesSuchmuster an:

(?<=,|^)([^,]*)(,\1)+(?=,|$)

Es sucht Duplikate in einer kommaseparierten Liste. Das ist nicht einfach, weil dieKommata stören. Eine kleine Analyse zeigt, wie es funktioniert:

Abbildung 3.10: Ausgetauschtes Datumsformat mit einem einzigen regulären Ausdruck

102

(? Erstes Teilmuster (unterdrückt, ?-Zeichen) <= Positiv (=) rückschauen (<) ,|^ Suche nach Komma oder (|) Textanfang (^))

Page 103: 382726314 X Php5 In 14 Tagen (Ddt)

Reguläre Ausdrücke

( Zweites Teilmuster (nicht unterdrückt) [^,]* Suche alles (*) außer dem Komma (^ negiert hier))( Drittes Teilmuster (nicht unterdrückt) ,\1 Komma mit folgendem Text, der dem ersten nicht unterdrückten Teilmuster (zweite Klammer)

entspricht – dies ist das Duplikat)+ Davon mindestens 1 oder beliebig viele(? Viertes Teilmuster (unterdrückt, ?-Zeichen) = Positiv (=) vorausschauen ,|$ Vollständigkeit untersuchen, es muss entweder ein

weiteres Komma oder (|) das Musterende ($) folgen)

Alles klar? Die Beispiele zeigen, wie stark reguläre Ausdrücke in der Praxis sind.Sie zeigen auch, dass reguläre Ausdrücke nicht trivial sind. Es kommt also wenigerauf die Anwendung der PHP-Funktionen an. Problematischer ist das Finden derpassenden Ausdrücke. Der folgende Abschnitt stellt deshalb einige häufig benutzteAusdrücke vor – zum Lernen und natürlich zum Anwenden.

Typische Suchmuster

In diesem Abschnitt werden einige häufig benötigte Suchmuster vorgestellt undkurz erläutert. Einerseits können Sie so reguläre Ausdrücke schnell anwenden,andererseits lernen, vergleichbare Muster selbst zu entwerfen. Die Muster werdennicht im Kontext eines PHP-Skripts dargestellt, da es sich hier lediglich um denimmer wieder gleichen Aufruf von preg_match, preg_match_all oder preg_replacehandeln würde.

Wenn Sie im Internet auf die Suche nach regulären Ausdrücken gehen,sollten Sie daran denken, dass es sehr viele Regex-Maschinen gibt, diekeineswegs zueinander vollkommen kompatibel sind. PHP verwendeteine weitgehend (aber nicht 100-prozentig) Perl-kompatible Methode.JavaScript, Unix-Shells, .NET, VBScript und andere Systeme weichendavon teilweise ab. Das Grundprinzip ist zwar allen gleich, aber die Syn-

103

tax variiert ebenso wie die Standardeinstellungen (beispielsweise ist Perlstandardmäßig »gierig«, und man unterdrückt dies explizit, während.NET »ungierig« ist und man die Option explizit einschalten muss). Siemüssen deshalb fremde Muster immer erst sorgfältig testen und eine

Page 104: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

scheinbare Fehlfunktion ist nicht immer dem Anbieter anzulasten, es seidenn, das Muster wurde explizit für PHP geschrieben.

Vorbemerkungen

Alle folgenden Beispiele verwenden als Begrenzungszeichen die Tilde (~). Wennin Ihrem Suchmuster nach der Tilde gesucht werden soll, müssen Sie ein anderesBegrenzungszeichen nutzen, das nicht im Muster selbst vorkommt.

Sie finden jeweils das Muster auf einen Blick und danach zeilenweise mit Erläute-rungen.

Sie finden auf der Buch-CD das Programm RegexExamples.php, das dieBeispiele enthält und ein Formular anbietet, mit dem sich schnelleTests ausführen lassen. Für die Ausführung ist JavaScript erforderlich.

HTML-Tags suchen

Tags zu suchen ist eine sehr häufige Aufgabe. Immer wieder müssen Daten um-und aufbereitet werden. Das folgende einfache Beispiel zeigt das Prinzip:

� HTML-Tag <b> suchen

~<b[^>]*>(.*?)</b>~i

~<b[^>]* Öffnendes b-Tag, alles außer >-Zeichen> Bis zum >-Zeichen(.*?) Beliebige Zeichen</b> Bis zum schließenden Tag~i Groß- und Kleinschreibung ist egal

� Beliebige Tags suchen

~<([A-Z][A-Z0-9]*)[^>]*>(.*?)</\\1>~iu

104

~<([A-Z] Das Tag muss mit einem Buchstaben beginnen [A-Z0-9]* Gefolgt von Buchstaben oder Zahlen ) Die Gruppe erfasst den Tagnamen [^>]*> Ende des Tags, beliebige Zeichen

innerhalb des Tags

Page 105: 382726314 X Php5 In 14 Tagen (Ddt)

Reguläre Ausdrücke

für Attribute(.*?) Beliebige Zeichen innerhalb des Tags</\\1> Der Tagname muss sich im schließenden Tag

wiederholen (eine Referenz)~iu Groß-/Klein egal, Gierigkeit aus

Umgang mit Leerzeichen

Leerzeichen umfassen im Allgemeinen alle beim Druck nicht sichtbaren Zeichen,also neben dem eigentlichen Leerschritt auch Tabulatoren und Zeilenumbrüche.Reguläre Ausdrücke verwenden dazu das Ersatzzeichen \s.

� Führende Leerzeichen

~^\s+~

~^ Zeichen muss am Beginn erscheinen\s+ Ein oder mehr Leerzeichen~

� Abschließende Leerzeichen

~\s+$~

~\s+ Ein oder mehr Leerzeichen$ Unmittelbar vor dem Ende des Textes~

IP-Adressen

Ebenso wie mit HTML-Tags hat man in der Webprogrammierung häufiger mit IP-Adressen zu tun. Drei Versionen sollen die möglichen Lösungsansätze zeigen:

� IP-Adressen (Schwache Version mit Gruppierung)Die Version erkennt zwar die Gruppen, prüft aber nicht den Sinn der Zahlen.IP-Nummern sind Bytes, habe Dezimal also einen Wertebereich zwischen 0

105

und 255. Das folgende Muster erkennt jedoch auch 980.378.275.455 noch alsgültig an:

~\b(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\b~

~

Page 106: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

\b Beliebige Wortgrenze(\d{1,3}) Erste Gruppe: 1 bis 3 Ziffern\. Gefolgt von einem Punkt(\d{1,3})\. Zweite Gruppe mit Punkt(\d{1,3})\. Dritte Gruppe mit Punkt(\d{1,3}) Letzte Gruppe\b~ gefolgt von einer Wortgrenze

� IP-Adressen (schwache Version ohne Gruppierung)Dieses Muster entspricht dem vorhergehenden, es verzichtet jedoch auf dieGruppierung der Teiladressen. Es ist vorzuziehen, wenn eine Auswertung dereinzelnen Blöcke nicht erforderlich ist.

~\b(?:\d{1,3}\.){3}\d{1,3}\b~

� IP-Adressen (starke Version, mit Gruppierung)Diese Version versucht etwas mehr Logik in die Sache zu bringen und erkenntnur korrekte Nummernkreise. Hier ist freilich fraglich, ob der Aufwand lohnt,ein Zugriff auf die Teilmuster des letzten Beispiels mit PHP und eine simplePrüfung (<= 255) ist vermutlich eleganter.

~\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) \.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b~

Der Unterschied besteht in der Definition der Ziffern:

(25[0-5] Beginnt mit 25, dann aber nur bis 5|2[0-4][0-9] Beginnt mit 20, 21, 22, 23, 24|[01]?[0-9][0-9]? Beginnt mit 0 oder 1 oder hat weniger

Stellen)

Prüfung von Zahlen

106

Wenn Daten aus Formularen angenommen werden, liegen diese immer als Zei-chenketten vor. Die Prüfung, ob es sich um korrekte Zahlen handelt, ist immerdann erforderlich, wenn die Weiterverarbeitung in numerischen Ausdrückenerfolgt und Fehler vorher abgefangen werden müssen.

Page 107: 382726314 X Php5 In 14 Tagen (Ddt)

Reguläre Ausdrücke

� Gleitkommazahlen (englisches Format, ganzer Ausdruck)

~^((?:[-]|[.]|[-.]|[0-9])[0-9]*)(?:(?:\.)([0-9]+))?$~

~

^ Muss am Textanfang beginnen ( Teilmuster (?: Keine Aufnahme als Teilmuster [-] Minuszeichen | Oder [.] Punkt | Oder [-.] Kombination aus beidem |Oder [0-9] Ziffer ) [0-9]* Gefolgt von 0 oder mehr Ziffern) Bilden die erste Gruppe(?: Unterdrückte Teilgruppe (?:\.) Beginnt mit Punkt, der ebenso bei der

Ausgabe unterdrückt wird ([0-9]+) Eine oder mehrere Ziffern)? Gruppe ein oder kein Mal$ Der Vergleichstext muss nun enden~

� Gleitkommazahlen (englisches Format mit Exponenten, beispielsweise 23e4)

Die folgende Variante funktioniert ähnlich, bindet jedoch zusätzlich noch dieBuchstaben e oder E mit ein, um den Exponenten abzutrennen.

~[-+]?([0-9]*\.)?[0-9]+([eE][-+]?[0-9]+)?~

� Ganzzahlen mit optional Vorzeichen (alle Vorkommen)

Dieses Muster ermittelt alle Vorkommen ganzer Zahlen in einem Text.

~[-+]?\b\d+\b~

~

107

[-+]? Optionales Vorzeichen\b Wortgrenze\d+ Ziffernfolge (eine oder mehrere Ziffern)\b Wortgrenze~

Page 108: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

� Hexzahlen im PHP-Format (z.B. 0xFF)

Das folgende Beispiel funktioniert ähnlich, setzt jedoch das für Hex-Literaletypische Präfix 0x davor. Die zweite Variante ist identisch, nutzt aber denSchalter i statt einer expliziten Angabe der Buchstaben in Groß- und Klein-schreibung.

~\b0[xX][0-9a-fA-F]+\b~

~\b0[X][0-9A-F]+\b~i

Datumsformate prüfen

Die folgenden Beispiele zeigen, wie mit Datumsformaten umgegangen werdenkann. Die Ausdrücke sind recht einfach, sodass sie sich leicht anpassen oder erwei-tern lassen.

� Datumsformat mm/dd/yyyy (Jahr nur 19XX und 20XX)

~(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01]) [- /.](19|20)\d\d~

~

(0[1-9]|1[012]) Monate 01-09 und 10, 11, 12[- /.] Erlaubte Trennzeichen

Abbildung 3.11: Das Muster fin-det alle ganzen Zahlen in einem Text (Bild vom Testprogramm)

108

(0[1-9] Tage 01 bis 09| oder[12][0-9] 10 bis 29 (1 oder 2 und 0 bis 9)| oder3[01] 30 oder 31

Page 109: 382726314 X Php5 In 14 Tagen (Ddt)

Reguläre Ausdrücke

)[- /.] Erlaubte Trennzeichen(19|20)\d\d 19nn oder 20nn, nn = jede Ziffer~

� Datumsformat yyyy-mm-dd (Jahr nur 19XX und 20XX)

Analog zum vorherigen Beispiel, aber mit anderer Reihenfolge.

~(19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12] [09]|3[01])~

Wörter suchen oder auf Merkmale hin analysieren

� Sucht die Schlüsselwörter print, printf, echo, print_r

Der Ausdruck ist recht einfach, die zulässigen Wörter sind lediglich durch |(oder) verknüpft.

~\b(print|echo|print_r|printf)\b~

� E-Mail-Adresse (einfache Version)

Bereits etwas anspruchsvoller, aber bei weitem noch nicht so perfekt wie mög-lich ist diese Prüfung einer E-Mail-Adresse.

~^([_a-z0-9-]+(\.[_a-z0-9-]+)*)@([a-z0-9-]+(?:\.[a-z0-9-]+)*)$~i

~

^ Prüfung ab Textanfang( Teilmuster für den Namen [_a-z0-9-]+ Beginnt mit _, -, Buchstaben, Zahlen (\. Gefolgt von einem Punkt [_a-z0-9-]+ Und _, -, Buchstaben oder Zahlen )* Diese Kombination ist optional)@ Das @-Zeichen( Teilmuster für die Domain [a-z0-9-]+ Zulässige Zeichen, ein oder mehr Mal

109

(?:\.[a-z0-9-]+) Toplevel, durch Punkt getrennt, das ?: am Anfang unterdrückt die Gruppe*) Das Teilmuster kann sich wiederholen$ Und der Textende ist auch Musterende~i Groß-/Klein ist wieder egal

Page 110: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

3.7 Datums- und Zeitfunktionen

PHP verfügt über vielfältige Datums- und Zeitfunktionen, die außerdem durch einKalendermodul ergänzt werden. Datumsberechnungen werden damit vereinfachtund helfen dabei, nutzerfreundliche Websites aufzubauen. In diesem Abschnittwerden verschiedene erweiterte Techniken der Datenausgabe vorgestellt. Dazugehört der Umgang mit Datums- und Zeitfunktionen sowie deren Formatierung.Aber auch die vielfältigen Möglichkeiten, Zahlen und Währungsangaben zu verar-beiten, werden behandelt.

Der Timestamp und die Serverzeit

Basis aller Zeitberechnungen in PHP bildet der Unix-Timestamp (Zeitstempel).Dies ist ein sekundengenauer Zähler, der den Zeitraum seit dem 1.1.1970 zählt.Dargestellt wird er im 32-Bit-Zahlenraum (Integer), sodass er Datumswerte bis2037 enthalten kann. Solange man sich innerhalb dieses Zeitraumes bewegt, sinddie internen Funktionen außerordentlich wirkungsvoll und einfach. Daten, diedavor oder danach berechnet werden sollen, bedürfen einer etwas eingehenderenBetrachtung.

Wenn Sie mit Zeitwerten arbeiten, die die aktuelle Zeit reflektieren, müssen Siebeachten, dass der Server möglicherweise nicht in Ihrer Zeitzone oder der derkünftigen Nutzer steht. Manche Provider stellen ihre Server auch nicht auf dieZeitzone ein, in der die Maschine physisch steht, sondern auf UTC (UniversalTime Conversion, früher als GMT, Greenwich Mean Time bezeichnet). Für dieAbfrage der lokalen Zeit des Browser benötigen Sie JavaScript; PHP allein hilfthier nicht wirklich weiter. Lesen Sie mehr dazu im Abschnitt »Tricks mit Java-Script: Lokale Zeit ermitteln« ab Seite 117.

Rechnen mit Datums- und Zeitwerten

Um unter PHP mit Datums- und Zeitwerten rechnen zu können, müssen Sie

110

zuerst über einen Zeitstempel verfügen – entweder den aktuellen (Jetzt-Zeit) oderden für einen bestimmten Zeitpunkt. Für beides gibt es entsprechende Funk-tionen.

Page 111: 382726314 X Php5 In 14 Tagen (Ddt)

Datums- und Zeitfunktionen

Die aktuelle Zeit wird mit time ermittelt. Eine bestimmte Zeit wird dagegen mitmktime (mk = engl. make; machen, erzeugen) erzeugt. Das folgende Beispiel zeigtdies und gibt die Zeitstempel im »Rohformat« aus.

Listing 3.17: time.php – Der aktuelle Zeitstempel

<?phpecho time();?>

Berechnungen mit Zeitwerten sind auf Basis des Zeitstempels sehr einfach. Da essich um ein Maß in Sekunden handelt, muss zur Berechnung eines anderenDatums lediglich die nötige Anzahl Sekunden abgezogen oder hinzugefügt wer-den. Dabei helfen folgende Umrechnungen:

� 1 Minute = 60 Sekunden

� 1 Stunde = 3.600 Sekunden

� 1 Tag = 86.400 Sekunden

� 1 Woche = 604.800 Sekunden

Monate sind naturgemäß unterschiedlich lang. Um also die Länge eines Monatszu ermitteln, muss bekannt sein, in welchem Jahr er liegt (auch wenn dies nurbeim Februar zu unterschiedlichen Ergebnissen führen kann).

Ausgabe formatierter Zeit- und Datumswerte

Nachdem das benötigte Datum nun ermittelt wurde, muss es meist formatiert wer-den. Mit den typischen Zeitstempeln kann in der Regel kein Benutzer etwas anfan-gen. Zur Speicherungen in Datenbanken sind sie dagegen gut geeignet. Vor allemwenn die spätere Formatierung flexibel angewendet werden soll, ist das Vorhaltender Datumswerte in Form von Zeitstempeln optimal.

Zur Formatierung bietet PHP gleich zwei Funktionen an: date und strftime.Während date ausschließlich englisch »spricht«, kennt strftime auch eine Lokali-sierung. Die Dopplung der Funktionen ist historisch bedingt und erschwert leider

111

den Umgang mit PHP etwas, weil derartige Inkonsistenzen die Lernkurve nichteben ebnen. Es bleibt in der Praxis nichts weiter übrig, als mit beiden Funktionenparallel zu arbeiten.

Page 112: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

Die folgende Tabelle stellt die beiden Funktionen und deren Formatparametergegenüber.

date strftime Beschreibung

a %p Erzeugt den Schriftzug »am« oder »pm«

A %r Erzeugt den Schriftzug »AM« oder »PM«

B Zeigt die Swatch-Internetzeit an, eine Zeitform, die den Tag ohne Zeitzonen in 1.000 Einheiten teilt

d %d Gibt den Tag im Monat an; zwei Ziffern mit führender Null zwi-schen 01 und 31 werden ausgegeben

%e Gibt den Tag im Monat mit führendem Leerzeichen bei einstelli-gen Werten an

D %a Erzeugt den Tag der Woche mit drei Buchstaben, beispielsweise »Fri«

F %B Ausgeschriebener Monatsname, beispielsweise »January«

h Stunde im 12-Stunden-Format zwischen 01 und 12 mit führender Null

H Stunde im 24-Stunden-Format zwischen 00 und 23 mit führender Null

g %I Stunde im 12-Stunden-Format ohne führende Null, also zwischen 1 und 12

G %H Stunde im 24-Stunden-Format ohne führende Null, also zwischen 00 und 23

i Minuten 00 bis 59 mit führender Null

%M Minuten 0 bis 59

I 1 in der Sommerzeit, sonst 0.

112

j Tag des Monats ohne führende Null: 1 bis 31 sind mögliche Werte.

l %A Ausgeschriebener Tag der Woche: »Friday«

Tabelle 3.6: Formatzeichen der Datumsformatierfunktion date und strftime

Page 113: 382726314 X Php5 In 14 Tagen (Ddt)

Datums- und Zeitfunktionen

L Boolescher Wert für das Schaltjahr: 0 oder 1

m %m Monat des Jahres von 01 bis 12, also mit führender Null

n Monat des Jahres ohne führende Null: 1 bis 12

M %b Monat als Abkürzung mit drei Buchstaben: »Jan«

O Zeitdifferenz zu UTC in Stunden als Differenzzeichenkette, bei-spielsweise »+0400«

r Datum und Zeit im RFC 822-Format. Folgendes Format wird ver-wendet: »Mon, 19 Mar 2004 12:05:00 +0100«. Die RFC 822 defi-niert Datumsformate, wie sie in E-Mails verwendet werden.

s %S Sekunden 00 bis 59 mit führender Null

S Suffix der englischen Ordnungszahlen: »th«, »nd« usw.

t Tage des Monats: 28 bis 31. Verwechseln Sie dies nicht mit der Nummer des Tages.

T Zeitzoneneinstellung des Computers, beispielsweise »EST« oder »MDT«

U Sekunden der UNIX-Epoche (seit 1.1.1970). Entspricht der Aus-gabe der Funktion time.

w %w Tag der Woche, beginnend mit 0 (Sonntag) bis 6 (Samstag)

W ISO-8601 Wochennummer des Jahres (ab erste Woche mit einem Montag drin)

Y %Y Jahr im vierstelligen Format »2001«

y %y Jahr im zweistelligen Format »01«

z %j Tag im Jahr: 0 bis 365

Z %Z Offset der Zeitzonen gegen UTC in Sekunden von -43 200 bis

date strftime Beschreibung

113

43 200 (86 400 Sekunden entspricht 1 Tag).

%G Das Jahr wie %Y, aber nur bei vollen Wochen. Angefangene Wochen zählen im vorhergehenden oder im folgenden Jahr

Tabelle 3.6: Formatzeichen der Datumsformatierfunktion date und strftime (Forts.)

Page 114: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

Beachten Sie, dass die Funktion date nur englische Namen fürWochentage oder Monate erzeugen kann. Benutzen Sie in lokalisiertenUmgebungen besser immer strftime.

Das Prinzip der Formatzeichen ist relativ einfach. Man stellt sich dazu eine Zei-chenkette zusammen, die alle benötigten Formatzeichen enthält. Für strftimesieht das dann beispielsweise folgendermaßen aus:

%d.%m.%Y

Nun ist der Funktion noch ein Datumswert zu übergeben. Aus diesem werden die

%g Wie %G, aber kurzes Jahresformat wie %y

%x Zeit nach lokalen Systemeinstellungen

%X Datum nach lokalen Systemeinstellungen

%c Zeit + Datum nach lokalen Systemeinstellungen

%D Entsprechung der Folge »%m/%d/%y«

%U Wochennummer, beginnt mit Sonntag

%V Kalenderwoche nach ISO 8601

%W Wochennummer, beginnt mit Montag

%R Komplette Zeit im 24-Stunden-Format

%C Jahrhundert (2003 gibt »20« zurück)

%t Tabulator

%n Zeilenvorschub

%% Prozentzeichen

date strftime Beschreibung

Tabelle 3.6: Formatzeichen der Datumsformatierfunktion date und strftime (Forts.)

114

benötigten Datumsfragmente dann extrahiert. Bei strftime kommt noch hinzu,dass die Lokalisierung vorher eingestellt werden kann, um die Namen von Mona-ten oder Wochentagen für eine bestimmte Sprache zu erzeugen. Das folgendeBeispiel zeigt, wie das geht:

Page 115: 382726314 X Php5 In 14 Tagen (Ddt)

Datums- und Zeitfunktionen

Listing 3.18: DateStrftime.php – Lokalisierte Datumsformatierung (hier: Windows-Version)

setlocale(LC_TIME, "ge");echo strftime("%A, %d. %B %Y", time());

Die Funktionen date und strftime geben jeweils die formatierte Zeichenkettezurück, sodass anschließend die Ausgabe mit echo oder print erfolgen kann. DieAngabe der Lokalisierung mit setlocale ist dagegen etwas tückisch, denn diebenötigten Angaben werden dem Betriebssystem entnommen und hier gibt esUnterschiede zwischen der Windows- und der Linux-Version. Mehr dazu findenSie im Abschnitt »Lokalisierung und Formatierung von Zeichen« ab Seite 376.

Jetzt muss man natürlich feststellen, auf welcher Plattform man gerade das Skriptlaufen lässt. Ein Plattformwechsel ist durchaus typisch, weil die meisten Entwick-ler ihre Skripte auf Windows entwickeln und beim Provider unter Linux im Pro-duktionsbetrieb laufen lassen. Dazu kann die Konstante PHP_OS eingesetzt werden:

Listing 3.19: DateStrftimeExt.php: Datumsausgaben betriebsystemunabhängig steuern

$sl = TRUE;if (PHP_OS == "WINNT"){ $sl = setlocale(LC_TIME, "German_Germany");}else { $sl = setlocale(LC_TIME, "de_DE");}if ($sl){ echo strftime("%A, %d. %B %Y", time());}else{ echo "Lokalisierung konnte nicht gesetzt werden";}

115

setlocale gibt entweder die aktuelle Einstellung zurück, wenn die neueakzeptiert werden konnte, oder FALSE, wenn die Zuweisung misslang.Auf dieser Rückgabe beruht die im letzten Skript gezeigte Ausgabesteue-rung.

Page 116: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

Die Konstante PHP_OS gibt auf Windows-Systemen »WINNT« zurück, unabhängigdavon, ob es sich um Windows NT, 2000 oder XP handelt. Auf Linux-Systemensteht »Linux« drin, auf FreeBSD beispielsweise »FreeBSD«. Um auch ältere Win-dows-Systeme zu erfassen, ist folgende Abfrage noch robuster:

if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { echo 'Dieser Server läuft unter Windows!';}else { echo 'Dieser Server läuft nicht unter Windows!';}

Beachten Sie, dass die Abfrage des Betriebssystems nichts mit dem Sys-tem zu tun hat, das der Benutzer der Seite nutzt bzw. auf dem der Brow-ser läuft.

Abschließend noch ein Wort zu der stillschweigend genutzten KonstantenLC_TIME. Damit wird setlocale mitgeteilt, dass die Einstellung nur für Zeit- undDatumswerte erfolgen soll. PHP kennt auch andere Funktionen, deren Ausgabensich lokalisieren lassen. Diese verlangen andere Konstanten. Mit LC_ALL wird dieLokalisierung global umgestellt. Im Abschnitt 9.1 »Mehrsprachige Webseiten« abSeite 374 finden Sie mehr Informationen dazu.

Datumsfragmente mit idate

Wenn man nur einen bestimmten Teil aus einem Zeitstempel benötigt, ist idateeine interessante Funktion, die mit PHP5 neu eingeführt wurde. Die folgendeTabelle zeigt, welche Formatanweisungen möglich sind:

Format Beschreibung

B Swatch Internet Time

116

d Tag des Monats

h Stunde (12:00 Format)

H Stunde (24:00 Format)

Tabelle 3.7: Formatanweisungen der Funktion idate

Page 117: 382726314 X Php5 In 14 Tagen (Ddt)

Datums- und Zeitfunktionen

Die Funktion gibt immer einen Zahlenwert zurück, was zwangsläufig bedeutet,dass immer nur eine der Formatanweisungen benutzt werden kann:

$jahr = idate('Y');

Der zweite, optionale Parameter bestimmt über einen Zeitstempel die zugrundeliegende Zeitangabe. Beachten Sie bei den zweistelligen Jahreszahlen, dass auchdiese als Zahl zurückgegeben werden. Das Jahr 2004 wird also mit dem Parameter»y« als Zahl 4, nicht als Zeichenkette »04« erscheinen.

i Minute

I 1, wenn Sommerzeit, sonst 0

L 1, wenn Schaltjahr, sonst 0

m Nummer des Monats

s Sekunden

t Tag im Monat

U Sekunden seit Beginn der Unix-Epoche, entspricht der von time erzeug-ten Ausgabe.

w Tag der Woche, wobei 0 der Sonntag ist.

W ISO-8601-Wochennummer. Die erste Woche beginnt am ersten Mon-tag im Jahr.

y Jahr (1 oder 2 Ziffern, 2005 wird als »5« zurückgegeben)

Y Jahr (4 Ziffern)

Z Tag im Jahr

Z Offset der Zeitzone in Sekunden

Format Beschreibung

Tabelle 3.7: Formatanweisungen der Funktion idate (Forts.)

117

Tricks mit JavaScript: Lokale Zeit ermitteln

Um die lokale Zeit eines Benutzers zu ermitteln, benötigen Sie JavaScript. Meistist es ausreichend, diese Information nur einmal, gleich beim ersten Seitenaufruf,

Page 118: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

zu ermitteln. Es bietet sich an, dazu das Refresh-Tag von HTML einzusetzen, daseinen erneuten Aufruf der Seite erzwingt. Der Benutzer muss deshalb nicht ein-greifen, ein Formular senden oder einen Link klicken. Gleichzeitig können Siefeststellen, ob JavaScript überhaupt aktiviert ist.

Das Ganze basiert auf einem einfachen Trick. Zuerst wird mit PHP festgestellt, obeine Zeitinformation bereits vorliegt. Das ist beim ersten Aufruf der Seite natürlichnicht der Fall. Dann wird eine JavaScript-Sequenz erzeugt, die die lokale Zeitabfragt. Es ist hilfreich, dazu ein Formular einzusetzen und das Formular dann perJavaScript senden zu lassen.

Listing 3.20: datejavascript.php – Lokale Zeit des Browsers ermitteln

<?phpecho isset($_POST['timeField']) ? "Time: {$_POST['timeField']}" : � "Keine Zeit";?><!-- Seiteninhalt --><?phpif (!isset($_POST['timeField']) && $_SERVER['REQUEST_METHOD'] == 'GET'){ echo <<<JAVASCRIPT <form name="timeForm" method="post"> <input type="hidden" name="timeField" value=""/> </form> <script language="JavaScript"> var today = new Date(); document.timeForm.timeField.value = today.toLocaleString(); document.timeForm.submit(); </script>JAVASCRIPT;}?>

Der Auslöser für das JavaScript ist die erste Zeile des zweiten PHP-Blocks:

118

if (!isset($_POST['timeField']) && $_SERVER['REQUEST_METHOD'] == 'GET')

Page 119: 382726314 X Php5 In 14 Tagen (Ddt)

Formatieren und Ausgeben

Hier wird einmal abgefragt, ob ein Formular oder eine Seite gesendet wurde. Beimersten Aufruf der Seite über den Browser kann es sich nie um ein Formular han-deln. Die Variable $_SERVER['REQUEST_METHOD'] enthält diese Information. DerUmgang mit derartigen Informationen wird später noch ausführlich behandelt.Der linke Teil der Abfrage umfasst die Erkennung der gesendete Zeitinformation.Verpackt wird diese in einem versteckten Feld im Formular mit dem Namen»timeForm«. Der Rest ist reines JavaScript. Bei ersten Aufruf wird der Skriptblockerzeugt und dann sofort ausgeführt. Dies führt zum Senden des Formulars, wasPHP wiederum wie beschrieben erkennt.

Ist JavaScript nicht vorhanden, verbleibt die Seite im ersten Zustand, entsprechendist die Variable $_POST['timeField'] niemals gesetzt. Darauf zielen die Abfragenmit der Funktion isset ab, die testet, ob eine Variable initialisiert wurde.

3.8 Formatieren und Ausgeben

Neben Datumsangaben sind meist Zeichenketten und Zahlen zu formatieren. Vorallem mehrsprachige Webseiten profitieren von den Möglichkeiten, die PHP bie-tet.

Zahlen formatieren

Eine wichtige Formatierfunktion ist number_format. Damit lassen sich Zahlen –intern immer im englischen Format vorliegend – in das im deutschsprachigenSprachraum übliche Format umwandeln. Das folgende Skript zeigt die Anwendung:

Listing 3.21: NumberFormat.php: Zahlen für eine deutsche Website formatieren

$z1 = 100000;$z2 = 2.67;$z3 = $z1 / $z2;echo "$z3 <br>";

119

echo number_format($z3, 2, ',', '.');

Die vier Parameter der Funktion number_format haben folgende Bedeutung:

1. Zahl, die formatiert werden soll.

2. Anzahl der Dezimalstellen, auf die gerundet werden soll.

Page 120: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

3. Zeichen für das Dezimaltrennzeichen (deutsch: Komma, englisch: Punkt).

4. Zeichen für das Tausendertrennzeichen (optional, deutsch: Punkt, englisch:Komma).

Umwandeln, Anpassen und Ausgeben

Mehr Möglichkeiten der Formatierung bieten die rund um printf etabliertenFunktionen, die teilweise aus der C-Welt übernommen wurden:

� printfDie Funktion formatiert einen oder mehrere Werte und gibt das Resultat wieprint sofort aus.

� sprintf Die Funktion formatiert einen oder mehrere Werte und gibt das Resultat alsZeichenkette zurück.

� vprintf Die Funktion formatiert einen oder mehrere Werte aus einem Array und gibtdas Resultat wie print sofort aus.

� vsprintf Die Funktion formatiert einen oder mehrere Werte aus einem Array und gibtdas Resultat als Zeichenkette zurück.

� sscanf Hiermit lässt sich eine Zeichenkette anhand vorhandener Funktionsmusterdurchsuchen und die Fundstellen werden in Variablen abgelegt.

� fscanf Funktioniert wie sscanf, als Eingabe wird aber eine Datei erwartet.

Der Trick bei der Nutzung besteht auch hier, ähnlich wie bei strftime, in der

Abbildung 3.12: Unformatierte (oben) und formatierte (unten) Zahl im Vergleich

120

Nutzung von Formatangaben. Die Eingabewerte werden dann so verändert, dasssie den Formatangaben entsprechen. Alle hier vorgestellten Funktionen verwen-den denselben Satz an Formaten, der nachfolgend vorgestellt wird.

Page 121: 382726314 X Php5 In 14 Tagen (Ddt)

Formatieren und Ausgeben

Die Formatstruktur

Jede Formatanweisung wird einem Datenwert zugeordnet. Sie beginnt immer mitdem Prozentzeichen als Sonderzeichen. Soll ein Prozentzeichen ausgegeben wer-den, schreibt man %%. Nach dem Prozentzeichen folgen (optional) verschiedeneFormathinweise, die abhängig von der Datenart sind. Zwischen dem Prozentzei-chen und den Formatinstruktionen kann optional ein Verweis auf einen bestimm-ten Datenwert stehen, bestehend aus der Argumentnummer und einem Backslash.Alle Funktionen erlauben die Angabe mehrerer Datenwerte, entweder als Parame-terkette oder als Array. Der Verweis auf einen bestimmten Parameter erhöht dieFlexibilität.

Am Ende steht dann eine Instruktion, die die Art der Formatierung bestimmt:

� bDas Argument wird als ganze Zahl betrachtet und als Binär-Wert ausgegeben.

� cDas Argument wird als ganze Zahl betrachtet und das entsprechende ASCII-Zeichen wird ausgegeben.

� dDas Argument wird als ganze Zahl betrachtet und ein Dezimalwert (mit Vor-zeichen) wird ausgegeben.

� uDas Argument wird als ganze Zahl betrachtet und ein Dezimalwert (ohne Vor-zeichen) wird ausgegeben.

� fDas Argument wird als float angesehen und eine Fließkomma-Zahl wird aus-gegeben.

� oDas Argument wird als ganze Zahl betrachtet und als Oktalzahl ausgegeben.

� sDas Argument wird als Zeichenkette betrachtet und unverändert ausgegeben.

121

� xDas Argument wird als ganze Zahl betrachtet und als Hexadezimal-Wert aus-gegeben (mit Kleinbuchstaben für die Hex-Zeichen »a« bis »f«).

Page 122: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

� XDas Argument wird als ganze Zahl betrachtet und als Hexadezimal-Wert aus-gegeben (mit Großbuchstaben für die Hex-Zeichen »A« bis »F«).

Alles zusammen sieht dann beispielsweise folgendermaßen aus:

� %3\sDiese Zeichenfolge gibt das dritte Argument (3\) als Zeichenkette (s) aus.

� %dDiese Zeichenfolge gibt das in der Liste nächste Argument als Dezimalzahlaus.

� %082dAuch dies formatiert eine Dezimalzahl. Die drei Zeichen haben hier folgendeBedeutung:

1. Füllwert, entweder ein Leerzeichen, die Zahl 0 oder ein beliebiges, voneinem Apostroph ’ eingeleitetes Zeichen

2. Die Anzahl der Stellen vor dem Dezimaltrennzeichen, auf die aufgefülltwird.

3. Die Anzahl der Stellen nach dem Dezimaltrennzeichen.

� %4\’_-10sDieses Format formatiert eine Zeichenkette linksbündig (das macht dasMinuszeichen, rechtsbündig ist der Standard), die das vierte Argument derParameterkette (4\) nutzt. Zum Auffüllen wird der Unterstrich benutzt (’_), derdie Zeichenkette auf zehn (10) Zeichen auffüllt. »s« definiert den Datenwertals Zeichenkette.

Die Beispiele und ihre Wirkung können Sie im folgenden Listing sehen:

Listing 3.22: FormatPrintf.php – Verschiedene Formatierungen

$s1 = "Markt+Technik";$s2 = 34.90;echo "$z3 <br>";

122

printf("Drittes Argument, Zeichenkette: <b>%3\$s </b><br> Nächstes Argument, Zahl: <b>%d </b><br> Aufgefüllte 4. Zeichenkette: <b>%4\$'_-10s </b><br> Nächstes Argument, Zahl: <b>%08.2f </b><br>", $s2, $s2,

Page 123: 382726314 X Php5 In 14 Tagen (Ddt)

Formatieren und Ausgeben

$s1, '16%');

Bei der Auswahl der passenden Werte gilt im Beispiel folgende Zuordnung:

� \3 und \4 deuten klar auf den passenden Wert hier: \3 erhält $s1, \4 das Literal'16%'.

� Die übrigen Formate haben keine Präferenzen zu einem spezifischen Wertund suchen deshalb die Argumente vom ersten beginnend ab: $d erhält $s2und das letzte Format den zweiten Wert, der ebenfalls $s2 enthält.

Für die Generierung von Farbwerten in HTML lässt sich das Formatzeichen »X«sehr gut nutzen. Wichtig ist hier, die Anzahl der Stellen mit 2 vorzugeben.Andernfalls würden kleine Werte (unter 16) lediglich eine Stelle belegen und derFarbcode damit nicht korrekt aufgebaut werden. Ein Füllzeichen muss nicht ange-geben werden, für Zahlen ist die 0 das voreingestellte Zeichen.

Listing 3.23: FormatSPrintf.php – Formatierung von HTML-Farbwerten

$r = 134;$g = 211;$b = 56;$code = sprintf("#%2X%2X%2X", $r, $g, $b);echo <<<DIV <div style="width:200px;height:100px;background-color:$code"> </div>DIV;

Im Quellcode der Seite sieht man, wie der Farbwert formatiert wurde:

<div style="width:200px;height:100px;background-color:#86D338"></div>

Abbildung 3.13: Komplexe Formatierungen mit Formatanweisungen

123

Lokalisierung mit setlocale

Informationen zu setlocale folgen im Abschnitt »Lokalisierung und Formatie-rung von Zeichen« ab Seite 376.

Page 124: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

Werte erkennen

Die Funktionen sscanf und fscanf drehen den Vorgang um. Erwartet wird eineZeichenkette, die bestimmte Wertfragmente enthält. Daraus wird dann der Wertextrahiert und an eine bereitgestellte Variable übergeben. Das letzte Beispiel eig-net sich besonders gut zur »Umkehrung«:

Listing 3.24: FormatScanf.php – Daten aus einer Zeichenkette extrahieren

$r = 134;$g = 211;$b = 56;$code = sprintf("#%2X%2X%2X", $r, $g, $b);echo "Die Farbwerte für $code sind: ";list($rr, $gg, $bb) = sscanf($code, "#%2X%2X%2X");echo "R = $rr, G = $gg, B = $bb";

Die Ausgabe zeigt, dass alle Farbwerte korrekt erkannt wurden. Trickreich ist hierdie Übernahme des von sscanf erzeugten Arrays mit list in die drei erwartetenVariablen. Mehr Informationen zu Array folgen im Abschnitt 5.1 »Datenfelder imEinsatz: Arrays« ab Seite 182.

Allerdings kann sscanf nicht erkennen, wenn die gesuchte Zeichenfolge in eineranderen eingebettet ist. Dafür kommen nur reguläre Ausdrücke in Frage, die mehrFormatangaben erlauben, dafür aber ungleich komplexer sind.

3.9 Referenz

Dieser Abschnitt enthält eine Übersicht aller in diesem Kapitel behandelten Funk-

Abbildung 3.14: Die einzelnen dezimalen Farb-werte wurden aus der hexadezima-len Folge extrahiert

124

tionen in tabellarischer Form.

Page 125: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz

Zeichenketten-Funktionen

Insgesamt kennt PHP5 knapp 90 Zeichenkettenfunktionen. Es ist sinnvoll, sichdieser schieren Fülle strukturiert zu nähern. Zuerst einmal kann man Gruppen bil-den, um die Funktionsnamen den bereits erwähnten typischen Operationen zuzu-ordnen. Sie finden dann weitaus schneller die passende Funktion.

Funktion Bedeutung

strpos Sucht eine Zeichenfolge in einer anderen und gibt die Position der Fundstelle zurück. Berücksichtigt Groß- und Kleinschreibung.

stripos Sucht eine Zeichenfolge in einer anderen und gibt die Position der Fundstelle zurück. Beachtet Groß- und Kleinschreibung nicht.

strrpos Sucht das letzte Auftreten einer Zeichenfolge und gibt die Position der Fundstelle zurück. Berücksichtigt Groß- und Kleinschreibung.

strchr Sucht ein einzelnes Zeichen in einer Zeichenfolge und gibt den Rest der Zeichenkette ab der Fundstelle zurück.

strrchr Sucht das letzte Auftreten eines Zeichens in einer Zeichenfolge und gibt den Rest der Zeichenkette ab der Fundstelle zurück.

strstr Sucht eine Zeichenfolge in einer anderen und gibt den Rest der Zei-chenfolge ab der Fundstelle zurück. Berücksichtigt Groß- und Klein-schreibung.

stristr Sucht eine Zeichenfolge in einer anderen und gibt den Rest der Zei-chenfolge ab der Fundstelle zurück. Beachtet Groß- und Kleinschrei-bung nicht.

strcmp Vergleicht zwei Zeichenfolgen.

strspn Ermittelt die Anzahl der Zeichen, die bei zwei zu vergleichenden Zeichenfolgen übereinstimmen.

strcspn Ermittelt die Anzahl der Zeichen, die bei zwei zu vergleichenden Zeichenfolgen nicht übereinstimmen.

125

strcasecmp Vergleicht zwei Zeichenfolgen auf binärer Basis. Beachtet Groß- und Kleinschreibung.

Tabelle 3.8: Zeichenkettenfunktionen zum Vergleichen, Suchen und Ersetzen

Page 126: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

strnatcasecmp Vergleicht zwei Zeichenfolgen auf binärer Basis und berücksichtigt die »natürliche« Erscheinungsform. Beachtet Groß- und Kleinschrei-bung.

strnatcmp Vergleicht zwei Zeichenfolgen auf binärer Basis und berücksichtigt die »natürliche« Erscheinungsform. Beachtet Groß- und Kleinschrei-bung nicht.

strncasecmp Vergleicht nur eine gegebene Anzahl von Zeichen zweier Zeichen-folgen und beachtet Groß- und Kleinschreibung dabei nicht.

strncmp Vergleicht nur eine gegebene Anzahl von Zeichen zweier Zeichen-folgen und beachtet Groß- und Kleinschreibung dabei.

str_replace Ersetzt Zeichen in einer Zeichenfolge durch andere Zeichen oder löscht sie

str_ireplace Wie str_replace, beachtet aber Groß- und Kleinschreibung nicht.

substr_replace Wie str_replace, jedoch kann der Suchbereich eingeschränkt wer-den.

Funktion Bedeutung

strlen Anzahl der Zeichen

crc32 Prüfsumme über eine Zeichenfolge

count_chars Häufigkeitsanalyse über in der Zeichenfolge enthaltenen Zeichen

str_word_count Häufigkeitsanalyse über die Wörter eines Textes

md5 MD5-Hash (Message Digest 5, eine Art Prüfsumme) einer Zeichen-folge

sha1 SHA1-Hash, eine andere Art Prüfsumme

Funktion Bedeutung

Tabelle 3.8: Zeichenkettenfunktionen zum Vergleichen, Suchen und Ersetzen (Forts.)

126

similar_text Diese Funktion berechnet die Ähnlichkeit zweier Zeichenketten als Prozentwert.

Tabelle 3.9: Zeichenkettenfunktionen zum Ermitteln von Eigenschaften

Page 127: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz

soundex Berechnet die Lautähnlichkeit von Zeichenkette

levenshtein Berechnet die Lautähnlichkeit nach dem Levenshtein-Algorithmus

metaphone Lautähnlichkeit analog zu saindex, aber unter Berücksichtigung der Grundregeln der englichen Sprache.

substr_count Ermittelt, wie oft eine Zeichenfolge in einer anderen steckt

Funktion Bedeutung

substr Gibt einen Teil einer Zeichenkette zurück.

trim, chop Entfernt Leerzeichen und Zeilenumbrüche vom Anfang und vom Ende.

ltrim Entfernt Leerzeichen und Zeilenumbrüche vom Anfang.

rtrim Entfernt Leerzeichen und Zeilenumbrüche vom Ende.

ucfirst Macht den ersten Buchstaben einer Zeichenkette zum Großbuchsta-ben.

ucwords Macht den ersten Buchstaben jedes Wortes zum Großbuchstaben.

strtolower Verwandelt alle Buchstaben in Kleinbuchstaben.

strtoupper Verwandelt alle Buchstaben in Großbuchstaben.

wordwrap Bricht längeren Text durch Einfügen von Zeilenumbrüchen um.

strtr Tauscht einzelne Zeichen auf Basis einer Austauschtabelle aus.

strtok Zerlegt eine Zeichenfolge durch sukzessive Aufrufe in Teile.

strrev Dreht eine Zeichenfolge um.

tr_shuffle Bringt die Zeichen mittels Zufallsgenerator durcheinander.

Funktion Bedeutung

Tabelle 3.9: Zeichenkettenfunktionen zum Ermitteln von Eigenschaften (Forts.)

127

strip_tags Entfernt HTML-Tags aus einer Zeichenfolge.

chunk_split Teilt eine Zeichenkette in Abschnitte gleicher Größe.

Tabelle 3.10: Zeichenkettenfunktionen zur Auswahl einer Teilzeichenkette und andere Umwandlungsoperationen

Page 128: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

str_rot13 Führt eine primitive Kodierung nach ROT13 aus.

str_repeat Wiederholt eine Zeichenkette mehrfach und gibt die resultierende Zeichenfolge zurück.

str_pad Füllt eine Zeichenfolge links oder rechts mit Füllzeichen auf.

Funktion Bedeutung

addslashes, addcslashes

Fügt Backslashes hinzu.

stripslashes, stripcslashes

Entfernt Backslashes.

Tabelle 3.11: Zeichenkettenfunktionen zur Behandlung einzelner Zeichen

Funktion Bedeutung

chr Gibt das dem Zeichencode entsprechende Zeichen zurück.

ord Gibt den Zeichencode zum angegebenen Zeichen zurück.

Tabelle 3.12: Zeichenkettenfunktionen zum Ermitteln von bestimmten Zeichen mit besonde-rer Bedeutung

Funktion Bedeutung

bin2hex Wandelt binäre Daten in ihre hexadezimale Form um

html_entity_decode Wandelt HTML-Entitäten in Zeichen um

html_entities Wandelt alle Sonderzeichen in HTML-Entitäten um

Funktion Bedeutung

Tabelle 3.10: Zeichenkettenfunktionen zur Auswahl einer Teilzeichenkette und andere Umwandlungsoperationen (Forts.)

128

html_specialchars Wandelt nur XML-Spezialzeichen in HTML-Entitäten um

Tabelle 3.13: Zeichenkettenfunktionen zum Umwandeln in einen anderen Datentyp und HTML-abhängige Funktionen

Page 129: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz

Funktionen für reguläre Ausdrücke

Referenz Ausgabe- und Datumsfunktionen

Funktion Bedeutung

setlocale Setzt eine bestimmte Lokalisierung

number_format Formatiert Zahlen

money_format Formatiert Währungsangaben, ist jedoch nur auf Linux verfügbar

localeconv Ermittelt die aktuellen Formatierbedingungen für Zahlen

Tabelle 3.14: Zeichenkettenfunktionen zum sprach- oder landesabhängigen Formatieren

Funktion Bedeutung

preg_match Ermittelt ein zutreffendes Suchmuster

preg_match_all Ermittelt alle zutreffendes Suchmuster

preg_grep Durchsucht ein Array nach einem Suchmuster und filtert Fund-stellen

preg_split Teilt eine Zeichenkette an einem Suchmuster und gibt jede Fund-stelle al Element eines Arrays zurück

Tabelle 3.15: Funktionen zum Suchen und Ersetzen mittels regulärer Ausdrücke

Funktion Bedeutung

date Formatiert eine Datumsangabe.

strftime Formatiert eine Datumsangabe in Abhängigkeit von der Lokalisierung.

printf Formatiert beliebige Werte und gibt sie sofort aus.

129

sprintf Formatiert beliebige Werte und gibt sie als Zeichenkette zurück.

Tabelle 3.16: Funktionen zum Erzeugen, Erkennen und Formatieren von Datums-, Zahlen- und Zeichenkettenwerten

Page 130: 382726314 X Php5 In 14 Tagen (Ddt)

Daten verarbeiten

3.10 Kontrollfragen

1. Was ist ein Literal?

2. Wie werden Konstanten definiert?

3. Wozu werden reguläre Ausdrücke eingesetzt?

4. Schreiben Sie ein Skript, dass anhand mehrere Parameter ein span-Tag mit kor-rekter hexadezimaler Angabe der Vordergrund- und Hintergrundfarbe mit Hilfedes style-Attributes erstellt und ausgibt. Tipp: Verwenden Sie printf.

vprintf Formatiert beliebige Werte aus einem Array und gibt sie sofort aus.

vsprintf Formatiert beliebige Werte aus einem Array und gibt sie als Zeichenkette zurück.

sscanf Extrahiert nach Formatangaben Werte aus einer Zeichenkette.

fscanf Extrahiert nach Formatangaben Werte aus einer Datei.

Funktion Bedeutung

Tabelle 3.16: Funktionen zum Erzeugen, Erkennen und Formatieren von Datums-, Zahlen- und Zeichenkettenwerten (Forts.)

130

Page 131: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

4

Page 132: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

Jetzt geht es richtig los. Zur richtigen Programmierung benötigen Sie zwangsläufigAnweisungen, die den Programmfluss steuern. Dieses Kapitel stellt alle Befehlevor, die dies in PHP erledigen. Wer bereits mit PHP 4 vertraut ist, kann diesen Tagüberspringen, denn PHP5 bietet hier nichts Neues. Alle Flussbefehle, die imZusammenhang mit den neuen objektorientierten Eigenschaften eingeführt wur-den, werden am Tag 8 behandelt.

4.1 Verzweigungen

Verzweigungen steuern den Programmfluss. PHP lehnt sich hier stark an die Syn-tax der Programmiersprache C an. Auf der gleich Basis sind auch die Befehle vonSprachen wie JavaScript, Java, C++ und C# entstanden.

Einfache Verzweigungen mit if

Der Befehl if dient der Programmierung einer einfachen Verzweigung. Damitkönnen Programmteile in Abhängigkeit von einer Bedingung ausgeführt werden.Als Bedingung verwendet man einen Ausdruck, der einen Booleschen Wertzurückgibt – also entweder Wahr (TRUE) oder Falsch (FALSE). Zur Verknüpfungvon solchen Ausdrücken gibt es verschiedene Operatoren, die in der Einführungbereits vorgestellt wurden.

Aufbau von Ausdrücken

Da PHP praktisch über ein sehr schwaches Typkonzept verfügt, werden oft internTypumwandlungen vorgenommen. Da Bedingungen Boolesche Werte benötigen,konvertiert PHP5 hier eventuell vorhandene andere Datentypen einfach. Das ent-sprechende Verhalten sollten Sie kennen, um den gewünschten Effekt auch tat-sächlich zu erzielen.

Ausdrücke bestehen generell aus zwei Arten von Operatoren. So genannte Ver-

132

gleichsoperatoren vergleichen zwei Werte, entweder zwei Variablen oder eineVariable und eine Konstante. Zu den Vergleichsoperatoren gehören >, <, >=, <=,==, != usw.

Page 133: 382726314 X Php5 In 14 Tagen (Ddt)

Verzweigungen

Denken Sie daran, dass der Operator »gleich« aus zwei Gleichheitszei-chen besteht!

$a > 345 <= $zahl$zahl1 == $zahl2

Derartige primitive Ausdrücke geben immer einen Wahrheitswert zurück. In derPraxis reicht das freilich nicht aus, deshalb kann man diese Ausdrücke mit logi-schen Operatoren verbinden: and, or bzw. &&, || usw. Daraus entstehen dann kom-plexere Gebilde, die ihrerseits wieder einen Wahrheitswert zurückgeben.

Mit if arbeiten

Der Befehl if macht nun nichts weiter, als das Ergebnis des Gesamtausdrucks aus-zuwerten und den im folgenden Block stehenden Code nur dann auszuführen,wenn der Ausdruck Wahr ist.

Listing 4.1: if.php – Einen logischen Ausdruck erstellen und auswerten

<?php$zahl = 20;if ($zahl > 10){ echo ?Zahl ist größer als 10: $zahl?;}?>

Bei der Erstellung von logischen Ausdrücken ist es wichtig zu wissen, dass dieseeine so genannte Assoziativität besitzen, die die Rangfolge bei der Verarbeitungbestimmt. Wenn diese intern festgelegte Reihenfolge nicht passt, setzen Sie ein-fach Klammern.

Assoziativität Rangfolge Operator

133

1. (höchster Rang) Links ,

2. Links or

3. Links xor

Tabelle 4.1: Rangfolge und Assoziativität von Operatoren

Page 134: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

In der Praxis bedeutet die Rangfolge »Links«, dass beispielsweise im folgendenAusdruck der linke Teil zuerst ausgeführt wird, weil der Operator || Links-assozia-tiv ist:

4. Links and

5. Rechts print

6. Rechts = += -= *= /= .= %= &= |= ^= <<= >>=

7. Links ? :

8. Links ||

9. Links &&

10. Links |

11. Links ^

12. Links &

13. Keine == != === !==

14. Keine < <= > >=

15. Links << >>

16. Links + – .

17. Links * / %

18. Rechts ! ~ ++ -- (int) (float) (string) (array) (object) @

19. Rechts [

20. (niedrigster Rang) Keine new

Assoziativität Rangfolge Operator

Tabelle 4.1: Rangfolge und Assoziativität von Operatoren (Forts.)

134

$b == 8 || $c == $d

Wird dagegen eine Typumwandlung mit (float) durchgeführt, wird der Ausdruckrechts vom Operator zuerst ausgeführt:

(float) $a = 3

Page 135: 382726314 X Php5 In 14 Tagen (Ddt)

Verzweigungen

Weil auch der Operator = Rechts-assoziativ ist, wird beim letzten Beispiel die Aus-wertung mit der 3 begonnen, dann erfolgt die Zuweisung an die Variable $a underst dann wird $a in eine Gleitkommazahl umgewandelt.

Keine Assoziativität bei den Operatoren == und deren Verwandten heißt, dass esfür das Ergebnis keinen Unterschied ergibt, ob ein Teilausdruck links oder rechtssteht. Dies kann ausgenutzt werden, um einen typischen Fehler zu vermeiden, derhäufig passiert: Die Verwechslung von = und ==. Schreiben Sie nämlich in einemif-Befehl nur ein einfaches Gleichheitszeichen, so wird PHP als Ergebnis derZuweisung den Inhalt zurückgeben und dann diesen Wert automatisch konvertie-ren. Sie erhalten also keine Fehlermeldung, wohl aber einen falschen Programm-fluss. Betrachten Sie folgenden Code:

$a = 23;if ($a = 23){ //}

Hier wird der Variablen $a in der Bedingung ein zweites Mal der Wert 23 zuge-wiesen und dann dem if-Befehl als Ergebnis des Ausdrucks übergeben. PHP kon-vertiert nun die 23 in TRUE (0 entspräche FALSE, alles andere ist TRUE). Um derartigeTippfehler zu vermeiden, vertauscht man die Operanden:

$a = 23;if (23 = $a){ //}

Würde korrekt 23 == $a geschrieben werden, funktionierte der Ausdruck wieerwartet, denn bei == spielt die Rangfolge keine Rolle. 23 = $a misslingt allerdings,weil zuerst der rechte Ausdruck ausgewertet wird (gelingt noch) und dann derlinke, was die Zuweisung einer Variablen zu einem Literal bedeuten würde – einklarer Fall für einen Syntaxfehler.

Eng verbunden mit der Assoziativität ist auch eine Optimierungseigenschaft vonPHP. Manche Ausdrücke müssen nämlich nicht vollständig ausgewertet werden:

135

$a = 3;if ($a == 3 || $b == 7)

Bei dieser Verknüpfung erkennt PHP zuerst einen Oder-verknüpften Ausdruck.Wenn der linke Zweig ($a == 3) wahr ist, dann wird der gesamte Ausdruck zwangs-

Page 136: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

läufig wahr sein. Es ist also nicht notwendig, den rechten Teil abzuarbeiten, denndies würde nie etwas am Ergebnis ändern. Das spart Rechenzeit und verbessert dasLaufzeitverhalten.

Anstatt einer Variablen kann jedoch auch eine Funktion aufgerufen werden:

if (TestFunction1() == 3 || TestFunction2() == 7)

Nun kann es im Programmkontext außerdem erforderlich sein, dass beide Test-funktionen tatsächlich aufgerufen werden, beispielsweise weil sie anderweitige fürden Programmfluss erforderliche Aktionen ausführen, unabhängig vom jeweiligenRückgabewert. Die Optimierung würde aber immer dann die Ausführung vonTestFunction2 verhindern, wenn TestFunction1 den Wert TRUE zurückgibt.

Es gibt zwei Lösungen für das Problem. Zum einen ist diese Art der Programmie-rung prinzipiell kein guter Stil. Man spricht von so genannten Nebeneffekten, d.h.zwei nicht in direktem Zusammenhang stehende Programmteile beeinflussen sichin Abhängigkeit von konkreten Daten gegenseitig. Das führt in der Praxis zuschwer beherrschbaren und kaum nachvollziehbaren Programmfehlern. Die ersteLösung besteht also darin, diese Art der Programmierung zu vermeiden:

$result1 = TestFunction1();$result2 = TestFunction2();if ($result1 == 3 || $result2 == 7)

Hier wird sichergestellt, dass beide Testfunktionen aufgerufen werden. Anschlie-ßend findet die Überprüfung statt, wobei es keine Rolle spielt, ob mit oder ohneOptimierung gearbeitet wird.

Die zweite Lösung heißt PHP und verwendet Operatoren, die die Optimierungeinfach unterdrücken. Dies ist immer dann angebracht, wenn Nebeneffekte ausge-schlossen werden sollen und die Abfrage der Teilausdrücke nur unnötige Schreib-arbeit verursachen würde.

Alternative Zweige mit else

Der Block, der hinter if folgt, wird immer dann ausgeführt, wenn die Bedingung

136

Wahr ist. Oft sind jedoch Aktionen auszuführen, wenn die Bedingung nicht erfülltist. Dazu wird ein alternativer Zweig geschrieben, der mit dem Schlüsselwort elseeinzuleiten ist. Auch hier gilt die Regel, dass mehrere Befehle nach else als Blockzusammengefasst werden müssen.

Page 137: 382726314 X Php5 In 14 Tagen (Ddt)

Verzweigungen

Listing 4.2: Else.php – Alternativer Verarbeitungszweig

<?php$hour = 13;if ($hour < 13){ echo "Vormittags";} else { echo "Nachmittags";}?>

Zweige mit if und else können beliebig geschachtelt werden, um umfangreicheBedingungen zu testen.

Mehrfachverzweigungen mit switch

Wenn sich in einem Skript mehrere Auswertungen mit if aneinanderreihen, kannman schnell die Übersicht verlieren. Da man auch deutlich häufiger auf Gleich-heit testet als mit anderen Operatoren, gibt einen speziellen Befehl dafür: switch.

switch verhält sich analog zu C, fällt allerdings immer von Zweig zu Zweig durch,wobei jeder Zweig durch das Schlüsselwort case angezeigt wird. Jeder Zweig mussdeshalb explizit mit dem Schlüsselwort break abgeschlossen werden. Der Codehinter den einzelnen case-Zweigen muss nicht mit einem Blockkennzeichenumschlossen werden, weil hier immer alles bis zum nächsten break oder demEnde von switch abgearbeitet wird.

Ein einfaches switch

Das folgende Skript zeigt eine einfache Anwendung von switch:

Listing 4.3: Switch.php – Mehrfachverzweigungen sind sehr kompakt

137

$hour = 13;echo "Tagesabschnitt für Stunde $hour: ";switch ($hour){ case 1:

Page 138: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

echo "Nachts"; break; case 7: echo "Morgens"; break; case 9: echo "Vormittags"; break; case 13: echo "Mittags"; break; case 16: echo "Nachmittags"; break; default: echo "Unbekannte Stunde";}

Die Testvariable ($hour) wird in den Kopf der Anweisung geschrieben. PHP ver-gleicht diese dann mit jedem Wert der case-Zweige und führt den Zweig aus, fürden Gleichheit der Werte festgestellt wurde. Der Vergleich nutzt die Stärke des ==-Operators, nicht von ===. Das bedeutet, dass case "13" anstatt case 13 im gezeig-ten Beispiel als Gleich erkannt werden würde.

Die break-Anweisungen sind erforderlich, damit der betroffene Zweig wieder ver-lassen wird, andernfalls wird einfach der Code des nächsten Zweiges ausgeführt,ohne dass dort eine erneute Prüfung stattfindet.

Falls kein Vergleich zutrifft, wird ein default-Zweig gesucht und – wenn vorhan-den – ausgeführt. Ist ein solcher Zweig nicht vorhanden, wird mit dem Code nachdem switch-Block fortgesetzt.

Falls die Prüfung mehrerer Werte denselben Code erfordert, kann man case-Zweige aneinander setzen, wie es das folgende Beispiel zeigt:

Listing 4.4: switchcase2.php – Mehrfache case-Zweige

$hour = 13;

138

echo "Tagesabschnitt für Stunde $hour: ";switch ($hour){ case 1: case 2:

Page 139: 382726314 X Php5 In 14 Tagen (Ddt)

Verzweigungen

case 3: case 4: case 5: echo "Nachts"; break; case 6: case 7: case 8: echo "Morgens"; break; case 9: case 10: case 11: echo "Vormittags"; break; case 12: case 13: case 14: echo "Mittags"; break; case 15: case 16: case 17: echo "Nachmittags"; break; default: echo "Unbekannte Stunde";}

Beachten Sie hier, dass die Aufeinanderfolge von case nicht bedeutet, dass dieCodeausführung am nächsten Zweig fortgesetzt wird, wie es bei fehlendem breakder Fall ist. Betrachten Sie die folgende Sequenz:

case 15: case 16: case 17:

Mit if könnte man dafür folgendes schreiben:

139

if ($hour == 15 or $hour == 16 or $hour == 17)

Der gesamte Block verhält sich darüber hinaus wie ein allein stehendes case.

Page 140: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

Komplexe Ausdrücke mit switch auswerten

Mit switch lassen sich in PHP auch komplexe Ausdrücke auswerten. Das unter-scheidet PHP von einigen anderen Sprachen, die nur strenge Vergleiche konstan-ter Werte zulassen. Mit der Möglichkeit, als Vergleichswerte im case-Zweig auchAusdrücke einsetzen zu können, kann man switch deutlich flexibler verwenden.Hier zeigt sich einer der wenigen Vorteile eines Interpreters gegenüber kompilie-renden Sprachen.

Intern vergleicht PHP freilich auch hier nur auf der Basis eines Booleschen Aus-drucks. Das Resultat eines Zweiges muss mit einem entsprechenden Gegenstückverknüpft werden. Dazu setzt man einfach im Kopf eine der Konstanten TRUE oderFALSE ein und in den case-Zweigen die Ausdrücke:

Listing 4.5: switchcaseplus.php – Variable Nutzung Boolescher Ausdrücke

$hour = 13;echo "Tagesabschnitt für Stunde $hour: ";switch (TRUE){ case $hour >= 0 and $hour < 5 or $hour > 22: echo "Nachts"; break; case $hour >= 5 and $hour < 8: echo "Morgens"; break; case $hour >= 8 and $hour < 11: echo "Vormittags"; break; case $hour >= 11 and $hour < 13: echo "Mittags"; break; case $hour >= 13 and $hour < 17: echo "Nachmittags"; break; case $hour >= 17 and $hour < 22:

140

echo "Abends"; break;}

PHP löst hier intern zuerst die Booleschen Ausdrücke auf und ermittelt den WertTRUE oder FALSE. Dann vergleicht er TRUE==TRUE oder TRUE==FALSE und springt in

Page 141: 382726314 X Php5 In 14 Tagen (Ddt)

Schleifen erstellen

den ersten passenden Zweig. Man muss hier aufpassen, dass immer nur ein Zweiggefunden wird, auch wenn ein danach liegender ebenso zutreffend wäre. switchkann immer nur einen Zweig ausführen. Das break wegzulassen ist keine Lösung,denn dann fällt der Programmablauf durch alle folgenden Zweige bis zum nächs-ten break durch. Die Auswertung eines weiteren case-Zweiges erfolgt nicht.

4.2 Schleifen erstellen

PHP verfügt über die in allen gängigen Programmiersprachen verfügbaren Schlei-fenanweisungen:

� do

� while

� for

do und while sind eng miteinander verwandt und bilden universelle Schleifen miteiner Abbruchbedingung (do) bzw. einer Eintrittsbedingung (while). for ist eineZählschleife, die numerische Werte abzählen kann.

Gemeinsamkeiten aller Schleifenanweisungen

Alle Schleifen weisen einige Gemeinsamkeiten auf, die sehr praktisch sind. Stan-dardmäßig werden Schleifen solange durchlaufen, bis die Auswertung der Prüf-bedingung einen erneuten Durchlauf verhindert. Dies kann bei falscherProgrammierung zu Endlosschleifen führen. Endlosschleifen sind meist uner-wünscht. Mit PHP sind solche Programmierfehler zwar lästig, aber nicht wirklichkritisch. Denn standardmäßig verfügt PHP über eine Skriptlaufzeit von 30 Sekun-den. Ist das Skript dann nicht beendet – egal ob es an einem Fehler hängt oder ineiner Schleife feststeckt –, wird es abgebrochen.

Der Schleifenablauf lässt sich aber nicht nur allein durch die Bedingung kontrol-lieren. Alle Schleifen verfügen über zwei spezielle Schlüsselwörter zur Steuerung,

141

die grundsätzlich nur zusammen mit if oder (seltener) switch eingesetzt werden:

� break

� continue

Page 142: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

Mit Hilfe von break wird die Schleife sofort und ohne weitere Prüfung verlassen.Natürlich muss man davor eine Bedingung schreiben:

if ($hour == 12) break;

Andernfalls würde die Schleife immer abgebrochen werden, unabhängig von dereigentlichen Schleifenbedingung, was ziemlich sinnlos ist.

Die Anweisung continue dagegen erzwingt eine sofortige Prüfung der Schleifenbe-dingung und springt dazu zum Schleifenkopf (bzw. bei do zum Ende). Der Code,der innerhalb der Schleife nach dem continue steht, wird nicht ausgeführt. Natür-lich muss man auch hier eine Bedingung schreiben:

if ($hour < 12) continue;

Damit werden alle Schleifenkonstrukte sehr flexibel. Zusammen mit der recht ein-fachen Definition ergeben sich starke und universelle Anweisungen.

Die Zählschleife for

for bildet eine Zählschleife – im weitesten Sinne. Aufbau und Bedingungen sindsehr flexibel einsetzbar. Insgesamt besteht der Kopf der for-Schleife aus drei,jeweils optional verwendbaren, Abschnitten:

1. Initialisierung

2. Laufbedingung

3. Iterationsanweisung

Alle drei Abschnitte ermöglichen die Angabe mehrere Ausdrücke. Wie der Name»Laufbedingung« bereits andeutet, muss die Bedingung Wahr ergeben, also beimEintritt in die Schleife TRUE ergeben. Ist das nicht mehr der Fall, wird die Schleifebeendet. Nach jedem Schleifendurchlauf wird die Iterationsanweisung ausgeführt,dann erfolgt die Prüfung der Laufbedingung. Die Initialisierung wird nur beimSchleifeneintritt ausgeführt.

Die grundsätzliche Syntax sieht folgendermaßen aus:

142

for (Initialisierung; Laufbedingung; Iterationsanweisung){ // Code in der Schleife}

Page 143: 382726314 X Php5 In 14 Tagen (Ddt)

Schleifen erstellen

Um nun mehrere Ausdrücke in einen Abschnitt zu packen, werden diese durchKommata getrennt:

for ($i = 0, $k = 2; $k < 5; $i++, $k++)

Beachten Sie hier, dass es sich wirklich um Ausdrücke handeln muss,die etwas zurückgeben (was genau, ist erstmal egal). Die Anweisung echo?Hallo for? gibt nichts zurück, sondern produziert eine Ausgabe. Siewürde deshalb nicht im Kopf der for-Schleife funktionieren.

Solche Konstruktionen sind aber eigentlich sowieso keine gute Idee, weil sie dieLesbarkeit erschweren und schnell Programmierfehler hervorrufen. Eigentlich istfor nämlich eine Zählschleife und nicht dazu gedacht, Ausgaben oder Code desSchleifenkörpers im Kopf aufzunehmen.

Typische Einsatzfälle

Wie bereits erwähnt, ist der Einsatz mit zählbaren Werten typisch für die for-Schleife. Im einfachsten (und häufigsten) Fall sieht das dann folgendermaßen aus:

Listing 4.6: forloop.php – Die einfachste for-Schleife

$rows = 7;echo '<table border="1">';for ($i = 0; $i < $rows; $i++){ echo "<tr><td>$i</td><td>&nbsp;...</td></tr>";}echo '</table>';

Es ist dabei sehr typisch, als Startwert 0 zu wählen, und dann die Anzahl derDurchläufe mit der Formel $laufwert < ENDWERT zu bestimmen. Die gezeigteSchleife produziert wie erwartet sieben Durchläufe und zählt dabei von null bissechs. Der naive Ansatz wäre sicher, hier von eins bis sieben zu zählen, zumal vieleAusgaben dies auch erwarten. Intern verwendet PHP jedoch – wie viele andereSprachen auch – so genannte null-basierte Indizes. Egal ob Arrays, Datenbankab-

143

fragen oder andere zählbare Quellen verwendet werden, fast immer hat der ersteWert der Reihe den Index 0. Wenn Sie es sich von vornherein angewöhnen, Zähl-schleifen null-basiert zu bauen, wird vieles leichter.

Page 144: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

Die Trennung von Logik und Benutzerschnittstelle gehört zu den elementarenRichtlinien der praktischen Informationsverarbeitung. PHP bietet hier nur einerudimentäre Unterstützung, aber auch im Kleinen lohnt es, das Programmdesignnach diesem Prinzip auszurichten. Wenn Sie im gezeigten Beispiel tatsächlicheine Ausgabe der Zählwerte wünschen und diese Ausgabe mit 1 beginnen soll,ändern sie nicht den Schleifenkopf. Fügen Sie stattdessen eine Berechnung alleinfür die Ausgabe ein:

echo "<tr><td>" . ($i + 1) . "</td><td>&nbsp;...</td></tr>";

Es wäre übrigens ein echter Programmierfehler, folgendes zu schreiben, wenn dieIterationsanweisung im Schleifenkopf weiter bestehen bleibt:

echo "<tr><td>" . ($i++) . "</td><td>&nbsp;...</td></tr>";

Diese Version würde den Wert für die Ausgabe zwar um eins erhöhen, zugleichaber auch den Inhalt der Laufvariablen verändern. Da diese aber ohnehin beijedem Durchgang erhöht wird, würde nun jeder Durchlauf deren Wert um zweierhöhen. Da sich die Abbruchbedingung nicht geändert hat, wird nur die Hälfteder Zeilen ausgegeben.

Aus der Beobachtung des Effekts sollten Sie nicht eine tolle Möglichkeit der nach-träglichen Manipulation der Schleifenvariable erkennen. Dies geht zwar, ist abereinfach nur schlechter Stil. Die Veränderung der Schleifenvariablen ist ein sogenannter Seiteneffekt, der schwer verständlich ist, wenn jemand anderes denCode liest1. Seiteneffekte sind schlecht. Vermeiden Sie es unbedingt, die Schlei-fenvariable während des Durchlaufs zu manipulieren.

Abbildung 4.1: Tabelle, mit einer einfachen Zählschleife erzeugt

144

Prüfen Sie sorgfältig, ob die Iterationsanweisung wirklich dazu führt,dass die Laufbedingung ungültig wird. Andernfalls haben Sie eine End-losschleife. Fügen Sie gegebenenfalls ein break mit einer weiterenBedingungen ein, um einen Notausstieg zu haben.

1 Jemand anderes sind auch Sie selbst, sechs Monate später!

Page 145: 382726314 X Php5 In 14 Tagen (Ddt)

Schleifen erstellen

Wenn Sie nun Ausgabewerte und Zählwerte gleichzeitig benötigen, bietet es sichan, den Kopf zu erweitern. Sie vermeiden so strikt die Manipulation der Schleifen-variablen und profitieren dennoch von der kompakten Schreibweise der for-Schleife. Das folgende Beispiel ist die vermutlich einfachste und primitivsteDemonstration dieser Technik, die jemals gefunden wurde:

Listing 4.7: forloop2.php – Trennung von Zählung (Logik) und Ausgabe (Benutzer-schnittstelle)

$rows = 7;echo '<table border="1">';for ($i = 0, $j = 1; $i < $rows; $i++, $j++){ echo "<tr><td>$j</td><td>&nbsp;...</td></tr>";}echo '</table>';

Das Beispiel deklariert eine zweite Variable $j, die nur der Ausgabe dient. Dieselässt sich gefahrlos manipulieren, weil sie keinen Einfluss auf die Laufbedingun-gen hat und damit keine Seiteneffekte auslösen kann.

Auch die Ausgabe ließe sich zu guter Letzt noch in den Schleifenkopf verlegen:

Listing 4.8: forloop3.php – Ausgaben im Kopf einer Schleife

$rows = 7;echo '<table border="1">';for ($i = 0, $j = 1; $i < $rows; $i++, print "<tr><td>$j</td><td>&nbsp;...</td></tr>", $j++){ }echo '</table>';

Grundsätzlich sollten sich solche Konstrukte auf seltene, einfache Fälle beschrän-ken. Sie sind schwer lesbar und Erweiterungen verlangen grobe Änderungen, waseine zusätzliche Fehlerquelle bedeutet.

145

Beachten Sie außerdem, dass der Befehl echo hier nicht einsetzbar ist. for erwartetAusdrücke und echo hat keinen Rückgabewert, was ein Merkmal eines Ausdrucksist. Deshalb wird hier print eingesetzt.

Page 146: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

Noch ein anderes Problem macht den Einsatz der letzten Variante weniger emp-fehlenswert. Die Reihenfolge der Parameter im Kopf im Abschnitt »Iterationsan-weisung« ist wichtig. Betrachten Sie beispielsweise Folgendes:

$i++, $j++, print "<tr><td>$j</td><td>&nbsp;...</td></tr>"

Diese Schleife würde nun mit der Ausgabe des Wertes 2 beginnen, denn der Start-wert für $j ist 1 und die erste Erhöhung $j++ findet vor der Ausgabe statt! Das istnicht unbedingt sofort erkennbar und deshalb eine weitere, unnötige Fehler-quelle.

Geschachtelte Schleifen

Wie alle Schleifen lässt sich auch for verschachteln. Hier sollte beachtet werden,dass sich die Laufvariablen auf keinen Fall gegenseitig beeinflussen. Außerdemmultipliziert sich die Anzahl der Durchläufe zur Anzahl der Gesamtdurchläufe.Hat die äußere Schleife acht Durchläufe und die innere ebenfalls, sind es insge-samt 64. Das klingt trivial, aber bei mehreren Verschachtelungsebenen erreichtman schnell Skriptlaufzeiten, die deutlich spürbar sind. Zeitkritische Abfragenhaben in Schleifen sowieso nichts verloren, aber in verschachtelten Schleifen kön-nen auch triviale Berechnungen problematisch werden. Denken Sie beispielsweisean den Aufbau eines Farbrades für RGB-Farben. Statt ein GIF vorzubereitenmöchten Sie es in PHP »life« erstellen. Das wären dann 256 x 256 x 256 Farben;leicht mit drei Schleifen erzeugbar. Diese drei Schleifen führen zu 16.777.216Ausgaben. Abgesehen davon, dass die Darstellung wenig hilfreich ist, führt alleineine Ausgabezeit von 5 ms pro Durchlauf zu einer Skriptlaufzeit von fast 1 Tag!

Speziell für die Problematik Farbrad benötigt man aber so viele Farben nicht. KeinSystem kann alle Farben wirklich darstellen. Für das Web benutzt man 216 sogenannte websichere Farben. Diese sind so definiert, dass als hexadezimale Farb-werte nur die Zahlen 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF zum Einsatz kommen.Folgende Lösung nutzt auch verschachtelte Schleifen, führt aber einige Berech-nungen außerhalb durch, um so mit wenigen Durchläufen zum Ergebnis zu kom-men:

146

Listing 4.9: forllopnested.php – Erzeugen eines Farbwählers mit websicheren Farben

$r = 0;$g = 0;$b = 0;// Farben ermitteln

Page 147: 382726314 X Php5 In 14 Tagen (Ddt)

Schleifen erstellen

$WebSafe = array (0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF);echo '<table cellspacing="0" cellpadding="0">';for($y = 0; $y < 12; $y++){ $b = $WebSafe[$y % 6]; echo '<tr height="10">'; for ($x = 0; $x < 18; $x++) { $g = $WebSafe[$x % 6]; $r = ($y < 6) ? $WebSafe[$x / 6] : $WebSafe[($x / 6) + 3]; printf('<td bgcolor="#%2x%2x%2x" width="10" height="10"></td>', $r, $g, $b); } echo "</tr>";}echo '</table>';

Das Skript nutzt ein Array (Datenfeld, siehe dazu Kapitel 5, Arrays, ab Seite 181),das die möglichen Basiswerte enthält. Dann wird die Farbfläche als Feld mit18 mal 12 Rechtecken definiert. Die äußere Schleife zählt die Reihen (12), dieinnere die Spalten (18). Dies erzeugt insgesamt 216 Durchläufe. Aus dem Werte-feld muss dann nur noch der richtige Wert entnommen werden, was durch einfa-che Modulus-Berechnungen erfolgt und den gewünschten Schachteleffekterzeugt, der ähnliche Farben nebeneinander platziert.

Die Ausgabe der Farben erfolgt durch Einfärben des Hintergrundes der erzeugten

Abbildung 4.2: forloopnested.php: Farbwähler mit sortierten Farben

147

Tabellenzellen. Die passenden RGB-Werte erzeugt die printf-Funktion, die mitder Formatanweisung %2X aus einer Dezimalzahl den passenden zweistelligenHexwert erzeugt.

Page 148: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

Die Universalschleifen do/while und while

Die Anweisungen do/while und while werden so lange durchlaufen, wie dieBedingung Wahr (TRUE) ist. Als Bedingung wird ein beliebiger Boolescher Aus-druck eingesetzt. Die grundsätzliche Syntax sieht folgendermaßen aus:

while (Bedingung){ // Code, der immer wieder ausgeführt wird}

do funktioniert ebenso, nur erfolgt die Prüfung erst am Ende:

do { // Code, der immer wieder ausgeführt wird} while (Bedingung)

do wird immer dann eingesetzt, wenn sichergestellt werden muss, dass der Inhaltmindestens einmal durchlaufen wird. Das ist der Fall, weil die Prüfung – wie derSchleifenkörper suggeriert – erst am Ende erfolgt. Dies ist freilich nicht mehr derFall, wenn vorher ein break eingesetzt wird.

Mit dem Training durch die for-Schleifen wird der Einsatz von while sehr ein-fach. Das folgende Beispiel zeigt eine fortgeschrittene Version des Farbwählers,diesmal nicht mit blockweiser, sondern fortlaufender Farbanordnung:

Listing 4.10: while.php – Verschachtelte while-Schleifen zum Aufbau eines Farbwählers

$r = 0;$g = 0;$b = 0;$ContR = array (0xCC, 0x66, 0x00, 0xFF, 0x99, 0x33);$ContG = array (0xFF, 0xCC, 0x99, 0x66, 0x33, 0x00);$ContG = array_merge($ContG, array_reverse($ContG));$ContB = array_merge($ContG, array_reverse($ContG));echo '<table cellspacing="0" cellpadding="0">';$y = 0;

148

while($y < 12){ $g = $ContG[$y++]; echo '<tr height="10">'; $x = 0; while($x < 18)

Page 149: 382726314 X Php5 In 14 Tagen (Ddt)

Schleifen erstellen

{ $r = $ContR[($x / 6) + (($y < 6) ? 0 : 3)]; $b = $ContB[$x++]; printf('<td bgcolor="#%2x%2x%2x" width="10" height="10"></td>', $r, $g, $b); } echo "</tr>";}echo '</table>';

Die Funktionen array, array_merge und array_reverse werden im Kapitel 3Arrays ab Seite 73 vorgestellt. Sie bauen wieder passende Datenfelder aus denFarbwerten 0x00, 0x33, 0x66, 0x99, 0xCC und 0xFF auf. Die beiden while-Schlei-fen durchlaufen dann die Werte 0 – 11 (12 Reihen) bzw. 0 – 17 (18 Spalten) underzeugen so das Muster. Die Ausgabe findet mit einer printf-Funktion statt, diedie Darstellung der von HTML benötigten RGB-Werte übernimmt. Die Erhö-hung der Schleifenwerte findet in der Abfrage der Arrays statt:

$g = $ContG[$y++];

Das ist sehr kompakt und einfach. Wenn Sie es besser lesbar mögen, kann manauch folgendes schreiben:

$g = $ContG[$y];$y++ ;

Falls Sie $y anders als zur Zählung verwenden, müssen Sie die Erhöhung (oderjede andere Änderung) ohnehin so platzieren, dass die Änderung erst am Ende derSchleife erfolgt.

Abbildung 4.3: Verschachtelte Schleifen wurden zum Erzeugen

149

dieser Farbfläche benutzt

Page 150: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

4.3 Benutzerdefinierte Funktionen

Mit Hilfe des Schlüsselwortes function lassen sich eigene Funktionen definieren.Da man einer Funktion Parameter übergeben kann, gibt es die Möglichkeit, erfor-derliche und optionale Parameter zu deklarieren. Da Parameter auch wegfallenoder in beliebiger Menge eingesetzt werden können, gibt es noch eine Reihe vonHilfsfunktionen in PHP, die dem Umgang mit Parametern dienen. Funktionsdefi-nitionen, wie sie hier vorgestellt werden, finden auch Eingang in die Klassendefini-tion. Dort heißen sie dann Methoden. Die hier vorgestellten Techniken sind alsorecht allgemeingültig.

Funktionen definieren

Funktionen kennen Sie bereits – praktisch besteht PHP aus Hunderten Funk-tionen. Diesen eingebauten Funktionen kann man leicht eigene hinzufügen undim Skript verwenden. Das ist praktisch, um größere Skripte modular aufzubauen,Code leichter wieder verwenden zu können und ein und denselben Ablauf mitverschiedenen Daten auszuführen.

Die Funktionsdefinition

Funktionen werden durch das Schlüsselwort function definiert. Die Definitionumfasst einen Funktionskopf, der Name und Parameter definiert. Diese sogenannte Signatur der Funktion bestimmt, wie die Funktion später aufgerufenwird. Im einfachsten Fall wird nichts übergeben, wie das folgende Beispiel zeigt:

Listing 4.11: functionsimple.php – Eine einfache Funktionsdefinition

function PrintTableRow(){ echo '<tr><td>Reihe</td></tr>';}echo '<table border="1">';

150

for ($i = 0; $i < 3; $i++){ PrintTableRow();}echo '</table>';

Page 151: 382726314 X Php5 In 14 Tagen (Ddt)

Benutzerdefinierte Funktionen

Der Funktionskopf ist hier in seiner einfachsten Form zu sehen:

function PrintTableRow()

Da keine Parameter verwendet werden, schreibt man nur zwei runden Klammernzum Abschluss. Schlüsselwort und Name sind ebenso obligatorisch. Der Namesollte so gewählt werden, dass ein Verb am Anfang steht, denn Funktionen führenAktionen aus, die man üblicherweise mit Verben bezeichnet.

Wie das Beispiel zeigt, eignen sich Funktionen zum Einsatz an allen Stellen imSkript, also auch in Schleifen.

Die Reihenfolge der Definition spielt keine Rolle. PHP5 beherrscht die sogenannte späte Bindung, bei der die Aufrufadressen der Funktionen erst am Endeder Seitenverarbeitung festgelegt werden. Deshalb funktioniert auch Folgendes:

echo '<table border="1">';for ($i = 0; $i < 3; $i++){ PrintTableRow();}echo '</table>';function PrintTableRow(){ echo '<tr><td>Reihe</td></tr>';}

Wenn in der Funktion Ausgaben erzeugt werden, erscheinen diese im Rahmender Ausführung auf der Seite an der Stelle, wo die Ausgabe beim Aufruf der Funk-tion gerade war. Das Beispiel nutzt diesen Effekt bereits.

Rückgabewerte

Abbildung 4.4: Ausgabe einer Tabelle mit einer Funktion

151

Besser ist es, die Ausgabe von Werten nicht der Funktion zu überlassen. Der Auf-rufer sollte (wieder im Sinne eines besseren Programmierstils) letztlich die Kon-trolle über die Ausgaben behalten. Wenn Funktionen Werte erzeugen, können Siediese mit Hilfe des Schlüsselwortes return zurückgeben.

Page 152: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

Eine Unterscheidung zwischen Prozeduren (kein Rückgabewert) undFunktionen (mit Rückgabewert), wie in BASIC (Visual Basic, VBA,VBScript), gibt es in PHP nicht. Es ist völlig dem Entwickler überlassen,ohne weitere Definition mit oder ohne return zu arbeiten. Lediglich derAufruf muss entsprechend angepasst werden.

Das letzte Beispiel sieht etwas besser aus, wenn es folgendermaßen definiert wird:

Listing 4.12: functionreturn.php – Eine Funktion mit Datenrückgabe

function PrintTableRow(){ return '<tr><td>Reihe</td></tr>';}echo '<table border="1">';for ($i = 0; $i < 3; $i++){ echo PrintTableRow();}echo '</table>';

Es ist nun dem Aufrufer überlassen, mit echo die sofortige Ausgabe zu veranlassenoder die erzeugte Zeichenkette vorher noch zu verarbeiten. Letzteres wäre mög-lich, wenn der Wert direkt einer Variablen übergeben wird:

$row = PrintTableRow();

Wenn nun die Anzahl der Zellen in der erzeugten Reihe variabel sein soll, müssteman für jede Variante eine neue Funktion definieren. Besser ist es, hier mit Para-metern zu arbeiten.

Eine Funktion kann immer nur einen Wert zurückgeben. Auch wenn Arrays nochnicht vorgestellt wurden, sei hier ein kleiner Ausflug präsentiert. Wenn Siemehrere Werte zurückgeben müssen, erstellen Sie ein Array:

function RetArray(){ $result = array(45, 26, 14);

152

return $result;}

Page 153: 382726314 X Php5 In 14 Tagen (Ddt)

Benutzerdefinierte Funktionen

Beim Aufruf weisen Sie die Rückgabewerte mit Hilfe der Funktion list wiedereinzelnen Variablen zu:

list($var1, $var2, $var3) = RetArray();

$var1 enthält nun 45, $var2 den Wert 26 usw.

Parameter der Funktionen

Parameter sind Werte, die der Aufrufer an die Funktion übergibt, um deren Ver-halten zu steuern. PHP5 kann pro Funktion mit beliebig vielen Parametern umge-hen. Die Anzahl ist nicht nur insgesamt frei gestellt, sondern kann abweichend vonder Definition schwanken. Dies ist tückisch (wenn auch recht bequem), denn derAufruf mit einer zu großen Anzahl Parameter führt nicht zwingend zu einer Feh-lermeldung. Da PHP typlos arbeitet und der Datentyp der Parameter ignoriertwird, ist die Fehlerquote hier aber ohnehin recht hoch. Der Umgang mit Parame-tern wird deshalb etwas detaillierter betrachtet.

Einfache Parameter

Einfache Parameter werden deklariert, indem man diese als Variablen im Kopf derFunktion aufschreibt:

function PrintTableRow($cells)

Diese Funktion hat einen Parameter. Innerhalb des Funktionskörpers kann überdie Variable $cells darauf zugegriffen werden. Mehrere Parameter werden durchKommata getrennt, so wie bei den eingebauten Funktionen:

function PrintTableRow($bgcolor, $cells)

Das folgende Beispiel erzeugt eine Reihe mit beliebig vielen Zellen, prüft die Ver-wendung der Parameter und akzeptiert außerdem noch eine Farbe für den Rei-henhintergrund:

Listing 4.13: functionparam1.php – Funktion mit Parametern aufrufen

153

function PrintTableRow($bgcolor, $cells){ $cellstring = ''; for ($i = 0; $i < $cells; $i++) {

Page 154: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

$cellstring .= "<td> $i </td>"; } $row = <<<RET <tr bgcolor="$bgcolor">$cellstring</tr>RET; return $row;}echo '<table border="1">';echo PrintTableRow('gray', 6);echo PrintTableRow('red', 6);echo PrintTableRow('gold', 6);echo '</table>';

Die Anzahl der Zellen wird durch den zweiten Parameter bestimmt, die Farbedurch den ersten. Das ergibt im Beispiel die folgende Ausgabe:

Optionale Parameter

Das letzte Beispiel nutzte den zweiten Parameter nicht wirklich, denn es wurdeimmer derselbe Wert übergeben. Es ist deshalb sinnvoll, für Standardaufrufe einenStandardwert zu definieren. Der Parameter ist dann optional verwendbar, fehlt dieAngabe, wird der Standardwert eingesetzt und die Variable ist in jedem Fall defi-niert.

Listing 4.14: functionparamopt.php – Optionale Parameter verwenden

function PrintTableRow($bgcolor, $cells = 6){ $cellstring = ''; for ($i = 0; $i < $cells; $i++) {

Abbildung 4.5: Ausgabe des letzten Listings mit variablen Farben

154

$cellstring .= "<td> $i </td>"; } $row = <<<RET <tr bgcolor="$bgcolor">$cellstring</tr>RET;

Page 155: 382726314 X Php5 In 14 Tagen (Ddt)

Benutzerdefinierte Funktionen

return $row;}echo '<table border="1">';echo PrintTableRow('gray');echo PrintTableRow('red');echo PrintTableRow('gold');echo '</table>';

Wenn der zweite Parameter fehlt, wird hier automatisch die Zahl 6 eingesetzt.Optionale Parameter dürfen nur am Ende der Parameterauflistung stehen. Das istlogisch, denn PHP kann die Position nur durch Abzählen feststellen. Da PHPkeine Datentypen erkennt, ist eine Auswahl von Parametern schwer zu definieren.Folgende Definition ist noch zulässig:

function PrintTableRow($bgcolor = 'gray', $cells = 6)

Beim Aufruf sind aber nicht alle Varianten möglich. Ohne Parameter ist die Sacheeindeutig:

PrintTableRow()

Mit beiden Parameter funktioniert es natürlich auch. Aber bei der Angabe nureines Parameters versagt der Aufruf. Das folgende Beispiel funktioniert nicht wieerwartet:

PrintTableRow(4)

PHP5 kann nicht erkennen, dass hier die Anzahl der Zellen gemeint war undoffensichtlich die Standardhintergrundfarbe zum Einsatz kommen soll. Auch mitden passenden Datentypen ist dies nicht möglich, weil sich die Parametertypen janicht zwingend unterscheiden müssen.

Unzulässig ist auch folgende Definition:

function PrintTableRow($bgcolor = 'gray', $cells)

PHP5 kann hier beim Aufruf nicht erkennen, welcher Parameter weggelassen wer-den soll.

Abschließend sei noch bemerkt, dass der Standardwert eine Konstante oder einLiteral sein muss. Variablen und Ausdrücke sind nicht erlaubt. Deshalb ist die fol-

155

gende Variante unzulässig:

function PrintTableRow($bgcolor = $gray, $cells = 3 + 3)

Page 156: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

Beliebige Parameteranzahl

Die Lösung des Dilemmas liegt in der Akzeptanz einer beliebigen Parameteran-zahl. Dazu wird der Kopf der Funktion einfach leer gelassen – wie in der erstenVariante. Der Aufrufer kann dennoch beliebig viele Parameter angeben. Danebenist die nachfolgend vorgestellte Angabe von vielen Parametern auch besser, alsdiese in endlosen Schlangen aufzuschreiben. Man findet in der professionellenProgrammierung selten Funktionen mit sechs oder mehr Parametern. Diedadurch erreichte Steuerbarkeit einer Funktion ist derart umfassend, dass wohlmeist ein Designfehler vorliegt. Komplexere Dinge erledigt man mit Klassen(siehe Tag 6).

Für den Umgang mit variabler Parameteranzahl stellt PHP5 einige spezielle Funk-tionen zur Verfügung:

� func_num_args Diese Funktion, aufgerufen innerhalb einer benutzerdefinierten Funktion,ermittelt die tatsächliche Anzahl der Parameter, die übergeben wurden.

� func_get_args Hiermit wird ein Array der Parameter erstellt. Der erste Parameter hat denIndex 0. So erhält man Zugriff auf alle Parameter.

� func_get_arc Alternativ zum Array kann jeder Parameter auch direkt über eine Funktionadressiert werden. Als Argument wird der null-basierte Index verwendet.

Es ist empfehlenswert, Funktionen immer so zu schreiben, dass zuerst die einge-henden Parameter geprüft werden. Es gehört zu den grundlegenden Regeln derProgrammierung, dass Eingabedaten nie vertraut werden darf. Würden alle Pro-gramme so erstellt werden, gäbe es kaum noch Programmierfehler, denn dieeigentlichen Algorithmen lassen sich meist gut testen.

Listing 4.15: functionparams.php – Intelligente Funktion mit variabler Parameterzahl

function BuildRow(){

156

$cells = 4; $Params = func_num_args(); if ($Params == 0) { return "<tr><td colspan=\"$cells\"></td></tr>"; }

Page 157: 382726314 X Php5 In 14 Tagen (Ddt)

Benutzerdefinierte Funktionen

$row = '<tr>'; for ($i = 0; $i < $Params; $i++) { $row .= '<td>' . func_get_arg($i) . '</td>'; } for ($k = $i; $k < $cells; $k++) { $row .= '<td>&nbsp;</td>'; } return "$row</tr>";}echo '<table border="1">';echo BuildRow('PLZ', 'Vorwahl', 'Ort', 'Aktiv?');echo BuildRow('12683', '030', 'Berlin');echo BuildRow('89315', '089', 'München', 'X');echo '</table>';

Diese Funktion prüft zuerst, ob Parameter vorliegen. Ist das nicht der Fall, wirdeine leere Reihe erzeugt:

<tr><td colspan="4"></td></tr>

Ohne diese Abfrage würde eine Reihe ohne Zelle erzeugt werden, was falsch ist:

<tr></tr>

Diese Funktion reagiert also auch bei falscher Parameterzahl korrekt. Will mandiesen Zustand (keine Parameter) vermeiden, so sollte man einen Laufzeitfehlererzeugen oder eine entsprechende Ausschrift.

Stehen nun Parameter zur Verfügung, wird zuerst deren Anzahl ermittelt:

$Params = func_num_args();

Abbildung 4.6: Ausgabe einer einfachen Tabelle mit benutzerdefinierter Funktion

157

Mit dieser Anzahl wird die Schleife bedient, die die Zellen erzeugt. Zu jeder Zellewird ein Parameter als Zelleninhalt abgerufen:

$row .= '<td>' . func_get_arg($i) . '</td>';

Page 158: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

HTML-Tabellen benötigen immer eine konstante Anzahl Tabellenzellen proReihe. Entweder man fügt für fehlende Zellen colspan-Attribute ein oder füllt dieZellen auf. Das Auffüllen geschieht hier mit einer weiteren Schleife, die auf demletzten Wert der ersten Schleife aufbaut. Diese wird dann durchlaufen, bis dieAnzahl der Zellen jeweils der maximalen Anzahl entspricht, die in der Variablen$cells definiert wurde.

Der Aufruf ist nun recht flexibel, kann jedoch noch verbessert werden. So ist esmöglich, feste mit optionalen Parametern zu kombinieren. Das zweite Beispiellegt einen Booleschen Parameter fest, der darüber entscheidet, ob Kopf- oderInhaltszellen erzeugt werden. Ein weiterer fester Parameter bestimmt die maxi-male Anzahl. Die Prüfung wird so erweitert, dass bei Unterschreiten der Anzahleine spezielle Ausgabe erzeugt wird.

Listing 4.16: functionparamsplus.php – Verbesserte Funktion mit variabler Parameter-zahl

function BuildRow($bHead, $iCells){ $cells = $iCells; $Params = func_num_args(); $sTag = $bHead ? 'th' : 'td'; if (($Params - 2) <= 0) { return "<tr><$sTag colspan=\"$cells\"></$sTag></tr>"; } $row = '<tr>'; for ($i = 2; $i < $Params; $i++) { $row .= "<$sTag>" . func_get_arg($i) . "</$sTag>"; } for ($k = $i - 2; $k < $cells; $k++) { $row .= "<$sTag>&nbsp;</$sTag>"; } return "$row</tr>";

158

}echo '<table border="1">';echo BuildRow(TRUE, 5, 'PLZ', 'Vorwahl', 'Ort', 'Aktiv?');echo BuildRow(FALSE, 5, '12683', '030', 'Berlin');echo BuildRow(FALSE, 5, '89315', '089', 'München', 'X');echo '</table>';

Page 159: 382726314 X Php5 In 14 Tagen (Ddt)

Benutzerdefinierte Funktionen

Die Anzahl der Parameter, die func_num_args ermittelt, bezieht hier die beiden festeam Anfang der Liste mit ein. Deshalb muss an zwei Stellen (Prüfung und Auffüllen)im Skript die Anzahl um zwei verringert werden. Ansonsten gibt es kaum Unter-schiede, abgesehen vom Aufruf, wo nun zwei Pflichtargumente erforderlich sind.

Referenzen auf Funktionen und Parameter

Wie in den letzten Abschnitten gezeigt, kann man mit Parametern Daten an eineFunktion übergeben und deren Wiederverwendungswert damit erhöhen. DieRückgabe nur eines Wertes mit return verschafft zusätzliche Flexibilität. Das istjedoch nicht immer ausreichend. Sollen von einer Funktion viele Werte geändertwerden, so kann dies auch über die Parameter erfolgen. Um die dazu verwendeteTechnik zu verstehen, muss man sich an ein wenig Theorie wagen.

Prinzip der Parameterübergabe

Einfache Skriptsprachen wie PHP arbeiten meist direkt mit Variablen. Wird derWert einer Variablen einer anderen Zugewiesen, findet tatsächlich eine Übertra-gung des Wertes statt. Variablen sind die Namen von Speicherbereichen imHauptspeicher des Computers. Die folgende Zuweisung überträgt nur den Wertvon einem Platz an einen anderen:

$a = $b;

Änderungen von $b zu einem späteren Zeitpunkt wirken sich nicht auf $a aus. Daso viele Daten durch den Speicher geschaufelt werden, ist dies nicht optimal.Moderne Sprachen wie C++ oder C# arbeiten deshalb anders. C++ bietet ein Zei-gerkonzept, bei dem statt mit den Werten nur mit den Speicheradressen (sogenannte Zeiger) gearbeitet wird. C# verwendet intern immer Zeiger, dem Benut-

Abbildung 4.7: Ausgabe einer Tabelle mit eigener Funktion

159

zer werden diese aber nicht zugänglich gemacht und erst beim Schreiben wird dersyntaktisch sichtbare Zusammenhang hergestellt und der Wert kopiert. Wegen derVerwaltung dieser Vorgänge spricht man von verwaltetem Code. C++ profitiertvon Zeigern durch deutlich höhere Geschwindigkeit, durch den direkten Spei-cherzugriff ist es jedoch fehleranfällig. C# ist nicht ganz so schnell, aber schneller

Page 160: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

als andere Sprachen und erzeugt sehr stabilen Code. PHP zu guter Letztbeherrscht weder Zeiger noch kennt es verwalteten Code. Man kann aber mitReferenzen arbeiten, die den Zusammenhang zwischen der ursprünglichen Spei-cherstelle und einem neuen Namen erhalten.

Übergibt man nun einer Funktion statt des Wertes eine Referenz auf eine Variablen,wirken sich Änderungen an den Parametern in der Funktion auf den Aufrufwert aus.

Listing 4.17: functionreference.php – Ändern der aufrufenden Variablen

function BuildRow(&$text){ $text = "<tr><td>$text</td></tr>";}echo '<table border="1">';$t = 'Reihe';BuildRow($t);echo $t;echo $t;echo $t;echo '</table>';

Die Referenzierung wird definiert, indem dem Parameter das &-Zeichen vorange-stellt wird. Damit bestimmt die Definition, ob referenziert wird oder nicht. Leiderkann auch in PHP5 das Prinzip auf den Kopf gestellt und die Referenzierung demAufrufer überlassen werden:

BuildRow(&$t);

Es ist deshalb gefährlich, Parameter zu verändern, wenn dies nicht ausdrücklicherwünscht ist. Neben der Prüfung der Parameter heißt dies in der Praxis, dasszuerst alle Werte in lokale Variablen kopiert und dann weiter verwendet werden.Zum Thema Variablen gibt es darüber hinaus noch einige weitere Besonderheitenim Zusammenhang mit Funktionen. Sie werden in den folgenden beidenAbschnitten vorgestellt.

Statische Variablen

160

Statische Variablen erlauben es, den Wert einer Variablen von Funktionsaufruf zuFunktionsaufruf zu erhalten. Das Beispiel mit dem Tabellenreihengeneratorkönnte so mit einer Zählfunktion erweitert werden. Als Schlüsselwort wird staticeingesetzt.

Page 161: 382726314 X Php5 In 14 Tagen (Ddt)

Benutzerdefinierte Funktionen

Listing 4.18: functionstatic.php – Verwendung statischer Variablen in Funktionen

function BuildRow($bRowNumber, $bHead, $iCells){ static $rowNumber = 1; $cells = $iCells; $Params = func_num_args(); $sTag = $bHead ? 'th' : 'td'; if (($Params - 2) <= 0) { if ($bRowNumber) $cells++; return "<tr><$sTag colspan=\"$cells\"></$sTag></tr>"; } $row = '<tr>'; if ($bRowNumber) { $row .= "<td>$rowNumber</td>"; $rowNumber++; } for ($i = 2; $i < $Params; $i++) { $row .= "<$sTag>" . func_get_arg($i) . "</$sTag>"; } for ($k = $i - 2; $k < $cells; $k++) { $row .= "<$sTag>&nbsp;</$sTag>"; } return "$row</tr>";}echo '<table border="1">';echo BuildRow(FALSE, FALSE, '&nbsp;', 'PLZ', 'Vorwahl', 'Ort', 'Aktiv?');echo BuildRow(TRUE, FALSE, 5, '12683', '030', 'Berlin');echo BuildRow(TRUE, FALSE, 5, '89315', '089', 'München', 'X');echo BuildRow(TRUE, FALSE, 5, '20686', '040', 'Hamburg', 'X');echo '</table>';

161

Die Deklaration einer statischen Variablen führt dazu, dass diese ihren letztenZustand behält, auch wenn der Programmfluss die Funktion wieder verlassen hat.Die Deklaration sollte immer auch die Zuweisung des Startwertes enthalten:

static $rowNumber = 1;

Page 162: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

PHP5 führt diese Zeile nur beim ersten Aufruf aus. Danach gilt die Variable alsbekannt und die 1 wird nicht erneut zugewiesen. Damit kann der Wert bei jedemAufruf verändert werden:

$rowNumber++;

Bei der Ausgabe ergibt sich daraus dann eine fortlaufende Zählung der Tabellen-reihen:

Die hier vorgestellte Methode mit statischen Variablen hat – trotz glei-chen Namens – nichts mit den statischen Mitgliedern von Klassen zutun. Der Name wurde bereits vor langer Zeit in PHP benutzt, in der andie mit PHP5 eingeführten objektorientierten Konzepte noch nicht zudenken war.

Globale Variablen und Konstanten

PHP5 kennt, abgesehen von den Schutzmechanismen für Variablen innerhalbvon Klassen, nur globale Variablen. Die Sichtbarkeit beschränkt sich auf dasBasisskript, innerhalb von Funktionen sind globale Variablen normalerweise nichtsichtbar. In der Regel helfen Parameter, den Zugriff auf Werte zu erlauben. Wennnichts Grundlegendes dagegen spricht, sind Parameter der beste Weg. Außerdemverfügt PHP5 über einige so genannte »Super-Arrays«, die wichtige Werte aus For-mularen oder Cookies enthalten und die auch in Funktionen abgerufen werdenkönnen.

Globale Variablen in Funktionen nutzen

Abbildung 4.8: Tabelle, gebaut mit einer zählende Spalte mit stati-schen Funktionsvariablen

162

Dennoch besteht manchmal der Wunsch, Variablen aus der obersten Skriptebenesichtbar zu machen, ohne dafür Parameter einzusetzen. Um das zu erledigen, kön-nen Sie sich den Zugriff mit dem Schlüsselwort global verschaffen. Dies istzugleich auch ein Vollzugriff auf die Variable, das heißt, Änderungen innerhalbder Funktion wirken sich direkt auf das Original aus.

Page 163: 382726314 X Php5 In 14 Tagen (Ddt)

Benutzerdefinierte Funktionen

Listing 4.19: functionglobal.php – Globale Variablen in Funktionen nutzen

$cells = 6;function PrintTableRow($bgcolor){ global $cells; $cellstring = ''; for ($i = 0; $i < $cells; $i++) { $cellstring .= "<td> $i </td>"; } $row = <<<RET <tr bgcolor="$bgcolor">$cellstring</tr>RET; return $row;}echo '<table border="1">';echo PrintTableRow('gray');echo PrintTableRow('red');echo PrintTableRow('gold');echo '</table>';

Das Schlüsselwort global macht die Variable lediglich bekannt, Name und Inhaltwerden dabei nicht verändert.

Konstanten in Funktionen

Konstanten sind in PHP generell global – sie müssen nicht explizit bekanntgemacht werden. Das Beispiel mit global könnte man deshalb auch folgenderma-ßen schreiben:

Listing 4.20: functionglobalconst.php – Konstanten zur Konfiguration einer Funktion

define('CELLS', 6);function PrintTableRow($bgcolor){

163

$cells = CELLS; $cellstring = ''; for ($i = 0; $i < $cells; $i++) { $cellstring .= "<td> $i </td>"; }

Page 164: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

$row = <<<RET <tr bgcolor="$bgcolor">$cellstring</tr>RET; return $row;}echo '<table border="1">';echo PrintTableRow('gray');echo PrintTableRow('red');echo PrintTableRow('gold');echo '</table>';

Sie können die Konstante CELLS hier natürlich direkt benutzen. Betrachten Siedennoch die folgende Zuweisung:

$cells = CELLS;

Hier wird die Konstante in einer lokalen Variablen benutzt. Man profitiert damitvon der Variablenauflösung in Zeichenketten und der Einsatz in Heredoc-Blöcken(die mit <<<) wird möglich.

Rekursive Funktionen

Die Rekursion gehört zum grundlegenden Handwerkszeug des Informatikers.Dahinter verbirgt sich die relativ einfache, aber Anfängern möglicherweiseunheimliche Technik, des Selbstaufrufs von Funktionen.

Es gibt viele Anwendungsmöglichkeiten. So kann eine Funktion beispielsweiseden Inhalt eines Verzeichnisses ermitteln. Ein Verzeichnis kann aber wiederumein Unterverzeichnis enthalten usw. Deren Anzahl ist per Definition nichtbegrenzt und deshalb wäre eine Schleife schwer zu programmieren. Auch inner-halb der Schleife müsste man die Zwischenergebnisse immer wieder ablegen, bei-spielsweise in einem Array. Das ist alles sehr umständlich. Da PHP-Funktionenlokale Variablen kennen, führt jeder Funktionsaufruf bereits zu einer abgeschlos-senen Instanz. Indem sich die Funktion selbst aufruft, durchläuft sie den Verzeich-nisbaum selbstständig.

Damit dabei keine Endlosschleife entsteht, muss explizit ein Ausstiegspunkt pro-

164

grammiert werden. Prinzipiell läuft das so ab, dass PHP bei jedem Aufruf einerFunktion die aktuelle Adresse des Programms auf einem Stapel ablegt. Am Endeder Funktion oder bei Ausführung von return wird die oberste Adresse vom Stapelwieder entnommen und diese zum Rücksprung benutzt. Wird die rekursive Funk-tion irgendwann beendet, löst sie nach und nach alle Stapelwerte wieder auf.

Page 165: 382726314 X Php5 In 14 Tagen (Ddt)

Benutzerdefinierte Funktionen

Da Stapel eine begrenzte Kapazität haben, muss man das Programm so gestalten,dass nicht endlos Aufrufe möglich sind, auch wenn die aktuellen Daten dies ver-langen. In PHP 4 war die Stapelgröße strikt auf 254 beschränkt. PHP5 kann offen-sichtlich dynamisch Speicher anfordern und verkraftet ca. 6.000 rekursive Aufrufe.Eine genaue Definition gibt es nicht, die Anzahl hängt vom Speicherverbrauchdes Skripts ab. Allerdings muss man beachten, dass Hunderte Funktionsaufrufe zuerheblichen Laufzeiten führen und die genannte Größe mehr als ausreichend ist.Sollten Sie mehr rekursive Aufrufe benötigen, liegt vermutlich ein Designfehlerim Programm vor.

Eine gute Demonstrationsmöglichkeit bieten immer wieder mathematische Funk-tionen. Die folgende Funktion ist sicher jedem vertraut:

f(x) = xy

PHP verfügt dafür über eine passende Funktion. Dennoch könnte man dies auchrekursiv lösen, indem der folgende Algorithmus angewendet wird:

f(x) = x * x(y-1)

Letzteres wird solange ausgeführt, bis y = 1 und damit x(y-1) = 1 ist, denn mit x × 1 =x ist der Weg beendet.

Listing 4.21: functionrekursion.php – Potenzrechnung mit rekursiven Aufrufen

function Power($base, $exp){ if ($exp > 0) { return $base * power($base, $exp - 1); } return 1;}echo Power(7, 2);

Das Beispiel gibt mit den Testwerten 7 und 2 natürlich 49 aus (72). Interessanter istder Ablauf der Funktion. Das schrittweise Verfolgen kann sehr hilfreich sein:

1. Der erste Aufruf füllt die Parameter mit den Werten 7 und 2.

165

2. Nach der Prüfung 2 > 0 (Wahr) erfolgt die erste Berechnung:

7 * (Nächster Aufruf mit 7 und 1)

Page 166: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

3. Prüfung, danach Selbstaufruf mit den neuen Parametern:

7 * (Nächster Aufruf mit 7 und 0)

4. Die Prüfung ist nun nicht mehr Wahr, der Rückgabewert wird mit 1 festgelegt.

5. Der zweite rekursive Aufruf wird ausgeführt, der Rückgabewert ist 7:

7 * 1

6. Der erste rekursive Aufruf wird ausgeführt, der Rückgabewert ist 49:

7 * 7

7. Es sind keine Stapelwerte mehr da und der letzte Rückgabewert geht ansHauptprogramm: 49.

Die Rücksprungprüfung im Beispiel wird übrigens durch die if-Anweisung erle-digt. Eine solche Prüfung ist unbedingt erforderlich. Andere rekursive Lösungenverwenden auch for oder foreach, die irgendwann keine definierten Startwertemehr erhalten und deshalb den im Schleifenkörper liegenden rekursiven Aufrufnicht mehr ausführen.

Variable Funktionen

Neben dem normalen Funktionsaufruf kennt PHP auch dynamische Funktionen,ähnlichen den dynamischen Variablen. Dabei wird der Name einer Funktioneiner Variablen zugewiesen und diese dann mit der Funktionssyntax aufgerufen.Der Vorteil dabei: Der Name der Funktion wird wie eine Zeichenkette behandeltwerden.

Das folgende Skript zeigt mehrere Versionen für Definition und Aufruf:

Listing 4.22: functiondynamic.php – Dynamische Funktionen sind sehr flexibel

function MakeB($text){ return "<b>$text</b>";}

166

function MakeI($text){ return "<i>$text</i>";}function MakeU($text)

Page 167: 382726314 X Php5 In 14 Tagen (Ddt)

Benutzerdefinierte Funktionen

{ return "<u>$text</u>";}$tag = 'I';$base = 'Make';$func = $base.$tag;echo 'Kursiv: ' . $func('Test') . '<br>';$func = $base.'U';echo 'Unterstrichen: ' . $func('Test') . '<br>';$func = "{$base}B";echo "Fett: {$func('Test')}";

Im Beispiel werden drei Funktionen definiert, die sich nur durch ihr Suffix unter-scheiden. Sie bauen einen als Parameter übergebenen Text jeweils in ein HTML-Tag ein. Der Funktionsname wird nun dynamisch zusammengesetzt:

$func = $base.$tag;

Der Aufruf erfolgt dann unter Zuhilfenahme der Funktionsschreibweise, angewen-det auf diese Variable:

$func('Test')

PHP kann dies an den runden Klammern erkennen. Der eigentliche Effekt bestehtdarin, dass man einen an zentraler Stelle platzierten Funktionsaufruf situationsab-hängig modifizieren kann. Dies kann unter Umständen ein Skript erheblich ver-einfachen. Die Lesbarkeit wird dadurch aber kaum gesteigert. Anfänger dürftenregelmäßig Schwierigkeiten haben, derartige Programme sofort zu durchschauen.

Noch ein weiterer Effekt macht den Einsatz lohnenswert. Die Auflösung von Vari-ablen in Zeichenketten wird sehr oft eingesetzt. Will man Rückgabewerte vonFunktionen direkt ausgeben, bleibt normalerweise nur eine Zeichenverkettung:

echo 'Kursiv: ' . $func('Test') . '<br>';

Viele Anführungszeichen, viele Punkte, wenig Lesbarkeit; derartige Konstruktesind ebenso häufig wie lästig. Eleganter sieht der folgende Aufbau aus:

echo "Fett: {$func('Test')}";

Die geschweiften Klammern sind zwingend erforderlich, um der Auflösungskomp-

167

onenten die korrekten Grenzen zu zeigen.

Abbildung 4.9: Ausgaben, erzeugt mit dynamischen Funktionsaufrufen

Page 168: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

Die dynamischen Funktionen kann man auch mit internen Namen verwenden,wie das folgende Beispiel zeigt:

Listing 4.23: functiondynintern.php – Interne Funktionen dynamisch aufrufen

$pf = 'sprintf';$dt = 'date';echo "Ausgabe: {$pf('Datum: %s', $dt('d.M.Y'))} ";

Hier werden die Funktionen sprintf und date (Datumsausgabe) verwendet unddie Ausgabe in eine Zeichenkette integriert. Die Ausgabe ist wenig spektakulär,aber die dadurch erreichte Flexibilität reicht hart an selbst modifizierenden Codeheran.

Sprachkonstrukte und Anweisungen, wie beispielsweise echo oderinclude, lassen sich nicht mit der dynamischen Funktionssyntax verwen-den, weil es eben keine Funktionen sind.

4.4 Modularisierung von Skripten

Schon bei der Erstellung der ersten Projekte wird der Wunsch aufkommen, ein-mal mühevoll fertig gestellte Programme wieder verwenden zu können. PHPkennt als äußeres Strukturierungskriterium nur Dateien. Will man also Teile einesanderen Programms wieder verwenden, muss man diese in eigenen Dateien able-gen. Für das Einbindung solcher Module werden spezielle Funktionen bereitge-stellt.

Module einbinden

Abbildung 4.10: Ausgabe, komplett mit dynamischen Funktionen erzeugt

168

PHP5 kennt insgesamt vier Anweisungen, die Module aus Dateien aufrufen:

Page 169: 382726314 X Php5 In 14 Tagen (Ddt)

Modularisierung von Skripten

� include

� require Beide Anweisungen öffnen eine angegebene Datei und binden den enthalte-nen Code so ein, also ob er an dieser Stelle geschrieben wäre.

� include_once

� require_once Auch diese beiden Anweisungen öffnen eine angegebene Datei und bindenden enthaltenen Code so ein, also ob er an dieser Stelle geschrieben wäre. Siewerden jedoch nur ein einziges Mal ausgeführt, auch wenn sie mehrfach auf-gerufen werden.

Der Unterschied zwischen include und require besteht in der Reaktion auf Feh-ler. Das liegt an den verschiedenen Verarbeitungszeitpunkten. require bindet erstdie Datei ein und führt dann das Skript aus. Fehlt das Modul (Datei nicht gefun-den), erscheint sofort ein fataler Fehler und die Ausführung bricht ab. includewird dagegen erst ausgeführt, wenn der Programmfluss am Befehl angekommenist. Eine fehlende Datei führt dann lediglich zu einer Warnung.

Bezüglich der reinen Verarbeitung der Inhalte verhalten sich beide Anweisungidentisch. Im folgenden Text wird deshalb keine Trennung mehr vorgenommenund nur mit include gearbeitet.

Module und HTML

Jedes Modul wird separat verarbeitet. Das bedeutet, dass PHP erstmal eine HTML-Datei erwartet. Steht PHP drin, muss dieses in die üblichen Markierungen <?phpund ?> eingeschlossen werden.

Betrachten Sie zuerst die Anwendung:

Listing 4.24: include.php – Nutzung von Moduldateien

<html><head><?php

169

$title = "Testseite";?> <title><?=$title?></title></head><body>

Page 170: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

<?phpinclude ('include/header.inc.php');echo 'Text auf der Seite';include ('include/footer.inc.php');?></body></html>

Der PHP-Block erscheint hier in der üblichen Form. Völlig unabhängig von derVerwendung steht jedes Modul für sich und deklariert seinen PHP-Bereich – wennvorhanden – unabhängig davon.

Listing 4.25: header.inc.php – Variablen der übergeordneten Instanz verwenden

<?php$header = <<<HEADER<h1>$title</h1>HEADER;?>

Das erste Modul, für den Kopfbereich, übernimmt die Variable $title. Das ist ohneweiteres möglich, da sich der Text so verhält, als wäre er an der Stelle der include-Anweisung geschrieben worden. Das Schlüsselwort global oder vergleichbareTechniken sind nicht erforderlich.

Listing 4.26: footer.inc.php – Der Fußbereich, ganz ohne PHP

<h4>(C) 2004 Markt+Technik</h4>

Der Fußbereich zeigt, dass sich die Anweisungen auch bestens zum Einbindungvon reinem HTML eignen. Dies verschafft möglicherweise eine bessere Flexibili-tät als die Verwendung der Heredoc-Syntax, wenn größere Textblöcke erforderlichsind.

Module finden

Im letzten Beispiel wurde der Pfad zu den Modulen – sie waren im Unterverzeich-

170

nis /include platziert – direkt angegeben. Möglicherweise haben Sie eine Samm-lung solcher Module an zentraler Stelle auf dem Server liegen. Dann wäre dieAngabe des Pfades immer und immer wieder sehr lästig. Deshalb kann man einenspeziellen Suchpfad angeben, der von PHP benutzt wird, wenn im aktuellen Ver-

Page 171: 382726314 X Php5 In 14 Tagen (Ddt)

Modularisierung von Skripten

zeichnis die entsprechende Datei nicht aufzufinden war. Der Wert kann entwederin der php.ini konfiguriert oder mit ini_set angegeben werden.

Listing 4.27: includeset.php – Pfad zu den Modulen über Suchpfade finden

<html><head><?phpini_set('include_path', 'include');$title = "Testseite";?> <title><?=$title?></title></head><body><?phpinclude ('header.inc.php');echo 'Text auf der Seite';include ('footer.inc.php');?></body></html>

Der Parameter, der gesetzt werden muss, heißt include_path. Der Pfad kann abso-lut oder relativ zu aktuellen Skriptposition sein. Damit man von der Verschiebungdes Skripts unabhängig ist, sollten auf Produktionssystemen absolute Pfade einge-setzt werden – im Gegensatz zur sonst üblichen Praxis, nur relative Pfade zu ver-wenden.

Mehrfachverwendung verhindern

Wenn innerhalb eines Moduls eine Funktions- oder Klassendefinition steht, ist diemehrfache Verwendung innerhalb eines Skripts fatal. Denn eine mehrfache Defi-nition – worauf es hinausläuft – ist nicht zulässig. Ein Laufzeitfehler wäre dieFolge. In solchen Fällen verwendet man include_once. Die Anweisung stelltsicher, dass der Inhalt nur beim ersten Mal ausgeführt wird. Viele große Projektverwenden nämlich verschachtelte Zugriffe auf die Module und andere Module

171

haben möglicherweise einen bestimmten Code bereits eingebunden. Mangelsanderer Konzepte sind solche Mehrfachverwendungen in PHP durchaus üblich.Aus informationstechnischer Sicht ist include_once zwar eher als »dirty hack« zubetrachten, aber es passt zu PHP als primitiver Sprache.

Page 172: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

Automatisches Einbinden mit __autoload

Die Funktion __autoload ist auf globaler Ebene zu definieren. Sie wird immerdann aufgerufen, wenn eine unbekannte Klasse instanziiert wird. Bei großen Pro-jekten spart diese Vorgehensweise unter Umständen das Laden großer Biblio-theken, falls einige Klasse daraus nur sporadisch benötigt werden. Praktisch siehtdas folgendermaßen aus:

function __autoload($classname){ include_once("{$classname}.inc.php");}$instance = new LibraryClass();

Bei der Ausführung wird folgendes ausgeführt:

include_once("LibraryClass.inc.php");

Ohne weitere objektorientierte Techniken wird man davon allerdings nicht profi-tieren können.

Informationen über Module ermitteln

Hat man vollends den Überblick über die gerade eingebundenen Module verlo-ren, bringen ein paar Hilfsfunktionen Licht in den Dschungel:

� get_included_files

� get_required_files

Beide Funktionen geben jeweils ein Array der Dateien zurück, die im Skript einge-schlossen wurden. Um eine vollständige Übersicht zu erhalten, müssen Sie dieFunktion am Ende des Skripts aufrufen, andernfalls werden nur die bis zu diesemZeitpunkt eingeschlossenen Module ermittelt. Das Array enthält an erster Stelleimmer auch das eigentliche Hauptskript. Beide Funktionen geben übrigens alledurch require oder include eingebundene Module an, sind also praktisch völligidentisch. Vermutlich ist dies ein Bug, denn die Namen suggerieren etwas anderes.

172

Wenn Sie noch keinen Umgang mit Arrays beherrschen, können Sie die folgendeAusgabe verwenden, um das Ergebnis zu sehen:

<html><head><?php

Page 173: 382726314 X Php5 In 14 Tagen (Ddt)

Fehlerbehandlung

ini_set('include_path', 'include');$title = "Testseite";?> <title><?=$title?></title></head><body><pre><?phpinclude ('header.inc.php');echo 'Test';include ('footer.inc.php');

Listing 4.28: getincluded.php – Informationen über eingeschlossene Module

print_r(get_required_files());?></pre></body></html>

Die Ausgabe erscheint gut lesbar, wenn man sie in <pre>-Tags einbaut. Dies eignetsich vor allem zur schnellen Fehlersuche:

4.5 Fehlerbehandlung

PHP5 führt einige neue Methoden der Behandlung von Laufzeitfehlern ein.Damit rückt die Sprache ein wenig in Richtung moderner Sprachen wie Java oder

Abbildung 4.11: Ausgabe der Liste der eingeschlosse-nen Dateien

173

C. Allerdings gelang den Entwicklern nur eine halbherzige Umsetzung, was denWert wieder etwas in Frage stellt. Nichtsdestotrotz lohnt es sich, die neuen Mög-lichkeiten auszuprobieren und einzusetzen.

Page 174: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

Aber auch die konventionelle Technik der Fehlerbehandlung ist nicht völligaußen vor. In der Kombination aller Varianten ist der Umgang mit unvermeid-lichen Fehlern recht brauchbar.

Konventionelle Fehlerbehandlung

Das folgende Skript prüft, ob ein Name in einer Liste von Namen vorhanden ist.Die Details der Verarbeitung sind hier völlig uninteressant, es geht nur um dasPrinzip. Die Funktion SearchName sucht einen Namen und gibt TRUE zurück,wenn die Suche erfolgreich war.

Listing 4.29: errorcontrolcon.php – Fehlerbehandlung ohne spezielle Sprachmittel

function SearchName($Name){ if (strlen($Name) < 3) { return false; } $Names = array ('Joerg', 'Uwe', 'Clemens', 'Lukas'); if (in_array($Name, $Names)) { return true; } else { return false; }}$name = 'Clemens';if (SearchName($name)){ echo 'Name gefunden';}else

174

{ echo 'Name nicht vorhanden';}

Das Skript ist prinzipiell korrekt geschrieben. Die Funktion folgt den üblichenRegeln und prüft vor der Verarbeitung, ob sinnvolle Daten vorliegen. Im Beispiel

Page 175: 382726314 X Php5 In 14 Tagen (Ddt)

Fehlerbehandlung

wird eine Mindestlänge von drei Zeichen für den Namen erwartet. Der Rückgabe-wert ist das eigentliche Problem: FALSE kann sowohl auf einen Datenfehler alsauch auf eine erfolglose Suche hindeuten. Genau hier setzen üblicherweise Feh-lerbehandlungen an. Falsche Daten sollten einen Laufzeitfehler erzeugen, wäh-rend eine misslungene Suche lediglich die passenden Rückgabewerte erzeugt.Manche Programme behelfen sich mit Hilfslösungen, indem sie Zahlenwertezurückgeben und dann -1, 0 und 1 usw. definieren. Das ist nicht schön, denn dieZustände »gefunden« und »nicht gefunden« sind eindeutig Boolescher Natur.

Laufzeitfehler selbst erzeugen – der klassische Weg

Der erste Ansatz ist bereits in PHP 4 möglich. Hierzu wird zuerst eine Funktiondeklariert, die Laufzeitfehler auffängt und damit eine gezielte Reaktion ermög-licht. Die Funktion set_error_handler definiert diese Verzweigung. Ist das erfolgt,wird mit trigger_error im Bedarfsfall der Fehler erzeugt.

Listing 4.30: errorcontroltrigger.php – Benutzerdefinierte Fehlerverwaltung

set_error_handler('SearchError');function SearchError($errno, $errstr, $errfile, $errline){ switch ($errno) { case E_USER_ERROR: echo "Fehler: $errstr"; exit; default: echo "$errstr on Line $errline in file $errfile"; }}function SearchName ($Name){ if (strlen($Name) < 3) { trigger_error('Name zu kurz', E_USER_ERROR);

175

} $Names = array ('Joerg', 'Uwe', 'Clemens', 'Lukas'); if (in_array($Name, $Names)) { return true; }

Page 176: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

else { return false; }}$name = 'xx';if (SearchName($name)){ echo 'Name gefunden';}else{ echo 'Name nicht vorhanden';}

Das Skript definiert zuerst eine so genannte Rückruffunktion, die aufgerufen wird,wenn ein Fehler auftritt:

set_error_handler('SearchError');

Dann muss freilich die Funktion selbst auch definiert werden. Sie hat einen defi-nierten Aufbau, denn PHP erwartet, dass bestimmte Parameter übergeben werdenkönnen:

function SearchError($errno, $errstr, $errfile, $errline)

Die Parameter haben von links nach rechts folgende festgeschriebene Bedeutung:

� Fehlernummer: Eine der internen Fehlernummern.

� Fehlertext: Ein Text, der den Fehler beschreibt.

� Datei: Die Datei, in der der Fehler ausgelöst wurde. Diese Angabe beachtetmit include oder require eingebundene Module.

� Zeile: Die Zeile, auf der der Laufzeitfehler in der entsprechenden Datei ausge-löst wurde.

Wie Sie diese Angaben nun verwenden, hängt ganz von der Applikation ab. Wich-tig ist auch, auf die verschiedenen Fehlerklassen reagieren zu können. Sie sind derfolgenden Tabelle zu entnehmen.

176

Page 177: 382726314 X Php5 In 14 Tagen (Ddt)

Fehlerbehandlung

Wert Konstante Beschreibung

1 E_ERROR Dies zeigt Fehler an, die nicht behoben werden können. Die Ausführung des Skripts wird abgebrochen.

2 E_WARNING Warnungen während der Laufzeit des Skripts. Führen nicht zum Abbruch.

4 E_PARSE Parser-Fehler während der Übersetzung. Das Skript startet gar nicht erst.

8 E_NOTICE Benachrichtigung während der Laufzeit. Wird oft unter-drückt, was aber keine gute Idee ist.

16 E_CORE_ERROR Fatale Fehler, ähnlich E_ERROR, aber woanders erzeugt.

32 E_CORE_WARNING Warnungen, ähnlich E_WARNING, aber woanders erzeugt.

64 E_COMPILE_ERROR Fataler Fehler zur Übersetzungszeit, erzeugt von der Zend-Engine.

128 E_COMPILE_WARNING Warnungen zur Übersetzungszeit, erzeugt von der Zend-Engine.

256 E_USER_ERROR Benutzerdefinierte Fehlermeldungen, erzeugt mit trigger_error.

512 E_USER_WARNING Benutzerdefinierte Warnung, erzeugt mit trigger_error.

1024 E_USER_NOTICE Benutzerdefinierte Benachrichtigung, erzeugt mit trigger_error.

2047 E_ALL Alle Fehler und Warnungen die unterstützt werden, mit Ausnahme von E_STRICT.

2048 E_STRICT Benachrichtigungen des Laufzeitsystems mit Vorschlägen für Änderungen des Programmcodes, die eine bessere Inter-operabilität und Kompatibilität Ihres Codes gewährleisten. Wurde mit PHP5 eingeführt.

Tabelle 4.2: Fehlercodes in PHP5

177

Wie das letzte Beispiel zeigte, eignet sich für den eigenen Code E_USER_ERROR.Weitere Konstanten kann man selbst definieren, diese sollte dann einfach Wertegrößer als 65.535 enthalten, damit keine Konflikte auch in künftigen Versionen

Page 178: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

auftreten. Im Übrigen ist noch zu beachten, dass die Werte Bitfelder darstellenund immer Zweier-Potenzen sind, damit man die Werte kombinieren kann.

Fehlerbehandlung mit PHP5

Mit PHP5 wurde eine neue Art der Fehlerbehandlung eingeführt, die auf einerneuen Kontrollstruktur basiert: try/catch. Die Idee dahinter: Man definiert einenBlock, umschlossen von try, in dem mit dem Auftreten von Laufzeitfehlerngerechnet wird. Um diese selbst zu erzeugen, wird die Anweisung throw verwen-det. Der so ausgelöste Fehler muss nun noch zentral abgefangen und verarbeitetwerden, quasi das Gegenstück zur Rückruffunktion muss her. Dies erledigt catch.

Das mag alles etwas abstrakt und unnütz aufwändig klingen. Es führt jedoch dazu,dass Applikation und Fehlerbehandlung getrennt werden. Damit wird ein Skriptleichter wartbar, lesbarer und letztendlich stabiler. Die hier gezeigten Trivialbei-spiele wären freilich einfacher zu schreiben, aber in der Praxis sind 5.000 Zeilenfür ein Skript durchaus üblich, und dann ist die Suche nach einem Fehler nichtmehr trivial, auch für Profis nicht.

Fehlerbehandlung mit try/catch verwenden

Das folgende Skript nutzt die Kombination aus try/catch und throw, um diebesprochene Trennung von Auswertung und Fehler zu realisieren:

Listing 4.31: errorcontroltrycatch.php – Kontrolle über Laufzeitfehler mit try/catch

function SearchName ($Name){ if (strlen($Name) < 3) { throw new Exception('Name zu kurz'); } $Names = array ('Joerg', 'Uwe', 'Clemens', 'Lukas'); if (in_array($Name, $Names))

178

{ return true; } else { return false;

Page 179: 382726314 X Php5 In 14 Tagen (Ddt)

Fehlerbehandlung

}}$name = 'Jo';try{ if (SearchName($name)) { echo 'Name gefunden'; } else { echo 'Name nicht vorhanden'; }}catch (Exception $ex){ echo $ex->getMessage();}

Eine Rückruffunktion wird nun nicht mehr benötigt. Es genügt, den Block, indem Fehler auftreten können, in try zu verpacken:

try{ ///}

Im Beispiel ist es die Funktion SearchName, die Probleme machen, also Fehler»werfen« kann. Der Begriff »werfen« hat hier Methode, denn »geworfen« wird derFehler mit throw (engl. werfen). Interessanterweise sind die Fehler in PHP5, dieausgelöst werden, keine Konstanten mehr, sondern Klassen. Da Klassen erst amnächsten Tag behandelt werden, nehmen Sie die Syntax hier erstmal kommentar-los hin:

throw new Exception('Name zu kurz');

Technisch wird hier eine Instanz mit new erzeugt und dann übergeben. Interessan-ter ist, was daraus im catch-Zweig wird. Hinter einem try können ein odermehrere catch-Zweige stehen. Jeder Zweig prüft nun, welche Art von Fehler hier

179

ankommt. Im Beispiel wurde dazu nur die Basisklasse Exception verwendet, diePHP5 bereitstellt:

catch (Exception $ex)

Page 180: 382726314 X Php5 In 14 Tagen (Ddt)

Programmieren

Die Variable $ex, die hier entsteht, erlaubt den Zugriff auf das Objekt, das newerzeugt hat, als der Fehler mit throw abgeschickt wurde. Darin steht – wenig über-raschend – der Fehlertext:

echo $ex->getMessage();

Der Aufruf zeigt den Zugriff auf eine Methode der Klasse, getMessage(). Genaue-res dazu finden Sie am Ende des folgenden Tages, wenn es die dann vermitteltenGrundlagen der objektorientierten Programmierung erlauben, tiefer in dieGeheimnisse der Fehlerbehandlung in PHP5 einzusteigen.

Laufzeitfehler, die das interne Laufzeitsystem erzeugt, lassen sich übri-gens mit try/catch nicht abfangen. Hierzu kann nur auf die bereitsbeschriebenen »konventionellen« Techniken verwiesen werden.

4.6 Kontrollfragen

1. Schreiben Sie ein Skript, das beliebige Zahlen zwischen 0 und 100 auf ganzeZehner rundet.

2. Können mit switch-Anweisungen auch Vergleiche mit beliebigen BooleschenAusdrücken erfolgen?

3. Welche Schleife wird benutzt, wenn sichergestellt werden muss, dass der Schlei-fenkörper mindestens einmal durchlaufen wird?

4. Zu Testzwecken kann es erforderlich sein, eine Schleifenbedingung so zu formu-lieren, dass eine Endlosschleife entsteht. Mit welchen Anweisungen kann ein»Notausstieg« programmiert werden?

180

Page 181: 382726314 X Php5 In 14 Tagen (Ddt)

Daten mit Arrays

verarbeiten

5

Page 182: 382726314 X Php5 In 14 Tagen (Ddt)

Daten mit Arrays verarbeiten

Sehr praktisch ist das Zusammenfassen ähnlicher Daten unter einem gemeinsa-men Namen. Solche Gebilde werden als Arrays (Datenfelder) bezeichnet. PHPbietet hier eine großartige Unterstützung mit vielen Funktionen an.

5.1 Datenfelder im Einsatz: Arrays

Arrays werden sehr oft benötigt. Ein sehr wichtiger Einsatzfall ist im Zusammen-hang mit Datenbanken zu finden. Abfragen an Datenbanken liefern sehr oft dieDaten als Arrays aus. Mit Arrays kann man dann sehr flexibel umgehen. TypischeFunktionen, die direkt und mit wenig Aufwand ausgeführt werden können, sind:

� Ausgaben in Schleifen

� Sortieren

� Zusammenfügen

� Suchen

Für all diese Aufgaben bietet PHP spezielle Funktionen und Sprachkonstrukte.Das führt zwangsläufig zu einer großen Anzahl von Möglichkeiten und Varianten,was den Umgang mit Arrays nicht unbedingt vereinfacht. Etwas Systematik kannda nicht schaden. Die folgenden drei Abschnitte entsprechen deshalb in etwa demLebenszyklus eines Arrays:

1. Das Array erstellen und befüllen

2. Das Array manipulieren

3. Das Array ausgeben

5.2 Arrays erstellen und befüllen

Arrays sind Sammlungen ähnlicher Daten unter einem gemeinsamen Namen. So

182

könnte man eine Liste von Ländercodes erstellen:

� de

� en

� ca

Page 183: 382726314 X Php5 In 14 Tagen (Ddt)

Arrays erstellen und befüllen

� fr

� it

� es

Will man damit in einem Programm umgehen, wäre die Anlage immer neuerVariablen wenig hilfreich:

$lang1 = "de"$lang2 = "en"

Stattdessen schreibt man alle Werte in eine Array-Variable und greift über einen sogenannten Index auf die Inhalte zu.

Die Aussage »ähnliche Daten« ist übrigens in PHP nicht ganz ernst zunehmen. Mangels strenger Typkontrolle kann man beliebige Daten ineinem Array zusammenpacken.

Ein einfaches Array erstellen

Um ein einfaches Array zu erstellen, benötigt man eine Angabe, die PHP mitteilt,dass es sich um ein solches handelt. Das folgende Beispiel zeigt dies:

$lang[] = "de";$lang[] = "en";$lang[] = "es";$lang[] = "fr";$lang[] = "it";

PHP erzeugt hier nur eine Variable mit dem Namen $lang, die insgesamt fünfWerte enthält. Im Gegensatz zu normalen Variablen überschreiben sich dieZuweisungen nicht gegenseitig. Das Geheimnis liegt in den eckigen Klammern.

Intern passiert ein klein wenig mehr, denn PHP muss die Werte unterscheidenkönnen. Dazu wird ein numerischer Index vergeben, also eine Zahl, mit der jedereinzelne Wert adressiert werden kann. Ohne weitere Angaben beginnt PHP mitdem Index 0 – man spricht dann von null-basierten Indizes – und zählt fortlaufend

183

in ganzen Zahlen weiter. Auf diesem Weg kann man auch gezielt jeden Wertabrufen:

Page 184: 382726314 X Php5 In 14 Tagen (Ddt)

Daten mit Arrays verarbeiten

Listing 5.1: arrayindex1.php – Ein einfaches Array erzeugen und darauf zugreifen

$lang[] = "de";$lang[] = "en";$lang[] = "es";$lang[] = "fr";$lang[] = "it";echo "Index Nr. 3 enthält den Wert: {$lang[3]}";

Die Schreibweise bei der Ausgabe wurde übrigens nur mit geschweiften Klam-mern ergänzt, um die Variablenauflösung in der Zeichenkette mit jeder beliebigenArrayart zu ermöglichen. Die »Grundform« sieht folgendermaßen aus:

$lang[3]

Das Beispiel gibt »fr« aus, der vierte Wert im Array. Da mit 0 begonnen wird zuzählen, führt der Index 3 zum gewünschten Ergebnis.

Die Anzahl der Elemente ermitteln

Um die Anzahl der Elemente zu ermitteln, stellt PHP5 die Funktion count zurVerfügung:

Listing 5.2: arraycount.php – Anzahl der Elemente ermitteln

$lang[] = "de";$lang[] = "en";$lang[] = "es";$lang[] = "fr";$lang[] = "it";echo 'Im Array sind ' . count($lang) . ' Werte';

Die Ausgabe entspricht hier der tatsächlichen Anzahl, also 5 im Beispiel (nicht 4,nach dem Motto »null-basiert«).

Den Index manipulieren

184

Der Index muss in PHP nicht fortlaufend sein. Sie können bei der Zuweisung»wild« Werte vergeben:

Page 185: 382726314 X Php5 In 14 Tagen (Ddt)

Arrays erstellen und befüllen

Listing 5.3: arrayindex2.php – Freie Vergabe von Indizes

$lang[4] = "de";$lang[5] = "en";$lang[17] = "es";$lang[3] = "fr";$lang[11] = "it";echo "Index Nr. 11 enthält den Wert: {$lang[11]}";

Auch dieses Array gibt als Anzahl 5 aus:

echo count($lang);

Das Array wird also nicht mit den fehlenden Werten aufgefüllt. Tatsächlich spieltder Index keine Rolle für die interne Verwaltung. Hauptsache, PHP kann die Indi-zes unterscheiden.

Wenn Sie die Angabe zeitweilig weglassen, setzt PHP immer mit dem höchstenvergebenen Index plus Eins fort. Betrachten Sie dazu folgende Sequenz:

$lang[4] = "de";$lang[23] = "en";$lang[17] = "es";$lang[] = "fr";

Der Wert »fr« wird hier mit dem Index 24 gespeichert, nicht mit 18, denn der bis-lang höchste vergebene Wert war 23.

Schlüssel statt Indizes

Das führt schnell zu der Überlegung, ob es nicht möglich wäre, statt der numeri-schen Indizes auch Zeichenketten zu verwenden. PHP zeigt sich tolerant underlaubt dies jederzeit und in jeder Form:

Listing 5.4: arrayindexhash.php – Sprechende Indizes verwenden

$lang['de'] = "Deutschland";$lang['en'] = "England";

185

$lang['es'] = "Spanien";$lang['fr'] = "Frankreich";$lang['ir'] = "Italien";echo "Das Land mit dem Code 'es' heißt: {$lang['es']}";

Page 186: 382726314 X Php5 In 14 Tagen (Ddt)

Daten mit Arrays verarbeiten

In der Informatik werden solche Schlüssel/Wert-Paare auch als Hashes oder Dic-tionaries bezeichnet. Der Fantasie sind bei der Namensvergabe keine Grenzengesetzt, sprechende oder logische Namen sind sinnvoll, sehr lange Ungetüme fehl-erträchtig. Auf jeden Fall wird nur dann von numerischen Indizes abgewichen,wenn es die Lesbarkeit und Verständlichkeit des Skripts deutlich erhöht oderzusätzliche Informationen in den Schlüsseln gespeichert werden.

Vergessen Sie nie die Anführungszeichen bei der Angabe der Schlüssel.PHP mag die direkte Angabe $lang[de] anstatt $lang['de'] tolerieren; diesist jedoch nur auf einen Trick zurückzuführen. Ist eine Konstante (wasde darstellt) nicht definiert, wird diese in eine Zeichenkette umgewan-delt, was dann vermutlich dem gewünschten Effekt entspricht. Ist dieKonstante jedoch zufällig vorhanden, wird der falsche Schlüssel verwen-det. Entspricht der Wert einem Schlüsselwort aus PHP, wird ein Fehlererzeugt.

Arraywerte schneller zuweisen

Die Zuweisung von Werten, die nicht automatisch generiert werden, kann in derbeschriebenen Form sehr mühselig sein. Deshalb gibt es ein spezielles Schlüssel-wort, das dies einfacher erledigt. Daraus entsteht dasselbe Array wie bei der bereitsgezeigten Version, die Nutzung von array spart lediglich Tipparbeit:

Listing 5.5: arrayindexarray.php – Arrays schneller anlegen

$lang = array('de', 'en', 'es', 'fr', 'it');echo "Index Nr. 3 enthält den Wert: {$lang[3]}";

Um nun Schlüssel oder andere als die automatischen Indizes verwenden zu kön-nen, muss die Syntax etwas erweitert werden:

Listing 5.6: arrayindex2array.php – Array mit unregelmäßigen Indizes

$lang = array(4 => 'de', 5 => 'en',

186

17 => 'es', 3 => 'fr', 11 => 'it');echo "Index Nr. 11 enthält den Wert: {$lang[11]}";

Page 187: 382726314 X Php5 In 14 Tagen (Ddt)

Arrays manipulieren

Der Operator => wird hier benutzt, um Schlüssel bzw. Indizes und Werte zu tren-nen. Es ist nahe liegend, dass dies auch mit Zeichenketten funktioniert:

Listing 5.7: arrayhasharray.php – Hash, ein Array mit Schlüsselwerten als Indizes

$lang = array('de' => "Deutschlang", 'en' => "England", 'es' => "Spanien", 'fr' => "Frankreich", 'ir' => "Italien");echo "Das Land mit dem Code 'es' heißt: {$lang['es']}";

Die »Ausdehnung« der Definition über mehrere Zeilen dient der besseren Lesbar-keit. Dies sollten Sie sich unbedingt angewöhnen. Es geht auch nicht darum, hiergegenüber der ersten Variante Zeilen zu sparen. Kompakter, schlecht lesbarerCode ist immer eine dumme Idee. Die obige Schreibweise ist leichter zu erfassenund bei großer Datenzahl schneller geschrieben. Weniger Zeichen zu schreibenheißt immer auch, die Fehlerquote zu verringern.

5.3 Arrays manipulieren

Nachdem die grundsätzlichen Wege, Daten in ein Array zu bekommen, keineHürde mehr darstellen, soll mit den Werten gearbeitet werden.

Werte entfernen

Gelegentlich stört ein Wert – er muss entfernt werden. Ebenso wie eine einfacheVariable kann jeder Teil eines Arrays mit unset entfernt werden:

Listing 5.8: arrayunsethash.php – Entfernen eines Elements aus einem Array

$lang =array('de' => "Deutschlang", 'en' => "England",

187

'es' => "Spanien", 'fr' => "Frankreich", 'ir' => "Italien");unset($lang['en']);echo "Das Land mit dem Code 'en' heißt: {$lang['en']}";

Page 188: 382726314 X Php5 In 14 Tagen (Ddt)

Daten mit Arrays verarbeiten

Das Beispiel führt zu einer Fehlermeldung:

Der fehlende Wert kann jederzeit mit der am Anfang beschriebenen Syntax wiederzugewiesen werden.

Der Arrayzeiger

PHP führt intern einen Zeiger für Arrays, der die gezielte Auswahl von Elementenerlaubt. Dies ist eine bereits mit PHP3 eingeführte Technik, die aber nichts anihrer Attraktivität verloren hat. Viele der neuen Arrayfunktionen, von denen auchmit PHP5 wieder weitere hinzukamen, nutzen diese Zeiger nicht. Einfache Arrayslassen sich mit den alten Funktionen dennoch gut verarbeiten. Zur Auswahl stehteine ganze Palette:

� reset($array); Der Zeiger wird auf das erste Element zurückgesetzt.

� current($array); Die Funktion gibt das Element zurück, auf das der Zeiger zurzeit zeigt.

� next($array); Die Funktion verschiebt den Zeiger zum nächsten Element. Sie gibt falsezurück, wenn kein Element mehr da ist, ansonsten den betreffenden Element-wert.

� prev($array); Die Funktion verschiebt den Zeiger zum vorhergehenden Element. Die Funk-tion gibt false zurück, wenn der Zeiger bereits auf dem ersten Element stand,

Abbildung 5.1: Der Fehlerfall zeigte den Erfolg der Aktion, das Element wurde aus dem Array entfernt

188

ansonsten den betreffenden Elementwert.

� key($array); Die Funktion gibt den Schlüssel oder Index des aktuellen Elements zurück.

Page 189: 382726314 X Php5 In 14 Tagen (Ddt)

Arrays manipulieren

Einige Beispiele, die diese Funktionen nutzen, finden Sie im Abschnitt 5.4 »Arraysausgeben« ab Seite 193. Darüber hinaus ist der Einsatz immer dann interessant,wenn die Indizes nicht sequenziell vergeben wurden und einfache Zählschleifendeshalb nicht in Frage kommen. Für das zuletzt bereits verwendete assoziativeArray sind die Funktionen recht praktisch:

$lang = array('de' => "Deutschland", 'en' => "England", 'es' => "Spanien", 'fr' => "Frankreich", 'ir' => "Italien");reset($lang);

Listing 5.9: arraynextprev.php – Zugriff auf Array-Elemente mit Zeigerfunktionen

do{ echo current($lang) . ' hat den Code ' . key($lang); echo '<br>';} while(next($lang));

Die Ausgabe zeigt, dass der Zugriff sequenziell in der Reihenfolge der Definitionerfolgt:

Das Beispiel sieht toll aus und funktioniert auch. Es hat jedoch eine böse Falle, dienicht offensichtlich ist. Sehen Sie sich zum Vergleich das folgende Array an:

$lang = array('de' => "Deutschland", 'en' => "England", 'es' => "0",

Abbildung 5.2: Sequenzielle Ausgabe eines Arrays mit Zeigerfunktionen

189

'fr' => "Frankreich", 'it' => "Italien");

Statt dem Wort »Spanien« wurde die Ziffer »0« eingesetzt – nach wie vor als Zei-chenkette wohlgemerkt. Diesmal wird folgende Ausgabe produziert:

Page 190: 382726314 X Php5 In 14 Tagen (Ddt)

Daten mit Arrays verarbeiten

Was ist passiert? PHPs Typlosigkeit hat hier einen Streich gespielt. Die Funktionnext gibt nämlich nicht true oder false, sondern den Elementwert oder falsezurück. Der Elementwert ist beim dritten Element jedoch »0«. Da die Prüfungmit while auf false erfolgt, konvertiert PHP den Wert stillschweigend in sein Boo-lesches Äquivalent, und da ist 0 == false, was planmäßig zum Abbruch führt. DieKorrektur ist einfach:

Listing 5.10: arraynextprevkorr.php – Korrigierter Zugriff mit next

$lang = array('de' => "Deutschland", 'en' => "England", 'es' => "0", 'fr' => "Frankreich", 'it' => "Italien");reset($lang);do{ echo current($lang) . ' hat den Code ' . key($lang); echo '<br>';} while(next($lang)!==FALSE);

Der Effekt besteht in der korrekten Auswertung in der while-Bedingung. Mit demOperator !== wird auch der ursprüngliche Typ geprüft und der ist auf der rechtenSeite Boolesch, auf der linken jedoch nicht. Nun kann es sein, dass man denAbbruch durchaus will. Dann setzt man als Wert des Arrays nicht eine Zeichen-kette ein, sondern den passenden Vergleichswert, TRUE oder FALSE. Folgendes Arraybricht wieder ab, diesmal ist es jedoch gewollt und logisch:

$lang = array('de' => "Deutschland", 'en' => "England", 'es' => FALSE, 'fr' => "Frankreich", 'it' => "Italien");

Abbildung 5.3: Sequenzielle Ausgabe mit Problemen

190

Auch der Zugriff auf key ist tückisch. Beginnt das Array mit dem Index 0 – das istder Standardfall – gibt key eben zuerst 0 zurück. Durchläuft man mit einerSchleife die Indizes, ist schon der erste Wert implizit FALSE. Die Operatoren ===und !== sind nett, ersetzen aber eine gewisse Typstrenge nur mangelhaft. So ein-fach wie PHP manchmal ist, so bösartig kann es im Detail werden.

Page 191: 382726314 X Php5 In 14 Tagen (Ddt)

Arrays manipulieren

Arrayfunktionen

Einige der ersten Arrayfunktionen, mit denen Sie praktisch Bekanntschaftmachen, werden die Sortierfunktionen sein. Sortiert wird in der Programmierungimmer wieder und in allen Varianten. PHP verfügt hier über eine inflationäreFunktionssammlung.

Die einfachste Sortierfunktion heißt sort. Sie sortiert alphanumerisch aufwärts,das heißt, zuerst kommen die Zahlen, dann Satzzeichen, dann Buchstaben. Diesentspricht dem Verlauf der ASCII-Werte der Zeichen. Es gibt weitere Sortierfunk-tionen, die ein anderes Verhalten aufweisen. Dazu später mehr.

$lang[] = "fr";$lang[] = "es";$lang[] = "it";$lang[] = "de";$lang[] = "en";sort($lang);

Listing 5.11: arraysort.php – Sortierung eines einfache Arrays

reset($lang);do{ echo current($lang) . '<br>';} while (next($lang) !== FALSE);

Das Ergebnis entspricht den Erwartungen:

Bei assoziativen Arrays ist das nicht ganz so einfach. Das folgende Beispiel zeigteinen interessanten Effekt:

Abbildung 5.4: Erfolgreiche Sortierung eines einfachen Arrays

191

$lang =array('de' => "Deutschland", 'en' => "England", 'es' => "Spanien", 'fr' => "Frankreich",

Page 192: 382726314 X Php5 In 14 Tagen (Ddt)

Daten mit Arrays verarbeiten

'ir' => "Italien");sort($lang);

Listing 5.12: arrayhashsorterr.php – Sieht gut aus, funktioniert aber nicht (siehe Text)

reset($lang);do{ echo current($lang) . ' hat den Code ' . key($lang); echo '<br>';} while(next($lang));

Die Ausgabe ist wenig hilfreich:

Für solche Fälle kennt PHP eine spezielle Sortierung: asort. Denn sort sortiertnur Werte, ignoriert die Schlüssel und legt die dann fehlenden Indizes nach demüblichen Schema neu an: aufsteigend mit 0 beginnend.

asort($lang);

Listing 5.13: arrayhashasort.php: Ausschnitt aus dem korrigierten Listing

Ersetzt man im letzten Listung sort durch asort, funktioniert wieder alles:

Eine Liste aller elementaren Sortierfunktionen finden Sie am Ende des Kapitels in

Abbildung 5.5: Sortierung ohne Schlüssel: sort ist sehr primitiv

Abbildung 5.6: Korrekte Sortierung, auch mit Indizes

192

der Kurzreferenz.

Page 193: 382726314 X Php5 In 14 Tagen (Ddt)

Arrays ausgeben

Arraydaten manipulieren

Richtig spannend wird es, wenn man die vielen Array-Funktionen, die PHP5 bie-tet, zum Einsatz bringen kann. Vielfach wird erst dadurch die Verarbeitung vonDaten richtig effizient. Alle Funktionen vorzustellen, führt hier zu weit, weil alleindie schiere Masse eine ausführliche Darstellung nur auf breitem Raum erlaubt.Die Referenz am Ende des Kapitels bietet eine ausreichende Hilfestellung bei derSuche nach der passenden Funktion.

5.4 Arrays ausgeben

Arrays kann man auf vielen Wegen ausgeben. Was konkret zum Einsatz kommt,hängt von der Situation und oft auch von den Daten selbst ab.

Arrays zählbar ausgeben: for

Die Anweisung for ist immer dann vorteilhaft, wenn man eine konkrete AnzahlElemente eines indizierten Arrays ausgeben möchte. Mit for tun sich assoziativeund verschachtelte Arrays recht schwer, denn diese Arrays kennen die nötigennumerischen Indizes nicht.

$lang[] = array('de', "Deutschland");$lang[] = array('en', "England");$lang[] = array('es', "Spanien");$lang[] = array('fr', "Frankreich");$lang[] = array('it', "Italien");for($i = 0; $i < count($lang); $i++)

Listing 5.14: arrayoutfor.php – Ausgabe der Elemente eines Arrays mit for

{ $larray = $lang[$i];

193

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 dieFunktion count benutzt, um den Endwert zu ermitteln. Als Startwert wird 0 einge-setzt. Die Formel $Laufwert < $Endwert ist typisch, da der Endwert durch die

Page 194: 382726314 X Php5 In 14 Tagen (Ddt)

Daten mit Arrays verarbeiten

Anzahl bestimmt wird, während der Laufwert bei 0 beginnt (null-basiertes Array).Deshalb wird als Endwert die Anzahl minus Eins benutzt.

Beliebige Arrays mit foreach durchlaufen

Das Problem der Indizes hat foreach nicht. Wie der Name bereits suggeriert, wirdhier jedes Element eines Arrays durchlaufen, unabhängig von der Anzahl und Artder Elemente.

$lang[] = array('de', "Deutschland");$lang[] = array('en', "England");$lang[] = array('es', "Spanien");$lang[] = array('fr', "Frankreich");$lang[] = array('it', "Italien");foreach ($lang as $larray)

Listing 5.15: arrayoutforeach.php – Flexibel, schnell, einfach: foreach zur Arrayausgabe

{ echo "Das Kürzel {$larray[0]} steht für {$larray[1]}<br>";}

Die Syntax der Anweisung ist recht einfach. Der erste Wert ist der Name des aus-zugebenden Arrays, danach folgt das Schlüsselwort as. Ab hier gibt es zwei Varian-ten. Die einfachste nennt einfach eine Variable ($larray im Beispiel), der derjeweils aktuelle Wert übergeben wird. Es ist dann Sache des Schleifencodes, diesesElement gegebenenfalls weiter zu zerlegen. Alternativ kann man auch zwei Vari-ablen angeben, die Schlüssel und Wert des Elements eines assoziativen Arrays auf-nehmen. Beachten Sie im folgenden Beispiel die andere Art der Definition unddie Angabe der Kopfparameter für foreach:

Abbildung 5.7: Ausgabe eines Arrays mit for

194

Listing 5.16: arrayoutforeachas.php – Ausgabe eines assoziativen Arrays

$lang['de'] = "Deutschland";$lang['en'] = "England";$lang['es'] = "Spanien";$lang['fr'] = "Frankreich";

Page 195: 382726314 X Php5 In 14 Tagen (Ddt)

Arrays ausgeben

$lang['it'] = "Italien";foreach ($lang as $iso => $name){

echo "Das Kürzel {$iso} steht für {$name}<br>";}

Die Ausgabe entspricht Abbildung 5.7. Der Code erscheint aber besser lesbar. Inden allermeisten Fällen wird man assoziative Arrays mit foreach ausgeben.

Einfache Arrays mit while, each, list ausgeben

Die Anweisungen while, each und list sind bereits seit den ersten PHP-Versionenzur Arraybehandlung im Einsatz. Arbeitet man mit einfachen Arrays, bieten sieeine erstaunliche Flexibilität.

$lang['de'] = "Deutschland";$lang['en'] = "England";$lang['es'] = "Spanien";$lang['fr'] = "Frankreich";$lang['it'] = "Italien";while (list($iso, $name) = each ($lang))

Listing 5.17: arrayoutwhile.php – Anstatt foreach kann man auch mit while arbeiten

{echo "Das Kürzel {$iso} steht für {$name}<br>";

}

Auf den ersten Blick erscheint dieser Ersatz für foreach unnötig kompliziert. Aller-dings ist die Anzahl der Elemente für list nicht begrenzt, was den Einsatz auchauf kompliziertere Konstrukte ausdehnen kann. Der Ablauf dieses Beispiels siehtfolgendermaßen aus:

1. Die while-Schleife startet mit dem ersten Aufruf von each

2. each ruft den ersten Wert ab und übergibt ihn an list

3. list zerlegt das Element und packt die Werte in die Variablen

195

4. Die Schleife wird durchlaufen

5. Der Vorgang beginnt wieder bei 2. Wenn each nichts mehr findet, gibt esFALSE zurück. Darauf reagiert auch list mit FALSE und der gesamte Ausdruckwird FALSE, was while zum Abbruch der Schleife veranlasst.

Die Ausgabe entspricht Abbildung 5.7.

Page 196: 382726314 X Php5 In 14 Tagen (Ddt)

Daten mit Arrays verarbeiten

Array mit array_walk durchlaufen

Eine der wichtigsten Arrayfunktionen ist array_walk. Die Funktion ruft für jedesArrayelement eine so genannte Rückruffunktion auf. Damit kann man praktischealles mit einem Array anstellen, was erforderlich ist. Das folgende Beispiel zeigt, wieein Array ausgegeben werden kann, wobei sich die Daten vielfältig behandeln lassen:

function CountryList($element){ list($iso, $name) = $element; echo "Der Code für $name ist: <b>$iso</b><br/>";}$lang[] = array('de', "Deutschland");$lang[] = array('en', "England");$lang[] = array('es', "Spanien");$lang[] = array('fr', "Frankreich");$lang[] = array('it', "Italien");array_walk($lang, 'CountryList');

Listing 5.18: arraywalk.php – Array mit benutzerdefinierter Funktion durchlaufen

Die Funktion array_walk sorgt dafür, dass die Elemente nacheinander an diebenutzerdefinierte Funktion CountryList übergeben werden. Die weitere Verar-beitung ist nur ein Beispiel. Es handelt sich hier um ein Array, dessen Elementewiederum Arrays sind. Da list zur Übertragung der Elemente in Variablenbenutzt wird, sind numerische Indizes erforderlich. Das folgende Array erfülltdiese Forderungen:

array('de', 'Deutschland')

Der interne Aufbau ist:

[0] => 'de'[1] => 'Deutschland'

Für derartige Ausgaben ist übrigens die Funktion print_r sehr hilfreich.

196

Fehlersuche mit print_r

Zur Fehlersuche braucht man oft einen schnellen Überblick über den aktuellenAufbau eines Arrays. Die Funktion print_r erledigt das. Für die reine Program-

Page 197: 382726314 X Php5 In 14 Tagen (Ddt)

Arrays ausgeben

mierung ist der Einsatz kaum zu gebrauchen, die Ausgabe ist eher technischerNatur und kann nicht formatiert werden.

Listing 5.19: print_r.php – Ausgabe eines Arrays zur Fehlersuche

<pre><?php$lang[] = array('de', "Deutschland");$lang[] = array('en', "England");$lang[] = array('es', "Spanien");$lang[] = array('fr', "Frankreich");$lang[] = array('it', "Italien");print_r($lang);?></pre>

Die Funktion verwendet außerdem einfache Zeilenumbrüche (\n) zur Formatie-rung. Um die Darstellung in HTML zu ermöglichen, müssen diese entweder in<br> umgewandelt werden oder die Ausgabe steht zwischen <pre>-Tags.

197

Abbildung 5.8: Ausgabe eines Arrays mit print_r zur Inhaltsanalyse

Page 198: 382726314 X Php5 In 14 Tagen (Ddt)

Daten mit Arrays verarbeiten

Mehr Analysemöglichkeiten für Skripte werden im Abschnitt 9.3 »Code röntgen:Die Reflection-API« ab Seite 395 vorgestellt. Außerdem sei auch auf die neueFunktion var_dump verwiesen, die sehr brauchbare Darstellungen komplexerArrays erzeugt.

5.5 Referenz der Arrayfunktionen

Funktion Bedeutung

array Erstellt ein neues Array aus konstanten Werten.

array_change_key_case Liefert das Array mit geänderter Groß- und Kleinschreibung der Schlüssel.

array_chunk Zerlegt ein Array in zwei Teile und gibt beide Teile als Arrays zurück.

array_combine Verbindet ein einfaches Array mit Schlüsseln und eines mit Werten zu einem assoziativen Array.

array_count_values Ermittelt die Häufung von Werten in einem Array.

array_diff Ermittelt die Unterschiede mehrerer Arrays anhand der Werte.

array_diff_assoc Ermittelt die Unterschiede mehrerer Arrays anhand der Indizes.

array_diff_uassocarray_udiff_assoc

Ermittelt die Unterschiede mehrerer Arrays anhand der Indizes durch Nutzung einer benutzerdefinierten Rück-ruffunktion.

array_fill Füllt das Array mit bestimmten konstanten Werten unter Angabe des Startindex.

array_filter Filtert ein Array unter Nutzung einer Rückruffunktion.

198

array_flip Vertauscht Schlüssel und Werte.

array_intersec Ermittelt die Schnittmenge mehrerer Arrays anhand der Indizes.

Tabelle 5.1: Arrayfunktionen in PHP5, alphabetisch sortiert

Page 199: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz der Arrayfunktionen

array_intersec_assoc Ermittelt die Schnittmenge mehrerer Arrays anhand der Schlüssel.

array_keys Gibt die Schlüssel eines assoziativen Arrays als einfaches Array zurück.

array_map Ruft für jedes Arrayelement eine benutzerdefinierte Rück-ruffunktion auf und akzeptiert für diese ein Array, das die Parameter definiert.

array_merge Verbindet zwei oder mehr Arrays miteinander.

array_merge_recursive Verbindet zwei oder mehr Arrays miteinander, wobei iden-tische Schlüsselwerte sich überschreiben, während numeri-sche Indizes ignoriert und die Werte angehängt werden.

array_multisort Sortiert mehrdimensionale Arrays.

array_pad Füllt ein Array mit Werten zu einer bestimmten Größe auf.

array_pop Liefert das oberste Element eines Arrays und setzt den Array-zeiger zurück.

array_push Fügt ein Element als neues oberstes Element einem Array hinzu.

array_rand Liefert zufällig ausgewählte Elemente eines Arrays.

array_reduce Verdichtet die Werte eines Arrays zu einem Wert mittels benutzerdefinierter Rückruffunktion.

array_reverse Vertauscht die Elemente eines Arrays, sodass das letzte Ele-ment nach der Operation das erste ist.

array_shift Liefert das erste Element eines Arrays und setzt den Array-zeiger zurück.

array_slice Extrahiert einen Teil aus einem Array und gibt diesen zurück.

Funktion Bedeutung

199

array_splice Entfernt einen Teil eines Arrays und ersetzt ihn durch ein anderes Array.

Tabelle 5.1: Arrayfunktionen in PHP5, alphabetisch sortiert (Forts.)

Page 200: 382726314 X Php5 In 14 Tagen (Ddt)

Daten mit Arrays verarbeiten

array_sum Die Summe aller numerischen Werte des Arrays.

array_udiff Ermittelt die Unterschiede mehrerer Arrays anhand der Werte durch Nutzung einer benutzerdefinierten Rück-ruffunktion.

array_unique Entfernt doppelte vorkommende Werte aus einem Array.

array_unshift Fügt ein Element als erstes Element eines Arrays ein.

array_values Liefert die Werte eines assoziativen Arrays als einfaches Array.

array_walk Ruft für jeden Wert eine Rückruffunktion auf.

arsort Sortiert assoziative Arrays absteigend.

asort Sortiert assoziative Arrays aufsteigend.

compact Erstellt ein Array aus skalaren Variablen.

count Ermittelt die Anzahl der Werte insgesamt.

current Gibt das aktuelle Element zurück, wenn mit Arrayzeigern gearbeitet wird.

each Gibt das aktuelle Element zurück, wenn mit Arrayzeigern gearbeitet wird und setzt den Zeiger eins weiter

end Setzt den Arrayzeiger auf das letzte Element im Array.

extract Exportiert die Werte eines Arrays als skalare Variablen.

in_array Ermittelt, ob ein Wert in einem Array enthalten ist.

key Gibt den aktuellen Schlüssel zurück.

krsort Sortiert assoziative Arrays absteigend nach den Schlüsseln.

ksort Sortiert assoziative Arrays aufsteigend nach den Schlüsseln.

Funktion Bedeutung

200

list Übernimmt alle Unterelemente des aktuellen Elements in Variablen.

Tabelle 5.1: Arrayfunktionen in PHP5, alphabetisch sortiert (Forts.)

Page 201: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz der Arrayfunktionen

natcasesort Sortiert »natürlich« aufsteigend ohne Rücksicht auf Groß- und Kleinschreibung.

natsort Sortiert »natürlich« aufsteigend.

next Gibt das aktuelle Element zurück, wenn mit Arrayzeigern gearbeitet wird und setzt den Zeiger eins weiter.

pos Gibt die aktuelle Position des Arrayzeigers zurück.

prev Gibt das aktuelle Element zurück, wenn mit Arrayzeigern gearbeitet wird und setzt den Zeiger eins zurück.

range Erstellt ein Array mit definierten numerischen Werten.

reset Setzt den Arrayzeiger auf die erste Position im Array.

rsort Sortiert einfache Arrays absteigend.

shuffle Sortiert die Werte eines Arrays mit Zufallsgenerator.

sizeof Ein anderer Name für count.

sort Sortiert einfache Arrays aufsteigend.

uasort Sortiert assoziative Arrays durch benutzerdefinierte Funk-tion.

uksort Sortiert assoziative Arrays nach dem Schlüssel durch benut-zerdefinierte Funktion.

usort Sortiert einfache Arrays durch benutzerdefinierte Funktion.

Funktion Bedeutung

Tabelle 5.1: Arrayfunktionen in PHP5, alphabetisch sortiert (Forts.)

201

Page 202: 382726314 X Php5 In 14 Tagen (Ddt)

Daten mit Arrays verarbeiten

5.6 Kontrollfragen

1. Worin unterscheiden sich normale von assoziativen Arrays?

2. Wie kann ein Element eines Array gezielt entfernt werden?

3. Schreiben Sie ein Skript, dass Arrays nutzt, um Namen von Mitarbeitern zu spei-chern.

4. Erweitern Sie das Skript, sodass zu jedem Mitarbeiter im selben Array zusätzlicheInformationen gespeichert werden. Tipp: Nutzen Sie verschachtelte assoziativeArrays dafür.

202

Page 203: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Pr g

ogrammierun

6

Page 204: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

PHP5 hat in seiner Entwicklung in Bezug auf die objektorientierte Programmierungeinen großen Sprung vorwärts gemacht. Auch wenn PHP keine im klassischenSinne objektorientierte Sprache ist, sind erstmals professionelle Softwareentwick-lungstechniken in gewissem Umfang möglich.

Bei den Programmiersprachen gibt es verschiedene Modelle, wie Syntax undSemantik aufgebaut sind. Alle Sprachen produzieren letztlich Code, der voneinem Prozessor verarbeitet werden muss. Prozessoren sind so genannte Von-Neu-mann-Maschinen, die Befehlsinstruktionen immer sequenziell verarbeiten, wobeisie vereinfacht einen Zyklus aus Speicheradresse aussenden, Speicher auslesen,Befehl empfangen, Befehl verarbeiten und Ergebnis ablegen durchlaufen. Die ers-ten Programmiersprachen folgten diesem Schema und waren so aufgebaut, dassprimär Befehl für Befehl abgearbeitet wird. Vom lateinischen Wort für Befehlen –imperare – kommt auch der Name dieser Sprachen: Imperative Programmierspra-chen. Typische Vertreter sind BASIC und C.

Im Laufe der Zeit wurde Code immer komplexer und aufwändiger. Es entstandenviele andere Versionen, von denen die objektorientierte weiteste Verbreitung fand.Objektorientierte Sprachen fassen Code zu Objekten zusammen, die aus Eigen-schaften und Methoden bestehen. Wie in der Natur werden Daten und Datenverarbeitende Funktionen zusammengefasst. Dies erleichtert die Wiederverwend-barkeit und Organisation von Code erheblich und erlaubt erst größere Applika-tionen. Typische Vertreter sind Java, C++, Delphi, C# und alle anderen .NET-Sprachen.

Neben der Unterscheidung der Art der Programmiersprache ist auch die Konstruk-tion des Sprachumsetzers wichtig. Denn der Prozessor versteht nur Maschinen-code, weshalb ein Übersetzungsvorgang stattfinden muss. Die ersten Sprachenwaren Interpretersprachen. Ein entsprechendes Programm übersetzte Befehl fürBefehl in Maschinencode. Weil dies in Schleifen uneffektiv ist – jeder Befehl wirdwieder und wieder übersetzt – entstanden so genannte Compiler. Diese übertragenin einem Lauf erst alle Codes in die Maschinensprache und das Betriebssystemlässt diesen Code dann ablaufen. Der Vorgang ist freilich aufwändiger und kom-plexer und war deshalb immer »richtigen« Programmiersprachen vorbehalten, diemit umfangreichen Entwicklungsumgebungen und vielen Hilfswerkzeugen ausge-

204

stattet waren. Die ersten BASIC-Versionen in Homecomputern der 80er Jahrewaren immer Interpretersprachen, weil nicht genug Speicher zum Ablegen derübersetzten Codes vorhanden war. C und C++ sind typische Compilersprachen.Java und die .NET-Sprachen sind auch Compilersprachen, nutzen aber einen spe-ziellen Zwischencode, der eine zweifache Übersetzung erfordert. Dies erlaubt einestärkere Kontrolle des Codes durch eine so genannte Laufzeitschicht und macht

Page 205: 382726314 X Php5 In 14 Tagen (Ddt)

Warum objektorientiert programmieren?

die Sprachen systemunabhängiger. Aus den Interpretersprachen entwickelten sichdie Skriptsprachen, die mit reduziertem Sprachumfang und einfachster Verarbei-tung kleine Programmieraufgaben erledigen und geringe Ansprüche an Entwick-lungsumgebung und das Know-how des Entwicklers stellen. Hier sind dietypischen Vertreter VBScript, JavaScript, Perl und auch die ersten Versionen vonPHP.

PHP ist in der Version 5, wie inzwischen auch Perl, ein Zwitter. Denn zum einenist es eine Skriptsprache, die per Interpreter verarbeitet wird. Sie ist prinzipiellimperativ strukturiert. Objektorientierte Sprachelemente sind vorhanden, derenVerwendung wird aber nicht erzwungen, wie das bei »echten« objektorientiertenSprachen der Fall ist. Die Verarbeitung basiert auf so genannten Token. Der Inter-preter zerlegt den Code also erst in einen eigenen Zwischencode – quasi ein demKompilieren ähnelnder Vorgang – und führt diesen Code dann interpretativ aus.Das ist effektiver als bei einem reinen Interpreter und vermeidet die Komplexitäteines echten Compilers. PHP selbst ist übrigens zu großen Teilen in C geschrie-ben, was vielleicht als ein Hinweis auf die Leistungsfähigkeit und Einsatzbreite derSprachen wichtig zu wissen ist.

6.1 Warum objektorientiert programmieren?

Die Behandlung von Objekten wurde in PHP5 grundlegend überarbeitet. Dieswar von der Community vehement gefordert worden, weil große Projekte von vie-len verfügbaren Bibliotheken leben und derartige Codesammlungen ohne objekt-orientierte Mittel kaum beherrschbar sind. Mit der zunehmenden Bedeutung vonPHP waren die Bibliothekenentwickler schnell an die Grenzen des bisherigenModells gestoßen.

Unabhängig davon profitieren auch kleine Projekte von objektorientierter Pro-grammierung, weil sie Code lesbarer und wartbarer macht. Nachteilig ist der etwashöhere Schreibaufwand, der Anfänger oft davon abhält, sich damit auseinander zusetzen.

205

Prinzipien und Paradigmen

Bei der objektorientierten Programmierung werden reale Sachverhalte in eineModellumgebung übertragen. Es ist also letztlich eine Technik zur Abstraktion.

Page 206: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

Bei imperativen Sprachen ist diese Abstraktion allein auf die Fähigkeit des Ent-wicklers abgestellt. Objektorientierte Sprachen bieten hier eine explizite Unterstüt-zung durch Sprachelemente. Der Formalismus, der dazu benutzt wird, ist nichtunbedingt trivial, hilft aber durch seine Strenge, besseren1 Code zu entwickeln.

Die folgende Darstellung ist so weit vereinfacht, wie es zum Verständnisvon PHP5 erforderlich ist. Objektorientierte Techniken, die PHP5 nichtunterstützt, werden nicht weiter betrachtet.

Das objektorientierte Modell nutzt folgende Begriffe:

� Objekte und Klassen

� Beziehungen und Eigenschaften von Objekten und Klassen

� Kommunikation mit und zwischen Objekten

Objekte sind Abstraktionen von im Problembereich existierenden Einheiten, diefür das System relevante Informationen zusammenfassen oder mit denen im Sys-tem zusammengearbeitet wird. Ein solches Objekt besitzt statische und dynami-sche Eigenschaften, die den Zustand des Objekts beschreiben, und Dienste, dieein Objekt ausführen kann bzw. anbietet, abgebildet durch Methoden.

Objekte mit gleichen Eigenschaften und gleichen Methoden werden zu Klassenzusammengefasst. Ein Objekt ist also Exemplar einer Klasse. Anders gedeutet isteine Klasse eine Vorlage, nach der gleichgeartete Objekte erzeugt werden. DieAbleitung eines Objekts aus einer Klasse wird in der Literatur häufig als Instanziie-ren bezeichnet. Auch wenn dies auf einer falschen Übersetzung des englischenBegriffs »instance« basiert, ist der Begriff inzwischen etabliert (ähnlich wie beim»Handy«).

Klassen können Hierarchien bilden. Ausgehend von einer Basisklasse »erben«untergeordnete Strukturen deren Eigenschaften und Methoden, verändern einigedavon oder ergänzen weitere. Aus einfachen Klassen entstehen so immer komple-xere Gebilde. Das hat Vorteile bei der Wiederverwendbarkeit und Wartbarkeit vonCode. Ändert man Details einer Basisklasse, ändern sich die abgeleiteten Klassenautomatisch mit, was Eingriffe in Code oft drastisch reduziert – entsprechend cle-

206

ver entworfene Modelle vorausgesetzt. Letzteres ist übrigens der Grund, warumAnfänger davon kaum profitieren. Ohne lange Erfahrung werden Modelle falschoder unbrauchbar entworfen und statt des erhofften Designvorteils entsteht Chaosund Konfusion.

1 Besser war im letzten Kapitel bereits als: »Schneller, Wartbarer, Lesbarer« definiert worden.

Page 207: 382726314 X Php5 In 14 Tagen (Ddt)

Syntax der Klassen

Wenn Klassen Abhängigkeiten haben, so haben deren Objekte diese auch.Objekte können einander enthalten und Teil eines anderen sein – ebenso wie esdie Klassenhierarchie vorschreibt. Da Objekte alles enthalten, was Code zumAblauf braucht – Daten in Form von Eigenschaften und verarbeitbare Anweisun-gen in Form von Methoden – führen sie gewissermaßen ein Eigenleben.

Ein reines objektorientiertes System kennt übriges ausschließlich Objekte. Das istein Paradigma der objektorientierten Sprachen – sie erzwingen eine derartige Pro-grammierweise. Das heißt in der Praxis, dass auch die einfachste Ausgabe (wie mitecho) das Anlegen wenigstens einer Klasse erfordert. Soweit geht PHP5 nicht –imperative und objektorientierte Elemente können parallel verwendet werden.

6.2 Syntax der Klassen

Bevor so richtig objektorientiert gearbeitet werden kann, muss wenigstens eineKlasse definiert werden. Dieser Abschnitt führt in die grundlegenden Prinzipiender objektorientierten Programmierung ein.

Eine Klasse definieren

Objektorientierte Programmierung beginnt immer mit der Definition einer Klasse.In PHP5 erledigt dies das in vielen Sprachen verwendete Schlüsselwort class.

Listing 6.1: ClassIntro1.php (erster Teil) – Definition einer Klasse

<?phpclass FontElement{ public $face = 'Verdana'; public $size = '3'; public $color = '#9999FF'; function Output($text) {

207

printf('<font face="%s" size="%s" color="%s">%s</font>', $this->face, $this->size, $this->color, $text); }}?>

Page 208: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

Definiert wurde hier eine Klasse mit dem Namen FontElement. Später sollen dar-aus Objekte erzeugt werden, die <font>-Tags erzeugen und ausgeben, vielfältigund flexibel formatiert, ohne dass HTML-Code geschrieben werden soll.

Sie enthält drei Eigenschaften: $face, $size und $color, die mit Standardwertenbelegt werden. Das Schlüsselwort public vor den Variablennamen deklariert sie alsöffentliche Mitglieder. Dazu und zu Alternativen später mehr. Neben den Eigen-schaften wurde auch eine Methode definiert: Output. Dies ist die einzige Aktion,die ausgeführt werden kann.

Das Programm ist soweit zwar fehlerfrei lauffähig, tut jedoch gar nichts. Die Klasseerlaubt die Erzeugung von Objekten, dies ist aber hier noch nicht passiert.

Ein Objekt erzeugen und benutzen

Objekte können Aktionen ausführen. Dazu muss man erst über ein solches verfü-gen. In PHP5 erledigt dies das Schlüsselwort new:

Listing 6.2: ClassIntro1.php (zweiter Teil): Definition einer Klasse

$myfont = new FontElement();echo 'Formatierte Ausgabe: ';$myfont->Output('Mustertext');

Das neue Objekt wird in einer Variablen ($myfont) gespeichert. new erzeugt es unddazu wird ihm der Name der Klasse mitgeteilt (FontElement). Das neue Objektkann nun sofort benutzt werden. Auf Eigenschaften und Methoden wird über dieVerweissyntax -> zugegriffen.

Freilich ist hier noch kein Vorteil gegenüber einem einfachen Funktionsaufruf zuerkennen, wie er am Tag 7 behandelt wurde. Tatsächlich bietet das objektorien-tierte Modell viel mehr.

Eine Klasse erweitern

208

Zuerst soll die Klasse erweitert werden. Dazu wird sie aber nicht einfach umge-schrieben, denn möglicherweise ist sie in dieser Form bereits woanders im Projektim Einsatz. Besser ist es, von der Klasse zu erben und darauf aufbauend eine erwei-terte Version zu erzeugen. Das erfolgt in PHP5 mit dem Schlüsselwort extends.

Page 209: 382726314 X Php5 In 14 Tagen (Ddt)

Syntax der Klassen

Bei der Vorüberlegung, welche Struktur entstehen soll, ist leicht zu erkennen, dassdas <font>-Tag keine brauchbare Basis darstellt. Besser wäre es, erstmal über einallgemeines »HTML-Element« zu verfügen und daraus speziellere Elementeabzuleiten. Ein solches Basiselement könnte nun folgendermaßen aussehen:

Listing 6.3: ClassIntro2.php – Eine flexible Basisklasse für HTML-Elemente

<?phpclass Element{ public $id; public $class; public $style; public $name; public $selfClosed; function GetAttribute($name, $attribute) { if (empty($attribute)) { return ''; } else { return sprintf('%s="%s"', $name, $attribute); } } function Output($text = '') { printf('<%s %s %s %s', $this->name, $this->GetAttribute('id', $this->id), $this->GetAttribute('class', $this->class), $this->GetAttribute('style', $this->style)); if ($this->selfClosed) { print('/>'); }

209

else { printf('>%s</%s>', $text, $this->name); } }}

Page 210: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

$element = new Element();$element->name = 'font';$element->style = 'font-size:22pt; font-weight:bold; color:red;';$element->selfClosed = FALSE;$element->Output('Mustertext');?>

Die Basisklasse ist offensichtlich in der Lage, alle Arten von Elementen zu erzeu-gen. Auf dieser Grundlage ist es nun leicht, spezialisiertere Klassen zu entwerfen,die den Aufwand zur Erzeugung neuer Elemente reduzieren.

Listing 6.4: ClassIntro3.php – FontElement profitiert von den Fähigkeiten von Element

class FontElement extends Element{ function __construct() { $this->name = 'font'; $this->selfClosed = FALSE; }}$element = new FontElement();$element->style = 'font-size:22pt; font-weight:bold; color:red;';$element->Output('Mustertext');

Der einzige Unterschied zur Klasse Element ist die Einführung eines so genanntenKonstruktors, der bestimmte Eigenschaften fixiert.

Konstruktoren und Destruktoren

Immer wenn ein neues Objekt entsteht, also in dem Augenblick, wenn new ausge-führt wird, beginnt das Leben des Objekts in einem undefinierten Zustand. Siehaben bereits gesehen, wie das Objekt durch das Setzen der Eigenschaften nutzbarwird. Oft wird jedoch der undefinierte Zustand nie benötigt, sondern das Objektsoll von vornherein bestimmte Grundeigenschaften enthalten. Der Vorgang desErzeugens und Definierens kann daher zusammengefasst werden. Das passiert

210

durch die Einführung einer speziellen Methode, die im Augenblick der Erzeu-gung automatisch aufgerufen wird: des Konstruktors.

Ein Konstruktor entsteht in PHP5 durch den reservierten Namen __construct.(Achtung! Das sind zwei Unterstriche vor dem Wort.) Im letzten Beispiel wurde

Page 211: 382726314 X Php5 In 14 Tagen (Ddt)

Syntax der Klassen

dies benutzt, um dem Objekt vom Typ FontElement gleich die richtigen Eigen-schaften mitzugeben.

Ein Objekt existiert, wie alle anderen Variablen auch, bis zum Ende des Skriptsoder bis es gezielt zerstört, also auf NULL gesetzt oder mit unset vernichtet wird.Normalerweise ergeben sich daraus keine Konsequenzen, PHP kümmert sich umdas Aufräumen des belegten Speichers. Es gibt jedoch Anwendungsfälle, in denenexterne Programme an der Kommunikation beteiligt sind, Datenbanken oderDateien beispielsweise. Nun wäre es fatal, wenn ein Objekt eine Verbindung zurDatenbank herstellt und dann zerstört wird, während die Verbindung offen bleibt.Es entstehen verwaiste Verbindungen – Zombies. Verfügt eine Datenbank nurüber primitive Kontrolltechniken oder eine begrenzte Anzahl von erlaubten Ver-bindungen, führt dies früher oder später zu Fehlern, die zudem sporadisch undschwer nachvollziehbar auftreten. Um das Verhalten am Ende der Existenz einesObjekts zu kontrollieren, werden Destruktoren eingesetzt. Sie werden unmittelbarvor der endgültigen Vernichtung aufgerufen.

Ein Destruktor entsteht in PHP5 durch den reservierten Namen __destruct.(Noch einmal – das sind zwei Unterstriche vor dem Wort.)

Verhalten der Konstruktoren und Destruktoren bei der Vererbung

Bei der Vererbung wird die Sache etwas komplizierter. Denn jede Klasse in derHierarchie kann einen eigenen Konstruktor2 haben. Wenn nun ein Objekt erzeugtwird, könnten sich die Effekte der Konstruktoren überlagern. Deshalb führt PHP5nur den letzten Konstruktor aus. Hätte im letzten Beispiel auch die Klasse Elementeinen Konstruktor, so wäre dieser beim Aufruf von FontElement nicht ausgeführtworden. Es bleibt dem Entwickler überlassen, den Aufruf des Konstruktors derElternklasse explizit zu erzwingen:

function __construct(){ parent::__construct();}

Das reservierte Wort parent verweist auf die Klasse, von der mit extends geerbt

211

wurde. Der Konstruktor wird über seinen Namen aufgerufen. Hinter parent darfnur mit dem statischen Verweisoperator :: gearbeitet werden, weil zum Zeitpunktdes Konstruktoraufrufs das Objekt noch nicht existiert und deshalb die Definition

2 Für Destruktoren gilt das ebenso, auch wenn diese nicht immer explizit erwähnt werden.

Page 212: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

direkt benutzt wird. Mehr zu statischen Mitgliedern und dem neuen Verweisope-rator folgt im nächsten Abschnitt.

Konstruktoren können Parameter wie jede andere Methode besitzen. Die Angabeerfolgt zusammen mit dem new-Operator hinter dem Namen der Klasse:

$object = new SuperFontElement('color:red');

Die Verwendung von Parametern ist meist sinnvoll, um dem künftigen Objektzusätzlich individuelle Eigenschaften zu verpassen.

Statische Mitglieder und Konstanten

Statische Mitglieder von Klassen existieren nicht im Kontext des Objekts, sondernder Klasse. Damit teilen sich alle Objekte einer Klasse diese Mitglieder, ganzgleich ob es sich dabei um Eigenschaften oder Methoden handelt.

Es sind viele Einsatzfälle denkbar:

� Nutzen Sie statische Mitglieder für Aufgaben, die keinen Bezug zu den spezifi-schen Daten eines Objekts haben, beispielsweise Umrechnungen.

� Statische Mitglieder erlauben die Implementierung von Verweiszählern, wiebeispielsweise Eigenschaften, die allen Objekten gleich sind.

� Statische Mitglieder erlauben den direkten Aufruf ohne vorherige Instanziie-rung. Dies ist sinnvoll, wenn man ohnehin nur ein Objekt benötigt.

Ein Zähler, der die Anzahl der bereits erzeugten Objekte ermittelt, kann beispiels-weise folgendermaßen implementiert werden:

Listing 6.5: ClassStatic.php – Die Anzahl von Objektinstanzen ermitteln

<?phpclass ElementCounter{ static $counter; public function __construct() {

212

ElementCounter::$counter++; } public function GetObjectNumber() { return ElementCounter::$counter;

Page 213: 382726314 X Php5 In 14 Tagen (Ddt)

Syntax der Klassen

}}$obj1 = new ElementCounter();$obj2 = new ElementCounter();$obj3 = new ElementCounter();printf('Es wurden %s Objekte erzeugt.', $obj3->GetObjectNumber());?>

Der Trick besteht hier im Schlüsselwort static, das die Variable $counter in denKlassenkontext überführt. Diese Variable existiert für alle Objekte nur einmal.Wird nun ein neues Objekt erzeugt, wird der Wert im Konstruktor um Einserhöht. Beachten Sie hier, dass der Verweis mit $this nicht gelingen kann, weil eskein Objekt- sondern ein Klassenverweis ist, der benötigt wird. Für letzteren wirdder Klassenname benutzt, gefolgt vom statischen Verweisoperator :: und dem voll-ständigen Namen der Variablen, inklusive dem $-Zeichen. Das gilt sowohl fürZugriffe innerhalb der Klasse, wie im Beispiel gezeigt, als auch für externe. Dabeigelingen externe Zugriffe nur, wenn kein oder der Modifizierer public verwendetwird.

Ein sehr typisches Designelement in der objektorientierten Programmierung sindso genannte Singleton-Klassen. Dies sind Klassen, die nur ein einziges Objekterzeugen dürfen. Oft sind auch in objektorientierten Ansätzen nämlich nicht Dut-zende Objekte im Spiel. Ein imperativer Ansatz würde jedoch das Konzept derWiederverwendbarkeit stören. Der Ausweg sind Singleton-Klassen. Um dies zusichern, benötigt man zwei Techniken. Zum einem muss der Konstruktor »ver-steckt« werden. Dies erfolgt mit dem Schlüsselwort private. So wird verhindert,dass der verwendende Code fälschlicherweise mit new weitere Objekte erzeugt.Stattdessen wird eine statische Methode angeboten, die die Instanziierung über-nimmt und dabei prüft, ob bereits ein Objekt existiert. Der erneute Aufruf von newreferenziert dann immer wieder dasselbe Objekt.

Listing 6.6: ClassSingleton.php – Volle Kontrolle über die Objekterzeugung

<?phpclass Singleton {

213

static private $instance = false; private $text = 'Keine Nachricht im Objekt'; private function __construct() {} static function instance() { if(!Singleton::$instance)

Page 214: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

{ Singleton::$instance = new Singleton(); } return Singleton::$instance; } function setText($text) { $this->text = $text; } function getText() { return $this->text; }}class Hello{ function __construct() { $single = Singleton::instance(); $single->setText('Hallo Welt!'); }}class Goodbye { function __construct() { $single = Singleton::instance(); $single->setText('Und tschüß...'); }}$single = Singleton::instance();echo ( $single->getText().'<br />' );$hello = new Hello();echo ( $single->getText().'<br />' );$hello = new Goodbye();echo ( $single->getText().'<br />' );?>

214

Das Beispiel nutzt in jeder Klasse, die Singleton verwendet, immer wieder dasselbeObjekt, weil eine erneute Erzeugung im Rahmen eines einfachen Methodenauf-rufs völlig sinnlos wäre – man hätte am Ende nur eine Anzahl verwaister Objekteim Speicher. In der Praxis wird diese Technik benutzt, um Ressourcen schonend

Page 215: 382726314 X Php5 In 14 Tagen (Ddt)

Syntax der Klassen

zu programmieren. Verbindungen zu Ressourcen, die nur einmalig vorhandensind, wie beispielsweise Dateien, werden über Singleton-Klassen verwaltet.

Konstanten in Objekten

Konstanten können auch in Objekten verwendet werden. Vor PHP5 waren Kon-stanten immer global, was den Einsatz etwas problematisch machte, weil leichtNamenskonflikte auftraten. Da sich Konstanten von Objekt zu Objekt nichtändern, verhalten sie sich wie statische Mitglieder und werden auch genau wiediese verwendet. Der einzige Unterschied besteht darin, dass sich der Inhalt zurLaufzeit nicht verändern lässt.

Im Gegensatz zu den globalen Konstanten, die mit define erzeugt werden, erfolgtdie Vereinbarung in Klassen mit dem Schlüsselwort const:

const WIDTH = 800;

Da Konstanten zwangsläufig in allen Instanzen identisch sind, verhalten sie sichwie statische Mitglieder. Der einzige Unterschied besteht darin, dass sie nichtgeschrieben werden können – außer bei der Definition. Diese Definition erfolgtauf Ebene der Klasse, wo auch alle anderen Eigenschaften, Methoden und Variab-len definiert werden.

Zugriffskontrolle

Meins, deins, für alle: private, public, protected

Generell sind in PHP5 alle Variablen global. Definitionen innerhalb einer Funk-tion sind dort lokal. Zum Übergang benutzt man das Schlüsselwort global. In derobjektorientierten Programmierung ist das bei weitem nicht genug. PHP5 bietethier gleich drei neue Schlüsselwörter für den Variablenschutz:

� public Der Standardwert hat nun sein eigenes Schlüsselwort. Derart deklarierte Vari-

215

ablen oder Methoden sind im gesamten Skript sichtbar. Aus Gründen derAbwärtskompatibilität kann die Angabe entfallen.

� private Die so deklarierte Variable oder Methode ist nur innerhalb der Klasse sichtbar,abgeleitete Klassen oder Aufrufe von Objekten können nicht zugreifen.

Page 216: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

� protected Dieses Schlüsselwort macht eine Variable oder Methode in den eigenen undabgeleiteten Klassen sichtbar, aber nicht darüber hinaus (mehr als private undweniger als public als Kurzformel).

Wenn Sie sich überlegen, wie die Deklaration konkret erfolgen soll, versuchen Siezuerst so restriktiv wie möglich zu deklarieren, also alles private zu machen. Dannändern Sie gezielt die Werte, die unbedingt woanders im Zugriff sein müssen.

Generell ist es jedoch ein gute Idee, den Zugriffsweg zu kontrollieren. So könnteman eine Klasse mit einer öffentlichen Eigenschaft immer folgendermaßen erstel-len:

class Test{ public $sTest;}

Besser ist es, den Weg der Daten in die Variablen und hinaus zu kontrollieren:

class Test{ private $sTest; public GetTestString() { return $this->sTest; } public SetTestString($st) { $this->sTest = $st; }}

Es ist nun leicht, in den Zugriffsmethoden Prüfungen und ein ordentliches Feh-lermanagement einzubauen, was ein öffentlicher Zugriff auf eine Mitgliedsvari-able nicht erlaubt.

Finale Klassen und Methoden: final

216

Dass sich Klassen leicht vererben lassen, wurde anhand des Schlüsselworts extendsbereits gezeigt. Manchmal soll dies aber nicht so sein, entweder für eine Klasse alssolche oder auch nur für einzelne Methoden. Denn manche Methoden sind fürdie Funktion der Objekte von elementarer Bedeutung. Gelingt der Schutz mit

Page 217: 382726314 X Php5 In 14 Tagen (Ddt)

Syntax der Klassen

private nicht, weil der Zugriff von außen anderweitig benötigt wird, muss dasÜberschreiben verhindert werden. Dies erledigt das Schlüsselwort final. Voneiner so gekennzeichneten Klasse kann nicht geerbt werden, bei »finalen« Metho-den ist das Überschreiben verboten.

Aufforderung zur Implementierung: abstract

Werden Klassen oder Methoden als abstract gekennzeichnet, wird der Benutzerexplizit dazu aufgefordert, hier eigenen Code zu schreiben. Praktisch ist dies dasGegenteil zu final – statt dem ausdrücklichen Verbot folgt nun das ausdrücklicheGebot. Der Schreiber der Klassen gibt damit Struktur, Namen und Aufbau vor,nicht jedoch die konkrete Implementierung, weil dies möglicherweise von derAnwendung abhängt.

Eine Ableitung von Objekten von abstrakten Klassen ist nicht möglich. Es mussdeshalb immer eine Implementierung erfolgen. Das gilt auch für abstrakte Metho-den. Es ist jedoch möglich, eine Klasse als abstrakt zu definieren und einige derMethoden bereits voll auszuformulieren. Das folgende Beispiel zeigt dies. Wäh-rend eine Instanziierung der Klasse Senior nicht gelingt, ist diese mit Junior mög-lich. Auch der Aufruf der in der abstrakten Klasse Senior (als nicht abstrakt)definierten Methode GetText gelingt wie erwartet:

Listing 6.7: ClassAbstract.php – Abstrakte Klassen können nur über extends benutzt wer-den

<?phpabstract class Senior{ protected $text = 'Hallo Senior!'; function GetText() { return $this->text; }}class Junior extends Senior

217

{ protected $text = 'Hallo Junior!';}$obj = new Junior();echo $obj->GetText();?>

Page 218: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

Um den Benutzer der Klasse zu zwingen, auch die Methode GetText selbst zuschreiben, wird diese ebenfalls als abstract definiert und der Inhalt entfernt:

abstract class Senior{ protected $text = 'Hallo Senior!'; abstract function GetText();}

PHP5 reagiert mit zwei typischen Fehlermeldungen, wenn die Zuordnung hiernicht stimmt. Der erste Fall tritt auf, wenn eine abstrakte Methode Code enthält:

Der zweite Fall tritt auf, wenn vergessen wurde, eine abstrakte Methode in einerabgeleiteten Klasse zu implementieren:

An dieser Stelle sei bereits auf Schnittstellen – Interfaces genannt – verwiesen dieeine ähnliche Rolle wie abstrakte Klassen übernehmen. Mehr dazu finden Sie imAbschnitt 6.3 »Schnittstellen zur Außenwelt«.

Klonen erlaubt: clone und die Methode __clone()

Klonen ist ein typischer Vorgang in der objektorientierten Welt. Bislang wurde inPHP immer eine vollständige Kopie eines Objekts übergeben, wenn die Zuwei-sung an eine andere Variable erfolgte. Damit sind natürlich auch Verweise, die dasursprüngliche Objekt hatte, Teil der Kopie. Das kann gewollt sein, meist ist es

Abbildung 6.1: Es wurde versucht, eine abstrakte Methode zu definieren

Abbildung 6.2: Eine abstrakte Methode wurde nicht implementiert

218

jedoch so, dass man eher mit einem einzigen Verweis, beispielsweise auf eineDatenbank oder Datei, arbeiten will, unabhängig von der Anzahl der Objekte. Diefolgende Version erlaubt mehr Kontrolle über den Kopiervorgang:

$clone = clone $object;

Page 219: 382726314 X Php5 In 14 Tagen (Ddt)

Syntax der Klassen

Dies entspricht dem Aufruf der Methode __clone() des Quellobjekts. Sie könnennun diese Methode (mit exakt diesem Namen) anlegen und den Kopiervorgangkontrollieren. Oder Sie können den internen Mechanismus nutzen, der alleEigenschaften des Quellobjekts kopiert.

Betrachten Sie zuerst folgenden Code:

Listing 6.8: ClassClone.php – Klassen und direkte Verwendung von Objektkopien

class CloneClass { var $id; } $o1 = new CloneClass(); $o1->id = "1"; $o2 = $o1; $o2->id = "2"; echo 'ID: ' . $o1->id;

Die Ausgabe zeigt, dass die beiden Objekte $o1 und $o2 auf dasselbe Basisobjektverweisen:

ID: 2

Anders sieht es aus, wenn die Zuweisung den clone-Befehl nutzt:

Listing 6.9: CloneClass2.php – Der Klon enthält eine Kopie aller Eigenschaften

class CloneClass { var $id; } $o1 = new CloneClass(); $o1->id = "1"; $o2 = clone $o1; $o2->id = "2"; echo 'ID: ' . $o1->id;

Dann wird ein unabhängiges Objekt instanziiert und die Zuweisung zu $o1 wirktsich in $o2 nicht mehr aus:

219

ID: 1

Das Verhalten der Klonierung kann mit __clone() beeinflusst werden. DieseMethode existiert intern und kopiert alle Eigenschaften. Man kann sie jedochüberschreiben und dann entscheiden, welche Eigenschaften zu kopieren sind.

Page 220: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

Listing 6.10: CloneClass3.php – Beeinflussung des Klonvorgangs über __clone()

class CloneClass { var $x; var $y; function __clone() { $this->y = 300; } } $o1 = new CloneClass(); $o1->x = "100"; $o1->y = "100"; $o2 = clone $o1; echo "XY: {$o2->x} x {$o2->y}";

Hier wird die Methode __clone() benutzt, um die Übertragung der Eigenschaftenzu beeinflussen.

Der in frühen Betaversionen kolportierte Zugriff über $that auf das zuklonende Objekt war seit der RC1 nicht mehr verfügbar. Abgesehen vonder mangelnden Flexibilität erscheint der Verlust kruder Syntax ver-schmerzbar. Insgesamt bleibt der Eindruck, dass ein wichtiges OOP-Feature miserabel implementiert wurde.

6.3 Schnittstellen zur Außenwelt

Nicht jeder schreibt PHP-Skripte nur für sich selbst. Entwickler von Bibliotheken– ganz gleich ob für die Öffentlichkeit oder das eigene Team – benötigen ganz spe-zielle Techniken, um lesbaren, klar strukturierten und wieder verwendbaren Codezu schreiben. Schnittstellen (Interfaces) sind eine sehr gute Methode dafür. Sie

220

sind neu in PHP5 eingeführt.

Page 221: 382726314 X Php5 In 14 Tagen (Ddt)

Schnittstellen zur Außenwelt

Schnittstellen (Interfaces)

Schnittstellen bieten eine gute Möglichkeit, die Verwendung eigener Klassen zukontrollieren. Ähnlich wie abstrakte Klassen bieten Interfaces nur eine Definitionder Struktur an, sie enthalten jedoch keinen Code. Dem potenziellen Nutzer einerBibliothek bieten sie die Chance, einen »gefilterten« Blick auf die Klassen zu wer-fen, die die Bibliothek anbietet. Es kann nämlich aus technischen Gründen erfor-derlich sein, bestimmte Teile der Bibliothek als öffentlich zu kennzeichnen.Wenn nun in einem anderen Teil des Projekts Ableitungen von Klassen erfolgen,so sollte sich die Verwendung an bestimmte Regeln halten. Aus der Tatsache, dassMethoden oder Eigenschaften öffentlich sind, folgt nicht zwingend, dass diese zurImplementierung geeignet sind. Die Information darüber, was wirklich »öffent-lich« im Sinne der freien Verwendung ist, definiert eine Schnittstelle. Der Nameist Programm – es handelt sich um eine Vereinbarungsschnittstelle zwischen demursprünglichen Entwickler der Klasse und dem Entwickler, der die Klasse späterverwendet. Zielgruppe sind also vor allem Programmierer, die Bibliotheken schrei-ben, welche in Projekten angepasst und modifiziert werden sollen.

Wenn Sie kleinere Projekte erstellen und keinen Code anderen Ent-wicklern zur Verfügung stellen, ist die Verwendung von Schnittstellenmeist nicht angebracht. Die Tatsache, dass PHP5 diese Funktionen bie-tet, soll nicht dazu verleiten, sie unbedingt nutzen zu müssen.

Schlüsselwörter

Die Definition einer Schnittstelle wird mit dem Schlüsselwort interface eingelei-tet. Es darf keine Eigenschaften enthalten und von allen Methoden darf nur der»Kopf« geschrieben werden; direkt abgeschlossen mit einem Semikolon, statt dergeschweiften Klammern.

Bei der Implementierung wird wie bei der Klassenvererbung vorgegangen, anstattextends wird jedoch das Schlüsselwort implements verwendet.

221

Herkunft und Mitgliedschaft

Beim Umgang mit komplexen Klassenhierarchien ist es oft notwendig, bei einemObjekt zu ermitteln, von welcher Klasse es abstammt. Im Beispiel der HTML-Ele-ment-Klassen lässt sich leicht eine solche Hierarchie entwickeln:

Page 222: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

� Element

� StylableElement

� ContainerElement

� FontElement

Die oberste Ebene definiert nur Elemente als solche, beispielsweise mit denEigenschaften Tag-Name und ID. Danach folgen weitere, speziellere Eigenschaf-ten bis hin zu einem konkreten HTML-Element (FontElement). Bei der Untersu-chung, ob ein Objekt nun ein Element aus der Hierarchie ist, wäre eineErkennung sehr aufwändig, weil es Dutzende HTML-Elemente gibt, die in ver-schiedenen Stufen der Hierarchie stehen. Gleichzeitig stellt sich die Frage, was andiesem Element öffentlich verwendet werden kann. Die Vereinbarung darübertrifft man mit Hilfe einer Schnittstelle, beispielsweise IElement genannt.

Listing 6.11: ClassInterface.php – Schnittstellen definieren und implementieren

<?phpinterface IElement{ public function GetTagName();}abstract class Element implements IElement{ protected $name; public function GetTagName() { return $this->name; }}final class FontElement extends Element{ public function __construct() { $this->name = 'font'; }

222

}$font = new FontElement();if ($font instanceof IElement){ echo strtoupper($font->GetTagName());

Page 223: 382726314 X Php5 In 14 Tagen (Ddt)

Schnittstellen zur Außenwelt

echo " ist ein Element";}?>

Das Beispiel definiert zuerst eine einfache Schnittstelle, die die Grundstrukturaller Elemente enthält. Anschließend wird die erste Ebene der Implementierungvorgenommen. Diese Klasse ist als abstrakt gekennzeichnet, weil ein »Element«ohne weitere Definition seiner Eigenschaften unsinnig ist. Der Benutzer wird alsogezwungen, seine eigenen Elemente darauf aufbauend zu implementieren. Dasletzte Element in der Kette (die Zwischenstufen StylableElement und Container-Element wurden hier zur Vereinfachung weggelassen) ist dann als final markiert,weil es keine Varianten von <font> gibt. Der Konstruktor legt die typischste Eigen-schaft des Elements fest, den Namen. Anschließend prüft das Skript, ob das Ele-ment von einer bestimmten Schnittstelle abstammt:

if ($font instanceof IElement)

Ohne die Schnittstelle müsste man hier auf alle Element-Klassen prüfen, wassicher deutlich aufwändiger ist. Außerdem kann man der Schnittstelle per Reflek-tion (siehe dazu Abschnitt 9.3 »Code röntgen: Die Reflection-API«. Die zulässigenöffentlichen Methoden entlocken, was die Gestaltung der abstrakten Basisklassenvereinfacht.

Der Operator instanceof funktioniert freilich auch mit Klassen, er zeigt lediglichan, dass ein Objekt von einer bestimmten Klasse oder Schnittstelle abstammt, ohneRücksicht auf die Vererbungskette. Alternativ kann die Funktion is_a verwendetwerden, die jedoch nicht so gut lesbaren Code ergibt:

if (is_a($font, 'IElement'))

Informationen über Klassen und Objekte

Typ-Informationen

PHP5 ist, wie alle Vorgängerversionen, seiner Tradition als Skriptsprache treugeblieben. Dazu gehört neben anderen Merkmalen auch der Verzicht auf typi-

223

sierte Variablen. PHP5 legt intern selbst fest, welchen Datentyp eine Variableannimmt oder eine Funktion zurückgibt. Es gibt zwar die bereits behandeltenUmwandlungsfunktionen, letztlich besteht aber kein Typzwang, wie er in »richti-gen« Programmiersprachen üblich ist.

Page 224: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

Bei kleineren Projekten ist der fehlende Zwang zur Deklaration (und Einhaltung)des Typs meist zu tolerieren, weil sich Fehler mit überschaubarem Aufwand fin-den lassen. Werden jedoch größere Bibliotheken entworfen, ist die Fehlersucheungleich schwerer und oft erst mit Hilfe spezieller Testapplikationen zu bewerk-stelligen. An dieser Stelle fehlt dann ein Typzwang, der zumindest elementareZuweisungs- und Konvertierfehler bereits auf der Parserebene verhindert.

Da die Programmierung in objektorientierter Art und Weise mit den hier vorge-stellten neuen Möglichkeiten von PHP5 zunimmt, wird sich der fehlende Typ-zwang umso drastischer auf die Qualität des Codes auswirken – im negativenSinne. Um das ein wenig zu umgehen, wird die Typisierung quasi über die Hinter-tür und nur für selbst definierte Klassen eingeführt. Erstaunlich inkonsequent,wenn man bedenkt, dass nun dafür eine Syntax zur Verfügung steht, gleichwohlaber nicht für eingebaute Typen verwendet werden darf.

Einzig für eigene als Klasse definierte Typen besteht die Möglichkeit, bei derAngabe von Parametern einen »Typ-Hinweis« mitzugeben. Dies unterstützt dieNutzbarkeit von Objekten, die aus einer Hierarchie entstammen.

Eigene Fehlerklassen erstellen

Bereits im letzten Kapitel war die neue Fehlerbehandlung ein Thema. Richtig leis-tungsfähig wird man damit aber erst, wenn man eigene Fehlerklassen erstellenkann. Dazu sind freilich Kenntnisse der objektorientierten Programmierung erfor-derlich, weshalb dieser Teil erst jetzt folgt.

Eigene Fehlerklassen basieren immer auf einer Vererbung der internen Fehler-klasse Exception. Dies verschafft der neuen Klasse einen Satz an Basismethoden,die sehr hilfreich sind.

Zuerst ist ein Blick auf die eingebauten Methoden hilfreich, wie ihn das folgendeBeispiel ermöglicht:

Listing 6.12: TryCatchBasis.php – Die Leistungen der Exception-Klasse testen

try

224

{ throw new Exception('Das ist ein provozierter Fehler', 12345); }catch (Exception $e) {

Page 225: 382726314 X Php5 In 14 Tagen (Ddt)

Schnittstellen zur Außenwelt

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 Informationen vorliegen, erzeugt durch diefolgenden Methoden:

� getMessage

Die eigentliche Fehlermeldung, am Ort der Instanziierung übergeben.

� getCode

Der Fehlercode, frei wählbar und hilfreich, um weiterführende Informationenabzurufen.

� getFile

Die Datei, in der der Fehler auftrat. Das ist das aktuelle Skript oder eine mitinclude oder require eingebundene Datei.

� getLine

Die Zeile, in der throw ausgelöst wurde. Dies ist hilfreich bei der Fehlersuche,wenn man clever genug ist, mögliche Tests und throw eng beieinander zuhalten.

Listing 6.13: TryCatchExceptionClass.php – Eigene Exception-Implementierung

<?phpclass DivideByZeroError extends Exception { const PREFIX = 'Fehler:';

Abbildung 6.3: Ausgaben der Methoden der Basisklasse Excep-tion

225

const CODE = 65535; function __construct($message) { parent::__construct(self::PREFIX.$message, self::CODE);

Page 226: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

} } function divide($by) { if ($by == 0) { throw new DivideByZeroError ('&nbsp;Division durch Null'); } return 1 / $by; } $test = 0;try { echo (divide($test)); }catch (DivideByZeroError $ex) { echo $ex->getCode() . ' ' . $ex->getMessage();}?>

Um eigene Fehlerklassen zu schreiben, müssen Sie lediglich von der BasisklasseException ableiten:

class DivideByZeroError extends Exception

Es steht Ihnen dann frei, das Verhalten der Basisklasse zu modifizieren. Im Bei-spiel wird der Konstruktor überschrieben und der Aufruf des Konstruktors derBasisklasse mit eigenen Fehlertexten ergänzt:

function __construct($message) { parent::__construct(self::PREFIX.$message, self::CODE); }

Beliebt ist auch das Überschreiben von __toString. Diese Methode liefert eineZeichenkettenentsprechung der Klasse und verkürzt die Ausgabe der Meldung.Beachten Sie außerdem die Syntax des Aufrufs des Konstruktors der Basisklasse mitparent::.

226

Die Anwendung ist einfach. Haben Sie im Code einen Fehler festgestellt, wirdeine Instanz der Fehlerklasse mit new erzeugt und mittels throw ausgelöst:

throw new DivideByZeroError ('&nbsp;Division durch Null');

Page 227: 382726314 X Php5 In 14 Tagen (Ddt)

Schnittstellen zur Außenwelt

Es ist jetzt Aufgabe des Hauptprogramms, den Block von Code mittels try zuumschließen, wo der Fehler auftreten könnte:

try { echo (divide($test)); }

Dann ist für jeden denkbaren Fehler (im Beispiel kann nur ein Fehler ausgelöstwerden) der passende catch-Zweig anzugeben:

catch (DivideByZeroError $ex)

Innerhalb des catch-Blocks kann dann auf den Fehler reagiert werden, beispiels-weise durch Ausgabe einer Fehlermeldung:

echo $ex->getCode() . ' ' . $ex->getMessage();

Die Ausgabe zeigt, wie die modifizierten Daten der selbst definierten Fehlerklassearbeiten:

Im nächsten Abschnitt zu __get und __set (siehe Seite 229) finden Sie eine wei-tere Anwendung.

Spezielle Zugriffsmethoden für Klassen

Einige spezielle Zugriffsmethoden für Klassen erlauben sehr flexible und kom-pakte Lösungen.

Der universelle Methodenaufruf mit __call()

Eine Funktion mit dem reservierten Namen __call wird immer dann aufgerufen,wenn keine andere Funktion mit dem benutzten Namen existiert. Als Parameterwerden der Name der versuchsweise aufgerufenen Funktion und ein Array mit den

Abbildung 6.4: Spezifische Fehlermeldung, mittels try/catch verwaltet

227

verwendeten Parametern übergeben. Theoretisch könnte man alle Methoden – bisauf den Konstruktor – hier bündeln. Praktisch dürfte es sich eher um Ausnahmenhandeln, weil die exzessive Anwendung die Lesbarkeit beeinträchtigt.

Page 228: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

Das folgende Beispiel zeigt die Anwendung. Es speichert Namen und gibt dieseüber Methodenaufrufe in verschiedenen Versionen wieder aus:

Listing 6.14: ClassCall.php – Methodenaufrufe über die Sammelstelle __call()

<?phpclass Member{ private $name; public function __construct($name) { $this->name = $name; }

public function __call($method, $param) { switch ($method) { case 'GetName': return $this->name; case 'GetUpperName': return strtoupper($this->name); case 'GetLowerName': return strtolower($this->name); } }}$m1 = new Member('Bernd Mustermann');echo $m1->GetName() . ' <br>';echo $m1->GetUpperName() . ' <br>';echo $m1->GetLowerName() . ' <br>';?>

Das Beispiel zeigt, dass Code durchaus kompakter werden kann. Voraussetzung istjedoch, dass der Inhalt der so zusammengefassten Methoden überschaubar ist. Beiden hier zur Demonstration eingesetzten Einzeilern ist dies sicher ideal.

228

Abbildung 6.5: Ausgabe des Skripts ClassCall.php

Page 229: 382726314 X Php5 In 14 Tagen (Ddt)

Schnittstellen zur Außenwelt

Der universelle Eigenschaftenaufruf mit __get() und __set()

In ganz engem Zusammenhang mit dem universellen Methodenaufruf steht dieBildung von Eigenschaften. Echte Eigenschaften gab es bislang in PHP nicht, dieBereitstellung von öffentlichen Variablen reicht dazu allein nicht aus. Eigenschaf-ten erlauben es nämlich, die Daten, die hinein geschrieben werden, bei derZuweisung oder Rückgabe zu kontrollieren. Ebenso kann vor dem Abruf derDaten eingegriffen werden. Im Sinne einer sicheren Programmierung ist es nunmöglich, den Datenfluss besser zu kontrollieren. Allerdings stellt PHP keinen dedi-zierten Weg zur Bildung von Eigenschaften bereit, wie dies andere Sprachen tun,sondern nur einen universellen über die Pseudoeigenschaftsmethoden __get(Lesen) und __set (Schreiben).

Das folgende Beispiel zeigt eine Anwendung, in der verhindert wird, dass der Auf-rufer versehentlich den Namen löscht und – falls dies trotzdem passiert ist – darangehindert wird einen leeren Namen zu lesen. Diese Klasse ist quasi »gentleman-like« programmiert. Sie akzeptiert alles und liefert dennoch nur sinnvolle Wertezurück. Damit eventuelle Fehlermeldungen auch einen gesicherten Weg aus derKlasse herausfinden, werden zwei eigene Exception-Klassen benutzt und die Feh-ler mit try/catch abgefangen:

Listing 6.15: ClassProperties.php – Kontrolle über den Datenfluss mit Eigenschaften

<?phpclass AgeException extends Exception{}class NameException extends Exception{}class Member{ private $name; private $age; public function __construct()

229

{ $name = ''; $age = 0; } public function __get($property)

Page 230: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

{ switch ($property) { case 'Name': if (preg_match('~^[A-Z][a-z]{3,}$~', $this->name)) { return $this->name; } else { throw new NameException('Name hat falsches Format: ' . $this->name); } break; case 'Age': if (preg_match('~^\d{1,2}$~', $this->age)) { return $this->age; } else { return 0; } break; } } public function __set($property, $value) { switch ($property) { case 'Name': if (preg_match('~^[A-Z][a-z]{3,}$~', $value)) { $this->name = $value; } else

230

{ throw new NameException('Name hat falsches Format: ' . $value); } break;

Page 231: 382726314 X Php5 In 14 Tagen (Ddt)

Schnittstellen zur Außenwelt

case 'Age': if (preg_match('~^\d{1,2}$~', $value)) { $this->age = $value; } else { throw new AgeException('Alter hat falsches Format: ' . $value); } break; } }}$m1 = new Member();try{ $m1->Name = 'Mustermann'; $m1->Age = 43; echo "{$m1->Name} ist {$m1->Age} alt.";}catch (NameException $nex){ echo $nex->getMessage();}catch (AgeException $aex){ echo $aex->getMessage();}?>

Die beiden Exception-Klassen unterscheiden sich nicht vom Original, sie werdenlediglich benutzt, um die Fehlermeldungen selbst mittels catch auseinanderzuhalten. Entsprechend bescheiden sieht die Definition aus:

class AgeException extends Exception{}

231

Die eigentliche Klasse definiert für die Eigenschaften sowohl __get als auch __set.Mittels einer switch-Anweisung werden zwei Eigenschaften gebildet, name undage. In beiden Fällen werden die Daten mittels regulärer Ausdrücke geprüft. Ent-sprechen sie nicht den erwarteten Wertebereichen, wird mittels throw new eine

Page 232: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

spezifische Ausnahme ausgelöst. Die fest programmierten Daten des Musterskriptsfunktionieren. Die folgende Zeile sorgt für die Ausgabe:

echo "{$m1->Name} ist {$m1->Age} alt.";

Diese Zeile wird nicht erreicht, wenn die Daten nicht den Erwartungen entspre-chen. Beträgt beispielsweise die Altersangabe 112, wird bei der Zuweisung (__set)die Ausnahme AgeException ausgelöst:

throw new AgeException('Alter hat falsches Format: ' . $value);

Der try-Zweig bricht dann sofort ab und PHP sucht nach einem passenden catch:

catch (AgeException $aex)

Hier kann nun eine Fehlerbehandlung stattfinden. Im einfachsten Fall bestehtdiese aus einer schlichten Ausgabe der Fehlermeldung:

echo $aex->getMessage();

Auch wenn die Eigenschaftsmethoden __get und __set auf den erstenBlick unsinnig erscheinen, entfalten sie jedoch im Zusammenspiel mitanderen neuen OOP-Techniken eine hohe Leistungsfähigkeit. Diesunterstützt vor allem die sichere, saubere Programmierung und damitstabilere Software.

6.4 Analyse und Kontrolle von Objekten

Der Analyse und Kontrolle von Objekten kommt vor allem bei der Fehlersucheeine große Bedeutung zu. Darüber hinaus kann man Bibliotheken universeller

Abbildung 6.6: Ausgabe, wenn die Daten gültig waren

Abbildung 6.7: Ausgabe, wenn die Daten fehlerhaft sind

232

und sicherer aufbauen, wenn die entsprechenden Techniken zum Einsatz kom-men, die in diesem Abschnitt beschrieben werden.

Page 233: 382726314 X Php5 In 14 Tagen (Ddt)

Analyse und Kontrolle von Objekten

__METHOD__

Die Konstante – gedacht als Erweiterung der bereits in PHP3 und 4 benutzten__LINE__ und __FILE__ – enthält den Namen der aktuellen Methode. Vor allemzur Fehlersuche ist die Angabe durchaus geeignet.

Beachten Sie, dass es sich in allen Fällen um zwei Unterstriche vor undnach dem Namen handelt.

Listing 6.16: ClassMETHOD.php – Informationen über die Methoden einer Klasse

class BaseClass { public $x; public $y; public function SetX($x) { $this->x = $x; echo __METHOD__ . '<br>'; } public function SetY($y) { $this->y = $y; echo __METHOD__ . '<br>'; } } $o1 = new BaseClass(); $o1->SetX(100); $o1->SetY(100); echo "XY: {$o1->x} x {$o1->y}";

Die Ausgabe klärt darüber auf, wann welche Methode aufgerufen wurde:

233

Abbildung 6.8: Ausgabe der aktuellen Methode mit __METHOD__

Page 234: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

Zeichenkettenform: __toString()

Generell gibt es von jedem Objekt eine Zeichenkettenform. PHP5 erstellt dieseautomatisch, wenn der Kontext es verlangt. Für die explizite Definition gibt es dieMethode __toString(). Betrachten Sie zuerst den Standardfall:

Listing 6.17: ClassToString.php – Ein einfaches echo gibt die Zeichenkettenform aus

class BaseClass { public $x; public $y; public function SetX($x) { $this->x = $x; } public function SetY($y) { $this->y = $y; } } $o1 = new BaseClass(); $o1->SetX(100); $o1->SetY(100); echo $o1;

Die interne Form folgt etwa dem folgenden Muster:

Das ist meist wenig hilfreich, weshalb die Möglichkeit besteht, das Verhalten zuändern. Dazu wird eine öffentliche Methode __toString definiert, deren Rückga-bewert die Ausgabe bestimmt.

class BaseClass {

Abbildung 6.9: Zeichenkettendarstellung eines Objekts

234

public $x; public $y; public function SetX($x) { $this->x = $x;

Page 235: 382726314 X Php5 In 14 Tagen (Ddt)

Analyse und Kontrolle von Objekten

} public function SetY($y) { $this->y = $y; } public function __toString() { return 'Object BaseClass'; }

Listing 6.18: ClassToStringO.php – Überschriebene __toString()-Methode

} $o1 = new BaseClass(); $o1->SetX(100); $o1->SetY(100); echo $o1;

Mit der nun erfolgten Ausgabe kann man im Zweifelsfall etwas mehr anfangen.

Beachten Sie wieder, dass es sich bei der Methode __toString() umzwei Unterstriche vor dem Namen handelt.

Abstammung von Klassen und Objekten

Die Abstammung von Klassen und Objekten ist immer dann interessant, wenn dieErstellung dynamisch erfolgt (Factory-Klassen) oder fremder Code benutzt wird.

Wovon ein Objekt abstammt

Abbildung 6.10: Kundenspezifische Zeichenkettenentsprechung

235

Verschiedene Funktionen können in PHP benutzt werden, um die Abstammungeines Objekts von einer Klasse festzustellen. Das mag auf den ersten Blick trivialerscheinen, weil man ja beim Schreiben des Codes genau weiß, wovon ein Objektabstammt. Bei großen Bibliotheken, fremdem Code oder komplexen Hierarchien

Page 236: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

ist das nicht immer der Fall. Betrachten Sie zuerst ein triviales Beispiel, das dasSchlüsselwort instanceof nutzt:

Listing 6.19: ClassInstanceOf.php – Abstammung eines Objekts mit instanceof klären

class BaseClass { public $x; public $y; public function SetX($x) { $this->x = $x; } public function SetY($y) { $this->y = $y; } } $o1 = new BaseClass(); $o1->SetX(100); $o1->SetY(100); if ($o1 instanceof BaseClass){ echo 'Object $o1 stammt von BaseClass';}else { echo 'Object $o1 stammt nicht von BaseClass';}

Hat man eine Hierarchie, lässt sich die Frage nach der Abstammung klären:

Listing 6.20: ClassInstanceOfExt.php – Die Abstammung eines Objekts wird geklärt

class BaseClass { protected $x;

236

protected $y; public function SetX($x) { $this->x = $x; } public function SetY($y)

Page 237: 382726314 X Php5 In 14 Tagen (Ddt)

Analyse und Kontrolle von Objekten

{ $this->y = $y; } } class DerivedClass extends BaseClass{ protected $x; protected $y; public function GetXY() { return "{$this->x}x{$this->y}"; }}$o1 = new DerivedClass(); $o1->SetX(100); $o1->SetY(100); if ($o1 instanceof BaseClass){ echo 'Object $o1 stammt von BaseClass';}else { echo 'Object $o1 stammt nicht von BaseClass';}

Die Ausgabe beider Skripte ist identisch und zeigt im Fall des zweiten Beispiels,dass ein Objekt immer auch von der Basisklasse abstammt:

Alternativ zu instanceof kann auch die Funktion is_a genutzt werden:

is_a($object, 'ClassName')

Die Lesbarkeit ist, nicht zuletzt wegen des wenig transparenten Namens, etwas ein-geschränkt. Dafür funktioniert die letzte Variante auch mit PHP 4, währendinstanceof erst mit PHP5 eingeführt wurde.

Abbildung 6.11: Jedes Objekt ist immer eine Instanz auch der Basisklasse

237

Abstammungshierarchie von Objekten

Um die Abstammungshierarchie von Objekten festzustellen, eignet sich die Funk-tion is_subclass_of.

Page 238: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

Listing 6.21: ClassSubclassOf.php – Feststellen, ob eine Klasse von einer anderen abstammt

class BaseClass { protected $x; protected $y; public function SetX($x) { $this->x = $x; } public function SetY($y) { $this->y = $y; } } class DerivedClass extends BaseClass{ protected $x; protected $y; public function GetXY() { return "{$this->x}x{$this->y}"; }}$o1 = new DerivedClass(); $o1->SetX(100); $o1->SetY(100); if (is_subclass_of($o1, 'BaseClass')){ echo 'DerivedClass stammt von BaseClass';}else { echo 'DerivedClass stammt nicht von BaseClass';}

238

Im Beispiel ist der folgende Ausdruck wahr:

if (is_subclass_of($o1, 'BaseClass'))

Dagegen wäre der folgende Ausdruck falsch:

Page 239: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz der OOP-Funktionen

if (is_subclass_of($o1, 'DerivedClass'))

Damit kann man, im Gegensatz zu instanceof, die tatsächliche Hierarchie fest-stellen und nicht nur eine globale Zugehörigkeit.

Test- und Informationsmethoden

Weitere Testmethoden für Objekte und Klassen finden Sie in der Kurzreferenz amEnde des Kapitels. Vor allem bei der Fehlersuche und zur Analyse fremden Codessind die Testmethoden interessant. Als Programmiermittel dürften sie sich wenigeranbieten.

6.5 Referenz der OOP-Funktionen

OOP-Schlüsselwörter

Schlüsselwort Beschreibung

class Deklariert eine Klasse.

var Deklariert eine öffentliche Mitgliedsvariable (veraltet, PHP 4-Syntax).

new Legt eine neue Instanz eines Objekts an.

extends Erweitert eine Klasse.

interface Deklariert eine Schnittstelle.

implements Implementiert eine Schnittstelle.

private Deklariert ein Mitglied einer Klasse als privat. Es ist damit für Aufrufer der Klasse nicht sichtbar.

public Deklariert ein Mitglied einer Klasse als öffentlich. Es ist damit für alle

239

Aufrufer der Klasse sichtbar. Dies ist der Standardwert, das heißt, ohne Angabe des Schlüsselwortes sind alle Mitglieder öffentlich.

protected Deklariert ein Mitglied einer Klasse als geschützt. Es ist damit für Auf-rufer der Klasse nicht sichtbar, kann jedoch in direkt abgeleiteten Klas-sen benutzt werden.

Page 240: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

OOP-Funktionen

parent Erlaubt den statischen Zugriff auf die Basisklasse.

Self Erlaubt den statischen Zugriff auf die eigene Klasse.

$this Pseudovariable zum Zugriff auf die Instanzform der eigenen Klasse.

const Deklariert eine Konstante im Kontext der Klasse.

try Leitet einen Block ein, der der Ausnahmebehandlung unterliegt

catch Leitet einen Block ein, der eine spezifische Ausnahme behandelt

throw Generiert eine Ausnahme

Funktion Beschreibung

call_user_method Ruft eine benutzerdefinierte Methode auf, verwendet jedoch dazu die Funktionssyntax und nicht den direkten Aufruf mit-tels des Operators ->. Dies kann sinnvoll sein, wenn der Pro-grammkontext explizit einen Funktionsaufruf verlangt oder einer der Parameter dynamisch verändert werden soll.

call_user_method_array Ruft eine benutzerdefinierte Methode auf, verwendet jedoch dazu die Funktionssyntax und nicht den direkten Aufruf mit-tels des Operators ->. Außerdem kann ein Array mit den Para-metern übergeben werden, die die Methode erwartet.

class_exists Prüft, ob eine bestimmte Klasse deklariert wurde. Sinnvoll beispielsweise nach dem dynamischen Einbinden von Modulen.

get_class_methods Gibt ein Array der Methoden einer Klasse zurück. Dient vor allem Analysezwecken.

Schlüsselwort Beschreibung

240

get_class_vars Gibt ein Array der Eigenschaften einer Klasse zurück. Dient vor allem Analysezwecken.

get_declared_classes Gibt ein Array mit allen deklarierten Klassen in PHP5 zurück. Dies umfasst sowohl die eingebauten als auch die im Augen-blick der Abfrage selbst definierten.

Page 241: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz der OOP-Funktionen

get_object_vars Ermittelt die Eigenschaften eines Objekts. Dies ist gut zur Analyse des Zustands, wenn nicht völlig klar, wie und wo das Objekt instanziiert wurde.

get_parent_class Gibt die Klasse zurück, von der eine abgeleitete Klasse abstammt.

is_a Gibt TRUE zurück, wenn das Objekt von der angegebenen Klasse abstammt.

is_subclass_of Gibt TRUE zurück, wenn ein Objekt von der angegebenen Basisklasse abstammt.

method_exists Gibt TRUE zurück, wenn die Methode existiert.

instanceof Gibt TRUE zurück, wenn das Objekt von einer Klasse abstammt.

__toString() Verändert beim Überschreiben in einer Klasse die Zeichen-kettenform des Objekts. Die Zeichenkettenform wird immer dann benutzt, wenn die Ausgabe eines Objekts direkt mit echo oder print erfolgt oder der Kontext des Codes eine Zei-chenkettenform verlangt.

__call() Ruft dynamisch Methoden auf. Die so deklarierte Funktion wird immer dann aufgerufen, wenn PHP in der betreffenden Klasse keine Methode des verlangten Namens findet.

__get() Ruft dynamisch Eigenschaften zum Lesen auf. Die so dekla-rierte Funktion wird immer dann aufgerufen, wenn PHP in der betreffenden Klasse keine Methode des verlangten Namens findet. Wenn man mit __set eine Eigenschaft defi-niert und mit __get nicht, erhält man eine »Nur-Schreib«-Eigenschaft.

__set() Ruft dynamisch Eigenschaften zum Schreiben auf. Die so deklarierte Funktion wird immer dann aufgerufen, wenn PHP in der betreffenden Klasse keine Methode des verlangten

Funktion Beschreibung

241

Namens findet. Wenn man mit __get eine Eigenschaft defi-niert und mit __set nicht, erhält man eine »Nur-Lese«-Eigen-schaft.

Page 242: 382726314 X Php5 In 14 Tagen (Ddt)

Objektorientierte Programmierung

6.6 Kontrollfragen

1. Schreiben Sie ein Skript, dass die Namen von Mitarbeitern in einer eigens dafürentwickelten Klasse speichert.

2. Erklären Sie den Unterschied zwischen private, public und protected.

3. Sie haben nur eine einzige Klasse in Ihrem Skript. Ist die Anwendung des Schlüs-selwortes protected sinnvoll? Begründen Sie die Antwort.

4. Welchen Vorteil bietet die Verwendung von __get und __set anstatt des direktenZugriffs auf öffentliche Eigenschaften, die mit public $name gekennzeichnetsind?

5. Wie schreiben Sie eine Klasse, deren Objekte beim Aufruf von new einen definier-ten Anfangszustand unabhängig von Parametern erhalten soll?

6. Wie schreiben Sie eine Klasse, von der nur eine Instanz erzeugt werden darf? Wienennt man dieses Entwurfsmuster?

__construct() Reservierter Name für den Konstruktor einer Klasse. Der Kon-struktor wird aufgerufen, bevor das Objekt erzeugt wird. Er wird vor allem verwendet, um einen definierten Zustand zu erzeugen. Auslöser ist der Aufruf des Schlüsselwortes new.

__destruct() Reservierter Name für den Destruktor einer Klasse. Der Des-truktor wird aufgerufen unmittelbar bevor das Objekt zerstört wird. Er wird vor allem verwendet, um mit dem Objekt ver-bundene Ressourcen zu bereinigen.

Funktion Beschreibung

242

Page 243: 382726314 X Php5 In 14 Tagen (Ddt)

Das Dateisystem

entdecken

7

Page 244: 382726314 X Php5 In 14 Tagen (Ddt)

Das Dateisystem entdecken

7.1 Dateizugriff organisieren

Das Dateisystem verwaltet Dateien und Ordner. PHP verfügt über einen umfassen-den Satz an Funktionen zum Lesen, Schreiben und Erzeugen von Dateien undVerzeichnissen. Die Anwendung ist unkritisch, wenn man sich mit einigen grund-legenden Techniken angefreundet hat.

Der Begriff Dateisystem wird in PHP übrigens etwas weiter gefasst undkann sich durchaus auf andere Server ausdehnen, die über HTTP oderFTP benutzt werden.

Grundlagen

Beim Umgang mit Datei- und Verzeichnisfunktionen sind einige Dinge wichtig.Zum einen ist immer wieder von so genannten Handles die Rede. Dahinter verber-gen sich Variablen, die einen Zeiger auf eine Datei oder ein Verzeichnis enthal-ten. Das Handle wird von jeder Funktion benutzt, um eine ganze bestimmte Dateiaufzurufen oder Verbindung zu einem Verzeichnis herzustellen. Intern handelt essich dabei um eine so genannte Ressource. Der prinzipielle Ablauf des Dateizu-griffs1 erfordert fast immer folgenden Ablauf:

1. Anfordern der Ressource

2. Erstellen des Handles mit dem Verweis auf die Ressource

3. Benutzen des Handles zum Zugriff auf die Ressource

4. Schließen der Ressource, damit andere darauf zugreifen können

5. Vernichten des Handles (optional, meist automatisch am Skript-Ende erledigt)

Ein ähnlicher Ablauf wird auch für Datenbanken benutzt. In allen Fällen heißendie im ersten und im vierten Schritt benötigten Funktionen typischerweisexxx_open und xxx_close, wobei xxx die spezifische Art der Ressource näherbeschreibt. Bei den Dateifunktionen von PHP5 steht dafür meist »f« oder »file«.

244

Wichtig ist auch, dass die Ressourcen meist exklusiv geöffnet werden. Das heißt,wenn ein Benutzer eine Datei liest oder schreibt, kann dies gleichzeitig kein ande-rer tun. Das ist unproblematisch, wenn immer wieder mit neuen Dateien gearbei-tet wird, wie bei der Sitzungsverwaltung. Greifen aber alle Benutzer auf dieselbe

1 Soweit nicht explizit etwas anderes erwähnt wird, gelten diese Ausführung auch für den Verzeichniszugriff.

Page 245: 382726314 X Php5 In 14 Tagen (Ddt)

Dateizugriff organisieren

Datei zu , kann es entweder zu Programmfehlern (»Zugriff verweigert«) oder zulangen Wartezeiten kommen. Dateien als Datenspeicher sind deshalb nurbegrenzt einsatzfähig. Stoßen sie an ihre Grenzen, kommen Datenbanken insSpiel, die fein granulierter sperren können (mal von SQLite abgesehen, dassDateien zum Speichern nutzt).

Der Zugriff über das Schema »Handle à Ressource« ist nicht allein auf Dateienbeschränkt, auch entfernte Server sind über die Protokolle http:// oder ftp://erreichbar. Das erweitert den Einsatzspielraum gewaltig, weil man sich über dieeigentlichen Protokolle kaum Gedanken machen muss. Beim HTTP ist lediglichzu beachten, dass die Laufzeiten eines Skripts erheblich länger sein können, weilerst eine Verbindung aufgebaut werden muss. Das impliziert auch, dass diese Ver-bindung fehlschlagen kann, was zusätzliche Fehlerabfragen erfordert. Daneben istauch zu beachten, dass meist nur lesend auf andere Server zugegriffen werdenkann.

Einsatzfälle

Einsatzfälle für den Datei- und Verzeichniszugriff gibt es ungeheuer viele. Hiersollen nur ein paar Anregungen gegeben werden:

� Abspeichern von Formulardaten

� Datei-Explorer auf dem Server

� Nachrichtenquelle für die News-Seite

� Speicher fürs Gästebuch

� Steuerung des Hochladens von Dateien

� Informationsspeicher für ein Content Management System

� Template-System-Steuerung

� Protokolldateien erzeugen

Das ist sicher nur ein kleiner Ausschnitt. Anhand vieler kleiner Beispiele wird im Fol-

245

genden eine Übersicht über die Anwendung der wichtigsten Funktionen gegeben.

Page 246: 382726314 X Php5 In 14 Tagen (Ddt)

Das Dateisystem entdecken

7.2 Praktischer Dateizugriff

Der Dateizugriff ist mit PHP relativ einfach. Eine breite Palette von Funktionensteht zur Verfügung, um alle erdenklichen Zugriffe zu erledigen. Dabei werdenimmer wieder einige Basistechniken benutzt, die Sie kennen sollten.

Prinzipien des Dateizugriffs

Es gibt mehrere Prinzipien beim Dateizugriff, die nicht nur für PHP gelten. Diesesollten Sie kennen, damit die Wirkungsweise der verschiedenen Funktionen trans-parent wird.

Dateizugriff

Grundsätzlich kann man zwei Gruppen von Funktionen unterscheiden, die PHPbeim Dateizugriff einsetzt:

� Funktionen mit direktem Dateizugriff

� Funktionen mit Datei-Handle auf eine geöffnete Datei

Funktionen mit direktem Dateizugriff benötigen einen Pfad. Sie lesen oder schrei-ben die Daten und geben die Datei danach wieder frei. Ein späterer Zugriff benö-tigt erneut diese Pfadangabe.

Anders funktionieren die zahlreichen Funktionen mit einem so genannten Datei-Handle. Dabei wird mit einer Funktion ein Verweis auf die geöffnete Dateierstellt, das Handle. Intern ist dies eine Ressource. Andere Funktionen benutztendann nur noch dieses Handle – gespeichert in einer Variablen – zum flexiblenZugriff. Am Ende der Zugriffe wird die Verbindung geschlossen, die Datei freige-geben und das Handle weggeworfen. Vor allem bei wiederholten Zugriffen inSchleifen ist dieses Verfahren einfacher und schneller.

Universeller Quellzugriff

246

PHP beherrscht das Konzept des Wrapper-Zugriffs (manchmal wird das auch alsMoniker bezeichnet). Dabei gilt als Dateipfad jede den Prinzipien einer URI (Uni-form Resource Identifier) entsprechende Form als zulässig. Der prinzipielle (verein-fachte) Aufbau sieht etwa folgendermaßen aus:

Page 247: 382726314 X Php5 In 14 Tagen (Ddt)

Praktischer Dateizugriff

wrapper://pfad/pfad/pfad/datei.extension

Der Standard-Wrapper für Dateien heißt file:///. Er kann entfallen, weil PHPohne Angabe davon ausgeht, dass es sich um eine lokale Datei handelt. Gültigelokale Pfadangaben sind beispielsweise:

� /pfad/pfad2/datei.ext Dies kennzeichnet einen absoluten Pfad auf dem aktuellen Laufwerk.

� pfadrelativ/datei.extDies kennzeichnet einen relativen Pfad, berechnet vom Ausführungsort desSkripts an.

� C:\pfad\datei.ext oder C:/pfad/datei.extPfad mit Laufwerk unter Windows. Die Art der Schrägstriche spielt in PHPkeine Rolle.

� \\sambasrv\sharedpath\local\datei.extEin Pfad zu einem gemeinsamen Laufwerk auf einem Windows- oder Samba-Server

� file:///pfad/datei.ext oder file://c|pfad/datei.extVollständige Angabe eines Pfades mit dem Wrapper. Beachten Sie, dass dieserWrapper mit absoluten Unix-Pfaden arbeitet, wenn der Pfad seinerseits mit /beginnt, was oft zu Pfadangaben wie file:///pfad führt..

Neben dem Dateizugriff sind folgende Wrapper erlaubt:

� http:// und https://Hier erfolgt der Zugriff über HTTP auf einen remoten Server. https:// nutzteine verschlüsselte Verbindung. PHP arbeitet mit dem Standard HTTP 1.0und löst eine GET-Anfrage aus. Grundsätzlich kann die Datei auf dem ent-fernten Server nur gelesen werden.

Wenn der Zugriff durch Kennwort geschützt ist, kann folgende Syntax verwen-det werden, um per Skript einen Zugang zu bekommen:

http://benutzername:[email protected]

� ftp:// und ftps://

247

Hier erfolgt der Zugriff über FTP auf einen remoten Server. ftps:// nutzt eineverschlüsselte Verbindung, wenn der Server dies unterstützt (sehr selten) undbenötigt eine PHP-Version, die SSL unterstützt. PHP kann die Datei auf dementfernten Server lesen und schreiben.

Page 248: 382726314 X Php5 In 14 Tagen (Ddt)

Das Dateisystem entdecken

Wenn der Zugriff durch Kennwort geschützt ist, kann folgende Syntax verwen-det werden, um per Skript einen Zugang zu bekommen:

ftp://benutzername:[email protected]

� php://PHP verwendet intern bestimmte Kanäle (so genannte Streams) für denZugriff auf Datenquellen. Der php://-Wrapper gestattet den Zugriff darauf. Diefolgende Tabelle zeigt die möglichen Kanäle:

� compress.zlib:// oder compress.bzip2://Zugriff auf komprimierte Dateien, die beim Lesen automatisch entpacktwerden.

Zugriff auf Dateiinhalte

Eine andere Technik ist mit dem Zugriff auf den Inhalt der Datei verbunden.Wenn man eine Datei liest, gibt es auch hier wieder mehrere Varianten:

� Die gesamte Datei lesen oder schreiben

� Die Datei zeilenweise lesen oder schreiben

Kanal Bedeutung

stdin Standardeingabe des Betriebssystems

stdout Standardausgabe des Betriebssystems

stderr Fehlerausgabe des Betriebssystems

output Zugriff auf den Ausgabepuffer, der von print oder echo verwendet wird

input Zugriff auf den Eingabepuffer, der unverarbeitete Daten aus POST-Anfragen enthält (Formulardaten)

filter Filter für Funktionen, die keine derartige Parametrisierung von Hause aus unterstützten.

248

� Die Datei zeichenweise lesen oder schreiben

Das Lesen der gesamten Datei erfolgt entweder als Zeichenkette oder als Array.Die Benutzung eines Arrays setzt voraus, dass es eine Möglichkeit gibt, den Inhaltin Elemente zu trennen. Das gilt in der Regel nur für Dateiinhalte, die Text

Page 249: 382726314 X Php5 In 14 Tagen (Ddt)

Praktischer Dateizugriff

umfassen, der mit Zeilenumbrüchen arbeitet. Binäre Dateiinhalte (das sind auchsolche mit proprietären Formaten wie MS Word oder Excel) lassen sich damitnicht verarbeiten. Beim Schreiben einer kompletten Datei muss beachtet werden,dass von der Zielapplikation eventuell erwartete Zeilenumbrüche erzeugt werden.

Beim zeilenweisen Lesen wird davon ausgegangen, dass die Daten auch tatsäch-lich in Zeilen angeordnet sind. Dann bieten sich Funktionen an, die das Errei-chen eines Zeileumbruchs anzeigen und damit eine bestimmte Aktion auslösen.

Binäre Daten kennen keine Zeilenumbrüche und können – ebenso wie natürlichTextdateien – zeichenweise gelesen werden. Dazu wird ein so genannter Datei-zeiger eingesetzt. Der Dateizeiger zeigt auf ein konkretes Zeichen, das als nächstesgelesen bzw. nach dessen Position geschrieben wird. Typischerweise wird währendder Abfrage von Zeichen in einer Schleife permanent überprüft, ob das Dateiendebereits erreicht wurde.

Wichtige Konstanten

PHP liefert einige wichtige Konstanten, die den Umgang mit Pfadangaben undDateinamen vereinfachen:

� DIRECTORY_SEPARATOR Der Verzeichnistrenner. Unter Windows ist dies der Doppelpunkt.

� PATH_SEPARATOR Der Pfadtrenner. Unter Windows ist dies der Backslash, unter Unix der Schräg-strich.

� GLOB_BRACEGLOB_ONLYDIR GLOB_MARKGLOB_NOSORTGLOB_NOCHECKGLOB_NOESCAPEKonstanten, die das Verhalten der glob-Funktion steuern.

249

� PATHINFO_DIRNAMEPATHINFO_BASENAMEPATHINFO_EXTENSIONKonstanten, die das Verhalten der pathinfo-Funktion steuern.

Page 250: 382726314 X Php5 In 14 Tagen (Ddt)

Das Dateisystem entdecken

� FILE_USE_INCLUDE_PATHFILE_APPENDFILE_IGNORE_NEW_LINESFILE_SKIP_EMPTY_LINESKonstanten, die das Verhalten verschiedener Datei-Funktionen steuern.

Nachrichtenquelle für eine News-Seite

Als erstes Beispiel soll eine Nachrichtenquelle für eine News-Seite mit Hilfe derDateifunktionen programmiert werden. Die Nachrichten werden in einer Text-datei abgelegt. Sie sind dort zeilenweise angeordnet, das heißt, jede Nachrichtsteht auf einer Zeile. Zuerst ein Blick auf die Textdatei:

Listing 7.1: news.txt (im Verzeichnis /data) – Die Nachrichten des Tages im Textformat

Das neue Buch "PHP5 und MysQL in 14 Tagen" ist erschienen.PHP für Profis und Agenturen: www.phptemple.de!PHP erneut erfolgreichste Skriptsprache im Web.

Auf der Website sollen diese Nachrichten untereinander ausgegeben werden:

Listing 7.2: filenewsreader.php – Ausgabe von Informationen aus einer Textdatei

$fh = fopen('data/news.txt', 'r');if (is_resource($fh)){ while ($line = fgets($fh)) { echo <<<NEWS <div style="width:175px; font-family: Verdana; margin-bottom:5px; border-left:2px #aaaaff solid; border-top:2px #aaaaff solid;"> $line </div>

250

NEWS; } fclose($fh);}else

Page 251: 382726314 X Php5 In 14 Tagen (Ddt)

Praktischer Dateizugriff

{ echo "Datei nicht gefunden";}

Dieses Skript nutzt zuerst fopen, um das Datei-Handle zu erhalten. Angegebenwerden muss der Pfad zur Datei und ein Parameter, der die Art des Zugriffsbestimmt (siehe unten). Dann wird geprüft, ob tatsächlich eine Ressource zurück-gegeben wurde, was is_resource erledigt. In der Schleife wird dann Zeile für Zeilemit fgets gelesen, wobei die Funktion den Inhalt der Zeile zurückgibt. Sind keineDaten mehr vorhanden, wird FALSE zurückgegeben und die Schleife ist beendet.Anschließend ist der Zugriff mit fclose zu beenden.

Die Funktion fopen ist enorm wichtig und wird nachfolgend genauer vorgestellt.

Dateizugriff mit der Funktion fopen

Der Dateizugriff mit fopen ist elementar. Die Funktion ist deshalb unbedingt einenähere Betrachtung wert. Der grundsätzliche Aufbau kann dem folgenden Syntax-diagramm entnommen werden:

fopen (string pfad, string mode [, int incl [, resource ctx]])

Lediglich die ersten beiden Parameter sind Pflichtangaben. Interessant ist dabeider zweite, eine Zeichenkette, die folgende Bedeutung hat:

Abbildung 7.1: Daten aus einer Datei lesen und formatiert ausgeben

251

Page 252: 382726314 X Php5 In 14 Tagen (Ddt)

Das Dateisystem entdecken

Die einzelnen Dateisysteme gehen unterschiedlich mit dem Zeilenumbruch um.Intern ist dieser durch ein Sonderzeichen definiert. Dabei gilt folgende Regel:

� \n: Zeilenumbruch unter Unix

� \r\n: Zeilenumbruch unter Windows

� \r: Zeilenumbruch unter MacOS

PHP versucht möglichst flexibel damit umzugehen, damit unter Windows erstellteDateien auch auf Linux laufen und umgekehrt. Da es dennoch Probleme mit

Modus Beschreibung

r Nur lesen, der Dateizeiger wird an den Anfang positioniert

r+ Lesen und Schreiben, der Dateizeiger wird an den Anfang positioniert

w Nur Schreiben. Der Dateizeiger wird an den Anfang gesetzt und eine vorhan-dene Datei auf die Länge 0 gesetzt. Existiert die Datei nicht, wird sie erzeugt.

w+ Lesen und Schreiben. Der Dateizeiger wird an den Anfang gesetzt und eine vorhandene Datei auf die Länge 0 gesetzt. Existiert die Datei nicht, wird sie erzeugt.

a Nur Schreiben. Der Dateizeiger wird an das Ende gesetzt, zu schreibende Daten werden angehängt. Existiert die Datei nicht, wird sie erzeugt.

a+ Lesen und Schreiben. Der Dateizeiger wird an das Ende gesetzt, zu schrei-bende Daten werden angehängt. Existiert die Datei nicht, wird sie erzeugt.

x Erzeugen und Schreiben einer lokalen Datei. Wrapper außer file:/// können nicht verwendet werden. Wenn die Datei bereits existiert, gibt fopen FALSE zurück.

x+ Erzeugen und Öffnen zum Lesen und Schreiben einer lokalen Datei. Wrapper außer file:/// können nicht verwendet werden. Wenn die Datei bereits existiert, gibt fopen FALSE zurück.

Tabelle 7.1: Modi für fopen

252

anderen Applikationen geben kann, besteht die Möglichkeit ein »Transfer«-Flagzu definieren, das PHP zur fortlaufenden Konvertierung veranlasst. Das Flag twird beim Öffnen dem Modus nachgestellt. Der Transfer funktioniert nur unterWindows und veranlasst PHP, Dateien mit Unix-Zeilenumbrüchen in solche mitWindows-Zeilenumbrüchen zu verwandeln. Damit das funktioniert, muss das t

Page 253: 382726314 X Php5 In 14 Tagen (Ddt)

Praktischer Dateizugriff

and einen der anderen Modifizierer vor dem optionalen +-Zeichen angehängt wer-den, beispielsweise wt oder at+. Alternativ dazu kann man auch b (auf allen Syste-men) verwenden, um explizit mit Binärdateien zu arbeiten, wo jegliche Zugriffeauf die Daten zu unterbleiben haben (rb, wb oder ab beispielsweise).

Wenn Sie bezüglich der Flags unsicher sind, verwenden Sie immer dieKombination mit b. Es ist immer besser, die Daten in ihrer ursprüngli-chen Form zu belassen, als unqualifizierte Änderungen anzubringen.

Der dritte Parameter ist optional. Er wird entweder auf TRUE oder FALSE gesetzt,wobei FALSE der Standard ist. Die Angabe von TRUE veranlasst die Funktion beimÖffnen der Datei den Suchpfad für Include-Dateien mit einzubeziehen, wenn dieDatei (bei relativen Pfadangaben) im aktuellen Verzeichnis nicht gefunden wer-den konnte.

Der vierte Parameter ist nur sinnvoll einsetzbar, wenn der verwendete Wrapper(http://, ftp:// oder php://) zusätzliche Angaben verlangt. Diese werden dann hierplatziert. Das erforderliche Format kann mit stream_context_create erstellt wer-den. Diese Funktion selbst akzeptiert wiederum ein Array. Das folgende Code-Fragment zeigt, wie die Anwendung erfolgt:

<?php$opts = array( 'http' => array( 'method' => "GET", 'header' => "Accept-language: de\r\n" . "Cookie: color=red\r\n" ) );$context = stream_context_create($opts);$fp = fopen('http://www.zielserver.de', 'r', false, $context);fpassthru($fp);fclose($fp);?>

Für HTTP sind folgende Optionen vorgesehen:

253

Page 254: 382726314 X Php5 In 14 Tagen (Ddt)

Das Dateisystem entdecken

Weitere Optionen der anderen Wrapper können der Online-Dokumentation ent-nommen werden.

Beliebige Code-Dateien ausgeben

Im nächsten Beispiel werden PHP-Quelltexte angezeigt. Für diesen einfachen Fallzeigt sich die Datei einfach selbst an.

Listing 7.3: filefile.php – Ausgaben einer Datei mit Zeilennummern

$path = basename($_SERVER['PHP_SELF']);$file = @file($path);if (is_array($file)){ echo '<pre>'; for ($i = 0; $i < count($file); $i++) { printf('%04d: %s', $i, htmlspecialchars($file[$i])); }

Option Beschreibung

method Methode, beispielsweise GET oder POST.

header Die Kopfzeilen (siehe Beispiel).

user_agent Information darüber, wie sich das Skript identifiziert. Man kann hier bei-spielsweise den Namen eines Browsers eintragen.

content Daten, die an POST-Anforderungen angehängt werden.

proxy URI eines Proxy-Servers, beispielsweise tcp://proxy.zielserver.de:9900

request_fulluri TRUE oder FALSE, wobei die Angabe TRUE in der HTTP-Anforderung die vollen Angabe der Zieladresse erzwingt (nicht üblich).

Tabelle 7.2: Kontext-Optionen für HTTP

254

echo '</pre>';}

Das Skript ermittelt zuerst den eigenen Namen aus der Servervariablen$_SERVER['PHP_SELF'] mit der Funktion basename. Dann wird die Funktion filebenutzt, um den Inhalt der Datei in ein Array zu überführen. Sicherheitshalber

Page 255: 382726314 X Php5 In 14 Tagen (Ddt)

Praktischer Dateizugriff

wird noch mit is_array geprüft, ob dies auch gelang. Dann wird die Schleife vomersten bis zum letzten Arrayelement durchlaufen. Da Zeilennummern ausgegebenwerden sollen, bietet sich hier for an, womit gleich eine Zählvariable zur Verfü-gung steht.

Die Funktion htmlspecialchars verhindert, dass die HTML-Zeichen <, > und &bei der Ausgabe ausgeführt werden. Zuletzt wird die Ausgabe noch mit printf for-matiert, wodurch die Zeilennummern auf gleiche Breite gebracht werden.

Protokolldatei

Das Schreiben einer Datei ist ebenso einfach wie das Lesen, entsprechendeZugriffsrechte vorausgesetzt. Eine Protokolldatei zu schreiben, ist ein guter Testfür ein einfaches Skript. Benötigt werden der Zugriff auf eine Datei mit fopen unddie Schreibfunktion fwrite. Außerdem sind noch einige Daten zu beschaffen, diegeschrieben werden sollen:

� Das aktuellen Datum und eine Zeitangabe (strftime)

� Informationen über den Browser (Servervariablen aus $_SERVER):

Abbildung 7.2: Ausgabe einer Datei mit Zeilen-nummern

255

� HTTP_USER_AGENTErmittelt die Kennung des Browsers, der gerade das Skript ausführt

� HTTP_ACCEPT_LANGUAGEErmittelt Informationen über die Spracheinstellungen des Browsers

Page 256: 382726314 X Php5 In 14 Tagen (Ddt)

Das Dateisystem entdecken

� REMOTE_ADDRDie IP-Adresse, mit der der Rechner des Nutzers mit dem Internet verbun-den ist

Die praktische Umsetzung zeigt das folgende Skript:

Listing 7.4: fileprotocol.php – Daten über den aktuellen Benutzer speichern

$dt = strftime('%d.%m.%Y %H:%M:%S', time());$bi = $_SERVER['HTTP_USER_AGENT'];$al = $_SERVER['HTTP_ACCEPT_LANGUAGE'];$ra = $_SERVER['REMOTE_ADDR'];$fh = fopen('data/protocol.txt', 'a');fwrite ($fh, sprintf("%s '%s' '%s' %s\n", $dt, $bi, $al, $ra));fclose($fh);$log = file_get_contents('data/protocol.txt');echo <<<LOG<pre>$log</pre>LOG;

Die Zusammenstellung der Protokolldaten erfolgt, nachdem diese den Servervari-ablen entnommen wurden, mit der Funktion sprintf. Die damit erzeugte Zei-chenkette wird dann mit fwrite in die Datei geschrieben. Damit der nachfolgendeTestzugriff gelingt, wird die Datei sofort wieder mit fclose geschlossen. $fh spei-chert im Beispiel das benutzte Handle.

Die Ausgabe erfolgt hier nur zu Testzwecken, andernfalls würde das Beispiel kei-nerlei Ausgabe erzeugen, was in der Praxis vermutlich der gewünschte Zweckwäre. Eingesetzt wird die Funktion file_get_contents, die die Datei vollständig ineine Zeichenkette einliest. Auf die Umwandlung in ein Array zur zeilenweisenAusgabe wird hier verzichtet, weil in der Ausgabe die <pre>-Tags für die Anzeigeder enthaltenen Zeilenumbrüche sorgen.

256

Abbildung 7.3: Testausgabe des Protokolls

Page 257: 382726314 X Php5 In 14 Tagen (Ddt)

Verzeichniszugriff

7.3 Verzeichniszugriff

Der Verzeichniszugriff geht eng zusammen mit dem Dateizugriff. Die Funktionenlassen sich gut kombinieren und für umfangreiche dateiorientierte Projekte nutzen.

Inhalt eines Verzeichnisses anzeigen

Um den Inhalt eines Verzeichnisses anzuzeigen, ist glob ein gute Funktion. DieAngabe von Platzhaltern erlaubt eine höhere Flexibilität, als die reinen Verzeich-nisfunktionen bieten.

Einfache Dateiliste

Eine einfache Dateiliste lässt sich sehr leicht mit der Funktion glob erstellen. Dasfolgende Skript zeigt alle PHP-Skripte an, die mit den Buchstaben »e« oder »f«beginnen:

Listing 7.5: fileglob.php – Gefilterte Dateiliste

$path = basename($_SERVER['PHP_SELF']);$files = glob("{[ef]*.php}", GLOB_BRACE); if (is_array($files)){ foreach ($files as $filename) { echo "$filename<br>"; }}

Die Funktion glob kann mit einigen Schaltern gesteuert werden, die in der folgen-den Tabelle erklärt werden:

Schalter Funktion

257

GLOB_BRACE Die Platzhalter verwenden Aufzählungssymbolik: {*.txt,*.php} usw.

GLOB_ONLYDIR Es werden nur Verzeichnisse erkannt

Tabelle 7.3: Schalter für die Funktion glob

Page 258: 382726314 X Php5 In 14 Tagen (Ddt)

Das Dateisystem entdecken

Mehrere Schalter können über eine einfache Oder-Verknüpfung | kombiniertwerden:

GLOB_BRACE | GLOB_ONLYDIR

Die Platzhalterzeichen erlauben folgende Angaben:

� {Platzhalter,Platzhalter}Eine Serie von Platzhaltern, die ODER-verknüpft sind, werden durch Kom-mata getrennt in geschweifte Klammern gesetzt. Dies funktioniert nur, wennder Schalter GLOB_BRACE verwendet wird.

� *Keines oder eine beliebige Anzahl Zeichen.

� ?Genau ein beliebiges Zeichen.

� []Genau ein Zeichen aus einer Zeichengruppe, die durch die Angabe in derKlammer bestimmt wird. Dies kann eine Aufzählung aus Zeichen sein, bei-spielsweise:

� [aef]Steht für die Buchstaben »a« oder »e« oder »f«.

GLOB_MARK Fügt einen Schrägstrich an alle erkannten Einträge an

GLOB_NOSORT Verhindert die Sortierung (Standard ist ein alphabetische Sortie-rung)

GLOB_NOCHECK Gibt das Suchmuster zurück, wenn keine Dateien gefunden wurden

GLOB_NOESCAPE Meta-Zeichen (Verzeichnistrennzeichen) werden nicht mit einem Backslash markiert (unter Windows unbedingt sinnvoll)

Schalter Funktion

Tabelle 7.3: Schalter für die Funktion glob (Forts.)

258

� [a-f]Steht für die Buchstaben »a«, »b«, »c«, »d«, »e« oder »f«.

� [0-9]Steht für die Zahlen 0 bis 9.

Page 259: 382726314 X Php5 In 14 Tagen (Ddt)

Verzeichniszugriff

Der gesamte Ausdruck in der Klammer kann negiert werden, indem das Zei-chen »!« vorangestellt wird:

� [!eEfF]Alle Zeichen außer »e«, »E«, »f« und »F«

Dieser Ausdruck muss in geschweiften Klammern stehen und funktioniert nur,wenn der Schalter GLOB_BRACE verwendet wird.

Insgesamt betrachtet ist glob schnell und einfach einzusetzen. Reicht die Kon-struktion der Suchmuster jedoch nicht aus, muss man auf reguläre Ausdrücke aus-weichen.

Manchmal soll nur festgestellt werden, ob eine Datei einem bestimmten Muster

Abbildung 7.4: Gefilterte Dateiliste mit der glob-Funktion

259

entspricht. Dieselbe Syntax wie bei glob kann mit fnmatch verwendet werden. Umeine Dateiliste zu durchsuchen, wird das dir-Objekt bemüht und dann mitfnmatch untersucht.

Page 260: 382726314 X Php5 In 14 Tagen (Ddt)

Das Dateisystem entdecken

Das Verzeichnisobjekt dir und verwandte Funktionen

PHP kennt eine Klasse dir, aus der sich ein Verzeichnisobjekt erstellen lässt. Dieprinzipielle Anwendung zeigt das folgende Skript. Dazu wird auch gleich dieFunktion fnmatch definiert. Diese mit PHP 4.3 eingeführte Funktion steht leiderauch mit PHP5 nicht unter Windows zur Verfügung, was unverständlich ist, weildie ebenso dem Unix-System entstammende Variante glob vorhanden ist. Um dasSkript universell zu machen, wird eine fnmatch-Variante selbst definiert.

Listing 7.6: filefnmatch.php – Verwendung von fnmatch und alternative Definition

if (!function_exists('fnmatch')){ function fnmatch($pattern, $file) { for($i=0; $i<strlen($pattern); $i++) { if($pattern[$i] == "*") { for($k = $i; $k < max(strlen($pattern), strlen($file)); $k++) { if(fnmatch(substr($pattern, $i+1), substr($file, $k))) { return TRUE; } } return FALSE; } if($pattern[$i] == "[") { $letter_set = array(); for($k = $i+1; $k < strlen($pattern); $k++) {

260

if($pattern[$k] != "]") { array_push($letter_set, $pattern[$k]); } else break; }

Page 261: 382726314 X Php5 In 14 Tagen (Ddt)

Verzeichniszugriff

foreach ($letter_set as $letter) { if(fnmatch($letter.substr($pattern, $k+1), substr($file, $i))) { return TRUE; } } return false; } if($pattern[$i] == "?") { continue; } if($pattern[$i] != $file[$i]) { return FALSE; } } return TRUE; } } $path = $_SERVER['SCRIPT_FILENAME'];$self = dirname($path);$dir = dir($self);if (is_object($dir)){ $dir->rewind(); while ($file = $dir->read()) { if (fnmatch("[ef]*.php", $file)) { echo "$file<br>"; } } $dir->close();}

261

Betrachten Sie zuerst nur den unteren Teil, der die Klasse dir verwendet. Hierwird zuerst der physikalische Pfad zur aktuellen Datei ermittelt, weil das Verzeich-nis gelesen werden soll, in dem das Skript selbst liegt.

$path = $_SERVER['SCRIPT_FILENAME'];

Page 262: 382726314 X Php5 In 14 Tagen (Ddt)

Das Dateisystem entdecken

Die Servervariable SCRIPT_FILENAME wird hier verwendet, weil sie den kom-pletten Pfad enthält und als einzige Variable sowohl unter dem IIS- als auch Apa-che-Webserver zur Verfügung steht. Es gibt zwar viele vermeintliche Alternativen,aber die funktionieren nie auf allen Systemen. Aus der Pfadangabe wird dann dasaktuelle Verzeichnis gewonnen:

$self = dirname($path);

Nun wird eine Instanz des Verzeichnisobjekts erstellt:

$dir = dir($self);

Nach einer Prüfung, ob es wirklich gelang, das Objekt zu erzeugen, wird dieinterne Liste der Dateien auf den Anfang gesetzt:

$dir->rewind();

Dann wird in einer Schleife Eintrag für Eintrag gelesen:

while ($file = $dir->read())

262

Abbildung 7.5: Gefilterte Dateiliste mit fnmatch

Page 263: 382726314 X Php5 In 14 Tagen (Ddt)

Verzeichniszugriff

Die Methode read gibt FALSE zurück, wenn keine Einträge mehr da sind. Inner-halb der Schleife wird dann jeder Dateiname mit fnmatch untersucht:

if (fnmatch("[ef]*.php", $file))

Die Funktion fnmatch, ob eingebaut oder selbst definiert, durchsucht nun denDateinamen entsprechend dem angegebenen Suchmuster Zeichen für Zeichenund gibt einen Booleschen Wert zurück (siehe Abbildung 7.5).

Zum Schluss sollen noch die Möglichkeiten, die dir bietet, auf einen Blick gezeigtwerden:

Die Benutzung des Handles ist dann interessant, wenn – aus welchen Gründenauch immer – mit den anderen Verzeichnisfunktionen gearbeitet werden soll.Diese Funktionen sind nicht objektorientiert, sondern werden ebenso wie dieDateifunktionen benutzt. Eine Auflistung finden Sie in der Kurzreferenz am Endedes Kapitels.

Eigenschaft Beschreibung

path Pfad, der zum Erzeugen des Objekts benutzt wurde

handle Verzeichnis-Handle, das andere Funktionen benutzen können

Tabelle 7.4: Eigenschaften der Klasse dir

Eigenschaft Beschreibung

read Liest den nächsten Eintrag aus dem Verzeichnis und gibt entweder eine Zeichenkette oder FALSE zurück.

rewind Setzt den Lesezeiger wieder auf den ersten Eintrag zurück.

close Schließt das Handle zum Verzeichnis.

Tabelle 7.5: Methoden der Klasse dir

263

Rekursive Dateiliste

Das folgende Beispiel erweitert die Ausgabe der Dateiliste auf eine beliebigeAnzahl von Unterverzeichnissen. Die Technik der Rekursion bietet eine einfacheLösung dafür:

Page 264: 382726314 X Php5 In 14 Tagen (Ddt)

Das Dateisystem entdecken

Listing 7.7: filerecursiveglob.php – Rekursive Version, um untergeordnete Verzeichnisse zu lesen

function rglob($sDir, $sPattern, $nFlags = NULL){ $sDir = escapeshellcmd($sDir); $aFiles = glob("$sDir/$sPattern", $nFlags); foreach (glob("$sDir/*", GLOB_ONLYDIR) as $sSubDir) { $aSubFiles = rglob($sSubDir, $sPattern, $nFlags); if (is_array($aSubFiles)) { $aFiles = array_merge($aFiles, $aSubFiles); } } return $aFiles;} $path = dirname(getcwd() . '/php5MuT');echo "Liste der PHP-Skripte in <b>$path</b>:<p/>";$aFiles = rglob($path, '{*.txt,a*.php}', GLOB_BRACE);foreach ($aFiles as $filepath){ echo dirname(realpath($filepath)) . ' : '; echo basename($filepath) . '<br>';}

Auch dieses Skript bietet wieder eine reiche Verwendung von Dateifunktionen.Zuerst wird der Pfad ermittelt. Im Beispiel wird das aktuelle Arbeitsverzeichnis(getcwd) und dort das Unterverzeichnis »php5MuT« genommen. Eine tiefere Ver-zeichnisstruktur kann Probleme bereiten, da die Laufzeit des Skripts bei tieferVerschachtelung erheblich ist:

$path = dirname(getcwd() . '/php5MuT');

Nun wird die rekursiv programmierte Funktion rglob vorgestellt. Sie gibt ein Arraymit allen Dateien zurück, die dem Suchmuster entsprechen, wobei alle Dateien inallen untergeordneten Verzeichnissen mit eingeschlossen werden. Bevor dieDetails der rekursiven Funktion diskutiert werden, ist ein Blick auf die Ausgabe

264

interessant. glob liefert komplette Dateipfade. Um diese zu trennen wird zuerst dervollständige Pfad mit realpath ermittelt, dann mit dirname der Pfad extrahiert. Fürbasename, genutzt zum Abtrennen des Dateinamens, reicht die Nennung desPfades ohne Veränderungen.

Page 265: 382726314 X Php5 In 14 Tagen (Ddt)

Verzeichniszugriff

Die Funktion rglob selbst basiert im wesentlichen auf glob. Zuerst wird die aktu-elle Dateiliste gemäß dem gewählten Suchmuster erstellt:

$aFiles = glob("$sDir/$sPattern", $nFlags);

Das Muster folgt dabei den üblichen Platzhalterregeln. Zusammen mit der Kon-stanten GLOB_BRACE kann außerdem eine Kette von Platzhalter angegebenwerden. Im Beispiel sieht dieses Muster folgendermaßen aus:

{*.txt,a*.php}

Dieses Muster betrifft alle Dateien mit der Endung »txt« und alle Dateien, die mitdem Buchstaben »a« beginnen und auf »php« enden. Nach der Erstellung derDateiliste werden die untergeordneten Verzeichnisse gesucht:

glob("$sDir/*", GLOB_ONLYDIR)

Hier sorgt der Schalter GLOB_ONLYDIR für das gewünschte Resultat. Um die Dateieninnerhalb des gefundenen Verzeichnisses zu lesen, erfolgt als erste Maßnahmeinnerhalb der foreach-Schleife der rekursive Aufruf. Die Ergebnisse werden dannmit array_merge an das bestehende Array angehängt. Damit leere Verzeichnissenicht stören, wird noch eine Abfrage mit is_array davor geschaltet.

Verzeichniszugriff mit Iteratoren

Iteratoren durchlaufen Auflistungen, vorzugsweise mit foreach. Das Konzeptwurde neu in PHP5 eingeführt und ist Teil der so genannten Standard PHPLibrary. Alle Iteratoren implementieren die Schnittstelle Iterator. Dies führt zuden folgenden Methoden:

� current()Die Methode gibt das aktuelle Element der Auflistung zurück.

� next()Die Methode setzt einen Schritt in der Auflistung weiter.

� valid()Die Methode gibt TRUE zurück, wenn ein weiteres Element beim vorhergehen-

265

den Aufruf von next gefunden wurde, sonst FALSE.

� rewind()Mit dieser Methode wird der Zeiger wieder an den Anfang gesetzt.

Page 266: 382726314 X Php5 In 14 Tagen (Ddt)

Das Dateisystem entdecken

Einige Iterator-Klassen bieten eine Reihe weiterer Methoden an, die den Zugriffauf spezifischen Eigenschaften der Elemente der Auflistung ermöglichen.

Verzeichnisse werden mit Instanzen des DirectoryIterators verarbeitet. FolgendeMethoden sind zusätzlich zu den Standardmethoden verfügbar:

Methode Beschreibung

fileATime Zeitpunkt des letzten Zugriffs.

fileCTime Zeitpunkt der letzten Änderung.

fileGroup Gruppe, der diese Datei zugeordnet ist.

fileInode Inode dieser Datei (nur Unix).

fileOwner Eigentümer dieser Datei.

filePerms Zugriffsrechte an dieser Datei.

fileSize Die Dateigröße.

fileType Der Dateityp.

getFileName Der Dateiname.

getPath Pfad zu dieser Datei.

hasMore Dieser Eintrag hat weitere Einträge.

isDir TRUE, wenn der Eintrag ein Verzeichnis ist.

isDot TRUE, wenn der Eintrag das Verzeichnis ».« oder »..« ist.

isExecutable Die Datei ist ausführbar.

isFile Dieser Eintrag ist eine Datei.

isLink Dieser Eintrag ist ein Link (nur Unix-Links).

isReadable Dieser Eintrag ist lesbar.

266

isWritable Dieser Eintrag ist schreibbar.

Tabelle 7.6: Methoden der Klasse DirectoryIterator

Page 267: 382726314 X Php5 In 14 Tagen (Ddt)

Verzeichniszugriff

Das folgende Beispiel zeigt, wie der Iterator verwendet wird:

Listing 7.8: dirIterator.php – Verzeichnisauflistung mittels SPL-Iterator

<html><style>* { font-family: Verdana; font-size:12pt}.dir { background-color:silver; margin-left:5px}.file { background-color:white; margin-left:15px}</style><body><?php function ShowDir($iter) { echo '<ul>'; for( ; $iter->valid(); $iter->next()) { if($iter->isDir() && !$iter->isDot()) { printf('<li class="dir">%s</li>', $iter->current()); } elseif($iter->isFile()) { echo '<li class="file">'. $iter->current() . ' (' . $iter->getSize(). ' Bytes)</li>'; } } echo '</ul>'; } ShowDir(new DirectoryIterator('D:/inetpub/wwwroot')); ?></body></html>

Damit das Skript bei Ihnen läuft, müssen Sie den Pfad im Aufruf anpassen:

ShowDir(new DirectoryIterator('D:/inetpub/wwwroot'));

267

Iteratoren bieten einen konsequent objektorientierten Zugriff und ersetzen im Falldes DirectoryIterators die bisherigen Dateifunktionen.

Page 268: 382726314 X Php5 In 14 Tagen (Ddt)

Das Dateisystem entdecken

7.4 Funktions-Referenz

Datei-Funktionen

Funktion Bedeutung

basename Dateiname in einer vollständigen Pfadangabe.

chgrp Ändert die Gruppenzugehörigkeit eines Dateieintrags (nur Unix).

chmod Ändert den Zugriffsmodus eines Dateieintrags.

chown Ändert den Eigentümer eines Dateieintrags (nur Unix*).

clearstatcache Löscht den Status-Cache mit den letzten Dateiinformationen.

copy Kopiert eine Datei oder benennt sie um.

delete Löscht eine Datei (ein Alias für unlink).

dirname Verzeichnisname in einer vollständigen Pfadangabe.

disk_free_space Ermittelt den freien Speicherplatz eines Datenträgers.

disk_total_space Ermittelt den gesamten Speicherplatz eines Datenträgers

fclose Schließt einen Dateizeiger.

feof Testet einen Dateizeiger auf das Dateiende.

fflush Überträgt den gesamten Inhalt in den Ausgabepuffer.

fgetc Liest ein Zeichen aus einer Datei an der Position des Dateizeigers.

fgetcsv Holt eine Zeile aus einer Datei und ermittelt CSV**-Felder .

fgets Holt die nächste Zeile ohne weitere Verarbeitung.

268

* Windows bietet dies auch, aber PHP unterstützt dies nicht auf Windows.** CSV = Comma Separated Values, allgemein: Dateien mit Feldtrennzeichen, beispielsweise Kommata, Semi-

kola oder Tabulatoren.

fgetss Holt die nächste Zeile und entfernt alle HTML-Tags.

Tabelle 7.7: Funktions-Referenz für Datei-Funktionen

Page 269: 382726314 X Php5 In 14 Tagen (Ddt)

Funktions-Referenz

Funktion Bedeutung

file_exists Prüft, ob eine Datei existiert.

file_get_contents Liest die gesamte Datei in eine Zeichenkette.

file_put_contents Schreibt eine Zeichenkette in eine Datei.

file Liest die gesamte Datei in ein Array.

fileatime Ermittelt den Zeitpunkt des letzten Dateizugriffs.

filectime Ermittelt den Zeitpunkt der letzten Änderung des Dateizeigers Inode.

filegroup Ermittelt die Gruppenzugehörigkeit einer Datei (nur Unix).

fileinode Ermittelt den Inode einer Datei (nur Unix).

filemtime Ermittelt den Zeitpunkt der letzten Änderung der Datei.

fileowner Ermittelt den Eigentümer einer Datei (nur Unix).

fileperms Ermittelt die Dateizugriffsregeln.

filesize Ermittelt die Dateigröße.

filetype Ermittelt den Dateitype.

flock Blockiert eine Datei für exklusiven Zugriff.

fnmatch Prüft einen Dateinamen gegen ein bestimmtes Muster.

fopen Öffnet eine Datei oder ein URL.

fpassthru Liefert alle ausstehenden Daten an einen Dateizeiger.

fputs Schreibt Daten in eine Datei, angegeben durch ein Dateihandle.

fread Liest eine Datei binär.

fscanf Liest aus einer Datei und interpretiert den Inhalt gemäß dem angegebenen Suchmuster.

269

fseek Setzt den Dateizeiger.

fstat Ermittelt Statusinformationen über eine Datei.

Tabelle 7.7: Funktions-Referenz für Datei-Funktionen (Forts.)

Page 270: 382726314 X Php5 In 14 Tagen (Ddt)

Das Dateisystem entdecken

ftell Ermittelt die Position des Dateizeigers.

ftruncate Schneidet eine Datei an der angegebenen Position ab.

fwrite Schreibt Binärdaten in eine Datei.

glob Sucht Pfadnamen anhand eines Suchmusters.

is_dir Ermittelt, ob die Pfadangabe ein Verzeichnisname ist.

is_executable Ermittelt, ob die Pfadangabe eine ausführbare Datei darstellt.

is_file Ermittelt, ob die Pfadangabe eine Datei bezeichnet.

is_link Ermittelt, ob die Pfadangabe ein symbolischer Link ist (nur Unix).

is_readable Ermittelt, ob die Pfadangabe auf eine lesbare Datei zeigt.

is_uploaded_file Ermittelt, ob die Pfadangabe auf eine per HTTP-Upload hochge-ladene Datei zeigt.

is_writable Ermittelt, ob die Pfadangabe auf eine beschreibbare Datei zeigt .

link Erzeugt eine Verknüpfung (nur Unix*).

linkinfo Liefert Informationen über einen Dateilink.

lstat Liefert Informationen über einen symbolischen Link.

move_uploaded_file Verschiebt eine hochgeladene Datei in ihr finales Verzeichnis.

parse_ini_file Untersucht eine Konfigurationsdatei.

pathinfo Ermittelt Informationen über eine Pfadangabe.

readfile Liest eine Datei und gibt den Inhalt sofort aus.

readlink Liest das Ziel eines symbolischen Links (nur Unix).

realpath Ermittelt den tatsächlichen vollständigen Pfad aus einer relativen Angabe.

Funktion Bedeutung

270

rename Benennt ein Verzeichnis oder eine Datei um.

* Windows kennt auch Verknüpfungen, die jedoch anders realisiert sind und von PHP nicht als Links erkanntwerden.

Tabelle 7.7: Funktions-Referenz für Datei-Funktionen (Forts.)

Page 271: 382726314 X Php5 In 14 Tagen (Ddt)

Funktions-Referenz

Funktion Bedeutung

rewind Setzt die Position des Dateizeigers zurück auf den Anfang.

stat Ermittelt Statusinformationen über eine Datei und speichert diese. Spätere Anforderungen rufen die zwischengespeicherten Daten ab, bis der Puffer mit clearstatcache gelöscht wird.

symlink Erzeugt einen neuen symbolischen Link (nur Unix).

tempnam Erzeugt einen temporären Dateinamen.

tmpfile Erzeugt einen temporären Dateinamen und die entsprechende Datei.

touch Ändert die letzte Zugriffszeit einer Datei.

umask Ändert die Umask einer Datei (nur Unix).

unlink Löscht eine Datei (auch Windows).

Tabelle 7.7: Funktions-Referenz für Datei-Funktionen (Forts.)

Funktion Bedeutung

chdir Ändert das aktuelle Verzeichnis, aus dem gelesen wird.

chroot Ändert das Stammverzeichnis.

closedir Schließt das Verzeichnis-Handle.

dir Instanziiert die Verzeichnisklasse (siehe Text).

dirname Ermittelt den Verzeichnisnamen in einer vollständigen Pfadan-gabe.

getcwd Ermittelt das aktuelle Arbeitsverzeichnis.

mkdir Erzeugt ein Verzeichnis.

opendir Öffnet ein Handle auf ein Verzeichnis prozedural.

271

readdir Liest einen Eintrag aus einem Verzeichnis-Handle.

rewinddir Setzt den Zeiger vor dem Lesen der Einträge auf den Anfang zurück.

Tabelle 7.8: Funktions-Referenz für Verzeichnis-Funktionen

Page 272: 382726314 X Php5 In 14 Tagen (Ddt)

Das Dateisystem entdecken

Prozess-Funktionen

rmdir Entfernt ein Verzeichnis.

scandir Liest Dateien eines bestimmten Pfades.

Funktion Bedeutung

escapeshellarg Behandelt eine Zeichenkette so, dass sie als Argument für Shell-Aufrufe verwenden werden kann, indem die Anführungszeichen maskiert werden.

escapeshellcmd Maskiert Shell-Zeichen so, dass sie nicht zum Missbrauch bei der Ausführung von Kommandos verwendet werden können.

exec Führt ein externes Programm aus. Die Funktion erzeugt keine Aus-gabe und gibt lediglich die letzte Zeile der Antwort des Programms zurück.

passthru Führt ein externes Programm aus und zeigt die Ausgabe an.

pclose Schließt ein Prozesshandle (einfache Form).

popen Öffnet einen Prozess und erzeugt das passende Prozesshandle (ein-fache Form).

proc_close Schließt ein Prozesshandle.

proc_get_status Ermittelt Statusinformationen über den Prozess. Die Rückgabe erfolgt als Array, dessen Elemente Angaben wie das verwendete Kommando (command), die Prozess-ID (pid) oder den Rückgabe-code (exitcode) enthalten.

proc_nice Ändert die Priorität des Prozesses (nicht auf Windows verfügbar).

Funktion Bedeutung

Tabelle 7.8: Funktions-Referenz für Verzeichnis-Funktionen (Forts.)

272

proc_open Öffnet einen Prozess und erzeugt das passende Prozesshandle.

proc_terminate Beendet einen Prozess.

Page 273: 382726314 X Php5 In 14 Tagen (Ddt)

Kontrollfragen

7.5 Kontrollfragen

1. Auf welche Datenquellen können die Dateifunktionen zugreifen?

2. Warum muss eine Datei nach der Benutzung wieder geschlossen werden?

3. Schreiben Sie ein Skript, dass eine beliebige Datei aus dem aktuellen Verzeichnisim Quelltext anzeigt, wobei der Benutzer die Datei selbst wählen kann. Tipp:Benutzen Sie HTML-Links <a href=script.php?filename=$name> und ermit-teln Sie den übergebenen Namen mittels $_GET[’name’].

4. Warum ist das in der letzten Übung verlangte Prinzip auf einer öffentlichenWebsite nicht unmodifiziert einsetzbar? Tipp: Denken Sie an mögliche Sicher-heitsprobleme.

shell_exec Führt ein Kommando über die Shell (Eingabeaufforderung) aus. Alternativ kann auch der »Backtick«-Operator ’kommando’ verwen-det werden.

system Führt ein externes Programm aus und zeigt die Ausgabe an.

Funktion Bedeutung

273

Page 274: 382726314 X Php5 In 14 Tagen (Ddt)
Page 275: 382726314 X Php5 In 14 Tagen (Ddt)

WOCH

TTTTaaaag g g g 1111 Einführung 21

TTTTaaaag g g g 2222 Erste Schritte 45

TTTTaaaag g g g 3333 Daten verarbeiten 73

WOCH

ETTTTaaaag g g g 4444 Programmieren 131

TTTTaaaag g g g 5555 Daten mit Arrays verarbeiten 181

TTTTaaaag g g g 6666 Objektorientierte Programmierung 203

TTTTaaaag g g g 7777 Das Dateisystem entdecken 243

TTTTaaaag g g g 8888 Formular- und Seitenmanagement 277

TTTTaaaag g g g 9999 Professionelle Programmierung 373

TTTTaaaag g g g 10101010 Kommunikation per HTTP, FTP und E-Mail 415

ETTTTaaaag g g g 11111111 Datenbankprogrammierung 443

TTTTaaaag g g g 12121212 Die integrierte Datenbank SQLite 497

TTTTaaaag g g g 13131313 Datenbanklösungen mit MySQL 509

TTTTaaaag g g g 14141414 XML und Webservices 549

Page 276: 382726314 X Php5 In 14 Tagen (Ddt)
Page 277: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seit nt

enmanageme

8

Page 278: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

8.1 Grundlagen in HTML

Formulare sind das wichtigste Element einer Website. Kaum eine Seite kann prak-tisch ohne Eingabeelemente betrieben werden, denn nur darüber kann der Nutzerwirklich Kontakt mit Ihnen aufnehmen.

Viel HTML – wenig PHP

Formulare haben viel mit HTML und wenig mit PHP zu tun. Das ist eigentlicheine der herausragenden Stärken von PHP, denn für den Programmierer ist wenigzu tun, damit man bequem an die Daten herankommt. Freilich müssen Sie sicherim Umgang mit den entsprechenden HTML-Tags sein. Springen Sie zum nächs-ten Abschnitt, wenn es keine Fragen mehr im Umgang mit <input>, <textarea>und <select> gibt. Besteht doch noch die eine oder andere Unsicherheit, lesen Sieunbedingt diesen Abschnitt.

Die HTML-Formularelemente kurz vorgestellt

Dieses Buch soll kein HTML-Buch ersetzen. Eine kompakte Darstellung auf denfolgenden Seiten ist jedoch hilfreich, vor allem wenn man alternativ vor einem1000-Seiten-Schinken eines HTML-Gurus sitzt.

Zuerst eine prinzipielle Aussage: Ein Formular entsteht, indem man die entspre-chenden Elemente in ein <form>-Tag packt. In den vorangegangenen Kapitelnwurde dies bereits häufig getan.

Nun müssen Sie alle Tags kennen, mit denen HTML-Formulare erstellt werdenkönnen. Die folgende Tabelle fasst diese zusammen:

Element Elementtyptype="..."

Beschreibung Wichtige Attribute

input text Einzeiliges Eingabefeld size, value, name

278

checkbox Kontrollkästchen value, checked, name

radio Optionsschaltfläche value, checked, name

submit Sendeschaltfläche value, name

Tabelle 8.1: Formular-Elemente in HTML

Page 279: 382726314 X Php5 In 14 Tagen (Ddt)

Grundlagen in HTML

Die Attribute sind für die Steuerung des Verhaltens erforderlich und haben die inder folgenden Tabelle gezeigte Bedeutung:

reset Schaltfläche zum Rück-setzen

value, name

password Verdecktes Eingabefeld size, value, name

hidden Unsichtbares Feld value, name

button Schaltfläche value, name

image Bild, ersetzt submit und ist damit wie eine Sendeschalt-fläche

src, name, Bildattribute

file Eingabefeld und Schalter zum Hochladen von Dateien

name, accept

select Dropdown-Liste multiple, name, size

option Element der Dropdown-Liste (nur Sub-Element)

value

textarea Mehrzeiliges Textfeld name, rows, cols

Attributname Beschreibung

name Name des Feldes. Unter diesem Namen wird es in PHP erreicht.

size Breite des Feldes, bei select ist dies die Anzahl der sichtbaren Zeilen.

value Der Wert des Felds. Kann aus Sicherheitsgründen bei type="file" und type="password" nicht gesetzt werden.

Element Elementtyptype="..."

Beschreibung Wichtige Attribute

Tabelle 8.1: Formular-Elemente in HTML (Forts.)

279

multiple Erlaubt die Mehrauswahl in Listen (select)

rows Anzahl der Zeilen bei textarea.

cols Anzahl der Spalten bei textarea.

Tabelle 8.2: Attribute der Formular-Elemente (nur formularspezifische wurden aufgeführt)

Page 280: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Daneben können alle Elemente natürlich mit den Standardattributen wie class,style, id usw. bestückt werden.

8.2 Auswerten der Daten aus Formularen

Die Nutzung von Formularen erfolgt immer in einer zweiteiligen Form. Zumeinen wird ein Formular benötigt, das die Namen der Felder und den potenziellenInhalt festlegt. Zum anderen wird ein PHP-Skript erstellt, das die Daten auswertetund beispielsweise in eine Datenbank schreibt. Die Aufnahme der Daten und dieAuswertung umfasst immer einige elementare Schritte.

Grundlegende Schritte

Vor der ersten Auswertung muss klar sein, wohin ein Formular gesendet werdensoll. Dies ist, bestimmt durch das Attribut action des <form>-Tags, natürlich derName eines PHP-Skripts.

Die einfachste Form ist der Selbstaufruf. Dabei ist das Skript, das das Formulardefiniert, selbst das Ziel. Es ist sinnvoll, das Ziel am Anfang des Skripts zu definie-ren:

$action = $_SERVER['PHP_SELF'];

Die Variable $action wird dann im Formular verwendet, beispielsweise bei einermit print oder echo erzeugten Ausgabe direkt, wie nachfolgend gezeigt:

echo <<<FORM <form action="$action" method="post"> … </form>FORM;

In einem reinen HTML-Text bietet sich hierfür die Kurzschreibweise an:

<form action="<?=$action?>" method="post">

280

…</form>

Andere Ziele müssen dagegen als relativer Pfad angegeben werden:

Page 281: 382726314 X Php5 In 14 Tagen (Ddt)

Auswerten der Daten aus Formularen

<form action="skripte/ziel.php" method="post"> …</form>

Den Zustand des Formulars erkennen

Es gibt prinzipiell zwei Zustände, in denen ein Skript aufgerufen werden kann,bestimmt durch die verwendete HTTP-Methode. Um Formulardaten zu versen-den, wird POST verwendet. Um ein Skript direkt auszuführen, wird GET verwen-det. Damit kann man unterscheiden, ob das Skript das erste Mal zur Anzeige desFormulars aufgerufen wurde, oder ob der Benutzer es bereits ausgefüllt und wiederabgesendet hat.

Der erste Schritt bei der Auswertung besteht also darin, überhaupt zu erkennen, obdas Formular einer Auswertung bedarf. Das folgende Beispiel zeigt, wie die Server-variable REQUEST_METHOD dazu verwendet wird:

Listing 8.1: formmethod.php – Erkennen der verwendeten HTTP-Methode

<?php$action = $_SERVER['PHP_SELF'];if ($_SERVER['REQUEST_METHOD'] == 'POST'){ echo 'Formular wurde gesendet';}else{ echo 'Erster Aufruf der Seite';}?><form action="<?=$action?>" method="post"> <input type="submit" value="Klick mich!"/></form>

Die Variable $_SERVER['REQUEST_METHOD'] enthält immer entweder dieZeichenfolge »GET« oder »POST«. Auf diese Weise wird der Aufruf unterschie-

281

den.

Abbildung 8.1: Anzeige des Formulars beim der ersten Start des Skripts

Page 282: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Die Herkunft erkennen

Manchmal ist es wichtig zu wissen, woher ein Formular kommt. Entweder ausSicherheitsgründen oder zur Organisation des Zusammenhangs der einzelnenSkripte einer Applikation muss auf den so genannten »Referer« zugegriffen wer-den. Auch hierfür steht eine Servervariable zur Verfügung: HTTP_REFERER. DieAbfrage ist meist nur sinnvoll, wenn das Formular bereits abgesendet wurde, weilder erste Aufruf eines Skripts auch durch direkte Eingabe der Adresse im Browsererfolgen kann. In diesem Fall existiert jedoch kein Referer und die Variable istleer.

Das folgende Skript kontrolliert den Selbstaufruf und sichert sich dagegen ab, voneiner anderen Quelle aus aufgerufen zu werden.

Listing 8.2: formmethodreferer.php – Die Herkunft eines Aufrufs ermitteln

<?php$action = $_SERVER['PHP_SELF'];if ($_SERVER['REQUEST_METHOD'] == 'POST'){ $ref = $_SERVER['HTTP_REFERER']; $srv = "http://{$_SERVER['SERVER_NAME']}$action"; echo "Formular wurde gesendet von: <br><b>$ref</b><br>an <br><b>$srv</b><hr>"; if (strcmp($srv, $ref) == 0) { echo "Korrekter Aufruf"; } else {

Abbildung 8.2: Anzeige des Formulars, nachdem es mit der Schaltfläche abgesen-det wurde

282

echo "Falscher Aufruf"; }}else{ echo 'Erster Aufruf der Seite';

Page 283: 382726314 X Php5 In 14 Tagen (Ddt)

Auswerten der Daten aus Formularen

}?><form action="<?=$action?>" method="post"> <input type="submit" value="Klick mich!"/></form>

Auch hier wird voll auf die Servervariablen gesetzt:

� HTTP_REFERER

Der Referer als vollständiger URL.

� SERVER_NAME

Der Name des Servers.

� PHP_SELF

Der Pfad zum Skript.

Aus diesen Angaben kann ein Vergleich erstellt werden:

http://<SERVER_NAME><PHP_SELF> == <HTTP_REFERER>

Als Vergleichsfunktion wird strcmp eingesetzt. Die Funktion gibt 0 zurück, wenndie beiden Zeichenketten identisch sind.

Auswertung von Formularen

Nachdem alle Randbedingungen geklärt sind, erfolgt die Auswertung von Formu-laren. PHP macht dies recht einfach. Es stellt alle von einem Formular erfassten

Abbildung 8.3: Testausgabe mit Auswertung des Referers

283

Daten in einem speziellen Array bereit: $_POST. Das Array ist global, kann alsoohne weiteres auch in Klassen und Funktionen benutzt werden. Die Schlüssel desArrays entsprechen dabei exakt den Feldnamen (wie mit name="" festgelegt).

Page 284: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Zustandsabfrage für Felder

Über den Datentyp müssen Sie sich bei Formularen keine Gedanken machen.HTML kennt nur Zeichenketten und als solche kommen alle Daten im Skript an.Umwandlungen müssen dann bei Bedarf explizit vorgenommen werden. Wichti-ger bei der ersten Auswertung ist die Erkennung leerer Felder. PHP erzeugt leereArrayelemente für unausgefüllte Felder. Man kann deshalb immer überprüfen, obein spezifisches Element auch vorhanden ist und die Abfrage danach gestalten.

Am einfachsten erfolgt die Auswertung mit empty. Im Skript sieht das dann folgen-dermaßen aus:

Listing 8.3: formisset.php – Prüfung, ob ein Feld ausgefüllt wurde

<?php$action = $_SERVER['PHP_SELF'];if ($_SERVER['REQUEST_METHOD'] == 'POST'){ if (!empty($_POST['Loginname'])) { echo "LoginName: {$_POST['Loginname']}<br>"; } if (!empty($_POST['Password'])) { echo "Kennwort: {$_POST['Password']}<br>"; }}else{ echo 'Erster Aufruf der Seite';}?><form action="<?=$action?>" method="post"> <table> <tr> <td>Name:</td> <td><input type="text" name="Loginname"/></td>

284

</tr> <tr> <td> Kennwort:</td> <td><input type="text" name="Password"/>

Page 285: 382726314 X Php5 In 14 Tagen (Ddt)

Auswerten der Daten aus Formularen

</td> </tr> </table> <br/> <input type="submit" value="Klick mich!"/></form

Im Beispiel wird der Zustand »Feld gefüllt« benötigt, deshalb erfolgt die Abfragedes Arrayelements mit !empty (Negation). Erst dann wird praktisch auf den Inhaltzugegriffen.

Textfelder

Mit <textarea> erstellte Textfelder verhalten sich prinzipiell erstmal wie normaleEingabefelder. Eine Besonderheit gibt es nur bei der Verarbeitung der Zeilenum-brüche. Der Benutzer kann in diesen Feldern mit der (Enter)-Taste Zeilenumbrü-che eingeben. Diese tauchen im Text des Feldes als Umbruchcode (\n) auf. Beider Ausgabe auf einer HTML-Seite bleibt der Umbruch freilich wirkungslos, weilder Zeilenumbruch in HTML mit <br> gekennzeichnet wird.

Die Funktion nl2br

PHP kennt zur Lösung des Problems die Funktion nl2br (eine Abkürzung für»new line to [gesprochen wie two = 2] break«). Das folgende Beispiel zeigt, wieDaten aus einem Textfeld erfasst und für die erneute Ausgabe aufbereitet werden:

Abbildung 8.4: Abfrage von Formulardaten mit Zustandstest

285

Listing 8.4: formtextarea.php – Korrekte Verarbeitung von Textarea-Feldern

<?php$action = $_SERVER['PHP_SELF'];if ($_SERVER['REQUEST_METHOD'] == 'POST')

Page 286: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

{ if (!empty($_POST['Loginname'])) { echo "LoginName: {$_POST['Loginname']}<br>"; } if (!empty($_POST['Subject'])) { $rawsubject = $_POST['Subject']; $htmsubject = nl2br($rawsubject); echo <<<T1 Text unformatiert: <div style="background:#ccccff"> $rawsubject </div>T1; echo <<<T2 Text wie in Form: <div style="background:#ccccff"> $htmsubject </div>T2; }}else{ echo 'Erster Aufruf der Seite';}?><form action="<?=$action?>" method="post"> <table> <tr> <td>Name:</td> <td><input type="text" name="Loginname"/></td> </tr> <tr> <td> Ihr Anliegen:</td>

286

<td> <textarea name="Subject" cols="30" rows="5"></textarea> </td> </tr> </table>

Page 287: 382726314 X Php5 In 14 Tagen (Ddt)

Auswerten der Daten aus Formularen

<br/> <input type="submit" value="Klick mich!"/></form>

Das Skript verwendet die übliche Auswertung mit empty zum Erkennen leerer Fel-der. Wie die Daten dann aufbereitet werden, hängt von der Anwendung ab. ImBeispiel werden die Daten in beiden Varianten ausgegeben.

Wenn Sie die Felddaten innerhalb von <pre>-Tags anzeigen, dann wer-den die integrierten Zeilenumbrüche ausgegeben. In einem solchenFall wäre die zusätzliche Angabe von <br> fatal, weil sich die Umbrücheverdoppeln würden.

Optionsfelder und Kontrollkästchen

Ebenso wichtig wie die übrigen Feldarten sind auch Optionsfelder und Kontroll-kästchen. Beide sind eng miteinander verwandt und werden ähnlich eingesetzt:

Abbildung 8.5: Verarbeitung von Zeilen-umbrüchen aus einem Textfeld

287

� Optionsfelder (Radiobutton) werden zu Gruppen zusammengefasst und erlau-ben die Auswahl genau einer Option (daher der Name1) aus einer Liste

1 Der englische Name rührt von der Anzeigeform her: Die runden Optionsfelder sehen aus wie runde Knöpfean alten Radios.

Page 288: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

� Kontrollkästchen (Checkbox) sind nicht miteinander verbunden und lasseneine Mehrfachauswahl zu, beispielsweise zur Konfiguration oder Kontrolle(daher der Name2).

Optionsfelder

Optionsfelder werden gruppiert, indem allen Feldern derselbe Name gegebenwird. Da immer nur ein Feld ausgewählt werden kann, übermittel der Browser zudem gemeinsamen Namen den Wert, den man dem Feld vorher zugeordnet hat.Der Wert wird mit dem Attribut value festgelegt. Im $_POST-Array taucht danngenau ein Wert auf.

Es ist möglich, dass kein Feld ausgewählt wurde. Erst wenn der Benutzerein Feld einmal angeklickt hat, kann er den Zustand »nichts ausge-wählt« nicht wieder herstellen. Es ist prinzipiell eine schlechte Benut-zerführung, einen Zustand vorzugeben, der nicht wieder hergestelltwerden kann. Sie sollten deshalb unbedingt den Anfangszustand dedi-ziert setzen.

Das folgende Beispiel zeigt eine Auswahl von mehreren Werten und setzt denAnfangszustand durch das Attribut checked auf den ersten Wert.

Kontrollkästchen

Jedes Kontrollkästchen setzt seinen eigenen Wert, weshalb jedes auch einen eige-nen Namen erhält. Der Wert ist standardmäßig entweder leer oder »on« (aktiviert).Wenn Sie einen anderen Wert benötigen, wird das Attribute value benutzt.Ebenso wie bei den Optionsfeldern dient das Attribute checked dazu, das Kontroll-kästchen bereits beim ersten Aufruf aktiviert erscheinen zu lassen.

Beispiel für Optionsfelder und Kontrollkästchen

Das folgende Beispiel zeigt, wie die Informationen aus Optionsfeldern und Kon-

288

trollkästchen verwendet werden. Beachten Sie vor allem die Benennung der Fel-der in HTML:

2 Der englische Name rührt auch hier von der Anzeigeform her: Kontrollkästchen lassen sich ankreuzen oderauswählen (engl. »to check«).

Page 289: 382726314 X Php5 In 14 Tagen (Ddt)

Auswerten der Daten aus Formularen

Listing 8.5: formcheckradio.php – Umgang mit Kontrollkästchen und Optionsfeldern

<?php$action = $_SERVER['PHP_SELF'];if ($_SERVER['REQUEST_METHOD'] == 'POST'){ if (!empty($_POST['PHPNote'])) { echo "PHP erhält die Note: {$_POST['PHPNote']}<br>"; } if (count($_POST) > 0) { $result = array(); foreach ($_POST as $name => $value) { switch ($name) { case 'Perl': case 'VBScript': case 'PHP': case 'C#': case 'C++': case 'Delphi': case 'Fortran': if ($value == 1) { $result[] = $name; } break; } } echo "Ich kenne folgende Sprachen: " . implode($result, ','); }}else{ echo 'Erster Aufruf der Seite';

289

}?><form action="<?=$action?>" method="post"> <table> <tr>

Page 290: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

<td valign="top">Welche Sprachen kennen Sie?</td> <td valign="top"> <input type="checkbox" name="Perl" value="1"/> Perl <br/> <input type="checkbox" name="VBScript" value="1"/> VBScript <br/> <input type="checkbox" name="PHP" value="1"/> PHP <br/> <input type="checkbox" name="C#" value="1"/> C# <br/> <input type="checkbox" name="C++" value="1"/> C++ <br/> <input type="checkbox" name="Delphi" value="1"/> Delphi <br/> <input type="checkbox" name="Fortran" value="1"/> Fortran <br/> </td> </tr> <tr> <td valign="top"> Wie beurteilen Sie PHP? </td> <td valign="top"> 1 <input type="radio" name="PHPNote" value="1"/> 2 <input type="radio" checked name="PHPNote" value="2"/> 3 <input type="radio" name="PHPNote" value="3"/> 4 <input type="radio" name="PHPNote" value="4"/> 5 <input type="radio" name="PHPNote" value="5"/> 6 <input type="radio" name="PHPNote" value="6"/> </td> </tr> </table> <br/> <input type="submit" value="Klick mich!"/></form>

Dieses Beispiel zeigt bereits einige wichtige Techniken beim Umgang mit Formu-larelementen. Vor allem die Auswertung einer großen Anzahl von Elementenbereitet mitunter Schwierigkeiten.

Beim Optionsfeld wurde der Name »PHPNote« für alle Elemente gewählt. In

290

PHP ist die Auswertung relativ einfach, denn es genügt die Abfrage des Arrayele-ments $_POST['PHPNote']. Im Beispiel wurde durch die Angabe des Attributes che-cked beim Formularelement eine Erstauswahl erzwungen. Damit ist sichergestellt,dass dieses Element immer einen Eintrag im Array erzeugt, denn der Benutzerkann Optionsfelder nicht abwählen. Die folgende Bedingung kann deshalb, theo-retisch, nie FALSE sein:

Page 291: 382726314 X Php5 In 14 Tagen (Ddt)

Auswerten der Daten aus Formularen

if (!empty($_POST['PHPNote']))

Sie wurde dennoch im Skript belassen, um zu zeigen, wie man mit einer leerenAuswahl umgehen muss.

Die Auswertung der Kontrollkästchen bereitet unter Umständen größere Prob-leme. Die Namen lassen sich natürlich alle einzeln auswerten. Bei Änderungenam Code ist die Fehlerquote aber relativ hoch. Das Skript reduziert die Abhängig-keit von den Namen auf eine einzige Stelle. Zuerst wird abgefragt, ob überhauptFelder ausgefüllt wurden:

if (count($_POST) > 0)

Dann werden alle Felder mit einer Schleife durchlaufen. Darin tauchen natürlichauch die auf, die nicht zu den Kontrollkästchen gehören:

foreach ($_POST as $name => $value)

Jetzt werden die benötigten Felder mit einer switch-Anweisung extrahiert. Wurdeein passendes Feld gefunden, wird der Wert ermittelt. Die Zahl 1 ist nur dann vor-handen, wenn das entsprechende Kontrollkästchen aktiviert wurde. Standardmä-ßig wird hier »on« übermittelt, man muss die 1 explizit mit dem HTML-Attributvalue vereinbaren.

if ($value == 1){ $result[] = $name;}

Wenn das entsprechende Kontrollkästchen aktiviert war, wird der Name in dasArray $result geschrieben. Der Trick mit dem Array dient der schnellen Generie-rung einer kommaseparierten Liste. Dies erledigt am Ende, bei der Ausgabe dieimplode-Funktion:

implode($result, ',')

Gegenüber dem systematischen Anhängen erkannter Einträge an die Zeichen-kette, beispielsweise nach dem Schema $result .= $value . ',', vermeidet mandamit das abschließende Komma (siehe Abbildung 8.6).

Der Umgang mit einer großen Anzahl von Kontrollkästchen und ähnlicher Ele-mente verlangt in der Praxis noch nach weiteren Lösungen. Das erste Beispiel in

291

diesem Abschnitt soll deshalb noch etwas verbessert werden. Um die Anzahl derElemente flexibel zu halten, wäre auch eine dynamische Generierung der Kon-trollkästchen wünschenswert. Dazu werden Feldnamen und Typen in einem Arrayfestgelegt. Die Namen enthalten ein Präfix, das die spätere Selektierung erleich-tert. Die Präfixe können etwa diesem Schema folgen:

Page 292: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

� Kontrollkästchen: chk

� Optionsfelder: rdb

� Textfelder: txt

� Textarea: txa

� Dropdown-Listen: drp

� Schaltflächen: btn

Das folgende Skript zeigt, wie das praktisch aussieht:

Listing 8.6: formcheckauto.php – Dynamisch Listen von Feldern ersetzen

$action = $_SERVER['PHP_SELF'];$checkboxes = array ( 'chkPerl' => array('Perl', 'Perl', '1', FALSE), 'chkPHP' => array('PHP', 'PHP', '1', TRUE), 'chkVBS' => array('VBScript', 'Visual Basic Script', '1', TRUE), 'chkCSharp' => array('C#', 'C# (.NET)', '1', FALSE), 'chkCPlusPlus' => array('C++', 'C++ (VSC++)', '1', FALSE), 'chkDelphi' => array('Delphi', 'Delphi (Pascal)', '1', FALSE), 'chkFortran' => array('Fortan', 'Fortran', '1', FALSE));

Abbildung 8.6: Das Formular in Aktion

292

if ($_SERVER['REQUEST_METHOD'] == 'POST'){ foreach ($_POST as $fName => $fValue) { if (substr($fName, 0, 3) == 'chk') {

Page 293: 382726314 X Php5 In 14 Tagen (Ddt)

Auswerten der Daten aus Formularen

echo <<<RESULT Feld $fName hat den Wert <b>{$checkboxes[$fName][1]}</b> <br/>RESULT; } } }else{ echo 'Erster Aufruf der Seite';}?><form action="<?=$action?>" method="post"> <table> <tr> <td valign="top">Welche Sprachen kennen Sie?</td> <td valign="top"> <?php foreach ($checkboxes as $boxName => $boxData) { printf('<input type="checkbox" name="%s" %s value="%s"/ > %s (%s)</br>', $boxName, ($boxData[3]) ? 'checked' : '', $boxData[2], $boxData[0], $boxData[1] ); } ?> </td> </tr> </table> <br/> <input type="submit" value="Klick mich!"/></form>

293

Hier wird zuerst ein Array definiert, das alle Daten enthält, die zur Konstruktionder Kontrollkästchen erforderlich sind. Die Struktur zeigt ein verschachteltesArray. Die erste Ebene enthält als Schlüssel den künftigen Namen des Felds:

$checkboxes = array ('chkPerl' => …

Page 294: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Jedes Kontrollkästchen bekommt dann einen Satz von Eigenschaften als weiteresArray:

… array('Perl', 'Perl', '1', FALSE)

Die Bedeutung der Elemente ist willkürlich gewählt. Im Beispiel gilt folgendes(nach den Indizes):

� [0]: Kurzname

� [1]: Langname oder Beschreibung

� [2]: Wert, der gesendet wird, wenn das Kontrollkästchen aktiviert ist

� [3]: Boolescher Wert, der den Zustand »checked« beim ersten Aufruf bestimmt

Die Generierung der Elemente lässt sich sehr praktisch mit printf erreichen. Hierwird – angeordnet in einer Schleife – der Inhalt des Arrays als Parameter an dieStellen im Feld übergeben, wo sie benötigt werden:

printf('<input type="checkbox" name="%s" %s value="%s"/ > %s (%s)</br>', $boxName, ($boxData[3]) ? 'checked' : '', $boxData[2], $boxData[0], $boxData[1] );

Die Variable $boxData repräsentiert hier das innere Array aus der Definition. DieAuswahl der Elemente erfolgt über die beschriebenen Indizes.

Bei der Auswertung wird die Tatsache ausgenutzt, dass die Feldnamen mit »chk«beginnen. Für die Auswahl sorgt eine if-Anweisung:

if (substr($fName, 0, 3) == 'chk')

Damit funktioniert die ganze Konstruktion auch, wenn weitere Formularelementeim Spiel sind, vorausgesetzt man hält sich konsequent an das Präfix-Schema.

Das hier vorgestellte Schema der automatischen Feldgenerierung wurde

294

exemplarisch für Kontrollkästchen gezeigt. Es gilt in ähnlicher Form füralle Feldelemente und sollte generell zur dynamischen Generierungeingesetzt werden.

Page 295: 382726314 X Php5 In 14 Tagen (Ddt)

Auswerten der Daten aus Formularen

Dropdown-Listen

Zu Dropdown-Listen pflegen Benutzer wie Webmaster eine gewisse Hassliebe. Siebieten den Vorteil, sehr viele vorbereitete Informationen in sehr kompakter Formanzeigen zu können. Formulare mit vielen Dropdown-Listen verbergen aber auchInformationen vor dem Benutzer; sie zwingen dazu, die Liste aufzuklappen, zuscrollen und dann eine Auswahl zu treffen. Statt eines Klicks sind oft zwei odermehr erforderlich. Dies ist unter Umständen lästig. In der Praxis muss man einenguten Kompromiss zwischen dem Volumen des Gesamtformulars und dem sicht-baren Informationsvolumen wahren. Listen mit Hunderten Einträgen sind schwerbeherrschbar und Listen mit nur zwei Einträgen besser durch Optionsfelder dar-stellbar. Die Wahrheit für die optimale Anzahl der Elemente liegt irgendwo zwi-schen drei und zwanzig Optionen.

Aufbau der Dropdown-Listen

Der prinzipielle Aufbau folgt folgendem Muster:

<select [attribute]> <option [attribute]>Angezeigter Text</option></select>

Die Anzahl der Option-Tags ist nicht begrenzt, sollte aber in den bereits erwähn-ten Grenzen liegen, um den Benutzer nicht mit unnützen Klicks zu quälen.

Standardattribute

Die Attribute des Select-Tags verlangen eine genauere Untersuchung, denn dieAngaben können sich auch auf den PHP-Code auswirken, der zur Auswertungbenötigt wird. Am einfachsten ist name, das wie bei allen anderen Tags zur Gene-rierung des Eintrags im $_POST-Array führt. Für die Anzeige entscheidend ist size.Hiermit legen Sie fest, wie viele Elemente gleichzeitig sichtbar sind. Der Standard-wert »1« erzeugt eine echte Klappliste, bei der nur der erste Eintrag sichtbar ist.Jeder andere Wert größer als 1 erzeugt eine Listbox, die die bestimmte Anzahl Ele-

295

mente anzeigt. Sind mehr Optionen vorhanden, wird ein Rollbalken erzeugt. Sindweniger Werte vorhanden, bleiben die Zeilen der Listbox leer (was sehr unprofes-sionell aussieht).

Page 296: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Listing 8.7: formselect.php – Aufbau und Auswertung einer Dropdown-Liste

$action = $_SERVER['PHP_SELF'];if ($_SERVER['REQUEST_METHOD'] == 'POST'){ if (!empty($_POST['Language'])) { echo "Ihre Auswahl: {$_POST['Language']}"; } }else{ echo 'Erster Aufruf der Seite';}?><form action="<?=$action?>" method="post"> <table> <tr> <td valign="top">Welche Sprachen kennen Sie?</td> <td valign="top"> <select name="Language" size="1s"> <option value="Perl">Perl</option> <option value="PHP">PHP</option> <option value="VBScript">VBScript</option> <option value="C++">C++</option> <option value="C#">C#</option> <option value="Delphi">Delphi</option> <option value="Fortran">Fortran</option> </select> </td> </tr> </table> <br/> <input type="submit" value="Klick mich!"/></form>

Die Nutzung der DropDown-Liste weist hier keinerlei Besonderheiten auf. Die

296

Übernahme des Wertes entspricht der Verhaltensweise aller anderen Formularele-mente.

Page 297: 382726314 X Php5 In 14 Tagen (Ddt)

Auswerten der Daten aus Formularen

Mehrfachauswahl

Die Dropdown-Liste kann mit der Angabe des Attributes multiple dazu veranlasstwerden, mehrere Werte gleichzeitig auswählbar zu machen. Wie die Mehrfach-auswahl erfolgt, hängt vom Betriebssystem ab. Unter Windows ist dazu die Steue-rungs-Taste (Strg) zu drücken, während Einträge mit der Maus gewählt werden.Da das Element nach wie vor nur einen Namen besitzt, stellt sich die Frage, wiemehrere Werte in PHP ankommen.

Der Trick besteht darin, PHP die Möglichkeit der Auswahl mehrerer Werte anzu-zeigen. Dazu wird dem Namen im HTML-Element einfach das Arraykennzeichen[] angehängt. PHP verpackt die Werte dann in ein Array, aus dem sie sich leichtwieder entnehmen lassen. Das letzte Beispiel wird dazu im HTML-Teil leicht ver-ändert und im PHP-Abschnitt um die Ausgabe des Arrays erweitert. Außerdemwird die bereits bei den Kontrollkästchen und Optionsfeldern gezeigte Technikder automatischen Generierung gezeigt, die auch bei Optionsfeldern gute Dienstetut:

Listing 8.8: formselectauto.php – Mehrfachauswahl von Listen

<?php$action = $_SERVER['PHP_SELF'];$select[] = array('Perl', 'Perl', FALSE);$select[] = array('PHP', 'PHP', TRUE);$select[] = array('VBScript', 'Visual Basic Script', TRUE);$select[] = array('C#', 'C# (.NET)', FALSE);

Abbildung 8.7: Die Dropdown-Liste in Aktion

297

$select[] = array('C++', 'C++ (VSC++)', FALSE);$select[] = array('Delphi', 'Delphi (Pascal)', FALSE);$select[] = array('Fortan', 'Fortran', FALSE);if ($_SERVER['REQUEST_METHOD'] == 'POST'){

Page 298: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

if (is_array($_POST['Language'])) { echo "Auswahl: " . implode($_POST['Language'], ', '); } }else{ echo 'Erster Aufruf der Seite';}?><form action="<?=$action?>" method="post"> <table> <tr> <td valign="top">Welche Sprachen kennen Sie?</td> <td valign="top"> <select name="Language[]" multiple size="5"> <?php foreach ($select as $option) { printf('<option value="%s" />%s</br>', $option[0], $option[1] ); } ?> </select> </td> </tr> </table> <br/> <input type="submit" value="Klick mich!"/></form>

Dieses Skript enthält zum einen wieder die Generierung der Optionen über eineals Array angelegte Definition, ähnlich wie bei den Kontrollkästchen gezeigt. DasArray enthält die nötigen Angaben zum Options-Wert (value) und dem Text, derdem Benutzer angezeigt wird. Der dritte Wert wird hier noch nicht verwendet; das

298

nächste Beispiel nimmt darauf Bezug. Die Ausgabe des Arrays erfolgt mit einerforeach-Schleife, in der eine printf-Funktion die <option>-Tags sachgerechterzeugt. Wichtig ist bei der Definition des Select-Tags, dass hinter dem Namen dieeckigen Klammern stehen, wenn das Attribut multiple benutzt wird:

<select name="Language[]" multiple size="5">

Page 299: 382726314 X Php5 In 14 Tagen (Ddt)

Auswerten der Daten aus Formularen

Im PHP-Teil geht es nun anders zur Sache. Es ist sinnvoll, statt auf ein leeres Feldauf ein Array zu prüfen:

if (is_array($_POST['Language']))

Das Array wird auch erzeugt, wenn nur ein Element angeklickt wurde. Danachsteht natürlich jede Array-Funktion zur Weiterverarbeitung zur Verfügung. ImBeispiel wird eine kommaseparierte Liste mit implode erzeugt.

Die Größe der Liste wurde übrigens auf 5 gesetzt (size="5"), sodass immer fünfWerte sichtbar sind. Bei einer Mehrfachauswahl ist dies unbedingt zu empfehlen,weil die Bedienung so einfacher ist als bei einer echten Klappliste.

Vorauswahl

Auch mit Listen ist die Vorauswahl möglich und sinnvoll. Dazu wird das <option>-Tag um das Attribut selected erweitert. Das letzte Beispiel enthielt bereits einenBooleschen Wert (der dritte Wert im Array $select), der die Vorauswahl steuernsollte. Eine Erweiterung der printf-Funktion zeigt, wie es geht:

Listing 8.9: formselectautoselect.php – Vorauswahl per Skript steuern

<?php$action = $_SERVER['PHP_SELF'];$select[] = array('Perl', 'Perl', FALSE);$select[] = array('PHP', 'PHP', TRUE);

Abbildung 8.8: Mehrfachauswahl einer automatisch erzeugten Box

299

$select[] = array('VBScript', 'Visual Basic Script', TRUE);$select[] = array('C#', 'C# (.NET)', FALSE);$select[] = array('C++', 'C++ (VSC++)', FALSE);$select[] = array('Delphi', 'Delphi (Pascal)', FALSE);$select[] = array('Fortan', 'Fortran', FALSE);if ($_SERVER['REQUEST_METHOD'] == 'POST')

Page 300: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

{ if (is_array($_POST['Language'])) { echo "Auswahl: " . implode($_POST['Language'], ', '); } }else{ echo 'Erster Aufruf der Seite';}?><form action="<?=$action?>" method="post"> <table> <tr> <td valign="top">Welche Sprachen kennen Sie?</td> <td valign="top"> <select name="Language[]" multiple size="5"> <?php foreach ($select as $option) { printf('<option value="%1$s" %3$s/>%2$s</br>', $option[0], $option[1], $option[2] ? 'selected' : '' ); } ?> </select> </td> </tr> </table> <br/> <input type="submit" value="Klick mich!"/></form>

Der Boolesche Wert wird genutzt, um direkt das entsprechende Attribut zu erzeu-gen:

300

$option[2] ? 'selected' : ''

Zur besseren Übersicht wurden außerdem die Parameter der printf-Funktionnummeriert:

printf('<option value="%1$s" %3$s/>%2$s</br>', …

Page 301: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

Probleme mit klassisch programmierten Formularen

Freilich ist die letzte Lösung nicht optimal, weil bei einem erneuten Aufruf derSeite immer wieder der Ursprungszustand wiederhergestellt wird. Besser ist es,wenn der Anfangs- und der Arbeitszustand getrennt gesteuert werden können.Denn der Benutzer erwartet bei Fehleingaben und ähnlichen Problemen, dassseine Werte erhalten bleiben. Man spricht bei solchen Formularen von so genann-ten »Sticky Forms« oder »klebrigen Formularen«, die so heißen, weil sie die letz-ten Werte behalten. Weil dies eine sehr wichtige Technik ist, wurde ihr eineigener Abschnitt gewidmet.

8.3 Professionelle Formulare und »Sticky Forms«

Professionelle Formulare verlangen nach mehr Funktionen als bislang gezeigtwurde. Die Auswertung der eingegebenen Daten in PHP ist nur eine Seite derMedaille. Die Programmierung muss auch eine gute Benutzerführung erlauben.Dazu gehört, dass dem Benutzer

� sinnvolle Ausfüllhinweise gegeben werden,

� hilfreiche Ausfüllhilfen mit clientseitigen Funktionen angeboten werden,

� er auf Fehler bei der Eingabe hingewiesen wird und

� der Inhalt des Formulars beim erneuten Laden nach Fehlern erhalten bleibt.

Abbildung 8.9: Vorauswahl beim ersten Aufruf der Seite

301

Sinnvolle Ausfüllhinweise sind Teil des Designs. Zu den typischen Vorgabengehört die Markierung von Pflichtfeldern und Informationen über die in Textfel-dern zu platzierenden Daten. Ausfüllhilfen werden meist in JavaScript erstellt undbetreffen beispielsweise die Prüfung von Feldinhalten, das Vorausfüllen auf der

Page 302: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Grundlage anderer Daten oder die Formatierung von Eingaben. Sie helfen, dieFehlerquote bei der Eingabe zu senken, und vereinfachen die serverseitige Prü-fung in PHP. Hinweise auf Eingabefehler sind in letzter Konsequenz immer ser-verseitig zu klären. Dazu gehören aussagekräftige Fehlermeldungen, Hinweise aufden Ort des Fehlers und Wege, diesen zu beseitigen. Nicht zuletzt ist all dies mitder Technik der »Sticky Forms« zu kombinieren, denn der fatalste Entwurfsfehlereiner Seite ist ein Formular, das minutenlang mühevoll ausgefüllt wurde und nachdem Senden und erneuten Laden mit einer Fehlermeldung und ohne die Datenerscheint.

Neben diesen Techniken ist auch die Größe eines Formulars von Bedeutung.Wenn sehr viele Felder erforderlich sind, muss eventuell über ein mehrseitigesFormular nachgedacht werden.

Ausfüllhinweise und Feldvorgaben

Tabellen haben beim Bau von Formularen eine große Bedeutung. Sie dienen vorallem der eindeutigen Platzierung von Feldbeschriftungen und Feldelementen.

Das Formular bauen

Eine der elementaren Techniken beim Formularbau betrifft den Einsatz vonTabellen. Damit Feldbeschriftungen und Felder vernünftig angeordnet erschei-nen, benutzt man zu ihrer Positionierung eine feste Struktur in Form einerTabelle nach folgendem Schema:

In HTML sieht das dann folgendermaßen aus:

Text Feld

Text Feld

...

302

<form action="<?=$action?>" method="post"> <table> <tr> <td>Name:</td> <td><input type="text" name="Loginname"/></td>

Page 303: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

</tr> <tr> <td> Kennwort:</td> <td><input type="text" name="Password"/> </td> </tr> </table> <br/> <input type="submit" value="Klick mich!"/></form>

Hinweistexte platzieren

Als nächstes sollten Hinweistexte platziert werden, um den Benutzer durch dasFormular zu führen. Dies erfolgt meist rechts vom Feld, sodass man bei derTabelle mit einem dreispaltigen Layout arbeitet. Ist das Formular nicht sehr hoch,die Felder aber recht breit, kann man den Text auch unter die Feldnamen oderunter die Felder selbst stellen. Er sollte sich dann aber optisch abheben, beispiels-weise durch eine andere Farbe und eventuell durch eine verkleinerte Schrifthöhe.

Wenn man nun bereits Eingabehilfen in Form von Erklärungen platziert, wärenauch aktive Hilfen interessant. Dabei hilft JavaScript.

Abbildung 8.10: Das perfekte Formular mit einer Tabelle erstellt

Abbildung 8.11: Zum Vergleich: Unprofessionelles Formular ohne Tabelle

303

Ausfüllhilfen mit JavaScript

Es ist grundsätzlich falsch, auf JavaScript zu verzichten. Es ist ebenso grundsätz-lich falsch, allein darauf zu vertrauen. Erst die Kombination aus JavaScript (client-

Page 304: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

seitig) und PHP (serverseitig) erlaubt wirklich gute und professionelle Lösungen.Es ist sicher so, dass dieses Buch sich dem Thema PHP widmet. Bei der Formular-verarbeitung hat JavaScript aber eine ganz bedeutende Stellung und der Verzichtauf eine clientseitige Unterstützung ist einfach nur dumm. Glauben Sie nicht Leu-ten, die die Verwendung von JavaScript verteufeln – die haben das Web nicht ver-standen.

Interaktion zwischen JavaScript und PHP

Die komplette Betrachtung um JavaScript füllt gleichfalls Bücher. DieserAbschnitt zeigt lediglich das Zusammenspiel mit PHP. Informationen über dengenauen Umgang mit allen Möglichkeiten, die JavaScript bietet, sind anderenQuellen vorbehalten. Prinzipiell gibt es zwei Wege der Interaktion:

� PHP steuert JavaScript durch dynamische Code-Erstellung

� JavaScript erzeugt Werte und übergibt sie an PHP

Da JavaScript im Browser abläuft und PHP auf dem Server, muss man sich Gedan-ken über die Schnittstelle machen. Damit PHP die Reaktion von JavaScript beein-flussen kann, ist es möglich, den JavaScript-Code dynamisch zu erstellen. Ist dieSeite aber erstmal an den Browser abgesendet worden, funktioniert das nicht mehr.Es ist deshalb eine mehr oder wenig einseitige Steuerung. Interessant ist es den-noch, denn Sie sollten Ihren JavaScript-Code so steuern, dass Sie auf den Erst- und

Abbildung 8.12: Platzierung von Hinweistexten und Eingabe-hilfen

304

Zweitaufruf des Formulars gegebenenfalls unterschiedlich reagieren. Das ist wich-tig, weil JavaScript von Hause aus nicht unterscheiden kann, aufgrund welcherHTTP-Anforderung die Seite gesendet wurde. Das folgende Beispiel erzeugt eineAnzeigebox in JavaScript, deren Inhalt von der HTTP-Methode gesteuert wird,indem der Anzeigetext in PHP dynamisch ausgewählt wurde:

Page 305: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

Listing 8.10: formjavascriptalert.php – JavaScript per PHP steuern

<html><head> <title>Formulare</title> <script language="JavaScript"> function Box(text) { alert(text); document.logon.submit(); } </script></head><body><?php$action = $_SERVER['PHP_SELF'];$jsText = $_SERVER['REQUEST_METHOD'] == 'POST' ? 'Zweiter Aufruf' : 'Erster Aufruf';?><form action="<?=$action?>" method="post" name="logon"> <br/> <input type="button" value="Klick mich!" onClick="Box('<?=$jsText?>');"/></form></body></html>

Die in diesem Skript gezeigten Techniken wiederholen sich in der einen oderanderen Form in allen anderen Varianten. Sie sollten das Prinzip vollkommen ver-standen haben, bevor Sie eigene Skripte entwerfen.

Zuerst wird hier mit einer JavaScript-Funktion gearbeitet, die im Kopf der Seitedefiniert wird:

<script language="JavaScript"> function Box(text) { alert(text);

305

document.logon.submit(); } </script>

Page 306: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Die Funktion erzeugt eine Ausgabebox (alert), die vor allem zur Demonstration,Fehlersuche und für einfache Meldungen an den Benutzer geeignet ist. Entschei-dender ist die zweite Zeile:

document.logon.submit();

Hiermit wird das Formular dazu veranlasst, sich abzusenden. Das entspricht demAnklicken einer »Submit«-Schaltfläche. Es spielt dabei keine Rolle, ob eine solchevorhanden ist oder nicht. In PHP können Sie übrigens nicht direkt unterscheiden,ob der Benutzer auf »Submit« geklickt hat oder das Skript das Absenden über-nahm. JavaScript arbeitet mit dem so genannten Document Object Model (DOM)der HTML-Seite. Dieses erlaubt eine objektorientierte Darstellung. document ver-weist auf das Dokument. Eines der darin definierten Elemente heißt »logon« – derName des Formulars. Der wurde hier willkürlich folgendermaßen festgelegt:

<form action="<?=$action?>" method="post" name="logon">

Das Formular wiederum kennt eine Methode submit(), das den Sendevorgang auslöst.

Nun fehlt noch die Steuerung der Funktion. Das Absenden erfolgt durch eineSchaltfläche, die selbst keine Funktion in HTML hat. Sie verfügt aber über einonClick-Ereignis, dass auf einen einfachen Mausklick reagiert:

<input type="button" value="Klick mich!" onClick="..."/>

Hier wird nun der Aufruf der bereits definierten Methode hineingepackt:

Box('…');

Und der Parameter der Methode wird seinerseits mit dem von PHP erzeugen Textgefüllt:

<?=$jsText?>

Alles zusammen sieht recht verwirrend aus, ist aber sinnvoll:

<input type="button" value="Klick mich!" onClick="Box('<?=$jsText?>');"/>

Zuletzt muss man sich noch Gedanken über die verwendete Variable $jsTextmachen, die in PHP erzeugt wird:

306

$jsText = $_SERVER['REQUEST_METHOD'] == 'POST' ? 'Zweiter Aufruf' : 'Erster Aufruf';

Das ablaufende Programm produziert nun zwei verschiedene Anzeigeboxen, jenachdem, ob es das erste oder zweite Mal aufgerufen wurde.

Page 307: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

Clientseitig auf Eingaben reagieren

Da es in JavaScript einen Zugriff auf das Formular gibt, kann auch auf alle Felderzugegriffen werden. Das ist hilfreich, damit Formulare mit offensichtlichen Feh-lern gar nicht erst gesendet werden. Das folgende Beispiel zeigt, wie bei einemFeld geprüft wird, ob ein Text darin steht. Diese Prüfung ist für Pflichtfelder sinn-voll. JavaScript spart das Zurücksenden des Formulars zum Server und damit demBenutzer Zeit und Bandbreite. Natürlich muss jede clientseitige Prüfung mit einerserverseitigen kombiniert werden, damit die Bedienung auch dann möglich ist,wenn JavaScript nicht aktiviert ist.

Listing 8.11: formjavascriptmand.php – Eingabeprüfungen mit JavaScript

<html><head> <title>Formulare</title> <script language="JavaScript"> function Check() { var ok = true; if (document.logon.Loginname.value == '') { alert ('Login Name wurde nicht angegeben'); ok = false; } if (document.logon.Password.value == '') { alert ('Kennwort wurde nicht angegeben');

Abbildung 8.13: Diese Box erscheint beim ersten (links) und beim zwei-ten (und folgenden) Klick (rechts)auf die Schaltfläche

307

ok = false; } if (ok) { document.logon.submit(); }

Page 308: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

} </script></head><body><?php$action = $_SERVER['PHP_SELF'];if ($_SERVER['REQUEST_METHOD']){ if (empty($_POST['Loginname'])) { echo "Login Name nicht ausgefüllt<br>"; } if (empty($_POST['Password'])) { echo "Kennwort nicht ausgefüllt<br>"; }}?><form action="<?=$action?>" method="post" name="logon"> <table> <tr> <td>Name:</td> <td><input type="text" name="Loginname"/></td> <td valign="top"><span style="color:red"*</span></td> </tr> <tr> <td valign="top"> Kennwort:</td> <td> <input type="password" name="Password"/><br/> <font size="-1">(Verdeckte Eingabe)</font> </td> <td valign="top"><span style="color:red"*</span></td> </tr> </table> <br/> <input type="button" value="Klick mich!" onClick="Check();"/>

308

</form></body></html>

Die JavaScript-Funktion Check enthält die clientseitige Prüfung. Der Feldzugriffsieht folgendermaßen aus:

Page 309: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

if (document.logon.Loginname.value == '')

document.logon verweist wieder auf das Formular, Loginname ist der Name desFeldes und value eine Eigenschaft, die den momentanen Wert zurückgibt. DerRest ist Standardprogrammierung und wird nur durch die eigene Fantasiebegrenzt.

Der PHP-Abschnitt wiederholt diese Prüfung, weil Sie nie darauf vertrauen dürfen,dass jeder Browser auch tatsächlich JavaScript ausführt.

Was nun noch fehlt, ist eine vernünftige Anzeige der Fehlertexte im Formular.Das ist ein Fall für CSS und natürlich JavaScript. Dabei muss man als Mittel zurFehlerausgabe nicht nur JavaScript sehen. Auch die in PHP erzeugten Fehlerin-formationen werden hier eingebunden.

Fehlerangaben

Fehlerangaben sind ein wichtiges Instrument der Benutzerführung. Dazu gibt esmehrere Techniken, die die Kombination aus JavaScript und PHP erfordern.

Eine wichtige Frage ist die Platzierung der Fehlertexte. Eine optimale Benutzer-führung verlangt, dass ein direkter Bezug zwischen Fehlerquelle und Fehlertexthergestellt werden muss. Dazu ist die Fehlerausgabe direkt neben (oder unter)dem Feld zu platzieren, das den Fehler verursachte. An zentraler Stelle – vorzugs-weise oberhalb des Formulars (damit der Fehler ohne Rollen sichtbar ist) – wirdeine Zusammenfassung aller Fehler gegeben. Wichtig ist auch, alle Fehler gleich-zeitig zur Anzeige zu bringen und damit zu verhindern, dass der Benutzer sich mitdem Trial-and-Error-Prinzip durch die Reaktionen des Formulars klickt. Falls dasLayout keinen Platz für detaillierte Meldungen neben den Feldern liefert, kannman mit Fußnoten arbeiten. Dabei wird eine Fehlernummer neben dem betroffe-nen Feld eingeblendet und unterhalb des Formulars eine ausführliche Erläute-rung geboten.

Techniken mit Style-Definitionen

309

Eine Problematik, die fast jedes Formular betrifft, ist die Platzierung der Fehler-texte. Generell benötigen Fehlertexte Platz. Wird dieser von vornherein freigehal-ten, wirkt ein Formular möglicherweise recht »locker«, was dem Layout der Seitewidersprechen kann. Nimmt man auf Fehler dagegen keine Rücksicht und blen-det sie einfach in einem fertigen Layout ein, kann die Seite »zerfallen«.

Page 310: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Was genau zu machen ist, hängt von der konkreten Situation ab. Häufig bietet essich an, den Platz der Fehlermeldung zu reservieren, indem man den Text schonbeim ersten Aufruf des Formulars auf die Seite schreibt. Dort wird er mit der Stil-Anweisung visibility:hidden unsichtbar gemacht. Dieser Stil wird in erster Instanzdurch JavaScript oder – wenn JavaScript nicht funktioniert – durch PHP ersetzt.Das folgende Skript zeigt, wie das funktioniert.

Listing 8.12: formjavascriptdisplay.php – Nutzung von CSS für die Fehlerausgabe

<html><head> <title>Formulare</title> <script language="JavaScript"> function Check() { var ok = true; if (document.logon.Loginname.value == '') { document.getElementById('errName').style.visibility � = "visible"; ok = false; } if (document.logon.Password.value == '') { document.getElementById('errPass').style.visibility � = "visible"; ok = false; } if (ok) { document.logon.submit(); } document.logon.submit(); } </script></head>

310

<body><?php$action = $_SERVER['PHP_SELF'];if ($_SERVER['REQUEST_METHOD'] == 'POST'){ if (empty($_POST['Loginname']))

Page 311: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

{ $displayName = 'visible'; } else { $displayName = 'hidden'; } if (empty($_POST['Password'])) { $displayPass = 'visible'; } else { $displayPass = 'hidden'; }} else { $displayName = 'hidden'; $displayPass = 'hidden';}?><form action="<?=$action?>" method="post" name="logon"> <table> <tr> <td>Name:</td> <td><input type="text" name="Loginname"/></td> <td valign="top"><span style="color:red">*</span></td> <td valign="top"><span id="errName" style="color:red;visibility:<?=$displayName?>"> Login Name wurde nicht angegeben </span></td> </tr> <tr> <td valign="top"> Kennwort:</td> <td>

311

<input type="password" name="Password"/><br/> <font size="-1">(Verdeckte Eingabe)</font> </td> <td valign="top"><span style="color:red">*</span></td> <td valign="top"><span id="errPass"

Page 312: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

style="color:red;visibility:<?=$displayPass?>"> Kennwort wurde nicht angegeben </span></td> </tr> </table> <br/> <input type="button" value="Klick mich!" onClick="Check();"/></form></body></html>

Dieses Skript erledigt alle dynamischen Darstellaufgaben hervorragend. Betrach-ten Sie zuerst den PHP-Abschnitt. Darin werden zwei Variablen festgelegt, die dieVorauswahl der Sichtbarkeit der Fehlertexte festlegen, $displayName und $dis-playPass. Beim ersten Aufruf, REQUEST_METHOD ist gleich GET, werdennatürlich keine Fehler angezeigt. Schaut man in den generierten HTML-Quell-text, sehen die Span-Tags, die die Fehlertexte umschließen, folgendermaßen aus:

<span id="errName" style="color:red;visibility:hidden">

Neben der Steuerung der Sichtbarkeit mit visibility:hidden wird dem Tag auch dasid-Attribut mitgegeben. Dies ist notwendig, um per JavaScript darauf zugreifen zukönnen. Die JavaScript-Prüfung ist dann auch der erste Versuch, den Fehlerzu-stand zu erkennen. Wie bereits beim letzten Beispiel gezeigt, führt ein Klick aufdie Schaltfläche zu einer Prüffunktion:

onClick="Check();"

Diese wiederum greift in der schon bekannten Weise auf die Felder zu und über-prüft deren Inhalt. Ist eines der Fehler leer, wird nun einfach der Fehlertext sicht-bar gemacht, indem die Voreinstellung überschrieben wird:

document.getElementById('errName').style.visibility = "visible";

getElementById ist eine Methode, die das Element mit dem betreffenden Namenbeschafft und bereitstellt. Für die Nutzung dieser Methode wurde das id-Attributbenötigt. Von dem betroffenen Element wird nun die Style-Auflistung ermittelt(.style) und aus dieser wiederum das Attribut .visibility. Dann ist nur noch der ent-sprechende Wert zu setzen, der den Angaben entspricht, wie man sie direkt mit

312

HTML machen würde (»hidden« oder »visible«).

War die Prüfung erfolgreich, wird das Formular gesendet.

Page 313: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

JavaScript geht nicht...

Wenn JavaScript ganz abgeschaltet ist, funktioniert die letzte Variante noch nichtzufrieden stellend, weil das Absenden der Form überhaupt nicht stattfindet. Einekleine Modifikation hilft, das Problem zu umgehen.

Listing 8.13: formjavascriptdisplaymod.php – Komplexe Verarbeitung von Eingaben mit JavaScript und CSS

<html><head> <title>Formulare</title> <script language="JavaScript"> function Check() { var ok = true; if (document.logon.Loginname.value == '') { document.getElementById('errName').style.visibility = "visible"; ok = false; } if (document.logon.Password.value == '') { document.getElementById('errPass').style.visibility = "visible"; ok = false; } return ok; } </script></head><body><?php$action = $_SERVER['PHP_SELF'];if ($_SERVER['REQUEST_METHOD'] == 'POST'){

313

if (empty($_POST['Loginname'])) { $displayName = 'visible'; } else

Page 314: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

{ $displayName = 'hidden'; } if (empty($_POST['Password'])) { $displayPass = 'visible'; } else { $displayPass = 'hidden'; }} else { $displayName = 'hidden'; $displayPass = 'hidden';}?><form action="<?=$action?>" method="post" name="logon"> <table> <tr> <td>Name:</td> <td><input type="text" name="Loginname"/></td> <td valign="top"><span style="color:red">*</span></td> <td valign="top"><span id="errName" style="color:red;visibility:<?=$displayName?>"> Login Name wurde nicht angegeben</span></td> </tr> <tr> <td valign="top"> Kennwort:</td> <td> <input type="password" name="Password"/><br/> <font size="-1">(Verdeckte Eingabe)</font> </td> <td valign="top"><span style="color:red">*</span></td> <td valign="top"><span id="errPass"

314

style="color:red;visibility:<?=$displayPass?>"> Kennwort wurde nicht angegeben</span></td> </tr> </table> <br/>

Page 315: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

<input type="submit" value="Klick mich!" onClick="return Check();"/></form></body></html>

Hier wurde nun die Schaltfläche wieder durch die originäre Variante type="sub-mit" ersetzt. Ohne JavaScript wird das onClick-Ereignis nicht erkannt und das For-mular sofort gesendet. Die Prüfung in PHP wird dann aktiviert und die Variableentsprechend gesetzt:

$displayName = 'visible';

Die JavaScript-Funktion Check wurde auch ein wenig geändert. Sie gibt nuneinen Booleschen Wert zurück, der das Ergebnis der Prüfung bestimmt:

return ok;

Dieses Ergebnis wird mit einem weiteren return an das Klickereignis übergebenund bestimmt dessen Rückgabewert. Ist der Wert false, wird der Klick nicht aus-geführt, ist er true, wird er ausgeführt.

In der Kombination der Maßnahmen erhält man das (fast) perfekte Formular:

� Ist JavaScript an, wird die Prüfung im Client durchgeführt

� Ohne JavaScript funktioniert es auch, dann kommt PHP zum Zuge

� Fehlermeldungen werden in jedem Fall dynamisch erzeugt und der Platz istimmer reserviert

Was jetzt noch fehlt ist der Erhalt der Werte im Fehlerfall. Dazu wird eine als»Sticky Forms« bezeichnete Technik verwendet.

Sticky Forms

Sticky Forms sind eigentlich nur ein simpler Programmierstil. Formulare gewin-nen dadurch jedoch signifikant an Professionalität und Benutzerfreundlichkeit.Das Prinzip ist einfach. Man nutzt die im POST-Zyklus erkannten Werte, um die

315

value-Attribute der Felder mit den alten Werten zu belegen.

Die JavaScript-Abschnitte aus den letzten Beispielen wurden in diesem Abschnittnicht erneut in die Beispiele eingebaut, weil bei einer rein clientseitigen Prüfungdie Werte ohnehin erhalten bleiben und die hier beschriebenen Techniken nicht

Page 316: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

sinnvoll einsetzbar sind. Für ein optimales »Fallback«, also die volle Funktionauch ohne JavaScript, sind jedoch beide Maßnahmen erforderlich.

Textfelder

Bei Eingabefeldern für Text ist dies besonders einfach. Das letzte Beispiel lässt sichleicht erweitern, um im Fehlerfall nun auch die Inhalte zu erhalten.

Listing 8.14: formstickytext.php – Fehlerprüfung mit Werterhalt in allen Feldern

<?php$action = $_SERVER['PHP_SELF'];if ($_SERVER['REQUEST_METHOD'] == 'POST'){ $Loginname = !empty($_POST['Loginname']) ? $_POST['Loginname'] : ''; $Password = !empty($_POST['Password']) ? $_POST['Password'] : ''; if (empty($_POST['Loginname'])) { $displayName = 'visible'; } else { $displayName = 'hidden'; } if (empty($_POST['Password'])) { $displayPass = 'visible'; } else { $displayPass = 'hidden'; }} else

316

{ $Loginname = ''; $Password = ''; $displayName = 'hidden'; $displayPass = 'hidden';

Page 317: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

}?><form action="<?=$action?>" method="post" name="logon"> <table> <tr> <td>Name:</td> <td><input type="text" name="Loginname" value="<?=$Loginname?>"/></td> <td valign="top"><span style="color:red">*</span></td> <td valign="top"><span id="errName" style="color:red;visibility:<?=$displayName?>"> Login Name wurde nicht angegeben</span></td> </tr> <tr> <td valign="top"> Kennwort:</td> <td> <input type="password" name="Password" value="<?=$Password?>"/><br/> <font size="-1">(Verdeckte Eingabe)</font> </td> <td valign="top"><span style="color:red">*</span></td> <td valign="top"><span id="errPass" style="color:red;visibility:<?=$displayPass?>"> Kennwort wurde nicht angegeben</span></td> </tr> </table> <br/> <input type="submit" value="Klick mich!" /></form>

Interessant ist hier der Abschnitt, in dem die Werte aus dem $_POST-Array über-nommen werden. Die hier erzeugten Variablen werden später verwendet:

$Loginname = !empty($_POST['Loginname']) ? $_POST['Loginname'] : '';$Password = !empty($_POST['Password']) ? $_POST['Password'] : '';

317

Der Einsatz der Werte erfolgt nun in den value-Attributen der Felder:

<input type="text" name="Loginname" value="<?=$Loginname?>"/><input type="password" name="Password" value="<?=$Password?>"/>

Page 318: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Die Kombination aus dynamischer Fehlererzeugung und Werterhaltung machtaus einem einfachen HTML-Formular durch den Einsatz von PHP und JavaScripteine hochwertige, benutzerfreundliche Lösung.

Textarea-Tags werden übrigens ebenso behandelt, nur wird hier statt des Attributesvalue der Wert zwischen die Tags geschrieben:

<textarea ...><?=$Wert?></textarea>

Optionsfelder und Kontrollkästchen

Optionsfelder und Kontrollkästchen unterscheiden sich kaum von der Behandlungder Textfelder. Statt des Wertes wird hier das Attribut checked gesetzt. Wenn mansich für die dynamische Erzeugung der Felder per Skript entschieden hat, ist dieUmsetzung nicht schwer. Das folgende Beispiel zeigt, wie es geht:

Listing 8.15: formstickycheckauto.php – Startwerte festlegen und Auswahlzustand bei einer dynamisch erzeugten Auswahl von Kontrollkästchen erhalten

<?phpfunction IsChecked($boxName, $boxData){ if ($_SERVER['REQUEST_METHOD'] == 'POST') { if (!empty($_POST[$boxName])) { return 'checked'; }

Abbildung 8.14: Erhalt der Werte, obwohl Fehler auftraten

318

} else { return ($boxData[3]) ? 'checked' : ''; } return '';

Page 319: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

}$action = $_SERVER['PHP_SELF'];$checkboxes = array ( 'chkPerl' => array('Perl', 'Perl', '1', FALSE), 'chkPHP' => array('PHP', 'PHP', '1', TRUE), 'chkVBS' => array('VBScript', 'Visual Basic Script', '1', TRUE), 'chkCSharp' => array('C#', 'C# (.NET)', '1', FALSE), 'chkCPlusPlus' => array('C++', 'C++ (VSC++)', '1', FALSE), 'chkDelphi' => array('Delphi', 'Delphi (Pascal)', '1', FALSE), 'chkFortran' => array('Fortan', 'Fortran', '1', FALSE));if ($_SERVER['REQUEST_METHOD'] == 'POST'){ foreach ($_POST as $fName => $fValue) { if (substr($fName, 0, 3) == 'chk') { echo <<<RESULT Feld $fName hat den Wert <b>{$checkboxes[$fName][1]}</b> <br/>RESULT; } } }else{ echo 'Erster Aufruf der Seite';}?><form action="<?=$action?>" method="post"> <table> <tr> <td valign="top">Welche Sprachen kennen Sie?</td> <td valign="top"> <?php foreach ($checkboxes as $boxName => $boxData) {

319

printf('<input type="checkbox" name="%s" %s value="%s"/ > %s (%s)</br>', $boxName, IsChecked($boxName, $boxData), $boxData[2],

Page 320: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

$boxData[0], $boxData[1] ); } ?> </td> </tr> </table> <br/> <input type="submit" value="Klick mich!"/></form>

Das Geheimnis liegt hier im Aufruf von IsChecked innerhalb der Ausgabefunktionprintf:

IsChecked($boxName, $boxData)

Übergeben werden der Name der aktuellen Box und die Daten, die im Array $box-Data stehen. Diese Daten bestimmen das Aussehen des jeweiligen Kontrollkäst-chens. Die eigentliche Arbeit wird nun in IsChecked erledigt. Beim Erstaufruf (GET)wird die Vorgabe aus dem Array benutzt, um eine Voreinstellung zu erreichen.

return ($boxData[3]) ? 'checked' : '';

Im POST-Zyklus wird dagegen der Zustand der gesendeten Felder mit dem geradein Bearbeitung befindlichen verglichen:

if (!empty($_POST[$boxName]))

Ist da ein Wert drin, dann gilt das Feld als gesetzt und die Zeichenkette »checked«wird zurückgegeben.

Optionsfelder werden mit demselben Attribut gesetzt, nur muss man hier die Mehr-fachauswahl verhindern, was die Sache aber im Prinzip noch weiter vereinfacht.

Listfelder

Listen aller Art sind etwas schwerer zu beherrschen. Der Erhalt des Wertes erfolgthier über das Setzen von selected-Attributen in den Option-Tags. Da dies jedes Tag

320

betreffen kann, muss man quasi über eine ausreichende Anzahl Variablen für jedeOption verfügen.

Page 321: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

Listing 8.16: formstickyselect.php – Einfache Listbox, die den gewählten Wert erhält

<?phpfunction CheckSelected($option){ if (!empty($_POST['Language'])) { if (is_array($_POST['Language'])) { if ($_POST['Language'] == $option[0]) { return 'selected'; } } } return '';}$action = $_SERVER['PHP_SELF'];$select[] = array('Perl', 'Perl', FALSE);$select[] = array('PHP', 'PHP', TRUE);$select[] = array('VBScript', 'Visual Basic Script', TRUE);$select[] = array('C#', 'C# (.NET)', FALSE);$select[] = array('C++', 'C++ (VSC++)', FALSE);$select[] = array('Delphi', 'Delphi (Pascal)', FALSE);$select[] = array('Fortan', 'Fortran', FALSE);if ($_SERVER['REQUEST_METHOD'] == 'POST'){ if (!empty($_POST['Language']) && is_array($_POST['Language'])) { echo "Auswahl: " . implode($_POST['Language'], ', '); } }else{ echo 'Erster Aufruf der Seite';}?>

321

<form action="<?=$action?>" method="post"> <table> <tr> <td valign="top">Welche Sprachen kennen Sie?</td> <td valign="top">

Page 322: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

<select name="Language" size="5"> <?php foreach ($select as $option) { printf('<option value="%1$s" %3$s />%2$s</br>', $option[0], $option[1], CheckSelected($option) ); } ?> </select> </td> </tr> </table> <br/> <input type="submit" value="Klick mich!"/></form>

Kern der Funktion ist die bereits bekannte Methode der Generierung der Wertemit einem Array. Dies vermeidet die sonst erforderliche Prüfung jedes einzelnenWertes und erleichtert die Lösung erheblich. Bei der Generierung jeder Optionwird nun eine Funktion aufgerufen, die den momentanen Zustand feststellt:

CheckSelected($option)

In dieser Funktion wird nun der Zustand des $_POST-Arrays geprüft und mit demaktuellen Optionswert verglichen:

if ($_POST['Language'] == $option[0])

Im Falle einer Übereinstimmung wird das erforderliche Attribute selected zurück-gegeben:

return 'selected';

Die printf-Funktion sorgt dann für die Anzeige.

Listfelder mit Mehrfachauswahl

322

Wenn die Mehrfachauswahl mit dem Attribut multiple ermöglicht wurde, wird dieSache scheinbar komplizierter. Das folgende Skript zeigt, wie es dennoch einfachgelöst werden kann:

Page 323: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

Listing 8.17: formstickyselectmulti.php – Mehrfachauswahl einer Liste mit automati-schem Erhalt der Werte

<?phpfunction CheckSelected($option){ if (!empty($_POST['Language'])) { if (is_array($_POST['Language'])) { if (in_array($option[0], $_POST['Language'])) { return 'selected'; } } } return '';}$action = $_SERVER['PHP_SELF'];$select[] = array('Perl', 'Perl', FALSE);$select[] = array('PHP', 'PHP', TRUE);$select[] = array('VBScript', 'Visual Basic Script', TRUE);$select[] = array('C#', 'C# (.NET)', FALSE);$select[] = array('C++', 'C++ (VSC++)', FALSE);$select[] = array('Delphi', 'Delphi (Pascal)', FALSE);$select[] = array('Fortan', 'Fortran', FALSE);if ($_SERVER['REQUEST_METHOD'] == 'POST'){ if (!empty($_POST['Language']) && is_array($_POST['Language'])) { echo "Auswahl: " . implode($_POST['Language'], ', '); } }else{ echo 'Erster Aufruf der Seite';}

323

?><form action="<?=$action?>" method="post"> <table> <tr> <td valign="top">Welche Sprachen kennen Sie?</td>

Page 324: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

<td valign="top"> <select name="Language[]" multiple size="5"> <?php foreach ($select as $option) { printf('<option value="%1$s" %3$s />%2$s</br>', $option[0], $option[1], CheckSelected($option) ); } ?> </select> </td> </tr> </table> <br/> <input type="submit" value="Klick mich!"/></form>

Hier wird zuerst die Mehrfachauswahl mit multiple zugelassen, neben den Array-kennzeichen im Namen natürlich:

<select name="Language[]" multiple size="5">

Dann muss die Auswertung in der Funktion angepasst werden. Schließlich ist nunstatt eines skalaren Wertes ein Array zu durchsuchen:

if (in_array($option[0], $_POST['Language']))

Die Funktion in_array leistet hier die passenden Dienste.

Abbildung 8.15:

324

Betrachtet man Aufwand und Nutzen, ist es immer wieder erstaunlich, warumderart viele technisch schlecht erstellte Formulare im Internet zu finden sind.

Erhalt der Werte einer Listbox mit Mehr-fachauswahl

Page 325: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

Mehrseitige Formulare mit versteckten Feldern

Mehrseitige Formulare sind immer dann sinnvoll, wenn die Anzahl der Felder zugroß ist, um ohne heftiges Umherscrollen gelesen werden zu können. Nun stelltsich natürlich die Frage, wie man von einer Seite zu nächsten gelangt, ohne dassdie Daten der vorhergehenden Seiten verloren gehen. Es gibt hier mehrereLösungsansätze, die je nach Situation den einen oder anderen Vorteil bringen.

Prinzipien und Forderungen

Vor der Erstellung der Formulare muss man sich klar machen, dass ein Zurück-blättern vom Browser mehr oder weniger wirkungsvoll verhindert wird. Es ist wich-tig, dass die Fehlerprüfungen auf jeder Seite stattfinden und nicht erst am Ende,weil ein gezielter Rückschritt sehr aufwändig zu programmieren ist und demBenutzer keinen wirklichen Vorteil bringt.

Das Zurückblättern ist ohnehin eine aufwändige Angelegenheit. Für den Benutzerist es unter Umständen jedoch durchaus hilfreich, wenn er sich in einem mehrsei-tigen Formular frei bewegen kann. Typischerweise werden dann oberhalb des For-mulars Fortschrittsanzeigen platziert, die Auskunft über den aktuellen Standgeben. So etwas ist vertrauensbildend und sorgt für einen unverkrampften Umgangmit den Seiten, was mithin den Erfolg steigert, dass ein Benutzern auch am Endeankommt.

Wie bereits bei den bisherigen Beispielen, macht erst eine geschickte Kombina-tion aus Styles, JavaScript, HTML und PHP wirklich das aus, was ein Benutzer alsbrauchbar empfindet. Wie es geht, soll anhand eines längeren Beispiels erläutertwerden. Es basiert im Wesentlichen auf der Nutzung versteckter Felder.

Versteckte Felder

HTML erlaubt die Verwendung von Feldern mit dem Typ »hidden«. Eine Anzei-geform hat dieser Typ nicht, das Feld ist versteckt. Im Quelltext der Seite bleibt esfreilich sichtbar, aus Sicherheitsgründen ist der Einsatz sinnlos. Versteckte Felder

325

erlauben es aber, die aktuell erfassten Werte mitzunehmen und damit auf dernächsten Seite im Kontext der Seite zu sehen, ohne dass dies zusätzlichen Pro-grammieraufwand verursacht. Denn die Inhalte versteckter Felder sind so wiejedes andere Formularfeld Teil des $_POST-Arrays.

Page 326: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Die Definition in HTML sieht folgendermaßen aus:

<input type="hidden" name="name" value="wert"

Die Angabe der Attribute name und value ist hier praktisch zwingend, weil ohnediese Daten die Auswertung sinnlos wäre.

Strategie zum Entwurf mehrseitiger Formulare

Bei der Programmierung von Formularen kann man mehrere Strategien verfolgen.Eine besteht darin, für jede Seite tatsächlich ein eigenes Skript zu programmieren.Das ist nett, aber nicht einfach, weil man beim Springen vom dritten zum erstenFormular alle Werte mitnehmen muss und es viele solche Sprungvarianten gibt.Besser ist es, alle Formulare auf ein und dieselbe Seite zu Schreiben. Der jeweilssichtbare Teil wird mit CSS-Attributen eingeblendet. Aktive Felder werden normaldefiniert, unaktive durch versteckte Felder realisiert. Verwendet man dann nochdie bereits bekannte Technik der »Sticky Forms« erhält man schnell eine wir-kungsvolle Umsetzung eines mehrseitige Formulars.

Beispiel: Mehrseitige Anmeldeprozedur

Eine mehrseitige Anmeldeprozedur kann beispielsweise folgende Schritte umfas-sen:

1. Persönliche Daten (Name, Anschrift)

2. Bestätigung, dass die AGB gelesen wurden (Kontrollkästchen)

3. Angabe von Anmeldename und Kennwort

4. Zusammenfassung aller Daten und Absenden

Oberhalb aller Formulare soll ein Fortschrittsbalken stehen, der den jeweiligenStand anzeigt und darüber informiert, wie viele Schritte noch vor einem liegen.

Listing 8.18: formmultipage.php – Ein mehrseitiges Formular auf einer Seite

326

<html><head> <title>Formulare</title> <style> * { font-family: Verdana; } .activeNumber

Page 327: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

{ color: red; background-color:#ddddff; text-align:center; font-size: 14pt; } .inactiveNumber { color: #cccccc; background-color:#ddddff; text-align:center; font-size: 14pt; } .activeTable { display:visible; height:100px; width:500px; } .inactiveTable { display:none } </style></head><body><?phpfunction GetField($name, $default = ''){ return empty($_POST[$name]) ? $default : $_POST[$name];}$action = $_SERVER['PHP_SELF'];$currentPage = empty($_POST['currentPage']) ? 1 : (int) $_POST['currentPage'];$prevDisabled = $nextDisabled = '';if (!empty($_POST['prev'])){

327

$currentPage--; if ($currentPage == 1) { $prevDisabled = 'disabled'; }

Page 328: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

}if (!empty($_POST['next'])){ $currentPage++; if ($currentPage == 4) { $nextDisabled = 'disabled'; } }$fldName = GetField('Name');$fldAddress = GetField('Address');$fldZip = GetField('Zip');$fldCity = GetField('City');$fldConditions = GetField('Conditions');$fldLogonName = GetField('LogonName');$fldPassword = GetField('Password');$fldNews = GetField('News');if (!empty($_POST['Send'])){ $currentPage = 0; echo "Vielen Dank für das Ausfüllen des Formulars"; exit;}?><table width="500"> <tr> <td class="<?= ($currentPage==1) ? 'activeNumber' : 'inactiveNumber' ?>">1</td> <td class="<?= ($currentPage==2) ? 'activeNumber' : 'inactiveNumber' ?>">2</td> <td class="<?= ($currentPage==3) ? 'activeNumber' : 'inactiveNumber' ?>">3</td> <td class="<?= ($currentPage==4) ? 'activeNumber' : 'inactiveNumber' ?>">4</td> </tr></table><form action="<?=$action?>" method="post">

328

<input type="hidden" name="currentPage" value="<?=$currentPage?>"/> <table width="500" class="<?= ($currentPage==1) ? 'activeTable' : 'inactiveTable' ?>"> <tr>

Page 329: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

<td valign="top">Name</td> <td valign="top"> <input type="text" name="Name" value="<?=$fldName?>"/> </td> </tr> <tr> <td valign="top">Anschrift</td> <td valign="top"> <input type="text" name="Address" value="<?=$fldAddress?>"/> </td> </tr> <tr> <td valign="top">PLZ-Ort</td> <td valign="top"> <input type="text" name="Zip" value="<?=$fldZip?>"/> - <input type="text" name="City" value="<?=$fldCity?>"/> </td> </tr> </table> <table width="500" class="<?= ($currentPage==2) ? 'activeTable' : 'inactiveTable' ?>"> <tr> <td valign="top">AGB's</td> <td valign="top" > <input type="checkbox" name="Conditions" value="Yes" <?=$fldConditions == 'Yes' ? 'checked' : ''?>/> bestätigen </td> </tr> </table> <table width="500" class="<?= ($currentPage==3) ? 'activeTable' : 'inactiveTable' ?>"> <tr> <td valign="top">Anmeldename</td> <td valign="top"> <input type="text" name="LogonName" value="<?=$fldLogonName?>"/>

329

</td> </tr> <tr> <td valign="top">Kennwort</td> <td valign="top">

Page 330: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

<input type="password" name="Password" value="<?=$fldPassword?>"/> </td> </tr> </table> <table width="500" class="<?= ($currentPage==4) ? 'activeTable' : 'inactiveTable' ?>"> <tr> <td valign="top">Newsletter</td> <td valign="top"> Type 1 <input type="radio" name="News" value="Type1" <?= $fldNews=='Type1' ? 'checked' : '' ?> /> <br/> Type 2 <input type="radio" name="News" value="Type2" <?= $fldNews=='Type2' ? 'checked' : '' ?> /> </td> </tr> <tr> <td valign="top">Kennwort</td> <td valign="top"> <input type="submit" name="Send" value="Abschicken"/> </td> </tr> </table> <table> <tr> <td></td> <td> <input <?=$prevDisabled?> type="submit" name="prev" value="Zurück"/> </td> <td> <input <?=$nextDisabled?> type="submit" name="next" value="Weiter"/> </td> <td></td> </tr>

330

</table></form></body></html>

Page 331: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

Das ist sicher nicht mehr ganz so kompakt wie die anderen Beispiele. Deshalb sollder Blick auf den Ablauf vorangestellt werden. Die folgenden Abbildungen zeigendie vier Seiten in Aktion.

Abbildung 8.16: Schritt1 umfasst die Anmeldedaten

Abbildung 8.17: Schritt 2 enthält nur die Bestäti-gung

Abbildung 8.18: Schritt 3 erlaubt die Erfassung von Anmeldename und Kennwort

331

Abbildung 8.19: In Schritt 4 kann noch ein Newslet-ter bestellt werden

Page 332: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Das Skript verwendet Stildefinitionen zur Kontrolle der Anzeige. Da sich dieTabellen mit den Formulardaten immer an derselben Stelle befinden sollen, wirdzum Ausblenden der nicht benötigten Teile das Attribut display verwendet. Dasbereits benutzte visibility kann Elemente zwar auch unsichtbar machen, diese neh-men aber weiterhin den im sichtbaren Zustand okkupierten Platz ein. Bei der Aus-gabe der Fehlermeldungen war das gewollt. Hier dürfte es eher störend sein,weshalb mit display mehr erreicht werden kann. Um die Umschaltung zu verein-fachen, werden innerhalb des Style-Tags am Anfang CSS-Klassen definiert, die dienötigen Einstellungen enthalten. Die aktive Tabelle erhält folgenden Stil:

display:visible;

Die inaktive (unsichtbare) Tabelle wird mit dem folgenden Code vom Bildschirmverbannt:

display:none;

Auch der Balken, der oberhalb des Formulars den Fortschritt anzeigt, nutzt Stile.Hier wird ebenfalls nur die Klasse umgeschaltet, die passenden Definitionenwurden entsprechend vorbereitet. Dann wird der Wert für die aktuelle Seite in$currentPage ermittelt und bei der betreffenden Tabelle auf »activeNumber«umgeschaltet:

<?= ($currentPage==1) ? 'activeNumber' : 'inactiveNumber' ?>

Die Variable $currentPage spielt ohnehin eine herausragende Rolle, denn sie steu-ert den aktuellen Zustand des Formulars. Beim ersten Aufruf ist sie undefiniertund wird in einen Anfangszustand versetzt:

$currentPage = empty($_POST['currentPage']) ? 1 : (int) $_POST['currentPage'];

Hier wird geprüft, ob das Formular bereits abgesendet wurde. Wenn das der Fallist, steht die aktuelle Seite in $_POST['currentPage'], andernfalls wird hier derStartwert 1 eingesetzt. Damit das Formular nun ständig den Wert mitnehmenkann, kommt wie angekündigt ein verstecktes Feld zum Einsatz:

<input type="hidden" name="currentPage" value="<?=$currentPage?>"/>

Auf diese Weise bildet sich ein Kreislauf aus Wertmitnahme, Werterzeugung und

332

Weiterverarbeitung. Die Weiterverarbeitung erfordert freilich etwas mehr Auf-wand, denn die Variable muss bei einem Vorwärtsschritt erhöht und bei einemRückwärtsschritt verringert werden. Es ist außerdem ein Zeichen guter Benutzer-führung, unmögliche Optionen gar nicht erst zuzulassen. Deshalb sind die Schalt-flächen passend zum Zustand zu deaktivieren. Das heißt, wenn das Formular auf

Page 333: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Formulare und »Sticky Forms«

Position 1 steht, ist die Schaltfläche (Zurück) inaktiv (es geht nicht weiter zurück)und auf Position 4 die Schaltfläche (Weiter). Beispielhaft soll hier der Vorgang füreine Schaltfläche erläutert werden. Zuerst ein Blick auf die Definition:

<input <?=$prevDisabled?> type="submit" name="prev" value="Zurück"/>

Diese Sendschaltfläche hat einen Namen, was sie von den bisher in den Beispielengezeigten Varianten unterscheidet. Damit wird auch die Schaltfläche Teil des$_POST-Arrays. Der Wert ist dabei völlig uninteressant, nur die Tatsache, welcheSchaltfläche angeklickt wurde, ist hier von Bedeutung.

Ein Formular mit mehreren Sende-Schaltflächen kann gesteuert wer-den, indem jede Schaltfläche einen Namen bekommt. Nur die Schalt-fläche, die angeklickt wurde, übermittelt ihren Wert an das PHP-Skript.

Die Variable $prevDisabled bestimmt nun durch Ausgabe der Zeichenkette»disabled«, dass die Schaltfläche deaktiviert wird. Diese Steuerung ist der Teil derBerechnung der folgenden Seite. Dazu wird die aktuelle Seitenzahl um einserhöht oder verringert:

$currentPage--;

Dann folgt die Prüfung, ob bereits die erste (1) oder letzte (4) Seite erreicht wordenist:

if ($currentPage == 1)

Ist das der Fall, wird der passende Wert erzeugt:

$prevDisabled = 'disabled';

Bleibt als letztes noch die Erkennung der Werte der eigentlichen Eingabefelder.Hierzu wurde eine kleine Funktion definiert, die den entsprechenden Vorgang füralle Felder vornimmt:

return empty($_POST[$name]) ? $default : $_POST[$name];

Der Standardwert kann zum Setzen eines Anfangszustands genutzt werden. Dieswurde hier nicht realisiert und ist eine gute Aufgabe für eigene Versuche. (Tipp:Es gehört mehr dazu, als hier nur Werte einzusetzen. Sie müssen auch

333

REQUEST_METHOD abfragen!)

Page 334: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

8.4 Dateien hochladen

Dateien hochladen gehört zu den Formularfunktionen. In der Praxis bereitet diesgelegentlich Schwierigkeiten, weil mehrere Prozesse zusammenspielen müssen.Dieser Abschnitt klärt über Hintergründe, Anwendung und praktische Beispieleauf.

Grundlagen

Um Dateien hochladen zu können, müssen diese vom Browser speziell verpacktwerden. Prinzipiell können per HTTP nur Text-Daten übertragen werden. DaDateien im allgemeinen Binärdaten sind (HTML-Felder enthalten dagegen nurText), müssen sie entsprechend kodiert werden. Um Kodierung und Dekodierungmuss man sich keine Gedanken machen. Alle Browser beherrschen das Kodierenebenso, wie PHP mit dem Dekodieren keine Probleme hat und dies intern erle-digt. Der Vorgang basiert auf der Nutzung der Methode POST und der üblichenFormularübertragung.

PHP unterstützt auch Dateiuploads nach der PUT-Methode, die bei-spielsweise vom Netscape Composer und dem W3C Amaya benutztwird. In der Praxis hat dies wenig Bedeutung, weil die meisten ProviderFTP und neuerdings auch WebDAV anbieten, was einfacher und trans-parenter ist und keine Skriptsteuerung verlangt.

Das Formular vorbereiten

Um ein Formular für das Hochladen von Dateien benutzen zu können, muss eszwei Dinge enthalten:

� Die Kodierungsanweisung im Form-Tag

� Ein spezielles Feld mit dem Attribut type="file"

Das Form-Tag sieht nun folgendermaßen aus:

334

<form enctype="multipart/form-data" method="post">

Das Attribut enctype ist hier entscheidende Teil. Nun muss noch das Feld einge-baut werden, dass die Auswahl der Datei lokal ermöglicht.

Page 335: 382726314 X Php5 In 14 Tagen (Ddt)

Dateien hochladen

Ein Feld mit dem Attribut type="file" unterliegt aus Sicherheitsgründenbestimmten Einschränkungen. So kann die Beschriftung nicht geändertwerden. Die Schaltfläche zum Durchsuchen ist immer mit dem Text(Durchsuchen) oder – in der englischen Version – mit (Browse) beschrif-tet. Das ist einsichtig, weil man mit anderen Beschriftungen (»Gewin-nen Sie 1 Million €«) den Benutzer zu einem ungewollten Klickverleitet könnte. Im Zusammenhang damit kann auch der Wert (Attributvalue) nicht vom Programm gesetzt werden. Denn sonst könnte maneine Vorauswahl hineinschreiben und die Aktion dann mit JavaScriptauslösen, sodass die Website lokale Dateien systematisch beschafft. Des-halb fällt das Thema »Sticky Forms« hier auch aus.

Nach dem Hochladen

Die empfangenen Dateien stellt PHP nicht wie die übrigen Felder als Variablenzur Verfügung, sondern speichert sie in einem temporären Verzeichnis ab. Diebeim Hochladen erzeugten Variablen enthalten nur Informationen über dieDatei, wie beispielsweise den Dateinamen und die Größe. Das Skript, das dieDaten empfängt, muss die Dateien aus dem temporären Speicher in das finaleVerzeichnis kopieren.

Das Kopieren kann entweder mit der Funktion copy oder mit move_uploaded_filesausgeführt werden. Die Funktion move_uploaded_files kann ausschließlich dietemporären Dateien kopieren. Das ist sicherer als copy, weil ein offen program-miertes Skript mit freier Wahl der Pfade eventuell missbraucht werden könnte, umvorhandene Dateien aus einem anderen Verzeichnis zu kopieren und so die Kon-trolle über den Server zu erlangen.

Prinzip

Das folgende Beispiel zeigt eine einfache Anwendung. Zur Erfolgskontrolle wirddas Verzeichnis, in dem die hochgeladenen Dateien landen, anschließend ausge-geben.

335

Listing 8.19: formuploadsimple.php – Formular zum Hochladen von Dateien

<?phpfunction GetFileError($num){

Page 336: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

$err = "Kein Fehler ($num)"; switch ($num) { case UPLOAD_ERR_OK: break; case UPLOAD_ERR_INI_SIZE: $err = "Die in der php.ini festgelegte Größe wurde überschritten"; break; case UPLOAD_ERR_FORM_SIZE: $err = "Die im Formular festgelegte Größe wurde überschritten"; break; case UPLOAD_ERR_PARTIAL: $err = "Es wurde nur ein Teil der Datei hochgeladen (Abbruch)"; break; case UPLOAD_ERR_NO_FILE: $err = "Es wurde keine Datei hochgeladen"; break; } return $err;}$action = $_SERVER['PHP_SELF'];if ($_SERVER['REQUEST_METHOD'] == 'POST'){ if (is_array($_FILES)) { $tmp = $_FILES['MyFile']['tmp_name']; $target = "upload/{$_FILES['MyFile']['name']}"; $error = GetFileError($_FILES['MyFile']['error']); echo "Dateien wurden hochgeladen:<br>"; echo <<<FILEDATA<ul> <li>Lokaler Name: {$_FILES['MyFile']['name']}</li> <li>MIME-Typ: {$_FILES['MyFile']['type']}</li> <li>Größe: {$_FILES['MyFile']['size']} Byte</li>

336

<li>Temporärname: {$tmp}</li> <li>Fehler: {$error}</li> <li>Lokaler Name: {$_FILES['MyFile']['name']}</li></ul>FILEDATA;

Page 337: 382726314 X Php5 In 14 Tagen (Ddt)

Dateien hochladen

if (move_uploaded_file($tmp, $target)) { echo "Datei übertragen. Aktueller Inhalt des Verzeichnisses:<p/>"; foreach (glob(dirname($target).'/*') as $file) { printf('<a href="%1$s">%1$s</a><br>', $file); } } else { echo "Keine Datei übertragen"; } }} else { echo "Bitte Datei hochladen (100 KB maximal)";}?><form action="<?=$action?>" method="post" enctype="multipart/form-data"><input type="hidden" name="MAX_FILE_SIZE" value="100000"> <table> <tr> <td>Ihre Datei:</td> <td> <input type="file" name="MyFile" /> </td> </tr> </table> <br/> <input type="submit" value="Jetzt Senden!" /></form>

Auf die Besonderheiten des Formulars wurde am Anfang bereits eingegangen.Eine zusätzlich Option wurde über ein verstecktes Feld hinzugefügt:

<input type="hidden" name="MAX_FILE_SIZE" value="100000">

337

Der reservierte Name MAX_FILE_SIZE weist PHP an, keine Dateien größer alsangegeben zu übertragen. Die Angabe erfolgt in Bytes. Der Wert kann jedochnicht die zusätzlich in der php.ini verpackten Restriktionen übergehen und stelltnur einen geringen Schutz gegen sehr große Dateien dar. Im Skript führt jedoch

Page 338: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

eine Überschreitung der Größe zu einem Fehler und die Generierung der tempo-rären Datei wird unterbunden. Trotz erfolgreicher Übertragung wird zumindestverhindert, dass jemand böswillig den Server zumüllt.

Der spannende Teil ist die Auswertung des Hochladevorgangs. PHP erzeugt einArray mit dem Namen $_FILES, das alle Informationen über die Dateien enthält.Dabei wird ein Eintrag für jedes mit type="file" gekennzeichnetes Feld erzeugt.Dieser Eintrag enthält wiederum ein Array, dessen Schlüssel folgende Bedeutunghaben:

Mit diesen Angaben kann man praktisch alles über die Datei erfahren und die wei-tere Vorgehensweise steuern. Der Name des Hauptschlüssels im Array $_FILESwird durch den Namen des Felds bestimmt:

<input type="file" name="MyFile" />

Der Arrayschlüssel lautet also »MyFile«. Für die Übertragung der Datei in dasfinale Verzeichnis benötigt man nun zwei Angaben. Zuerst wird der temporäreName als Quelle ermittelt:

$tmp = $_FILES['MyFile']['tmp_name'];

Dann der Zielname, erstellt aus einem frei wählbaren Verzeichnis und demursprünglichen Namen:

Schlüssel Bedeutung

name Ursprünglicher Namen der Datei auf der Festplatte des Benutzers

type Der MIME-Typ, der die Art der Datei beschreibt, beispielsweise »image/gif« für ein GIF-Bild

size Die Größe der Datei in Byte

tmp_name Der Name der Datei im temporären Verzeichnis

error Der Fehlercode, der beim Hochladen erzeugt wurde (0 = Fehlerfrei)

Tabelle 8.3: Bedeutung der Schlüssel des Arrays, die zu einer Datei erzeugt werden

338

$target = "upload/{$_FILES['MyFile']['name']}";

Nun wird der Kopiervorgang ausgelöst:

if (move_uploaded_file($tmp, $target))

Page 339: 382726314 X Php5 In 14 Tagen (Ddt)

Dateien hochladen

Die Funktion gibt FALSE zurück, wenn es sich nicht um eine hochgeladene Dateihandelt. Ist die Datei dagegen bereits vorhanden, wird sie einfach überschrieben.Das nächste Beispiel behandelt mehr solcher »Feinheiten«.

Hier wird im Anschluss lediglich noch der aktuelle Inhalt des Verzeichnisses ange-zeigt:

foreach (glob(dirname($target).'/*') as $file)

Das Kapitel zu Dateifunktionen geht ausführlich auf die eingesetzten Funktionenglob und dirname ein.

Die Dateien werden außerdem als Link angeboten:

printf('<a href="%1$s">%1$s</a><br>', $file);

Nach ein paar Ladevorgängen sieht die Ausgabe dann ungefähr wie im folgendenBild aus:

Typische Probleme

Abbildung 8.20: Hochladen von Dateien mit Auswer-tung und Dateiliste

339

Es gibt einige Probleme, die in der Praxis auftreten können und die bisher nichtbeachtet wurden:

� Laden mehrere Benutzer gleichnamige Dateien hoch, überschreiben sichdiese gegenseitig

Page 340: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

� Will ein Benutzer mehrere Dateien hochladen, ist das sehr umständlich

� Es besteht keine Einschränkung des Dateityps; ein Benutzer könnte auch aus-führbare Dateien hochladen

� Die Größenbegrenzung ist mit ein paar Tricks zu umgehen

� Es gibt Probleme mit exotischen Zeichen in Dateinamen

An dieser Stelle sollen nur einige Lösungsansätze aufgezeigt werden. Um einÜberschreiben zu vermeiden, wird die Funktion file_exists eingesetzt. Wiedann darauf reagiert wird, hängt von der Applikation ab. Eine andere Lösungbesteht darin, jedem Benutzer ein eigenes Verzeichnis zu geben, aber dann müssteman ein Skript implementieren, das eine Benutzeranmeldung umfasst. Ist dieseohnehin vorhanden, sind persönliche Verzeichnisse optimal.

Für den Dateityp bietet es sich an, ein Array mit zulässigen Typen zu erstellen.Typisch sind folgende Werte:

� Bilder: image/gif, image/jpg, image/jpeg, image/png, image/x-png

� Text und HTML: text/txt, text/html

� Binärdaten: application/octet-stream

� Sounddaten: audio/basic

� Videos: video/mpeg

Prüfen Sie dann mit der Funktion in_array, ob der gesendete Typ in der Liste dererlaubten Werte enthalten ist.

Für einen zuverlässigen Schutz gegen sehr große Dateien finden Sie im Folgen-den Abschnitt zu den Konfigurationsmöglichkeiten mehr Informationen.

Probleme mit exotischen (beispielsweise asiatischen) Zeichen in Dateinamenkann man in den Griff bekommen, indem das iconv-Modul geladen und zurUmwandlung benutzt wird.

Konfigurationsmöglichkeiten

340

Zahlreiche Konfigurationsmöglichkeiten bietet die Datei php.ini. Wenn Sie kei-nen Zugriff darauf haben, bietet sich die Funktion ini_set an, mit der sich dieEinstellungen zeitweilig ändern lassen.

Page 341: 382726314 X Php5 In 14 Tagen (Ddt)

Von der Seite zum Projekt

Konfigurationsmöglichkeiten mit der Datei php.ini

In der Datei php.ini sind mehrere Optionen versteckt, die das Hochladeverhaltensteuern:

8.5 Von der Seite zum Projekt

Bislang waren immer nur einzelne Seiten zu erstellen. Praktisch besteht jedochjede Website aus mehreren Seiten. Die Verbindung mit Hyperlinks ist Sache vonHTML und dürfte kaum Probleme bereiten. Spannender ist es, wie Daten vonSeite zu Seite übertragen werden.

Die HTTP-Methode GET

Option Beschreibung

file_uploads Boolescher Wert, der bestimmt, ob überhaupt ein Hochladen erlaubt ist. »0« verhindert das Hochladen, der Standardwert »1« erlaubt es.

upload_max_filesize Maximale Größe in Byte, die Dateien haben dürfen. Statt Bytes kann auch eine Kombination mit »K« (beispielsweise »50K«) für Kilobyte oder »M« (beispielsweise »1M«) für Megabyte benutzt werden. Der Standardwert beträgt »2M« (2 Megabyte).

upload_tmp_dir Verzeichnis für die temporären Dateien. Hier müssen Schreib-rechte bestehen, sonst geht gar nichts.

post_max_size Maximale Größe in Byte, die der gesamte Umfang des Formu-lars haben darf. Der Wert muss größer als upload_max_filesize sein, damit keine weitere Einschränkung besteht.

Tabelle 8.4: Optionen zur Konfiguration des Hochladeverhaltens

341

Die HTTP-Methode GET wird immer dann benutzt, wenn der Benutzer aufeinen Link klickt. Außer dem einfachen Aufruf der nächsten Seite verfügt GETjedoch auch über Möglichkeiten, Daten mitzugeben. Damit wird der Zusammen-halt zwischen den Seiten gesteuert.

Page 342: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Bevor es richtig losgeht, sollten Sie den Aufbau des URL komplett kennen. Hierein Muster:

http://www.comzept.de/pfad/index.php?val1=wert&val%202=wert#hash

Die einzelnen Teile haben nun folgende Bedeutung:

URL-Teil Bedeutung

http Protokoll

:// Trennzeichen

www Servername (kann entfallen)

comzept Domain

de Toplevel-Domain

/pfad Pfad (kann entfallen)

/index.php Datei, die aufgerufen wird (Ressource)

Alle weiteren Angaben sind optional:

? Trennzeichen zur Abtrennung der Parameter

val1=wert Parameterpaar

val1 Parametername

wert Parameterwert

& Trennzeichen zwischen Parametern

val%202=wert Weiteres Parameterpaar, kodiert

val%202 Parametername, kodiert

wert Parameterwert

# Abtrennung des Hash-Zeichens

342

hash Hash-Wert als Sprungziel (Sprungsmarken werden mit <a name="hash"> gebildet)

Tabelle 8.5: Aufbau eines URL

Page 343: 382726314 X Php5 In 14 Tagen (Ddt)

Von der Seite zum Projekt

Einen URL untersuchen

Liegt ein URL im Skript vor und Sie benötigen spezielle Angaben daraus, hilft dieFunktion parse_url:

Listing 8.20: getparseurl.php – Informationen über den Aufbau eines URL ermitteln

<?php$url = "http://www.comzept.de/pfad/index.php?val1=wert&val%202=wert#hash";$p = parse_url($url);foreach ($p as $name => $content){ echo "<b>$name:</b> $content<br>";}?>

Die Ausgabe des von parse_url erzeugten Arrays zeigt die wichtigsten Komponen-ten:

Der Rest dieses Abschnitts rankt sich mehr oder minder intensiv um das als»Query« bezeichnete Element.

Kodierung des URL

Da einige Zeichen eine Sonderfunktion erfüllen, ist eine Kodierung der URL-Daten erforderlich. Das betrifft vor allem den Inhalt der im Muster als »wert«gekennzeichneten Bereiche. Die übliche Kodierung ersetzt Sonderzeichen durchihren Hexwert. So wird ein Leerzeichen durch %20 ersetzt. »%« leitet den Codeein, 20 steht für dezimal 32 und dies ist der ASCII-Wert des Leerzeichens.

Abbildung 8.21: Teile eines URL

343

Damit es nicht so schwierig ist, kennt PHP die Funktionen url_encode undurl_decode. Dabei ist die Rückumwandlung meist nicht erforderlich, weil diesPHP intern erledigt. Der folgende Abschnitt zeigt die praktische Anwendung.

Page 344: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Die Länge eines URL ist auf ca. 2.000 Zeichen begrenzt. Es ist wichtig,das zu beachten, weil überhängende Werte einfach abgeschnitten wer-den. Für große Datenmengen ist GET deshalb nicht geeignet. WeichenSie dann auf POST aus, wie im vorhergehenden Abschnitt beschrieben.

Die im letzten Beispiel als »Query« bezeichnet Folge kann damit dekodiert undweiter untersucht werden:

Listing 8.21: getparseurldecode.php – »Händische« Zerlegung eines URL

$url = "http://www.comzept.de/pfad/index.php?val1=wert&val%202=wert#hash";$p = parse_url($url);foreach ($p as $name => $content){ if ($name == 'query') { $pairs = explode('&', urldecode($content)); foreach ($pairs as $values) { foreach (explode('=', $values) as $w => $v) { echo "$w = '$v'<br>"; } } } else { echo "<b>$name:</b> $content<br>"; }}

Die systematische Zerlegung erfolgt hier anhand der Trennzeichen mit explodeund die Ausgabe mit foreach-Schleifen. Dies war nur als kleine Übung zumWarmwerden gedacht, wie es einfacher geht, folgt gleich.

344

Daten per URL übermitteln

Um Daten per URL übermitteln zu können, werden an den Link entsprechendePaare aus Namen und Werten angehängt. Damit ergibt sich ein ähnliches Schemawie bei den POST-Daten aus einem Formular. Folgerichtig stellt PHP die Daten

Page 345: 382726314 X Php5 In 14 Tagen (Ddt)

Von der Seite zum Projekt

auch wieder zur Verfügung, und zwar als Array mit dem Namen $_GET. Dekodie-rung und Splittung der Werte aus dem Query-Fragment wird automatisch erledigt.Das funktioniert freilich nur, wenn der Benutzer auch auf einen Link klickt unddie Auswertung im aufgerufenen Skript stattfindet. Um einen als Zeichenkette vor-liegenden Link zu zerlegen, muss man wie im letzten Beispiel gezeigt vorgehen.

Listing 8.22: get_page1.php: Übergabe von Werten per URL

<?php$action = $_SERVER['PHP_SELF'];foreach ($_GET as $name => $content){ echo "$name = '$content'<br>";}?><a href="<?=$action?>?para1=Name&para2=Die Straße">Klick mich!</a>

Der Link, der nun im Browser erscheint, zeigt, dass die Kodierung automatischerfolgt ist (das macht der Browser):

Die Dekodierung erledigt PHP:

Einsatzbeispiele

In Projekten wird die Übertragung von Daten per URL sehr oft eingesetzt, um dasVerhalten der nächsten Seite zu steuern. Das folgende Beispiel zeigt, wie dies ein-gesetzt werden kann. Es wird zunächst eine Seite definiert, die eine Navigationenthält. Diese ruft eine zweite Seite auf, deren Inhalt durch den übergebenenWert gesteuert wird.

Zuerst die Definition der ersten Seite mit der Navigation. Hier ist von PHP noch

Abbildung 8.22: Parameter nach der Übertragung per URL

345

nichts zu sehen:

Listing 8.23: get_navigation.php – Eine einfache Navigation überträgt Werte

<?php$action = "get_navigationtarget.php";

Page 346: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

?><a href="<?=$action?>?title=Startseite">Startseite</a><br/><a href="<?=$action?>?title=Impressum">Impressum</a><br/><a href="<?=$action?>?title=Produkte">Produkte</a><br/><a href="<?=$action?>?title=AGBs">AGBs</a><br/><a href="<?=$action?>?title=Anmeldung">Anmeldung</a><br/>

Das Ziel, get_navigationtarget.php, muss diese Werte nun erkennen. Das folgendeSkript zeigt, wie es geht:

Listing 8.24: get_navigationtarget.php – Auswertung der GET-Daten

<?php$title = empty($_GET['title']) ? 'Startseite' : $_GET['title'];?><h1><?=$title?></h1>Bla bla bla, ...

Auch hier ist zu beachten, wie schon bei POST, dass der erwartete Wert nicht vor-handen ist. Deshalb wird mit empty geprüft, ob das erwartete Element überhauptim Array ist.

Sicherheitsprobleme

Die Übertragung von Daten per GET führt zu einigen Sicherheitsproblemen. Fürden Benutzer sind die Daten in der Adresszeile des Browsers sichtbar und deshalbauch leicht zu fälschen. Sie dürfen den Daten deshalb niemals vertrauen. Auf kei-nen Fall dürfen außerdem sensible Informationen wie Kennwörter oder Daten-banksteuerungen übertragen werden. Man kann aber nicht immer auf GETverzichten. Deshalb sind Strategien gefragt, die Sicherheit der Seite zu erhöhen.

Abbildung 8.23: Dynamisch erstellter Inhalt, der Titel wurde als Parameter übergeben

346

Daten sicher übertragen

Eine Methode besteht darin, die Daten im URL kodiert zu übertragen. Ein geziel-tes Fälschen ist damit praktisch unmöglich. Willkürliche Änderungen lassen sich

Page 347: 382726314 X Php5 In 14 Tagen (Ddt)

Von der Seite zum Projekt

leicht erkennen. In solchen Fällen wird immer wieder auf die Startseite der Appli-kation zurückgesprungen. Eine Möglichkeit, die Kodierung vorzunehmen, sind sogenannte Hashes. Dazu werden die vorbereiteten Daten in eine gesonderte Dateigeschrieben und per include eingebunden. Eine solche Datei könnte am Beispielder Navigation folgendermaßen aussehen:

Listing 8.25: get_includenav.php – Definition der Basisdaten

<?php$titles[] = "Startseite";$titles[] = "Impressum";$titles[] = "Produkte";$titles[] = "AGBs";$titles[] = "Anmeldung";?>

Vor dem Absenden an die Folgeseite werden diese Daten nun mit Hilfe der Funk-tion md5 zu einem Hash verpackt.

Hashfunktionen liefern eine Art Prüfsumme über Daten, die generellnicht wieder in den ursprünglichen Wert umgewandelt werden (sie ent-halten meist viel weniger Daten als die Datenquelle hatte). MD5 stehtfür Message Digest Version 5 und ist sehr weit verbreitet.

Listing 8.26: get_navigationmd5.php – Linkgenerierung mit Kodierung

<?phpinclude('get_includenav.php');$action = "get_navigationtargetmd5.php";foreach ($titles as $title){ printf('<a href="%1$s?t=%3$s">%2$s</a><br/>', $action, $title, md5($title));}

347

?>

Interessant ist hier eigentlich nur der erzeugte Quelltext:

<body><a href="get_navigationtarget.php?t=c84369cb124912750ac1c1982a7b7e05">Startseite</a><br/>

Page 348: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

<a href="get_navigationtarget.php?t=fd2648427fd682bc2a48d7cf5668b3db">Impressum</a><br/><a href="get_navigationtarget.php?t=3d87a30682721340bdfe22c3a1dfae28">Produkte</a><br/><a href="get_navigationtarget.php?t=2bbf0f4a652ef7ccd201ca9ab89032f8">AGBs</a><br/><a href="get_navigationtarget.php?t=05d316097eb9d1289765b00d23b6aba8">Anmeldung</a><br/></body>

Nun muss auf der Zielseite der Code wieder aufgelöst werden. Dazu kodiert mandie Werte aus dem Array $titles erneut und vergleicht sie mit den gesendetenHashcodes:

Listing 8.27: get_navigationtargetmd5.php – Mit MD5 kodierte Links

<?phpinclude('get_includenav.php');$t = empty($_GET['t']) ? 0 : $_GET['t'];$title = 'Startseite';foreach ($titles as $name){ if (md5($name) == $t) { $title = $name; break; }}?><h1><?=$title?></h1>Bla bla bla, ...

Der entscheidende Punkt ist hier der Vergleich zwischen dem am Ziel berechnetHash (linker Term) und dem per GET übertragenen:

if (md5($name) == $t)

Der URL der Seite sieht nun folgendermaßen aus:

348

get_navigationtargetmd5.php?t=3d87a30682721340bdfe22c3a1dfae28

Dahinter kann auch ein Profi nicht mehr Ansatzweise vermuten, welche Art vonDaten übertragen werden könnte. Bedenkt man, dass die Datenquelle in der Praxiskeine einfachen Zeichenketten sind, sondern durchaus schon an sich kryptischeParameter, dann ist das Verfahren sehr sicher.

Page 349: 382726314 X Php5 In 14 Tagen (Ddt)

Cookies und Sessions

Grenzen des Verfahrens

Man stößt freilich auf eine andere Grenze des Verfahrens. Wenn die zu übertra-genden Daten nicht statisch sind, also bereits beim Laden der Seite feststehen,kann man Hashes nicht einsetzen. Kryptografische Verfahren wären zwar möglich,erhöhen aber den Aufwand erheblich. Die entstehenden URLs sind sehr lang undder Rechenaufwand ist teilweise erheblich. Man kann Daten jedoch noch elegan-ter transportieren: Mit Sessions und passend dafür entworfenen Cookies.

8.6 Cookies und Sessions

Cookies und Sessions sind untrennbar miteinander verbunden, weshalb sie auchhier zusammen behandelt werden. Sie werden zwar noch sehen, dass Sessionsauch ohne Cookies funktionieren, aber Cookies sind als Grundlage unerlässlich.Dieser Abschnitt zeigt, was Cookies und Sessions überhaupt sind, wozu sie dienenund wie man sie praktisch programmiert.

Cookies

Cookies sind Daten, die der Browser im Auftrag eines Servers ablegt und immerdann wieder ausliefert, wenn der Benutzer vom selben Server erneut Daten abruft.Die entscheidende Aussage dabei ist, dass der Browser darüber entscheidet, was eran den Server zurück sendet und ob überhaupt Daten gespeichert werden. Allemodernen Browser verfügen über Möglichkeiten, Cookies abzuschalten.

An sich sind Cookies ungefährlich. Sie können lediglich Name/Wert-Paare enthal-ten und entsprechen damit dem bereits behandelten Schema der POST- undGET-Daten. Sie sind halt ein weiterer Weg, Daten von Seite zu Seite mitzuneh-men.

Hintergrund

349

Cookies genießen leider einen recht schlechten Ruf, was an einigen missbräuch-lichen Einsätzen liegt. Generell verfügen Cookies über ein Verfallsdatum. DemBrowser wird darin mitgeteilt, für welchen Zeitraum er das Cookie behalten undwieder ausliefern soll. Ein Sonderfall stellen so genannten Sessioncookies dar, die

Page 350: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

am Ende der Benutzersitzung verfallen – im Allgemeinen dann, wenn der Browsergeschlossen wird.

Viele Benutzer akzeptieren inzwischen nur noch Sessioncookies, wes-halb deren Anwendung unkritisch ist. Auf reguläre Cookies sollte mansich als Webentwickler besser nicht verlassen.

Die Entwicklung der Cookies geht auf das Protokoll HTTP zurück. HTTP gilt alsso genanntes status- oder zustandsloses Protokoll. Der Browser initiiert die Verbin-dung und fordert vom Server eine Ressource (HTML, PHP-Skript, Bild, ...) an.Der Server liefert die Ressource aus und beide Seiten beenden den Vorgang. Obdie nächste Anfrage vom selben Browser oder einem anderen kommt, kann nichtsicher festgestellt werden. Die IP-Nummern der Browser sind nämlich meist dyna-misch und wechseln bei manchen großen Providern auch während der Benutzer-sitzung.

Die Firma Netscape hatte schon sehr früh die Idee, dass der Server eine kleineInformationsdatei zusammen mit den Daten sendet, die der Browser speichert.Die Daten enthalten eine Identifikationsnummer. Der Browser sendet bei dernächsten Anfrage an den Server dies Nummer wieder zurück. Daran kann der Ser-ver (bzw. das Programm, das dort abläuft) dann erkennen, um welchen Browser essich handelt. Das Cookie war geboren.

Den schlechten Ruf erhielten Cookies, weil die Wiedererkennung pro Ressourcepassiert. Der Server verpackt die Cookies nämlich im Kopf (Header) jeder HTTP-Antwort. Jedes Element einer Seite wird durch eine Anforderung geholt und mitder Antwort geliefert. Eine Seite enthält neben dem eigentlichen Inhalt auch Bil-der oder Flash-Objekte. Diese werden alle mit einzelnen HTTP-Anforderungenbeschafft. Dabei kann es sein, dass ein Bild, das als Werbebanner auf der Seitesteht, von einem anderen Server geholt wird, als die eigentliche Seite. DerartigeAd-Server (Werbefirmen betreiben die) liefern nun für viele Seiten die Bannerund damit auch Cookies aus. Natürlich gelangen die Cookies wieder zurück undso kann man leicht Bewegungsprofile erstellen, die den Weg des Benutzers überverschiedene ans Netzwerk angeschlossene Seiten aufzeichnen. Der Benutzerselbst bleibt zwar anonym, aber für eine kritische Presse hat es dennoch gereicht

350

und flugs war der Ruf der Cookies versaut. Der Übeltäter, der als erster auf die Ideekam, war übrigens die Firma Doubleclick, die dieses Geschäft auch heute noch(neben vielen anderen) erfolgreich betreibt.

Page 351: 382726314 X Php5 In 14 Tagen (Ddt)

Cookies und Sessions

Nichtsdestotrotz sind Cookies ein wertvolles und zudem leicht beherrschbaresInstrument des Webprogrammierers und deshalb sollten Sie sich damit unbedingtauseinandersetzen.

Cookies in PHP

Am Anfang wurde bereits erwähnt, dass Cookies ähnliche Name/Wert-Paare ent-halten wie die POST- oder GET-Werte. Folgerichtig stehen auch Cookies alsArray mit dem Namen $_COOKIE zur Verfügung. Die Erzeugung übernimmtdagegen eine spezielle Funktionen: setcookie. Die Parameter dieser Funktionreflektieren den Inhalt des Cookies:

� nameDies ist der einzige Pflichtparameter (alle anderen sind optional) und er ent-hält den Namen des Cookies. Beim Abruf empfangener Cookies ist dies derSchlüssel des Arrays.

� valueDies ist der Wert, der ins Cookie gesetzt wird. Die Angabe erfordert eine Zei-chenkette. Wenn der Wert leer bleibt, wird das Cookie gelöscht. Das Cookiewird auch gelöscht, wenn das Verfallsdatum in der Vergangenheit liegt, wes-halb meist beide Angaben zum Löschen kombiniert werden.

� expireDies ist das Verfallsdatum als Unix-Zeitstempel. Auch diese Angabe ist optio-nal. Wird nichts angegeben, entsteht ein Sessioncookie.

� pathEin relativer Pfad innerhalb der Applikation, damit nicht immer alle Cookieszurückgesendet werden. Selten benutzt.

� domainSchränkt die Domain ein, wenn der Server über Subdomains verfügt. Wenndas Cookie von www.comzept.de kommt, wird es auch nur dahin wiederzurückgesendet. Soll es auch auf www1.comzept.de landen, muss die Domainauf comzept.de eingeschränkt werden.

351

� secureWird der Wert auf 1 gesetzt, wird das Cookie nur gesendet, wenn eine sichereVerbindung besteht. Der Standardwert ist 0.

Page 352: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Die setcookie-Funktion kodiert Daten automatisch nach dem URL-Schema. Wenn Sie das nicht wünschen, setzen Sie setrawcookie ein.Abgesehen von der Kodierung sind beide Funktionen identisch.

Beschränkungen

Cookies sind auch wegen grundsätzlicher Beschränkungen nicht über längereZeiträume zuverlässig. Diese Grenzen sind in der Spezifikation festgelegt:

� Der Browser muss nur 300 Cookies speichern

� Ein Cookie darf maximal 4 KByte groß sein, inklusive des Namens

� Jede Domain darf nur 20 Cookies senden

Wird eine der Grenzen erreicht, werden die im Kontext ältesten Cookies gelöscht.Zu große Cookies werden gekürzt.

Personalisierung mit Cookies

Cookies lassen sich zur Personalisierung einsetzen. Das folgende Beispiel setztSchriftart und -größe einer Website nach Benutzervorgaben. Wenn der Benutzerspäter wiederkommt, findet er die letzte Einstellung wieder vor. Die Daten werdenin einem Cookie gespeichert.

Listing 8.28: cookiestandard.php – Cookies setzen und wieder auslesen

<?php$action = $_SERVER['PHP_SELF'];if (!empty($_POST['face']) && !empty($_POST['size'])){ $expires = time() + 300; setcookie('face', $_POST['face'], $expires); setcookie('size', $_POST['size'], $expires);}if (!empty($_COOKIE['face'])){

352

$face = $_COOKIE['face']; $size = $_COOKIE['size'];}else{ $face = 'Courier New';

Page 353: 382726314 X Php5 In 14 Tagen (Ddt)

Cookies und Sessions

$size = 3;}?>Ihre Auswahl für Schriftart und -größe:<br><form action="<?=$action?>" method="post"> <select name="face" size="1"> <option value="Verdana">Verdana</option> <option value="Arial">Arial</option> <option value="Tahoma">Tahoma</option> <option value="Times Roman">Times Roman</option> </select> <select name="size" size="1"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> <option value="5">5</option> <option value="6">6</option> <option value="7">7</option> </select> <br/> <input type="submit" value="Einstellen"/></form><hr/><font face="<?=$face?>" size="<?=$size?>">Dies ist ein Mustertext, der im gewählten Font angezeigt wird</font>

Das Skript enthält zum einen ein Formular für die Einstellung, zum anderen dieLogik zum Setzen und Auslesen der Cookies. Das Formular dürfte keine Problemebereiten, es müsste für den praktischen Einsatz lediglich etwas ausgebaut werden(Achtung! Übungsgelegenheit). Nach dem Absenden wird geprüft, ob die Werteauch beide gesetzt wurden:

if (!empty($_POST['face']) && !empty($_POST['size']))

Ist das der Fall, werden die Cookies erzeugt. Zuerst wird das Verfallsdatum berech-net. Im Beispiel sind es fünf Minuten (300 Sekunden):

353

$expires = time() + 300;

Dann werden die Werte aus dem Formular benutzt, um zwei Cookies zu erzeugen:

setcookie('face', $_POST['face'], $expires);setcookie('size', $_POST['size'], $expires);

Page 354: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Damit ist das Skript in dieser Phase praktisch beendet. Die Cookies sind jetzt imBrowser angekommen. Das wirkt sich auf die Einstellungen nicht sofort aus, son-dern erst beim nächsten (zweiten) Abruf der Seite durch den Browser. Nun sendetder Browser die Daten zurück und das $_COOKIE-Array ist gefüllt:

if (!empty($_COOKIE['face']))

Dann werden die beiden Cookies zurückgeholt:

$face = $_COOKIE['face'];$size = $_COOKIE['size'];

Mit einem Font-Tag wird dann noch die Ausgabe kontrolliert:

<font face="<?=$face?>" size="<?=$size?>">

Die »Verzögerung« von der Eingabe zum Effekt – bedingt durch den zusätzlichenWeg der Daten vom Server zum Browser und zurück – kann durch eine Erweite-rung beseitigt werden. Dazu sind praktisch die Arrays aus dem $_POST- und dem$_COOKIES-Array zu verschmelzen. Wie das geht, zeigt die verbesserte Version:

Listing 8.29: cookieadvanced.php – Verbesserte Version des Cookie-Programms

<?php$action = $_SERVER['PHP_SELF'];if (!empty($_POST['face']) && !empty($_POST['size'])){ $expires = time() + 300; setcookie('face', $_POST['face'], $expires); setcookie('size', $_POST['size'], $expires);}$super = array_merge($_COOKIE, $_POST);if (count($super) > 1){ $face = $super['face']; $size = $super['size'];}else{ $face = 'Courier New';

354

$size = 3;}?>Ihre Auswahl für Schriftart und -größe:<br><form action="<?=$action?>" method="post"> <select name="face" size="1">

Page 355: 382726314 X Php5 In 14 Tagen (Ddt)

Cookies und Sessions

<option value="">-- Ihre Auswahl --</option> <option value="Verdana">Verdana</option> <option value="Arial">Arial</option> <option value="Tahoma">Tahoma</option> <option value="Times Roman">Times Roman</option> </select> <select name="size" size="1"> <option value="">-- Ihre Auswahl --</option> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> <option value="5">5</option> <option value="6">6</option> <option value="7">7</option> </select> <br/> <input type="submit" value="Einstellen"/></form><hr/><font face="<?=$face?>" size="<?=$size?>">Dies ist ein Mustertext, der im gewählten Font angezeigt wird</font>

Damit das Formular nicht dominant wird – es soll nur wirksam werden, wennexplizit ein Wert ausgewählt wurde – wird eine Blindoption ohne Wert eingesetzt:

<option value="">-- Ihre Auswahl --</option>

Dann wird im Skript selbst das aktuelle $_POST-Array mit dem $_COOKIE-Arrayverschmolzen. Die Werte überschreiben sich dabei gegenseitig, denn es kann sein,dass sowohl das Formular gesetzt ist als auch Cookies zurückkommen:

$super = array_merge($_COOKIE, $_POST);

Es ist dabei wichtig, dass das $_POST-Array dominant ist, deshalb wird es an diezweite Stelle gesetzt. Damit kann der Benutzer den aktuellen Wert im Cookiejederzeit überschreiben.

355

Es gibt übrigens auch ein fertiges Superarray $_REQUEST in PHP, dasseine Sammlung aller Werte aus $_POST, $_COOKIE, $_GET und$_FILES. Das ist nicht immer sinnvoll, weil es leicht Konflikte mit denanderen Arrays geben kann. Außerdem kann die Reihenfolge der Ver-mischung nicht kontrolliert werden. Deshalb wurde im Beispielarray_merge eingesetzt.

Page 356: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Das Skript ist nun schon sehr weit entwickelt. Was als nächste Aufgabe ansteht,wäre die Kodierung der Cookie-Werte.

Andere Daten in Cookies speichern

Cookies können nur Zeichenketten speichern. PHP kann andere Skalare leichtumwandeln. Arrays sind dagegen schwerer zu verpacken. Eine gute Idee ist dieVerwendung von serialize. Diese Funktion erstellt eine Zeichenkettenform auchaus komplexen Variablen. Das folgende Beispiel verpackt die Daten aus dem letz-ten Skript in ein Array und muss deshalb nur noch ein Cookie senden. Das erhöht– nebenbei – durchaus die Chance, dass es auch wiederkommt.

Listing 8.30: cookieserialize.php – Cookies zum Speichern verpackter Arrays verwenden (das verwendete Formular entspricht exakt dem letzten Listing 8.29)

<?php$action = $_SERVER['PHP_SELF'];if (!empty($_POST['face']) && !empty($_POST['size'])){ $expires = time() + 300; $array = array('face' => $_POST['face'], 'size' => $_POST['size']); setcookie('font', serialize($array), $expires);}if (empty($_COOKIE['font'])){ $cookies = array();

Abbildung 8.24: Ausgabe, gesteu-ert durch den Zustand eines Cookies

356

}else{ $cookies = unserialize($_COOKIE['font']);}

Page 357: 382726314 X Php5 In 14 Tagen (Ddt)

Cookies und Sessions

$super = array_merge($cookies, $_POST);if (count($super) > 1){ $face = $super['face']; $size = $super['size'];}else{ $face = 'Courier New'; $size = 3;}?>

Zuerst werden die Daten, die das Cookie speichern soll, in ein Array gepackt:

$array = array('face' => $_POST['face'], 'size' => $_POST['size']);

Dann wird das Array serialisiert und gesendet:

setcookie('font', serialize($array), $expires);

Beim Auspacken des Cookies geht man dann genau umgekehrt vor:

$cookies = unserialize($_COOKIE['font']);

Das entstehende Array entspricht genau dem ursprünglich verpackten und kannsofort verwendet werden:

$super = array_merge($cookies, $_POST);

Der Rest ist gegenüber der letzten Version unverändert.

Als Letztes ist ein Blick auf die Daten interessant, die PHP beim Serialisierenerzeugt:

a:2:{s:4:"face";s:5:"Arial";s:4:"size";s:1:"3";}

Aus diesen Daten kann PHP alles entnehmen, was zur Regenerierung der Datenerforderlich ist.

Cookies und Sicherheit

357

Cookies sind nicht zuverlässig und nicht sicher. Es ist generell ein sehr schlechteIdee, Benutzernamen und Kennwörter in Cookies zu speichern. Bemächtigt sichjemand eines Computers, auf dem Kennwörter in Cookies gespeichert sind,könnte er sich damit unter einem fremden Namen anmelden. Außerdem liegen

Page 358: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

die Cookies in allen Browsern im Klartext vor, sodass »Schnüffelprogramme«intime Daten finden könnten.

Am besten ist es, wenn man nur IDs speichert und alle anderen Daten konsequentauf dem Server belässt. Genau das ist die Aufgabe der Session-Verwaltung.

Session-Verwaltung

Bei der Session-Verwaltung geht es im weitesten Sinne darum, zu einem bestimm-ten Benutzer gehörende Daten während der gesamten Sitzung zu speichern unddie Zuordnung aufrecht zu erhalten. Cookies sind ursprünglich zu diesem Zweckentworfen worden. Nun darf man die Dinge nicht durcheinander bringen. Es istnicht der Sinn des Cookies, diese Daten zu speichern. Wie bereits gezeigt wurde,sind Cookies nicht wirklich sicher. Die Session-Verwaltung macht es sich zur Auf-gabe, zum einen jeden technisch möglichen Weg zum Erhalt des Zustands zu nut-zen und außerdem die Speicherung der eigentlichen Daten als integrierte Aufgabezu übernehmen.

Datenspeichermethoden

Werden Daten zu einem Benutzer gespeichert, muss zuerst die Frage gestellt wer-den, wo diese abgelegt werden. Speichern ist ein weitläufiger Begriff. Grundsätz-lich bietet sich folgendes an:

� Dateisystem

� Datenbank

� Computer-Speicher

� Andere Server

PHP verwendet davon standardmäßig das Dateisystem. Es ist auf Unix-Systemenmit Apache Webserver und PHP-Modul möglich, auch den Computer-Speicherzu nutzen. Der Preis für höhere Leistung ist die Inkompatibilität der Skripte mitanderen Systemen. Datenbanken sind schnell, elegant und meist optimal, das

358

Modul zum Speichern aber muss selbst implementiert werden. Andere Server sindeher Notlösungen, um Cluster zu ermöglichen, wenn keine Datenbank vorhandenist. Apropos Cluster: Sind mehrere Webserver zur Lastverteilung zusammenge-schlossen, so ist nicht unbedingt sicherzustellen, dass Abrufe ein und desselbenBrowsers immer beim gleichen Server landen. Wenn nun die Sitzungsdaten auf

Page 359: 382726314 X Php5 In 14 Tagen (Ddt)

Cookies und Sessions

der einen Maschine auf der Festplatte liegen und der nächste Start des Skripts voneiner anderen erfolgt, geht der durch die Session-Verwaltung erreichte Zusam-menhang wieder verloren. Alle größeren Systeme nutzen deshalb Datenbanken.Damit aber nicht schon bei den ersten Versuchen eine Datenbank benötigt wird,nutzt PHP ohne weitere Einstellungen das Dateisystem.

Methoden zur Übertragung der Session-ID

Die Session-ID spielt eine herausragende Rolle bei der Session-Verwaltung. Siesorgt dafür, dass der Benutzer beim erneuten Abruf wieder erkannt wird. Nur dieSession-ID wird letztlich auf seinem Computer gespeichert. Die Daten verlassenniemals den Server. Der Erhalt der Session-ID ist also wesentlich für die Funktion.Damit das funktioniert, muss diese mit dem Code der HTML-Seite zum Browserund von dort wieder zurück gelangen. Es gibt drei Wege, die in PHP5 dafür zurVerfügung stehen:

1. GET

2. POST

3. Cookies

Cookies sind übrigens eigens zu diesem Zweck erfunden worden und PHP5nimmt diese als Standard. Nur wenn Cookies – aus welchen Gründen auch immer– nicht benutzt werden sollen, kommen andere Techniken zum Zuge. Letztlichgeht es immer nur darum, die Session-ID hin und her zu transportieren.

Wird GET verwendet, erfolgt die Verpackung im URL, als einfacher GET-Para-meter:

http://www.comzept.de/test.php?sid=ASD934DE42BC906876AF73F10932B

Es ist dann natürlich erforderlich, dafür zu sorgen, dass dieser Parameter in jedeninternen Seitenaufruf eingebaut wird. PHP erledigt dies intern sehr zuverlässig,wenn es sich um einfaches HTML oder komplette Links in JavaScript handelt.

Bei POST muss die Session-ID Teil des Formulars werden. Dazu wird ein ver-stecktes Feld eingebaut:

359

<form ...> <input type="hidden" name="sid" � value="ASD934DE42BC906876AF73F10932B"/></form>

Page 360: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Die Session-Verwaltung in PHP5 erledigt nun mehrere Dinge intern:

� Verwendung von Sessioncookies, wenn möglich

� Übergang auf GET und POST, wenn erforderlich oder explizit verlangt

� Speicherung von Variablen im Kontext einer Sitzung in Dateien

� Kontrolle der Sitzungsdauer

Wenn Sie die Daten nicht in Dateien, sondern anders speichern möchten, müssendie entsprechenden Behandlungsfunktionen manuell ausprogrammiert werden.PHP5 stellt dafür einen Schnittstelle über Rückruffunktionen bereit, die dies rechteinfach macht. Für »normale« Applikationen reicht es jedoch, die interne Verwal-tung zu verwenden und so schnell von Session-Variablen zu profitieren.

Vorbereitung

Wie bereits erwähnt, speichert PHP5 standardmäßig die Sitzungsdaten in Dateien.Als Ziel wird das temporäre Verzeichnis benutzt. In der Auslieferungsversion derphp.ini wird der Unix-Dateipfad verwendet. Da 90% aller Entwicklungsumgebun-gen unter Windows laufen, kommt es regelmäßig zu einer Fehlermeldung beimersten Start der Session-Verwaltung.

Wenn dieser Fehler auftritt, öffnen Sie die für Ihre Installation zuständige Dateiphp.ini. Wenn Sie nicht wissen, wo diese liegt, erstellen Sie ein Skript, in dem dieFunktion phpinfo aufgerufen wird. Im Kopf der Seite steht die benötigte Angabe:

Abbildung 8.25: Fehler, die Ses-sion-Verwaltung konnte die Daten nicht speichern

Abbildung 8.26: So ermitteln Sie die richtige

360

In dieser Datei suchen Sie nun nach folgender Zeile:

session.save_path = "/tmp"

php.ini

Page 361: 382726314 X Php5 In 14 Tagen (Ddt)

Cookies und Sessions

Ändern Sie die Zeile so, dass sie auf ein existierendes Verzeichnis zeigt:

session.save_path = "C:\Windows\Temp"

Noch ein Problem ergibt sich, wenn Skripte aus dem Internet übernommen wer-den, die Funktionen wie session_register verwenden. Es ist mit PHP5 nichtempfehlenswert, diese Funktionen einzusetzen, weil aus Sicherheitsgründeneinige damit verbundene Automatismen ausgeschaltet wurden. Das erschwert denUmgang. Einfacher ist es, das bereits mehrfach verwendete Schema mit dem glo-balen Array zu nutzen und das für die Session-Verwaltung zuständige Array$_SESSION zu verwenden.

Anwendung

Um die Session-Verwaltung zu verwenden, genügt es, am Anfang des Skripts fol-gende Funktion aufrufen:

session_start();

Nun sind noch die Variablen festzulegen, die als Teil der Session-Verwaltunggespeichert werden sollen. Dazu dient das $_SESSION-Array:

$_SESSION['VariablenName'] = $Variable;

Springt nun der Benutzer auf eine Folgeseite, steht dort, nachdem erneutsession_start aufgerufen wurde, das Array mitsamt Inhalt wieder zur Verfügung.Das folgende Beispiel zeigt, wie es in der Praxis funktioniert. Es realisiert eine ein-fache Benutzeranmeldung. Nach erfolgreicher Anmeldung sollen alle Seitenzugänglich sein:

Listing 8.31: sessionlogon.php – Anmeldeseite mit Speicherung der Anmeldedaten in einer Session-Variablen

<?phpsession_start();include('get_includenav.php');$target = "sessionpages.php";$action = $_SERVER['PHP_SELF'];

361

$names = array('Admin' => 'admin', 'Test' => 'test');function CreateLinks(){ global $titles, $target; $links = ''; foreach ($titles as $title)

Page 362: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

{ $links .= sprintf('<a href="%1$s?t=%2$s">%2$s</a><br/>', $target, $title); } return $links;}function CheckLogon(){ global $names; if ($_SERVER['REQUEST_METHOD'] != 'POST') return FALSE; foreach ($names as $Logon => $Password) { echo '#'; if (!empty($_POST['Logon']) && $_POST['Logon'] == $Logon && !empty($_POST['Password']) && $_POST['Password'] == $Password) { $_SESSION['LogonName'] = $Logon; return TRUE; } } return FALSE;}if ($_SERVER['REQUEST_METHOD'] == 'POST'){ CheckLogon();}?><form action="<?=$action;?>" method="post"><table> <tr> <td rowspan="2"> <?=CreateLinks();?> </td> <td>Name:</td>

362

<td> <input type="text" name="Logon"/> </td> </tr> <tr>

Page 363: 382726314 X Php5 In 14 Tagen (Ddt)

Cookies und Sessions

<td>Kennwort:</td> <td> <input type="password" name="Password"/> </td> </tr> <tr> <td colspan="2"></td> <td> <input type="submit" name="Submit" value="Anmelden"/> </td> </tr></table></form>

Der erste Prozess, der erkannt werden muss, ist das Absenden des Formulars. Nurdann kann der Benutzer eine Anmeldung versucht haben:

if ($_SERVER['REQUEST_METHOD'] == 'POST')

Ist das der Fall, wird die Funktion zum Prüfen der Anmeldedaten ausgeführt:

CheckLogon();

Diese Funktion sollte in praktischen Anwendungen durch eine spezifischerenVariante ersetzt werden. Hier wird nur gegen die Daten eines fest programmiertenArrays geprüft. Ist diese Prüfung erfolgreich, wird der Anmeldename in die Sessiongeschrieben:

$_SESSION['LogonName'] = $Logon;

Dieser Vorgang ist sicher, weil der Benutzer keine Möglichkeit hat, den Inhalt die-ses Arrays zu manipulieren. Beim Erzeugen der Links wird nun die erfolgreicheAnmeldung zur Anzeigesteuerung benutzt. Es ist nun wichtig, dass auf jeder belie-bigen Folgeseite die Information darüber vorliegt, ob die Anmeldung erfolgte. DasSkript sessionpages.php zeigt, wie dies aussehen kann:

Listing 8.32: sessionpages.php – Zugriff auf die Session-Variablen

<?phpsession_start();

363

function ReCreateVariables(){ foreach($_SESSION as $name => $value) { global $$name;

Page 364: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

$$name = $value; }}ReCreateVariables();$title = empty($_GET['title']) ? 'Startseite' : $_GET['title'];?><h1><?=$title?></h1>Sie sind angemeldet als: <?=$LogonName?>

Mit dem Aufruf von session_start stehen die gespeicherten Variablen im$_SESSION-Array wieder bereit. Sie können nun direkt darauf zugreifen. Falls eseinfacher oder praktikabler ist, alle Variablen wieder als globale verfügbar zumachen. Die Funktion ReCreateVariables() erledigt das. Dazu durchläuft man dasArray mit foreach:

foreach($_SESSION as $name => $value)

Nun wird über die Syntax der variablen Variablennamen die aktuelle lokale Vari-able $name zur globalen Variablen erklärt:

global $$name;

Dieser nun global deklarierten Variablen wird der entsprechende Wert zugewie-sen:

$$name = $value;

Wichtig ist hier nur, die zwei $$-Zeichen statt des normalerweise verwendeten ein-zelnen anzugeben, damit der Text in $name den Namen der Variable bestimmt,nicht die Variable selbst (indirekter Zugriff).

Wenn nun im $_SESSION-Array ein Eintrag mit dem Namen LogonName exis-tiert, steht dieser als Variable zur Verfügung, sodass folgendes funktioniert:

<?=$LogonName?>

Die Referenz Session-Verwaltung zeigt weitere Session-Funktionen. Deren An-wendung unterliegt teilweise Restriktionen der konkreten PHP-Installation. Derhier beschriebene Weg mit dem $_SESSION-Array ist nicht die einzige Möglich-keit, aber er funktioniert garantiert immer.

364

Page 365: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz

8.7 Referenz

Wichtige Systemarrays

Server- und Umgebungsvariablen

Name Beschreibung

$_GET Variablen einer GET-Anforderung, die als Teil des URL übertragen wurden.

$_POST Variablen einer POST-Anforderung, die als Teil eines Formulars übertragen wurden.

$_REQUEST Alle Variablen, egal ob per GET oder POST übertragen.

$_COOKIES Auflistung aller definierten und empfangenen Cookies der letzten Anforderung.

$_SESSION Auflistung aller Sitzungsvariablen und deren Werte. Dieses Array ist nur nutzbar, wenn die Sitzungsverwaltung aktiviert wurde.

$_FILES Informationen über zuletzt hochgeladene Dateien, deren Größe, MIME-Typ, Name und temporärer Name. Dieses Array ist nur gefüllt, wenn Formu-lare mit der entsprechenden Kodierung gesendet werden.

Tabelle 8.6: Systemarrays in PHP5

Name der Variable Beschreibung

ALL_HTTP Alle HTTP-Header, die vom Client zum Server gesen-det wurden. Das Ergebnis sind Header, die mit HTTP_ beginnen.

ALL_RAW Alle HTTP-Header, die vom Client zum Server gesen-det wurden. Im Ergebnis werden Header gesendet, die

365

kein Präfix haben.

APPL_MD_PATH Gibt den Pfad zur Metabasis der Applikation an (nur IIS/Windows).

Tabelle 8.7: Server- und Umgebungsvariablen

Page 366: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

APPL_PHYSICAL_PATH Gibt den physischen Pfad zur Metabasis der Applikation an (nur IIS/Windows).

AUTH_NAME Name des Nutzers bei Eingabe in das Kennwortfeld des Browsers.

AUTH_PASSWORD Das Kennwort einer Autorisierung, wenn es im Kenn-wortfeld des Browsers eingegeben wurde (nur Win-dows).

AUTH_TYPE Art der Autorisierung, wenn Nutzer Zugriff auf ein geschütztes Dokument haben möchten (nur Windows).

CERT_COOKIE Eindeutige ID eines Clientzertifikats.

CERT_FLAGS Flag des Clientzertifikats, Bit 0 ist 1, wenn das Client-zertifikat vorhanden ist, Bit 1 ist 1, wenn das Clientzerti-fikat nicht überprüft wurde.

CERT_ISSUER Das Issuer-(Herausgeber)-Feld des Clientzertifikats.

CERT_KEYSIZE Bitzahl bei einer SSL-Verbindung.

CERT_SECRETKEYSIZE Anzahl der Bits eines privaten Zertifikatschlüssels.

CERT_SERIALNUMBER Die Seriennummer des Zertifikats.

CERT_SERVER_ISSUER Das Issuer-(Herausgeber)-Feld des Serverzertifikats (Issuer-Feld).

CERT_SERVER_SUBJECT Beschreibung des Zertifikats (Server).

CERT_SUBJECT Beschreibung des Zertifikats (Client).

CONTENT_LENGTH Länge des zu sendenden Inhalts.

CONTENT_TYPE Art des Inhalts (MIME-Type).

DATE_GMT Datum/Uhrzeit des Servers in Zone GMT.

Name der Variable Beschreibung

366

DATE_LOCAL Datum/Uhrzeit des Servers.

DOCUMENT_NAME Name des ausführenden Dokuments.

Tabelle 8.7: Server- und Umgebungsvariablen (Forts.)

Page 367: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz

DOCUMENT_ROOT Pfad zum Dokument ohne Dateiname.

GATEWAY_INTERFACE Art des Interfaces, das der Server benutzt.

HTTP_ACCEPT Enthält die MIME-Typen, die der Browser akzeptieren kann und will.

HTTP_COOKIE Die Cookie-Daten, wenn Cookies gesendet wurden.

HTTP_REFERER Die letzte Adresse, von welcher der Browser kam. Wurde die Seite direkt aufgerufen, ist der Wert leer.

HTTP_USER_AGENT Kennung des Browsers, beispielsweise Mozilla/4.0 (compatible; MSIE 5.0; Windows NT). Der Wert kann auf vielen Systemen manipuliert werden und ist deshalb mit Vorsicht zu betrachten.

HTTPS Ist ON, wenn der Server SSL benutzt.

HTTPS_KEYSIZE Schlüssellänge der HTTPS-Verbindung (40bit, 128bit...).

HTTPS_SECRETKEYSIZE Schlüssellänge bei privaten Zertifikaten.

HTTPS_SERVER_ISSUER Issuer-Feld des Serverzertifikats bei sicherer Über-tragung.

HTTPS_SERVER_SUBJECT Eine Beschreibung des Servers.

INSTANCE_ID ID-Nummer der Instanz (nur IIS).

INSTANCE_META_PATH Der Metabasispfad (nur IIS).

LOCAL_ADDR Die in der Anforderung benutzte Serveradresse.

LOGON_USER Das lokale Benutzerkonto.

PATH_INFO Pfadinformation für den Client.

PATH_TRANSLATED Übertragung der Pfadinformation ins physische Format.

Name der Variable Beschreibung

367

QUERY_STRING Inhalt des Querystrings (Parameter-URL).

REMOTE_ADDR Die IP-Adresse des Nutzers.

Tabelle 8.7: Server- und Umgebungsvariablen (Forts.)

Page 368: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

Der Abruf erfolgt immer mit $_SERVER['NAME']. Beachten Sie, dass nicht auf allenServern alle Variablen zur Verfügung stehen und dass sich die Inhalte teilweisegeringfügig unterscheiden, beispielsweise bei Pfadangaben.

Session-Verwaltung

REMOTE_HOST Der Name des Computers des Nutzers.

REQUEST_METHOD Die Methode der Datenübertragung eines Formulars. Kann GET, PUT oder POST sein.

REQUEST_URI URI der Anforderung.

SCRIPT_FILENAME Name eines Skripts.

SCRIPT_NAME Name eines Skripts.

SERVER_NAME Der Hostname des Servers, eine DNS- oder IP-Adresse.

SERVER_PORT Port, der vom Server benutzt wird (normalerweise 80).

SERVER_PORT_SECURE Port, der bei sicherer Übertragung benutzt wird (Standard: 443).

SERVER_PROTOCOL Das verwendete Protokoll und die Version (beispiels-weise HTTP1.1).

SERVER_SIGNATURE Signatur des Servers (einstellbare Info).

SERVER_SOFTWARE Der Name und die Version der auf dem Server laufen-den Software.

Funktion Beschreibung

Name der Variable Beschreibung

Tabelle 8.7: Server- und Umgebungsvariablen (Forts.)

368

session_cache_expire Die aktuelle Cache-Verfallszeit.

session_cache_limiter Art der Cacheverwaltung.

session_decode Dekodiert die Daten einer Session.

Tabelle 8.8: Funktionen der Session-Verwaltung

Page 369: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz

session_destroy Beendet die Session und zerstört alle Daten.

session_encode Kodiert die Daten der aktuellen Session.

session_get_cookie_params Liefert die Session-Cookie Parameter.

session_id Setzt oder ermittelt die aktuelle Session-ID.

session_is_registered Überprüft, ob eine globale Variable in einer Session bereits registriert ist.

session_module_name Ermittelt oder setzt das aktuelle Session-Modul.

session_name Ermittelt oder setzt den Namen der aktuellen Session.

session_regenerate_id Erzeugt eine neue Session-ID, ohne die Session dabei zu beenden.

session_register Registriert eine Variable als Session-Variable.

session_save_path Ermittelt oder setzt den aktuellen Speicherpfad der Session (zum temporären Verzeichnis).

session_set_cookie_params Setzt die Parameter für das Session-Cookie.

session_set_save_handler Setzt benutzerdefinierte Rückruffunktionen.

session_start Initialisiert eine Session.

session_unregister Nimmt eine Variable aus den Session-Variablen wieder raus.

session_unset Löscht alle Session-Variablen.

session_write_close Speichert alle Session-Daten und beendet die Session.

Initialisierungseintrag Beschreibung

Funktion Beschreibung

Tabelle 8.8: Funktionen der Session-Verwaltung (Forts.)

369

session.name Der Name der Session. Dieser Wert wird für den Url-Para-meter und das Cookie verwendet. Der Standardwert ist PHPSESSID.

Tabelle 8.9: Konfiguration der Session-Verwaltung

Page 370: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

session.auto_start Wenn aktiviert, wird bei einer neuen Anfrage eine neue Session gestartet, nicht erst beim Aufruf von session_start. Standardmäßig deaktiviert (0), zum Aktivieren auf 1 setzen.

session.serialize_handler Name der Methode, mit der die Daten serialisiert werden. Standardwert ist »php«, Alternative ist »wddx«.

session.gc_probability Regelt die Wahrscheinlichkeit, mit der die Aufräumroutine alte Session-Daten beseitigt. Standardwert ist 1.

session.gc_divisor Regelt die Wahrscheinlichkeit, mit der die Aufräumroutine alte Session-Daten beseitigt, im Verbund mit »gc_probability«. Die finale Wahrscheinlichkeit beträgt gc_probability/gc_divisor. Der Standardwert ist 100, dass heißt die Aufräumroutine startet mit einer Wahrscheinlich-keit von 1%.

session.gc_maxlifetime Lebensdauer einer Session in Sekunden. Der Standardwert beträgt 1440 (24 Minuten).

session.referer_check Zeichenkette, die im Referer (Quelle des HTTP-Abrufs) enthalten sein muss. Wenn aktiviert, verhindert man damit den Einsprung in eine Session von außerhalb. Standard-mäßig nicht verwendet.

session.entropy_file Pfad einer Quelldatei, die beim Erzeugen der Session-ID benutzt wird. Standardmäßig nicht verwendet.

session.entropy_length Anzahl der Bytes, die aus der Entropie-Datei gelesen wer-den. Standardwert ist 0.

session.use_cookies Erlaubt Cookies. Standardwert ist 1 (aktiviert).

session.use_only_cookies Erzwingt (!) Cookies. Cookies werden auch so verwendet, aber mit dieser Option kann man alternative Wege aus Kompatibilitätsgründen verhindern. Standardmäßig nicht aktiviert (0).

Initialisierungseintrag Beschreibung

370

session.cookie_lifetime Lebensdauer des Cookies. Standardwert ist 0, was typische Session-Cookies erzeugt.

session.cookie_path Pfad-Angabe des Session-Cookies.

Tabelle 8.9: Konfiguration der Session-Verwaltung (Forts.)

Page 371: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz

session.cookie_domain Domain-Angabe des Session-Cookies.

session.cookie_secure Erzwingt die Verwendung sicherer Verbindungen für das Cookie.

session.cache_limiter Methode der Cacheverwaltung. Standardwert ist »noca-che« (nicht verwendet).

session.cache_expire Verfallszeitpunkt des Cache in Minuten. Standardwert ist 180.

session.use_trans_sid Aktiviert die transparente SID-Umsetzung, bei der ver-steckte Felder und URL-Erweiterung zur Übergabe der Session-ID verwendet werden.

session.bug_compat_42 Erzeugt eine Warnung, wenn versucht wird, mit einem Trick globale Variablen mit Session-Daten zu erzeugen, obwohl dies durch andere Einstellungen verboten ist und zusätzlich bug_compat_warn aktiviert wurde.

session.bug_compat_warn Erzeugt eine Warnung, wenn versucht wird, mit einem Trick globale Variablen mit Session-Daten zu erzeugen, obwohl dies durch andere Einstellungen verboten ist.

session.hash_function Prüfsummenfunktion für die Session-ID. 0 = MD5 (128 Bit), 1 = SHA-1 (160 Bit). Standard ist MD5.

session.hash_bits_per_character

Gespeicherte Bits pro Zeichen in der Session-ID. Kann 4, 5 oder 6 sein. 4 ist der Standardwert.

url_rewriter.tags Tags, die zur Aufnahme der Session-ID umgeschrieben werden dürfen.

Initialisierungseintrag Beschreibung

Tabelle 8.9: Konfiguration der Session-Verwaltung (Forts.)

371

Page 372: 382726314 X Php5 In 14 Tagen (Ddt)

Formular- und Seitenmanagement

8.8 Kontrollfragen

1. Warum sollten unbedingt immer »Sticky Forms« verwendet werden?

2. Welche Funktionen unterstützen das Hochladen von Dateien? Was ist beim Auf-bau des Formulars zu beachten?

3. Welche Methoden verwenden Sessions, um die Session-Daten zu speichern?

4. Wie können Cookies missbraucht werden?

372

Page 373: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Pr g

ogrammierun

9

Page 374: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

9.1 Mehrsprachige Webseiten

Immer häufiger wird gefordert, Webseiten mehrsprachig anzubieten. Es ist geradefür kleine Unternehmen ein Segen, Kunden überall auf der Welt ansprechen zukönnen. Mit Deutsch allein kommt man dabei aber nicht weit, mit Englisch wie-derum kann man hierzulande nicht viel erreichen. Es ist deshalb oft notwendig,Websites in mehreren Sprachen anzubieten. Damit der Aufwand nicht ins Uner-messliche steigt, gibt es verschiedene Techniken, Gestaltung und Inhalt zu tren-nen, sodass die aufwändige Grafik nur einmal existiert, die variablen Inhaltedagegen getrennt gehalten werden.

Vor der Auswahl der Sprache steht jedoch die Erkennung der Daten durch denBenutzer. Dies erfolgt durch Auswertung der vom Browser übermittelten Daten.

Browserdaten erkennen

Der Browser überträgt verschiedene Informationen an den Server, die sich dann inPHP-Skripten auswerten lassen. Neben Betriebssystem, Name des Browsers undverschiedenen Angaben zu akzeptierten Daten gehört auch die bevorzugte Spra-che dazu.

Sprache im Browser einrichten

Jeder Benutzer sollte in seinem Browser die bevorzugte Sprache einrichten, damitSkripte, wie die nachfolgend vorgestellten, auch funktionieren. Die entsprechen-den Menükommandos zeigt die folgende Liste:

� Netscape/Mozilla: Bearbeiten | Einstellungen | Navigator | Sprachen

� Internet Explorer: Extras | Internetoptionen | Allgemein | Sprachen

� Opera: Datei | Einstellungen | Sprachen

� Tango: Sprache | Spracheinstellungen

374

� Lynx 2.7: O (Options) | G (lanGuage)

Nach dieser Angabe ist natürlich interessant zu wissen, was davon in PHP5ankommt. Benutzt wird eine Servervariable aus dem Array $_SERVER:HTTP_ACCEPT_LANGUAGE.

Page 375: 382726314 X Php5 In 14 Tagen (Ddt)

Mehrsprachige Webseiten

Diese enthält eine Liste der akzeptierten Sprachen mit einer Angabe der Wertig-keit. Die erste Sprache ist immer die bevorzugte, der Rest teilt sich mit abnehmen-der Wichtung in Relation zu 1. Das »q« steht für »Quality«. Die in der letztenAbbildung gezeigte Angabe sendet der Browser folgendermaßen:

de,en-us;q=0.7,fr;q=0.3

Das bedeutet, die Hauptsprache ist Deutsch, Englisch wird mit 0,7 gegenüberFranzösisch mit 0,3 bevorzugt, wobei 1 = ideal wäre. Die meisten Browser legendie Werte selbst fest, sodass hier wenig Wahl besteht. Tatsächlich wäre es auchmöglich, folgende Angabe zu machen:

de,en-us;q=0.8,e-gb;q=0.7

Hier wird mitgeteilt, dass Deutsch ideal ist, amerikanisches Englisch akzeptiertwird und alternativ auch britisches Englisch gern gelesen wird.

Sie sollten als Programmierer nicht darauf vertrauen, dass irgendwermehr als die Standardsprache halbwegs sinnvoll eingestellt hat. Die fol-genden Anwendungen werten deshalb auch nur diesen Teil aus.

Abbildung 9.1: Spracheinstellungen im Internet Explorer

375

Sprache auswerten

Das Auswerten der Sprache ist nun recht einfach. Man muss nur die Servervari-able abfragen und den ersten Teil, bis zum Komma, abtrennen:

Page 376: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

Listing 9.1: browserlang.php – Sprachakzeptanz abfragen und Hauptsprache ermitteln

<?php$lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];echo "Sprachinfo des Browsers: $lang<br>";$l = str_replace(strstr($lang, ','), '', $lang);echo "Erkannten Hauptsprache: <b>$l</b>";?>

Das Skript nutzt Zeichenkettenfunktionen, um den Anfang der Sprachinformationund damit die Hauptsprache herauszufinden. Für die Auswertung der übrigenInformationen bietet sich der Zugriff auf reguläre Ausdrücke oder die Zerlegungmit explode an.

Steht die Sprache fest, muss nur noch entsprechend darauf reagiert werden.

Lokalisierung und Formatierung von Zeichen

Die Lokalisierung basiert meist auf der Anwendung der Funktion setlocale, diedas Verhalten anderer Funktionen beeinflusst. Allerdings ist der Umgang damitrecht tückisch. Leider hat sich dies mit PHP5 nicht gebessert, was umso unverständ-licher ist, als dass neuere Programmierumgebungen wie .NET auch unter Windowsden gängigen Standards folgen und es keinen Grund gibt, dies nicht zu adaptieren.

Generell gilt folgende Regel für Linux-Systeme:

� Das Basisformat ist »Sprache_Land«, also beispielsweise »de_DE« oder»de_AT«

� Braucht man bestimmte Sonderzeichen einer Sprache, die bei der Ausgabeangezeigt werden sollen, kann man eine Codeseite angeben: »kr_KR.949«.

Windows-Systeme erwarten statt der Ländercodes Sprachnamen, abgekürzt oder

Abbildung 9.2: Sprachinfo und extrahierte Hauptsprache

376

ausgeschrieben:

Das Basisformat ist »Germany« oder »ge« bzw. »German_Germany« oder»German_Austria«. Es ist also in den allermeisten Fällen erforderlich, das vomBrowser gesendete Sprachkürzel »de« in die passende Form umzuwandeln.

Page 377: 382726314 X Php5 In 14 Tagen (Ddt)

Mehrsprachige Webseiten

Die Funktion setlocale anwenden

Alle Formate, die printf und verwandte Funktionen benutzen, reagieren auf dieEinstellungen mit setlocale. Damit ist es möglich, die korrekten Formatierungenfür Zahlen zu erhalten, also Komma als Dezimaltrennzeichen in deutschen Tex-ten usw. Außerdem lassen sich Zeitangaben bei der Ausgabe mit strftime in diepassende Sprache setzen.

Für die Formatierung von Zahlen muss setlocale entweder mit der KonstantenLC_ALL oder LC_NUMERIC bedacht werden:

setlocale(LC_NUMERIC, "German_Germany");

Die Angabe muss vor der ersten Ausgabe mit einer der printf-Funktionen erfol-gen. Um die Rückstellung der vorherigen Angabe zu ermöglichen, können Sie denRückgabewert von setlocale abfragen.

Die folgende Tabelle zeigt alle Konstanten, die setlocale benutzt:

strcoll entspricht der Funktion strcmp mit dem Unterschied, dass dieLokalisierung berücksichtig wird. strcmp ignoriert diese.

Konstante Bedeutung Betroffene Funktion

LC_ALL Beeinflusst alle Formate printf

LC_COLLATE Beeinflusst Zeichenkettenvergleiche strcoll

LC_CTYPE Klassifizierung von Zeichen strtoupper

LC_MONETARY Betrifft Währungsangaben localeconv

LC_NUMERIC Betrifft Zahlenangaben localeconv

LC_TIME Beeinflusst Datums- und Zeitangaben strftime

Tabelle 9.1: Konstanten, die setlocale benutzt

377

Die Anwendung der Funktion kann auch auf Bedingungen des Betriebssystemsabgestimmt werden. Wenn der zweite Parameter leer ist (nicht fehlend, sonderneine leere Zeichenkette), dann wird versucht, eine Umgebungsvariable zu lesen,deren Namen der Konstanten entspricht. Der folgende Ausdruck liest die Umge-bungsvariable LC_NUMERIC und setzt setlocale entsprechend:

Page 378: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

setlocale(LC_NUMERIC, "");

Clever ist es auch, eine Fallbackmöglichkeit zu schaffen. Wie anfangs bereitsbeschrieben, sind auf den verschiedenen Betriebssystemen die Lokalisierungszei-chenfolgen zu beachten. Deshalb kann an dieser Stelle auch ein Array angegebenwerden:

setlocale(LC_NUMERIC, array("de", "de_DE", "German_Germany"));

Es gibt natürlich auch die Möglichkeit, hier die Sprache zu wechseln, um unterallen Umständen ein reproduzierbares Ergebnis zu erzielen.

Das Format der aktuellen Lokalisierung ermitteln

Um festzustellen, wie PHP5 intern arbeitet und wie sich die Parameter der Lokali-sierung auswirken, gibt es die Funktion localeconv. Sie gibt ein Array zurück, dasdie entsprechenden Angaben enthält. Die folgende Abbildung zeigt die Ausgabefür die Lokalisierung Deutsch:

Abbildung 9.3: Ausgabe der Lokalisie-rungszeichen für Deutsch/Deutschland (links) und Deutsch/

378

Die Angaben können hilfreich sein, um den Platzbedarf von Zeichenketten zuberechnen oder die Wirkung der Lokalisierung zu überwachen. Um die Tabellenzu erstellen, wurde folgendes Skript verwendet:

Schweiz (rechts)

Page 379: 382726314 X Php5 In 14 Tagen (Ddt)

Dynamisch Bilder erzeugen

Listing 9.2: localeconv.php – Lokalisierungsangaben ausgeben

<table border="1" style="font-family:Verdana; font-weight:bold"><?phpsetlocale(LC_ALL, "German_Switzerland");$lc = localeconv();foreach ($lc as $key => $val){

echo "<tr><td>$key</td>";echo "<td>$val</td></tr>";

}?></table>

9.2 Dynamisch Bilder erzeugen

Das dynamische Erzeugen von Bildern gehört mit zu den am häufigsten diskutier-ten Möglichkeiten von PHP. Die mit PHP5 verfügbare neue GrafikbibliothekGD2 stellt neue Funktionen bereit, um professionell wirkende Grafiken dyna-misch zu erstellen.

Prinzip

Um das Prinzip der Erzeugung dynamischer Bilder zu verstehen, muss man sichnoch mal den Ablauf des Seitenaufbaus im Browser in Erinnerung rufen. Nachdem Laden der HTML-Seite analysiert der Browser den Inhalt und beginnt mitder Darstellung. Findet er ein Bild, leitet er für die angegebene Bildquelle eineweitere asynchrone1 HTTP-Anforderung ein. Der dann zurückgegebene binäreDatenstrom wird wiederum interpretiert und, ins entsprechende Bildformatzurückgewandelt, zur Darstellung verwendet. Bei dem Vorgang an sich ist es demBrowser egal, woher das Bild kommt. Er erwartet letztlich nur einen simplenDatenstrom. Den kann man erzeugen, indem man auf dem Server ein Bild ablegt

379

und dem Webserver die Auslieferung überlässt. Es ist aber auch möglich, dieArbeit von einem Skript erledigen zu lassen, das die Daten fachgerecht generiert.Als Quelle wird dann einfach ein PHP-Skript genommen:

<img src="imagegen.php"/>

1 Deswegen erscheinen die Bilder manchmal erst viel später.

Page 380: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

Dem Skript kann man selbstverständlich auch GET-Parameter übergeben, um dasVerhalten zu steuern:

<img src="imagegen.php?name=Joerg%20Krause"/>

Aufbau eines bilderzeugenden Skripts

Für ein bilderzeugendes Skript muss man mehrere Dinge beachten:

� Es dürfen ausschließlich Bilddaten erzeugt werden. Schon ein einziges Leer-zeichen zerstört das Bild.

� Es müssen die passenden Kopfzeilen erzeugt werden, damit der Browser weiß,welcher Bildtyp zu den Binärdaten passt.

� Das Skript sollte relativ schnell und effizient sein, sonst verzögert sich der Bild-aufbau erheblich.

Die passenden Kopfzeilen werden mit der Funktion header erzeugt. Die Ausgabenselbst können wie üblich mit printf oder echo gesendet werden. Für header wirddie Angabe des Inhaltstyps (Content-type) benötigt. Der konkrete Typ richtet sichnach der Bildart. Möglich sind folgende Werte:

� image/gif

� image/jpg

� image/png

� image/wbmp (für WAP)

Der Aufruf der Funktion sieht dann beispielsweise für ein PNG-Bild folgender-maßen aus:

header ('Content-type: image/png');

Es ist übrigens gut möglich, zusätzlich noch Cookies an das Bild anzu-hängen, was oft bei Werbebannern gemacht wird. Werbebanner sindletztlich auch nur Bilder (mal von Flash-Bannern abgesehen) und lassensich sehr gut dynamisch erzeugen.

380

Typische Probleme

Gleich vorab soll auf einige typische Probleme hingewiesen werden. Da der Brow-ser ein Bild erwatet, wird er eine Fehlerausgabe des Skripts nicht interpretieren

Page 381: 382726314 X Php5 In 14 Tagen (Ddt)

Dynamisch Bilder erzeugen

können. Der Internet Explorer zeigt beispielsweise ein Ersatzbild mit einem rotenKreuz – aber eben keine Fehlermeldung. Hierfür gibt es mehrere Lösungen:

� Erzeugen Sie im Fehlerfall andere Kopfzeilen, die die Ausgabe von Text erlau-ben.

� Geben Sie ein (vorher korrekt programmiertes) Ersatzbild aus, das den Fehleranzeigt.

� Verwenden Sie try/catch und den @-Operator, um Fehler abzufangen und dieFehlertexte in eine Protokolldatei zu schreiben.

Allen Varianten gemeinsam ist, dass die Fehlerausgabe mehr Aufwand verursachtals bei normalen Skripten. Der erreichte Effekt einer dynamischen Bildausgabe istallerdings oft diesen Mehraufwand wert.

Einführung in die Grafikbibliothek GD2

Vor den ersten Versuchen mit der Grafikbibliothek GD2 sollten Sie testen, ob dieErweiterung aktiviert wurde und zur Verfügung steht. Der Aufruf von phpinfo istder ideale Platz dafür.

Prinzip der Bilderzeugung

Abbildung 9.4: GD2 wurde erfolg-reich aktiviert

381

Die Bilderzeugung erfolgt in zwei Stufen. Zuerst muss eine Zeichenfläche erstelltwerden, auf der anschließend alle Zeichen- und Textoperationen stattfinden.Dazu dient die Funktion imagecreate:

$img = imagecreate(480, 80);

Page 382: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

Der Bildtyp, also GIF oder PNG, muss hier noch nicht festgelegt werden. Das Zei-chenmodul arbeitet generell mit einem internen Format, aus dem alle anderenFormate verlustfrei erstellt werden können.

Der Rückgabewert $img enthält nun ein Handle auf die interne Abbildung des Bil-des. Alle folgenden Funktionen nutzen dieses Handle zum Zugriff.

Sind alle Bildoperationen fertig, wird das Bild erzeugt und an den Browser gesen-det. Dazu dienen die Funktion imagegif, imagepng, imagejpeg usw.:

imagegif($img);

Der Suffix bestimmt dabei den Bildtyp, der sich je nach Applikation unterscheidenkann.

Alternativ zur Erzeugung eines neuen Bilds kann die Bilddatei auf einem bereitsexistierenden Bild basieren. Dazu dienen die Funktionen imagecreatefromgif,imagecreatefrompng, imagecreatefromjpeg usw.

$img = imagecreatefromgif('pfad/zur/datei.gif');

Die Bildgröße wird dabei zwangsläufig von der Quelldatei bestimmt. Liegt das Bildvor, kann man nun entweder Text darauf schreiben oder mit Bildfunktionenmalen. Die Vielfalt der Funktionen lässt eine ausführliche Betrachtung in diesemRahmen nicht zu, deshalb sollen zwei Beispiele das Prinzip demonstrieren.

Textausgabe

Die Textausgabe ist deshalb ein schwieriges Thema, weil der Text auf der Flächeplatziert werden muss, was nicht einfach ist, wenn die Menge nicht passt. Hiermuss gegebenenfalls gerechnet werden. Ausgangspunkt ist die Funktionimagettfbbox, die die voraussichtlichen Maße des Textes in Abhängigkeit vomFont (Schriftart) und der verlangten Größe ermittelt. Die Funktion gibt einArray zurück, dessen Elemente die entsprechenden Angaben enthalten:

Index Beschreibung

0 Links oben, X

382

1 Links unten, Y

2 Rechts unten, X

Tabelle 9.2: Bedeutung der Elemente des mit imagettfbbox erzeugten Arrays

Page 383: 382726314 X Php5 In 14 Tagen (Ddt)

Dynamisch Bilder erzeugen

Die Funktion kann einen Winkel einbeziehen, um den der Text gedrehterscheint. Die Koordinaten, die den Platzbedarf angeben, sind jedoch immer abso-lut in den Ecken eines virtuellen Rechtecks, das im Fall einer Drehung entspre-chend groß ist, um den Text aufzunehmen.

Aus den Daten lässt sich nun entnehmen, ob der Text passt und man kann entspre-chend reagieren, um den Text gegebenenfalls passend zu machen. Stimmt alles,erfolgt die eigentliche Ausgabe mit imagettftext. Benötigt werden folgende Angaben:

� Position der linken, oberen Ecke in zwei Werten für x und y.

� Winkel, um den der Text gedreht erscheint.

� Schriftart, in Form eines Pfades zu einer TrueType-Datei.

� Schrifthöhe in Punkt (1 Punkt entspricht 1/72«).

� Die Farbe, die benutzt werden soll.

� Der Text, der dargestellt wird.

Die Schriftart wird als Pfad zu einer TrueType-Datei angegeben. Achten Sie hierdarauf, dass nur Dateien verwendet werden, zu denen auch die erforderlichenRechte vorhanden sind. Das Herauskopieren aus anderen Anwendungen funktio-niert zwar meist, ist aber nur selten legal. Frei Fonts sind im Internet leicht zu fin-den, wenngleich diese auch eine schwankende Qualität haben. Wer seinen Server

3 Rechts unten, Y

4 Oben rechts, X

5 Oben rechts, Y

6 Oben links, X

7 Oben links, Y

Index Beschreibung

Tabelle 9.2: Bedeutung der Elemente des mit imagettfbbox erzeugten Arrays (Forts.)

383

unter Windows betreibt hat es da leichter, denn er kann die typischen Windows-Fonts nutzen, beispielsweise Verdana oder Tahoma.

Die Farbe wird als RGB-Wert erwartet, der vorher in der Farbtabelle der Bildbasisregistriert werden muss. Dabei geht die GD2-Bibliothek so vor, dass die erste Farb-zuweisung immer die Hintergrundfarbe festlegt, während alle Folgenden der Farb-

Page 384: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

tabelle zugeordnet werden. Jede Zuweisung gibt ein Handle auf die Farbe zurückund dieses Handle ist zu verwenden. Im folgenden Fragment ist $img das Handleauf das Bild und $handle_black das Handle auf die Farbe (nach der Ausführung).Die definierte Farbe ist schwarz:

$handle_black = imagecolorallocate($img, 0, 0, 0);

Die drei Werte stehen für die Farbkanäle Rot, Grün und Blau. Erlaubt sind Anga-ben zwischen 0 (kein Anteil der Farbe) und 255 (voller Anteil).

Um hexadezimale Angaben zu verwenden, bietet sich die Angabe vonHex-Literalen der Art 0xFF an.

Das folgende Beispiel erzeugt einen Text auf einer grauen Grundfläche:

Listing 9.3: imagettftext.php – Text auf einer Bildfläche ausgeben

<?php$size = 44;$font = 'fonts/verdana.ttf';$text = 'Überschrift';$img = imagecreate(480, 80);imagecolorallocate($img, 0xCC, 0xCC, 0xCC);$handle_black = imagecolorallocate($img, 0, 0, 0);imagettftext($img, $size, 0, 80, 65, $handle_black, $font, $text);header ('Content-type: image/gif');imagegif($img);?>

Das Skript wendet sehr direkt die zuvor beschriebenen Funktionen an. Es erzeugtdie folgende Ausgabe:

Abbildung 9.5: Dynamisch erzeugtes Bild

384

Soweit das Skript nicht unmittelbar nach der Ausgabe endet, ist es empfehlens-wert, den belegten Speicherplatz sofort freizugeben. Bedenken Sie, dass Bildererheblichen Speicherplatz in Anspruch nehmen können. Das sehr einfache Bilddes letzten Beispiels benötigt etwa 115 KByte.

Page 385: 382726314 X Php5 In 14 Tagen (Ddt)

Dynamisch Bilder erzeugen

Bildfunktionen

Ähnlich wie bei Text geht es auch bei den Bildfunktionen. Typische Operationenumfassen hier das Zeichnen von Rechtecken, Kreisen, Linien oder Punkten. Teilder Bildfunktionen ist aber auch das Verarbeiten anderer Bilder, das Filtern, Ver-zerren, Kopieren, Zerschneiden usw.

Das folgende Beispiel zeichnet olympische Ringe (es ist während der Sommer-olympiade 2004 in Athen entstanden):

Listing 9.4: imagerings.php – Dynamisches Zeichnen olympischer Ringe

<?phpclass Circle{ public $x; public $y; public $color; const RAD = 50; public function __construct($x, $y, $color) { $this->x = $x; $this->y = $y; $this->color = $color; }}$size = 14;$font = 'fonts/verdana.ttf';$text = 'Athen 2004';$img = imagecreate(280, 180);$white = imagecolorallocate($img, 0xFF, 0xFF, 0xFF);$blue = imagecolorallocate($img, 0, 0, 0xFF);$black = imagecolorallocate($img, 0, 0, 0);$red = imagecolorallocate($img, 0xFF, 0, 0);$yellow = imagecolorallocate($img, 0xFF, 0xFF, 0);$green = imagecolorallocate($img, 0, 0x64, 0);$pos = array(new Circle(70, 50, $blue),

385

new Circle(105, 80, $yellow), new Circle(140, 50, $black ), new Circle(175, 80, $green), new Circle(210, 50, $red));foreach ($pos as $point)

Page 386: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

{ for ($i = 0; $i < 15; $i++) { imagearc($img, $point->x, $point->y, � Circle::RAD+$i, Circle::RAD+$i, � 0, 360, $point->color); } imagearc($img, $point->x, $point->y, � Circle::RAD+$i, Circle::RAD+$i, � 0, 360, $white); }header ('Content-type: image/gif');imagegif($img);?>

Das Skript ist keineswegs perfekt, aber es zeigt das Prinzip und die Möglichkeiten.Vor allem die korrekte Verschlingung der Ringe wäre weitaus aufwändiger. DieAnwendung der Klasse Circle demonstriert dennoch, wie die Zeichenfunktionenvon objektorientierten Ansätzen profitieren können. Die Darstellung der Kreise alsObjekte vereinfacht das Skript erheblich und trägt wesentlich zur Lesbarkeit bei.

Wenn Sie das Skript laufen lassen, können sie die farbige Version erleben, die derDruck nur mangelhaft wiedergeben kann.

Anwendungsbeispiel »Dynamischer Werbebanner«

Das folgende Beispiel zeigt die Anwendung der GD2-Bibliothek anhand einerKlasse, die dynamisch rechteckige Bereiche erstellt und mit Bildern, Farben und

Abbildung 9.6: Vereinfachte Version der olympischen Ringe, mit GD2 erstellt

386

Texten füllt. Anhand von weiteren Parametern kann man daraus beispielsweisedynamisch Banner erstellen, deren Inhalt je nach Situation und Benutzer variiert.

Page 387: 382726314 X Php5 In 14 Tagen (Ddt)

Dynamisch Bilder erzeugen

Nutzung

Die Klasse erwartet die Werte als GET-Parameter. Dieser Teil lässt sich freilichleicht anpassen. Die folgende Tabelle zeigt Namen und Bedeutung der Parameter:

Quellcode der Klasse

Die Klasse nutzt konsequent die neuen Möglichkeiten von PHP5 und eignet sichideal für den Einbau in eigene Projekte. Ein kleines Testskript folgt am Ende.

Listing 9.5: gd2lib.inc.php – Hilfsklasse zum Erstellen von Bannern

<?php

Parameter Bedeutung

W Breite

H Höhe

A Ausrichtung, beispielsweise »left«

C Farbe des Textes

FF Font, der verwendet werden soll

FS Schrifthöhe in Punkt

BG Pfad zu einem Hintergrundbild. Das Bild bestimmt die Größe des Banners, sodass die Werte H und W ignoriert werden

BC Hintergrundfarbe, wird nur akzeptiert, wenn kein Hintergrundbild angege-ben wird

B Randbreite in Pixel

TXT Der Text, der auf dem Banner angezeigt wird

Tabelle 9.3: Bedeutung der Parameter zur Steuerung der Grafikklasse

387

class NoticeException extends Exception{}class ImagingException extends Exception{

Page 388: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

public function __construct($msg) { parent::__construct($msg); }}class LSImage{ private $intWidth; private $intHeight; private $strAlign; private $strColor; private $strFontFace; private $strFontSize; private $intBorder; private $strBackColor; private $strBackImage; private $strText; private $strType; private $im; private $dx; private $dy; const FONTPATH = 'fonts/'; public function __construct($strSetType = 'png') { set_error_handler(array($this, 'throwError')); $this->strType = $strSetType; $arrURI = $_GET; if ($arrURI['LSF'] = 'image') { if (isset($arrURI['BG'])) { $this->strBackImage = $arrURI['BG']; } else {

388

$this->strBackColor= $arrURI['BC']; } $this->intWidth = $arrURI['W']; $this->intHeight = $arrURI['H']; $this->strAlign = $arrURI['A'];

Page 389: 382726314 X Php5 In 14 Tagen (Ddt)

Dynamisch Bilder erzeugen

$this->strColor = $arrURI['C']; $this->strFontFace = self::FONTPATH � . strtolower($arrURI['FF']); if (substr($this->strFontFace, -4) != '.ttf') $this->strFontFace .= '.ttf'; if (!file_exists ($this->strFontFace)) $this->strFontFace = self::FONTPATH . 'verdana.ttf'; $this->strFontSize = $arrURI['FS']; $this->intBorder = $arrURI['B']; $this->strText=stripslashes(urldecode($arrURI['TXT'])); } } private function throwError($errno, $err, $line, $file) { throw new NoticeException("$errno, $err, $line, $file"); } private function getcolor($strColor) { $r = hexdec(substr($strColor, 0, 2)); $g = hexdec(substr($strColor, 2, 2)); $b = hexdec(substr($strColor, 4, 2)); return imagecolorallocate($this->im, $r, $g, $b); } private function CheckBackgroundSize() { if ($this->intWidth != imagesx($this->im) � or $this->intHeight != imagesy($this->im)) { return true; } else { return false; } } private function create()

389

{ $pi = pathinfo($this->strBackImage); $extension = isset($pi['extension']) ? $pi['extension'] : ''; switch ($extension) {

Page 390: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

case 'jpg': case 'jpeg': $this->im = imagecreatefromjpeg($this->strBackImage); break; case 'gif': $this->im = imagecreatefromgif($this->strBackImage); break; case 'png': $this->im = imagecreatefrompng($this->strBackImage); break; default: $this->im = imagecreate($this->intWidth, � $this->intHeight); } if ($this->im === FALSE) { throw new ImageException('Kein Hintergrund und � keine Bildgröße angegeben'); } if ($this->CheckBackgroundSize()) { $bi = imagecreate($this->intWidth, $this->intHeight); if (function_exists('imagecopyresampled')) imagecopyresampled($bi, $this->im, 0, 0, 0, 0, � imagesx($bi), imagesy($bi), � imagesx($this->im), � imagesy($this->im)); else imagecopyresized($bi, $this->im, 0, 0, 0, 0, � imagesx($bi), � imagesy($bi), � imagesx($this->im), � imagesy($this->im)); $this->im = $bi; } $this->getcolor($this->strBackColor);

390

$arr = imagettfbbox($this->strFontSize, 0, � $this->strFontFace, $this->strText); // if ($arr === FALSE) { throw new ImagingException('TTF wird nicht unterstützt');

Page 391: 382726314 X Php5 In 14 Tagen (Ddt)

Dynamisch Bilder erzeugen

} else { $this->dy = $this->intHeight - � (($this->intHeight / 2) - (abs($arr[7]) / 2)); $this->dy = $this->dy < 0 ? $this->intHeight : $this->dy; switch (strtolower($this->strAlign)) { case 'center': case 'middle': $this->dx = ($this->intWidth / 2) - ($arr[2] / 2); break; case 'right': $this->dx = $this->intWidth - $arr[2]; break; case 'left': default: $this->dx = 0; break; } } $this->dx += $this->intBorder; for ($i = 0; $i < $this->intBorder; $i++) { imagerectangle($this->im, $i, $i, � $this->intWidth-$i-1, � $this->intHeight-$i-1, � $this->getColor($this->strBackColor)); } } public function image() { try { $this->create(); header( "Content-type: image/" . $this->strType); $fg = $this->getcolor($this->strColor);

391

imagettftext($this->im, (int) $this->strFontSize, 0, � $this->dx, $this->dy, $fg, � $this->strFontFace, $this->strText); call_user_func('Image' . $this->strType, $this->im); ImageDestroy($this->im);

Page 392: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

} catch (ImagingException $ex) { echo $ex->getMessage(); } }} $objImg = new LSImage;$objImg->image();?>

Das Skript beginnt mit der Erstellung zweier Fehlerklassen, damit die Fehler-behandlung über die neue Ausnahmesteuerung mit try/catch erfolgen kann. Dieerste Klasse soll lediglich Warnungen erfassen:

class NoticeException extends Exception

Auslöser ist die Umleitung der klassischen Laufzeitfehler im Konstruktor:

set_error_handler(array($this, 'throwError'));

Tritt ein Fehler auf, wird die Methode throwError aufgerufen, die ihrerseits denFehler in eine Ausnahme verwandelt:

private function throwError($errno, $err, $line, $file){ throw new NoticeException("$errno, $err, $line, $file");}

Das eigentliche Bilderzeugungsprogramm beginnt im Konstruktor mit der Über-nahme der GET-Parameter in lokale Eigenschaften. Danach muss das Hauptpro-gramm die Methode image aufrufen, um das Bild zu erstellen. Die Bilderzeugungist in einem try-Block untergebracht, um Ausnahmen abfangen zu können. ImFehlerfall werden dann andere Header erzeugt, was die Fehlersuche vereinfacht.Die Ausführung der Bilderzeugung erfolgt in der privaten Methode create:

$this->create();

Gelingt die Bilderzeugung, wird der Text auf dem Bild platziert. Hier könnte man

392

Cache-Techniken ansetzen, um die mit create erzeugten Daten zu erhalten unddie aufwändige Erstellungsprozedur zu vermeiden.

Die Ausgabe vollzieht sich in mehreren Schritten. Zuerst wird die Vordergrund-farbe ermittelt:

$fg = $this->getcolor($this->strColor);

Page 393: 382726314 X Php5 In 14 Tagen (Ddt)

Dynamisch Bilder erzeugen

Dann wird anhand der vorausberechneten Größen der Text im verlangten True-Type-Font geschrieben:

imagettftext($this->im, (int) $this->strFontSize, 0, $this->dx, $this->dy, $fg, $this->strFontFace, $this->strText);

Dann wird die Kopfzeile für den Browser erzeugt, die den Bildtyp bestimmt:

header( "Content-type: image/" . $this->strType);

Passend dazu wird der entsprechende Typ erzeugt und gesendet:

call_user_func('Image' . $this->strType, $this->im);

Der Funktionsaufruf »baut« aus dem Wort Image und dem Typ den Funktionsna-men zusammen, also beispielsweise ImagePng. Anschließend wird das Bild zerstört,um den belegten Speicher sofort freizugeben:

ImageDestroy($this->im);

Der eigentliche Bildaufbau findet in create statt. Es wird zunächst ermittelt, ob derPfad eines Hintergrundbildes vorliegt:

$pi = pathinfo($this->strBackImage);$extension = isset($pi['extension']) ? $pi['extension'] : '';

Je nach Bildtyp erfolgt dann im switch-Zweig der Aufbau der Zeichenfläche ausdem Hintergrundbild. Das Hintergrundbild bestimmt bis hierher die Bildgröße:

$this->im = imagecreatefromgif($this->strBackImage);

Nur wenn kein Hintergrundbild existiert, werden die Größen- und Breitenparame-ter übernommen:

$this->im = imagecreate($this->intWidth, $this->intHeight);

Alle folgenden Funktionen beziehen sich dann auf das Handle in der Eigenschaft$this->im. Stimmen Hintergrundbild und verlangte Bildgröße nicht überein, wirddas Hintergrundbild auf die erforderliche Größe skaliert. Dazu wird folgendeFunktion verwendet:

imagecopyresampled($bi, $this->im, 0, 0, 0, 0, imagesx($bi), imagesy($bi),

393

imagesx($this->im), imagesy($this->im));

Die Basis für die Platzierung des Textes ist die Berechnung der erforderlichenGröße:

$arr = imagettfbbox($this->strFontSize, 0, $this->strFontFace, $this->strText);

Page 394: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

Je nach Ausrichtung wird dann der Startpunkt basierend auf den von imagettfboxzurückgegebenen Werten berechnet. Für zentrierte Texte beispielsweise wird aus-gehend von den Mittelpunkten die Verschiebung nach folgender Formel ermittelt:

$this->dx = ($this->intWidth / 2) - ($arr[2] / 2);

Für den Rand erfolgt die Generierung von Rechtecken, die dem Bild überlagertwerden. Die folgende Funktion erledigt dies auch für Randbreiten > 1, wenn derAufruf in einer Schleife erfolgt und mit der Schleifenvariablen $i gesteuert wird:

imagerectangle($this->im, $i, $i, $this->intWidth-$i-1, $this->intHeight-$i-1, $this->getColor($this->strBackColor));

Die Randfarbe wird von der Hintergrundfarbe bestimmt. Der Einsatz ist vor allemim Zusammenhang mit Hintergrundbildern sinnvoll.

Beispielaufruf

Das folgende Skript enthält ein IMG-Tag, das einen passenden Aufruf enthält:

Listing 9.6: gd2libTest.php – Test der Bibliothek aus einer HTML-Datei heraus

<img src="http://localhost/MuT/gd2lib.inc.php?W=480&H=80&A=left&TXT=PHP5%20und%20MySQL%20in%2014%20Tagen&C=FF0000&B=2&FF=Verdana&FS=17&BC=CCCCCC&BG=fonts/MuTBase.JPG" width="480" height="80"/>

Beachten Sie hier, dass der Text gegebenenfalls URL-kodiert werden muss. DieBibliothek sorgt für die entsprechende Dekodierung.

Abbildung 9.7: Banner, der mit der Bibliothek erzeugt wurde. Der Text kann als Parameter ausge-

394

tauscht werden.

Page 395: 382726314 X Php5 In 14 Tagen (Ddt)

Code röntgen: Die Reflection-API

9.3 Code röntgen: Die Reflection-API

PHP5 verfügt über eine Programmierschnittstelle, die die Analyse von Codeerlaubt. Dies ist sinnvoll, um aus anderen Programmen heraus die Eigenschaftenund den Aufbau von Klassen und Bibliotheken zu ermitteln. Hilfreich könnendiese Techniken zum einen für die Entwickler von Editoren sein, zum anderenaber auch für Bibliotheksprogrammierer, die flexibel auf veränderte Bedingungenreagieren möchten und damit weniger Probleme mit Updates, Versionen undVarianten haben. Ein dritter und sehr wichtiger Komplex ist die Entwicklung vonautomatischen Code-Dokumentierern. Dies sind Programme, die Code analysie-ren und daraus Quelltextdokumentationen erstellen.

Die Reflection-API als Objektmodell

Der Zugriff auf die Reflection-Informationen erfolgt über ein Objektmodell, dasfolgenden Aufbau hat:

<?php class Reflection { } interface Reflector { } class Reflection_Exception extends Exception { } class ReflectionFunction implements Reflector { } class ReflectionParameter implements Reflector { } class ReflectionMethod extends Reflection_Function { } class ReflectionClass implements Reflector { } class ReflectionProperty implements Reflector { } class ReflectionExtension implements Reflector { }?>

Das Prinzip ist einfach. Um Informationen über alle Funktionen eines Skriptsherauszufinden, wird eine Instanz der Klasse ReflectionFunction erzeugt undbenutzt. Die Basisklasse Reflection stellt Hilfsfunktionen zur Verfügung, um dieermittelten Daten auslesen zu können.

Listing 9.7: ReflectionBase.php – Die Struktur der internen Klasse Exception ermitteln

395

<pre><?php Reflection::export(new ReflectionClass('Exception'));?></pre>

Page 396: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

Diese Zeile erzeugt folgende Ausgabe:

Abbildung 9.8:

396

Praktisch ist die Ausgabe eine Kopie der Definition, wobei alle theoretisch mög-lichen Mitglieder des abgefragten Objekts – hier also einer Klasse – angezeigt wer-

Ausgabe der Reflection-Abfrage einer Klasse

Page 397: 382726314 X Php5 In 14 Tagen (Ddt)

Code röntgen: Die Reflection-API

den. Die gezeigte Klasse hat also keine Konstanten, keine statische Eigenschaften,keine statische Methoden, sechs normale Eigenschaften und neun Methoden.

Die Reflection-Klassen im Detail

Der folgende Abschnitt zeigt die zur Verfügung stehenden Klassen, deren Defini-tion und einfache Anwendungsbeispiele.

Informationen über Funktionen ermitteln

Mit ReflectionFunction werden Funktionen analysiert. Die folgende Tabelle zeigtdie zur Verfügung stehenden Methoden, die jeweils konkrete Informationen überdie Funktion zurückgeben.

Methode Bedeutung

__construct(string name) Konstruktor, erwartet den Namen einer Funktion

string getName() Gibt den Namen der Funktion zurück

bool isInternal() Wahr, wenn dies eine eingebaute Funktion ist

bool isUserDefined() Wahr, wenn dies eine benutzerdefinierte Funktion ist

string getFileName() Den Namen der Datei, wo diese Methode definiert ist

int getStartLine() Den Namen der Zeile, wo die Definition beginnt

int getEndLine() Den Namen der Zeile, wo die Definition endet

string getDocComment() Kommentare, die die Funktion dokumentieren

array getStaticVariables() Array der statischen Variablen

mixed invoke(mixed* args) Führt die Funktion mit bestimmten Argumenten aus

string toString() Zeichenkettendarstellung der Funktion

397

bool returnsReference() Wahr, wenn der Rückgabewert eine Referenz ist

Reflection_Parameter[] getParameters()

Die Parameter der Funktion

Tabelle 9.4: Methoden der Klasse ReflectionFunction

Page 398: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

Um eine Funktion zu untersuchen, muss natürlich erst ein Objekt der Klasseerzeugt werden.

Informationen über Parameter ermitteln

Da Funktionen oft Parameter haben, besteht ein enger Zusammenhang zwischenReflectionFunction und ReflectionParameter. Das folgende Beispiel zeigt beideKlassen in der Anwendung:

Listing 9.8: ReflectionFunction.php – Analyse einer Funktion mit Parametern

/** * Eine einfache Zählfunktion * * @return int */function counter($debug, $multiplier = 6){ static $c = 1; echo $debug; return $c++ * $multiplier;}$func= new ReflectionFunction('counter');printf( "===> Die %s Function'%s'\n". " deklariert in %s\n". " von Zeile %d bis %d\n", $func->isInternal() ? 'interne' : 'benutzerdefinierte', $func->getName(), $func->getFileName(), $func->getStartLine(), $func->getEndline());printf("---> Dokumentation:\n %s\n", var_export($func->getDocComment(), 1));if ($statics = $func->getStaticVariables())

398

{ printf("---> Statische Variablen: %s\n", var_export($statics, 1));}foreach ($func->getParameters() as $name => $param)

Page 399: 382726314 X Php5 In 14 Tagen (Ddt)

Code röntgen: Die Reflection-API

{ printf("Parameter '%s' %s [%s]\n", $name, $param->getName(), $param->isPassedByReference() ? '&' : '');}printf("\n\n---> Aufrufergebnisse: ");var_dump($func->invoke(array('Test', 2)));

»Reflektiert« wird hier die Funktion counter. Das Ergebnis zeigt alle erforderlichenDetails, um die Funktion zu verstehen:

Vor allem Programme, die Dokumentationen automatisch erstellen, könnendavon erheblich profitieren.

Daten einer Klasse ermitteln

Mit Hilfe der Klasse ReflectionClass lassen sich andere Klassen – eingebaute odereigene – analysieren. Das folgende Skript versucht diese Analyse möglichst umfas-send. Es verwendet einige der bereitgestellten Methoden:

Listing 9.9: ReflectClass.php – Ermittlung von Daten einer Klasse

Abbildung 9.9: Ergebnis der Analyse einer benutzer-definierten Funktion

399

interface Serializable{ // ...}class Object

Page 400: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

{ // ...}class Counter extends Object implements Serializable{ const START= 0; private static $c= Counter::START; public function count() { return self::$c++; } public static function ReflectMe($config) { $class = new Reflection_Class($config); printf( "===> Die %s%s%s %s '%s' [extends %s]\n". " deklariert in %s\n". " von Zeile %d bis %d\n". " mit dem Modifikatoren %d [%s]\n", $class->isInternal() ? 'interne' : 'benutzerdefinierte', $class->isAbstract() ? ' abstrakte' : '', $class->isFinal() ? ' finale' : '', $class->isInterface() ? 'Schnittstelle' : 'Klasse', $class->getName(), var_export($class->getParentClass(), 1), $class->getFileName(), $class->getStartLine(), $class->getEndline(), $class->getModifiers(), implode(' ', Reflection::getModifierNames($class->getModifiers())) ); printf("---> Dokumentation:\n %s\n", var_export($class->getDocComment(), 1)); printf("---> Implementiert:\n %s\n", var_export($class->getInterfaces(), 1)); printf("---> Konstanten: %s\n",

400

var_export($class->getConstants(), 1)); printf("---> Eigenschaften: %s\n", var_export($class->getProperties(), 1)); printf("---> Methoden: %s\n", var_export($class->getMethods(), 1));

Page 401: 382726314 X Php5 In 14 Tagen (Ddt)

Code röntgen: Die Reflection-API

if ($class->isInstantiable()) { $counter = $class->newInstance(); echo '---> $counter ist Instanz? '; var_dump($class->isInstance($counter)); echo '---> new Object() ist Instanz? '; var_dump($class->isInstance(new Object())); } }}Counter::ReflectMe('Counter');

Die Funktion newInstance kann mit einer variablen Anzahl von Argumenten auf-gerufen werden, die durch den Konstruktor der analysierten Klasse bestimmt wer-den. Es wird eine Warnung ausgegeben, wenn die Instanziierung nicht korrekterfolgt.

Folgende Sequenz ermittelt, ob ein Objekt eine Instanz einer bestimmten Klasseist:

$class= new Reflection_Class('Foo');$class->isInstance($arg)

Diese Information kann auch folgendermaßen gewonnen werden:

if ($arg instanceof Foo)

Information über Methoden einer Klassen ermitteln

Die Informationen über eine Klasse enthalten bereits die Namen der Methoden.Das folgende Beispiel zeigt die Anwendung:

Listing 9.10: ReflectMethod.php – Daten über eine Methode ermitteln

class Counter { private static $c= 0; /**

401

* Zähler erhöhen * * @final * @static * @access public * @return int

Page 402: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

*/ final public static function increment() { self::$c++; return self::$c;

Abbildung 9.10: Informationen über eine Klasse und deren Mitglieder

402

}}// Create an instance of the Reflection_Method class$method= new ReflectionMethod('Counter', 'increment');// Print out basic information

Page 403: 382726314 X Php5 In 14 Tagen (Ddt)

Code röntgen: Die Reflection-API

printf( "===> Die %s%s%s%s%s%s%s Methode '%s' (welche %s ist)\n". " Deklariert in %s\n". " von Zeile %d bis %d\n". " hat die Modifikatoren %d[%s]\n",$method->isInternal() ? 'interne' : 'benutzerdefinierte',$method->isAbstract() ? ' abstrakte' : '',$method->isFinal() ? ' finale' : '',$method->isPublic() ? ' öffentliche' : '',$method->isPrivate() ? ' private' : '',$method->isProtected() ? ' geschützte' : '',$method->isStatic() ? ' statische' : '',$method->getName(),$method->isConstructor() ? 'ein Konstruktor' � : 'eine reguläre Methode',$method->getFileName(),$method->getStartLine(),$method->getEndline(),$method->getModifiers(),implode(' ', � Reflection::getModifierNames($method->getModifiers())));printf("---> Dokumentation:\n %s\n", var_export($method->getDocComment(), 1));if ($statics= $method->getStaticVariables()){ printf("---> Statische Variables: %s\n", var_export($statics, 1));}printf("---> Aufrufergebnisse: ");var_dump($method->invoke(NULL));

Das Aufrufen einer privaten oder geschützten (protected) Methode führt auchüber Reflektion zu einem Fehler. Das ist durchaus unverständlich, weil ein derarti-ger Schutz vor allem ungewollte Zugriffen verhindert. Der explizite Einsatz einerReflektionstechnik benötigt jedoch einen derartigen Schutz nicht. Der Zugriff aufMethoden verlangt als erstes Argument eine Instanz der Klasse. Bei statischen Auf-rufen gibt es diese Instanz nicht, deshalb wird hier der Wert NULL angegeben.

403

Klassen haben neben Methoden meist auch Eigenschaften. Neben der einfachenListe, die bereits die Reflektion der Klasse erbrachte, können auch Detaildatenermittelt werden. Das folgende Beispiel zeigt, wie mit ReflectionProperty gear-beitet wird.

Page 404: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

Listing 9.11: ReflectMethodProps.php – Eigenschaften einer Klasse

class PropTest{ public $number = 5;}$prop= new ReflectionProperty('PropTest', 'number');printf( "===> Die %s%s%s%s Eigenschaft '%s' (welche %s deklariert wurde)\n". " hat die folgenden Modifizierer %s\n", $prop->isPublic() ? ' öffentlich' : '', $prop->isPrivate() ? ' privat' : '', $prop->isProtected() ? ' geschüttz' : '', $prop->isStatic() ? ' statisch' : '', $prop->getName(), $prop->isDefault() ? 'zur Kompilierzeit' : 'zur Laufzeit', var_export(Reflection::getModifierNames( � $prop>getModifiers()),� 1));$obj= new PropTest();print("---> Der Wert ist: ");var_dump($prop->getValue($obj));$prop->setValue($obj, 10);print("---> Setze den Wert 10, der Wert ist jetzt: ");var_dump($prop->getValue($obj));

Abbildung 9.11: Informationen über eine Methode

404

var_dump($obj);

Das Aufrufen einer privaten oder geschützten (protected) Methode führt auchüber Reflektion zu einem Fehler.

Page 405: 382726314 X Php5 In 14 Tagen (Ddt)

Code röntgen: Die Reflection-API

Daten über PHP-Erweiterungen ermitteln

Mit ReflectionExtension lassen sich Informationen über Erweiterungen ermit-teln. Das ist manchmal hilfreich, um die Kompatibilität eines Skripts zu sichernoder die Konfiguration eines entfernten Computers, beispielsweise beim Provider,zu untersuchen.

Zuerst müssen alle vorhandenen Erweiterungen ermittelt werden, was mit dem fol-genden Beispiel demonstriert wird.

Listing 9.12: ReflectGetExtension.php – Eine Erweiterung untersuchen

$aExtensions = get_loaded_extensions();foreach ($aExtensions as $num => $name){ echo "$num. $name <br>";}

Hat man einmal die Namen, kann eine weitere Untersuchung mit Reflection-Extension erfolgen.

Die einzelnen Daten können sehr umfangreich werden; die im folgenden Beispielgezeigte umfassende Darstellung einer Erweiterung dient mehr der Demonstra-tion der Möglichkeiten. In der Praxis dürfte die gezielte Abfrage einer bestimmtenTeilinformation sinnvoller sein. Um konkret festzustellen, ob eine Erweiterungvorhanden ist, wird am besten in_array eingesetzt und dann die Ausgabe gestartet:

Listing 9.13: ReflectGetExtensionDetails.php – Details von PHP-Erweiterungen ermitteln

Abbildung 9.12: Aufruf einer Methode über Reflection

405

$aExtensions = get_loaded_extensions();if (in_array('SQLite', $aExtensions)){ echo "Erweiterung 'sqlite' existiert<br>"; $ext= new ReflectionExtension('sqlite');

Page 406: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

printf( "Name : %s\n". "Version : %s\n". "Funktionen : [%d] %s\n". "Konstanten : [%d] %s\n". "INI-Einträge : [%d] %s\n", $ext->getName(), $ext->getVersion() ? $ext->getVersion() : 'NO_VERSION', sizeof($ext->getFunctions()), var_export($ext->getFunctions(), 1), sizeof($ext->getConstants()), var_export($ext->getConstants(), 1), sizeof($ext->getINIEntries()), var_export($ext->getINIEntries(), 1) );}

Die Ausgabe zeigt die Klassen der Erweiterung. Mit den bereits vorgestellten Tech-

Abbildung 9.13: Liste der geladenen Erweiterungen einer typischen PHP-Installation

406

niken der Reflektion kann man so schnell alle Details ermitteln und damit Edito-ren oder Debugger steuern, Code robuster gestalten oder einfach nur lernen.

Damit stehen alle wichtigen Techniken zur Verfügung, Code per Skript zu analy-sieren, zu dokumentieren und fremde Bibliotheken im Kontext korrekt zu verar-beiten.

Page 407: 382726314 X Php5 In 14 Tagen (Ddt)

Funktions-Referenz GD2

9.4 Funktions-Referenz GD2

Abbildung 9.14: Ausschnitt aus der Beschreibung einer einzigen Extension (SQLite)

Funktion Bedeutung

exif_imagetype Ermittelt Daten über ein vorhandenes Bild.

exif_read_data Liest eingebettet EXIF-Daten aus einer JPG- oder TIF-Datei. Solche Daten werden beispielsweise von Digital-kameras erzeugt.

exif_thumbnail Extrahiert in JPG- oder TIF-Dateien eingebettete Thumb-nail*-Dateien.

407

* Verkleinertes Abbild der Hauptdatei.

gd_info Beschafft Informationen über die Bibliothek selbst.

Tabelle 9.5: Funktions-Referenz für GD2-Funktionen

Page 408: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

Funktion Bedeutung

getimagesize Ermittelt die Ausmaße einer GIF-, JPEG-, PNG- oder SWF-Grafik-Datei.

image_type_to_mime_type Ermittelt die MIME-Typen für getimagesize, exif_read_data, exif_thumbnail, exif_imagetype.

image2wbmp Gibt das Bild im BMP-Format an den Browser aus.

imagealphablending Setzt den Modus für Alpha-Blending.

imageantialias Schaltet das Antialiasing ein oder aus.

imagearc Zeichnet eine Teil-Ellipse.

imagechar Stellt ein Zeichen mit horizontaler Ausrichtung dar.

imagecharup Zeichnet einen vertikal ausgerichteten Charakter.

imagecolorallocate Bestimmt die Farbe einer Grafik.

imagecolorallocatealpha Bestimmt die Farbe unter Verwendung eines Alpha-Werts.

imagecolorat Ermittelt den Farbwert eines Bildpunktes.

imagecolorclosest Ermittelt den Farbwert-Index, der den angegebenen Farben am nächsten liegt.

imagecolorclosestalpha Gibt den Index der Farbe in der Farbtabelle, die dem angegebenen Wert am Nächsten liegt.

imagecolorclosesthwb Gibt den Index der Farbe in der Farbtabelle, die dem angegebenen Wert am Nächsten liegt. Als Werte werden Helligkeit, Sättigung und Kontrast verwendet.

imagecolordeallocate Löscht eine Farbdefinition.

imagecolorexact Ermittelt den Index-Wert der angegebenen Farbe.

imagecolorexactalpha Index einer Farbe.

408

imagecolormatch Versucht die Werte der Farbtabelle in Übereinstimmung mit den tatsächlichen Farben zu bringen.

Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.)

Page 409: 382726314 X Php5 In 14 Tagen (Ddt)

Funktions-Referenz GD2

imagecolorresolve Ermittelt den Index-Wert der angegebenen Farbe oder die nächstmögliche Alternative dazu.

imagecolorresolvealpha Ermittelt den Index einer Farbe oder – wenn die Farbe nicht existiert – die bestmögliche Entsprechung.

imagecolorset Setzt die Farbe für den angegebenen Paletten-Index.

imagecolorsforindex Ermittelt die Farbwerte einer angegebenen Farb-Palette.

imagecolorstotal Ermittelt die Anzahl der definierten Farben eines Bildes.

imagecolortransparent Definiert eine Farbe als transparent

imagecopy Kopiert einen Bildausschnitt

imagecopymerge Kopiert einen Bildausschnitt und verbindet mit einem anderen.

imagecopymergegray Kopiert einen Bildausschnitt und verbindet mit einem anderen mit Graustufen.

imagecopyresampled Kopiert einen Bildausschnitt und verbindet mit einem anderen mit erneuter Kompression.

imagecopyresized Kopiert einen Bildausschnitt und verbindet mit einem anderen mit Größenänderung.

imagecreate Erzeugt ein neues Bild.

imagecreatefromgd2 Erzeugt ein neues Bild aus GD2-Rohdaten.

imagecreatefromgd2part Erzeugt ein neues Bild aus GD2-Rohdaten und Fragmen-ten eines Bilds.

imagecreatefromgd Erzeugt ein neues Bild aus GD-Rohdaten.

imagecreatefromgif Erzeugt ein neues Bild aus GIF-Daten.

imagecreatefromjpeg Erzeugt ein neues Bild aus JPG-Daten.

Funktion Bedeutung

409

imagecreatefrompng Erzeugt ein neues Bild aus PNG-Daten.

imagecreatefromstring Erzeugt ein neues Bild aus einem Stream.

Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.)

Page 410: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

imagecreatefromwbmp Erzeugt ein neues Bild aus WBMP-Daten.

imagecreatefromxbm Erzeugt ein neues Bild aus XBM-Daten.

imagecreatefromxpm Erzeugt ein neues Bild aus XPM-Daten.

imagecreatetruecolor Erzeugt ein neues Bild mit voller Farbunterstützung.

imagedashedline Zeichnen einer gestrichelten Linie.

imagedestroy Löscht ein Bild.

imageellipse Zeichnete eine Ellipse oder einen Kreis.

imagefill Füllen mit Farbe (»flood fill«).

imagefilledarc Zeichnet einen gefüllten elliptischen Kreisbogen.

imagefilledellipse Zeichnet eine gefüllte Ellipse.

imagefilledpolygon Zeichnet ein gefülltes Vieleck (Polygon).

imagefilledrectangle Zeichnet ein gefülltes Rechteck.

imagefilltoborder Flächen-Farbfüllung (»flood fill«) mit einer angegebenen Farbe bis zum nächsten Rand.

imagefontheight Ermittelt die Font-Höhe.

imagefontwidth Ermittelt die Font-Breite.

imageftbbox Ergibt Daten über die Fläche, die ein TrueType-Text in Anspruch nimmt.

imagefttext Schreibt Text in das Bild.

imagegammacorrect Anwendung einer Gamma-Korrektur auf ein GD-Bild.

imagegd2 Ausgabe im internen GD2-Format.

imagegd Ausgabe im internen GD-Format.

Funktion Bedeutung

410

imagegif Ausgabe eines Bildes an den Browser oder in eine Datei .

Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.)

Page 411: 382726314 X Php5 In 14 Tagen (Ddt)

Funktions-Referenz GD2

imageinterlace Schaltet die Interlaced-Darstellung eines Bildes an oder aus.

imageistruecolor Ermittelt, ob das Bild Vollfarbunterstützung hat.

imagejpeg Ausgabe des Bildes im Browser oder als Datei als JPG.

imagelayereffect Setzt Alpha-Blending mit Layer-Effekten.

imageline Zeichnen einer Linie.

imageloadfont Lädt einen neuen Font.

imagepalettecopy Kopiert eine Farbpalette.

imagepng Ausgabe des Bildes im Browser oder als Datei als PNG.

imagepolygon Zeichnen eines Vielecks (Polygons).

imagepsbbox Ermittelt die Ausmaße des Rechtecks, das für die Ausgabe eines Textes unter Verwendung eines PostScript-Fonts (Typ 1) notwendig ist.

imagepscopyfont Erstellt eine Kopie eines bereits geladenen Fonts für wei-tere Veränderungen.

imagepsencodefont Ändert die Vektor-Beschreibung eines Fonts.

imagepsextendfont Vergrößert oder komprimiert einen Font.

imagepsfreefont Gibt den durch einen Typ 1 PostScript-Font belegten Speicher wieder frei.

imagepsloadfont Lädt einen Typ 1 PostScript-Font aus einer Datei.

imagepsslantfont Setzt einen Font schräg.

imagepstext Ausgabe eines Textes auf einem Bild unter Verwendung von Typ 1 PostScript-Fonts.

Funktion Bedeutung

411

imagerectangle Zeichnet ein Rechteck.

imagerotate Rotiert ein Bild um einen Winkel.

imagesavealpha Speichert Alpha-Kanal-Informationen.

Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.)

Page 412: 382726314 X Php5 In 14 Tagen (Ddt)

Professionelle Programmierung

imagesetbrush Setzt ein Bild, das als Füllung für Linien dient.

imagesetpixel Setzt ein einzelnes Pixel.

imagesetstyle Setzt einen Stil für Linien.

imagesetthickness Setzt die Breite für Linien und andere Figuren.

imagesettile Setzt ein kachelbares Bild für Füllungen.

imagestring Zeichnet eine horizontalen Zeichenfolge.

imagestringup Zeichnet eine vertikale Zeichenfolge.

imagesx Ermittelt die Bild-Breite.

imagesy Ermittelt die Bild-Höhe.

imagetruecolortopalette Konvertiert eine Vollfarbpalette in ein Palettenbild.

imagettfbbox Ermittelt die Rahmenmaße für die Ausgabe eines Textes im TrueType-Format.

imagettftext Erzeugt TTF-Text im Bild.

imagetypes Gibt die von der aktuell verwendeten PHP-Version unter-stützten Grafik-Formate zurück.

imagewbmp Gibt das Bild im WBMP-Format aus.

iptcembed Bettet binäre IPTC-Informationen ins Bild ein.

iptcparse Holt binäre IPTC-Informationen aus einem Bild ein. Informationen zu IPTC sind unter http://www.iptc.org/ zu finden.

jpeg2wbmp Konvertiert JPEG nach WBMP.

png2wbmp Konvertiert PNG nach WBMP.

read_exif_data Liest die EXIF Header-Infos einer JPEG-Grafik

Funktion Bedeutung

412

Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.)

Page 413: 382726314 X Php5 In 14 Tagen (Ddt)

Kontrollfragen

9.5 Kontrollfragen

1. Welche Servervariable wird benutzt, um die bevorzugte Sprache des Benutzers zuermitteln?

2. Welcher Header wird in HTTP benötigt, um dem Browser anzuzeigen, dass einBild gesendet wird?

3. Schreiben Sie ein Skript, dass dezimale Farbangaben wie 255, 192, 128 in hexa-dezimale Zeichenfolgen der Art »#FFCC99« umrechnet und umgekehrt.

4. Wozu dient das Werkzeug Reflection?

413

Page 414: 382726314 X Php5 In 14 Tagen (Ddt)
Page 415: 382726314 X Php5 In 14 Tagen (Ddt)

HT

TP, FTP undE-Mail

Kommunikation per

10

Page 416: 382726314 X Php5 In 14 Tagen (Ddt)

Kommunikation per HTTP, FTP und E-Mail

10.1 Konzepte in PHP5

Bedingt durch die Vielzahl möglicher Datenquellen kommt der Server-Server-Kommunikation immer stärkere Bedeutung zu. PHP5 stellt eine ganze Reihe vonMethoden zum Zugriff auf andere Server zur Verfügung, einiges davon ist völligneu. Damit ergeben sich spannende Möglichkeiten für die Nutzung von PHP5.Einige Anregungen finden Sie hier:

� Holen von News-Daten von einem anderen Server (HTTP)

� Senden von Status-Informationen an einen anderen Server (HTTP)

� Versenden von Formularinhalten per E-Mail (SMTP)

� Aktualisierung von Inhalten auf einem Download-Server (FTP)

In Klammern finden Sie die jeweils bevorzugten Protokolle.

Prinzip der Streams und Wrapper

PHP5 nutzt ein einheitliches Konzept zur Organisation des Zugriffs auf Daten, sogenannte Wrapper. Dies sind protokollähnliche Bezeichner (im Englischen oft alsMoniker, Spitznamen, bezeichnet):

wrapper://dateiname

Ein Wrapper gilt als Standard: file://, diese Angabe kann entfallen. Das vereinfachtden Zugriff auf lokalen Dateien, die besonders häufig benötigt werden. Wrapperfinden immer dann Anwendung, wenn Daten von einer Datenquelle geholt oderin eine Datenquelle geschrieben werden. Das gilt beispielsweise für sämtlicheDateifunktionen. Statt also für neue Zugriffsarten (per HTTP oder FTP) immerneue Bibliotheken zu verwenden, bietet PHP5 einen vereinheitlichten Weg desZugriffs an. Damit die Verwaltung intern funktioniert, werden ergänzend sogenannte Streams eingesetzt. Diese bietet elementare Zugriffsfunktionen wie Öff-nen, Lesen, Schreiben, Schließen. Wenn man eigene Wrapper entwickeln will –auch dies ist Teil des universellen Konzepts – programmiert man praktisch diese

416

Stream-Funktion für einen spezifischen Fall.

Page 417: 382726314 X Php5 In 14 Tagen (Ddt)

Konzepte in PHP5

Die eingebauten Wrapper

PHP5 kommt mit einigen eingebauten Wrappern daher, die für die meisten Zwe-cke ausreichend sind. Konkret sind dies:

� file://<Pfad>/<Datei>Die Angabe file:// kann entfallen, sodass normale Pfadangaben weiterhin nutz-bar sind. Als mögliche Varianten sind relative Pfade (/pfad/name.ext), absolutePfade (D:\pfad\name.ext oder /pfad/pfad/datei.ext) oder Netzwerkshares (\\ser-ver\freigabe\name) erlaubt. Wenn die Angabe mit file:// erfolgt und unter Win-dows Laufwerkbuchstaben adressiert werden, sind dies mit | abzutrennen.

� http://<url>, https://<url>Um auf andere Webserver zuzugreifen, werden diese Wrapper benutzt. https://baut eine verschlüsselte Verbindung auf. Der URL wird in der übliche Formangegeben. Um Name und Kennwort für eine geschützte Verbindung anzuge-ben, wird die Form http://<name>:<kennwort>@<url> benutzt.

� ftp://<url>, ftps://<url>Um auf andere Server per FTP zuzugreifen, werden diese Wrapper benutzt.ftps:// baut eine verschlüsselte Verbindung auf. Um Name und Kennwort füreine geschützte Verbindung anzugeben, wird die Form ftp://<name>:<kenn-wort>@<url> benutzt.

� php://<channel>Um auf die internen Ein- und Ausgabekanäle des Betriebssystems zuzugreifen,bietet PHP einen speziellen Wrapper an. Zulässige Kanalnamen sind »stdin«(Eingabe, beispielsweise Tastaturabfragen am Systemprompt), »stdout« (Aus-gabe), »stderr« (Fehlerausgaben), »output« (Ausgabepuffer, der von echo, printusw. verwendet wird), »input« (Rohdaten aus einem Formular bei POST-Anforderungen und »filter« (benutzerspezifische oder eingebaute Filter, dieDaten während der Übertragung verändern).

� compress.zlib://, compress.bzip2:// Komprimiert oder dekomprimiert die Daten während der Übertragung nachdem ZIP-Verfahren. Beim Schreiben in eine Datei entstehen GZ-Dateien,keine Archive.

417

Page 418: 382726314 X Php5 In 14 Tagen (Ddt)

Kommunikation per HTTP, FTP und E-Mail

Filter

Filter werden im Stream eingebunden, um – für den Aufrufer transparent – Datenwährend der Übertragung zu ändern. Der Vorteil liegt in der Anwendung. DerBenutzer eines Filters arbeitet weiter wie gewohnt mit Standardfunktionen wiefopen und fread. Dabei gibt er lediglich an, einen bestimmten Filter benutzen zuwollen. Wie dieser funktioniert, eingebunden wird und wie dieser konkret mit denDaten interagiert, bleibt völlig im Hintergrund. Eingebaute Filter können fol-gende Aktionen ausführen (zuvor der Name des Filters):

� string.rot13Eine ROT13-Kodierung, bei der die Buchstaben um 13 Stellen im Alphabetverschoben werden, um eine sehr schwache Verschlüsselung zu erreichen.

� string.toupperVerwandelt alle Zeichen in Großbuchstaben.

� string.tolowerVerwandelt alle Zeichen in Kleinbuchstaben.

� string.strip_tagsEntfernt alle Tags aus dem Datenstrom.

� convert.base64-encodeKonvertiert die Daten in das Base64-Format.

� convert.base64-decodeKonvertiert die Base64-Daten aus dem Base64-Format in eine lesbare Form.

� convert.quoted-printable-encodeKonvertiert die Daten in das Quoted-Printable-Format (in E-Mails benutzt).

� convert.quoted-printable-decodeKonvertiert die Quoted-Printable-Daten aus dem Quoted-Printable-Format ineine lesbare Form.

Damit die Filter wirklich universell sind, kann man auch bei der Angabe von php://filter wiederum Pfade angeben, die ihrerseits die Wrapper http:// oder ftp://verwenden.

418

Page 419: 382726314 X Php5 In 14 Tagen (Ddt)

Streams und Wrapper anwenden

10.2 Streams und Wrapper anwenden

Die Anwendung der Wrapper ist relativ einfach. Eigentlich müssen Sie lediglichmit den Dateifunktionen umgehen können, die bereits ausführlich vorgestelltwurden.

Daten von einer fremden Website beschaffen

Das folgende Skript holt sich Daten von einer fremden Website. Es liest die Datenein und ermittelt die Inhalte bestimmter META-Tags, wenn diese vorhanden sind.Das klingt weitaus aufregender, als es ist:

Listing 10.1: wrapperhttp.php – Zugriff mit Standardfunktionen auf Webserver

<?php$url = 'http://www.apache.org';$tags = get_meta_tags($url);if (count($tags) > 0){ foreach($tags as $name => $content) { echo strtoupper($name); echo " = $content<br>"; }}?>

Der eigentliche Aufwand – also das Herstellen der Verbindung – erledigt der Wrap-per. Die Filterung und Ausgabe derw META-Tags erfolgt mit Hilfe der Funktionget_meta_tags. Die Anwendung unterscheidet sich nicht von der beim Lesen nor-maler Dateien. Allerdings kann die Ausführzeit des Skripts etwas länger sein, daeinige Zeit für den Verbindungsaufbau und die Übertragung der Seite benötigt wird.

Abbildung 10.1: Ausgabe der META-Tags der

419

Sie sollten außerdem auf eine Fehlerbehandlung achten, da Internet-Verbindungen aus den verschiedensten Gründen fehlschlagen können.

Website www.apache.org

Page 420: 382726314 X Php5 In 14 Tagen (Ddt)

Kommunikation per HTTP, FTP und E-Mail

Das folgende Skript liest den Inhalt einer Webseite direkt ein und speichert dieSeite (ohne Bilder oder andere Ressourcen) lokal ab:

Listing 10.2: wrapperhttppage.php – Zugriff mit Dateifunktionen

<?php$url = 'www.apache.org';$page = file_get_contents(?http://$url?);if (strlen($page) > 0){ $file = "data/$url.htm"; $f = fopen($file, 'w'); fwrite($f, $page); fclose($f); echo 'Dateigr&ouml;&szlig;e: ' . filesize($file) . ' Byte';}?>

Auch hier wird lediglich eine Standardfunktion, file_get_contents, benutzt. ZurKontrolle wird noch die Dateigröße angezeigt. Zum Testzeitpunkt waren diesstolze 12 KByte.

Daten komprimiert speichern

Weitaus kompakter geht es, wenn man die Daten komprimiert speichern kann.Das folgende Beispiel verwendet einen weiteren Wrapper beim Speichern derDatei, der das erledigt:

Listing 10.3: wrapperhttpcompress.php – Abspeichern einer Datei in komprimierter Form

<?php$url = 'www.apache.org';$page = file_get_contents("http://$url");if (strlen($page) > 0){ $file = "data/$url.gz"; $f = fopen("compress.zlib://$file", 'w');

420

fwrite($f, $page); fclose($f); echo 'Dateigr&ouml;&szlig;e: ' . filesize($file) . ' Byte';}?>

Page 421: 382726314 X Php5 In 14 Tagen (Ddt)

Streams und Wrapper anwenden

Das Ergebnis unterscheidet sich vor allem im Platzverbrauch von der vorherigenVersion. Die Startseite der Apache Group belegt nun nur noch 3,6 KByte. Eineverbesserte Version liest die Datei wieder aus und zeigt sie zeilenweise an. Dabeiwird wiederum ein Wrapper benutzt, der auf die abgespeicherte Version zugreift.

Listing 10.4: wrappedecompress.php – Komprimierte Datei erzeugen und dekom-primieren

<?php$url = 'www.apache.org';$page = file_get_contents("http://$url");if (strlen($page) > 0){ $file = "data/$url.gz"; $f = fopen("compress.zlib://$file", 'w'); fwrite($f, $page); fclose($f); echo 'Dateigr&ouml;&szlig;e: ' . filesize($file) . ' Byte'; echo '<br><br>'; $farray = file("compress.zlib://$file"); foreach($farray as $num => $content) { printf('%03d: %s<br />', $num, htmlspecialchars($content)); }}?>

Hier wird eine weitere Dateifunktion, file, benutzt und diesmal reagiert derselbeWrapper in genau umgekehrter Weise. Da die Daten gelesen werden, erwartet erkomprimierte Daten und diese werden folgerichtig wiederhergestellt. Der eigentli-che Vorgang des Komprimierens und Dekomprimierens ist für den Anwendertransparent – man muss weder bei der Benutzung noch bei der Wahl der Funk-tionen darauf Rücksicht nehmen.

Damit ist zum Thema Wrapper eigentlich schon fast alles gesagt. Der Fantasiesind beim Anwenden kaum Grenzen gesetzt. Vor allem aber kann man kompli-zierte Zugriffe mit den Socket-Funktionen meist vermeiden.

421

Page 422: 382726314 X Php5 In 14 Tagen (Ddt)

Kommunikation per HTTP, FTP und E-Mail

10.3 Filter verwenden

Das folgende Skript holt sich wie bereits beim vorhergehenden Beispiel Daten voneiner fremden Website. Es liest im Gegensatz dazu nur den Inhalt der Seite, alsoohne HTML-Tags. Dies kann beispielsweise für eine Indizierung sinnvoll sein.Anstatt also die Ausgabe mit htmlspecialchars sichtbar zu machen, soll nun einFilter die Arbeit erledigen.

Wie bereits angedeutet, basieren Filter auf der Wrapper-Syntax und haben fol-gende Struktur:

php://filter:/<AKTION>=<NAME>

Aus den verfügbaren Filtern kann man nun wählen. string.strip_tags ist dabei vonder Funktion mit strip_tags vergleichbar, spart jedoch den expliziten Aufruf die-ser Funktion.

Listing 10.5: wrapperfilter.php – Filtern von Daten während des Lesevorgangs

Abbildung 10.2: Komprimiert und dennoch direkt lesbar: Wrapper machen’s möglich

422

<?php$url = 'www.apache.org';$page = file_get_contents("http://$url");if (strlen($page) > 0){

Page 423: 382726314 X Php5 In 14 Tagen (Ddt)

Filter verwenden

$file = "data/$url.gz"; $f = fopen("$file", 'w'); fwrite($f, $page); fclose($f); echo 'Dateigr&ouml;&szlig;e: ' . filesize($file) . ' Byte'; echo '<br><br>'; $farray = file("php://filter/read=string.strip_tags/resource=$file"); foreach($farray as $num => $content) { if (strlen(trim($content)) == 0) continue; printf('%03d: %s<br />', $num, $content); }}?>

Die einzige Ergänzung zu den vorhergehenden Beispielen besteht in der Angabedes Filters:

file("php://filter/read=string.strip_tags/resource=$file")

Die Syntax wird transparent, wenn man das zuvor gezeigte Muster zu Rate zieht.php://filter leitet die Sequenz ein. Danach folgen bis zu drei Aktionen, die ent-sprechende Parameter verlangen:

� resource=<DATENQUELLE>Diese Angabe ist zwingend erforderlich. Nutzen Sie für die Angabe von<DATENQUELLE> wieder die bereits bekannten Wrapper.

� readGeben Sie an, welche Filteraktion beim Lesen ausgeführt werden soll. DerAbschnitt »Filter« auf Seite 418 zeigte die möglichen Filter bereits.

� writeGeben Sie an, welche Filteraktion beim Schreiben ausgeführt werden soll.Der Abschnitt »Filter« auf Seite 418 zeigte die möglichen Filter bereits.

Es bietet sich nun an, alle Möglichkeiten miteinander zu kombinieren, was tat-sächlich funktioniert.

423

Listing 10.6: wrapperfiltercompress.php – Filtern und Komprimieren in einem Schritt

<?php$url = 'www.apache.org';$page = file_get_contents("http://$url");

Page 424: 382726314 X Php5 In 14 Tagen (Ddt)

Kommunikation per HTTP, FTP und E-Mail

if (strlen($page) > 0){ $file = "data/$url.gz"; $f = fopen("compress.zlib://$file", 'w'); fwrite($f, $page); fclose($f); echo 'Dateigr&ouml;&szlig;e: ' . filesize($file) . ' Byte'; echo '<br><br>'; $farray = file("php://filter/read=string.strip_tags/ resource=compress.zlib://$file"); foreach($farray as $num => $content) { if (strlen(trim($content)) == 0) continue; printf('%03d: %s<br />', $num, $content); }}?>

Der Aufruf des Filters sieht nun folgendermaßen aus:

php://filter/read=string.strip_tags/resource=compress.zlib://$file

Der Wrapper compress.zlib:// wurde in die resource-Aktion eingebaut. Die Ausgabezeigt die Wirkung des Filters und zugleich die erzeugte Dateigröße an:

424

Damit sind die eingebauten Anwendungen erschöpft. In der Praxis dürfte das fürdie meisten Fälle ausreichen.

Abbildung 10.3: Komprimieren und Filtern in einem Schritt

Page 425: 382726314 X Php5 In 14 Tagen (Ddt)

Die Stream-Funktionen

10.4 Die Stream-Funktionen

Generell kann man mit den Stream-Funktionen, auf denen Wrapper und Filterbasieren, sehr viel mehr anstellen. Sie sind aber recht komplex in der Anwendung.Sie bieten einen generellen und vereinheitlichten Weg für den Umgang mitDatenübertragungen.

Eigene Wrapper und Filter

Es gibt allerdings noch die Möglichkeit, eigene Wrapper und natürlich aucheigene Filter zu schreiben. Diese tauchen dann unter eigenem Namen auf. Diesist jedoch ein deutlich größerer Aufwand und lohnt sich vermutlich nur für Biblio-theksentwickler. Die Darstellung sprengt auch den Rahmen dieses Buches. WennSie eigene Versuche in dieser Richtung unternehmen möchten, müssen Sie füreinen eigenen Wrapper eine Klasse schreiben, die auf den Stream-Funktionen auf-baut, also stream_open zum Öffnen, stream_read zum Lesen und stream_closezum Schließen, um nur einige zu nennen. Diese Klasse wird dann mitstream_register_wrapper in Verbindung mit einem eigenen Moniker registriert.

Auch eigene Filter lassen sich entwickeln. Dies funktioniert etwas anders. Hiermüssen Sie eine Klasse erstellen, die von php_user_filter erbt. Dort ist dann dieMethode filter zu schreiben. Mit Hilfe der bereits für Wrapper benutzten Stream-Funktionen erfolgt dann der Zugriff auf den Datenstrom, der beim Durchlaufender filter-Methode verändert werden kann.

Anwendung spezifischer Stream-Funktionen

Zusätzlich zu den vorgestellten Techniken gibt es hier noch den Begriff desStream-Kontexts. Dies sind Parametersammlungen, die einer Stream-verarbeiten-den Instanz übergeben werden, um das Verhalten global zu beeinflussen.

Beim Zugriff mittels http:// (als Beispiel) werden Sie sicher schnell bemerken, dasssich bestimmte Seiten nicht ohne weiteres benutzen lassen. Möglicherweise feh-

425

len einige Kopfzeilen der HTTP-Anforderung, wie beispielsweise die akzeptierteSprache.

Die meisten Dateifunktionen erlauben es, einen so genannten Kontext-Parameteranzugeben.

Page 426: 382726314 X Php5 In 14 Tagen (Ddt)

Kommunikation per HTTP, FTP und E-Mail

Listing 10.7: streamcontext.php – Parameter für Wrapper

<?php$opts = array( 'http'=>array( 'method'=>"GET", 'header'=>"Accept-language: de\r\n"));$context = stream_context_create($opts);$fp = fopen('http://www.google.com', 'r', false, $context);fpassthru($fp);fclose($fp);?>

Das Skript übermittelt an den Wrapper http zwei Informationen: Die Methode fürden Abruf ist GET (dies ist auch die Standardmethode) und die akzeptierteSprache. Im Beispiel wird die Startseite von Google abgerufen. Nutzt manwww.google.com (nicht »de«), versucht Google die Sprache dem Parameter Accept-language zu entnehmen und schaltet auf die entsprechende Landesversion um.

Damit lassen sich Webseiten recht komfortabel steuern und nutzen.

Beachten Sie jedoch unbedingt die Urheberrechte und täuschen Sieniemals fremde Inhalt vor. Vereinbaren Sie besser Kooperationen mitden Anbietern der Datenquellen und profitieren Sie so von den unbe-grenzten Möglichkeiten des Webs.

Abbildung 10.4: Google auf Deutsch – Statt per Browser über einen parametri-sierten Wrapper aufgerufen

426

10.5 E-Mail versenden

Das Versenden von E-Mail gehört zu den häufiger benötigten Aufgaben. Es dientauf fast jeder Website dazu, die Nutzerkommunikation zu verbessern. PHP5 unter-

Page 427: 382726314 X Php5 In 14 Tagen (Ddt)

E-Mail versenden

stützt E-Mail auf vielfältige Weise, angefangen von der einfachen mail-Funktion,über Socket-Programmierung für die Kommunikation per POP3 bis hin zu ganzenBibliotheken für IMAP-Postfächer.

Grundlagen

Auch wenn das einfache Versenden von E-Mail verhältnismäßig leicht zu pro-grammieren ist, sollten Sie einige elementare Grundlagen beherrschen. Dies hilft,bei größeren Projekten den richtigen Ansatz zu finden und schneller zu brauch-baren Ergebnissen zu kommen. Bedenken Sie auch, dass eine Server-zu-Server-Kommunikation immer auch fremde Maschinen beeinflusst. Dem sauberen Pro-tokollfluss kommt deshalb eine große Bedeutung zu.

Generell werden in der E-Mail-Kommunikation sowohl verschiedene Protokolleals auch Kodierungsverfahren verwendet. Als Transportprotokolle kommen zumEinsatz:

� SMTPSMTP (Simple Mail Transfer Protocol) dient der Weiterleitung von E-Mail voneinem Server zu einem anderen. Es dient auch dazu, die erstmalig auf einemClient erzeugte E-Mail an den ersten Server der Übertragungskette weiterzu-leiten.

� POP3POP3 (Post Office Protocol, Version 3) ruft E-Mail von einem Server ab, umdiese auf einem Client dem Benutzer zur Verfügung zu stellen.

� IMAP4IMAP4 (Internet Message Access Protocol, Version 4) ist ein Protokoll zumEmpfang von E-Mail (wie POP3) und zur Verwaltung der E-Mails einesClients auf einem Server. Das vollständige Herunterladen, wie bei POP3 erfor-derlich, ist nicht notwendig.

Im Internet kommen praktisch nur SMTP und POP3 zum Einsatz. Clients wieOutlook benutzen beide Protokolle, SMTP zum Senden und POP3 zum Empfan-gen.

427

Als Kodierungsverfahren stehen folgende zur Auswahl:

� UUEncodeDer Name steht für Unix-to-Unix-Encode, ein Kodierverfahren aus der Unix-Welt. Das Verfahren ist inzwischen relativ selten anzutreffen, war in der

Page 428: 382726314 X Php5 In 14 Tagen (Ddt)

Kommunikation per HTTP, FTP und E-Mail

Anfangszeit des Internet jedoch das am weitesten verbreitete. Viele Clientsbeherrschen es heute noch.

Vorteile bietet es bei der Kodierung von binären Daten über Transportsysteme,die nur 7-Bit-Zeichen verarbeiten können. Dateien werden um ca. 40% vergrö-ßert.

PHP5 verfügt über Funktionen zur Kodierung und Dekodierung dieses Ver-fahrens.

� Quoted PrintableDiese Verfahren wird häufiger benutzt. Es lässt den Text praktisch unverändertund kodiert nur Sonderzeichen, beispielsweise wird das Leerzeichen zu =20.Das Verfahren eignet sich nur für die Kodierung von Text, nicht für Binär-daten für Anhänge.

PHP5 verfügt über Funktionen zur Kodierung und Dekodierung dieses Ver-fahrens.

� Base64Ein anderes Verfahren, dessen Schwerpunkt auf Dateianhängen liegt. Eskodiert ebenfalls auf 7-Bit und vergrößert die Daten dabei um 37%. Es wirdheute am häufigsten eingesetzt, weil es das Standardverfahren für Anhänge ist,die nach MIME (siehe unten) verpackt werden.

PHP5 verfügt über Funktionen zur Kodierung und Dekodierung dieses Ver-fahrens.

� BinHexEin Verfahren aus der Apple-Welt, das sich außerhalb dieser nicht durchsetzenkonnte. Gute Clients sollten es beherrschen. Das Verfahren komprimiert,sodass die ursprüngliche Größe trotz der Umsetzung auf 7-Bit oft unterschrit-ten wird.

PHP5 verfügt nicht über Funktionen zur Kodierung und Dekodierung diesesVerfahrens.

� MIMEEin globaler Standard (Multipurpose Internet Mail Extensions), der nicht selbst

428

ein reines Kodierverfahren beschreibt, sondern eine vollständige Anweisungzum Aufbau kompletter Nachrichten ist. Zur Kodieren von Binärdaten wirddas bereits erwähnt Base64 empfohlen. MIME beschreibt den Aufbau derE-Mail einschließlich der Art der Verpackung von Text, Anhängen, Informati-onszeilen (Kopfzeilen) usw.

Page 429: 382726314 X Php5 In 14 Tagen (Ddt)

E-Mail versenden

Vorbereitung

Wenn Sie auf einem Entwicklungssystem die E-Mail-Funktion ausprobieren wol-len, müssen Sie über einen SMTP-Server verfügen. Der SMTP-Server Ihres Inter-net-Providers ist vermutlich nicht nutzbar, weil externe Zugriffe eine Autorisierungverlangen, um den Missbrauch durch SPAM-Versender zu vermeiden. Dies ist mitzusätzlichem Aufwand verbunden. Meist verlangen die SMTP-Server, dass unmit-telbar zuvor ein autorisierter POP3-Zugriff erfolgt.

Als Lösung können Sie lokal einen eigenen SMTP-Server betreiben. Hier gibt esmehrere Varianten. Windows 2000 Professional, Server, Windows Server 2003 undWindows XP Professional werden mit den Internet-Informationsdiensten geliefert,die einen SMTP-Server enthalten. Der Server ist sehr einfach zu konfigurierenund harmoniert bestens mit PHP. Die Windows 9X/Me- und XP Home-Variantensind leider außen vor. Hier muss man freie Programme bemühen, die es zuhaufgibt. Linux kommt ebenfalls mit dem integrierten Sendmail-Programm. Hier istdie Konfiguration etwas aufwändiger, außerdem erwartet PHP bei der Nutzungvon Sendmail, dass es selbst auf Linux läuft.

Steht der SMTP-Server, muss PHP noch ein wenig dafür konfiguriert werden.

Wenn Sie einen SMTP-Server für Windows benötigen, weil Sie mit XPHome oder Windows 9x/Me arbeiten, schauen Sie sich den ArGoSoftMail Server an:

http://www.argosoft.com/applications/mailserver/

Läuft er lokal, ist als Name des SMTP-Servers localhost anzugeben.

Konfiguration für Windows

In der php.ini sind einige Parameter zu setzen:

SMTP = localhostsmtp_port = 25

Der erste, SMTP, bezeichnet die Adresse des SMTP-Server. Geben Sie den Netzwer-

429

knamen, die IP-Adresse oder einen vollqualifizierten Domänennamen ein. Derzweite, smtp_port, bezeichnet den Port, an dem der SMTP-Server läuft. 25 ist derStandardport.

Page 430: 382726314 X Php5 In 14 Tagen (Ddt)

Kommunikation per HTTP, FTP und E-Mail

Dann ist noch die Absenderadresse zu setzen:

sendmail_from = [email protected]

Ohne diese Angabe wird die Funktion mail nicht arbeiten.

Konfiguration für Linux

Unter Linux erledigt nicht PHP in Kombination mit SMTP die Arbeit, sondernalleine Sendmail. Damit entfallen die für Windows erforderlichen Konfigurations-schritte. Dafür müssen Sie Sendmail konfigurieren, wozu der folgende Parameterdient:

sendmail_path = -t –i

Welche Parameter hier nötig und möglich sind, ist der Dokumentation zu Send-mail zu entnehmen. Sie hängen außerdem von der konkreten Installation ab.

Praktische Umsetzung

Das Versenden von E-Mail mit PHP ist sehr einfach. Voraussetzung ist allerdingseine Installation, in der PHP einen SMTP-Server erreichen kann, wie zuvorbeschrieben. Wird beim Provider gehostet, ist dies in der Regel gewährleistet.

Einfache E-Mails versenden

Eine einfache E-Mail zu versenden basiert auf nur einer einzigen Funktion: mail.Die Funktion kennt mehrere Parameter:

� toEine Zeichenkette, die die Mail-Adresse angibt, an die gesendet werden soll.Sie können mehrere Adressen angeben und durch Kommata trennen. Beach-ten Sie aber, dass die Empfänger dann alle Adressen sehen können. Besser istes, Blind Carbon Copy zu verwenden (siehe unten).

� subject

430

Eine Zeichenkette, die als Betreff benutzt wird.

� messageEine Zeichenkette, die die eigentliche Nachricht darstellt.

Page 431: 382726314 X Php5 In 14 Tagen (Ddt)

E-Mail versenden

� headerZusätzliche benutzerdefinierte Kopfzeilen, die das Verhalten der E-Mailbeeinflussen. Hier können Angaben zur Priorität, Cc (Carbon Copy), Bcc(Blind Carbon Copy) usw. eingetragen werden. Die Kopfzeilen müssen imRohformat geschrieben werden. Die Angabe des Parameters ist optional.

� parametersDiese Option ist nur anwendbar, wenn PHP auf Linux läuft und zum Versen-den Sendmail. Geben Sie dann Parameter für Sendmail an. Es ist möglich, dieVerwendung dieser Option in der Datei php.ini zu sperren. Deshalb kann derEinsatz bei einem Massenhoster nicht garantiert werden.

Die Funktion gibt einen Booleschen Wert zurück, der den Erfolg repräsentiert.Außerdem wird eine Fehlermeldung angezeigt, wenn etwas schief läuft. Da Feh-lermeldungen meist stören und die E-Mail-Übertragung in der Praxis durchausihre Tücken hat, ist ein Abfangen und Auswerten der Fehler unerlässlich. Das fol-gende Skript zeigt, wie das aussieht:

Listing 10.8: sendmail.php – E-Mail versenden und auf mögliche Fehler reagieren

<?phpini_set('track_errors', 1);$to = '[email protected]';$subject = 'Test-Nachricht';$message = 'Dies ist ein Test, ob E-Mail geht.';if (PHP_OS=='WINNT'){ ini_set('sendmail_from', '[email protected]');

$from = '';}else { $from = "From:[email protected]\n";}$success = @mail($to, $subject, $message, $from);if ($success){

431

echo "Mail an $to versendet";} else { echo $php_errormsg;

Page 432: 382726314 X Php5 In 14 Tagen (Ddt)

Kommunikation per HTTP, FTP und E-Mail

}?>

Folgendes ist zu beachten. Wenn Sie mit Windows arbeiten und den Windows-SMTP-Server verwenden, muss die Angabe »From:« in der Datei php.ini einge-stellt werden. Um dies zu vereinfachen, wird der Parameter im Skript dynamischgesetzt:

ini_set('sendmail_from', '[email protected]');

Außerdem wird das Abfangen des Fehlers programmiert. Dazu ist zuerst die Feh-lerverfolgung in PHP5 einzuschalten:

ini_set('track_errors', 1);

Beim Senden wird der mail-Funktion ein @ zum Unterdrücken des Fehlers voran-gestellt:

$success = @mail($to, $subject, $message, $from);

Die Variable $success enthält nun im Fehlerfall FALSE. Darauf kann entsprechendreagiert werden. Trat ein Fehler auf, wird die interne Fehlervariable$php_errormsg benutzt:

echo $php_errormsg;

Programmierung mit Kopfzeilen

Um weitere Verwendungsmöglichkeiten zu erkunden, müssen Sie sich mit denKopfzeilen beschäftigen. Diese werden als vierter Parameter in der folgendenForm angegeben:

<Name>: <Werte>\r\n

Abbildung 10.5: Fehlerausgabe des Pro-gramms: Hier war der Mail-Server falsch kon-figuriert

432

Dabei ist <Name> durch den Namen, beispielsweise »From« zu ersetzen und<Werte> durch die von dieser Kopfzeile verlangten Parameter:

From: [email protected]\r\n

\r\n bezeichnet einen Zeilenumbruch, der in der E-Mail zum Abtrennen derKopfzeilen benötigt wird.

Page 433: 382726314 X Php5 In 14 Tagen (Ddt)

E-Mail versenden

Die Zeilenendezeichen \r\n werden unter Windows verwendet. Wennder SMTP-Server unter Unix läuft, ist meist \n ausreichend, manchmalerforderlich. Probieren Sie Ihr Skript sorgfältig aus, bevor Sie es auslie-fern.

Die folgende Tabelle zeigt einige typische Kopfzeilen, die Sie verwenden können:

Name Parameterbeispiel Bedeutung

To Empfänger, kann auch eine Liste sein

From [email protected] <Testname> Absender. Die Angabe in spitzen Klammern ist optional. Die Klammern selbst müssen hier geschrieben wer-den, wenn die Angabe erfolgt.

Cc [email protected], [email protected] Kopie an. Liste von Empfängern, die die E-Mail auch erhalten und die sichtbar ist (Cc = Carbon copy). Trennung durch Kommata.

Bcc [email protected], [email protected] Kopie an. Liste von Empfängern, die die E-Mail auch erhalten und die nicht sichtbar ist (Bcc = Blind carbon copy). Trennung durch Kommata.

Date Mon, 16 Aug 2004 11:29:28 +02 Datum, das beim Empfänger als das Absendedatum angezeigt wird. Nutzen Sie am einfachsten date('r').

Reply-To Gewünschte Antwortadresse. Fehlt die Angabe, wir »To« verwendet.

X-Mailer PHP-Mail Version 5 Ein Hinweis, welches Programm zum Versenden benutzt wurde.

X-PriorityX-MSMail-

1High

Priorität (niedrig, hoch, …) in der all-gemeinen Variante und in einer für

433

Priority Outlook/Outlook Express

MIME-Version

1.0 Version der MIME-Kodierung

Tabelle 10.1: Namen und Parameter typischer Kopfzeilen für E-Mails

Page 434: 382726314 X Php5 In 14 Tagen (Ddt)

Kommunikation per HTTP, FTP und E-Mail

Dies ist nur eine kleine Auswahl der Möglichkeiten. Sie reicht aber für die häufigs-ten Fälle aus:

� Versenden von HTML-Mails

� Versenden von Anhängen

Anhänge versenden

Anhänge und HTML werden in ähnlicher Weise versendet. Die Kodierung folgtdem MIME-Standard. Dabei wird der Inhalt der Nachricht in mehrere Blöcke auf-geteilt, die jeweils eigene Kopfzeilen haben und zusätzlich durch spezielle Gren-zen voneinander getrennt sind. Die Kopfzeilen helfen dem Client, die Nachrichtwieder zusammenzubauen. Binäre Daten, wie beispielsweise Bilder, werden meistBase64-kodiert, damit sie auch über 7-Bit-Übertragungssysteme geliefert werdenkönnen. Beachten Sie, dass sich Nachrichten dabei um bis zu 30% vergrößernkönnen.

Zuerst ein Beispiel: eine Klasse, die Anhänge versenden kann:

Listing 10.9: sendattachments.php – Anhänge zusammen mit E-Mail versenden

<?php class MailException extends Exception

Content-type multipart/alternative Inhaltstyp nach MIME-Kriterien

Content-Transfer-Encoding

base64 Kodierungsverfahren für Anhänge

Content-Disposition

attachment;\n\tfile-name=anhang.txt

Bezeichnung eines Anhangs

Name Parameterbeispiel Bedeutung

Tabelle 10.1: Namen und Parameter typischer Kopfzeilen für E-Mails (Forts.)

434

{}class SendHtmlMail{ private function getMime($filename) {

Page 435: 382726314 X Php5 In 14 Tagen (Ddt)

E-Mail versenden

$types = array( ".jpg" => "image/jpeg", ".jpeg" => "image/jpeg", ".png" => "image/png" ); $filename = basename($filename); $ext = strtolower(substr($filename, strrpos($filename, "."))); return $types[$ext]; } public function sendMail($to, $from, $headers, $subject, $message, $attachments="") { $bound = uniqid(time()."_"); $header = "From: $from\r\n"; $content = ''; foreach($headers as $head => $value) $header .= "$head: $value\r\n"; if(is_array($attachments)) { $header .= "MIME-Version: 1.0\r\n"; $header .= "Content-Type: multipart/mixed; boundary=\"$bound\"\r\n"; $content .= "--$bound\r\n"; $content .= "Content-Type: text/html; charset=\"iso-8859-1\"\r\n"; $content .= "Content-Transfer-Encoding: 8bit\r\n\r\n$message\r\n\r\n"; foreach($attachments as $attch) { if($fp = fopen($attch, "rb")) { $bn = basename($attch); $content .= "--$bound\r\n" ; $content .= "Content-Type: ".$this->getMime($attch)."; name=\"$bn\"\r\n" ;

435

$content .= "Content-Transfer-Encoding: base64\r\n"; $content .= "Content-Disposition: inline; filename=\"$bn\"\r\n\r\n" ; $content .= chunk_split(base64_encode(fread($fp, filesize($attch))))."\r\n";

Page 436: 382726314 X Php5 In 14 Tagen (Ddt)

Kommunikation per HTTP, FTP und E-Mail

fclose($fp); } else { throw new MailException("Fehler, kann Anhang $attch nicht öffnen."); } } $content .= "--$bound--\r\n"; } else { $content .= "Content-Type: text/html; charset=\"iso-8859-1\"\r\n"; $content .= "Content-Transfer-Encoding: 8bit\r\n\r\n$message\r\n\r\n"; } ini_set('track_errors', 1); if (!@mail($to, $subject, $content, $header)) { throw new MailException($php_errormsg); } } }$sm = new SendHtmlMail();$to = '[email protected]';$from = '[email protected]';$headers = array('X-Mailer:' => 'PHP '.phpversion());$msg = 'Test für Anhänge';$subject = 'Test Nr. 5';$attachment = array('data/Telefon.jpg', 'data/7650Nokia.jpg');try{ $sm->sendMail($to, $from, $headers, $subject, $msg, $attachment); echo "An: $to, Von: $from<br>Kopfzeilen: "; print_r($headers);}

436

catch (MailException $ex){ die($ex->getMessage());}?>

Page 437: 382726314 X Php5 In 14 Tagen (Ddt)

E-Mail versenden

Das Skript verwendet die neuen objektorientierten Möglichkeiten in PHP5. Sowird eine eigene Ausnahmenklasse MailException definiert, um Ausnahmengezielt abfangen zu können:

class MailException extends Exception

Die Klasse ist noch recht einfach. Als Anhänge sind nur Bilder zulässig, derenMIME-Typ die private Methode getMime ermittelt:

private function getMime($filename)

Für JPEG-Bilder ist der MIME-Typ beispielsweise »image/jpeg«. Der MIME-Standard verlangt die Vereinbarung einer eindeutigen Zeichenkette zur Trennungvon Datenblöcken. Eine sichere Methode ist die Nutzung der folgenden Funk-tionen:

$bound = uniqid(time()."_");

Daraus entstehen Zeichenfolgen wie beispielsweise »1092655372_4120990c6010f«.Nach der Generierung der Kopfzeilen, die hier bequemerweise als Array über-geben werden, sind die Anhänge als Nachrichtenblöcke aufbereitet dem Text derE-Mail anzufügen. Zuerst wird dabei geprüft, ob Anhänge vorliegen. Die Methodeerwartet diese als ein Array aus Dateinamen:

if(is_array($attachments))

Ist das der Fall, soll MIME verwendet werden. Diese Angabe gehört in den Hea-der:

$header .= "MIME-Version: 1.0\r\n";

Dazu muss mitgeteilt werden, dass mehrere Blöcke verschiedenen Inhalts (mixed)folgen:

$header .= "Content-Type: multipart/mixed; boundary=\"$bound\"\r\n";

In dieser Zeile wird auch die Blockgrenze mit Hilfe des Parameters boundary fest-gelegt.

Der Inhalt beginnt nun mit einem solchen Abgrenzungszeichen. Die beidenMinuszeichen leiten die Grenze ein und sind zwingend erforderlich:

437

$content .= "--$bound\r\n" ;

Vor die Anhänge wird der Text der Nachricht gestellt. Die Klasse ist in der Lage,auch gleich HTML-E-Mails zu versenden:

$content .= "Content-Type: text/html; charset=\"iso-8859-1\"\r\n";

Page 438: 382726314 X Php5 In 14 Tagen (Ddt)

Kommunikation per HTTP, FTP und E-Mail

Wenn Sie Text versenden möchten, nutzen Sie den MIME-Typ »text/text« anstatt»text/html«. Der Zeichensatz erlaubt die Verwendung des üblichen westeuropä-ischen Standardzeichensatzes für Windows, sodass auch Umlaute übertragen wer-den.

Dann folgt die Kodierung und die Nachricht selbst:

$content .= "Content-Transfer-Encoding: 8bit\r\n\r\n $message\r\n\r\n";

Hier wird nichts zusätzlich kodiert, da es sich nur um ASCII handelt. Das Übertra-gungsmedium wird aber angewiesen, nach Möglichkeit mit 8 Bit zu übertragen,damit die erweiterten Zeichen des Zeichensatzes erhalten bleiben.

Nun beginnt der Zusammenbau der Anhänge. Jeder Anhang wird einzeln erstellt.

foreach($attachments as $attch)

Zuerst wird die Datei im Binärmodus geöffnet:

if($fp = fopen($attch, "rb"))

Dann wird der Basisname ermittelt. Diese Angabe wird übertragen, während derlokale Pfad, aus dem die Dateien gelesen werden, dem Empfänger nicht mitgeteiltwird:

$bn = basename($attch)

Der Anhang wird dann wieder mit einem Grenzzeichen eingeleitet:

$content .= "--$bound\r\n" ;

Es folgen drei Kopfzeilen, die den Anhang beschreiben. Sie sehen nach der Verar-beitung folgendermaßen aus:

Content-Type: image/jpg; name="telefon.jpg" Content-Transfer-Encoding: base64Content-Disposition: inline; filename="telefon.jpg";

Nun muss der Anhang noch gemäß den MIME-Kriterien zerlegt und kodiert wer-den. Die folgenden Funktionen erledigen dies:

chunk_split(base64_encode(fread($fp, filesize($attch))))

438

Lesen Sie den Ablauf von innen nach außen:

1. fread liest die Datei komplett, die Dateigröße wird dabei mit filesize ermit-telt

Page 439: 382726314 X Php5 In 14 Tagen (Ddt)

E-Mail versenden

2. base64_encode kodiert die Datei mit dem Base64-Verfahren (wie in der Kopf-zeile gefordert)

3. chunk_split zerlegt den Datenstrom in Blöcke, die verhindern, dass Mail-Ser-ver störende Leerzeilen oder Umbrüche einfügen

Am Ende aller Anhänge wird eine weitere Grenze eingefügt, die mit zwei Minus-zeichen abschließt und so dem Mailclient mitteilt, dass keine weiteren Anhängefolgen:

$content .= "--$bound--\r\n";

Der Rest ist trivial. Die E-Mail wird versendet und der Erfolg mit einer if-Anwei-sung überwacht:

if (!@mail($to, $subject, $content, $header))

Trat ein Fehler auf, wird eine Ausnahme ausgelöst:

throw new MailException($php_errormsg);

Das Hauptprogramm definiert die nötigen Angaben zum Betrieb der Klasse. Dieeinzige öffentliche Methode sendMail macht die Verwendung extrem einfach.

Um HTML zu versenden, muss der Nachrichtenkörper lediglich eine gültigeHTML-Seite enthalten, also wenigstens mit den Tags <html></html> umschlossensein.

Abbildung 10.6: E-Mail mit zwei Anhängen in einem E-Mail-Client, mit der Klasse SendHtml-Mail versendet

439

Abbildung 10.7: HTML-Mail, mit demselben Pro-gramm versendet

Page 440: 382726314 X Php5 In 14 Tagen (Ddt)

Kommunikation per HTTP, FTP und E-Mail

Andere Lösungen

Das Versenden von HTML oder Anhängen wird von Hunderten im Internet kur-sierenden Klassen und Funktionssammlungen unterstützt. Die vorliegende Vari-ante ist insofern dennoch interessant, weil sie ausreichend kompakt ist, umkomplett verstanden zu werden. Außerdem nutzt sie die neuen Funktionen inPHP5, was nahezu alle anderen Lösungen nicht tun. Sie ist außerdem hinrei-chend alltagstauglich und leicht erweiterbar, wenn es erforderlich sein sollte.

10.6 Funktions-Referenz Stream-Funktionen

Funktion Bedeutung

stream_context_create Erzeugt ein Kontext-Handle, das andere Funktionen nutzen, um auf so konfigurierte Streams zurückzugrei-fen. Der Kontext-Parameter ist neu in vielen Dateifunk-tionen in PHP5 hinzugekommen.

stream_context_get_default Ermittelt den aktuellen Kontext eines Streams.

stream_context_get_options Ermittelt gesetzte Optionen für Kontexte, Streams oder Wrapper.

stream_context_set_option Setzt Optionen für Kontexte, Streams oder Wrapper.

stream_context_set_params Setzte Parameter für Kontexte, Streams oder Wrapper.

stream_copy_to_stream Kopiert Daten von einem Stream zu einem anderen.

stream_filter_append Fügt einem Stream ein Filter hinzu. Die Filteraktion wird nach dem Übertragen der Daten ausgeführt.

stream_filter_prepend Fügt ein Filter vor einem Stream an. Die Filteraktion wird vor dem Übertragen der Daten ausgeführt.

stream_filter_register Registriert einen benutzerspezifischen Filter. Das Filter

440

kann durch eine Klasse implementiert werden, die von php_user_filter abgeleitet wird.

Tabelle 10.2: Funktions-Referenz für Stream-Funktionen

Page 441: 382726314 X Php5 In 14 Tagen (Ddt)

Funktions-Referenz Stream-Funktionen

stream_get_contents Liest ausstehende Daten eines Streams in eine Zeichen-kette.

stream_get_filters Ermittelt eine Liste von registrierten Filtern.

stream_get_line Liest eine Zeile aus einem Stream bis zu einem verein-barten Trennzeichen.

stream_get_meta_data Ermittelt Kopfzeilen und Metadaten aus einem Stream.

stream_get_transports Ermittelt eine Liste von Sockets, die Transportaufgaben auf niedriger Ebene übernehmen.

stream_get_wrappers Ermittelt eine Liste registrierter Streams.

stream_register_wrapper Alias für stream_wrapper_register.

stream_select Äquivalent für den select-Systemaufruf.

stream_set_blocking Setzt blocking/non-blocking-Mode für einen Stream.

stream_set_timeout Setzt die Abbruchzeit für einen Stream.

stream_set_write_buffer Setzt Dateipufferung für einen Stream.

stream_socket_accept Akzeptiert eine Socket-Verbindung für den Stream. Die Verbindung muss mit stream_socket_server erstellt worden sein.

stream_socket_client Eröffnet eine Socket-Verbindung ins Internet oder zu einer Unix-Domain.

stream_socket_enable_crypto Schaltet die Verschlüsselung für eine bestehende Ver-bindung ein oder aus.

stream_socket_get_name Ermittelt den Namen eines lokalen oder remoten Sockets.

stream_socket_recvfrom Empfängt Daten von einem Socket, unabhängig davon, ob die Verbindung noch besteht oder nicht.

Funktion Bedeutung

441

stream_socket_sendto Sendet eine Nachricht an einen Socket, unabhängig davon, ob die Verbindung noch besteht oder nicht.

Tabelle 10.2: Funktions-Referenz für Stream-Funktionen (Forts.)

Page 442: 382726314 X Php5 In 14 Tagen (Ddt)

Kommunikation per HTTP, FTP und E-Mail

10.7 Kontrollfragen

1. Welche Aufgabe erfüllen Wrapper?

2. Was muss ein fremder Entwickler beachten, wenn er einen von Ihnen entwickeltesFilter verwendet?

3. Was bedeuten die Kopfzeilen Cc: und Bcc: in einer E-Mail?

4. Welches Protokoll nutzt die mail-Funktion zur Übertragung der Daten?

stream_socket_server Erstellt eine Socket-Verbindung ins Internet oder zu einer Unix-Domain.

stream_wrapper_register Registriert einen benutzerspezifischen Wrapper, der als PHP-Klasse implementiert sein muss.

Funktion Bedeutung

Tabelle 10.2: Funktions-Referenz für Stream-Funktionen (Forts.)

442

Page 443: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbank-pr g

ogrammierun

11

Page 444: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

11.1 Prinzip der Datenbankprogrammierung

Die Datenbankprogrammierung ist für fast alle Webseiten von elementarer Bedeu-tung. Es geht dabei immer wieder darum, anfallende Daten zustands- und seiten-unabhängig zu erfassen und wieder bereitzustellen. Der Datenbankserver arbeitet– auch wenn das entsprechende Programm auf derselben Maschine wie der Web-server läuft – unabhängig von PHP oder Apache.

Um eine Datenbank zu nutzen, sind deshalb mehrere Schritte erforderlich, dieden Einstieg nicht einfach machen (wenn man es kann, mag dies primitiv klin-gen):

� Aufsetzen oder Beschaffen eines Datenbankservers

� Aktivierung eines Client-Moduls in PHP, das mit dem Server kommunizierenkann

� Herstellen einer Verbindung zum Datenbankserver am Beginn des Skripts,meist unter Angabe der IP-Adresse oder des Netzwerknamens

� Herstellen einer Verbindung zu einer konkreten Datenbank, die der Daten-bankserver verwaltet

� Aufbau einer Abfrageverbindung, um SQL-Kommandos an den Datenbank-server zu senden

� Auswerten der Antwort und Aufbereitung der Daten, um diese für die Ausgabeoder für weitere Abfragen verwenden zu können

Insgesamt ist also einiger Aufwand zu treiben. Dafür hat man ein System, das mitdrei Datensätzen ebenso gut zurechtkommt wie mit 3 Millionen. Außerdem lassensich auch komplexe Daten mit vielfältigen Verknüpfungen verwalten.

Einiger Lernaufwand ist dennoch nötig. Während Sie das Aufsetzen und Verwal-ten des Datenbankservers an den Provider oder einen Administrator abschiebenkönnen, bleibt die Last der Programmierung allein beim Entwickler der Website.Unerlässlich sind deshalb SQL-Kenntnisse.

444

11.2 Die universelle Abfragesprache SQL

Dieser Abschnitt führt ganz allgemein in SQL ein. Dabei wird, soweit nichts ande-res explizit erwähnt wird, sowohl MySQL als auch SQLite unterstützt. Das heißt,

Page 445: 382726314 X Php5 In 14 Tagen (Ddt)

Die universelle Abfragesprache SQL

die entsprechende Abfragen funktionieren in beiden Fällen. Auf die Besonderhei-ten der beiden Systeme wird in eigenen Abschnitten eingegangen.

Wenn Sie SQL bereits kennen, nutzen Sie diesen Abschnitt zum Auffrischen undWiederholen. SQL ist im Zusammenhang mit der PHP-Programmierung enormwichtig und sollte unbedingt beherrscht werden.

Was ist SQL?

SQL, am besten englisch ausgesprochen (»ess-kju-ell«), steht für Structured QueryLanguage – Strukturierte Abfragesprache. SQL wird verwendet, um mit einerDatenbank zu kommunizieren. Nach der Definition der NormungsinstitutionANSI (American National Standards Institute) ist SQL der verbindliche Industrie-standard für die Abfrage relationaler Datenbankmanagementsysteme (RDBMS).SQL-Anweisungen bestimmen, wie eine Abfrage oder Befehl aussehen soll, dendas System empfängt und ausführt. Der Standard erlaubt es, mit einem Basisbe-fehlssatz nahezu alle RDBMS zu bedienen. Abweichend von den Basisbefehlenverfügen alle am Markt operierenden System – egal ob kommerziell oder OpenSource – über mehr oder wenige sinnvolle und natürlich zu allen anderen inkom-patible Erweiterungen. MySQL ist hierin ebenso wenig eine Ausnahme wieSQLite, Oracle, MS SQL, Sybase oder Access. Der Basisbefehlssatz, den manunbedingt beherrschen muss, wird jedoch weitgehend identisch unterstützt. Dazugehören SELECT (Abfragen), UPDATE (Aktualisieren), DELETE (Löschen),INSERT (Einfügen) und DROP (Entfernen).

Tabellen und Abfragen

Relationale Datenbanken halten Daten in Tabellen. Tabellen werden durch eineneindeutigen Namen identifiziert, der in den Abfragebefehlen anzugeben ist.Tabellen bestehen aus Spalten, die jeweils einen ganz bestimmten Datentyp auf-nehmen können. Sie bestehen auch aus Reihen, in denen zusammengehörendeDaten gespeichert sind. Eine einfache Tabelle mit Daten kann in etwa folgender-maßen dargestellt werden:

445

Page 446: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

Die Tabelle abfragen: SELECT

Grundsätzlich lässt sich jede Datenbank vollständig über SQL-Befehle bedienen.Gerade am Anfang wird das eher weniger der Fall sein. Alle Datenbankprogrammebieten mehr oder weniger elegante dialogorientierte Oberflächen, die zumindestdas Anlegen der Tabellen und das Eingeben der ersten Daten erlauben, ohne dieAnweisungen selbst zu beherrschen.

Bei Abfragen sieht es anders aus. Für viele kommerzielle Datenbanken gibt es sogenannte Query Builder und einige freie Programme mit entsprechender Funk-tion sind auch verfügbar. Derartige Tools verhindern jedoch, dass man SQL wirk-lich lernt. Das ist fatal, denn früher oder später stößt jedes dieser Programme anseine Grenzen. Das sind aber selten die Grenzen von SQL. Die Beschäftigung mitder Abfragesyntax ist deshalb unbedingt zu empfehlen.

Der prinzipielle Aufbau einer Abfrage folgt folgendem Schema:

SELECT Spaltename1 [, Spaltenname2, ...] FROM Tabelle [WHERE Bedingungen]

Die eckigen Klammern deuten optionale Angaben an. Die Anweisungen solltenaußerdem, auch wenn sie hier aus Gründen der Lesbarkeit mehrzeilig erscheinen,als eine Zeichenkette gelesen werden. Auf die oben gezeigte Tabelle »Wetter«bezogen, sieht eine konkrete Abfrage beispielsweise folgendermaßen aus:

Stadt Bundesland Tief Hoch

Berlin Berlin 5 13

München Bayern 2 16

Hamburg Hamburg 7 12

Frankfurt Hessen 7 11

Tabelle 11.1: Eine sehr einfache SQL-Tabelle mit Wetterdaten (Tabellenname: Wetter)

446

SELECT Stadt, Bundesland FROM Wetter WHERE Tief > 5

Entfällt die mit WHERE eingeleitete Bedingung, werden alle Reihen der Tabellezurückgegeben. Die Auswahl der Spalten bestimmt die Liste unmittelbar hinterSELECT. Hier kann alternativ auch das Sternchen (*) angegeben werden, um alleSpalten zu holen.

Page 447: 382726314 X Php5 In 14 Tagen (Ddt)

Die universelle Abfragesprache SQL

SELECT * FROM ist die häufigste Abfrage – zumindest in der Literatur. Inder Praxis sollte dies nicht so sein, weil die Abfrage von Spalten, dienicht benötigt werden, unnütz Zeit kostet. Die Darstellung auf demPapier lässt sich aber verkürzen und die Angaben sind weniger spezi-fisch, was zur Erklärung manchmal hilfreich ist.

Hat eine Abfrage mit SELECT Erfolg, besteht die Antwort aus einem oder mehrerenso genannten Datensätzen. Jeder Datensatz kann wiederum ein oder mehrere Fel-der enthalten. Der Datensatz ist quasi eine konkrete Version einer Reihe derDatenbank, ergänzt um berechnete Spalten oder reduziert auf ausgewählte Spal-ten. Nur wenn die Abfrage aus dem einfachen * besteht, entspricht ein Datensatzeiner Reihe.

Bedingungen formulieren: WHERE

Die WHERE-Bedingung ist sehr wichtig. Sie folgt denselben Prinzipien wie if inPHP5, das heißt, es muss sich ein Boolescher Ausdruck formulieren lassen. Dazugehören in erster Linie die Vergleichsoperatoren:

� >Größer als; meist nur mit numerischen Werten sinnvoll

� <Kleiner als; meist nur mit numerischen Werten sinnvoll

� >=Größer als oder gleich; meist nur mit numerischen Werten sinnvoll

� <=Kleiner als oder gleich; meist nur mit numerischen Werten sinnvoll

� <>Ungleich; kann fast immer verwendet werden

� =Gleich; kann fast immer verwendet werden

Ebenso wichtig sind die Booleschen Operatoren, die bereits aus PHP bekannt sind

447

und sich in SQL analog verhalten: OR, AND, NOT.

Beachten Sie beim Gleichheitszeichen, dass dies in SQL einfach ist,nicht doppelt wie in PHP. Ein doppeltes Gleichheitszeichen gibt es inSQL generell nicht.

Page 448: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

Neben diesen kennt SQL aber auch einige sehr spezielle Operatoren, die in PHPnicht vorhanden sind:

� LIKEDieser Operator erlaubt eine Ähnlichkeitssuche mit Platzhaltern. SQL verwen-det sehr untypische Platzhalter: % steht für jedes beliebige Zeichen in beliebi-ger Anzahl und _ für genau ein beliebiges Zeichen.

Beispiel:

SELECT * FROM Wetter WHERE Stadt LIKE 'B%'

Diese Anweisung gibt alle Städte zurück, die mit dem Buchstaben »B« begin-nen.

� INSQL verwendet zur Referenz auf Datensätze oft deren Schlüsselnummern(darauf wird noch genauer eingegangen). Listen solcher Referenzen kann manmit IN verwenden. Aber auch einfache Zahlenvergleich sind gut möglich.

Beispiel:

SELECT * FROM Wetter WHERE Tief IN (1,3,5,7)

Dieser Befehl fragt alle ungeraden Tiefsttemperaturwerte aller Datensätze ab.

� BETWEENHiermit lassen sich Wertebereiche abfragen.

Beispiel:

SELECT * FROM Wetter WHERE Hoch BETWEEN 10 AND 15

Zeichenketten müssen in SQL mit einfachen Anführungszeichen umschlossenwerden. Die meisten Systeme verkraften zwar auch doppelte, die Zusammenarbeitmit PHP profitiert aber erheblich von einer einheitlichen Schreibweise, weshalbeinfache Anführungszeichen zum Standard gehören sollten.

Die SELECT-Anweisung kann weitaus mehr. Dazu gehören Befehle zum Sortieren,Filtern, Verknüpfen mit anderen Tabellen usw. Um dies ausprobieren zu können,brauchen Sie jedoch mehr Tabellen, die erstmal erzeugt und befüllt sein müssen.

448

Tabellen anlegen und füllen

Werden Tabellen per Code erzeugt, braucht man eine spezielle Anweisung dafür.Dieser Abschnitt zeigt, wie Tabellen erzeugt und gefüllt werden.

Page 449: 382726314 X Php5 In 14 Tagen (Ddt)

Die universelle Abfragesprache SQL

Tabelle erzeugen: CREATE TABLE

In SQL heißt diese CREATE TABLE. Die grundlegende Syntax lautet:

CREATE TABLE Tabellenname (SpaltenName DatenTyp [Einschränkung], [SpaltenName DatenTyp [Einschränkung], ...] )

Die Angabe der Einschränkung bestimmt nähere Eigenschaften der Spalte, abhän-gig vom gewählten Datentyp. Die Angabe ist optional, weil alle Datentypen mitbestimmten Basiseigenschaften ausgestattet sind. Der Spaltenname ist frei wählbar.Die meisten Systeme verkraften sogar Leerzeichen (keine gute Idee), wenn derName in Anführungszeichen steht.

Der Datentyp einer Spalte

Der Datentyp ist eine sehr wichtige Eigenschaft einer Spalte. SQL ist prinzipielltypstreng und überwacht die Einhaltung der Daten in den Feldern umfassend. Inder Praxis wird dies etwas aufgeweicht betrachtet. So konvertiert SQLite internalles so lange, bis es passt, und akzeptiert meist alles. MySQL hält die Typen strengein, konvertiert aber auch weit reichend, und die Daten »passend« zu machen.Andere Datenbanken sind strenger und reagieren mit Fehlermeldungen auf fal-sche Typen. Generell geht SQL aber nie so locker mit Datentypen um wie PHP.

SQL verfügt über eine Vielzahl von Funktionen, die speziell für Zeichenkettenoder Zahlen gedacht sind. Der falsche Datentyp für die zu berechnende Spalteführt dann in jedem Fall zu einer Fehlermeldung.

Die Angabe des Datentyps erfolgt durch ein aussagekräftiges Schlüsselwort undoptional durch Parameter. Die folgende Tabelle zeigt die wichtigsten Angaben, dievon fast allen SQL-Datenbanken akzeptiert werden.

Datentyp Beschreibung

CHAR(Anzahl) Feld mit fester Anzahl von Zeichen.

449

VARCHAR(Maximum) Feld mit variabler Anzahl Zeichen, begrenzt auf ein Maxi-mum.

INT(Breite) Ganzzahlfeld mit der durch Breite angegebenen Anzahl Stellen

Page 450: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

Die meisten Datenbanken bieten vordefinierte Variationen von INT für verschie-dene Mengen, beispielsweise BIGINT oder TINYINT in MySQL.

Tabellen erzeugen

Das folgende Beispiel zeigt, wie die am Anfang benutzte Tabelle erzeugt wird:

CREATE TABLE Wetter (Stadt VARCHAR(100), Bundesland VARCHAR(30), Tief INT(2), Hoch INT(2))

Generell gilt die Regel, dass die Datentypen so fein und begrenzend wie möglichausgelegt werden sollten. So dürfte es in Mitteleuropa kaum vorkommen, dass drei-stellige Temperaturen auftreten. Deshalb wird INT(2) geschrieben. Es gibt keinBundesland, das mehr als 30 Zeichen für den Namen benötigt (Mecklenburg-Vor-pommern ist der längste Name mit genau 22 Zeichen). Beachten Sie, dass dieTemperaturangabe -11°C in einem INT-Feld auch nur zwei Stellen benötigt, weildas Vorzeichen separat gespeichert wird.

Derartige Beschränkungen verhindern zumindest teilweise, dass unsinnige Daten

DATE Ein Datumsfeld (nur die Angabe des Datums ist möglich).

DATETIME Ein Datums- und Zeitfeld (enthält Datums- und Zeitinfor-mationen).

FLOAT(Breite, Dezimal) Gleitkommazahl mit Breite Stellen und Dezimal Nachkom-mastellen.

TEXT Längere Textdaten (VARCHAR und CHAR sind auf 255 Zeichen begrenzt).

BLOB Größere Binärdaten (BLOB = Binary Large Objects).

Datentyp Beschreibung

450

in die Datenbank gelangen. Sie führen freilich, wenn es dennoch versucht wird,zu Fehlermeldungen im Skript, die gesondert behandelt werden müssen. Inkonsis-tente Datenbestände sind jedoch weitaus schlimmer als Fehler bei der Eingabe,die frühzeitig auf das Problem hinweisen.

Page 451: 382726314 X Php5 In 14 Tagen (Ddt)

Die universelle Abfragesprache SQL

Einschränkungen

Bei der Definition der Syntax für CREATE TABLE war von Einschränkungen dieRede. Dies sind Angaben, die den möglichen Inhalt unabhängig vom Datentypbestimmen. Drei solcher Zusatzangaben sind besonders wichtig:

� UNIQUEHiermit wird bestimmt, dass alle Werte in der Spalte nur ein Mal vorkommendürfen, das heißt, sie müssen eindeutig (engl. unique) sein.

� PRIMARY KEYBestimmt, dass diese Teile den Primärschlüssel stellt. Zur Bedeutung derSchlüssel folgt noch eine detaillierte Betrachtung. PRIMARY KEY impliziertimmer UNIQUE.

� AUTO_INCREMENTNicht offizieller SQL-Standard aber dennoch von fast allen Datenbank unter-stützt sind Felder, deren Werte automatisch erzeugt werden, ohne dass diesbeim Einfügen angegeben werden muss.

� NULL, NOT NULLNULL erklärt, dass das betreffende Feld beim Schreiben der Daten leer bleibendarf, NOT NULL bestimmt, dass dies nie der Fall sein darf.

� DEFAULTBestimmt einen Standardwert, wenn bei der Eingabe der Daten kein Wertangegeben oder das Feld überhaupt nicht spezifiziert wurde.

Es gibt weitaus mehr Einschränkungen als die gezeigten, allerdings werden diemeisten weder von MySQL noch von SQLite unterstützt, weshalb die Ausführun-gen auf die elementaren Angaben beschränkt werden.

Daten einfügen

Nachdem die Tabelle existiert, kann sie mit Daten gefüllt werden. Dazu wird dieAnweisung INSERT benutzt. INSERT verlangt die Angabe des Tabellennamens, einerListe der Spalten, die mit Daten bestückt werden, und eine Liste der Werte, die

451

hineingeschrieben werden. Die Angabe der Spalten kann entfallen, wenn allebeschrieben werden. Eine weitere Wetterangabe in der Mustertabelle könnte fol-gendermaßen eingefügt werden:

Page 452: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

INSERT INTO Wetter (Stadt, Bundesland, Hoch, Tief) VALUES ('Dresden', 'Sachsen', 17, 8)

Beachten Sie, dass Zeichenketten in einfachen Anführungszeichen stehen, nume-rische Werte jedoch nicht. Die Anzahl der Werte hinter VALUES muss exakt derAnzahl der Spalten entsprechen, damit die Zuordnung stimmt. Spalten, die hiernicht auftreten, werden entsprechend den Einschränkungsregeln befüllt, also mitStandardwerten (DEFAULT), automatischen Werten (AUTO_INCREMENT) oder NULL-Werten (NULL).

Aktualisieren und Löschen von Daten

Nachdem die Daten nun in der Tabelle sind und auch gelesen werden können,sind Lösch- und Aktualisierungsvorgänge an der Tagesordnung.

Aktualisierung von Daten: UPDATE

Die Änderung der Inhalte wird mit der Anweisung UPDATE vorgenommen. Diegrundlegende Syntax folgt folgendem Schema:

UPDATE TabellenName SET SpaltenName = Wert [, SpaltenName = Wert, ...] [WHERE Bedingung]

Die Bedingung entspricht weitgehend den bei SELECT möglichen Angaben. OhneBedingung werden immer alle Spalten geändert. Die Zuweisung neuer Wertekann mit Konstanten oder Berechnungen, auch mit Referenzen auf andere Spal-ten erfolgen.

Im Beispiel lässt sich die Temperatur für eine bestimmte Stadt folgendermaßenändern:

UPDATE Wetter SET Hoch = 17, Tief = 23 WHERE Stadt = 'Berlin'

Man kann auch alle Temperaturen einfach erhöhen:

452

UPDATE Wetter SET Hoch = Hoch + 1

Page 453: 382726314 X Php5 In 14 Tagen (Ddt)

Die universelle Abfragesprache SQL

Löschen von Daten: DELETE

Dem Löschen dient die Anweisung DELETE. Die Anwendung entspricht demSchema der bisher vorgestellten Befehle:

DELETE FROM TabellenName [WHERE Bedingung]

Auch wenn die Bedingung optional ist, die Angabe dürfte in den allermeisten Fäl-len unbedingt erforderlich sein – sonst ist die Tabelle nämlich leer.

Für die Bedingung kann wieder ein Ausdruck nach dem bei SELECT gezeigtenSchema benutzt werden. Beachten Sie, dass SQL kein »Undo/Rückgängig« etc.kennt – was weg ist, ist endgültig weg.

Löschen und Leeren von Tabellen: DROP und TRUNCATE

Um eine Tabelle komplett wieder loszuwerden, nutzen Sie die Anweisung DROP:

DROP TabellenName

Ebenso einfach geht das Leeren, das alle Daten entfernt, aber die Tabelle selbsterhält:

TRUNCATE TabellenName

Gegenüber der Anweisung DELETE FROM TabellenName (ohne Bedingung) ist, TRUN-CATE schneller. Der Effekt ist derselbe – alles ist weg.

Fortgeschrittene Abfragen mit SELECT

SELECT allein ist ungeheuer mächtig. Der korrekte Umgang damit verlangt abereiniges an Hintergrundwissen. Dennoch kommt man sehr schnell nicht mehrohne die Basisabfragen aus. Dieser Abschnitt zeigt die Anwendung von SELECT aufdem Niveau, wie es auf durchschnittlich komplexen PHP-Seiten durchaus zurAnwendung kommen kann. Wenn Sie noch weiter lernen möchten, ist der Zugriffauf spezielle Literatur erforderlich.

453

SELECT komplett betrachtet

Mit SELECT werden Datenbanken abgefragt. Dabei ist FROM die einzige Angabe, diezwingend erforderlich ist. Das bereits vorgestellte WHERE ist optional. Insgesamt sindes fünf derartige Operatoren, die zum Einsatz kommen können:

Page 454: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

� FROMBestimmt die Tabelle (oder mehrere Tabellen), aus denen gelesen wird.

� WHERESchränkt die Auswahl durch eine global wirkende Bedingung ein.

� GROUP BYGruppiert Ergebnisse, sodass sie mit anderen zusammengefasst werden kön-nen.

� HAVINGWendet Auswahleinschränkungen auf Gruppen an.

� ORDER BYSortiert die Ergebnisse.

Bei der Auswahl der Spalten kann zudem bestimmt werden, ob alle oder nur ein-deutige Ergebnisse zurückgegeben werden sollen:

� ALL

� DISTINCT

Die Angabe ALL entfällt in den allermeisten Fällen, weil dies der Standardwert ist.Für die Abfrage eindeutiger Werte schreiben Sie beispielsweise:

SELECT DISTINCT Bundesland FROM Wetter

Dies verhindert, dass die mehrfach in der Tabelle auftretenden Bundesländer auchmehrfach ausgegeben werden.

GROUP BY gruppiert Ergebnisse und wenn Einschränkungen bei der Auswahl derGruppen gefragt sind, dann ist HAVING die erste Wahl. Nach HAVING können diesel-ben Booleschen Ausdrücke stehen wie nach WHERE. Mehr dazu weiter unten.

Aggregierende Funktionen

Aggregierende Funktionen führen Berechnungen mit der durch SELECT erstelltenAuswahl von Daten aus. Die häufigste und einfachste ist COUNT – hiermit wirdschlicht die Anzahl der Reihen ermittelt. Wichtig sind folgende Funktionen:

454

� COUNT, COUNT(*)Hiermit wird die Anzahl der ausgewählten oder (*) aller Reihen der Abfrageermittelt.

Page 455: 382726314 X Php5 In 14 Tagen (Ddt)

Die universelle Abfragesprache SQL

� MAX, MINEnthält die betroffene Spalte Werte, die eine Ordnung bilden, kann hiermitder größte bzw. kleinste Wert ermittelt werden.

� SUMFür numerische Spalten ermittelt diese Funktion die Summe.

� AVGFür numerische Spalten ermittelt diese Funktion den Durchschnitt.

Beachten Sie, dass die Aggregatfunktionen keine Liste von Daten erzeugen, sondernskalare Werte. Dies muss bei der Auswertung mit PHP-Funktionen berücksichtigtwerden. Bei den PHP-Beispielen wird darauf nochmals explizit eingegangen.

Die Anwendung in SQL sieht folgendermaßen aus:

SELECT AVG(Hoch) FROM Wetter

Damit erhält man die durchschnittliche Tageshöchsttemperatur aus der Wetter-Tabelle. Selbstverständlich sind auch hier WHERE-Bedingungen eine gute Idee:

SELECT AVG(Hoch) FROM Wetter WHERE Bundesland = 'Bayern'

Nun erhalten Sie die durchschnittliche Tageshöchsttemperatur aller bayerischenStädte.

Gruppierungen

Gruppierungen mit GROUP BY wurden bereits kurz erwähnt. Der Sinn ist wenigerder damit erreichbare Sortiereffekt, sondern die Möglichkeit, die Aggregatfunktio-nen auf die Gruppen anzuwenden.

SELECT MAX(Hoch), Bundesland FROM Wetter GROUP BY Bundesland HAVING Bundesland LIKE 'B%'

Diese Abfrage ermittelt die höchste Tagestemperatur aller Bundesländer, die mit»B« beginnen. Die Einschränkung mit HAVING ist freilich optional, meist genügtdie Gruppierung alleine. Wichtig ist, dass die Liste der Spalten hinter GROUP BY in

455

der Liste der Spalten hinter SELECT enthalten sein muss. Gruppierung und Aus-gabe korrespondieren immer. Die Aggregatfunktionen sind davon nicht betroffen,deshalb kann die Funktion MAX im Beispiel hinzugefügt werden.

Page 456: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

Ergebnisse sortieren

Tabellen sind in SQL niemals sortiert oder geordnet. Allein die Abfrage entschei-det, wie die Daten auszugeben sind. Um die entsprechende Kontrolle zu bekom-men, wird ans Ende der Anweisung ORDER BY angehängt. Zwei zusätzlicheKlauseln bestimmen die Richtung:

� ASCDies steht für »ascending«, also aufsteigend.

� DESCDies steht für »descending«, also absteigend.

Hinter ORDER BY lassen sich mehrere Spalten angeben, um uneindeutige Sortierun-gen nach nur einer Spalte zu vermeiden:

SELECT * FROM Wetter ORDER BY Bundesland, Stadt ASC

Das konkrete Sortierverhalten kann SQL nicht bestimmen. Dazu bietet jedesDatenbanksystem verschiedene Einstellmöglichkeiten, die nicht standardisiertsind. Dies betrifft beispielsweise die Einordnung von Umlauten ins Alphabet.

Die Klausel ASC kann auch entfallen, denn die aufsteigende Sortierung ist derStandardfall. Dies gilt aber nur, wenn ORDER BY angegeben wurde. Ohne diesesSchlüsselwort wird unsortiert ausgegeben.

Funktionen

SQL verfügt über eine reiche Funktionspalette, unter anderem:

� Mathematische Funktionen

� Zeichenkettenverarbeitung

� Datumsfunktionen

� Systemfunktionen

Leider ist der Standard hier kaum beachtet worden. Deshalb gibt es teilweise dras-tische Unterschiede in Ausstattung und Syntax bei verschiedenen Datenbanken.

456

Der Abschnitt 11.4 »Erste Schritte mit MySQL und MySQLi« ab Seite 477 zeigtdie konkreten Funktionen für MySQL und im Kapitel Die integrierte DatenbankSQLite ab Seite 497 finden Sie entsprechende Auflistung für SQLite.

Page 457: 382726314 X Php5 In 14 Tagen (Ddt)

Die universelle Abfragesprache SQL

Verknüpfungen zwischen Tabellen

Der Name »Relationale Datenbanken« enthält einen Hinweis auf eine ganz spezi-fische Eigenschaft – die Relationen oder Beziehungen. Richtig wertvoll wird derUmgang mit Datenbanken erst, wenn man zwischen Tabellen derartige Beziehun-gen aufbaut.

Das ist in fast allen Anwendung auch notwendig, um die Datenhaltung effektiv undnicht redundant zu halten. Wenn Sie die gut gefüllte Wetter-Tabelle sehen, werdenSie bemerken, dass dieselben Bundesländer immer wieder aufgeführt sind. Ändertman nun den Namen, beispielsweise wegen eines Schreibfehlers, dann müsste manalle betreffenden Felder ändern. Hat man dabei schon einen Fehler, beispielsweiseaus früheren Einträgen, wird die Datenbank inkonsistent. Deshalb versucht man,redundante Daten zu vermeiden. Dazu wird eine zweite Tabelle erstellt und mitder ersten verknüpft. Damit das Verknüpfen funktioniert, erhält außerdem jedeTabellen einen Primärschlüssel, der jeden Datensatz eindeutig kennzeichnet.

Normalisierungen

Folgende neue Tabelle ist also für die Wetter-Anwendung erforderlich:

Nun wird die Wetter-Tabelle so geändert, dass nur noch mit Relationen gearbeitetwird:

ID Name

1 Berlin

2 Bayern

3 Hamburg

4 Hessen

Tabelle 11.2: Tabelle für Bundesländer (Ausschnitt)

457

ID Stadt BundeslandID Hoch Tief

1 Berlin 1 7 13

2 München 2 8 16

Tabelle 11.3: Tabelle für Wetterdaten (Ausschnitt)

Page 458: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

Die Verknüpfung ist nun noch zu definieren. Je nach Datenbank ist dies eine ein-fache Festlegung oder ein fest programmierter Wert, der von der Datenbank über-wacht wird. MySQL und SQLite überwachen nichts und deshalb genügt dieAngabe der Verknüpfung bei der Abfrage, was durch Vergleich der beiden betrof-fenen Felder geschieht:

Bundesland.ID = Wetter.BundeslandID

Damit das in einer Anweisung gelingt, muss man etwas beachten. So kann jedemSpaltennamen immer der Tabellenname vorangestellt werden, getrennt durcheinen Punkt. Das ist meist notwendig, weil sich die Spaltennamen gleichen kön-nen und der SQL-Server dann nicht mehr weiß, welche Spalte gemeint ist.

Das Aufbrechen einer Tabellen in mehrere zur Vermeidung von Redundanzenwird als Normalisierung bezeichnet. Es gibt mehrere Stufen der Normalisierung,für deren vollständige Darstellung jedoch mehr Platz erforderlich ist. An dieserStelle sei auf entsprechende Spezialliteratur zu Datenbanken verwiesen.

Prinzipien verknüpfter Abfragen

Verknüpfte Abfragen können auf mehreren Wegen erstellt werden. Der einfachsteweg ist ein so genannter »Inner Join«, bei dem ein Kreuzprodukt aus beiden Tabel-len erstellt wird:

SELECT * FROM Wetter, Bundesland

Das führt dazu, dass jede Zeile der einen Tabelle mit jeder der anderen Tabelle»gekreuzt« wird. 100 Wetterdaten für 16 Bundesländer ergeben dann 1.600 Rei-hen. Damit kann in der Praxis niemand etwas anfangen. Also ist die benötigte

3 Regensburg 2 9 15

4 Altötting 2 8 15

ID Stadt BundeslandID Hoch Tief

Tabelle 11.3: Tabelle für Wetterdaten (Ausschnitt) (Forts.)

458

Verknüpfung anzugeben:

SELECT * FROM Wetter, Bundesland � WHERE Bundesland.ID = Wetter.BundeslandID

Nun werden die Datensätze aus der ersten Tabelle mit den passenden aus derzweiten verknüpft. Da jeder Wettereintrag nur in einem Bundesland platziert sein

Page 459: 382726314 X Php5 In 14 Tagen (Ddt)

Die universelle Abfragesprache SQL

kann, sind es nun nur noch die benötigten 100 Datensätze. Im Unterschied zureinfachen Abfrage der ersten Tabelle steht nun aber auch der Name des Bundes-landes zur Verfügung. Korrekter (im Sinne von: effizienter) ist folgende Version:

SELECT W.Stadt, W.Hoch, W.Tief, B.Name � FROM Wetter W, Bundesland B � WHERE B.ID = W.BundeslandID

Damit ist die mit der ersten Abfrage erreichte Ausgabe wieder da, allerdings dies-mal basierend auf einer verbesserten Datenstruktur.

Zum »Inner Join« kann eine alternative Syntax verwendet werden:

SELECT W.Stadt, W.Hoch, W.Tief, B.Name � FROM Wetter W INNER JOIN Bundesland B � ON B.ID = W.BundeslandID

Weitere Bedingungen, die in der ersten Variante mit AND angehängt werden, müss-ten hier in einer zusätzlichen WHERE-Bedingung stehen. Der »Inner Join« beziehtNULL-Felder nicht mit ein. Es kann nämlich sein, dass zu einer Stadt noch keineZuordnung des Bundeslandes erfolgte. Was passiert mit solchen Feldern? Grund-sätzlich ist NULL in SQL kein unmöglicher Zustand. Es gibt nun mehrere Varian-ten:

� Die linke Tabelle (links vom JOIN) enthält NULL-Werte und die entsprechendenZeilen sollen trotzdem verarbeitet werden.

In diesem Fall wird als Schlüsselwort LEFT JOIN gewählt.

� Die rechte Tabelle (rechts vom JOIN) enthält NULL-Werte und die entsprechen-den Zeilen sollen trotzdem verarbeitet werden.

In diesem Fall wird als Schlüsselwort RIGHT JOIN gewählt.

Freilich kann man auch die Tabellen vertauschen und die Schlüsselwörter genauumgekehrt einsetzen.

Es gibt weitere JOIN-Varianten, die hier jedoch zu weit führen und die – leider –auch nicht alle von MySQL unterstützt werden.

459

Fortgeschrittene SQL-Techniken

Einige fortgeschrittene SQL-Techniken seien an dieser Stelle kurz erwähnt. Siesind leicht zu verstehen und einzusetzen. Das Handbuch zu MySQL gibt hier aus-führlich Auskunft.

Page 460: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

Primärschlüssel

Bislang wurde einfach angenommen, dass die Tabellen einen Schlüssel haben.Generell ist dies keine Option, sondern ein Primärschlüssel ist erforderlich, damitdie Datenbank die Datensätze unterscheiden kann. Praktischerweise definiert manmeist eine Spalte als INT (oder größer, beispielsweise BIGINT) und versieht sie mitdem Attribut AUTO_INCREMENT. MySQL legt die benötigten Schlüssel dann automa-tisch an. Andere Tabellen können dann Referenzen dazu nutzen. Ohne Primär-schlüssel könnte die Datenbank die Datensätze nicht eindeutig unterscheiden undAbfragen wären nicht ausführbar.

Alternativ zur automatischen Vergabe ist freilich auch die Nutzung anderer Spal-ten mit eindeutigen Werten möglich, ebenso wie sich der Primärschlüssel ausmehreren Spalten zusammensetzen kann, um eindeutig zu sein.

Unterabfragen

Manchmal benötigt man die Ergebnisse einer Abfrage für eine andere. SQL kanndiese Verknüpfung direkt ausführen, also ohne PHP dazwischen. Man spricht vonso genannten Sub-Selects:

SELECT * FROM Wetter WHERE BundeslandID NOT IN (SELECT ID FROM Bundesland WHERE NAME LIKE 'B%')

Zuerst wird hier die innere Abfrage ausgeführt. Sie ergibt eine Liste der Primär-schlüssel der Tabelle Bundesland für alle Länder, die mit »B« beginnen. DieseListe (beispielsweise 1,2,4,8) wird für den IN-Operator benutzt. Dann wird dieAbfrage für alle Bundesländer ausgeführt, die nicht (NOT IN) mit »B« beginnen.Sub-Selects sind vor allem bei verknüpften Tabellen häufiger im Einsatz.

Sub-Selects sind meist langsamer als JOIN und äquivalent verwendbar. Es gibt aberauch Fälle, in denen sie sich nicht gegenseitig ersetzen können. In der Praxisbraucht man beides.

460

UNION

Mit UNION lassen sich zwei Abfragen kombinieren. Das Resultat können zwei ver-schiedene, aber auch zwei gleichartig strukturierte Tabellen sein. Die Kombina-

Page 461: 382726314 X Php5 In 14 Tagen (Ddt)

Der MySQL-Dialekt

tion von Abfragen hebt einige Beschränkungen der JOIN-Verfahren auf. DasSchlüsselwort muss immer zwischen zwei gültigen SELECT-Abfragen stehen:

SELECT * FROM Wetter UNION SELECT * FROM Stadt

Beim programmtechnischen Zugriff mittels PHP5 müssen spezielle Abfrageme-thoden verwendet werden, um die unterschiedlich strukturierten Tabellen ausle-sen zu können.

11.3 Der MySQL-Dialekt

Dieser Abschnitt zeigt die Besonderheiten, Funktionen und Eigenarten vonMySQL 4 und teilweise auch MySQL 5. Soweit MySQL sich an das im vorherge-henden Abschnitt beschriebene Standard-SQL hält, wird dies nicht nochmals wie-derholt.

Grobe Abweichungen vom SQL92-Standard

Die folgende Liste zeigt in loser Folge einige der wichtigsten Erweiterungen,Funktionen und Einschränkungen in MySQL. Die meisten Angaben gelten fürMySQL ab Version 4.1, MySQL 5 zeichnet sich demgegenüber vor allem durchhöhere Stabilität, Geschwindigkeit und (nach der finalen Version) Zuverlässigkeitaus.

� CREATE TABLE t2 LIKE t1Erzeugt eine neue Tabelle t2, die exakt denselben Aufbau wie t1 hat.

� INSERT ON DUPLICATE KEY UPDATEFügt Daten ein, bzw. aktualisiert, wenn der Primärschlüssel bereits existiert.Das ist eine elegante Kombination aus INSERT und UPDATE.

� Erweiterungen bei den GROUP BY-Funktionen: STD, BIT_OR, BIT_AND,GROUP_CONCAT(SpaltenName SEPARATOR ',')

461

Die neue Aggregat-Funktion GROUP_CONCAT, kann die Werte einer Abfrage alsZeichenkette zusammenfassen. Es kann ein Trennzeichen ähnlich wie inPHPs join-Funktion angegeben werden.

� LIKE kann auch auf numerische Spalten angewendet werden.

Page 462: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

� Der SELECT-Befehl ist um zwei Schlüsselwörter erweitert worden: INTO OUTFILEund STRAIGHT_JOIN.

� Neu sind die Befehle OPTIMIZE TABLE und SHOW.

� GROUP BY muss nicht alle Spalten enthalten, die ausgegeben werden.

� Zusätzlich zu den Operatoren OR und AND können die Symbole || und &&genutzt werden. Für die Verknüpfung von Zeichenketten muss anstatt || dieFunktion CONCAT verwendet werden.

� Der Operator % kann als Synonym für MOD eingesetzt werden.

� Logische Operatoren können auch auf der linken Seite eines SELECT-Befehlsgeschrieben werden:

SELECT spalte1 >= 100 FROM table

� Der Abruf der letzten automatisch generierten ID erfolgt mit der FunktionLAST_INSERT_ID().

� Diverse neue Funktionen, unter anderem versteht MySQL reguläre Ausdrückemit REGEXP und RLIKE (siehe Liste im nächsten Abschnitt).

� Neue Befehle: REPLACE ersetzt UPDATE und INSERT. Ist der Datensatz vorhanden(identifiziert am Primärschlüssel), wird INSERT ausgeführt, andernfalls UPDATE.

� FLUSH löscht Statusinformationen.

Datentypen

Die Datentypen sind in MySQL sehr umfangreich. Die folgende Tabelle zeigtalles, was geht, unabhängig davon, ob es dem SQL92-Standard entspricht odernicht.

Datentyp Beschreibung

TINYINT [(M)] [UNSIGNED] [Z] Kleine Ganzzahlen, von 0 bis 255 oder -128 bis 127

SMALLINT [(M)] [UNSIGNED] [Z] Ganzzahlen, entweder von 0 bis 65.535 oder von

462

-32.768 bis +32.767

MEDIUMINT [(M)] [UNSIGNED] [Z] Ganzzahlen, entweder von 0 bis 16.777.215 oder von -8.388.608 bis +8.388.607

Tabelle 11.4: Datentypen in MySQL (Z steht für ZEROFILL, M für die Stellenzahl)

Page 463: 382726314 X Php5 In 14 Tagen (Ddt)

Der MySQL-Dialekt

INT [(M)] [UNSIGNED] [Z]INTEGER

Ganzzahlen, entweder von 0 bis 4.294.967.295 oder von -2.147.283.648 bis +2.147.283.647

BIGINT [(M)] [UNSIGNED] [Z] Ganzzahlen, entweder von 0 bis 18.446.744.073.709.551.615 oder von -9.223.372.036.854.775.808 bis +9.223.372.036.854.775.807

FLOAT(precision) [Z] Fließkommazahl, immer vorzeichenbehaftet. precision kann 4 oder 8 sein; 4 steht für einfache Genauigkeit, 8 für doppelte.

FLOAT[(M,D)] [Z] Fließkommazahl, deren Wertebereich von -3,40282346638 bis -1,175494351-38 reicht und die 0 sowie den Wertebereich von +1,175494351-38 bis +3,40282346638 umfasst.

DOUBLE[(M,D)] [Z]DOUBLEPRECISION[(M,D)] [Z]REAL[(M,D)] [Z]

Fließkommazahl, deren Wertebereich von -1,7976931348623157308 bis -2,2250738585072014-308, und von -2,2250738585072014-308 bis +1,7976931348623157308 reicht, inklusive der 0.

DECIMAL(M,D) [Z]NUMERIC

Ungepackte Fließkommazahl, immer vorzeichenbe-haftet. Zahlen werden als Zeichenkette gespeichert, jede Ziffer steht in einem Byte. Das Komma, Vor-zeichen usw. belegen jeweils ein Byte.

DATE Datum im Format »YYYY-MM-DD«. Der Werte-bereich geht vom 1.1.1000 bis zum 31.12.9999.

DATETIME Datum und Zeit im Format »YYYY-MM-DD hh:mm:ss«.

TIMESTAMP[(M)] Zeitstempel, Wertebereich von 1.1.1970 bis zum 31.12.2036. Für die Angabe des Anzeigebereiches M gilt:14: YYYYMMDDhhmmss

Datentyp Beschreibung

463

12: YYMMDDhhmmss8: YYYYMMDD6: YYMMDD

Tabelle 11.4: Datentypen in MySQL (Z steht für ZEROFILL, M für die Stellenzahl) (Forts.)

Page 464: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

Funktionen

Funktionen sind schlecht standardisiert. Jede Datenbank kocht ihr eigenes Süpp-

TIME Zeit im Wertebereich von -838:59:59 bis +838:59:59 mit dem Ausgabeformat »hh:mm:ss«.

YEAR Jahr, Wertebereich 1901 bis 2155, Ausgabe YYYY.

CHAR(M) [BINARY] Zeichenkette fester Länge M. Wertebereich 1 bis 255. Leerzeichen am Ende werden automatisch für die Ausgabe entfernt. Sortieren und Selektieren berücksichtigt Groß- und Kleinschreibung nicht, wenn Sie nicht BINARY verwenden.

VARCHAR(M) [BINARY] Zeichenkette variabler Länge, maximal M. Werte-bereich 1 bis 255. Leerzeichen am Ende werden automatisch für die Ausgabe entfernt. Sortieren und Selektieren berücksichtigt Groß- und Kleinschrei-bung nicht, wenn Sie nicht BINARY verwenden.

TINYBLOB, TINYTEXT BLOB oder TEXT mit maximal 255 Byte

BLOB, TEXT BLOB oder TEXT mit maximal 65.535 Byte

MEDIUMBLOB, MEDIUMTEXT BLOB oder TEXT mit maximal 16.777.215 Byte

LONGBLOB, LONGTEXT BLOB oder TEXT mit maximal 4.294.967.295 Byte

ENUM('wert1', 'wert2', ...,) Aufzählung. Ein Feld dieses Typs kann nur eine Zeichenkette enthalten, die einem Objekt der Auf-zählung entspricht.

SET('wert1', 'wert2' , ...,) Wie ENUM, kann aber mehrere Werte aus der Liste enthalten.

Datentyp Beschreibung

Tabelle 11.4: Datentypen in MySQL (Z steht für ZEROFILL, M für die Stellenzahl) (Forts.)

464

chen. MySQL ist da keine Ausnahme. Deshalb finden Sie an dieser Stelle alleFunktionen der aktuellen Version mit entsprechenden Einsatzhinweisen.

Page 465: 382726314 X Php5 In 14 Tagen (Ddt)

Der MySQL-Dialekt

Funktionen für mathematische Berechnungen

Funktion Beschreibung

ABS(x) Absoluter Betrag der Zahl x.

ACOS(x) Der Arkuskosinus der Zahl x.

ASIN(num) Der Arcussinus der Zahl x.

ATAN(num) Der Arkustangens der Zahl x.

ATN2(num1, num2) Der Arkustangens (Winkel) zwischen num1 und num2.

BIT_COUNT(num) Die Anzahl der Bits, die in einer Zahl 1 sind.

CEILING(x) Die kleinste Ganzzahl größer oder gleich dem Ausdruck

COS(x) Der Kosinus der Zahl x.

COT(x) Der Kotangens der Zahl x.

CRC32(expr) Die zyklische Prüfsumme eines Ausdrucks als 32-Bit-Wert.

DEGREES(x) Eine Umrechnung von Radiant in Grad.

num1 DIV num2 Eine Ganzzahldivision, funktioniert auch mit sehr großen Zahlen (BIGINT).

EXP(x) Die Potenz zur Basis e.

FLOOR(x) Die größte Ganzzahl kleiner oder gleich dem angegebe-nen numerischen Ausdruck.

GREATEST(x1,x2,..) Gibt den größten Wert der Liste zurück.

LEAST(x1,x2,...) Gibt den kleinsten Wert der Liste zurück.

LN(x) Der natürliche Logarithmus der Zahl x.

LOG(x), LOG(b, x) Der natürliche Logarithmus (ohne b) oder Logarithmus zur Basis b.

465

LOG2(x) Der Logarithmus zur Basis 2 der Zahl x.

LOG10(x) Der dekadische Logarithmus der Zahl x.

Tabelle 11.5: Mathematische Funktionen

Page 466: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

Funktionen zur Steuerung des Kontrollflusses

MOD(n, m)n % mn MOD m

Modulus, das ist der Rest einer Ganzzahldivision. Alle drei Schreibweisen sind völlig identisch im Verhalten.

PI() Die Konstante π

POWER(x,y) Potenzrechnung (x hoch y).

RADIANS(x) Umrechnung von Grad in Radiant der Zahl x.

RAND(x) Ermittelt eine Zufallszahl, x ist dabei optional und bestimmt, wenn angegeben, den Startwert.

ROUND(x, d) Rundet Werte mathematisch, die Angabe der Stellen d ist optional, ohne Angabe wird auf ganze Zahlen gerundet

SIGN(x) Das Vorzeichen der Zahl x, gibt -1, 0 oder 1 zurück.

SIN(x) Der Sinus der Zahl x.

SQRT(x) Die Quadratwurzel der Zahl x.

TAN(x) Der Tangens der Zahl x.

TRUNCATE(x, d) Gibt die Zahl x, gekürzt auf d Dezimalstellen, zurück.

Funktion Beschreibung

ISNULL(expr) Wertet den Ausdruck expr aus und gibt 1 zurück, wenn der Ausdruck NULL ist, sonst 0.

IFNULL(expr1, expr2) Wertet den Ausdruck expr1 aus und gibt expr2 zurück, wenn der Ausdruck NULL ist, sonst expr1

Funktion Beschreibung

Tabelle 11.5: Mathematische Funktionen (Forts.)

466

IF(expr1,expr2,expr3) Wenn der Ausdruck expr1 Wahr ist, wird expr2 zurückge-geben, sonst expr3

Tabelle 11.6: Diese Funktionen können die Auswahl Bedingungsabhängig ändern

Page 467: 382726314 X Php5 In 14 Tagen (Ddt)

Der MySQL-Dialekt

Funktionen für aggregierende Berechnungen

CASE val WHEN cval THEN result [WHEN cval THEN result] [ELSE result]END

Eine Mehrfachverzweigung, die etwa dem switch in PHP entspricht. Der Wert val wird mit cval verglichen und bei Gleichheit wird result zurückgegeben. Ansonsten wird mit dem nächsten Zweig fortgesetzt.

NULLIF(expr1, expr2) Gibt NULL zurück, wenn expr1 gleich expr2 ist.

Funktion Beschreibung

AVG(Ausdruck) Der Durchschnitt der Felder.

COUNT(Ausdruck) Die Anzahl der Felder.

COUNT(DISTINCT Ausdruck) Die Anzahl eindeutiger Felder.

COUNT (*) Repräsentiert die Anzahl aller Datensätze einer Tabelle.

GROUP_CONCAT(Ausdruck) Verbundene Zeichenkette aller Teilfelder.

SUM(Ausdruck) Die Summe der Felder (Addition).

MAX(Ausdruck) Das Feld mit dem größten Wert bestimmt das Ergebnis.

MIN(Ausdruck) Das Feld mit dem kleinsten Wert bestimmt das Ergebnis.

STD(Ausdruck)STDDEV(Ausdruck)

Statistische Standardabweichung aller Werte der Liste

BIT_OR(Ausdruck) Führt ein bitweises Oder aus.

BIT_AND(Ausdruck) Führt ein bitweises Und aus.

Funktion Beschreibung

Tabelle 11.6: Diese Funktionen können die Auswahl Bedingungsabhängig ändern (Forts.)

467

BIT_XOR(Ausdruck) Führt ein bitweises Exklusives Oder aus.

Tabelle 11.7: Aggregat-Funktionen

Page 468: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

Funktionen für Systeminformationen

Funktion Beschreibung

BENCHMARK(count, expr) Die Funktion führt einen Ausdruck expr so oft aus, wie count angibt. Bei der Ausführung auf Kommandozeile wird die gesamte Ausführungszeit ausgegeben. Man kann damit die Effizienz seiner Ausdrücke testen.

CHARSET(str) Ermittelt, in welchem Zeichensatz eine Zeichenfolge definiert ist.

COERCIBILITY(str) Ermittelt die Zugehörigkeit einer Zeichenfolge zu einem Vergleichszeichensatz. Gibt einen Wert zwischen 0 und 3 zurück, wobei 0 explizite Vergleichbarkeit (höchster Rang), 1 keine Vergleichbarkeit, 2 implizite Vergleichbarkeit und 3 übergehende Vergleichbarkeit bedeutet.

COLLATION(str) Ermittelt den Vergleichszeichensatz, der die Sortierkriterien bestimmt.

CONNECTION_ID() Nummer des Threads, der die aktuelle Verbindung verar-beitet.

CURRENT_USER() Aktueller MySQL-Nutzername mit Rechnernamen.

DATABASE() Gibt den Namen der aktuellen Datenbank aus.

USER() SYSTEM_USER()SESSION_USER()

Aktueller MySQL-Nutzername

FORMAT(n, d) Formatiert eine Zahl n mit Kommata als Tausendergruppen-symbol und Punkt als Dezimaltrennzeichen mit d Dezimal-stellen.

FOUND_ROWS() Anzahl der Reihen, so wie die Abfrage ohne LIMIT erfolgt wäre, auch wenn LIMIT benutzt wurde.

LAST_INSERT_ID() Gibt den zuletzt erzeugten Wert einer AUTO_INCREMENT-Spalte zurück.

468

VERSION() Gibt die Versionsnummer des MySQL-Server an.

Tabelle 11.8: Die System- und Informationsfunktionen

Page 469: 382726314 X Php5 In 14 Tagen (Ddt)

Der MySQL-Dialekt

Verschlüsselungsfunktionen

GET_LOCK(str, to) Erzeugt eine Verriegelung (Lock) mit dem Namen str und dem Zeitüberschreitungswert to.

RELEASE_LOCK(str) Gibt die Verriegelung str wieder frei.

IS_FREE_LOCK(str) Prüft, ob die Verriegelung str frei ist.

INET_ATON(expr) Die Numerische Entsprechung einer als Zeichenkette ange-gebenen IP.

INET_NTOA(expr) Die IP-Nummer in Punktschreibweise aus einer Zahl.

UUID() Universal Unique Identifier für RPC-Verbindungen.

Funktion Beschreibung

PASSWORD(str) Erzeugt ein Kennwort zur Zeichenkette str.

ENCRYPT(str, seed) Erzeugt ein Kennwort zur Zeichenkette str und mit dem Startwert seed. Nutzt das Unix-Kommando crypt. Unter Windows wird NULL zurückgegeben.

ENCODE(str, pass) Einfaches Verschlüsselungsverfahren auf Basis des Kenn-worts pass. Zum Speichern sollten BLOB-Spalten verwen-det werden.

DECODE(str, pass)

SHA(str)SHA1(str)

Erstellt einen Hashwert nach SHA bzw. SHA1.

MD5(str) Erstellt einen Hashwert nach MD5.

DES_ENCRYPT(str, key) Triple-DES-Ver- und Entschlüsselung. Kann nur genutzt

Funktion Beschreibung

Tabelle 11.8: Die System- und Informationsfunktionen (Forts.)

469

werden, wenn MySQL mit SSL-Unterstützung kompiliert wurde.

DES_DECRYPT(str, key)

Tabelle 11.9: Verschlüsselungsfunktionen

Page 470: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

Zeichenkettenfunktionen

AES_ENCRYPT(str, key) AES-(Advanced Encryption Standard)-Ver- und Entschlüs-selung. Kann nur genutzt werden, wenn MySQL mit SSL-Unterstützung kompiliert wurde.

AES_DECRYPT(str, key)

Funktion Beschreibung

ASCII(str) Gibt den ASCII-Code des Zeichens str zurück. Hat str mehr als ein Zeichen, wird nur das erste Zeichen über-prüft.

CHAR(n,...) Wandelt die Zahlen n in die entsprechenden ASCII-Zei-chen um. Mehrere Argumente werden zu einer Zeichen-kette kombiniert.

CHAR_LENGTH(str)CHARACTER_LENGTH(str)

Die echte Zeichenlänge einer Zeichenkette. Manche Zei-chen bestehen aus mehreren Bytes, sodass LENGTH die fal-sche Länge liefert, während CHAR_LENGTH die erwartete Zahl liefert.

COMPRESS(str) Komprimiert eine Zeichenfolgen nach dem ZIP-Verfah-ren.

CONCAT(str,...) Verknüpft alle Argumente zu einer Zeichenkette. Wenn eines der Argumente NULL ist, wird NULL zurückgegeben.

ELT(n, str1, str2,...) Gibt die durch n bezeichnete Zeichenkette zurück: str1, wenn n=1 usw.

EXPORT_SET(bit, on, off, sep, number)

Exportiert einen Bitwert in eine Zeichenfolge, in der jedes Zeichen ein Bit repräsentiert. Das Zeichen on bestimmt

Funktion Beschreibung

Tabelle 11.9: Verschlüsselungsfunktionen (Forts.)

470

Bits mit dem Wert 1, off steht für 0, sep bestimmt ein Trennzeichen und number die Anzahl der Bits, die aus dem Wert bit ausgewertet werden sollen.

Tabelle 11.10: Zeichenkettenfunktionen

Page 471: 382726314 X Php5 In 14 Tagen (Ddt)

Der MySQL-Dialekt

FIELD(str,str1,str2..) Gibt die Position von str in str1, str2 usw. zurück: Wenn str2=str, wird 2 zurückgegeben.

FIND_IN_SET(str, list) Gibt die Position von str in der Liste list zurück. Die Liste besteht aus kommaseparierten Werten.

INSERT(str,st,len,new) Fügt len Zeichen der Zeichenkette new an der Stelle st der Zeichenkette str ein.

INSTR(str, sub) Entspricht LOCATE, nur die Argumente sind vertauscht.

LCASE, LOWER(str) Wandelt in Kleinbuchstaben um.

LEFT(str, len) Gibt len Zeichen vom linken Ende der Zeichenkette str zurück.

LENGTH(str) Länge der Zeichenketten str.

LOAD_FILE(name) Lädt eine Datei und gibt den Inhalt als Zeichenkette zurück.

LOCATE(sub, str)POSITION(sub IN str)

Bestimmt die Position der Zeichenkette sub in der Zei-chenkette str.

LPAD(str,len,pad) Fügt pad links an str an, gibt jedoch nur len Zeichen zurück.

LTRIM(str) Entfernt Leerzeichen vom linken Ende.

MAKE_SET(bits,list) Wählt die Elemente der Liste list anhand der gesetzten Bits in bits aus.

MID(str, pos, len) Gibt len Zeichen von Position pos an der Zeichenkette str zurück.

QUOTE() Fügt Anführungszeichen hinzu und markiert solche im Text mit einem Backslash. NULL-Werte werden als Zeichenfolge "NULL" zurückgegeben.

Funktion Beschreibung

471

REPEAT(str, count) Wiederholt die Zeichenkette str count mal.

REPLACE(str,from,to) Ersetzt alle Vorkommen von from in der Zeichenkette str durch to.

Tabelle 11.10: Zeichenkettenfunktionen (Forts.)

Page 472: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

REVERSE(str) Dreht eine Zeichenkette um.

RIGHT(str, len) Gibt len Zeichen vom rechten Ende der Zeichenkette str zurück.

RPAD(str,len,pad) Fügt pad rechts an str an, gibt jedoch nur len Zeichen zurück.

RTRIM(str) Entfernt Leerzeichen vom rechten Ende.

SOUNDEX(str) Gibt die Lautfolge für str zurück.

SPACE(n) Gibt n Leerzeichen zurück.

SUBSTRING(str FROM lenSUBSTRING(str,pos,len)SUBSTRING(str FROM pos FOR len)

Andere Schreibweisen für RIGHT und MID.

SUBSTRING(str, pos) Gibt Teile von str ab Position pos zurück.

SUBSTRING_INDEX(str, delimiter, count)

Gibt den linken Teil einer Zeichenkette zurück, nachdem count mal das Zeichen delimiter aufgetreten ist.

TRIM BOTH|LEADING|TRAILING rem FROM str

BOTH entspricht TRIM, LEADING entspricht LTRIM, TRAILING entspricht RTRIM, FROM ist optional, rem ist optional und steht für das zu entfernende Zeichen, str wird bearbeitet.

TRIM(str) Entfernt Leerzeichen von beiden Enden der Zeichenkette str.

UCASE, UPPER(str) Wandelt Zeichen in Großbuchstaben um.

UNCOMPRESS(str) Hebt ein Komprimierung wieder auf (siehe auch COMPRESS).

Funktion Beschreibung

Tabelle 11.10: Zeichenkettenfunktionen (Forts.)

472

Page 473: 382726314 X Php5 In 14 Tagen (Ddt)

Der MySQL-Dialekt

Zahlenformatierungen

Datums- und Zeitfunktionen

Funktion Beschreibung

CONV(n,from,to) Konvertiert Zahlen zwischen verschiedenen Zahlenbasen. Zurückgegeben wird immer eine Zeichenkette mit der ermittel-ten Zahl. n ist die Zahl, from die ursprüngliche Zahlenbasis, to die Zielbasis.

BIN(n) Gibt eine Zahl n als Zeichenkette im Binärformat zurück, BIN(7) ergibt beispielsweise »111«.

BIT_LENGTH(n) Gibt die Länge einer Zeichenkette in Bits zurück.

OCT(n) Gibt eine Zahl n als Zeichenkette im Oktalformat zurück.

HEX(n) Gibt eine Zahl n als Zeichenkette im Hexadezimalformat zurück.

UNHEX() Umkehrfunktion zu HEX

Tabelle 11.11: Zeichenkettenfunktionen für Zahlen

Funktion Beschreibung

DAYOFWEEK(date) Der Tag der Woche, 1 ist Sonntag (1 – 7).

WEEKDAY(date) Der Tag der Woche, 0 ist Montag (0 – 6).

DAYOFMONTH(date) Der Tag des Monats (1 – 31).

DAYOFYEAR(date) Der Tag des Jahres (1 – 366).

MONTH(date) Der Monat (1 – 12).

DAYNAME(date) Der Wochentag (englisch, ausgeschrieben).

473

MONTHNAME(date) Der Monat (englisch, ausgeschrieben).

QUARTER(date) Das Quartal (1 – 4).

Tabelle 11.12: Datums- und Zeitfunktionen

Page 474: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

WEEK(date)WEEK(date, first)

Die Woche im Jahr (0 – 52). Das optionale Argument first bestimmt, welcher Wochentag als Beginn gezählt wird. 0 entspricht dem Sonntag.

YEAR(date) Das Jahr (1000 – 9999).

HOUR(time) Die Stunde (0 – 23).

MINUTE(time) Die Minute (0 – 59).

SECOND(time) Die Sekunde (0 – 59).

PERIOD_ADD(p,n) Addiert n Monate zur Periode p. Die Periode wird im For-mat YYYYMM oder YYMM erwartet. Zurückgegeben wird immer die Langform YYYYMM.

PERIOD_DIFF(p1,p2) Gibt die Differenz in Monaten zwischen p1 und p2 zurück

TO_DAYS(date) Die Anzahl der Tage seit dem Jahr 0.

FROM_DAYS(dn) Ermittelt ein Datum aus der Tageszahl dn.

CURDATE()CURRENT_DATE

Das aktuelle Datum (Systemzeit des Servers).

CURTIME()CURRENT_TIME

Die aktuelle Zeit (Systemzeit des Servers)

NOW()SYSDATE()CURRENT_TIMESTAMP

Datum und Uhrzeit (Systemzeit des Servers)

UNIX_TIMESTAMP Unix Timestamp (Sekunden in GMT seit den 1.1.1970, 0 Uhr). Die Funktion wird auch von der Windows-Version unterstützt.

FROM_UNIXTIME(stp) Gibt ein Datum entsprechend dem Unix Timestamp stp zurück.

Funktion Beschreibung

474

FROM_UNIXTIME(stp, format)

Gibt ein Datum entsprechend dem Unix Timestamp stp zurück. Das Datum ist entsprechend format formatiert.

Tabelle 11.12: Datums- und Zeitfunktionen (Forts.)

Page 475: 382726314 X Php5 In 14 Tagen (Ddt)

Der MySQL-Dialekt

Datumsformatierungen

SEC_TO_TIME(sec) Rechnet die Angabe in Sekunden in das Format HH:MM:SS um.

TIME_TO_SEC(time) Rechnet eine Zeitangabe in Sekunden um.

Typ-Konstante Bedeutung Formatangabe

SECOND Sekunde ss

MINUTE Minute mm

HOUR Stunde hh

DAY Tag DD

MONTH Monat MM

YEAR Jahr YY

MINUTE_SECONDS Minute und Sekunde mm:ss

HOUR_MINUTE Stunde und Minute hh:mm

DAY_HOUR Tag und Stunde DD hh

YEAR_MONTH Jahr und Monat YY-MM

HOUR_SECOND Stunde, Minute, Sekunde hh:mm:ss

DAY_MINUTE Tag, Stunde, Minute DD hh:mm

DAY_SECONDS Tag, Stunde, Minute, Sekunde DD hh:mm:ss

Tabelle 11.13: Zeittypen für Datumsberechnungen

Funktion Beschreibung

Tabelle 11.12: Datums- und Zeitfunktionen (Forts.)

475

Page 476: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

Funktion Berechnung

DATE_FORMAT(date, format) Formatiert den Wert date mit dem Format format, des-sen Elemente wurden in der vorherigen Tabelle bereits beschrieben.

TIME_FORMAT(time, format) Formatiert den Wert time mit dem Format format, des-sen Elemente wurden in der vorherigen Tabelle bereits beschrieben.

Tabelle 11.14: Datumsformatierungen

Code Bedeutung (Wertebereich) Code Bedeutung (Wertebereich)

%M Monatsname (January – December) %k Stunde (0 – 23) ohne führende Null

%W Wochenname (Monday – Sunday) %h Stunde (01 – 12) mit führender Null

%D Monat mit engl. Suffix (1st, 2nd, 3rd usw.)

%I Stunde (1 – 12) ohne führende Null

%Y Jahr mit 4 Stellen %l Minuten (0 – 59)

%y Jahr mit 2 Stellen %i Minuten (00 – 59) mit führender Null

%a Abgekürzter Wochentag (Mon – Sun)

%n Zeit, 12-Stunden-Format: hh:mm:ss AM|PM

%d Tag des Monats (00 – 31) %T Zeit, 24-Stunden-Format: hh:mm:ss

%e Tag des Monats (0 – 31) %S Sekunde (00 – 59) mit führender Null

%m Monat (00 – 12) mit führender Null %s Sekunde (0 – 59)

%c Monat (0 – 12) ohne führende Null %p AM oder PM

%b Abgekürzter Monatsname %w Wochentag

476

(Jan–Dec) (0=Sonntag, 6=Samstag)

%j Tag des Jahres (000 – 366) %U Woche, Sonntag ist der erste Tag der Woche (00 – 52)

Tabelle 11.15: Platzhalter für Datumsformatierungen

Page 477: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte mit MySQL und MySQLi

11.4 Erste Schritte mit MySQL und MySQLi

MySQLi (MySQL improved) ist eine neue Erweiterung von PHP zur besserenUnterstützung von MySQL. Im Bundle mit dem neuen, objektorientierten Ansatzund der neuen MySQL-Version 4 ergibt sich ein außerordentlich leistungsfähigesSystem.

MySQLi vorbereiten

Seit PHP5 gehört die MySQL-Unterstützung aus rechtlichen Gründen nicht mehrzum integrierten Paket. In PHP 4 waren die MySQL-Module noch fest in denKern kompiliert. Mit der Version 5 ist nun alles wieder wie bereits bei PHP3 – manmuss die MySQL-Dateien zusätzlich einbinden. Auf dem Entwicklungssystemunter Windows erfolgt dies durch das Auskommentieren der entsprechenden Zeilein der Datei php.ini:

extension=php_mysql.dll

Dann haben Sie noch die Wahl, statt der alten MySQL-Module die neuen, objekt-orientierten MySQLi-Dateien zu nutzen:

extension=php_mysqli.dll

In diesem Buch werden die neuen Module vorgestellt. Über weite Strecken istSyntax und Nutzung praktisch identisch, sodass eine Umstellung nicht schwer ist.

%H Stunde (00 – 23) mit führender Null

%u Woche, Montag ist der erste Tag der Woche (00 – 52)

%% Prozentzeichen

Code Bedeutung (Wertebereich) Code Bedeutung (Wertebereich)

Tabelle 11.15: Platzhalter für Datumsformatierungen (Forts.)

477

Page 478: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

Verbindung testen

Nach der Installation von MySQL steht ein kurzer Test an, ob alles geklappt hat.Unter XP muss dazu der entsprechende Dienst gestartet sein, unter Linux derMySQL-Daemon.

Dann kommt folgendes Skript zum Zuge, um alles zu testen:

Listing 11.1: mysqliconnect.php – Ein erster Versuch mit MySQL und PHP

$mysqli = new mysqli("localhost", "root", "", "test");$query = $mysqli->query("SELECT version() AS version");$result = $query->fetch_assoc();echo "Wir arbeiten mit MySQL Version {$result['version']}";$mysqli->Close();

Das Skript geht davon aus, dass das Standardkennwort (leer) und der Benutzer-name (»root«) nicht geändert wurden und dass die Datenbank auf derselbenMaschine wie der Webserver läuft (»localhost«). Haben Sie eine andere Installa-tion, müssen Sie die Daten in der ersten Zeile entsprechend anpassen.

Die Klasse mysqli1 stellt nun den Zugriff auf die neuen Funktionen bereit. Im Bei-spiel führt die Instanziierung des Objekts in $mysqli auch gleich zum Öffnen derVerbindung. Die SQL-Abfrage ermittelt dann die Versionsnummer des Daten-bankservers:

SELECT version() AS version

Die Methode zur Abfrage der Datenbank heißt query:

$mysqli->query()

Wenn Ergebnisse entstehen, kann man diese mit fetch_assoc in ein assoziativesArray überführen:

$result = $query->fetch_assoc();

Der Zugriff auf das Array entspricht wieder einfachem PHP, als Schlüsselnametaucht der Alias der SQL-Abfrage auf.

478

Hat alles geklappt, wird die Verbindung wieder geschlossen. Die Ausgabe solltenun in etwa wie nachfolgend gezeigt aussehen:

1 Es gibt auch nach wie vor einen prozeduralen Zugriff, der hier nicht weiter betrachtet wird.

Page 479: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte mit MySQL und MySQLi

Nun kann man voll loslegen und praktisch mit der Datenbank arbeiten.

Die folgenden Skripte wurden mit MySQL 4.1 getestet. Die einwand-freie Funktionsweise von MySQL 5 kann derzeit nicht garantiert wer-den. Außerdem ist mindestens die Version PHP5.1 für die MySQLi-Erweiterungen zu empfehlen. Die erste Final, PHP5.0.0, versagte beieinigen Abfragen.

Genereller Datenbankzugriff

Für die folgenden Skript wird immer wieder dieselbe Methode zum Datenbankzu-griff verwendet und als Include-Datei eingebunden. Dies erleichtert Änderungen:

Listing 11.2: mysqli.inc.php – Inhalt der Include-Datei, noch ohne Fehlermanagement

<?php$mysqli = new mysqli("localhost", "root", "", "test");?>

Die Variable $mysqli wird in allen Skripten dieses Kapitels verwendet. Das Einbin-den erfolgt über folgende Zeile:

include("mysqli.inc.php");

Diese Anweisung wird nicht in jedem Listing immer wieder abgebildet.

Mit der Datenbank arbeiten

Um mit der Datenbank arbeiten zu können, werden zuerst Tabellen benötigt.Diese können Sie entweder mit einem Werkzeug oder per Skript anlegen. AlsWerkzeug kommen unter Windows das MySQLCC (MySQL Control Center)

Abbildung 11.1: Alles neu: PHP5 mit MySQLi und MySQL 5.0-alpha

479

oder phpMyAdmin in Frage. Dieser Abschnitt zeigt alle nötigen Schritte in Skript-form.

Page 480: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

Tabellen vorbereiten

Zuerst werden die passenden Tabellen benötigt. MySQL verfügt über eine sehrgute Funktion beim Anlegen von Tabellen. Es kann prüfen, ob die Tabelle bereitsexistiert. Damit muss man sich erstmal nicht darum kümmern, ob das Skriptbereits aufgerufen wurde.

Die beiden Tabellen, die hier benötigt werden, enthalten die Bundesländer undWetterdaten. Sie haben folgende Definitionen:

Listing 11.3: Definition der Wetter-Tabelle

CREATE TABLE IF NOT EXISTS wetter ( ID bigint(20) NOT NULL auto_increment, Stadt varchar(100) NOT NULL default '', Hoch int(2) NOT NULL default '0', Tief int(2) NOT NULL default '0', BundeslandID bigint(20) default '0', PRIMARY KEY (ID), KEY StadtIndex (Stadt));

Listing 11.4: Definition der Tabelle der Bundesländer

CREATE TABLE IF NOT EXISTS bundesland ( ID int(2) NOT NULL auto_increment, Name varchar(30) NOT NULL default '', PRIMARY KEY (ID));

Das folgende PHP-Skript nutzt diese Anweisungen, um die Tabellen anzulegen.Die Angabe von IF NOT EXISTS verhindert, dass die Tabellen erneut erzeugt wer-den, wenn sie bereits existieren.

Listing 11.5: mysqlicreatetables.php – Die benötigten Datentabellen erzeugen

$tables['Wetter'] = <<<TABLE1

480

CREATE TABLE IF NOT EXISTS wetter ( ID bigint(20) NOT NULL auto_increment, Stadt varchar(100) NOT NULL default '', Hoch int(2) NOT NULL default '0', Tief int(2) NOT NULL default '0', BundeslandID bigint(20) default '0',

Page 481: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte mit MySQL und MySQLi

PRIMARY KEY (ID), KEY StadtIndex (Stadt));TABLE1;$tables['Bundesland'] = <<<TABLE2CREATE TABLE IF NOT EXISTS bundesland ( ID int(2) NOT NULL auto_increment, Name varchar(30) NOT NULL default '', PRIMARY KEY (ID));TABLE2;if (is_object($mysqli)){ foreach ($tables as $table => $definition) { $result = $mysqli->query($definition); if ($result === FALSE) { echo "Konnte Tabelle '$table' nicht erzeugen<br>"; echo "<b>Fehler:</b> {$mysqli->error}<br>"; } else { echo "Tabelle '$table' wurde erzeugt/überprüft<br>"; } }}

Das Skript nutzt ein Array zum Zusammenstellen der Kommandos. Die Elementedes Arrays enthalten die SQL-Anweisungen und werden dann einzeln an dieDatenbank gesendet. Die Methode query gibt bei Abfragen mit Ergebnissen einErgebnisobjekt zurück. Bei Anweisung, die nichts zurückgeben, wie beispielsweiseCREATE TABLE wird entweder TRUE (Erfolg) oder FALSE (Fehler) zurückgegeben.

Abbildung 11.2: Ausgabe bei erfolgreicher Abarbeitung

481

Der Umgang mit Fehlern ist generell sehr wichtig. Deshalb ist die Ausgabe vonFehlermeldungen in fast jedem Skript zu finden. Der Zugriff auf die letzte Fehler-meldung erfolgt mit $mysqli über die Eigenschaft error:

Page 482: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

echo "<b>Fehler:</b> {$mysqli->error}<br>";

Provozieren Sie einen Fehler, indem Sie die Syntax der SQL-Anweisung einwenig »unqualifiziert« verändern. Sie erhalten dann beispielsweise folgende Aus-gabe:

Die Tabellen füllen

Es gibt mehrere Wege, Tabellen zu füllen:

� Formulare werden verwendet – dann gibt der Benutzer die Daten von Handein.

� Abfrage einer anderen Datenbank oder Tabelle.

� Durch das Auslesen von Textdateien.

� Durch den Import von XML.

Für die Generierung der Bundesländer bietet es sich an, eine Textdatei zu verwen-den, die die Namen enthält. Das ist einfacher als alle INSERT-Anweisungen aufzu-schreiben. Die nötigen Techniken wurden alle bereits behandelt. Zuerst dieTextdatei:

Listing 11.6: bl.txt im Verzeichnis /data – Textdatei mit Bundesländern

'Baden-Württemberg' 'Bayern' 'Berlin' 'Brandenburg' 'Bremen' 'Hamburg' 'Hessen'

Abbildung 11.3: Ausgabe mit Fehlermeldung

482

'Mecklenburg-Vorpommern''Niedersachsen''Nordrhein-Westfalen' 'Rheinland-Pfalz' 'Saarland' 'Sachsen'

Page 483: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte mit MySQL und MySQLi

'Sachsen-Anhalt''Schleswig-Holstein ''Thüringen'

Auch für ein paar Wetterdaten wurde eine Textdatei vorbereitet. Da hier mehrereSpalten existieren, wurde ein Trennzeichen definiert, das Komma. Die erste Zeileenthält die Feldnamen:

Listing 11.7: wetter.txt im Verzeichnis /data – Textdatei mit Wetterdaten

Stadt,Hoch,Tief,BundeslandID'Berlin',22,12,3'Hamburg',20,10,6'Stuttgart',24,18,1'München',23,17,2'Regensburg',23,19,2'Dresden',19,12,13'Leipzig',19,12,13'Wittenberge',19,12,4'Angermünde',18,12,4'Frankfurt/Oder',19,13,4'Cottbus',22,17,4'Hof',19,14,2'Nürnberg',19,11,2'Würzburg',19,16,2'Augsburg',22,18,2

Die einfachen Anführungszeichen in den Datendateien vereinfachendie Verarbeitung erheblich. Dies ist hier vor allem gemacht worden, umdie ersten Skripte überschaubar zu halten. In der Praxis gibt es freilichLösungen, die beliebige Daten korrekt einfügen.

Das folgende Skript zeigt eine mögliche Lösung:

Listing 11.8: mysqliinsert.php: Programmgesteuert Datensätze einfügen

$path = 'data';

483

$imports = array('Wetter' => 'wetter.txt', 'Bundesland' => 'bl.txt');if (is_object($mysqli)){ foreach ($imports as $table => $file)

Page 484: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

{ $content = file("$path/$file"); $fields = array_shift($content); $mysqli->query("TRUNCATE $table"); foreach ($content as $line) { $sql = "INSERT INTO $table ($fields) VALUES ($line)"; $result = $mysqli->query($sql); if ($result === FALSE) { echo "Konnte Anweisung nicht ausführen<br>"; echo "<b>Fehler:</b> {$mysqli->error}<br>"; } else { echo "Anweisung ausgeführt: <b>$sql</b><br>"; } } }}

Dieses Skript beginnt mit der Definition eines Arrays, das die Tabellennamen undDateinamen enthält und miteinander verknüpft:

$imports = array('Wetter' => 'wetter.txt', 'Bundesland' => 'bl.txt');

Der Vorteil der vorgestellten Lösung liegt in ihrer leichten Erweiterbarkeit. Umweitere Tabelle mit Daten zu beschicken, muss man lediglich dieses Array erwei-tern und natürlich die Daten bereitstellen.

Dann durchläuft die erste foreach-Schleife alle Elemente dieses Arrays:

foreach ($imports as $table => $file)

So erhält man Tabellen- und Dateinamen. Dann werden die Daten in ein weiteresArray überführt, wozu die Funktion file hervorragend geeignet ist:

$content = file("$path/$file");

484

Die erste Zeile enthält die Feldnamen. Das erste Element eines Arrays lässt sichmit array_shift extrahieren, sodass in $fields die Feldliste steht:

$fields = array_shift($content);

Page 485: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte mit MySQL und MySQLi

Dann wird die jeweils zu bearbeitende Tabelle gelöscht, um zu verhindern, dassbei mehrfachem Aufruf des Skripts die Daten doppelt erscheinen:

$mysqli->query("TRUNCATE $table");

Hier ist etwas Vorsicht angebracht! Wenn Sie mit anderen Skripten derWetter-Tabelle weitere Daten hinzugefügt haben, gehen diese durchden Aufruf von TRUNCATE unwiderruflich verloren.

Für den Rest der Datei (array_shift extrahiert nicht nur, sondern entfernt auchgleich die erste Zeile) werden nun die Zeilen gelesen und zu INSERT-Anweisungenverarbeitet:

foreach ($content as $line)$sql = "INSERT INTO $table ($fields) VALUES ($line)";

Der Aufbau einer SQL-Anweisung in einer eigenen Variable ist sinnvoll, um dasdynamisch konstruierte Gebilde leicht überwachen zu können. Die fertige Anwei-sung wird dann an den SQL-Server gesendet:

$result = $mysqli->query($sql);

Zuletzt folgt noch die bereits bekannte Fehlerausgabe.

Mit den so erstellten Tabellen kann nun gearbeitet werden. Die folgendenAbschnitte behandeln die bereits im Einführungsteil zu SQL präsentierten Abfra-gen im praktischen Kontext eines PHP-Skripts.

Einfache Abfragen

Die Abfragetechnik in PHP folgt immer ein und demselben Schema:

1. Verbinden mit der Datenbank.

2. Senden der Abfrage und Erhalt des Ergebnisobjekts.

3. Überführen des Ergebnisobjekts in ein Array.

4. Ausgaben oder Verarbeiten des Arrays.

485

In selteneren Fällen werden nur einzelne Daten abgefragt und ohne Arrays gear-beitet. Aufgrund der starken Arrayfunktionen in PHP ist die Nutzung jedoch meistangebracht.

Page 486: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

Listing 11.9: mysqlselectfrom.php – Einfache Abfrage mit Ausgabe

if (is_object($mysqli)){ $sql = "SELECT Stadt, Hoch, Tief FROM Wetter"; $result = $mysqli->query($sql); while ($rs = $result->fetch_assoc()) { echo <<<WETTER Die Tagestemperaturen für {$rs['Stadt']}: <ul> <li>Höchsttemperatur: {$rs['Hoch']} °C</li> <li>Tiefsttemperatur: {$rs['Tief']} °C</li> </ul>WETTER; } }

Mit der folgenden Abfrage entsteht ein Ergebnisobjekt:

$result = $mysqli->query($sql);

Diese Objekte beinhalten praktisch die gesamten Daten, die die SQL-Anweisungzurückgibt. Da es sich um eine Tabelle, also eine zweidimensionale Struktur han-delt, braucht man zwei Schritte zum Auseinandernehmen. Im ersten Schritt wer-den die Zeilen abgerufen und jeweils in einem Array gespeichert:

while ($rs = $result->fetch_assoc())

Die Methode fetch_assoc holt die Daten in ein assoziatives Array. Die Schlüsselwerden aus den Spaltennamen gebildet. Sind keine Daten mehr vorhanden, gibtdie Funktion FALSE zurück und der gesamte Ausdruck wird FALSE, woraufhin whileabbricht.

Innerhalb der Schleife kann man jetzt auf die Daten mit der normalen Arraysyntaxzugreifen:

{$rs['Stadt']}

Die Ausgabe zeigt, dass alle Daten durchlaufen werden.

486

Der Fantasie beim Formatieren sind nun keine Grenzen gesetzt. Schwieriger ist esdenn auch, die passenden SQL-Anweisung zu finden, um bestimmte Details zuermitteln. In den folgenden Skripten werden andere Abfragen und andere Aus-gabemethoden vorgestellt. Dies dient vor allem der Demonstration der Möglich-keiten.

Page 487: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte mit MySQL und MySQLi

Komplexe Abfragen

Als nächstes sollen die durchschnittlichen Höchst- und Tiefsttemperaturen ermit-telt werden. Dazu eignet sich die Aggregat-Funktion AVG:

Listing 11.10: mysqlselectavgfrom.php – Durchschnittswerte ermitteln

$sql = "SELECT AVG(Hoch) AS MittelHoch, � AVG(Tief) AS MittelTief FROM Wetter";$result = $mysqli->query($sql);$data = $result->fetch_object();

Abbildung 11.4: Ausgabe einer Datenbanktabelle in formatiertem HTML

487

echo "Die mittlere Höchsttemperatur � beträgt {$data->MittelHoch} °C<br>";echo "Die mittlere Tiefsttemperatur � beträgt {$data->MittelTief} °C<br>";

Page 488: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

Eingesetzt wird hier die Methode fetch_object. Sie gibt den aktuellen Datensatzals Objekt zurück, oder FALSE, falls keine Daten mehr da sind. Da hier nur skalareWerte abgefragt werden, ist eine Schleife nicht erforderlich.

Das Objekt enthält eine Eigenschaft für jedes Feld, der Name entspricht auch demFeldnamen:

$data->MittelHoch

Im Beispiel wurden die Namen gegenüber den originalen Spaltennamen nochdurch den Alias-Operator AS geändert. Das ist sinnvoll, wenn die Spaltennamenselbst auch abgefragt werden sollen.

Das folgende Beispiel zeigt, wie gleichzeitig die höchsten und niedrigsten Werteermittelt werden.

Listing 11.11: mysqlselectmimaxfrom.php – Minimale und maximale Temperaturen

$sql = "SELECT AVG(Hoch) AS MittelHoch, � AVG(Tief) AS MittelTief, � MIN(Tief) AS Tief, � MAX(Hoch) AS Hoch � FROM Wetter";$result = $mysqli->query($sql);$data = $result->fetch_object();echo "Die mittlere Höchsttemperatur � beträgt {$data->MittelHoch} °C<br>";echo "Die mittlere Tiefsttemperatur � 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.

Abbildung 11.5: Berechnung der Durchschnittstemperaturen

488

Abbildung 11.6: Der höchste und niedrigste Wert werden ermittelt

Page 489: 382726314 X Php5 In 14 Tagen (Ddt)

Erste Schritte mit MySQL und MySQLi

Bei der Ausgabe fällt auf, dass die Kommastellen wenig praxistauglich sind. BevorSie jetzt auf die Idee kommen, dafür printf oder number_format einzusetzen, istein Blick in die Funktionssammlung von MySQL interessant. Hier wird man beiROUND fündig:

Listing 11.12: mysqlselectroundfrom.php – Rundung der Ausgabewerte vor der Ausgabe

$sql = "SELECT ROUND(AVG(Hoch),2) AS MittelHoch, � ROUND(AVG(Tief),2) AS MittelTief FROM Wetter";$result = $mysqli->query($sql);$data = $result->fetch_object();echo "Die mittlere Höchsttemperatur � beträgt {$data->MittelHoch} °C<br>";echo "Die mittlere Tiefsttemperatur � beträgt {$data->MittelTief} °C<br>";

Diese Ausgabe ist schon eher überzeugend, allerdings muss MySQL vorerst bei dersprachabhängigen Darstellung passen:

Verknüpfungen abfragen

Als nächstes soll wieder eine Ausgabe der Temperaturen der einzelnen Städteerfolgen, jedoch mit der Angabe des jeweiligen Bundeslandes.

Listing 11.13: mysqlselectjoin1.php: Zusatzinformationen aus verknüpfter Tabelle holen

$sql = "SELECT Stadt, Hoch, Tief, Name AS Bundesland � FROM Wetter W JOIN Bundesland B � ON W.BundeslandID = B.ID";$result = $mysqli->query($sql);while($rs = $result->fetch_object()){

Abbildung 11.7: Gerundete Temperaturwerte

489

echo "{$rs->Stadt} ({$rs->Bundesland}):<br>"; echo "&uarr; {$rs->Hoch}°C, &darr; {$rs->Tief}°C<br>";}

Die Abfrageform in PHP ändert sich hier nicht – die gesamte Arbeit erledigt dieSQL-Anweisung. Die Ausgabe zeigt ein für den geringen Aufwand durchaus

Page 490: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

respektables Ergebnis (die Pfeile werden durch die Entitäten &uarr; und &darr;erzeugt):

Etwas kniffliger wird es, wenn die Verknüpfung mit Aggregierungen verbundenwerden soll. So könnte man die mittleren Temperaturen in einem Bundeslandabfragen. Dazu werden die Städte aus demselben Bundesland gruppiert und ausden Werten einer Gruppe der Durchschnittswert berechnet.

Abbildung 11.8: Informationen aus zwei Tabellen: Städte und Bundesländer

490

Listing 11.14: mysqlselectjoingroup.php – Mittlere Temperaturen nach Bundesland

$sql = "SELECT ROUND(AVG(Hoch),2) AS MittelHoch, � ROUND(AVG(Tief),2) AS MittelTief, � Name AS Bundesland �

Page 491: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz MySQLi

FROM Wetter W JOIN Bundesland B � ON W.BundeslandID = B.ID � GROUP BY W.BundeslandID � ";$result = $mysqli->query($sql);while($rs = $result->fetch_object()){ echo "{$rs->Bundesland}:<br>"; echo "&uarr; {$rs->MittelHoch}°C,"; echo "&darr; {$rs->MittelTief}°C<br>";}

Der Trick besteht hier in der Anwendung von GROUP BY. Erst nach der Gruppie-rung werden die Aggregat-Funktionen zur Berechnung des Durchschnitts ange-wendet.

11.5 Referenz MySQLi

Alle Methoden werden auf einer Instanz der Klasse mysqli oder auf einem Resul-tatobjekt ausgeführt, beispielsweise:

$mi = new mysqli('localhost', 'root', '', 'test');

Abbildung 11.9: Mittelwerte der Temperaturen pro Bundesland

491

$mi->commit();

Alternativ ist immer auch der direkte prozedurale Aufruf möglich:

mysqli_commit();

Page 492: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

Eigenschaft Beschreibung

affected_rows Anzahl der Datensätze, die von der letzten Anweisung betroffen waren (nur für UPDATE/INSERT/REPLACE, nicht jedoch für SELECT).

errno Die Nummer des Fehlercodes.

error Eine Beschreibung des Fehlers (englisch).

field_count Anzahl der Spalten, die die letzte Abfrage zurückgegeben hat.

host_info Informationen über den Server und die Art der Verbindung, bei-spielsweise »localhost via TCP/IP«.

info Informationen über die letzte Abfrage.

insert_id Die letzte durch INSERT in einem AUTO_INCREMENT-Feld erzeugte ID.

protocol_version Die Version des MySQL-Protokolls (aktuell: 10).

sqlstate Status einer vorher gesendeten Abfrage.

thread_id ID des Threads in dem die Abfrage abgearbeitet wird .

thread_safe Ermittelt, ob Threadsicherheit besteht.

warning_count Anzahl der Warnungen, die die letzte Abfrage auslösten.

Tabelle 11.16: Eigenschaften des MySQLi-Objekts

Methode Beschreibung

autocommit Schaltet die automatische Bestätigung von Transaktionen ein oder aus.

change_user Ändert den Benutzer für die aktuelle Verbindung.

character_set_name Gibt den aktuellen Zeichensatz zurück.

492

close Schließt die Verbindung.

commit Bestätigt die aktuelle Transaktion.

Tabelle 11.17: Methoden des MySQLi-Objekts

Page 493: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz MySQLi

connect Öffnet eine neue Verbindung zum MySQL-Server.

get_client_info Gibt Informationen über die verwendet MySQL-Version zurück.

get_client_version Gibt Informationen über den MySQL-Client zurück.

get_host_info Gibt Informationen über den Server und die Verbindung zurück.

init Vorbereiten eines MySQLi-Objekts für spätere Verwendung.

info Gibt die automatisch erstellte ID der letzten Abfrage zurück.

kill Versucht den von MySQL belegten Thread zu beenden.

multi_query Sendet eine Abfrage an die Datenbank.

more_results Ermittelt, ob weitere Ergebnissätze von Mehrfachabfragen vor-handen sind.

next_result Nächste Ergebnissätze von Mehrfachabfragen abholen.

options Setzt verschiedene Optionen.

ping Sendet einen Ping zur Kontrolle der Verbindung an den Server.

prepare Kann eine Abfrage vorbereiten. Vorbereitete Abfragen sind schneller bei wiederholter Ausführung.

query Eine Abfrage direkt an den Server senden.

real_connect Kann eine Verbindung öffnen.

real_query Kann eine Abfrage ausführen.

rollback Kann eine Transaktion rückabwickeln.

select_db Wird eine andere Datenbank als Standard auswählen. Ent-spricht dem SQL-Befehl USE.

Methode Beschreibung

493

send_query Sendet eine Abfrage an die Datenbank.

sqlstate SQL Status und Fehlercodes einer vorhergehenden Abfrage ermitteln.

Tabelle 11.17: Methoden des MySQLi-Objekts (Forts.)

Page 494: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

ssl_set Kann eine gesicherte SSL-Verbindung aufbauen.

stat Der aktuellen Status des Systems.

stmt_init Initialisiert eine Abfrage und gibt ein Objekt zurück, mit dem diese Abfrage gesteuert werden kann. Tabelle 11.18 und Tabelle 11.19 zeigen die Eigenschaften und Methoden dieses Objekts.

thread_safe Ermittelt, ob Threadsicherheit gegeben ist oder nicht.

use_result Bereitet einen Ergebnissatz zur Verwendung vor.

Eigenschaft Beschreibung

affected_rows Anzahl der von der Anweisung betroffenen Datensätze. Damit kann man den Erfolg von UPDATE oder DELETE überwachen.

errno Der letzte Fehlercode.

error Eine Beschreibung des Fehlers (englisch).

param_count Die Anzahl der Parameter.

sqlstate Ermittelt SQL Status und Fehlercodes einer vorhergehenden Abfrage.

ssl_set Kann eine gesicherte SSL-Verbindung aufbauen.

Tabelle 11.18: Eigenschaften des Anweisungs-Objekts

Methode Beschreibung

bind_param Erstellt Parameter für die Anweisung.

bind_result Erstellt ein Ergebnisobjekt.

Methode Beschreibung

Tabelle 11.17: Methoden des MySQLi-Objekts (Forts.)

494

close Schließt die Verbindung.

data_seek Setzt den Ergebnissatzzeiger auf einen bestimmten Datensatz.

Tabelle 11.19: Methoden des Anweisungs-Objekts

Page 495: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz MySQLi

execute Führt die Anweisung aus.

fetch Holt den Ergebnissatz mit verschiedenen Optionen.

fetch_result Holt den Ergebnissatz.

get_metadata Holt globale Informationen zur Anweisung.

prepare Bereitet die Anweisung vor (vorab Kompilierung).

send_long_data Sendet große Datenpakete.

store_result Speichert die Ergebnisse zwischen.

Eigenschaft Beschreibung

current_field Ermittelt das aktuelle Feld des Ergebnissatzes.

field_count Ermittelt die Anzahl der Felder.

length Ermittelt die Länge (Breite) eines Feldes.

num_rows Ermittelt die Anzahl der Reihen im Ergebnissatz.

Tabelle 11.20: Eigenschaften des Ergebnis-Objekts

Methode Beschreibung

close Schließt die Verbindung.

data_seek Setzt den Ergebnissatzzeiger auf eine bestimmte Reihe.

fetch_field_direct Holt direkt ein bestimmtes Feld.

fetch_field Holt das nächste Feld einer Liste.

fetch_fields Holt Felder als Array.

Methode Beschreibung

Tabelle 11.19: Methoden des Anweisungs-Objekts (Forts.)

495

fetch_lengths Ermittelt die Breite des aktuellen Feldes.

fetch_object Holt die Reihe als Objekt. Felder sind nun Eigenschaften.

Tabelle 11.21: Methoden des Ergebnis-Objekts

Page 496: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbankprogrammierung

11.6 Kontrollfragen

1. Warum sollte die neue Bibliothek MySQLi anstatt der alten eingesetzt werden?

2. Was versteht man unter Normalisierung?

3. Sie müssen bei der Ausgabe von Daten aus einer MySQL-Datenbank Datums-formate anpassen und \n in <br> umwandeln. Wie gehen Sie vor?

4. Wie würden Sie eine Abfrage formulieren, bei der aus zwei Tabellen Datenzugleich entnommen werden müssen? Welche Voraussetzungen müssen dieTabellen erfüllen?

fetch_row Holt die Reihe als numerisches (einfaches) Array.

fetch_assoc Holt die Reihe als assoziatives Array. Die Schlüssel sind die Spal-tennamen.

field_seek Setzt den Zeiger auf ein spezifisches Feld.

Methode Beschreibung

Tabelle 11.21: Methoden des Ergebnis-Objekts (Forts.)

496

Page 497: 382726314 X Php5 In 14 Tagen (Ddt)

Die integrierte Dat te

enbank SQLi

12

Page 498: 382726314 X Php5 In 14 Tagen (Ddt)

Die integrierte Datenbank SQLite

Nicht immer muss es MySQL sein. Statt einer ausgewachsenen Datenbank reichtoft ein einfacheres System. Die Lücke zwischen einfachen Textdateien als Daten-speicher und einem relationalen Datenbankmanagementsystem schließt in PHP5das SQLite-Modul.

12.1 Hintergrund

SQLite ist fest in PHP integriert. Ein weiteres Modul oder Treiber sind nicht erfor-derlich. Sie können deshalb immer davon ausgehen, dass jede PHP5-Installationwenigstens SQLite bereitstellt. Wenn kleine Datenbankprojekte erstellt werden,die auf Funktionen großer Datenbankmanagementsysteme nicht angewiesen sind,ist SQLite völlig ausreichend. Allerdings muss man sich auch der Grenzen bewusstsein, um gegebenenfalls rechtzeitig wechseln zu können.

SQLite ist eine einfache Datenbank-Lösung, die als »Public Domain« entwickeltwurde. Dies ist eine sehr freie Art der Softwareverteilung, bei der völlig auf Rechteund Lizenzen verzichtet wird. Der Code wird jedermann ohne Beschränkungenzur Verfügung gestellt.

Der Entwickler ist D. Richard Hipp der Firma Hipp, Wyrick & Company, Inc. Dieneueste Version wird unter folgender Adresse bereitgestellt:

http://www.hwaci.com/sw/sqlite

SQLite soll dabei kein Ersatz für MySQL oder PostgreSQL sein. Nur einfachsteAnwendungen werden auf größere Datenbank verzichten können. Wenn Siebereits einige Erfahrung mit Datenbanken haben und Oracle oder MS SQL Ser-ver kennen, wird Ihnen MySQL möglicherweise primitiv vorgekommen sein. Dasses noch deutlich einfacher geht, zeigt SQLite. Dafür profitiert auch SQLite vonder Vereinfachung. Zugriffe sind extrem schnell und die Speicherung der Datennutzt den Speicher effektiv. Einige Benchmarks versprechen etwa doppelteGeschwindigkeit gegenüber dem bereits recht flotten MySQL.

Einige der typischen Aufgaben, denen SQLite ohne weiteres gewachsen ist, sind:

� Konfigurationsdaten einer Applikation speichern.

498

� Serialisierte Objekte zur Mitnahme von Seite zu Seite speichern.

� Speicherung von Anmeldeinformationen.

� Ein Gästebuch.

� Eine News-Site, auf der aktuelle Nachrichten zu finden sind.

Page 499: 382726314 X Php5 In 14 Tagen (Ddt)

Vor- und Nachteile

Ein komplettes Content Management System oder größere Foren dürften alleinaufgrund der komplexeren SQL-Abfragen mit MySQL besser bedient sein.

12.2 Vor- und Nachteile

Bevor Sie intensiv in SQLite einsteigen, sollten Sie einige technische Hinter-gründe und deren Auswirkungen auf Projekte kennen.

Wann SQLite vorteilhaft ist

SQLite bietet sich an, wenn einfache Projekte völlig unabhängig von der PHP5-Konfiguration laufen müssen. Die Zugriffe sollten vorrangig lesend erfolgen.Außerdem sollte PHP5 nur auf einem einzigen System laufen, nicht im Clusteroder mit Lastverteilung. Der Anspruch an die Sicherheit der Daten sollte ehergering sein oder eine häufige Datensicherung ist vorhanden.

Vorteilhaft ist SQLite deshalb, weil sie vielen Entwicklern mit wenig Datenbanker-fahrung die Chance bietet, ohne großen Aufwand eine datenbankgestützte Appli-kation zu bauen. Denn mit der Integration reduziert sich die Lernkurve auf PHPund sehr wenige SQL-Befehle selbst. Eine kleinere Einstiegbarriere erlaubt dannvielleicht den Ersatz uneffektiver Bastellösungen durch eine halbwegs saubere Pro-grammierung, was insgesamt schon als Vorteil betrachtet werden kann.

Wann SQLite nachteilig ist

SQLite speichert die gesamte Datenbank in einer einzigen Datei. Schreibzugriffevon einer Benutzersitzung aus führen dazu, dass die gesamten Datei kurzzeitiggesperrt wird. Das führt bei häufigen Schreibzugriffen sehr schnell dazu, dassPHP5 ständig auf die erneute Freigabe der Datenbank wartet und dies verschlech-tert drastisch die Antwortzeiten des Systems.

499

Die Speicherung von Sitzungsdaten in SQLite ist deshalb eher nachteilig. Solltedas Dateisystem feststellen, dass die Datenbankdatei korrupt ist, wird man alle Sit-zungsdaten aller Sitzungen verlieren. Beim bisherigen System beträfe dies nureine Sitzung. Außerdem verfügt SQLite über keine »Selbstheilungskräfte«, wie sieandere DBMS kennen. John Lim von PHPEverywhere und Sterling Hughes

Page 500: 382726314 X Php5 In 14 Tagen (Ddt)

Die integrierte Datenbank SQLite

haben einige Benchmarks erstellt, die klar beweisen, das SQLite bei schreibinten-siven Aufgaben wie der Sitzungsverwaltung deutlich langsamer ist als die einge-baute Sitzungsverwaltung oder andere Datenbanklösungen.

Allerdings muss auch gesagt werden, dass die in den Tests erreichten Zugriffszah-len weit höher liegen, als die meisten Webseiten jemals erreichen. Insofern sindderartige Aussagen immer auch akademischer Natur.

12.3 Einführung

Die Implementierung von SQLite folgt weitgehend den Vorgaben der C-Imple-mentierung1. Wo immer es an Dokumentation mangelt, kann man also gut auf dieOriginalausgabe der Datenbank zurückgreifen.

Eine einfache Beispielanwendung

Ein einfaches Beispiel soll zeigen, wie der Zugriff prinzipiell aussieht. Das Skriptgeht davon aus, dass ein Verzeichnis data unterhalb des Speicherorts des Skriptsexistiert, wo die Datenbankdatei erzeugt werden kann:

Listing 12.1: SQLiteCreate.php – Erzeugen einer Datenbank und einer Tabelle

<?phpecho ( 'SQLite Version: '.sqlite_libversion().'<br />');echo ( 'SQLite Encoding: '.sqlite_libencoding().'<br />');$sqliteDb='data/users.db';if ( !$db = sqlite_open($sqliteDb, 0666, $err) ) die($err);$sql = "CREATE TABLE � users � ( � id INTEGER PRIMARY KEY, � login STRING UNIQUE, �

500

password STRING, � email STRING �

1 SQLite ist eine C-Bibliothek für den Einbau in C-Projekte. Die Umsetzung als PHP-Modul bricht jedochnicht mit diesen Vorgaben, sodass weitgehend identische Aufrufe benutzt werden können, abgesehen vonzwingenden syntaktischen Differenzen zwischen C und PHP.

Page 501: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung

)";if ( !sqlite_query($sql, $db) ) die(sqlite_last_error($db).': '. � sqlite_error_string(sqlite_last_error($db)));echo ( "Datenbank $sqliteDb erfolgreich angelegt." );sqlite_close($db);?>

Dieses Skript erzeugt eine neue SQLite-Datenbank und darin eine einfacheTabelle mit vier Spalten: id, login, password und email zur Implementierung einerBenutzeranmeldung.

Eine Benutzerverwaltung mit SQLite

Das folgende Listing zeigt ein weiteres Skript auf einen Blick, eine Erläuterung derwichtigen Passagen folgt danach. Es handelt sich um die noch rudimentäre Benut-zerverwaltung, die die bereits erzeugte Tabelle mit zwei Datensätzen befüllt undeinen Anmeldevorgang simuliert:

Listing 12.2: SQliteSelect.php – Primitive Benutzerverwaltung mit SQLite

<?php $sqliteDb = 'data/users.db'; if (!$db = sqlite_open($sqliteDb, 0666, $err)) { die("Fehler: $err"); } $users = array( � array( � 'login'=>'jbloggs', � 'password'=>md5('secret'), � 'email'=>'[email protected]' � ), � array( � 'login'=>'jsmith', � 'password'=>md5('secret'), �

501

'email'=>'[email protected]' � ) ); $result = sqlite_query($db, "SELECT * FROM users");$num = sqlite_num_rows($result);if ($num == 0)

Page 502: 382726314 X Php5 In 14 Tagen (Ddt)

Die integrierte Datenbank SQLite

{ foreach ($users as $user) { $sql = "INSERT INTO users (login, password, email) � VALUES � ( � '{$user['login']}', � '{$user['password']}', � '{$user['email']}' � )"; echo "$sql<br>"; if (!sqlite_query($db, $sql)) { die ("Fehler: " . sqlite_last_error($db).': '. � sqlite_error_string(sqlite_last_error($db))); } } echo 'Daten erfasst'; } else{ echo 'Daten bereits vorhanden';}

sqlite_close($db); ?> <h1>Abfrage der Datenbank</h1><?php if (!$db = sqlite_open($sqliteDb, 0666, $err)) { die($err); }$sql = "SELECT * FROM users";

if (!$result = sqlite_query($db, $sql)) { die (sqlite_last_error($db).': '. �

502

sqlite_error_string(sqlite_last_error($db))); }echo ('<h2>Datenausgabe</h2>'); while ($row = sqlite_fetch_array($result, SQLITE_ASSOC)) {

Page 503: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung

echo "{$row['id']} <b>Name: </b>{$row['login']} � <b>E-Mail:</b> {$row['email']}<br />"; } if ($_SERVER['REQUEST_METHOD']=='POST' && $_POST['action']=='test'){ $pw_encrypted = md5($_POST['logpass']); $sql = "SELECT * FROM users WHERE login='{$_POST['logname']}' � AND password='$pw_encrypted'"; $result = sqlite_query($db, $sql);

echo sqlite_error_string(sqlite_last_error($db)); if (sqlite_num_rows($result) == 1) { echo '<p>Anmeldung erfolgreich</p>'; } else { echo '<p>Benutzername oder Kennwort konnten � nicht gefunden werden.</p>'; }}?><h1>Test der Anmeldung</h1><form action="<?=$_SERVER['PHP_SELF']?>" method="post"><input type="hidden" name="action" value="test"/><table> <tr> <td> Name: </td> <td> <input type="text" name="logname" value=""/> </td> </tr> <tr> <td> Kennwort: </td>

503

<td> <input type="text" name="logpass" value=""/> </td> </tr> <tr>

Page 504: 382726314 X Php5 In 14 Tagen (Ddt)

Die integrierte Datenbank SQLite

<td> &nbsp; </td> <td> <input type="submit" value="Anmelden (Test)"/> </td> </tr></table></form><?phpsqlite_close($db); ?>

Um Kennwortdaten in der Datenbank zu schützen, werden die Zeichenfolgen alsMD5-Hash abgelegt. Dies ist eine irreversible Abbildung beliebiger Daten. Umspäter das Kennwort verifizieren zu können, wird die Eingabe erneut mit MD5berechnet und die kodierten Werte werden verglichen.

Das Skript ist relativ primitiv, es erfasst nur zwei Musterdatensätze, die hart kodiertsind. Um festzustellen, ob die Daten schon vorhanden sind, erfolgt zuerst eine ein-fache Abfrage:

$result = sqlite_query($db, "SELECT * FROM users");

Dann wird ermittelt, wie viele Daten vorliegen:

$num = sqlite_num_rows($result);

Die Funktion sqlite_num_rows ermittelt die Anzahl der von der in $result gespei-cherten Abfrage zurückgegebenen Datensätze.

Sind keine Datensätze vorhanden, werden die als Array vorliegenden Daten mittelsINSERT gespeichert. Geht dabei etwas schief, enthält sqlite_last_error($db) denletzte Fehlercode. Um den passenden Text anzuzeigen, was meist hilfreicher ist,wird eine weitere Funktion benötigt:

sqlite_error_string(sqlite_last_error($db))

Für die Ausgabe von Datensätzen kann der Inhalt zeilenweisen einem Array zuge-führt werden. Auch hier dient der Verweis auf den Ergebnissatz, wiederum in

504

$result abgelegt, als Datenquelle:

while ($row = sqlite_fetch_array($result, SQLITE_ASSOC))

Page 505: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung

Der Vergleich des eingegebenen Kennworts mit dem gespeicherten erfolgt ineiner weiteren Abfrage. Damit dieser Programmteil nur ausgeführt wird, wenn einFormular gesendet wurde, wird eine Servervariable verwendet:

$_SERVER['REQUEST_METHOD']=='POST'

Das Kennwort muss wieder mit MD5 verpackt werden:

$pw_encrypted = md5($_POST['logpass']);

Dieser Wert wird dann für die Abfrage verwendet. Der Rest ist bekannt – es folgteine Abfrage und diese wird mit sqlite_num_rows untersucht. Nur wenn genau einDatensatz gefunden wurde, war die Prüfung erfolgreich.

Am Ende des Skripts sollten Sie nicht vergessen, die Verbindung zu schließen,damit SQLite die Datei freigeben kann, in der die Datenbank gespeichert ist.

sqlite_close($db);

Ausblick

SQLite kann Daten auch im Speicher des Computers halten, was für temporäreDaten sehr vorteilhaft ist. Unter Linux konnten dafür bislang die Shared-Memory-Funktionen eingesetzt werden, die jedoch unter Windows nicht zur Verfügungstanden, was echte Cross-Plattform-Entwicklung natürlich verhindert. Hier bietetSQLite nun eine einfache und elegante Lösung. Natürlich hat SQLite seine Gren-zen. Bei sehr vielen konkurrierenden Zugriffen führt die Sperrung der gesamtenDatenbankdatei dazu, dass neu eintreffende Abfragen verzögert werden. Das kannpraktisch bis zum Deadlock führen – dem Skriptstillstand. Allerdings ist dasGespann Dateisystem-Speicher-SQLite äußerst leistungsfähig und die allergrößteMasse aller Sites erreicht niemals die kritische Benutzerzahl.

Spannend sind auch die Möglichkeiten, SQLite beliebige Funktionen hinzuzufü-gen, die innerhalb von SQL-Abfragen benutzt werden können. Damit kann mansehr effizient die Defizite der Datenbank ausgleichen und erreicht eine mithinhöhere Leistungsfähigkeit als bei größeren Datenbanksystemen. Es ist durchausdenkbar, dass auch anspruchsvolle Lösungen für geringen oder mittlere Last mit

505

SQLite programmiert werden.

Page 506: 382726314 X Php5 In 14 Tagen (Ddt)

Die integrierte Datenbank SQLite

12.4 Referenz SQLite

Funktion und Parameter Beschreibung

sqlite_array_query Führt eine Abfrage aus und gibt ein Array mit den Ergeb-nissen zurück.

sqlite_busy_timeout Maximale Wartezeit auf ein Kommando. Der Standard-wert beträgt 60 Sekunden, die Angabe muss in Millisekun-den erfolgen.

sqlite_changes Anzahl der Spalten, die das letzte Kommando verändert hat.

sqlite_close Schließt die Verbindung.

sqlite_column Holt eine Spalte anhand des angegebenen Indizes aus der aktuellen Ergebnisreihe.

sqlite_create_aggregate Nutzt eine PHP-Funktion zur Verarbeitung von Daten aus einer Tabelle.

sqlite_create_function Definiert eine PHP-Funktion callback unter dem Namen function zur Verwendung in künftigen SQL-Befehlen.

sqlite_current Holt einen Ergebnissatz aus dem Ergebnisarray zurück.

sqlite_error_string Zeigt eine Fehlermeldung an. Siehe auch sqlite_last_error.

sqlite_escape_string Markiert in SQLite unzulässige Zeichen.

sqlite_fetch_array Holt die nächste Reihe aus dem Ergebnissatz.

sqlite_fetch_single Holt die erste Spalte aus dem Ergebnissatz.

sqlite_fetch_string Alias für sqlite_fetch_single.

sqlite_field_name Name eines Feldes

506

sqlite_has_more TRUE, wenn weitere Reihen verfügbar sind.

sqlite_last_error Letzte Fehlernummer.

sqlite_last_insert_rowed Letzte ID, die in einer INTEGER PRIMARY KEY Spalte einge-fügt wurde.

Page 507: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz SQLite

sqlite_libencoding Zeichenkodierung der Bibliothek, beispielsweise ISO-8859-1.

sqlite_libversion Die Versionsnummer der Bibliothek.

sqlite_next Sucht einen Ergebnisdatensatz zur Ausgabe, gibt aber selbst nichts aus. Wird zur Schleifensteuerung verwendet.

sqlite_num_fields Anzahl der Felder im Ergebnis.

sqlite_num_rows Anzahl der Reihen im Ergebnis.

sqlite_open Öffnet die Datenbank und erzeugt die Datei, wenn sie nicht existiert, mit dem angegebenen Zugriffsmode (Unix-Dateiparameter, beispielsweise 0666 (oktal)).

sqlite_popen Wie sqlite_open, aber persistent.

sqlite_query Fragt die Datenbank ab und gibt ein Handle auf die Ergeb-nisse zurück.

sqlite_rewind Setzt den Zeiger auf die erste Reihe. Wird für die Schlei-fensteuerung verwendet.

sqlite_seek Setzt den Zeiger auf die angegebene Reihe. Wird für die Schleifensteuerung verwendet.

sqlite_udf_decode_binary Dekodiert Binärdaten, bevor diese an eine benutzerdefi-nierte Funktion gesendet werden.

sqlite_udf_encode_binary Kodiert Binärdaten, bevor diese von einer benutzerdefi-nierte Funktion zurückgenommen werden.

sqlite_unbuffered_query Wie sqlite_query, aber das Ergebnis wird nicht sofort komplett geholt, sondern erst, wenn es andere Funktionen anfordern. Schneller bei einfachen sequenziellen Lese-zugriffen.

Funktion und Parameter Beschreibung

507

Page 508: 382726314 X Php5 In 14 Tagen (Ddt)

Die integrierte Datenbank SQLite

12.5 Kontrollfragen

1. Was müssen Sie tun, um SQLite benutzt zu können?

2. Welche Datentypen kennt SQLite?

3. Sie möchten mehrere Tausend Datensätze für eine hoch frequentierte Websiteverwalten. Ist SQLite dafür die beste Wahl? Begründen Sie die Antwort?

4. Welche Aufgabe hat der Befehl VACUUM?

508

Page 509: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen

mit MySQL

13

Page 510: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

13.1 Bibliotheks-Verwaltung mit MySQL

Dieses Kapitel widmet sich einem Praxisprojekt: Der Verwaltung einer Bibliothek.Dabei soll, neben der einfachen Auflistung von Büchern auch deren Position ineinem Regalsystem erfasst werden.

Schrittfolge

Um das Programm komplett zu erstellen, werden zwei Schritte erforderlich:

� Entwicklung einer Datenbankstruktur

� Aufbau der Oberfläche

� Programmierung der Geschäftslogik

Die Trennung von Oberfläche und Logik erfolgt durch ein primitives Template-System, einer Verwaltung von Vorlagen. Dies erleichtert die Erweiterung derFunktionalität erheblich.

Weitere Aufgaben

Das Programm ist ein guter Start in Richtung komplexer Datenbankanwendun-gen. Es ist zugleich Grundlage des nächsten Kapitels, indem die Daten der Büchereleganter beschafft werden. Das eigentliche Problem bei der Nutzung des Pro-gramms ist nämlich nicht dessen Erstellung, sondern dessen Nutzung. Das Erfas-sen aller Buchdaten ist rech mühselig. Glücklicherweise bietet Amazon einenWebservice, der die Beschaffung der Daten erleichtert. Vorerst wird jedoch – auchzur Korrektur später automatisch erfasster Daten – ein Eingabeformular benutzt.

Funktionen

Das Programm soll grundsätzlich folgende Funktionen anbieten:

510

� Erfassung von Büchern

� Suchen nach Titel, Autor und ISBN

� Erfassung der Regalposition und optional einer Anzahl vorhandener Exemplare

Page 511: 382726314 X Php5 In 14 Tagen (Ddt)

Bibliotheks-Verwaltung mit MySQL

� Erfassung von Lesern, an die die Bücher ausgeliehen werden

� Verwaltung der Leihvorgänge

Da ein Benutzer ein Buch immer nur einmal ausleihen kann, sind nur sehr einfa-che Beziehungen erforderlich. Allerdings kann jeder Benutzer mehrere verschie-dene Bücher ausleihen, was zu folgenden Tabellen führt:

� Tabelle »Books« für die Erfassung der Bücher

� Tabelle »Reader« für die Leser, die Bücher ausleihen können

� Tabelle »LendOut« für die Leihvorgänge

Der Vorteil bei der Ausleihtabelle besteht im Aufbau einer Historie, das heißt, hierkann man schnell nach der Häufigkeit und Dauer der Ausleihe suchen.

Nebenbei bemerkt eignet sich das Programm auch für eine private Bibliothek.Wer mit seinen Kumpels und Kollegen einen Buchpool bildet und sich gegensei-tig Bücher ausleiht, kann hier verwalten, welches Exemplar gerade wo unterwegsist.

Für die eigentliche Funktionalität muss außerdem festgelegt werden, welche Sei-ten und Formulare erforderlich sind. Dabei sind zwei »Betriebsarten« zu beach-ten: Ausleih- und Verwaltungsmodus. Im Ausleihmodus sollen Leser oder derBibliothekar suchen und Ausleihvorgänge steuern können, im Verwaltungsmoduskönnen neue Bücher erfasst oder alte entfernt werden. Benötigt werden dazu fol-gende Seiten:

� Startseite mit Auswahl der Betriebsart

� Seite zum Suchen von Büchern

� Seite zum Steuern des Ausleih- und Rücknamevorgangs

� Seite zum Erfassen, Anzeigen und Löschen von Benutzern

� Seite zum Erfassen, Anzeigen und Löschen von Büchern

Auf eine Verwaltung der Bediener durch eigene Anmeldenamen und Kennwörtersoll hier – vor allem aus Platzgründen – verzichten werden. Die dazu benötigten

511

Techniken unterscheiden sich nicht grundlegend von den gezeigten und dieUmsetzung sollte nach der Absolvierung des Kapitels nicht schwer fallen.

Page 512: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

13.2 Vorbereitung der Datenbank

Für den Entwurf der Datenbank müssen Sie sich zuerst über eine Struktur derTabellen im Klaren sein. Die drei benötigten Tabellen wurden bereits erwähnt.Der Aufbau wird so gewählt, dass alle Daten im passenden Datenformat gespei-chert werden können.

Das Anlegen kann mit einem Werkzeug erfolgen. Im Beispiel wurde das MySQLControl Center verwendet. Für dieses Projekt wurde lokal eine eigene Datenbankangelegt. Wenn Sie ausschließlich auf dem Server eines Providers arbeiten unddort nur eine fertige Datenbank verwenden können, müssen Sie diesen Schritt aus-lassen und bei der Nutzung der Skripte den Namen der Datenbank anpassen. Wiedas zu erfolgen hat, wird im entsprechenden Code-Abschnitt erläutert.

Datenbank anlegen

Falls noch nicht geschehen, legen Sie die für die Datenbank-Übungen erforderli-che Datenbank »marktundtechnik« an. Sie können auch jeden anderen Namennehmen, müssen dann aber die Skripte entsprechend anpassen. Dies ist nicht sehrschwer, weil es eine zentrale Konfigurationsdatei für die Datenbankparameter gibt.

Datenbank mit SQL

Wenn Sie mit SQL arbeiten, geben Sie am Prompt der MySQL-Konsole folgendesein:

CREATE DATABASE marktundtechnik;

Weitere Parameter werden für diese Projekt nicht benötigt.

Datenbank mit MySQL Control Center anlegen

Um die Datenbank mit MySQL Control Center anzulegen, starten Sie das Pro-

512

gramm. Beim ersten Mal müssen Sie den Server angeben (vermutlich »localhost«,wenn es lokal läuft) und gegebenenfalls Anmeldename und Kennwort. Nach einerfrischen Installation ist der Anmeldename »root« und das Kennwort ist leer. ImZweig Datenbanken des Servers klicken Sie mit der rechten Maustaste und wählendann im Kontextmenü den Eintrag Neue Datenbank.

Page 513: 382726314 X Php5 In 14 Tagen (Ddt)

Vorbereitung der Datenbank

Danach erscheint ein Eingabefeld, wo der Name der Datenbank eingetragen wird.Weitere Optionen sind nicht erforderlich.

Tabellen anlegen

Nun werden die drei benötigten Tabellen angelegt. Zuerst wird wieder der dazuerforderliche SQL-Code vorgestellt. Danach wird der entsprechende Dialog imControl Center gezeigt. Die Tabellen weisen keine Besonderheiten auf, wenn-gleich einige Dinge zu beachten sind.

Jede Tabelle enthält ein Feld id, das als Typ BIGINT und AUTO_INCREMENT definiertwurde. Dieses Feld ist zugleich der Primärschlüssel der Tabelle (PRIMARY KEY).Des Weiteren wurde bei einigen Feldern explizit NOT NULL angegeben, um zu ver-hindern, dass beim Anlegen der Datensätze Spalten leer bleiben. Felder, nachdenen gesucht werden könnte, wurden außerdem mit einem Index bedacht.

Tabellen mit SQL anlegen

Das folgende Listing zeigt die SQL-Anweisungen, mit denen die drei Tabellenerzeugt werden können:

Listing 13.1: SQL-Anweisungen zum Erstellen der Tabellen

## Tabellenstruktur für Tabelle `books`#CREATE TABLE books (

Abbildung 13.1: Eine neue Datenbank mit MySQL Control Center anlegen

513

id bigint(20) NOT NULL auto_increment, title varchar(100) NOT NULL default '', subtitle varchar(255) default '', isbn varchar(10) NOT NULL default '', author varchar(100) default '', number int(11) NOT NULL default '1',

Page 514: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

shelf varchar(15) default '', PRIMARY KEY (id), KEY idx_title (title)) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;# --------------------------------------------------------## Tabellenstruktur für Tabelle `lendout`#CREATE TABLE lendout ( id bigint(20) NOT NULL auto_increment, books_id bigint(20) NOT NULL default '0', reader_id bigint(20) NOT NULL default '0', lendingdate datetime NOT NULL default '0000-00-00 00:00:00', lendingperiod timestamp NOT NULL, PRIMARY KEY (id)) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;# --------------------------------------------------------## Tabellenstruktur für Tabelle `reader`#CREATE TABLE reader ( id bigint(20) NOT NULL auto_increment, name varchar(100) NOT NULL default '', surname varchar(100) NOT NULL default '', address varchar(150) NOT NULL default '', zip varchar(5) NOT NULL default '', city varchar(100) NOT NULL default '', active tinyint(1) NOT NULL default '0', created datetime NOT NULL default '0000-00-00 00:00:00', PRIMARY KEY (id), KEY idx_name (name), KEY idx_surname (surname)) ENGINE=MyISAM DEFAULT CHARSET=latin1;

Beachten Sie, dass die Anweisungen nur Tabellen erzeugen. Um das Skript erneutanzuwenden, müssen eventuell vorhandene Tabellen mit DROP TABLE entfernt wer-den.

514

Page 515: 382726314 X Php5 In 14 Tagen (Ddt)

Vorbereitung der Datenbank

Tabellen mit MySQL Control Center anlegen

Zuerst wird die Buchtabelle erzeugt. Neben Titel, Autor und ISBN werden auchdie Regalnummer und die Anzahl der Exemplare erfasst.

Wenn im Control Center eine entsprechende Zeile der Tabelle ausgewählt wird,lassen sich die Parameter, beispielsweise die Anzahl der Zeichen beim DatentypVARCHAR, erfassen.

Die Kundentabelle weist keine Besonderheiten auf:

Als letztes folgt die Ausleihtabelle:

Abbildung 13.2: Struktur der Tabelle »Books«

Abbildung 13.3: Die Tabelle »Reader« zur Erfassung der Bibliotheks-nutzer

Abbildung 13.4: Struktur der Tabelle

515

Nach diesem Schritt steht die Datenbank bereit, um Daten aufzunehmen und spä-ter natürlich Abfragen zu beantworten.

»LendOut«

Page 516: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

13.3 Die Seiten des Projekts

Am Anfang wurde bereits eine Übersicht über die benötigten Formulare gezeigt.Diese werden hier nacheinander in der genannten Reihenfolge vorgestellt.

Vorbereitungen – das Template-System

Wie bereits kurz erwähnt, sollen Geschäftslogik und Design weitgehend getrenntwerden. Dazu wird ein kleines Template-System entwickelt, das die Verwaltungder Seiten übernimmt. Das Prinzip ist recht einfach. Eine zentrale Instanz,index.php, verwaltet alle Seiten. Diesem Skript wird mitgeteilt, welche Vorlage(Template) aufgerufen werden soll. Die Vorlage besteht immer aus zwei Teilen:Dem PHP-Code und dem HTML-Code. Damit beide interagieren können, gibt esein paar Steuerzeichen, die im HTML eingebaut werden und die in PHP aufzulö-sen sind.

Der Sinn der Übung besteht nicht darin, ein professionelles Template-System wiephpTemple (http://www.phptemple.de) oder Smarty (http://smarty.php.net) zuersetzen, sondern die prinzipielle Arbeitsweise und den Nutzen transparent wer-den zu lassen. Die Technik lässt sich leicht auf andere Projekte übertragen. Hatman erstmal das Gefühl dafür, wann ein solches System Vorteile bringt, fällt esleichter, sich für den Einarbeitungsaufwand in ein kommerzielles Produkt zu ent-scheiden.

Die Steuerzeichen des Template-Systems

Die wichtigste Funktion besteht in der Datenausgabe. Dazu werden Variablenerforderlich. Diese sehen – der Einfachheit halber – genauso aus, wie in PHP.Allerdings kann man sie direkt ins HTML schreiben:

<div>{$variable}</div>

Die geschweiften Klammern verhindern Konflikte mit Währungsangaben mit Dol-larzeichen.

516

Zum Ausgeben von Tabellen braucht man eine Konstruktion, die Datenwiederho-lungen erzeugt. In PHP sind dies Schleifen. In HTML wird einfach ein Kommen-tar eingefügt, den das Skript verarbeiten kann (Editoren ignorieren diesenKommentar):

Page 517: 382726314 X Php5 In 14 Tagen (Ddt)

Die Seiten des Projekts

<table> <!-- #REPEAT:readers --> <tr> <td> ${readers:name} </td> <td> ${readers:surname} </td> </tr> <!-- #END:readers --></table>

Das Programm wiederholt jetzt das Tag mit sämtlichen darin befindlichen Codesso oft, wie das Array readers dies ermöglicht. Das Array muss assoziativ sein. DerIndex mit dem Namen surname wird über ${readers:surname} abgerufen. Tech-nisch werden dazu reguläre Ausdrücke und simple Ersetzungsfunktionen benutzt.

Funktionsweise des Template-Systems

Das eingesetzte Template-System beschränkt sich auf eine einzige Klasse, die inder Datei engine.inc.php definiert ist. Sie dient dazu, die HTML-Vorlagen zuerkennen, Variablen zu suchen und zu ersetzen und gegebenenfalls Blöcke mitsich wiederholenden Daten zu bilden. Das Resultat wird direkt ausgegeben.

Damit die Steuerung funktioniert, ist folgender Einsatz vorgesehen:

$engine = include_once('engine.inc.php');

Diese Zeile schließt die Klasse ein, erzeugt eine Instanz und gibt sie wiederzurück. Dadurch hat die Klasse sowohl Zugriff auf die globalen Variablen, derenWerte benutzt werden können, als auch die Möglichkeit, sich selbst dem aufrufen-den Code bereitzustellen.

Der aufrufende Code muss nun den Namen des Templates zuweisen:

$engine->Html = 'Start.html';

517

Alternativ kann dies entfallen; dann wird versucht, die Daten aus der GET-Variab-len TEMPLATE zu übernehmen. Die Steuerung kann damit vom Code in dasTemplate selbst verlagert werden:

<a href=?index.php?TEMPLATE=start?>Zur Startseite</a>

Page 518: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

Wenn ein expliziter Code-Block erforderlich ist, muss man diesen lediglich wiedie Vorlage benennen und ihm die Dateierweiterung .php geben. Damit das funk-tioniert, wurde die Startseite index.php folgendermaßen definiert:

Listing 13.2: index.php – Universelle Startseite für das Template-System

<?php$engine = include_once('engine.inc.php');if (!isset($_GET['TEMPLATE'])){ $engine->Html = 'start.html'; $engine->Code = 'start.php';} else { $engine->Html = "{$_GET['TEMPLATE']}.html"; $engine->Code = "{$_GET['TEMPLATE']}.php";}if (strlen($engine->Code) > 0 && file_exists($engine->Code)){ include_once($engine->Code);}$engine->RunTemplate();?>

Zuletzt muss die Ausführung auf der jeweiligen Seite angestoßen werden. DieStandardseite index.php erledigt dies durch Aufruf von runTemplate():

$engine->RunTemplate();

Zuvor sollten freilich die Variablen mit Werten belegt werden, die im Templatebenutzt werden. Dies wiederum basiert auf normalem PHP-Code.

Code des Template-Systems

Nachfolgend finden Sie den Code des Template-Systems mit einigen Erläuterun-gen zur Funktionsweise durchsetzt. Der Code ist chronologisch zu lesen und voll-

518

ständig in mehreren Abschnitten:

<?phpclass SimpleTemplate{

Page 519: 382726314 X Php5 In 14 Tagen (Ddt)

Die Seiten des Projekts

Zwei private Variablen speichern den Namen der Vorlage und optional inkludier-ten und implizit ausgeführten Code (ebenso als Dateiname übergeben). DerZugriff erfolgt später über die __set-Methode und den Konstruktor:

private $templateCode; private $templateHtml;

Die Steuerung der Vorlage erfolgt durch eingebettete Kommentare, die von demfolgenden regulären Ausdruck erkannt werden:

const COMMENT = '/<!-- #(.+):(.+)\s+--(>)/iU';

Der Konstruktor kann zur Übergabe der Vorlage und des Codes benutzt werden.Beide Parameter sind optional, sodass sie auch entfallen können:

public function __construct($html = '', $code = '') { $this->templateCode = $code; $this->templateHtml = $html; }

Diese Methode erzeugt zwei virtuelle Eigenschaften Html und Code, die die ent-sprechenden Werte im Objekt setzen:

public function __set($prop, $value) { switch ($prop) { case 'Html': $this->templateHtml = $value; break; case 'Code': $this->templateCode = $value; break; } }

Diese Methode führt die vorbereitete Vorlage aus. Sie durchsucht die Seite,erkennt die Blöcke und löst diese auf.

public function runTemplate()

519

{

Dazu wird zuerst die Vorlage selbst geladen:

if (strlen($this->templateHtml) > 0) {

Page 520: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

$html = file_get_contents($this->templateHtml); } if (strlen($this->templateCode) > 0) { include_once($this->templateCode); }

Nun werden die Wiederholungen aufgelöst. Dazu werden zuerst alle Blöckeermittelt, die mit der Kommentarsyntax umschlossen sind:

$matches = array(); $blocksFound = preg_match_all(self::COMMENT, $html, $matches, PREG_OFFSET_CAPTURE); if ($blocksFound) { $page = '';

Der reguläre Ausdruck produziert ein Array in $matches, dessen Elemente fol-gende Bedeutung haben:

� 0: Array der Kommentaranfänge

� 1: TAGs

� 2: Variablen

� 3: Ende-Offset

Liegt ein solches Array vor, beginnt die eigentliche Analyse. Anhand der Blockda-ten wird mit Hilfe von schnellen Zeichenkettenfunktionen der umschlossene Teilermittelt, weil diese Daten gegebenenfalls wiederholt werden müssen. Das passiertinnerhalb der for-Schleife praktisch für jeden Block:

if (is_array($matches)){ $startBlock = $lastStartBlock = 0; for ($i = 0; $i < count($matches[1]); $i++) { $token = strtoupper($matches[1][$i][0]); $offsetComment = (int)$matches[0][$i][1];

520

$offsetContent = ((int)$matches[3][$i][1]) + 1; $variableName = $matches[2][$i][0];

Die Auflösung der Daten erfolgt in zwei Schritten. Beim Start des Blocks werdendie Anfangspositionen gespeichert (REPEAT), beim Ende werden die Daten zur

Page 521: 382726314 X Php5 In 14 Tagen (Ddt)

Die Seiten des Projekts

Wiederholung benutzt (END). Die eigentliche Arbeit erledigt die Methode repeat-Block, die nachfolgend erläutert wird:

switch ($token){ case 'REPEAT': $startBlock = $offsetContent; $startComment = $offsetComment; break; case 'END': $block = substr($html, $startBlock, � $offsetComment - $startBlock); $block = $this->repeatBlock($block, $variableName); $len = $startComment - $lastStartBlock; $page .= substr($html, $lastStartBlock, $len).$block; $lastStartBlock = $offsetContent; break; }

Die wiederholten Daten werden der fertigen Seite, die in $page gespeichert wird,hinzugefügt:

}$page .= substr($html, $offsetContent);}

Zum Schluss werden noch die übrig gebliebenen Variablen ersetzt:

echo ($this->varReplace($page)); } else { echo 'Nicht gefunden'; }}

Wie bereits angedeutet, kommt repeatBlock eine große Bedeutung zu. DerMethode wird der Code eines Blockes und der Name einer Variablen übergeben.Ist diese Variable ein Array, wird der Block für jedes Array-Element einmal ausge-

521

geben und die Daten des jeweiligen Elements werden benutzt, um die Blockvari-ablen zu ersetzen. Letzteres findet in einer eigenen Methode statt, arrayReplace.Damit der Zugriff auf globale Variablen gelingt, wird der Name der Variablen alsdynamische Variable ($$-Syntax) benutzt:

Page 522: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

private function repeatBlock($block, $varName){ global $$varName; $result = ''; if (is_array($$varName)) { $this->index = 0; foreach ($$varName as $value) { $result .= $this->arrayReplace($block); $this->index++; } } else { $result = $block; } return $result;}

arrayReplace nutzt einen regulären Ausdruck, um die Variablen in der Vorlage zufinden und zu ersetzen. Das eigentliche Ersetzen passiert in der RückruffunktionreplaceArray, die ihrerseits Zugriff auf den globalen Adressraum hat:

private function arrayReplace($block){ $block = preg_replace_callback('/\{\$([^:}]*):(.+)\}/U', � array($this, 'replaceArray'), $block); return $block;}

private $index = 0;private function replaceArray($repl){ global $$repl[2]; $arr = $$repl[2]; return $arr[$this->index];}

522

Vergleichbar arbeitet die Ersetzung der normalen skalaren Variablen, wo ebensoeine Rückruffunktion benutzt wird. Auch hier geht es nur um einen vereinfachtenZugriff auf den globalen Adressraum:

Page 523: 382726314 X Php5 In 14 Tagen (Ddt)

Die Seiten des Projekts

private function varReplace($block){ $block = preg_replace_callback('/\{\$(.+)\}/U', � array($this, 'replaceSkalar'), $block); return $block;}private function replaceSkalar($repl){ global $$repl[1]; return $$repl[1];}

Der Rest der Datei steht außerhalb der Klassendefinition. Er erzeugt eine Instanzder Klasse und gibt das entsprechende Objekt als Ergebnis der Ausführung zurück.Damit kann die include-Funktion (oder include_once) als Quelle einer Zuwei-sung benutzt werden. Zuvor wird noch versucht, den Namen des Templates ausder GET-Variablen TEMPLATE zu entnehmen, wobei dieser Schritt optional ist,das heißt, er schlägt nicht fehl, wenn die Angabe fehlt:

$__template = isset($_GET['TEMPLATE']) ? $_GET['TEMPLATE'] : '';return new SimpleTemplate($__template); ?>

Das vorgestellte Prinzip ist einfach und schnell und bietet einigen Raum für Ver-besserungen. Es zeigt vor allem, wie effektiv mit regulären Ausdrücken und derneuen objektorientierten Syntax von PHP5 gearbeitet werden kann.

Der Code der Seiten

Die folgenden Skripte zeigen eine kurze Übersicht über die Vorlagen, also denHTML-Code der Seiten (befreit von gestalterischen Elementen, um die Lesbarkeitzu verbessern) und die Variablen, die die dynamischen Daten enthalten.

Startseite mit Auswahl der Betriebsart

Die Startseite ist relativ einfach, denn es sind nur folgende Zustände zu unterschei-

523

den:

� Ausleihen oder Rücknehmen eines Buches (Betriebszustand)

� Anlegen oder Sperren von Kunden (Betriebszustand)

� Anlegen oder Herausnehmen von Büchern (Administrationszustand)

Page 524: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

Das vorliegende Beispiel ist vergleichsweise rudimentär und unterscheidet nichtexplizit zwischen Betriebs- und Administrationszustand. Dies ist jedoch leicht zuimplementieren. Solange eine solche Bibliotheksverwaltung lokal im Intranetläuft, ist die Unterscheidung möglicherweise nicht erforderlich. Eine öffentlicheSeite müsste um entsprechende Sicherheitsfunktionen ergänzt werden.

Da keine weitere Logik erforderlich ist, besteht diese Seite lediglich aus einigenLinks auf die anderen Vorlagen:

Listing 13.3: start.html – Startseite der Bibliotheksverwaltung

<html><body><h1>Bibliotheksverwaltung</h1>Willkommen in der Bibliotheksverwaltung mit PHP5<h2>Funktionsauswahl</h2><a href="index.php?TEMPLATE=kunden">Kundenverwaltung</a><br /><br /><a href="index.php?TEMPLATE=buecher">B&uuml;cherverwaltung</a><br /><br />Heute ist der {$today}.</body></html>

PHP-Code wird hier kaum benötigt, lediglich eine Variable zur Anzeige desDatums ist zu definieren:

Listing 13.4: start.php – Erzeugen einer deutschen, systemunabhängigen Datumsanzeige

<?phpif (PHP_OS == 'WINNT'){ setlocale(LC_TIME, 'ge');} else

524

{ setlocale(LC_TIME, 'de_DE');}$today = strftime('%d. %B %Y'); ?>

Page 525: 382726314 X Php5 In 14 Tagen (Ddt)

Die Seiten des Projekts

Seite zum Steuern des Ausleih- und Rücknahmevorgangs

Die Seite zum Steuern des Ausleih- und Rücknahmevorgangs benötigt folgendeelementare Funktionen:

� Suchen des Kunden, wenn nicht vorhanden, Sprung zur Kundenerwaltung

� Suche des Buches (wird vorausgesetzt, dass nur vorhandene Bücher genutztwerden)

� Ausleihe oder Rücknahmevorgang

Hier wird auf jeden Fall der Datenbankzugriff benötigt, der in einer Include-Dateiabgelegt wird, die auch den anderen Modulen zur Verfügung steht. In dieser Dateiwird ein MySQLi-Objekt erzeugt, das in allen folgenden Codes den Zugriff auf dieDatenbank erlaubt:

Listing 13.5: database.inc.php – Include-Datei für den Datenbankzugriff

<?php$mysqli = new mysqli('localhost', 'root', '', 'marktundtechnik');if (!is_object){ die ('Fehler: Verbindung zur Datenbank konnte � nicht aufgebaut werden');}?>

Seite zum Erfassen, Anzeigen und Löschen von Kunden

Die Seite umfasst folgende Funktionen:

� Liste aller Kunden anzeigen

� Anzeige von Details

� Ändern bestehender Kunden

� Anlegen neuer Kunden

525

� Setzen einer Sperre (anstatt eines endgültigen Löschens)

Die HTML-Vorlage ist sehr direkt aufgebaut und enthält mehrere Tabellen, diejeweils die Darstellung einer Funktion steuern:

Page 526: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

Listing 13.6: kunden.html – Vorlage für die Verwaltung der Kunden

<html><body><h1>Bibliotheksverwaltung</h1><a href="index.php">Startseite</a>|<a href="index.php?TEMPLATE=ausleihe">Ausleihe</a>|<a href="index.php?TEMPLATE=buecher">B&uuml;cherverwaltung</a><h2>Kundenverwaltung</h2><h3>Informationen</h3><table border="1" width="400"> <caption style="font-weight:bold">Kundenliste</caption> <tr> <th> Status </th> <th> Name </th> <th> Aktion </th> </tr> <!-- #REPEAT:reader --> <tr> <td> {$reader:state} </td> <td> {$reader:name}, {$reader:surname} </td> <td> <a href="index.php?TEMPLATE=kunden&amp;id={$reader:id}"> � Details ansehen</a> </td> </tr>

526

<!-- #ENDREPEAT:reader --></table><!-- #IF:detail_name --><table border="1" width="400"> <caption style="font-weight:bold">

Page 527: 382726314 X Php5 In 14 Tagen (Ddt)

Die Seiten des Projekts

Details f&uuml;r Kunde {$detail_name}</caption> <tr> <td> Name, Vorname: </td> <td> {$detail_name}, {$detail_surname} </td> </tr> <tr> <td> Anschrift: </td> <td> {$detail_zip} {$detail_city} </td> </tr></table><!-- #ENDIF:detail_name --><h3>&Auml;ndern</h3><form action="index.php?TEMPLATE=kunden" method="post"><input type="hidden" name="action" value="change" /><input type="hidden" name="id" value="{$detail_id}" /><table border="1" width="400"> <caption style="font-weight:bold">Kundendaten</caption> <tr> <td> Daten: </td> <td> <input type="text" name="reader_name" value="{$detail_name}"/>, <input type="text" name="reader_surname" value="{$detail_surname}"/> </td> </tr> <tr>

527

<td> PLZ&nbsp;Ort: </td> <td> <input type="text" name="reader_zip" value="{$detail_zip}"/>

Page 528: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

<input type="text" name="reader_city" value="{$detail_city}"/> </td> </tr> <tr> <td> Status </td> <td> <input type="checkbox" name="reader_state" {$detail_state} value="1"/> </td> </tr> <tr> <td> </td> <td> <input type="submit" value="&Auml;ndern" /> </td> </tr></table></form><h3>Neuer Kunde</h3><form action="index.php?TEMPLATE=kunden" method="post"><input type="hidden" name="action" value="new" /><table border="1" width="400"> <caption style="font-weight:bold">Kundendaten</caption> <tr> <td> Daten: </td> <td> <input type="text" name="reader_name" />, <input type="text" name="reader_surname" /> </td> </tr> <tr>

528

<td> PLZ&nbsp;Ort: </td> <td> <input type="text" name="reader_zip" />

Page 529: 382726314 X Php5 In 14 Tagen (Ddt)

Die Seiten des Projekts

<input type="text" name="reader_city" /> </td> </tr> <tr> <td> </td> <td> <input type="submit" value="Erfassen" /> </td> </tr></table></form></body></html>

Besonderheiten gibt es hier nicht. Beachten Sie den Aufruf der anderen Vorlagenim Menü:

index.php?TEMPLATE=ausleihe

Die Steuerung des Codes erfolgt durch versteckte Felder in den Formularen:

<input type="hidden" name="action" value="change" /><input type="hidden" name="id" value="{$detail_id}" />

Dabei werden auch dynamische Daten ({$detail_id}) übergeben, wodurch Vorlageund Code miteinander in Verbindung stehen. Die Vorlage verwendet außerdemeine #IF-Anweisung, um einen Teil der Seite nur bei Bedarf anzuzeigen.

Weitaus aufschlussreicher ist der PHP-Code, der mit dieser Vorlage verknüpft ist:

Listing 13.7: kunden.php – Steuerung der Vorlage mittels Datenbankabfragen

<?phpinclude('database.inc.php');// Neuer Kunde?if ($_SERVER['REQUEST_METHOD']=='POST' && isset($_POST['action'])){ switch ($_POST['action'])

529

{ case 'new': $mysqli->query("INSERT INTO reader � (name, surname, zip, city, � active, created)� VALUES ('{$_POST['reader_name']}', �

Page 530: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

'{$_POST['reader_surname']}', � '{$_POST['reader_zip']}', � '{$_POST['reader_city']}', � 1, CURDATE())"); break; case 'change': $state = isset($_POST['reader_state']) ? 1 : 0; $mysqli->query("UPDATE reader SET � name='{$_POST['reader_name']}', � surname='{$_POST['reader_surname']}', � zip='{$_POST['reader_zip']}', � city='{$_POST['reader_city']}', � active=$state � WHERE id = {$_POST['id']}"); break; }}// Liste fuellen$query = $mysqli->query("SELECT id, name, surname, IF(active=0,'Gesperrt','Aktiv') AS state FROM reader");while($arr = $query->fetch_assoc()){ $reader[] = $arr; }// Details abrufen, wenn gefordertif (isset($_GET['id'])){ $query = $mysqli->query("SELECT * FROM reader WHERE id = {$_GET['id']}"); $detail = $query->fetch_assoc(); $detail_id = $detail['id']; $detail_name = $detail['name']; $detail_surname = $detail['surname']; $detail_zip = $detail['zip']; $detail_city = $detail['city']; $detail_state = ($detail['active']==1) ? 'checked' : ''; }

530

?>

Am Anfang des Codes werden die beiden Formulare ausgewertet, erst danacherfolgt der Aufbau der Liste für die Ausgabe. Auf diese Weise wird sichergestellt,dass die soeben erfolgten Änderungen sofort in der Anzeige reflektiert werden.

Page 531: 382726314 X Php5 In 14 Tagen (Ddt)

Die Seiten des Projekts

Unbedingt erforderlich ist auch der Einschluss der Vorbereitung der Datenbank-abfrage:

include('database.inc.php');

Die Aktionen werden nur ausgewertet, wenn ein Formular abgesendet wurde.Dazu wird die Servervariable REQUEST_METHOD auf den Wert »POST« hin unter-sucht:

if ($_SERVER['REQUEST_METHOD']=='POST' && isset($_POST['action']))

Anschließend werden die passenden INSERT- bzw. UPDATE-Befehle gebildet, um dieDaten in der Datenbank zu verändern. Als Quelle dienen die Felder der Formu-lare.

Sind alle Kommandos abgearbeitet, werden die aktuellen Daten aus der Daten-bank gelesen:

$query = $mysqli->query("SELECT id, name, surname, � IF(active=0,'Gesperrt','Aktiv') AS state FROM reader");

Der SELECT-Befehl sorgt gleich für die korrekte Aufbereitung der Daten für die Aus-gabe. Das Feld active der Tabelle reader kann die Werte 0 oder 1 enthalten. Fürdie Anzeige werden diese Werte mit der MySQL-Funktion IF in die Zeichenket-ten »Gesperrt« bzw. »Aktiv« umgewandelt. Es ist generell eine gute Idee, fest ver-drahtete Berechnungen in der Datenbank erledigen zu lassen, und damit denlokalen Code zu entlasten. Nur dann, wenn die Berechnungen selbst dynamischsind und möglicherweise häufig verändert werden oder wenn sie sehr komplexsind, ist PHP besser geeignet.

Die fertige Abfrage muss nun noch in ein Array überführt werden, damit die Tem-plate-Engine die Daten in einer Schleife ausgeben kann:

while($arr = $query->fetch_assoc()){ $reader[] = $arr; }

Damit kann dann innerhalb des Abschnitts <!-- #REPEAT:reader --> folgendeSyntax zum Abruf der Felder benutzt werden:

531

{$reader:name}

Für das Füllen der Detaildaten und der Formulare wird keine Schleife benötigt.Die erforderlichen Variablen werden deshalb in skalarer Form abgerufen (nacheiner erneuten SELECT-Anweisung):

Page 532: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

$detail_id = $detail['id'];

In der Vorlage wird dieser Wert beispielsweise benutzt, um die aktuelle Auswahlim Formular zu halten:

<input type="hidden" name="id" value="{$detail_id}" />

Die folgende Abbildung zeigt, wie die Seite in Aktion aussieht:

Abbildung 13.5: Die Kundenverwaltung der Bibliothek

532

Seite zum Erfassen, Anzeigen und Löschen von Büchern

Die Seite zur Verwaltung der Bücher ist praktisch gleichartig zur Verwaltung derKunden aufgebaut. Lediglich die Daten unterscheiden sich und damit Anzahl undBenennung der Spalten der Tabellen.

Page 533: 382726314 X Php5 In 14 Tagen (Ddt)

Die Seiten des Projekts

Zuerst wird wieder die Vorlage gezeigt:

Listing 13.8: buecher.html – Die Vorlage zur Verwaltung der Bücher

<html><body><h1>Bibliotheksverwaltung</h1><a href="index.php">Startseite</a>|<a href="index.php?TEMPLATE=ausleihe">Ausleihe</a>|<a href="index.php?TEMPLATE=kunden">Kundenverwaltung</a><h2>B&uuml;cherverwaltung</h2><h3>Informationen</h3><table border="1" width="400"> <caption style="font-weight:bold">B&uuml;cherliste</caption> <tr> <th> Anzahl </th> <th> ISBN </th> <th> Titel </th> <th> Aktion </th> </tr> <!-- #REPEAT:books --> <tr> <td> {$books:number} </td> <td nowrap> {$books:isbn}

533

</td> <td> {$books:title} </td> <td><a href="index.php?TEMPLATE=buecher&amp;id={$books:id}&amp;action=show">

Page 534: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

Details</a> </td> </tr> <!-- #ENDREPEAT:books --></table><a href="index.php?TEMPLATE=buecher&amp;action=new">Neues Buch erfassen</a><h3>&Auml;ndern&nbsp;/&nbsp;Erfassen</h3><form action="index.php?TEMPLATE=buecher" method="post"><input type="hidden" name="action" value="{$detail_action}" /><input type="hidden" name="id" value="{$detail_id}" /><table border="1" width="400"> <caption style="font-weight:bold">Buchdaten</caption> <tr> <td> Titel: </td> <td> <input type="text" name="book_title" value="{$detail_title}" size="40"/> </td> </tr> <tr> <td> Untertitel: </td> <td> <input type="text" name="book_subtitle" value="{$detail_subtitle}" size="40"/> </td> </tr> <tr> <td> Autor: </td> <td>

534

<input type="text" name="book_author" value="{$detail_author}"/> </td> </tr> <tr>

Page 535: 382726314 X Php5 In 14 Tagen (Ddt)

Die Seiten des Projekts

<td> ISBN: </td> <td> <input type="text" name="book_isbn" value="{$detail_isbn}"/> </td> </tr> <tr> <td> Anzahl </td> <td> <input type="text" name="book_number" � value="{$detail_number}" size="4"/> </td> </tr> <tr> <td> Ausgeliehen </td> <td> <input type="text" name="book_lend" value="{$detail_lend}" � size="4" disabled/> </td> </tr> <tr> <td> </td> <td> <input type="submit" value="{$detail_actiontext}" /> </td> </tr></table></form></body></html>

535

Die Vorlage ist bewusst etwas anders als die für die Kunden verwendete aufgebaut.Detailanzeige, Änderung und Erfassung sind in einem Formular zusammenge-fasst, dessen Funktion sich damit dynamisch ändert. Ein verstecktes Feld, dessenWert über eine Variable gesteuert wird, macht es möglich:

<input type="hidden" name="action" value="{$detail_action}" />

Page 536: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

Auch die Beschriftung der Sendeschaltfläche ist dynamisch:

<input type="submit" value="{$detail_actiontext}" />

Die eigentliche Arbeit erledigt der Code-Teil:

Listing 13.9: buecher.php – Logik zur Verwaltung der Bücher

<?phpinclude('database.inc.php');$detail_action = 'change';$detail_actiontext = '&Auml;ndern';if ($_SERVER['REQUEST_METHOD']=='GET' && isset($_GET['action'])){ switch ($_GET['action']) { case 'new': $detail_action = 'new'; $detail_actiontext = 'Erfassen'; break; }}if ($_SERVER['REQUEST_METHOD']=='POST' && isset($_POST['action'])){ switch ($_POST['action']) { case 'new': $mysqli->query("INSERT INTO books � (title, subtitle, author, isbn, number)� VALUES ('{$_POST['book_title']}', � '{$_POST['book_subtitle']}',� '{$_POST['book_author']}', � '{$_POST['book_isbn']}', � {$_POST['book_number']} � )"); break; case 'change': $mysqli->query("UPDATE books SET �

536

title='{$_POST['book_title']}', � subtitle='{$_POST['book_subtitle']}', � author='{$_POST['book_author']}', � isbn='{$_POST['book_isbn']}', � number={$_POST['book_number']}, � WHERE id = {$_POST['id']}");

Page 537: 382726314 X Php5 In 14 Tagen (Ddt)

Die Seiten des Projekts

break; }}$query = $mysqli->query("SELECT * FROM books");while($arr = $query->fetch_assoc()){ $books[] = $arr; }// Details abrufen, wenn gefordertif (isset($_GET['id'])){ $query = $mysqli->query("SELECT *, � (SELECT COUNT(*) FROM lendout WHERE books_id = b.id) AS lend � FROM books b WHERE id = {$_GET['id']}"); $detail = $query->fetch_assoc(); $detail_id = $detail['id']; $detail_title = $detail['title']; $detail_subtitle= $detail['subtitle']; $detail_author = $detail['author']; $detail_number = $detail['number']; $detail_isbn = $detail['isbn']; $detail_lend = $detail['lend']; }?>

Der erste Teil steuert die Umschaltung des Formulars. Danach folgen das Einfü-gen (INSERT) und Aktualisieren (UPDATE) der Datenbank. Im letzten Teil werdenwieder die frisch aktualisierten Daten abgefragt, damit stets die aktuelle Liste zusehen ist. Eine Besonderheit stellt die letzte Abfrage dar, die auf einem so genann-ten Sub-Select basiert. (Achtung! Dazu ist mindestens MySQL 4 erforderlich.)Diese Unterabfrage sichert den Zugriff auf die Tabelle lendout, um die Anzahl derbereits ausgeliehenen Bücher zu erhalten.

Die Geschäftslogik des Ausleihvorgangs

Die Logik des Ausleihvorgangs muss folgende Aufgaben erfüllen:

537

� Suchen des Kunden, beispielsweise anhand der Kundennummer

� Suchen des Buches, beispielsweise anhand der ISBN

� »Buchen« des Ausleihvorgangs oder der Rücknahme eines Buches

Page 538: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

Außerdem sind noch einige Verwaltungsfunktionen denkbar, die hier nicht voll-ständig ausgeführt wurden:

� Auflistung komplett ausgeliehener Bücher

� Auflistung nicht pünktlich zurückgegebener Bücher

� Auflistung säumiger Kunden

Die Vorlage der Ausleihseite

Auch die Ausleihseite besteht aus einer Vorlage und einer Code-Seite. Die Vorlage

Abbildung 13.6: Die Bücherseite mit Details über ein Buch

538

ist vergleichsweise schlicht. Auch hier werden wieder mehrere Formulare einge-setzt, um die Daten zu sammeln. Die Daten (Kunde, Buch) werden in Sitzungs-variablen gespeichert, damit sie bis zum Ende des Vorgangs erhalten bleiben. DerTeil der Seite, der die eigentliche Buchung ausführt, wird erst sichtbar, wenn alleDaten vorliegen.

Page 539: 382726314 X Php5 In 14 Tagen (Ddt)

Die Seiten des Projekts

Listing 13.10: ausleihe.html – Vorlage für die Ausleihseite

<html><body><h1>Bibliotheksverwaltung</h1><a href="index.php">Startseite</a>|<a href="index.php?TEMPLATE=kunden">Kundenverwaltung</a>|<a href="index.php?TEMPLATE=buecher">B&uuml;cherverwaltung</a><h2>Kunden</h2><table><tr> <td> <h3>Kunden suchen</h3> <form action="index.php?TEMPLATE=ausleihe" method="post"> <input type="hidden" name="action" value="reader" /> Name <input type="text" name="reader" /> <input type="submit" value="Suchen" /> </form> </td> <td> <h3>Buch suchen</h3> <form action="index.php?TEMPLATE=ausleihe" method="post"> <input type="hidden" name="action" value="book" /> ISBN <input type="text" name="book" /> <input type="submit" value="Suchen" /> </form> </td></tr></table><!-- #IF:noerror --><h3>Ausleihe- oder R&uuml;cknahme</h3>Ausleih- oder R&uuml;cknahmeinformationen:<ul> <li>Kundenname:&nbsp;{$reader_name}</li> <li>Kundennummer: {$reader_id}</li>

539

<li>Buchtitel: {$book_title}</li> <li>ISBN: {$book_isbn}</li> <li>Es sind noch <b>{$available}</b> B&uuml;cher verf&uuml;gbar.</li></ul><table>

Page 540: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

<tr> <td> <form action="index.php?TEMPLATE=ausleihe" � method="post"> <input type="hidden" name="reader" � value="{$reader_id}" /> <input type="hidden" name="book" value="{$book_id}" /> <input type="hidden" name="action" value="lend" /> <input type="submit" value="Ausleihen" {$disabled} /> </form> </td> </tr></table><!-- #ENDIF:noerror --><table width="400" border="1"><caption>&Uuml;bersicht aller Vorg&auml;nge des Kunden</caption> <tr> <th>Buch ID</th> <th>Ausleihdatum</th> <th>F&auml;lligkeit</th> <th>Aktion</th> </tr><!-- #REPEAT:history --> <tr> <td>{$history:books_id}</td> <td>{$history:lendingdate}</td> <td>{$history:due}</td> <td> <ahref="index.php?TEMPLATE=ausleihe&amp;action=return&amp;id={$history:id}">R&uuml;ckgabe</a> </td> </tr><!-- #ENDREPEAT:history --></table><!-- #IF:error -->Fehler:&nbsp;{$reader_error}{$book_error}

540

<!-- #ENDIF:error --></body></html>

Diese Seite nutzt die Wiederholungs-Anweisungen zum Aufbau und definierteinen Sprung auf die Ausleihseite.

Page 541: 382726314 X Php5 In 14 Tagen (Ddt)

Die Seiten des Projekts

Der Code der Ausleihseite

Listing 13.11: ausleihe.php – Die Steuerung der Vorlage nutzt intensiv die Datenbank

<?phpinclude('database.inc.php');session_start();$reader_name = $_SESSION['reader_name'];$reader_id = $_SESSION['reader_id'];$book_title = $_SESSION['book_title'];$book_id = $_SESSION['book_id'];$book_isbn = $_SESSION['book_isbn'];$number = $_SESSION['number'];unset($error);if ( ($_SERVER['REQUEST_METHOD']=='POST' && isset($_POST['action'])) || ($_SERVER['REQUEST_METHOD']=='GET' && isset($_GET['action']))){ switch ($_REQUEST['action']) { case 'lend': $sql = "INSERT INTO lendout (books_id, reader_id, � lendingdate, lendingperiod, active) � VALUES ( � {$_POST['book']}, � {$_POST['reader']}, � CURDATE(),� 30, 1 � )"; $mysqli->query($sql); break; case 'return': $mysqli->query("UPDATE lendout SET active = 0 � WHERE id = {$_GET['id']}"); break;

541

case 'reader': $result = $mysqli->query("SELECT * FROM reader � WHERE name LIKE '%{$_POST['reader']}%' � OR id = '{$_POST['reader']}'"); if ($result->num_rows == 1) {

Page 542: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

$arr = $result->fetch_assoc(); $reader_name = $arr['name']; $reader_id = $arr['id']; } else { $reader_error = 'Auswahl des Kunden war nicht eindeutig'; $error = true; } break; case 'book': $result = $mysqli->query("SELECT * FROM books � WHERE title LIKE '%{$_POST['book']}%' � OR isbn = '{$_POST['book']}'"); if ($result->num_rows == 1) { $arr = $result->fetch_assoc(); $book_title = $arr['title']; $book_id = $arr['id']; $book_isbn = $arr['isbn']; $number = $arr['number']; } else { $book_error = 'Auswahl des Buches war nicht eindeutig'; $error = true; } break; }}if (!isset($error)){ $result = $mysqli->query("SELECT COUNT(*) AS lent � FROM lendout � WHERE books_id=$book_id AND active=1");

542

$books_to_lend = $result->fetch_object(); $available = $number - $books_to_lend->lent; $disabled = ($available <= 0) ? 'disabled' : ''; $_SESSION['reader_name'] = $reader_name; $_SESSION['reader_id'] = $reader_id;

Page 543: 382726314 X Php5 In 14 Tagen (Ddt)

Die Seiten des Projekts

$_SESSION['book_title'] = $book_title; $_SESSION['book_id'] = $book_id; $_SESSION['book_isbn'] = $book_isbn; $_SESSION['number'] = $number; // Ermittle die Liste aller bisherigen Buchungen fuer diesen Benutzer $sql = "SELECT id, books_id, � DATE_FORMAT(lendingdate, '%d.%m.%Y') AS lendingdate, � IF(TO_DAYS(CURDATE())-TO_DAYS(lendingdate) � > lendingperiod, 'Überfällig', � CONCAT('Noch ', 30 - (TO_DAYS(CURDATE()) � - TO_DAYS(lendingdate)), ' Tage') ) AS due � FROM lendout � WHERE reader_id=$reader_id � AND active=1"; $result = $mysqli->query($sql); if ($result != false) { while($arr = $result->fetch_assoc()) { $history[] = $arr; } } $noerror = true; }?>

Der Code basiert wieder auf dem bereits mehrfach verwendeten Prinzip der Über-tragung eines Kommandos, dessen Erkennen in einem switch-Zweig und derSteuerung der entsprechenden SQL-Anweisungen auf Basis der Auswahl. Freilichist bei der Ausleihe (und Rückgabe) etwas mehr zu tun, da nun alle drei Tabellenan der Aktion beteiligt sind.

Da Kunden und Bücher getrennt gesucht werden, werden die bereits gefundenenWerte in Sitzungsvariablen festgehalten. Damit das Sitzungsmanagement funktio-niert, wird es am Beginn des Skripts gestartet:

session_start();

Dann werden die bestehenden Sitzungsvariablen wiedergeholt, soweit vorhanden.

543

Die Fehlervariable $error dient der Steuerung des entsprechenden #IF-Blockes.Der Inhalt eines solchen Blockes wird ausgeführt, wenn die Variable existiert. Umunter allen Umständen zu verhindern, dass die Variable ohne Vorliegen einesFehlergrunds einen Wert enthält, wird sie zunächst zurückgesetzt:

unset($error);

Page 544: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

Dann werden die entsprechenden Abfragen an die Datenbank gesendet. BeimEinfügen wird die zulässige Ausleihzeit des Buches fest auf 30 Tage gesetzt. DieTabelle nutzt außerdem die Spalte active, um festzuhalten, welche Datensätzeaktiv sind. Um eine Historie des Benutzerverhaltens zu erhalten, werden dieDatensätze bei Rückgabe des Buches nicht gelöscht, sondern der Wert in derSpalte active wird lediglich auf 0 gesetzt:

UPDATE lendout SET active = 0 WHERE id = {$_GET['id']}

Die Anzeige der noch verfügbaren Bücher nutzt dieselbe Spalte, was die Abfragesehr einfach macht:

SELECT COUNT(*) AS lent FROM lendout � WHERE books_id=$book_id AND active=1

Etwas komplexer ist die Abfrage der Liste der Ausleihvorgänge (Historie). Hier wer-den Datumsfunktionen von MySQL benutzt, um die Anzahl der noch verbleiben-den Ausleihtage festzustellen und gegebenenfalls eine Meldung anzuzeigen, wennein Buch überfällig ist. Damit kann verhindert werden, dass säumige Nutzer wei-tere Bücher ausleihen.

SELECT id, books_id,

DATE_FORMAT formatiert das interne Datumsformat für die Ausgabe im DeutschenFormat:

DATE_FORMAT(lendingdate, '%d.%m.%Y') AS lendingdate,

Die IF-Funktion ermittelt die Datumsdifferenz. Dazu wird mit TO_DAYS ein Tages-zähler ermittelt und mit dem aktuellen Datum (CURDATE) verglichen. Als Ver-gleichswert wird die individuelle Ausleihdauer (in lendingperiod) herangezogen,sodass die Funktion auf vereinbarte Verlängerungen korrekt reagieren kann. DieIF-Funktion gibt bei wahrer Bedingung den ersten Teil zurück (hier das Wort»Überfällig«), andernfalls einen Text, der mittels CONCAT zusammengesetzt wird.

IF(TO_DAYS(CURDATE())-TO_DAYS(lendingdate) > lendingperiod, � 'Überfällig',

CONCAT erlaubt die Angabe mehrerer Parameter, die zu einer Zeichenkette zusam-mengefasst werden, unabhängig vom Datentyp. Der Name der damit neu erzeug-

544

ten Spalte wird als due (fällig) bezeichnet:

CONCAT('Noch ', 30 - (TO_DAYS(CURDATE())-TO_DAYS(lendingdate)), ' Tage') ) AS due

Page 545: 382726314 X Php5 In 14 Tagen (Ddt)

Die Seiten des Projekts

Als Auswahlkriterium gilt logischerweise der ausgewählte Nutzer. Außerdem wer-den nur offene Leihvorgänge angezeigt:

FROM lendout �WHERE reader_id=$reader_id �AND active=1

Die Übergabe der Abfrageergebnisse an das Array $history korrespondiert wiedermit der Vorlage:

while($arr = $result->fetch_assoc()){ $history[] = $arr; }

In der Vorlage wird beispielsweise die Fälligkeit folgendermaßen ausgegeben:

<td>{$history:due}</td>

Wie das fertig aussieht, kann der nächsten Abbildung entnommen werden:

545

Abbildung 13.7: Die Ausleihseite in Aktion

Page 546: 382726314 X Php5 In 14 Tagen (Ddt)

Datenbanklösungen mit MySQL

13.4 Ausblick

Die vorgestellte Bibliothek zeigte zwei entscheidende Techniken:

� Funktionsweise eines rudimentären Template-Systems

� Praktische Anwendung der MySQLi-Bibliothek

Freilich bleibt noch einiges zu tun, um ein solches System praxistauglich zumachen. Dazu gehört zuerst sicher eine vernünftige Gestaltung, die mit einerReihe von Maßnahmen einhergeht. Zuerst sollte eine zentrale Datei erstellt wer-den, die CSS-(Style)-Definitionen enthält und in allen Vorlagen Verwendung fin-det. Außerdem fehlen noch einige grundlegende Funktionen:

� Benutzerverwaltung und Absicherung gegen unbefugten Zugriff.

� Regalverwaltung. Es gibt zwar bereits eine Spalte shelf, aber diese wird nichtbenutzt. Für Benutzer könnte sich daran eine Suchfunktion anschließen, überdie die Bücher schnell gefunden werden.

� Fehlermanagement. Datenbank- und Bedienfehler führen meist zum Pro-grammabbruch

� Online-Zugriff. Die derzeitige Lösung ist praktisch nur intranet-tauglich. Füreine auch online betreibbare Bibliothek sind noch einige Ergänzungen(Benutzeranmeldung, Benutzerdienste, Sicherheitsfunktionen) einzubauen.

� Hilfen bei der Erfassung von Buchdaten.

Zumindest der letzte Punkt kann eine echte Erleichterung sein. Dazu finden Sieim nächsten Kapitel XML und Webservices ein entsprechendes Projekt, das denAmazon-Webservice nutzt, um vollständige bibliografische Daten zu einem Buchauf Grundlage der ISBN zu beschaffen. So ist auch der Katalog schnell gefüllt.

Nicht zuletzt kann auch die Umstellung auf ein kommerzielles Templatesystemwie phpTemple ein Schritt hin zu einer schnell erstellten und leicht zu wartendenLösung sein, weil hier beispielsweise die SQL-Abfragen unverändert übernommenwerden können. Auf PHP kann weitgehend verzichtet werden, weil einige Makro-funktionen – gesteuert über XML-Tags – die Arbeit erledigen.

546

Page 547: 382726314 X Php5 In 14 Tagen (Ddt)

Kontrollfragen

13.5 Kontrollfragen

1. Wozu werden Template-Systeme eingesetzt?

2. Erweitern Sie die Applikation, sodass zu jedem Leser der Bibliothek mehrere Tele-fonnummern gespeichert werden können. Wie gehen Sie vor?

3. Formulieren Sie eine SQL-Abfrage, die die nötigen Daten zur Erstellung vonMahnschreiben erzeugt, um säumige Leser auf die bereits abgelaufene Rück-gabefrist hinzuweisen.

547

Page 548: 382726314 X Php5 In 14 Tagen (Ddt)
Page 549: 382726314 X Php5 In 14 Tagen (Ddt)

XML und

Webservices

14

Page 550: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

XML ist inzwischen für viele Anwendungen eine fest etablierte Technologie. MitPHP5 fanden auch neue, vereinheitlichte Bibliotheken Verwendung, die die Ver-arbeitung und Erzeugung von XML noch einfacher machen.

14.1 Vorbemerkungen

Dieser Abschnitt klärt in knapper Form über einige Grundlagen auf, die Sie unbe-dingt beherrschen sollten, bevor Sie praktisch mit XML arbeiten.

XML

XML ist eine Auszeichnungssprache für Dokumente, die strukturierte Informa-tionen enthalten. Derartige Informationen enthalten beliebigen Inhalt, also Text,Bilder usw. und Angaben darüber, was dieser Inhalt bedeutet; eine Art Semantikdes Inhalts. In einem XML-Dokument wird eine Überschrift nicht deshalb zurÜberschrift, weil Sie in einer bestimmten Weise erscheint, sondern weil sie explizitals Überschrift markiert wurde. Überhaupt spielt die Formatierung keine Rolleund ist auch technisch nicht möglich. Erst das Ausgabegerät kann die Informa-tionen anhand ihrer Struktur in eine für den Menschen (oder andere Maschinen)sinnvoll formatierte Form überführen.

XML ist eine so genannte Markup-Sprache (XML = Extensible Markup Langu-age). Sie dient der Erstellung von Strukturen für Dokumente. XML ist ein Stan-dard, der die Art der Strukturierung beschreibt.

Was ist ein Dokument?

Die Anzahl der Applikationen, die XML bereits nutzen, unterstützen oder sogardarauf angewiesen sind, wächst drastisch. XML ist längst im Alltag angekommenund keineswegs mehr ein Exot unter den Datenformaten. Webprogrammierer, vorallem mit einem Hintergrund im grafischen Bereich, werden das vielleicht noch

550

nicht so empfunden haben. Wer jedoch mit Datenformaten hantiert, kommtinzwischen fast unweigerlich mit XML in Berührung. Allerdings ist XML kein All-heilmittel und auch kein Ersatz für relationale Datenbanken. Es ist eine sinnvolleErgänzung, die vor allem den systemübergreifenden Datenaustausch stark verein-facht.

Page 551: 382726314 X Php5 In 14 Tagen (Ddt)

Vorbemerkungen

XML-Dokumente sind die Basis dieses Datenaustauschs. Letztlich ist XML immerwieder eine Datei, gleich ob sie als solche im Dateisystem abgelegt oder als Teileiner HTTP-Anforderung gesendet wird. Der Sinn eines solchen Dokumentsergibt sich aus der gewählten Strukturbeschreibung. Ein XML-Dokument kanndeshalb Webseiten beschreiben (XHTML), Vektorgrafiken enthalten (SVG),mathematische Formeln darstellen (MathML) oder sogar Rechnungen beinhalten(EMC). Es gibt Tausende derartige vordefinierte Formate und weitere lassen sichjederzeit mit bestimmten Werkzeugen (und entsprechenden Fachkenntnissen)entwickeln. XML selbst ist also nur eine Art Metasprache, die eine Vorschrift zurDefinition von Datenformaten darstellt. XML-Dokumente entstehen, indem siedieser globalen Vorschrift genügen, egal ob die konkrete Struktur im Einzelfallsinnvoll ist oder nicht.

XML versus HTML

XML und HTML haben nur oberflächlich etwas gemeinsam. In HTML ist sowohlder Satz an Formatanweisungen als auch deren Semantik festgelegt. Das Tag <b>bezeichnet seinen Inhalt als fett und es wird erwartet, dass ein Ausgabegerät diesauch so darstellt. Das Tag <test> ist dagegen bedeutungslos und wird ignoriert.Zugleich wird ein Ausgabegerät (der Browser) definiert und damit das Verhaltender mit HTML markierten Dokumente fixiert.

XML ist universeller und legt solche Regeln nicht fest. Die Ähnlichkeit hat ihrenUrsprung in den gemeinsamen Wurzeln. HTML wurde auf der Basis der Urformaller Auszeichnungssprachen, SGML, entworfen und letztlich ist XML eine ver-einfachte Form von SGML. Daher rührt die Ähnlichkeit.

XML definiert niemals selbst eine Semantik oder ein Satz von Tags. Dies ist Auf-gabe der anwendenden Instanz. Ein Sinn aus einem XML-Dokument ergibt sicherst, wenn eine bestimmte, konkrete Auszeichnungssprache, den Regel von XMLfolgend, entworfen wurde. In diesem Abschnitt wird noch die Variante RSS vorge-stellt, die der Erstellung von Dokumenten dient, die Einträge in einem Weblogbeschreiben. Für HTML gibt es auch eine Entsprechung in XML, XHTMLgenannt.

551

Um nun die Semantik (den Sinn) in ein Dokument zu bekommen, benötigt maneine Instanz, die in der Lage ist, XML zu interpretieren. Einen Sinn kann man frei-lich nur erkennen, wenn man das Ziel der Darstellung kennt. Ohne ein Programm,das eine Weiterverarbeitung vornimmt, bleibt XML Selbstzweck. Deshalb kommtdiesem Verarbeitungsprozess, nicht zuletzt in PHP, eine große Bedeutung zu.

Page 552: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

Warum XML?

Aus der einleitenden Darstellung wird sich nicht zwingend ableiten lassen, warumXML nun eine so große Bedeutung zukommt. XML wurde für reichhaltig struktu-rierte Dokumente entworfen, die ein hohes Maß an Automation bei der Verarbei-tung erfordern.

Weder HTML noch SGML können dies in dieser exzessiven Form leisten. HTMLscheitert an der Vermischung von Struktur und Semantik, was die Nutzbarkeit ein-schränkt1. SGML ist universell, zugleich aber sehr viel komplizierter und mit vie-len Nebeneffekten durch die komplexe Syntax belegt. Programme, die SGML gutverarbeiten, sind sehr teuer. Viele Anwendungen benötigen eine derartige Kom-plexität nicht, weshalb sich SGML außerhalb der Druckindustrie nie durchgesetzthat.

XML ist einfacher, klar strukturiert und leicht zu lesen. Programme, die XML ver-arbeiten – so genannte Parser – sind leicht zu erstellen und zu perfektionieren.Praktisch existieren sie für alle Sprachen, Plattformen und Systeme in vollkommenausgereifter Form. Im Verbund mit der technischen Unterstützung wird XMLpraktisch einsetzbar – deshalb ist XML so wichtig.

Wie XML definiert ist

XML wird durch eine Reihe von Dokumenten definiert, die alle im Web zugäng-lich sind. Sie sind teilweise äußerst abstrakt und sehr technisch, im Alltag alsowenig hilfreich. Ein Blick hinein kann dennoch nicht schaden, um ein Gefühl fürden Hintergrund zu bekommen. Wer selbst XML-Formate entwerfen möchte,muss diese Dokumente tatsächlich verstanden haben. Hier folgt eine Auswahl:

� Extensible Markup Language (XML) 1.0 (http://www.w3.org/TR/WD-xml)Definiert die Syntax von XML, sozusagen die Mutter aller Dokumente.

� XML Pointer Language (XPointer, http://www.w3.org/TR/1998/WD-xptr-19980303) and XML Linking Language (Xlink, http://www.w3.org/TR/1998/WD-xlink-19980303)

552

Beide Standards definieren Wege, um Links (Verknüpfungen) zwischenDatenquellen zu definieren. Sie sind quasi der Ersatz des HTML-Tags <a> inder XML-Welt. XPointer beschreibt, wie eine Ressource adressiert wird (wo

1 Für seinen ureigenen Zweck, nämlich Webseiten, war dies jedoch sehr sinnvoll.

Page 553: 382726314 X Php5 In 14 Tagen (Ddt)

Vorbemerkungen

man sie findet), während XLink die Beziehungen zwischen zwei oder mehrRessourcen definiert.

� Extensible Style Language (XSL) (http://www.w3.org/TR/WD-xsl)Dieser Standard definiert die globale Methodik der Formatierung und Trans-formation (letzteres im wichtigen Teilstandard XSLT). Damit wird für XMLdie Brücke zur realen Welt der Ausgabegeräte geschaffen. Eine einführendeBeschreibung folgt im nächsten Abschnitt.

� XPath und XQueryFür die gezielte Auswahl von Teilstrukturen aus einem XML-Dokument dientdie Abfragesprache XPath. XQuery dient ähnlichen Zwecken und ist bei sehrdatenorientierten Strukturen von Vorteil, da es Ähnlichkeiten mit SQL auf-weist. XPath ist bereits seit einiger Zeit etabliert, XQuery noch in der Entwick-lung begriffen.

� DTD und SchemaVor der Struktur steht die Strukturdefinition. Eine DTD (Document Type Defi-nition) definiert, welche Tags wie von anderen abhängen. Leider ist die DTDselbst nicht in einem XML-Format definiert, was die automatisierte Verarbei-tung etwas erschwert. Das neuere XML Schema ist dagegen selbst ein XML-Dialekt. Im Gegensatz zur DTD weist es einen starke Unterstützung für Struk-turen auf, wie sie Datenbank nutzen. So sind beispielsweise viele elementareDatentypen definiert.

Für die Arbeit mit XML und PHP sollten Sie mit XML, XSLT und in Grundzügenmit XPath und DTD vertraut sein. Das ist mitnichten trivial, aber der Einsatz lohntsich.

XSLT

XSLT ist die Transformationssprache für XML-Dokumente. Mit Hilfe von XSLTkann beispielsweise ein Teil eines XML-Datenpaketes in HTML umgewandeltwerden, damit der Browser es anzeigen kann. XSLT ist nicht einfach, aberunglaublich mächtig.

553

Einführung

XSLT erlaubt eine weitgehend eigene Kontrolle der weiteren Verarbeitungs-schritte gegenüber dem, was fertige Programm anbieten. XSLT erlaubt es außer-

Page 554: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

dem, fertige Vokabulare einfach zu übernehmen und an eigene Bedingungenanzupassen. Damit wird der Datenaustausch weiter vereinfacht und verbessert.

Neben der Transformation von XML nach XML ist vor allem, die Umwandlungvon XML nach HTML und von XML nach Text von Bedeutung. XSLT erzeugt inder Regel eine frei formatierte Datei. Dies ist meist XML, muss jedoch nicht. AlsEingabe muss jedoch immer XML vorliegen. XSLT nutzt zur Auswahl von Knotendie Abfragesprache XPath, womit sich der Kreis zu den anderen Standards wiederschließt. Sie müssen also mindestens XPath und XSLT beherrschen, um praktischUmwandlungsprogramme schreiben zu können. PHP wird hierbei zur reinenHilfsschicht degradiert, es dient lediglich dem Laden der Dateien von der Fest-platte (oder von einem Server) und dem Rückschreiben der Ergebnisse.

Eine kompakte Einführung in XSLT

XSLT ist eine so genannte funktionale Programmiersprache. Das Prinzip unter-scheidet sich grundlegend von den objektorientierten oder imperativen Sprachen,wie beispielsweise PHP. Der Programmfluss selbst wird in erster Linie durch Auto-matismen initiiert, in zweiter Linie dann durch Regeln. Regeln definieren Sie, umbestimmte Effekte beim Auftreten von bestimmten Daten zu erreichen. Vorteilderartiger Systeme ist die weitgehende – bei XSLT per Definition die vollkom-mene – Befreiung von Seiteneffekten. Wenn eine Regel gilt, dann wird diese undnur diese ausgeführt und dies in immer der gleichen Art und Weise. Dazu gehörtauch, dass Variablen zwar verfügbar sind, beispielsweise um einer Regel einenWert zu übergeben, ihren Inhalt aber nachträglich nicht ändern können. Sie ver-halten sich also eher wie die Konstanten in PHP, abgesehen davon, dass der Inhaltdynamisch definiert werden kann. Nachträgliche Änderungen könnten Seitenef-fekte erzeugen, was nicht erlaubt ist.

Dennoch kann man damit erstaunlich effektiv programmieren und verblüffendeResultate erzielen. Nicht immer ist XSLT die perfekte Sprache. Richtig leistungs-fähig wird sie erst in Kombination mit einer modernen objektorientierten Sprache,die hinreichende imperative Merkmale aufweist. Es ist nahe liegend, Transforma-tion und Programm mit PHP in einen Kontext zu überführen. Zuvor sind jedochwenigstens elementare Kenntnisse von XSLT notwendig.

554

Page 555: 382726314 X Php5 In 14 Tagen (Ddt)

Vorbemerkungen

Die Basisregeln in XSLT

XSLT basiert auf XML, weshalb jede Datei durch die entsprechende Deklarationeingeleitet wird. Dann folgt das Wurzelelement <stylesheet>. Das W3C emp-fiehlt als Standardnamensraum xsl; diese Angabe ist aber im Prinzip freiwillig. Esist jedoch empfehlenswert, generell den Standardnamensraum zu verwenden.Daraus ergibt sich folgendes Grundgerüst für XSLT:

Listing 14.1: Ein leeres XSLT-Programm-Fragment

<?xml version="1.0" encoding="UTF-8" ?><xsl:stylesheet version="1.0" xmlns:<b>xsl</b>="http://www.w3.org/1999/XSL/Transform"><xsl:stylesheet>

Durch die Erweiterung des Attributes xmlns wird der Namensraumalias xsl festge-legt. Zwischen den Wurzelelementen wird nun das Regelwerk aufgebaut. Einezentrale Rolle spielt das Element <xsl:template>. Templates (dt. Vorlagen) bildendie Stufen der eigentlichen Transformation. Dabei gibt es zwei Arten von Templa-tes. Zum einen können sie durch eine XPath-Anweisung in ihrer Zuständigkeitprogrammiert werden. Die folgende Regel zeigt, wie jedes Element <name> zueiner Ausgabe im Ausgabedatenstrom führt:

<xsl:template match="name"> <h1>NAME</h1></xsl:template>

Eine andere Methode ist der Aufruf benannter Vorlagen, dazu später mehr. DerInhalt des Elements findet hier freilich noch keine Berücksichtigung. Text kann,wie gezeigt, direkt ausgegeben werden. Beachten Sie dabei, dass es sich auch hierum wohlgeformtes XML handeln muss; HTML muss also gegebenenfalls denRegeln von XHTML 1.0 entsprechend modifiziert werden.

Wenn Sie eine Vorlage mit <xsl:template select="regelname"> benennen, kön-nen Sie diese folgendermaßen aufrufen:

<xsl:call-template name="regelname"/>2

555

Soll explizit Text ausgegeben werden, der mit dem verwendeten XML-Editornicht darstellbar ist, muss das <xsl:text>-Element eingesetzt werden. Das isteigentlich – nach der Spezifikation – immer notwendig. Die direkte Angabe vonText oder Tags ist eine Vereinfachung.

2 Schreibt man XSLT mit einem einfachen Editor, trifft dies nur bedingt zu.

Page 556: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

<xsl:template match="name"> Hier folgt ein Zeilenumbruch: <xsl:text>0x0A</xsl:text></xsl:template>

Wo Text ist, sind Kommentare nicht weit. Diese entsprechen, XML-konform, denaus HTML bekannten und werden nicht in den Ausgabedatenstrom übernom-men:

<!-- Ein Kommentar in XSLT sieht aus wie in HTML -->

Vorlagen werden meist verschachtelt angewendet. Das folgende Beispiel zeigt dasGrundgerüst einer HTML-Seite, wie sie mit XSLT erzeugt wird:

<xsl:template match="/"> <html> <body> <xsl:apply-templates /> </body> </html></xsl:template>

Zuerst erkennt der XSLT-Prozessor hier, dass die Vorlage das Wurzelelement derXML-Quelle verarbeitet. Dann wird das Grundgerüst der HTML-Seite erstellt.Innerhalb des Body-Tags wird versucht, alle übrigen Elemente durch Aufruf derpassenden Vorlagen zu verarbeiten. Dass <xsl:apply-template> keine Parameterhat, ist ein spezieller Fall. Er setzt voraus, dass alle Elemente irgendwo auf einepassende Vorlage stoßen, wobei der Prozessor den besten Treffer auswählt und die-sen – und nur diesen – ausführt.

Allerdings besitzt der Prozessor eine Fallback-Funktion. Wenn kein Templatezutrifft, wird der Inhalt des aktuellen Tags genommen und als gültiger Ausgabe-wert betrachtet. Voraussetzung ist aber, dass wenigstens an einer Stelle<xsl:apply-template> steht, um die Ausgabe auszulösen.

Sollen Inhalte von Tags gezielt ausgegeben werden, was sicher der häufigste Wegist, findet <xsl:value-of> Verwendung. Das Attribut select wählt den Inhalt desdurch eine XPath-Anweisung ermittelten Knotens und die gesamte Anweisung gibtdiesen als Zeichenkette aus.

556

<xsl:template match="B"> <xsl:value-of select="."/></xsl:template>

Beim Einsatz innerhalb einer Vorlage bezieht sich der Pfad, den select akzeptiert,auf den übergebenen Knoten, ist also relativ. Sie können aber absolute Angaben

Page 557: 382726314 X Php5 In 14 Tagen (Ddt)

Vorbemerkungen

verwenden. Der allein stehende Punkt reflektiert in XPath den aktuellen Knoten,im Beispiel also den Inhalt des Tags <B>. Auf eben diesem Wege werden auchAttribute gelesen. Das folgende Beispiel sucht nach Elementen vom Typ <a> undgibt den Inhalt des Attributes href aus:

<xsl:template match="a"> <xsl:value-of select="@href"/></xsl:template>

Der direkte Zugriff mit einer absoluten XPath-Anweisung wäre a/@href. Sie kön-nen auf den Parameter eines Attributes auch direkt zugreifen. Ein <a href>-Tagwird folgendermaßen in <img src> transformiert:

<xsl:template match="a"> <img src="{@href}" /></xsl:template>

Die Schreibweise mit den geschweiften Klammern ist immer dann angebracht,wenn der Einsatz eines Tags aufgrund der Syntax nicht möglich ist. Andererseits istes mit <xsl:element> und <xsl:attribute> möglich, beliebige Tags indirekt zuerzeugen.

Mit XSLT programmieren

Bei XSLT spricht man von einer funktionalen Programmiersprache. Zum Pro-grammieren gehören jedoch nicht nur Regeln, wie sie in XSLT durch die Vorla-gen gebildet werden, sondern auch Programmanweisungen.

Zuerst soll eine einfache Verzweigung mit <xsl:if> vorgestellt werden:

<xsl:if test="@directory='hasfiles'"> Dieses Verzeichnis enthält Dateien</xsl:if>

Der Test kann verschiedene Operatoren und Funktionen verwenden, um Knotennach allerhand Kriterien zu untersuchen. Eine Else-Anweisung gibt es übrigensnicht, hierfür ist die Mehrfachverzweigung gedacht:

<xsl:choose>

557

<xsl:when test="attribute='archive'"> Archiv </xsl:when> <xsl:when test="attribute='compressed'"> Compressed

Page 558: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

</xsl:when> <xsl:when test="attribute='hidden'"> Hidden </xsl:when> <xsl:otherwise> Unknown </xsl:otherwise></xsl:choose>

Wollen Sie Listen von bestimmten Tags an einer Stelle ausgeben, ist <xsl:for-each> sehr praktisch, was ähnlich wie das foreach von PHP funktioniert. for-Schlei-fen im Sinne imperativer Programmierung gibt es jedoch nicht, weil veränderlicheZustände nicht erlaubt sind.

<xsl:for-each select="name"> <a href="{.}"><xsl:value-of select="."/></a><br/></xsl:for-each>

Die <xsl:for-each>-Anweisung gibt, wie <xsl:template> auch, jeweils einen aktu-ellen Knoten für jedes Element aus, das gefunden wurde. Deshalb funktioniertauch hier der verkürzte Zugriff auf den Inhalt mit dem Punkt-Alias.

Von Interesse ist oft auch eine Sortiermöglichkeit. Sie können dazu innerhalbeiner Schleife mit <xsl:sort> eine Anweisung platzieren, die das zuverlässig erle-digt:

<xsl:for-each select="name"> <xsl:sort select="." order="descending"/> <a href="{.}"><xsl:value-of select="."/></a><br/></xsl:for-each>

Das Element <xsl:sort> ist übrigens auch in <xsl:apply-templates> anwendbar.Es versteht freilich einige Attribute mehr, mit denen die Steuerung der Sortierungerfolgen kann.

Was Sie in XSLT nicht finden, sind die Anweisungen »for« und »while«. Beidebenötigen Variable, deren Zustand sich ändert. Das ist in funktionalen Sprachennicht möglich, weswegen die Anweisungen nicht sinnvoll sind. KomplexereSchleifen werden durch Rekursion (Selbstaufruf einer Vorlage) und Parameter

558

ermöglicht. Oft ist dies kompakter als vergleichsweise imperative Programme, fürPHP-Programmierer aber möglicherweise ungewohnt.

Variablen sind dennoch verwendbar, sie verhalten sich aber ähnlich den Konstan-ten in anderen Sprachen. Sie können Werte, Knoten oder Knotenbäume spei-chern:

Page 559: 382726314 X Php5 In 14 Tagen (Ddt)

Vorbemerkungen

<xsl:variable name="fieldata" select="attribute"/>

Variable können natürlich auch komplexere Inhalte aufnehmen:

<xsl:variable name="alldata"> <xsl:if test="position()=last()"> TRUE </xsl:if> <xsl:if test="position()=1"> FALSE </xsl:if> </xsl:variable>

Sie sehen im letzten Beispiel auch die Verwendung von XPath-Funktionen. Vari-ablen gelten nur innerhalb der Vorlage oder der Schleife, in der sie definiertwurden. Dieses Verhalten ist nicht modifizierbar, das heißt, es gibt keine Modifika-toren wie public oder private, wie sie bei den objektorientierten Funktionen vonPHP anwendbar sind.

Ähnlich wie Variablen werden auch Parameter verwendet. Damit können Sieeiner Vorlage verschiedene Werte übergeben, damit diese sich je nach Art des Auf-rufs unterschiedlich verhält. Der Aufruf sieht folgendermaßen aus (am Beispieleiner benannten Vorlage):

<xsl:call-template name="show.files"> <xsl:with-param name="handler">no</xsl:with-param></xsl:call-template>

Innerhalb der Vorlage werden die übergebenen Parameter dann so verwendet:

<xsl:template name="show.files"> <xsl:param name="handler" select=""/> ...

Das select-Attribut in <xsl:param> bestimmt einen Standardwert, wenn der Para-meter nicht übergeben wurde.

XSLT praktisch verwenden

559

XSLT verfügt über einige komplexere Anweisungen, die für größere Projekte vonBedeutung sind. Dazu gehört <xsl:number> zum Erzeugen fortlaufender Num-mern oder Buchstabenfolgen.

Oft ist auch der Umgang mit ganzen Knoten notwendig, statt dem Textinhalt desKnotens. Dann findet <xsl:copy-of> Verwendung. Sollen Knoten und Attribute

Page 560: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

kopiert werden, können mehrere Anweisungen mit <xsl:copy> zusammengefasstwerden.

Das folgende Beispiel kopiert ein XML-Dokument vollständig in ein anderes:

<xsl:template match="*"> <xsl:copy> <xsl:copy-of select="@*"> <xsl:apply-templates/> </xsl:copy></xsl:template>

Wenn Sie sich die Frage stellen, warum ein Dokument ausgerechnet mit XSLTunverändert kopiert werden sollte, ist ein Blick auf <xsl:output> angebracht. Mitdieser Anweisung kann die Kodierung des Ausgabestromes gesteuert werden. DasTag steht immer am Anfang des Dokumentes. Wenn Ihr XML-Dokument UTF-8kodiert ist, können Sie es mit der Kopiervorlage des letzten Beispiels leicht in einanderes Format bringen, nebenbei auch ins HTML 4.0-Format:

<xsl:output encoding="ISO-8859-1" method="html"/>

Sie können weitere XSLT-Dateien mit Hilfe der Anweisungen <xsl:output> und<xsl:include> importieren. Die erste Funktion kann ein Dokument so einfügen,als wäre der Inhalt an dieser Stelle geschrieben worden. Der Import hat einegeringe Priorität. Stehen vorhandene und importierte Regeln miteinander im Kon-flikt, unterliegen die importierten.

Die XSLT-Funktionen

Transformationen laufen oft in Abhängigkeit von konkreten Daten ab. XSLT stelltdeshalb einige elementare Funktionen zur Verfügung, die meist in select- und test-Attributen benutzt werden.

Funktion Beschreibung

boolean()number()

Typumwandlungen ausführen. Manche Funktionen brau-chen einen bestimmten Typ als Parameter.

560

string()

format_number() Formatiert Zahlen für die Ausgabe.

Tabelle 14.1: XSLT-Funktionen

Page 561: 382726314 X Php5 In 14 Tagen (Ddt)

Vorbemerkungen

ceiling()floor()round()

Zahlenberechnungen und Runden. Die Funktionsweise entspricht den Funktion ceil, floor und round in PHP.

concat() Verbindet Zeichenketten, ähnlich der Kombination mit dem Punkt-Operator in PHP.

contains()starts-with()

Ermittelt, ob eine Zeichenkette bestimmte Zeichen enthält bzw. ob sie mit einer bestimmten Zeichenfolge beginnt.

normalize_whitespace() Kontrolliert den Umgang mit Leerzeichen.

string-length() Ermittelt die Anzahl Zeichen einer Zeichenkette.

substring()substring-before() substring-after()

Ermittelt Teile einer Zeichenkette, entweder nach der Posi-tion oder in Abhängigkeit von einem Schlüsselzeichen

translate() Tauscht Zeichen aus.

count()sum()

Zählt Knoten und summiert Werte aus Knotenlisten.

generate-id()local-name()name()

Funktionen zum Umgang mit Knoten, der Erzeugung von Identifikatoren usw.

false()true()not()

Diese Funktionen können Boolesche Werte für Boolesche Ausdrücke beschaffen. Dies ist erforderlich, weil innerhalb von Ausdrücken kein Zugriff auf Schlüsselwörter (wie true) besteht.

current()last()position()

Bestimmt die Position des Zeigers in Knotenlisten .

document() Realisiert den Dateizugriff, lädt ein anderes Modul.

key() Einen Knoten mit bestimmten Eigenschaften finden.

Funktion Beschreibung

561

id()

element-available() Eine Parserfunktion suchen und mitteilen, ob der Parser diese unterstützt.

Tabelle 14.1: XSLT-Funktionen (Forts.)

Page 562: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

Die Funktionen finden auch bei der Knotenselektion mit XPath Verwendung.Mehr dazu im nächsten Abschnitt.

XPath

XPath ist eine Abfragesprache, die direkt oder über XSLT zum Einsatz kommt, umKnoten oder Knotenlisten zu ermitteln.

Daten mit XPath suchen

Mit Hilfe einer Transformation wurde im letzten Abschnitt bereits eine Auswahlaus XML-Daten vorgenommen. Immer dann, wenn auf Knoten oder Inhalte mitmatch="" oder select="" zugegriffen wurde, kam bereits XPath zum Einsatz. Dieseeinfache Form reicht in der Praxis nur selten aus. Der vollständige Name fürXPath lautet XML Path Language 1.0.

Um in der hierarchischen Struktur eines XML-Dokuments gezielt Knoten adres-sieren zu können, wird XPath eingesetzt. Ohne eine solche Sprache würden Doku-mente immer sequenziell durchlaufen werden, was wenig praxistauglich ist.Gerade bei Webanwendungen, die oft vielen Benutzern einen Ausschnitt auseiner großen Datenmenge zur Verfügung stellen, ist die schnelle Auswahl eminentwichtig. Die Abfrage durch Beschreibung eines Pfades und verzichtet dabei aufSchleifen oder andere zyklische Elemente. Damit ist die Konstruktion zur Laufzeitund in Abhängigkeit vom aktuellen Auftreten von Knoten möglich. Wie der Nameder Sprache andeutet, ähnelt die Auswahl von Knoten den Pfadangaben im Datei-system eines Betriebssystems. Das ist nahe liegend, weil auch dort Daten hierar-chisch angeordnet sind. Eine typische XPath-Anweisung könnte also folgenderma-ßen aussehen:

eintrag/name/vorname

Sie adressiert einen Knoten <vorname>, der Kind von <name> ist, was wiederumKind von <eintrag> sein muss:

<eintrag>

562

<name> <vorname>

Es gibt verschiedene Knotentypen in XML. XPath muss diese adressieren können.Konkret unterschieden werden die in der folgenden Tabelle dargestellten Typen:

Page 563: 382726314 X Php5 In 14 Tagen (Ddt)

Vorbemerkungen

Tabelle 14.2: Knotentypen, die mit XPath adressiert werden können

Grundlagen für die Entwicklung von Ausdrücken

XPath basiert auf Ausdrücken, die den Weg zu einem Knoten beschreiben. DerWeg kann – ebenso wie beim Dateisystem – durch absolute oder relative Pfadanga-ben beschrieben werden. Absolute Angaben beginnen immer an der Dokumenten-wurzel. Wenn der Ausdruck einen Pfad über mehrere Knoten hinweg beschreibt,werden die Elemente durch Schrägstriche getrennt:

dirlist/directory/file

Jedes dieser Elemente wird allgemein als Lokalisierungsschritt bezeichnet. Dieeben gezeigte und häufig verwendete Darstellung durch einen Knotennamen isteine verkürzte Form. Tatsächlich kann jeder Schritt aus drei Teilen bestehen:

1. Achsenbezeichner

2. Knotentest

3. Prädikate

XPath-Knoten Darstellung Kurzform

Wurzelknoten /

Elementknoten ElementName (keine Kurzform)

Kindknoten child:: (kann entfallen)

Attributknoten attribute::AttributName @AttributName

Textknoten self::node() .

Elternknoten parent::node() ..

Prozessinformation Nicht darstellbar

Namensraum alias:

563

Der Achsenbezeichner modifiziert die Auswahl des Knotens auf der Grundlageseiner Position im Baum. Als Trennzeichen zwischen dem Achsenbezeichner unddem nächsten Teil des Ausdrucks werden zwei Doppelpunkte geschrieben "::".Dies wird im nächsten Abschnitt noch weiter erläutert.

Page 564: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

Der Knotentest beschreibt den Knoten selbst, beispielsweise eine direkte Auswahldurch Nennung des Tagnamens. Die Prädikate stehen in eckigen Klammern undwerden meist zur Auswahl von Attributen verwendet. Insgesamt ergibt sich bei-spielsweise folgender Ausdruck:

child::directory[attribute::hasfiles='true']

child:: ist der Achsenbezeichner, hier wird also beginnend von der aktuellen Posi-tion das nächste Kindelement gesucht. Dann wird das Element selbst benannt:directory. Es wird also das nächste Kindelement mit dem Namen <directory>gesucht. Das Prädikat schränkt die Suche weiter ein; hier auf das Vorhandenseineines Attributes hasfile mit dem Parameter 'true'3.

Um solche Ausdrücke nun entwickeln zu können, ist in erster Linie eine Kenntnisder Achsenbezeichner notwendig.

Die XPath-Achsenbezeichner

Achsenbezeichner können einen oder mehrere Knoten auswählen. Die konkreteAuswahl hängt vom aktuellen Knoten ab. Wenn ein Dokument sequenziell durch-laufen wird, können die Bezeichner mehrere Knoten selektieren. Die folgendeTabelle zeigt alle Achsenbezeichner für Elemente auf einen Blick.

Elemente sind hier Tag-Namen, also keine Attribute und keine Namensraumbe-zeichnungen:

Achsenname Suchrichtung Beschreibung

self – Der aktuelle Knoten.

child vor Die Kinder des Knotens.

parent Die Eltern des Knotens.

descendant vor Alle Nachfahren (Kinder und Kindeskinder) .

descendant-or-self vor Alle Nachfahren und der Knoten selbst.

564

3 Die einfachen Anführungszeichen innerhalb der doppelten gehören bei XPath dazu.

ancestor rück Alle Vorfahren (Eltern und deren Eltern).

ancestor-or-self rück Alle Vorfahren und der Knoten selbst.

Page 565: 382726314 X Php5 In 14 Tagen (Ddt)

Vorbemerkungen

Tabelle 14.3: Achsenbezeichner für Element-Knoten

Neben den Achsenbezeichnern für Elemente gibt es noch zwei spezielle: attributezur Auswahl von Attributen und namespace zur Lokalisierung von Namensräu-men. Es bietet sich an dieser Stelle an, die Wirkung der Achsenbezeichner miteinem Testprogramm zu lernen. Damit alle erdenklichen Kombinationen auchgetestet werden können, wird eine XML-Datei entworfen, die entsprechende Ach-sen auch aufweist:

Listing 14.2: axischeck.xml – Testdatei zum Testen von Achsenzugriffen

<?xml version="1.0" encoding="iso-8859-1" ?><Start> <Ebene1_1></Ebene1_1> <Ebene1_2> <Ebene1_2_1></Ebene1_2_1> <Ebene1_2_2> <Ebene1_2_2_1></Ebene1_2_2_1> <Ebene1_2_2_2></Ebene1_2_2_2> </Ebene1_2_2> <Ebene1_2_3></Ebene1_2_3> <Ebene1_2_4></Ebene1_2_3> </Ebene1_2> <Ebene1_3></Ebene1_3> <Ebene1_4>

following vor Alle folgenden Knoten im Dokument, die nicht direkte Nachfahren sind.

following-sibling vor Alle folgenden Geschwister.

preceding rück Alle vorhergehenden Knoten, die nicht Eltern sind.

preceding-sibling rück Alle vorhergehenden Geschwister.

Achsenname Suchrichtung Beschreibung

565

<Ebene1_4_1></Ebene1_4_1> </Ebene1_4></Start>

Diese Datei dient im folgenden Beispiel als Basis für die ersten Programmierversu-che mit PHP und XPath. Sie ist so aufgebaut, dass aus den Elementnamen die

Page 566: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

Position im Baum ablesbar ist, was für das Verständnis der XPath-Abfragen sinnvollist.

Mit PHP5 XPath-Ausdrücke verarbeiten

Lesen Sie zunächst das folgende Listing:

Listing 14.3: xpathcheck.php – XPath-Achsenbezeichner ausprobieren

<?phpclass XPathCheck{ private $nodes; private $xp; private $expressions = array('self', 'child', 'parent', 'descendant', 'descendant-or-self', 'ancestor', 'ancestor-or-self', 'preceding', 'preceding-sibling', 'following', 'following-sibling'); private function PrintNodeList() { foreach ($this->nodes as $node) { printf('&lt;%s&gt;', ucfirst($node->tagName)); } } public function __construct($path) {

566

$dom = new DomDocument(); $dom->load($path); $this->xp = new DomXPath($dom); } public function ShowNodeList()

Page 567: 382726314 X Php5 In 14 Tagen (Ddt)

Vorbemerkungen

{ echo '<table width="400" border="1">'; echo '<caption>Element-Beziehungen, ausgehend von � "Ebene1_2"</caption>'; foreach ($this->expressions as $expression) { $this->nodes = � $this->xp->query("//Ebene1_2/$expression::*"); echo "<tr><td>$expression</td>"; echo "<td>"; $this->PrintNodeList(); echo "</td></tr>"; } echo '</table>'; }}$x = new XPathCheck('data/axischeck.xml');$x->ShowNodeList();?>

Führt man diese Schritte für alle XPath-Standardanweisungen aus, bezogen aufden Knoten <Ebene1_2> als Startknoten, ergibt sich folgende Abbildung:

Der Ausdruck selektiert lediglich die Achse (achsenbezeichner::*), wobei das *

Abbildung 14.1: Ausgabe der Kno-tenlisten für alle typischen Achsen-bezeichner

567

zur Auswahl aller Elemente der Achse führt. In der Praxis könnten sich hier Ele-mentnamen anschließen, um die Auswahl einzuschränken, sowie eines odermehrere Attribute, die auftreten oder bestimmte Parameter aufweisen müssen.

Page 568: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

Ein konkretes XML-Format: RSS

RSS dient dem Austauschen von Nachrichten und Inhalten über Nachrichten-seiten. Auch große Nachrichtendienste wie Wired oder Slashdot bieten Informa-tionen zur Übernahme auf anderen Seiten an. Eine private Seite kann diesdeutlich aufwerten. Das Prinzip ist dabei einfach. Der RSS-Feed (ein XML-Frag-ment) wird von der Spenderseite abgeholt. Enthalten ist der Nachrichtentext ineiner Art Zusammenfassung und ein Link zurück zum Spender, der zum Volltextführt. Davon profitieren beide Seiten. Der Spender erhält Zugriffe durch Nutzerfremder Seiten und der Empfänger hat ständig aktuelle Inhalte zu bieten, ohnedafür irgendwelchen Aufwand treiben zu müssen.

RSS kann bis zu einzelnen Nachrichten und Stichwörtern herunter gebrochenwerden, nur die letzten Änderungen oder Aktualisierungen enthalten oder auchzum Verfolgen von Einträgen in einer Änderungsliste dienen. Der Einsatzzweckist keineswegs auf Nachrichten beschränkt, die aus Sport oder Politik stammen,sondern kann ebenso die Informationen einer Serverüberwachung umfassen.

RSS-fähige Programme werden als Aggregatoren bezeichnet, das sind quasi Daten-sammler der Weblog-Gemeinschaft. Weblogs wiederum zeichnen sich selbst oftdurch die Fähigkeit aus, Inhalte im RSS-Format bereitstellen zu können. Auf dieseArt und Weise werden Weblogs gleichzeitig zu Spendern und Empfängern – einwilder Kreislauf aus Nachrichtenaustauschprogrammen entsteht.

Rückblick

RSS umfasst allerdings weit mehr als ein einfaches XML-Format zum Nachrich-tenaustausch. Ein Blick zurück ist hilfreich, um die Zusammenhänge zu verstehenund die richtige Strategie zur Programmierung eigener RSS-Programme zu fin-den.

Die erste Version war RSS 0.90, entworfen von Netscape als Format zum Aufbauvon Portalen, die sich die wichtigsten aktuellen Nachrichten von großen Nach-richtenanbietern holen. Diese Version war äußerst komplex, was einer schnellenVerbreitung eher abträglich ist. Daraus entstand in kurzer Zeit RSS 0.91, eine ver-

568

einfachte Ausgabe. Netscape verlor schnell das Interesse am Betrieb von Portalenund die Fa. UserLand Software übernahm die weitere Entwicklung als Basis ihrerkommerziellen Weblog-Produkte. In der Zwischenzeit spaltete sich eine weitere,nicht kommerzielle Gruppe ab und entwarf ein weiteres Format, basierend aufdem alten 0.90, bevor es die Vereinfachungen der 0.91 erfuhr. Dieses Format, sei-

Page 569: 382726314 X Php5 In 14 Tagen (Ddt)

Vorbemerkungen

nerseits mit dem ebenso sehr komplexen RDF verwandt, wurde als RSS 1.0bezeichnet. UserLand war in diesen Prozess nicht involviert worden und torpediertseitdem die Entwicklung mit einer Reihe eigener Versionen, die aus der 0.91-Reihe fortgeführt wurden. Auf diesem Wege entstanden RSS 0.92, RSS 0.93 undRSS 0.94. Weil die 1.0 bereits vergeben war, heißt die finale UserLand-VersionRSS 2.0. Womit das Chaos perfekt ist.

Welche Version man verwenden sollte

Es gibt mittlerweile sieben Versionen. Normalerweise macht eine derartige Versi-onspolitik ein Produkt nachhaltig kaputt. RSS hat sich dennoch rasend verbreitet,vermutlich aufgrund des anhaltenden Bedarfs am automatischen Informationsaus-tausch. Die folgende Tabelle hilft, die richtige Version zu wählen:

Version Ursprung Hinweis Status Empfehlung

0.90 Netscape Obsolet wegen 1.0

Nicht mehr verwenden.

0.91 UserLand Besonders einfach

Offiziell obso-let wegen 2.0, aber noch sehr populär

Leicht zu verwenden für einfa-che Ansprüche, interessant, wenn spätere Migration auf 2.0 geplant ist.

0.92, 0.93, 0.94

UserLand Umfassender als 0.91

Obsolet wegen 2.0

Nicht mehr verwenden, besser auf 2.0 setzen.

1.0 RSS-DEV Working Group

RDF-basiert, erweiterbar, offener Standard

Stabil, aktuell Wird verwendet, wenn auch mit RDF-Quellen gearbeitet wird.

2.0 UserLand 0.9X kompatibel, erweiterbar

Stabil, aktuell Generell empfehlenswert.

569

Tabelle 14.4: Übersicht über die RSS-Versionen

Page 570: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

Wie RSS praktisch aussieht

Für den Einsatz von RSS auf einer mit PHP programmierten Webseite brauchtman zuerst ein Programm, das RSS-Feeds anderer Seiten liest. Eigene Angebotesetzen immerhin auch eigene Nachrichten voraus, und die sind ungleich schwererzu produzieren. Das Lesen der Datenquelle ist einfach – sie ist unter einer spezifi-schen URL zu erreichen. Auf Webseiten kann man diese meist hinter dem Bild-chen zu finden.

Das folgende Beispiel stammt von xml.com und ist in RSS 0.91 erstellt:

Listing 14.4: Ein RSS 0.91-Feed, gefunden auf xml.com

<rss version="0.91"><channel>

<title>XML.com</title><link>http://www.xml.com/</link> <description>XML.com features a rich mix of information and services

for the XML community.</description><language>en-us</language><item>

<title>Normalizing XML, Part 2</title><link>

http://www.xml.com/pub/a/2002/12/04/normalizing.html </link>

<description>In this second and final look at applying relationalnormalization techniques to W3C XML Schema data modeling, WillProvost discusses when not to normalize, the scope of uniquenessand the fourth and fifth normal forms.</description>

</item><item>

<title>The .NET Schema Object Model</title><link>

http://www.xml.com/pub/a/2002/12/04/som.html </link>

<description>Priya Lakshminarayanan describes in detail the use ofthe .NET Schema Object Model for programmatic manipulation of W3C

570

XML Schemas.</description></item><item>

<title>SVG's Past and Promising Future</title><link>

Page 571: 382726314 X Php5 In 14 Tagen (Ddt)

Vorbemerkungen

http://www.xml.com/pub/a/2002/12/04/svg.html </link>

<description>In this month's SVG column, Antoine Quint looks back at SVG's journey through 2002 and looks forward to 2003.</description>

</item></channel>

</rss>

Die Datenquelle präsentiert einen Kanal (Thema), der einen Titel, den Linkzurück zur Quelle und eine Beschreibung enthält. Optional kann eine Spracheangegeben werden, gefolgt von einer Reihe konkreter Einträge, die jeweils wiedereinen Titel, den Rücklink und eine Beschreibung enthalten.

In RSS 1.0 sieht exakt derselbe Inhalt etwas anders aus:

Listing 14.5: RSS1.0-Feed, identisch mit dem Inhalt aus Listing 14.4

<rdf:RDFxmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"xmlns="http://purl.org/rss/1.0/"xmlns:dc="http://purl.org/dc/elements/1.1/"><channel rdf:about="http://www.xml.com/cs/xml/query/q/19">

<title>XML.com</title><link>http://www.xml.com/</link><description>XML.com features a rich mix of information and services

for the XML community.</description><language>en-us</language><items>

<rdf:Seq><rdf:li rdf:resource = �"http://www.xml.com/pub/a/2002/12/04/normalizing.html"/><rdf:li rdf:resource = �"http://www.xml.com/pub/a/2002/12/04/som.html"/><rdf:li rdf:resource = �"http://www.xml.com/pub/a/2002/12/04/svg.html"/>

</rdf:Seq></items>

571

</channel><item rdf:about = "http://www.xml.com/pub/a/2002/12/04/�normalizing.html"><title>Normalizing XML, Part 2</title><link>

http://www.xml.com/pub/a/2002/12/04/normalizing.html

Page 572: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

</link><description>In this second and final look at applying relational

normalization techniques to W3C XML Schema data modeling, Will Provost discusses when not to normalize, the scope of uniqueness and the fourth and fifth normal forms.</description>

<dc:creator>Will Provost</dc:creator><dc:date>2002-12-04</dc:date>

</item><item rdf:about =

"http://www.xml.com/pub/a/2002/12/04/som.html"><title>The .NET Schema Object Model</title><link>http://www.xml.com/pub/a/2002/12/04/som.html</link><description>Priya Lakshminarayanan describes in detail the use of

the .NET Schema Object Model for programmatic manipulation of W3C XML Schemas.</description>

<dc:creator>Priya Lakshminarayanan</dc:creator><dc:date>2002-12-04</dc:date>

</item><item rdf:about =

"http://www.xml.com/pub/a/2002/12/04/svg.html"><title>SVG's Past and Promising Future</title><link>http://www.xml.com/pub/a/2002/12/04/svg.html</link><description>In this month's SVG column, Antoine Quint looks back at SVG's journey through 2002 and looks forward to 2003.</description><dc:creator>Antoine Quint</dc:creator><dc:date>2002-12-04</dc:date>

</item></rdf:RDF>

Die Informationen sind hier ein wenig ausführlicher. So gehören auch der Namedes Autors und ein Datum dazu. Das Datum ist sehr sinnvoll, wenn man aus einergroßen Anzahl von Nachrichten nur die aktuellsten anzeigen möchte. Die Struk-tur ist dennoch weitgehend der früherer RSS-Versionen angepasst. Wer kompa-tible Software schreiben möchte, die mit Datenquellen beider Arten umgehenkann (das ist heute leider notwendig, weil beide Versionen gleichermaßen benutztwerden), kann sich mit folgenden »Umrechnungsregeln« zwischen 1.0 und 0.91behelfen (aus der Sicht von 1.0):

572

1. Das Stammelement ist rdf:RDF statt rss. Beim Lesen mit PHP ist es nicht zwin-gend erforderlich, dies zu prüfen – man kann der Quelle durchaus vertrauen.

2. RSS 1.0 verwendet den Namensraum http://purl.org/rss/1.0/ als Standard-namensraum. Die RDF-spezifischen Elemente sind durch den eigenen RDF-

Page 573: 382726314 X Php5 In 14 Tagen (Ddt)

Vorbemerkungen

Namensraum http://www.w3.org/1999/02/22-rdf-syntax-ns# abgetrennt. DieSpezifika von RDF kann man aber getrost ignorieren für einen einfachenNachrichtenleser. Metadaten (Autor, Datum) befinden sich im Namensraumdes so genannten Dublin Core4 http://purl.org/dc/elements/1.1/.

Wenn Sie nun mit einem Parser arbeiten, der Namensräume nur beschränktverarbeiten kann – wie SimpleXML in PHP5 – dann ignorieren Sie das ein-fach. Innerhalb eines vollständigen RDF-basierten RSS 1.0-Feed dürften Ele-mentnamenskonflikte kaum auftreten. Der blanke Leseprozess ist alsounkritisch. Erzeugen lassen sich RSS-Quellen damit freilich nicht. Sie müssenlediglich vertrauen, dass bei der Definition der Quelle die Standardaliase nichtverändert wurden. Ein Parser, der Namensräume nicht kennt, wird den Ele-mentnamen als dc:creator lesen, nicht als creator. Solange Sie von einem fixenAlias dc ausgehen, ist das in Ordnung. Tauscht das jemand gegen dublc aus,versagt das Skript. Programme mit vollständiger Namensraumunterstützungwürden eine solche Änderung tolerieren. Letztere können auch erkennen, wel-che Version benutzt wurde und ihre Strategie beim Verarbeiten anpassen.

3. Ein bedeutender Unterschied ist die Tatsache, dass das <item>-Element außer-halb des <channel>-Elements steht, bei RSS 0.91 war es genau umgekehrt. ZurVerarbeitung beider Versionen muss man damit zwangsläufig Teile des Codesdoppelt schreiben. Nebenbei bemerkt: RSS 0.90 (außerhalb) und RSS 2.0(innerhalb) zeugen von leichten Designzweifeln bei den Erfindern.

4. Das zusätzliche <item>-Element innerhalb des <channel>-Elements kann igno-riert werden. Es bedient lediglich native RDF-Parser.

RSS 2.0 wurde bereits mehrfach angesprochen. Auch hierfür ist ein Blick auf den-selben Inhalt im RSS 2.0-Format empfehlenswert:

<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">

<channel><title>XML.com</title><link>http://www.xml.com/</link><description>XML.com features a rich mix of information and services

for the XML community.</description>

573

<language>en-us</language><item>

<title>Normalizing XML, Part 2</title>

4 Der Name geht zurück auf die Dublin Core Metadata Initiative, eine Organisation, die Beschreibungsfor-mate für Metadaten von Dokumenten entwirft (siehe http://dublincore.org).

Page 574: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

<link> http://www.xml.com/pub/a/2002/12/04/normalizing.html </link>

<description>In this second and final look at applying relational normalization techniques to W3C XML Schema data modeling, Will Provost discusses when not to normalize, the scope of uniqueness and the fourth and fifth normal forms.</description><dc:creator>Will Provost</dc:creator><dc:date>2002-12-04</dc:date>

</item><item>

<title>The .NET Schema Object Model</title><link>

http://www.xml.com/pub/a/2002/12/04/som.html </link>

<description>Priya Lakshminarayanan describes in detail the use of the .NET Schema Object Model for programmatic manipulation of W3C XML Schemas.</description><dc:creator>Priya Lakshminarayanan</dc:creator><dc:date>2002-12-04</dc:date>

</item><item>

<title>SVG's Past and Promising Future</title><link>

http://www.xml.com/pub/a/2002/12/04/svg.html </link>

<description>In this month's SVG column, Antoine Quint looks back at SVG's journey through 2002 and looks forward to 2003.</description><dc:creator>Antoine Quint</dc:creator><dc:date>2002-12-04</dc:date>

</item></channel>

</rss>

RSS 2.0 verwendet Namenräume analog zu RSS 1.0, aber es wird kein RDF verwen-det. Ebenso wie bei RDF 0.91 gibt es keinen Standardnamensraum und die Inhalt

574

sind innerhalb des <channel>-Elements. Code, der flexibel mit RSS 0.91 und 1.0umgehen kann, wird mit nur geringem Aufwand auch RSS 2.0 lesen können.

Page 575: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung in die libxml2

14.2 Einführung in die libxml2

Generell ist XML ein Thema für Website-Entwickler, denn es gibt vielfältigeAnwendungen:

� Webservices (basieren weitgehend auf XML)

� Datenim- und -export aus und zu unbekannten oder inkompatiblen Daten-quellen

� Aufbereitung von XHTML für verschiedene Medien

� Bereitstellung oder Konsumierung von RSS-Kanälen

In PHP 4 war nur der SAX-Parser Expat fester Teil des Pakets. Alle anderen Erwei-terungen waren entweder nicht immer vorhanden oder noch im Entwicklungssta-dium (und sind es bis heute). Applikationen, die XML nutzen, waren damit etwaseingeschränkt, denn nicht alle Provider und Webhoster boten die nötige Unterstüt-zung. PHP DOM wurde zwar heiß diskutiert und oft als Wunderwaffe der XML-Entwickler gepriesen, dümpelte aber mangels Provider-Unterstützung eher vorsich hin. Wichtige Applikationen wie das Templatesystem phpTemple (http://www.phptemple.de) sind jedoch auf XML angewiesen, um hohe Performance auchbei anspruchsvollen Seiten zu ermöglichen.

Neu in PHP5

Anstatt des Sammelsuriums von teilweise unfertigen und inkompatiblen Biblio-theken (XSLT von Ginger Alliance, Expat, XML DOM) wurde nun auf das sehrweit entwickelten Projekt Gnome XML Parser libxml gesetzt. Die Vorgängerver-sion war bereits die Basis für die XML DOM-Erweiterungen. Vielleicht haben Siebereits mit den DOM-Erweiterungen gearbeitet und herausgefunden, dass diesevon den drei Bibliotheken der schwächste Teil waren. Dies als die herausragendeXML-Neuerung in PHP5 anzupreisen erscheint nicht einsichtig. Allerdings lag dieschlechte Qualität nicht an der verwendeten Bibliothek, sondern an den Mängelnder Integration in PHP. Dieses Problem wurde durch den Wechsel der Entwickler

575

und Verbesserungen in der Struktur der Programmierung beseitigt.

Die Gnome-Bibliothek liefert einen der schnellsten Parser, die es gibt. Sie ist rela-tiv jung und die Entwickler konnten von den Fehlern der anderen Parser lernen.Und nicht zuletzt werden viele in der XML-Welt dringend benötigte Standardsunterstützt:

Page 576: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

� SAX- und DOM-Programmierschnittstellen

� Validierung mit DTDs und XML Schema

� XSLT, XPath, XPointer und Xinclude

� Integrierte Parser für HTML und Docbook-XML

� XMLSec (XML Security Library)

Insgesamt gewinnt PHP dadurch deutlich und es lohnt sich unbedingt, sich mitder Bibliothek und den neuen Möglichkeiten intensiver auseinander zu setzen.Für viele Projekte gibt es dazu auch keine Alternative, weil die weitere Unterstüt-zung der alten Bibliotheken nicht geplant ist. Andererseits bleiben die Funktions-schnittstellen erhalten, um eine Abwärtskompatibilität zu gewährleisten. Es ist alsonicht zwingend erforderlich, alle XML-basierten Skripte nun umzuschreiben. Bisauf wenige Ausnahmen sollten alle Programme unverändert laufen. Es lohnt sichdennoch, den vorhandenen Code zu untersuchen, weil sich eventuell bessere oderschnellere Möglichkeiten bieten und der eine oder andere »Work-Around« oder»Dirty-Hack« sich nun sauber programmieren lässt.

Die neuen DOM-Funktionen

Die Kernfunktionen der DOM-Erweiterung sind inzwischen weitgehend stabil ausSicht der Entwicklung. Das ist sehr wichtig, weil ständige Änderungen an den Bib-liotheken den Einsatz in ernsthaften Projekten arg gefährden. Hier hatte PHP bis-lang das Nachsehen gegenüber .NET und Java, wo es eine sehr solide und langeZeit stabile Basisplattform gab und gibt.

HTML verarbeiten

Webseiten bestehen aus HTML und HTML ist verwandt mit XML. Es sollte alsomöglich sein, HTML mit einem XML-Parser zu verarbeiten. Weit gefehlt, denntatsächlich stammt HTML von der alten Auszeichnungssprache SGML ab, aus derauch XML entwickelt wurde. XML definiert eine strengere Syntax und erlaubt

576

damit schnellere und stabilere Parser. Prinzipiell kann also ein XML-Parser einfa-che HTML-Seiten nicht verarbeitet, bevor diese nicht »XML-tauglich« gemachtwerden. Auch dazu gibt es einen entsprechenden Standard: XHTML. In der Praxisist dies jedoch nicht immer einfach, weil man dazu spezielle Programme braucht,die die Umwandlung vornehmen – oder PHP5.

Page 577: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung in die libxml2

Das folgende Beispiel zeigt ein typisches, aus XML-Sicht nicht »wohlgeformtes«XML-Dokument:

Listing 14.6: XmlDomBadHtml.html – Ein einfaches HTML-Dokument, nicht wohlge-formt

<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><html><head><title>XML</title></head><body><h2>HTML mit DOM verarbeiten</h2><p> Diese Seite enthält nach XML-Regeln nicht wohlgeformte Tags.<br><form><select><option>Red<option selected>Blue<option>Green</select></form></body></html>

Dem neuen DOM-Parser ist dies ziemlich egal, er liest diese HTML-Seite pro-blemlos ein. Das folgende Skript verarbeitet die Seite und agiert dabei mit Knoten,Knotenauflistungen, Elementen und Attributen:

Listing 14.7: XmlDomHtml.php – Laden und Verarbeiten von nicht wohlgeformtem HTML

<?php$doc = new DomDocument();$doc->loadHtmlFile('data/XmlDomBadHtml.html');$head = $doc->getElementsByTagName('head');

577

$body = $doc->getElementsByTagName('body');function getTitle ($head){ foreach ( $head as $header ) { if ( $header->tagName == 'title' )

Page 578: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

echo ("<b>Page Title:</b> ".$header->textContent. � "<br />\n"); }}

function parseBody($body){ $bodyTag = $body->item(0); foreach ($bodyTag->childNodes as $element) { $content = htmlspecialchars( � utf8_decode($element->textContent)); switch ($element->tagName) { case 'h2': echo "<b>Header 2:</b> $content<br />\n"; break; case 'p': echo "<b>Paragraph:</b> $content<br />\n"; break; case 'form': foreach ($element->childNodes as $input) { if ($input->nodeType != XML_ELEMENT_NODE) continue; if ($input->tagName == 'select') parseSelect($input); } break; default: echo $content->tagName.'<br>'; break; } }}

function parseSelect($select){

578

echo "<b>Select:</b>\n<ul>\n"; $options = $select->childNodes; foreach ($options as $option) { $content = htmlspecialchars( �

Page 579: 382726314 X Php5 In 14 Tagen (Ddt)

Einführung in die libxml2

utf8_decode($option->textContent)); echo "<li>{$content}"; if ($option->hasAttribute('selected')) { echo ' <<< SELECTED'; } echo "</li>\n"; } echo "</ul>\n";}

getTitle($head);parseBody($body);?>

Die Nutzung der DOM-Klasse beginnt immer mit der Instanziierung des Objekts:

$doc = new DomDocument();

Dann müssen die zu verarbeitenden Daten beschafft werden. In diesem Beispielwird mit HTML gearbeitet, also wird die entsprechende Methode benutzt:

$doc->loadHtmlFile('data/XmlDomBadHtml.html');

Dann werden die Elemente <head> und <body> als Knotenlisten ermittelt:

$head = $doc->getElementsByTagName('head');$body = $doc->getElementsByTagName('body');

Es schließen sich drei Funktionen an, die Teile der Datei untersuchen. Beispiel-haft soll die Analyse des <body>-Tags näher vorgestellt werden. Da getElementsBy-TagName eine Knotenauflistung zurückgibt, im konkreten Fall aber bekannt ist, dasses nur ein Element geben kann, wird dieses gezielt ermittelt:

$bodyTag = $body->item(0);

Stattdessen könnte die Auflistung auch mit foreach durchlaufen werden. Das Ele-ment selbst (der Typ wäre hier DomElement) wird nun seinerseits mit foreachdurchlaufen, um alle Elemente der obersten Ebene zu erreichen:

foreach ($bodyTag->childNodes as $element)

579

Will man alle Elemente erreichen, muss die Funktion rekursiv programmiert wer-den.

Zu jedem Element wird nun der Inhalt ermittelt. Diese Funktion ist zwar nichtdem DOM-Standard entsprechend, ist aber eine recht brauchbare PHP-Erweite-

Page 580: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

rung. Zusätzlich ist noch darauf zu achten, dass das Dokument gegebenenfallsUTF-8-kodiert ist. UTF-8 ist das Standardzeichenformat für XML. Für die Aus-gabe im Beispiel wird außerdem noch HTML-Code sichtbar gemacht:

$content = htmlspecialchars(utf8_decode($element->textContent));

Die weitere Verarbeitung richtet sich nach dem gefundenen Elementnamen:

switch ($element->tagName)

Wenn man weitere Untersuchungen anstellt ist es wichtig zu wissen, welchen Typein Knoten hat. Denn die von der Knotenliste extrahierten Knoten sind nichtimmer Elemente, sondern können auch Kommentare, Zeichen, Namensräume,Prozessanweisungen usw. sein:

if ($input->nodeType != XML_ELEMENT_NODE) continue;

Informationen über die verwendbaren Konstanten finden Sie in derKurzreferenz am Ende des Kapitels.

In der Funktion parseSelect ist noch eine weitere Methode zu finden, die hier dazudient, die Existenz eines Attributes zu ermitteln:

if ($option->hasAttribute('selected'))

Die Abbildung zeigt, wie das Skript arbeitet:

Das DOM-Modul ist äußerst leistungsfähig und konnte hier nur oberflächlichangerissen werden. Für größere XML-Projekte ist es unbedingt zu empfehlen und

Abbildung 14.2: Verarbeitung einer HTML-Seite mit dem DOM-Modul

580

die hier gezeigten Techniken lassen sich in jeder Richtung ausbauen. InformierenSie sich in der Kurzreferenz über die verfügbaren Methoden und Eigenschaften,um einen schnellen Einstieg zu finden.

Page 581: 382726314 X Php5 In 14 Tagen (Ddt)

SimpleXML

14.3 SimpleXML

Neben der DOM-Erweiterung, die auf libxml basiert, fand eine zweite XML-Schnittstelle Eingang in PHP5: SimpleXML. Diese Bibliothek ist bekannt als»Object Mapping XML API«.

Unterschiede zur DOM-Schnittstelle

DOM (Document Object Model) erzeugt aus einem XML-Dokument einen voll-ständigen Baum von Objekten. Der Zugriff in einer Applikation ist sehr direktmöglich, dafür ist der Speicherverbrauch erheblich, weil immer die gesamte Struk-tur im Speicher steht. Ein vollständiges Abbild eines Dokuments als DOM istnicht trivial – auch in der Verarbeitung. SimpleXML nutzt ebenso eine objektori-entierte Sicht, vereinfacht diese jedoch drastisch. Statt der »offiziellen« DOM-API,die alle Programmiersprachen mit DOM-Unterstützung anbieten, überführt Simp-leXML die Daten direkt in eine einfache, an PHP angepasste Objektstruktur.

Als Ausgangspunkt für erste Versuche soll folgendes Dokument dienen:

Listing 14.8: data/dbconfig.xml – Eine einfache XML-Datei zum Testen

<?xml version="1.0"?> <config> <email>[email protected]</email> <database> <host>localhost</host> <type>MySQL</type> <user>dbuser</user> <pass>geheim</pass> <dbname>simpletest</dbname> </database> </config>

Ein Parser, der dies verarbeitet, könnten nun folgendermaßen aussehen:

581

Listing 14.9: SimpleXML1.php – Einfachste Verarbeitung von XML mit SimpleXML

<?php $xml = file_get_contents('data/dbconfig.xml'); $config = simplexml_load_string($xml);

Page 582: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

echo ('<pre>'); print_r($config); echo ('</pre>'); ?>

Die print_r-Funktion zeigt die vollständige Objektstruktur an, wie sie von Simple-XML erkannt wurde:

Das folgende Skript nutzt diese Struktur, um auf eine MySQL-Datenbank zuzu-greifen. Dazu werden die Daten aus der XML-Datei zur Konfiguration benutzt:

Listing 14.10: SimpleXMLConfig.php – Nutzung von XML zur Konfiguration

<?php$xml = file_get_contents('data/dbconfig.xml');$config = simplexml_load_string($xml);$db = $config->database;try{ if (!@mysql_connect($db->host,$db->user,$db->pass)) { throw new Exception('Database Connect Error'.mysql_error()); }}catch (Exception $e)

Abbildung 14.3: Objektstruktur eines XML-Dokuments

582

{ echo 'Error: ' . $e->GetMessage(); echo '<br/>'; echo 'Sende E-Mail an: ' . $config->email;}?>

Page 583: 382726314 X Php5 In 14 Tagen (Ddt)

SimpleXML

Wie der Name verspricht, ist SimpleXML tatsächlich sehr einfach. Parsen undNutzen ist praktisch unmittelbar miteinander verbunden und eignet sich hervorra-gend für kleinere Sequenzen, beispielsweise Konfigurationsdateien.

Ein Schritt weiter

XML ist großartig und äußerst vielseitig einsetzbar. Der erste Abschnitt zeigtebereits, dass es mit den neuen XML-Funktionen in PHP5 nicht schwer ist, damitumzugehen. PHP 4 verlangte hier einiges mehr an Programmierung stellte damiteine zu große Hürde dar, um auch einfache Problemstellungen mit XML zu lösen.

Prinzipiell gibt es drei Wege, um auf XML zuzugreifen. Die meisten Program-mierumgebungen nutzen alle drei, SAX, DOM und XSLT:

� SAX (Simple API for XML) bietet eine ereignisbasierte Verarbeitung und ver-langt, dass die Elemente manuell gelesen werden, indem das aktuell verarbei-tete auf einem Stapelspeicher abgelegt und von da wieder entnommen wird.

� DOM (Document Object Model) ist sehr flexibel und verständlich, verlangtjedoch reichlich Ressourcen. Außerdem ist der Aufwand bei sehr trivialen Auf-gabenstellungen recht hoch.

� XSLT (XSL Transformation) ist eine funktionale Programmiersprache zumTransformieren von XML in anderes XML oder in Text oder HTML. Es istsehr mächtig, aber auch sehr gewöhnungsbedürftig.

SimpleXML ist eine neue und allein stehende Erweiterung in PHP5 und dientdazu, den Zugriff auf XML-Dokumente zu erledigen. Dabei geht es in erster Liniedarum, die Daten in eine interne Datenstruktur zu übertragen, die sich besser wei-terverarbeiten lässt. Dazu gehören beispielsweise Arrays oder Datenbanken. XMLdient also keineswegs dazu, intern nur noch damit zu arbeiten, sondern soll vorallem den vollkommen plattformneutralen Datenaustausch gewährleisten. Neben-bei kann man seine Daten nun auch in einem der vielen XML-Editoren bearbei-ten, was recht bequem sein kann. Mit einem PHP-Array können eben nur sehrwenige andere Systeme irgendwas anfangen.

583

Besonders leistungsfähig ist SimpleXML, wenn man gezielt auf die Attribute einesElements oder dessen Inhalt zugreifen möchte. Andere, komplexere Zugriffsme-thoden verlangen andere Erweiterungen. Der Namensteil »Simple« kann hierwörtlich genommen werden. Damit wird die Erweiterung aber richtig praxistaug-lich, denn die Lernkurve ist verhältnismäßig flach.

Page 584: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

In diesem Abschnitt wird gezeigt, wie mit SimpleXML eine XML-Datei gelesenund verarbeitet wird. Dazu wird der Inhalt teilweise extrahiert und eine Abfragemit der Abfragesprache XPath realisiert. Als Beispielformat soll das beliebte RSSdienen, das bereits am Anfang des Kapitels kurz vorgestellt wurde. RSS wird zumDatenaustausch der beliebten Weblogs benutzt. Hier wird die einfachste Versionvon RSS verwendet, nicht die komplexere »Muttersprache« RDF, die einigesmehr an XML-Techniken nutzt und die meisten Anwendungen massiv überfor-dert. Wer mag, findet im Web reichlich Quellen zu RDF. Auf jeden Fall müssenSie mit XML-Namensräumen und XPath vertraut sein, um alle folgenden Skriptelesen zu können.

XML Lesen

Als Ausgangspunkt der ersten Versuche mit SimpleXML soll die folgende RSS-Datei dienen:

<?xml version="1.0" encoding="utf-8" ?><rss version="0.91"><channel> <title>PHP: Hypertext Preprocessor</title> <link>http://www.php.net/</link> <description> The PHP scripting language web site </description></channel><item> <title>PHP 5.0.0 Beta 4 Released</title> <link>http://www.php.net/downloads.php</link> <description>PHP 5.0 Beta 4 has been released. The third beta of PHP is also scheduled to be the last one (barring unexpected surprises).</description></item><item> <title>PHP Template Project</title> <link>http://www.phptemple.de</link> <description>

584

A design oriented template system for PHP is announced by Comzept Systemhaus GmbH. It compiles dynamic HTML into native PHP. </description></item></rss>

Page 585: 382726314 X Php5 In 14 Tagen (Ddt)

SimpleXML

Listing 14.11: rssfeed091.xml – Eine erste RSS-Datei als Datenbasis

Um mit dieser Datei arbeiten zu können, wird in SimpleXML ein entsprechendesObjekt erzeugt:

$rss = simplexml_load_file('/data/rssfeed091.xml');

Danach steht das Objekt in der Variablen $rss zur Verfügung, vorausgesetzt dieDatei liegt im Verzeichnis /data und heißt rssfeed091.xml. Diese Angaben entspre-chen den Daten auf der Buch-CD.

Auf die Elemente (Tags) kann nun mit der üblichen Objektsyntax zugegriffen wer-den, so als wären es Eigenschaften:

echo $rss->channel->title;

Das vollständige Skript sieht folgendermaßen aus:

Listing 14.12: SimpleXMLRss091.php – Einfachster Zugriff auf eine RSS-Quelle

<?php$rss = simplexml_load_file('data/rssfeed091.xml');echo "Channel <b>{$rss->channel->title}</b> ";echo "(See <a href=\"{$rss->channel->link}\">{$rss->channel->link}</a>)";?>

Mit der Musterdatei sollte nun folgendes ausgegeben werden:

Falls ein Element auf einer Stufe in der Hierarchie mehr als einmal vorkommt,legt PHP diese Daten in einem Array an. Im Beispiel finden Sie zwar nur ein Ele-ment <channel>, darunter aber zwei Element <item>. Der Zugriff darauf sieht dannfolgendermaßen aus:

Wie üblich kann zum Zugriff auf alle Elemente eines Arrays die Anweisungforeach zum Einsatz kommen:

Abbildung 14.4: Ausgabe der Channel-Daten einer RSS-Quelle

585

<?php$rss = simplexml_load_file('data/rssfeed091.xml');echo "Channel <b>{$rss->channel->title}</b> ";echo "(See <a href=\"{$rss->channel->link}\">{$rss->channel->link}</a>)";echo "<br/><br/>";

Page 586: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

foreach ($rss->item as $item){ echo "{$item->title} <br/>";}?>

Listing 14.13: SimpleXMLRss091b.php – Ausgabe mehrerer Elemente

Die folgende Abbildung zeigt, wie die Titel der RSS-Quelle erscheinen:

Nach den Elementen sind nun die Attribute an der Reihe. Diese stehen ebenfallsals Arrayelemente zur Verfügung und können über ihre Namen erreicht werden:

Listing 14.14: SimpleXMLRssVersion.php (Ausschnitt) – Ermittlung der Version aus dem Attribute version des Stammelements

echo $rss['version'];

Damit kann man in der Praxis schon eine Menge erreichen, denn Sie können nunXML lesen und mit den bereits bekannten Mitteln in PHP weiterverarbeiten. Dasreicht nicht immer aus, macht aber die ersten Schritte sehr einfach.

Andere XML-Funktionen, wie Kommentare oder Prozessanweisungen, lassen sichmit dieser Technik freilich nicht nutzen. Sie können diese Entitäten nicht errei-chen. Essentielle Informationen werden auf diesem Wege jedoch selten übermit-telt, sodass SimpleXML durchaus seine Daseinsberechtigung hat.

SimpleXML-Methoden

Der Zugriff auf Attribute gestaltet sich bei den Objekten der tiefer liegenden Struk-

Abbildung 14.5: Ausgabe aller Elemente einer Auflistung

586

tur ebenso. Hier können Sie jederzeit die Arraysyntax ansetzen. Wenn Sie dagegeneine Auflistung der Attribute benötigen, beispielsweise um mit foreach zugreifenzu können, nutzen Sie die Methode attributes():

Page 587: 382726314 X Php5 In 14 Tagen (Ddt)

SimpleXML

foreach($rss->item[0]->attributes() as $a => $b) { echo $a,'="',$b,"\"\n";}

Der erste Parameter ergibt dabei immer den Namen des Attributes, der zweite denInhalt.

Hat man komplexe Strukturen, sind Vereinfachungen hilfreich. Eine Methodehilft dabei, einen Zweig von Knoten gesondert weiterzuverarbeiten: children().Zurückgegeben wird ein Objekt, das ähnlich wie das Stammobjekt aufgebaut istund nur die Kindelemente enthält.

Selbstverständlich kann mit SimpleXml der Inhalt der Knoten geändert werden.Um die geänderte Version wieder schreiben zu können, ist die Methode AsXml()hilfreich. Die Methode gibt den Inhalt als Zeichenkette zurück. Die Speicherungin einer Datei müssen Sie dagegen selbst organisieren.

Hilfreich ist – für umfassendere XML-Zugriffe – das Laden von Objekten, diemit den regulären DOM-Erweiterungen erzeugt wurden. Die Methodesimplexml_import_dom dient diesem Zweck. Der Abschnitt über DOM zeigt dieAnwendung.

Komplexere Abfragen mit XPath gestalten

Der Zugriff auf eine tief liegende Ebene oder eine Parametrisierung misslingt mitdem direkten Objektzugriff meist oder wird so kompliziert, dass die ganze Verein-fachung, die erreicht wurde, wieder aufgebraucht ist. Will man mehr, kommtXPath zum Einsatz.

Der folgende Code findet alle Texte, die innerhalb aller <title>-Element stehen:

Listing 14.15: SimpleXMLXpath.php – Abfrage mit XPath

<?php$rss = simplexml_load_file('data/rssfeed091.xml');foreach ($rss->xpath('//title') as $title){

587

echo "$title <br />";}?>

Page 588: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

Die xpath-Methode durchsucht das SimpleXML-Objekt und gibt ein Array mitden Fundstellen zurück. Der Parameter enthält die XPath-Anweisung, die rechtkomplex werden kann. Die beiden Schrägstriche sagen: »Suche Elemente mitdem Namen title an jeder beliebigen Stelle im Dokument«. Im Beispiel heißtdas, dass sowohl die Titel des <channel> als auch der <item>-Elemente gefundenwerden.

Sollen dagegen nur die <title>-Elemente gefunden werden, die sich innerhalbvon <item> befinden, wäre folgende Anweisung einzusetzen:

//item/title

Spätestens hier wäre der Zugriff über das Objektmodell nur noch mit sehr viel Pro-grammierung zu erreichen und außerdem unglaublich langsam. XPath ist dagegensehr schnell.

XML-Namensräume

SimpleXML erledigt den XML-Zugriff leicht und einfach. Das Lesen von RSS 1.0-Quellen wird so für jeden beherrschbar. Allerdings benutzt RSS 1.0 auch XML-Namensräume, was die Sache wieder etwas komplizierter werden lässt. DaNamensräume generell eine herausragende Rolle in XML übernehmen, ist eineAuseinandersetzung damit unerlässlich.

Der Namensraum eines XML-Elements ist immer durch einen URL definiert. ImDokument wird aus Gründen der Lesbarkeit ein Alias benutzt, der dann zu folgen-den Elementformen führt:

<my:title>

my: ist dabei der Alias für einen Namensraum. Treffen mehrere XML-Dialekte auf-

Abbildung 14.6: Abfrage mit XPath: Schnell und flexibel

588

einander, kann man diese mit Hilfe der Namensräume auseinander halten. DenAlias wählt man im Zieldokument so, dass er konfliktfrei aufgelöst werden kann.Mit Hilfe von Namensräumen können Sie leicht zwischen dem RSS-Element<title> und dem HTML-Element mit demselben Namen unterscheiden.

Page 589: 382726314 X Php5 In 14 Tagen (Ddt)

SimpleXML

Aus Sicht des XML-Zugriffs mit SimpleXML werden die Dinge nun plötzlichkomplizierter. Denn ein Zugriff über das Objektmodell wird mit $rss->my:titlenicht gelingen. Wird nur ->title geschrieben, verfügt der Prozessor jedoch überkeine Informationen, welches <title>-Element denn nun gemeint ist. Schreibtman die vollständigen Namensräume mit auf, stehen nun zwei Arten von <title>-Elementen zur Auswahl:

{http://www.w3.org/1999/xhtml}:title{http://purl.org/rss/1.0}:title

Der gesamte Name wird als »qualifizierter Name« (qualified name) bezeichnet.Der Alias ändert nichts an der Bezeichnung. Wie bereits erwähnt, dient dies ledig-lich der Lesbarkeit. Es vereinfacht aber auch den Zugriff ein wenig. Es gibt keinekorrekten Aliasnamen im Sinne einer Vorgabe oder Norm, sondern lediglich Vor-schläge, welche Namen ohne den konfliktfrei gewählt werden sollten. VergessenSie jedoch nicht, dass der Alias primär der Konfliktauflösung dient und deshalbbewusst Änderungen unterliegt. Typische Aliase für die beiden erwähnten Namensind die folgenden:

<xhtml:title><rss:title>

Der URL, der den Namensraum definiert, führt nicht dazu, dass die ver-arbeitende Software online auf Informationen zugreift. Auch muss dieverlinkte Seite nicht wirklich existieren. Die Angabe sichert lediglich dieweltweite Eindeutigkeit und sollte deshalb den Namen der Firmen-Domain enthalten.

Um den Umgang von SimpleXML mit Namensräumen kennen zu lernen, wirddie bereits bekannte RSS-Datei mit einem solchen ausgestattet, was der Übergangzur Version 1.0 erledigt:

<?xml version="1.0" encoding="UTF-8"?><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns="http://purl.org/rss/1.0/"

589

><channel rdf:about="http://www.php.net/"> <title>PHP: Hypertext Preprocessor</title> <link>http://www.php.net/</link> <description>The PHP scripting language web

Page 590: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

site</description></channel><item rdf:about="http://www.php.net/downloads.php"> <title>PHP 5.0.0 Beta 3 Released</title> <link>http://www.php.net/downloads.php</link> <description> PHP 5.0 Beta 3 has been released. The third beta of PHP is also scheduled to be the last one (barring unexpected surprises). </description> <dc:date>2004-01-02</dc:date></item><item rdf:about="http://shiflett.org/archive/19"> <title>PHP Community Site Project Announced</title> <link>http://shiflett.org/archive/19</link> <description> Members of the PHP community are seeking volunteers to help develop the first web site that is created both by the community and for the community. </description> <dc:date>2003-12-18</dc:date></item></rdf:RDF>

Listing 14.16: rssfeed100.xml – RSS-Datei mit Namensräumen (explizite Aliase rdf und dc)

Dieses Dokument enthält drei Namensräume, davon sind im Kopf zwei explizitdefiniert und den Aliasnamen rdf und dc zugeordnet. Die folgende Syntax zeigt,wie der Alias definiert wird:

xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#

Die Definition wird immer als Attribut xmlns im Wurzelelement des den Namens-raum benutzenden Dokumentteils geschrieben. Man kann die Definition auch injedem einzelnen betroffenen Element wiederholen. Nach dem Doppelpunkt folgt

590

der gewählte Alias und danach der eigentlichen Namensraum als vollständigerURL.

Die Elemente <rdf:RDF>, <rdf:about> und <dc:date> zeigen die Anwendung.

Page 591: 382726314 X Php5 In 14 Tagen (Ddt)

SimpleXML

RDF ist die »Mutterdefinition« von RSS und steht für Resource Descrip-tion Format. Dies ist eine sehr komplexe Form der Beschreibung vonverlinkten Dokumenten. Unter http://www.w3.org/RDF/ finden Sie eineeingehende technische Beschreibung.

Eine Definition im gezeigten Dokument hat keinen Alias:

xmlns="http://purl.org/rss/1.0/"

Das ist der Standardnamensraum, der für alle Elemente gilt, die keinen Alias auf-weisen (der dritte im Bunde der oben erwähnten). RSS 1.0 verlangt diese Angabe,während die erste öffentliche Version 0.91 ausdrücklich keinen Namensraum ver-langt, was freilich zu praktischen Problemen beim Datenaustausch führen kann.Mehr dazu finden Sie im Abschnitt »Ein konkretes XML-Format: RSS« ab Seite568.

Namensräume mit SimpleXML verwenden

Die Suche von Elementen mit bestimmten Namensräumen verlangt nach einemneuen Satz von Methoden, über den SimpleXML nicht verfügt. In der Praxis ver-sagt die Anwendung dennoch nicht, denn SimpleXML ignoriert die Namens-räume einfach. Damit sind auch Dateien mit Namensraumaliasen lesbar,zumindest solange die interne Vereinfachung nicht zu Namenskonflikten führt.

XML Namensräume und XPath

SimpleXML ignoriert die Namensräume jedoch nicht völlig. Es existiert lediglichkeine dedizierte Syntax für den Zugriff. Mit XPath sieht es anders aus. Hier kannder Namensraum bei der Abfrage durchaus angegeben werden. Freilich ist dieskeine Leistung von SimpleXML, sondern vom intern verwendeten XPath-Modul.Damit die Übergabe der Namensräume auch mit dem Standardnamensraumfunktioniert, muss wenigstens dieser registriert werden. Dann funktioniert auch dieAuflösung der übrigen Namensräume, wie es das nächste Beispiel weiter untenzeigt.

591

Um alle Titel aus den Elementen <rss:title> zu finden, muss der Namensraum-Alias registriert werden. Der vollständige Namensraum kann beispielsweise folgen-dermaßen aussehen:

http://purl.org/rss/1.0/

Page 592: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

Der Alias ist frei wählbar, aber der RSS-Standard empfiehlt für das vorliegende Bei-spiel die Verwendung von »rss«. Die XPath-Abfrage sollte dann folgendermaßenaussehen:

//rss:item/rss:title

Leider gibt es keinen Weg, in XPath einen Standardnamensraum zu definieren,wie es in XML selbst möglich ist. Das ist in der Praxis aber nur selten problema-tisch. Wichtig ist es, an den Namensraum bei der Abfrage zu denken, auch wennim Dokument ein Standardnamensraum verwendet wird und der Alias nicht inallen Tags auftaucht.

Das folgende Listing zeigt die fertige Lösung:

Listing 14.17: simplens.php – Abfrage eines RSS-Feed mit Namensräumen

<?php$s = simplexml_load_file('data/rssfeed100.xml');$s->registerXPathNamespace('rss', 'http://purl.org/rss/1.0/');$titles = $s->xpath('//rss:item[ � starts-with(dc:date, "2004-01-")]/rss:title');foreach ($titles as $title) { print "$title\n";}?>

Die erste Zeile entspricht den letzten Beispielen. Dann folgt die Registrierung desNamensraums:

$s->registerXPathNamespace('rss', 'http://purl.org/rss/1.0/');

Die Abfrage selbst greift außerdem demonstrativ auf XSLT-Funktionen zurück,um die Ergebnisse einzuschränken. Die Funktion starts-with prüft, ob eine Zei-chenkette mit einer bestimmten Zeichenfolge beginnt. Der Alias dc deutet daraufhin, dass die Daten der Namensraumdefinition aus den Dublin Core Metadataentstammen, die Datumswerte spezifiziert.

Abbildung 14.7:

592

Auswahl des Titels aus dem RSS-Feed

Page 593: 382726314 X Php5 In 14 Tagen (Ddt)

SOAP-Webservices

Andere Funktionen

Wie bereits erwähnt, ist es möglich, Attribute und Elementinhalte auszutauschen,indem ein neuer Wert zugewiesen wird. Der geänderte Inhalt des Dokumentskann komplett als Zeichenkette zurückgegeben werden, wozu die MethodeAsXml() auf den Wurzelknoten angewendet wird.

Insgesamt ist SimpleXML ein feines und kleines Modul, das den Zugriff auf XMLstark vereinfacht und ohne tief gehende Kenntnisse leicht bedienbar ist. GroßeProjekte können davon weniger profitieren, weil die Einschränkungen program-miertechnisch kaum zum umschiffen sind. Hier können Sie jedoch mit dem zwei-ten XML-Modul, der libxml2, nahtlos ansetzen.

14.4 SOAP-Webservices

Webservices sind bereits seit einiger Zeit heftig umworben und immer wiederTitelthema von Entwicklerkonferenzen und Produktankündigungen. Eine breiteNutzung ist indes nicht auszumachen. Die ersten Anfänge sind trotzdem ermuti-gend und die technischen Vorteile sind so gravierend, dass eine Beschäftigung mitden Möglichkeiten unbedingt lohnt.

Grundlagen

PHP5 stellt mit dem SOAP-Erweiterungen ein integriertes Paket zur Verfügung,mit dem sich Webservices anbieten und nutzen lassen. Damit ist der Einsatz derarteinfach geworden, dass man sich deutlich mehr Gedanken um mögliche Anwen-dungen als um deren Realisierung machen muss.

Gedanken

Webservices sind im Grund genommen nur entfernte Methodenaufrufe. Auf

593

irgendeinem Server läuft ein Programm, das öffentliche Methoden zur Verfügungstellt. Werden diese – mit oder ohne Parameter – aufgerufen, gibt eine spezielleMethode Daten zurück oder führt Aktionen aus. Das Ganze nennt man danneinen Webservice (oder Web-Dienst oder XML-Webdienst), wenn sich der Anbie-ter des Dienstes an bestimmte Standards hält. Das ist sehr sinnvoll, denn damit öff-

Page 594: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

net er sich einer Palette von Clients, die denselben Standard benutzen.Prominente Beispiele solcher Anbieter sind Amazon und Google, deren Dienste»Bücher kaufen« und »Suchen« so anderen Servern zur Verfügung gestellt wer-den. Dabei umfasst die Definition »Webservice« bereits sehr viel, lediglich dieAusformulierung der Abfragen muss bekannt gemacht werden. Da entsprechendeClients für alle Betriebssysteme und Programmierumgebungen zur Verfügung ste-hen, muss sich der Anbieter keine Gedanken über die Nutzbarkeit seines Angebotsmachen. Entsprechend zahlreich sollten Anbieter und Nutzer sein – theoretisch.

In der Praxis setzen sich Webservices recht zögerlich durch. Vermutlich hat dieskeine technischen Ursachen, sondern deutet eher auf mentale Hemmnisse hin.Webservices sind primär für die Server-Server-Kommunikation geschaffen. Auchbei der Abwicklung von automatisierten Abfragen in der Server-Client-Kommuni-kation sind Webservices stark. Das massiv clientgeprägte Web mit einem nichtWebservice-tauglichen Browser steht dem natürlich entgegen. Es existierenschlicht keine Strukturen in Unternehmen und deren IT-Abteilungen, in der Kate-gorie »Dienst« zu denken. Dabei ist eigentlich alles ganz einfach.

Prinzipien

Die Kommunikation zwischen Server und Server oder zwischen Client und Serverbenutzt zwei bereits etablierte Techniken:

1. HTTP oder SMTP

HTTP und SMTP sind die möglichen Transportprotokolle, die die Datenübertragen. Meist kommt nur HTTP zum Einsatz. Das bedeutet, dass Aufrufevon Webservices problemlos Firewalls überwinden. Der Anbieter muss seinemassiven Sicherheitseinrichtungen also kein bisschen öffnen, was äußerstattraktiv ist.

SMTP ist kaum in Benutzung, würde jedoch auch interessant sein, weil es eineasynchrone Kommunikation zulässt. Man stelle sich vor, ein Client, beispiels-weise ein Handy, sendet eine Anfrage an einen solchen Dienst über die MMS-Funktion. Der Dienstanbieter setzt dies auf SMTP um und der Webservice-Anbieter beantwortet die Anforderung auf gleichem Wege. Der Dienstanbieter

594

erstellt aus der Antwort wiederum eine MMS und schon kann man billig inter-aktiv kommunizieren – ohne wie bei WAP ständig online zu sein. Liest hiereigentlich jemand von Vodafone mit?

Page 595: 382726314 X Php5 In 14 Tagen (Ddt)

SOAP-Webservices

2. XML

Die Nachrichten selbst – also die Parameterdaten einer Anfrage und die Ergeb-nisse – werden mittels SOAP (Simple Object Access Protocol) kodiert. Dies istein XML-Dialekt. Damit ist die Weiterverarbeitung schnell und einfach, dennXML-Parser stehen immer und überall zur Verfügung. Die Dienstinformatio-nen selbst, also die Namen der Methoden und die Datentypen der Parameterwerden über eine Informationsdatei im Format WSDL (Web Services Descrip-tion Language) angeboten, wobei es sich hier selbstverständlich wiederum umXML handelt.

Auf der Grundlage der Basistechnologien HTTP und XML ist es für Herstellernicht schwer, Webservices in Programmiersprachen und Betriebssysteme zu inte-grieren. Jeder neuere Website, die mit PHP5, Java oder ASP.NET in den letztendrei bis fünf Jahren entstanden ist, steht die Technologie praktisch zur Verfügung.

Technisch läuft das Ganze dann so ab, dass zuerst der Dienst selbst programmiertwird, meist eine Klasse mit einer Anzahl öffentlicher Methoden, die den Benut-zern bereitgestellt werden. Zu diesem Angebot wird dann die Beschreibungsdateiim WSDL-Format erstellt. Dann wird der SOAP-Server der Programmierumge-bung damit beauftragt, Anfragen anzunehmen, an die Methoden weiterzuleitenund deren Beantwortung zu überwachen. Die Außenkommunikation wird, wennsie über HTTP abgewickelt wird, vom Webserver erledigt, der selbst nichts vonSOAP oder XML wissen muss. Für ihn sind es lediglich HTTP-Anforderungen,deren Körper (Body) statt HTML-Seiten SOAP-Nachrichten enthält.

Technische Grundlagen

Um nicht völlig im Dunkeln zu tappen, was den technischen Ablauf betrifft, soll-ten Sie sich kurz mit den technischen Grundlagen vertraut machen. Die hier vor-liegende Beschreibung ist stark vereinfacht, reicht aber für das Verständnis derersten Beispiele aus.

An allererster Stelle steht immer SOAP. SOAP realisiert den eigentlichen Funk-tionsaufruf. Die SOAP-Nachricht besteht aus drei Teilen:

595

� Umschlag (SOAP Envelope)

� Kopf (SOAP Header)

� Inhalt (SOAP Body)

Page 596: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

Der Inhalt kann in einer Nachricht in mehrere Blöcke verteilt sein. In XML siehtdas Ganze dann folgendermaßen aus:

Listing 14.18: Rumpf einer SOAP-Nachricht (ohne Daten)

<SOAP-ENV:Envelope xmlns:SOAP-ENV=?http://schemas.xmlsoap.org/soap/envelope/?> <SOAP-ENV:Header> </SOAP-ENV:Header> <SOAP-ENV:Body> </SOAP-ENV:Body></SOAP-ENV:Envelope>

Zusätzlich zur eigenen Namensraumdefinition werden meist noch XML-Schemafür die Datentypen und Schema-Instance vereinbart. Als Namensraumaliase kom-men die Abkürzungen »xsd« und »xsi« zum Einsatz.

Was auch immer im Inhalt der SOAP-Body-Tags steht, hängt bereits von der kon-kreten Anwendung ab. Der Aufbau ist zwar sehr streng, die Benennung der Tagsund die Struktur variieren jedoch. Damit sich die beiden Kommunikationspartnerüber diese Struktur einig werden – und zwar automatisch – kommt WSDL zumEinsatz.

WSDL steht für Web Services Description Language. Dies ist eine vom Anbieter zuerstellende XML-Datei, die auf Anforderung des Clients gesendet wird. Sie mussgenaue Informationen über folgende Dienstmerkmale enthalten:

� Kommunikationsport, also den URL, unter dem der Dienst bereitsteht

� Bindung, also die Verknüpfung zwischen Dienst und Ein- und Ausgabeopera-tionen

� Porttypen, eine Definition der Kommunikationsrichtung der zulässigen Opera-tionen

� Nachrichten, die Namen der von den Ports angebotenen Methoden und derenParameter- und Rückgabedefinitionen

� Datentypen, also die konkret von einem Parameter oder Rückgabewert benutz-

596

ten Datentypen

Bei den Datentypen wird zur Beschreibung XML-Schema benutzt. Zulässig sindsowohl die in XML-Schema definierten elementaren Typen (string, integer) alsauch so genannte Komplextypen, die benutzerdefinierte Datensammlungenbeschreiben.

Page 597: 382726314 X Php5 In 14 Tagen (Ddt)

SOAP-Webservices

Mit diesen Informationen ist der Client nun in der Lage, die vom Server bereitge-stellten Methoden so umzusetzen, dass dem Programmierer tatsächlich eine Klassemit den ursprünglich definierten Methoden vorgehalten wird. Das ist die eigentli-che Faszination: Während auf der einen Seite der Dienst mit PHP und den SOAP-Erweiterungen benutzt wird, als Betriebssystem Unix läuft und als DatenbankMySQL benutzt wird, steht dem Client möglicherweise ein Windows XP und einin Access geschriebene Benutzeroberfläche zur Verfügung. Und wenn der Serverspäter auf das Gespann Java/Sun Solaris wechselt, sollte der Client davon nichtsmitbekommen. Das ist die schöne neue Welt der Webservices.

Alle Theorie ist jedoch langweilig, wenn man nichts praktisch unternimmt. Des-halb schließt dieses Kapitel mit zwei kleinen Projekten ab. Eines konsumiert einenim Internet öffentlich angebotenen Webservice, das andere bietet selbst einen sol-chen an.

Der Amazon-Webservice: ein Überblick

Dieser Abschnitt stellt die Amazon-Webservices kurz vor und gibt einen erstenEinblick in mögliche Anwendungen. Dies erhebt keinerlei Anspruch auf Vollstän-digkeit.

Die Definition der Dienste: WSDL

Die folgende WSDL-Datei stellt den Zustand und das Angebot des Dienstes Mitte2004 dar. Auf die Datei wird im folgenden Bezug genommen. Man kann ihrsowohl die Struktur der aufrufbaren Funktionen als auch die Datentypen der Para-meter und Rückgabewerte entnehmen:

Listing 14.19: WSDL-Datei des Amazon-Webservices (stark gekürzt)

<wsdl:definitions xmlns:typens="http://soap.amazon.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://

597

schemas.xmlsoap.org/wsdl/" targetNamespace="http://soap.amazon.com" name="AmazonSearch"> <wsdl:types> <xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema"

Page 598: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

targetNamespace="http://soap.amazon.com"> <xsd:complexType name="ProductInfo"> <xsd:all> <xsd:element name="TotalResults" type="xsd:string" minOccurs="0"/> <xsd:element name="TotalPages" type="xsd:string" minOccurs="0"/> <xsd:element name="ListName" type="xsd:string" minOccurs="0"/> <xsd:element name="Details" type="typens:DetailsArray" minOccurs="0"/> </xsd:all> </xsd:complexType> <xsd:complexType name="DetailsArray"> <xsd:complexContent> <xsd:restriction base="soapenc:Array"> <xsd:attribute ref="soapenc:arrayType" wsdl:arrayType="typens:Details[]"/> </xsd:restriction> </xsd:complexContent> </xsd:complexType> <xsd:complexType name="Details"> <xsd:all> <xsd:element name="Url" type="xsd:string" minOccurs="0"/> <xsd:element name="Asin" type="xsd:string" minOccurs="0"/> <xsd:element name="ProductName" type="xsd:string" minOccurs="0"/> <xsd:element name="Catalog" type="xsd:string" minOccurs="0"/> <xsd:element name="KeyPhrases" type="typens:KeyPhraseArray" minOccurs="0"/> <xsd:element name="Artists" type="typens:ArtistArray" minOccurs="0"/> <xsd:element name="Authors" type="typens:AuthorArray" minOccurs="0"/> <xsd:element name="Mpn" type="xsd:string" minOccurs="0"/> <xsd:element name="Starring" type="typens:StarringArray"

598

minOccurs="0"/> <xsd:element name="Directors" type="typens:DirectorArray" minOccurs="0"/> <xsd:element name="TheatricalReleaseDate" type="xsd:string" minOccurs="0"/>

Page 599: 382726314 X Php5 In 14 Tagen (Ddt)

SOAP-Webservices

<xsd:element name="ReleaseDate" type="xsd:string" minOccurs="0"/> <xsd:element name="Manufacturer" type="xsd:string" minOccurs="0"/> <xsd:element name="Distributor" type="xsd:string" minOccurs="0"/> <xsd:element name="ImageUrlSmall" type="xsd:string" minOccurs="0"/> <xsd:element name="ImageUrlMedium" type="xsd:string" minOccurs="0"/> <xsd:element name="ImageUrlLarge" type="xsd:string" minOccurs="0"/> <xsd:element name="ListPrice" type="xsd:string" minOccurs="0"/> <xsd:element name="OurPrice" type="xsd:string" minOccurs="0"/> <xsd:element name="UsedPrice" type="xsd:string" minOccurs="0"/> <xsd:element name="RefurbishedPrice" type="xsd:string" minOccurs="0"/> <xsd:element name="CollectiblePrice" type="xsd:string" minOccurs="0"/> <xsd:element name="ThirdPartyNewPrice" type="xsd:string" minOccurs="0"/> <xsd:element name="NumberOfOfferings" type="xsd:string" minOccurs="0"/> <xsd:element name="ThirdPartyNewCount" type="xsd:string" minOccurs="0"/> <xsd:element name="UsedCount" type="xsd:string" minOccurs="0"/> <xsd:element name="CollectibleCount" type="xsd:string" minOccurs="0"/> <xsd:element name="RefurbishedCount" type="xsd:string" minOccurs="0"/> <xsd:element name="ThirdPartyProductInfo" type="typens:ThirdPartyProductInfo" minOccurs="0"/>

599

<xsd:element name="SalesRank" type="xsd:string" minOccurs="0"/> <xsd:element name="BrowseList" type="typens:BrowseNodeArray" minOccurs="0"/> <xsd:element name="Media" type="xsd:string"

Page 600: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

minOccurs="0"/> <xsd:element name="ReadingLevel" type="xsd:string" minOccurs="0"/> <xsd:element name="NumberOfPages" type="xsd:string" minOccurs="0"/> <xsd:element name="NumberOfIssues" type="xsd:string" minOccurs="0"/> <xsd:element name="IssuesPerYear" type="xsd:string" minOccurs="0"/> <xsd:element name="SubscriptionLength" type="xsd:string" minOccurs="0"/> <xsd:element name="DeweyNumber" type="xsd:string" minOccurs="0"/> <xsd:element name="RunningTime" type="xsd:string" minOccurs="0"/> <xsd:element name="Publisher" type="xsd:string" minOccurs="0"/> <xsd:element name="NumMedia" type="xsd:string" minOccurs="0"/> <xsd:element name="Isbn" type="xsd:string" minOccurs="0"/> <xsd:element name="Features" type="typens:FeaturesArray" minOccurs="0"/> <xsd:element name="MpaaRating" type="xsd:string" minOccurs="0"/> <xsd:element name="EsrbRating" type="xsd:string" minOccurs="0"/> <xsd:element name="AgeGroup" type="xsd:string" minOccurs="0"/> <xsd:element name="Availability" type="xsd:string" minOccurs="0"/> <xsd:element name="Upc" type="xsd:string" minOccurs="0"/> <xsd:element name="Tracks" type="typens:TrackArray" minOccurs="0"/> <xsd:element name="Accessories" type="typens:AccessoryArray" minOccurs="0"/> <xsd:element name="Platforms" type="typens:PlatformArray" minOccurs="0"/>

600

<xsd:element name="Encoding" type="xsd:string" minOccurs="0"/> <xsd:element name="Reviews" type="typens:Reviews" minOccurs="0"/> <xsd:element name="SimilarProducts"

Page 601: 382726314 X Php5 In 14 Tagen (Ddt)

SOAP-Webservices

type="typens:SimilarProductsArray" minOccurs="0"/> <xsd:element name="Lists" type="typens:ListArray" minOccurs="0"/> <xsd:element name="Status" type="xsd:string" minOccurs="0"/> </xsd:all> </xsd:complexType> <xsd:complexType name="AsinRequest"> <xsd:all> <xsd:element name="asin" type="xsd:string"/> <xsd:element name="tag" type="xsd:string"/> <xsd:element name="type" type="xsd:string"/> <xsd:element name="devtag" type="xsd:string"/> <xsd:element name="offer" type="xsd:string" minOccurs="0"/> <xsd:element name="offerpage" type="xsd:string" minOccurs="0"/> <xsd:element name="locale" type="xsd:string" minOccurs="0"/> </xsd:all> </xsd:complexType> </xsd:schema> </wsdl:types> <message name="AsinSearchRequest"> <part name="AsinSearchRequest" type="typens:AsinRequest"/> </message> <message name="AsinSearchResponse"> <part name="return" type="typens:ProductInfo"/> </message> <portType name="AmazonSearchPort"> <operation name="AsinSearchRequest"> <input message="typens:AsinSearchRequest"/> <output message="typens:AsinSearchResponse"/> </operation> </portType> <binding name="AmazonSearchBinding" �

601

type="typens:AmazonSearchPort"> <soap:binding style="rpc" � transport="http://schemas.xmlsoap.org/soap/http"/> <!-- Binding for Amazon Web APIs - RPC, SOAP over HTTP --> <operation name="AsinSearchRequest">

Page 602: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

<soap:operation soapAction="http://soap.amazon.com"/> <input> <soap:body use="encoded" � encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"� namespace="http://soap.amazon.com"/> </input> <output> <soap:body use="encoded" � encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"� namespace="http://soap.amazon.com"/> </output> </operation> </binding> <service name="AmazonSearchService"> <!-- Endpoint for Amazon Web APIs --> <port name="AmazonSearchPort" binding="typens:AmazonSearchBinding"> <soap:address location="http://soap.amazon.com/onca/soap2"/> </port> </service></wsdl:definitions>

Lesen Sie diese Datei am besten von unten nach oben. In dieser Reihenfolgebauen nämlich die Informationen aufeinander auf. Beachten Sie auch, dass dieDaten, um hier überhaupt druckbar zu sein, sehr stark gekürzt wurden (auch wennes nicht so aussieht). Praktisch sind nur die Informationen enthalten, die im Fol-genden direkt benötigt werden – für die ISBN-Suche mit AsinSearchRequest. Aberwie liest man nun solch eine Datei?

Alles beginnt bei der Deklaration eines Dienstes und der Festlegung seinesNamens:

<service name="AmazonSearchService">

Der Dienst definiert einen Port und eine Bindung:

<port name="AmazonSearchPort" binding="typens:AmazonSearchBinding">

Die Bindung definiert die Namen der Funktionen (Operation genannt) und deren

602

Kodierung und Namensräume. Für AsinSearchRequest sieht das folgendermaßenaus:

<operation name="AsinSearchRequest">

Page 603: 382726314 X Php5 In 14 Tagen (Ddt)

SOAP-Webservices

Der Port-Typ definiert dieselbe Funktion und nennt dazu die konkrete Ein- undAusgabefunktion, die getrennt definiert werden:

<portType name="AmazonSearchPort">

Der SOAP-Client baut aus diesen Angaben dann einen normalen PHP-Funktions-aufruf zusammen. Der Port nennt für die in der Bindung vereinbarte Operationdie Namen der Funktionen für beide Transportrichtungen, input ist die Abfrage,output die Rückgabe:

<operation name="AsinSearchRequest"> <input message="typens:AsinSearchRequest"/> <output message="typens:AsinSearchResponse"/></operation>

Diese Funktionen wiederum erscheinen als Nachrichten und definieren dieDatentypen, die benutzt werden. Der Typ AsinRequest wird für die Anfragebenutzt:

<message name="AsinSearchRequest"> <part name="AsinSearchRequest" type="typens:AsinRequest"/></message>

Der Typ ProductInfo wird bei der Antwort verwendet:

<message name="AsinSearchResponse"> <part name="return" type="typens:ProductInfo"/></message>

Beides sind offensichtlich keine elementaren Typen, weshalb sich weiter oben dieTypdefinition im XML-Schema-Stil anschließt. Komplexe Typen setzen sich,gegebenenfalls über mehrere Schritte, aus einfachen Typen zusammen. Die Defi-nition eines komplexen Typs wird folgendermaßen eingeleitet:

<xsd:complexType name="AsinRequest">

Darunter folgen entweder weitere komplexe Typen oder – in letzter Konsequenz –elementare:

<xsd:element name="asin" type="xsd:string" />

Erscheint der Zusatz minOccurs="0", ist das Feld optional:

603

<xsd:element name="locale" type="xsd:string" minOccurs="0" />

Freilich kann hier auch eine bestimmte Mindest- oder Maximalanzahl festgelegtwerden. XML-Schema erlaubt eine sehr feine Definition von Datentypen.

Page 604: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

Ein Datentyp für die Antwort ProductInfo, Details, enthält beispielsweise einenVerweis auf DetailsArray:

<xsd:element name="Details" type="typens:DetailsArray" � minOccurs="0" />

Dieses ist als SOAP-Array (Achtung! Basisdatentyp) definiert, das Elemente desTyps Details (Achtung! benutzerdefinierter Komplextyp) enthält:

<xsd:attribute ref="soapenc:arrayType" � wsdl:arrayType="typens:Details[]" />

Details seinerseits enthält nun nur noch Basistypen (wie string), wie der folgendeAusschnitt zeigt:

<xsd:element name="Asin" type="xsd:string" minOccurs="0" />

Es können jedoch auch weitere komplexe Typen folgen, die andernorts in derWSDL-Datei definiert sind (möglicherweise aber nicht abgedruckt, weil sonst dashalbe Buch voll WSDL-Code wäre):

<xsd:element name="Authors" type="typens:AuthorArray" � minOccurs="0" />

Mit diesen Informationen ist der SOAP-Client nun in der Lage, die Daten für eineAnfrage zu erstellen und aus der Antwort die Daten wieder zu entnehmen. Einekorrekte Anfrage, wie sie das nachfolgend vorgestellte Beispiel erzeugt, sieht fol-gendermaßen aus:

Listing 14.20: Anfrage an Amazon: Aufruf der Funktion AsinSearchRequest

<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://soap.amazon.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body>

604

<ns1:AsinSearchRequest> <AsinSearchRequest xsi:type="ns1:AsinRequest"> <asin xsi:type="xsd:string">3827264553</asin> <tag xsi:type="xsd:string">activeserverpa0e</tag> <type xsi:type="xsd:string">heavy</type> <devtag xsi:type="xsd:string">XXXXXXXXXXX</devtag>

Page 605: 382726314 X Php5 In 14 Tagen (Ddt)

SOAP-Webservices

<locale xsi:type="xsd:string">de</locale> </AsinSearchRequest> </ns1:AsinSearchRequest> </SOAP-ENV:Body></SOAP-ENV:Envelope>

Man kann hier sehr gut erkennen, wie im Body der Nachricht die Anfrage getreuder Datentyp-Definition aufgebaut ist. Erkennbar ist aber auch, dass mehr als 50%der Daten keine realen Daten sind. Der so genannte Overhead einer SOAP-Ver-bindung ist bei vielen kurzen Anfragen erheblich.

Etwas umfassender fällt die Antwort aus (hier die »lite«-Version, ohne Kundenre-zensionen):

Listing 14.21: Eine SOAP-Antwort (auf exakt die zuvor gezeigte Anfrage)

<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:amazon="http://soap.amazon.com"> <SOAP-ENV:Body> <namesp57:AsinSearchRequestResponse xmlns:namesp57="http://soap.amazon.com"> <return xsi:type="amazon:ProductInfo"> <Details SOAP-ENC:arrayType="amazon:Details[1]" xsi:type="SOAP-ENC:Array"> <Details xsi:type="amazon:Details"> <Url xsi:type="xsd:string"> http://www.amazon.de/exec/obidos/ASIN/3827264553/ activeserverpa0e?dev-t=D3D53F5TPPE230%26 camp=2025%26link_code=sp1 </Url> <Asin xsi:type="xsd:string">3827264553</Asin>

605

<ProductName xsi:type="xsd:string"> .NET Windows Forms in 21 Tagen . Oberflächen programmieren </ProductName> <Catalog xsi:type="xsd:string">Book</Catalog> <Authors SOAP-ENC:arrayType="xsd:string[1]" xsi:type="SOAP-ENC:Array">

Page 606: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

<Author xsi:type="xsd:string">Chris Payne</Author> </Authors> <ReleaseDate xsi:type="xsd:string">15. Januar 2003 </ReleaseDate> <Manufacturer xsi:type="xsd:string">Markt+Technik </Manufacturer> <ImageUrlSmall xsi:type="xsd:string"> http://images-eu.amazon.com/images/P/3827264553.03.THUMBZZZ.jpg </ImageUrlSmall> <ImageUrlMedium xsi:type="xsd:string"> http://images-eu.amazon.com/images/P/3827264553.03.MZZZZZZZ.jpg </ImageUrlMedium> <ImageUrlLarge xsi:type="xsd:string"> http://images-eu.amazon.com/images/P/3827264553.03.LZZZZZZZ.jpg </ImageUrlLarge> <ListPrice xsi:type="xsd:string">EUR 46,68</ListPrice> <OurPrice xsi:type="xsd:string">EUR 49,95</OurPrice> <UsedPrice xsi:type="xsd:string">EUR 12,70</UsedPrice> </Details> </Details> </return> </namesp57:AsinSearchRequestResponse> </SOAP-ENV:Body></SOAP-ENV:Envelope>

Man kann gut erkennen, dass XML ideal für die Übertragung komplexer hierarchi-scher Daten geeignet ist. Die Rückübertragung nach PHP ist nun wieder Sachedes SOAP-Clients. Dieser erstellt übrigens ein Objekt daraus. Angeschaut mitvar_dump sieht es folgendermaßen aus (wieder exakt dieselbe Anfrage).

Der Zugriff auf die Daten ist nun recht einfach. Man muss lediglich wissen, wo dieDaten die man benötigt, in der Hierarchie stehen. Man muss außerdem natürlichauf Arrays, Indizes usw. achten, um gegebenenfalls mit Schleifen arbeiten zu kön-nen. Um das zu erfahren, ist jedoch das Lesen der Rohdaten und der WSDL-Dateien erforderlich, falls sich niemand die Mühe gemacht hat, eine umfassendeverbale Beschreibung mitzuliefern.

606

Page 607: 382726314 X Php5 In 14 Tagen (Ddt)

SOAP-Webservices

Webservices konsumieren

Um einen Webservice zu konsumieren, benötigen Sie zuerst einen solchen Dienstund dessen Dienstbeschreibung. Steht das bereit, erledigen PHP5 und dessenSOAP-Erweiterungen die ganze Arbeit.

Als Beispiel soll die im letzten Kapitel erstellte Bibliotheks-Anwendung erweitertwerden. Das Erfassen von möglicherweise hunderten Büchern ist recht mühevoll.Wenn der Server ohnehin online ist, bietet es sich an, die Daten von einem Buch-händler zu beschaffen. Dankbares Opfer wird in diesem Fall Amazon, da dort einentsprechender Dienst angeboten wird.

Die Daten beschaffen

Der Start beginnt auf der folgenden Website (ab hier in Englisch):

http://www.amazon.com/webservices

Abbildung 14.8: Das vom SOAP-Client aus der zuvor gezeigten Antwort erzeugte Objekt

607

Melden Sie sich zuerst bei Amazon an, um die kostenlose Zugangsberechtigungzu erhalten. Dazu sind lediglich eine E-Mail-Adresse und ein Kennwort erforder-lich, das selbst gewählt werden kann. Optional laden Sie dort zuerst des SDK (Ent-wicklerpaket) herunter. Das Paket hat den Namen kit.zip und ist auch auf der CDzum Buch unter /Bibliothek/SOAP zu finden (Version vom August 2004). Das

Page 608: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

SDK enthält Beispielprogramme, Hilfen und Hinweise, auch für PHP. Das enthal-tene Programm nutzt jedoch die für PHP 4 entwickelte Version, die auf demPEAR-Paket NuSOAP basiert. Die in diesem Buch gezeigte Version wurde dage-gen vom Autor explizit für PHP5 geschrieben und soweit vereinfacht, dass einschneller Einstieg gelingt.

Versuchsaufbau und Vorbereitung von PHP5

Die SOAP-Erweiterung in PHP5 steht unter Windows als php_soap.dll zur Verfü-gung, deren Aktivierung wie üblich in der php.ini erfolgt. Unter Linux wird PHP5mit dem Schalter --with-soap kompiliert. Ein Blick in die Anzeige der Funktionphpinfo zeigt, ob es geklappt hat:

Dann ist noch zu überprüfen, ob die Konfigurationsparameter in der Datei php.ini ein-getragen sind. Wenn der folgende Abschnitt nicht zu finden ist, fügen Sie ihn hinzu:

[soap]soap.wsdl_cache_enabled=1soap.wsdl_cache_dir="c:\windows\temp"soap.wsdl_cache_ttl=86400

Achten Sie auf den Pfad zum Verzeichnis temporärer Daten (das Beispiel gilt fürWindows).

Ziel des SOAP-Projekts

Ziel der Anwendung ist die Vereinfachung der Datenerfassung bei der Aufnahme

Abbildung 14.9: Die SOAP-Erweiterun-gen wurden erfolgreich aktiviert

608

von Büchern in die im vorigen Kapitel vorgestellte Bibliotheksverwaltung. Dabeisoll die Angabe der ISBN genügen, alle übrigen Daten sollen aus der Amazon-Datenbank geholt werden. Dazu gehört beispielsweise auch ein Bild des Buches,was sonst sicher nur mühevoll zu beschaffen wäre. Wie das am Ende aussehen soll,zeigt die Abbildung14.10.

Page 609: 382726314 X Php5 In 14 Tagen (Ddt)

SOAP-Webservices

Dies spart vor allem viel Zeit bei der Erfassung, vermeidet aber auch lästige Tipp-fehler. Die hier benutzten Daten stellen nur einen kleinen Teil der Möglichkeitendar, die Amazon bietet.

Erweiterung der Anwendung

Die Erweiterung der Anwendung umfasst nur zwei Schritte:

1. Aufbau eines Moduls, das den Webservice abfragt

2. Erweiterung der bisherigen Bibliotheksverwaltung

Das Modul wird natürlich PHP5-typisch als Klasse implementiert. Sie kann dannauch von anderen Anwendungen benutzt werden. Diese ist allerdings – im Gegen-

Abbildung 14.10: Erfassung von Büchern: Alle Daten außer der ISBN liefert der Webservice

609

satz zu vielen fertigen Lösungen im Internet – bewusst minimalistisch program-miert worden, um die mindestens notwendigen Funktionen demonstrieren zukönnen, ohne dabei erschlagend viele Code zu benutzen.

Page 610: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

Listing 14.22: soapclient.inc.php – Eine primitive Klasse zur Benutzung des Amazon-Webservice

<?php// Webserviceclass AmazonSearch{ private $Client = null; const LANG = 'de'; const MODE = 'heavy'; const TAG = 'activeserverpa0e'; const WSDL_FILE = 'http://soap.amazon.com/schemas2/AmazonWebServices.wsdl'; const TOKEN = 'IHR_AMAZON_TOKEN'; public function __construct() { try { $this->Client = new SoapClient(self::WSDL_FILE, array('trace' => 1)); } catch (SoapFault $ex) { die("Fehler beim Zugriff:&nbsp;{$ex->message()}"); } } public function SearchISBN($Keyword) { $params = array( 'asin' => $Keyword, 'tag' => self::TAG, 'type' => self::MODE, 'locale' => self::LANG, 'devtag' => self::TOKEN); try {

610

$result = $this->Client->AsinSearchRequest($params); } catch (Exception $ex) { die(($this->Client->__getLastResponse()));

Page 611: 382726314 X Php5 In 14 Tagen (Ddt)

SOAP-Webservices

} return isset($result) ? $result : ''; } }?>

Die Klasse definiert einige Informationen, die der Dienst benötigt, als Konstanten:

� const LANG = 'de'Die Sprache des Katalogs, der abgefragt werden soll. Wenn Sie deutscheBücher bearbeiten, muss die Sprache »de« sein.

� const MODE = 'heavy'Der Modus, hier kann »lite« für kurze Ergebnisse oder »heavy« für ausführli-che stehen. Untersuchen Sie zuerst »lite« und nutzen Sie »heavy« nur dann,wenn Sie die benötigten Angaben andernfalls nicht finden.

� const TAG = 'activeserverpa0e'Der Token eines Affiliate-Programms. Diese Angabe ist optional.

� const WSDL_FILE ='http://soap.amazon.com/schemas2/AmazonWebServices.wsdl'

Die Adresse, unter der der Webservice zur Verfügung steht.

� const TOKEN = 'IHR_AMAZON_TOKEN'Der Token, den Sie bei der Anmeldung von Amazon erhalten haben.

Zuerst wird immer eine Instanz des SOAP-Clients benötigt:

$this->Client = new SoapClient(self::WSDL_FILE, � array('trace' => 1));

Der zweite Parameter, array('trace' => 1), kann entfallen, wenn die Anwendungstabil läuft und der Zugriff auf die originalen SOAP-Nachrichten nicht mehr benö-tigt wird. Sie können Methoden wie __getLastResponse() nur benutzen, wenn mittrace die Speicherung der Daten eingeschaltet wurde. Dies ist allerdings mit mehrSpeicher und damit höherer Serverbelastung verbunden und sollte deshalb nurwährend der Aufbauphase benutzt werden.

Die eigentliche Abfrage wird in der Methode searchISBN ausgeführt:

611

public function SearchISBN($Keyword)

Dort wird zunächst ein Array aus den Parametern zusammengestellt. Die Namender Schlüsselfelder müssen den Anforderungen in der WSDL-Datei entsprechen,andernfalls reagiert der Amazon-Server mit einer Fehlermeldung. Stehen alleDaten bereit, erfolgt die Abfrage:

Page 612: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

$result = $this->Client->AsinSearchRequest($params);

Der Name AsinSearchRequest ist wiederum in der WSDL-Datei als möglicheDienstfunktion festgelegt. Der Einbau in einen try-Block fängt Fehler ab. Da essich hier um eine Internetverbindung handelt, kann diese natürlich fehlschlagenund dann hilft der Block bei der Fehlerverarbeitung. Wenn Sie außerhalb wiedermit try/catch arbeiten, sollten Sie mögliche Fehler mit throw new weiterreichenund gegebenenfalls eigene Fehlerklassen für Ihre Zwecke entwerfen. Im Beispielist die Fehlerverwaltung dem angesprochenen minimalistischen Prinzip zumOpfer gefallen. Für andere Fehler ersetzen Sie SoapFault durch Exception:

catch (SoapFault $ex)

Am Ende sind die ermittelten Daten nur noch zurückzugeben:

return isset($result) ? $result : '';

Die hier zurückgegebenen Daten sind ein Objekt, das in seinen Eigenschaftenweitere Objekte und Arrays enthält. Der Aufbau entspricht der Struktur der in derWSDL-Datei für die spezifische Anfrage verwendeten Rückgabedaten.

Einbau in das Bibliotheksskript

Der Einbau in das Bibliotheksskript ist relativ einfach. Die Funktionalität wurdedabei etwas erweitert und in der Vorlage musste Platz für die zusätzlichen Datengeschaffen werden. Zuerst ein Blick auf die erweiterte Vorlage:

Listing 14.23: buecher.html: Die Vorlage zur Organisation der Buchdatenbank

<html><body><h1>Bibliotheksverwaltung</h1><a href="index.php">Startseite</a>|<a href="index.php?TEMPLATE=ausleihe">Ausleihe</a>|<a href="index.php?TEMPLATE=kunden">Kundenverwaltung</a><h2>B&uuml;cherverwaltung</h2>

612

<h3>Informationen</h3><table border="1" width="600"> <caption style="font-weight:bold">B&uuml;cherliste</caption> <tr> <th>

Page 613: 382726314 X Php5 In 14 Tagen (Ddt)

SOAP-Webservices

Anzahl </th> <th> ISBN </th> <th> Titel </th> <th> Aktion </th> </tr> <!-- #REPEAT:books --> <tr> <td> {$books:number} </td> <td nowrap> {$books:isbn} </td> <td> {$books:title} </td> <td> <a href="index.php?TEMPLATE=buecher&amp;id={$books:id}&amp; action=show">Details</a> <a href="index.php?TEMPLATE=buecher&amp;id={$books:id}&amp; action=del">L&ouml;schen</a> </td> </tr> <!-- #ENDREPEAT:books --></table><a href="index.php?TEMPLATE=buecher&amp;action=new">� Neues Buch erfassen</a><h3>&Auml;ndern&nbsp;/&nbsp;Erfassen</h3><form action="index.php?TEMPLATE=buecher" method="post"><input type="hidden" name="action" value="{$detail_action}" />

613

<input type="hidden" name="id" value="{$detail_id}" /><table border="1" width="600"> <caption style="font-weight:bold">Buchdaten</caption> <tr> <td>

Page 614: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

Titel: </td> <td> <input type="text" name="book_title" � value="{$detail_title}" size="40"/> </td> <td rowspan="7" valign="top" width="250"> <!-- #IF:detail_publisher --> Verlag:&nbsp;{$detail_publisher} <!-- #ENDIF:detail_publisher --> <br/><br/> <!-- #IF:detail_image --> <img src="{$detail_image}"/> <!-- #ENDIF:detail_image --> </td> </tr> <tr> <td> Untertitel: </td> <td> <input type="text" name="book_subtitle" � value="{$detail_subtitle}" size="40"/> </td> </tr> <tr> <td> Autor: </td> <td> <input type="text" name="book_author" � value="{$detail_author}"/> </td> </tr> <tr> <td> ISBN:

614

</td> <td> <input type="text" name="book_isbn" value="{$detail_isbn}"/> </td> </tr>

Page 615: 382726314 X Php5 In 14 Tagen (Ddt)

SOAP-Webservices

<tr> <td> Anzahl </td> <td> <input type="text" name="book_number" � value="{$detail_number}" size="4"/> </td> </tr> <tr> <td> Ausgeliehen </td> <td> <input type="text" name="book_lend" value="{$detail_lend}" � size="4" disabled/> </td> </tr> <tr> <td> </td> <td> <input type="submit" value="{$detail_actiontext}" /> </td> </tr></table></form></body></html>

Neu – gegenüber der im letzten Kapitel vorgestellten Variante – ist die Ausgabeder Zusatzdaten:

<td rowspan="7" valign="top" width="250"> <!-- #IF:detail_publisher --> Verlag:&nbsp;{$detail_publisher} <!-- #ENDIF:detail_publisher --> <br/><br/>

615

<!-- #IF:detail_image --> <img src="{$detail_image}"/> <!-- #ENDIF:detail_image --> </td>

Page 616: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

Dies ist optional, weil die Daten nicht zwingend vorliegen. Deshalb erfolgt derEinbau in IF-Abschnitte.

Der PHP-Code zu dieser Vorlage ist ebenfalls nur wenig umfassender als die letzteVersion:

Listing 14.24: buecher.php – Steuerung der Vorlage mit allen Datenbankzugriffen

<?phpinclude('database.inc.php');include('soapclient.inc.php');//$detail_action = 'change';$detail_actiontext = '&Auml;ndern'; if ($_SERVER['REQUEST_METHOD']=='GET' && isset($_GET['action'])){ switch ($_GET['action']) { case 'new': $detail_action = 'new'; $detail_actiontext = 'Erfassen'; break; case 'del': $mysqli->query("DELETE FROM books � WHERE id = {$_GET['id']}"); break; }}if ($_SERVER['REQUEST_METHOD']=='POST' && isset($_POST['action'])){ switch ($_POST['action']) { case 'new': if ((empty($_POST['book_author']) � || empty($_POST['book_title'])) � && isset($_POST['book_isbn'])) {

616

$asin = str_replace('-', '', $_POST['book_isbn']); $ws = new AmazonSearch(); $data = $ws->SearchISBN($asin); $detail_subtitle = $_POST['book_subtitle']; $detail_author �

Page 617: 382726314 X Php5 In 14 Tagen (Ddt)

SOAP-Webservices

= utf8_decode($data->Details[0]->Authors[0]); $detail_isbn = $_POST['book_isbn']; $detail_title � = utf8_decode($data->Details[0]->ProductName); $detail_image � = utf8_decode($data->Details[0]->ImageUrlMedium); $detail_publisher � = utf8_decode($data->Details[0]->Manufacturer); } else { $detail_subtitle = $_POST['book_subtitle']; $detail_author = $_POST['book_author']; $detail_isbn = $_POST['book_isbn']; $detail_title = $_POST['book_title']; } $detail_number = empty($_POST['book_number']) ? 1 :� (int) $_POST['book_number']; $mysqli->query("INSERT INTO books � (title, subtitle, author, isbn, number)� VALUES ('{$detail_title}', � '{$detail_subtitle}',� '{$detail_author}', � '{$detail_isbn}',� {$detail_number} )"); break; case 'change': $mysqli->query("UPDATE books SET � title='{$_POST['book_title']}', � subtitle='{$_POST['book_subtitle']}', � author='{$_POST['book_author']}', � isbn='{$_POST['book_isbn']}', � number={$_POST['book_number']}, � WHERE id = {$_POST['id']}"); break; }

617

}// Liste fuellen$query = $mysqli->query("SELECT * FROM books");while($arr = $query->fetch_assoc()){

Page 618: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

$books[] = $arr; }// Details abrufen, wenn gefordertif (isset($_GET['id'])){ $query = $mysqli->query("SELECT *, (SELECT COUNT(*) � FROM lendout � WHERE books_id = b.id) AS lend � FROM books b � WHERE id = {$_GET['id']}"); $detail = $query->fetch_assoc(); $detail_id = $detail['id']; $detail_title = $detail['title']; $detail_subtitle= $detail['subtitle']; $detail_author = $detail['author']; $detail_number = $detail['number']; $detail_isbn = $detail['isbn']; $detail_lend = $detail['lend']; }?>

Hier sollen nur die Aspekte erläutert werden, die gegenüber der letzten Versionneu sind. Zuerst muss natürlich das SOAP-Modul eingebunden werden:

include('soapclient.inc.php');

Die Abfrage der Amazon-Datenbank erfolgt, wenn ein neues Buch erfasst wurdeund keine Daten außer der ISBN-Nummer im Formular angegeben wurden:

if ((empty($_POST['book_author']) � || empty($_POST['book_title'])) && isset($_POST['book_isbn']))

Für die Abfrage müssen die Trennzeichen aus der ISBN entfernt werden, andern-falls funktioniert AsinSearchRequest nicht:

$asin = str_replace('-', '', $_POST['book_isbn']);

Dann wird eine Instanz der SOAP-Klasse erstellt:

$ws = new AmazonSearch();

618

In diesem Augenblick wird bereits die WSDL-Datei gelesen. Da die Speicherzeitim Cache 1 Tag beträgt, muss man beim ersten Abruf und nach jedem weiterenTag hier mit einer etwas längeren Wartezeit rechnen. Die Größe beträgt ca.52 KByte.

Page 619: 382726314 X Php5 In 14 Tagen (Ddt)

SOAP-Webservices

Dann erfolgt die Abfrage, dies ist immer »life«, benötigt eine Internetverbindungund dauert fast immer eine oder mehrere Sekunden (auch bei DSL):

$data = $ws->SearchISBN($asin);

Die Daten werden nun entsprechend der erwarteten Struktur entnommen, bei-spielsweise der erste Autor:

$detail_author = utf8_decode($data->Details[0]->Authors[0]);

Beachten Sie hier, dass die Daten UTF-8-kodiert sind. Dies ist XML-Standard.PHP liefert die passende Funktion zum Dekodieren mit: utf8_decode.

Der Aufruf $data->Details[0]->Authors[0] sollte Ihnen keine Rätsel bereiten:

$data Das Datenobjekt ->Details Die Eigenschaft Details (ergibt ein Array) [0] Das erste Element des Arrays, dies ist wieder

ein Objekt, diesmal vom Typ Details ->Authors Darin ist eine Eigenschaft Authors definiert [0] Die Eigenschaft enthält ein Array, dessen erstes

Element benutzt wird

Das das erste Element direkt benutzt werden kann, ist der WSDL-Beschreibungoder dem Antwortpaket zu entnehmen. Der Datentyp für einen Autor ist »string«,also eine simple Zeichenkette. Die übrigen Abrufe folgen demselben Schema,wobei beispielsweise der Titel keine Unterteilung in ein Array mehr besitzt:

utf8_decode($data->Details[0]->ProductName)

Fazit

Damit ist eigentlich alles gesagt. Die Amazon-Webservices sind äußerst komplexund für jeden Entwickler eine Herausforderung. Es lohnt sich jedoch, denn mankann mit den Affiliate-Programmen oder den zShops Geld verdienen. Der automa-tisierte Zugriff erlaubt einen personalarmen Betrieb eines solchen Shops unddamit werden auch kleine Einnahmen attraktiv, weil sie nicht sofort wieder vonKosten aufgefressen werden. Wer übrigens eine Bibliothek betreibt, der kann

619

direkt Kosten sparen, denn er muss nun künftig eingehende Bücher, die ohne elek-tronische Datensätze ankommen, nicht mehr von Hand erfassen. Mit Hilfe eineskleinen, preiswerten Barcode-Scanners lässt sich die ISBN vom Buchrückenabnehmen und schon sind die kompletten Daten von Amazon geholt und in derDatenbank. Was will man mehr?

Page 620: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

14.5 Referenz

Die Referenz zeigt die in diesem Kapitel behandelten XML-Module SimpleXMLund DOM.

Referenz SimpleXML

Funktion Beschreibung

simplexml_import_dom Die Funktion importiert ein SimpleXML-Objekt aus einem DOM-Knoten und gibt das SimpleXML-Objekt zurück.

simplexml_export_dom Exportiert ein SimpleXML-Objekt als DOM-Knoten.

simplexml_load_file Lädt eine XML-Datei und gibt ein SimpleXML-Objekt zurück.

simplexml_load_string Lädt XML aus einer Zeichenkette und gibt ein Simple-XML-Objekt zurück.

Tabelle 14.5: Funktionen, die SimpleXML-Objekte erzeugen oder verarbeiten

Methode Beschreibung

AsXML Exportiert den Inhalt als XML-Zeichenkette.

attributes Die Liste der Attribute eines Elements.

children Die Aufzählung der Kindelemente eines Elements.

xpath Führt eine XPath-Abfrage aus.

registerXPathNamespace Registriert einen Namensraumalias für die Verwendung in XPath-Ausdrücken

Tabelle 14.6: Methoden des SimpleXML-Objekts

620

Page 621: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz

Referenz DOM

Klasse Bedeutung

DomAttr Repräsentiert ein Attribut.

DomCData Repräsentiert einen CDATA-Abschnitt.

DomComment Repräsentiert einen Kommentar.

DomDocument Die Basisklasse, das Dokument selbst.

DomDocumentType Die DOCTYPE-Deklaration des Dokuments.

DomElement Repräsentiert ein Element.

DomEntity Repräsentiert eine Entität, beispielsweise &amp;.

DomEntityReference Repräsentiert eine Verweis-Entität (auf eine externe Datei).

DomNode Allgemeine Knotenklasse (kann alles außer DomDocument sein).

DomProcessingInstruction Repräsentiert eine Prozessanweisung, beispielsweise <?id?>

DomText Frei stehender Text zwischen Elementen.

DomXPath Erlaubt Zugriff per XPath-Abfrage auf das Dokument.

Tabelle 14.7: Liste der Klassen, die im DOM-Modul definiert sind

Methode Bedeutung

createAttributeNS Erzeugt ein Attribut mit Namensraum.

createAttribute Erzeugt ein Attribut.

createElementNS Erzeugt ein Element mit Namensraum.

621

createElement Erzeugt ein neues Element.

createTextNode Erzeugt einen Textknoten.

Tabelle 14.8: Methoden der Klasse DomDocument

Page 622: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

createDocumentFragment Erzeugt ein Fragment neuer Knoten.

createComment Erzeugt einen Kommentarknoten <!-- .. -->.

createCDATASection Erzeugt einen CDATA-Abschnitt.

createProcessingInstruction Erzeugt eine Prozessanweisung.

createAttribute Erzeugt ein Attribut.

createEntityReferenz Erzeugt einen Verweis.

getElementsByTagName Gibt eine Liste aller Elemente mit dem gegebenen Namen zurück.

getElementsByTagNameNS Gibt eine Liste aller Elemente mit dem gegebenen Namen in einem bestimmten Namensraum zurück.

getElementById Gibt ein Element mit einem bestimmten ID-Attribut zurück.

renameNode Benennt einen Knoten um.

load Lädt ein Dokument aus einem Stream (beispielsweise eine Datei).

save Speichert das Dokument.

loadXML Lädt XML aus einer Zeichenkette.

saveXML Speichert XML als Zeichenkette.

loadHTML Lädt eine HTML-Datei.

saveHTML Speichert eine HTML-Datei.

validate Prüft gegen eine DTD.

validateSchema Prüft gegen ein Schema (XSD).

validateRelaxNG Prüft gegen ein RelaxNG-Schema.

Methode Bedeutung

622

insertBefore Fügt vor einem anderen Knoten ein.

replaceChild Ersetzt ein Kindelement.

Tabelle 14.8: Methoden der Klasse DomDocument (Forts.)

Page 623: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz

Die Methode getElementById gibt Element-Objekte vom Typ DomElement zurück.

Die Methoden getElementsByTagName und getElementsByTagNameNS geben Auflis-tungen mit Elementen des Typs DomNode zurück (interne Klasse DomNodeList).

removeChild Entfernt ein Kindelement.

appendChild Hängt ein Kindelement an.

hasChildNodes Prüft, ob Kindelemente vorhanden sind.

cloneNode Klont einen Knoten.

hasAttributes Ermittelt, ob ein Element Attribute hat.

isSameNode Vergleicht zwei Knoten auf Identität.

isDefaultNamespace Prüft, ob der Knoten im Standardnamensraum ist.

isEqualNode Vergleicht zwei Knoten auf Gleichheit.

Methode Bedeutung

getAttribute Ermittelt ein Attribut.

setAttribute Erzeugt und definiert ein Attribut.

removeAttribute Entfernt das Attribut.

getAttributeNS Ermittelt ein Attribut in einem bestimmten Namensraum.

setAttributeNS Erzeugt und definiert ein Attribut in einem bestimmten Namens-raum.

removeAttributeNS Entfernt das Attribut in einem bestimmten Namensraum.

getAttributeNode Ermittelt das Attribut als Objekt vom Typ DomAttr.

Methode Bedeutung

Tabelle 14.8: Methoden der Klasse DomDocument (Forts.)

623

setAttributeNode Setzt ein Attribut auf Basis eines DomAttr-Objekts.

setIdAttribute Setzt das Attribut mit den Namen ID.

Tabelle 14.9: Methoden der Klasse DomElement

Page 624: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

Die Methode getAttributeNode gibt ein Objekt vom Typ DomAttr zurück.

setIdAttributeNS Setzt das Attribut mit den Namen ID in einem bestimmten Namensraum.

isSameNode Vergleicht zwei Attribute auf Identität.

isDefaultNamespace Prüft, ob der Attribute im Standardnamensraum ist.

isEqualNode Vergleicht zwei Attribute auf Gleichheit.

Eigenschaft Bedeutung

textContent Inhalt des Elements als Text (ohne Kindelemente).

nodeType Siehe DomNode. Ist bei Elementen immer XML_ELEMENT_NODE.

childNodes Auflistung von Kindelementen als DomNodeList, eine aufzählbare Liste von DomNode-Objekten.

tagName Name des Elements

Tabelle 14.10: Eigenschaften der Klasse DomElement

Methode Bedeutung

isId Das Attribut ist ein ID-Attribut.

insertBefore Fügt ein Attribut vor dem aktuellen ein.

isSameNode Vergleicht zwei Attribute auf Identität.

isDefaultNamespace Prüft, ob das Attribut im Standardnamensraum ist.

isEqualNode Vergleicht zwei Attribute auf Gleichheit.

Methode Bedeutung

Tabelle 14.9: Methoden der Klasse DomElement (Forts.)

624

Tabelle 14.11: Methoden der Klasse DomAttribute

Page 625: 382726314 X Php5 In 14 Tagen (Ddt)

Referenz

Eigenschaft Bedeutung

value Wert des Attributes.

name Name des Attributes.

specified Gibt TRUE zurück, wenn das Attribut seinen aktuellen Wert aus dem Dokument erhielt und nicht erst durch spätere Änderung.

ownerElement Gibt das DomElement zurück, zu dem dieses Attribut gehört.

Tabelle 14.12: Methoden der Klasse DomAttribute

Name der Konstanten Bedeutung

XML_ELEMENT_NODE Element

XML_ATTRIBUTE_NODE Attribut

XML_TEXT_NODE Textknoten

XML_CDATA_SECTION_NODE CDATA-Abschnitt

XML_ENTITY_REF_NODE Entity-Verweis

XML_ENTITY_NODE Entity-Knoten

XML_PI_NODE Prozessanweisung

XML_COMMENT_NODE Kommentar

XML_DOCUMENT_NODE Dokument

XML_DOCUMENT_TYPE_NODE Document Type Deklaration

XML_DOCUMENT_FRAG_NODE Dokumentfragment

XML_NOTATION_NODE Notation (in einer DTD)

XML_GLOBAL_NAMESPACE Globaler Namensraum

625

XML_LOCAL_NAMESPACE Lokaler Namensraum

XML_HTML_DOCUMENT_NODE Dokument im HTML-Modus

Tabelle 14.13: Konstanten des DOM-Moduls

Page 626: 382726314 X Php5 In 14 Tagen (Ddt)

XML und Webservices

14.6 Kontrollfragen

1. Welche Technologien sind wichtig, wenn mit XML gearbeitet wird?

2. Welche Methode der Verarbeitung ist bei einem ca. 12 MByte großen XML-Dokument wahrscheinlich die beste, SAX oder DOM? Begründen Sie die Ant-wort.

XML_DTD_NODE Document Type Definition

XML_ELEMENT_DECL_NODE Element-Deklaration einer DTD

XML_ATTRIBUTE_DECL_NODE Attribut-Deklaration einer DTD

XML_ENTITY_DECL_NODE Entitäts-Deklaration einer DTD

XML_NAMESPACE_DECL_NODE Namensraum-Deklaration

XML_ATTRIBUTE_CDATA CDATA-Teil eines Attributes

XML_ATTRIBUTE_ID ID-Attribut

XML_ATTRIBUTE_IDREF IDREF-Attribut

XML_ATTRIBUTE_IDREFS Kollektion von IDREF-Attributen

XML_ATTRIBUTE_ENTITY Entität in einem Attribut

XML_ATTRIBUTE_NMTOKEN NMTOKEN

XML_ATTRIBUTE_NMTOKENS Aufzählung von NMTOKEN Elementen

XML_ATTRIBUTE_ENUMERATION Attribut-Aufzählung

XML_ATTRIBUTE_NOTATION Attribut-Notation

Name der Konstanten Bedeutung

Tabelle 14.13: Konstanten des DOM-Moduls (Forts.)

626

3. Nennen Sie typische Erscheinungsformen eines so genannten Knotens in einemXML-Dokument.

4. Welche Bedeutung hat die DTD (Document Type Definition)?

Page 627: 382726314 X Php5 In 14 Tagen (Ddt)

Antworten auf die K

ontrollfragen

15

Page 628: 382726314 X Php5 In 14 Tagen (Ddt)

Antworten auf die Kontrollfragen

1. Tag

1. Wann wurde PHP das erste Mal unter diesem Namen bekannt?

A 1995, als PHP/FI. Damals war es noch einen Sammlung von Perl-Skripten.

2. Was bedeutet »PHP«?

A Dies ist ein so genanntes rekursives Acronym: PHP Hypertext Preprocessor.

3. Worauf ist der Name »Zend« zurückzuführen?

A Ein Kunstwort aus Namensteilen der Gründer der Firma Zend, Ze vonZeev Suraski und nd aus Andi Gutmans. Zend stellt den Sprachkern fürPHP bereit und vertreibt außerdem kommerzielle Werkzeuge für PHP, wiebeispielsweise eine IDE, Verschlüsselungs- und Cachetools.

4. Anhand welcher Begrenzungszeichen werden PHP-Fragmente in HTML-Codeerkannt?

A <?php und ?>. Andere Varianten werden nicht empfohlen. De Kurzform<?=$var?> zum Einbetten von Variablen sollte nur in Ausnahmefällenbenutzt werden.

5. Welches Protokoll wird zur Übertragung von HTML-Seiten vom Server zumBrowser verwendet?

A HTTP – Hypertext Transfer Protocol.

6. Welcher Webserver kann auf allen Betriebssystemen zur Entwicklung eingesetztwerden?

A Der Apache-Webserver ist auf allen Plattformen zu Hause.

7. Wofür steht der Begriff WAMP?

A W = Windows, A = Apache, M = MySQL, P = PHP. Die beliebteste Kom-bination für PHP-Entwickler, wird von weit über 90% der Anwenderbenutzt.

8. Welche Bedeutung hat die Adresse »http://localhost«?

628

A Verweis auf den lokalen Webserver (!). Der Name weicht meist vomNamen des lokalen Computers ab. Die zugehörigen IP-Adresse ist127.0.0.1.

Page 629: 382726314 X Php5 In 14 Tagen (Ddt)

2. Tag

9. Mit welcher Funktion kann PHP5 ausführlich getestet werden?

A phpinfo() heißt die Funktion. Folgendes Skript zeigt, wie die Funktiongenutzt wird:

<?phpphpinfo();?>

2. Tag

1. Wie geben Sie mehrzeilige Texte aus PHP-Code heraus am besten aus?

A Mit der Heredoc-Syntax werden mehrzeilige Texte aus PHP-Code herausausgegeben:

<?phpecho <<<TEXT <table> <!-- Es folgt viel Text --> </table>TEXT;?>

2. Welche Funktion eignet sich zur Ausgabe hexadezimaler Farbangaben?

A printf könnte hilfreich sein. Die Option %2X erzeugt zweistellige hexade-zimale Zahlen. Als Eingabewert dienen alle Zahlenliterale, also beispiels-weise 32 (Dezimal) oder 0x20 (Hexadezimal) oder 040 (Oktal).

3. Welche Kommentarform wird am besten verwendet, wenn hinter einer Codezeileein kurzer, auf die Zeile bezogener Kommentar stehen soll? Mögliche Variantensind: /* */, // oder #.

A Es sollte immer // verwendet werden. # ist unüblich und /* scheitert, wennumschließende mehrzeilige Kommentare benutzt werden, um den Blockauszukommentieren.

629

Page 630: 382726314 X Php5 In 14 Tagen (Ddt)

Antworten auf die Kontrollfragen

3. Tag

1. Was ist ein Literal?

A Jede Form von im Quellcode direkt geschriebener konstanter Werte, alsobeispielsweise 0x22, 345.66, "string" usw.

2. Wie werden Konstanten definiert?

A Konstanten werden mit der Funktion define erstellt.

3. Wozu werden reguläre Ausdrücke eingesetzt?

A Reguläre Ausdrücke werden zum Suchen und Ersetzen benutzt, wenn dieSuchmuster komplex oder in sich variabel sind.

4. Schreiben Sie ein Skript, dass anhand mehrerer Parameter ein span-Tag mit kor-rekter hexadezimaler Angabe der Vordergrund- und Hintergrundfarbe mit Hilfedes style-Attributes erstellt und ausgibt. Tipp: Verwenden Sie printf.

A printf ist aufgrund der Parameter gut geeignet, um Tags übersichtlich zuerstellen.

<?phpprintf('<span style="color:#%2X%2X%2X; � background-color:#%2X%2X%2X"', � 0xFF, 0x00, 0xCC, 0xDD, 0xDD, 0xDD);?>

4. Tag

1. Schreiben Sie ein Skript, das beliebige Zahlen zwischen 0 und 100 auf ganzeZehner rundet.

A Die Antwort ist verblüffend einfach: Verwenden Sie die Funktion round.

630

Bei der Angabe der Anzahl »Kommastellen« auf die gerundet werden soll,sind auch negative Zahlen erlaubt, was dann zum Runden vor demKomma führt.

Das folgende Skript prüft das Verhalten für ein paar Testzahlen:

Page 631: 382726314 X Php5 In 14 Tagen (Ddt)

4. Tag

<?php$test = array(4.5, 25, 67, 99.4);foreach ($test as $number){ echo round($number, -1); echo '<br>';}?>

Ausgegeben werden die Zahlen : 0, 30, 70 und 100.

2. Können mit switch-Anweisungen auch Vergleiche mit beliebigen BooleschenAusdrücken erfolgen?

A Ja, denn PHP behandelt die Ausdrücke erst zur Laufzeit und erledigt dannden Vergleich. Wenn man im Verzweigungskopf den Wert TRUE schreibt,müssen die Ausdrücke auch TRUE oder FALSE zurückgeben, sodass ein Testauf Gleichheit funktioniert. Wichtig ist jedoch darauf zu achten, dassimmer nur ein (oder kein) Zweig die Bedingung zum Zeitpunkt derAbfrage erfüllt, sonst ist die Reaktion nicht korrekt.

switch(TRUE){ case $test < 9 && $test > 4: // Reaktion break;}

3. Welche Schleife wird benutzt, wenn sichergestellt werden muss, dass der Schlei-fenkörper mindestens einmal durchlaufen wird?

A Die do-Schleife. Da der Test erst am Ende mit Hilfe der while-Bedingungerfolgt, wird der Körper garantiert einmal durchlaufen, auch wenn dieBedingung dann nicht erfüllt ist und die Schleife sofort wieder verlassenwird.

4. Zu Testzwecken kann es erforderlich sein, eine Schleifenbedingung so zu formu-lieren, dass eine Endlosschleife entsteht. Mit welchen Anweisungen kann ein»Notausstieg« programmiert werden?

631

A Die Bedingung für den Ausstieg wird mit if erstellt, der Ausstieg erfolgtdann mit break. switch ist nicht geeignet, weil die Zweige selbst mit breakenden und ein weiteres break nicht mehr ausgeführt wird.

Page 632: 382726314 X Php5 In 14 Tagen (Ddt)

Antworten auf die Kontrollfragen

5. Tag

1. Worin unterscheiden sich normale von assoziativen Arrays?

A Assoziative Arrays verwenden Schlüsselwerte, die meist als Zeichenkettenvorliegen, zur Adressierung der Elemente, beispielsweise $arr[?name?].»Normale« Arrays haben dagegen immer Zahlen, über die Elementeerreicht werden, beispielsweise $arr[8].

2. Wie kann ein Element eines Array gezielt entfernt werden?

A Mit unset kann ein Element entfernt werden. PHP organisiert die Schlüs-selwerte einfacher Arrays danach nicht neu. Sie müssen das selbst erledi-gen, wozu die Funktion array_values dient:

<?php$test = array(4.5, 25, 67, 99.4);unset($test[2]);$test = array_values($test);foreach ($test as $index => $number){ echo "$index => $number"; echo '<br>';}?>

A Die Ausgabe der Indizes lautet 0, 1, 2. Ohne array_values wäre es 0, 1, 3.

3. Schreiben Sie ein Skript, dass Arrays nutzt um Namen von Mitarbeitern zu spei-chern. Erweitern Sie das Skript, sodass zu jedem Mitarbeiter im selben Arrayzusätzliche Informationen gespeichert werden. Tipp: Nutzen Sie verschachtelteassoziative Arrays dafür.

A Sie können innerhalb einer array-Definition für den Wert ein weiteresArray verwenden. Als Schlüssel sind dagegen nur Zahlen oder Zeichenket-ten erlaubt. Beachten Sie jedoch, dass mehr als drei Ebenen nur schwerhandhabbar sind und meist auf einen Designfehler beim Entwurf desDatenmodells hindeuten.

632

<?php$ma = array( 'Bernd Muster' => array('030/12345', 'Raum 123'), 'Katja Muster' => array('089/54321', 'Raum 999'), );

Page 633: 382726314 X Php5 In 14 Tagen (Ddt)

6. Tag

foreach ($ma as $name => $details){ echo "$name => $details[0], $details[1]"; echo '<br>';}?>

6. Tag

1. Schreiben Sie ein Skript, dass die Namen von Mitarbeitern in einer eigens dafürentwickelten Klasse speichert.

A Sie sollten auch bei derart einfachen Aufgabenstellungen grundsätzlichversuchen, die elementaren OOP-Techniken zu verwenden. Dazu gehörtder Schutz der Daten in privaten Mitgliedern und der Zugriff über __getbzw. __set und die Verwendung eines sinnvollen Konstruktors.

<?phpclass Employee{ private $_name; private $_telephon; private $_room; public function __construct($n, $t, $r) { $this->_name = $n; $this->_telephon = $t; $this->_room = $r; } public function __get($property) { switch ($property) {

633

case 'name': return $this->_name; case 'telephon': return $this->_telephon; case 'room':

Page 634: 382726314 X Php5 In 14 Tagen (Ddt)

Antworten auf die Kontrollfragen

return $this->_room; } }}$ma = array(new Employee('Bernd Muster', '0172/654321', '215'), new Employee('Katja Muster', '0172/123456', '948'));foreach ($ma as $em){ echo "{$em->name} => {$em->telephon}, {$em->room}"; echo '<br>';}?>

2. Erklären Sie den Unterschied zwischen private, public und protected.

A Mitglieder, die in einer Klasse als private gekennzeichnet sind, könnennur von Methoden der Klasse selbst benutzt werden. Mitglieder, die ineiner Klasse als public gekennzeichnet sind, können von Methoden oderFunktionen des gesamten Skripts benutzt werden. Mitglieder, die in einerKlasse als protected gekennzeichnet sind, können nur von Methoden derKlasse selbst und solchen einer von dieser Klasse abgeleiten Klasse benutztwerden.

3. Sie haben nur eine einzige Klasse in Ihrem Skript. Ist die Anwendung des Schlüs-selwortes protected sinnvoll? Begründen Sie die Antwort.

A Nein, die Anwendung ist nicht sinnvoll. Sie können das so gekennzeich-nete Mitglied nicht aus Ihrem Skript erreichen und für die Benutzunginnerhalb der Klasse wäre private ausreichend.

4. Welchen Vorteil bietet die Verwendung von __get und __set anstatt des direktenZugriffs auf öffentliche Eigenschaften, die mit public $name gekennzeichnetsind?

A Die Pseudo-Eigenschaften können den Weg der Daten in die Eigenschafthinein und wieder heraus kontrollieren. Man kann so sicherstellen, dassniemals ungültige Daten im Objekt gespeichert sind. Man kann auchsicherstellen, dass niemals ungültige Daten das Objekt verlassen. Beides

634

trägt zur Codesicherheit bei. Außerdem kann eine Klasse weitaus mehrEigenschaften bereitstellen, als Mitgliedsvariablen vorhanden sind. Dieinterne Form der Datenspeicherung kann also anderen Optimierungskrite-rien unterliegen als die Darstellung der Daten nach außen.

Page 635: 382726314 X Php5 In 14 Tagen (Ddt)

7. Tag

5. Wie schreiben Sie eine Klasse, deren Objekte beim Aufruf von new einen definier-ten Anfangszustand unabhängig von Parametern erhalten soll?

A Sollen die Daten des Anfangszustands nicht variabel sein, bietet sich derEinsatz von Konstanten an. Im Konstruktor werden die Konstanten dannden Eigenschaften zugewiesen. Dies erleichtert die Wartung der Klasse.

6. Wie schreiben Sie eine Klasse, von der nur eine Instanz erzeugt werden darf? Wienennt man dieses Entwurfsmuster?

A Das Muster wird Singleton genannt. Man deklariert den Konstruktor pri-vat, um den wiederholten Aufruf zu verhindern. Ersatzweise schafft maneine Methode, in der mittels new eine Instanz erzeugt und zurückgegebenwird. Die Methode speichert die erzeugte Instanz in einer Variablen undprüft beim erneuten Aufruf, ob die Instanz bereits existiert. Ist das der Fall,wird die bereits existente Version zurückgegeben, die zugleich die einzigeInstanz der Klasse ist.

7. Tag

1. Auf welche Datenquellen können die Dateifunktionen zugreifen?

A Dank des universellen Stream-Konzeptes können Dateifunktionen ausbeliebigen Datenquellen zurückgreifen. Bereits fertig implementiert ist derZugriff auf Dateien des Dateisystems, Ein-/Ausgabekanäle des Betriebssys-tems, Webserver per HTTP und FTP-Server. Für andere Datenquellen las-sen sich benutzerdefinierte Wrapper erstellen.

2. Warum muss eine Datei nach der Benutzung wieder geschlossen werden?

A Der schreibende Zugriff auf Dateien ist exklusiv, damit sich die Änderun-gen nicht unbemerkt überschreiben. Um künftige Zugriffe nicht zu verzö-gern, sollten Dateien schnell wieder geschlossen werden. Bedenken Sie,dass Webserver Multiuser-Zugriffe gestatten, ein Skript also gleichzeitigmehrfach starten kann.

635

Page 636: 382726314 X Php5 In 14 Tagen (Ddt)

Antworten auf die Kontrollfragen

3. Schreiben Sie ein Skript, dass eine beliebige Datei aus dem aktuellen Verzeichnisim Quelltext anzeigt, wobei der Benutzer die Datei selbst wählen kann. Tipp:Benutzen Sie HTML-Links <a href=script.php?filename=$name> und ermit-teln Sie den übergebenen Namen mittels $_GET[’name’].

A Zuerst benötigt man eine Dateiliste. Hier ist das Verzeichnisobjekt diroder der DirectoryIterator der SPL eine gute Wahl.

4. Warum ist das in der letzten Übung verlangte Prinzip auf einer öffentlichenWebsite nicht unmodifiziert einsetzbar? Tipp: Denken Sie an mögliche Sicher-heitsprobleme.

A Der GET-Parameter lässt sich im Browser leicht manipulieren. Da diemeisten Dateifunktionen auch Pfade mit relativen Angaben wie ../../../ ver-arbeiten können, lassen sich so leicht Dateien auslesen, die nicht zumfreien Zugriff gedacht sind. Auch wenn das Betriebssystem meist verhin-dert, dass Systemdateien gelesen werden können, reicht oft die Auswahleiner Include-Datei oder anderer PHP-Skripte, um an Kennwörter oderSysteminformationen zu gelangen.

Zur Lösung arbeitet man mit Hashwerten oder ID-Nummern, sodass imLink nur Werte stehen, die keinen Rückschluss auf die Datei zulassen.Den Dateinamen einfach als MD5-Hash abzulegen ist freilich keineLösung, denn Profis erkennen derartige Hashes sehr schnell und könnensie ebenso schnell auch erzeugen.

8. Tag

1. Warum sollten unbedingt immer »Sticky Forms« verwendet werden?

A Es ist ausgesprochen lästig für den Benutzer, wenn er nach einem Fehlerdie mühevoll eingegebenen Daten verliert. Formulare sind ohnehin dieStelle mit der höchsten Abbruchquote. Schlecht gemachte Formulare stei-gern den Misserfolg der Site enorm.

636

Inzwischen gilt der Verzicht auf »Sticky Forms« auch einfach nur alsunprofessionell.

Page 637: 382726314 X Php5 In 14 Tagen (Ddt)

9. Tag

2. Welche Funktionen unterstützen das Hochladen von Dateien? Was ist beim Auf-bau des Formulars zu beachten?

A Die wichtigste Funktion ist move_uploaded_file, eingesetzt zum Transportder Dateien zum endgültigen Ziel. Beim Formular ist zu beachten, dassdas Attribut enctype im Form-Tag angegeben wird, damit der Browser dieDaten korrekt verpackt.

3. Welche Methoden verwenden Sessions, um die Session-Daten zu speichern?

A Zum Speichern werden Dateien oder Datenbanken benutzt, seltener derSystemspeichern. PHP5 unterstützt standardmäßig Dateien. Beachten Sie,dass die Sessiondaten, die im Sessioncookie gespeichert werden, lediglicheine Referenznummer auf die im Webserver oder einer Datenbank gespei-cherten Daten enthalten.

4. Wie können Cookies missbraucht werden?

A Cookies können mit jeder Anforderung gesendet werden. Da die Objekteeiner Website nicht vom selben Server stammen müssen, können beimLaden einer Website auch Cookies verschiedener Server übermittelt wer-den. Besucht derselbe Benutzer eine andere Website, wobei ein Objektvon demselben Server stammt wie beim Besuch der vorhergehenden Site,erhält der Server das Cookie von der zweiten Site wieder zurück. Damitkann man Bewegungsprofile erstellen, die den Weg des Benutzers durchsWeb markieren. Firmen wie Doubleclick nutzen dies als Geschäftsmodell,um kundenspezifische Werbebanner zu schalten.

9. Tag

1. Welche Servervariable wird benutzt, um die bevorzugte Sprache des Benutzers zuermitteln?

A HTTP_ACCEPT_LANGUAGE kann verwendet werden. Zur Abfrageschreiben Sie:

637

$lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];

Page 638: 382726314 X Php5 In 14 Tagen (Ddt)

Antworten auf die Kontrollfragen

2. Welcher Header wird in HTTP benötigt, um dem Browser anzuzeigen, dass einBild gesendet wird?

A Benötigt wird Content-type:. Als Parameter ist image/<typ> anzugeben,wobei der Typ dem Bildformat entspricht:

Content-type: image/gif

3. Schreiben Sie ein Skript, dass dezimale Farbangaben wie 255, 192, 128 in hexa-dezimale Zeichenfolgen der Art »#FFCC99« umrechnet und umgekehrt.

A Nutzen Sie die Funktion printf dafür. Folgende Zeile muss nur noch ineine Funktion eingebaut werden:

printf('#%2X%2X%2X', 255, 192, 128);

4. Wozu dient das Werkzeug Reflection?

A Mittels Reflection kann Code analysiert werden.

10. Tag

1. Welche Aufgabe erfüllen Wrapper?

A Wrapper dienen dazu, benutzerspezifische Ein-/Ausgabekanäle zu schaf-fen, sodass beispielsweise die normalen Dateifunktionen darauf zugreifenkönnen.

2. Was muss ein fremder Entwickler beachten, wenn er einen von Ihnen entwickeltesFilter verwendet?

A Nichts – Filter sind für die Benutzung transparent.

3. Was bedeuten die Kopfzeilen Cc: und Bcc: in einer E-Mail?

A Cc: (Carbon Copy, Durchschlag) sendet eine Kopie der E-Mail an denangegebenen Empfänger, wobei alle anderen Empfänger dessen Adressesehen. Bcc: dagegen sendet ebenso eine Kopie, andere Empfänger können

638

dies aber nicht sehen (Blind Carbon Copy, Blinddurchschlag).

4. Welches Protokoll nutzt die mail-Funktion zur Übertragung der Daten?

A Diese Funktion überträgt die Daten mittels SMTP zum Server.

Page 639: 382726314 X Php5 In 14 Tagen (Ddt)

11. Tag

11. Tag

1. Warum sollte die neue Bibliothek MySQLi anstatt der alten eingesetzt werden?

A MySQLi ist auf die Datenbank MySQL 4 abgestimmt. Außerdem unter-stützt das neue Modul die objektorientierte Programmierung.

2. Was versteht man unter Normalisierung?

A Stark vereinfacht die Vermeidung redundanter Datenhaltung in einer rela-tionalen Datenbank.

3. Sie müssen bei der Ausgabe von Daten aus einer MySQL-Datenbank Datums-formate anpassen und \n in <br> umwandeln. Wie gehen Sie vor?

A MySQL verfügt über einen reichen Vorrat an Zeichenkettenfunktionen.Suchen Sie eine Funktion zum Suchen und Ersetzen, die dies erledigt.Dies ist meist schneller als nl2br im PHP-Skript.

4. Wie würden Sie eine Abfrage formulieren, bei der aus zwei Tabellen Datenzugleich entnommen werden müssen? Welche Voraussetzungen müssen dieTabellen erfüllen.

A Dies ist ein so genannter JOIN:

SELECT t1.feldname, t2.feldname FROM tabelle1 t1 INNER JOIN tabelle2 t2 ON t1.id = t2.id

Die Schreibweise tabelle1 t1 definiert einen Aliasnamen für tabelle1.Damit lassen sich gleichnamige Felder auseinander halten, ohne die mög-licherweise langen Tabellennamen schreiben zu müssen. t1.id = t2.id ver-knüpft die beiden Tabellen miteinander, indem die Primärschlüsselverglichen werden. t1.feldname, t2.feldname ruft dann aus jeder Tabelleeine bestimmte Spalte ab.

12. Tag

639

1. Was müssen Sie tun, um SQLite benutzt zu können?

A Nichts, SQLite gehört zum Standardumfang von PHP5.

Page 640: 382726314 X Php5 In 14 Tagen (Ddt)

Antworten auf die Kontrollfragen

2. Welche Datentypen kennt SQLite?

A Zahlen und Zeichen, alle anderen Datentypnamen sind nur aus Kompati-bilitätsgründen im Umfang reservierter Namen. SQLite prüft sonst keineDaten sondern behandelt diese immer als Zeichenketten.

3. Sie möchten mehrere Tausend Datensätze für eine hoch frequentierte Websiteverwalten. Ist SQLite dafür die beste Wahl? Begründen Sie die Antwort?

A SQLite ist dafür nicht geeignet, weil die Verwaltung der Datenbank aufDateiebene stattfindet. Während des Zugriffs auf eine Tabelle ist die Dateigesperrt, was konkurrierende Zugriffe unmöglich macht. Bei hoher Lastwird SQlite deshalb sehr langsam.

4. Welche Aufgabe hat der Befehl VACUUM?

A Der Befehl verdichtet die Datenbank nach Löschvorgängen. Die separateOptimierung verhindert, dass einfache Löschvorgänge zu lange dauern.

13. Tag

1. Wozu werden Template-Systeme eingesetzt?

A Template-Systeme dienen dazu, Code und Layout zu trennen.

2. Erweitern Sie die Applikation, sodass zu jedem Leser der Bibliothek mehrere Tele-fonnummern gespeichert werden können. Wie gehen Sie vor?

A Legen Sie eine weitere Tabelle in der Datenbank an. Erweitern Sie dieTabelle der Leser um einen ID-Spalte. Verknüpfen Sie die Spalte mit derneuen Telefon-Tabelle und einer dort befindlichen Korrespondenz-Spalte.Sie benötigen also mindestens drei Spalten in der Telefon-Tabelle: id (Pri-märschlüssel), telephon-id (Verknüpfung, gleiche Werte können freilichmehrfach auftreten, wenn ein Kunde mehrere Telefonnummern hat), tele-phon (die eigentlichen Daten).

3. Formulieren Sie eine SQL-Abfrage, die die nötigen Daten zur Erstellung vonMahnschreiben erzeugt, um säumige Leser auf die bereits abgelaufene Rückga-

640

befrist hinzuweisen.

A Der folgende Ausdruck (Teil der Abfrage) wird TRUE, wenn die Ausleihfristabgelaufen ist:

TO_DAYS(CURDATE())-TO_DAYS(lendingdate) > lendingperiod

Page 641: 382726314 X Php5 In 14 Tagen (Ddt)

14. Tag

14. Tag

1. Welche Technologien sind wichtig, wenn mit XML gearbeitet wird?

A Neben XML benötigen Sie eine Definitionssprache, also DTD oder XML-Schema. Außerdem zur Verarbeitung XSLT und XPath.

2. Welche Methode der Verarbeitung ist bei einem ca. 12 MByte großen XML-Dokument wahrscheinlich die beste, SAX oder DOM? Begründen Sie die Ant-wort.

A SAX ist besser. Die sequenzielle ereignisbasierte Verarbeitung ist optimalbei großen Datenmengen. DOM würde das Dokument als Ganzes versu-chen im Speicher zu halten und dabei sehr viel Speicherplatz benötigen,sodass der Zugriff sehr langsam wird.

3. Nennen Sie typische Erscheinungsformen eines so genannten Knotens in einemXML-Dokument.

A Neben Elementen sind auch Kommentare, Attribute, CDATA-Abschnitteund Prozessanweisungen Teil eines XML-Dokuments. Da das gesamteDokument als Baum dargestellt werden kann, dessen Verzweigungen Kno-ten sind, können Knoten praktisch jeden Teil des Dokuments darstellen.

4. Welche Bedeutung hat die DTD (Document Type Definition)?

A Die DTD definiert die in einem XML-Dokument zulässigen Elemente(deren Namen) und deren Attribute sowie die Abhängigkeiten zwischendiesen. Dazu gehört beispielsweise, welche Elemente in welcher Zahl Kin-delemente anderer sein dürfen. Ohne DTD kann man lediglich prüfen, obein XML-Dokument wohlgeformt ist. Mit DTD kann man prüfen, ob esaußerdem gültig ist. Jedes XML-Dokument sollte eine DTD haben oderalternativ das neuere XML-Schema, das einem vergleichbaren Zweckdient.

641

Page 642: 382726314 X Php5 In 14 Tagen (Ddt)
Page 643: 382726314 X Php5 In 14 Tagen (Ddt)

StichwortverzeichnisSymbols$_COOKIE 351$_REQUEST 355-> 208__autoload 172__call 227__clone 218__construct 210__destruct 211__FILE__ 233__get 229__LINE__ 233__METHOD__ 233__set 229__toString 226, 234_subclass_of 237

AAbfragesyntax 446Abstammung 235abstract 217Achsenbezeichner 564Addition 79Ähnlichkeiten 88aggregierende Funktionen (SQL) 454Algebra, logische 81ALL (SQL) 454alternative Zweige 136Anführungszeichen 85Anhänge versenden 434Apache– installieren 30– testen 35ArGoSoft Mail Server 429Array 182– Assoziative 191– erstellen 183– Funktionen 198– Werte 186– Zeiger 188array_walk 196

Ausgaben 55– formatieren 59AUTO_INCREMENT (SQL) 451AVG (SQL) 455

BBackslash 84Bad Request 42Base64 428basename 254Benennungsregeln 65Benutzerverwaltung (mit SQLite) 501BETWEEN (SQL) 448Bilder 49– Funktionen 385– Zeugung 381BinHex 428Boolesches 81break 137, 141Browserdaten erkennen 374

Ccase 137catch 178childNodes 579children (SimpleXML) 587chunk_split 94class 207clone 218Coding Standards 65continue 141Cookies 349– Beschränkungen 352– Personalisierung 352copy 335count 184COUNT (SQL) 454CREATE TABLE (SQL) 449current 188current (Iterator) 265, 265

643

Arraydaten 193ASC (SQL) 456ASCII-Zeichensatz 91Assoziativität 133attributes (SimpleXML) 586Attributknoten 563Ausdrücke 78

DD. Richard Hipp 498date 111Dateien hochladen 334Dateisystem 244Dateizugriff 246Datenspeichermethoden 358

Page 644: 382726314 X Php5 In 14 Tagen (Ddt)

Stichwortverzeichnis

Datentypen 74, 83Datentypen (SQL) 449Datum 110default 138DEFAULT (SQL) 451define 76defined 77Dekrement 79DELETE (SQL) 453DESC (SQL) 456Destruktoren 210dir 260DirectoryIterator 266DISTINCT (SQL) 454Division 79DNS 70do 148DOM 576– domAttr (Klasse) 624– Funktionen 576– Konstanten 625Domain Name System siehe DNSDomAttr 624DomDocument 579– getAttributeNode 624– getElementsById 623– getElementsByTagName 623DomXPath 566DROP (SQL) 453Dropdown-Listen 295DTD 553dynamisch Bilder erzeugen 379dynamischer Werbebanner 386

Eeach 195echo 55Eigenschaftenaufruf 229Elementknoten 563else 136Elternknoten 563E-Mail 426– versenden 430empty 287Entitäten 93Ersetzen 87

fclose 251Fehler (PHP) 42– Behandlung 173– Codes 177– Klassen erstellen 224– Nummer 176– Text 176fgets 251file_get_contents 420Filter 422, 440final 216fnmatch 260fopen 251for 142foreach 194Formatieren 119– Zahlen 119Formatregel 60Formulare 52– bauen 302fscanf 120func_get_arc 156func_get_args 156func_num_args 156function 150Funktionen 150– Referenzen 159– Rekursive 164– Variable 166Funktionsdefinition 150

GGD2 379Geschichte 22GET 341get_included_files 172get_meta_tags 419get_required_files 172getElementsByTagName 579getMessage 225gleich 82glob 257global 162globale Variablen 162Grafikbibliothek 379Grafiken 379

644

Exception 224exklusives Oder 83Extensible Markup Language siehe XML

FFactory-Klassen 235Farbrad 146

Größe (Datentyp) 83größer als 82Große Textmengen 57GROUP BY (SQL) 454Gruppierungen 98Gruppierungen (SQL) 455Gutmans, Andi 22

Page 645: 382726314 X Php5 In 14 Tagen (Ddt)

Stichwortverzeichnis

HHandle 244Hashes 186HAVING (SQL) 454header 380heredoc 57Herkunft erkennen 282Hinweistexte platzieren 303hochladen 334HTML 46– Formularelemente 278– Mails 434htmlentities 93HTTP 71HTTP_REFERER 282httpd.conf 40

Iiconv-Modul 340idate 116if 132imagecreate 381imagecreatefromgif 382imagecreatefromjpeg 382imagecreatefrompng 382imagegif 382imagettfbbox 382imagettftext 383IMAP4 427implements 221IN (SQL) 448include 169include_once 169Inkrement 79Inner Join 458INSERT INTO (SQL) 451instanceof 236Interfaces 221is_a 237is_resource 251ISO-8859-1 91

JJavaScript 47, 303– lokale Zeit 117JOIN (SQL) 459

Konfigurationsschritte 38Konstanten 76Konstruktoren 210Kontext 440Kontrollkästchen 287Krause, Jörg 17

LLaufzeitfehler 175LC_TIME 116LEFT JOIN (SQL) 459libxml2 575LIKE (SQL) 448links-assoziativ 134list 195Literale 74loadHtmlFile 579localeconv 378Lokalisierung 376

MMail 426MAX (SQL) 455MAX_FILE_SIZE 337MD5 347Mehrfachauswahl 297, 322Mehrfachverzweigungen 137mehrseitige Formulare 325mehrsprachige Webseiten 374Message Digest Version 5 347MIME 428MIN (SQL) 455mktime 111Modularisierung 168Module einbinden 168Modulus 79Moniker 416move_uploaded_files 335MSI Installer 30multipart/form-data 334Multiplikation 79Multipurpose Internet Mail Extensions siehe

MIMEMySQL– Aggregat-Funktionen 467– Beispielprojekt 510– Control Center 513

645

Kkey 188Kindknoten 563Klasse 207kleiner als 82Kombinationsoperatoren 81Kommentare 62

– Datentypen 462– Datumsformatierungen 475– Datumsfunktionen 473– Dialekt 461– Funktionen 464– Kontrollflussfunktionen 466– Mathematische Funktionen 465

Page 646: 382726314 X Php5 In 14 Tagen (Ddt)

Stichwortverzeichnis

– Systemfunktionen 468– Verschlüsselungsfunktionen 469– Zahlenformatierungen 473– Zeichenkettenfunktionen 470– Zeitfunktionen 473MySQLi 477– Einfache Abfragen 485– Komplexe Abfragen 487– Referenz 491– Testen 478– Verknüpfungen abfragen 489

NNamenskonventionen 68new 208, 471, 471next 188next (Iterator) 265Nicht 82nl2br 94, 285nodeType 580Normalisierungen 457NOT NULL (SQL) 451NULL (SQL) 451number_format 119

OObjekt erzeugen 208Objektorientierte Programmierung 204oder 82Operatoren 78Optionsfelder 288ORDER BY (SQL) 454, 456

PParadigmen 205Parameter 153– Anzahl, beliebige 156– optionale 154– Übergabe 159parent 211parse_url 343PEAR 65PECL-Module 29Personal Home Page (Tools)/Forms

Interpreter 22PHP– Blöcke 27

PHP5 23– installieren 37– konfigurieren 37phpTemple 516, 575Platzhalter 96POP3 427preg_match 100preg_match_all 100preg_replace 100preg_replace_callback 100prev 188Primärschlüssel 460PRIMARY KEY (SQL) 451print 58print_r 196printf 60, 120– Formatstruktur 121Prinzip des Seitenabrufs 70private 215professionelle Programmieren 62professionelle Programmierung 373protected 216Protokolldatei 255public 215PUT 334

QQuery Builder 446Quoted Printable 428

RRangfolge 133Rasmus Lerdorf 22RDF 569Rechnen 79rechts-assoziativ 135Reflection::export 395Reflection-API 395ReflectionClass 399ReflectionExtension 405ReflectionFunction 397Reflection-Informationen 395Reflection-Klassen 397ReflectionMethod 402ReflectionProperty 403Regex-Maschinen 103registerXPathNamespace 592

646

– einbetten 53– Skripte ausführen 41PHP Hypertext Preprocessor 22php.ini 38PHP_OS 116PHP_SELF 254PHP4 23

reguläre Ausdrücke 95rekursive Dateiliste 263rekursive Funktionen 164REQUEST_METHOD 281require 169require_once 169reset 188

Page 647: 382726314 X Php5 In 14 Tagen (Ddt)

Stichwortverzeichnis

Ressource 244rewind (Iterator) 265RIGHT JOIN (SQL) 459Root-Server 70RSS 568Rückgabewerte 151

SSchema 553Schleifen 141Schnittstellen 221Seitenmanagement 277SELECT (SQL) 446serialize 356Servervariablen 365Serverzeit 110Sessions– Cookies 350– Verwaltung 358set_error_handler 175setcookie 352setlocale 115, 376setrawcookie 352Sicherheitsprobleme 346SimpleXML 581– Referenz 620simplexml_import_dom 587simplexml_load_file 585, 620simplexml_load_string 620Singleton 213Smarty 516SMTP 427SOAP 593, 595– Body 595– Envelope 595– Header 595– Projekt 608Socket-Verbindung 441Sonderzeichen 84, 85sort 191Sortieren (SQL) 456Sortierfunktion 191SPAM-Versender 429Sprache 374sprintf 120SQL 445– Aggregate Funktionen 467

sqlite_last_error 504sqlite_num_rows 504sqlite_query 504sscanf 120Standard-Wrapper 247static 213statische Mitglieder 212statische Variablen 160Sticky Forms 301, 315str_ireplace 89str_replace 89strcmp 89stream_context_create 253Streams 416– Funktionen 425, 440strftime 111strip_tags 422stripos 88strlen 90strnatcasecmp 88strncasecmp 88strpos 87strrpos 88Structured Query Language siehe SQLSub-Select 460substr 92Subtraktion 79Suchen 87Suchmuster 103SUM (SQL) 455Suraski, Zeev 22switch 137

TTabellen 49, 445Tabulator 85Teilausdrücke 79Teilzeichenketten 90Template-System 516Textfelder 285Textknoten 563throw 178Timestamp 110trigger_error 175trinärer Operator 82TRUNCATE (SQL) 453try 178

647

SQL92 461SQLite 498– Beispiel 500– Referenz 506sqlite_close 505sqlite_error_string 504sqlite_fetch_array 504

Typ-Informationen 223

UUmgebungsvariablen 365und 82ungleich 82UNIQUE (SQL) 451

Page 648: 382726314 X Php5 In 14 Tagen (Ddt)

Stichwortverzeichnis

Unix-Timestamp 110unserialize 357unset 187Unterabfragen 460UPDATE (SQL) 452url_decode 343url_encode 343UserLand 569UTC 110utf8_decode 580UUEncode 427

Vvalid (Iterator) 265variable Funktionen 166Variablen 74– Erkennung 84– globale 162– statische 160Vererbung 211Vergleiche 81verketten 80Verknüpfungen (SQL) 457Verzeichnisobjekt dir 260Verzeichniszugriff 257Verzweigungen 132Vorauswahl 299Vorbereitung 24Vorzeichen 79vprintf 120vsprintf 120

WWagenrücklauf 85WAMP 29WebDAV 334Webserver bauen 29Webservices 593– konsumieren 607WHERE (SQL) 447while 148wordwrap 93Wrapper 416– Eigene 425Wrapper-Zugriff 246WSDL 597

XXML 550– Definition 552– Namensräume (SimpleXML) 588– Path Language 1.0. 562– Pointer Language 552XPath 562– Achsenbezeichner 564– ancestor 564– Ausdrücke 563– Ausdrücke in PHP5 566– child 564– descendant 564– following 565– parent 564– preceding 565– self 564xpath (SimpleXML) 588XPath-Knoten 563XQuery 553xsl:apply-template 556xsl:call-template 559xsl:copy-of 559xsl:for-each 558xsl:if 557xsl:include 560xsl:number 559xsl:output 560xsl:sort 558xsl:template 555xsl:text 555xsl:value-of 556XSLT 553– Basisregeln 555– Funktionen 560

ZZeichen 90Zeichencodes 91Zeichenfolgen verketten 80Zeichenketten 83– erkennen 85– Funktionen 86– Operationen 86Zeichenklassen 96Zeilenvorschub 85

648

wsdl_cache_dir 608wsdl_cache_enabled 608wsdl_cache_ttl 608Wurzelknoten 563

Zeit 110Zend-Engine 23Zielgruppe 16Zugriffskontrolle 215Zugriffsmethoden 227

Page 649: 382726314 X Php5 In 14 Tagen (Ddt)

Copyright

Daten, Texte, Design und Grafiken dieses eBooks, sowie die eventuell angebotenen eBook-Zusatzdaten sind urheberrechtlich geschützt.

Dieses eBook stellen wir lediglich als Einzelplatz-Lizenz zur Verfügung! Jede andere Verwendung dieses eBooks oder zugehöriger Materialien und

Informationen, einschliesslich der Reproduktion, der Weitergabe, des Weitervertriebs, der Platzierung im Internet, in Intranets, in Extranets anderen Websites, der

Veränderung, des Weiterverkaufs und der Veröffentlichung bedarf der schriftlichen Genehmigung des Verlags.

Bei Fragen zu diesem Thema wenden Sie sich bitte an: mailto:[email protected]

Zusatzdaten Möglicherweise liegt dem gedruckten Buch eine CD-ROM mit Zusatzdaten bei. Die Zurverfügungstellung dieser Daten auf der Website ist eine freiwillige Leistung des

Verlags. Der Rechtsweg ist ausgeschlossen.

Hinweis

Dieses und andere eBooks können Sie rund um die Uhr und legal auf unserer Website

(http://www.informit.de) herunterladen