382726314 X Php5 In 14 Tagen (Ddt)
Upcoming SlideShare
Loading in...5
×
 

382726314 X Php5 In 14 Tagen (Ddt)

on

  • 15,360 views

 

Statistics

Views

Total Views
15,360
Views on SlideShare
15,360
Embed Views
0

Actions

Likes
0
Downloads
40
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

382726314 X Php5 In 14 Tagen (Ddt) 382726314 X Php5 In 14 Tagen (Ddt) Document Transcript

  • PHP5 in 14 Tagen
  • Unser Online-Tipp für noch mehr Wissen ... ... aktuelles Fachwissen rund um die Uhr — zum Probelesen, Downloaden oder auch auf Papier. www.InformIT.de
  • PHP PHP5 JÖRG KRAUSE eBook Die nicht autorisierte Weitergabe dieses eBooks ist eine Verletzung des Urheberrechts!
  • 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 einen eventuellen 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 Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und Herausgeber dankbar. Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle und Arbeiten ist nicht zulässig. Fast alle Hardware- und Software-Bezeichnungen, die in diesem Buch erwähnt werden, sind gleichzeitig auch eingetragene Marken oder 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/Germany Alle Rechte vorbehalten Lektorat: Boris Karnikowski, bkarnikowski@pearson.de Herstellung: Philipp Burkart, pburkart@pearson.de Korrektur: Haide Fiebeler-Krause, Berlin Satz: reemers publishing services gmbh, Krefeld, (www.reemers.de) Coverkonzept: independent Medien-Design, München Coverlayout: Sabine Krohberger Druck und Verarbeitung: Bercker, Kevelaer Printed in Germany
  • Inhaltsverzeichnis Vorwort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Für wen das Buch und die Reihe gedacht sind . . . . . . . . . 15 Unsere Zielgruppe als Leser . . . . . . . . . . . . . . . . . . . . . . . . 16 PHP und ... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 Ein paar Worte zum Autor . . . . . . . . . . . . . . . . . . . . . . . . . 17 In diesem Buch verwendete Konventionen . . . . . . . . . . . . 18 Woche 1 – Wochenvorschau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Tag 1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 1.1 Die Geschichte von PHP . . . . . . . . . . . . . . . . . . . . . . . . . . 22 PHP/FI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 PHP3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 PHP4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 PHP5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 1.2 PHP auf einen Blick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Ein wenig Vorbereitung . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Mit PHP spielen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 Ein paar wichtige Techniken . . . . . . . . . . . . . . . . . . . . . . . 27 1.3 Einen Webserver bauen . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Vorbemerkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 WAMP vorbereiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 1.4 Apache installieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 Installationsstart. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 Erste Schritte. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 Apache testen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Weitere Einstellungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Apache automatisch starten . . . . . . . . . . . . . . . . . . . . . . . . 36 1.5 PHP5 installieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 1.6 PHP5 konfigurieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 Die Datei php.ini . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 Wichtige Konfigurationsschritte . . . . . . . . . . . . . . . . . . . . . 38 5
  • Inhaltsverzeichnis 1.7 PHP5 testen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 Konfiguration des Webservers für PHP5 . . . . . . . . . . . . . . 40 PHP-Skripte ausführen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 Wenn es nicht funktioniert. . . . . . . . . . . . . . . . . . . . . . . . . 42 1.8 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 Tag 2 Erste Schritte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 2.1 Einfache HTML-Seiten . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 HTML-Refresh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 Tabellen und Bilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Formulare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 2.2 PHP einbetten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 Wie PHP den Code erkennt . . . . . . . . . . . . . . . . . . . . . . . . 53 2.3 Ausgaben erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Die Ausgabe mit echo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Variablen ausgeben. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 Ausgabe von großen Textmengen . . . . . . . . . . . . . . . . . . . 57 Vielfältige Formatierungen mit print und Verwandten. . . 58 2.4 Professionelles Programmieren . . . . . . . . . . . . . . . . . . . . . 62 Nicht nur für die Nachwelt: Kommentare. . . . . . . . . . . . . 62 Benennungsregeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 2.5 Webserver und Browser. . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 Prinzip des Seitenabrufs . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 HTTP auf einen Blick . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 2.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 Tag 3 Daten verarbeiten. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 3.1 Variablen und Literale . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 3.2 Konstanten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 Konstanten definieren und nutzen. . . . . . . . . . . . . . . . . . . 76 3.3 Rechnen und Vergleichen mit Ausdrücken . . . . . . . . . . . . 78 Ausdrücke und Operatoren. . . . . . . . . . . . . . . . . . . . . . . . . 78 3.4 Allgemeine Aussagen zu Zeichenketten . . . . . . . . . . . . . . . 83 Datentyp und Größe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 Umgang mit Sonderzeichen. . . . . . . . . . . . . . . . . . . . . . . . 84 Zeichenketten erkennen und bestimmen . . . . . . . . . . . . . 85 Zeichenkettenoperationen . . . . . . . . . . . . . . . . . . . . . . . . . 86 6
  • Inhaltsverzeichnis 3.5 Die Zeichenkettenfunktionen . . . . . . . . . . . . . . . . . . . . . . 86 Suchen und Ersetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 Ermitteln von Eigenschaften einer Zeichenkette . . . . . . . 90 Teilzeichenketten und Behandlung einzelner Zeichen . . 90 HTML-abhängige Funktionen . . . . . . . . . . . . . . . . . . . . . . 92 3.6 Reguläre Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Einführung in die Welt der regulären Ausdrücke . . . . . . . 95 Anwendungsbeispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 Typische Suchmuster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 3.7 Datums- und Zeitfunktionen . . . . . . . . . . . . . . . . . . . . . . . 110 Der Timestamp und die Serverzeit . . . . . . . . . . . . . . . . . . 110 Rechnen mit Datums- und Zeitwerten . . . . . . . . . . . . . . . 110 Tricks mit JavaScript: Lokale Zeit ermitteln . . . . . . . . . . . 117 3.8 Formatieren und Ausgeben. . . . . . . . . . . . . . . . . . . . . . . . . 119 Zahlen formatieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 Umwandeln, Anpassen und Ausgeben . . . . . . . . . . . . . . . . 120 3.9 Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 Zeichenketten-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . 125 Funktionen für reguläre Ausdrücke . . . . . . . . . . . . . . . . . . 129 Referenz Ausgabe- und Datumsfunktionen . . . . . . . . . . . . 129 3.10 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 Tag 4 Programmieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 4.1 Verzweigungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 Einfache Verzweigungen mit if . . . . . . . . . . . . . . . . . . . . . 132 Alternative Zweige mit else. . . . . . . . . . . . . . . . . . . . . . . . . 136 Mehrfachverzweigungen mit switch . . . . . . . . . . . . . . . . . 137 4.2 Schleifen erstellen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 Gemeinsamkeiten aller Schleifenanweisungen . . . . . . . . . 141 Die Zählschleife for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 Die Universalschleifen do/while und while . . . . . . . . . . . . 148 4.3 Benutzerdefinierte Funktionen. . . . . . . . . . . . . . . . . . . . . . 150 Funktionen definieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150 Parameter der Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . 153 Referenzen auf Funktionen und Parameter. . . . . . . . . . . . 159 Statische Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 Globale Variablen und Konstanten . . . . . . . . . . . . . . . . . . 162 Rekursive Funktionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 Variable Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 7
  • Inhaltsverzeichnis 4.4 Modularisierung von Skripten . . . . . . . . . . . . . . . . . . . . . . 168 Module einbinden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 Informationen über Module ermitteln. . . . . . . . . . . . . . . . 172 4.5 Fehlerbehandlung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 Konventionelle Fehlerbehandlung . . . . . . . . . . . . . . . . . . 174 Fehlerbehandlung mit PHP5 . . . . . . . . . . . . . . . . . . . . . . . 178 4.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180 Tag 5 Daten mit Arrays verarbeiten. . . . . . . . . . . . . . . . . . . . . . . . . . . . 181 5.1 Datenfelder im Einsatz: Arrays . . . . . . . . . . . . . . . . . . . . . . 182 5.2 Arrays erstellen und befüllen . . . . . . . . . . . . . . . . . . . . . . . 182 Ein einfaches Array erstellen . . . . . . . . . . . . . . . . . . . . . . . 183 Die Anzahl der Elemente ermitteln . . . . . . . . . . . . . . . . . . 184 Den Index manipulieren . . . . . . . . . . . . . . . . . . . . . . . . . . 184 Schlüssel statt Indizes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 Arraywerte schneller zuweisen . . . . . . . . . . . . . . . . . . . . . . 186 5.3 Arrays manipulieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 Werte entfernen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 Der Arrayzeiger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 Arrayfunktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191 Arraydaten manipulieren . . . . . . . . . . . . . . . . . . . . . . . . . . 193 5.4 Arrays ausgeben. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 Arrays zählbar ausgeben: for . . . . . . . . . . . . . . . . . . . . . . . . 193 Beliebige Arrays mit foreach durchlaufen . . . . . . . . . . . . . 194 Einfache Arrays mit while, each, list ausgeben . . . . . . . . . 195 Array mit array_walk durchlaufen . . . . . . . . . . . . . . . . . . . 196 Fehlersuche mit print_r . . . . . . . . . . . . . . . . . . . . . . . . . . . 196 5.5 Referenz der Arrayfunktionen . . . . . . . . . . . . . . . . . . . . . . 198 5.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202 Tag 6 Objektorientierte Programmierung . . . . . . . . . . . . . . . . . . . . . . 203 6.1 Warum objektorientiert programmieren?. . . . . . . . . . . . . . 205 6.2 Syntax der Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207 Eine Klasse definieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207 Ein Objekt erzeugen und benutzen . . . . . . . . . . . . . . . . . . 208 Eine Klasse erweitern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208 Zugriffskontrolle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 8
  • Inhaltsverzeichnis 6.3 Schnittstellen zur Außenwelt . . . . . . . . . . . . . . . . . . . . . . . 220 Schnittstellen (Interfaces) . . . . . . . . . . . . . . . . . . . . . . . . . . 221 Informationen über Klassen und Objekte . . . . . . . . . . . . . 223 Eigene Fehlerklassen erstellen . . . . . . . . . . . . . . . . . . . . . . 224 Spezielle Zugriffsmethoden für Klassen. . . . . . . . . . . . . . . 227 6.4 Analyse und Kontrolle von Objekten . . . . . . . . . . . . . . . . . 232 __METHOD__ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 Zeichenkettenform: __toString() . . . . . . . . . . . . . . . . . . . . 234 Abstammung von Klassen und Objekten . . . . . . . . . . . . . . 235 6.5 Referenz der OOP-Funktionen . . . . . . . . . . . . . . . . . . . . . 239 OOP-Schlüsselwörter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239 OOP-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240 6.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242 Tag 7 Das Dateisystem entdecken. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243 7.1 Dateizugriff organisieren. . . . . . . . . . . . . . . . . . . . . . . . . . . 244 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244 Einsatzfälle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245 7.2 Praktischer Dateizugriff . . . . . . . . . . . . . . . . . . . . . . . . . . . 246 Prinzipien des Dateizugriffs . . . . . . . . . . . . . . . . . . . . . . . . 246 Wichtige Konstanten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249 Nachrichtenquelle für eine News-Seite . . . . . . . . . . . . . . . 250 Beliebige Code-Dateien ausgeben . . . . . . . . . . . . . . . . . . . 254 Protokolldatei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255 7.3 Verzeichniszugriff . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257 Inhalt eines Verzeichnisses anzeigen . . . . . . . . . . . . . . . . . 257 Das Verzeichnisobjekt dir und verwandte Funktionen . . . 260 Rekursive Dateiliste. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263 Verzeichniszugriff mit Iteratoren . . . . . . . . . . . . . . . . . . . . 265 7.4 Funktions-Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268 Datei-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268 Prozess-Funktionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272 7.5 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273 9
  • Inhaltsverzeichnis Woche 2 – Wochenvorschau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275 Tag 8 Formular- und Seitenmanagement . . . . . . . . . . . . . . . . . . . . . . . 277 8.1 Grundlagen in HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278 Viel HTML – wenig PHP. . . . . . . . . . . . . . . . . . . . . . . . . . 278 8.2 Auswerten der Daten aus Formularen . . . . . . . . . . . . . . . . 280 Grundlegende Schritte . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280 Auswertung von Formularen . . . . . . . . . . . . . . . . . . . . . . . 283 Textfelder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285 Optionsfelder und Kontrollkästchen . . . . . . . . . . . . . . . . . 287 Dropdown-Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295 8.3 Professionelle Formulare und »Sticky Forms« . . . . . . . . . . 301 Ausfüllhinweise und Feldvorgaben . . . . . . . . . . . . . . . . . . 302 Ausfüllhilfen mit JavaScript . . . . . . . . . . . . . . . . . . . . . . . . 303 Fehlerangaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 Sticky Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315 Mehrseitige Formulare mit versteckten Feldern . . . . . . . . 325 8.4 Dateien hochladen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334 Prinzip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335 Konfigurationsmöglichkeiten . . . . . . . . . . . . . . . . . . . . . . . 340 8.5 Von der Seite zum Projekt . . . . . . . . . . . . . . . . . . . . . . . . . 341 Die HTTP-Methode GET . . . . . . . . . . . . . . . . . . . . . . . . . 341 Daten per URL übermitteln . . . . . . . . . . . . . . . . . . . . . . . . 344 Sicherheitsprobleme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346 8.6 Cookies und Sessions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349 Cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349 Session-Verwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358 8.7 Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365 Wichtige Systemarrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365 Server- und Umgebungsvariablen . . . . . . . . . . . . . . . . . . . 365 Session-Verwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368 8.8 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372 Tag 9 Professionelle Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . 373 9.1 Mehrsprachige Webseiten . . . . . . . . . . . . . . . . . . . . . . . . . 374 Browserdaten erkennen . . . . . . . . . . . . . . . . . . . . . . . . . . . 374 Lokalisierung und Formatierung von Zeichen . . . . . . . . . 376 10
  • Inhaltsverzeichnis 9.2 Dynamisch Bilder erzeugen . . . . . . . . . . . . . . . . . . . . . . . . 379 Prinzip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379 Einführung in die Grafikbibliothek GD2 . . . . . . . . . . . . . 381 Anwendungsbeispiel »Dynamischer Werbebanner« . . . . . 386 9.3 Code röntgen: Die Reflection-API . . . . . . . . . . . . . . . . . . . 395 Die Reflection-API als Objektmodell . . . . . . . . . . . . . . . . . 395 Die Reflection-Klassen im Detail . . . . . . . . . . . . . . . . . . . . 397 9.4 Funktions-Referenz GD2 . . . . . . . . . . . . . . . . . . . . . . . . . . 407 9.5 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413 Tag 10 Kommunikation per HTTP, FTP und E-Mail. . . . . . . . . . . . . . 415 10.1 Konzepte in PHP5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416 Prinzip der Streams und Wrapper . . . . . . . . . . . . . . . . . . . 416 Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418 10.2 Streams und Wrapper anwenden . . . . . . . . . . . . . . . . . . . . 419 Daten von einer fremden Website beschaffen . . . . . . . . . . 419 Daten komprimiert speichern. . . . . . . . . . . . . . . . . . . . . . . 420 10.3 Filter verwenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422 10.4 Die Stream-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . 425 Eigene Wrapper und Filter. . . . . . . . . . . . . . . . . . . . . . . . . 425 Anwendung spezifischer Stream-Funktionen . . . . . . . . . . 425 10.5 E-Mail versenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427 Vorbereitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429 Praktische Umsetzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430 10.6 Funktions-Referenz Stream-Funktionen . . . . . . . . . . . . . . 440 10.7 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 442 Tag 11 Datenbankprogrammierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443 11.1 Prinzip der Datenbankprogrammierung . . . . . . . . . . . . . . 444 11.2 Die universelle Abfragesprache SQL . . . . . . . . . . . . . . . . . 444 Was ist SQL? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445 Tabellen und Abfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445 Tabellen anlegen und füllen . . . . . . . . . . . . . . . . . . . . . . . 448 Aktualisieren und Löschen von Daten . . . . . . . . . . . . . . . . 452 Fortgeschrittene Abfragen mit SELECT . . . . . . . . . . . . . . 453 Verknüpfungen zwischen Tabellen . . . . . . . . . . . . . . . . . . 457 Fortgeschrittene SQL-Techniken. . . . . . . . . . . . . . . . . . . . 459 11
  • Inhaltsverzeichnis 11.3 Der MySQL-Dialekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461 Grobe Abweichungen vom SQL92-Standard . . . . . . . . . . 461 Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 462 Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 464 11.4 Erste Schritte mit MySQL und MySQLi . . . . . . . . . . . . . . 477 MySQLi vorbereiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477 Verbindung testen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 478 Mit der Datenbank arbeiten . . . . . . . . . . . . . . . . . . . . . . . . 479 11.5 Referenz MySQLi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491 11.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 496 Tag 12 Die integrierte Datenbank SQLite . . . . . . . . . . . . . . . . . . . . . . . 497 12.1 Hintergrund. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 498 12.2 Vor- und Nachteile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499 Wann SQLite vorteilhaft ist . . . . . . . . . . . . . . . . . . . . . . . . 499 Wann SQLite nachteilig ist . . . . . . . . . . . . . . . . . . . . . . . . 499 12.3 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 500 Eine einfache Beispielanwendung . . . . . . . . . . . . . . . . . . . 500 Eine Benutzerverwaltung mit SQLite . . . . . . . . . . . . . . . . 501 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505 12.4 Referenz SQLite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506 12.5 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 508 Tag 13 Datenbanklösungen mit MySQL . . . . . . . . . . . . . . . . . . . . . . . . 509 13.1 Bibliotheks-Verwaltung mit MySQL. . . . . . . . . . . . . . . . . . 510 Schrittfolge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510 Weitere Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510 Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510 13.2 Vorbereitung der Datenbank. . . . . . . . . . . . . . . . . . . . . . . . 512 Datenbank anlegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 512 Tabellen anlegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513 13.3 Die Seiten des Projekts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 516 Vorbereitungen – das Template-System. . . . . . . . . . . . . . . 516 Funktionsweise des Template-Systems. . . . . . . . . . . . . . . . 517 Der Code der Seiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 523 Die Geschäftslogik des Ausleihvorgangs . . . . . . . . . . . . . . 537 13.4 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 546 13.5 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 547 12
  • Inhaltsverzeichnis Tag 14 XML und Webservices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 549 14.1 Vorbemerkungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 550 XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 550 XSLT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 553 XPath . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 562 Ein konkretes XML-Format: RSS. . . . . . . . . . . . . . . . . . . . 568 14.2 Einführung in die libxml2 . . . . . . . . . . . . . . . . . . . . . . . . . 575 Neu in PHP5. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 575 Die neuen DOM-Funktionen . . . . . . . . . . . . . . . . . . . . . . 576 14.3 SimpleXML. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 581 Unterschiede zur DOM-Schnittstelle . . . . . . . . . . . . . . . . 581 Ein Schritt weiter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 583 XML-Namensräume . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 588 14.4 SOAP-Webservices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 593 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 593 Der Amazon-Webservice: ein Überblick . . . . . . . . . . . . . . 597 Webservices konsumieren . . . . . . . . . . . . . . . . . . . . . . . . . 607 14.5 Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 620 Referenz SimpleXML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 620 Referenz DOM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 621 14.6 Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 626 Anhang A Antworten auf die Kontrollfragen . . . . . . . . . . . . . . . . . . . . . . . . 627 Stichwortverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 643 13
  • Vorwort Sie möchten PHP lernen? Kein Problem, nehmen Sie sich ein Buch, ein paar Tage Zeit und schon können Sie PHP?! Leider ist das nicht ganz so, denn PHP ist keine 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 viel mehr 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 maximalen Nutzen 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 Techniken eingebunden ist, í wo Sie im Internet mehr Quellen finden und wie Sie Hilfe bei praktischen Problemen erhalten. Für wen das Buch und die Reihe gedacht sind Das vorliegende Buch ist Teil der sehr erfolgreichen »14-Tage«-Reihe. Warum 14 Tage? An wen wurde dabei als Zielgruppe gedacht? 15
  • Vorwort Unsere Zielgruppe als Leser Das Buch wendet sich an Leser, die bisher mit anderen Skriptsprachen oder auch nur mit HTML gearbeitet haben oder sich in der Ausbildung befinden. Elemen- tare Grundlagen der Programmierung sind hilfreich, vieles lässt sich aber auch gut aus dem Zusammenhang erschließen. Dieses Buch ist so ausgelegt, dass Sie jedes Kapitel an einem Tag durcharbeiten können. Nach nur 14 Tagen haben Sie dann alle wichtigen Funktionen kennen gelernt 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ügig in spezifische Probleme einzuarbeiten und dort die Feinheiten der jeweiligen Funktion zu verstehen. Dieses Buch kann und will nicht jedes Detail behandeln. Eine umfassende Darstellung würde gut den zehnfachen Umfang erfordern. Um in einer überschaubaren Zeit zu greifbaren Ergebnissen zu gelangen, müssen Sie sich 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 Tages einige 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 sind kein 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 mit den Technologien auseinander setzen, die rund um den Webserver benutzt wer- den. An erster Stelle steht natürlich HTML. Ohne HTML-Kenntnisse werden Sie kein einziges brauchbares PHP-Programm erstellen. Damit eng verbunden ist Java- Script, das HTML an einigen Stellen etwas unter die Arme greift. Ohne JavaScript kommen Sie aus, bleiben aber immer in der Amateur-Liga. Gleiches gilt für Datenbanken und die Datenbankabfragesprache SQL. Es gibt viele kleine und gute Programme, die auf eine Datenbankanbindung verzichten. 16
  • Für wen das Buch und die Reihe gedacht sind Praktisch jedes größere Projekt baut jedoch darauf auf, und es sind signifikante Vorteile, die den Einsatz von Datenbanksystemen in so breiter Front vorangetrie- ben haben. SQL spielt deshalb in diesem Buch eine bedeutende Rolle. In engem Zusammenhang damit steht XML – eingesetzt zum Datenaustausch und zur Schaffung von universellen Datenschnittstellen. Auch dieses Thema nimmt brei- ten Raum ein und zwingt so auch den Leser zur aktiven Auseinandersetzung damit. Zwischen Browser und Webserver pendeln die Daten nicht im luftleeren Raum. Das Web basiert auf Protokollen, die den Verkehr regeln – allen voran HTTP. Da Sie als PHP-Entwickler die Kontrolle über den Webserver haben, nehmen Sie die Rolle des Verkehrspolizisten ein. Und an dieser Stelle müssen Sie zwangsläufig HTTP kennen. Damit eng verbunden sind die anderen im Internet verwendeten Protokolle 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. Er arbeitet 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. Des Weiteren veröffentlichte er mehr als 30 Fachbücher zu Themen wie Windows 2000, Windows XP, Microsoft Active Server Pages, ASP.NET, C#, VB.NET, PHP sowie Electronic Commerce und Online Marketing. 17
  • Vorwort Interessierten Lesern steht der Autor für professionelle Softwareentwicklung und Programmierung 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ösung eines 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
  • W T ag 1 Einführung 21 O T ag 2 Erste Schritte 45 C Tag 3 Daten verarbeiten 73 H T ag 4 Programmieren 131 E T ag 5 Daten mit Arrays verarbeiten 181 Tag 6 Objektorientierte Programmierung 203 T ag 7 Das Dateisystem entdecken 243 W T ag 8 Formular- und Seitenmanagement 277 O Tag 9 Professionelle Programmierung 373 C Tag 10 Kommunikation per HTTP, FTP und E-Mail 415 H Tag 11 Datenbankprogrammierung 443 E Tag 12 Die integrierte Datenbank SQLite 497 Tag 13 Datenbanklösungen mit MySQL 509 Tag 14 XML und Webservices 549
  • Einführung 1
  • Einführung 1.1 Die Geschichte von PHP Dieser Abschnitt zeigt einen kurzen Abriss der Geschichte von PHP, die zwar erst kurz aber dafür umso beeindruckender ist. PHP/FI 1995 entwickelte der damals erst 17-jährige Däne Rasmus Lerdorf unter dem Namen PHP/FI einen Satz von Perl-Skripten zur Erfassung der Zugriffe auf seine Website. 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 Leistung und höherer Geschwindigkeit, als dies mit den damaligen Perl-Werkzeugen mög- lich war. PHP/FI kannte Variablen, konnte Formularinhalte interpretieren und besaß eine Perl-ähnliche Syntax, die allerdings noch relativ inkonsistent war. Lerdorf entschied sich, den Quellcode von PHP/FI im Sinne der Debian Free Software 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 mehrere Entwickler 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 von PHP3 abgelöst. PHP3 Im gleichen Jahr starteten Andi Gutmans und Zeev Suraski eine Kooperation mit Rasmus Lerdorf. Auf der Basis von PHP/FI 2.0 aufbauend kündigten sie PHP 3.0 als offiziellen Nachfolger an, denn die bestehende Leistungsfähigkeit von PHP/FI 2.0 hatte sich vor allem im Bereich von eCommerce-Applikationen als nicht ausreichend erwiesen. Die neue Sprache sollte unter einem neuen Namen veröf- fentlicht werden; vor allem deswegen, um die im alten Namen implizierte einge- schränkte Nutzung aufzugeben. Man einigte sich auf PHP, das – ganz im Stile des GNU-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. 22
  • Die Geschichte von PHP PHP3 überzeugte vor allem durch seine Erweiterungsmöglichkeiten, die solide Infrastruktur, die Möglichkeit einer objektorientierten Syntax sowie die Unterstüt- zung der gängigsten Datenbanken. Dutzende von Entwicklern sahen diese Stärken und beteiligten sich mit neuen Modulen an PHP3. Möglicherweise war gerade die Zusammenarbeit von vielen Entwicklern einer der Gründe für den gewaltigen Erfolg von PHP. Ende 1998 wurde PHP von einigen Zehntausend Benutzern ver- wendet. Die Installationen von PHP deckten dabei schätzungsweise 10% aller Websites im Netz ab. PHP4 Bereits kurz nach dem offiziellen Release von PHP3 begannen Gutmans und Suraski, den Kern von PHP umzuschreiben. Die Leistung komplexer Applika- tionen sollte gesteigert werden und der Basiscode sollte modularer werden. Mitte 1999 wurde die neue PHP-Engine unter dem Namen »Zend« (gebildet aus Teilen der Vornamen Zeev und Andi) erstmalig eingeführt, der auch Namensgeber der Firma der beiden ist: Zend Ltd. (www.zend.com). Ein Jahr später konnte PHP4.0 offiziell freigegeben werden. Basierend auf der »Zend«-Engine glänzte diese Ver- sion neben ihrer stark verbesserten Leistung vor allem durch ihre Unterstützung verschiedenster Webserver, HTTP-Sessions, Ausgabepufferung und vielen neuen Sprachkonstrukten. PHP 4 wird weltweit von unzähligen Entwicklern erfolgreich eingesetzt. Mit meh- reren Millionen Installationen (schätzungsweise 20% aller Websites) ist PHP damit eine der erfolgreichsten Entwicklungen im dynamischen Internet. PHP5 Die Arbeit an der Spezifikation und der Verbesserung der »Zend«-Engine als Basis für die neuen Leistungsmerkmale von PHP5 ist der nächste Schritt. Obwohl PHP 4 bereits optimal an die Aufgabe »Webserverprogrammierung« angepasst ist und vielen Entwicklern ein verlässliches Werkzeug in die Hand gibt, blieben Kri- tikpunkte bestehen. Vor allem die Profilager, wo mit C++ und Java programmiert wird, bemängelten die eingeschränkten Fähigkeiten der Sprache im Bereich Objektorientierung (OOP). Eine weitere wichtige Funktion war die Möglichkeit, einen Applikationsserver aufzubauen. Skriptprogramme laufen normalerweise nur auf 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. 23
  • 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 einem Applikationsserver ist allerdings nach wie vor weit und breit nichts zu sehen. Dies wird »Skripter« freuen, denen die Einfachheit des Systems am Herzen liegt, und das Profilager zu Recht an Java, C# und C++ festhalten lassen. PHP5 ist ein gutes, professionelles und leistungsfähiges Tool geworden, mit dem sich kleine und mittlere Projekte schnell und effizient erstellen lassen. Diesen Markt 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 einige Vertreter der PHP-Community partout nicht verstehen wollen. 1.2 PHP auf einen Blick Wenn Sie neugierig sind und darauf brennen, schnell in PHP einzusteigen, sollten Sie sich nicht zurückhalten lassen. Es ist durchaus hilfreich, schon mal ein paar Skripte laufen zu lassen, bevor die ganzen theoretischen Schritte durchgegangen werden. 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 nicht vorziehen möchten und auch keinen Provider haben, der Skripte ausführt, gehen Sie 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. Wenn Sie sich damit auskennen, nutzen Sie den Ihnen bekannten Webserver und legen Sie ein virtuelles Verzeichnis mit dem Namen »MuT« an, in das Sie dann alle Skripte speichern. Rufen Sie die fertigen Skripte nun wie folgt auf: http://localhost/MuT/<scriptname> 24
  • PHP auf einen Blick Statt <scriptname> steht der Name des Skripts, beispielsweise listing1.php. Die Dateierweiterung benötigt der Webserver, um die Datei an den PHP-Interpreter weiterzureichen. 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 PHP ist nicht oder falsch installiert. Zur Lösung dürfen Sie schon mal zum zweiten Tag blättern und schmulen. Mit PHP spielen PHP wird in HTML eingebettet. Das heißt, Sie erstellen primär eine einfache HTML-Seite und bringen darin PHP unter. Der erste Schritt besteht deshalb in der 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-Adresse 127.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. Am zweiten Tag werden ein paar Tipps vorgestellt, wie man ein Entwicklungssystem geschickt aufbaut. Nun geht es aber wirklich los! Das erste Skript diente nur als Test für die Funktion des Webservers und von PHP. Es enthielt freilich noch keinen PHP-Code. Eine kleine Erweiterung soll nun auch PHP ins Spiel bringen: 25
  • 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 Zeilen sind, 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. Zeilenweise betrachtet sieht dies nun folgendermaßen aus: í <?php Hier geht es los: Diese Zeichenfolge leitet einen Programmteil ein, der von PHP 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 hat den 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 des Namens. 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 dieser Befehl nun zu Ende ist. Der Zeilenwechsel selbst ist PHP ziemlich egal (meis- tens jedenfalls), das Programm liest sich aber so besser. í print($text); Hier ist auch wieder richtig viel los: »print« ist eine Funktion, die PHP bereit stellt und die Daten ausgibt; in diesem Fall die Variable $text und damit deren Inhalt. Ausgeben heißt bei PHP »an dieser Stelle ins HTML einfügen«. Denn die eigentliche Ausgabe zum Browser – das Senden der Daten – erledigt der Webserver. 26
  • PHP auf einen Blick í ?> Damit alles seine Ordnung hat, wird der PHP-Abschnitt korrekt beendet. Der Rest 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 über 100 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 dieser Abschnitt. 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 oben nach 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 Programmierer macht 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ßenden Sequenzen und die abschließenden Semikola. Manche Befehle wirken über einen bestimmten Bereich hinweg bzw. fassen mehrere 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) { $text = "a und b sind gleich"; 1 Um Papier zu sparen, wird der langweilige HTML-Teil hier nicht jedes Mal mitgedruckt 27
  • Einführung print($text); } ?> PHP wird deshalb der Gruppe der so genannten Klammersprachen zugeordnet, zu der auch Java und alle C-Versionen zählen. Die Klammer ersetzt übrigens das Semikolon, aber das haben Sie sicher bereits bemerkt. Eine wichtige Technik ist das Einrücken. Das ist dem PHP-Interpreter natürlich wieder egal, es dient nur der Verbesserung der Lesbarkeit. Tun Sie sich selbst und allen späteren Entwicklern des 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 Quelltext sind ein gutes Mittel, sich als mitteilsam zu erweisen und die Lesbarkeit weiter zu verbessern. Dazu setzen Sie wieder spezielle Zeichen ein: <?php // $a und $b wurden bereits woanders definiert if ($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 diesem Thema 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 man Daten 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 dritte Tag handfeste Informationen zu bieten haben. Damit die Sache in der Praxis nicht langweilig wird (wer kommt schon mit 2.000 Funktionen aus?), können Sie auch eigene Funktionen definieren. 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 eingehenden Betrachtung. Tag 6 widmet sich ganz diesem Thema, ebenso wie den selbst erstell- ten Funktionen. 28
  • 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 bearbeitet werden, einem Webserver, PHP5 ausführen kann, und einem Browser, der die Skripte vom Webserver abruft. Für die Bearbeitung von Datenbanken wird noch ein Datenbanksystem benötigt. Vorbemerkungen PHP5 ist fest in der Linux-Welt etabliert. Inzwischen existiert jedoch auch eine sehr stabile Version für Windows, die den Einsatz auch auf Produktionssystemen erlaubt. 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 unter Linux 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 genannten WAMP-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 allerneuesten Versionen möchten, müssen Sie diese entsprechend aus dem Internet herunterla- den. Benötigt werden: í PHP5 – Binärdistribution für Windows í PHP5 PECL-Module í Apache 2 – Binärdistribution für Windows 29
  • 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.php Nutzen 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 werden könnten. PHP5 selbst ist ca. 7,5 MB groß, die Module brauchen etwa 930 KB. í Apache: httpd.apache.org/download.cgi Wählen Sie hier die Option »Win32 Binary (MSI Installer)«. Der Dateiname hat in etwa die Form »apache_2.0.50-win32-x86-no_ssl.msi«. Es handelt sich also 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 Webserver genau diese Version finden, können Sie sie auch von der CD nehmen. Windows-Installer-Pakete sind mit Windows XP eingeführt worden und laufen sofort auf Windows XP Pro, XP Home und Windows Server 2003. Sie laufen auch auf einem aktualisierten Windows 98 und Me. Falls MSI als Dateierweiterung nicht bekannt ist, müssen Sie sich über die Microsoft-Download-Seite ein entsprechendes Update beschaffen. Benötigt wird mindestens Microsoft Installer 1.2. Gehen Sie bei Bedarf auf folgende Website: www.microsoft.com/downloads/release.asp?Release ID=32831 (Win 9X) bzw. www.microsoft.com/downloads/release.asp?Re leaseID=32832 (NT4, 2000). Nach dem die Installationspakete vorliegen, beginnt die Installation. Installieren Sie zuerst den Webserver, testen Sie ihn und fahren Sie dann mit PHP5 fort. 1.4 Apache installieren Dank des Installers ist die Installation ganz einfach. Die Schritte werden nachfol- gend gezeigt und anhand der Bemerkungen können Sie die nötigen Angaben machen. Starten Sie nun den Installer und überspringen Sie den Startbildschirm des Assistenten mit NEXT. 30
  • Apache installieren Installationsstart Nach dem Start der Installation müssen Sie die Lizenzbedingungen bestätigen und eine Informationsseite mit allgemeinen Hinweisen über sich ergehen lassen. Gehen Sie jeweils mit NEXT weiter, bis Sie zur Seite SERVER INFORMATION gelangt sind. Abbildung 1.1: Auch bei GPL- Projekten sind die Lizenzbestimmun- gen anzuerkennen Erste Schritte Die erste erforderliche Eingabe konfiguriert den Server. Webserver sind immer Teil einer Domain, wenn Sie im Inter- oder Intranet laufen, beispielsweise php.net. Sie haben immer einen bestimmten Namen, der meist »www« lautet. Der Webserver der PHP Group heißt deshalb www.php.net. Es ist möglich, dass Sie lokal über keine Domain verfügen, weil ihr Rechner direkt am Internet hängt, Sie also keine eigenen Namensdienste betreiben. Die Angabe der richtigen Domain 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 stellt sicher, dass Anfragen nicht über Router ans Internet oder andere Computer im Netzwerk weitergeleitet werden. Meist ist die entsprechende Verknüpfung zwi- 31
  • Einführung schen localhost und 127.0.0.1 schon vorhanden. Um dies zu prüfen, öffnen Sie die Datei hosts (ohne Dateierweiterung) im Verzeichnis c:windowssystem32 driversetc. Ist die Datei leer, wird folgende Zeile eingefügt: 127.0.0.1 localhost Achten Sie beim Speichern darauf, dass der Editor keine Dateierweiterung anhä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, wenn Sie 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 frei sein. Haben Sie keinen anderen Webserver installiert, ist dieser Port frei. Abbildung 1.2: Einstellungen für ein lokales System 32
  • 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). Abbildung 1.3: Benutzerdefinierte Installation – verhindert unnö- tige Module Sie können nun auf der nächsten Seite einige Optionen abwählen, die für PHP5 nicht benötigt werden. Falls Sie sich intensiver mit Apache auseinandersetzen möchten, belassen Sie die Dokumentation im Installationspaket. Allerdings sind die Texte auch online verfügbar. Mit einer schnellen Internetverbindung können Sie online schneller arbeiten (siehe Abbildung 1.4). Im nächsten Schritt müssen Sie den Start der Installation nur noch bestätigen, den Rest erledigt der Installer dann automatisch (siehe Abbildung 1.5). Falls Fehler auftreten, werden diese in Konsolenfenstern angezeigt. Am Ende erhalten Sie eine Erfolgsmeldung. Nun kann der Webserver getestet werden. Ein erneuter Start des Installers erlaubt die Reparatur oder Vervollständi- gung einer bestehenden Installation, die nicht komplett installiert wurde oder aus anderen Gründen nicht läuft. 33
  • Einführung Abbildung 1.4: Was nicht benö- tigt wird, fliegt raus Abbildung 1.5: Während der Installation 34
  • Apache installieren Apache testen Der erste Test ist sehr einfach. Öffnen Sie Ihren Browser und geben Sie folgende Adresse ein: http://localhost Abbildung 1.6: Der Webserver funktioniert Weitere Einstellungen Der Apache Webserver wird über eine Konfigurationsdatei mit dem Namen httpd.conf verwaltet. Diese ist über START | ALLE PROGRAMME | APACHE HTTP SERVER 2.0.50 | CONFIGURE APACHE SERVER zu finden. Prüfen Sie beispielsweise Ihre Einstellungen, die Sie während der Installation vor- genommen haben, indem Sie nach einer Zeile suchen, die mit SERVERNAME beginnt. Dort sollte folgendes stehen: ServerName localhost:80 35
  • 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 dieses Verzeichnis htdocs und liegt unter C:ProgrammeApache GroupApache2htdocs Apache automatisch starten Es ist sinnvoll, wenn der Webserver nach dem Start des Betriebssystems sofort zur Verfügung steht. Bei manueller Arbeitsweise können Sie Apache über entspre- chende Aufrufe starten und stoppen. Über START | ALLE PROGRAMME | APACHE HTTP SERVER 2.0.50 | CONTROL APACHE SERVER finden Sie die Optionen START, STOP und RESTART. Abbildung 1.7: Dienst-Verwaltung für den Apache Webserver 36
  • PHP5 installieren Unter Win9X fügen Sie den Startaufruf in den Autostart-Ordner des Startmenüs ein. Auf XP und 2000 wird Apache automatisch als Dienst installiert und startet mit 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 suchen Sie 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 den STARTTYP 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ählen Sie ein passendes Verzeichnis, am besten im Zweig Ihres Apache-Webservers: C:ProgrammeApache Groupphp5 Entpacken Sie alle Dateien dorthinein. Denken Sie daran, dass Winzip beim Ent- packen evtl. den Namen der Datei in den Pfad einbaut. Unter dem Verzeichnis php5 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 Module nach folgendem Pfad kopieren: C:ProgrammeApache Groupphp5ext Hier liegen bereits die standardmäßig in PHP5 eingebundenen Module. 1.6 PHP5 konfigurieren PHP5 wird – ähnlich wie der Apache Webserver – über eine Konfigurationsdatei mit dem Namen php.ini konfiguriert. Nach der Installation gibt es diese noch nicht. Stattdessen finden Sie zwei Musterdateien: 37
  • Einführung í php.ini-dist Dies ist die Datei mit allen Standardoptionen. í php.ini-recommended Eine Datei mit der empfohlenen Konfiguration. Erstellen Sie nun eine Kopie von php.ini-dist und nennen diese php.ini. Lassen Sie 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 ist das 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 Eigentlich muss man bei PHP5 nicht viel konfigurieren. Einige Optionen sind dennoch 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: 38
  • PHP5 testen extension_dir = "./" Tragen Sie hier den Pfad zum ext-Verzeichnis ein: extension_dir = "C:ProgrammeApache GroupPHP5ext" 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 der Eintrag bereits vorhanden ist, das Semikolon weg. Um ein PECL-Modul zu benutzten, fügen Sie den entsprechenden Eintrag hinzu. Für dieses Buch sind fol- gende Einträge sinnvoll: extension = php_gd2.dll extension = php_mysqli.dll extension = 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 dienen zwei Optionen: upload_tmp_dir = "C:Windowstemp" 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 Dateien auf maximal 8 MB ein, der Standardwert 2 MB ist meist zu klein. Die Verwaltung von Benutzersitzungen spielt in PHP5 eine große Rolle. Damit später alles funktioniert, ist auch hier eine entsprechende Änderung erforderlich: session.save_path = "C:Windowstemp" 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. 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- 39
  • Einführung ver ein primitives Stück Software. Er erkennt Anfragen mit dem Protokoll HTTP auf Port 80 (falls so konfiguriert) und führt diese aus. Das heißt, er erkennt mindes- tens die HTTP-Kommandos GET (Anfordern einer Ressource, beispielsweise einer Datei) und POST (Anfordern einer Ressource, der zugleich Formulardaten übergeben werden). GET führt standardmäßig dazu, dass der Webserver auf der Festplatte 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 gute Ergänzung. Alle Skripte in diesem Buch tragen die Dateierweiterung .php und las- sen sich nur dann unverändert einsetzen, wenn dies auch im Apache entsprechend konfiguriert 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 (Common Gateway 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 immer nur ein Browser läuft und damit kaum parallele Zugriffe stattfinden, gibt es jedoch keinen Unterschied in der Zugriffsgeschwindigkeit. CGI bietet dafür einen durch- aus ernst zu nehmenden Vorteil. Da bei jedem Aufruf die entsprechende Instanz gestartet wird, die die Anfrage ausführt, werden auch eventuell vorhandene Ände- rungen an der Konfiguration sofort erkannt. Da gerade in der ersten Zeit häufig mit den Konfigurationen gespielt wird, wäre ein ständiges Stoppen und Starten des Servers suboptimal. Denn läuft PHP als Modul, wird es nur einmal gestartet und bleibt für jeden folgenden Aufruf aktiv. Änderungen an der PHP-Konfiguration wirken sich damit nicht sofort aus, da diese nur beim Start gelesen wird. Änderungen an der httpd.conf erfordern immer einen Neustart des Web- servers – also des Apache2-Dienstes im Besonderen. Das Betriebssystem selbst muss niemals neu gestartet werden. 40
  • 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 .php Action application/x-httpd-php "/php/php-cgi.exe" Änderungen wirken sich erst aus, wenn der Apache-Dienst neu gestartet wird. Die drei 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 bei den 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 Funktion bereit, die über die gesamte Konfiguration Auskunft gibt: phpinfo. Diese wird benutzt, um das allerkleinste PHP-Skript zu schreiben: Listing 1.3: info.php – Informationen über PHP5 ermitteln <?php phpinfo(); ?> Speichern Sie diesen Text unter dem Namen info.php unter htdocs ab. Rufen Sie dann 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 andere Daten über Ihr PHP5. Wenn Sie Änderungen an der Datei php.ini vornehmen, beispielsweise Erweiterungen hinzufügen, erscheinen hier Angaben über jedes einzelne Modul. Sie können so sehr schnell kontrollieren, ob die Einstellungen den gewünschten Effekt haben. Stimmt alles, kann es jetzt mit der Programmie- rung richtig losgehen. 41
  • Einführung Abbildung 1.8: PHP5 läuft auf einem Apache- Webserver 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. Ein paar 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 so bereits belegt. Weisen Sie dem IIS eine andere IP-Adresse, einen anderen Port zu oder deaktivieren Sie ihn ganz. Starten Sie die Apache-Installation erneut mit der Option REPAIR. í 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 42
  • 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 LOG FILES | 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.conf nicht 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-Code erkannt? 5. Welches Protokoll wird zur Übertragung von HTML-Seiten vom Server zum Browser verwendet? 6. Welcher Webserver kann auf allen Betriebssystemen zur Entwicklung eingesetzt werden? 7. Wofür steht der Begriff WAMP? 8. Welche Bedeutung hat die Adresse »http://localhost«? 9. Mit welcher Funktion kann PHP5 ausführlich getestet werden? 43
  • Erste Schritte 2
  • Erste Schritte Die ersten Schritte mit PHP beginnen immer mit HTML. Denn es ist das Ziel jeder PHP-Anwendung, HTML-Seiten zu erzeugen. Darauf aufbauend wird das Einbetten 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 von Webserver und Browser näher bringen. 2.1 Einfache HTML-Seiten Ihre Entwicklungsumgebung sollte also in der Lage sein, HTML-Seiten über einen 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 wenn dies funktioniert, lohnt es, mit PHP5 weiterzumachen. Viele Fehler bei der Entwicklung von PHP entstehen, weil schlechtes oder nicht lauffähiges HTML erzeugt wird. Browser tolerieren zwar falsches HTML, reagie- ren also nicht mit einer Fehlermeldung. Sie zeigen aber im Zweifelsfall gar nichts oder völlig unsinniges an. Nicht immer ist ein falsches PHP-Skript daran schuld. Wenn beispielsweise vergessen wurde, das <title>-Tag zu schließen, nimmt der Browser den gesamten Text als Titel und die Seite selbst bleibt weiß. Solche Feh- ler offenbart ein Blick in den Quelltext, beim Internet Explorer am einfachsten über das Kontextmenü (rechte Maustaste) – QUELLTEXT ANZEIGEN. 46
  • 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>-Tags stehen. 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 Stildateien werden über das Tag <link> eingebunden: <link href="stile/comzept.css" type="text/css"/> 47
  • 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 <sub> ja Tiefer gestellt <p> wahlweise Absatz Tabelle 2.1: Wichtige Formatier- und Auszeichnungselemente 48
  • Einfache HTML-Seiten Tag Wichtige Attribute Container? Beschreibung <br> clear="left|right" nein Zeilenumbruch, das Attribut clear bestimmt das Verhalten angrenzender Blöcke <img> src, width, height, nein Fügt ein Bild ein, src bestimmt hspace, vspace 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 Tabelle 2.1: Wichtige Formatier- und Auszeichnungselemente (Forts.) Wenn keine Attribute benannt sind, können dennoch immer die Standardattribute benutzt werden: í style Enthält eingebettete Stildefinitionen, beispielsweise color:red; í class Verweist auf eine Klasse, die in Stildefinitionen der Seite erstellt wurde. í id Verweist 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 der Formatierung. Die Grundstruktur einer Tabelle basiert auf folgenden Tags: í <table></table> Diese Tags umschließen die Tabelle. 49
  • 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 erscheint fett 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 unsichtbar bleibt. Abbildung 2.1: Eine einfache Tabelle Wichtige Tabellenattribute Die folgenden Tabellen zeigen die wichtigsten Attribute für die Tags <table> und <td>. 50
  • Einfache HTML-Seiten 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> Vor allem der Umgang mit colspan und rowspan ist nicht ganz einfach. Dabei muss beachtet werden, dass die Gesamtzahl der Zellen pro Reihe immer gleich ist, damit sich eine konstante Anzahl von Spalten bildet. Andernfalls zerfällt die Tabelle. Das folgende Bild zeigt, wie eine Verknüpfung von Zellen mit colspan="2" bzw. rowspan="2" aussieht: Abbildung 2.2: Verknüpfte Zellen mit rowspan und colspan 51
  • 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 sich durch die Verknüpfungen die erforderliche Anzahl von drei Zellen ergibt. Das heißt, die Angabe <td> steht für eine Zelle, während <td colspan="2"> für zwei Zellen 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äufige Unterstützung für die Verarbeitung von Formulardaten. Deshalb ist eine gute Kenntnis der Formularelemente unerlässlich. Ein Formular erstellen Ein Formular entsteht, indem Formularelemente neben normalem HTML inner- halb des Tags <form> gestellt wird. Die Formularelemente Die folgende Tabelle zeigt alle Formularelemente und die wichtigsten Attribute: 52
  • PHP einbetten 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) Die Benutzung wird an anhand vieler Beispiele im Buch dokumentiert, sodass eine ausführliche Darstellung an dieser Stelle nicht sinnvoll erscheint. Sollte Ihnen das eine oder andere Tag gänzlich unbekannt erscheinen, konsultieren Sie die entsprechende Fachliteratur zum Thema HTML. 2.2 PHP einbetten Bislang wurde der Text in den HTML-Seiten statisch ausgegeben. Nach diesen Grundlagen ist es nun an der Zeit, PHP5 zum Leben zu erwecken. Die folgenden Beispiele zeigen, wie das mit PHP funktioniert. Die genaue Arbeitsweise wird im Laufe des Tages noch genauer erläutert. Probieren Sie die Beispiele erst aus. Wie PHP den Code erkennt Wenn der Browser eine PHP-Seite beim Webserver anfordert, erkennt dieser an der Dateierweiterung, dass PHP für die Verarbeitung zuständig ist. Der Webserver übergibt dem PHP-Modul die Information, welche Seite verarbeitet werden soll. 53
  • Erste Schritte Das Modul lädt diese Seite dann von der Festplatte und liest den Inhalt Zeile für Zeile ein. In einem mehrstufigen Prozess entstehen daraus Zwischencodes, die interpretiert 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 nicht und erlaubt weiterhin die Mischung von PHP und (X)HTML. Es gibt noch andere Erkennungszeichen, 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 kann zwar theoretische Erwägungen darüber anstellen, ob die mehrfache Eröffnung von Code-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. Dies betrifft normalerweise den HTML-Anteil. Da die Verarbeitung von oben nach unten erfolgt, wird die Seite genau so gesendet, wie sie aufgebaut ist. Es ist nun Sache der eingebetteten Codes, an den richtigen Stellen vernünftige Ausgaben zu erzeugen. Listing 2.5: PhpStart1.php: Eine einfache Ausgabe <html> <head> <title>PhpStart1</title> </head> <body> <?php echo "PHP beginnt hier..."; ?> </body> </html> 54
  • Ausgaben erzeugen Innerhalb der Code-Blöcke folgt nun PHP-Anweisung auf PHP-Anweisung. Dies kann sehr verschieden aussehen, weil PHP im Umgang mit Code sehr flexibel ist. Zur besseren Lesbarkeit schreibt man in der Regel immer genau eine Anweisung pro Zeile oder teilt sehr lange Konstrukte sogar auf. Mehrere Anweisungen auf einer 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 an der Stelle in die HTML-Ausgabe eingebaut, wo der entsprechende Ausgabebefehl steht. 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 zu Funktionen wie print müssen hier keinen Klammern gesetzt werden. Solange es nur ein einziges Argument gibt, darf dieses aber in runde Klammern gestellt wer- den. Diese Schreibweise ist jedoch unüblich und sollte auch deshalb vermieden werden, um die Natur als Sprachanweisung im Code herauszuheben. Außerdem verkraftet echo beliebig viele Argumente, die durch Kommata getrennt werden. Listing 2.6 zeigt die möglichen Varianten: Listing 2.6: EchoVariants.php – Ausgaben mit der Anweisung echo <?php echo("Hallo PHP5"); echo "<br />"; 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. 55
  • Erste Schritte Variablen ausgeben Variablen sind benannte Speicherstellen für Daten. Am vierten Tag wird dieses Thema ausführlich behandelt. Einstweilen sei nur auf die grundlegende Syntax hingewiesen: $text ist eine Variable mit dem Namen »text«. Für PHP ist sie erkennbar durch das vorangestellte $-Zeichen. Eine solche explizite Erkennungsmöglichkeit erlaubt es, Variablen auch mitten in anderem Text zu erkennen. Das funktioniert tatsächlich, sodass Sie auch folgendes schreiben 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, Variablen in Zeichenketten einzubetten, ist deshalb außerordentlich hilfreich. Sie spart jede Menge Verkettungen und Aufreihungen von Parametern und macht Ihre Pro- gramme lesbarer und einfacher. Abbildung 2.3: Ausgabe mit echo Nun kann es manchmal vorkommen, dass dieses Verhalten nicht gewünscht ist. Sie haben es sicher schon bemerkt: Zeichenfolgen werden in Anführungszeichen gepackt. 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: 56
  • 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: Abbildung 2.4: Variablen werden in einfachen Anführungszeichen nicht erkannt Ausgabe von großen Textmengen Die Möglichkeit, PHP und HTML zu vermischen, ist ein schneller und einfacher Weg dynamische Webseiten zu erstellen. Leider führt diese Vermischung zu sehr schlecht lesbaren Programmen. Solange Sie mit Trivialskripten wie in diesem Kapitel arbeiten, fällt das nicht auf. Später jedoch – wenn richtige Projekte an der Tagesordnung sind – wird der Code schnell unleserlich. Damit schleichen sich Fehler 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 liest einen solchen Block, der auch viele Zeilen umfassen kann, als eine geschlossene Zeichenfolge. Die bereits gezeigte Erkennung von Variablen und die Kennzeich- nung derselben mit geschweiften Klammern steht auch hier zur Verfügung. Das folgende Beispiel zeigt die Anwendung: Listing 2.10: EchoHeredoc.php – Ausgabe von Textblöcken innerhalb von PHP-Code <?php $version = 5; $text = <<<BLOCK1 <h3>Wir lernen PHP in 14 Tagen</h3> Dieses Buch vermittelt PHP{$version} in nur 14 Tagen, von Grund auf und verständlich. BLOCK1; 57
  • 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 auf einer Zeile erscheint. Wichtig ist dabei: Es werden alle Zeichen auf der Zeile aus- gewertet, der Abschnittsname muss deshalb am Anfang der Zeile stehen – ohne irgendwelche Leerzeichen und Tabulatorschritte davor. Der folgende Fehler erscheint, wenn das Endkennzeichen des Abschnitts nicht erkannt wurde: Parse error: parse error, unexpected $end in D:Inetpubwwwrootbuecher.sitephp5MuTEchoHeredoc.php on line 18 Bei $end handelt es sich um einen Hinweis auf einen so genannten Token. Token stellen Code-Fragmente bei der internen Verarbeitung dar. Die Angaben sind hilfreich bei der Fehlersuche. In diesem konkre- ten Fall wurde das Ende des Dokuments erreicht, obwohl dies aufgrund der vorangegangenen Codes nicht zu erwarten war. Variablen werden erkannt, wenn Sie in der im letzten Abschnitt gezeigten Weise erkennbar sind. Abbildung 2.5: Ausgabe von HTML mit Here- doc-Syntax aus PHP-Code heraus Die geschweiften Klammern sollten Sie sich gleich zu Beginn angewöhnen, auch wenn 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- liegen, wie es die Anzeige erfordert. »Formatierung« heißt das Zauberwort, mit dem 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 der internen Programmierung her – zwar auch eine Sprachanweisung, verhält sich 58
  • Ausgaben erzeugen aber im Gegensatz zu echo immer wie eine Funktion. Funktionen geben Daten zurück, wenn sie in Ausdrücken verwendet werden. Auf diese Feinheiten wird in den nächsten Tagen noch genau eingegangen. An dieser Stelle ist es nur wichtig zu wissen, dass Sie mit print Daten ausgeben können. Der Aufruf gibt, wenn die Ausgabe gelang, den Wahrheitswert TRUE (Wahr) zurück. Meist wird dies jedoch nicht 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 im Buch wird darauf hingewiesen, ansonsten wird durchgehend echo verwendet. Formatierung von Ausgaben PHP und HTML sind im Zusammenspiel sehr leistungsfähig. Ein typischer Anwendungsfall besteht im dynamischen Erzeugen von HTML-Tags. Stellen Sie sich vor, eine Navigation aufzubauen, bestehend aus mehreren Hyperlinks, die etwa 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 und der 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; echo '</a>'; ?> Die Funktion printf ist hier ausgesprochen hilfreich, denn sie erlaubt die Erstel- lung einer Formatieranweisung und die getrennte Übergabe von Parametern. Dies 59
  • Erste Schritte erhält die Lesbarkeit des HTML-Tags (oder können Sie die erste echo-Anweisung des 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 Formatanweisungen bezeichnet. Das %-Zeichen leitet sie ein und die nachfolgende Ziffer bestimmt, welches der folgenden Argumente als Datenquelle genommen werden soll. %1 nimmt sich also den Inhalt der Variablen $href. Nach der Zahl folgt eine Format- regel. $s steht für die Ausgabe einer Zeichenkette. Abbildung 2.6: Professionell dynamisch erzeugter Link Eine genauere Betrachtung der Parameter erfordert etwas mehr Lernaufwand. printf gehört mit zu den komplexeren Ausgabemethoden. Die Mühe lohnt sich bereits ganz am Anfang, denn der Programmierung der Benutzerschnittstelle muss immer 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 Arguments angibt. Nach dem Trennzeichen $ folgt ein weiterer Buchstabe, der bestimmt, wie die Daten formatiert werden. Dazwischen können, je nach Aufgabenstellung, wei- tere Formatierzeichen folgen: %N$F-D? 60
  • 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üllt werden 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 im Bedarfsfall mathematisch korrekt. 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 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 <?php $r = 204; $g = 102; $b = 153; printf('<font color="#%1$X%2$X%3$X">Farbausgabe mit printf</font>', 61
  • 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 von Daten bewegen, sollten Sie sich mit bestimmten Programmierstilen vertraut machen. Diese Techniken und Methoden helfen, später den Überblick zu behal- ten und besseren Code zu schreiben. Der Begriff »besser« bedeutet hier: schneller lesbar, einfacher wartbar, zuverlässiger, stabiler, schneller. Code auf möglichst kleinem Raum unterzubringen und der verbreitete Stolz, 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 guter Code ohne gute Kommentare. Dabei sollten Kommentare nie beschreiben, wie der Code arbeitet, sondern was er tut. Denn gute Entwickler können sehr wohl fremden 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- wändiger. 62
  • Professionelles Programmieren Warum Sie Kommentare schreiben sollten Kommentare sollten Sie unbedingt auch dann schreiben, wenn Sie nur für sich selbst 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, geraten so zu einer störenden Mammutaufgabe, die zudem unpassenderweise immer parallel zu einem neuen, spannenden Projekt anfallen. Nicht zuletzt sind Kommentare auch dann hilfreich, wenn man Code nur für den Eigenbedarf entwickelt. Später, wenn Sie mehr Erfahrung haben, lesen Sie Ihren Code 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 dabei sind. Änderungen gab es hier nicht. Der Stil ist weitgehend aus der Programmier- sprache C übernommen worden. Man unterscheidet Kommentarzeichen, die nur fü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 zum nä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«: <?php # Hier beginnt der zweite Teil der Formularauswertung ?> 63
  • Erste Schritte Die Wirkung ist identisch mit der ersten Variante. Beide können auch mitten auf der Zeile beginnen und gelten dann ab dort: <?php echo "Ausgabe"; // Nur zum Test ?> Der Parser führt den PHP-Code am Zeilenanfang aus und ignoriert den Rest der Zeile. Die Schreibweise mit den Schrägstrichen entspricht der in C üblichen und ist weiter verbreitet. Sie sollten aus stilistischen Gründen entweder generell nur eine 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 beginnen immer mit den Zeichen /* und enden mit */. Sie werden oft eingesetzt, um an den Anfang der Seite einen längeren Abschnitt mit Informationen über Programm und 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. Aber es 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 zweiten Zeile aufhört und die zweite Zeile ($d usw.) ausgeführt wird. Deshalb sind zwei verschiedene Kommentarzeichen sehr hilfreich, denn hätte man gleich den Zei- lenkommentar verwendet, hätten die Kommentarzeichen ihre Wirkung nicht ver- fehlt: 64
  • 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 und professionellen Code aus. Regeln aus der PEAR-Welt Fest zu PHP gehört eine große Sammlung an Bibliotheken und Skripten, die immer wiederkehrende Aufgaben erledigen: PEAR (PHP Extension and Application Repo- sitory). Da PEAR-Code weitgehend in PHP geschrieben wird und viele Hundert Entwickler dazu beisteuern, wurde dafür eine Vorgabe entworfen, welchen Stil der Code haben muss, damit alle anderen damit problemlos arbeiten können. Diese Vorgaben sind sehr sinnvoll und sollten – mangels anderer Richtlinien – für alle mittleren und größeren PHP-Projekte gelten. Kleine Testskripte und andere »Ein- zeiler« profitieren davon freilich nur bedingt, sodass man es hier etwas lockerer sehen kann. In der PEAR-Welt heißen diese Vorgaben »Coding Standards«. PHP Coding Standards PHP ist eine so genannte Klammersprache, Blöcke zusammengehörigen Codes werden dabei durch geschweifte Klammern markiert. Es liegt in der Natur der Sache, dass diese Blöcke oft tief verschachtelt werden. Um zusammengehörende Klammern halbwegs lesbar zu behalten, werden die innen liegenden Codes einge- rückt. Der Coding Standard schreibt eine Einrücktiefe von vier Zeichen und die Verwendung von Leerzeichen vor. Editoren sollten so eingestellt werden, dass sie bei Druck auf die (TAB)-Taste vier Leerzeichen erzeugen. 65
  • Erste Schritte Auch die Zeilenlänge hat Einfluss auf die Lesbarkeit. Mehr als 85 Zeichen sind schlecht darstellbar, wenn in modernen Entwicklungsumgebungen noch Werk- zeugkästen und Symbolleisten auf dem Bildschirm platziert werden müssen. PHP ist sehr tolerant in Bezug auf Zeilenumbrüche, deshalb ist eine »Verteilung« von Code 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 gesetzt werden, auch wenn dies im konkreten Kontext nicht zwingend erforderlich ist. Ein Beispiel zeigt dies: <?php if ($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: <?php if ($a < $b) echo $a; echo $b; ?> Das ist zwar nett eingerückt, nur interessiert das PHP kaum. Ohne Klammern wirkt if nur auf eine einzige Anweisung. Deshalb würde der erste Versuch besser folgendermaßen aussehen: <?php if ($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ür sich allein einnimmt. Noch besser lesbar, aber weniger verbreitet, ist es, auch die öffnende Klammer allein auf eine Zeile zu setzen. Dies betont die Verschachte- lungsebene noch intensiver: <?php if ($a < $b) { 66
  • 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-Befehl möglich: <?php switch (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 Leerzeichen steht. Die runde Klammer nach dem Funktionsnamen folgt ohne Leerzeichen und nach jedem Komma in der Parameterliste steht wiederum ein Leerzeichen. Das schließende Semikolon folgt wieder ohne Pause. Manchmal kommt es vor, dass sehr viele Zuweisungen dieser Art hintereinander stehen. Dann kann man diese nach den Gleichheitszeichen ausrichten, um die Lesbarkeit weiter zu verbessern: 67
  • 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 | // | license@php.net so we can mail you a copy immediately. | // +---------------------------------------------------------------+ // | Authors: Original Author <author@example.com> | // | Your Name <you@example.com> | // +---------------------------------------------------------------+ // // $Id$ ?> Die erste Zeile steuert die Anzeige im Unix-Editor VIM, was vermutlich nur eine Minderheit wirklich tangiert. Namenskonventionen Ob Sie deutsch oder englisch schreiben, bleibt Ihnen überlassen. Hier sollte das potenzielle Zielpublikum beachtet werden. Wer seine Skripte im Internet anbie- tet, sollte keine deutschen Namen verwenden. Für den Eigenbedarf ist es völlig in Ordnung und weit besser, als unpassende oder falsche englische Begriffe zu ver- wenden, was eher peinlich ist. 68
  • 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 beschrieben werden. 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 nutzen Sie Verben zur Benennung, gegebenenfalls um ein Objekt ergänzt: $save(), $load() oder auch $ladeDaten(). Namensteile werden durch Großbuchstaben getrennt, wobei der erste Buchstabe klein ist. Ist die Zuordnung zu einem Modul (Package in der PEAR-Welt genannt) wichtig, wird der Modulname davor gestellt und durch Unterstrich getrennt: HTML_getData(). Klassennamen beschreiben den Sinn der Klasse: Log, HTML_Error_Resolver. Sie beginnen 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 sie innerhalb der Klasse dieselbe Aufgabe übernehmen. Es ist üblich, private Mitglie- der von Klassen mit einem führenden Unterstrich zu benennen: $_content. Die Regel stammt aus den PHP 4-Zeiten und ist nicht zwingend bei PHP5, weil es einen speziellen Modifizierer gibt: private. Wie Sie es verwenden, sollten Sie pro Projekt selbst festlegen und diese Regel dann konsequent durchhalten. Konstanten werden immer in Großbuchstaben geschrieben. Auch hier gilt: Sind sie Teil eines Moduls, wird der Modulname davor gesetzt und durch Unterstrich getrennt: DB_INFO, XML_ERROR, WIDTH, HEIGHT. 2.5 Webserver und Browser Webserver und Browser sind die beiden Programme, die bei der Bereitstellung und Nutzung von Webseiten eine herausragende Rolle einnehmen. Sie verständi- gen sich mit Hilfe des Protokolls HTTP miteinander. Dieser Abschnitt führt kurz in Grundlagen und Prinzipien ein – Handwerkszeug für künftige PHP-Entwickler. 69
  • 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, wozu es dient: dem Übertragen von durch Hyperlinks verknüpften Seiten. Bevor jedoch die 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. Der Browser nutzt nun Betriebssystemfunktionen, um eine Verbindung mit dem Server herzustellen. Der erste Schritt besteht in der Auflösung der Adresse. Intern wird auf Datenebene die Protokollkombination TCP/IP benutzt. Sender und Empfänger einer Botschaft benötigen hier eine eindeutige IP-Adresse der Art 134.15.211.65. Weil sich derartige Adressen schlecht merken (und vermarkten) las- sen, wurde das Domain Name System (DNS) entwickelt. Es basiert auf dem gleichnamigen DNS-Protokoll und verwendet DNS-Server, die Zuordnungen zwi- schen Adressen und Domain-Namen speichern. Diese Server bilden im Internet eine Hierarchie, wobei der Ausgangspunkt von etwas mehr als einem Dutzend Root-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 den nächsten erreichbaren Name-Server nach der Adresse. Dies ist der Server, den der Provider beim Aufbau der Verbindung ins Internet zugewiesen hat. Große Firmen betreiben manchmal auch eigene Name-Server. Der Name-Server wird freilich nicht alle Millionen Adressen und deren IP-Nummern kennen. Also fragt er einen der Root-Server an, ob diesem eine verwaltende Instanz für das benötigte Toplevel bekannt 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 dem Betreiber 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 entweder ein 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 IP-Adresse. Diese Information wird über das DNS-Protokoll dem zuerst anfragen- den Name-Server übermittel und gelangt von dort zum Browser. Der Browser besitzt seinerseits eine eindeutige Adresse, die vom Provider beim Verbindungsauf- bau vergeben wurde. In Netzen mit festen IP-Adressen läuft meist ein Dienst, der 70
  • 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 aktuellen Anforderung nichts zu tun haben. Um bestimmte Dienste gezielt erreichen zu können, werden so genannte Ports verwendet. Webserver haben im Allgemeinen (Standard) den Port 80. Mail-Server (SMTP) haben den Port 25 und POP3-Server erreicht man über Port 110. Der Browser hat auch einen Port, der jedoch nicht fest zugeordnet ist sondern dynamisch aus dem Adresspool oberhalb 1.024 vergeben wird, beispielsweise 4.768. IP-Adresse und Port bilden zusammen einen so genann- ten Socket. Zwischen zwei solchen Sockets spielt sich dann die Kommunikation ab. Während IP für die Adresszuordnung sorgt, wird der Datenstrom per TCP gesteuert. Hier findet die Verpackung der Daten in Pakete und die Fehlerprüfung statt. Die Daten, die der Browser nun sendet, werden ihrerseits wieder in ein höheres Protokoll verpackt: HTTP. Dies sind lesbare Codes, die bestimmte Aktionen am Server 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ßen aus: GET info.php GET ist das Kommando, danach folgt die Ressource, die mit GET angefordert werden soll. Der Webserver erkennt das Kommando und weiß nun, dass er die Datei laden und ausgeben soll. Wurde die Dateierweiterung verknüpft, kann sich noch ein Programm dazwischen schieben und die Datei verarbeiten, bevor sie gesendet wird. Die Erweiterung .php übergibt die Daten an PHP und dort wird die Datei geladen, nach den Skript-Tags durchsucht, der Code untersucht und ausge- führt und eine fertige HTML-Seite erstellt. Diese wird wieder an den Webserver zurückgegeben und der sendet sie dann in Beantwortung der Anforderung aus: HTTP/1.1 200 OK Content-Length: 46783 <html> <!-- Hier folgt die Seite --> </html> 71
  • 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 Beispiel gezeigt, beispielsweise auch Angaben über den Server, Cookies usw. Im Beispiel wurde lediglich die Länge der Seite übertragen. Nach einer Leerzeile beginnt dann der Inhalt der Datei. Wenn eine HTML-Seite nun drei Bilder enthält, dann wird für jedes Bild eine eigene GET-Anforderung erstellt und gesendet. Der Webserver wird meist keine Verknüpfung für Bilder haben und diese als Ressource direkt von der Festplatte laden und senden. Binärdaten werden ebenso angehängt wie alle anderen Informationen – freilich so codiert, dass nichts verloren geht. Damit nicht jedes Mal wieder die gesamte Kette von Name-Servern abgefragt wird, speichern diese das letzte Ergebnis einige Zeit, sodass der gesamte Ablauf stark beschleunig wird. Neben GET kann der Browser auch POST verwenden und seinerseits Daten an die Anforderung anhängen. Dies wird genutzt, um Daten aus Formularen zu über- tragen. Diese Informationen werden PHP ebenfalls bereitgestellt, sodass sie in Skripten 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 Codezeile ein kurzer, auf die Zeile bezogener Kommentar stehen soll? Mögliche Varianten sind: /* */, // oder #. 72
  • Daten verarbeiten 3
  • Daten verarbeiten Elementarer Bestandteil eines jeden Programms ist die Möglichkeit, Daten zu erfassen, zu speichern und damit vielfältige Operationen anzustellen. PHP bietet dafü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. Sie lassen sich jederzeit verändern und können in Ausdrücken als Ersatz für literale Konstrukte 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 Datentypen sehr 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 fest und wechselt den Typ auch mal auf halber Strecke. Das ist nicht immer von Vorteil, auch wenn Sie am Anfang weniger einrichten und festlegen müssen als bei anderen Sprachen. Es führt jedoch zu Problemen, wenn Ihre Anwendung so programmiert ist, dass ein bestimmter Typ erforderlich ist und dies gleichzeitig nicht ständig absichert. Es kommt dann zu so genannten Seiteneffekten, die schwer nachvollziehbare Fehlerquellen sind. Nichtsdestotrotz kennt PHP natürlich den intern und automatisch festgelegten 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 nicht explizit erzwingen). Die automatische Ermittlung erfolgt im Rahmen des Zuwei- sungsprozesses: 74
  • 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 Zahlen haben die Zahlenbasis 8 und die Ziffern 0 bis 7. Sie werden erkannt, wenn eine führende Null davor geschrieben wird: $zahl = 040; Hexadezimalzahlen haben die Zahlenbasis 16 und verwenden als Ziffernzeichen fü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. Hier erkennt 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 deshalb auch 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 diese Form folgendermaßen geschrieben: $wert = 3E4; Das »E« entspricht also »zehn hoch«. An dieser Stelle sei auch erwähnt, dass die Genauigkeit durch die Rechenbreite des Computers bedingt nicht unendlich ist und auch nicht genau dem reellen Zahlenraum entspricht. Scheinbar verrechnet sich PHP bei sehr vielen Stellen deshalb manchmal geringfügig. 75
  • Daten verarbeiten Literale Die rechte Seite der Zuweisungen, wie ?23?, 13.04 oder 3E4 werden als Literale bezeichnet – es sind konstante Werte mit einem definierten Aufbau. PHP erkennt bestimmte Literale und erzeugt daraus entsprechende Werte. Hinweise zur Definition In der Standardeinstellung von PHP5 wird erwartet, dass Variablen vor der ersten Verwendung deklariert werden. Damit ist gemeint, dass mindestens einmal ein Wert zugewiesen wurde und PHP damit den Datentyp feststellen konnte. Auch wenn 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. Haben Sie nämlich versehentlich einen Tippfehler gemacht oder Groß- und Kleinschrei- bung verwechselt, weist eine Fehlermeldung auf diesen Umstand hin. Ohne diese würde Ihr Programm wegen der automatischen Typerkennung und -umwandlung möglicherweise dennoch funktionieren – bis bestimmte Daten auftreten, bei denen es aus dem Tritt kommt. Solche sporadisch auftretenden Fehler sind nur sehr 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 wird der Name der Konstanten als Zeichenfolge und der zuzuweisende Wert. define('BREITE', 640); 76
  • Konstanten In Ausdrücken werden Konstanten ebenso wie Variablen verwendet. Sie werden jedoch 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 <?php define('BREITE', 640); echo "Die Breite ist : ", BREITE; ?> Abbildung 3.1: Ausgabe einer Konstanten Umgang mit Konstanten Da Konstanten nur einmalig definiert werden können, kann es zu Problemen kommen, wenn Konfigurationen von Programmteilen mehrfach erfolgen. Das kann notwendig sein, um Module unabhängig voneinander anzuwenden. Jedes Modul soll beispielsweise in der Lage sein, sich seine benötigten Konstanten selbst zu definieren, wenn keine übergeordnete Instanz dies bereits getan hat. Diese Art zu programmieren ergibt größtmögliche Sicherheit – das Modul bleibt in jedem Kontext stabil. Zur Abfrage einer bereits erfolgten Definition stellt PHP eine weitere Funktion zur Verfü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 Beispiel zeigt: <?php if (!defined('BREITE')) { define ('BREITE', 640); } ?> Wie schon bei der Definition der Konstanten wird auch hier der Name, nicht jedoch die Konstante selbst angegeben. Das ist logisch, weil diese ja möglicher- weise noch gar nicht existiert. 77
  • Daten verarbeiten 3.3 Rechnen und Vergleichen mit Ausdrücken Ganz nebenbei wurde der Begriff »Ausdruck« bereits erwähnt. Es handelt sich dabei 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 eine Addition aus. Der zweite ergibt die Zeichenfolge »Heute ist Mittwoch«. Der Punkt ist ein Operator zur Verbindung von Zeichen. Der dritte Ausdruck vergleicht zwei Zahlen, 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, Literalen und Operatoren. Den Operatoren kommt also eine sehr große Bedeutung zu. Es gibt sehr viele davon, die für die unterschiedlichsten Zwecke eingesetzt werden können. Ausdrücke Als Ausdruck werden alle Kombinationen aus Literalen, Variablen, Konstanten oder Funktionsaufrufen und Operatoren bezeichnet, die einen konkreten skalaren Wert zurückgeben. Dabei ist es erst einmal nicht relevant, welchen Datentyp die- ser Wert hat. Typische Ausdrücke sind: $a + 4 45 – 56 * 99 $zahl1-- $string = $buchstabe . " " . $ende Die Benutzung erfolgt entweder beim Funktionsaufruf, als Zuweisung (Ziel ist eine Variable) oder im Kontext anderer Ausdrücke. 78
  • Rechnen und Vergleichen mit Ausdrücken Rechnen mit Operatoren Zum Rechnen werden folgende Operatoren verwendet: 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 Der Modulus-Operator kommt sehr häufig zum Einsatz, auch wenn der Rest einer Division im Alltagsleben kaum eine Rolle spielt. Viele Divisionen ergeben jedoch gebrochene Zahlen und dies ist bei der Ausgabe nicht immer praktisch. So müssen Sie vielleicht in einer Tabelle jede dritte Zeile einfärben. Aber wie erkennt man jede 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, wurde eine dritte Zeile erreicht. Interessant sind auch die Inkrement- und Dekrementoperatoren. Mit diesen wird der 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 Operators. Das folgende Beispiel zeigt dies: 79
  • 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: Abbildung 3.2: Ausgabe von Listing 3.2 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 der Variablen $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 der Anwendung 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; echo $a . $b; ?> Dieses Skript gibt nicht 20 aus, sondern »137«. Das liegt daran, dass der Punkt nur Zeichenfolgen verbinden kann. Damit das funktioniert, wandelt PHP die beiden Variablen entsprechend um; aus der Zahl 13 wird die Zeichenfolge »13« usw. Dies 80
  • Rechnen und Vergleichen mit Ausdrücken geschieht hier übrigens nur für diesen Ausdruck, danach stehen die Variablen unverändert als Zahlen zur Verfügung. Im Hinblick auf die Programmsicherheit und 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 Zuweisung kombiniert werden. Das spart lediglich Tipparbeit und hat keinen Effekt auf den Programmfluss. 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 stammt von George Boole, der die Grundlagen der logischen Algebra entwickelte, und wird deshalb immer groß geschrieben. Ausdrücke, die sich logisch auswerten las- sen, geben immer einen Wahrheitswert zurück, also entweder »Wahr«, in PHP als TRUE ausgedrückt oder »Falsch«, wofür in PHP die Konstante FALSE steht. PHP erkennt zwar logische Ausdrücke und kann bei Vergleichen entsprechend damit umgehen, nutzt aber intern Zahlen zur Darstellung. Das führt dazu, dass andere Ausdrucksarten fast immer auch in einen Wahrheitswert gewandelt wer- den, wenn dies vom Kontext her erforderlich ist. Das führt manchmal zu schweren Programmfehlern. Achten Sie deshalb unbedingt auf eine saubere Konstruktion der Ausdrücke. 81
  • Daten verarbeiten Die Vergleichsoperatoren finden Sie in der folgenden Tabelle: 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 Wenn man nun dergestalt Ausdrücke entwickelt, ist auch eine Kombination von Teilausdrücken erforderlich, um komplexere Strukturen abzubilden. Dazu wer- den die logischen Operatoren eingesetzt, meist in Verbindung mit Klammern, um die Rangfolge zu bestimmen. Operator Bedeutung || Oder. Wahr, wenn einer der beiden Operanden Wahr ist. && Und. Wahr, wenn beide Operanden Wahr sind. ! 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 82
  • Allgemeine Aussagen zu Zeichenketten Operator Bedeutung and Und. Wahr, wenn beide Operanden Wahr sind. xor Exklusives Oder. Wahr, wenn einer der Operanden Wahr ist, jedoch nicht beide (ausschließendes Oder). Tabelle 3.3: Logische (Boolesche) Operatoren (Forts.) Scheinbar sind die Operatoren || und or bzw. && und and identisch. Dem ist jedoch 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 nach rechts, 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 der Regel »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 Designer der Sprache eine Reihenfolge festlegen. Mehr Informationen zur Rangfolge (Asso- ziativität) der Operatoren werden im Zusammenhang mit dem if-Befehl (Siehe Abschnitt »Einfache Verzweigungen mit if«). 3.4 Allgemeine Aussagen zu Zeichenketten Zeichenketten oder Zeichenfolgen sind ein elementarer Datentyp in PHP. Die Ausgabe von Text nach HTML erfolgt immer als Zeichenkette. Aus diesem Grund sind Operationen mit Zeichenketten elementar und Sie sollten sich mit den Möglichkeiten von PHP ebenso auseinandersetzen wie mit den vielfältigen Ein- satzfällen. Datentyp und Größe Als Datentyp unterscheidet PHP nicht zwischen Zeichenketten und einzelnen Zeichen. Es gilt deshalb generell, dass ein einzelnes Zeichen wie eine Zeichen- 83
  • 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 der Operator (string) verwendet: $string = (string) 11; Zeichenketten können bis zu 2 Milliarden Zeichen enthalten (2 GByte), dies ist jedoch ein eher theoretischer Wert, da das Laufzeitverhalten unter sehr großen Datenmengen massiv leidet. Der Umfang typischer HTML-Seiten von einigen Dutzend KByte kann jedoch problemlos verarbeitet werden. Zeichenketten haben keine definierte (vorgegebene) Länge oder ein spezielles Endzeichen, wie dies bei C der Fall ist. Für die Abfrage der aktuellen Länge gibt es eine passende Funktion. Umgang mit Sonderzeichen Zeichenketten selbst stehen in ihrer literalen Form in Anführungszeichen. Wenn Sie nun ein solches Zeichen selbst ausgeben möchten, muss es maskiert werden. Die Maskierung erfolgt durch ein spezielles Maskierungszeichen, das in PHP wie in 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, die sich über die Tastatur nicht eingeben lassen, weil sie im Editor eine besondere Aufgabe haben. Das betrifft vor allem Zeilenumbrüche und Tabulatoren. Weitere Sonderzeichen, die PHP erkennt und nutzt, zeigt die folgende Tabelle: 84
  • Allgemeine Aussagen zu Zeichenketten 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 Beachten Sie, dass die Zeilenumbrüche unter Windows und Unix unter- schiedlich sind. Während Unix nur n einsetzt, wird unter Windows nr 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: 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. 85
  • 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 mit Hilfe eines einfachen Vergleichsmusters gearbeitet werden, das eine exakte Über- einstimmung bedingt. Zum anderen gibt es universelle Suchmuster, so genannte reguläre Ausdrücke. Letztere werden in Abschnitt 3.6 »Reguläre Ausdrücke« ab Seite 95 behandelt. 3.5 Die Zeichenkettenfunktionen Die praktische Anwendung der Zeichenkettenfunktionen in diesem Abschnitt zeigt die Einsatzbandbreite. Es geht in PHP – dies werden Sie später noch deutli- cher sehen – mehr um das Auffinden der passenden Funktionen als um Probleme bei der Anwendung. Leider sind die Benennungsregeln bestenfalls als konfus zu bezeichnen. Sie zeigen eine der Mankos freier Software: Viele unkoordinierte Ent- wicklungsschritte und mangelnde Dominanz einer Ordnungsinstanz führen zu Chaos. 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 86
  • 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 einer Datenbank oder einem Formular empfangen wurden, auf bestimmte Merkmale hin 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 aus einem Formular, und möchten ermitteln, ob sich darin ein bestimmtes Wort befindet. Dann wird die Funktion strpos eingesetzt. Sie liefert die Position der Fundstelle: 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: Abbildung 3.3: 13 bedeutet das 14. Zeichen, weil die Zählung mit 0 beginnt Man nennt eine solche Zählweise »nullbasiert«, das heißt, das erste Zeichen hat den Index 0. Ist der Text nicht enthalten, wird FALSE zurückgegeben. Das ist zwar fü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 erleichtert nur nicht unbedingt die Programmierung. Denn Sie müssen in Ihrem Programm vor der weiteren Verarbeitung des Ergebnisses an die Reaktionen auf verschiedene Datentypen denken. strpos beispielsweise gibt eine ganze Zahl zurück (integer), wenn die Zeichenfolge gefunden wurde, oder einen Booleschen Wert (boolean), wenn die Suche erfolglos war. Durch die bereits beschriebene 87
  • 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 von strpos gibt. strrpos sucht die Fundstelle vom Ende her (was gleichbedeutend mit der letzten Fundstelle ist, wenn man von vorn sucht) während stripos bei der Suche 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 einige Funktionen, die einen Vergleich erlauben. Vergleichsfunktionen ermitteln typischerweise drei Zustände und geben einen von drei 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 wird also 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 den Erwartungen entspricht. Ein zweites Merkmal derartiger Vergleiche ist die Vorge- hensweise bei mehreren Zeichen: Der Algorithmus prüft Zeichen für Zeichen, bis er einen Unterschied findet. Der Vergleich der Zeichenfolge »11« mit »2« führt bereits beim ersten Zeichen zu einem Unterschied. »1« wird mit »2« verglichen; die »2« ist natürlich größer und deshalb produzieren derartige Funktionen als Ergebnis: »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- cmp, strnatcasecmp und strncasecmp setzen »a« gleich »A«, der Teilname »case« deutet darauf hin. Der Teilname »nat« verhindert dagegen die binäre Suche und versucht eine so genannte natürliche Suche. Hierbei werden Zahlen extrahiert und als Zahl, nicht als Zeichen behandelt, wobei 11 dann größer als 2 ist. 88
  • 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« zur korrespondierenden »1« liegt. Mit strnatcmp sieht es anders aus. Hier wird der Zahlenanteil extrahiert und – soweit der Rest übereinstimmt – als Zahl verglichen. Deshalb ist $text1 nun kleiner als $text2. Abbildung 3.4: Natürlich oder nicht: Verschiedene Vergleichsergebnisse 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 Basis der Rückgabewerte 0, 1 oder -1. Diese Anwendung wird im Zusammenhang mit Arrays am achten Tag noch gezeigt. Ersetzen von Zeichen Ebenso wichtig wie das Suchen von Mustern ist das Ersetzen der Fundstelle durch anderen Text. PHP stellt zwei Funktionen zur Verfügung: str_replace berück- sichtigt beim Suchvorgang Groß- und Kleinschreibung, während str_ireplace dies nicht tut. Listing 3.6: StrReplace.php: Ersetzen eines Teils einer Zeichenkette <?php $text1 = "Dies ist Bild_2"; $text2 = "Bild_11"; $text3 = str_replace("Bild_2", $text2, $text1); echo $text3; ?> 89
  • 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 leere Zeichenkette 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ören sollten. Informationen dazu werden in Abschnitt 3.6 »Reguläre Ausdrücke« ab Seite 95 vermittelt. Ermitteln von Eigenschaften einer Zeichenkette Bei der Beurteilung des Inhalts einer Zeichenfolge kommt es natürlich auch auf die allgemeinen Eigenschaften an. Besonders häufig wird die Länge benötigt, also die Anzahl der Zeichen. Bei der Auswertung von Formulardaten können so Pflichtfelder 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 zusammen mit der Auswahl von Teilzeichenketten oder der Positionsbestimmung (strpos) benutzt werden. Abbildung 3.5: Anzahl der Zeichen in einer Zeichenkettenvariablen Teilzeichenketten und Behandlung einzelner Zeichen Bei der Behandlung einzelner Zeichen werden Sie mit zwei Dingen in Berührung kommen. Zum einen wird gelegentlich mit dem Zeichencode gearbeitet, das heißt, Sie haben den Code eines Zeichens aus dem verwendeten Zeichensatz und 90
  • 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 auf einzelne Zeichen. Umgang mit Zeichencodes Vor dem Zugriff auf Zeichencodes steht natürlich die Kenntnis derselben. PHP kann mit seinen Zeichenkettenfunktionen nur den erweiterten ASCII-Zeichensatz (Codes 0 bis 255) darstellen, dies entspricht ISO-8859-1. Abbildung 3.6: Die Zeichenta- belle des Zeichen- satzes ISO-8859-1 Entsprechend dieser Tabelle führt chr(65) zum Zeichen »A« und ord("_") zum Code 128. Dies entspricht bei Zeichencodes oberhalb 127 nicht der oft verwende- ten Unicode-Kodierung, die »breitere« Zeichen verwendet (16 statt 8 Bit) und auch asiatische Schriften verarbeiten kann. Kritisch ist das lediglich, wenn Sie ver- suchen, dezimale HTML-Entitäten zu erstellen, beispielsweise &#8364;. Dieses Zeichen entspricht der Entität &euro; und stellte das Eurozeichen dar. Leider kann PHP mit dem Code 8364 nichts anfangen und umgekehrt kann nicht garan- tiert werden, dass ein Browser bei &#128; auch wirklich1 das €-Symbol anzeigt. 1 Solange das Betriebssystem den Zeichensatz ISO-8859-1 oder einen darauf aufbauenden verwendet, kann es die Zeichen schon dekodieren und der Browser wird sie dann auch darstellen. Da der Zeichensatz aber in jeder Sprache etwas variiert, ist das Ergebnis mehr oder weniger unvorhersehbar. 91
  • Daten verarbeiten Zugriff auf einzelne Zeichen Am Anfang wurde bereits gezeigt, dass jedes Zeichen in einer Zeichenfolge einen Index hat, beginnend mit 0. Dieser Index kann auch zur gezielten Auswahl genau eines 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. Der erste 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 Anzahl der Zeichen an, von der Startposition beginnend immer nach rechts zählend. Das folgende Beispiel nutzt diese Funktion und einige andere, um die Werte der Parameter 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 ist sehr typisch für die PHP-Programmierung. Das Beispiel gibt das Wort »Muster« aus. HTML-abhängige Funktionen Der Sinn und Zweck nahezu jeden PHP-Programms ist die Erzeugung von HTML. Entsprechend umfangreich ist auch hier das Angebot an Funktionen. 92
  • 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ührt werden 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, lediglich der Text zum Umwandeln muss angegeben werden. Der zweite Parameter kann eine 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. Ohne Angabe 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 von Zeichen pro Zeile angezeigt werden, ist wordwrap eine Hilfe. Die Funktion verhin- dert, dass der Umbruch willkürlich mitten im Wort erfolgt. Als Umbruchzeichen wird 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 <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); 93
  • Daten verarbeiten ?> </pre> Die Funktion kennt noch zwei weitere Parameter. Der dritte gibt ein alternatives Zeichen für den Umbruch an, der vierte kann auf 1 gesetzt werden, um einen Umbruch auch mitten im Wort zuzulassen. Abbildung 3.7: Umbruchsteuerung zur Formatierung der Ausgabe 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. Auch dies 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 wird durch den Stil bestimmt (Attribut style des HTML-Tags <div>): Abbildung 3.8: Das linke Bild ist ohne nl2br ent- standen, das rechte mit. Noch eine interessante Anwendung von Zeichenkettenfunktionen betrifft die Zer- legung von Zeichenfolgen. Telefonnummern oder Bankleitzahlen sind beispiels- weise besser lesbar, wenn man sie mit Leerzeichen gruppiert. Die Funktion chunk_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. 94
  • 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änge geteilt. Der letzte Abschnitt kann freilich kleiner sein, was in diesem Fall aber gewollt ist. An jeden Abschnitt wird das Leerzeichen angehängt, das durch den dritten 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 indes nicht. Denn die Suche nach Zeichen in einem Text kann komplexen Regeln gehorchen. Nicht alle Arten von Suchen sind mit regulären Ausdrücken abbildbar. Derartige Ausdrücke können im Prinzip nur statischen Text beschreiben, nicht jedoch Berechnungen anstellen. Die Suche nach einer Zahl, einer bestimmten Buchstabenfolge oder einer Zeichenart ist einfach. Eine Prüfsumme oder einen Zahlenbereich 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, die E-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 aber erstmal eine gewisse Systematik erkannt, ist es nicht unmöglich, auch schwierige Aufgaben zu lösen. Auch wenn reguläre Ausdrücke sehr »unleserlich« aussehen können, eine einfache Variante ist oft ausreichend. Das Grundprinzip Betrachten Sie folgenden Satz: 95
  • 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 Zeichenkettenfunktionen auch. 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 konkreten Zeichen oder Zeichenfolgen werden diese einfach aufgeschrieben. Die zweite wichtige 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 ist es, die Zeichen durch Platzhalter zu ersetzen. Der wichtigste Platzhalter ersetzt ein beliebiges 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 selbst konstruieren. Dazu werden so genannte Zeichenklassen eingesetzt, gekennzeich- net durch eckige Klammern. Diese Gebilde stehen für genau ein Zeichen, das den Kriterien entsprechen muss: [PH]HP Dieses Muster steht für PHP oder HHP. Der Inhalt der Zeichenklasse kann negiert werden, das heißt, alle Zeichen außer den angegebenen sind zulässig: [^PH]HP Das letzte Muster erkennt – neben vielen anderen – AHP, BHP usw. nicht jedoch PHP 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: 96
  • Reguläre Ausdrücke 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 Wiederholungen definieren Damit man mit Zeichen und Zeichenklassen flexibel umgehen kann, lässt sich ein Wiederholungsoperator anhängen. Dieser Zeigt an, wie oft das Zeichen vorkom- men soll: {n, m} Dabei steht das n für die Mindestzahl und das m für die Maximalzahl. Beide Werte sind 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 es eine verkürzte Schreibweise, die häufig zum Einsatz kommt: 97
  • 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 festlegen zu 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 man noch ein Aufhebungszeichen, wozu der Backslash eingesetzt wird. Suchen Sie also nach einem Punkt, schreiben Sie . . Gruppierungen Nicht nur einzelne Zeichen, sondern auch Kombinationen lassen sich mit den Wiederholungsoperatoren +, *, ? und {n,m} verwenden. Gruppen entstehen durch runde 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 »Muster gefunden«, sondern auch ein Array mit den Teilmustern, die durch Gruppen defi- niert wurden. Anwendungsbeispiele Bevor Sie mit regulären Ausdrücken beginnen, sollten Sie sich ein kleines Skript bauen, das Eingabe und Test vereinfacht. Das folgende Listing zeigt eine einfache Version. Dabei lernen Sie auch gleich einige PHP-Funktionen kennen, die mit 98
  • Reguläre Ausdrücke regulären Ausdrücken umgehen können. Sie beginnen immer mit dem Präfix preg_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) { 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. 99
  • 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 des angegebenen Musters, bleibt also nicht stehen, wenn eine erste Übereinstimmung gefunden wurde. Für Testzwecke ist dies sehr hilfreich. Wird dagegen nur nach einem (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 und preg_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 genannte Rückruffunktion aufgerufen, in der man zusätzliche Berechnungen anstellen kann. Das bringt viel Dynamik in den Ersetzungsvorgang. Das folgende Beispiel zeigt die Anwendung. Ein Text soll nach einem Muster, hier der Zeichenfolge ~#~ durchsucht werden. Das Nummernzeichen # soll dabei durch eine fortlaufende Ziffer ersetzt werden. Mit preg_replace_callback ist das sehr einfach zu realisieren: 100
  • 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 vier Mal. In der Funktion zählt eine statische Variable (static) die Aufrufe mit und gibt den aktuellen Wert, beginnend mit 1, zurück. Der Rückgabewert wird zum Ersetzen der Zeichenfolge benutzt. Statische Variablen behalten ihren Wert und führen die Zuweisung nur beim ersten Aufruf aus. Für die Ausgabe werden außerdem noch die normalen Zeilenumbrüche, wie sie bei der Heredoc-Notation entstehen, in HTML-typische <br>-Tags konvertiert. Abbildung 3.9: Ausgabe des Skripts mit dynamisch ersetzten Ziffern 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 Angabe von Referenzen auf Gruppen des Ausdrucks. Wie bereits in der Einfüh- rung beschrieben, werden Gruppen durch runde Klammern gebildet. Auf diese kann mit einer speziellen Referenzsyntax zugegriffen werden. Angenommen, Sie suchen nach einem bestimmten Datumsformat, beispielsweise der Art 8/9/2004 (amerikanisches Format) in einem Text. Sie möchten nun an die- 101
  • Daten verarbeiten sen Stellen die deutsche Version 9.8.2004 sehen. Dies ist mit preg_replace in einem Schritt erledigt. Listing 3.16: RegexReplace2.php: Ersetzen mit Zugriff auf Teilmuster <?php $text = <<<TEXT Dieser 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 vorkommen darf: 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 damit zwar zur Erkennung herangezogen, fallen aber aus den Teilmustern heraus. In der Ersatzzeichenkette kann nun auf die Teilmuster mit der Syntax n zugegriffen werden, wobei n einfach die Nummer der öffnenden Klammer ist. 2 bezeichnet also die zweite Klammer, mithin der Tag im amerikanischen Datumsformat. Die Punkte in der Ersatzzeichenkette werden wie normale Zeichen behandelt. Abbildung 3.10: Ausgetauschtes Datumsformat mit einem einzigen regulären Ausdruck Reguläre Ausdrücke können weit komplexer sein. Schauen Sie sich folgendes Suchmuster an: (?<=,|^)([^,]*)(,1)+(?=,|$) Es sucht Duplikate in einer kommaseparierten Liste. Das ist nicht einfach, weil die Kommata stören. Eine kleine Analyse zeigt, wie es funktioniert: (? Erstes Teilmuster (unterdrückt, ?-Zeichen) <= Positiv (=) rückschauen (<) ,|^ Suche nach Komma oder (|) Textanfang (^) ) 102
  • 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 weniger auf die Anwendung der PHP-Funktionen an. Problematischer ist das Finden der passenden Ausdrücke. Der folgende Abschnitt stellt deshalb einige häufig benutzte Ausdrücke vor – zum Lernen und natürlich zum Anwenden. Typische Suchmuster In diesem Abschnitt werden einige häufig benötigte Suchmuster vorgestellt und kurz erläutert. Einerseits können Sie so reguläre Ausdrücke schnell anwenden, andererseits lernen, vergleichbare Muster selbst zu entwerfen. Die Muster werden nicht im Kontext eines PHP-Skripts dargestellt, da es sich hier lediglich um den immer wieder gleichen Aufruf von preg_match, preg_match_all oder preg_replace handeln 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, die keineswegs zueinander vollkommen kompatibel sind. PHP verwendet eine weitgehend (aber nicht 100-prozentig) Perl-kompatible Methode. JavaScript, Unix-Shells, .NET, VBScript und andere Systeme weichen davon teilweise ab. Das Grundprinzip ist zwar allen gleich, aber die Syn- tax variiert ebenso wie die Standardeinstellungen (beispielsweise ist Perl standardmäßig »gierig«, und man unterdrückt dies explizit, während .NET »ungierig« ist und man die Option explizit einschalten muss). Sie müssen deshalb fremde Muster immer erst sorgfältig testen und eine 103
  • Daten verarbeiten scheinbare Fehlfunktion ist nicht immer dem Anbieter anzulasten, es sei denn, das Muster wurde explizit für PHP geschrieben. Vorbemerkungen Alle folgenden Beispiele verwenden als Begrenzungszeichen die Tilde (~). Wenn in Ihrem Suchmuster nach der Tilde gesucht werden soll, müssen Sie ein anderes Begrenzungszeichen 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 die Beispiele enthält und ein Formular anbietet, mit dem sich schnelle Tests 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 ~ <([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 104
  • 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 und 255. Das folgende Muster erkennt jedoch auch 980.378.275.455 noch als gültig an: ~b(d{1,3}).(d{1,3}).(d{1,3}).(d{1,3})b~ ~ 105
  • 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 die Gruppierung der Teiladressen. Es ist vorzuziehen, wenn eine Auswertung der einzelnen 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 erkennt nur korrekte Nummernkreise. Hier ist freilich fraglich, ob der Aufwand lohnt, ein Zugriff auf die Teilmuster des letzten Beispiels mit PHP und eine simple Prü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 Wenn Daten aus Formularen angenommen werden, liegen diese immer als Zei- chenketten vor. Die Prüfung, ob es sich um korrekte Zahlen handelt, ist immer dann erforderlich, wenn die Weiterverarbeitung in numerischen Ausdrücken erfolgt und Fehler vorher abgefangen werden müssen. 106
  • 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 die Buchstaben 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. ~[-+]?bd+b~ ~ [-+]? Optionales Vorzeichen b Wortgrenze d+ Ziffernfolge (eine oder mehrere Ziffern) b Wortgrenze ~ 107
  • Daten verarbeiten Abbildung 3.11: Das Muster fin- det alle ganzen Zahlen in einem Text (Bild vom Testprogramm) í Hexzahlen im PHP-Format (z.B. 0xFF) Das folgende Beispiel funktioniert ähnlich, setzt jedoch das für Hex-Literale typische Präfix 0x davor. Die zweite Variante ist identisch, nutzt aber den Schalter 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 werden kann. 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)dd~ ~ (0[1-9]|1[012]) Monate 01-09 und 10, 11, 12 [- /.] Erlaubte Trennzeichen ( 0[1-9] Tage 01 bis 09 | oder [12][0-9] 10 bis 29 (1 oder 2 und 0 bis 9) | oder 3[01] 30 oder 31 108
  • Reguläre Ausdrücke ) [- /.] Erlaubte Trennzeichen (19|20)dd 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)dd[- /.](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 (?:.[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 109
  • Daten verarbeiten 3.7 Datums- und Zeitfunktionen PHP verfügt über vielfältige Datums- und Zeitfunktionen, die außerdem durch ein Kalendermodul ergänzt werden. Datumsberechnungen werden damit vereinfacht und helfen dabei, nutzerfreundliche Websites aufzubauen. In diesem Abschnitt werden verschiedene erweiterte Techniken der Datenausgabe vorgestellt. Dazu gehö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 bis 2037 enthalten kann. Solange man sich innerhalb dieses Zeitraumes bewegt, sind die internen Funktionen außerordentlich wirkungsvoll und einfach. Daten, die davor oder danach berechnet werden sollen, bedürfen einer etwas eingehenderen Betrachtung. Wenn Sie mit Zeitwerten arbeiten, die die aktuelle Zeit reflektieren, müssen Sie beachten, dass der Server möglicherweise nicht in Ihrer Zeitzone oder der der künftigen Nutzer steht. Manche Provider stellen ihre Server auch nicht auf die Zeitzone ein, in der die Maschine physisch steht, sondern auf UTC (Universal Time Conversion, früher als GMT, Greenwich Mean Time bezeichnet). Für die Abfrage der lokalen Zeit des Browser benötigen Sie JavaScript; PHP allein hilft hier 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 zuerst über einen Zeitstempel verfügen – entweder den aktuellen (Jetzt-Zeit) oder den für einen bestimmten Zeitpunkt. Für beides gibt es entsprechende Funk- tionen. 110
  • Datums- und Zeitfunktionen Die aktuelle Zeit wird mit time ermittelt. Eine bestimmte Zeit wird dagegen mit mktime (mk = engl. make; machen, erzeugen) erzeugt. Das folgende Beispiel zeigt dies und gibt die Zeitstempel im »Rohformat« aus. Listing 3.17: time.php – Der aktuelle Zeitstempel <?php echo time(); ?> Berechnungen mit Zeitwerten sind auf Basis des Zeitstempels sehr einfach. Da es sich um ein Maß in Sekunden handelt, muss zur Berechnung eines anderen Datums 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 Monats zu ermitteln, muss bekannt sein, in welchem Jahr er liegt (auch wenn dies nur beim 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 allem wenn die spätere Formatierung flexibel angewendet werden soll, ist das Vorhalten der 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 den Umgang mit PHP etwas, weil derartige Inkonsistenzen die Lernkurve nicht eben ebnen. Es bleibt in der Praxis nichts weiter übrig, als mit beiden Funktionen parallel zu arbeiten. 111
  • Daten verarbeiten Die folgende Tabelle stellt die beiden Funktionen und deren Formatparameter gegenü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. 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 112
  • Datums- und Zeitfunktionen date strftime Beschreibung 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 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.) 113
  • Daten verarbeiten date strftime Beschreibung %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 Tabelle 3.6: Formatzeichen der Datumsformatierfunktion date und strftime (Forts.) Beachten Sie, dass die Funktion date nur englische Namen für Wochentage oder Monate erzeugen kann. Benutzen Sie in lokalisierten Umgebungen 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 strftime sieht das dann beispielsweise folgendermaßen aus: %d.%m.%Y Nun ist der Funktion noch ein Datumswert zu übergeben. Aus diesem werden die 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 folgende Beispiel zeigt, wie das geht: 114
  • 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 Zeichenkette zurück, sodass anschließend die Ausgabe mit echo oder print erfolgen kann. Die Angabe der Lokalisierung mit setlocale ist dagegen etwas tückisch, denn die benötigten Angaben werden dem Betriebssystem entnommen und hier gibt es Unterschiede zwischen der Windows- und der Linux-Version. Mehr dazu finden Sie im Abschnitt »Lokalisierung und Formatierung von Zeichen« ab Seite 376. Jetzt muss man natürlich feststellen, auf welcher Plattform man gerade das Skript laufen 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"; } setlocale gibt entweder die aktuelle Einstellung zurück, wenn die neue akzeptiert werden konnte, oder FALSE, wenn die Zuweisung misslang. Auf dieser Rückgabe beruht die im letzten Skript gezeigte Ausgabesteue- rung. 115
  • Daten verarbeiten Die Konstante PHP_OS gibt auf Windows-Systemen »WINNT« zurück, unabhängig davon, ob es sich um Windows NT, 2000 oder XP handelt. Auf Linux-Systemen steht »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 Konstanten LC_TIME. Damit wird setlocale mitgeteilt, dass die Einstellung nur für Zeit- und Datumswerte erfolgen soll. PHP kennt auch andere Funktionen, deren Ausgaben sich lokalisieren lassen. Diese verlangen andere Konstanten. Mit LC_ALL wird die Lokalisierung global umgestellt. Im Abschnitt 9.1 »Mehrsprachige Webseiten« ab Seite 374 finden Sie mehr Informationen dazu. Datumsfragmente mit idate Wenn man nur einen bestimmten Teil aus einem Zeitstempel benötigt, ist idate eine interessante Funktion, die mit PHP5 neu eingeführt wurde. Die folgende Tabelle zeigt, welche Formatanweisungen möglich sind: Format Beschreibung B Swatch Internet Time d Tag des Monats h Stunde (12:00 Format) H Stunde (24:00 Format) Tabelle 3.7: Formatanweisungen der Funktion idate 116
  • Datums- und Zeitfunktionen Format Beschreibung 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 Tabelle 3.7: Formatanweisungen der Funktion idate (Forts.) 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 zugrunde liegende Zeitangabe. Beachten Sie bei den zweistelligen Jahreszahlen, dass auch diese als Zahl zurückgegeben werden. Das Jahr 2004 wird also mit dem Parameter »y« als Zahl 4, nicht als Zeichenkette »04« erscheinen. Tricks mit JavaScript: Lokale Zeit ermitteln Um die lokale Zeit eines Benutzers zu ermitteln, benötigen Sie JavaScript. Meist ist es ausreichend, diese Information nur einmal, gleich beim ersten Seitenaufruf, 117
  • Daten verarbeiten zu ermitteln. Es bietet sich an, dazu das Refresh-Tag von HTML einzusetzen, das einen erneuten Aufruf der Seite erzwingt. Der Benutzer muss deshalb nicht ein- greifen, ein Formular senden oder einen Link klicken. Gleichzeitig können Sie feststellen, ob JavaScript überhaupt aktiviert ist. Das Ganze basiert auf einem einfachen Trick. Zuerst wird mit PHP festgestellt, ob eine Zeitinformation bereits vorliegt. Das ist beim ersten Aufruf der Seite natürlich nicht der Fall. Dann wird eine JavaScript-Sequenz erzeugt, die die lokale Zeit abfragt. Es ist hilfreich, dazu ein Formular einzusetzen und das Formular dann per JavaScript senden zu lassen. Listing 3.20: datejavascript.php – Lokale Zeit des Browsers ermitteln <?php echo isset($_POST['timeField']) ? "Time: {$_POST['timeField']}" :  "Keine Zeit"; ?> <!-- Seiteninhalt --> <?php if (!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: if (!isset($_POST['timeField']) && $_SERVER['REQUEST_METHOD'] == 'GET') 118
  • Formatieren und Ausgeben Hier wird einmal abgefragt, ob ein Formular oder eine Seite gesendet wurde. Beim ersten Aufruf der Seite über den Browser kann es sich nie um ein Formular han- deln. Die Variable $_SERVER['REQUEST_METHOD'] enthält diese Information. Der Umgang 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 Skriptblock erzeugt und dann sofort ausgeführt. Dies führt zum Senden des Formulars, was PHP wiederum wie beschrieben erkennt. Ist JavaScript nicht vorhanden, verbleibt die Seite im ersten Zustand, entsprechend ist die Variable $_POST['timeField'] niemals gesetzt. Darauf zielen die Abfragen mit 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. Vor allem 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 deutschsprachigen Sprachraum ü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>"; 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. 119
  • Daten verarbeiten 3. Zeichen für das Dezimaltrennzeichen (deutsch: Komma, englisch: Punkt). 4. Zeichen für das Tausendertrennzeichen (optional, deutsch: Punkt, englisch: Komma). Abbildung 3.12: Unformatierte (oben) und formatierte (unten) Zahl im Vergleich Umwandeln, Anpassen und Ausgeben Mehr Möglichkeiten der Formatierung bieten die rund um printf etablierten Funktionen, die teilweise aus der C-Welt übernommen wurden: í printf Die Funktion formatiert einen oder mehrere Werte und gibt das Resultat wie print sofort aus. í sprintf Die Funktion formatiert einen oder mehrere Werte und gibt das Resultat als Zeichenkette zurück. í vprintf Die Funktion formatiert einen oder mehrere Werte aus einem Array und gibt das Resultat wie print sofort aus. í vsprintf Die Funktion formatiert einen oder mehrere Werte aus einem Array und gibt das Resultat als Zeichenkette zurück. í sscanf Hiermit lässt sich eine Zeichenkette anhand vorhandener Funktionsmuster durchsuchen 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 Nutzung von Formatangaben. Die Eingabewerte werden dann so verändert, dass sie den Formatangaben entsprechen. Alle hier vorgestellten Funktionen verwen- den denselben Satz an Formaten, der nachfolgend vorgestellt wird. 120
  • Formatieren und Ausgeben Die Formatstruktur Jede Formatanweisung wird einem Datenwert zugeordnet. Sie beginnt immer mit dem Prozentzeichen als Sonderzeichen. Soll ein Prozentzeichen ausgegeben wer- den, schreibt man %%. Nach dem Prozentzeichen folgen (optional) verschiedene Formathinweise, 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 die Flexibilität. Am Ende steht dann eine Instruktion, die die Art der Formatierung bestimmt: í b Das Argument wird als ganze Zahl betrachtet und als Binär-Wert ausgegeben. í c Das Argument wird als ganze Zahl betrachtet und das entsprechende ASCII- Zeichen wird ausgegeben. í d Das Argument wird als ganze Zahl betrachtet und ein Dezimalwert (mit Vor- zeichen) wird ausgegeben. í u Das Argument wird als ganze Zahl betrachtet und ein Dezimalwert (ohne Vor- zeichen) wird ausgegeben. í f Das Argument wird als float angesehen und eine Fließkomma-Zahl wird aus- gegeben. í o Das Argument wird als ganze Zahl betrachtet und als Oktalzahl ausgegeben. í s Das Argument wird als Zeichenkette betrachtet und unverändert ausgegeben. í x Das Argument wird als ganze Zahl betrachtet und als Hexadezimal-Wert aus- gegeben (mit Kleinbuchstaben für die Hex-Zeichen »a« bis »f«). 121
  • Daten verarbeiten í X Das 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: í %3s Diese Zeichenfolge gibt das dritte Argument (3) als Zeichenkette (s) aus. í %d Diese Zeichenfolge gibt das in der Liste nächste Argument als Dezimalzahl aus. í %082d Auch dies formatiert eine Dezimalzahl. Die drei Zeichen haben hier folgende Bedeutung: 1. Füllwert, entweder ein Leerzeichen, die Zahl 0 oder ein beliebiges, von einem Apostroph ’ eingeleitetes Zeichen 2. Die Anzahl der Stellen vor dem Dezimaltrennzeichen, auf die aufgefüllt wird. 3. Die Anzahl der Stellen nach dem Dezimaltrennzeichen. í %4’_-10s Dieses Format formatiert eine Zeichenkette linksbündig (das macht das Minuszeichen, rechtsbündig ist der Standard), die das vierte Argument der Parameterkette (4) nutzt. Zum Auffüllen wird der Unterstrich benutzt (’_), der die Zeichenkette auf zehn (10) Zeichen auffüllt. »s« definiert den Datenwert als 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>"; 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, 122
  • 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 Wert und suchen deshalb die Argumente vom ersten beginnend ab: $d erhält $s2 und das letzte Format den zweiten Wert, der ebenfalls $s2 enthält. Abbildung 3.13: Komplexe Formatierungen mit Formatanweisungen 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 der Farbcode 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> Lokalisierung mit setlocale Informationen zu setlocale folgen im Abschnitt »Lokalisierung und Formatie- rung von Zeichen« ab Seite 376. 123
  • Daten verarbeiten Werte erkennen Die Funktionen sscanf und fscanf drehen den Vorgang um. Erwartet wird eine Zeichenkette, die bestimmte Wertfragmente enthält. Daraus wird dann der Wert extrahiert 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 hier die Übernahme des von sscanf erzeugten Arrays mit list in die drei erwarteten Variablen. Mehr Informationen zu Array folgen im Abschnitt 5.1 »Datenfelder im Einsatz: Arrays« ab Seite 182. Abbildung 3.14: Die einzelnen dezimalen Farb- werte wurden aus der hexadezima- len Folge extrahiert Allerdings kann sscanf nicht erkennen, wenn die gesuchte Zeichenfolge in einer anderen eingebettet ist. Dafür kommen nur reguläre Ausdrücke in Frage, die mehr Formatangaben erlauben, dafür aber ungleich komplexer sind. 3.9 Referenz Dieser Abschnitt enthält eine Übersicht aller in diesem Kapitel behandelten Funk- tionen in tabellarischer Form. 124
  • Referenz Zeichenketten-Funktionen Insgesamt kennt PHP5 knapp 90 Zeichenkettenfunktionen. Es ist sinnvoll, sich dieser 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. strcasecmp Vergleicht zwei Zeichenfolgen auf binärer Basis. Beachtet Groß- und Kleinschreibung. Tabelle 3.8: Zeichenkettenfunktionen zum Vergleichen, Suchen und Ersetzen 125
  • Daten verarbeiten Funktion Bedeutung 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. Tabelle 3.8: Zeichenkettenfunktionen zum Vergleichen, Suchen und Ersetzen (Forts.) 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 similar_text Diese Funktion berechnet die Ähnlichkeit zweier Zeichenketten als Prozentwert. Tabelle 3.9: Zeichenkettenfunktionen zum Ermitteln von Eigenschaften 126
  • Referenz Funktion Bedeutung 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 Tabelle 3.9: Zeichenkettenfunktionen zum Ermitteln von Eigenschaften (Forts.) 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. 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 127
  • Daten verarbeiten Funktion Bedeutung 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. Tabelle 3.10: Zeichenkettenfunktionen zur Auswahl einer Teilzeichenkette und andere Umwandlungsoperationen (Forts.) Funktion Bedeutung addslashes, Fügt Backslashes hinzu. addcslashes stripslashes, Entfernt Backslashes. stripcslashes 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 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 128
  • Referenz 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 Funktionen für reguläre Ausdrücke 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 Referenz Ausgabe- und Datumsfunktionen 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. 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 129
  • Daten verarbeiten Funktion Bedeutung 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. Tabelle 3.16: Funktionen zum Erzeugen, Erkennen und Formatieren von Datums-, Zahlen- und Zeichenkettenwerten (Forts.) 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 Hilfe des style-Attributes erstellt und ausgibt. Tipp: Verwenden Sie printf. 130
  • Programmieren 4
  • Programmieren Jetzt geht es richtig los. Zur richtigen Programmierung benötigen Sie zwangsläufig Anweisungen, die den Programmfluss steuern. Dieses Kapitel stellt alle Befehle vor, 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 im Zusammenhang 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 von Sprachen wie JavaScript, Java, C++ und C# entstanden. Einfache Verzweigungen mit if Der Befehl if dient der Programmierung einer einfachen Verzweigung. Damit können Programmteile in Abhängigkeit von einer Bedingung ausgeführt werden. Als Bedingung verwendet man einen Ausdruck, der einen Booleschen Wert zurückgibt – also entweder Wahr (TRUE) oder Falsch (FALSE). Zur Verknüpfung von solchen Ausdrücken gibt es verschiedene Operatoren, die in der Einführung bereits vorgestellt wurden. Aufbau von Ausdrücken Da PHP praktisch über ein sehr schwaches Typkonzept verfügt, werden oft intern Typumwandlungen 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- gleichsoperatoren vergleichen zwei Werte, entweder zwei Variablen oder eine Variable und eine Konstante. Zu den Vergleichsoperatoren gehören >, <, >=, <=, ==, != usw. 132
  • Verzweigungen Denken Sie daran, dass der Operator »gleich« aus zwei Gleichheitszei- chen besteht! $a > 3 45 <= $zahl $zahl1 == $zahl2 Derartige primitive Ausdrücke geben immer einen Wahrheitswert zurück. In der Praxis 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 diese eine so genannte Assoziativität besitzen, die die Rangfolge bei der Verarbeitung bestimmt. Wenn diese intern festgelegte Reihenfolge nicht passt, setzen Sie ein- fach Klammern. Assoziativität Rangfolge Operator 1. (höchster Rang) Links , 2. Links or 3. Links xor Tabelle 4.1: Rangfolge und Assoziativität von Operatoren 133
  • Programmieren Assoziativität Rangfolge Operator 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 Tabelle 4.1: Rangfolge und Assoziativität von Operatoren (Forts.) In der Praxis bedeutet die Rangfolge »Links«, dass beispielsweise im folgenden Ausdruck der linke Teil zuerst ausgeführt wird, weil der Operator || Links-assozia- tiv ist: $b == 8 || $c == $d Wird dagegen eine Typumwandlung mit (float) durchgeführt, wird der Ausdruck rechts vom Operator zuerst ausgeführt: (float) $a = 3 134
  • 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 und erst dann wird $a in eine Gleitkommazahl umgewandelt. Keine Assoziativität bei den Operatoren == und deren Verwandten heißt, dass es für das Ergebnis keinen Unterschied ergibt, ob ein Teilausdruck links oder rechts steht. Dies kann ausgenutzt werden, um einen typischen Fehler zu vermeiden, der häufig passiert: Die Verwechslung von = und ==. Schreiben Sie nämlich in einem if-Befehl nur ein einfaches Gleichheitszeichen, so wird PHP als Ergebnis der Zuweisung 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 derartige Tippfehler zu vermeiden, vertauscht man die Operanden: $a = 23; if (23 = $a) { // } Würde korrekt 23 == $a geschrieben werden, funktionierte der Ausdruck wie erwartet, denn bei == spielt die Rangfolge keine Rolle. 23 = $a misslingt allerdings, weil zuerst der rechte Ausdruck ausgewertet wird (gelingt noch) und dann der linke, was die Zuweisung einer Variablen zu einem Literal bedeuten würde – ein klarer Fall für einen Syntaxfehler. Eng verbunden mit der Assoziativität ist auch eine Optimierungseigenschaft von PHP. Manche Ausdrücke müssen nämlich nicht vollständig ausgewertet werden: $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- 135
  • Programmieren läufig wahr sein. Es ist also nicht notwendig, den rechten Teil abzuarbeiten, denn dies würde nie etwas am Ergebnis ändern. Das spart Rechenzeit und verbessert das Laufzeitverhalten. 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ür den Programmfluss erforderliche Aktionen ausführen, unabhängig vom jeweiligen Rückgabewert. Die Optimierung würde aber immer dann die Ausführung von TestFunction2 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 sich in Abhängigkeit von konkreten Daten gegenseitig. Das führt in der Praxis zu schwer beherrschbaren und kaum nachvollziehbaren Programmfehlern. Die erste Lö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 ohne Optimierung gearbeitet wird. Die zweite Lösung heißt PHP und verwendet Operatoren, die die Optimierung einfach 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 Wahr ist. Oft sind jedoch Aktionen auszuführen, wenn die Bedingung nicht erfüllt ist. Dazu wird ein alternativer Zweig geschrieben, der mit dem Schlüsselwort else einzuleiten ist. Auch hier gilt die Regel, dass mehrere Befehle nach else als Block zusammengefasst werden müssen. 136
  • 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 umfangreiche Bedingungen zu testen. Mehrfachverzweigungen mit switch Wenn sich in einem Skript mehrere Auswertungen mit if aneinanderreihen, kann man 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 muss deshalb explizit mit dem Schlüsselwort break abgeschlossen werden. Der Code hinter den einzelnen case-Zweigen muss nicht mit einem Blockkennzeichen umschlossen werden, weil hier immer alles bis zum nächsten break oder dem Ende 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 $hour = 13; echo "Tagesabschnitt für Stunde $hour: "; switch ($hour) { case 1: 137
  • 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ür den 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 nach dem 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; echo "Tagesabschnitt für Stunde $hour: "; switch ($hour) { case 1: case 2: 138
  • 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 die Codeausführung am nächsten Zweig fortgesetzt wird, wie es bei fehlendem break der Fall ist. Betrachten Sie die folgende Sequenz: case 15: case 16: case 17: Mit if könnte man dafür folgendes schreiben: if ($hour == 15 or $hour == 16 or $hour == 17) Der gesamte Block verhält sich darüber hinaus wie ein allein stehendes case. 139
  • 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 auch Ausdrü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ück verknüpft werden. Dazu setzt man einfach im Kopf eine der Konstanten TRUE oder FALSE 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: echo "Abends"; break; } PHP löst hier intern zuerst die Booleschen Ausdrücke auf und ermittelt den Wert TRUE oder FALSE. Dann vergleicht er TRUE==TRUE oder TRUE==FALSE und springt in 140
  • Schleifen erstellen den ersten passenden Zweig. Man muss hier aufpassen, dass immer nur ein Zweig gefunden wird, auch wenn ein danach liegender ebenso zutreffend wäre. switch kann 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 mit einer Abbruchbedingung (do) bzw. einer Eintrittsbedingung (while). for ist eine Zä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 falscher Programmierung zu Endlosschleifen führen. Endlosschleifen sind meist uner- wünscht. Mit PHP sind solche Programmierfehler zwar lästig, aber nicht wirklich kritisch. 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 in einer 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, die grundsätzlich nur zusammen mit if oder (seltener) switch eingesetzt werden: í break í continue 141
  • 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 der eigentlichen 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 sind sehr 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 beim Eintritt in die Schleife TRUE ergeben. Ist das nicht mehr der Fall, wird die Schleife beendet. Nach jedem Schleifendurchlauf wird die Iterationsanweisung ausgeführt, dann erfolgt die Prüfung der Laufbedingung. Die Initialisierung wird nur beim Schleifeneintritt ausgeführt. Die grundsätzliche Syntax sieht folgendermaßen aus: for (Initialisierung; Laufbedingung; Iterationsanweisung) { // Code in der Schleife } 142
  • Schleifen erstellen Um nun mehrere Ausdrücke in einen Abschnitt zu packen, werden diese durch Kommata 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. Sie würde deshalb nicht im Kopf der for-Schleife funktionieren. Solche Konstruktionen sind aber eigentlich sowieso keine gute Idee, weil sie die Lesbarkeit erschweren und schnell Programmierfehler hervorrufen. Eigentlich ist for nämlich eine Zählschleife und nicht dazu gedacht, Ausgaben oder Code des Schleifenkö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 der Durchläufe mit der Formel $laufwert < ENDWERT zu bestimmen. Die gezeigte Schleife produziert wie erwartet sieben Durchläufe und zählt dabei von null bis sechs. Der naive Ansatz wäre sicher, hier von eins bis sieben zu zählen, zumal viele Ausgaben dies auch erwarten. Intern verwendet PHP jedoch – wie viele andere Sprachen auch – so genannte null-basierte Indizes. Egal ob Arrays, Datenbankab- fragen oder andere zählbare Quellen verwendet werden, fast immer hat der erste Wert der Reihe den Index 0. Wenn Sie es sich von vornherein angewöhnen, Zähl- schleifen null-basiert zu bauen, wird vieles leichter. 143
  • Programmieren Abbildung 4.1: Tabelle, mit einer einfachen Zählschleife erzeugt Die Trennung von Logik und Benutzerschnittstelle gehört zu den elementaren Richtlinien der praktischen Informationsverarbeitung. PHP bietet hier nur eine rudimentäre Unterstützung, aber auch im Kleinen lohnt es, das Programmdesign nach diesem Prinzip auszurichten. Wenn Sie im gezeigten Beispiel tatsächlich eine Ausgabe der Zählwerte wünschen und diese Ausgabe mit 1 beginnen soll, ändern sie nicht den Schleifenkopf. Fügen Sie stattdessen eine Berechnung allein fü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 die Iterationsanweisung 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, zugleich aber auch den Inhalt der Laufvariablen verändern. Da diese aber ohnehin bei jedem Durchgang erhöht wird, würde nun jeder Durchlauf deren Wert um zwei erhöhen. Da sich die Abbruchbedingung nicht geändert hat, wird nur die Hälfte der 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 aber einfach nur schlechter Stil. Die Veränderung der Schleifenvariablen ist ein so genannter Seiteneffekt, der schwer verständlich ist, wenn jemand anderes den Code liest1. Seiteneffekte sind schlecht. Vermeiden Sie es unbedingt, die Schlei- fenvariable während des Durchlaufs zu manipulieren. 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 weiteren Bedingungen ein, um einen Notausstieg zu haben. 1 Jemand anderes sind auch Sie selbst, sechs Monate später! 144
  • Schleifen erstellen Wenn Sie nun Ausgabewerte und Zählwerte gleichzeitig benötigen, bietet es sich an, 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 primitivste Demonstration 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. Diese lä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, was eine zusätzliche Fehlerquelle bedeutet. Beachten Sie außerdem, dass der Befehl echo hier nicht einsetzbar ist. for erwartet Ausdrücke und echo hat keinen Rückgabewert, was ein Merkmal eines Ausdrucks ist. Deshalb wird hier print eingesetzt. 145
  • 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 ist nicht 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ßerdem multipliziert 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 erreicht man schnell Skriptlaufzeiten, die deutlich spürbar sind. Zeitkritische Abfragen haben in Schleifen sowieso nichts verloren, aber in verschachtelten Schleifen kön- nen auch triviale Berechnungen problematisch werden. Denken Sie beispielsweise an den Aufbau eines Farbrades für RGB-Farben. Statt ein GIF vorzubereiten mö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.216 Ausgaben. Abgesehen davon, dass die Darstellung wenig hilfreich ist, führt allein eine 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. Kein System kann alle Farben wirklich darstellen. Für das Web benutzt man 216 so genannte 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: Listing 4.9: forllopnested.php – Erzeugen eines Farbwählers mit websicheren Farben $r = 0; $g = 0; $b = 0; // Farben ermitteln 146
  • 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 mit 18 mal 12 Rechtecken definiert. Die äußere Schleife zählt die Reihen (12), die innere 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 Schachteleffekt erzeugt, der ähnliche Farben nebeneinander platziert. Abbildung 4.2: forloopnested.php: Farbwähler mit sortierten Farben Die Ausgabe der Farben erfolgt durch Einfärben des Hintergrundes der erzeugten Tabellenzellen. Die passenden RGB-Werte erzeugt die printf-Funktion, die mit der Formatanweisung %2X aus einer Dezimalzahl den passenden zweistelligen Hexwert erzeugt. 147
  • Programmieren Die Universalschleifen do/while und while Die Anweisungen do/while und while werden so lange durchlaufen, wie die Bedingung 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 Inhalt mindestens einmal durchlaufen wird. Das ist der Fall, weil die Prüfung – wie der Schleifenkörper suggeriert – erst am Ende erfolgt. Dies ist freilich nicht mehr der Fall, 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; while($y < 12) { $g = $ContG[$y++]; echo '<tr height="10">'; $x = 0; while($x < 18) 148
  • 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 3 Arrays ab Seite 73 vorgestellt. Sie bauen wieder passende Datenfelder aus den Farbwerten 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) und erzeugen so das Muster. Die Ausgabe findet mit einer printf-Funktion statt, die die 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 man auch folgendes schreiben: $g = $ContG[$y]; $y++ ; Falls Sie $y anders als zur Zählung verwenden, müssen Sie die Erhöhung (oder jede andere Änderung) ohnehin so platzieren, dass die Änderung erst am Ende der Schleife erfolgt. Abbildung 4.3: Verschachtelte Schleifen wurden zum Erzeugen dieser Farbfläche benutzt 149
  • 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 wegfallen oder in beliebiger Menge eingesetzt werden können, gibt es noch eine Reihe von Hilfsfunktionen 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 also recht allgemeingültig. Funktionen definieren Funktionen kennen Sie bereits – praktisch besteht PHP aus Hunderten Funk- tionen. Diesen eingebauten Funktionen kann man leicht eigene hinzufügen und im Skript verwenden. Das ist praktisch, um größere Skripte modular aufzubauen, Code leichter wieder verwenden zu können und ein und denselben Ablauf mit verschiedenen Daten auszuführen. Die Funktionsdefinition Funktionen werden durch das Schlüsselwort function definiert. Die Definition umfasst einen Funktionskopf, der Name und Parameter definiert. Diese so genannte Signatur der Funktion bestimmt, wie die Funktion später aufgerufen wird. 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">'; for ($i = 0; $i < 3; $i++) { PrintTableRow(); } echo '</table>'; 150
  • Benutzerdefinierte Funktionen Der Funktionskopf ist hier in seiner einfachsten Form zu sehen: function PrintTableRow() Da keine Parameter verwendet werden, schreibt man nur zwei runden Klammern zum Abschluss. Schlüsselwort und Name sind ebenso obligatorisch. Der Name sollte so gewählt werden, dass ein Verb am Anfang steht, denn Funktionen führen Aktionen aus, die man üblicherweise mit Verben bezeichnet. Wie das Beispiel zeigt, eignen sich Funktionen zum Einsatz an allen Stellen im Skript, also auch in Schleifen. Die Reihenfolge der Definition spielt keine Rolle. PHP5 beherrscht die so genannte späte Bindung, bei der die Aufrufadressen der Funktionen erst am Ende der 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 Rahmen der Ausführung auf der Seite an der Stelle, wo die Ausgabe beim Aufruf der Funk- tion gerade war. Das Beispiel nutzt diesen Effekt bereits. Abbildung 4.4: Ausgabe einer Tabelle mit einer Funktion Rückgabewerte 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 Sie diese mit Hilfe des Schlüsselwortes return zurückgeben. 151
  • Programmieren Eine Unterscheidung zwischen Prozeduren (kein Rückgabewert) und Funktionen (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 der Aufruf 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 veranlassen oder 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üsste man 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 noch nicht vorgestellt wurden, sei hier ein kleiner Ausflug präsentiert. Wenn Sie mehrere Werte zurückgeben müssen, erstellen Sie ein Array: function RetArray() { $result = array(45, 26, 14); return $result; } 152
  • Benutzerdefinierte Funktionen Beim Aufruf weisen Sie die Rückgabewerte mit Hilfe der Funktion list wieder einzelnen 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 von der Definition schwanken. Dies ist tückisch (wenn auch recht bequem), denn der Aufruf mit einer zu großen Anzahl Parameter führt nicht zwingend zu einer Feh- lermeldung. Da PHP typlos arbeitet und der Datentyp der Parameter ignoriert wird, 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 der Funktion aufschreibt: function PrintTableRow($cells) Diese Funktion hat einen Parameter. Innerhalb des Funktionskörpers kann über die Variable $cells darauf zugegriffen werden. Mehrere Parameter werden durch Kommata 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 function PrintTableRow($bgcolor, $cells) { $cellstring = ''; for ($i = 0; $i < $cells; $i++) { 153
  • 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 Farbe durch den ersten. Das ergibt im Beispiel die folgende Ausgabe: Abbildung 4.5: Ausgabe des letzten Listings mit variablen Farben Optionale Parameter Das letzte Beispiel nutzte den zweiten Parameter nicht wirklich, denn es wurde immer derselbe Wert übergeben. Es ist deshalb sinnvoll, für Standardaufrufe einen Standardwert zu definieren. Der Parameter ist dann optional verwendbar, fehlt die Angabe, 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++) { $cellstring .= "<td> $i </td>"; } $row = <<<RET <tr bgcolor="$bgcolor">$cellstring</tr> RET; 154
  • 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 ist logisch, denn PHP kann die Position nur durch Abzählen feststellen. Da PHP keine 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 Sache eindeutig: PrintTableRow() Mit beiden Parameter funktioniert es natürlich auch. Aber bei der Angabe nur eines Parameters versagt der Aufruf. Das folgende Beispiel funktioniert nicht wie erwartet: PrintTableRow(4) PHP5 kann nicht erkennen, dass hier die Anzahl der Zellen gemeint war und offensichtlich die Standardhintergrundfarbe zum Einsatz kommen soll. Auch mit den passenden Datentypen ist dies nicht möglich, weil sich die Parametertypen ja nicht 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 ein Literal sein muss. Variablen und Ausdrücke sind nicht erlaubt. Deshalb ist die fol- gende Variante unzulässig: function PrintTableRow($bgcolor = $gray, $cells = 3 + 3) 155
  • 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 ersten Variante. Der Aufrufer kann dennoch beliebig viele Parameter angeben. Daneben ist die nachfolgend vorgestellte Angabe von vielen Parametern auch besser, als diese in endlosen Schlangen aufzuschreiben. Man findet in der professionellen Programmierung selten Funktionen mit sechs oder mehr Parametern. Die dadurch erreichte Steuerbarkeit einer Funktion ist derart umfassend, dass wohl meist 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 den Index 0. So erhält man Zugriff auf alle Parameter. í func_get_arc Alternativ zum Array kann jeder Parameter auch direkt über eine Funktion adressiert 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 der Programmierung, dass Eingabedaten nie vertraut werden darf. Würden alle Pro- gramme so erstellt werden, gäbe es kaum noch Programmierfehler, denn die eigentlichen Algorithmen lassen sich meist gut testen. Listing 4.15: functionparams.php – Intelligente Funktion mit variabler Parameterzahl function BuildRow() { $cells = 4; $Params = func_num_args(); if ($Params == 0) { return "<tr><td colspan="$cells"></td></tr>"; } 156
  • 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>'; Abbildung 4.6: Ausgabe einer einfachen Tabelle mit benutzerdefinierter Funktion Diese Funktion prüft zuerst, ob Parameter vorliegen. Ist das nicht der Fall, wird eine 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 man diesen Zustand (keine Parameter) vermeiden, so sollte man einen Laufzeitfehler erzeugen oder eine entsprechende Ausschrift. Stehen nun Parameter zur Verfügung, wird zuerst deren Anzahl ermittelt: $Params = func_num_args(); Mit dieser Anzahl wird die Schleife bedient, die die Zellen erzeugt. Zu jeder Zelle wird ein Parameter als Zelleninhalt abgerufen: $row .= '<td>' . func_get_arg($i) . '</td>'; 157
  • Programmieren HTML-Tabellen benötigen immer eine konstante Anzahl Tabellenzellen pro Reihe. Entweder man fügt für fehlende Zellen colspan-Attribute ein oder füllt die Zellen auf. Das Auffüllen geschieht hier mit einer weiteren Schleife, die auf dem letzten Wert der ersten Schleife aufbaut. Diese wird dann durchlaufen, bis die Anzahl 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 es möglich, feste mit optionalen Parametern zu kombinieren. Das zweite Beispiel legt einen Booleschen Parameter fest, der darüber entscheidet, ob Kopf- oder Inhaltszellen erzeugt werden. Ein weiterer fester Parameter bestimmt die maxi- male Anzahl. Die Prüfung wird so erweitert, dass bei Unterschreiten der Anzahl eine 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>"; } 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>'; 158
  • Benutzerdefinierte Funktionen Die Anzahl der Parameter, die func_num_args ermittelt, bezieht hier die beiden feste am 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. Abbildung 4.7: Ausgabe einer Tabelle mit eigener Funktion Referenzen auf Funktionen und Parameter Wie in den letzten Abschnitten gezeigt, kann man mit Parametern Daten an eine Funktion übergeben und deren Wiederverwendungswert damit erhöhen. Die Rückgabe nur eines Wertes mit return verschafft zusätzliche Flexibilität. Das ist jedoch nicht immer ausreichend. Sollen von einer Funktion viele Werte geändert werden, so kann dies auch über die Parameter erfolgen. Um die dazu verwendete Technik zu verstehen, muss man sich an ein wenig Theorie wagen. Prinzip der Parameterübergabe Einfache Skriptsprachen wie PHP arbeiten meist direkt mit Variablen. Wird der Wert einer Variablen einer anderen Zugewiesen, findet tatsächlich eine Übertra- gung des Wertes statt. Variablen sind die Namen von Speicherbereichen im Hauptspeicher des Computers. Die folgende Zuweisung überträgt nur den Wert von einem Platz an einen anderen: $a = $b; Änderungen von $b zu einem späteren Zeitpunkt wirken sich nicht auf $a aus. Da so 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 (so genannte Zeiger) gearbeitet wird. C# verwendet intern immer Zeiger, dem Benut- zer werden diese aber nicht zugänglich gemacht und erst beim Schreiben wird der syntaktisch sichtbare Zusammenhang hergestellt und der Wert kopiert. Wegen der Verwaltung dieser Vorgänge spricht man von verwaltetem Code. C++ profitiert von Zeigern durch deutlich höhere Geschwindigkeit, durch den direkten Spei- cherzugriff ist es jedoch fehleranfällig. C# ist nicht ganz so schnell, aber schneller 159
  • Programmieren als andere Sprachen und erzeugt sehr stabilen Code. PHP zu guter Letzt beherrscht weder Zeiger noch kennt es verwalteten Code. Man kann aber mit Referenzen 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. Leider kann auch in PHP5 das Prinzip auf den Kopf gestellt und die Referenzierung dem Aufrufer überlassen werden: BuildRow(&$t); Es ist deshalb gefährlich, Parameter zu verändern, wenn dies nicht ausdrücklich erwünscht ist. Neben der Prüfung der Parameter heißt dies in der Praxis, dass zuerst alle Werte in lokale Variablen kopiert und dann weiter verwendet werden. Zum Thema Variablen gibt es darüber hinaus noch einige weitere Besonderheiten im Zusammenhang mit Funktionen. Sie werden in den folgenden beiden Abschnitten vorgestellt. Statische Variablen Statische Variablen erlauben es, den Wert einer Variablen von Funktionsaufruf zu Funktionsaufruf zu erhalten. Das Beispiel mit dem Tabellenreihengenerator könnte so mit einer Zählfunktion erweitert werden. Als Schlüsselwort wird static eingesetzt. 160
  • 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>'; Die Deklaration einer statischen Variablen führt dazu, dass diese ihren letzten Zustand behält, auch wenn der Programmfluss die Funktion wieder verlassen hat. Die Deklaration sollte immer auch die Zuweisung des Startwertes enthalten: static $rowNumber = 1; 161
  • Programmieren PHP5 führt diese Zeile nur beim ersten Aufruf aus. Danach gilt die Variable als bekannt und die 1 wird nicht erneut zugewiesen. Damit kann der Wert bei jedem Aufruf verändert werden: $rowNumber++; Bei der Ausgabe ergibt sich daraus dann eine fortlaufende Zählung der Tabellen- reihen: Abbildung 4.8: Tabelle, gebaut mit einer zählende Spalte mit stati- schen Funktionsvariablen Die hier vorgestellte Methode mit statischen Variablen hat – trotz glei- chen Namens – nichts mit den statischen Mitgliedern von Klassen zu tun. Der Name wurde bereits vor langer Zeit in PHP benutzt, in der an die mit PHP5 eingeführten objektorientierten Konzepte noch nicht zu denken war. Globale Variablen und Konstanten PHP5 kennt, abgesehen von den Schutzmechanismen für Variablen innerhalb von Klassen, nur globale Variablen. Die Sichtbarkeit beschränkt sich auf das Basisskript, innerhalb von Funktionen sind globale Variablen normalerweise nicht sichtbar. In der Regel helfen Parameter, den Zugriff auf Werte zu erlauben. Wenn nichts Grundlegendes dagegen spricht, sind Parameter der beste Weg. Außerdem verfügt PHP5 über einige so genannte »Super-Arrays«, die wichtige Werte aus For- mularen oder Cookies enthalten und die auch in Funktionen abgerufen werden können. Globale Variablen in Funktionen nutzen Dennoch besteht manchmal der Wunsch, Variablen aus der obersten Skriptebene sichtbar 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 ist zugleich auch ein Vollzugriff auf die Variable, das heißt, Änderungen innerhalb der Funktion wirken sich direkt auf das Original aus. 162
  • 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 Inhalt werden dabei nicht verändert. Konstanten in Funktionen Konstanten sind in PHP generell global – sie müssen nicht explizit bekannt gemacht 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) { $cells = CELLS; $cellstring = ''; for ($i = 0; $i < $cells; $i++) { $cellstring .= "<td> $i </td>"; } 163
  • 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 Sie dennoch die folgende Zuweisung: $cells = CELLS; Hier wird die Konstante in einer lokalen Variablen benutzt. Man profitiert damit von 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öglicherweise unheimliche Technik, des Selbstaufrufs von Funktionen. Es gibt viele Anwendungsmöglichkeiten. So kann eine Funktion beispielsweise den Inhalt eines Verzeichnisses ermitteln. Ein Verzeichnis kann aber wiederum ein Unterverzeichnis enthalten usw. Deren Anzahl ist per Definition nicht begrenzt 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-Funktionen lokale 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- grammiert werden. Prinzipiell läuft das so ab, dass PHP bei jedem Aufruf einer Funktion die aktuelle Adresse des Programms auf einem Stapel ablegt. Am Ende der Funktion oder bei Ausführung von return wird die oberste Adresse vom Stapel wieder entnommen und diese zum Rücksprung benutzt. Wird die rekursive Funk- tion irgendwann beendet, löst sie nach und nach alle Stapelwerte wieder auf. 164
  • 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 Speicherverbrauch des Skripts ab. Allerdings muss man beachten, dass Hunderte Funktionsaufrufe zu erheblichen Laufzeiten führen und die genannte Größe mehr als ausreichend ist. Sollten Sie mehr rekursive Aufrufe benötigen, liegt vermutlich ein Designfehler im 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 auch rekursiv 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 ist der Ablauf der Funktion. Das schrittweise Verfolgen kann sehr hilfreich sein: 1. Der erste Aufruf füllt die Parameter mit den Werten 7 und 2. 2. Nach der Prüfung 2 > 0 (Wahr) erfolgt die erste Berechnung: 7 * (Nächster Aufruf mit 7 und 1) 165
  • 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 ans Hauptprogramm: 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ösungen verwenden auch for oder foreach, die irgendwann keine definierten Startwerte mehr erhalten und deshalb den im Schleifenkörper liegenden rekursiven Aufruf nicht mehr ausführen. Variable Funktionen Neben dem normalen Funktionsaufruf kennt PHP auch dynamische Funktionen, ähnlichen den dynamischen Variablen. Dabei wird der Name einer Funktion einer Variablen zugewiesen und diese dann mit der Funktionssyntax aufgerufen. Der Vorteil dabei: Der Name der Funktion wird wie eine Zeichenkette behandelt werden. 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>"; } function MakeI($text) { return "<i>$text</i>"; } function MakeU($text) 166
  • 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 besteht darin, 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ürften regelmäß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 von Funktionen direkt ausgeben, bleibt normalerweise nur eine Zeichenverkettung: echo 'Kursiv: ' . $func('Test') . '<br>'; Viele Anführungszeichen, viele Punkte, wenig Lesbarkeit; derartige Konstrukte sind 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- onenten die korrekten Grenzen zu zeigen. Abbildung 4.9: Ausgaben, erzeugt mit dynamischen Funktionsaufrufen 167
  • 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 und die Ausgabe in eine Zeichenkette integriert. Die Ausgabe ist wenig spektakulär, aber die dadurch erreichte Flexibilität reicht hart an selbst modifizierenden Code heran. Abbildung 4.10: Ausgabe, komplett mit dynamischen Funktionen erzeugt Sprachkonstrukte und Anweisungen, wie beispielsweise echo oder include, 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. PHP kennt als äußeres Strukturierungskriterium nur Dateien. Will man also Teile eines anderen Programms wieder verwenden, muss man diese in eigenen Dateien able- gen. Für das Einbindung solcher Module werden spezielle Funktionen bereitge- stellt. Module einbinden PHP5 kennt insgesamt vier Anweisungen, die Module aus Dateien aufrufen: 168
  • 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 binden den enthaltenen Code so ein, also ob er an dieser Stelle geschrieben wäre. Sie werden 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 erst die 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. include wird dagegen erst ausgeführt, wenn der Programmfluss am Befehl angekommen ist. Eine fehlende Datei führt dann lediglich zu einer Warnung. Bezüglich der reinen Verarbeitung der Inhalte verhalten sich beide Anweisung identisch. Im folgenden Text wird deshalb keine Trennung mehr vorgenommen und 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 <?php und ?> eingeschlossen werden. Betrachten Sie zuerst die Anwendung: Listing 4.24: include.php – Nutzung von Moduldateien <html> <head> <?php $title = "Testseite"; ?> <title><?=$title?></title> </head> <body> 169
  • Programmieren <?php include ('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 der Verwendung steht jedes Modul für sich und deklariert seinen PHP-Bereich – wenn vorhanden – 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 ohne weiteres 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 vergleichbare Techniken 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 Einbindung von reinem HTML eignen. Dies verschafft möglicherweise eine bessere Flexibili- tät als die Verwendung der Heredoc-Syntax, wenn größere Textblöcke erforderlich sind. Module finden Im letzten Beispiel wurde der Pfad zu den Modulen – sie waren im Unterverzeich- nis /include platziert – direkt angegeben. Möglicherweise haben Sie eine Samm- lung solcher Module an zentraler Stelle auf dem Server liegen. Dann wäre die Angabe des Pfades immer und immer wieder sehr lästig. Deshalb kann man einen speziellen Suchpfad angeben, der von PHP benutzt wird, wenn im aktuellen Ver- 170
  • Modularisierung von Skripten zeichnis die entsprechende Datei nicht aufzufinden war. Der Wert kann entweder in der php.ini konfiguriert oder mit ini_set angegeben werden. Listing 4.27: includeset.php – Pfad zu den Modulen über Suchpfade finden <html> <head> <?php ini_set('include_path', 'include'); $title = "Testseite"; ?> <title><?=$title?></title> </head> <body> <?php include ('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 Verschiebung des 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 die mehrfache Verwendung innerhalb eines Skripts fatal. Denn eine mehrfache Defi- nition – worauf es hinausläuft – ist nicht zulässig. Ein Laufzeitfehler wäre die Folge. In solchen Fällen verwendet man include_once. Die Anweisung stellt sicher, dass der Inhalt nur beim ersten Mal ausgeführt wird. Viele große Projekt verwenden nämlich verschachtelte Zugriffe auf die Module und andere Module haben möglicherweise einen bestimmten Code bereits eingebunden. Mangels anderer Konzepte sind solche Mehrfachverwendungen in PHP durchaus üblich. Aus informationstechnischer Sicht ist include_once zwar eher als »dirty hack« zu betrachten, aber es passt zu PHP als primitiver Sprache. 171
  • Programmieren Automatisches Einbinden mit __autoload Die Funktion __autoload ist auf globaler Ebene zu definieren. Sie wird immer dann 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 sieht das 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 die Funktion am Ende des Skripts aufrufen, andernfalls werden nur die bis zu diesem Zeitpunkt eingeschlossenen Module ermittelt. Das Array enthält an erster Stelle immer auch das eigentliche Hauptskript. Beide Funktionen geben übrigens alle durch require oder include eingebundene Module an, sind also praktisch völlig identisch. Vermutlich ist dies ein Bug, denn die Namen suggerieren etwas anderes. Wenn Sie noch keinen Umgang mit Arrays beherrschen, können Sie die folgende Ausgabe verwenden, um das Ergebnis zu sehen: <html> <head> <?php 172
  • Fehlerbehandlung ini_set('include_path', 'include'); $title = "Testseite"; ?> <title><?=$title?></title> </head> <body> <pre> <?php include ('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 eignet sich vor allem zur schnellen Fehlersuche: Abbildung 4.11: Ausgabe der Liste der eingeschlosse- nen Dateien 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 C. Allerdings gelang den Entwicklern nur eine halbherzige Umsetzung, was den Wert wieder etwas in Frage stellt. Nichtsdestotrotz lohnt es sich, die neuen Mög- lichkeiten auszuprobieren und einzusetzen. 173
  • Programmieren Aber auch die konventionelle Technik der Fehlerbehandlung ist nicht völlig auß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 das Prinzip. 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 { echo 'Name nicht vorhanden'; } Das Skript ist prinzipiell korrekt geschrieben. Die Funktion folgt den üblichen Regeln und prüft vor der Verarbeitung, ob sinnvolle Daten vorliegen. Im Beispiel 174
  • 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 als auch 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 Zahlenwerte zurückgeben und dann -1, 0 und 1 usw. definieren. Das ist nicht schön, denn die Zustä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 Funktion deklariert, 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); } $Names = array ('Joerg', 'Uwe', 'Clemens', 'Lukas'); if (in_array($Name, $Names)) { return true; } 175
  • 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 werden kö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 beachtet mit 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 der folgenden Tabelle zu entnehmen. 176
  • 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 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 Werte größer als 65.535 enthalten, damit keine Konflikte auch in künftigen Versionen 177
  • Programmieren auftreten. Im Übrigen ist noch zu beachten, dass die Werte Bitfelder darstellen und 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 einer neuen Kontrollstruktur basiert: try/catch. Die Idee dahinter: Man definiert einen Block, umschlossen von try, in dem mit dem Auftreten von Laufzeitfehlern gerechnet wird. Um diese selbst zu erzeugen, wird die Anweisung throw verwen- det. Der so ausgelöste Fehler muss nun noch zentral abgefangen und verarbeitet werden, 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 Skript leichter wartbar, lesbarer und letztendlich stabiler. Die hier gezeigten Trivialbei- spiele wären freilich einfacher zu schreiben, aber in der Praxis sind 5.000 Zeilen für ein Skript durchaus üblich, und dann ist die Suche nach einem Fehler nicht mehr trivial, auch für Profis nicht. Fehlerbehandlung mit try/catch verwenden Das folgende Skript nutzt die Kombination aus try/catch und throw, um die besprochene 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)) { return true; } else { return false; 178
  • 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, in dem 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 der Fehler mit throw (engl. werfen). Interessanterweise sind die Fehler in PHP5, die ausgelöst werden, keine Konstanten mehr, sondern Klassen. Da Klassen erst am nä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 oder mehrere catch-Zweige stehen. Jeder Zweig prüft nun, welche Art von Fehler hier ankommt. Im Beispiel wurde dazu nur die Basisklasse Exception verwendet, die PHP5 bereitstellt: catch (Exception $ex) 179
  • Programmieren Die Variable $ex, die hier entsteht, erlaubt den Zugriff auf das Objekt, das new erzeugt 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 vermittelten Grundlagen der objektorientierten Programmierung erlauben, tiefer in die Geheimnisse 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 bereits beschriebenen »konventionellen« Techniken verwiesen werden. 4.6 Kontrollfragen 1. Schreiben Sie ein Skript, das beliebige Zahlen zwischen 0 und 100 auf ganze Zehner rundet. 2. Können mit switch-Anweisungen auch Vergleiche mit beliebigen Booleschen Ausdrü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
  • Daten mit Arrays verarbeiten 5
  • Daten mit Arrays verarbeiten Sehr praktisch ist das Zusammenfassen ähnlicher Daten unter einem gemeinsa- men Namen. Solche Gebilde werden als Arrays (Datenfelder) bezeichnet. PHP bietet 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 die Daten als Arrays aus. Mit Arrays kann man dann sehr flexibel umgehen. Typische Funktionen, 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 kann da nicht schaden. Die folgenden drei Abschnitte entsprechen deshalb in etwa dem Lebenszyklus 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 könnte man eine Liste von Ländercodes erstellen: í de í en í ca 182
  • Arrays erstellen und befüllen í fr í it í es Will man damit in einem Programm umgehen, wäre die Anlage immer neuer Variablen wenig hilfreich: $lang1 = "de" $lang2 = "en" Stattdessen schreibt man alle Werte in eine Array-Variable und greift über einen so genannten Index auf die Inhalte zu. Die Aussage »ähnliche Daten« ist übrigens in PHP nicht ganz ernst zu nehmen. Mangels strenger Typkontrolle kann man beliebige Daten in einem 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ünf Werte enthält. Im Gegensatz zu normalen Variablen überschreiben sich die Zuweisungen nicht gegenseitig. Das Geheimnis liegt in den eckigen Klammern. Intern passiert ein klein wenig mehr, denn PHP muss die Werte unterscheiden können. Dazu wird ein numerischer Index vergeben, also eine Zahl, mit der jeder einzelne Wert adressiert werden kann. Ohne weitere Angaben beginnt PHP mit dem Index 0 – man spricht dann von null-basierten Indizes – und zählt fortlaufend in ganzen Zahlen weiter. Auf diesem Weg kann man auch gezielt jeden Wert abrufen: 183
  • 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 beliebigen Arrayart 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 zu zä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 zur Verfü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 Der Index muss in PHP nicht fortlaufend sein. Sie können bei der Zuweisung »wild« Werte vergeben: 184
  • 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 spielt der 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öchsten vergebenen 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 und erlaubt dies jederzeit und in jeder Form: Listing 5.4: arrayindexhash.php – Sprechende Indizes verwenden $lang['de'] = "Deutschland"; $lang['en'] = "England"; $lang['es'] = "Spanien"; $lang['fr'] = "Frankreich"; $lang['ir'] = "Italien"; echo "Das Land mit dem Code 'es' heißt: {$lang['es']}"; 185
  • 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 Grenzen gesetzt, 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 oder zusä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; dies ist jedoch nur auf einen Trick zurückzuführen. Ist eine Konstante (was de darstellt) nicht definiert, wird diese in eine Zeichenkette umgewan- delt, was dann vermutlich dem gewünschten Effekt entspricht. Ist die Konstante jedoch zufällig vorhanden, wird der falsche Schlüssel verwen- det. Entspricht der Wert einem Schlüsselwort aus PHP, wird ein Fehler erzeugt. Arraywerte schneller zuweisen Die Zuweisung von Werten, die nicht automatisch generiert werden, kann in der beschriebenen Form sehr mühselig sein. Deshalb gibt es ein spezielles Schlüssel- wort, das dies einfacher erledigt. Daraus entsteht dasselbe Array wie bei der bereits gezeigten 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', 17 => 'es', 3 => 'fr', 11 => 'it'); echo "Index Nr. 11 enthält den Wert: {$lang[11]}"; 186
  • 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, hier gegenüber der ersten Variante Zeilen zu sparen. Kompakter, schlecht lesbarer Code ist immer eine dumme Idee. Die obige Schreibweise ist leichter zu erfassen und bei großer Datenzahl schneller geschrieben. Weniger Zeichen zu schreiben heißt immer auch, die Fehlerquote zu verringern. 5.3 Arrays manipulieren Nachdem die grundsätzlichen Wege, Daten in ein Array zu bekommen, keine Hürde mehr darstellen, soll mit den Werten gearbeitet werden. Werte entfernen Gelegentlich stört ein Wert – er muss entfernt werden. Ebenso wie eine einfache Variable 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", 'es' => "Spanien", 'fr' => "Frankreich", 'ir' => "Italien"); unset($lang['en']); echo "Das Land mit dem Code 'en' heißt: {$lang['en']}"; 187
  • Daten mit Arrays verarbeiten Das Beispiel führt zu einer Fehlermeldung: Abbildung 5.1: Der Fehlerfall zeigte den Erfolg der Aktion, das Element wurde aus dem Array entfernt Der fehlende Wert kann jederzeit mit der am Anfang beschriebenen Syntax wieder zugewiesen werden. Der Arrayzeiger PHP führt intern einen Zeiger für Arrays, der die gezielte Auswahl von Elementen erlaubt. Dies ist eine bereits mit PHP3 eingeführte Technik, die aber nichts an ihrer Attraktivität verloren hat. Viele der neuen Arrayfunktionen, von denen auch mit PHP5 wieder weitere hinzukamen, nutzen diese Zeiger nicht. Einfache Arrays lassen sich mit den alten Funktionen dennoch gut verarbeiten. Zur Auswahl steht eine 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 false zurü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, ansonsten den betreffenden Elementwert. í key($array); Die Funktion gibt den Schlüssel oder Index des aktuellen Elements zurück. 188
  • Arrays manipulieren Einige Beispiele, die diese Funktionen nutzen, finden Sie im Abschnitt 5.4 »Arrays ausgeben« ab Seite 193. Darüber hinaus ist der Einsatz immer dann interessant, wenn die Indizes nicht sequenziell vergeben wurden und einfache Zählschleifen deshalb nicht in Frage kommen. Für das zuletzt bereits verwendete assoziative Array 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 Definition erfolgt: Abbildung 5.2: Sequenzielle Ausgabe eines Arrays mit Zeigerfunktionen Das Beispiel sieht toll aus und funktioniert auch. Es hat jedoch eine böse Falle, die nicht offensichtlich ist. Sehen Sie sich zum Vergleich das folgende Array an: $lang = array('de' => "Deutschland", 'en' => "England", 'es' => "0", '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: 189
  • Daten mit Arrays verarbeiten Abbildung 5.3: Sequenzielle Ausgabe mit Problemen Was ist passiert? PHPs Typlosigkeit hat hier einen Streich gespielt. Die Funktion next gibt nämlich nicht true oder false, sondern den Elementwert oder false zurück. Der Elementwert ist beim dritten Element jedoch »0«. Da die Prüfung mit 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. Die Korrektur 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 dem Operator !== wird auch der ursprüngliche Typ geprüft und der ist auf der rechten Seite Boolesch, auf der linken jedoch nicht. Nun kann es sein, dass man den Abbruch durchaus will. Dann setzt man als Wert des Arrays nicht eine Zeichen- kette ein, sondern den passenden Vergleichswert, TRUE oder FALSE. Folgendes Array bricht wieder ab, diesmal ist es jedoch gewollt und logisch: $lang = array('de' => "Deutschland", 'en' => "England", 'es' => FALSE, 'fr' => "Frankreich", 'it' => "Italien"); Auch der Zugriff auf key ist tückisch. Beginnt das Array mit dem Index 0 – das ist der Standardfall – gibt key eben zuerst 0 zurück. Durchläuft man mit einer Schleife 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. 190
  • Arrays manipulieren Arrayfunktionen Einige der ersten Arrayfunktionen, mit denen Sie praktisch Bekanntschaft machen, werden die Sortierfunktionen sein. Sortiert wird in der Programmierung immer wieder und in allen Varianten. PHP verfügt hier über eine inflationäre Funktionssammlung. Die einfachste Sortierfunktion heißt sort. Sie sortiert alphanumerisch aufwärts, das heißt, zuerst kommen die Zahlen, dann Satzzeichen, dann Buchstaben. Dies entspricht 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: Abbildung 5.4: Erfolgreiche Sortierung eines einfachen Arrays Bei assoziativen Arrays ist das nicht ganz so einfach. Das folgende Beispiel zeigt einen interessanten Effekt: $lang =array('de' => "Deutschland", 'en' => "England", 'es' => "Spanien", 'fr' => "Frankreich", 191
  • 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: Abbildung 5.5: Sortierung ohne Schlüssel: sort ist sehr primitiv Für solche Fälle kennt PHP eine spezielle Sortierung: asort. Denn sort sortiert nur 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: Abbildung 5.6: Korrekte Sortierung, auch mit Indizes Eine Liste aller elementaren Sortierfunktionen finden Sie am Ende des Kapitels in der Kurzreferenz. 192
  • 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 von Daten richtig effizient. Alle Funktionen vorzustellen, führt hier zu weit, weil allein die schiere Masse eine ausführliche Darstellung nur auf breitem Raum erlaubt. Die Referenz am Ende des Kapitels bietet eine ausreichende Hilfestellung bei der Suche 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 Anzahl Elemente eines indizierten Arrays ausgeben möchte. Mit for tun sich assoziative und verschachtelte Arrays recht schwer, denn diese Arrays kennen die nötigen numerischen 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]; echo "Das Kürzel {$larray[0]} steht für {$larray[1]}<br>"; } Das eine Zählschleife einen Start- und einen Endwert benötigt, wird hier die Funktion count benutzt, um den Endwert zu ermitteln. Als Startwert wird 0 einge- setzt. Die Formel $Laufwert < $Endwert ist typisch, da der Endwert durch die 193
  • 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. Abbildung 5.7: Ausgabe eines Arrays mit for Beliebige Arrays mit foreach durchlaufen Das Problem der Indizes hat foreach nicht. Wie der Name bereits suggeriert, wird hier jedes Element eines Arrays durchlaufen, unabhängig von der Anzahl und Art der 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 der jeweils aktuelle Wert übergeben wird. Es ist dann Sache des Schleifencodes, dieses Element 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 und die Angabe der Kopfparameter für foreach: Listing 5.16: arrayoutforeachas.php – Ausgabe eines assoziativen Arrays $lang['de'] = "Deutschland"; $lang['en'] = "England"; $lang['es'] = "Spanien"; $lang['fr'] = "Frankreich"; 194
  • 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. In den 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-Versionen zur Arraybehandlung im Einsatz. Arbeitet man mit einfachen Arrays, bieten sie eine 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 auch auf kompliziertere Konstrukte ausdehnen kann. Der Ablauf dieses Beispiels sieht folgendermaß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 4. Die Schleife wird durchlaufen 5. Der Vorgang beginnt wieder bei 2. Wenn each nichts mehr findet, gibt es FALSE zurück. Darauf reagiert auch list mit FALSE und der gesamte Ausdruck wird FALSE, was while zum Abbruch der Schleife veranlasst. Die Ausgabe entspricht Abbildung 5.7. 195
  • Daten mit Arrays verarbeiten Array mit array_walk durchlaufen Eine der wichtigsten Arrayfunktionen ist array_walk. Die Funktion ruft für jedes Arrayelement eine so genannte Rückruffunktion auf. Damit kann man praktische alles mit einem Array anstellen, was erforderlich ist. Das folgende Beispiel zeigt, wie ein 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 die benutzerdefinierte Funktion CountryList übergeben werden. Die weitere Verar- beitung ist nur ein Beispiel. Es handelt sich hier um ein Array, dessen Elemente wiederum Arrays sind. Da list zur Übertragung der Elemente in Variablen benutzt wird, sind numerische Indizes erforderlich. Das folgende Array erfüllt diese Forderungen: array('de', 'Deutschland') Der interne Aufbau ist: [0] => 'de' [1] => 'Deutschland' Für derartige Ausgaben ist übrigens die Funktion print_r sehr hilfreich. Fehlersuche mit print_r Zur Fehlersuche braucht man oft einen schnellen Überblick über den aktuellen Aufbau eines Arrays. Die Funktion print_r erledigt das. Für die reine Program- 196
  • Arrays ausgeben mierung ist der Einsatz kaum zu gebrauchen, die Ausgabe ist eher technischer Natur 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. Abbildung 5.8: Ausgabe eines Arrays mit print_r zur Inhaltsanalyse 197
  • 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 neue Funktion var_dump verwiesen, die sehr brauchbare Darstellungen komplexer Arrays 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_uassoc Ermittelt die Unterschiede mehrerer Arrays anhand der array_udiff_assoc 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. 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 198
  • Referenz der Arrayfunktionen Funktion Bedeutung 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. array_splice Entfernt einen Teil eines Arrays und ersetzt ihn durch ein anderes Array. Tabelle 5.1: Arrayfunktionen in PHP5, alphabetisch sortiert (Forts.) 199
  • Daten mit Arrays verarbeiten Funktion Bedeutung 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. list Übernimmt alle Unterelemente des aktuellen Elements in Variablen. Tabelle 5.1: Arrayfunktionen in PHP5, alphabetisch sortiert (Forts.) 200
  • Referenz der Arrayfunktionen Funktion Bedeutung 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. Tabelle 5.1: Arrayfunktionen in PHP5, alphabetisch sortiert (Forts.) 201
  • 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ätzliche Informationen gespeichert werden. Tipp: Nutzen Sie verschachtelte assoziative Arrays dafür. 202
  • Objektorientierte Programmierung 6
  • Objektorientierte Programmierung PHP5 hat in seiner Entwicklung in Bezug auf die objektorientierte Programmierung einen großen Sprung vorwärts gemacht. Auch wenn PHP keine im klassischen Sinne objektorientierte Sprache ist, sind erstmals professionelle Softwareentwick- lungstechniken in gewissem Umfang möglich. Bei den Programmiersprachen gibt es verschiedene Modelle, wie Syntax und Semantik aufgebaut sind. Alle Sprachen produzieren letztlich Code, der von einem Prozessor verarbeitet werden muss. Prozessoren sind so genannte Von-Neu- mann-Maschinen, die Befehlsinstruktionen immer sequenziell verarbeiten, wobei sie 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, dass primä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 entstanden viele 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 Daten verarbeitende 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 Sprachen waren Interpretersprachen. Ein entsprechendes Programm übersetzte Befehl für Befehl in Maschinencode. Weil dies in Schleifen uneffektiv ist – jeder Befehl wird wieder und wieder übersetzt – entstanden so genannte Compiler. Diese übertragen in einem Lauf erst alle Codes in die Maschinensprache und das Betriebssystem lässt diesen Code dann ablaufen. Der Vorgang ist freilich aufwändiger und kom- plexer und war deshalb immer »richtigen« Programmiersprachen vorbehalten, die mit umfangreichen Entwicklungsumgebungen und vielen Hilfswerkzeugen ausge- stattet waren. Die ersten BASIC-Versionen in Homecomputern der 80er Jahre waren 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 eine stärkere Kontrolle des Codes durch eine so genannte Laufzeitschicht und macht 204
  • Warum objektorientiert programmieren? die Sprachen systemunabhängiger. Aus den Interpretersprachen entwickelten sich die 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 die typischen Vertreter VBScript, JavaScript, Perl und auch die ersten Versionen von PHP. PHP ist in der Version 5, wie inzwischen auch Perl, ein Zwitter. Denn zum einen ist es eine Skriptsprache, die per Interpreter verarbeitet wird. Sie ist prinzipiell imperativ strukturiert. Objektorientierte Sprachelemente sind vorhanden, deren Verwendung wird aber nicht erzwungen, wie das bei »echten« objektorientierten Sprachen der Fall ist. Die Verarbeitung basiert auf so genannten Token. Der Inter- preter zerlegt den Code also erst in einen eigenen Zwischencode – quasi ein dem Kompilieren ähnelnder Vorgang – und führt diesen Code dann interpretativ aus. Das ist effektiver als bei einem reinen Interpreter und vermeidet die Komplexität eines 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 der Sprachen wichtig zu wissen ist. 6.1 Warum objektorientiert programmieren? Die Behandlung von Objekten wurde in PHP5 grundlegend überarbeitet. Dies war 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 von PHP waren die Bibliothekenentwickler schnell an die Grenzen des bisherigen Modells gestoßen. Unabhängig davon profitieren auch kleine Projekte von objektorientierter Pro- grammierung, weil sie Code lesbarer und wartbarer macht. Nachteilig ist der etwas höhere Schreibaufwand, der Anfänger oft davon abhält, sich damit auseinander zu setzen. Prinzipien und Paradigmen Bei der objektorientierten Programmierung werden reale Sachverhalte in eine Modellumgebung übertragen. Es ist also letztlich eine Technik zur Abstraktion. 205
  • 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 nicht unbedingt trivial, hilft aber durch seine Strenge, besseren1 Code zu entwickeln. Die folgende Darstellung ist so weit vereinfacht, wie es zum Verständnis von PHP5 erforderlich ist. Objektorientierte Techniken, die PHP5 nicht unterstü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, die fü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, die ein Objekt ausführen kann bzw. anbietet, abgebildet durch Methoden. Objekte mit gleichen Eigenschaften und gleichen Methoden werden zu Klassen zusammengefasst. Ein Objekt ist also Exemplar einer Klasse. Anders gedeutet ist eine Klasse eine Vorlage, nach der gleichgeartete Objekte erzeugt werden. Die Ableitung eines Objekts aus einer Klasse wird in der Literatur häufig als Instanziie- ren bezeichnet. Auch wenn dies auf einer falschen Übersetzung des englischen Begriffs »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 einige davon oder ergänzen weitere. Aus einfachen Klassen entstehen so immer komple- xere Gebilde. Das hat Vorteile bei der Wiederverwendbarkeit und Wartbarkeit von Code. Ändert man Details einer Basisklasse, ändern sich die abgeleiteten Klassen automatisch mit, was Eingriffe in Code oft drastisch reduziert – entsprechend cle- ver entworfene Modelle vorausgesetzt. Letzteres ist übrigens der Grund, warum Anfänger davon kaum profitieren. Ohne lange Erfahrung werden Modelle falsch oder unbrauchbar entworfen und statt des erhofften Designvorteils entsteht Chaos und Konfusion. 1 Besser war im letzten Kapitel bereits als: »Schneller, Wartbarer, Lesbarer« definiert worden. 206
  • 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 es die Klassenhierarchie vorschreibt. Da Objekte alles enthalten, was Code zum Ablauf 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 ist ein Paradigma der objektorientierten Sprachen – sie erzwingen eine derartige Pro- grammierweise. Das heißt in der Praxis, dass auch die einfachste Ausgabe (wie mit echo) 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 eine Klasse definiert werden. Dieser Abschnitt führt in die grundlegenden Prinzipien der 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 <?php class FontElement { public $face = 'Verdana'; public $size = '3'; public $color = '#9999FF'; function Output($text) { printf('<font face="%s" size="%s" color="%s">%s</font>', $this->face, $this->size, $this->color, $text); } } ?> 207
  • 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ältig und flexibel formatiert, ohne dass HTML-Code geschrieben werden soll. Sie enthält drei Eigenschaften: $face, $size und $color, die mit Standardwerten belegt 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 Klasse erlaubt 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 und dazu wird ihm der Name der Klasse mitgeteilt (FontElement). Das neue Objekt kann nun sofort benutzt werden. Auf Eigenschaften und Methoden wird über die Verweissyntax -> zugegriffen. Freilich ist hier noch kein Vorteil gegenüber einem einfachen Funktionsaufruf zu erkennen, wie er am Tag 7 behandelt wurde. Tatsächlich bietet das objektorien- tierte Modell viel mehr. Eine Klasse erweitern 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 Projekt im 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. 208
  • Syntax der Klassen Bei der Vorüberlegung, welche Struktur entstehen soll, ist leicht zu erkennen, dass das <font>-Tag keine brauchbare Basis darstellt. Besser wäre es, erstmal über ein allgemeines »HTML-Element« zu verfügen und daraus speziellere Elemente abzuleiten. Ein solches Basiselement könnte nun folgendermaßen aussehen: Listing 6.3: ClassIntro2.php – Eine flexible Basisklasse für HTML-Elemente <?php class 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('/>'); } else { printf('>%s</%s>', $text, $this->name); } } } 209
  • 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 genannten Konstruktors, 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. Sie haben bereits gesehen, wie das Objekt durch das Setzen der Eigenschaften nutzbar wird. Oft wird jedoch der undefinierte Zustand nie benötigt, sondern das Objekt soll von vornherein bestimmte Grundeigenschaften enthalten. Der Vorgang des Erzeugens und Definierens kann daher zusammengefasst werden. Das passiert 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 210
  • 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 Skripts oder bis es gezielt zerstört, also auf NULL gesetzt oder mit unset vernichtet wird. Normalerweise ergeben sich daraus keine Konsequenzen, PHP kümmert sich um das Aufräumen des belegten Speichers. Es gibt jedoch Anwendungsfälle, in denen externe Programme an der Kommunikation beteiligt sind, Datenbanken oder Dateien beispielsweise. Nun wäre es fatal, wenn ein Objekt eine Verbindung zur Datenbank 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 und schwer nachvollziehbar auftreten. Um das Verhalten am Ende der Existenz eines Objekts zu kontrollieren, werden Destruktoren eingesetzt. Sie werden unmittelbar vor 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 der Hierarchie kann einen eigenen Konstruktor2 haben. Wenn nun ein Objekt erzeugt wird, könnten sich die Effekte der Konstruktoren überlagern. Deshalb führt PHP5 nur den letzten Konstruktor aus. Hätte im letzten Beispiel auch die Klasse Element einen Konstruktor, so wäre dieser beim Aufruf von FontElement nicht ausgeführt worden. Es bleibt dem Entwickler überlassen, den Aufruf des Konstruktors der Elternklasse explizit zu erzwingen: function __construct() { parent::__construct(); } Das reservierte Wort parent verweist auf die Klasse, von der mit extends geerbt wurde. Der Konstruktor wird über seinen Namen aufgerufen. Hinter parent darf nur mit dem statischen Verweisoperator :: gearbeitet werden, weil zum Zeitpunkt des 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. 211
  • 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 Angabe erfolgt 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 Objekt zusätzlich individuelle Eigenschaften zu verpassen. Statische Mitglieder und Konstanten Statische Mitglieder von Klassen existieren nicht im Kontext des Objekts, sondern der Klasse. Damit teilen sich alle Objekte einer Klasse diese Mitglieder, ganz gleich 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, wie beispielsweise 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 <?php class ElementCounter { static $counter; public function __construct() { ElementCounter::$counter++; } public function GetObjectNumber() { return ElementCounter::$counter; 212
  • 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 den Klassenkontext überführt. Diese Variable existiert für alle Objekte nur einmal. Wird nun ein neues Objekt erzeugt, wird der Wert im Konstruktor um Eins erhöht. Beachten Sie hier, dass der Verweis mit $this nicht gelingen kann, weil es kein Objekt- sondern ein Klassenverweis ist, der benötigt wird. Für letzteren wird der Klassenname benutzt, gefolgt vom statischen Verweisoperator :: und dem voll- ständigen Namen der Variablen, inklusive dem $-Zeichen. Das gilt sowohl für Zugriffe innerhalb der Klasse, wie im Beispiel gezeigt, als auch für externe. Dabei gelingen externe Zugriffe nur, wenn kein oder der Modifizierer public verwendet wird. Ein sehr typisches Designelement in der objektorientierten Programmierung sind so genannte Singleton-Klassen. Dies sind Klassen, die nur ein einziges Objekt erzeugen 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 der Wiederverwendbarkeit stören. Der Ausweg sind Singleton-Klassen. Um dies zu sichern, 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 new referenziert dann immer wieder dasselbe Objekt. Listing 6.6: ClassSingleton.php – Volle Kontrolle über die Objekterzeugung <?php class Singleton { static private $instance = false; private $text = 'Keine Nachricht im Objekt'; private function __construct() {} static function instance() { if(!Singleton::$instance) 213
  • 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 />' ); ?> Das Beispiel nutzt in jeder Klasse, die Singleton verwendet, immer wieder dasselbe Objekt, weil eine erneute Erzeugung im Rahmen eines einfachen Methodenauf- rufs völlig sinnlos wäre – man hätte am Ende nur eine Anzahl verwaister Objekte im Speicher. In der Praxis wird diese Technik benutzt, um Ressourcen schonend 214
  • Syntax der Klassen zu programmieren. Verbindungen zu Ressourcen, die nur einmalig vorhanden sind, 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 leicht Namenskonflikte auftraten. Da sich Konstanten von Objekt zu Objekt nicht ändern, verhalten sie sich wie statische Mitglieder und werden auch genau wie diese verwendet. Der einzige Unterschied besteht darin, dass sich der Inhalt zur Laufzeit nicht verändern lässt. Im Gegensatz zu den globalen Konstanten, die mit define erzeugt werden, erfolgt die Vereinbarung in Klassen mit dem Schlüsselwort const: const WIDTH = 800; Da Konstanten zwangsläufig in allen Instanzen identisch sind, verhalten sie sich wie statische Mitglieder. Der einzige Unterschied besteht darin, dass sie nicht geschrieben werden können – außer bei der Definition. Diese Definition erfolgt auf 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 der objektorientierten Programmierung ist das bei weitem nicht genug. PHP5 bietet hier gleich drei neue Schlüsselwörter für den Variablenschutz: í public Der Standardwert hat nun sein eigenes Schlüsselwort. Derart deklarierte Vari- ablen oder Methoden sind im gesamten Skript sichtbar. Aus Gründen der Abwä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. 215
  • Objektorientierte Programmierung í protected Dieses Schlüsselwort macht eine Variable oder Methode in den eigenen und abgeleiteten Klassen sichtbar, aber nicht darüber hinaus (mehr als private und weniger als public als Kurzformel). Wenn Sie sich überlegen, wie die Deklaration konkret erfolgen soll, versuchen Sie zuerst 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önnte man 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 Dass sich Klassen leicht vererben lassen, wurde anhand des Schlüsselworts extends bereits gezeigt. Manchmal soll dies aber nicht so sein, entweder für eine Klasse als solche oder auch nur für einzelne Methoden. Denn manche Methoden sind für die Funktion der Objekte von elementarer Bedeutung. Gelingt der Schutz mit 216
  • 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. Von einer 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 Benutzer explizit dazu aufgefordert, hier eigenen Code zu schreiben. Praktisch ist dies das Gegenteil zu final – statt dem ausdrücklichen Verbot folgt nun das ausdrückliche Gebot. Der Schreiber der Klassen gibt damit Struktur, Namen und Aufbau vor, nicht jedoch die konkrete Implementierung, weil dies möglicherweise von der Anwendung abhängt. Eine Ableitung von Objekten von abstrakten Klassen ist nicht möglich. Es muss deshalb 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 der Methoden 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 <?php abstract class Senior { protected $text = 'Hallo Senior!'; function GetText() { return $this->text; } } class Junior extends Senior { protected $text = 'Hallo Junior!'; } $obj = new Junior(); echo $obj->GetText(); ?> 217
  • Objektorientierte Programmierung Um den Benutzer der Klasse zu zwingen, auch die Methode GetText selbst zu schreiben, 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 hier nicht stimmt. Der erste Fall tritt auf, wenn eine abstrakte Methode Code enthält: Abbildung 6.1: Es wurde versucht, eine abstrakte Methode zu definieren Der zweite Fall tritt auf, wenn vergessen wurde, eine abstrakte Methode in einer abgeleiteten Klasse zu implementieren: Abbildung 6.2: Eine abstrakte Methode wurde nicht implementiert An dieser Stelle sei bereits auf Schnittstellen – Interfaces genannt – verwiesen die eine ähnliche Rolle wie abstrakte Klassen übernehmen. Mehr dazu finden Sie im Abschnitt 6.3 »Schnittstellen zur Außenwelt«. Klonen erlaubt: clone und die Methode __clone() Klonen ist ein typischer Vorgang in der objektorientierten Welt. Bislang wurde in PHP immer eine vollständige Kopie eines Objekts übergeben, wenn die Zuwei- sung an eine andere Variable erfolgte. Damit sind natürlich auch Verweise, die das ursprüngliche Objekt hatte, Teil der Kopie. Das kann gewollt sein, meist ist es jedoch so, dass man eher mit einem einzigen Verweis, beispielsweise auf eine Datenbank oder Datei, arbeiten will, unabhängig von der Anzahl der Objekte. Die folgende Version erlaubt mehr Kontrolle über den Kopiervorgang: $clone = clone $object; 218
  • Syntax der Klassen Dies entspricht dem Aufruf der Methode __clone() des Quellobjekts. Sie können nun diese Methode (mit exakt diesem Namen) anlegen und den Kopiervorgang kontrollieren. Oder Sie können den internen Mechanismus nutzen, der alle Eigenschaften 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 Basisobjekt verweisen: 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 wirkt sich in $o2 nicht mehr aus: ID: 1 Das Verhalten der Klonierung kann mit __clone() beeinflusst werden. Diese Methode existiert intern und kopiert alle Eigenschaften. Man kann sie jedoch überschreiben und dann entscheiden, welche Eigenschaften zu kopieren sind. 219
  • 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 Eigenschaften zu beeinflussen. Der in frühen Betaversionen kolportierte Zugriff über $that auf das zu klonende Objekt war seit der RC1 nicht mehr verfügbar. Abgesehen von der 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 Code zu schreiben. Schnittstellen (Interfaces) sind eine sehr gute Methode dafür. Sie sind neu in PHP5 eingeführt. 220
  • Schnittstellen zur Außenwelt Schnittstellen (Interfaces) Schnittstellen bieten eine gute Möglichkeit, die Verwendung eigener Klassen zu kontrollieren. Ähnlich wie abstrakte Klassen bieten Interfaces nur eine Definition der Struktur an, sie enthalten jedoch keinen Code. Dem potenziellen Nutzer einer Bibliothek 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, dass Methoden oder Eigenschaften öffentlich sind, folgt nicht zwingend, dass diese zur Implementierung geeignet sind. Die Information darüber, was wirklich »öffent- lich« im Sinne der freien Verwendung ist, definiert eine Schnittstelle. Der Name ist Programm – es handelt sich um eine Vereinbarungsschnittstelle zwischen dem ursprünglichen Entwickler der Klasse und dem Entwickler, der die Klasse später verwendet. 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 Schnittstellen meist 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 der geschweiften Klammern. Bei der Implementierung wird wie bei der Klassenvererbung vorgegangen, anstatt extends wird jedoch das Schlüsselwort implements verwendet. Herkunft und Mitgliedschaft Beim Umgang mit komplexen Klassenhierarchien ist es oft notwendig, bei einem Objekt zu ermitteln, von welcher Klasse es abstammt. Im Beispiel der HTML-Ele- ment-Klassen lässt sich leicht eine solche Hierarchie entwickeln: 221
  • Objektorientierte Programmierung í Element í StylableElement í ContainerElement í FontElement Die oberste Ebene definiert nur Elemente als solche, beispielsweise mit den Eigenschaften 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 eine Erkennung sehr aufwändig, weil es Dutzende HTML-Elemente gibt, die in ver- schiedenen Stufen der Hierarchie stehen. Gleichzeitig stellt sich die Frage, was an diesem Element öffentlich verwendet werden kann. Die Vereinbarung darüber trifft man mit Hilfe einer Schnittstelle, beispielsweise IElement genannt. Listing 6.11: ClassInterface.php – Schnittstellen definieren und implementieren <?php interface 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'; } } $font = new FontElement(); if ($font instanceof IElement) { echo strtoupper($font->GetTagName()); 222
  • Schnittstellen zur Außenwelt echo " ist ein Element"; } ?> Das Beispiel definiert zuerst eine einfache Schnittstelle, die die Grundstruktur aller Elemente enthält. Anschließend wird die erste Ebene der Implementierung vorgenommen. Diese Klasse ist als abstrakt gekennzeichnet, weil ein »Element« ohne weitere Definition seiner Eigenschaften unsinnig ist. Der Benutzer wird also gezwungen, seine eigenen Elemente darauf aufbauend zu implementieren. Das letzte 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, was sicher 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 Basisklassen vereinfacht. Der Operator instanceof funktioniert freilich auch mit Klassen, er zeigt lediglich an, dass ein Objekt von einer bestimmten Klasse oder Schnittstelle abstammt, ohne Rücksicht auf die Vererbungskette. Alternativ kann die Funktion is_a verwendet werden, 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 treu geblieben. Dazu gehört neben anderen Merkmalen auch der Verzicht auf typi- sierte Variablen. PHP5 legt intern selbst fest, welchen Datentyp eine Variable annimmt oder eine Funktion zurückgibt. Es gibt zwar die bereits behandelten Umwandlungsfunktionen, letztlich besteht aber kein Typzwang, wie er in »richti- gen« Programmiersprachen üblich ist. 223
  • 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 Fehlersuche ungleich schwerer und oft erst mit Hilfe spezieller Testapplikationen zu bewerk- stelligen. An dieser Stelle fehlt dann ein Typzwang, der zumindest elementare Zuweisungs- 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 negativen Sinne. 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, gleichwohl aber nicht für eingebaute Typen verwendet werden darf. Einzig für eigene als Klasse definierte Typen besteht die Möglichkeit, bei der Angabe von Parametern einen »Typ-Hinweis« mitzugeben. Dies unterstützt die Nutzbarkeit 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 erstellen kann. 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 folgende Beispiel ermöglicht: Listing 6.12: TryCatchBasis.php – Die Leistungen der Exception-Klasse testen try { throw new Exception('Das ist ein provozierter Fehler', 12345); } catch (Exception $e) { 224
  • 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 die folgenden Methoden: í getMessage Die eigentliche Fehlermeldung, am Ort der Instanziierung übergeben. í getCode Der Fehlercode, frei wählbar und hilfreich, um weiterführende Informationen abzurufen. í getFile Die Datei, in der der Fehler auftrat. Das ist das aktuelle Skript oder eine mit include 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 zu halten. Abbildung 6.3: Ausgaben der Methoden der Basisklasse Excep- tion Listing 6.13: TryCatchExceptionClass.php – Eigene Exception-Implementierung <?php class DivideByZeroError extends Exception { const PREFIX = 'Fehler:'; const CODE = 65535; function __construct($message) { parent::__construct(self::PREFIX.$message, self::CODE); 225
  • 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 Basisklasse Exception 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 der Basisklasse 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 eine Zeichenkettenentsprechung der Klasse und verkürzt die Ausgabe der Meldung. Beachten Sie außerdem die Syntax des Aufrufs des Konstruktors der Basisklasse mit parent::. Die Anwendung ist einfach. Haben Sie im Code einen Fehler festgestellt, wird eine Instanz der Fehlerklasse mit new erzeugt und mittels throw ausgelöst: throw new DivideByZeroError ('&nbsp;Division durch Null'); 226
  • Schnittstellen zur Außenwelt Es ist jetzt Aufgabe des Hauptprogramms, den Block von Code mittels try zu umschließ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öst werden) 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 Fehlerklasse arbeiten: Abbildung 6.4: Spezifische Fehlermeldung, mittels try/catch verwaltet 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 Parameter werden der Name der versuchsweise aufgerufenen Funktion und ein Array mit den verwendeten Parametern übergeben. Theoretisch könnte man alle Methoden – bis auf den Konstruktor – hier bündeln. Praktisch dürfte es sich eher um Ausnahmen handeln, weil die exzessive Anwendung die Lesbarkeit beeinträchtigt. 227
  • 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() <?php class 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 ist jedoch, dass der Inhalt der so zusammengefassten Methoden überschaubar ist. Bei den hier zur Demonstration eingesetzten Einzeilern ist dies sicher ideal. Abbildung 6.5: Ausgabe des Skripts ClassCall.php 228
  • Schnittstellen zur Außenwelt Der universelle Eigenschaftenaufruf mit __get() und __set() In ganz engem Zusammenhang mit dem universellen Methodenaufruf steht die Bildung von Eigenschaften. Echte Eigenschaften gab es bislang in PHP nicht, die Bereitstellung von öffentlichen Variablen reicht dazu allein nicht aus. Eigenschaf- ten erlauben es nämlich, die Daten, die hinein geschrieben werden, bei der Zuweisung oder Rückgabe zu kontrollieren. Ebenso kann vor dem Abruf der Daten eingegriffen werden. Im Sinne einer sicheren Programmierung ist es nun mö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 – daran gehindert wird einen leeren Namen zu lesen. Diese Klasse ist quasi »gentleman- like« programmiert. Sie akzeptiert alles und liefert dennoch nur sinnvolle Werte zurück. Damit eventuelle Fehlermeldungen auch einen gesicherten Weg aus der Klasse 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 <?php class AgeException extends Exception { } class NameException extends Exception { } class Member { private $name; private $age; public function __construct() { $name = ''; $age = 0; } public function __get($property) 229
  • 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 { throw new NameException('Name hat falsches Format: ' . $value); } break; 230
  • 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 werden lediglich benutzt, um die Fehlermeldungen selbst mittels catch auseinander zuhalten. Entsprechend bescheiden sieht die Definition aus: class AgeException extends Exception { } Die eigentliche Klasse definiert für die Eigenschaften sowohl __get als auch __set. Mittels einer switch-Anweisung werden zwei Eigenschaften gebildet, name und age. 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 231
  • Objektorientierte Programmierung spezifische Ausnahme ausgelöst. Die fest programmierten Daten des Musterskripts funktionieren. Die folgende Zeile sorgt für die Ausgabe: echo "{$m1->Name} ist {$m1->Age} alt."; Abbildung 6.6: Ausgabe, wenn die Daten gültig waren 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 besteht diese aus einer schlichten Ausgabe der Fehlermeldung: echo $aex->getMessage(); Abbildung 6.7: Ausgabe, wenn die Daten fehlerhaft sind Auch wenn die Eigenschaftsmethoden __get und __set auf den ersten Blick unsinnig erscheinen, entfalten sie jedoch im Zusammenspiel mit anderen neuen OOP-Techniken eine hohe Leistungsfähigkeit. Dies unterstützt vor allem die sichere, saubere Programmierung und damit stabilere Software. 6.4 Analyse und Kontrolle von Objekten Der Analyse und Kontrolle von Objekten kommt vor allem bei der Fehlersuche eine große Bedeutung zu. Darüber hinaus kann man Bibliotheken universeller und sicherer aufbauen, wenn die entsprechenden Techniken zum Einsatz kom- men, die in diesem Abschnitt beschrieben werden. 232
  • 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 allem zur Fehlersuche ist die Angabe durchaus geeignet. Beachten Sie, dass es sich in allen Fällen um zwei Unterstriche vor und nach 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: Abbildung 6.8: Ausgabe der aktuellen Methode mit __METHOD__ 233
  • Objektorientierte Programmierung Zeichenkettenform: __toString() Generell gibt es von jedem Objekt eine Zeichenkettenform. PHP5 erstellt diese automatisch, wenn der Kontext es verlangt. Für die explizite Definition gibt es die Methode __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: Abbildung 6.9: Zeichenkettendarstellung eines Objekts 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 { public $x; public $y; public function SetX($x) { $this->x = $x; 234
  • 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. Abbildung 6.10: Kundenspezifische Zeichenkettenentsprechung Beachten Sie wieder, dass es sich bei der Methode __toString() um zwei Unterstriche vor dem Namen handelt. Abstammung von Klassen und Objekten Die Abstammung von Klassen und Objekten ist immer dann interessant, wenn die Erstellung dynamisch erfolgt (Factory-Klassen) oder fremder Code benutzt wird. Wovon ein Objekt abstammt Verschiedene Funktionen können in PHP benutzt werden, um die Abstammung eines Objekts von einer Klasse festzustellen. Das mag auf den ersten Blick trivial erscheinen, weil man ja beim Schreiben des Codes genau weiß, wovon ein Objekt abstammt. Bei großen Bibliotheken, fremdem Code oder komplexen Hierarchien 235
  • Objektorientierte Programmierung ist das nicht immer der Fall. Betrachten Sie zuerst ein triviales Beispiel, das das Schlü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; protected $y; public function SetX($x) { $this->x = $x; } public function SetY($y) 236
  • 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: Abbildung 6.11: Jedes Objekt ist immer eine Instanz auch der Basisklasse 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ährend instanceof erst mit PHP5 eingeführt wurde. Abstammungshierarchie von Objekten Um die Abstammungshierarchie von Objekten festzustellen, eignet sich die Funk- tion is_subclass_of. 237
  • 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'; } Im Beispiel ist der folgende Ausdruck wahr: if (is_subclass_of($o1, 'BaseClass')) Dagegen wäre der folgende Ausdruck falsch: 238
  • 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 am Ende des Kapitels. Vor allem bei der Fehlersuche und zur Analyse fremden Codes sind die Testmethoden interessant. Als Programmiermittel dürften sie sich weniger anbieten. 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 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. 239
  • Objektorientierte Programmierung Schlüsselwort Beschreibung 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 OOP-Funktionen 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. 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. 240
  • Referenz der OOP-Funktionen Funktion Beschreibung 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 Namens findet. Wenn man mit __get eine Eigenschaft defi- niert und mit __set nicht, erhält man eine »Nur-Lese«-Eigen- schaft. 241
  • Objektorientierte Programmierung Funktion Beschreibung __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. 6.6 Kontrollfragen 1. Schreiben Sie ein Skript, dass die Namen von Mitarbeitern in einer eigens dafür entwickelten 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 direkten Zugriffs auf öffentliche Eigenschaften, die mit public $name gekennzeichnet sind? 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? Wie nennt man dieses Entwurfsmuster? 242
  • Das Dateisystem entdecken 7
  • 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 und Verzeichnissen. Die Anwendung ist unkritisch, wenn man sich mit einigen grund- legenden Techniken angefreundet hat. Der Begriff Dateisystem wird in PHP übrigens etwas weiter gefasst und kann sich durchaus auf andere Server ausdehnen, die über HTTP oder FTP 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 Datei aufzurufen oder Verbindung zu einem Verzeichnis herzustellen. Intern handelt es sich 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ßen die im ersten und im vierten Schritt benötigten Funktionen typischerweise xxx_open und xxx_close, wobei xxx die spezifische Art der Ressource näher beschreibt. Bei den Dateifunktionen von PHP5 steht dafür meist »f« oder »file«. 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. 244
  • Dateizugriff organisieren Datei zu , kann es entweder zu Programmfehlern (»Zugriff verweigert«) oder zu langen Wartezeiten kommen. Dateien als Datenspeicher sind deshalb nur begrenzt einsatzfähig. Stoßen sie an ihre Grenzen, kommen Datenbanken ins Spiel, die fein granulierter sperren können (mal von SQLite abgesehen, dass Dateien zum Speichern nutzt). Der Zugriff über das Schema »Handle à Ressource« ist nicht allein auf Dateien beschränkt, auch entfernte Server sind über die Protokolle http:// oder ftp:// erreichbar. Das erweitert den Einsatzspielraum gewaltig, weil man sich über die eigentlichen Protokolle kaum Gedanken machen muss. Beim HTTP ist lediglich zu beachten, dass die Laufzeiten eines Skripts erheblich länger sein können, weil erst eine Verbindung aufgebaut werden muss. Das impliziert auch, dass diese Ver- bindung fehlschlagen kann, was zusätzliche Fehlerabfragen erfordert. Daneben ist auch zu beachten, dass meist nur lesend auf andere Server zugegriffen werden kann. Einsatzfälle Einsatzfälle für den Datei- und Verzeichniszugriff gibt es ungeheuer viele. Hier sollen 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- genden eine Übersicht über die Anwendung der wichtigsten Funktionen gegeben. 245
  • Das Dateisystem entdecken 7.2 Praktischer Dateizugriff Der Dateizugriff ist mit PHP relativ einfach. Eine breite Palette von Funktionen steht zur Verfügung, um alle erdenklichen Zugriffe zu erledigen. Dabei werden immer wieder einige Basistechniken benutzt, die Sie kennen sollten. Prinzipien des Dateizugriffs Es gibt mehrere Prinzipien beim Dateizugriff, die nicht nur für PHP gelten. Diese sollten Sie kennen, damit die Wirkungsweise der verschiedenen Funktionen trans- parent wird. Dateizugriff Grundsätzlich kann man zwei Gruppen von Funktionen unterscheiden, die PHP beim 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 Datei erstellt, das Handle. Intern ist dies eine Ressource. Andere Funktionen benutzten dann nur noch dieses Handle – gespeichert in einer Variablen – zum flexiblen Zugriff. Am Ende der Zugriffe wird die Verbindung geschlossen, die Datei freige- geben und das Handle weggeworfen. Vor allem bei wiederholten Zugriffen in Schleifen ist dieses Verfahren einfacher und schneller. Universeller Quellzugriff PHP beherrscht das Konzept des Wrapper-Zugriffs (manchmal wird das auch als Moniker 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: 246
  • Praktischer Dateizugriff wrapper://pfad/pfad/pfad/datei.extension Der Standard-Wrapper für Dateien heißt file:///. Er kann entfallen, weil PHP ohne Angabe davon ausgeht, dass es sich um eine lokale Datei handelt. Gültige lokale Pfadangaben sind beispielsweise: í /pfad/pfad2/datei.ext Dies kennzeichnet einen absoluten Pfad auf dem aktuellen Laufwerk. í pfadrelativ/datei.ext Dies kennzeichnet einen relativen Pfad, berechnet vom Ausführungsort des Skripts an. í C:pfaddatei.ext oder C:/pfad/datei.ext Pfad mit Laufwerk unter Windows. Die Art der Schrägstriche spielt in PHP keine Rolle. í sambasrvsharedpathlocaldatei.ext Ein Pfad zu einem gemeinsamen Laufwerk auf einem Windows- oder Samba- Server í file:///pfad/datei.ext oder file://c|pfad/datei.ext Vollständige Angabe eines Pfades mit dem Wrapper. Beachten Sie, dass dieser Wrapper 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:// nutzt eine verschlüsselte Verbindung. PHP arbeitet mit dem Standard HTTP 1.0 und 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:kennwort@www.adresse.de í ftp:// und ftps:// Hier erfolgt der Zugriff über FTP auf einen remoten Server. ftps:// nutzt eine verschlüsselte Verbindung, wenn der Server dies unterstützt (sehr selten) und benötigt eine PHP-Version, die SSL unterstützt. PHP kann die Datei auf dem entfernten Server lesen und schreiben. 247
  • 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:kennwort@www.adresse.de í php:// PHP verwendet intern bestimmte Kanäle (so genannte Streams) für den Zugriff auf Datenquellen. Der php://-Wrapper gestattet den Zugriff darauf. Die folgende Tabelle zeigt die möglichen Kanäle: 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. í compress.zlib:// oder compress.bzip2:// Zugriff auf komprimierte Dateien, die beim Lesen automatisch entpackt werden. 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 í 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 Inhalt in Elemente zu trennen. Das gilt in der Regel nur für Dateiinhalte, die Text 248
  • Praktischer Dateizugriff umfassen, der mit Zeilenumbrüchen arbeitet. Binäre Dateiinhalte (das sind auch solche mit proprietären Formaten wie MS Word oder Excel) lassen sich damit nicht 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ürlich Textdateien – zeichenweise gelesen werden. Dazu wird ein so genannter Datei- zeiger eingesetzt. Der Dateizeiger zeigt auf ein konkretes Zeichen, das als nächstes gelesen bzw. nach dessen Position geschrieben wird. Typischerweise wird während der Abfrage von Zeichen in einer Schleife permanent überprüft, ob das Dateiende bereits erreicht wurde. Wichtige Konstanten PHP liefert einige wichtige Konstanten, die den Umgang mit Pfadangaben und Dateinamen 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_BRACE GLOB_ONLYDIR GLOB_MARK GLOB_NOSORT GLOB_NOCHECK GLOB_NOESCAPE Konstanten, die das Verhalten der glob-Funktion steuern. í PATHINFO_DIRNAME PATHINFO_BASENAME PATHINFO_EXTENSION Konstanten, die das Verhalten der pathinfo-Funktion steuern. 249
  • Das Dateisystem entdecken í FILE_USE_INCLUDE_PATH FILE_APPEND FILE_IGNORE_NEW_LINES FILE_SKIP_EMPTY_LINES Konstanten, 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 der Dateifunktionen programmiert werden. Die Nachrichten werden in einer Text- datei abgelegt. Sie sind dort zeilenweise angeordnet, das heißt, jede Nachricht steht 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> NEWS; } fclose($fh); } else 250
  • Praktischer Dateizugriff { echo "Datei nicht gefunden"; } Dieses Skript nutzt zuerst fopen, um das Datei-Handle zu erhalten. Angegeben werden muss der Pfad zur Datei und ein Parameter, der die Art des Zugriffs bestimmt (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 Zeile mit fgets gelesen, wobei die Funktion den Inhalt der Zeile zurückgibt. Sind keine Daten mehr vorhanden, wird FALSE zurückgegeben und die Schleife ist beendet. Anschließend ist der Zugriff mit fclose zu beenden. Abbildung 7.1: Daten aus einer Datei lesen und formatiert ausgeben 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 eine nä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 dabei der zweite, eine Zeichenkette, die folgende Bedeutung hat: 251
  • Das Dateisystem entdecken 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 Die einzelnen Dateisysteme gehen unterschiedlich mit dem Zeilenumbruch um. Intern ist dieser durch ein Sonderzeichen definiert. Dabei gilt folgende Regel: í n: Zeilenumbruch unter Unix í rn: Zeilenumbruch unter Windows í r: Zeilenumbruch unter MacOS PHP versucht möglichst flexibel damit umzugehen, damit unter Windows erstellte Dateien auch auf Linux laufen und umgekehrt. Da es dennoch Probleme mit anderen Applikationen geben kann, besteht die Möglichkeit ein »Transfer«-Flag zu definieren, das PHP zur fortlaufenden Konvertierung veranlasst. Das Flag t wird beim Öffnen dem Modus nachgestellt. Der Transfer funktioniert nur unter Windows und veranlasst PHP, Dateien mit Unix-Zeilenumbrüchen in solche mit Windows-Zeilenumbrüchen zu verwandeln. Damit das funktioniert, muss das t 252
  • 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 Zugriffe auf die Daten zu unterbleiben haben (rb, wb oder ab beispielsweise). Wenn Sie bezüglich der Flags unsicher sind, verwenden Sie immer die Kombination 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 die Datei (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 hier platziert. 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: dern" . "Cookie: color=redrn" ) ); $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
  • Das Dateisystem entdecken 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 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 Fall zeigt 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])); } echo '</pre>'; } Das Skript ermittelt zuerst den eigenen Namen aus der Servervariablen $_SERVER['PHP_SELF'] mit der Funktion basename. Dann wird die Funktion file benutzt, um den Inhalt der Datei in ein Array zu überführen. Sicherheitshalber 254
  • Praktischer Dateizugriff wird noch mit is_array geprüft, ob dies auch gelang. Dann wird die Schleife vom ersten bis zum letzten Arrayelement durchlaufen. Da Zeilennummern ausgegeben werden 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. Abbildung 7.2: Ausgabe einer Datei mit Zeilen- nummern Protokolldatei Das Schreiben einer Datei ist ebenso einfach wie das Lesen, entsprechende Zugriffsrechte vorausgesetzt. Eine Protokolldatei zu schreiben, ist ein guter Test für ein einfaches Skript. Benötigt werden der Zugriff auf eine Datei mit fopen und die Schreibfunktion fwrite. Außerdem sind noch einige Daten zu beschaffen, die geschrieben werden sollen: í Das aktuellen Datum und eine Zeitangabe (strftime) í Informationen über den Browser (Servervariablen aus $_SERVER): HTTP_USER_AGENT Ermittelt die Kennung des Browsers, der gerade das Skript ausführt HTTP_ACCEPT_LANGUAGE Ermittelt Informationen über die Spracheinstellungen des Browsers 255
  • Das Dateisystem entdecken REMOTE_ADDR Die 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' %sn", $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 nachfolgende Testzugriff 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 Zweck wäre. Eingesetzt wird die Funktion file_get_contents, die die Datei vollständig in eine Zeichenkette einliest. Auf die Umwandlung in ein Array zur zeilenweisen Ausgabe wird hier verzichtet, weil in der Ausgabe die <pre>-Tags für die Anzeige der enthaltenen Zeilenumbrüche sorgen. Abbildung 7.3: Testausgabe des Protokolls 256
  • Verzeichniszugriff 7.3 Verzeichniszugriff Der Verzeichniszugriff geht eng zusammen mit dem Dateizugriff. Die Funktionen lassen 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. Die Angabe 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. Das folgende 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 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 257
  • Das Dateisystem entdecken Schalter Funktion 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) Tabelle 7.3: Schalter für die Funktion glob (Forts.) Mehrere Schalter können über eine einfache Oder-Verknüpfung | kombiniert werden: 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, wenn der 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 der Klammer bestimmt wird. Dies kann eine Aufzählung aus Zeichen sein, bei- spielsweise: [aef] Steht für die Buchstaben »a« oder »e« oder »f«. [a-f] Steht für die Buchstaben »a«, »b«, »c«, »d«, »e« oder »f«. [0-9] Steht für die Zahlen 0 bis 9. 258
  • 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. Abbildung 7.4: Gefilterte Dateiliste mit der glob-Funktion Manchmal soll nur festgestellt werden, ob eine Datei einem bestimmten Muster entspricht. Dieselbe Syntax wie bei glob kann mit fnmatch verwendet werden. Um eine Dateiliste zu durchsuchen, wird das dir-Objekt bemüht und dann mit fnmatch untersucht. 259
  • Das Dateisystem entdecken Das Verzeichnisobjekt dir und verwandte Funktionen PHP kennt eine Klasse dir, aus der sich ein Verzeichnisobjekt erstellen lässt. Die prinzipielle Anwendung zeigt das folgende Skript. Dazu wird auch gleich die Funktion fnmatch definiert. Diese mit PHP 4.3 eingeführte Funktion steht leider auch mit PHP5 nicht unter Windows zur Verfügung, was unverständlich ist, weil die ebenso dem Unix-System entstammende Variante glob vorhanden ist. Um das Skript 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++) { if($pattern[$k] != "]") { array_push($letter_set, $pattern[$k]); } else break; } 260
  • 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(); } Betrachten Sie zuerst nur den unteren Teil, der die Klasse dir verwendet. Hier wird 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']; 261
  • 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 das aktuelle 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 die interne Liste der Dateien auf den Anfang gesetzt: $dir->rewind(); Dann wird in einer Schleife Eintrag für Eintrag gelesen: while ($file = $dir->read()) Abbildung 7.5: Gefilterte Dateiliste mit fnmatch 262
  • 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 den Dateinamen entsprechend dem angegebenen Suchmuster Zeichen für Zeichen und gibt einen Booleschen Wert zurück (siehe Abbildung 7.5). Zum Schluss sollen noch die Möglichkeiten, die dir bietet, auf einen Blick gezeigt werden: 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 Die Benutzung des Handles ist dann interessant, wenn – aus welchen Gründen auch immer – mit den anderen Verzeichnisfunktionen gearbeitet werden soll. Diese Funktionen sind nicht objektorientiert, sondern werden ebenso wie die Dateifunktionen benutzt. Eine Auflistung finden Sie in der Kurzreferenz am Ende des Kapitels. Rekursive Dateiliste Das folgende Beispiel erweitert die Ausgabe der Dateiliste auf eine beliebige Anzahl von Unterverzeichnissen. Die Technik der Rekursion bietet eine einfache Lösung dafür: 263
  • 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 tiefer Verschachtelung erheblich ist: $path = dirname(getcwd() . '/php5MuT'); Nun wird die rekursiv programmierte Funktion rglob vorgestellt. Sie gibt ein Array mit allen Dateien zurück, die dem Suchmuster entsprechen, wobei alle Dateien in allen untergeordneten Verzeichnissen mit eingeschlossen werden. Bevor die Details der rekursiven Funktion diskutiert werden, ist ein Blick auf die Ausgabe interessant. glob liefert komplette Dateipfade. Um diese zu trennen wird zuerst der vollständige Pfad mit realpath ermittelt, dann mit dirname der Pfad extrahiert. Für basename, genutzt zum Abtrennen des Dateinamens, reicht die Nennung des Pfades ohne Veränderungen. 264
  • 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 angegeben werden. Im Beispiel sieht dieses Muster folgendermaßen aus: {*.txt,a*.php} Dieses Muster betrifft alle Dateien mit der Endung »txt« und alle Dateien, die mit dem Buchstaben »a« beginnen und auf »php« enden. Nach der Erstellung der Dateiliste werden die untergeordneten Verzeichnisse gesucht: glob("$sDir/*", GLOB_ONLYDIR) Hier sorgt der Schalter GLOB_ONLYDIR für das gewünschte Resultat. Um die Dateien innerhalb des gefundenen Verzeichnisses zu lesen, erfolgt als erste Maßnahme innerhalb der foreach-Schleife der rekursive Aufruf. Die Ergebnisse werden dann mit array_merge an das bestehende Array angehängt. Damit leere Verzeichnisse nicht stören, wird noch eine Abfrage mit is_array davor geschaltet. Verzeichniszugriff mit Iteratoren Iteratoren durchlaufen Auflistungen, vorzugsweise mit foreach. Das Konzept wurde neu in PHP5 eingeführt und ist Teil der so genannten Standard PHP Library. Alle Iteratoren implementieren die Schnittstelle Iterator. Dies führt zu den 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- den Aufruf von next gefunden wurde, sonst FALSE. í rewind() Mit dieser Methode wird der Zeiger wieder an den Anfang gesetzt. 265
  • Das Dateisystem entdecken Einige Iterator-Klassen bieten eine Reihe weiterer Methoden an, die den Zugriff auf spezifischen Eigenschaften der Elemente der Auflistung ermöglichen. Verzeichnisse werden mit Instanzen des DirectoryIterators verarbeitet. Folgende Methoden 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. isWritable Dieser Eintrag ist schreibbar. Tabelle 7.6: Methoden der Klasse DirectoryIterator 266
  • 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')); Iteratoren bieten einen konsequent objektorientierten Zugriff und ersetzen im Fall des DirectoryIterators die bisherigen Dateifunktionen. 267
  • 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. fgetss Holt die nächste Zeile und entfernt alle HTML-Tags. Tabelle 7.7: Funktions-Referenz für Datei-Funktionen * 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. 268
  • 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. fseek Setzt den Dateizeiger. fstat Ermittelt Statusinformationen über eine Datei. Tabelle 7.7: Funktions-Referenz für Datei-Funktionen (Forts.) 269
  • Das Dateisystem entdecken Funktion Bedeutung 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. rename Benennt ein Verzeichnis oder eine Datei um. Tabelle 7.7: Funktions-Referenz für Datei-Funktionen (Forts.) * Windows kennt auch Verknüpfungen, die jedoch anders realisiert sind und von PHP nicht als Links erkannt werden. 270
  • 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. 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 271
  • Das Dateisystem entdecken Funktion Bedeutung rmdir Entfernt ein Verzeichnis. scandir Liest Dateien eines bestimmten Pfades. Tabelle 7.8: Funktions-Referenz für Verzeichnis-Funktionen (Forts.) Prozess-Funktionen 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). proc_open Öffnet einen Prozess und erzeugt das passende Prozesshandle. proc_terminate Beendet einen Prozess. 272
  • Kontrollfragen Funktion Bedeutung 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. 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 Verzeichnis im 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 öffentlichen Website nicht unmodifiziert einsetzbar? Tipp: Denken Sie an mögliche Sicher- heitsprobleme. 273
  • W T ag 1 Einführung 21 O T ag 2 Erste Schritte 45 C Tag 3 Daten verarbeiten 73 H T ag 4 Programmieren 131 E Tag 5 Daten mit Arrays verarbeiten 181 Tag 6 Objektorientierte Programmierung 203 T ag 7 Das Dateisystem entdecken 243 W Tag 8 Formular- und Seitenmanagement 277 O Tag 9 Professionelle Programmierung 373 C Tag 10 Kommunikation per HTTP, FTP und E-Mail 415 H Tag 11 Datenbankprogrammierung 443 E Tag 12 Die integrierte Datenbank SQLite 497 Tag 13 Datenbanklösungen mit MySQL 509 Tag 14 XML und Webservices 549
  • Formular- und Seitenmanagement 8
  • 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 Nutzer wirklich Kontakt mit Ihnen aufnehmen. Viel HTML – wenig PHP Formulare haben viel mit HTML und wenig mit PHP zu tun. Das ist eigentlich eine der herausragenden Stärken von PHP, denn für den Programmierer ist wenig zu tun, damit man bequem an die Daten herankommt. Freilich müssen Sie sicher im 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 Sie unbedingt diesen Abschnitt. Die HTML-Formularelemente kurz vorgestellt Dieses Buch soll kein HTML-Buch ersetzen. Eine kompakte Darstellung auf den folgenden Seiten ist jedoch hilfreich, vor allem wenn man alternativ vor einem 1000-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 Kapiteln wurde dies bereits häufig getan. Nun müssen Sie alle Tags kennen, mit denen HTML-Formulare erstellt werden können. Die folgende Tabelle fasst diese zusammen: Element Elementtyp Beschreibung Wichtige Attribute type="..." input text Einzeiliges Eingabefeld size, value, name checkbox Kontrollkästchen value, checked, name radio Optionsschaltfläche value, checked, name submit Sendeschaltfläche value, name Tabelle 8.1: Formular-Elemente in HTML 278
  • Grundlagen in HTML Element Elementtyp Beschreibung Wichtige Attribute type="..." reset Schaltfläche zum Rück- value, name setzen password Verdecktes Eingabefeld size, value, name hidden Unsichtbares Feld value, name button Schaltfläche value, name image Bild, ersetzt submit und ist src, name, damit wie eine Sendeschalt- Bildattribute fläche file Eingabefeld und Schalter name, accept zum Hochladen von Dateien select Dropdown-Liste multiple, name, size option Element der Dropdown-Liste value (nur Sub-Element) textarea Mehrzeiliges Textfeld name, rows, cols Tabelle 8.1: Formular-Elemente in HTML (Forts.) Die Attribute sind für die Steuerung des Verhaltens erforderlich und haben die in der folgenden Tabelle gezeigte Bedeutung: 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. 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) 279
  • 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. Zum einen wird ein Formular benötigt, das die Namen der Felder und den potenziellen Inhalt festlegt. Zum anderen wird ein PHP-Skript erstellt, das die Daten auswertet und beispielsweise in eine Datenbank schreibt. Die Aufnahme der Daten und die Auswertung umfasst immer einige elementare Schritte. Grundlegende Schritte Vor der ersten Auswertung muss klar sein, wohin ein Formular gesendet werden soll. Dies ist, bestimmt durch das Attribut action des <form>-Tags, natürlich der Name eines PHP-Skripts. Die einfachste Form ist der Selbstaufruf. Dabei ist das Skript, das das Formular definiert, 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 einer mit 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"> … </form> Andere Ziele müssen dagegen als relativer Pfad angegeben werden: 280
  • 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 des Formulars aufgerufen wurde, oder ob der Benutzer es bereits ausgefüllt und wieder abgesendet hat. Der erste Schritt bei der Auswertung besteht also darin, überhaupt zu erkennen, ob das 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 die Zeichenfolge »GET« oder »POST«. Auf diese Weise wird der Aufruf unterschie- den. Abbildung 8.1: Anzeige des Formulars beim der ersten Start des Skripts 281
  • Formular- und Seitenmanagement Abbildung 8.2: Anzeige des Formulars, nachdem es mit der Schaltfläche abgesen- det wurde Die Herkunft erkennen Manchmal ist es wichtig zu wissen, woher ein Formular kommt. Entweder aus Sicherheitsgründen oder zur Organisation des Zusammenhangs der einzelnen Skripte einer Applikation muss auf den so genannten »Referer« zugegriffen wer- den. Auch hierfür steht eine Servervariable zur Verfügung: HTTP_REFERER. Die Abfrage ist meist nur sinnvoll, wenn das Formular bereits abgesendet wurde, weil der erste Aufruf eines Skripts auch durch direkte Eingabe der Adresse im Browser erfolgen kann. In diesem Fall existiert jedoch kein Referer und die Variable ist leer. Das folgende Skript kontrolliert den Selbstaufruf und sichert sich dagegen ab, von einer 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 { echo "Falscher Aufruf"; } } else { echo 'Erster Aufruf der Seite'; 282
  • 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, wenn die beiden Zeichenketten identisch sind. Abbildung 8.3: Testausgabe mit Auswertung des Referers 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 Daten in einem speziellen Array bereit: $_POST. Das Array ist global, kann also ohne weiteres auch in Klassen und Funktionen benutzt werden. Die Schlüssel des Arrays entsprechen dabei exakt den Feldnamen (wie mit name="" festgelegt). 283
  • 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 leere Arrayelemente für unausgefüllte Felder. Man kann deshalb immer überprüfen, ob ein 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> </tr> <tr> <td> Kennwort:</td> <td><input type="text" name="Password"/> 284
  • 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 Abfrage des Arrayelements mit !empty (Negation). Erst dann wird praktisch auf den Inhalt zugegriffen. Abbildung 8.4: Abfrage von Formulardaten mit Zustandstest Textfelder Mit <textarea> erstellte Textfelder verhalten sich prinzipiell erstmal wie normale Eingabefelder. 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. Bei der Ausgabe auf einer HTML-Seite bleibt der Umbruch freilich wirkungslos, weil der 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, wie Daten aus einem Textfeld erfasst und für die erneute Ausgabe aufbereitet werden: Listing 8.4: formtextarea.php – Korrekte Verarbeitung von Textarea-Feldern <?php $action = $_SERVER['PHP_SELF']; if ($_SERVER['REQUEST_METHOD'] == 'POST') 285
  • 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> <td> <textarea name="Subject" cols="30" rows="5"></textarea> </td> </tr> </table> 286
  • 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. Im Beispiel werden die Daten in beiden Varianten ausgegeben. Abbildung 8.5: Verarbeitung von Zeilen- umbrüchen aus einem Textfeld Wenn Sie die Felddaten innerhalb von <pre>-Tags anzeigen, dann wer- den die integrierten Zeilenumbrüche ausgegeben. In einem solchen Fall wäre die zusätzliche Angabe von <br> fatal, weil sich die Umbrüche verdoppeln 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: í 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öpfe an alten Radios. 287
  • Formular- und Seitenmanagement í Kontrollkästchen (Checkbox) sind nicht miteinander verbunden und lassen eine Mehrfachauswahl zu, beispielsweise zur Konfiguration oder Kontrolle (daher der Name2). Optionsfelder Optionsfelder werden gruppiert, indem allen Feldern derselbe Name gegeben wird. Da immer nur ein Feld ausgewählt werden kann, übermittel der Browser zu dem gemeinsamen Namen den Wert, den man dem Feld vorher zugeordnet hat. Der Wert wird mit dem Attribut value festgelegt. Im $_POST-Array taucht dann genau ein Wert auf. Es ist möglich, dass kein Feld ausgewählt wurde. Erst wenn der Benutzer ein 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 hergestellt werden kann. Sie sollten deshalb unbedingt den Anfangszustand dedi- ziert setzen. Das folgende Beispiel zeigt eine Auswahl von mehreren Werten und setzt den Anfangszustand 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- 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 oder auswählen (engl. »to check«). 288
  • 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'; } ?> <form action="<?=$action?>" method="post"> <table> <tr> 289
  • 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 Elementen bereitet mitunter Schwierigkeiten. Beim Optionsfeld wurde der Name »PHPNote« für alle Elemente gewählt. In 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 Benutzer kann Optionsfelder nicht abwählen. Die folgende Bedingung kann deshalb, theo- retisch, nie FALSE sein: 290
  • Auswerten der Daten aus Formularen if (!empty($_POST['PHPNote'])) Sie wurde dennoch im Skript belassen, um zu zeigen, wie man mit einer leeren Auswahl 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 Änderungen am 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 überhaupt Felder ausgefüllt wurden: if (count($_POST) > 0) Dann werden alle Felder mit einer Schleife durchlaufen. Darin tauchen natürlich auch 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. Wurde ein 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-Attribut value vereinbaren. if ($value == 1) { $result[] = $name; } Wenn das entsprechende Kontrollkästchen aktiviert war, wird der Name in das Array $result geschrieben. Der Trick mit dem Array dient der schnellen Generie- rung einer kommaseparierten Liste. Dies erledigt am Ende, bei der Ausgabe die implode-Funktion: implode($result, ',') Gegenüber dem systematischen Anhängen erkannter Einträge an die Zeichen- kette, beispielsweise nach dem Schema $result .= $value . ',' , vermeidet man damit 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 diesem Abschnitt soll deshalb noch etwas verbessert werden. Um die Anzahl der Elemente flexibel zu halten, wäre auch eine dynamische Generierung der Kon- trollkästchen wünschenswert. Dazu werden Feldnamen und Typen in einem Array festgelegt. Die Namen enthalten ein Präfix, das die spätere Selektierung erleich- tert. Die Präfixe können etwa diesem Schema folgen: 291
  • Formular- und Seitenmanagement í Kontrollkästchen: chk í Optionsfelder: rdb í Textfelder: txt í Textarea: txa í Dropdown-Listen: drp í Schaltflächen: btn Abbildung 8.6: Das Formular in Aktion 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)); if ($_SERVER['REQUEST_METHOD'] == 'POST') { foreach ($_POST as $fName => $fValue) { if (substr($fName, 0, 3) == 'chk') { 292
  • 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> Hier wird zuerst ein Array definiert, das alle Daten enthält, die zur Konstruktion der Kontrollkästchen erforderlich sind. Die Struktur zeigt ein verschachteltes Array. Die erste Ebene enthält als Schlüssel den künftigen Namen des Felds: $checkboxes = array ('chkPerl' => … 293
  • Formular- und Seitenmanagement Jedes Kontrollkästchen bekommt dann einen Satz von Eigenschaften als weiteres Array: … 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. Hier wird – angeordnet in einer Schleife – der Inhalt des Arrays als Parameter an die Stellen 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. Die Auswahl 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 Formularelemente im Spiel sind, vorausgesetzt man hält sich konsequent an das Präfix-Schema. Das hier vorgestellte Schema der automatischen Feldgenerierung wurde exemplarisch für Kontrollkästchen gezeigt. Es gilt in ähnlicher Form für alle Feldelemente und sollte generell zur dynamischen Generierung eingesetzt werden. 294
  • Auswerten der Daten aus Formularen Dropdown-Listen Zu Dropdown-Listen pflegen Benutzer wie Webmaster eine gewisse Hassliebe. Sie bieten den Vorteil, sehr viele vorbereitete Informationen in sehr kompakter Form anzeigen zu können. Formulare mit vielen Dropdown-Listen verbergen aber auch Informationen vor dem Benutzer; sie zwingen dazu, die Liste aufzuklappen, zu scrollen und dann eine Auswahl zu treffen. Statt eines Klicks sind oft zwei oder mehr erforderlich. Dies ist unter Umständen lästig. In der Praxis muss man einen guten Kompromiss zwischen dem Volumen des Gesamtformulars und dem sicht- baren Informationsvolumen wahren. Listen mit Hunderten Einträgen sind schwer beherrschbar 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 die Angaben können sich auch auf den PHP-Code auswirken, der zur Auswertung benö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- mente anzeigt. Sind mehr Optionen vorhanden, wird ein Rollbalken erzeugt. Sind weniger Werte vorhanden, bleiben die Zeilen der Listbox leer (was sehr unprofes- sionell aussieht). 295
  • 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 Übernahme des Wertes entspricht der Verhaltensweise aller anderen Formularele- mente. 296
  • Auswerten der Daten aus Formularen Abbildung 8.7: Die Dropdown-Liste in Aktion Mehrfachauswahl Die Dropdown-Liste kann mit der Angabe des Attributes multiple dazu veranlasst werden, 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, wie mehrere 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 leicht wieder entnehmen lassen. Das letzte Beispiel wird dazu im HTML-Teil leicht ver- ändert und im PHP-Abschnitt um die Ausgabe des Arrays erweitert. Außerdem wird die bereits bei den Kontrollkästchen und Optionsfeldern gezeigte Technik der automatischen Generierung gezeigt, die auch bei Optionsfeldern gute Dienste tut: 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); $select[] = array('C++', 'C++ (VSC++)', FALSE); $select[] = array('Delphi', 'Delphi (Pascal)', FALSE); $select[] = array('Fortan', 'Fortran', FALSE); if ($_SERVER['REQUEST_METHOD'] == 'POST') { 297
  • 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 eine als Array angelegte Definition, ähnlich wie bei den Kontrollkästchen gezeigt. Das Array enthält die nötigen Angaben zum Options-Wert (value) und dem Text, der dem Benutzer angezeigt wird. Der dritte Wert wird hier noch nicht verwendet; das nächste Beispiel nimmt darauf Bezug. Die Ausgabe des Arrays erfolgt mit einer foreach-Schleife, in der eine printf-Funktion die <option>-Tags sachgerecht erzeugt. Wichtig ist bei der Definition des Select-Tags, dass hinter dem Namen die eckigen Klammern stehen, wenn das Attribut multiple benutzt wird: <select name="Language[]" multiple size="5"> 298
  • Auswerten der Daten aus Formularen Im PHP-Teil geht es nun anders zur Sache. Es ist sinnvoll, statt auf ein leeres Feld auf ein Array zu prüfen: if (is_array($_POST['Language'])) Das Array wird auch erzeugt, wenn nur ein Element angeklickt wurde. Danach steht natürlich jede Array-Funktion zur Weiterverarbeitung zur Verfügung. Im Beispiel wird eine kommaseparierte Liste mit implode erzeugt. Abbildung 8.8: Mehrfachauswahl einer automatisch erzeugten Box Die Größe der Liste wurde übrigens auf 5 gesetzt (size="5"), sodass immer fünf Werte 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 einen Booleschen Wert (der dritte Wert im Array $select), der die Vorauswahl steuern sollte. 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); $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') 299
  • 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: $option[2] ? 'selected' : '' Zur besseren Übersicht wurden außerdem die Parameter der printf-Funktion nummeriert: printf('<option value="%1$s" %3$s/>%2$s</br>', … 300
  • Professionelle Formulare und »Sticky Forms« Abbildung 8.9: Vorauswahl beim ersten Aufruf der Seite Probleme mit klassisch programmierten Formularen Freilich ist die letzte Lösung nicht optimal, weil bei einem erneuten Aufruf der Seite 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, dass seine 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 ein eigener Abschnitt gewidmet. 8.3 Professionelle Formulare und »Sticky Forms« Professionelle Formulare verlangen nach mehr Funktionen als bislang gezeigt wurde. Die Auswertung der eingegebenen Daten in PHP ist nur eine Seite der Medaille. 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. Sinnvolle Ausfüllhinweise sind Teil des Designs. Zu den typischen Vorgaben gehört die Markierung von Pflichtfeldern und Informationen über die in Textfel- dern zu platzierenden Daten. Ausfüllhilfen werden meist in JavaScript erstellt und betreffen beispielsweise die Prüfung von Feldinhalten, das Vorausfüllen auf der 301
  • Formular- und Seitenmanagement Grundlage anderer Daten oder die Formatierung von Eingaben. Sie helfen, die Fehlerquote 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 auf den Ort des Fehlers und Wege, diesen zu beseitigen. Nicht zuletzt ist all dies mit der Technik der »Sticky Forms« zu kombinieren, denn der fatalste Entwurfsfehler einer Seite ist ein Formular, das minutenlang mühevoll ausgefüllt wurde und nach dem Senden und erneuten Laden mit einer Fehlermeldung und ohne die Daten erscheint. Neben diesen Techniken ist auch die Größe eines Formulars von Bedeutung. Wenn sehr viele Felder erforderlich sind, muss eventuell über ein mehrseitiges Formular nachgedacht werden. Ausfüllhinweise und Feldvorgaben Tabellen haben beim Bau von Formularen eine große Bedeutung. Sie dienen vor allem der eindeutigen Platzierung von Feldbeschriftungen und Feldelementen. Das Formular bauen Eine der elementaren Techniken beim Formularbau betrifft den Einsatz von Tabellen. Damit Feldbeschriftungen und Felder vernünftig angeordnet erschei- nen, benutzt man zu ihrer Positionierung eine feste Struktur in Form einer Tabelle nach folgendem Schema: Text Feld Text Feld ... In HTML sieht das dann folgendermaßen aus: <form action="<?=$action?>" method="post"> <table> <tr> <td>Name:</td> <td><input type="text" name="Loginname"/></td> 302
  • 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> Abbildung 8.10: Das perfekte Formular mit einer Tabelle erstellt Abbildung 8.11: Zum Vergleich: Unprofessionelles Formular ohne Tabelle Hinweistexte platzieren Als nächstes sollten Hinweistexte platziert werden, um den Benutzer durch das Formular zu führen. Dies erfolgt meist rechts vom Feld, sodass man bei der Tabelle mit einem dreispaltigen Layout arbeitet. Ist das Formular nicht sehr hoch, die Felder aber recht breit, kann man den Text auch unter die Feldnamen oder unter 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ären auch aktive Hilfen interessant. Dabei hilft JavaScript. 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- 303
  • Formular- und Seitenmanagement Abbildung 8.12: Platzierung von Hinweistexten und Eingabe- hilfen 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 Verzicht auf 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. Dieser Abschnitt zeigt lediglich das Zusammenspiel mit PHP. Informationen über den genauen Umgang mit allen Möglichkeiten, die JavaScript bietet, sind anderen Quellen 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 die Seite 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 Zweitaufruf des Formulars gegebenenfalls unterschiedlich reagieren. Das ist wich- tig, weil JavaScript von Hause aus nicht unterscheiden kann, aufgrund welcher HTTP-Anforderung die Seite gesendet wurde. Das folgende Beispiel erzeugt eine Anzeigebox in JavaScript, deren Inhalt von der HTTP-Methode gesteuert wird, indem der Anzeigetext in PHP dynamisch ausgewählt wurde: 304
  • 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 oder anderen 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 Seite definiert wird: <script language="JavaScript"> function Box(text) { alert(text); document.logon.submit(); } </script> 305
  • 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 dem Anklicken einer »Submit«-Schaltfläche. Es spielt dabei keine Rolle, ob eine solche vorhanden 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« – der Name 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 eine Schaltfläche, die selbst keine Funktion in HTML hat. Sie verfügt aber über ein onClick-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 Text gefü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 $jsText machen, die in PHP erzeugt wird: $jsText = $_SERVER['REQUEST_METHOD'] == 'POST' ? 'Zweiter Aufruf' : 'Erster Aufruf'; Das ablaufende Programm produziert nun zwei verschiedene Anzeigeboxen, je nachdem, ob es das erste oder zweite Mal aufgerufen wurde. 306
  • Professionelle Formulare und »Sticky Forms« Abbildung 8.13: Diese Box erscheint beim ersten (links) und beim zwei- ten (und folgenden) Klick (rechts)auf die Schaltfläche Clientseitig auf Eingaben reagieren Da es in JavaScript einen Zugriff auf das Formular gibt, kann auch auf alle Felder zugegriffen werden. Das ist hilfreich, damit Formulare mit offensichtlichen Feh- lern gar nicht erst gesendet werden. Das folgende Beispiel zeigt, wie bei einem Feld 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 dem Benutzer Zeit und Bandbreite. Natürlich muss jede clientseitige Prüfung mit einer serverseitigen 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'); ok = false; } if (ok) { document.logon.submit(); } 307
  • 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();"/> </form> </body> </html> Die JavaScript-Funktion Check enthält die clientseitige Prüfung. Der Feldzugriff sieht folgendermaßen aus: 308
  • Professionelle Formulare und »Sticky Forms« if (document.logon.Loginname.value == '') document.logon verweist wieder auf das Formular, Loginname ist der Name des Feldes und value eine Eigenschaft, die den momentanen Wert zurückgibt. Der Rest ist Standardprogrammierung und wird nur durch die eigene Fantasie begrenzt. 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 zur Fehlerausgabe 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 es mehrere 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 Fehlertext hergestellt 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) – wird eine Zusammenfassung aller Fehler gegeben. Wichtig ist auch, alle Fehler gleich- zeitig zur Anzeige zu bringen und damit zu verhindern, dass der Benutzer sich mit dem Trial-and-Error-Prinzip durch die Reaktionen des Formulars klickt. Falls das Layout keinen Platz für detaillierte Meldungen neben den Feldern liefert, kann man 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 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 Seite widersprechen kann. Nimmt man auf Fehler dagegen keine Rücksicht und blen- det sie einfach in einem fertigen Layout ein, kann die Seite »zerfallen«. 309
  • Formular- und Seitenmanagement Was genau zu machen ist, hängt von der konkreten Situation ab. Häufig bietet es sich an, den Platz der Fehlermeldung zu reservieren, indem man den Text schon beim ersten Aufruf des Formulars auf die Seite schreibt. Dort wird er mit der Stil- Anweisung visibility:hidden unsichtbar gemacht. Dieser Stil wird in erster Instanz durch 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> <body> <?php $action = $_SERVER['PHP_SELF']; if ($_SERVER['REQUEST_METHOD'] == 'POST') { if (empty($_POST['Loginname'])) 310
  • 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> <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" 311
  • 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 die Vorauswahl der Sichtbarkeit der Fehlertexte festlegen, $displayName und $dis- playPass. Beim ersten Aufruf, REQUEST_METHOD ist gleich GET, werden natü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 das id-Attribut mitgegeben. Dies ist notwendig, um per JavaScript darauf zugreifen zu kö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 auf die 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 Namen beschafft und bereitstellt. Für die Nutzung dieser Methode wurde das id-Attribut benö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 HTML machen würde (»hidden« oder »visible«). War die Prüfung erfolgreich, wird das Formular gesendet. 312
  • Professionelle Formulare und »Sticky Forms« JavaScript geht nicht... Wenn JavaScript ganz abgeschaltet ist, funktioniert die letzte Variante noch nicht zufrieden stellend, weil das Absenden der Form überhaupt nicht stattfindet. Eine kleine 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') { if (empty($_POST['Loginname'])) { $displayName = 'visible'; } else 313
  • 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" style="color:red;visibility:<?=$displayPass?>"> Kennwort wurde nicht angegeben</span></td> </tr> </table> <br/> 314
  • 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 Variable entsprechend gesetzt: $displayName = 'visible'; Die JavaScript-Funktion Check wurde auch ein wenig geändert. Sie gibt nun einen Booleschen Wert zurück, der das Ergebnis der Prüfung bestimmt: return ok; Dieses Ergebnis wird mit einem weiteren return an das Klickereignis übergeben und 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 ist immer 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 value-Attribute der Felder mit den alten Werten zu belegen. Die JavaScript-Abschnitte aus den letzten Beispielen wurden in diesem Abschnitt nicht erneut in die Beispiele eingebaut, weil bei einer rein clientseitigen Prüfung die Werte ohnehin erhalten bleiben und die hier beschriebenen Techniken nicht 315
  • Formular- und Seitenmanagement sinnvoll einsetzbar sind. Für ein optimales »Fallback«, also die volle Funktion auch ohne JavaScript, sind jedoch beide Maßnahmen erforderlich. Textfelder Bei Eingabefeldern für Text ist dies besonders einfach. Das letzte Beispiel lässt sich leicht 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 { $Loginname = ''; $Password = ''; $displayName = 'hidden'; $displayPass = 'hidden'; 316
  • 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'] : ''; 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?>"/> 317
  • Formular- und Seitenmanagement Die Kombination aus dynamischer Fehlererzeugung und Werterhaltung macht aus einem einfachen HTML-Formular durch den Einsatz von PHP und JavaScript eine hochwertige, benutzerfreundliche Lösung. Abbildung 8.14: Erhalt der Werte, obwohl Fehler auftraten Textarea-Tags werden übrigens ebenso behandelt, nur wird hier statt des Attributes value der Wert zwischen die Tags geschrieben: <textarea ...><?=$Wert?></textarea> Optionsfelder und Kontrollkästchen Optionsfelder und Kontrollkästchen unterscheiden sich kaum von der Behandlung der Textfelder. Statt des Wertes wird hier das Attribut checked gesetzt. Wenn man sich für die dynamische Erzeugung der Felder per Skript entschieden hat, ist die Umsetzung 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 <?php function IsChecked($boxName, $boxData) { if ($_SERVER['REQUEST_METHOD'] == 'POST') { if (!empty($_POST[$boxName])) { return 'checked'; } } else { return ($boxData[3]) ? 'checked' : ''; } return ''; 318
  • 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) { printf('<input type="checkbox" name="%s" %s value="%s"/ > %s (%s)</br>', $boxName, IsChecked($boxName, $boxData), $boxData[2], 319
  • 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 Ausgabefunktion printf: 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 gerade in 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 erfolgt hier über das Setzen von selected-Attributen in den Option-Tags. Da dies jedes Tag betreffen kann, muss man quasi über eine ausreichende Anzahl Variablen für jede Option verfügen. 320
  • Professionelle Formulare und »Sticky Forms« Listing 8.16: formstickyselect.php – Einfache Listbox, die den gewählten Wert erhält <?php function 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'; } ?> <form action="<?=$action?>" method="post"> <table> <tr> <td valign="top">Welche Sprachen kennen Sie?</td> <td valign="top"> 321
  • 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 Werte mit einem Array. Dies vermeidet die sonst erforderliche Prüfung jedes einzelnen Wertes und erleichtert die Lösung erheblich. Bei der Generierung jeder Option wird 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 dem aktuellen 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 Wenn die Mehrfachauswahl mit dem Attribut multiple ermöglicht wurde, wird die Sache scheinbar komplizierter. Das folgende Skript zeigt, wie es dennoch einfach gelöst werden kann: 322
  • Professionelle Formulare und »Sticky Forms« Listing 8.17: formstickyselectmulti.php – Mehrfachauswahl einer Liste mit automati- schem Erhalt der Werte <?php function 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'; } ?> <form action="<?=$action?>" method="post"> <table> <tr> <td valign="top">Welche Sprachen kennen Sie?</td> 323
  • 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 nun statt 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: Erhalt der Werte einer Listbox mit Mehr- fachauswahl Betrachtet man Aufwand und Nutzen, ist es immer wieder erstaunlich, warum derart viele technisch schlecht erstellte Formulare im Internet zu finden sind. 324
  • Professionelle Formulare und »Sticky Forms« Mehrseitige Formulare mit versteckten Feldern Mehrseitige Formulare sind immer dann sinnvoll, wenn die Anzahl der Felder zu groß ist, um ohne heftiges Umherscrollen gelesen werden zu können. Nun stellt sich natürlich die Frage, wie man von einer Seite zu nächsten gelangt, ohne dass die Daten der vorhergehenden Seiten verloren gehen. Es gibt hier mehrere Lö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 dem Benutzer keinen wirklichen Vorteil bringt. Das Zurückblättern ist ohnehin eine aufwändige Angelegenheit. Für den Benutzer ist 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 Stand geben. So etwas ist vertrauensbildend und sorgt für einen unverkrampften Umgang mit den Seiten, was mithin den Erfolg steigert, dass ein Benutzern auch am Ende ankommt. Wie bereits bei den bisherigen Beispielen, macht erst eine geschickte Kombina- tion aus Styles, JavaScript, HTML und PHP wirklich das aus, was ein Benutzer als brauchbar empfindet. Wie es geht, soll anhand eines längeren Beispiels erläutert werden. 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 es freilich sichtbar, aus Sicherheitsgründen ist der Einsatz sinnlos. Versteckte Felder erlauben es aber, die aktuell erfassten Werte mitzunehmen und damit auf der nächsten Seite im Kontext der Seite zu sehen, ohne dass dies zusätzlichen Pro- grammieraufwand verursacht. Denn die Inhalte versteckter Felder sind so wie jedes andere Formularfeld Teil des $_POST-Arrays. 325
  • 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 ohne diese 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 ersten Formular alle Werte mitnehmen muss und es viele solche Sprungvarianten gibt. Besser ist es, alle Formulare auf ein und dieselbe Seite zu Schreiben. Der jeweils sichtbare Teil wird mit CSS-Attributen eingeblendet. Aktive Felder werden normal definiert, unaktive durch versteckte Felder realisiert. Verwendet man dann noch die 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 jeweiligen Stand anzeigt und darüber informiert, wie viele Schritte noch vor einem liegen. Listing 8.18: formmultipage.php – Ein mehrseitiges Formular auf einer Seite <html> <head> <title>Formulare</title> <style> * { font-family: Verdana; } .activeNumber 326
  • 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> <?php function 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'])) { $currentPage--; if ($currentPage == 1) { $prevDisabled = 'disabled'; } 327
  • 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"> <input type="hidden" name="currentPage" value="<?=$currentPage?>"/> <table width="500" class="<?= ($currentPage==1) ? 'activeTable' : 'inactiveTable' ?>"> <tr> 328
  • 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?>"/> </td> </tr> <tr> <td valign="top">Kennwort</td> <td valign="top"> 329
  • 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> </table> </form> </body> </html> 330
  • Professionelle Formulare und »Sticky Forms« Das ist sicher nicht mehr ganz so kompakt wie die anderen Beispiele. Deshalb soll der Blick auf den Ablauf vorangestellt werden. Die folgenden Abbildungen zeigen die 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 Abbildung 8.19: In Schritt 4 kann noch ein Newslet- ter bestellt werden 331
  • Formular- und Seitenmanagement Das Skript verwendet Stildefinitionen zur Kontrolle der Anzeige. Da sich die Tabellen mit den Formulardaten immer an derselben Stelle befinden sollen, wird zum Ausblenden der nicht benötigten Teile das Attribut display verwendet. Das bereits 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 die nötigen Einstellungen enthalten. Die aktive Tabelle erhält folgenden Stil: display:visible; Die inaktive (unsichtbare) Tabelle wird mit dem folgenden Code vom Bildschirm verbannt: display:none; Auch der Balken, der oberhalb des Formulars den Fortschritt anzeigt, nutzt Stile. Hier wird ebenfalls nur die Klasse umgeschaltet, die passenden Definitionen wurden 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 undefiniert und 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 Fall ist, steht die aktuelle Seite in $_POST['currentPage'], andernfalls wird hier der Startwert 1 eingesetzt. Damit das Formular nun ständig den Wert mitnehmen kann, 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 Weiterverarbeitung. Die Weiterverarbeitung erfordert freilich etwas mehr Auf- wand, denn die Variable muss bei einem Vorwärtsschritt erhöht und bei einem Rü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 332
  • 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ür eine 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 Beispielen gezeigten Varianten unterscheidet. Damit wird auch die Schaltfläche Teil des $_POST-Arrays. Der Wert ist dabei völlig uninteressant, nur die Tatsache, welche Schaltflä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 der Berechnung der folgenden Seite. Dazu wird die aktuelle Seitenzahl um eins erhöht oder verringert: $currentPage--; Dann folgt die Prüfung, ob bereits die erste (1) oder letzte (4) Seite erreicht worden ist: 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ür alle Felder vornimmt: return empty($_POST[$name]) ? $default : $_POST[$name]; Der Standardwert kann zum Setzen eines Anfangszustands genutzt werden. Dies wurde 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 REQUEST_METHOD abfragen!) 333
  • Formular- und Seitenmanagement 8.4 Dateien hochladen Dateien hochladen gehört zu den Formularfunktionen. In der Praxis bereitet dies gelegentlich Schwierigkeiten, weil mehrere Prozesse zusammenspielen müssen. Dieser Abschnitt klärt über Hintergründe, Anwendung und praktische Beispiele auf. Grundlagen Um Dateien hochladen zu können, müssen diese vom Browser speziell verpackt werden. Prinzipiell können per HTTP nur Text-Daten übertragen werden. Da Dateien im allgemeinen Binärdaten sind (HTML-Felder enthalten dagegen nur Text), müssen sie entsprechend kodiert werden. Um Kodierung und Dekodierung muss man sich keine Gedanken machen. Alle Browser beherrschen das Kodieren ebenso, wie PHP mit dem Dekodieren keine Probleme hat und dies intern erle- digt. Der Vorgang basiert auf der Nutzung der Methode POST und der üblichen Formularübertragung. PHP unterstützt auch Dateiuploads nach der PUT-Methode, die bei- spielsweise vom Netscape Composer und dem W3C Amaya benutzt wird. In der Praxis hat dies wenig Bedeutung, weil die meisten Provider FTP 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 es zwei Dinge enthalten: í Die Kodierungsanweisung im Form-Tag í Ein spezielles Feld mit dem Attribut type="file" Das Form-Tag sieht nun folgendermaßen aus: <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. 334
  • Dateien hochladen Ein Feld mit dem Attribut type="file" unterliegt aus Sicherheitsgründen bestimmten Einschränkungen. So kann die Beschriftung nicht geändert werden. 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 Klick verleitet könnte. Im Zusammenhang damit kann auch der Wert (Attribut value) nicht vom Programm gesetzt werden. Denn sonst könnte man eine Vorauswahl hineinschreiben und die Aktion dann mit JavaScript auslö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 Variablen zur Verfügung, sondern speichert sie in einem temporären Verzeichnis ab. Die beim Hochladen erzeugten Variablen enthalten nur Informationen über die Datei, wie beispielsweise den Dateinamen und die Größe. Das Skript, das die Daten empfängt, muss die Dateien aus dem temporären Speicher in das finale Verzeichnis kopieren. Das Kopieren kann entweder mit der Funktion copy oder mit move_uploaded_files ausgeführt werden. Die Funktion move_uploaded_files kann ausschließlich die temporären Dateien kopieren. Das ist sicherer als copy, weil ein offen program- miertes Skript mit freier Wahl der Pfade eventuell missbraucht werden könnte, um vorhandene 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 wird das Verzeichnis, in dem die hochgeladenen Dateien landen, anschließend ausge- geben. Listing 8.19: formuploadsimple.php – Formular zum Hochladen von Dateien <?php function GetFileError($num) { 335
  • 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> <li>Temporärname: {$tmp}</li> <li>Fehler: {$error}</li> <li>Lokaler Name: {$_FILES['MyFile']['name']}</li> </ul> FILEDATA; 336
  • 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"> Der reservierte Name MAX_FILE_SIZE weist PHP an, keine Dateien größer als angegeben zu übertragen. Die Angabe erfolgt in Bytes. Der Wert kann jedoch nicht die zusätzlich in der php.ini verpackten Restriktionen übergehen und stellt nur einen geringen Schutz gegen sehr große Dateien dar. Im Skript führt jedoch 337
  • 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 zumindest verhindert, dass jemand böswillig den Server zumüllt. Der spannende Teil ist die Auswertung des Hochladevorgangs. PHP erzeugt ein Array 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 Bedeutung haben: 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 Mit diesen Angaben kann man praktisch alles über die Datei erfahren und die wei- tere Vorgehensweise steuern. Der Name des Hauptschlüssels im Array $_FILES wird durch den Namen des Felds bestimmt: <input type="file" name="MyFile" /> Der Arrayschlüssel lautet also »MyFile«. Für die Übertragung der Datei in das finale Verzeichnis benötigt man nun zwei Angaben. Zuerst wird der temporäre Name als Quelle ermittelt: $tmp = $_FILES['MyFile']['tmp_name']; Dann der Zielname, erstellt aus einem frei wählbaren Verzeichnis und dem ursprünglichen Namen: $target = "upload/{$_FILES['MyFile']['name']}"; Nun wird der Kopiervorgang ausgelöst: if (move_uploaded_file($tmp, $target)) 338
  • Dateien hochladen Die Funktion gibt FALSE zurück, wenn es sich nicht um eine hochgeladene Datei handelt. 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 Funktionen glob 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 folgenden Bild aus: Abbildung 8.20: Hochladen von Dateien mit Auswer- tung und Dateiliste Typische Probleme Es gibt einige Probleme, die in der Praxis auftreten können und die bisher nicht beachtet wurden: í Laden mehrere Benutzer gleichnamige Dateien hoch, überschreiben sich diese gegenseitig 339
  • 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. Wie dann darauf reagiert wird, hängt von der Applikation ab. Eine andere Lösung besteht darin, jedem Benutzer ein eigenes Verzeichnis zu geben, aber dann müsste man ein Skript implementieren, das eine Benutzeranmeldung umfasst. Ist diese ohnehin 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 der erlaubten 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 Dateinamen kann man in den Griff bekommen, indem das iconv-Modul geladen und zur Umwandlung benutzt wird. Konfigurationsmöglichkeiten 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 die Einstellungen zeitweilig ändern lassen. 340
  • Von der Seite zum Projekt Konfigurationsmöglichkeiten mit der Datei php.ini In der Datei php.ini sind mehrere Optionen versteckt, die das Hochladeverhalten steuern: 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 8.5 Von der Seite zum Projekt Bislang waren immer nur einzelne Seiten zu erstellen. Praktisch besteht jedoch jede Website aus mehreren Seiten. Die Verbindung mit Hyperlinks ist Sache von HTML und dürfte kaum Probleme bereiten. Spannender ist es, wie Daten von Seite zu Seite übertragen werden. Die HTTP-Methode GET Die HTTP-Methode GET wird immer dann benutzt, wenn der Benutzer auf einen Link klickt. Außer dem einfachen Aufruf der nächsten Seite verfügt GET jedoch auch über Möglichkeiten, Daten mitzugeben. Damit wird der Zusammen- halt zwischen den Seiten gesteuert. 341
  • Formular- und Seitenmanagement Bevor es richtig losgeht, sollten Sie den Aufbau des URL komplett kennen. Hier ein 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 hash Hash-Wert als Sprungziel (Sprungsmarken werden mit <a name="hash"> gebildet) Tabelle 8.5: Aufbau eines URL 342
  • Von der Seite zum Projekt Einen URL untersuchen Liegt ein URL im Skript vor und Sie benötigen spezielle Angaben daraus, hilft die Funktion 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: Abbildung 8.21: Teile eines URL 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 durch ihren Hexwert. So wird ein Leerzeichen durch %20 ersetzt. »%« leitet den Code ein, 20 steht für dezimal 32 und dies ist der ASCII-Wert des Leerzeichens. Damit es nicht so schwierig ist, kennt PHP die Funktionen url_encode und url_decode. Dabei ist die Rückumwandlung meist nicht erforderlich, weil dies PHP intern erledigt. Der folgende Abschnitt zeigt die praktische Anwendung. 343
  • 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. Weichen Sie dann auf POST aus, wie im vorhergehenden Abschnitt beschrieben. Die im letzten Beispiel als »Query« bezeichnet Folge kann damit dekodiert und weiter 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 explode und die Ausgabe mit foreach-Schleifen. Dies war nur als kleine Übung zum Warmwerden gedacht, wie es einfacher geht, folgt gleich. Daten per URL übermitteln Um Daten per URL übermitteln zu können, werden an den Link entsprechende Paare aus Namen und Werten angehängt. Damit ergibt sich ein ähnliches Schema wie bei den POST-Daten aus einem Formular. Folgerichtig stellt PHP die Daten 344
  • 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 und die 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 automatisch erfolgt ist (das macht der Browser): Die Dekodierung erledigt PHP: Abbildung 8.22: Parameter nach der Übertragung per URL Einsatzbeispiele In Projekten wird die Übertragung von Daten per URL sehr oft eingesetzt, um das Verhalten 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 Navigation enthält. Diese ruft eine zweite Seite auf, deren Inhalt durch den übergebenen Wert gesteuert wird. Zuerst die Definition der ersten Seite mit der Navigation. Hier ist von PHP noch nichts zu sehen: Listing 8.23: get_navigation.php – Eine einfache Navigation überträgt Werte <?php $action = "get_navigationtarget.php"; 345
  • 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 folgende Skript 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 überhaupt im Array ist. Abbildung 8.23: Dynamisch erstellter Inhalt, der Titel wurde als Parameter übergeben Sicherheitsprobleme Die Übertragung von Daten per GET führt zu einigen Sicherheitsproblemen. Für den Benutzer sind die Daten in der Adresszeile des Browsers sichtbar und deshalb auch 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 GET verzichten. Deshalb sind Strategien gefragt, die Sicherheit der Seite zu erhöhen. 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 346
  • 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 so genannte Hashes. Dazu werden die vorbereiteten Daten in eine gesonderte Datei geschrieben und per include eingebunden. Eine solche Datei könnte am Beispiel der 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 generell nicht wieder in den ursprünglichen Wert umgewandelt werden (sie ent- halten meist viel weniger Daten als die Datenquelle hatte). MD5 steht für Message Digest Version 5 und ist sehr weit verbreitet. Listing 8.26: get_navigationmd5.php – Linkgenerierung mit Kodierung <?php include('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)); } ?> Interessant ist hier eigentlich nur der erzeugte Quelltext: <body> <a href="get_navigationtarget.php?t=c84369cb124912750ac1c1982a7b7e05"> Startseite</a><br/> 347
  • 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 man die Werte aus dem Array $titles erneut und vergleicht sie mit den gesendeten Hashcodes: Listing 8.27: get_navigationtargetmd5.php – Mit MD5 kodierte Links <?php include('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 berechnet Hash (linker Term) und dem per GET übertragenen: if (md5($name) == $t) Der URL der Seite sieht nun folgendermaßen aus: get_navigationtargetmd5.php?t=3d87a30682721340bdfe22c3a1dfae28 Dahinter kann auch ein Profi nicht mehr Ansatzweise vermuten, welche Art von Daten übertragen werden könnte. Bedenkt man, dass die Datenquelle in der Praxis keine einfachen Zeichenketten sind, sondern durchaus schon an sich kryptische Parameter, dann ist das Verfahren sehr sicher. 348
  • 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 und der 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 auch hier zusammen behandelt werden. Sie werden zwar noch sehen, dass Sessions auch ohne Cookies funktionieren, aber Cookies sind als Grundlage unerlässlich. Dieser Abschnitt zeigt, was Cookies und Sessions überhaupt sind, wozu sie dienen und wie man sie praktisch programmiert. Cookies Cookies sind Daten, die der Browser im Auftrag eines Servers ablegt und immer dann wieder ausliefert, wenn der Benutzer vom selben Server erneut Daten abruft. Die entscheidende Aussage dabei ist, dass der Browser darüber entscheidet, was er an den Server zurück sendet und ob überhaupt Daten gespeichert werden. Alle modernen 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- und GET-Daten. Sie sind halt ein weiterer Weg, Daten von Seite zu Seite mitzuneh- men. Hintergrund Cookies genießen leider einen recht schlechten Ruf, was an einigen missbräuch- lichen Einsätzen liegt. Generell verfügen Cookies über ein Verfallsdatum. Dem Browser wird darin mitgeteilt, für welchen Zeitraum er das Cookie behalten und wieder ausliefern soll. Ein Sonderfall stellen so genannten Sessioncookies dar, die 349
  • Formular- und Seitenmanagement am Ende der Benutzersitzung verfallen – im Allgemeinen dann, wenn der Browser geschlossen wird. Viele Benutzer akzeptieren inzwischen nur noch Sessioncookies, wes- halb deren Anwendung unkritisch ist. Auf reguläre Cookies sollte man sich als Webentwickler besser nicht verlassen. Die Entwicklung der Cookies geht auf das Protokoll HTTP zurück. HTTP gilt als so 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. Ob die nächste Anfrage vom selben Browser oder einem anderen kommt, kann nicht sicher 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 kleine Informationsdatei zusammen mit den Daten sendet, die der Browser speichert. Die Daten enthalten eine Identifikationsnummer. Der Browser sendet bei der nä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 es sich handelt. Das Cookie war geboren. Den schlechten Ruf erhielten Cookies, weil die Wiedererkennung pro Ressource passiert. Der Server verpackt die Cookies nämlich im Kopf (Header) jeder HTTP- Antwort. Jedes Element einer Seite wird durch eine Anforderung geholt und mit der Antwort geliefert. Eine Seite enthält neben dem eigentlichen Inhalt auch Bil- der oder Flash-Objekte. Diese werden alle mit einzelnen HTTP-Anforderungen beschafft. Dabei kann es sein, dass ein Bild, das als Werbebanner auf der Seite steht, von einem anderen Server geholt wird, als die eigentliche Seite. Derartige Ad-Server (Werbefirmen betreiben die) liefern nun für viele Seiten die Banner und damit auch Cookies aus. Natürlich gelangen die Cookies wieder zurück und so kann man leicht Bewegungsprofile erstellen, die den Weg des Benutzers über verschiedene ans Netzwerk angeschlossene Seiten aufzeichnen. Der Benutzer selbst bleibt zwar anonym, aber für eine kritische Presse hat es dennoch gereicht und flugs war der Ruf der Cookies versaut. Der Übeltäter, der als erster auf die Idee kam, war übrigens die Firma Doubleclick, die dieses Geschäft auch heute noch (neben vielen anderen) erfolgreich betreibt. 350
  • Cookies und Sessions Nichtsdestotrotz sind Cookies ein wertvolles und zudem leicht beherrschbares Instrument des Webprogrammierers und deshalb sollten Sie sich damit unbedingt auseinandersetzen. 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 als Array mit dem Namen $_COOKIE zur Verfügung. Die Erzeugung übernimmt dagegen eine spezielle Funktionen: setcookie. Die Parameter dieser Funktion reflektieren den Inhalt des Cookies: í name Dies ist der einzige Pflichtparameter (alle anderen sind optional) und er ent- hält den Namen des Cookies. Beim Abruf empfangener Cookies ist dies der Schlüssel des Arrays. í value Dies 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 Cookie wird auch gelöscht, wenn das Verfallsdatum in der Vergangenheit liegt, wes- halb meist beide Angaben zum Löschen kombiniert werden. í expire Dies ist das Verfallsdatum als Unix-Zeitstempel. Auch diese Angabe ist optio- nal. Wird nichts angegeben, entsteht ein Sessioncookie. í path Ein relativer Pfad innerhalb der Applikation, damit nicht immer alle Cookies zurückgesendet werden. Selten benutzt. í domain Schränkt die Domain ein, wenn der Server über Subdomains verfügt. Wenn das Cookie von www.comzept.de kommt, wird es auch nur dahin wieder zurückgesendet. Soll es auch auf www1.comzept.de landen, muss die Domain auf comzept.de eingeschränkt werden. í secure Wird der Wert auf 1 gesetzt, wird das Cookie nur gesendet, wenn eine sichere Verbindung besteht. Der Standardwert ist 0. 351
  • 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ängere Zeiträ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 setzt Schriftart und -größe einer Website nach Benutzervorgaben. Wenn der Benutzer später wiederkommt, findet er die letzte Einstellung wieder vor. Die Daten werden in 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'])) { $face = $_COOKIE['face']; $size = $_COOKIE['size']; } else { $face = 'Courier New'; 352
  • 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 die Logik zum Setzen und Auslesen der Cookies. Das Formular dürfte keine Probleme bereiten, es müsste für den praktischen Einsatz lediglich etwas ausgebaut werden (Achtung! Übungsgelegenheit). Nach dem Absenden wird geprüft, ob die Werte auch 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): $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); 353
  • Formular- und Seitenmanagement Damit ist das Skript in dieser Phase praktisch beendet. Die Cookies sind jetzt im Browser angekommen. Das wirkt sich auf die Einstellungen nicht sofort aus, son- dern erst beim nächsten (zweiten) Abruf der Seite durch den Browser. Nun sendet der 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ätzlichen Weg 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'; $size = 3; } ?> Ihre Auswahl für Schriftart und -größe:<br> <form action="<?=$action?>" method="post"> <select name="face" size="1"> 354
  • 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, wenn explizit 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-Array verschmolzen. 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 die zweite Stelle gesetzt. Damit kann der Benutzer den aktuellen Wert im Cookie jederzeit überschreiben. Es gibt übrigens auch ein fertiges Superarray $_REQUEST in PHP, dass eine Sammlung aller Werte aus $_POST, $_COOKIE, $_GET und $_FILES. Das ist nicht immer sinnvoll, weil es leicht Konflikte mit den anderen Arrays geben kann. Außerdem kann die Reihenfolge der Ver- mischung nicht kontrolliert werden. Deshalb wurde im Beispiel array_merge eingesetzt. 355
  • Formular- und Seitenmanagement Das Skript ist nun schon sehr weit entwickelt. Was als nächste Aufgabe ansteht, wäre die Kodierung der Cookie-Werte. Abbildung 8.24: Ausgabe, gesteu- ert durch den Zustand eines Cookies Andere Daten in Cookies speichern Cookies können nur Zeichenketten speichern. PHP kann andere Skalare leicht umwandeln. Arrays sind dagegen schwerer zu verpacken. Eine gute Idee ist die Verwendung von serialize. Diese Funktion erstellt eine Zeichenkettenform auch aus 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(); } else { $cookies = unserialize($_COOKIE['font']); } 356
  • 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 kann sofort 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 Serialisieren erzeugt: 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 Daten erforderlich ist. Cookies und Sicherheit Cookies sind nicht zuverlässig und nicht sicher. Es ist generell ein sehr schlechte Idee, Benutzernamen und Kennwörter in Cookies zu speichern. Bemächtigt sich jemand eines Computers, auf dem Kennwörter in Cookies gespeichert sind, könnte er sich damit unter einem fremden Namen anmelden. Außerdem liegen 357
  • 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 konsequent auf 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 und die Zuordnung aufrecht zu erhalten. Cookies sind ursprünglich zu diesem Zweck entworfen worden. Nun darf man die Dinge nicht durcheinander bringen. Es ist nicht 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 Aufgabe zu ü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-Systemen mit Apache Webserver und PHP-Modul möglich, auch den Computer-Speicher zu nutzen. Der Preis für höhere Leistung ist die Inkompatibilität der Skripte mit anderen Systemen. Datenbanken sind schnell, elegant und meist optimal, das Modul zum Speichern aber muss selbst implementiert werden. Andere Server sind eher Notlösungen, um Cluster zu ermöglichen, wenn keine Datenbank vorhanden ist. Apropos Cluster: Sind mehrere Webserver zur Lastverteilung zusammenge- schlossen, so ist nicht unbedingt sicherzustellen, dass Abrufe ein und desselben Browsers immer beim gleichen Server landen. Wenn nun die Sitzungsdaten auf 358
  • Cookies und Sessions der einen Maschine auf der Festplatte liegen und der nächste Start des Skripts von einer 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. Sie sorgt dafür, dass der Benutzer beim erneuten Abruf wieder erkannt wird. Nur die Session-ID wird letztlich auf seinem Computer gespeichert. Die Daten verlassen niemals 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 Browser und von dort wieder zurück gelangen. Es gibt drei Wege, die in PHP5 dafür zur Verfügung stehen: 1. GET 2. POST 3. Cookies Cookies sind übrigens eigens zu diesem Zweck erfunden worden und PHP5 nimmt diese als Standard. Nur wenn Cookies – aus welchen Gründen auch immer – nicht benutzt werden sollen, kommen andere Techniken zum Zuge. Letztlich geht 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 jeden internen 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: <form ...> <input type="hidden" name="sid"  value="ASD934DE42BC906876AF73F10932B"/> </form> 359
  • 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üssen die entsprechenden Behandlungsfunktionen manuell ausprogrammiert werden. PHP5 stellt dafür einen Schnittstelle über Rückruffunktionen bereit, die dies recht einfach 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 der php.ini wird der Unix-Dateipfad verwendet. Da 90% aller Entwicklungsumgebun- gen unter Windows laufen, kommt es regelmäßig zu einer Fehlermeldung beim ersten Start der Session-Verwaltung. Abbildung 8.25: Fehler, die Ses- sion-Verwaltung konnte die Daten nicht speichern Wenn dieser Fehler auftritt, öffnen Sie die für Ihre Installation zuständige Datei php.ini. Wenn Sie nicht wissen, wo diese liegt, erstellen Sie ein Skript, in dem die Funktion phpinfo aufgerufen wird. Im Kopf der Seite steht die benötigte Angabe: Abbildung 8.26: So ermitteln Sie die richtige php.ini In dieser Datei suchen Sie nun nach folgender Zeile: session.save_path = "/tmp" 360
  • Cookies und Sessions Ändern Sie die Zeile so, dass sie auf ein existierendes Verzeichnis zeigt: session.save_path = "C:WindowsTemp" Noch ein Problem ergibt sich, wenn Skripte aus dem Internet übernommen wer- den, die Funktionen wie session_register verwenden. Es ist mit PHP5 nicht empfehlenswert, diese Funktionen einzusetzen, weil aus Sicherheitsgründen einige damit verbundene Automatismen ausgeschaltet wurden. Das erschwert den Umgang. 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-Verwaltung gespeichert werden sollen. Dazu dient das $_SESSION-Array: $_SESSION['VariablenName'] = $Variable; Springt nun der Benutzer auf eine Folgeseite, steht dort, nachdem erneut session_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 Seiten zugänglich sein: Listing 8.31: sessionlogon.php – Anmeldeseite mit Speicherung der Anmeldedaten in einer Session-Variablen <?php session_start(); include('get_includenav.php'); $target = "sessionpages.php"; $action = $_SERVER['PHP_SELF']; $names = array('Admin' => 'admin', 'Test' => 'test'); function CreateLinks() { global $titles, $target; $links = ''; foreach ($titles as $title) 361
  • 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> <td> <input type="text" name="Logon"/> </td> </tr> <tr> 362
  • 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. Nur dann 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 spezifischeren Variante ersetzt werden. Hier wird nur gegen die Daten eines fest programmierten Arrays geprüft. Ist diese Prüfung erfolgreich, wird der Anmeldename in die Session geschrieben: $_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 erfolgreiche Anmeldung zur Anzeigesteuerung benutzt. Es ist nun wichtig, dass auf jeder belie- bigen Folgeseite die Information darüber vorliegt, ob die Anmeldung erfolgte. Das Skript sessionpages.php zeigt, wie dies aussehen kann: Listing 8.32: sessionpages.php – Zugriff auf die Session-Variablen <?php session_start(); function ReCreateVariables() { foreach($_SESSION as $name => $value) { global $$name; 363
  • 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 es einfacher oder praktikabler ist, alle Variablen wieder als globale verfügbar zu machen. Die Funktion ReCreateVariables() erledigt das. Dazu durchläuft man das Array 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. Der hier beschriebene Weg mit dem $_SESSION-Array ist nicht die einzige Möglich- keit, aber er funktioniert garantiert immer. 364
  • Referenz 8.7 Referenz Wichtige Systemarrays 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 Server- und Umgebungsvariablen 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 kein Präfix haben. APPL_MD_PATH Gibt den Pfad zur Metabasis der Applikation an (nur IIS/Windows). Tabelle 8.7: Server- und Umgebungsvariablen 365
  • Formular- und Seitenmanagement Name der Variable Beschreibung 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. DATE_LOCAL Datum/Uhrzeit des Servers. DOCUMENT_NAME Name des ausführenden Dokuments. Tabelle 8.7: Server- und Umgebungsvariablen (Forts.) 366
  • Referenz Name der Variable Beschreibung 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. QUERY_STRING Inhalt des Querystrings (Parameter-URL). REMOTE_ADDR Die IP-Adresse des Nutzers. Tabelle 8.7: Server- und Umgebungsvariablen (Forts.) 367
  • Formular- und Seitenmanagement Name der Variable Beschreibung 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. Tabelle 8.7: Server- und Umgebungsvariablen (Forts.) Der Abruf erfolgt immer mit $_SERVER['NAME']. Beachten Sie, dass nicht auf allen Servern alle Variablen zur Verfügung stehen und dass sich die Inhalte teilweise geringfügig unterscheiden, beispielsweise bei Pfadangaben. Session-Verwaltung Funktion Beschreibung 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 368
  • Referenz Funktion Beschreibung 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. s