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
    titleListing 1/title
  /head
  body
    h1Unser 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
    titleListing 2/title
  /head
  body
    h1Unser 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
 titleKein 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
  titleTitel 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.
í    tdInhalt/td
     Innerhalb der Reihe stehen diese Tags für jede Zeile.
í    thKopf/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
   thName/ththAnschrift/ththTelefon/th
  /tr
  tr
   td/tdtd/tdtd/td
  /tr
  tr
   td/tdtd/tdtd/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
         thName/ththAnschrift/ththTelefon/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
 titlePhpStart1/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
  h3Wir 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=_selfSeite 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$XFarbausgabe 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=#CC6699Farbausgabe 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 brDigit: , $digit;
echo brNumber: , --$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 brNormal :  . strcmp($text1, $text2);
echo brNatü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
         tdAusdruck/td
         tdinput type=text name=expression
                    value=$expression//td
       /tr
        tr
         tdMuster/td
         tdinput type=text name=pattern
                    value=$pattern//td
       /tr
        tr
         td/td
         tdinput 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 $valbr/;
            }
         }
      }
   } 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 enthauml;lt viele neue Funktionen.
~#~. Regulauml;re Ausdruuml;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 enthauml;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        /bbr
        Nächstes Argument, Zahl:               b%d           /bbr
        Aufgefüllte 4. Zeichenkette:           b%4$'_-10s   /bbr
        Nächstes Argument, Zahl:               b%08.2f       /bbr,
        $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 trtd$i/tdtdnbsp;.../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 trtd . ($i + 1) . /tdtdnbsp;.../td/tr;
Es wäre übrigens ein echter Programmierfehler, folgendes zu schreiben, wenn die
Iterationsanweisung im Schleifenkopf weiter bestehen bleibt:
echo trtd . ($i++) . /tdtdnbsp;.../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 trtd$j/tdtdnbsp;.../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 trtd$j/tdtdnbsp;.../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 trtd$j/tdtdnbsp;.../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 'trtdReihe/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 'trtdReihe/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 'trtdReihe/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 trtd 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 .= 'tdnbsp;/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:
trtd 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 .= $sTagnbsp;/$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 = trtd$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 .= $sTagnbsp;/$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/bbr/;
}
$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 $filenamebr;
    }
}
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; $istrlen($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 $filebr;
      }
   }
   $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:
            brb$ref/bbran
            brb$srv/bhr;
     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_NAMEPHP_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
       tdName:/td
       tdinput type=text name=Loginname//td
     /tr
     tr
       td
          Kennwort:/td
          tdinput 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
      tdName:/td
      tdinput 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=topWelche 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=topWelche 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=topWelche Sprachen kennen Sie?/td
       td valign=top
          select name=Language size=1s
            option value=PerlPerl/option
            option value=PHPPHP/option
            option value=VBScriptVBScript/option
            option value=C++C++/option
            option value=C#C#/option
            option value=DelphiDelphi/option
            option value=FortranFortran/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=topWelche 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=topWelche 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
      tdName:/td
      tdinput type=text name=Loginname//td



302
Professionelle Formulare und »Sticky Forms«


    /tr
    tr
      td
         Kennwort:/td
         tdinput 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
    titleFormulare/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
   titleFormulare/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ülltbr;
      }
      if (empty($_POST['Password']))
      {
           echo Kennwort nicht ausgefülltbr;
      }
}
?
form action=?=$action? method=post name=logon
  table
      tr
        tdName:/td
        tdinput type=text name=Loginname//td
        td valign=topspan 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=topspan 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
   titleFormulare/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
      tdName:/td
      tdinput type=text name=Loginname//td
      td valign=topspan style=color:red*/span/td
      td valign=topspan 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=topspan style=color:red*/span/td
      td valign=topspan 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
   titleFormulare/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
      tdName:/td
      tdinput type=text name=Loginname//td
      td valign=topspan style=color:red*/span/td
      td valign=topspan 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=topspan style=color:red*/span/td
      td valign=topspan 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
       tdName:/td
       tdinput type=text name=Loginname
                   value=?=$Loginname?//td
       td valign=topspan style=color:red*/span/td
       td valign=topspan 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=topspan style=color:red*/span/td
       td valign=topspan 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=topWelche 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=topWelche 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=topWelche 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
         titleFormulare/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=topName/td
  td valign=top
     input type=text name=Name value=?=$fldName?/
  /td
/tr
tr
  td valign=topAnschrift/td
  td valign=top
     input type=text name=Address value=?=$fldAddress?/
  /td
/tr
tr
  td valign=topPLZ-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=topAGB'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=topAnmeldename/td
  td valign=top
     input type=text name=LogonName
            value=?=$fldLogonName?/
  /td
/tr
tr
  td valign=topKennwort/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=topNewsletter/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=topKennwort/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
     liLokaler Name: {$_FILES['MyFile']['name']}/li
     liMIME-Typ:     {$_FILES['MyFile']['type']}/li
     liGröße:        {$_FILES['MyFile']['size']} Byte/li
     liTemporärname: {$tmp}/li
     liFehler:       {$error}/li
     liLokaler 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/abr', $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
       tdIhre 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/abr', $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=wertval%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=wertval%202=wert#hash;
$p = parse_url($url);
foreach ($p as $name = $content)
{
   echo b$name:/b $contentbr;
}
?
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=wertval%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 $contentbr;
    }
}
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=Namepara2=Die StraßeKlick 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=StartseiteStartseite/abr/
a    href=?=$action??title=ImpressumImpressum/abr/
a    href=?=$action??title=ProdukteProdukte/abr/
a    href=?=$action??title=AGBsAGBs/abr/
a    href=?=$action??title=AnmeldungAnmeldung/abr/
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/abr/',
            $action,
            $title,
            md5($title));
}
?
Interessant ist hier eigentlich nur der erzeugte Quelltext:
body
a href=get_navigationtarget.php?t=c84369cb124912750ac1c1982a7b7e05
Startseite/abr/



                                                                                       347
Formular- und Seitenmanagement


a href=get_navigationtarget.php?t=fd2648427fd682bc2a48d7cf5668b3db
Impressum/abr/
a href=get_navigationtarget.php?t=3d87a30682721340bdfe22c3a1dfae28
Produkte/abr/
a href=get_navigationtarget.php?t=2bbf0f4a652ef7ccd201ca9ab89032f8
AGBs/abr/
a href=get_navigationtarget.php?t=05d316097eb9d1289765b00d23b6aba8
Anmeldung/abr/
/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=VerdanaVerdana/option
        option value=ArialArial/option
        option value=TahomaTahoma/option
        option value=Times RomanTimes Roman/option
    /select
    select name=size size=1
        option value=11/option
        option value=22/option
        option value=33/option
        option value=44/option
        option value=55/option
        option value=66/option
        option value=77/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=VerdanaVerdana/option
        option value=ArialArial/option
        option value=TahomaTahoma/option
        option value=Times RomanTimes Roman/option
    /select
    select name=size size=1
        option value=-- Ihre Auswahl --/option
        option value=11/option
        option value=22/option
        option value=33/option
        option value=44/option
        option value=55/option
        option value=66/option
        option value=77/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/abr/',
                               $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
         tdName:/td
         td
             input type=text name=Logon/
         /td
    /tr
    tr



362
Cookies und Sessions


         tdKennwort:/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.
session_unset                    Löscht alle Session-Variablen.
session_write_close              Speichert alle Session-Daten und beendet die Session.

Tabelle 8.8: Funktionen der Session-Verwaltung (Forts.)


Initialisierungseintrag       Beschreibung

session.name                  Der Name der Session. Dieser Wert wird für den Url-Para-
                              meter und das Cookie verwendet. Der Standardwert ist
                              PHPSESSID.

Tabelle 8.9: Konfiguration der Session-Verwaltung



                                                                                        369
Formular- und Seitenmanagement



Initialisierungseintrag          Beschreibung

session.auto_start               Wenn aktiviert, wird bei einer neuen Anfrage eine neue
                                 Session gestartet, nicht erst beim Aufruf von session_start.
                                 Standardmäßig deaktiviert (0), zum Aktivieren auf 1 setzen.
session.serialize_handler        Name der Methode, mit der die Daten serialisiert werden.
                                 Standardwert ist »php«, Alternative ist »wddx«.

session.gc_probability           Regelt die Wahrscheinlichkeit, mit der die Aufräumroutine
                                 alte Session-Daten beseitigt. Standardwert ist 1.
session.gc_divisor               Regelt die Wahrscheinlichkeit, mit der die Aufräumroutine
                                 alte Session-Daten beseitigt, im Verbund mit
                                 »gc_probability«. Die finale Wahrscheinlichkeit beträgt
                                 gc_probability/gc_divisor. Der Standardwert ist 100, dass
                                 heißt die Aufräumroutine startet mit einer Wahrscheinlich-
                                 keit von 1%.

session.gc_maxlifetime           Lebensdauer einer Session in Sekunden. Der Standardwert
                                 beträgt 1440 (24 Minuten).

session.referer_check            Zeichenkette, die im Referer (Quelle des HTTP-Abrufs)
                                 enthalten sein muss. Wenn aktiviert, verhindert man damit
                                 den Einsprung in eine Session von außerhalb. Standard-
                                 mäßig nicht verwendet.

session.entropy_file             Pfad einer Quelldatei, die beim Erzeugen der Session-ID
                                 benutzt wird. Standardmäßig nicht verwendet.

session.entropy_length           Anzahl der Bytes, die aus der Entropie-Datei gelesen wer-
                                 den. Standardwert ist 0.
session.use_cookies              Erlaubt Cookies. Standardwert ist 1 (aktiviert).

session.use_only_cookies         Erzwingt (!) Cookies. Cookies werden auch so verwendet,
                                 aber mit dieser Option kann man alternative Wege aus
                                 Kompatibilitätsgründen verhindern. Standardmäßig nicht
                                 aktiviert (0).

session.cookie_lifetime          Lebensdauer des Cookies. Standardwert ist 0, was typische
                                 Session-Cookies erzeugt.
session.cookie_path              Pfad-Angabe des Session-Cookies.

Tabelle 8.9: Konfiguration der Session-Verwaltung (Forts.)



370
Referenz



Initialisierungseintrag        Beschreibung

session.cookie_domain          Domain-Angabe des Session-Cookies.

session.cookie_secure          Erzwingt die Verwendung sicherer Verbindungen für das
                               Cookie.
session.cache_limiter          Methode der Cacheverwaltung. Standardwert ist »noca-
                               che« (nicht verwendet).
session.cache_expire           Verfallszeitpunkt des Cache in Minuten. Standardwert ist
                               180.

session.use_trans_sid          Aktiviert die transparente SID-Umsetzung, bei der ver-
                               steckte Felder und URL-Erweiterung zur Übergabe der
                               Session-ID verwendet werden.

session.bug_compat_42          Erzeugt eine Warnung, wenn versucht wird, mit einem
                               Trick globale Variablen mit Session-Daten zu erzeugen,
                               obwohl dies durch andere Einstellungen verboten ist und
                               zusätzlich bug_compat_warn aktiviert wurde.

session.bug_compat_warn        Erzeugt eine Warnung, wenn versucht wird, mit einem
                               Trick globale Variablen mit Session-Daten zu erzeugen,
                               obwohl dies durch andere Einstellungen verboten ist.

session.hash_function          Prüfsummenfunktion für die Session-ID. 0 = MD5
                               (128 Bit), 1 = SHA-1 (160 Bit). Standard ist MD5.

session.hash_bits_per_         Gespeicherte Bits pro Zeichen in der Session-ID. Kann 4, 5
character                      oder 6 sein. 4 ist der Standardwert.
url_rewriter.tags              Tags, die zur Aufnahme der Session-ID umgeschrieben
                               werden dürfen.
Tabelle 8.9: Konfiguration der Session-Verwaltung (Forts.)




                                                                                        371
Formular- und Seitenmanagement



8.8       Kontrollfragen
1. Warum sollten unbedingt immer »Sticky Forms« verwendet werden?
2. Welche Funktionen unterstützen das Hochladen von Dateien? Was ist beim Auf-
      bau des Formulars zu beachten?
3. Welche Methoden verwenden Sessions, um die Session-Daten zu speichern?
4. Wie können Cookies missbraucht werden?




372
Professionelle
Programmierung




   9
Professionelle Programmierung



9.1        Mehrsprachige Webseiten
Immer häufiger wird gefordert, Webseiten mehrsprachig anzubieten. Es ist gerade
für kleine Unternehmen ein Segen, Kunden überall auf der Welt ansprechen zu
können. Mit Deutsch allein kommt man dabei aber nicht weit, mit Englisch wie-
derum kann man hierzulande nicht viel erreichen. Es ist deshalb oft notwendig,
Websites in mehreren Sprachen anzubieten. Damit der Aufwand nicht ins Uner-
messliche steigt, gibt es verschiedene Techniken, Gestaltung und Inhalt zu tren-
nen, sodass die aufwändige Grafik nur einmal existiert, die variablen Inhalte
dagegen getrennt gehalten werden.
Vor der Auswahl der Sprache steht jedoch die Erkennung der Daten durch den
Benutzer. Dies erfolgt durch Auswertung der vom Browser übermittelten Daten.


Browserdaten erkennen
Der Browser überträgt verschiedene Informationen an den Server, die sich dann in
PHP-Skripten auswerten lassen. Neben Betriebssystem, Name des Browsers und
verschiedenen Angaben zu akzeptierten Daten gehört auch die bevorzugte Spra-
che dazu.


Sprache im Browser einrichten
Jeder Benutzer sollte in seinem Browser die bevorzugte Sprache einrichten, damit
Skripte, wie die nachfolgend vorgestellten, auch funktionieren. Die entsprechen-
den Menükommandos zeigt die folgende Liste:
í     Netscape/Mozilla: Bearbeiten | Einstellungen | Navigator | Sprachen
í     Internet Explorer: Extras | Internetoptionen | Allgemein | Sprachen
í     Opera: Datei | Einstellungen | Sprachen
í     Tango: Sprache | Spracheinstellungen
í     Lynx 2.7: O (Options) | G (lanGuage)
Nach dieser Angabe ist natürlich interessant zu wissen, was davon in PHP5
ankommt. Benutzt wird eine Servervariable aus dem Array $_SERVER:
HTTP_ACCEPT_LANGUAGE.




374
Mehrsprachige Webseiten




                                              Abbildung 9.1:
                                              Spracheinstellungen im Internet
                                              Explorer

Diese enthält eine Liste der akzeptierten Sprachen mit einer Angabe der Wertig-
keit. Die erste Sprache ist immer die bevorzugte, der Rest teilt sich mit abnehmen-
der Wichtung in Relation zu 1. Das »q« steht für »Quality«. Die in der letzten
Abbildung gezeigte Angabe sendet der Browser folgendermaßen:
de,en-us;q=0.7,fr;q=0.3
Das bedeutet, die Hauptsprache ist Deutsch, Englisch wird mit 0,7 gegenüber
Französisch mit 0,3 bevorzugt, wobei 1 = ideal wäre. Die meisten Browser legen
die Werte selbst fest, sodass hier wenig Wahl besteht. Tatsächlich wäre es auch
möglich, folgende Angabe zu machen:
de,en-us;q=0.8,e-gb;q=0.7
Hier wird mitgeteilt, dass Deutsch ideal ist, amerikanisches Englisch akzeptiert
wird und alternativ auch britisches Englisch gern gelesen wird.

          Sie sollten als Programmierer nicht darauf vertrauen, dass irgendwer
          mehr als die Standardsprache halbwegs sinnvoll eingestellt hat. Die fol-
          genden Anwendungen werten deshalb auch nur diesen Teil aus.


Sprache auswerten
Das Auswerten der Sprache ist nun recht einfach. Man muss nur die Servervari-
able abfragen und den ersten Teil, bis zum Komma, abtrennen:




                                                                                  375
Professionelle Programmierung


Listing 9.1: browserlang.php – Sprachakzeptanz abfragen und Hauptsprache ermitteln

?php
$lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
echo Sprachinfo des Browsers: $langbr;
$l = str_replace(strstr($lang, ','), '', $lang);
echo Erkannten Hauptsprache: b$l/b;
?
Das Skript nutzt Zeichenkettenfunktionen, um den Anfang der Sprachinformation
und damit die Hauptsprache herauszufinden. Für die Auswertung der übrigen
Informationen bietet sich der Zugriff auf reguläre Ausdrücke oder die Zerlegung
mit explode an.

                                                     Abbildung 9.2:
                                                     Sprachinfo und extrahierte
                                                     Hauptsprache

Steht die Sprache fest, muss nur noch entsprechend darauf reagiert werden.


Lokalisierung und Formatierung von Zeichen
Die Lokalisierung basiert meist auf der Anwendung der Funktion setlocale, die
das Verhalten anderer Funktionen beeinflusst. Allerdings ist der Umgang damit
recht tückisch. Leider hat sich dies mit PHP5 nicht gebessert, was umso unverständ-
licher ist, als dass neuere Programmierumgebungen wie .NET auch unter Windows
den gängigen Standards folgen und es keinen Grund gibt, dies nicht zu adaptieren.
Generell gilt folgende Regel für Linux-Systeme:
í     Das Basisformat ist »Sprache_Land«, also beispielsweise »de_DE« oder
      »de_AT«
í     Braucht man bestimmte Sonderzeichen einer Sprache, die bei der Ausgabe
      angezeigt werden sollen, kann man eine Codeseite angeben: »kr_KR.949«.
Windows-Systeme erwarten statt der Ländercodes Sprachnamen, abgekürzt oder
ausgeschrieben:
Das Basisformat ist »Germany« oder »ge« bzw. »German_Germany« oder
»German_Austria«. Es ist also in den allermeisten Fällen erforderlich, das vom
Browser gesendete Sprachkürzel »de« in die passende Form umzuwandeln.



376
Mehrsprachige Webseiten


Die Funktion setlocale anwenden
Alle Formate, die printf und verwandte Funktionen benutzen, reagieren auf die
Einstellungen mit setlocale. Damit ist es möglich, die korrekten Formatierungen
für Zahlen zu erhalten, also Komma als Dezimaltrennzeichen in deutschen Tex-
ten usw. Außerdem lassen sich Zeitangaben bei der Ausgabe mit strftime in die
passende Sprache setzen.
Für die Formatierung von Zahlen muss setlocale entweder mit der Konstanten
LC_ALL oder LC_NUMERIC bedacht werden:
setlocale(LC_NUMERIC, German_Germany);
Die Angabe muss vor der ersten Ausgabe mit einer der printf-Funktionen erfol-
gen. Um die Rückstellung der vorherigen Angabe zu ermöglichen, können Sie den
Rückgabewert von setlocale abfragen.
Die folgende Tabelle zeigt alle Konstanten, die setlocale benutzt:

Konstante        Bedeutung                             Betroffene Funktion
LC_ALL           Beeinflusst alle Formate              printf

LC_COLLATE       Beeinflusst Zeichenkettenvergleiche   strcoll

LC_CTYPE         Klassifizierung von Zeichen           strtoupper

LC_MONETARY      Betrifft Währungsangaben              localeconv

LC_NUMERIC       Betrifft Zahlenangaben                localeconv

LC_TIME          Beeinflusst Datums- und Zeitangaben   strftime

Tabelle 9.1: Konstanten, die setlocale benutzt

           strcoll entspricht der Funktion strcmp mit dem Unterschied, dass die
           Lokalisierung berücksichtig wird. strcmp ignoriert diese.


Die Anwendung der Funktion kann auch auf Bedingungen des Betriebssystems
abgestimmt werden. Wenn der zweite Parameter leer ist (nicht fehlend, sondern
eine leere Zeichenkette), dann wird versucht, eine Umgebungsvariable zu lesen,
deren Namen der Konstanten entspricht. Der folgende Ausdruck liest die Umge-
bungsvariable LC_NUMERIC und setzt setlocale entsprechend:


                                                                                 377
Professionelle Programmierung


setlocale(LC_NUMERIC, );
Clever ist es auch, eine Fallbackmöglichkeit zu schaffen. Wie anfangs bereits
beschrieben, sind auf den verschiedenen Betriebssystemen die Lokalisierungszei-
chenfolgen zu beachten. Deshalb kann an dieser Stelle auch ein Array angegeben
werden:
setlocale(LC_NUMERIC, array(de, de_DE, German_Germany));
Es gibt natürlich auch die Möglichkeit, hier die Sprache zu wechseln, um unter
allen Umständen ein reproduzierbares Ergebnis zu erzielen.


Das Format der aktuellen Lokalisierung ermitteln
Um festzustellen, wie PHP5 intern arbeitet und wie sich die Parameter der Lokali-
sierung auswirken, gibt es die Funktion localeconv. Sie gibt ein Array zurück, das
die entsprechenden Angaben enthält. Die folgende Abbildung zeigt die Ausgabe
für die Lokalisierung Deutsch:




                                                             Abbildung 9.3:
                                                             Ausgabe der Lokalisie-
                                                             rungszeichen für
                                                             Deutsch/Deutschland
                                                             (links) und Deutsch/
                                                             Schweiz (rechts)

Die Angaben können hilfreich sein, um den Platzbedarf von Zeichenketten zu
berechnen oder die Wirkung der Lokalisierung zu überwachen. Um die Tabellen
zu erstellen, wurde folgendes Skript verwendet:


378
Dynamisch Bilder erzeugen


Listing 9.2: localeconv.php – Lokalisierungsangaben ausgeben

table border=1 style=font-family:Verdana; font-weight:bold
?php
setlocale(LC_ALL, German_Switzerland);
$lc = localeconv();
foreach ($lc as $key = $val)
{
      echo trtd$key/td;
      echo td$val/td/tr;
}
?
/table



9.2        Dynamisch Bilder erzeugen
Das dynamische Erzeugen von Bildern gehört mit zu den am häufigsten diskutier-
ten Möglichkeiten von PHP. Die mit PHP5 verfügbare neue Grafikbibliothek
GD2 stellt neue Funktionen bereit, um professionell wirkende Grafiken dyna-
misch zu erstellen.


Prinzip
Um das Prinzip der Erzeugung dynamischer Bilder zu verstehen, muss man sich
noch mal den Ablauf des Seitenaufbaus im Browser in Erinnerung rufen. Nach
dem Laden der HTML-Seite analysiert der Browser den Inhalt und beginnt mit
der Darstellung. Findet er ein Bild, leitet er für die angegebene Bildquelle eine
weitere asynchrone1 HTTP-Anforderung ein. Der dann zurückgegebene binäre
Datenstrom wird wiederum interpretiert und, ins entsprechende Bildformat
zurückgewandelt, zur Darstellung verwendet. Bei dem Vorgang an sich ist es dem
Browser egal, woher das Bild kommt. Er erwartet letztlich nur einen simplen
Datenstrom. Den kann man erzeugen, indem man auf dem Server ein Bild ablegt
und dem Webserver die Auslieferung überlässt. Es ist aber auch möglich, die
Arbeit von einem Skript erledigen zu lassen, das die Daten fachgerecht generiert.
Als Quelle wird dann einfach ein PHP-Skript genommen:
img src=imagegen.php/
1   Deswegen erscheinen die Bilder manchmal erst viel später.



                                                                                            379
Professionelle Programmierung


Dem Skript kann man selbstverständlich auch GET-Parameter übergeben, um das
Verhalten zu steuern:
img src=imagegen.php?name=Joerg%20Krause/


Aufbau eines bilderzeugenden Skripts
Für ein bilderzeugendes Skript muss man mehrere Dinge beachten:
í     Es dürfen ausschließlich Bilddaten erzeugt werden. Schon ein einziges Leer-
      zeichen zerstört das Bild.
í     Es müssen die passenden Kopfzeilen erzeugt werden, damit der Browser weiß,
      welcher Bildtyp zu den Binärdaten passt.
í     Das Skript sollte relativ schnell und effizient sein, sonst verzögert sich der Bild-
      aufbau erheblich.
Die passenden Kopfzeilen werden mit der Funktion header erzeugt. Die Ausgaben
selbst können wie üblich mit printf oder echo gesendet werden. Für header wird
die Angabe des Inhaltstyps (Content-type) benötigt. Der konkrete Typ richtet sich
nach der Bildart. Möglich sind folgende Werte:
í     image/gif
í     image/jpg
í     image/png
í     image/wbmp (für WAP)
Der Aufruf der Funktion sieht dann beispielsweise für ein PNG-Bild folgender-
maßen aus:
header ('Content-type: image/png');

            Es ist übrigens gut möglich, zusätzlich noch Cookies an das Bild anzu-
            hängen, was oft bei Werbebannern gemacht wird. Werbebanner sind
            letztlich auch nur Bilder (mal von Flash-Bannern abgesehen) und lassen
            sich sehr gut dynamisch erzeugen.

Typische Probleme
Gleich vorab soll auf einige typische Probleme hingewiesen werden. Da der Brow-
ser ein Bild erwatet, wird er eine Fehlerausgabe des Skripts nicht interpretieren


380
Dynamisch Bilder erzeugen


können. Der Internet Explorer zeigt beispielsweise ein Ersatzbild mit einem roten
Kreuz – aber eben keine Fehlermeldung. Hierfür gibt es mehrere Lösungen:
í   Erzeugen Sie im Fehlerfall andere Kopfzeilen, die die Ausgabe von Text erlau-
    ben.
í   Geben Sie ein (vorher korrekt programmiertes) Ersatzbild aus, das den Fehler
    anzeigt.
í   Verwenden Sie try/catch und den @-Operator, um Fehler abzufangen und die
    Fehlertexte in eine Protokolldatei zu schreiben.
Allen Varianten gemeinsam ist, dass die Fehlerausgabe mehr Aufwand verursacht
als bei normalen Skripten. Der erreichte Effekt einer dynamischen Bildausgabe ist
allerdings oft diesen Mehraufwand wert.


Einführung in die Grafikbibliothek GD2
Vor den ersten Versuchen mit der Grafikbibliothek GD2 sollten Sie testen, ob die
Erweiterung aktiviert wurde und zur Verfügung steht. Der Aufruf von phpinfo ist
der ideale Platz dafür.




                                                                    Abbildung 9.4:
                                                                    GD2 wurde erfolg-
                                                                    reich aktiviert


Prinzip der Bilderzeugung
Die Bilderzeugung erfolgt in zwei Stufen. Zuerst muss eine Zeichenfläche erstellt
werden, auf der anschließend alle Zeichen- und Textoperationen stattfinden.
Dazu dient die Funktion imagecreate:
$img = imagecreate(480, 80);



                                                                                  381
Professionelle Programmierung


Der Bildtyp, also GIF oder PNG, muss hier noch nicht festgelegt werden. Das Zei-
chenmodul arbeitet generell mit einem internen Format, aus dem alle anderen
Formate verlustfrei erstellt werden können.
Der Rückgabewert $img enthält nun ein Handle auf die interne Abbildung des Bil-
des. Alle folgenden Funktionen nutzen dieses Handle zum Zugriff.
Sind alle Bildoperationen fertig, wird das Bild erzeugt und an den Browser gesen-
det. Dazu dienen die Funktion imagegif, imagepng, imagejpeg usw.:
imagegif($img);
Der Suffix bestimmt dabei den Bildtyp, der sich je nach Applikation unterscheiden
kann.
Alternativ zur Erzeugung eines neuen Bilds kann die Bilddatei auf einem bereits
existierenden Bild basieren. Dazu dienen die Funktionen imagecreatefromgif,
imagecreatefrompng, imagecreatefromjpeg usw.
$img = imagecreatefromgif('pfad/zur/datei.gif');
Die Bildgröße wird dabei zwangsläufig von der Quelldatei bestimmt. Liegt das Bild
vor, kann man nun entweder Text darauf schreiben oder mit Bildfunktionen
malen. Die Vielfalt der Funktionen lässt eine ausführliche Betrachtung in diesem
Rahmen nicht zu, deshalb sollen zwei Beispiele das Prinzip demonstrieren.


Textausgabe
Die Textausgabe ist deshalb ein schwieriges Thema, weil der Text auf der Fläche
platziert werden muss, was nicht einfach ist, wenn die Menge nicht passt. Hier
muss gegebenenfalls gerechnet werden. Ausgangspunkt ist die Funktion
imagettfbbox, die die voraussichtlichen Maße des Textes in Abhängigkeit vom
Font (Schriftart) und der verlangten Größe ermittelt. Die Funktion gibt ein
Array zurück, dessen Elemente die entsprechenden Angaben enthalten:

Index      Beschreibung

0          Links oben, X

1          Links unten, Y
2          Rechts unten, X

Tabelle 9.2: Bedeutung der Elemente des mit imagettfbbox erzeugten Arrays



382
Dynamisch Bilder erzeugen



Index      Beschreibung

3          Rechts unten, Y

4          Oben rechts, X

5          Oben rechts, Y

6          Oben links, X
7          Oben links, Y
Tabelle 9.2: Bedeutung der Elemente des mit imagettfbbox erzeugten Arrays (Forts.)

Die Funktion kann einen Winkel einbeziehen, um den der Text gedreht
erscheint. Die Koordinaten, die den Platzbedarf angeben, sind jedoch immer abso-
lut in den Ecken eines virtuellen Rechtecks, das im Fall einer Drehung entspre-
chend groß ist, um den Text aufzunehmen.
Aus den Daten lässt sich nun entnehmen, ob der Text passt und man kann entspre-
chend reagieren, um den Text gegebenenfalls passend zu machen. Stimmt alles,
erfolgt die eigentliche Ausgabe mit imagettftext. Benötigt werden folgende Angaben:
í   Position der linken, oberen Ecke in zwei Werten für x und y.
í   Winkel, um den der Text gedreht erscheint.
í   Schriftart, in Form eines Pfades zu einer TrueType-Datei.
í   Schrifthöhe in Punkt (1 Punkt entspricht 1/72«).
í   Die Farbe, die benutzt werden soll.
í   Der Text, der dargestellt wird.
Die Schriftart wird als Pfad zu einer TrueType-Datei angegeben. Achten Sie hier
darauf, dass nur Dateien verwendet werden, zu denen auch die erforderlichen
Rechte vorhanden sind. Das Herauskopieren aus anderen Anwendungen funktio-
niert zwar meist, ist aber nur selten legal. Frei Fonts sind im Internet leicht zu fin-
den, wenngleich diese auch eine schwankende Qualität haben. Wer seinen Server
unter Windows betreibt hat es da leichter, denn er kann die typischen Windows-
Fonts nutzen, beispielsweise Verdana oder Tahoma.
Die Farbe wird als RGB-Wert erwartet, der vorher in der Farbtabelle der Bildbasis
registriert werden muss. Dabei geht die GD2-Bibliothek so vor, dass die erste Farb-
zuweisung immer die Hintergrundfarbe festlegt, während alle Folgenden der Farb-



                                                                                        383
Professionelle Programmierung


tabelle zugeordnet werden. Jede Zuweisung gibt ein Handle auf die Farbe zurück
und dieses Handle ist zu verwenden. Im folgenden Fragment ist $img das Handle
auf das Bild und $handle_black das Handle auf die Farbe (nach der Ausführung).
Die definierte Farbe ist schwarz:
$handle_black = imagecolorallocate($img, 0, 0, 0);
Die drei Werte stehen für die Farbkanäle Rot, Grün und Blau. Erlaubt sind Anga-
ben zwischen 0 (kein Anteil der Farbe) und 255 (voller Anteil).

          Um hexadezimale Angaben zu verwenden, bietet sich die Angabe von
          Hex-Literalen der Art 0xFF an.


Das folgende Beispiel erzeugt einen Text auf einer grauen Grundfläche:

Listing 9.3: imagettftext.php – Text auf einer Bildfläche ausgeben

?php
$size = 44;
$font = 'fonts/verdana.ttf';
$text = 'Überschrift';
$img = imagecreate(480, 80);
imagecolorallocate($img, 0xCC, 0xCC, 0xCC);
$handle_black = imagecolorallocate($img, 0, 0, 0);
imagettftext($img, $size, 0, 80, 65, $handle_black, $font, $text);
header ('Content-type: image/gif');
imagegif($img);
?
Das Skript wendet sehr direkt die zuvor beschriebenen Funktionen an. Es erzeugt
die folgende Ausgabe:


                                                                     Abbildung 9.5:
                                                                     Dynamisch
                                                                     erzeugtes Bild

Soweit das Skript nicht unmittelbar nach der Ausgabe endet, ist es empfehlens-
wert, den belegten Speicherplatz sofort freizugeben. Bedenken Sie, dass Bilder
erheblichen Speicherplatz in Anspruch nehmen können. Das sehr einfache Bild
des letzten Beispiels benötigt etwa 115 KByte.


384
Dynamisch Bilder erzeugen


Bildfunktionen
Ähnlich wie bei Text geht es auch bei den Bildfunktionen. Typische Operationen
umfassen hier das Zeichnen von Rechtecken, Kreisen, Linien oder Punkten. Teil
der Bildfunktionen ist aber auch das Verarbeiten anderer Bilder, das Filtern, Ver-
zerren, Kopieren, Zerschneiden usw.
Das folgende Beispiel zeichnet olympische Ringe (es ist während der Sommer-
olympiade 2004 in Athen entstanden):

Listing 9.4: imagerings.php – Dynamisches Zeichnen olympischer Ringe

?php
class Circle
{
   public $x;
   public $y;
   public $color;
   const RAD = 50;
   public function __construct($x, $y, $color)
   {
      $this-x = $x;
      $this-y = $y;
      $this-color = $color;
   }
}
$size = 14;
$font = 'fonts/verdana.ttf';
$text = 'Athen 2004';
$img = imagecreate(280, 180);
$white = imagecolorallocate($img, 0xFF, 0xFF, 0xFF);
$blue = imagecolorallocate($img, 0, 0, 0xFF);
$black = imagecolorallocate($img, 0, 0, 0);
$red = imagecolorallocate($img, 0xFF, 0, 0);
$yellow = imagecolorallocate($img, 0xFF, 0xFF, 0);
$green = imagecolorallocate($img, 0, 0x64, 0);
$pos = array(new Circle(70, 50, $blue),
             new Circle(105, 80, $yellow),
             new Circle(140, 50, $black ),
             new Circle(175, 80, $green),
             new Circle(210, 50, $red));
foreach ($pos as $point)



                                                                                   385
Professionelle Programmierung


{
      for ($i = 0; $i  15; $i++)
      {
         imagearc($img, $point-x, $point-y,               
                        Circle::RAD+$i, Circle::RAD+$i,                 
                        0, 360, $point-color);
      }
      imagearc($img, $point-x, $point-y,              
                     Circle::RAD+$i, Circle::RAD+$i,                
                     0, 360, $white);
}
header ('Content-type: image/gif');
imagegif($img);
?
Das Skript ist keineswegs perfekt, aber es zeigt das Prinzip und die Möglichkeiten.
Vor allem die korrekte Verschlingung der Ringe wäre weitaus aufwändiger. Die
Anwendung der Klasse Circle demonstriert dennoch, wie die Zeichenfunktionen
von objektorientierten Ansätzen profitieren können. Die Darstellung der Kreise als
Objekte vereinfacht das Skript erheblich und trägt wesentlich zur Lesbarkeit bei.




                                              Abbildung 9.6:
                                              Vereinfachte Version der olympischen Ringe,
                                              mit GD2 erstellt

Wenn Sie das Skript laufen lassen, können sie die farbige Version erleben, die der
Druck nur mangelhaft wiedergeben kann.


Anwendungsbeispiel »Dynamischer Werbebanner«
Das folgende Beispiel zeigt die Anwendung der GD2-Bibliothek anhand einer
Klasse, die dynamisch rechteckige Bereiche erstellt und mit Bildern, Farben und
Texten füllt. Anhand von weiteren Parametern kann man daraus beispielsweise
dynamisch Banner erstellen, deren Inhalt je nach Situation und Benutzer variiert.




386
Dynamisch Bilder erzeugen


Nutzung
Die Klasse erwartet die Werte als GET-Parameter. Dieser Teil lässt sich freilich
leicht anpassen. Die folgende Tabelle zeigt Namen und Bedeutung der Parameter:

Parameter     Bedeutung

W             Breite
H             Höhe

A             Ausrichtung, beispielsweise »left«
C             Farbe des Textes

FF            Font, der verwendet werden soll

FS            Schrifthöhe in Punkt
BG            Pfad zu einem Hintergrundbild. Das Bild bestimmt die Größe des Banners,
              sodass die Werte H und W ignoriert werden

BC            Hintergrundfarbe, wird nur akzeptiert, wenn kein Hintergrundbild angege-
              ben wird

B             Randbreite in Pixel

TXT           Der Text, der auf dem Banner angezeigt wird

Tabelle 9.3: Bedeutung der Parameter zur Steuerung der Grafikklasse


Quellcode der Klasse
Die Klasse nutzt konsequent die neuen Möglichkeiten von PHP5 und eignet sich
ideal für den Einbau in eigene Projekte. Ein kleines Testskript folgt am Ende.

Listing 9.5: gd2lib.inc.php – Hilfsklasse zum Erstellen von Bannern

?php
class NoticeException extends Exception
{
}
class ImagingException extends Exception
{



                                                                                        387
Professionelle Programmierung


      public function __construct($msg)
      {
         parent::__construct($msg);
      }
}
class LSImage
{
   private $intWidth;
   private $intHeight;
   private $strAlign;
   private $strColor;
   private $strFontFace;
   private $strFontSize;
   private $intBorder;
   private $strBackColor;
   private $strBackImage;
   private $strText;
   private $strType;
   private $im;
   private $dx;
   private $dy;

      const FONTPATH = 'fonts/';

      public function __construct($strSetType = 'png')
      {
         set_error_handler(array($this, 'throwError'));
         $this-strType = $strSetType;
         $arrURI = $_GET;
         if ($arrURI['LSF'] = 'image')
         {
            if (isset($arrURI['BG']))
            {
               $this-strBackImage = $arrURI['BG'];
            }
            else
            {
               $this-strBackColor= $arrURI['BC'];
            }
            $this-intWidth    = $arrURI['W'];
            $this-intHeight   = $arrURI['H'];
            $this-strAlign    = $arrURI['A'];



388
Dynamisch Bilder erzeugen


        $this-strColor    = $arrURI['C'];
        $this-strFontFace = self::FONTPATH
                           . strtolower($arrURI['FF']);
        if (substr($this-strFontFace, -4) != '.ttf')
           $this-strFontFace .= '.ttf';
        if (!file_exists ($this-strFontFace))
           $this-strFontFace = self::FONTPATH . 'verdana.ttf';
        $this-strFontSize = $arrURI['FS'];
        $this-intBorder   = $arrURI['B'];
        $this-strText=stripslashes(urldecode($arrURI['TXT']));
    }
}

private function throwError($errno, $err, $line, $file)
{
   throw new NoticeException($errno, $err, $line, $file);
}

private function getcolor($strColor)
{
    $r = hexdec(substr($strColor, 0, 2));
    $g = hexdec(substr($strColor, 2, 2));
    $b = hexdec(substr($strColor, 4, 2));
    return imagecolorallocate($this-im, $r, $g, $b);
  }

  private function CheckBackgroundSize()
  {
      if ($this-intWidth != imagesx($this-im)
          or $this-intHeight != imagesy($this-im))
      {
         return true;
      } else {
         return false;
      }
  }
private function create()
{
    $pi = pathinfo($this-strBackImage);
    $extension = isset($pi['extension']) ? $pi['extension'] : '';
    switch ($extension)
    {



                                                                            389
Professionelle Programmierung


        case 'jpg':
        case 'jpeg':
            $this-im       = imagecreatefromjpeg($this-strBackImage);
            break;
        case 'gif':
            $this-im       = imagecreatefromgif($this-strBackImage);
            break;
        case 'png':
            $this-im       = imagecreatefrompng($this-strBackImage);
            break;
        default:
            $this-im       = imagecreate($this-intWidth,     
                                          $this-intHeight);
      }
      if ($this-im === FALSE)
      {
         throw new ImageException('Kein Hintergrund und            
                                   keine Bildgröße angegeben');
      }
      if ($this-CheckBackgroundSize())
      {
         $bi = imagecreate($this-intWidth, $this-intHeight);
         if (function_exists('imagecopyresampled'))
             imagecopyresampled($bi, $this-im, 0, 0, 0, 0,            
                                imagesx($bi),
                                imagesy($bi),      
                                imagesx($this-im),        
                                imagesy($this-im));
         else
               imagecopyresized($bi, $this-im, 0, 0, 0, 0,            
                                imagesx($bi),      
                                imagesy($bi),      
                                imagesx($this-im),        
                                imagesy($this-im));
         $this-im = $bi;
      }
      $this-getcolor($this-strBackColor);
      $arr = imagettfbbox($this-strFontSize, 0,       
                          $this-strFontFace, $this-strText); //
      if ($arr === FALSE)
      {
         throw new ImagingException('TTF wird nicht unterstützt');



390
Dynamisch Bilder erzeugen


   }
   else
   {
     $this-dy = $this-intHeight -  
                   (($this-intHeight / 2) - (abs($arr[7]) / 2));
     $this-dy = $this-dy  0 ? $this-intHeight : $this-dy;
     switch (strtolower($this-strAlign))
     {
        case 'center':
           case 'middle':
           $this-dx = ($this-intWidth / 2) - ($arr[2] / 2);
           break;
        case 'right':
           $this-dx = $this-intWidth - $arr[2];
            break;
        case 'left':
        default:
             $this-dx = 0;
             break;
       }
   }
   $this-dx += $this-intBorder;
   for ($i = 0; $i  $this-intBorder; $i++)
   {
        imagerectangle($this-im, $i, $i, 
                        $this-intWidth-$i-1, 
                        $this-intHeight-$i-1, 
                        $this-getColor($this-strBackColor));
   }
}
public function image()
{
   try
   {
       $this-create();
       header( Content-type: image/ . $this-strType);
       $fg = $this-getcolor($this-strColor);
       imagettftext($this-im, (int) $this-strFontSize, 0,      
                    $this-dx, $this-dy, $fg, 
                     $this-strFontFace, $this-strText);
       call_user_func('Image' . $this-strType, $this-im);
       ImageDestroy($this-im);



                                                                           391
Professionelle Programmierung


          }
          catch (ImagingException $ex)
          {
             echo $ex-getMessage();
          }
      }
}

$objImg = new LSImage;
$objImg-image();
?
Das Skript beginnt mit der Erstellung zweier Fehlerklassen, damit die Fehler-
behandlung über die neue Ausnahmesteuerung mit try/catch erfolgen kann. Die
erste Klasse soll lediglich Warnungen erfassen:
class NoticeException extends Exception
Auslöser ist die Umleitung der klassischen Laufzeitfehler im Konstruktor:
set_error_handler(array($this, 'throwError'));
Tritt ein Fehler auf, wird die Methode throwError aufgerufen, die ihrerseits den
Fehler in eine Ausnahme verwandelt:
private function throwError($errno, $err, $line, $file)
{
   throw new NoticeException($errno, $err, $line, $file);
}
Das eigentliche Bilderzeugungsprogramm beginnt im Konstruktor mit der Über-
nahme der GET-Parameter in lokale Eigenschaften. Danach muss das Hauptpro-
gramm die Methode image aufrufen, um das Bild zu erstellen. Die Bilderzeugung
ist in einem try-Block untergebracht, um Ausnahmen abfangen zu können. Im
Fehlerfall werden dann andere Header erzeugt, was die Fehlersuche vereinfacht.
Die Ausführung der Bilderzeugung erfolgt in der privaten Methode create:
$this-create();
Gelingt die Bilderzeugung, wird der Text auf dem Bild platziert. Hier könnte man
Cache-Techniken ansetzen, um die mit create erzeugten Daten zu erhalten und
die aufwändige Erstellungsprozedur zu vermeiden.
Die Ausgabe vollzieht sich in mehreren Schritten. Zuerst wird die Vordergrund-
farbe ermittelt:
$fg = $this-getcolor($this-strColor);


392
Dynamisch Bilder erzeugen


Dann wird anhand der vorausberechneten Größen der Text im verlangten True-
Type-Font geschrieben:
imagettftext($this-im, (int) $this-strFontSize, 0,
             $this-dx, $this-dy, $fg,
             $this-strFontFace, $this-strText);
Dann wird die Kopfzeile für den Browser erzeugt, die den Bildtyp bestimmt:
header( Content-type: image/ . $this-strType);
Passend dazu wird der entsprechende Typ erzeugt und gesendet:
call_user_func('Image' . $this-strType, $this-im);
Der Funktionsaufruf »baut« aus dem Wort Image und dem Typ den Funktionsna-
men zusammen, also beispielsweise ImagePng. Anschließend wird das Bild zerstört,
um den belegten Speicher sofort freizugeben:
ImageDestroy($this-im);
Der eigentliche Bildaufbau findet in create statt. Es wird zunächst ermittelt, ob der
Pfad eines Hintergrundbildes vorliegt:
$pi = pathinfo($this-strBackImage);
$extension = isset($pi['extension']) ? $pi['extension'] : '';
Je nach Bildtyp erfolgt dann im switch-Zweig der Aufbau der Zeichenfläche aus
dem Hintergrundbild. Das Hintergrundbild bestimmt bis hierher die Bildgröße:
$this-im = imagecreatefromgif($this-strBackImage);
Nur wenn kein Hintergrundbild existiert, werden die Größen- und Breitenparame-
ter übernommen:
$this-im = imagecreate($this-intWidth, $this-intHeight);
Alle folgenden Funktionen beziehen sich dann auf das Handle in der Eigenschaft
$this-im. Stimmen Hintergrundbild und verlangte Bildgröße nicht überein, wird
das Hintergrundbild auf die erforderliche Größe skaliert. Dazu wird folgende
Funktion verwendet:
imagecopyresampled($bi, $this-im, 0, 0, 0, 0,
                   imagesx($bi), imagesy($bi),
                   imagesx($this-im), imagesy($this-im));
Die Basis für die Platzierung des Textes ist die Berechnung der erforderlichen
Größe:
$arr = imagettfbbox($this-strFontSize, 0, $this-strFontFace,
                    $this-strText);


                                                                                     393
Professionelle Programmierung


Je nach Ausrichtung wird dann der Startpunkt basierend auf den von imagettfbox
zurückgegebenen Werten berechnet. Für zentrierte Texte beispielsweise wird aus-
gehend von den Mittelpunkten die Verschiebung nach folgender Formel ermittelt:
$this-dx = ($this-intWidth / 2) - ($arr[2] / 2);
Für den Rand erfolgt die Generierung von Rechtecken, die dem Bild überlagert
werden. Die folgende Funktion erledigt dies auch für Randbreiten  1, wenn der
Aufruf in einer Schleife erfolgt und mit der Schleifenvariablen $i gesteuert wird:
imagerectangle($this-im, $i, $i,
               $this-intWidth-$i-1,
               $this-intHeight-$i-1,
               $this-getColor($this-strBackColor));
Die Randfarbe wird von der Hintergrundfarbe bestimmt. Der Einsatz ist vor allem
im Zusammenhang mit Hintergrundbildern sinnvoll.


Beispielaufruf
Das folgende Skript enthält ein IMG-Tag, das einen passenden Aufruf enthält:

Listing 9.6: gd2libTest.php – Test der Bibliothek aus einer HTML-Datei heraus

img src=http://localhost/MuT/
gd2lib.inc.php?W=480H=80A=leftTXT=PHP5%20und%20MySQL%20in%2014%20Tagen
C=FF0000B=2FF=VerdanaFS=17BC=CCCCCCBG=fonts/MuTBase.JPG
width=480 height=80/
Beachten Sie hier, dass der Text gegebenenfalls URL-kodiert werden muss. Die
Bibliothek sorgt für die entsprechende Dekodierung.

                                                                    Abbildung 9.7:
                                                                    Banner, der mit
                                                                    der Bibliothek
                                                                    erzeugt wurde.
                                                                    Der Text kann als
                                                                    Parameter ausge-
                                                                    tauscht werden.




394
Code röntgen: Die Reflection-API



9.3      Code röntgen: Die Reflection-API
PHP5 verfügt über eine Programmierschnittstelle, die die Analyse von Code
erlaubt. Dies ist sinnvoll, um aus anderen Programmen heraus die Eigenschaften
und den Aufbau von Klassen und Bibliotheken zu ermitteln. Hilfreich können
diese Techniken zum einen für die Entwickler von Editoren sein, zum anderen
aber auch für Bibliotheksprogrammierer, die flexibel auf veränderte Bedingungen
reagieren möchten und damit weniger Probleme mit Updates, Versionen und
Varianten haben. Ein dritter und sehr wichtiger Komplex ist die Entwicklung von
automatischen Code-Dokumentierern. Dies sind Programme, die Code analysie-
ren und daraus Quelltextdokumentationen erstellen.


Die Reflection-API als Objektmodell
Der Zugriff auf die Reflection-Informationen erfolgt über ein Objektmodell, das
folgenden Aufbau hat:
?php
class Reflection { }
interface Reflector { }
class Reflection_Exception extends Exception { }
class ReflectionFunction implements Reflector { }
class ReflectionParameter implements Reflector { }
class ReflectionMethod extends Reflection_Function { }
class ReflectionClass implements Reflector { }
class ReflectionProperty implements Reflector { }
class ReflectionExtension implements Reflector { }
?
Das Prinzip ist einfach. Um Informationen über alle Funktionen eines Skripts
herauszufinden, wird eine Instanz der Klasse ReflectionFunction erzeugt und
benutzt. Die Basisklasse Reflection stellt Hilfsfunktionen zur Verfügung, um die
ermittelten Daten auslesen zu können.

Listing 9.7: ReflectionBase.php – Die Struktur der internen Klasse Exception ermitteln

pre
?php
  Reflection::export(new ReflectionClass('Exception'));
?
/pre


                                                                                         395
Professionelle Programmierung


Diese Zeile erzeugt folgende Ausgabe:




                                                          Abbildung 9.8:
                                                          Ausgabe der Reflection-
                                                          Abfrage einer Klasse

Praktisch ist die Ausgabe eine Kopie der Definition, wobei alle theoretisch mög-
lichen Mitglieder des abgefragten Objekts – hier also einer Klasse – angezeigt wer-


396
Code röntgen: Die Reflection-API


den. Die gezeigte Klasse hat also keine Konstanten, keine statische Eigenschaften,
keine statische Methoden, sechs normale Eigenschaften und neun Methoden.


Die Reflection-Klassen im Detail
Der folgende Abschnitt zeigt die zur Verfügung stehenden Klassen, deren Defini-
tion und einfache Anwendungsbeispiele.


Informationen über Funktionen ermitteln
Mit ReflectionFunction werden Funktionen analysiert. Die folgende Tabelle zeigt
die zur Verfügung stehenden Methoden, die jeweils konkrete Informationen über
die Funktion zurückgeben.

Methode                          Bedeutung
__construct(string name)         Konstruktor, erwartet den Namen einer Funktion
string getName()                 Gibt den Namen der Funktion zurück
bool isInternal()                Wahr, wenn dies eine eingebaute Funktion ist
bool isUserDefined()             Wahr, wenn dies eine benutzerdefinierte Funktion ist
string getFileName()             Den Namen der Datei, wo diese Methode definiert ist
int getStartLine()               Den Namen der Zeile, wo die Definition beginnt
int getEndLine()                 Den Namen der Zeile, wo die Definition endet
string getDocComment()           Kommentare, die die Funktion dokumentieren
array getStaticVariables()       Array der statischen Variablen
mixed invoke(mixed* args)        Führt die Funktion mit bestimmten Argumenten aus
string toString()                Zeichenkettendarstellung der Funktion
bool returnsReference()          Wahr, wenn der Rückgabewert eine Referenz ist
Reflection_Parameter[]           Die Parameter der Funktion
getParameters()

Tabelle 9.4: Methoden der Klasse ReflectionFunction



                                                                                          397
Professionelle Programmierung


Um eine Funktion zu untersuchen, muss natürlich erst ein Objekt der Klasse
erzeugt werden.


Informationen über Parameter ermitteln
Da Funktionen oft Parameter haben, besteht ein enger Zusammenhang zwischen
ReflectionFunction und ReflectionParameter. Das folgende Beispiel zeigt beide
Klassen in der Anwendung:

Listing 9.8: ReflectionFunction.php – Analyse einer Funktion mit Parametern

/**
  * Eine einfache Zählfunktion
  *
  * @return     int
  */
function counter($debug, $multiplier = 6)
{
     static $c = 1;
     echo $debug;
     return $c++ * $multiplier;
}
$func= new ReflectionFunction('counter');
printf(
      === Die %s Function'%s'n.
           deklariert in %sn.
           von Zeile %d bis %dn,
      $func-isInternal() ? 'interne' : 'benutzerdefinierte',
      $func-getName(),
      $func-getFileName(),
      $func-getStartLine(),
      $func-getEndline()
);
printf(--- Dokumentation:n %sn,
         var_export($func-getDocComment(), 1));
if ($statics = $func-getStaticVariables())
{
      printf(--- Statische Variablen: %sn,
             var_export($statics, 1));
}
foreach ($func-getParameters() as $name = $param)



398
Code röntgen: Die Reflection-API


{
    printf(Parameter '%s' %s [%s]n,
           $name,
           $param-getName(),
           $param-isPassedByReference() ? '' : '');
}
printf(nn--- Aufrufergebnisse: );
var_dump($func-invoke(array('Test', 2)));
»Reflektiert« wird hier die Funktion counter. Das Ergebnis zeigt alle erforderlichen
Details, um die Funktion zu verstehen:




                                                 Abbildung 9.9:
                                                 Ergebnis der Analyse einer benutzer-
                                                 definierten Funktion


Vor allem Programme, die Dokumentationen automatisch erstellen, können
davon erheblich profitieren.


Daten einer Klasse ermitteln
Mit Hilfe der Klasse ReflectionClass lassen sich andere Klassen – eingebaute oder
eigene – analysieren. Das folgende Skript versucht diese Analyse möglichst umfas-
send. Es verwendet einige der bereitgestellten Methoden:

Listing 9.9: ReflectClass.php – Ermittlung von Daten einer Klasse

interface Serializable
{      // ...
}
class Object



                                                                                         399
Professionelle Programmierung


{      // ...
}
class Counter extends Object implements Serializable
{
   const START= 0;
   private static $c= Counter::START;
   public function count()
   {
      return self::$c++;
   }

      public static function ReflectMe($config)
      {
          $class = new Reflection_Class($config);
          printf(
                === Die %s%s%s %s '%s' [extends %s]n.
                      deklariert in %sn.
                      von Zeile %d bis %dn.
                      mit dem Modifikatoren %d [%s]n,
               $class- isInternal() ? 'interne' : 'benutzerdefinierte',
                $class-isAbstract() ? ' abstrakte' : '',
                $class-isFinal() ? ' finale' : '',
                $class-isInterface() ? 'Schnittstelle' : 'Klasse',
                $class-getName(),
             var_export($class-getParentClass(), 1),
             $class-getFileName(),
             $class-getStartLine(),
             $class-getEndline(),
             $class-getModifiers(),
             implode(' ',
                  Reflection::getModifierNames($class-getModifiers()))
             );
          printf(--- Dokumentation:n %sn,
                   var_export($class-getDocComment(), 1));
          printf(--- Implementiert:n %sn,
                   var_export($class-getInterfaces(), 1));
          printf(--- Konstanten: %sn,
                   var_export($class-getConstants(), 1));
          printf(--- Eigenschaften: %sn,
                   var_export($class-getProperties(), 1));
          printf(--- Methoden: %sn,
                   var_export($class-getMethods(), 1));



400
Code röntgen: Die Reflection-API


       if ($class-isInstantiable())
       {
          $counter = $class-newInstance();
          echo '--- $counter ist Instanz? ';
          var_dump($class-isInstance($counter));
          echo '--- new Object() ist Instanz? ';
          var_dump($class-isInstance(new Object()));
       }
   }
}
Counter::ReflectMe('Counter');
Die Funktion newInstance kann mit einer variablen Anzahl von Argumenten auf-
gerufen werden, die durch den Konstruktor der analysierten Klasse bestimmt wer-
den. Es wird eine Warnung ausgegeben, wenn die Instanziierung nicht korrekt
erfolgt.
Folgende Sequenz ermittelt, ob ein Objekt eine Instanz einer bestimmten Klasse
ist:
$class= new Reflection_Class('Foo');
$class-isInstance($arg)
Diese Information kann auch folgendermaßen gewonnen werden:
if ($arg instanceof Foo)


Information über Methoden einer Klassen ermitteln
Die Informationen über eine Klasse enthalten bereits die Namen der Methoden.
Das folgende Beispiel zeigt die Anwendung:

Listing 9.10: ReflectMethod.php – Daten über eine Methode ermitteln

class Counter
{
   private static $c= 0;
   /**
    * Zähler erhöhen
    *
    * @final
    * @static
    * @access public
    * @return int


                                                                                       401
Professionelle Programmierung




                                              Abbildung 9.10:
                                              Informationen über eine Klasse und
                                              deren Mitglieder


        */
      final public static function increment()
      {
           self::$c++;
           return self::$c;
      }
}
// Create an instance of the Reflection_Method class
$method= new ReflectionMethod('Counter', 'increment');
// Print out basic information



402
Code röntgen: Die Reflection-API


printf(
   === Die %s%s%s%s%s%s%s Methode '%s' (welche %s ist)n.
         Deklariert in %sn.
         von Zeile %d bis %dn.
         hat die Modifikatoren %d[%s]n,
$method-isInternal() ? 'interne' : 'benutzerdefinierte',
$method-isAbstract() ? ' abstrakte' : '',
$method-isFinal() ? ' finale' : '',
$method-isPublic() ? ' öffentliche' : '',
$method-isPrivate() ? ' private' : '',
$method-isProtected() ? ' geschützte' : '',
$method-isStatic() ? ' statische' : '',
$method-getName(),
$method-isConstructor() ? 'ein Konstruktor'      
                          : 'eine reguläre Methode',
$method-getFileName(),
$method-getStartLine(),
$method-getEndline(),
$method-getModifiers(),
implode(' ',  
         Reflection::getModifierNames($method-getModifiers())));
printf(--- Dokumentation:n %sn, var_export($method-getDocComment(),
1));
if ($statics= $method-getStaticVariables())
{
     printf(--- Statische Variables: %sn, var_export($statics, 1));
}
printf(--- Aufrufergebnisse: );
var_dump($method-invoke(NULL));
Das Aufrufen einer privaten oder geschützten (protected) Methode führt auch
über Reflektion zu einem Fehler. Das ist durchaus unverständlich, weil ein derarti-
ger Schutz vor allem ungewollte Zugriffen verhindert. Der explizite Einsatz einer
Reflektionstechnik benötigt jedoch einen derartigen Schutz nicht. Der Zugriff auf
Methoden verlangt als erstes Argument eine Instanz der Klasse. Bei statischen Auf-
rufen gibt es diese Instanz nicht, deshalb wird hier der Wert NULL angegeben.
Klassen haben neben Methoden meist auch Eigenschaften. Neben der einfachen
Liste, die bereits die Reflektion der Klasse erbrachte, können auch Detaildaten
ermittelt werden. Das folgende Beispiel zeigt, wie mit ReflectionProperty gear-
beitet wird.




                                                                                      403
Professionelle Programmierung




                                                 Abbildung 9.11:
                                                 Informationen über eine Methode



Listing 9.11: ReflectMethodProps.php – Eigenschaften einer Klasse

class PropTest
{
   public $number = 5;
}
$prop= new ReflectionProperty('PropTest', 'number');
printf(
     === Die %s%s%s%s Eigenschaft '%s'
           (welche %s deklariert wurde)n.
          hat die folgenden Modifizierer %sn,
     $prop-isPublic() ? ' öffentlich' : '',
     $prop-isPrivate() ? ' privat' : '',
     $prop-isProtected() ? ' geschüttz' : '',
     $prop-isStatic() ? ' statisch' : '',
     $prop-getName(),
     $prop-isDefault() ? 'zur Kompilierzeit' : 'zur Laufzeit',
     var_export(Reflection::getModifierNames(       
                $propgetModifiers()),       
  1)
);
$obj= new PropTest();
print(--- Der Wert ist: );
var_dump($prop-getValue($obj));
$prop-setValue($obj, 10);
print(--- Setze den Wert 10, der Wert ist jetzt: );
var_dump($prop-getValue($obj));
var_dump($obj);
Das Aufrufen einer privaten oder geschützten (protected) Methode führt auch
über Reflektion zu einem Fehler.




404
Code röntgen: Die Reflection-API




                                                 Abbildung 9.12:
                                                 Aufruf einer Methode über Reflection


Daten über PHP-Erweiterungen ermitteln
Mit ReflectionExtension lassen sich Informationen über Erweiterungen ermit-
teln. Das ist manchmal hilfreich, um die Kompatibilität eines Skripts zu sichern
oder die Konfiguration eines entfernten Computers, beispielsweise beim Provider,
zu untersuchen.
Zuerst müssen alle vorhandenen Erweiterungen ermittelt werden, was mit dem fol-
genden Beispiel demonstriert wird.

Listing 9.12: ReflectGetExtension.php – Eine Erweiterung untersuchen

$aExtensions = get_loaded_extensions();
foreach ($aExtensions as $num = $name)
{
    echo $num. $name br;
}
Hat man einmal die Namen, kann eine weitere Untersuchung mit Reflection-
Extension erfolgen.
Die einzelnen Daten können sehr umfangreich werden; die im folgenden Beispiel
gezeigte umfassende Darstellung einer Erweiterung dient mehr der Demonstra-
tion der Möglichkeiten. In der Praxis dürfte die gezielte Abfrage einer bestimmten
Teilinformation sinnvoller sein. Um konkret festzustellen, ob eine Erweiterung
vorhanden ist, wird am besten in_array eingesetzt und dann die Ausgabe gestartet:

Listing 9.13: ReflectGetExtensionDetails.php – Details von PHP-Erweiterungen ermitteln

$aExtensions = get_loaded_extensions();
if (in_array('SQLite', $aExtensions))
{
    echo Erweiterung 'sqlite' existiertbr;
    $ext= new ReflectionExtension('sqlite');


                                                                                        405
Professionelle Programmierung




                Abbildung 9.13:
                Liste der geladenen Erweiterungen
                einer typischen PHP-Installation

      printf(
          Name         : %sn.
          Version      : %sn.
          Funktionen : [%d] %sn.
          Konstanten : [%d] %sn.
          INI-Einträge : [%d] %sn,
          $ext-getName(),
          $ext-getVersion() ? $ext-getVersion() : 'NO_VERSION',
          sizeof($ext-getFunctions()),
          var_export($ext-getFunctions(), 1),
          sizeof($ext-getConstants()),
          var_export($ext-getConstants(), 1),
          sizeof($ext-getINIEntries()),
          var_export($ext-getINIEntries(), 1)
      );
}
Die Ausgabe zeigt die Klassen der Erweiterung. Mit den bereits vorgestellten Tech-
niken der Reflektion kann man so schnell alle Details ermitteln und damit Edito-
ren oder Debugger steuern, Code robuster gestalten oder einfach nur lernen.
Damit stehen alle wichtigen Techniken zur Verfügung, Code per Skript zu analy-
sieren, zu dokumentieren und fremde Bibliotheken im Kontext korrekt zu verar-
beiten.


406
Funktions-Referenz GD2




                                                                                Abbildung 9.14:
                                                                                Ausschnitt aus der
                                                                                Beschreibung
                                                                                einer einzigen
                                                                                Extension
                                                                                (SQLite)



9.4        Funktions-Referenz GD2

Funktion                              Bedeutung
exif_imagetype                        Ermittelt Daten über ein vorhandenes Bild.
exif_read_data                        Liest eingebettet EXIF-Daten aus einer JPG- oder TIF-
                                      Datei. Solche Daten werden beispielsweise von Digital-
                                      kameras erzeugt.
exif_thumbnail                        Extrahiert in JPG- oder TIF-Dateien eingebettete Thumb-
                                      nail*-Dateien.
gd_info                               Beschafft Informationen über die Bibliothek selbst.

Tabelle 9.5: Funktions-Referenz für GD2-Funktionen
*   Verkleinertes Abbild der Hauptdatei.




                                                                                              407
Professionelle Programmierung



Funktion                            Bedeutung
getimagesize                        Ermittelt die Ausmaße einer GIF-, JPEG-, PNG- oder
                                    SWF-Grafik-Datei.
image_type_to_mime_type             Ermittelt die MIME-Typen für getimagesize,
                                    exif_read_data, exif_thumbnail, exif_imagetype.

image2wbmp                          Gibt das Bild im BMP-Format an den Browser aus.
imagealphablending                  Setzt den Modus für Alpha-Blending.
imageantialias                      Schaltet das Antialiasing ein oder aus.
imagearc                            Zeichnet eine Teil-Ellipse.
imagechar                           Stellt ein Zeichen mit horizontaler Ausrichtung dar.
imagecharup                         Zeichnet einen vertikal ausgerichteten Charakter.
imagecolorallocate                  Bestimmt die Farbe einer Grafik.
imagecolorallocatealpha             Bestimmt die Farbe unter Verwendung eines Alpha-
                                    Werts.
imagecolorat                        Ermittelt den Farbwert eines Bildpunktes.
imagecolorclosest                   Ermittelt den Farbwert-Index, der den angegebenen
                                    Farben am nächsten liegt.
imagecolorclosestalpha              Gibt den Index der Farbe in der Farbtabelle, die dem
                                    angegebenen Wert am Nächsten liegt.
imagecolorclosesthwb                Gibt den Index der Farbe in der Farbtabelle, die dem
                                    angegebenen Wert am Nächsten liegt. Als Werte werden
                                    Helligkeit, Sättigung und Kontrast verwendet.
imagecolordeallocate                Löscht eine Farbdefinition.
imagecolorexact                     Ermittelt den Index-Wert der angegebenen Farbe.
imagecolorexactalpha                Index einer Farbe.
imagecolormatch                     Versucht die Werte der Farbtabelle in Übereinstimmung
                                    mit den tatsächlichen Farben zu bringen.
Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.)




408
Funktions-Referenz GD2



Funktion                       Bedeutung
imagecolorresolve              Ermittelt den Index-Wert der angegebenen Farbe oder
                               die nächstmögliche Alternative dazu.
imagecolorresolvealpha         Ermittelt den Index einer Farbe oder – wenn die Farbe
                               nicht existiert – die bestmögliche Entsprechung.
imagecolorset                  Setzt die Farbe für den angegebenen Paletten-Index.
imagecolorsforindex            Ermittelt die Farbwerte einer angegebenen Farb-Palette.
imagecolorstotal               Ermittelt die Anzahl der definierten Farben eines Bildes.
imagecolortransparent          Definiert eine Farbe als transparent
imagecopy                      Kopiert einen Bildausschnitt
imagecopymerge                 Kopiert einen Bildausschnitt und verbindet mit einem
                               anderen.
imagecopymergegray             Kopiert einen Bildausschnitt und verbindet mit einem
                               anderen mit Graustufen.
imagecopyresampled             Kopiert einen Bildausschnitt und verbindet mit einem
                               anderen mit erneuter Kompression.
imagecopyresized               Kopiert einen Bildausschnitt und verbindet mit einem
                               anderen mit Größenänderung.
imagecreate                    Erzeugt ein neues Bild.
imagecreatefromgd2             Erzeugt ein neues Bild aus GD2-Rohdaten.
imagecreatefromgd2part         Erzeugt ein neues Bild aus GD2-Rohdaten und Fragmen-
                               ten eines Bilds.
imagecreatefromgd              Erzeugt ein neues Bild aus GD-Rohdaten.
imagecreatefromgif             Erzeugt ein neues Bild aus GIF-Daten.
imagecreatefromjpeg            Erzeugt ein neues Bild aus JPG-Daten.
imagecreatefrompng             Erzeugt ein neues Bild aus PNG-Daten.
imagecreatefromstring          Erzeugt ein neues Bild aus einem Stream.
Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.)



                                                                                       409
Professionelle Programmierung



Funktion                            Bedeutung
imagecreatefromwbmp                 Erzeugt ein neues Bild aus WBMP-Daten.
imagecreatefromxbm                  Erzeugt ein neues Bild aus XBM-Daten.
imagecreatefromxpm                  Erzeugt ein neues Bild aus XPM-Daten.
imagecreatetruecolor                Erzeugt ein neues Bild mit voller Farbunterstützung.
imagedashedline                     Zeichnen einer gestrichelten Linie.
imagedestroy                        Löscht ein Bild.
imageellipse                        Zeichnete eine Ellipse oder einen Kreis.
imagefill                           Füllen mit Farbe (»flood fill«).
imagefilledarc                      Zeichnet einen gefüllten elliptischen Kreisbogen.
imagefilledellipse                  Zeichnet eine gefüllte Ellipse.
imagefilledpolygon                  Zeichnet ein gefülltes Vieleck (Polygon).
imagefilledrectangle                Zeichnet ein gefülltes Rechteck.
imagefilltoborder                   Flächen-Farbfüllung (»flood fill«) mit einer angegebenen
                                    Farbe bis zum nächsten Rand.
imagefontheight                     Ermittelt die Font-Höhe.
imagefontwidth                      Ermittelt die Font-Breite.
imageftbbox                         Ergibt Daten über die Fläche, die ein TrueType-Text in
                                    Anspruch nimmt.
imagefttext                         Schreibt Text in das Bild.
imagegammacorrect                   Anwendung einer Gamma-Korrektur auf ein GD-Bild.
imagegd2                            Ausgabe im internen GD2-Format.
imagegd                             Ausgabe im internen GD-Format.
imagegif                            Ausgabe eines Bildes an den Browser oder in eine Datei .

Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.)




410
Funktions-Referenz GD2



Funktion                       Bedeutung
imageinterlace                 Schaltet die Interlaced-Darstellung eines Bildes an oder
                               aus.
imageistruecolor               Ermittelt, ob das Bild Vollfarbunterstützung hat.
imagejpeg                      Ausgabe des Bildes im Browser oder als Datei als JPG.
imagelayereffect               Setzt Alpha-Blending mit Layer-Effekten.
imageline                      Zeichnen einer Linie.
imageloadfont                  Lädt einen neuen Font.
imagepalettecopy               Kopiert eine Farbpalette.
imagepng                       Ausgabe des Bildes im Browser oder als Datei als PNG.
imagepolygon                   Zeichnen eines Vielecks (Polygons).
imagepsbbox                    Ermittelt die Ausmaße des Rechtecks, das für die Ausgabe
                               eines Textes unter Verwendung eines PostScript-Fonts
                               (Typ 1) notwendig ist.
imagepscopyfont                Erstellt eine Kopie eines bereits geladenen Fonts für wei-
                               tere Veränderungen.
imagepsencodefont              Ändert die Vektor-Beschreibung eines Fonts.
imagepsextendfont              Vergrößert oder komprimiert einen Font.
imagepsfreefont                Gibt den durch einen Typ 1 PostScript-Font belegten
                               Speicher wieder frei.
imagepsloadfont                Lädt einen Typ 1 PostScript-Font aus einer Datei.
imagepsslantfont               Setzt einen Font schräg.
imagepstext                    Ausgabe eines Textes auf einem Bild unter Verwendung
                               von Typ 1 PostScript-Fonts.
imagerectangle                 Zeichnet ein Rechteck.
imagerotate                    Rotiert ein Bild um einen Winkel.
imagesavealpha                 Speichert Alpha-Kanal-Informationen.

Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.)


                                                                                       411
Professionelle Programmierung



Funktion                            Bedeutung
imagesetbrush                       Setzt ein Bild, das als Füllung für Linien dient.
imagesetpixel                       Setzt ein einzelnes Pixel.
imagesetstyle                       Setzt einen Stil für Linien.
imagesetthickness                   Setzt die Breite für Linien und andere Figuren.
imagesettile                        Setzt ein kachelbares Bild für Füllungen.
imagestring                         Zeichnet eine horizontalen Zeichenfolge.
imagestringup                       Zeichnet eine vertikale Zeichenfolge.
imagesx                             Ermittelt die Bild-Breite.
imagesy                             Ermittelt die Bild-Höhe.
imagetruecolortopalette             Konvertiert eine Vollfarbpalette in ein Palettenbild.
imagettfbbox                        Ermittelt die Rahmenmaße für die Ausgabe eines Textes
                                    im TrueType-Format.
imagettftext                        Erzeugt TTF-Text im Bild.
imagetypes                          Gibt die von der aktuell verwendeten PHP-Version unter-
                                    stützten Grafik-Formate zurück.
imagewbmp                           Gibt das Bild im WBMP-Format aus.
iptcembed                           Bettet binäre IPTC-Informationen ins Bild ein.
iptcparse                           Holt binäre IPTC-Informationen aus einem Bild ein.
                                    Informationen zu IPTC sind unter http://www.iptc.org/ zu
                                    finden.
jpeg2wbmp                           Konvertiert JPEG nach WBMP.
png2wbmp                            Konvertiert PNG nach WBMP.
read_exif_data                      Liest die EXIF Header-Infos einer JPEG-Grafik

Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.)




412
Kontrollfragen



9.5     Kontrollfragen
1. Welche Servervariable wird benutzt, um die bevorzugte Sprache des Benutzers zu
   ermitteln?
2. Welcher Header wird in HTTP benötigt, um dem Browser anzuzeigen, dass ein
   Bild gesendet wird?
3. Schreiben Sie ein Skript, dass dezimale Farbangaben wie 255, 192, 128 in hexa-
   dezimale Zeichenfolgen der Art »#FFCC99« umrechnet und umgekehrt.
4. Wozu dient das Werkzeug Reflection?




                                                                                413
Kommunikation per
 HTTP, FTP und
    E-Mail



     1 0
Kommunikation per HTTP, FTP und E-Mail



10.1 Konzepte in PHP5
Bedingt durch die Vielzahl möglicher Datenquellen kommt der Server-Server-
Kommunikation immer stärkere Bedeutung zu. PHP5 stellt eine ganze Reihe von
Methoden zum Zugriff auf andere Server zur Verfügung, einiges davon ist völlig
neu. Damit ergeben sich spannende Möglichkeiten für die Nutzung von PHP5.
Einige Anregungen finden Sie hier:
í     Holen von News-Daten von einem anderen Server (HTTP)
í     Senden von Status-Informationen an einen anderen Server (HTTP)
í     Versenden von Formularinhalten per E-Mail (SMTP)
í     Aktualisierung von Inhalten auf einem Download-Server (FTP)
In Klammern finden Sie die jeweils bevorzugten Protokolle.


Prinzip der Streams und Wrapper
PHP5 nutzt ein einheitliches Konzept zur Organisation des Zugriffs auf Daten, so
genannte Wrapper. Dies sind protokollähnliche Bezeichner (im Englischen oft als
Moniker, Spitznamen, bezeichnet):
wrapper://dateiname
Ein Wrapper gilt als Standard: file://, diese Angabe kann entfallen. Das vereinfacht
den Zugriff auf lokalen Dateien, die besonders häufig benötigt werden. Wrapper
finden immer dann Anwendung, wenn Daten von einer Datenquelle geholt oder
in eine Datenquelle geschrieben werden. Das gilt beispielsweise für sämtliche
Dateifunktionen. Statt also für neue Zugriffsarten (per HTTP oder FTP) immer
neue Bibliotheken zu verwenden, bietet PHP5 einen vereinheitlichten Weg des
Zugriffs an. Damit die Verwaltung intern funktioniert, werden ergänzend so
genannte Streams eingesetzt. Diese bietet elementare Zugriffsfunktionen wie Öff-
nen, Lesen, Schreiben, Schließen. Wenn man eigene Wrapper entwickeln will –
auch dies ist Teil des universellen Konzepts – programmiert man praktisch diese
Stream-Funktion für einen spezifischen Fall.




416
Konzepte in PHP5


Die eingebauten Wrapper
PHP5 kommt mit einigen eingebauten Wrappern daher, die für die meisten Zwe-
cke ausreichend sind. Konkret sind dies:
í   file://Pfad/Datei
    Die Angabe file:// kann entfallen, sodass normale Pfadangaben weiterhin nutz-
    bar sind. Als mögliche Varianten sind relative Pfade (/pfad/name.ext), absolute
    Pfade (D:pfadname.ext oder /pfad/pfad/datei.ext) oder Netzwerkshares (ser-
    verfreigabename) erlaubt. Wenn die Angabe mit file:// erfolgt und unter Win-
    dows Laufwerkbuchstaben adressiert werden, sind dies mit | abzutrennen.
í   http://url, https://url
    Um auf andere Webserver zuzugreifen, werden diese Wrapper benutzt. https://
    baut eine verschlüsselte Verbindung auf. Der URL wird in der übliche Form
    angegeben. Um Name und Kennwort für eine geschützte Verbindung anzuge-
    ben, wird die Form http://name:kennwort@url benutzt.
í   ftp://url, ftps://url
    Um auf andere Server per FTP zuzugreifen, werden diese Wrapper benutzt.
    ftps:// baut eine verschlüsselte Verbindung auf. Um Name und Kennwort für
    eine geschützte Verbindung anzugeben, wird die Form ftp://name:kenn-
    wort@url benutzt.
í   php://channel
    Um auf die internen Ein- und Ausgabekanäle des Betriebssystems zuzugreifen,
    bietet PHP einen speziellen Wrapper an. Zulässige Kanalnamen sind »stdin«
    (Eingabe, beispielsweise Tastaturabfragen am Systemprompt), »stdout« (Aus-
    gabe), »stderr« (Fehlerausgaben), »output« (Ausgabepuffer, der von echo, print
    usw. verwendet wird), »input« (Rohdaten aus einem Formular bei POST-
    Anforderungen und »filter« (benutzerspezifische oder eingebaute Filter, die
    Daten während der Übertragung verändern).
í   compress.zlib://, compress.bzip2://
    Komprimiert oder dekomprimiert die Daten während der Übertragung nach
    dem ZIP-Verfahren. Beim Schreiben in eine Datei entstehen GZ-Dateien,
    keine Archive.




                                                                                 417
Kommunikation per HTTP, FTP und E-Mail



Filter
Filter werden im Stream eingebunden, um – für den Aufrufer transparent – Daten
während der Übertragung zu ändern. Der Vorteil liegt in der Anwendung. Der
Benutzer eines Filters arbeitet weiter wie gewohnt mit Standardfunktionen wie
fopen und fread. Dabei gibt er lediglich an, einen bestimmten Filter benutzen zu
wollen. Wie dieser funktioniert, eingebunden wird und wie dieser konkret mit den
Daten interagiert, bleibt völlig im Hintergrund. Eingebaute Filter können fol-
gende Aktionen ausführen (zuvor der Name des Filters):
í     string.rot13
      Eine ROT13-Kodierung, bei der die Buchstaben um 13 Stellen im Alphabet
      verschoben werden, um eine sehr schwache Verschlüsselung zu erreichen.
í     string.toupper
      Verwandelt alle Zeichen in Großbuchstaben.
í     string.tolower
      Verwandelt alle Zeichen in Kleinbuchstaben.
í     string.strip_tags
      Entfernt alle Tags aus dem Datenstrom.
í     convert.base64-encode
      Konvertiert die Daten in das Base64-Format.
í     convert.base64-decode
      Konvertiert die Base64-Daten aus dem Base64-Format in eine lesbare Form.
í     convert.quoted-printable-encode
      Konvertiert die Daten in das Quoted-Printable-Format (in E-Mails benutzt).
í     convert.quoted-printable-decode
      Konvertiert die Quoted-Printable-Daten aus dem Quoted-Printable-Format in
      eine lesbare Form.
Damit die Filter wirklich universell sind, kann man auch bei der Angabe von
php://filter wiederum Pfade angeben, die ihrerseits die Wrapper http:// oder ftp://
verwenden.




418
Streams und Wrapper anwenden



10.2 Streams und Wrapper anwenden
Die Anwendung der Wrapper ist relativ einfach. Eigentlich müssen Sie lediglich
mit den Dateifunktionen umgehen können, die bereits ausführlich vorgestellt
wurden.


Daten von einer fremden Website beschaffen
Das folgende Skript holt sich Daten von einer fremden Website. Es liest die Daten
ein und ermittelt die Inhalte bestimmter META-Tags, wenn diese vorhanden sind.
Das klingt weitaus aufregender, als es ist:

Listing 10.1: wrapperhttp.php – Zugriff mit Standardfunktionen auf Webserver

?php
$url = 'http://www.apache.org';
$tags = get_meta_tags($url);
if (count($tags)  0)
{
    foreach($tags as $name = $content)
    {
        echo strtoupper($name);
        echo  = $contentbr;
    }
}
?
Der eigentliche Aufwand – also das Herstellen der Verbindung – erledigt der Wrap-
per. Die Filterung und Ausgabe derw META-Tags erfolgt mit Hilfe der Funktion
get_meta_tags. Die Anwendung unterscheidet sich nicht von der beim Lesen nor-
maler Dateien. Allerdings kann die Ausführzeit des Skripts etwas länger sein, da
einige Zeit für den Verbindungsaufbau und die Übertragung der Seite benötigt wird.

                                        Abbildung 10.1:
                                        Ausgabe der META-Tags der
                                        Website www.apache.org

          Sie sollten außerdem auf eine Fehlerbehandlung achten, da Internet-
          Verbindungen aus den verschiedensten Gründen fehlschlagen können.



                                                                                    419
Kommunikation per HTTP, FTP und E-Mail


Das folgende Skript liest den Inhalt einer Webseite direkt ein und speichert die
Seite (ohne Bilder oder andere Ressourcen) lokal ab:

Listing 10.2: wrapperhttppage.php – Zugriff mit Dateifunktionen

?php
$url = 'www.apache.org';
$page = file_get_contents(?http://$url?);
if (strlen($page)  0)
{
    $file = data/$url.htm;
    $f = fopen($file, 'w');
    fwrite($f, $page);
    fclose($f);
    echo 'Dateigrouml;szlig;e: ' . filesize($file) . ' Byte';
}
?
Auch hier wird lediglich eine Standardfunktion, file_get_contents, benutzt. Zur
Kontrolle wird noch die Dateigröße angezeigt. Zum Testzeitpunkt waren dies
stolze 12 KByte.


Daten komprimiert speichern
Weitaus kompakter geht es, wenn man die Daten komprimiert speichern kann.
Das folgende Beispiel verwendet einen weiteren Wrapper beim Speichern der
Datei, der das erledigt:

Listing 10.3: wrapperhttpcompress.php – Abspeichern einer Datei in komprimierter
Form

?php
$url = 'www.apache.org';
$page = file_get_contents(http://$url);
if (strlen($page)  0)
{
    $file = data/$url.gz;
    $f = fopen(compress.zlib://$file, 'w');
    fwrite($f, $page);
    fclose($f);
    echo 'Dateigrouml;szlig;e: ' . filesize($file) . ' Byte';
}
?


420
Streams und Wrapper anwenden


Das Ergebnis unterscheidet sich vor allem im Platzverbrauch von der vorherigen
Version. Die Startseite der Apache Group belegt nun nur noch 3,6 KByte. Eine
verbesserte Version liest die Datei wieder aus und zeigt sie zeilenweise an. Dabei
wird wiederum ein Wrapper benutzt, der auf die abgespeicherte Version zugreift.

Listing 10.4: wrappedecompress.php – Komprimierte Datei erzeugen und dekom-
primieren

?php
$url = 'www.apache.org';
$page = file_get_contents(http://$url);
if (strlen($page)  0)
{
    $file = data/$url.gz;
    $f = fopen(compress.zlib://$file, 'w');
    fwrite($f, $page);
    fclose($f);
    echo 'Dateigrouml;szlig;e: ' . filesize($file) . ' Byte';
    echo 'brbr';
    $farray = file(compress.zlib://$file);
    foreach($farray as $num = $content)
    {
        printf('%03d: %sbr /', $num, htmlspecialchars($content));
    }
}
?
Hier wird eine weitere Dateifunktion, file, benutzt und diesmal reagiert derselbe
Wrapper in genau umgekehrter Weise. Da die Daten gelesen werden, erwartet er
komprimierte Daten und diese werden folgerichtig wiederhergestellt. Der eigentli-
che Vorgang des Komprimierens und Dekomprimierens ist für den Anwender
transparent – man muss weder bei der Benutzung noch bei der Wahl der Funk-
tionen darauf Rücksicht nehmen.
Damit ist zum Thema Wrapper eigentlich schon fast alles gesagt. Der Fantasie
sind beim Anwenden kaum Grenzen gesetzt. Vor allem aber kann man kompli-
zierte Zugriffe mit den Socket-Funktionen meist vermeiden.




                                                                                  421
Kommunikation per HTTP, FTP und E-Mail



                                                      Abbildung 10.2:
                                                      Komprimiert und dennoch
                                                      direkt lesbar: Wrapper machen’s
                                                      möglich




10.3 Filter verwenden
Das folgende Skript holt sich wie bereits beim vorhergehenden Beispiel Daten von
einer fremden Website. Es liest im Gegensatz dazu nur den Inhalt der Seite, also
ohne HTML-Tags. Dies kann beispielsweise für eine Indizierung sinnvoll sein.
Anstatt also die Ausgabe mit htmlspecialchars sichtbar zu machen, soll nun ein
Filter die Arbeit erledigen.
Wie bereits angedeutet, basieren Filter auf der Wrapper-Syntax und haben fol-
gende Struktur:
php://filter:/AKTION=NAME
Aus den verfügbaren Filtern kann man nun wählen. string.strip_tags ist dabei von
der Funktion mit strip_tags vergleichbar, spart jedoch den expliziten Aufruf die-
ser Funktion.

Listing 10.5: wrapperfilter.php – Filtern von Daten während des Lesevorgangs

?php
$url = 'www.apache.org';
$page = file_get_contents(http://$url);
if (strlen($page)  0)
{


422
Filter verwenden


     $file = data/$url.gz;
     $f = fopen($file, 'w');
     fwrite($f, $page);
     fclose($f);
     echo 'Dateigrouml;szlig;e: ' . filesize($file) . ' Byte';
     echo 'brbr';
     $farray =
       file(php://filter/read=string.strip_tags/resource=$file);
     foreach($farray as $num = $content)
     {
         if (strlen(trim($content)) == 0) continue;
         printf('%03d: %sbr /', $num, $content);
     }
}
?
Die einzige Ergänzung zu den vorhergehenden Beispielen besteht in der Angabe
des Filters:
file(php://filter/read=string.strip_tags/resource=$file)
Die Syntax wird transparent, wenn man das zuvor gezeigte Muster zu Rate zieht.
php://filter leitet die Sequenz ein. Danach folgen bis zu drei Aktionen, die ent-
sprechende Parameter verlangen:
í    resource=DATENQUELLE
     Diese Angabe ist zwingend erforderlich. Nutzen Sie für die Angabe von
     DATENQUELLE wieder die bereits bekannten Wrapper.
í    read
     Geben Sie an, welche Filteraktion beim Lesen ausgeführt werden soll. Der
     Abschnitt »Filter« auf Seite 418 zeigte die möglichen Filter bereits.
í    write
     Geben Sie an, welche Filteraktion beim Schreiben ausgeführt werden soll.
     Der Abschnitt »Filter« auf Seite 418 zeigte die möglichen Filter bereits.
Es bietet sich nun an, alle Möglichkeiten miteinander zu kombinieren, was tat-
sächlich funktioniert.

Listing 10.6: wrapperfiltercompress.php – Filtern und Komprimieren in einem Schritt

?php
$url = 'www.apache.org';
$page = file_get_contents(http://$url);


                                                                                    423
Kommunikation per HTTP, FTP und E-Mail


if (strlen($page)  0)
{
    $file = data/$url.gz;
    $f = fopen(compress.zlib://$file, 'w');
    fwrite($f, $page);
    fclose($f);
    echo 'Dateigrouml;szlig;e: ' . filesize($file) . ' Byte';
    echo 'brbr';
    $farray = file(php://filter/read=string.strip_tags/
                    resource=compress.zlib://$file);
    foreach($farray as $num = $content)
    {
        if (strlen(trim($content)) == 0) continue;
        printf('%03d: %sbr /', $num, $content);
    }
}
?
Der Aufruf des Filters sieht nun folgendermaßen aus:
php://filter/read=string.strip_tags/resource=compress.zlib://$file
Der Wrapper compress.zlib:// wurde in die resource-Aktion eingebaut. Die Ausgabe
zeigt die Wirkung des Filters und zugleich die erzeugte Dateigröße an:




                                           Abbildung 10.3:
                                           Komprimieren und Filtern in einem Schritt

Damit sind die eingebauten Anwendungen erschöpft. In der Praxis dürfte das für
die meisten Fälle ausreichen.


424
Die Stream-Funktionen



10.4 Die Stream-Funktionen
Generell kann man mit den Stream-Funktionen, auf denen Wrapper und Filter
basieren, sehr viel mehr anstellen. Sie sind aber recht komplex in der Anwendung.
Sie bieten einen generellen und vereinheitlichten Weg für den Umgang mit
Datenübertragungen.


Eigene Wrapper und Filter
Es gibt allerdings noch die Möglichkeit, eigene Wrapper und natürlich auch
eigene Filter zu schreiben. Diese tauchen dann unter eigenem Namen auf. Dies
ist jedoch ein deutlich größerer Aufwand und lohnt sich vermutlich nur für Biblio-
theksentwickler. Die Darstellung sprengt auch den Rahmen dieses Buches. Wenn
Sie eigene Versuche in dieser Richtung unternehmen möchten, müssen Sie für
einen eigenen Wrapper eine Klasse schreiben, die auf den Stream-Funktionen auf-
baut, also stream_open zum Öffnen, stream_read zum Lesen und stream_close
zum Schließen, um nur einige zu nennen. Diese Klasse wird dann mit
stream_register_wrapper in Verbindung mit einem eigenen Moniker registriert.
Auch eigene Filter lassen sich entwickeln. Dies funktioniert etwas anders. Hier
müssen Sie eine Klasse erstellen, die von php_user_filter erbt. Dort ist dann die
Methode filter zu schreiben. Mit Hilfe der bereits für Wrapper benutzten Stream-
Funktionen erfolgt dann der Zugriff auf den Datenstrom, der beim Durchlaufen
der filter-Methode verändert werden kann.


Anwendung spezifischer Stream-Funktionen
Zusätzlich zu den vorgestellten Techniken gibt es hier noch den Begriff des
Stream-Kontexts. Dies sind Parametersammlungen, die einer Stream-verarbeiten-
den Instanz übergeben werden, um das Verhalten global zu beeinflussen.
Beim Zugriff mittels http:// (als Beispiel) werden Sie sicher schnell bemerken, dass
sich bestimmte Seiten nicht ohne weiteres benutzen lassen. Möglicherweise feh-
len einige Kopfzeilen der HTTP-Anforderung, wie beispielsweise die akzeptierte
Sprache.
Die meisten Dateifunktionen erlauben es, einen so genannten Kontext-Parameter
anzugeben.



                                                                                   425
Kommunikation per HTTP, FTP und E-Mail


Listing 10.7: streamcontext.php – Parameter für Wrapper

?php
$opts = array(
       'http'=array(
           'method'=GET,
           'header'=Accept-language: dern));
$context = stream_context_create($opts);
$fp = fopen('http://www.google.com', 'r', false, $context);
fpassthru($fp);
fclose($fp);
?
Das Skript übermittelt an den Wrapper http zwei Informationen: Die Methode für
den Abruf ist GET (dies ist auch die Standardmethode) und die akzeptierte
Sprache. Im Beispiel wird die Startseite von Google abgerufen. Nutzt man
www.google.com (nicht »de«), versucht Google die Sprache dem Parameter Accept-
language zu entnehmen und schaltet auf die entsprechende Landesversion um.



                                                               Abbildung 10.4:
                                                               Google auf
                                                               Deutsch – Statt
                                                               per Browser über
                                                               einen parametri-
                                                               sierten Wrapper
                                                               aufgerufen

Damit lassen sich Webseiten recht komfortabel steuern und nutzen.

          Beachten Sie jedoch unbedingt die Urheberrechte und täuschen Sie
          niemals fremde Inhalt vor. Vereinbaren Sie besser Kooperationen mit
          den Anbietern der Datenquellen und profitieren Sie so von den unbe-
          grenzten Möglichkeiten des Webs.



10.5 E-Mail versenden
Das Versenden von E-Mail gehört zu den häufiger benötigten Aufgaben. Es dient
auf fast jeder Website dazu, die Nutzerkommunikation zu verbessern. PHP5 unter-

426
E-Mail versenden


stützt E-Mail auf vielfältige Weise, angefangen von der einfachen mail-Funktion,
über Socket-Programmierung für die Kommunikation per POP3 bis hin zu ganzen
Bibliotheken für IMAP-Postfächer.


Grundlagen
Auch wenn das einfache Versenden von E-Mail verhältnismäßig leicht zu pro-
grammieren ist, sollten Sie einige elementare Grundlagen beherrschen. Dies hilft,
bei größeren Projekten den richtigen Ansatz zu finden und schneller zu brauch-
baren Ergebnissen zu kommen. Bedenken Sie auch, dass eine Server-zu-Server-
Kommunikation immer auch fremde Maschinen beeinflusst. Dem sauberen Pro-
tokollfluss kommt deshalb eine große Bedeutung zu.
Generell werden in der E-Mail-Kommunikation sowohl verschiedene Protokolle
als auch Kodierungsverfahren verwendet. Als Transportprotokolle kommen zum
Einsatz:
í   SMTP
    SMTP (Simple Mail Transfer Protocol) dient der Weiterleitung von E-Mail von
    einem Server zu einem anderen. Es dient auch dazu, die erstmalig auf einem
    Client erzeugte E-Mail an den ersten Server der Übertragungskette weiterzu-
    leiten.
í   POP3
    POP3 (Post Office Protocol, Version 3) ruft E-Mail von einem Server ab, um
    diese auf einem Client dem Benutzer zur Verfügung zu stellen.
í   IMAP4
    IMAP4 (Internet Message Access Protocol, Version 4) ist ein Protokoll zum
    Empfang von E-Mail (wie POP3) und zur Verwaltung der E-Mails eines
    Clients auf einem Server. Das vollständige Herunterladen, wie bei POP3 erfor-
    derlich, ist nicht notwendig.
Im Internet kommen praktisch nur SMTP und POP3 zum Einsatz. Clients wie
Outlook benutzen beide Protokolle, SMTP zum Senden und POP3 zum Empfan-
gen.
Als Kodierungsverfahren stehen folgende zur Auswahl:
í   UUEncode
    Der Name steht für Unix-to-Unix-Encode, ein Kodierverfahren aus der Unix-
    Welt. Das Verfahren ist inzwischen relativ selten anzutreffen, war in der


                                                                               427
Kommunikation per HTTP, FTP und E-Mail


      Anfangszeit des Internet jedoch das am weitesten verbreitete. Viele Clients
      beherrschen es heute noch.
      Vorteile bietet es bei der Kodierung von binären Daten über Transportsysteme,
      die nur 7-Bit-Zeichen verarbeiten können. Dateien werden um ca. 40% vergrö-
      ßert.
      PHP5 verfügt über Funktionen zur Kodierung und Dekodierung dieses Ver-
      fahrens.
í     Quoted Printable
      Diese Verfahren wird häufiger benutzt. Es lässt den Text praktisch unverändert
      und kodiert nur Sonderzeichen, beispielsweise wird das Leerzeichen zu =20.
      Das Verfahren eignet sich nur für die Kodierung von Text, nicht für Binär-
      daten für Anhänge.
      PHP5 verfügt über Funktionen zur Kodierung und Dekodierung dieses Ver-
      fahrens.
í     Base64
      Ein anderes Verfahren, dessen Schwerpunkt auf Dateianhängen liegt. Es
      kodiert ebenfalls auf 7-Bit und vergrößert die Daten dabei um 37%. Es wird
      heute am häufigsten eingesetzt, weil es das Standardverfahren für Anhänge ist,
      die nach MIME (siehe unten) verpackt werden.
      PHP5 verfügt über Funktionen zur Kodierung und Dekodierung dieses Ver-
      fahrens.
í     BinHex
      Ein Verfahren aus der Apple-Welt, das sich außerhalb dieser nicht durchsetzen
      konnte. Gute Clients sollten es beherrschen. Das Verfahren komprimiert,
      sodass die ursprüngliche Größe trotz der Umsetzung auf 7-Bit oft unterschrit-
      ten wird.
      PHP5 verfügt nicht über Funktionen zur Kodierung und Dekodierung dieses
      Verfahrens.
í     MIME
      Ein globaler Standard (Multipurpose Internet Mail Extensions), der nicht selbst
      ein reines Kodierverfahren beschreibt, sondern eine vollständige Anweisung
      zum Aufbau kompletter Nachrichten ist. Zur Kodieren von Binärdaten wird
      das bereits erwähnt Base64 empfohlen. MIME beschreibt den Aufbau der
      E-Mail einschließlich der Art der Verpackung von Text, Anhängen, Informati-
      onszeilen (Kopfzeilen) usw.


428
E-Mail versenden



Vorbereitung
Wenn Sie auf einem Entwicklungssystem die E-Mail-Funktion ausprobieren wol-
len, müssen Sie über einen SMTP-Server verfügen. Der SMTP-Server Ihres Inter-
net-Providers ist vermutlich nicht nutzbar, weil externe Zugriffe eine Autorisierung
verlangen, um den Missbrauch durch SPAM-Versender zu vermeiden. Dies ist mit
zusätzlichem Aufwand verbunden. Meist verlangen die SMTP-Server, dass unmit-
telbar zuvor ein autorisierter POP3-Zugriff erfolgt.
Als Lösung können Sie lokal einen eigenen SMTP-Server betreiben. Hier gibt es
mehrere Varianten. Windows 2000 Professional, Server, Windows Server 2003 und
Windows XP Professional werden mit den Internet-Informationsdiensten geliefert,
die einen SMTP-Server enthalten. Der Server ist sehr einfach zu konfigurieren
und harmoniert bestens mit PHP. Die Windows 9X/Me- und XP Home-Varianten
sind leider außen vor. Hier muss man freie Programme bemühen, die es zuhauf
gibt. Linux kommt ebenfalls mit dem integrierten Sendmail-Programm. Hier ist
die Konfiguration etwas aufwändiger, außerdem erwartet PHP bei der Nutzung
von Sendmail, dass es selbst auf Linux läuft.
Steht der SMTP-Server, muss PHP noch ein wenig dafür konfiguriert werden.

          Wenn Sie einen SMTP-Server für Windows benötigen, weil Sie mit XP
          Home oder Windows 9x/Me arbeiten, schauen Sie sich den ArGoSoft
          Mail Server an:
          http://www.argosoft.com/applications/mailserver/
          Läuft er lokal, ist als Name des SMTP-Servers localhost anzugeben.


Konfiguration für Windows
In der php.ini sind einige Parameter zu setzen:
SMTP = localhost
smtp_port = 25
Der erste, SMTP, bezeichnet die Adresse des SMTP-Server. Geben Sie den Netzwer-
knamen, die IP-Adresse oder einen vollqualifizierten Domänennamen ein. Der
zweite, smtp_port, bezeichnet den Port, an dem der SMTP-Server läuft. 25 ist der
Standardport.




                                                                                  429
Kommunikation per HTTP, FTP und E-Mail


Dann ist noch die Absenderadresse zu setzen:
sendmail_from = webmaster@domain.net
Ohne diese Angabe wird die Funktion mail nicht arbeiten.


Konfiguration für Linux
Unter Linux erledigt nicht PHP in Kombination mit SMTP die Arbeit, sondern
alleine Sendmail. Damit entfallen die für Windows erforderlichen Konfigurations-
schritte. Dafür müssen Sie Sendmail konfigurieren, wozu der folgende Parameter
dient:
sendmail_path = -t –i
Welche Parameter hier nötig und möglich sind, ist der Dokumentation zu Send-
mail zu entnehmen. Sie hängen außerdem von der konkreten Installation ab.


Praktische Umsetzung
Das Versenden von E-Mail mit PHP ist sehr einfach. Voraussetzung ist allerdings
eine Installation, in der PHP einen SMTP-Server erreichen kann, wie zuvor
beschrieben. Wird beim Provider gehostet, ist dies in der Regel gewährleistet.


Einfache E-Mails versenden
Eine einfache E-Mail zu versenden basiert auf nur einer einzigen Funktion: mail.
Die Funktion kennt mehrere Parameter:
í     to
      Eine Zeichenkette, die die Mail-Adresse angibt, an die gesendet werden soll.
      Sie können mehrere Adressen angeben und durch Kommata trennen. Beach-
      ten Sie aber, dass die Empfänger dann alle Adressen sehen können. Besser ist
      es, Blind Carbon Copy zu verwenden (siehe unten).
í     subject
      Eine Zeichenkette, die als Betreff benutzt wird.
í     message
      Eine Zeichenkette, die die eigentliche Nachricht darstellt.




430
E-Mail versenden


í   header
    Zusätzliche benutzerdefinierte Kopfzeilen, die das Verhalten der E-Mail
    beeinflussen. Hier können Angaben zur Priorität, Cc (Carbon Copy), Bcc
    (Blind Carbon Copy) usw. eingetragen werden. Die Kopfzeilen müssen im
    Rohformat geschrieben werden. Die Angabe des Parameters ist optional.
í   parameters
    Diese Option ist nur anwendbar, wenn PHP auf Linux läuft und zum Versen-
    den Sendmail. Geben Sie dann Parameter für Sendmail an. Es ist möglich, die
    Verwendung dieser Option in der Datei php.ini zu sperren. Deshalb kann der
    Einsatz bei einem Massenhoster nicht garantiert werden.
Die Funktion gibt einen Booleschen Wert zurück, der den Erfolg repräsentiert.
Außerdem wird eine Fehlermeldung angezeigt, wenn etwas schief läuft. Da Feh-
lermeldungen meist stören und die E-Mail-Übertragung in der Praxis durchaus
ihre Tücken hat, ist ein Abfangen und Auswerten der Fehler unerlässlich. Das fol-
gende Skript zeigt, wie das aussieht:

Listing 10.8: sendmail.php – E-Mail versenden und auf mögliche Fehler reagieren

?php
ini_set('track_errors', 1);
$to = 'test@test.net';
$subject = 'Test-Nachricht';
$message = 'Dies ist ein Test, ob E-Mail geht.';
if (PHP_OS=='WINNT')
{
     ini_set('sendmail_from', 'joerg@krause.net');
       $from = '';
}
else
{
     $from = From:joerg@krause.netn;
}
$success = @mail($to, $subject, $message, $from);
if ($success)
{
   echo Mail an $to versendet;
}
else
{
   echo $php_errormsg;



                                                                                   431
Kommunikation per HTTP, FTP und E-Mail


}
?
Folgendes ist zu beachten. Wenn Sie mit Windows arbeiten und den Windows-
SMTP-Server verwenden, muss die Angabe »From:« in der Datei php.ini einge-
stellt werden. Um dies zu vereinfachen, wird der Parameter im Skript dynamisch
gesetzt:
ini_set('sendmail_from', 'test@test.net');
Außerdem wird das Abfangen des Fehlers programmiert. Dazu ist zuerst die Feh-
lerverfolgung in PHP5 einzuschalten:
ini_set('track_errors', 1);
Beim Senden wird der mail-Funktion ein @ zum Unterdrücken des Fehlers voran-
gestellt:
$success = @mail($to, $subject, $message, $from);
Die Variable $success enthält nun im Fehlerfall FALSE. Darauf kann entsprechend
reagiert werden. Trat ein Fehler auf, wird die interne Fehlervariable
$php_errormsg benutzt:
echo $php_errormsg;

                                                          Abbildung 10.5:
                                                          Fehlerausgabe des Pro-
                                                          gramms: Hier war der
                                                          Mail-Server falsch kon-
                                                          figuriert


Programmierung mit Kopfzeilen
Um weitere Verwendungsmöglichkeiten zu erkunden, müssen Sie sich mit den
Kopfzeilen beschäftigen. Diese werden als vierter Parameter in der folgenden
Form angegeben:
Name: Wertern
Dabei ist Name durch den Namen, beispielsweise »From« zu ersetzen und
Werte durch die von dieser Kopfzeile verlangten Parameter:
From: test@absender.dern
rn bezeichnet einen Zeilenumbruch, der in der E-Mail zum Abtrennen der
Kopfzeilen benötigt wird.


432
E-Mail versenden


           Die Zeilenendezeichen rn werden unter Windows verwendet. Wenn
           der SMTP-Server unter Unix läuft, ist meist n ausreichend, manchmal
           erforderlich. Probieren Sie Ihr Skript sorgfältig aus, bevor Sie es auslie-
           fern.

Die folgende Tabelle zeigt einige typische Kopfzeilen, die Sie verwenden können:

Name            Parameterbeispiel                Bedeutung

To                                               Empfänger, kann auch eine Liste sein
From            test@test.de Testname          Absender. Die Angabe in spitzen
                                                 Klammern ist optional. Die Klammern
                                                 selbst müssen hier geschrieben wer-
                                                 den, wenn die Angabe erfolgt.
Cc              test@test.de, weiter@weiter.de   Kopie an. Liste von Empfängern, die
                                                 die E-Mail auch erhalten und die
                                                 sichtbar ist (Cc = Carbon copy).
                                                 Trennung durch Kommata.

Bcc             test@test.de, weiter@weiter.de   Kopie an. Liste von Empfängern, die
                                                 die E-Mail auch erhalten und die
                                                 nicht sichtbar ist (Bcc = Blind carbon
                                                 copy). Trennung durch Kommata.
Date            Mon, 16 Aug 2004 11:29:28 +02 Datum, das beim Empfänger als das
                                              Absendedatum angezeigt wird. Nutzen
                                              Sie am einfachsten date('r').

Reply-To                                         Gewünschte Antwortadresse. Fehlt die
                                                 Angabe, wir »To« verwendet.

X-Mailer        PHP-Mail Version 5               Ein Hinweis, welches Programm zum
                                                 Versenden benutzt wurde.

X-Priority      1                                Priorität (niedrig, hoch, …) in der all-
X-MSMail-       High                             gemeinen Variante und in einer für
Priority                                         Outlook/Outlook Express

MIME-           1.0                              Version der MIME-Kodierung
Version
Tabelle 10.1: Namen und Parameter typischer Kopfzeilen für E-Mails



                                                                                       433
Kommunikation per HTTP, FTP und E-Mail



Name              Parameterbeispiel                     Bedeutung

Content-type      multipart/alternative                 Inhaltstyp nach MIME-Kriterien

Content-          base64                                Kodierungsverfahren für Anhänge
Transfer-
Encoding

Content-          attachment;ntfile-                  Bezeichnung eines Anhangs
Disposition       name=anhang.txt
Tabelle 10.1: Namen und Parameter typischer Kopfzeilen für E-Mails (Forts.)

Dies ist nur eine kleine Auswahl der Möglichkeiten. Sie reicht aber für die häufigs-
ten Fälle aus:
í     Versenden von HTML-Mails
í     Versenden von Anhängen


Anhänge versenden
Anhänge und HTML werden in ähnlicher Weise versendet. Die Kodierung folgt
dem MIME-Standard. Dabei wird der Inhalt der Nachricht in mehrere Blöcke auf-
geteilt, die jeweils eigene Kopfzeilen haben und zusätzlich durch spezielle Gren-
zen voneinander getrennt sind. Die Kopfzeilen helfen dem Client, die Nachricht
wieder zusammenzubauen. Binäre Daten, wie beispielsweise Bilder, werden meist
Base64-kodiert, damit sie auch über 7-Bit-Übertragungssysteme geliefert werden
können. Beachten Sie, dass sich Nachrichten dabei um bis zu 30% vergrößern
können.
Zuerst ein Beispiel: eine Klasse, die Anhänge versenden kann:

Listing 10.9: sendattachments.php – Anhänge zusammen mit E-Mail versenden

?php
class MailException extends Exception
{
}
class SendHtmlMail
{
   private function getMime($filename)
   {


434
E-Mail versenden


    $types = array(
      .jpg = image/jpeg,
      .jpeg = image/jpeg,
      .png = image/png );
    $filename = basename($filename);
    $ext = strtolower(substr($filename, strrpos($filename, .)));
    return $types[$ext];
}

public function sendMail($to, $from, $headers, $subject,
                         $message, $attachments=)
{
  $bound = uniqid(time()._);
  $header = From: $fromrn;
  $content = '';

    foreach($headers as $head = $value)
      $header .= $head: $valuern;

    if(is_array($attachments))
    {
      $header .= MIME-Version: 1.0rn;
      $header .= Content-Type: multipart/mixed;
                  boundary=$boundrn;
      $content .= --$boundrn;
      $content .= Content-Type: text/html;
                   charset=iso-8859-1rn;
      $content .= Content-Transfer-Encoding:
                   8bitrnrn$messagernrn;
      foreach($attachments as $attch)
      {
        if($fp = fopen($attch, rb))
        {
          $bn = basename($attch);
          $content .= --$boundrn ;
          $content .= Content-Type: .$this-getMime($attch).;
                       name=$bnrn ;
          $content .= Content-Transfer-Encoding: base64rn;
          $content .= Content-Disposition: inline;
                       filename=$bnrnrn ;
          $content .= chunk_split(base64_encode(fread($fp,
                                   filesize($attch)))).rn;



                                                                         435
Kommunikation per HTTP, FTP und E-Mail


           fclose($fp);
         }
         else
         {
           throw new MailException(Fehler, kann Anhang $attch
                                    nicht öffnen.);
         }
       }
       $content .= --$bound--rn;
      }
      else
      {
        $content .= Content-Type: text/html;
                     charset=iso-8859-1rn;
        $content .= Content-Transfer-Encoding:
                     8bitrnrn$messagernrn;
      }
      ini_set('track_errors', 1);
      if (!@mail($to, $subject, $content, $header))
      {
        throw new MailException($php_errormsg);
      }
    }
}
$sm = new SendHtmlMail();
$to = 'joerg@krause.net';
$from = 'joerg@krause.net';
$headers = array('X-Mailer:' = 'PHP '.phpversion());
$msg = 'Test für Anhänge';
$subject = 'Test Nr. 5';
$attachment = array('data/Telefon.jpg', 'data/7650Nokia.jpg');
try
{
  $sm-sendMail($to, $from, $headers, $subject, $msg, $attachment);
  echo An: $to, Von: $frombrKopfzeilen: ;
  print_r($headers);
}
catch (MailException $ex)
{
  die($ex-getMessage());
}
?



436
E-Mail versenden


Das Skript verwendet die neuen objektorientierten Möglichkeiten in PHP5. So
wird eine eigene Ausnahmenklasse MailException definiert, um Ausnahmen
gezielt abfangen zu können:
class MailException extends Exception
Die Klasse ist noch recht einfach. Als Anhänge sind nur Bilder zulässig, deren
MIME-Typ die private Methode getMime ermittelt:
private function getMime($filename)
Für JPEG-Bilder ist der MIME-Typ beispielsweise »image/jpeg«. Der MIME-
Standard verlangt die Vereinbarung einer eindeutigen Zeichenkette zur Trennung
von Datenblöcken. Eine sichere Methode ist die Nutzung der folgenden Funk-
tionen:
$bound = uniqid(time()._);
Daraus entstehen Zeichenfolgen wie beispielsweise »1092655372_4120990c6010f«.
Nach der Generierung der Kopfzeilen, die hier bequemerweise als Array über-
geben werden, sind die Anhänge als Nachrichtenblöcke aufbereitet dem Text der
E-Mail anzufügen. Zuerst wird dabei geprüft, ob Anhänge vorliegen. Die Methode
erwartet diese als ein Array aus Dateinamen:
if(is_array($attachments))
Ist das der Fall, soll MIME verwendet werden. Diese Angabe gehört in den Hea-
der:
$header .= MIME-Version: 1.0rn;
Dazu muss mitgeteilt werden, dass mehrere Blöcke verschiedenen Inhalts (mixed)
folgen:
$header .= Content-Type: multipart/mixed; boundary=$boundrn;
In dieser Zeile wird auch die Blockgrenze mit Hilfe des Parameters boundary fest-
gelegt.
Der Inhalt beginnt nun mit einem solchen Abgrenzungszeichen. Die beiden
Minuszeichen leiten die Grenze ein und sind zwingend erforderlich:
$content .= --$boundrn ;
Vor die Anhänge wird der Text der Nachricht gestellt. Die Klasse ist in der Lage,
auch gleich HTML-E-Mails zu versenden:
$content .= Content-Type: text/html; charset=iso-8859-1rn;




                                                                               437
Kommunikation per HTTP, FTP und E-Mail


Wenn Sie Text versenden möchten, nutzen Sie den MIME-Typ »text/text« anstatt
»text/html«. Der Zeichensatz erlaubt die Verwendung des üblichen westeuropä-
ischen Standardzeichensatzes für Windows, sodass auch Umlaute übertragen wer-
den.
Dann folgt die Kodierung und die Nachricht selbst:
$content .= Content-Transfer-Encoding: 8bitrnrn
             $messagernrn;
Hier wird nichts zusätzlich kodiert, da es sich nur um ASCII handelt. Das Übertra-
gungsmedium wird aber angewiesen, nach Möglichkeit mit 8 Bit zu übertragen,
damit die erweiterten Zeichen des Zeichensatzes erhalten bleiben.
Nun beginnt der Zusammenbau der Anhänge. Jeder Anhang wird einzeln erstellt.
foreach($attachments as $attch)
Zuerst wird die Datei im Binärmodus geöffnet:
if($fp = fopen($attch, rb))
Dann wird der Basisname ermittelt. Diese Angabe wird übertragen, während der
lokale Pfad, aus dem die Dateien gelesen werden, dem Empfänger nicht mitgeteilt
wird:
$bn = basename($attch)
Der Anhang wird dann wieder mit einem Grenzzeichen eingeleitet:
$content .= --$boundrn ;
Es folgen drei Kopfzeilen, die den Anhang beschreiben. Sie sehen nach der Verar-
beitung folgendermaßen aus:
Content-Type: image/jpg; name=telefon.jpg
Content-Transfer-Encoding: base64
Content-Disposition: inline; filename=telefon.jpg;
Nun muss der Anhang noch gemäß den MIME-Kriterien zerlegt und kodiert wer-
den. Die folgenden Funktionen erledigen dies:
chunk_split(base64_encode(fread($fp, filesize($attch))))
Lesen Sie den Ablauf von innen nach außen:
1. fread liest die Datei komplett, die Dateigröße wird dabei mit filesize ermit-
   telt




438
E-Mail versenden


2. base64_encode kodiert die Datei mit dem Base64-Verfahren (wie in der Kopf-
   zeile gefordert)
3. chunk_split zerlegt den Datenstrom in Blöcke, die verhindern, dass Mail-Ser-
   ver störende Leerzeilen oder Umbrüche einfügen
Am Ende aller Anhänge wird eine weitere Grenze eingefügt, die mit zwei Minus-
zeichen abschließt und so dem Mailclient mitteilt, dass keine weiteren Anhänge
folgen:
$content .= --$bound--rn;
Der Rest ist trivial. Die E-Mail wird versendet und der Erfolg mit einer if-Anwei-
sung überwacht:
if (!@mail($to, $subject, $content, $header))
Trat ein Fehler auf, wird eine Ausnahme ausgelöst:
throw new MailException($php_errormsg);
Das Hauptprogramm definiert die nötigen Angaben zum Betrieb der Klasse. Die
einzige öffentliche Methode sendMail macht die Verwendung extrem einfach.

                                                                  Abbildung 10.6:
                                                                  E-Mail mit zwei
                                                                  Anhängen in
                                                                  einem E-Mail-
                                                                  Client, mit der
                                                                  Klasse SendHtml-
                                                                  Mail versendet

Um HTML zu versenden, muss der Nachrichtenkörper lediglich eine gültige
HTML-Seite enthalten, also wenigstens mit den Tags html/html umschlossen
sein.




                                                                  Abbildung 10.7:
                                                                  HTML-Mail, mit
                                                                  demselben Pro-
                                                                  gramm versendet




                                                                                439
Kommunikation per HTTP, FTP und E-Mail


Andere Lösungen
Das Versenden von HTML oder Anhängen wird von Hunderten im Internet kur-
sierenden Klassen und Funktionssammlungen unterstützt. Die vorliegende Vari-
ante ist insofern dennoch interessant, weil sie ausreichend kompakt ist, um
komplett verstanden zu werden. Außerdem nutzt sie die neuen Funktionen in
PHP5, was nahezu alle anderen Lösungen nicht tun. Sie ist außerdem hinrei-
chend alltagstauglich und leicht erweiterbar, wenn es erforderlich sein sollte.



10.6 Funktions-Referenz Stream-Funktionen

Funktion                            Bedeutung
stream_context_create               Erzeugt ein Kontext-Handle, das andere Funktionen
                                    nutzen, um auf so konfigurierte Streams zurückzugrei-
                                    fen. Der Kontext-Parameter ist neu in vielen Dateifunk-
                                    tionen in PHP5 hinzugekommen.
stream_context_get_default          Ermittelt den aktuellen Kontext eines Streams.
stream_context_get_options          Ermittelt gesetzte Optionen für Kontexte, Streams oder
                                    Wrapper.
stream_context_set_option           Setzt Optionen für Kontexte, Streams oder Wrapper.
stream_context_set_params           Setzte Parameter für Kontexte, Streams oder Wrapper.
stream_copy_to_stream               Kopiert Daten von einem Stream zu einem anderen.
stream_filter_append                Fügt einem Stream ein Filter hinzu. Die Filteraktion
                                    wird nach dem Übertragen der Daten ausgeführt.
stream_filter_prepend               Fügt ein Filter vor einem Stream an. Die Filteraktion
                                    wird vor dem Übertragen der Daten ausgeführt.
stream_filter_register              Registriert einen benutzerspezifischen Filter. Das Filter
                                    kann durch eine Klasse implementiert werden, die von
                                    php_user_filter abgeleitet wird.

Tabelle 10.2: Funktions-Referenz für Stream-Funktionen




440
Funktions-Referenz Stream-Funktionen



Funktion                         Bedeutung
stream_get_contents              Liest ausstehende Daten eines Streams in eine Zeichen-
                                 kette.
stream_get_filters               Ermittelt eine Liste von registrierten Filtern.
stream_get_line                  Liest eine Zeile aus einem Stream bis zu einem verein-
                                 barten Trennzeichen.
stream_get_meta_data             Ermittelt Kopfzeilen und Metadaten aus einem Stream.
stream_get_transports            Ermittelt eine Liste von Sockets, die Transportaufgaben
                                 auf niedriger Ebene übernehmen.
stream_get_wrappers              Ermittelt eine Liste registrierter Streams.
stream_register_wrapper          Alias für stream_wrapper_register.
stream_select                    Äquivalent für den select-Systemaufruf.
stream_set_blocking              Setzt blocking/non-blocking-Mode für einen Stream.
stream_set_timeout               Setzt die Abbruchzeit für einen Stream.
stream_set_write_buffer          Setzt Dateipufferung für einen Stream.
stream_socket_accept             Akzeptiert eine Socket-Verbindung für den Stream. Die
                                 Verbindung muss mit stream_socket_server erstellt
                                 worden sein.
stream_socket_client             Eröffnet eine Socket-Verbindung ins Internet oder zu
                                 einer Unix-Domain.
stream_socket_enable_crypto Schaltet die Verschlüsselung für eine bestehende Ver-
                                 bindung ein oder aus.
stream_socket_get_name           Ermittelt den Namen eines lokalen oder remoten
                                 Sockets.
stream_socket_recvfrom           Empfängt Daten von einem Socket, unabhängig davon,
                                 ob die Verbindung noch besteht oder nicht.
stream_socket_sendto             Sendet eine Nachricht an einen Socket, unabhängig
                                 davon, ob die Verbindung noch besteht oder nicht.

Tabelle 10.2: Funktions-Referenz für Stream-Funktionen (Forts.)




                                                                                           441
Kommunikation per HTTP, FTP und E-Mail



Funktion                              Bedeutung
stream_socket_server                  Erstellt eine Socket-Verbindung ins Internet oder zu
                                      einer Unix-Domain.
stream_wrapper_register               Registriert einen benutzerspezifischen Wrapper, der als
                                      PHP-Klasse implementiert sein muss.

Tabelle 10.2: Funktions-Referenz für Stream-Funktionen (Forts.)



10.7 Kontrollfragen
1. Welche Aufgabe erfüllen Wrapper?
2. Was muss ein fremder Entwickler beachten, wenn er einen von Ihnen entwickeltes
      Filter verwendet?
3. Was bedeuten die Kopfzeilen Cc: und Bcc: in einer E-Mail?
4. Welches Protokoll nutzt die mail-Funktion zur Übertragung der Daten?




442
Datenbank-
programmierung




   1 1
Datenbankprogrammierung



11.1 Prinzip der Datenbankprogrammierung
Die Datenbankprogrammierung ist für fast alle Webseiten von elementarer Bedeu-
tung. Es geht dabei immer wieder darum, anfallende Daten zustands- und seiten-
unabhängig zu erfassen und wieder bereitzustellen. Der Datenbankserver arbeitet
– auch wenn das entsprechende Programm auf derselben Maschine wie der Web-
server läuft – unabhängig von PHP oder Apache.
Um eine Datenbank zu nutzen, sind deshalb mehrere Schritte erforderlich, die
den Einstieg nicht einfach machen (wenn man es kann, mag dies primitiv klin-
gen):
í     Aufsetzen oder Beschaffen eines Datenbankservers
í     Aktivierung eines Client-Moduls in PHP, das mit dem Server kommunizieren
      kann
í     Herstellen einer Verbindung zum Datenbankserver am Beginn des Skripts,
      meist unter Angabe der IP-Adresse oder des Netzwerknamens
í     Herstellen einer Verbindung zu einer konkreten Datenbank, die der Daten-
      bankserver verwaltet
í     Aufbau einer Abfrageverbindung, um SQL-Kommandos an den Datenbank-
      server zu senden
í     Auswerten der Antwort und Aufbereitung der Daten, um diese für die Ausgabe
      oder für weitere Abfragen verwenden zu können
Insgesamt ist also einiger Aufwand zu treiben. Dafür hat man ein System, das mit
drei Datensätzen ebenso gut zurechtkommt wie mit 3 Millionen. Außerdem lassen
sich auch komplexe Daten mit vielfältigen Verknüpfungen verwalten.
Einiger Lernaufwand ist dennoch nötig. Während Sie das Aufsetzen und Verwal-
ten des Datenbankservers an den Provider oder einen Administrator abschieben
können, bleibt die Last der Programmierung allein beim Entwickler der Website.
Unerlässlich sind deshalb SQL-Kenntnisse.



11.2 Die universelle Abfragesprache SQL
Dieser Abschnitt führt ganz allgemein in SQL ein. Dabei wird, soweit nichts ande-
res explizit erwähnt wird, sowohl MySQL als auch SQLite unterstützt. Das heißt,


444
Die universelle Abfragesprache SQL


die entsprechende Abfragen funktionieren in beiden Fällen. Auf die Besonderhei-
ten der beiden Systeme wird in eigenen Abschnitten eingegangen.
Wenn Sie SQL bereits kennen, nutzen Sie diesen Abschnitt zum Auffrischen und
Wiederholen. SQL ist im Zusammenhang mit der PHP-Programmierung enorm
wichtig und sollte unbedingt beherrscht werden.


Was ist SQL?
SQL, am besten englisch ausgesprochen (»ess-kju-ell«), steht für Structured Query
Language – Strukturierte Abfragesprache. SQL wird verwendet, um mit einer
Datenbank zu kommunizieren. Nach der Definition der Normungsinstitution
ANSI (American National Standards Institute) ist SQL der verbindliche Industrie-
standard für die Abfrage relationaler Datenbankmanagementsysteme (RDBMS).
SQL-Anweisungen bestimmen, wie eine Abfrage oder Befehl aussehen soll, den
das System empfängt und ausführt. Der Standard erlaubt es, mit einem Basisbe-
fehlssatz nahezu alle RDBMS zu bedienen. Abweichend von den Basisbefehlen
verfügen alle am Markt operierenden System – egal ob kommerziell oder Open
Source – über mehr oder wenige sinnvolle und natürlich zu allen anderen inkom-
patible Erweiterungen. MySQL ist hierin ebenso wenig eine Ausnahme wie
SQLite, Oracle, MS SQL, Sybase oder Access. Der Basisbefehlssatz, den man
unbedingt beherrschen muss, wird jedoch weitgehend identisch unterstützt. Dazu
gehören SELECT (Abfragen), UPDATE (Aktualisieren), DELETE (Löschen),
INSERT (Einfügen) und DROP (Entfernen).


Tabellen und Abfragen
Relationale Datenbanken halten Daten in Tabellen. Tabellen werden durch einen
eindeutigen Namen identifiziert, der in den Abfragebefehlen anzugeben ist.
Tabellen bestehen aus Spalten, die jeweils einen ganz bestimmten Datentyp auf-
nehmen können. Sie bestehen auch aus Reihen, in denen zusammengehörende
Daten gespeichert sind. Eine einfache Tabelle mit Daten kann in etwa folgender-
maßen dargestellt werden:




                                                                                     445
Datenbankprogrammierung



Stadt                  Bundesland           Tief                  Hoch

Berlin                 Berlin               5                     13

München                Bayern               2                     16
Hamburg                Hamburg              7                     12

Frankfurt              Hessen               7                     11

Tabelle 11.1: Eine sehr einfache SQL-Tabelle mit Wetterdaten (Tabellenname: Wetter)


Die Tabelle abfragen: SELECT
Grundsätzlich lässt sich jede Datenbank vollständig über SQL-Befehle bedienen.
Gerade am Anfang wird das eher weniger der Fall sein. Alle Datenbankprogramme
bieten mehr oder weniger elegante dialogorientierte Oberflächen, die zumindest
das Anlegen der Tabellen und das Eingeben der ersten Daten erlauben, ohne die
Anweisungen selbst zu beherrschen.
Bei Abfragen sieht es anders aus. Für viele kommerzielle Datenbanken gibt es so
genannte Query Builder und einige freie Programme mit entsprechender Funk-
tion sind auch verfügbar. Derartige Tools verhindern jedoch, dass man SQL wirk-
lich lernt. Das ist fatal, denn früher oder später stößt jedes dieser Programme an
seine Grenzen. Das sind aber selten die Grenzen von SQL. Die Beschäftigung mit
der Abfragesyntax ist deshalb unbedingt zu empfehlen.
Der prinzipielle Aufbau einer Abfrage folgt folgendem Schema:
SELECT Spaltename1 [, Spaltenname2, ...]
  FROM Tabelle
 [WHERE Bedingungen]
Die eckigen Klammern deuten optionale Angaben an. Die Anweisungen sollten
außerdem, auch wenn sie hier aus Gründen der Lesbarkeit mehrzeilig erscheinen,
als eine Zeichenkette gelesen werden. Auf die oben gezeigte Tabelle »Wetter«
bezogen, sieht eine konkrete Abfrage beispielsweise folgendermaßen aus:
SELECT Stadt, Bundesland FROM Wetter WHERE Tief  5
Entfällt die mit WHERE eingeleitete Bedingung, werden alle Reihen der Tabelle
zurückgegeben. Die Auswahl der Spalten bestimmt die Liste unmittelbar hinter
SELECT. Hier kann alternativ auch das Sternchen (*) angegeben werden, um alle
Spalten zu holen.



446
Die universelle Abfragesprache SQL


          SELECT * FROM ist die häufigste Abfrage – zumindest in der Literatur. In
          der Praxis sollte dies nicht so sein, weil die Abfrage von Spalten, die
          nicht benötigt werden, unnütz Zeit kostet. Die Darstellung auf dem
          Papier lässt sich aber verkürzen und die Angaben sind weniger spezi-
          fisch, was zur Erklärung manchmal hilfreich ist.

Hat eine Abfrage mit SELECT Erfolg, besteht die Antwort aus einem oder mehreren
so genannten Datensätzen. Jeder Datensatz kann wiederum ein oder mehrere Fel-
der enthalten. Der Datensatz ist quasi eine konkrete Version einer Reihe der
Datenbank, ergänzt um berechnete Spalten oder reduziert auf ausgewählte Spal-
ten. Nur wenn die Abfrage aus dem einfachen * besteht, entspricht ein Datensatz
einer Reihe.

Bedingungen formulieren: WHERE
Die WHERE-Bedingung ist sehr wichtig. Sie folgt denselben Prinzipien wie if in
PHP5, das heißt, es muss sich ein Boolescher Ausdruck formulieren lassen. Dazu
gehören in erster Linie die Vergleichsoperatoren:
í   
    Größer als; meist nur mit numerischen Werten sinnvoll
í   
    Kleiner als; meist nur mit numerischen Werten sinnvoll
í   =
    Größer als oder gleich; meist nur mit numerischen Werten sinnvoll
í   =
    Kleiner als oder gleich; meist nur mit numerischen Werten sinnvoll
í   
    Ungleich; kann fast immer verwendet werden
í   =
    Gleich; kann fast immer verwendet werden
Ebenso wichtig sind die Booleschen Operatoren, die bereits aus PHP bekannt sind
und sich in SQL analog verhalten: OR, AND, NOT.

          Beachten Sie beim Gleichheitszeichen, dass dies in SQL einfach ist,
          nicht doppelt wie in PHP. Ein doppeltes Gleichheitszeichen gibt es in
          SQL generell nicht.


                                                                                      447
Datenbankprogrammierung


Neben diesen kennt SQL aber auch einige sehr spezielle Operatoren, die in PHP
nicht vorhanden sind:
í     LIKE
      Dieser Operator erlaubt eine Ähnlichkeitssuche mit Platzhaltern. SQL verwen-
      det sehr untypische Platzhalter: % steht für jedes beliebige Zeichen in beliebi-
      ger Anzahl und _ für genau ein beliebiges Zeichen.
      Beispiel:
      SELECT * FROM Wetter WHERE Stadt LIKE 'B%'
      Diese Anweisung gibt alle Städte zurück, die mit dem Buchstaben »B« begin-
      nen.
í     IN
      SQL verwendet zur Referenz auf Datensätze oft deren Schlüsselnummern
      (darauf wird noch genauer eingegangen). Listen solcher Referenzen kann man
      mit IN verwenden. Aber auch einfache Zahlenvergleich sind gut möglich.
      Beispiel:
      SELECT * FROM Wetter WHERE Tief IN (1,3,5,7)
      Dieser Befehl fragt alle ungeraden Tiefsttemperaturwerte aller Datensätze ab.
í     BETWEEN
      Hiermit lassen sich Wertebereiche abfragen.
      Beispiel:
      SELECT * FROM Wetter WHERE Hoch BETWEEN 10 AND 15
Zeichenketten müssen in SQL mit einfachen Anführungszeichen umschlossen
werden. Die meisten Systeme verkraften zwar auch doppelte, die Zusammenarbeit
mit PHP profitiert aber erheblich von einer einheitlichen Schreibweise, weshalb
einfache Anführungszeichen zum Standard gehören sollten.
Die SELECT-Anweisung kann weitaus mehr. Dazu gehören Befehle zum Sortieren,
Filtern, Verknüpfen mit anderen Tabellen usw. Um dies ausprobieren zu können,
brauchen Sie jedoch mehr Tabellen, die erstmal erzeugt und befüllt sein müssen.


Tabellen anlegen und füllen
Werden Tabellen per Code erzeugt, braucht man eine spezielle Anweisung dafür.
Dieser Abschnitt zeigt, wie Tabellen erzeugt und gefüllt werden.


448
Die universelle Abfragesprache SQL


Tabelle erzeugen: CREATE TABLE
In SQL heißt diese CREATE TABLE. Die grundlegende Syntax lautet:
CREATE TABLE Tabellenname
  (SpaltenName DatenTyp [Einschränkung],
  [SpaltenName DatenTyp [Einschränkung], ...]
  )
Die Angabe der Einschränkung bestimmt nähere Eigenschaften der Spalte, abhän-
gig vom gewählten Datentyp. Die Angabe ist optional, weil alle Datentypen mit
bestimmten Basiseigenschaften ausgestattet sind. Der Spaltenname ist frei wählbar.
Die meisten Systeme verkraften sogar Leerzeichen (keine gute Idee), wenn der
Name in Anführungszeichen steht.


Der Datentyp einer Spalte
Der Datentyp ist eine sehr wichtige Eigenschaft einer Spalte. SQL ist prinzipiell
typstreng und überwacht die Einhaltung der Daten in den Feldern umfassend. In
der Praxis wird dies etwas aufgeweicht betrachtet. So konvertiert SQLite intern
alles so lange, bis es passt, und akzeptiert meist alles. MySQL hält die Typen streng
ein, konvertiert aber auch weit reichend, und die Daten »passend« zu machen.
Andere Datenbanken sind strenger und reagieren mit Fehlermeldungen auf fal-
sche Typen. Generell geht SQL aber nie so locker mit Datentypen um wie PHP.
SQL verfügt über eine Vielzahl von Funktionen, die speziell für Zeichenketten
oder Zahlen gedacht sind. Der falsche Datentyp für die zu berechnende Spalte
führt dann in jedem Fall zu einer Fehlermeldung.
Die Angabe des Datentyps erfolgt durch ein aussagekräftiges Schlüsselwort und
optional durch Parameter. Die folgende Tabelle zeigt die wichtigsten Angaben, die
von fast allen SQL-Datenbanken akzeptiert werden.

Datentyp                   Beschreibung
CHAR(Anzahl)               Feld mit fester Anzahl von Zeichen.
VARCHAR(Maximum)           Feld mit variabler Anzahl Zeichen, begrenzt auf ein Maxi-
                           mum.
INT(Breite)                Ganzzahlfeld mit der durch Breite angegebenen Anzahl
                           Stellen



                                                                                        449
Datenbankprogrammierung



Datentyp                    Beschreibung
DATE                        Ein Datumsfeld (nur die Angabe des Datums ist möglich).
DATETIME                    Ein Datums- und Zeitfeld (enthält Datums- und Zeitinfor-
                            mationen).
FLOAT(Breite, Dezimal)      Gleitkommazahl mit Breite Stellen und Dezimal Nachkom-
                            mastellen.
TEXT                        Längere Textdaten (VARCHAR und CHAR sind auf
                            255 Zeichen begrenzt).
BLOB                        Größere Binärdaten (BLOB = Binary Large Objects).


Die meisten Datenbanken bieten vordefinierte Variationen von INT für verschie-
dene Mengen, beispielsweise BIGINT oder TINYINT in MySQL.


Tabellen erzeugen
Das folgende Beispiel zeigt, wie die am Anfang benutzte Tabelle erzeugt wird:
CREATE TABLE Wetter
  (Stadt VARCHAR(100),
   Bundesland VARCHAR(30),
   Tief INT(2),
   Hoch INT(2))
Generell gilt die Regel, dass die Datentypen so fein und begrenzend wie möglich
ausgelegt werden sollten. So dürfte es in Mitteleuropa kaum vorkommen, dass drei-
stellige Temperaturen auftreten. Deshalb wird INT(2) geschrieben. Es gibt kein
Bundesland, das mehr als 30 Zeichen für den Namen benötigt (Mecklenburg-Vor-
pommern ist der längste Name mit genau 22 Zeichen). Beachten Sie, dass die
Temperaturangabe -11°C in einem INT-Feld auch nur zwei Stellen benötigt, weil
das Vorzeichen separat gespeichert wird.
Derartige Beschränkungen verhindern zumindest teilweise, dass unsinnige Daten
in die Datenbank gelangen. Sie führen freilich, wenn es dennoch versucht wird,
zu Fehlermeldungen im Skript, die gesondert behandelt werden müssen. Inkonsis-
tente Datenbestände sind jedoch weitaus schlimmer als Fehler bei der Eingabe,
die frühzeitig auf das Problem hinweisen.




450
Die universelle Abfragesprache SQL


Einschränkungen
Bei der Definition der Syntax für CREATE TABLE war von Einschränkungen die
Rede. Dies sind Angaben, die den möglichen Inhalt unabhängig vom Datentyp
bestimmen. Drei solcher Zusatzangaben sind besonders wichtig:
í   UNIQUE
    Hiermit wird bestimmt, dass alle Werte in der Spalte nur ein Mal vorkommen
    dürfen, das heißt, sie müssen eindeutig (engl. unique) sein.
í   PRIMARY KEY
    Bestimmt, dass diese Teile den Primärschlüssel stellt. Zur Bedeutung der
    Schlüssel folgt noch eine detaillierte Betrachtung. PRIMARY KEY impliziert
    immer UNIQUE.
í   AUTO_INCREMENT
    Nicht offizieller SQL-Standard aber dennoch von fast allen Datenbank unter-
    stützt sind Felder, deren Werte automatisch erzeugt werden, ohne dass dies
    beim Einfügen angegeben werden muss.
í   NULL, NOT NULL
    NULL erklärt, dass das betreffende Feld beim Schreiben der Daten leer bleiben
    darf, NOT NULL bestimmt, dass dies nie der Fall sein darf.
í   DEFAULT
    Bestimmt einen Standardwert, wenn bei der Eingabe der Daten kein Wert
    angegeben oder das Feld überhaupt nicht spezifiziert wurde.
Es gibt weitaus mehr Einschränkungen als die gezeigten, allerdings werden die
meisten weder von MySQL noch von SQLite unterstützt, weshalb die Ausführun-
gen auf die elementaren Angaben beschränkt werden.


Daten einfügen
Nachdem die Tabelle existiert, kann sie mit Daten gefüllt werden. Dazu wird die
Anweisung INSERT benutzt. INSERT verlangt die Angabe des Tabellennamens, einer
Liste der Spalten, die mit Daten bestückt werden, und eine Liste der Werte, die
hineingeschrieben werden. Die Angabe der Spalten kann entfallen, wenn alle
beschrieben werden. Eine weitere Wetterangabe in der Mustertabelle könnte fol-
gendermaßen eingefügt werden:




                                                                                     451
Datenbankprogrammierung


INSERT INTO Wetter
      (Stadt, Bundesland, Hoch, Tief)
       VALUES
      ('Dresden', 'Sachsen', 17, 8)
Beachten Sie, dass Zeichenketten in einfachen Anführungszeichen stehen, nume-
rische Werte jedoch nicht. Die Anzahl der Werte hinter VALUES muss exakt der
Anzahl der Spalten entsprechen, damit die Zuordnung stimmt. Spalten, die hier
nicht auftreten, werden entsprechend den Einschränkungsregeln befüllt, also mit
Standardwerten (DEFAULT), automatischen Werten (AUTO_INCREMENT) oder NULL-
Werten (NULL).


Aktualisieren und Löschen von Daten
Nachdem die Daten nun in der Tabelle sind und auch gelesen werden können,
sind Lösch- und Aktualisierungsvorgänge an der Tagesordnung.


Aktualisierung von Daten: UPDATE
Die Änderung der Inhalte wird mit der Anweisung UPDATE vorgenommen. Die
grundlegende Syntax folgt folgendem Schema:
UPDATE TabellenName
   SET SpaltenName = Wert [, SpaltenName = Wert, ...]
  [WHERE Bedingung]
Die Bedingung entspricht weitgehend den bei SELECT möglichen Angaben. Ohne
Bedingung werden immer alle Spalten geändert. Die Zuweisung neuer Werte
kann mit Konstanten oder Berechnungen, auch mit Referenzen auf andere Spal-
ten erfolgen.
Im Beispiel lässt sich die Temperatur für eine bestimmte Stadt folgendermaßen
ändern:
UPDATE Wetter SET Hoch = 17, Tief = 23 WHERE Stadt = 'Berlin'
Man kann auch alle Temperaturen einfach erhöhen:
UPDATE Wetter SET Hoch = Hoch + 1




452
Die universelle Abfragesprache SQL


Löschen von Daten: DELETE
Dem Löschen dient die Anweisung DELETE. Die Anwendung entspricht dem
Schema der bisher vorgestellten Befehle:
DELETE FROM TabellenName [WHERE Bedingung]
Auch wenn die Bedingung optional ist, die Angabe dürfte in den allermeisten Fäl-
len unbedingt erforderlich sein – sonst ist die Tabelle nämlich leer.
Für die Bedingung kann wieder ein Ausdruck nach dem bei SELECT gezeigten
Schema benutzt werden. Beachten Sie, dass SQL kein »Undo/Rückgängig« etc.
kennt – was weg ist, ist endgültig weg.


Löschen und Leeren von Tabellen: DROP und TRUNCATE
Um eine Tabelle komplett wieder loszuwerden, nutzen Sie die Anweisung DROP:
DROP TabellenName
Ebenso einfach geht das Leeren, das alle Daten entfernt, aber die Tabelle selbst
erhält:
TRUNCATE TabellenName
Gegenüber der Anweisung DELETE FROM TabellenName (ohne Bedingung) ist, TRUN-
CATE schneller. Der Effekt ist derselbe – alles ist weg.


Fortgeschrittene Abfragen mit SELECT
SELECT allein ist ungeheuer mächtig. Der korrekte Umgang damit verlangt aber
einiges an Hintergrundwissen. Dennoch kommt man sehr schnell nicht mehr
ohne die Basisabfragen aus. Dieser Abschnitt zeigt die Anwendung von SELECT auf
dem Niveau, wie es auf durchschnittlich komplexen PHP-Seiten durchaus zur
Anwendung kommen kann. Wenn Sie noch weiter lernen möchten, ist der Zugriff
auf spezielle Literatur erforderlich.


SELECT komplett betrachtet
Mit SELECT werden Datenbanken abgefragt. Dabei ist FROM die einzige Angabe, die
zwingend erforderlich ist. Das bereits vorgestellte WHERE ist optional. Insgesamt sind
es fünf derartige Operatoren, die zum Einsatz kommen können:


                                                                                        453
Datenbankprogrammierung


í     FROM
      Bestimmt die Tabelle (oder mehrere Tabellen), aus denen gelesen wird.
í     WHERE
      Schränkt die Auswahl durch eine global wirkende Bedingung ein.
í     GROUP BY
      Gruppiert Ergebnisse, sodass sie mit anderen zusammengefasst werden kön-
      nen.
í     HAVING
      Wendet Auswahleinschränkungen auf Gruppen an.
í     ORDER BY
      Sortiert die Ergebnisse.
Bei der Auswahl der Spalten kann zudem bestimmt werden, ob alle oder nur ein-
deutige Ergebnisse zurückgegeben werden sollen:
í     ALL
í     DISTINCT
Die Angabe ALL entfällt in den allermeisten Fällen, weil dies der Standardwert ist.
Für die Abfrage eindeutiger Werte schreiben Sie beispielsweise:
SELECT DISTINCT Bundesland FROM Wetter
Dies verhindert, dass die mehrfach in der Tabelle auftretenden Bundesländer auch
mehrfach ausgegeben werden.
GROUP BY gruppiert Ergebnisse und wenn Einschränkungen bei der Auswahl der
Gruppen gefragt sind, dann ist HAVING die erste Wahl. Nach HAVING können diesel-
ben Booleschen Ausdrücke stehen wie nach WHERE. Mehr dazu weiter unten.


Aggregierende Funktionen
Aggregierende Funktionen führen Berechnungen mit der durch SELECT erstellten
Auswahl von Daten aus. Die häufigste und einfachste ist COUNT – hiermit wird
schlicht die Anzahl der Reihen ermittelt. Wichtig sind folgende Funktionen:
í     COUNT, COUNT(*)
      Hiermit wird die Anzahl der ausgewählten oder (*) aller Reihen der Abfrage
      ermittelt.




454
Die universelle Abfragesprache SQL


í   MAX, MIN
    Enthält die betroffene Spalte Werte, die eine Ordnung bilden, kann hiermit
    der größte bzw. kleinste Wert ermittelt werden.
í   SUM
    Für numerische Spalten ermittelt diese Funktion die Summe.
í   AVG
    Für numerische Spalten ermittelt diese Funktion den Durchschnitt.
Beachten Sie, dass die Aggregatfunktionen keine Liste von Daten erzeugen, sondern
skalare Werte. Dies muss bei der Auswertung mit PHP-Funktionen berücksichtigt
werden. Bei den PHP-Beispielen wird darauf nochmals explizit eingegangen.
Die Anwendung in SQL sieht folgendermaßen aus:
SELECT AVG(Hoch) FROM Wetter
Damit erhält man die durchschnittliche Tageshöchsttemperatur aus der Wetter-
Tabelle. Selbstverständlich sind auch hier WHERE-Bedingungen eine gute Idee:
SELECT AVG(Hoch) FROM Wetter WHERE Bundesland = 'Bayern'
Nun erhalten Sie die durchschnittliche Tageshöchsttemperatur aller bayerischen
Städte.


Gruppierungen
Gruppierungen mit GROUP BY wurden bereits kurz erwähnt. Der Sinn ist weniger
der damit erreichbare Sortiereffekt, sondern die Möglichkeit, die Aggregatfunktio-
nen auf die Gruppen anzuwenden.
SELECT MAX(Hoch), Bundesland
   FROM Wetter
   GROUP BY Bundesland
   HAVING Bundesland LIKE 'B%'
Diese Abfrage ermittelt die höchste Tagestemperatur aller Bundesländer, die mit
»B« beginnen. Die Einschränkung mit HAVING ist freilich optional, meist genügt
die Gruppierung alleine. Wichtig ist, dass die Liste der Spalten hinter GROUP BY in
der Liste der Spalten hinter SELECT enthalten sein muss. Gruppierung und Aus-
gabe korrespondieren immer. Die Aggregatfunktionen sind davon nicht betroffen,
deshalb kann die Funktion MAX im Beispiel hinzugefügt werden.




                                                                                      455
Datenbankprogrammierung


Ergebnisse sortieren
Tabellen sind in SQL niemals sortiert oder geordnet. Allein die Abfrage entschei-
det, wie die Daten auszugeben sind. Um die entsprechende Kontrolle zu bekom-
men, wird ans Ende der Anweisung ORDER BY angehängt. Zwei zusätzliche
Klauseln bestimmen die Richtung:
í     ASC
      Dies steht für »ascending«, also aufsteigend.
í     DESC
      Dies steht für »descending«, also absteigend.
Hinter ORDER BY lassen sich mehrere Spalten angeben, um uneindeutige Sortierun-
gen nach nur einer Spalte zu vermeiden:
SELECT * FROM Wetter ORDER BY Bundesland, Stadt ASC
Das konkrete Sortierverhalten kann SQL nicht bestimmen. Dazu bietet jedes
Datenbanksystem verschiedene Einstellmöglichkeiten, die nicht standardisiert
sind. Dies betrifft beispielsweise die Einordnung von Umlauten ins Alphabet.
Die Klausel ASC kann auch entfallen, denn die aufsteigende Sortierung ist der
Standardfall. Dies gilt aber nur, wenn ORDER BY angegeben wurde. Ohne dieses
Schlüsselwort wird unsortiert ausgegeben.


Funktionen
SQL verfügt über eine reiche Funktionspalette, unter anderem:
í     Mathematische Funktionen
í     Zeichenkettenverarbeitung
í     Datumsfunktionen
í     Systemfunktionen
Leider ist der Standard hier kaum beachtet worden. Deshalb gibt es teilweise dras-
tische Unterschiede in Ausstattung und Syntax bei verschiedenen Datenbanken.
Der Abschnitt 11.4 »Erste Schritte mit MySQL und MySQLi« ab Seite 477 zeigt
die konkreten Funktionen für MySQL und im Kapitel Die integrierte Datenbank
SQLite ab Seite 497 finden Sie entsprechende Auflistung für SQLite.




456
Die universelle Abfragesprache SQL



Verknüpfungen zwischen Tabellen
Der Name »Relationale Datenbanken« enthält einen Hinweis auf eine ganz spezi-
fische Eigenschaft – die Relationen oder Beziehungen. Richtig wertvoll wird der
Umgang mit Datenbanken erst, wenn man zwischen Tabellen derartige Beziehun-
gen aufbaut.
Das ist in fast allen Anwendung auch notwendig, um die Datenhaltung effektiv und
nicht redundant zu halten. Wenn Sie die gut gefüllte Wetter-Tabelle sehen, werden
Sie bemerken, dass dieselben Bundesländer immer wieder aufgeführt sind. Ändert
man nun den Namen, beispielsweise wegen eines Schreibfehlers, dann müsste man
alle betreffenden Felder ändern. Hat man dabei schon einen Fehler, beispielsweise
aus früheren Einträgen, wird die Datenbank inkonsistent. Deshalb versucht man,
redundante Daten zu vermeiden. Dazu wird eine zweite Tabelle erstellt und mit
der ersten verknüpft. Damit das Verknüpfen funktioniert, erhält außerdem jede
Tabellen einen Primärschlüssel, der jeden Datensatz eindeutig kennzeichnet.

Normalisierungen
Folgende neue Tabelle ist also für die Wetter-Anwendung erforderlich:

ID                                  Name
1                                   Berlin

2                                   Bayern

3                                   Hamburg
4                                   Hessen

Tabelle 11.2: Tabelle für Bundesländer (Ausschnitt)

Nun wird die Wetter-Tabelle so geändert, dass nur noch mit Relationen gearbeitet
wird:

ID         Stadt               BundeslandID           Hoch                     Tief
1          Berlin              1                      7                        13

2          München             2                      8                        16
Tabelle 11.3: Tabelle für Wetterdaten (Ausschnitt)


                                                                                               457
Datenbankprogrammierung



ID         Stadt                 BundeslandID        Hoch      Tief

3          Regensburg            2                   9         15

4          Altötting             2                   8         15
Tabelle 11.3: Tabelle für Wetterdaten (Ausschnitt) (Forts.)

Die Verknüpfung ist nun noch zu definieren. Je nach Datenbank ist dies eine ein-
fache Festlegung oder ein fest programmierter Wert, der von der Datenbank über-
wacht wird. MySQL und SQLite überwachen nichts und deshalb genügt die
Angabe der Verknüpfung bei der Abfrage, was durch Vergleich der beiden betrof-
fenen Felder geschieht:
Bundesland.ID = Wetter.BundeslandID
Damit das in einer Anweisung gelingt, muss man etwas beachten. So kann jedem
Spaltennamen immer der Tabellenname vorangestellt werden, getrennt durch
einen Punkt. Das ist meist notwendig, weil sich die Spaltennamen gleichen kön-
nen und der SQL-Server dann nicht mehr weiß, welche Spalte gemeint ist.
Das Aufbrechen einer Tabellen in mehrere zur Vermeidung von Redundanzen
wird als Normalisierung bezeichnet. Es gibt mehrere Stufen der Normalisierung,
für deren vollständige Darstellung jedoch mehr Platz erforderlich ist. An dieser
Stelle sei auf entsprechende Spezialliteratur zu Datenbanken verwiesen.


Prinzipien verknüpfter Abfragen
Verknüpfte Abfragen können auf mehreren Wegen erstellt werden. Der einfachste
weg ist ein so genannter »Inner Join«, bei dem ein Kreuzprodukt aus beiden Tabel-
len erstellt wird:
SELECT * FROM Wetter, Bundesland
Das führt dazu, dass jede Zeile der einen Tabelle mit jeder der anderen Tabelle
»gekreuzt« wird. 100 Wetterdaten für 16 Bundesländer ergeben dann 1.600 Rei-
hen. Damit kann in der Praxis niemand etwas anfangen. Also ist die benötigte
Verknüpfung anzugeben:
SELECT * FROM Wetter, Bundesland         
    WHERE Bundesland.ID = Wetter.BundeslandID
Nun werden die Datensätze aus der ersten Tabelle mit den passenden aus der
zweiten verknüpft. Da jeder Wettereintrag nur in einem Bundesland platziert sein


458
Die universelle Abfragesprache SQL


kann, sind es nun nur noch die benötigten 100 Datensätze. Im Unterschied zur
einfachen Abfrage der ersten Tabelle steht nun aber auch der Name des Bundes-
landes zur Verfügung. Korrekter (im Sinne von: effizienter) ist folgende Version:
SELECT W.Stadt, W.Hoch, W.Tief, B.Name     
    FROM Wetter W, Bundesland B    
    WHERE B.ID = W.BundeslandID
Damit ist die mit der ersten Abfrage erreichte Ausgabe wieder da, allerdings dies-
mal basierend auf einer verbesserten Datenstruktur.
Zum »Inner Join« kann eine alternative Syntax verwendet werden:
SELECT W.Stadt, W.Hoch, W.Tief, B.Name     
    FROM Wetter W INNER JOIN Bundesland B      
    ON B.ID = W.BundeslandID
Weitere Bedingungen, die in der ersten Variante mit AND angehängt werden, müss-
ten hier in einer zusätzlichen WHERE-Bedingung stehen. Der »Inner Join« bezieht
NULL-Felder nicht mit ein. Es kann nämlich sein, dass zu einer Stadt noch keine
Zuordnung des Bundeslandes erfolgte. Was passiert mit solchen Feldern? Grund-
sätzlich ist NULL in SQL kein unmöglicher Zustand. Es gibt nun mehrere Varian-
ten:
í   Die linke Tabelle (links vom JOIN) enthält NULL-Werte und die entsprechenden
    Zeilen sollen trotzdem verarbeitet werden.
    In diesem Fall wird als Schlüsselwort LEFT JOIN gewählt.
í   Die rechte Tabelle (rechts vom JOIN) enthält NULL-Werte und die entsprechen-
    den Zeilen sollen trotzdem verarbeitet werden.
    In diesem Fall wird als Schlüsselwort RIGHT JOIN gewählt.
Freilich kann man auch die Tabellen vertauschen und die Schlüsselwörter genau
umgekehrt einsetzen.
Es gibt weitere JOIN-Varianten, die hier jedoch zu weit führen und die – leider –
auch nicht alle von MySQL unterstützt werden.


Fortgeschrittene SQL-Techniken
Einige fortgeschrittene SQL-Techniken seien an dieser Stelle kurz erwähnt. Sie
sind leicht zu verstehen und einzusetzen. Das Handbuch zu MySQL gibt hier aus-
führlich Auskunft.


                                                                                        459
Datenbankprogrammierung


Primärschlüssel
Bislang wurde einfach angenommen, dass die Tabellen einen Schlüssel haben.
Generell ist dies keine Option, sondern ein Primärschlüssel ist erforderlich, damit
die Datenbank die Datensätze unterscheiden kann. Praktischerweise definiert man
meist eine Spalte als INT (oder größer, beispielsweise BIGINT) und versieht sie mit
dem Attribut AUTO_INCREMENT. MySQL legt die benötigten Schlüssel dann automa-
tisch an. Andere Tabellen können dann Referenzen dazu nutzen. Ohne Primär-
schlüssel könnte die Datenbank die Datensätze nicht eindeutig unterscheiden und
Abfragen wären nicht ausführbar.
Alternativ zur automatischen Vergabe ist freilich auch die Nutzung anderer Spal-
ten mit eindeutigen Werten möglich, ebenso wie sich der Primärschlüssel aus
mehreren Spalten zusammensetzen kann, um eindeutig zu sein.

Unterabfragen
Manchmal benötigt man die Ergebnisse einer Abfrage für eine andere. SQL kann
diese Verknüpfung direkt ausführen, also ohne PHP dazwischen. Man spricht von
so genannten Sub-Selects:
SELECT * FROM Wetter
    WHERE BundeslandID
          NOT IN
         (SELECT ID FROM Bundesland WHERE NAME LIKE 'B%')
Zuerst wird hier die innere Abfrage ausgeführt. Sie ergibt eine Liste der Primär-
schlüssel der Tabelle Bundesland für alle Länder, die mit »B« beginnen. Diese
Liste (beispielsweise 1,2,4,8) wird für den IN-Operator benutzt. Dann wird die
Abfrage für alle Bundesländer ausgeführt, die nicht (NOT IN) mit »B« beginnen.
Sub-Selects sind vor allem bei verknüpften Tabellen häufiger im Einsatz.
Sub-Selects sind meist langsamer als JOIN und äquivalent verwendbar. Es gibt aber
auch Fälle, in denen sie sich nicht gegenseitig ersetzen können. In der Praxis
braucht man beides.

UNION
Mit UNION lassen sich zwei Abfragen kombinieren. Das Resultat können zwei ver-
schiedene, aber auch zwei gleichartig strukturierte Tabellen sein. Die Kombina-




460
Der MySQL-Dialekt


tion von Abfragen hebt einige Beschränkungen der JOIN-Verfahren auf. Das
Schlüsselwort muss immer zwischen zwei gültigen SELECT-Abfragen stehen:
SELECT * FROM Wetter UNION SELECT * FROM Stadt
Beim programmtechnischen Zugriff mittels PHP5 müssen spezielle Abfrageme-
thoden verwendet werden, um die unterschiedlich strukturierten Tabellen ausle-
sen zu können.



11.3 Der MySQL-Dialekt
Dieser Abschnitt zeigt die Besonderheiten, Funktionen und Eigenarten von
MySQL 4 und teilweise auch MySQL 5. Soweit MySQL sich an das im vorherge-
henden Abschnitt beschriebene Standard-SQL hält, wird dies nicht nochmals wie-
derholt.


Grobe Abweichungen vom SQL92-Standard
Die folgende Liste zeigt in loser Folge einige der wichtigsten Erweiterungen,
Funktionen und Einschränkungen in MySQL. Die meisten Angaben gelten für
MySQL ab Version 4.1, MySQL 5 zeichnet sich demgegenüber vor allem durch
höhere Stabilität, Geschwindigkeit und (nach der finalen Version) Zuverlässigkeit
aus.
í   CREATE TABLE t2 LIKE t1
    Erzeugt eine neue Tabelle t2, die exakt denselben Aufbau wie t1 hat.
í   INSERT ON DUPLICATE KEY UPDATE
    Fügt Daten ein, bzw. aktualisiert, wenn der Primärschlüssel bereits existiert.
    Das ist eine elegante Kombination aus INSERT und UPDATE.
í   Erweiterungen bei den GROUP BY-Funktionen:           STD,   BIT_OR,     BIT_AND,
    GROUP_CONCAT(SpaltenName SEPARATOR ',')
    Die neue Aggregat-Funktion GROUP_CONCAT, kann die Werte einer Abfrage als
    Zeichenkette zusammenfassen. Es kann ein Trennzeichen ähnlich wie in
    PHPs join-Funktion angegeben werden.
í   LIKE kann auch auf numerische Spalten angewendet werden.




                                                                                 461
Datenbankprogrammierung


í     Der SELECT-Befehl ist um zwei Schlüsselwörter erweitert worden: INTO OUTFILE
      und STRAIGHT_JOIN.
í     Neu sind die Befehle OPTIMIZE TABLE und SHOW.
í     GROUP BY muss nicht alle Spalten enthalten, die ausgegeben werden.
í     Zusätzlich zu den Operatoren OR und AND können die Symbole || und 
      genutzt werden. Für die Verknüpfung von Zeichenketten muss anstatt || die
      Funktion CONCAT verwendet werden.
í     Der Operator % kann als Synonym für MOD eingesetzt werden.
í     Logische Operatoren können auch auf der linken Seite eines SELECT-Befehls
      geschrieben werden:
      SELECT spalte1 = 100 FROM table
í     Der Abruf der letzten automatisch generierten ID erfolgt mit der Funktion
      LAST_INSERT_ID().
í     Diverse neue Funktionen, unter anderem versteht MySQL reguläre Ausdrücke
      mit REGEXP und RLIKE (siehe Liste im nächsten Abschnitt).
í     Neue Befehle: REPLACE ersetzt UPDATE und INSERT. Ist der Datensatz vorhanden
      (identifiziert am Primärschlüssel), wird INSERT ausgeführt, andernfalls UPDATE.
í     FLUSH löscht Statusinformationen.


Datentypen
Die Datentypen sind in MySQL sehr umfangreich. Die folgende Tabelle zeigt
alles, was geht, unabhängig davon, ob es dem SQL92-Standard entspricht oder
nicht.

Datentyp                                Beschreibung
TINYINT [(M)] [UNSIGNED] [Z]            Kleine Ganzzahlen, von 0 bis 255 oder -128 bis 127
SMALLINT [(M)] [UNSIGNED] [Z]           Ganzzahlen, entweder von 0 bis 65.535 oder von
                                        -32.768 bis +32.767
MEDIUMINT [(M)] [UNSIGNED] [Z] Ganzzahlen, entweder von 0 bis 16.777.215 oder
                                        von -8.388.608 bis +8.388.607

Tabelle 11.4: Datentypen in MySQL (Z steht für ZEROFILL, M für die Stellenzahl)


462
Der MySQL-Dialekt



Datentyp                            Beschreibung
INT [(M)] [UNSIGNED] [Z]            Ganzzahlen, entweder von 0 bis 4.294.967.295 oder
INTEGER                             von -2.147.283.648 bis +2.147.283.647
BIGINT [(M)] [UNSIGNED] [Z]         Ganzzahlen, entweder von 0 bis
                                    18.446.744.073.709.551.615 oder von
                                    -9.223.372.036.854.775.808 bis
                                    +9.223.372.036.854.775.807
FLOAT(precision) [Z]                Fließkommazahl, immer vorzeichenbehaftet.
                                    precision kann 4 oder 8 sein; 4 steht für einfache
                                    Genauigkeit, 8 für doppelte.
FLOAT[(M,D)] [Z]                    Fließkommazahl, deren Wertebereich von
                                    -3,40282346638 bis -1,175494351-38 reicht und die 0
                                    sowie den Wertebereich von +1,175494351-38 bis
                                    +3,40282346638 umfasst.
DOUBLE[(M,D)] [Z]                   Fließkommazahl, deren Wertebereich von
DOUBLEPRECISION[(M,D)] [Z]          -1,7976931348623157308 bis -2,2250738585072014-308,
REAL[(M,D)] [Z]                     und von -2,2250738585072014-308 bis
                                    +1,7976931348623157308 reicht, inklusive der 0.
DECIMAL(M,D) [Z]                    Ungepackte Fließkommazahl, immer vorzeichenbe-
NUMERIC                             haftet. Zahlen werden als Zeichenkette gespeichert,
                                    jede Ziffer steht in einem Byte. Das Komma, Vor-
                                    zeichen usw. belegen jeweils ein Byte.
DATE                                Datum im Format »YYYY-MM-DD«. Der Werte-
                                    bereich geht vom 1.1.1000 bis zum 31.12.9999.
DATETIME                            Datum und Zeit im Format
                                    »YYYY-MM-DD hh:mm:ss«.
TIMESTAMP[(M)]                      Zeitstempel, Wertebereich von 1.1.1970 bis zum
                                    31.12.2036. Für die Angabe des Anzeigebereiches
                                    M gilt:
                                    14: YYYYMMDDhhmmss
                                    12: YYMMDDhhmmss
                                    8: YYYYMMDD
                                    6: YYMMDD
Tabelle 11.4: Datentypen in MySQL (Z steht für ZEROFILL, M für die Stellenzahl) (Forts.)



                                                                                         463
Datenbankprogrammierung



Datentyp                               Beschreibung
TIME                                   Zeit im Wertebereich von -838:59:59 bis +838:59:59
                                       mit dem Ausgabeformat »hh:mm:ss«.
YEAR                                   Jahr, Wertebereich 1901 bis 2155, Ausgabe YYYY.
CHAR(M) [BINARY]                       Zeichenkette fester Länge M. Wertebereich 1 bis
                                       255. Leerzeichen am Ende werden automatisch für
                                       die Ausgabe entfernt. Sortieren und Selektieren
                                       berücksichtigt Groß- und Kleinschreibung nicht,
                                       wenn Sie nicht BINARY verwenden.
VARCHAR(M) [BINARY]                    Zeichenkette variabler Länge, maximal M. Werte-
                                       bereich 1 bis 255. Leerzeichen am Ende werden
                                       automatisch für die Ausgabe entfernt. Sortieren und
                                       Selektieren berücksichtigt Groß- und Kleinschrei-
                                       bung nicht, wenn Sie nicht BINARY verwenden.
TINYBLOB, TINYTEXT                     BLOB oder TEXT mit maximal 255 Byte

BLOB, TEXT                             BLOB oder TEXT mit maximal 65.535 Byte

MEDIUMBLOB, MEDIUMTEXT                 BLOB oder TEXT mit maximal 16.777.215 Byte

LONGBLOB, LONGTEXT                     BLOB oder TEXT mit maximal 4.294.967.295 Byte

ENUM('wert1', 'wert2', ...,)           Aufzählung. Ein Feld dieses Typs kann nur eine
                                       Zeichenkette enthalten, die einem Objekt der Auf-
                                       zählung entspricht.
SET('wert1', 'wert2' , ...,)           Wie ENUM, kann aber mehrere Werte aus der Liste
                                       enthalten.

Tabelle 11.4: Datentypen in MySQL (Z steht für ZEROFILL, M für die Stellenzahl) (Forts.)


Funktionen
Funktionen sind schlecht standardisiert. Jede Datenbank kocht ihr eigenes Süpp-
chen. MySQL ist da keine Ausnahme. Deshalb finden Sie an dieser Stelle alle
Funktionen der aktuellen Version mit entsprechenden Einsatzhinweisen.




464
Der MySQL-Dialekt


Funktionen für mathematische Berechnungen

Funktion                     Beschreibung
ABS(x)                       Absoluter Betrag der Zahl x.
ACOS(x)                      Der Arkuskosinus der Zahl x.
ASIN(num)                    Der Arcussinus der Zahl x.
ATAN(num)                    Der Arkustangens der Zahl x.
ATN2(num1, num2)             Der Arkustangens (Winkel) zwischen num1 und num2.
BIT_COUNT(num)               Die Anzahl der Bits, die in einer Zahl 1 sind.
CEILING(x)                   Die kleinste Ganzzahl größer oder gleich dem Ausdruck
COS(x)                       Der Kosinus der Zahl x.
COT(x)                       Der Kotangens der Zahl x.
CRC32(expr)                  Die zyklische Prüfsumme eines Ausdrucks als 32-Bit-Wert.
DEGREES(x)                   Eine Umrechnung von Radiant in Grad.
num1 DIV num2                Eine Ganzzahldivision, funktioniert auch mit sehr großen
                             Zahlen (BIGINT).
EXP(x)                       Die Potenz zur Basis e.
FLOOR(x)                     Die größte Ganzzahl kleiner oder gleich dem angegebe-
                             nen numerischen Ausdruck.
GREATEST(x1,x2,..)           Gibt den größten Wert der Liste zurück.
LEAST(x1,x2,...)             Gibt den kleinsten Wert der Liste zurück.
LN(x)                        Der natürliche Logarithmus der Zahl x.
LOG(x), LOG(b, x)            Der natürliche Logarithmus (ohne b) oder Logarithmus
                             zur Basis b.
LOG2(x)                      Der Logarithmus zur Basis 2 der Zahl x.
LOG10(x)                     Der dekadische Logarithmus der Zahl x.

Tabelle 11.5: Mathematische Funktionen



                                                                                     465
Datenbankprogrammierung



Funktion                         Beschreibung
MOD(n, m)                        Modulus, das ist der Rest einer Ganzzahldivision. Alle drei
n % m                            Schreibweisen sind völlig identisch im Verhalten.
n MOD m

PI()                             Die Konstante π
POWER(x,y)                       Potenzrechnung (x hoch y).
RADIANS(x)                       Umrechnung von Grad in Radiant der Zahl x.
RAND(x)                          Ermittelt eine Zufallszahl, x ist dabei optional und
                                 bestimmt, wenn angegeben, den Startwert.
ROUND(x, d)                      Rundet Werte mathematisch, die Angabe der Stellen d ist
                                 optional, ohne Angabe wird auf ganze Zahlen gerundet
SIGN(x)                          Das Vorzeichen der Zahl x, gibt -1, 0 oder 1 zurück.
SIN(x)                           Der Sinus der Zahl x.
SQRT(x)                          Die Quadratwurzel der Zahl x.
TAN(x)                           Der Tangens der Zahl x.
TRUNCATE(x, d)                   Gibt die Zahl x, gekürzt auf d Dezimalstellen, zurück.
Tabelle 11.5: Mathematische Funktionen (Forts.)


Funktionen zur Steuerung des Kontrollflusses

Funktion                         Beschreibung
ISNULL(expr)                     Wertet den Ausdruck expr aus und gibt 1 zurück, wenn der
                                 Ausdruck NULL ist, sonst 0.
IFNULL(expr1, expr2)             Wertet den Ausdruck expr1 aus und gibt expr2 zurück,
                                 wenn der Ausdruck NULL ist, sonst expr1
IF(expr1,expr2,expr3)            Wenn der Ausdruck expr1 Wahr ist, wird expr2 zurückge-
                                 geben, sonst expr3

Tabelle 11.6: Diese Funktionen können die Auswahl Bedingungsabhängig ändern




466
Der MySQL-Dialekt



Funktion                      Beschreibung
CASE val                      Eine Mehrfachverzweigung, die etwa dem switch in PHP
    WHEN cval THEN result     entspricht. Der Wert val wird mit cval verglichen und bei
  [WHEN cval THEN             Gleichheit wird result zurückgegeben. Ansonsten wird mit
result]                       dem nächsten Zweig fortgesetzt.
  [ELSE result]
END

NULLIF(expr1, expr2)          Gibt NULL zurück, wenn expr1 gleich expr2 ist.
Tabelle 11.6: Diese Funktionen können die Auswahl Bedingungsabhängig ändern (Forts.)


Funktionen für aggregierende Berechnungen

Funktion                      Beschreibung
AVG(Ausdruck)                 Der Durchschnitt der Felder.
COUNT(Ausdruck)               Die Anzahl der Felder.
COUNT(DISTINCT Ausdruck)      Die Anzahl eindeutiger Felder.
COUNT (*)                     Repräsentiert die Anzahl aller Datensätze einer Tabelle.
GROUP_CONCAT(Ausdruck)        Verbundene Zeichenkette aller Teilfelder.
SUM(Ausdruck)                 Die Summe der Felder (Addition).
MAX(Ausdruck)                 Das Feld mit dem größten Wert bestimmt das Ergebnis.
MIN(Ausdruck)                 Das Feld mit dem kleinsten Wert bestimmt das Ergebnis.
STD(Ausdruck)                 Statistische Standardabweichung aller Werte der Liste
STDDEV(Ausdruck)

BIT_OR(Ausdruck)              Führt ein bitweises Oder aus.
BIT_AND(Ausdruck)             Führt ein bitweises Und aus.
BIT_XOR(Ausdruck)             Führt ein bitweises Exklusives Oder aus.

Tabelle 11.7: Aggregat-Funktionen




                                                                                      467
Datenbankprogrammierung


Funktionen für Systeminformationen

Funktion                     Beschreibung
BENCHMARK(count, expr) Die Funktion führt einen Ausdruck expr so oft aus, wie count
                             angibt. Bei der Ausführung auf Kommandozeile wird die
                             gesamte Ausführungszeit ausgegeben. Man kann damit die
                             Effizienz seiner Ausdrücke testen.
CHARSET(str)                 Ermittelt, in welchem Zeichensatz eine Zeichenfolge
                             definiert ist.
COERCIBILITY(str)            Ermittelt die Zugehörigkeit einer Zeichenfolge zu einem
                             Vergleichszeichensatz. Gibt einen Wert zwischen 0 und 3
                             zurück, wobei 0 explizite Vergleichbarkeit (höchster Rang),
                             1 keine Vergleichbarkeit, 2 implizite Vergleichbarkeit und 3
                             übergehende Vergleichbarkeit bedeutet.
COLLATION(str)               Ermittelt den Vergleichszeichensatz, der die Sortierkriterien
                             bestimmt.
CONNECTION_ID()              Nummer des Threads, der die aktuelle Verbindung verar-
                             beitet.
CURRENT_USER()               Aktueller MySQL-Nutzername mit Rechnernamen.
DATABASE()                   Gibt den Namen der aktuellen Datenbank aus.
USER() SYSTEM_USER()         Aktueller MySQL-Nutzername
SESSION_USER()

FORMAT(n, d)                 Formatiert eine Zahl n mit Kommata als Tausendergruppen-
                             symbol und Punkt als Dezimaltrennzeichen mit d Dezimal-
                             stellen.
FOUND_ROWS()                 Anzahl der Reihen, so wie die Abfrage ohne LIMIT erfolgt
                             wäre, auch wenn LIMIT benutzt wurde.
LAST_INSERT_ID()             Gibt den zuletzt erzeugten Wert einer AUTO_INCREMENT-Spalte
                             zurück.
VERSION()                    Gibt die Versionsnummer des MySQL-Server an.
Tabelle 11.8: Die System- und Informationsfunktionen




468
Der MySQL-Dialekt



Funktion                    Beschreibung
GET_LOCK(str, to)           Erzeugt eine Verriegelung (Lock) mit dem Namen str und
                            dem Zeitüberschreitungswert to.
RELEASE_LOCK(str)           Gibt die Verriegelung str wieder frei.
IS_FREE_LOCK(str)           Prüft, ob die Verriegelung str frei ist.
INET_ATON(expr)             Die Numerische Entsprechung einer als Zeichenkette ange-
                            gebenen IP.
INET_NTOA(expr)             Die IP-Nummer in Punktschreibweise aus einer Zahl.
UUID()                      Universal Unique Identifier für RPC-Verbindungen.

Tabelle 11.8: Die System- und Informationsfunktionen (Forts.)


Verschlüsselungsfunktionen

Funktion                       Beschreibung
PASSWORD(str)                  Erzeugt ein Kennwort zur Zeichenkette str.
ENCRYPT(str, seed)             Erzeugt ein Kennwort zur Zeichenkette str und mit dem
                               Startwert seed. Nutzt das Unix-Kommando crypt. Unter
                               Windows wird NULL zurückgegeben.
ENCODE(str, pass)              Einfaches Verschlüsselungsverfahren auf Basis des Kenn-
                               worts pass. Zum Speichern sollten BLOB-Spalten verwen-
                               det werden.
DECODE(str, pass)

SHA(str)                       Erstellt einen Hashwert nach SHA bzw. SHA1.
SHA1(str)

MD5(str)                       Erstellt einen Hashwert nach MD5.
DES_ENCRYPT(str, key)          Triple-DES-Ver- und Entschlüsselung. Kann nur genutzt
                               werden, wenn MySQL mit SSL-Unterstützung kompiliert
                               wurde.
DES_DECRYPT(str, key)

Tabelle 11.9: Verschlüsselungsfunktionen



                                                                                           469
Datenbankprogrammierung



Funktion                         Beschreibung
AES_ENCRYPT(str, key)            AES-(Advanced Encryption Standard)-Ver- und Entschlüs-
                                 selung. Kann nur genutzt werden, wenn MySQL mit SSL-
                                 Unterstützung kompiliert wurde.
AES_DECRYPT(str, key)

Tabelle 11.9: Verschlüsselungsfunktionen (Forts.)


Zeichenkettenfunktionen

Funktion                         Beschreibung
ASCII(str)                       Gibt den ASCII-Code des Zeichens str zurück. Hat str
                                 mehr als ein Zeichen, wird nur das erste Zeichen über-
                                 prüft.
CHAR(n,...)                      Wandelt die Zahlen n in die entsprechenden ASCII-Zei-
                                 chen um. Mehrere Argumente werden zu einer Zeichen-
                                 kette kombiniert.
CHAR_LENGTH(str)                 Die echte Zeichenlänge einer Zeichenkette. Manche Zei-
CHARACTER_LENGTH(str)            chen bestehen aus mehreren Bytes, sodass LENGTH die fal-
                                 sche Länge liefert, während CHAR_LENGTH die erwartete
                                 Zahl liefert.
COMPRESS(str)                    Komprimiert eine Zeichenfolgen nach dem ZIP-Verfah-
                                 ren.
CONCAT(str,...)                  Verknüpft alle Argumente zu einer Zeichenkette. Wenn
                                 eines der Argumente NULL ist, wird NULL zurückgegeben.
ELT(n, str1, str2,...)           Gibt die durch n bezeichnete Zeichenkette zurück: str1,
                                 wenn n=1 usw.
EXPORT_SET(bit, on, off,         Exportiert einen Bitwert in eine Zeichenfolge, in der jedes
sep, number)                     Zeichen ein Bit repräsentiert. Das Zeichen on bestimmt
                                 Bits mit dem Wert 1, off steht für 0, sep bestimmt ein
                                 Trennzeichen und number die Anzahl der Bits, die aus
                                 dem Wert bit ausgewertet werden sollen.
Tabelle 11.10: Zeichenkettenfunktionen




470
Der MySQL-Dialekt



Funktion                       Beschreibung
FIELD(str,str1,str2..)         Gibt die Position von str in str1, str2 usw. zurück: Wenn
                               str2=str, wird 2 zurückgegeben.
FIND_IN_SET(str, list)         Gibt die Position von str in der Liste list zurück. Die Liste
                               besteht aus kommaseparierten Werten.
INSERT(str,st,len,new)         Fügt len Zeichen der Zeichenkette new an der Stelle st der
                               Zeichenkette str ein.
INSTR(str, sub)                Entspricht LOCATE, nur die Argumente sind vertauscht.
LCASE, LOWER(str)              Wandelt in Kleinbuchstaben um.
LEFT(str, len)                 Gibt len Zeichen vom linken Ende der Zeichenkette str
                               zurück.
LENGTH(str)                    Länge der Zeichenketten str.
LOAD_FILE(name)                Lädt eine Datei und gibt den Inhalt als Zeichenkette
                               zurück.
LOCATE(sub, str)               Bestimmt die Position der Zeichenkette sub in der Zei-
POSITION(sub IN str)           chenkette str.
LPAD(str,len,pad)              Fügt pad links an str an, gibt jedoch nur len Zeichen
                               zurück.
LTRIM(str)                     Entfernt Leerzeichen vom linken Ende.
MAKE_SET(bits,list)            Wählt die Elemente der Liste list anhand der gesetzten
                               Bits in bits aus.
MID(str, pos, len)             Gibt len Zeichen von Position pos an der Zeichenkette str
                               zurück.
QUOTE()                        Fügt Anführungszeichen hinzu und markiert solche
                               im Text mit einem Backslash. NULL-Werte werden als
                               Zeichenfolge NULL zurückgegeben.
REPEAT(str, count)             Wiederholt die Zeichenkette str count mal.
REPLACE(str,from,to)           Ersetzt alle Vorkommen von from in der Zeichenkette str
                               durch to.

Tabelle 11.10: Zeichenkettenfunktionen (Forts.)



                                                                                          471
Datenbankprogrammierung



Funktion                         Beschreibung
REVERSE(str)                     Dreht eine Zeichenkette um.
RIGHT(str, len)                  Gibt len Zeichen vom rechten Ende der Zeichenkette str
                                 zurück.
RPAD(str,len,pad)                Fügt pad rechts an str an, gibt jedoch nur len Zeichen
                                 zurück.
RTRIM(str)                       Entfernt Leerzeichen vom rechten Ende.
SOUNDEX(str)                     Gibt die Lautfolge für str zurück.
SPACE(n)                         Gibt n Leerzeichen zurück.
SUBSTRING(str FROM len           Andere Schreibweisen für RIGHT und MID.
SUBSTRING(str,pos,len)
SUBSTRING(str FROM pos
          FOR len)

SUBSTRING(str, pos)              Gibt Teile von str ab Position pos zurück.
SUBSTRING_INDEX(str,             Gibt den linken Teil einer Zeichenkette zurück, nachdem
  delimiter, count)              count mal das Zeichen delimiter aufgetreten ist.
TRIM                             BOTH entspricht TRIM, LEADING entspricht LTRIM, TRAILING
BOTH|LEADING|TRAILING            entspricht RTRIM, FROM ist optional, rem ist optional und
rem FROM str                     steht für das zu entfernende Zeichen, str wird bearbeitet.
TRIM(str)                        Entfernt Leerzeichen von beiden Enden der Zeichenkette
                                 str.
UCASE, UPPER(str)                Wandelt Zeichen in Großbuchstaben um.
UNCOMPRESS(str)                  Hebt ein Komprimierung wieder auf (siehe auch
                                 COMPRESS).

Tabelle 11.10: Zeichenkettenfunktionen (Forts.)




472
Der MySQL-Dialekt


Zahlenformatierungen

Funktion                 Beschreibung
CONV(n,from,to)          Konvertiert Zahlen zwischen verschiedenen Zahlenbasen.
                         Zurückgegeben wird immer eine Zeichenkette mit der ermittel-
                         ten Zahl. n ist die Zahl, from die ursprüngliche Zahlenbasis, to
                         die Zielbasis.
BIN(n)                   Gibt eine Zahl n als Zeichenkette im Binärformat zurück,
                         BIN(7) ergibt beispielsweise »111«.

BIT_LENGTH(n)            Gibt die Länge einer Zeichenkette in Bits zurück.
OCT(n)                   Gibt eine Zahl n als Zeichenkette im Oktalformat zurück.
HEX(n)                   Gibt eine Zahl n als Zeichenkette im Hexadezimalformat
                         zurück.
UNHEX()                  Umkehrfunktion zu HEX

Tabelle 11.11: Zeichenkettenfunktionen für Zahlen


Datums- und Zeitfunktionen

Funktion                      Beschreibung
DAYOFWEEK(date)               Der Tag der Woche, 1 ist Sonntag (1 – 7).
WEEKDAY(date)                 Der Tag der Woche, 0 ist Montag (0 – 6).
DAYOFMONTH(date)              Der Tag des Monats (1 – 31).
DAYOFYEAR(date)               Der Tag des Jahres (1 – 366).
MONTH(date)                   Der Monat (1 – 12).
DAYNAME(date)                 Der Wochentag (englisch, ausgeschrieben).
MONTHNAME(date)               Der Monat (englisch, ausgeschrieben).
QUARTER(date)                 Das Quartal (1 – 4).

Tabelle 11.12: Datums- und Zeitfunktionen




                                                                                      473
Datenbankprogrammierung



Funktion                        Beschreibung
WEEK(date)                      Die Woche im Jahr (0 – 52). Das optionale Argument
WEEK(date, first)               first bestimmt, welcher Wochentag als Beginn gezählt
                                wird. 0 entspricht dem Sonntag.
YEAR(date)                      Das Jahr (1000 – 9999).
HOUR(time)                      Die Stunde (0 – 23).
MINUTE(time)                    Die Minute (0 – 59).
SECOND(time)                    Die Sekunde (0 – 59).
PERIOD_ADD(p,n)                 Addiert n Monate zur Periode p. Die Periode wird im For-
                                mat YYYYMM oder YYMM erwartet. Zurückgegeben wird
                                immer die Langform YYYYMM.
PERIOD_DIFF(p1,p2)              Gibt die Differenz in Monaten zwischen p1 und p2
                                zurück
TO_DAYS(date)                   Die Anzahl der Tage seit dem Jahr 0.
FROM_DAYS(dn)                   Ermittelt ein Datum aus der Tageszahl dn.
CURDATE()                       Das aktuelle Datum (Systemzeit des Servers).
CURRENT_DATE

CURTIME()                       Die aktuelle Zeit (Systemzeit des Servers)
CURRENT_TIME

NOW()                           Datum und Uhrzeit (Systemzeit des Servers)
SYSDATE()
CURRENT_TIMESTAMP

UNIX_TIMESTAMP                  Unix Timestamp (Sekunden in GMT seit den 1.1.1970, 0
                                Uhr). Die Funktion wird auch von der Windows-Version
                                unterstützt.
FROM_UNIXTIME(stp)              Gibt ein Datum entsprechend dem Unix Timestamp stp
                                zurück.
FROM_UNIXTIME(stp,              Gibt ein Datum entsprechend dem Unix Timestamp stp
format)                         zurück. Das Datum ist entsprechend format formatiert.

Tabelle 11.12: Datums- und Zeitfunktionen (Forts.)




474
Der MySQL-Dialekt



Funktion                       Beschreibung
SEC_TO_TIME(sec)               Rechnet die Angabe in Sekunden in das Format
                               HH:MM:SS um.
TIME_TO_SEC(time)              Rechnet eine Zeitangabe in Sekunden um.
Tabelle 11.12: Datums- und Zeitfunktionen (Forts.)


Datumsformatierungen

Typ-Konstante        Bedeutung                           Formatangabe
SECOND               Sekunde                             ss
MINUTE               Minute                              mm
HOUR                 Stunde                              hh
DAY                  Tag                                 DD
MONTH                Monat                               MM
YEAR                 Jahr                                YY
MINUTE_SECONDS       Minute und Sekunde                  mm:ss
HOUR_MINUTE          Stunde und Minute                   hh:mm
DAY_HOUR             Tag und Stunde                      DD hh
YEAR_MONTH           Jahr und Monat                      YY-MM
HOUR_SECOND          Stunde, Minute, Sekunde             hh:mm:ss
DAY_MINUTE           Tag, Stunde, Minute                 DD hh:mm
DAY_SECONDS          Tag, Stunde, Minute, Sekunde        DD hh:mm:ss
Tabelle 11.13: Zeittypen für Datumsberechnungen




                                                                                     475
Datenbankprogrammierung



Funktion                               Berechnung
DATE_FORMAT(date, format)              Formatiert den Wert date mit dem Format format, des-
                                       sen Elemente wurden in der vorherigen Tabelle bereits
                                       beschrieben.
TIME_FORMAT(time, format)              Formatiert den Wert time mit dem Format format, des-
                                       sen Elemente wurden in der vorherigen Tabelle bereits
                                       beschrieben.
Tabelle 11.14: Datumsformatierungen


Code Bedeutung (Wertebereich)                    Code Bedeutung (Wertebereich)
%M      Monatsname (January – December) %k               Stunde (0 – 23) ohne führende Null
%W      Wochenname (Monday – Sunday)             %h      Stunde (01 – 12) mit führender
                                                         Null
%D      Monat mit engl. Suffix                   %I      Stunde (1 – 12) ohne führende Null
        (1st, 2nd, 3rd usw.)
%Y      Jahr mit 4 Stellen                       %l      Minuten (0 – 59)
%y      Jahr mit 2 Stellen                       %i      Minuten (00 – 59) mit führender
                                                         Null
%a      Abgekürzter Wochentag                    %n      Zeit, 12-Stunden-Format: hh:mm:ss
        (Mon – Sun)                                      AM|PM
%d      Tag des Monats (00 – 31)                 %T      Zeit, 24-Stunden-Format: hh:mm:ss
%e      Tag des Monats (0 – 31)                  %S      Sekunde (00 – 59) mit führender
                                                         Null
%m      Monat (00 – 12) mit führender Null %s            Sekunde (0 – 59)
%c      Monat (0 – 12) ohne führende Null %p             AM oder PM
%b      Abgekürzter Monatsname                   %w      Wochentag
        (Jan–Dec)                                        (0=Sonntag, 6=Samstag)
%j      Tag des Jahres (000 – 366)               %U      Woche, Sonntag ist der erste Tag
                                                         der Woche (00 – 52)

Tabelle 11.15: Platzhalter für Datumsformatierungen



476
Erste Schritte mit MySQL und MySQLi



Code Bedeutung (Wertebereich)                Code Bedeutung (Wertebereich)
%H      Stunde (00 – 23) mit führender       %u      Woche, Montag ist der erste Tag der
        Null                                         Woche (00 – 52)
%%      Prozentzeichen
Tabelle 11.15: Platzhalter für Datumsformatierungen (Forts.)



11.4 Erste Schritte mit MySQL und MySQLi
MySQLi (MySQL improved) ist eine neue Erweiterung von PHP zur besseren
Unterstützung von MySQL. Im Bundle mit dem neuen, objektorientierten Ansatz
und der neuen MySQL-Version 4 ergibt sich ein außerordentlich leistungsfähiges
System.


MySQLi vorbereiten
Seit PHP5 gehört die MySQL-Unterstützung aus rechtlichen Gründen nicht mehr
zum integrierten Paket. In PHP 4 waren die MySQL-Module noch fest in den
Kern kompiliert. Mit der Version 5 ist nun alles wieder wie bereits bei PHP3 – man
muss die MySQL-Dateien zusätzlich einbinden. Auf dem Entwicklungssystem
unter Windows erfolgt dies durch das Auskommentieren der entsprechenden Zeile
in der Datei php.ini:
extension=php_mysql.dll
Dann haben Sie noch die Wahl, statt der alten MySQL-Module die neuen, objekt-
orientierten MySQLi-Dateien zu nutzen:
extension=php_mysqli.dll
In diesem Buch werden die neuen Module vorgestellt. Über weite Strecken ist
Syntax und Nutzung praktisch identisch, sodass eine Umstellung nicht schwer ist.




                                                                                          477
Datenbankprogrammierung



Verbindung testen
Nach der Installation von MySQL steht ein kurzer Test an, ob alles geklappt hat.
Unter XP muss dazu der entsprechende Dienst gestartet sein, unter Linux der
MySQL-Daemon.
Dann kommt folgendes Skript zum Zuge, um alles zu testen:

Listing 11.1: mysqliconnect.php – Ein erster Versuch mit MySQL und PHP

$mysqli = new mysqli(localhost, root, , test);
$query = $mysqli-query(SELECT version() AS version);
$result = $query-fetch_assoc();
echo Wir arbeiten mit MySQL Version {$result['version']};
$mysqli-Close();
Das Skript geht davon aus, dass das Standardkennwort (leer) und der Benutzer-
name (»root«) nicht geändert wurden und dass die Datenbank auf derselben
Maschine wie der Webserver läuft (»localhost«). Haben Sie eine andere Installa-
tion, müssen Sie die Daten in der ersten Zeile entsprechend anpassen.
Die Klasse mysqli1 stellt nun den Zugriff auf die neuen Funktionen bereit. Im Bei-
spiel führt die Instanziierung des Objekts in $mysqli auch gleich zum Öffnen der
Verbindung. Die SQL-Abfrage ermittelt dann die Versionsnummer des Daten-
bankservers:
SELECT version() AS version
Die Methode zur Abfrage der Datenbank heißt query:
$mysqli-query()
Wenn Ergebnisse entstehen, kann man diese mit fetch_assoc in ein assoziatives
Array überführen:
$result = $query-fetch_assoc();
Der Zugriff auf das Array entspricht wieder einfachem PHP, als Schlüsselname
taucht der Alias der SQL-Abfrage auf.
Hat alles geklappt, wird die Verbindung wieder geschlossen. Die Ausgabe sollte
nun in etwa wie nachfolgend gezeigt aussehen:



1   Es gibt auch nach wie vor einen prozeduralen Zugriff, der hier nicht weiter betrachtet wird.



478
Erste Schritte mit MySQL und MySQLi


                                                 Abbildung 11.1:
                                                 Alles neu: PHP5 mit MySQLi und
                                                 MySQL 5.0-alpha

Nun kann man voll loslegen und praktisch mit der Datenbank arbeiten.

          Die folgenden Skripte wurden mit MySQL 4.1 getestet. Die einwand-
          freie Funktionsweise von MySQL 5 kann derzeit nicht garantiert wer-
          den. Außerdem ist mindestens die Version PHP5.1 für die MySQLi-
          Erweiterungen zu empfehlen. Die erste Final, PHP5.0.0, versagte bei
          einigen Abfragen.


Genereller Datenbankzugriff
Für die folgenden Skript wird immer wieder dieselbe Methode zum Datenbankzu-
griff verwendet und als Include-Datei eingebunden. Dies erleichtert Änderungen:

Listing 11.2: mysqli.inc.php – Inhalt der Include-Datei, noch ohne Fehlermanagement

?php
$mysqli = new mysqli(localhost, root, , test);
?
Die Variable $mysqli wird in allen Skripten dieses Kapitels verwendet. Das Einbin-
den erfolgt über folgende Zeile:
include(mysqli.inc.php);
Diese Anweisung wird nicht in jedem Listing immer wieder abgebildet.


Mit der Datenbank arbeiten
Um mit der Datenbank arbeiten zu können, werden zuerst Tabellen benötigt.
Diese können Sie entweder mit einem Werkzeug oder per Skript anlegen. Als
Werkzeug kommen unter Windows das MySQLCC (MySQL Control Center)
oder phpMyAdmin in Frage. Dieser Abschnitt zeigt alle nötigen Schritte in Skript-
form.




                                                                                       479
Datenbankprogrammierung


Tabellen vorbereiten
Zuerst werden die passenden Tabellen benötigt. MySQL verfügt über eine sehr
gute Funktion beim Anlegen von Tabellen. Es kann prüfen, ob die Tabelle bereits
existiert. Damit muss man sich erstmal nicht darum kümmern, ob das Skript
bereits aufgerufen wurde.
Die beiden Tabellen, die hier benötigt werden, enthalten die Bundesländer und
Wetterdaten. Sie haben folgende Definitionen:

Listing 11.3: Definition der Wetter-Tabelle

CREATE TABLE IF NOT EXISTS wetter (
  ID bigint(20) NOT NULL auto_increment,
  Stadt varchar(100) NOT NULL default '',
  Hoch int(2) NOT NULL default '0',
  Tief int(2) NOT NULL default '0',
  BundeslandID bigint(20) default '0',
  PRIMARY KEY (ID),
  KEY StadtIndex (Stadt)
);

Listing 11.4: Definition der Tabelle der Bundesländer

CREATE TABLE IF NOT EXISTS bundesland (
  ID int(2) NOT NULL auto_increment,
  Name varchar(30) NOT NULL default '',
  PRIMARY KEY (ID)
);
Das folgende PHP-Skript nutzt diese Anweisungen, um die Tabellen anzulegen.
Die Angabe von IF NOT EXISTS verhindert, dass die Tabellen erneut erzeugt wer-
den, wenn sie bereits existieren.

Listing 11.5: mysqlicreatetables.php – Die benötigten Datentabellen erzeugen

$tables['Wetter'] = TABLE1
CREATE TABLE IF NOT EXISTS wetter (
  ID bigint(20) NOT NULL auto_increment,
  Stadt varchar(100) NOT NULL default '',
  Hoch int(2) NOT NULL default '0',
  Tief int(2) NOT NULL default '0',
  BundeslandID bigint(20) default '0',



480
Erste Schritte mit MySQL und MySQLi


  PRIMARY KEY (ID),
  KEY StadtIndex (Stadt)
);
TABLE1;
$tables['Bundesland'] = TABLE2
CREATE TABLE IF NOT EXISTS bundesland (
  ID int(2) NOT NULL auto_increment,
  Name varchar(30) NOT NULL default '',
  PRIMARY KEY (ID)
);
TABLE2;
if (is_object($mysqli))
{
    foreach ($tables as $table = $definition)
    {
        $result = $mysqli-query($definition);
        if ($result === FALSE)
        {
            echo Konnte Tabelle '$table' nicht erzeugenbr;
            echo bFehler:/b {$mysqli-error}br;

        }
        else
        {
            echo Tabelle '$table' wurde erzeugt/überprüftbr;
        }
    }
}
Das Skript nutzt ein Array zum Zusammenstellen der Kommandos. Die Elemente
des Arrays enthalten die SQL-Anweisungen und werden dann einzeln an die
Datenbank gesendet. Die Methode query gibt bei Abfragen mit Ergebnissen ein
Ergebnisobjekt zurück. Bei Anweisung, die nichts zurückgeben, wie beispielsweise
CREATE TABLE wird entweder TRUE (Erfolg) oder FALSE (Fehler) zurückgegeben.

                                      Abbildung 11.2:
                                      Ausgabe bei erfolgreicher Abarbeitung

Der Umgang mit Fehlern ist generell sehr wichtig. Deshalb ist die Ausgabe von
Fehlermeldungen in fast jedem Skript zu finden. Der Zugriff auf die letzte Fehler-
meldung erfolgt mit $mysqli über die Eigenschaft error:



                                                                                     481
Datenbankprogrammierung


echo bFehler:/b {$mysqli-error}br;
Provozieren Sie einen Fehler, indem Sie die Syntax der SQL-Anweisung ein
wenig »unqualifiziert« verändern. Sie erhalten dann beispielsweise folgende Aus-
gabe:


                                                                      Abbildung 11.3:
                                                                      Ausgabe mit
                                                                      Fehlermeldung


Die Tabellen füllen
Es gibt mehrere Wege, Tabellen zu füllen:
í     Formulare werden verwendet – dann gibt der Benutzer die Daten von Hand
      ein.
í     Abfrage einer anderen Datenbank oder Tabelle.
í     Durch das Auslesen von Textdateien.
í     Durch den Import von XML.
Für die Generierung der Bundesländer bietet es sich an, eine Textdatei zu verwen-
den, die die Namen enthält. Das ist einfacher als alle INSERT-Anweisungen aufzu-
schreiben. Die nötigen Techniken wurden alle bereits behandelt. Zuerst die
Textdatei:

Listing 11.6: bl.txt im Verzeichnis /data – Textdatei mit Bundesländern

'Baden-Württemberg'
'Bayern'
'Berlin'
'Brandenburg'
'Bremen'
'Hamburg'
'Hessen'
'Mecklenburg-Vorpommern'
'Niedersachsen'
'Nordrhein-Westfalen'
'Rheinland-Pfalz'
'Saarland'
'Sachsen'



482
Erste Schritte mit MySQL und MySQLi


'Sachsen-Anhalt'
'Schleswig-Holstein '
'Thüringen'
Auch für ein paar Wetterdaten wurde eine Textdatei vorbereitet. Da hier mehrere
Spalten existieren, wurde ein Trennzeichen definiert, das Komma. Die erste Zeile
enthält die Feldnamen:

Listing 11.7: wetter.txt im Verzeichnis /data – Textdatei mit Wetterdaten

Stadt,Hoch,Tief,BundeslandID
'Berlin',22,12,3
'Hamburg',20,10,6
'Stuttgart',24,18,1
'München',23,17,2
'Regensburg',23,19,2
'Dresden',19,12,13
'Leipzig',19,12,13
'Wittenberge',19,12,4
'Angermünde',18,12,4
'Frankfurt/Oder',19,13,4
'Cottbus',22,17,4
'Hof',19,14,2
'Nürnberg',19,11,2
'Würzburg',19,16,2
'Augsburg',22,18,2

           Die einfachen Anführungszeichen in den Datendateien vereinfachen
           die Verarbeitung erheblich. Dies ist hier vor allem gemacht worden, um
           die ersten Skripte überschaubar zu halten. In der Praxis gibt es freilich
           Lösungen, die beliebige Daten korrekt einfügen.

Das folgende Skript zeigt eine mögliche Lösung:

Listing 11.8: mysqliinsert.php: Programmgesteuert Datensätze einfügen

$path = 'data';
$imports = array('Wetter' = 'wetter.txt',
                 'Bundesland' = 'bl.txt');
if (is_object($mysqli))
{
    foreach ($imports as $table = $file)



                                                                                          483
Datenbankprogrammierung


      {
          $content = file($path/$file);
          $fields = array_shift($content);
          $mysqli-query(TRUNCATE $table);
          foreach ($content as $line)
          {
              $sql = INSERT INTO $table ($fields) VALUES ($line);
              $result = $mysqli-query($sql);
              if ($result === FALSE)
              {
                  echo Konnte Anweisung nicht ausführenbr;
                  echo bFehler:/b {$mysqli-error}br;

              }
              else
              {
                    echo Anweisung ausgeführt: b$sql/bbr;
              }
          }
      }
}
Dieses Skript beginnt mit der Definition eines Arrays, das die Tabellennamen und
Dateinamen enthält und miteinander verknüpft:
$imports = array('Wetter' = 'wetter.txt', 'Bundesland' = 'bl.txt');
Der Vorteil der vorgestellten Lösung liegt in ihrer leichten Erweiterbarkeit. Um
weitere Tabelle mit Daten zu beschicken, muss man lediglich dieses Array erwei-
tern und natürlich die Daten bereitstellen.
Dann durchläuft die erste foreach-Schleife alle Elemente dieses Arrays:
foreach ($imports as $table = $file)
So erhält man Tabellen- und Dateinamen. Dann werden die Daten in ein weiteres
Array überführt, wozu die Funktion file hervorragend geeignet ist:
$content = file($path/$file);
Die erste Zeile enthält die Feldnamen. Das erste Element eines Arrays lässt sich
mit array_shift extrahieren, sodass in $fields die Feldliste steht:
$fields = array_shift($content);




484
Erste Schritte mit MySQL und MySQLi


Dann wird die jeweils zu bearbeitende Tabelle gelöscht, um zu verhindern, dass
bei mehrfachem Aufruf des Skripts die Daten doppelt erscheinen:
$mysqli-query(TRUNCATE $table);

          Hier ist etwas Vorsicht angebracht! Wenn Sie mit anderen Skripten der
          Wetter-Tabelle weitere Daten hinzugefügt haben, gehen diese durch
          den Aufruf von TRUNCATE unwiderruflich verloren.

Für den Rest der Datei (array_shift extrahiert nicht nur, sondern entfernt auch
gleich die erste Zeile) werden nun die Zeilen gelesen und zu INSERT-Anweisungen
verarbeitet:
foreach ($content as $line)
$sql = INSERT INTO $table ($fields) VALUES ($line);
Der Aufbau einer SQL-Anweisung in einer eigenen Variable ist sinnvoll, um das
dynamisch konstruierte Gebilde leicht überwachen zu können. Die fertige Anwei-
sung wird dann an den SQL-Server gesendet:
$result = $mysqli-query($sql);
Zuletzt folgt noch die bereits bekannte Fehlerausgabe.
Mit den so erstellten Tabellen kann nun gearbeitet werden. Die folgenden
Abschnitte behandeln die bereits im Einführungsteil zu SQL präsentierten Abfra-
gen im praktischen Kontext eines PHP-Skripts.


Einfache Abfragen
Die Abfragetechnik in PHP folgt immer ein und demselben Schema:
1. Verbinden mit der Datenbank.
2. Senden der Abfrage und Erhalt des Ergebnisobjekts.
3. Überführen des Ergebnisobjekts in ein Array.
4. Ausgaben oder Verarbeiten des Arrays.
In selteneren Fällen werden nur einzelne Daten abgefragt und ohne Arrays gear-
beitet. Aufgrund der starken Arrayfunktionen in PHP ist die Nutzung jedoch meist
angebracht.




                                                                                     485
Datenbankprogrammierung


Listing 11.9: mysqlselectfrom.php – Einfache Abfrage mit Ausgabe

  if (is_object($mysqli))
{
     $sql = SELECT Stadt, Hoch, Tief FROM Wetter;
     $result = $mysqli-query($sql);
     while ($rs = $result-fetch_assoc())
     {
         echo WETTER
         Die Tagestemperaturen für {$rs['Stadt']}:
         ul
             liHöchsttemperatur: {$rs['Hoch']} °C/li
             liTiefsttemperatur: {$rs['Tief']} °C/li
         /ul
WETTER;
     }
}
Mit der folgenden Abfrage entsteht ein Ergebnisobjekt:
$result = $mysqli-query($sql);
Diese Objekte beinhalten praktisch die gesamten Daten, die die SQL-Anweisung
zurückgibt. Da es sich um eine Tabelle, also eine zweidimensionale Struktur han-
delt, braucht man zwei Schritte zum Auseinandernehmen. Im ersten Schritt wer-
den die Zeilen abgerufen und jeweils in einem Array gespeichert:
while ($rs = $result-fetch_assoc())
Die Methode fetch_assoc holt die Daten in ein assoziatives Array. Die Schlüssel
werden aus den Spaltennamen gebildet. Sind keine Daten mehr vorhanden, gibt
die Funktion FALSE zurück und der gesamte Ausdruck wird FALSE, woraufhin while
abbricht.
Innerhalb der Schleife kann man jetzt auf die Daten mit der normalen Arraysyntax
zugreifen:
{$rs['Stadt']}
Die Ausgabe zeigt, dass alle Daten durchlaufen werden.
Der Fantasie beim Formatieren sind nun keine Grenzen gesetzt. Schwieriger ist es
denn auch, die passenden SQL-Anweisung zu finden, um bestimmte Details zu
ermitteln. In den folgenden Skripten werden andere Abfragen und andere Aus-
gabemethoden vorgestellt. Dies dient vor allem der Demonstration der Möglich-
keiten.


486
Erste Schritte mit MySQL und MySQLi




                                      Abbildung 11.4:
                                      Ausgabe einer Datenbanktabelle in formatiertem
                                      HTML


Komplexe Abfragen
Als nächstes sollen die durchschnittlichen Höchst- und Tiefsttemperaturen ermit-
telt werden. Dazu eignet sich die Aggregat-Funktion AVG:

Listing 11.10: mysqlselectavgfrom.php – Durchschnittswerte ermitteln

$sql = SELECT AVG(Hoch) AS MittelHoch,       
               AVG(Tief) AS MittelTief FROM Wetter;
$result = $mysqli-query($sql);
$data = $result-fetch_object();
echo Die mittlere Höchsttemperatur       
      beträgt {$data-MittelHoch} °Cbr;
echo Die mittlere Tiefsttemperatur       
      beträgt {$data-MittelTief} °Cbr;




                                                                                        487
Datenbankprogrammierung


Eingesetzt wird hier die Methode fetch_object. Sie gibt den aktuellen Datensatz
als Objekt zurück, oder FALSE, falls keine Daten mehr da sind. Da hier nur skalare
Werte abgefragt werden, ist eine Schleife nicht erforderlich.
Das Objekt enthält eine Eigenschaft für jedes Feld, der Name entspricht auch dem
Feldnamen:
$data-MittelHoch
Im Beispiel wurden die Namen gegenüber den originalen Spaltennamen noch
durch den Alias-Operator AS geändert. Das ist sinnvoll, wenn die Spaltennamen
selbst auch abgefragt werden sollen.

                                           Abbildung 11.5:
                                           Berechnung der Durchschnittstemperaturen

Das folgende Beispiel zeigt, wie gleichzeitig die höchsten und niedrigsten Werte
ermittelt werden.

Listing 11.11: mysqlselectmimaxfrom.php – Minimale und maximale Temperaturen

$sql = SELECT AVG(Hoch) AS MittelHoch,        
               AVG(Tief) AS MittelTief,        
               MIN(Tief) AS Tief,      
               MAX(Hoch) AS Hoch      
        FROM Wetter;
$result = $mysqli-query($sql);
$data = $result-fetch_object();
echo Die mittlere Höchsttemperatur        
      beträgt {$data-MittelHoch} °Cbr;
echo Die mittlere Tiefsttemperatur        
      beträgt {$data-MittelTief} °Cbr;
echo Die höchste Höchsttemperatur beträgt {$data-Hoch} °Cbr;
echo Die niedrigste Tiefsttemperatur beträgt {$data-Tief} °Cbr;
Das Prinzip der Abfrage entspricht hier dem vorhergehenden Beispiel.


                                           Abbildung 11.6:
                                           Der höchste und niedrigste Wert werden
                                           ermittelt




488
Erste Schritte mit MySQL und MySQLi


Bei der Ausgabe fällt auf, dass die Kommastellen wenig praxistauglich sind. Bevor
Sie jetzt auf die Idee kommen, dafür printf oder number_format einzusetzen, ist
ein Blick in die Funktionssammlung von MySQL interessant. Hier wird man bei
ROUND fündig:

Listing 11.12: mysqlselectroundfrom.php – Rundung der Ausgabewerte vor der Ausgabe

$sql = SELECT ROUND(AVG(Hoch),2) AS MittelHoch,           
               ROUND(AVG(Tief),2) AS MittelTief
        FROM Wetter;
$result = $mysqli-query($sql);
$data = $result-fetch_object();
echo Die mittlere Höchsttemperatur       
      beträgt {$data-MittelHoch} °Cbr;
echo Die mittlere Tiefsttemperatur       
      beträgt {$data-MittelTief} °Cbr;
Diese Ausgabe ist schon eher überzeugend, allerdings muss MySQL vorerst bei der
sprachabhängigen Darstellung passen:

                                          Abbildung 11.7:
                                          Gerundete Temperaturwerte


Verknüpfungen abfragen
Als nächstes soll wieder eine Ausgabe der Temperaturen der einzelnen Städte
erfolgen, jedoch mit der Angabe des jeweiligen Bundeslandes.

Listing 11.13: mysqlselectjoin1.php: Zusatzinformationen aus verknüpfter Tabelle holen

$sql = SELECT Stadt, Hoch, Tief, Name AS Bundesland             
        FROM Wetter W JOIN Bundesland B       
            ON W.BundeslandID = B.ID;
$result = $mysqli-query($sql);
while($rs = $result-fetch_object())
{
    echo {$rs-Stadt} ({$rs-Bundesland}):br;
    echo uarr; {$rs-Hoch}°C, darr; {$rs-Tief}°Cbr;
}
Die Abfrageform in PHP ändert sich hier nicht – die gesamte Arbeit erledigt die
SQL-Anweisung. Die Ausgabe zeigt ein für den geringen Aufwand durchaus


                                                                                        489
Datenbankprogrammierung


respektables Ergebnis (die Pfeile werden durch die Entitäten uarr; und darr;
erzeugt):




                               Abbildung 11.8:
                               Informationen aus zwei Tabellen: Städte und Bundesländer

Etwas kniffliger wird es, wenn die Verknüpfung mit Aggregierungen verbunden
werden soll. So könnte man die mittleren Temperaturen in einem Bundesland
abfragen. Dazu werden die Städte aus demselben Bundesland gruppiert und aus
den Werten einer Gruppe der Durchschnittswert berechnet.

Listing 11.14: mysqlselectjoingroup.php – Mittlere Temperaturen nach Bundesland

$sql = SELECT ROUND(AVG(Hoch),2) AS MittelHoch,          
               ROUND(AVG(Tief),2) AS MittelTief,          
               Name AS Bundesland       

490
Referenz MySQLi


        FROM Wetter W JOIN Bundesland B      
            ON W.BundeslandID = B.ID     
            GROUP BY W.BundeslandID     
            ;
$result = $mysqli-query($sql);
while($rs = $result-fetch_object())
{
    echo {$rs-Bundesland}:br;
    echo uarr; {$rs-MittelHoch}°C,;
    echo darr; {$rs-MittelTief}°Cbr;
}
Der Trick besteht hier in der Anwendung von GROUP BY. Erst nach der Gruppie-
rung werden die Aggregat-Funktionen zur Berechnung des Durchschnitts ange-
wendet.




                       Abbildung 11.9:
                       Mittelwerte der Temperaturen pro Bundesland




11.5 Referenz MySQLi
Alle Methoden werden auf einer Instanz der Klasse mysqli oder auf einem Resul-
tatobjekt ausgeführt, beispielsweise:
$mi = new mysqli('localhost', 'root', '', 'test');
$mi-commit();
Alternativ ist immer auch der direkte prozedurale Aufruf möglich:
mysqli_commit();




                                                                                 491
Datenbankprogrammierung



Eigenschaft                Beschreibung
affected_rows              Anzahl der Datensätze, die von der letzten Anweisung betroffen
                           waren (nur für UPDATE/INSERT/REPLACE, nicht jedoch für
                           SELECT).
errno                      Die Nummer des Fehlercodes.
error                      Eine Beschreibung des Fehlers (englisch).
field_count                Anzahl der Spalten, die die letzte Abfrage zurückgegeben hat.
host_info                  Informationen über den Server und die Art der Verbindung, bei-
                           spielsweise »localhost via TCP/IP«.
info                       Informationen über die letzte Abfrage.
insert_id                  Die letzte durch INSERT in einem AUTO_INCREMENT-Feld erzeugte
                           ID.

protocol_version           Die Version des MySQL-Protokolls (aktuell: 10).
sqlstate                   Status einer vorher gesendeten Abfrage.
thread_id                  ID des Threads in dem die Abfrage abgearbeitet wird .
thread_safe                Ermittelt, ob Threadsicherheit besteht.
warning_count              Anzahl der Warnungen, die die letzte Abfrage auslösten.
Tabelle 11.16: Eigenschaften des MySQLi-Objekts


Methode                    Beschreibung
autocommit                 Schaltet die automatische Bestätigung von Transaktionen ein
                           oder aus.
change_user                Ändert den Benutzer für die aktuelle Verbindung.
character_set_name         Gibt den aktuellen Zeichensatz zurück.
close                      Schließt die Verbindung.
commit                     Bestätigt die aktuelle Transaktion.
Tabelle 11.17: Methoden des MySQLi-Objekts




492
Referenz MySQLi



Methode                 Beschreibung
connect                 Öffnet eine neue Verbindung zum MySQL-Server.
get_client_info         Gibt Informationen über die verwendet MySQL-Version
                        zurück.
get_client_version      Gibt Informationen über den MySQL-Client zurück.
get_host_info           Gibt Informationen über den Server und die Verbindung
                        zurück.
init                    Vorbereiten eines MySQLi-Objekts für spätere Verwendung.
info                    Gibt die automatisch erstellte ID der letzten Abfrage zurück.
kill                    Versucht den von MySQL belegten Thread zu beenden.
multi_query             Sendet eine Abfrage an die Datenbank.
more_results            Ermittelt, ob weitere Ergebnissätze von Mehrfachabfragen vor-
                        handen sind.
next_result             Nächste Ergebnissätze von Mehrfachabfragen abholen.
options                 Setzt verschiedene Optionen.
ping                    Sendet einen Ping zur Kontrolle der Verbindung an den Server.
prepare                 Kann eine Abfrage vorbereiten. Vorbereitete Abfragen sind
                        schneller bei wiederholter Ausführung.
query                   Eine Abfrage direkt an den Server senden.
real_connect            Kann eine Verbindung öffnen.
real_query              Kann eine Abfrage ausführen.
rollback                Kann eine Transaktion rückabwickeln.
select_db               Wird eine andere Datenbank als Standard auswählen. Ent-
                        spricht dem SQL-Befehl USE.
send_query              Sendet eine Abfrage an die Datenbank.
sqlstate                SQL Status und Fehlercodes einer vorhergehenden Abfrage
                        ermitteln.

Tabelle 11.17: Methoden des MySQLi-Objekts (Forts.)


                                                                                        493
Datenbankprogrammierung



Methode                    Beschreibung
ssl_set                    Kann eine gesicherte SSL-Verbindung aufbauen.
stat                       Der aktuellen Status des Systems.
stmt_init                  Initialisiert eine Abfrage und gibt ein Objekt zurück, mit dem
                           diese Abfrage gesteuert werden kann. Tabelle 11.18 und Tabelle
                           11.19 zeigen die Eigenschaften und Methoden dieses Objekts.
thread_safe                Ermittelt, ob Threadsicherheit gegeben ist oder nicht.
use_result                 Bereitet einen Ergebnissatz zur Verwendung vor.

Tabelle 11.17: Methoden des MySQLi-Objekts (Forts.)


Eigenschaft                Beschreibung
affected_rows              Anzahl der von der Anweisung betroffenen Datensätze. Damit
                           kann man den Erfolg von UPDATE oder DELETE überwachen.
errno                      Der letzte Fehlercode.
error                      Eine Beschreibung des Fehlers (englisch).
param_count                Die Anzahl der Parameter.
sqlstate                   Ermittelt SQL Status und Fehlercodes einer vorhergehenden
                           Abfrage.
ssl_set                    Kann eine gesicherte SSL-Verbindung aufbauen.

Tabelle 11.18: Eigenschaften des Anweisungs-Objekts


Methode                    Beschreibung
bind_param                 Erstellt Parameter für die Anweisung.
bind_result                Erstellt ein Ergebnisobjekt.
close                      Schließt die Verbindung.
data_seek                  Setzt den Ergebnissatzzeiger auf einen bestimmten Datensatz.
Tabelle 11.19: Methoden des Anweisungs-Objekts




494
Referenz MySQLi



Methode                  Beschreibung
execute                  Führt die Anweisung aus.
fetch                    Holt den Ergebnissatz mit verschiedenen Optionen.
fetch_result             Holt den Ergebnissatz.
get_metadata             Holt globale Informationen zur Anweisung.
prepare                  Bereitet die Anweisung vor (vorab Kompilierung).
send_long_data           Sendet große Datenpakete.
store_result             Speichert die Ergebnisse zwischen.
Tabelle 11.19: Methoden des Anweisungs-Objekts (Forts.)


Eigenschaft              Beschreibung
current_field            Ermittelt das aktuelle Feld des Ergebnissatzes.
field_count              Ermittelt die Anzahl der Felder.
length                   Ermittelt die Länge (Breite) eines Feldes.
num_rows                 Ermittelt die Anzahl der Reihen im Ergebnissatz.
Tabelle 11.20: Eigenschaften des Ergebnis-Objekts


Methode                  Beschreibung
close                    Schließt die Verbindung.
data_seek                Setzt den Ergebnissatzzeiger auf eine bestimmte Reihe.
fetch_field_direct       Holt direkt ein bestimmtes Feld.
fetch_field              Holt das nächste Feld einer Liste.
fetch_fields             Holt Felder als Array.
fetch_lengths            Ermittelt die Breite des aktuellen Feldes.
fetch_object             Holt die Reihe als Objekt. Felder sind nun Eigenschaften.
Tabelle 11.21: Methoden des Ergebnis-Objekts



                                                                                        495
Datenbankprogrammierung



Methode                    Beschreibung
fetch_row                  Holt die Reihe als numerisches (einfaches) Array.
fetch_assoc                Holt die Reihe als assoziatives Array. Die Schlüssel sind die Spal-
                           tennamen.
field_seek                 Setzt den Zeiger auf ein spezifisches Feld.
Tabelle 11.21: Methoden des Ergebnis-Objekts (Forts.)



11.6 Kontrollfragen
1. Warum sollte die neue Bibliothek MySQLi anstatt der alten eingesetzt werden?
2. Was versteht man unter Normalisierung?
3. Sie müssen bei der Ausgabe von Daten aus einer MySQL-Datenbank Datums-
      formate anpassen und n in br umwandeln. Wie gehen Sie vor?
4. Wie würden Sie eine Abfrage formulieren, bei der aus zwei Tabellen Daten
      zugleich entnommen werden müssen? Welche Voraussetzungen müssen die
      Tabellen erfüllen?




496
Die integrierte
Datenbank SQLite




    1 2
Die integrierte Datenbank SQLite


Nicht immer muss es MySQL sein. Statt einer ausgewachsenen Datenbank reicht
oft ein einfacheres System. Die Lücke zwischen einfachen Textdateien als Daten-
speicher und einem relationalen Datenbankmanagementsystem schließt in PHP5
das SQLite-Modul.



12.1 Hintergrund
SQLite ist fest in PHP integriert. Ein weiteres Modul oder Treiber sind nicht erfor-
derlich. Sie können deshalb immer davon ausgehen, dass jede PHP5-Installation
wenigstens SQLite bereitstellt. Wenn kleine Datenbankprojekte erstellt werden,
die auf Funktionen großer Datenbankmanagementsysteme nicht angewiesen sind,
ist SQLite völlig ausreichend. Allerdings muss man sich auch der Grenzen bewusst
sein, um gegebenenfalls rechtzeitig wechseln zu können.
SQLite ist eine einfache Datenbank-Lösung, die als »Public Domain« entwickelt
wurde. Dies ist eine sehr freie Art der Softwareverteilung, bei der völlig auf Rechte
und Lizenzen verzichtet wird. Der Code wird jedermann ohne Beschränkungen
zur Verfügung gestellt.
Der Entwickler ist D. Richard Hipp der Firma Hipp, Wyrick  Company, Inc. Die
neueste Version wird unter folgender Adresse bereitgestellt:
http://www.hwaci.com/sw/sqlite
SQLite soll dabei kein Ersatz für MySQL oder PostgreSQL sein. Nur einfachste
Anwendungen werden auf größere Datenbank verzichten können. Wenn Sie
bereits einige Erfahrung mit Datenbanken haben und Oracle oder MS SQL Ser-
ver kennen, wird Ihnen MySQL möglicherweise primitiv vorgekommen sein. Dass
es noch deutlich einfacher geht, zeigt SQLite. Dafür profitiert auch SQLite von
der Vereinfachung. Zugriffe sind extrem schnell und die Speicherung der Daten
nutzt den Speicher effektiv. Einige Benchmarks versprechen etwa doppelte
Geschwindigkeit gegenüber dem bereits recht flotten MySQL.
Einige der typischen Aufgaben, denen SQLite ohne weiteres gewachsen ist, sind:
í     Konfigurationsdaten einer Applikation speichern.
í     Serialisierte Objekte zur Mitnahme von Seite zu Seite speichern.
í     Speicherung von Anmeldeinformationen.
í     Ein Gästebuch.
í     Eine News-Site, auf der aktuelle Nachrichten zu finden sind.

498
Vor- und Nachteile


Ein komplettes Content Management System oder größere Foren dürften allein
aufgrund der komplexeren SQL-Abfragen mit MySQL besser bedient sein.



12.2 Vor- und Nachteile
Bevor Sie intensiv in SQLite einsteigen, sollten Sie einige technische Hinter-
gründe und deren Auswirkungen auf Projekte kennen.


Wann SQLite vorteilhaft ist
SQLite bietet sich an, wenn einfache Projekte völlig unabhängig von der PHP5-
Konfiguration laufen müssen. Die Zugriffe sollten vorrangig lesend erfolgen.
Außerdem sollte PHP5 nur auf einem einzigen System laufen, nicht im Cluster
oder mit Lastverteilung. Der Anspruch an die Sicherheit der Daten sollte eher
gering sein oder eine häufige Datensicherung ist vorhanden.
Vorteilhaft ist SQLite deshalb, weil sie vielen Entwicklern mit wenig Datenbanker-
fahrung die Chance bietet, ohne großen Aufwand eine datenbankgestützte Appli-
kation zu bauen. Denn mit der Integration reduziert sich die Lernkurve auf PHP
und sehr wenige SQL-Befehle selbst. Eine kleinere Einstiegbarriere erlaubt dann
vielleicht den Ersatz uneffektiver Bastellösungen durch eine halbwegs saubere Pro-
grammierung, was insgesamt schon als Vorteil betrachtet werden kann.


Wann SQLite nachteilig ist
SQLite speichert die gesamte Datenbank in einer einzigen Datei. Schreibzugriffe
von einer Benutzersitzung aus führen dazu, dass die gesamten Datei kurzzeitig
gesperrt wird. Das führt bei häufigen Schreibzugriffen sehr schnell dazu, dass
PHP5 ständig auf die erneute Freigabe der Datenbank wartet und dies verschlech-
tert drastisch die Antwortzeiten des Systems.
Die Speicherung von Sitzungsdaten in SQLite ist deshalb eher nachteilig. Sollte
das Dateisystem feststellen, dass die Datenbankdatei korrupt ist, wird man alle Sit-
zungsdaten aller Sitzungen verlieren. Beim bisherigen System beträfe dies nur
eine Sitzung. Außerdem verfügt SQLite über keine »Selbstheilungskräfte«, wie sie
andere DBMS kennen. John Lim von PHPEverywhere und Sterling Hughes



                                                                                   499
Die integrierte Datenbank SQLite


haben einige Benchmarks erstellt, die klar beweisen, das SQLite bei schreibinten-
siven Aufgaben wie der Sitzungsverwaltung deutlich langsamer ist als die einge-
baute Sitzungsverwaltung oder andere Datenbanklösungen.
Allerdings muss auch gesagt werden, dass die in den Tests erreichten Zugriffszah-
len weit höher liegen, als die meisten Webseiten jemals erreichen. Insofern sind
derartige Aussagen immer auch akademischer Natur.



12.3 Einführung
Die Implementierung von SQLite folgt weitgehend den Vorgaben der C-Imple-
mentierung1. Wo immer es an Dokumentation mangelt, kann man also gut auf die
Originalausgabe der Datenbank zurückgreifen.


Eine einfache Beispielanwendung
Ein einfaches Beispiel soll zeigen, wie der Zugriff prinzipiell aussieht. Das Skript
geht davon aus, dass ein Verzeichnis data unterhalb des Speicherorts des Skripts
existiert, wo die Datenbankdatei erzeugt werden kann:

Listing 12.1: SQLiteCreate.php – Erzeugen einer Datenbank und einer Tabelle

?php
echo ( 'SQLite Version: '.sqlite_libversion().'br /');
echo ( 'SQLite Encoding: '.sqlite_libencoding().'br /');
$sqliteDb='data/users.db';
if ( !$db = sqlite_open($sqliteDb, 0666, $err) )
   die($err);
$sql = CREATE TABLE          
           users        
               (        
               id INTEGER PRIMARY KEY,                    
               login STRING UNIQUE,                   
               password STRING,                   
               email STRING              
1   SQLite ist eine C-Bibliothek für den Einbau in C-Projekte. Die Umsetzung als PHP-Modul bricht jedoch
    nicht mit diesen Vorgaben, sodass weitgehend identische Aufrufe benutzt werden können, abgesehen von
    zwingenden syntaktischen Differenzen zwischen C und PHP.



500
Einführung


               );
if ( !sqlite_query($sql, $db) )
   die(sqlite_last_error($db).': '.        
       sqlite_error_string(sqlite_last_error($db)));
echo ( Datenbank $sqliteDb erfolgreich angelegt. );
sqlite_close($db);
?
Dieses Skript erzeugt eine neue SQLite-Datenbank und darin eine einfache
Tabelle mit vier Spalten: id, login, password und email zur Implementierung einer
Benutzeranmeldung.


Eine Benutzerverwaltung mit SQLite
Das folgende Listing zeigt ein weiteres Skript auf einen Blick, eine Erläuterung der
wichtigen Passagen folgt danach. Es handelt sich um die noch rudimentäre Benut-
zerverwaltung, die die bereits erzeugte Tabelle mit zwei Datensätzen befüllt und
einen Anmeldevorgang simuliert:

Listing 12.2: SQliteSelect.php – Primitive Benutzerverwaltung mit SQLite

?php
$sqliteDb = 'data/users.db';
if (!$db = sqlite_open($sqliteDb, 0666, $err))
{
   die(Fehler: $err);
}
$users = array(   
   array(  
       'login'='jbloggs',     
       'password'=md5('secret'),      
       'email'='jbloggs@yahoo.com'        
       ),  
   array(  
       'login'='jsmith',     
       'password'=md5('secret'),      
       'email'='jsmith@php.net'      
       ) );
$result = sqlite_query($db, SELECT * FROM users);
$num = sqlite_num_rows($result);
if ($num == 0)



                                                                                  501
Die integrierte Datenbank SQLite


{
      foreach ($users as $user)
      {
         $sql = INSERT INTO users (login, password, email)
                 VALUES      
                     (      
                         '{$user['login']}',         
                         '{$user['password']}',        
                         '{$user['email']}'         
                     );
         echo $sqlbr;
         if (!sqlite_query($db, $sql))
         {
             die (Fehler:  . sqlite_last_error($db).': '.
                   sqlite_error_string(sqlite_last_error($db)));
         }
      }
      echo 'Daten erfasst';
}
else
{
      echo 'Daten bereits vorhanden';
}

sqlite_close($db);
?
h1Abfrage der Datenbank/h1
?php
if (!$db = sqlite_open($sqliteDb, 0666, $err))
{
   die($err);
}
$sql = SELECT * FROM users;

if (!$result = sqlite_query($db, $sql))
{
   die (sqlite_last_error($db).': '.            
        sqlite_error_string(sqlite_last_error($db)));
}
echo ('h2Datenausgabe/h2');
while ($row = sqlite_fetch_array($result, SQLITE_ASSOC))
{



502
Einführung


   echo {$row['id']} bName: /b{$row['login']}   
         bE-Mail:/b {$row['email']}br /;
}
if ($_SERVER['REQUEST_METHOD']=='POST'  $_POST['action']=='test')
{
    $pw_encrypted = md5($_POST['logpass']);
    $sql = SELECT * FROM users WHERE login='{$_POST['logname']}'   
             AND password='$pw_encrypted';
    $result = sqlite_query($db, $sql);
      echo sqlite_error_string(sqlite_last_error($db));
    if (sqlite_num_rows($result) == 1)
    {
         echo 'pAnmeldung erfolgreich/p';
    }
    else
    {
         echo 'pBenutzername oder Kennwort konnten 
               nicht gefunden werden./p';
    }
}
?
h1Test der Anmeldung/h1
form action=?=$_SERVER['PHP_SELF']? method=post
input type=hidden name=action value=test/
table
    tr
         td
             Name:
         /td
         td
             input type=text name=logname value=/
         /td
    /tr
    tr
         td
             Kennwort:
         /td
         td
             input type=text name=logpass value=/
         /td
    /tr
    tr



                                                                        503
Die integrierte Datenbank SQLite


         td
             nbsp;
         /td
         td
             input type=submit value=Anmelden (Test)/
         /td
    /tr
/table
/form
?php
sqlite_close($db);
?
Um Kennwortdaten in der Datenbank zu schützen, werden die Zeichenfolgen als
MD5-Hash abgelegt. Dies ist eine irreversible Abbildung beliebiger Daten. Um
später das Kennwort verifizieren zu können, wird die Eingabe erneut mit MD5
berechnet und die kodierten Werte werden verglichen.
Das Skript ist relativ primitiv, es erfasst nur zwei Musterdatensätze, die hart kodiert
sind. Um festzustellen, ob die Daten schon vorhanden sind, erfolgt zuerst eine ein-
fache Abfrage:
$result = sqlite_query($db, SELECT * FROM users);
Dann wird ermittelt, wie viele Daten vorliegen:
$num = sqlite_num_rows($result);
Die Funktion sqlite_num_rows ermittelt die Anzahl der von der in $result gespei-
cherten Abfrage zurückgegebenen Datensätze.
Sind keine Datensätze vorhanden, werden die als Array vorliegenden Daten mittels
INSERT gespeichert. Geht dabei etwas schief, enthält sqlite_last_error($db) den
letzte Fehlercode. Um den passenden Text anzuzeigen, was meist hilfreicher ist,
wird eine weitere Funktion benötigt:
sqlite_error_string(sqlite_last_error($db))
Für die Ausgabe von Datensätzen kann der Inhalt zeilenweisen einem Array zuge-
führt werden. Auch hier dient der Verweis auf den Ergebnissatz, wiederum in
$result abgelegt, als Datenquelle:
while ($row = sqlite_fetch_array($result, SQLITE_ASSOC))




504
Einführung


Der Vergleich des eingegebenen Kennworts mit dem gespeicherten erfolgt in
einer weiteren Abfrage. Damit dieser Programmteil nur ausgeführt wird, wenn ein
Formular gesendet wurde, wird eine Servervariable verwendet:
$_SERVER['REQUEST_METHOD']=='POST'
Das Kennwort muss wieder mit MD5 verpackt werden:
$pw_encrypted = md5($_POST['logpass']);
Dieser Wert wird dann für die Abfrage verwendet. Der Rest ist bekannt – es folgt
eine Abfrage und diese wird mit sqlite_num_rows untersucht. Nur wenn genau ein
Datensatz gefunden wurde, war die Prüfung erfolgreich.
Am Ende des Skripts sollten Sie nicht vergessen, die Verbindung zu schließen,
damit SQLite die Datei freigeben kann, in der die Datenbank gespeichert ist.
sqlite_close($db);


Ausblick
SQLite kann Daten auch im Speicher des Computers halten, was für temporäre
Daten sehr vorteilhaft ist. Unter Linux konnten dafür bislang die Shared-Memory-
Funktionen eingesetzt werden, die jedoch unter Windows nicht zur Verfügung
standen, was echte Cross-Plattform-Entwicklung natürlich verhindert. Hier bietet
SQLite nun eine einfache und elegante Lösung. Natürlich hat SQLite seine Gren-
zen. Bei sehr vielen konkurrierenden Zugriffen führt die Sperrung der gesamten
Datenbankdatei dazu, dass neu eintreffende Abfragen verzögert werden. Das kann
praktisch bis zum Deadlock führen – dem Skriptstillstand. Allerdings ist das
Gespann Dateisystem-Speicher-SQLite äußerst leistungsfähig und die allergrößte
Masse aller Sites erreicht niemals die kritische Benutzerzahl.
Spannend sind auch die Möglichkeiten, SQLite beliebige Funktionen hinzuzufü-
gen, die innerhalb von SQL-Abfragen benutzt werden können. Damit kann man
sehr effizient die Defizite der Datenbank ausgleichen und erreicht eine mithin
höhere Leistungsfähigkeit als bei größeren Datenbanksystemen. Es ist durchaus
denkbar, dass auch anspruchsvolle Lösungen für geringen oder mittlere Last mit
SQLite programmiert werden.




                                                                             505
Die integrierte Datenbank SQLite



12.4 Referenz SQLite

Funktion und Parameter           Beschreibung
sqlite_array_query               Führt eine Abfrage aus und gibt ein Array mit den Ergeb-
                                 nissen zurück.
sqlite_busy_timeout              Maximale Wartezeit auf ein Kommando. Der Standard-
                                 wert beträgt 60 Sekunden, die Angabe muss in Millisekun-
                                 den erfolgen.
sqlite_changes                   Anzahl der Spalten, die das letzte Kommando verändert
                                 hat.
sqlite_close                     Schließt die Verbindung.
sqlite_column                    Holt eine Spalte anhand des angegebenen Indizes aus der
                                 aktuellen Ergebnisreihe.
sqlite_create_aggregate          Nutzt eine PHP-Funktion zur Verarbeitung von Daten aus
                                 einer Tabelle.
sqlite_create_function           Definiert eine PHP-Funktion callback unter dem Namen
                                 function zur Verwendung in künftigen SQL-Befehlen.

sqlite_current                   Holt einen Ergebnissatz aus dem Ergebnisarray zurück.
sqlite_error_string              Zeigt eine Fehlermeldung an. Siehe auch
                                 sqlite_last_error.

sqlite_escape_string             Markiert in SQLite unzulässige Zeichen.
sqlite_fetch_array               Holt die nächste Reihe aus dem Ergebnissatz.
sqlite_fetch_single              Holt die erste Spalte aus dem Ergebnissatz.
sqlite_fetch_string              Alias für sqlite_fetch_single.
sqlite_field_name                Name eines Feldes
sqlite_has_more                  TRUE, wenn weitere Reihen verfügbar sind.

sqlite_last_error                Letzte Fehlernummer.
sqlite_last_insert_rowed         Letzte ID, die in einer INTEGER PRIMARY KEY Spalte einge-
                                 fügt wurde.



506
Referenz SQLite



Funktion und Parameter     Beschreibung
sqlite_libencoding         Zeichenkodierung der Bibliothek, beispielsweise ISO-
                           8859-1.
sqlite_libversion          Die Versionsnummer der Bibliothek.
sqlite_next                Sucht einen Ergebnisdatensatz zur Ausgabe, gibt aber
                           selbst nichts aus. Wird zur Schleifensteuerung verwendet.
sqlite_num_fields          Anzahl der Felder im Ergebnis.
sqlite_num_rows            Anzahl der Reihen im Ergebnis.
sqlite_open                Öffnet die Datenbank und erzeugt die Datei, wenn sie
                           nicht existiert, mit dem angegebenen Zugriffsmode (Unix-
                           Dateiparameter, beispielsweise 0666 (oktal)).
sqlite_popen               Wie sqlite_open, aber persistent.
sqlite_query               Fragt die Datenbank ab und gibt ein Handle auf die Ergeb-
                           nisse zurück.
sqlite_rewind              Setzt den Zeiger auf die erste Reihe. Wird für die Schlei-
                           fensteuerung verwendet.
sqlite_seek                Setzt den Zeiger auf die angegebene Reihe. Wird für die
                           Schleifensteuerung verwendet.
sqlite_udf_decode_binary   Dekodiert Binärdaten, bevor diese an eine benutzerdefi-
                           nierte Funktion gesendet werden.
sqlite_udf_encode_binary   Kodiert Binärdaten, bevor diese von einer benutzerdefi-
                           nierte Funktion zurückgenommen werden.
sqlite_unbuffered_query    Wie sqlite_query, aber das Ergebnis wird nicht sofort
                           komplett geholt, sondern erst, wenn es andere Funktionen
                           anfordern. Schneller bei einfachen sequenziellen Lese-
                           zugriffen.




                                                                                     507
Die integrierte Datenbank SQLite



12.5 Kontrollfragen
1. Was müssen Sie tun, um SQLite benutzt zu können?
2. Welche Datentypen kennt SQLite?
3. Sie möchten mehrere Tausend Datensätze für eine hoch frequentierte Website
      verwalten. Ist SQLite dafür die beste Wahl? Begründen Sie die Antwort?
4. Welche Aufgabe hat der Befehl VACUUM?




508
Datenbanklösungen
   mit MySQL




    1 3
Datenbanklösungen mit MySQL



13.1 Bibliotheks-Verwaltung mit MySQL
Dieses Kapitel widmet sich einem Praxisprojekt: Der Verwaltung einer Bibliothek.
Dabei soll, neben der einfachen Auflistung von Büchern auch deren Position in
einem Regalsystem erfasst werden.


Schrittfolge
Um das Programm komplett zu erstellen, werden zwei Schritte erforderlich:
í     Entwicklung einer Datenbankstruktur
í     Aufbau der Oberfläche
í     Programmierung der Geschäftslogik
Die Trennung von Oberfläche und Logik erfolgt durch ein primitives Template-
System, einer Verwaltung von Vorlagen. Dies erleichtert die Erweiterung der
Funktionalität erheblich.


Weitere Aufgaben
Das Programm ist ein guter Start in Richtung komplexer Datenbankanwendun-
gen. Es ist zugleich Grundlage des nächsten Kapitels, indem die Daten der Bücher
eleganter beschafft werden. Das eigentliche Problem bei der Nutzung des Pro-
gramms ist nämlich nicht dessen Erstellung, sondern dessen Nutzung. Das Erfas-
sen aller Buchdaten ist rech mühselig. Glücklicherweise bietet Amazon einen
Webservice, der die Beschaffung der Daten erleichtert. Vorerst wird jedoch – auch
zur Korrektur später automatisch erfasster Daten – ein Eingabeformular benutzt.


Funktionen
Das Programm soll grundsätzlich folgende Funktionen anbieten:
í     Erfassung von Büchern
í     Suchen nach Titel, Autor und ISBN
í     Erfassung der Regalposition und optional einer Anzahl vorhandener
      Exemplare


510
Bibliotheks-Verwaltung mit MySQL


í   Erfassung von Lesern, an die die Bücher ausgeliehen werden
í   Verwaltung der Leihvorgänge
Da ein Benutzer ein Buch immer nur einmal ausleihen kann, sind nur sehr einfa-
che Beziehungen erforderlich. Allerdings kann jeder Benutzer mehrere verschie-
dene Bücher ausleihen, was zu folgenden Tabellen führt:
í   Tabelle »Books« für die Erfassung der Bücher
í   Tabelle »Reader« für die Leser, die Bücher ausleihen können
í   Tabelle »LendOut« für die Leihvorgänge
Der Vorteil bei der Ausleihtabelle besteht im Aufbau einer Historie, das heißt, hier
kann man schnell nach der Häufigkeit und Dauer der Ausleihe suchen.
Nebenbei bemerkt eignet sich das Programm auch für eine private Bibliothek.
Wer mit seinen Kumpels und Kollegen einen Buchpool bildet und sich gegensei-
tig Bücher ausleiht, kann hier verwalten, welches Exemplar gerade wo unterwegs
ist.
Für die eigentliche Funktionalität muss außerdem festgelegt werden, welche Sei-
ten und Formulare erforderlich sind. Dabei sind zwei »Betriebsarten« zu beach-
ten: Ausleih- und Verwaltungsmodus. Im Ausleihmodus sollen Leser oder der
Bibliothekar suchen und Ausleihvorgänge steuern können, im Verwaltungsmodus
können neue Bücher erfasst oder alte entfernt werden. Benötigt werden dazu fol-
gende Seiten:
í   Startseite mit Auswahl der Betriebsart
í   Seite zum Suchen von Büchern
í   Seite zum Steuern des Ausleih- und Rücknamevorgangs
í   Seite zum Erfassen, Anzeigen und Löschen von Benutzern
í   Seite zum Erfassen, Anzeigen und Löschen von Büchern
Auf eine Verwaltung der Bediener durch eigene Anmeldenamen und Kennwörter
soll hier – vor allem aus Platzgründen – verzichten werden. Die dazu benötigten
Techniken unterscheiden sich nicht grundlegend von den gezeigten und die
Umsetzung sollte nach der Absolvierung des Kapitels nicht schwer fallen.




                                                                                      511
Datenbanklösungen mit MySQL



13.2 Vorbereitung der Datenbank
Für den Entwurf der Datenbank müssen Sie sich zuerst über eine Struktur der
Tabellen im Klaren sein. Die drei benötigten Tabellen wurden bereits erwähnt.
Der Aufbau wird so gewählt, dass alle Daten im passenden Datenformat gespei-
chert werden können.
Das Anlegen kann mit einem Werkzeug erfolgen. Im Beispiel wurde das MySQL
Control Center verwendet. Für dieses Projekt wurde lokal eine eigene Datenbank
angelegt. Wenn Sie ausschließlich auf dem Server eines Providers arbeiten und
dort nur eine fertige Datenbank verwenden können, müssen Sie diesen Schritt aus-
lassen und bei der Nutzung der Skripte den Namen der Datenbank anpassen. Wie
das zu erfolgen hat, wird im entsprechenden Code-Abschnitt erläutert.


Datenbank anlegen
Falls noch nicht geschehen, legen Sie die für die Datenbank-Übungen erforderli-
che Datenbank »marktundtechnik« an. Sie können auch jeden anderen Namen
nehmen, müssen dann aber die Skripte entsprechend anpassen. Dies ist nicht sehr
schwer, weil es eine zentrale Konfigurationsdatei für die Datenbankparameter gibt.


Datenbank mit SQL
Wenn Sie mit SQL arbeiten, geben Sie am Prompt der MySQL-Konsole folgendes
ein:
CREATE DATABASE marktundtechnik;
Weitere Parameter werden für diese Projekt nicht benötigt.


Datenbank mit MySQL Control Center anlegen
Um die Datenbank mit MySQL Control Center anzulegen, starten Sie das Pro-
gramm. Beim ersten Mal müssen Sie den Server angeben (vermutlich »localhost«,
wenn es lokal läuft) und gegebenenfalls Anmeldename und Kennwort. Nach einer
frischen Installation ist der Anmeldename »root« und das Kennwort ist leer. Im
Zweig Datenbanken des Servers klicken Sie mit der rechten Maustaste und wählen
dann im Kontextmenü den Eintrag Neue Datenbank.



512
Vorbereitung der Datenbank




                                    Abbildung 13.1:
                                    Eine neue Datenbank mit MySQL
                                    Control Center anlegen

Danach erscheint ein Eingabefeld, wo der Name der Datenbank eingetragen wird.
Weitere Optionen sind nicht erforderlich.


Tabellen anlegen
Nun werden die drei benötigten Tabellen angelegt. Zuerst wird wieder der dazu
erforderliche SQL-Code vorgestellt. Danach wird der entsprechende Dialog im
Control Center gezeigt. Die Tabellen weisen keine Besonderheiten auf, wenn-
gleich einige Dinge zu beachten sind.
Jede Tabelle enthält ein Feld id, das als Typ BIGINT und AUTO_INCREMENT definiert
wurde. Dieses Feld ist zugleich der Primärschlüssel der Tabelle (PRIMARY KEY).
Des Weiteren wurde bei einigen Feldern explizit NOT NULL angegeben, um zu ver-
hindern, dass beim Anlegen der Datensätze Spalten leer bleiben. Felder, nach
denen gesucht werden könnte, wurden außerdem mit einem Index bedacht.


Tabellen mit SQL anlegen
Das folgende Listing zeigt die SQL-Anweisungen, mit denen die drei Tabellen
erzeugt werden können:

Listing 13.1: SQL-Anweisungen zum Erstellen der Tabellen

#
# Tabellenstruktur für Tabelle `books`
#
CREATE TABLE books (
  id bigint(20) NOT NULL auto_increment,
  title varchar(100) NOT NULL default '',
  subtitle varchar(255) default '',
  isbn varchar(10) NOT NULL default '',
  author varchar(100) default '',
  number int(11) NOT NULL default '1',


                                                                                    513
Datenbanklösungen mit MySQL


  shelf varchar(15) default '',
  PRIMARY KEY (id),
  KEY idx_title (title)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
# --------------------------------------------------------
#
# Tabellenstruktur für Tabelle `lendout`
#
CREATE TABLE lendout (
  id bigint(20) NOT NULL auto_increment,
  books_id bigint(20) NOT NULL default '0',
  reader_id bigint(20) NOT NULL default '0',
  lendingdate datetime NOT NULL default '0000-00-00 00:00:00',
  lendingperiod timestamp NOT NULL,
  PRIMARY KEY (id)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
# --------------------------------------------------------
#
# Tabellenstruktur für Tabelle `reader`
#
CREATE TABLE reader (
  id bigint(20) NOT NULL auto_increment,
  name varchar(100) NOT NULL default '',
  surname varchar(100) NOT NULL default '',
  address varchar(150) NOT NULL default '',
  zip varchar(5) NOT NULL default '',
  city varchar(100) NOT NULL default '',
  active tinyint(1) NOT NULL default '0',
  created datetime NOT NULL default '0000-00-00 00:00:00',
  PRIMARY KEY (id),
  KEY idx_name (name),
  KEY idx_surname (surname)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
Beachten Sie, dass die Anweisungen nur Tabellen erzeugen. Um das Skript erneut
anzuwenden, müssen eventuell vorhandene Tabellen mit DROP TABLE entfernt wer-
den.




514
Vorbereitung der Datenbank


Tabellen mit MySQL Control Center anlegen
Zuerst wird die Buchtabelle erzeugt. Neben Titel, Autor und ISBN werden auch
die Regalnummer und die Anzahl der Exemplare erfasst.




                                                        Abbildung 13.2:
                                                        Struktur der Tabelle
                                                        »Books«

Wenn im Control Center eine entsprechende Zeile der Tabelle ausgewählt wird,
lassen sich die Parameter, beispielsweise die Anzahl der Zeichen beim Datentyp
VARCHAR, erfassen.
Die Kundentabelle weist keine Besonderheiten auf:




                                                        Abbildung 13.3:
                                                        Die Tabelle »Reader« zur
                                                        Erfassung der Bibliotheks-
                                                        nutzer

Als letztes folgt die Ausleihtabelle:




                                                        Abbildung 13.4:
                                                        Struktur der Tabelle
                                                        »LendOut«

Nach diesem Schritt steht die Datenbank bereit, um Daten aufzunehmen und spä-
ter natürlich Abfragen zu beantworten.




                                                                                 515
Datenbanklösungen mit MySQL



13.3 Die Seiten des Projekts
Am Anfang wurde bereits eine Übersicht über die benötigten Formulare gezeigt.
Diese werden hier nacheinander in der genannten Reihenfolge vorgestellt.


Vorbereitungen – das Template-System
Wie bereits kurz erwähnt, sollen Geschäftslogik und Design weitgehend getrennt
werden. Dazu wird ein kleines Template-System entwickelt, das die Verwaltung
der Seiten übernimmt. Das Prinzip ist recht einfach. Eine zentrale Instanz,
index.php, verwaltet alle Seiten. Diesem Skript wird mitgeteilt, welche Vorlage
(Template) aufgerufen werden soll. Die Vorlage besteht immer aus zwei Teilen:
Dem PHP-Code und dem HTML-Code. Damit beide interagieren können, gibt es
ein paar Steuerzeichen, die im HTML eingebaut werden und die in PHP aufzulö-
sen sind.
Der Sinn der Übung besteht nicht darin, ein professionelles Template-System wie
phpTemple (http://www.phptemple.de) oder Smarty (http://smarty.php.net) zu
ersetzen, sondern die prinzipielle Arbeitsweise und den Nutzen transparent wer-
den zu lassen. Die Technik lässt sich leicht auf andere Projekte übertragen. Hat
man erstmal das Gefühl dafür, wann ein solches System Vorteile bringt, fällt es
leichter, sich für den Einarbeitungsaufwand in ein kommerzielles Produkt zu ent-
scheiden.


Die Steuerzeichen des Template-Systems
Die wichtigste Funktion besteht in der Datenausgabe. Dazu werden Variablen
erforderlich. Diese sehen – der Einfachheit halber – genauso aus, wie in PHP.
Allerdings kann man sie direkt ins HTML schreiben:
div{$variable}/div
Die geschweiften Klammern verhindern Konflikte mit Währungsangaben mit Dol-
larzeichen.
Zum Ausgeben von Tabellen braucht man eine Konstruktion, die Datenwiederho-
lungen erzeugt. In PHP sind dies Schleifen. In HTML wird einfach ein Kommen-
tar eingefügt, den das Skript verarbeiten kann (Editoren ignorieren diesen
Kommentar):



516
Die Seiten des Projekts


table
  !-- #REPEAT:readers --
  tr
    td
       ${readers:name}
    /td
    td
       ${readers:surname}
    /td
  /tr
  !-- #END:readers --
/table
Das Programm wiederholt jetzt das Tag mit sämtlichen darin befindlichen Codes
so oft, wie das Array readers dies ermöglicht. Das Array muss assoziativ sein. Der
Index mit dem Namen surname wird über ${readers:surname} abgerufen. Tech-
nisch werden dazu reguläre Ausdrücke und simple Ersetzungsfunktionen benutzt.


Funktionsweise des Template-Systems
Das eingesetzte Template-System beschränkt sich auf eine einzige Klasse, die in
der Datei engine.inc.php definiert ist. Sie dient dazu, die HTML-Vorlagen zu
erkennen, Variablen zu suchen und zu ersetzen und gegebenenfalls Blöcke mit
sich wiederholenden Daten zu bilden. Das Resultat wird direkt ausgegeben.
Damit die Steuerung funktioniert, ist folgender Einsatz vorgesehen:
$engine = include_once('engine.inc.php');
Diese Zeile schließt die Klasse ein, erzeugt eine Instanz und gibt sie wieder
zurück. Dadurch hat die Klasse sowohl Zugriff auf die globalen Variablen, deren
Werte benutzt werden können, als auch die Möglichkeit, sich selbst dem aufrufen-
den Code bereitzustellen.
Der aufrufende Code muss nun den Namen des Templates zuweisen:
$engine-Html = 'Start.html';
Alternativ kann dies entfallen; dann wird versucht, die Daten aus der GET-Variab-
len TEMPLATE zu übernehmen. Die Steuerung kann damit vom Code in das
Template selbst verlagert werden:
a href=?index.php?TEMPLATE=start?Zur Startseite/a




                                                                                   517
Datenbanklösungen mit MySQL


Wenn ein expliziter Code-Block erforderlich ist, muss man diesen lediglich wie
die Vorlage benennen und ihm die Dateierweiterung .php geben. Damit das funk-
tioniert, wurde die Startseite index.php folgendermaßen definiert:

Listing 13.2: index.php – Universelle Startseite für das Template-System

?php
$engine = include_once('engine.inc.php');
if (!isset($_GET['TEMPLATE']))
{
     $engine-Html = 'start.html';
     $engine-Code = 'start.php';
}
else
{
     $engine-Html = {$_GET['TEMPLATE']}.html;
     $engine-Code = {$_GET['TEMPLATE']}.php;
}
if (strlen($engine-Code)  0  file_exists($engine-Code))
{
     include_once($engine-Code);
}
$engine-RunTemplate();
?
Zuletzt muss die Ausführung auf der jeweiligen Seite angestoßen werden. Die
Standardseite index.php erledigt dies durch Aufruf von runTemplate():
$engine-RunTemplate();
Zuvor sollten freilich die Variablen mit Werten belegt werden, die im Template
benutzt werden. Dies wiederum basiert auf normalem PHP-Code.


Code des Template-Systems
Nachfolgend finden Sie den Code des Template-Systems mit einigen Erläuterun-
gen zur Funktionsweise durchsetzt. Der Code ist chronologisch zu lesen und voll-
ständig in mehreren Abschnitten:
?php
class SimpleTemplate
{




518
Die Seiten des Projekts


Zwei private Variablen speichern den Namen der Vorlage und optional inkludier-
ten und implizit ausgeführten Code (ebenso als Dateiname übergeben). Der
Zugriff erfolgt später über die __set-Methode und den Konstruktor:
   private $templateCode;
   private $templateHtml;
Die Steuerung der Vorlage erfolgt durch eingebettete Kommentare, die von dem
folgenden regulären Ausdruck erkannt werden:
   const COMMENT = '/!-- #(.+):(.+)s+--()/iU';
Der Konstruktor kann zur Übergabe der Vorlage und des Codes benutzt werden.
Beide Parameter sind optional, sodass sie auch entfallen können:
   public function __construct($html = '', $code = '')
   {
      $this-templateCode = $code;
      $this-templateHtml = $html;
   }
Diese Methode erzeugt zwei virtuelle Eigenschaften Html und Code, die die ent-
sprechenden Werte im Objekt setzen:
   public function __set($prop, $value)
   {
      switch ($prop)
      {
          case 'Html':
              $this-templateHtml = $value;
              break;
          case 'Code':
              $this-templateCode = $value;
              break;
      }
   }
Diese Methode führt die vorbereitete Vorlage aus. Sie durchsucht die Seite,
erkennt die Blöcke und löst diese auf.
   public function runTemplate()
   {
Dazu wird zuerst die Vorlage selbst geladen:
       if (strlen($this-templateHtml)  0)
       {



                                                                                519
Datenbanklösungen mit MySQL


            $html = file_get_contents($this-templateHtml);
         }
         if (strlen($this-templateCode)  0)
         {
            include_once($this-templateCode);
         }
Nun werden die Wiederholungen aufgelöst. Dazu werden zuerst alle Blöcke
ermittelt, die mit der Kommentarsyntax umschlossen sind:
         $matches = array();
         $blocksFound = preg_match_all(self::COMMENT, $html,
                                       $matches, PREG_OFFSET_CAPTURE);
         if ($blocksFound)
         {
            $page = '';
Der reguläre Ausdruck produziert ein Array in $matches, dessen Elemente fol-
gende Bedeutung haben:
í     0: Array der Kommentaranfänge
í     1: TAGs
í     2: Variablen
í     3: Ende-Offset
Liegt ein solches Array vor, beginnt die eigentliche Analyse. Anhand der Blockda-
ten wird mit Hilfe von schnellen Zeichenkettenfunktionen der umschlossene Teil
ermittelt, weil diese Daten gegebenenfalls wiederholt werden müssen. Das passiert
innerhalb der for-Schleife praktisch für jeden Block:
if (is_array($matches))
{
   $startBlock = $lastStartBlock = 0;
   for ($i = 0; $i  count($matches[1]); $i++)
   {
      $token = strtoupper($matches[1][$i][0]);
      $offsetComment = (int)$matches[0][$i][1];
      $offsetContent = ((int)$matches[3][$i][1]) + 1;
      $variableName = $matches[2][$i][0];
Die Auflösung der Daten erfolgt in zwei Schritten. Beim Start des Blocks werden
die Anfangspositionen gespeichert (REPEAT), beim Ende werden die Daten zur




520
Die Seiten des Projekts


Wiederholung benutzt (END). Die eigentliche Arbeit erledigt die Methode repeat-
Block, die nachfolgend erläutert wird:
switch ($token)
{
   case 'REPEAT':
      $startBlock = $offsetContent;
      $startComment = $offsetComment;
      break;
   case 'END':
      $block = substr($html, $startBlock,     
                      $offsetComment - $startBlock);
      $block = $this-repeatBlock($block, $variableName);
      $len = $startComment - $lastStartBlock;
      $page .= substr($html, $lastStartBlock, $len).$block;
      $lastStartBlock = $offsetContent;
      break;
}
Die wiederholten Daten werden der fertigen Seite, die in $page gespeichert wird,
hinzugefügt:
}
$page .= substr($html, $offsetContent);
}
Zum Schluss werden noch die übrig gebliebenen Variablen ersetzt:
       echo ($this-varReplace($page));
    }
    else
    {
       echo 'Nicht gefunden';
    }
}
Wie bereits angedeutet, kommt repeatBlock eine große Bedeutung zu. Der
Methode wird der Code eines Blockes und der Name einer Variablen übergeben.
Ist diese Variable ein Array, wird der Block für jedes Array-Element einmal ausge-
geben und die Daten des jeweiligen Elements werden benutzt, um die Blockvari-
ablen zu ersetzen. Letzteres findet in einer eigenen Methode statt, arrayReplace.
Damit der Zugriff auf globale Variablen gelingt, wird der Name der Variablen als
dynamische Variable ($$-Syntax) benutzt:




                                                                                   521
Datenbanklösungen mit MySQL


private function repeatBlock($block, $varName)
{
   global $$varName;
   $result = '';
   if (is_array($$varName))
   {
      $this-index = 0;
      foreach ($$varName as $value)
      {
          $result .= $this-arrayReplace($block);
          $this-index++;
      }
   }
   else
   {
      $result = $block;
   }
   return $result;
}
arrayReplace nutzt einen regulären Ausdruck, um die Variablen in der Vorlage zu
finden und zu ersetzen. Das eigentliche Ersetzen passiert in der Rückruffunktion
replaceArray, die ihrerseits Zugriff auf den globalen Adressraum hat:
private function arrayReplace($block)
{
   $block = preg_replace_callback('/{$([^:}]*):(.+)}/U',      
            array($this, 'replaceArray'), $block);
   return $block;
}

private $index = 0;
private function replaceArray($repl)
{
    global $$repl[2];
    $arr = $$repl[2];
    return $arr[$this-index];
}
Vergleichbar arbeitet die Ersetzung der normalen skalaren Variablen, wo ebenso
eine Rückruffunktion benutzt wird. Auch hier geht es nur um einen vereinfachten
Zugriff auf den globalen Adressraum:




522
Die Seiten des Projekts


private function varReplace($block)
{
    $block = preg_replace_callback('/{$(.+)}/U',        
             array($this, 'replaceSkalar'), $block);
    return $block;
}
private function replaceSkalar($repl)
{
    global $$repl[1];
    return $$repl[1];
}
Der Rest der Datei steht außerhalb der Klassendefinition. Er erzeugt eine Instanz
der Klasse und gibt das entsprechende Objekt als Ergebnis der Ausführung zurück.
Damit kann die include-Funktion (oder include_once) als Quelle einer Zuwei-
sung benutzt werden. Zuvor wird noch versucht, den Namen des Templates aus
der GET-Variablen TEMPLATE zu entnehmen, wobei dieser Schritt optional ist,
das heißt, er schlägt nicht fehl, wenn die Angabe fehlt:
$__template = isset($_GET['TEMPLATE']) ? $_GET['TEMPLATE'] : '';
return new SimpleTemplate($__template);
?
Das vorgestellte Prinzip ist einfach und schnell und bietet einigen Raum für Ver-
besserungen. Es zeigt vor allem, wie effektiv mit regulären Ausdrücken und der
neuen objektorientierten Syntax von PHP5 gearbeitet werden kann.


Der Code der Seiten
Die folgenden Skripte zeigen eine kurze Übersicht über die Vorlagen, also den
HTML-Code der Seiten (befreit von gestalterischen Elementen, um die Lesbarkeit
zu verbessern) und die Variablen, die die dynamischen Daten enthalten.

Startseite mit Auswahl der Betriebsart
Die Startseite ist relativ einfach, denn es sind nur folgende Zustände zu unterschei-
den:
í   Ausleihen oder Rücknehmen eines Buches (Betriebszustand)
í   Anlegen oder Sperren von Kunden (Betriebszustand)
í   Anlegen oder Herausnehmen von Büchern (Administrationszustand)


                                                                                     523
Datenbanklösungen mit MySQL


Das vorliegende Beispiel ist vergleichsweise rudimentär und unterscheidet nicht
explizit zwischen Betriebs- und Administrationszustand. Dies ist jedoch leicht zu
implementieren. Solange eine solche Bibliotheksverwaltung lokal im Intranet
läuft, ist die Unterscheidung möglicherweise nicht erforderlich. Eine öffentliche
Seite müsste um entsprechende Sicherheitsfunktionen ergänzt werden.
Da keine weitere Logik erforderlich ist, besteht diese Seite lediglich aus einigen
Links auf die anderen Vorlagen:

Listing 13.3: start.html – Startseite der Bibliotheksverwaltung

html
body
h1Bibliotheksverwaltung/h1
Willkommen in der Bibliotheksverwaltung mit PHP5
h2Funktionsauswahl/h2
a href=index.php?TEMPLATE=kundenKundenverwaltung/a
br /
br /
a href=index.php?TEMPLATE=buecherBuuml;cherverwaltung/a
br /
br /
Heute ist der {$today}.
/body
/html
PHP-Code wird hier kaum benötigt, lediglich eine Variable zur Anzeige des
Datums ist zu definieren:

Listing 13.4: start.php – Erzeugen einer deutschen, systemunabhängigen Datumsanzeige

?php
if (PHP_OS == 'WINNT')
{
     setlocale(LC_TIME, 'ge');
}
else
{
     setlocale(LC_TIME, 'de_DE');
}
$today = strftime('%d. %B %Y');
?




524
Die Seiten des Projekts


Seite zum Steuern des Ausleih- und Rücknahmevorgangs
Die Seite zum Steuern des Ausleih- und Rücknahmevorgangs benötigt folgende
elementare Funktionen:
í   Suchen des Kunden, wenn nicht vorhanden, Sprung zur Kundenerwaltung
í   Suche des Buches (wird vorausgesetzt, dass nur vorhandene Bücher genutzt
    werden)
í   Ausleihe oder Rücknahmevorgang
Hier wird auf jeden Fall der Datenbankzugriff benötigt, der in einer Include-Datei
abgelegt wird, die auch den anderen Modulen zur Verfügung steht. In dieser Datei
wird ein MySQLi-Objekt erzeugt, das in allen folgenden Codes den Zugriff auf die
Datenbank erlaubt:

Listing 13.5: database.inc.php – Include-Datei für den Datenbankzugriff

?php
$mysqli = new mysqli('localhost', 'root', '', 'marktundtechnik');
if (!is_object)
{
    die ('Fehler: Verbindung zur Datenbank konnte         
          nicht aufgebaut werden');
}
?


Seite zum Erfassen, Anzeigen und Löschen von Kunden
Die Seite umfasst folgende Funktionen:
í   Liste aller Kunden anzeigen
í   Anzeige von Details
í   Ändern bestehender Kunden
í   Anlegen neuer Kunden
í   Setzen einer Sperre (anstatt eines endgültigen Löschens)
Die HTML-Vorlage ist sehr direkt aufgebaut und enthält mehrere Tabellen, die
jeweils die Darstellung einer Funktion steuern:




                                                                                        525
Datenbanklösungen mit MySQL


Listing 13.6: kunden.html – Vorlage für die Verwaltung der Kunden

html
body
h1Bibliotheksverwaltung/h1
a href=index.phpStartseite/a
|
a href=index.php?TEMPLATE=ausleiheAusleihe/a
|
a href=index.php?TEMPLATE=buecherBuuml;cherverwaltung/a
h2Kundenverwaltung/h2
h3Informationen/h3
table border=1 width=400
  caption style=font-weight:boldKundenliste/caption
  tr
    th
       Status
    /th
    th
       Name
    /th
    th
        Aktion
    /th
  /tr !-- #REPEAT:reader --
  tr
    td
       {$reader:state}
    /td
    td
       {$reader:name}, {$reader:surname}
    /td
    td
        a href=index.php?TEMPLATE=kundenamp;id={$reader:id}    
           Details ansehen/a
    /td
  /tr
  !-- #ENDREPEAT:reader --
/table
!-- #IF:detail_name --
table border=1 width=400
  caption style=font-weight:bold



526
Die Seiten des Projekts


   Details fuuml;r Kunde {$detail_name}/caption
  tr
    td
       Name, Vorname:
    /td
    td
        {$detail_name}, {$detail_surname}
    /td
  /tr
  tr
    td
       Anschrift:
    /td
    td
        {$detail_zip} {$detail_city}
    /td
  /tr
/table
!-- #ENDIF:detail_name --
h3Auml;ndern/h3
form action=index.php?TEMPLATE=kunden method=post
input type=hidden name=action value=change /
input type=hidden name=id value={$detail_id} /
table border=1 width=400
  caption style=font-weight:boldKundendaten/caption
  tr
    td
       Daten:
    /td
    td
        input type=text name=reader_name
               value={$detail_name}/,
        input type=text name=reader_surname
               value={$detail_surname}/
    /td
  /tr
  tr
    td
       PLZnbsp;Ort:
    /td
    td
        input type=text name=reader_zip value={$detail_zip}/



                                                                              527
Datenbanklösungen mit MySQL


        input type=text name=reader_city
               value={$detail_city}/
    /td
  /tr
  tr
    td
       Status
    /td
    td
        input type=checkbox name=reader_state
               {$detail_state} value=1/
    /td
  /tr
  tr
    td
    /td
    td
        input type=submit value=Auml;ndern /
    /td
  /tr
/table
/form
h3Neuer Kunde/h3
form action=index.php?TEMPLATE=kunden method=post
input type=hidden name=action value=new /
table border=1 width=400
  caption style=font-weight:boldKundendaten/caption
  tr
    td
       Daten:
    /td
    td
        input type=text name=reader_name /,
        input type=text name=reader_surname /
    /td
  /tr
  tr
    td
       PLZnbsp;Ort:
    /td
    td
        input type=text name=reader_zip /



528
Die Seiten des Projekts


        input type=text name=reader_city /
    /td
  /tr
  tr
    td
    /td
    td
        input type=submit value=Erfassen /
    /td
  /tr
/table
/form
/body
/html
Besonderheiten gibt es hier nicht. Beachten Sie den Aufruf der anderen Vorlagen
im Menü:
index.php?TEMPLATE=ausleihe
Die Steuerung des Codes erfolgt durch versteckte Felder in den Formularen:
input type=hidden name=action value=change /
input type=hidden name=id value={$detail_id} /
Dabei werden auch dynamische Daten ({$detail_id}) übergeben, wodurch Vorlage
und Code miteinander in Verbindung stehen. Die Vorlage verwendet außerdem
eine #IF-Anweisung, um einen Teil der Seite nur bei Bedarf anzuzeigen.
Weitaus aufschlussreicher ist der PHP-Code, der mit dieser Vorlage verknüpft ist:

Listing 13.7: kunden.php – Steuerung der Vorlage mittels Datenbankabfragen

?php
include('database.inc.php');
// Neuer Kunde?
if ($_SERVER['REQUEST_METHOD']=='POST'  isset($_POST['action']))
{
   switch ($_POST['action'])
   {
       case 'new':
           $mysqli-query(INSERT INTO reader         
                           (name, surname, zip, city,        
                            active, created)      
                           VALUES ('{$_POST['reader_name']}',             

                                                                                    529
Datenbanklösungen mit MySQL


                                  '{$_POST['reader_surname']}',   
                                  '{$_POST['reader_zip']}', 
                                  '{$_POST['reader_city']}', 
                                  1, CURDATE()));
           break;
       case 'change':
           $state = isset($_POST['reader_state']) ? 1 : 0;
           $mysqli-query(UPDATE reader SET        
                           name='{$_POST['reader_name']}',            
                           surname='{$_POST['reader_surname']}',          
                           zip='{$_POST['reader_zip']}',          
                           city='{$_POST['reader_city']}',            
                           active=$state        
                           WHERE id = {$_POST['id']});
           break;
    }
}
// Liste fuellen
$query = $mysqli-query(SELECT id, name, surname,
IF(active=0,'Gesperrt','Aktiv') AS state FROM reader);
while($arr = $query-fetch_assoc())
{
   $reader[] = $arr;
}
// Details abrufen, wenn gefordert
if (isset($_GET['id']))
{
    $query = $mysqli-query(SELECT * FROM reader WHERE id =
{$_GET['id']});
    $detail = $query-fetch_assoc();
    $detail_id    = $detail['id'];
    $detail_name     = $detail['name'];
    $detail_surname = $detail['surname'];
    $detail_zip      = $detail['zip'];
    $detail_city     = $detail['city'];
    $detail_state    = ($detail['active']==1) ? 'checked' : '';
}
?
Am Anfang des Codes werden die beiden Formulare ausgewertet, erst danach
erfolgt der Aufbau der Liste für die Ausgabe. Auf diese Weise wird sichergestellt,
dass die soeben erfolgten Änderungen sofort in der Anzeige reflektiert werden.



530
Die Seiten des Projekts


Unbedingt erforderlich ist auch der Einschluss der Vorbereitung der Datenbank-
abfrage:
include('database.inc.php');
Die Aktionen werden nur ausgewertet, wenn ein Formular abgesendet wurde.
Dazu wird die Servervariable REQUEST_METHOD auf den Wert »POST« hin unter-
sucht:
if ($_SERVER['REQUEST_METHOD']=='POST'  isset($_POST['action']))
Anschließend werden die passenden INSERT- bzw. UPDATE-Befehle gebildet, um die
Daten in der Datenbank zu verändern. Als Quelle dienen die Felder der Formu-
lare.
Sind alle Kommandos abgearbeitet, werden die aktuellen Daten aus der Daten-
bank gelesen:
$query = $mysqli-query(SELECT id, name, surname,       
         IF(active=0,'Gesperrt','Aktiv') AS state FROM reader);
Der SELECT-Befehl sorgt gleich für die korrekte Aufbereitung der Daten für die Aus-
gabe. Das Feld active der Tabelle reader kann die Werte 0 oder 1 enthalten. Für
die Anzeige werden diese Werte mit der MySQL-Funktion IF in die Zeichenket-
ten »Gesperrt« bzw. »Aktiv« umgewandelt. Es ist generell eine gute Idee, fest ver-
drahtete Berechnungen in der Datenbank erledigen zu lassen, und damit den
lokalen Code zu entlasten. Nur dann, wenn die Berechnungen selbst dynamisch
sind und möglicherweise häufig verändert werden oder wenn sie sehr komplex
sind, ist PHP besser geeignet.
Die fertige Abfrage muss nun noch in ein Array überführt werden, damit die Tem-
plate-Engine die Daten in einer Schleife ausgeben kann:
while($arr = $query-fetch_assoc())
{
   $reader[] = $arr;
}
Damit kann dann innerhalb des Abschnitts !-- #REPEAT:reader -- folgende
Syntax zum Abruf der Felder benutzt werden:
{$reader:name}
Für das Füllen der Detaildaten und der Formulare wird keine Schleife benötigt.
Die erforderlichen Variablen werden deshalb in skalarer Form abgerufen (nach
einer erneuten SELECT-Anweisung):



                                                                                    531
Datenbanklösungen mit MySQL


$detail_id = $detail['id'];
In der Vorlage wird dieser Wert beispielsweise benutzt, um die aktuelle Auswahl
im Formular zu halten:
input type=hidden name=id value={$detail_id} /
Die folgende Abbildung zeigt, wie die Seite in Aktion aussieht:




                                          Abbildung 13.5:
                                          Die Kundenverwaltung der Bibliothek


Seite zum Erfassen, Anzeigen und Löschen von Büchern
Die Seite zur Verwaltung der Bücher ist praktisch gleichartig zur Verwaltung der
Kunden aufgebaut. Lediglich die Daten unterscheiden sich und damit Anzahl und
Benennung der Spalten der Tabellen.



532
Die Seiten des Projekts


Zuerst wird wieder die Vorlage gezeigt:

Listing 13.8: buecher.html – Die Vorlage zur Verwaltung der Bücher

html
body
h1Bibliotheksverwaltung/h1
a href=index.phpStartseite/a
|
a href=index.php?TEMPLATE=ausleiheAusleihe/a
|
a href=index.php?TEMPLATE=kundenKundenverwaltung/a
h2Buuml;cherverwaltung/h2
h3Informationen/h3
table border=1 width=400
  caption style=font-weight:boldBuuml;cherliste/caption
  tr
    th
       Anzahl
    /th
    th
       ISBN
    /th
    th
       Titel
    /th
    th
       Aktion
    /th
  /tr
  !-- #REPEAT:books --
  tr
    td
       {$books:number}
    /td
    td nowrap
       {$books:isbn}
    /td
    td
       {$books:title}
    /td
    td
a href=index.php?TEMPLATE=buecheramp;id={$books:id}amp;action=show


                                                                                      533
Datenbanklösungen mit MySQL


Details
/a
     /td
  /tr
  !-- #ENDREPEAT:books --
/table
a href=index.php?TEMPLATE=buecheramp;action=newNeues Buch erfassen/
a
h3Auml;ndernnbsp;/nbsp;Erfassen/h3
form action=index.php?TEMPLATE=buecher method=post
input type=hidden name=action value={$detail_action} /
input type=hidden name=id value={$detail_id} /
table border=1 width=400
  caption style=font-weight:boldBuchdaten/caption
  tr
     td
       Titel:
     /td
     td
        input type=text name=book_title
               value={$detail_title} size=40/
     /td
  /tr
  tr
     td
       Untertitel:
     /td
     td
        input type=text name=book_subtitle
               value={$detail_subtitle} size=40/
     /td
  /tr
  tr
     td
       Autor:
     /td
     td
        input type=text name=book_author
               value={$detail_author}/
     /td
  /tr
  tr



534
Die Seiten des Projekts


    td
       ISBN:
    /td
    td
        input type=text name=book_isbn value={$detail_isbn}/
    /td
  /tr
  tr
    td
       Anzahl
    /td
    td
        input type=text name=book_number    
               value={$detail_number} size=4/
    /td
  /tr
  tr
    td
       Ausgeliehen
    /td
    td
        input type=text name=book_lend value={$detail_lend}            
               size=4 disabled/
    /td
  /tr
  tr
    td
    /td
    td
        input type=submit value={$detail_actiontext} /
    /td
  /tr
/table
/form
/body
/html
Die Vorlage ist bewusst etwas anders als die für die Kunden verwendete aufgebaut.
Detailanzeige, Änderung und Erfassung sind in einem Formular zusammenge-
fasst, dessen Funktion sich damit dynamisch ändert. Ein verstecktes Feld, dessen
Wert über eine Variable gesteuert wird, macht es möglich:
input type=hidden name=action value={$detail_action} /



                                                                                  535
Datenbanklösungen mit MySQL


Auch die Beschriftung der Sendeschaltfläche ist dynamisch:
input type=submit value={$detail_actiontext} /
Die eigentliche Arbeit erledigt der Code-Teil:

Listing 13.9: buecher.php – Logik zur Verwaltung der Bücher

?php
include('database.inc.php');
$detail_action = 'change';
$detail_actiontext = 'Auml;ndern';
if ($_SERVER['REQUEST_METHOD']=='GET'  isset($_GET['action']))
{
   switch ($_GET['action'])
   {
       case 'new':
           $detail_action = 'new';
           $detail_actiontext = 'Erfassen';
           break;
   }
}
if ($_SERVER['REQUEST_METHOD']=='POST'  isset($_POST['action']))
{
   switch ($_POST['action'])
   {
       case 'new':
           $mysqli-query(INSERT INTO books        
                           (title, subtitle, author, isbn, number)                 
                           VALUES ('{$_POST['book_title']}',                   
                           '{$_POST['book_subtitle']}',            
                           '{$_POST['book_author']}',             
                           '{$_POST['book_isbn']}',           
                            {$_POST['book_number']}           
                           ));
           break;
       case 'change':
           $mysqli-query(UPDATE books SET       
                           title='{$_POST['book_title']}',                 
                           subtitle='{$_POST['book_subtitle']}',                   
                           author='{$_POST['book_author']}',                   
                           isbn='{$_POST['book_isbn']}',               
                           number={$_POST['book_number']},                 
                           WHERE id = {$_POST['id']});


536
Die Seiten des Projekts


            break;
    }
}
$query = $mysqli-query(SELECT * FROM books);
while($arr = $query-fetch_assoc())
{
   $books[] = $arr;
}
// Details abrufen, wenn gefordert
if (isset($_GET['id']))
{
    $query = $mysqli-query(SELECT *,      
    (SELECT COUNT(*) FROM lendout WHERE books_id = b.id) AS lend               
     FROM books b WHERE id = {$_GET['id']});
    $detail = $query-fetch_assoc();
    $detail_id      = $detail['id'];
    $detail_title   = $detail['title'];
    $detail_subtitle= $detail['subtitle'];
    $detail_author = $detail['author'];
    $detail_number = $detail['number'];
    $detail_isbn    = $detail['isbn'];
    $detail_lend    = $detail['lend'];
}
?
Der erste Teil steuert die Umschaltung des Formulars. Danach folgen das Einfü-
gen (INSERT) und Aktualisieren (UPDATE) der Datenbank. Im letzten Teil werden
wieder die frisch aktualisierten Daten abgefragt, damit stets die aktuelle Liste zu
sehen ist. Eine Besonderheit stellt die letzte Abfrage dar, die auf einem so genann-
ten Sub-Select basiert. (Achtung! Dazu ist mindestens MySQL 4 erforderlich.)
Diese Unterabfrage sichert den Zugriff auf die Tabelle lendout, um die Anzahl der
bereits ausgeliehenen Bücher zu erhalten.


Die Geschäftslogik des Ausleihvorgangs
Die Logik des Ausleihvorgangs muss folgende Aufgaben erfüllen:
í   Suchen des Kunden, beispielsweise anhand der Kundennummer
í   Suchen des Buches, beispielsweise anhand der ISBN
í   »Buchen« des Ausleihvorgangs oder der Rücknahme eines Buches



                                                                                    537
Datenbanklösungen mit MySQL




                                             Abbildung 13.6:
                                             Die Bücherseite mit Details über ein Buch

Außerdem sind noch einige Verwaltungsfunktionen denkbar, die hier nicht voll-
ständig ausgeführt wurden:
í     Auflistung komplett ausgeliehener Bücher
í     Auflistung nicht pünktlich zurückgegebener Bücher
í     Auflistung säumiger Kunden


Die Vorlage der Ausleihseite
Auch die Ausleihseite besteht aus einer Vorlage und einer Code-Seite. Die Vorlage
ist vergleichsweise schlicht. Auch hier werden wieder mehrere Formulare einge-
setzt, um die Daten zu sammeln. Die Daten (Kunde, Buch) werden in Sitzungs-
variablen gespeichert, damit sie bis zum Ende des Vorgangs erhalten bleiben. Der
Teil der Seite, der die eigentliche Buchung ausführt, wird erst sichtbar, wenn alle
Daten vorliegen.


538
Die Seiten des Projekts


Listing 13.10: ausleihe.html – Vorlage für die Ausleihseite

html
body
h1Bibliotheksverwaltung/h1
a href=index.phpStartseite/a
|
a href=index.php?TEMPLATE=kundenKundenverwaltung/a
|
a href=index.php?TEMPLATE=buecherBuuml;cherverwaltung/a
h2Kunden/h2
table
tr
     td
          h3Kunden suchen/h3
          form action=index.php?TEMPLATE=ausleihe method=post
          input type=hidden name=action value=reader /
          Name input type=text name=reader /
          input type=submit value=Suchen /
          /form
     /td
     td
          h3Buch suchen/h3
          form action=index.php?TEMPLATE=ausleihe method=post
          input type=hidden name=action value=book /
          ISBN input type=text name=book /
          input type=submit value=Suchen /
          /form
     /td
/tr
/table
!-- #IF:noerror --
h3Ausleihe- oder Ruuml;cknahme/h3
Ausleih- oder Ruuml;cknahmeinformationen:
ul
     liKundenname:nbsp;{$reader_name}/li
     liKundennummer: {$reader_id}/li
     liBuchtitel: {$book_title}/li
     liISBN: {$book_isbn}/li
     liEs sind noch b{$available}/b Buuml;cher verfuuml;gbar./li
/ul
table



                                                                                        539
Datenbanklösungen mit MySQL


      tr
         td
             form action=index.php?TEMPLATE=ausleihe
                    method=post
                                                 
                input type=hidden name=reader
                       value={$reader_id} /
                input type=hidden name=book value={$book_id} /
                input type=hidden name=action value=lend /
                input type=submit value=Ausleihen {$disabled} /
             /form
         /td
    /tr
/table
!-- #ENDIF:noerror --
table width=400 border=1
captionUuml;bersicht aller Vorgauml;nge des Kunden/caption
    tr
         thBuch ID/th
         thAusleihdatum/th
         thFauml;lligkeit/th
         thAktion/th
    /tr
!-- #REPEAT:history --
    tr
         td{$history:books_id}/td
         td{$history:lendingdate}/td
         td{$history:due}/td
         td
             a
href=index.php?TEMPLATE=ausleiheamp;action=returnamp;id={$history:id}
Ruuml;ckgabe/a
         /td
    /tr
!-- #ENDREPEAT:history --
/table
!-- #IF:error --
Fehler:nbsp;{$reader_error}{$book_error}
!-- #ENDIF:error --
/body
/html
Diese Seite nutzt die Wiederholungs-Anweisungen zum Aufbau und definiert
einen Sprung auf die Ausleihseite.


540
Die Seiten des Projekts


Der Code der Ausleihseite

Listing 13.11: ausleihe.php – Die Steuerung der Vorlage nutzt intensiv die Datenbank

?php
include('database.inc.php');
session_start();
$reader_name = $_SESSION['reader_name'];
$reader_id = $_SESSION['reader_id'];
$book_title = $_SESSION['book_title'];
$book_id = $_SESSION['book_id'];
$book_isbn = $_SESSION['book_isbn'];
$number = $_SESSION['number'];
unset($error);
if (
     ($_SERVER['REQUEST_METHOD']=='POST'  isset($_POST['action']))
     ||
     ($_SERVER['REQUEST_METHOD']=='GET'  isset($_GET['action'])))
{
     switch ($_REQUEST['action'])
     {
         case 'lend':
             $sql = INSERT INTO lendout (books_id, reader_id,               
                      lendingdate, lendingperiod, active)          
                              VALUES (     
                              {$_POST['book']},      
                              {$_POST['reader']},        
                              CURDATE(),    
                              30, 1    
                              );
             $mysqli-query($sql);
             break;
         case 'return':
             $mysqli-query(UPDATE lendout SET active = 0             
                                     WHERE id = {$_GET['id']});
             break;
         case 'reader':
             $result = $mysqli-query(SELECT * FROM reader              
                    WHERE name LIKE '%{$_POST['reader']}%'           
                    OR id = '{$_POST['reader']}');
             if ($result-num_rows == 1)
             {



                                                                                       541
Datenbanklösungen mit MySQL


                 $arr = $result-fetch_assoc();
                 $reader_name = $arr['name'];
                 $reader_id = $arr['id'];
            }
            else
            {
                 $reader_error = 'Auswahl des Kunden war
                                  nicht eindeutig';
                 $error = true;
            }
            break;
        case 'book':
            $result = $mysqli-query(SELECT * FROM books  
                       WHERE title LIKE '%{$_POST['book']}%'   
                       OR isbn = '{$_POST['book']}');
            if ($result-num_rows == 1)
            {
                 $arr = $result-fetch_assoc();
                 $book_title = $arr['title'];
                 $book_id    = $arr['id'];
                 $book_isbn = $arr['isbn'];
                 $number = $arr['number'];
            }
            else
            {
                 $book_error = 'Auswahl des Buches war
                                nicht eindeutig';
                 $error = true;
            }
            break;
    }
}
if (!isset($error))
{
    $result = $mysqli-query(SELECT COUNT(*) AS lent  
                             FROM lendout  
                              WHERE books_id=$book_id AND active=1);
    $books_to_lend = $result-fetch_object();
    $available = $number - $books_to_lend-lent;
    $disabled = ($available = 0) ? 'disabled' : '';
    $_SESSION['reader_name'] = $reader_name;
    $_SESSION['reader_id'] = $reader_id;



542
Die Seiten des Projekts


     $_SESSION['book_title'] = $book_title;
     $_SESSION['book_id']      = $book_id;
     $_SESSION['book_isbn'] = $book_isbn;
     $_SESSION['number']       = $number;
     // Ermittle die Liste aller bisherigen Buchungen fuer diesen Benutzer
     $sql = SELECT id, books_id,   
             DATE_FORMAT(lendingdate, '%d.%m.%Y') AS lendingdate,            
             IF(TO_DAYS(CURDATE())-TO_DAYS(lendingdate)     
                        lendingperiod, 'Überfällig',     
                CONCAT('Noch ', 30 - (TO_DAYS(CURDATE())     
                       - TO_DAYS(lendingdate)), ' Tage') ) AS due            
                FROM lendout  
                WHERE reader_id=$reader_id    
                  AND active=1;
     $result = $mysqli-query($sql);
     if ($result != false)
     {
         while($arr = $result-fetch_assoc())
         {
            $history[] = $arr;
         }
     }
     $noerror = true;
}
?
Der Code basiert wieder auf dem bereits mehrfach verwendeten Prinzip der Über-
tragung eines Kommandos, dessen Erkennen in einem switch-Zweig und der
Steuerung der entsprechenden SQL-Anweisungen auf Basis der Auswahl. Freilich
ist bei der Ausleihe (und Rückgabe) etwas mehr zu tun, da nun alle drei Tabellen
an der Aktion beteiligt sind.
Da Kunden und Bücher getrennt gesucht werden, werden die bereits gefundenen
Werte in Sitzungsvariablen festgehalten. Damit das Sitzungsmanagement funktio-
niert, wird es am Beginn des Skripts gestartet:
session_start();
Dann werden die bestehenden Sitzungsvariablen wiedergeholt, soweit vorhanden.
Die Fehlervariable $error dient der Steuerung des entsprechenden #IF-Blockes.
Der Inhalt eines solchen Blockes wird ausgeführt, wenn die Variable existiert. Um
unter allen Umständen zu verhindern, dass die Variable ohne Vorliegen eines
Fehlergrunds einen Wert enthält, wird sie zunächst zurückgesetzt:
unset($error);


                                                                                  543
Datenbanklösungen mit MySQL


Dann werden die entsprechenden Abfragen an die Datenbank gesendet. Beim
Einfügen wird die zulässige Ausleihzeit des Buches fest auf 30 Tage gesetzt. Die
Tabelle nutzt außerdem die Spalte active, um festzuhalten, welche Datensätze
aktiv sind. Um eine Historie des Benutzerverhaltens zu erhalten, werden die
Datensätze bei Rückgabe des Buches nicht gelöscht, sondern der Wert in der
Spalte active wird lediglich auf 0 gesetzt:
UPDATE lendout SET active = 0 WHERE id = {$_GET['id']}
Die Anzeige der noch verfügbaren Bücher nutzt dieselbe Spalte, was die Abfrage
sehr einfach macht:
SELECT COUNT(*) AS lent FROM lendout      
  WHERE books_id=$book_id AND active=1
Etwas komplexer ist die Abfrage der Liste der Ausleihvorgänge (Historie). Hier wer-
den Datumsfunktionen von MySQL benutzt, um die Anzahl der noch verbleiben-
den Ausleihtage festzustellen und gegebenenfalls eine Meldung anzuzeigen, wenn
ein Buch überfällig ist. Damit kann verhindert werden, dass säumige Nutzer wei-
tere Bücher ausleihen.
SELECT id, books_id,
DATE_FORMAT formatiert das interne Datumsformat für die Ausgabe im Deutschen
Format:
DATE_FORMAT(lendingdate, '%d.%m.%Y') AS lendingdate,
Die IF-Funktion ermittelt die Datumsdifferenz. Dazu wird mit TO_DAYS ein Tages-
zähler ermittelt und mit dem aktuellen Datum (CURDATE) verglichen. Als Ver-
gleichswert wird die individuelle Ausleihdauer (in lendingperiod) herangezogen,
sodass die Funktion auf vereinbarte Verlängerungen korrekt reagieren kann. Die
IF-Funktion gibt bei wahrer Bedingung den ersten Teil zurück (hier das Wort
»Überfällig«), andernfalls einen Text, der mittels CONCAT zusammengesetzt wird.
IF(TO_DAYS(CURDATE())-TO_DAYS(lendingdate)  lendingperiod,        
               'Überfällig',
CONCAT erlaubt die Angabe mehrerer Parameter, die zu einer Zeichenkette zusam-
mengefasst werden, unabhängig vom Datentyp. Der Name der damit neu erzeug-
ten Spalte wird als due (fällig) bezeichnet:
CONCAT('Noch ',
       30 - (TO_DAYS(CURDATE())-TO_DAYS(lendingdate)),
       ' Tage') ) AS due




544
Die Seiten des Projekts


Als Auswahlkriterium gilt logischerweise der ausgewählte Nutzer. Außerdem wer-
den nur offene Leihvorgänge angezeigt:
FROM lendout  
WHERE reader_id=$reader_id   
AND active=1
Die Übergabe der Abfrageergebnisse an das Array $history korrespondiert wieder
mit der Vorlage:
while($arr = $result-fetch_assoc())
{
  $history[] = $arr;
}
In der Vorlage wird beispielsweise die Fälligkeit folgendermaßen ausgegeben:
td{$history:due}/td
Wie das fertig aussieht, kann der nächsten Abbildung entnommen werden:




                                                      Abbildung 13.7:
                                                      Die Ausleihseite in Aktion




                                                                                   545
Datenbanklösungen mit MySQL



13.4 Ausblick
Die vorgestellte Bibliothek zeigte zwei entscheidende Techniken:
í     Funktionsweise eines rudimentären Template-Systems
í     Praktische Anwendung der MySQLi-Bibliothek
Freilich bleibt noch einiges zu tun, um ein solches System praxistauglich zu
machen. Dazu gehört zuerst sicher eine vernünftige Gestaltung, die mit einer
Reihe von Maßnahmen einhergeht. Zuerst sollte eine zentrale Datei erstellt wer-
den, die CSS-(Style)-Definitionen enthält und in allen Vorlagen Verwendung fin-
det. Außerdem fehlen noch einige grundlegende Funktionen:
í     Benutzerverwaltung und Absicherung gegen unbefugten Zugriff.
í     Regalverwaltung. Es gibt zwar bereits eine Spalte shelf, aber diese wird nicht
      benutzt. Für Benutzer könnte sich daran eine Suchfunktion anschließen, über
      die die Bücher schnell gefunden werden.
í     Fehlermanagement. Datenbank- und Bedienfehler führen meist zum Pro-
      grammabbruch
í     Online-Zugriff. Die derzeitige Lösung ist praktisch nur intranet-tauglich. Für
      eine auch online betreibbare Bibliothek sind noch einige Ergänzungen
      (Benutzeranmeldung, Benutzerdienste, Sicherheitsfunktionen) einzubauen.
í     Hilfen bei der Erfassung von Buchdaten.
Zumindest der letzte Punkt kann eine echte Erleichterung sein. Dazu finden Sie
im nächsten Kapitel XML und Webservices ein entsprechendes Projekt, das den
Amazon-Webservice nutzt, um vollständige bibliografische Daten zu einem Buch
auf Grundlage der ISBN zu beschaffen. So ist auch der Katalog schnell gefüllt.
Nicht zuletzt kann auch die Umstellung auf ein kommerzielles Templatesystem
wie phpTemple ein Schritt hin zu einer schnell erstellten und leicht zu wartenden
Lösung sein, weil hier beispielsweise die SQL-Abfragen unverändert übernommen
werden können. Auf PHP kann weitgehend verzichtet werden, weil einige Makro-
funktionen – gesteuert über XML-Tags – die Arbeit erledigen.




546
Kontrollfragen



13.5 Kontrollfragen
1. Wozu werden Template-Systeme eingesetzt?
2. Erweitern Sie die Applikation, sodass zu jedem Leser der Bibliothek mehrere Tele-
   fonnummern gespeichert werden können. Wie gehen Sie vor?
3. Formulieren Sie eine SQL-Abfrage, die die nötigen Daten zur Erstellung von
   Mahnschreiben erzeugt, um säumige Leser auf die bereits abgelaufene Rück-
   gabefrist hinzuweisen.




                                                                                  547
XML und
Webservices




 1 4
XML und Webservices


XML ist inzwischen für viele Anwendungen eine fest etablierte Technologie. Mit
PHP5 fanden auch neue, vereinheitlichte Bibliotheken Verwendung, die die Ver-
arbeitung und Erzeugung von XML noch einfacher machen.



14.1 Vorbemerkungen
Dieser Abschnitt klärt in knapper Form über einige Grundlagen auf, die Sie unbe-
dingt beherrschen sollten, bevor Sie praktisch mit XML arbeiten.


XML
XML ist eine Auszeichnungssprache für Dokumente, die strukturierte Informa-
tionen enthalten. Derartige Informationen enthalten beliebigen Inhalt, also Text,
Bilder usw. und Angaben darüber, was dieser Inhalt bedeutet; eine Art Semantik
des Inhalts. In einem XML-Dokument wird eine Überschrift nicht deshalb zur
Überschrift, weil Sie in einer bestimmten Weise erscheint, sondern weil sie explizit
als Überschrift markiert wurde. Überhaupt spielt die Formatierung keine Rolle
und ist auch technisch nicht möglich. Erst das Ausgabegerät kann die Informa-
tionen anhand ihrer Struktur in eine für den Menschen (oder andere Maschinen)
sinnvoll formatierte Form überführen.
XML ist eine so genannte Markup-Sprache (XML = Extensible Markup Langu-
age). Sie dient der Erstellung von Strukturen für Dokumente. XML ist ein Stan-
dard, der die Art der Strukturierung beschreibt.


Was ist ein Dokument?
Die Anzahl der Applikationen, die XML bereits nutzen, unterstützen oder sogar
darauf angewiesen sind, wächst drastisch. XML ist längst im Alltag angekommen
und keineswegs mehr ein Exot unter den Datenformaten. Webprogrammierer, vor
allem mit einem Hintergrund im grafischen Bereich, werden das vielleicht noch
nicht so empfunden haben. Wer jedoch mit Datenformaten hantiert, kommt
inzwischen fast unweigerlich mit XML in Berührung. Allerdings ist XML kein All-
heilmittel und auch kein Ersatz für relationale Datenbanken. Es ist eine sinnvolle
Ergänzung, die vor allem den systemübergreifenden Datenaustausch stark verein-
facht.



550
Vorbemerkungen


XML-Dokumente sind die Basis dieses Datenaustauschs. Letztlich ist XML immer
wieder eine Datei, gleich ob sie als solche im Dateisystem abgelegt oder als Teil
einer HTTP-Anforderung gesendet wird. Der Sinn eines solchen Dokuments
ergibt sich aus der gewählten Strukturbeschreibung. Ein XML-Dokument kann
deshalb Webseiten beschreiben (XHTML), Vektorgrafiken enthalten (SVG),
mathematische Formeln darstellen (MathML) oder sogar Rechnungen beinhalten
(EMC). Es gibt Tausende derartige vordefinierte Formate und weitere lassen sich
jederzeit mit bestimmten Werkzeugen (und entsprechenden Fachkenntnissen)
entwickeln. XML selbst ist also nur eine Art Metasprache, die eine Vorschrift zur
Definition von Datenformaten darstellt. XML-Dokumente entstehen, indem sie
dieser globalen Vorschrift genügen, egal ob die konkrete Struktur im Einzelfall
sinnvoll ist oder nicht.


XML versus HTML
XML und HTML haben nur oberflächlich etwas gemeinsam. In HTML ist sowohl
der Satz an Formatanweisungen als auch deren Semantik festgelegt. Das Tag b
bezeichnet seinen Inhalt als fett und es wird erwartet, dass ein Ausgabegerät dies
auch so darstellt. Das Tag test ist dagegen bedeutungslos und wird ignoriert.
Zugleich wird ein Ausgabegerät (der Browser) definiert und damit das Verhalten
der mit HTML markierten Dokumente fixiert.
XML ist universeller und legt solche Regeln nicht fest. Die Ähnlichkeit hat ihren
Ursprung in den gemeinsamen Wurzeln. HTML wurde auf der Basis der Urform
aller Auszeichnungssprachen, SGML, entworfen und letztlich ist XML eine ver-
einfachte Form von SGML. Daher rührt die Ähnlichkeit.
XML definiert niemals selbst eine Semantik oder ein Satz von Tags. Dies ist Auf-
gabe der anwendenden Instanz. Ein Sinn aus einem XML-Dokument ergibt sich
erst, wenn eine bestimmte, konkrete Auszeichnungssprache, den Regel von XML
folgend, entworfen wurde. In diesem Abschnitt wird noch die Variante RSS vorge-
stellt, die der Erstellung von Dokumenten dient, die Einträge in einem Weblog
beschreiben. Für HTML gibt es auch eine Entsprechung in XML, XHTML
genannt.
Um nun die Semantik (den Sinn) in ein Dokument zu bekommen, benötigt man
eine Instanz, die in der Lage ist, XML zu interpretieren. Einen Sinn kann man frei-
lich nur erkennen, wenn man das Ziel der Darstellung kennt. Ohne ein Programm,
das eine Weiterverarbeitung vornimmt, bleibt XML Selbstzweck. Deshalb kommt
diesem Verarbeitungsprozess, nicht zuletzt in PHP, eine große Bedeutung zu.



                                                                               551
XML und Webservices


Warum XML?
Aus der einleitenden Darstellung wird sich nicht zwingend ableiten lassen, warum
XML nun eine so große Bedeutung zukommt. XML wurde für reichhaltig struktu-
rierte Dokumente entworfen, die ein hohes Maß an Automation bei der Verarbei-
tung erfordern.
Weder HTML noch SGML können dies in dieser exzessiven Form leisten. HTML
scheitert an der Vermischung von Struktur und Semantik, was die Nutzbarkeit ein-
schränkt1. SGML ist universell, zugleich aber sehr viel komplizierter und mit vie-
len Nebeneffekten durch die komplexe Syntax belegt. Programme, die SGML gut
verarbeiten, sind sehr teuer. Viele Anwendungen benötigen eine derartige Kom-
plexität nicht, weshalb sich SGML außerhalb der Druckindustrie nie durchgesetzt
hat.
XML ist einfacher, klar strukturiert und leicht zu lesen. Programme, die XML ver-
arbeiten – so genannte Parser – sind leicht zu erstellen und zu perfektionieren.
Praktisch existieren sie für alle Sprachen, Plattformen und Systeme in vollkommen
ausgereifter Form. Im Verbund mit der technischen Unterstützung wird XML
praktisch einsetzbar – deshalb ist XML so wichtig.


Wie XML definiert ist
XML wird durch eine Reihe von Dokumenten definiert, die alle im Web zugäng-
lich sind. Sie sind teilweise äußerst abstrakt und sehr technisch, im Alltag also
wenig hilfreich. Ein Blick hinein kann dennoch nicht schaden, um ein Gefühl für
den Hintergrund zu bekommen. Wer selbst XML-Formate entwerfen möchte,
muss diese Dokumente tatsächlich verstanden haben. Hier folgt eine Auswahl:
í     Extensible Markup Language (XML) 1.0 (http://www.w3.org/TR/WD-xml)
      Definiert die Syntax von XML, sozusagen die Mutter aller Dokumente.
í     XML Pointer Language (XPointer, http://www.w3.org/TR/1998/WD-xptr-
      19980303) and XML Linking Language (Xlink, http://www.w3.org/TR/1998/
      WD-xlink-19980303)
      Beide Standards definieren Wege, um Links (Verknüpfungen) zwischen
      Datenquellen zu definieren. Sie sind quasi der Ersatz des HTML-Tags a in
      der XML-Welt. XPointer beschreibt, wie eine Ressource adressiert wird (wo


1   Für seinen ureigenen Zweck, nämlich Webseiten, war dies jedoch sehr sinnvoll.



552
Vorbemerkungen


    man sie findet), während XLink die Beziehungen zwischen zwei oder mehr
    Ressourcen definiert.
í   Extensible Style Language (XSL) (http://www.w3.org/TR/WD-xsl)
    Dieser Standard definiert die globale Methodik der Formatierung und Trans-
    formation (letzteres im wichtigen Teilstandard XSLT). Damit wird für XML
    die Brücke zur realen Welt der Ausgabegeräte geschaffen. Eine einführende
    Beschreibung folgt im nächsten Abschnitt.
í   XPath und XQuery
    Für die gezielte Auswahl von Teilstrukturen aus einem XML-Dokument dient
    die Abfragesprache XPath. XQuery dient ähnlichen Zwecken und ist bei sehr
    datenorientierten Strukturen von Vorteil, da es Ähnlichkeiten mit SQL auf-
    weist. XPath ist bereits seit einiger Zeit etabliert, XQuery noch in der Entwick-
    lung begriffen.
í   DTD und Schema
    Vor der Struktur steht die Strukturdefinition. Eine DTD (Document Type Defi-
    nition) definiert, welche Tags wie von anderen abhängen. Leider ist die DTD
    selbst nicht in einem XML-Format definiert, was die automatisierte Verarbei-
    tung etwas erschwert. Das neuere XML Schema ist dagegen selbst ein XML-
    Dialekt. Im Gegensatz zur DTD weist es einen starke Unterstützung für Struk-
    turen auf, wie sie Datenbank nutzen. So sind beispielsweise viele elementare
    Datentypen definiert.
Für die Arbeit mit XML und PHP sollten Sie mit XML, XSLT und in Grundzügen
mit XPath und DTD vertraut sein. Das ist mitnichten trivial, aber der Einsatz lohnt
sich.


XSLT
XSLT ist die Transformationssprache für XML-Dokumente. Mit Hilfe von XSLT
kann beispielsweise ein Teil eines XML-Datenpaketes in HTML umgewandelt
werden, damit der Browser es anzeigen kann. XSLT ist nicht einfach, aber
unglaublich mächtig.


Einführung
XSLT erlaubt eine weitgehend eigene Kontrolle der weiteren Verarbeitungs-
schritte gegenüber dem, was fertige Programm anbieten. XSLT erlaubt es außer-


                                                                                 553
XML und Webservices


dem, fertige Vokabulare einfach zu übernehmen und an eigene Bedingungen
anzupassen. Damit wird der Datenaustausch weiter vereinfacht und verbessert.
Neben der Transformation von XML nach XML ist vor allem, die Umwandlung
von XML nach HTML und von XML nach Text von Bedeutung. XSLT erzeugt in
der Regel eine frei formatierte Datei. Dies ist meist XML, muss jedoch nicht. Als
Eingabe muss jedoch immer XML vorliegen. XSLT nutzt zur Auswahl von Knoten
die Abfragesprache XPath, womit sich der Kreis zu den anderen Standards wieder
schließt. Sie müssen also mindestens XPath und XSLT beherrschen, um praktisch
Umwandlungsprogramme schreiben zu können. PHP wird hierbei zur reinen
Hilfsschicht degradiert, es dient lediglich dem Laden der Dateien von der Fest-
platte (oder von einem Server) und dem Rückschreiben der Ergebnisse.


Eine kompakte Einführung in XSLT
XSLT ist eine so genannte funktionale Programmiersprache. Das Prinzip unter-
scheidet sich grundlegend von den objektorientierten oder imperativen Sprachen,
wie beispielsweise PHP. Der Programmfluss selbst wird in erster Linie durch Auto-
matismen initiiert, in zweiter Linie dann durch Regeln. Regeln definieren Sie, um
bestimmte Effekte beim Auftreten von bestimmten Daten zu erreichen. Vorteil
derartiger Systeme ist die weitgehende – bei XSLT per Definition die vollkom-
mene – Befreiung von Seiteneffekten. Wenn eine Regel gilt, dann wird diese und
nur diese ausgeführt und dies in immer der gleichen Art und Weise. Dazu gehört
auch, dass Variablen zwar verfügbar sind, beispielsweise um einer Regel einen
Wert zu übergeben, ihren Inhalt aber nachträglich nicht ändern können. Sie ver-
halten sich also eher wie die Konstanten in PHP, abgesehen davon, dass der Inhalt
dynamisch definiert werden kann. Nachträgliche Änderungen könnten Seitenef-
fekte erzeugen, was nicht erlaubt ist.
Dennoch kann man damit erstaunlich effektiv programmieren und verblüffende
Resultate erzielen. Nicht immer ist XSLT die perfekte Sprache. Richtig leistungs-
fähig wird sie erst in Kombination mit einer modernen objektorientierten Sprache,
die hinreichende imperative Merkmale aufweist. Es ist nahe liegend, Transforma-
tion und Programm mit PHP in einen Kontext zu überführen. Zuvor sind jedoch
wenigstens elementare Kenntnisse von XSLT notwendig.




554
Vorbemerkungen


Die Basisregeln in XSLT
XSLT basiert auf XML, weshalb jede Datei durch die entsprechende Deklaration
eingeleitet wird. Dann folgt das Wurzelelement stylesheet. Das W3C emp-
fiehlt als Standardnamensraum xsl; diese Angabe ist aber im Prinzip freiwillig. Es
ist jedoch empfehlenswert, generell den Standardnamensraum zu verwenden.
Daraus ergibt sich folgendes Grundgerüst für XSLT:

Listing 14.1: Ein leeres XSLT-Programm-Fragment

?xml version=1.0 encoding=UTF-8 ?
xsl:stylesheet version=1.0
     xmlns:bxsl/b=http://www.w3.org/1999/XSL/Transform
xsl:stylesheet
Durch die Erweiterung des Attributes xmlns wird der Namensraumalias xsl festge-
legt. Zwischen den Wurzelelementen wird nun das Regelwerk aufgebaut. Eine
zentrale Rolle spielt das Element xsl:template. Templates (dt. Vorlagen) bilden
die Stufen der eigentlichen Transformation. Dabei gibt es zwei Arten von Templa-
tes. Zum einen können sie durch eine XPath-Anweisung in ihrer Zuständigkeit
programmiert werden. Die folgende Regel zeigt, wie jedes Element name zu
einer Ausgabe im Ausgabedatenstrom führt:
xsl:template match=name
  h1NAME/h1
/xsl:template
Eine andere Methode ist der Aufruf benannter Vorlagen, dazu später mehr. Der
Inhalt des Elements findet hier freilich noch keine Berücksichtigung. Text kann,
wie gezeigt, direkt ausgegeben werden. Beachten Sie dabei, dass es sich auch hier
um wohlgeformtes XML handeln muss; HTML muss also gegebenenfalls den
Regeln von XHTML 1.0 entsprechend modifiziert werden.
Wenn Sie eine Vorlage mit xsl:template select=regelname benennen, kön-
nen Sie diese folgendermaßen aufrufen:
xsl:call-template name=regelname/
Soll explizit Text ausgegeben werden, der mit dem verwendeten XML-Editor2
nicht darstellbar ist, muss das xsl:text-Element eingesetzt werden. Das ist
eigentlich – nach der Spezifikation – immer notwendig. Die direkte Angabe von
Text oder Tags ist eine Vereinfachung.

2   Schreibt man XSLT mit einem einfachen Editor, trifft dies nur bedingt zu.



                                                                                                 555
XML und Webservices


xsl:template match=name
  Hier folgt ein Zeilenumbruch: xsl:text0x0A/xsl:text
/xsl:template
Wo Text ist, sind Kommentare nicht weit. Diese entsprechen, XML-konform, den
aus HTML bekannten und werden nicht in den Ausgabedatenstrom übernom-
men:
!-- Ein Kommentar in XSLT sieht aus wie       in HTML --
Vorlagen werden meist verschachtelt angewendet. Das folgende Beispiel zeigt das
Grundgerüst einer HTML-Seite, wie sie mit XSLT erzeugt wird:
xsl:template match=/
  html
    body
      xsl:apply-templates /
    /body
  /html
/xsl:template
Zuerst erkennt der XSLT-Prozessor hier, dass die Vorlage das Wurzelelement der
XML-Quelle verarbeitet. Dann wird das Grundgerüst der HTML-Seite erstellt.
Innerhalb des Body-Tags wird versucht, alle übrigen Elemente durch Aufruf der
passenden Vorlagen zu verarbeiten. Dass xsl:apply-template keine Parameter
hat, ist ein spezieller Fall. Er setzt voraus, dass alle Elemente irgendwo auf eine
passende Vorlage stoßen, wobei der Prozessor den besten Treffer auswählt und die-
sen – und nur diesen – ausführt.
Allerdings besitzt der Prozessor eine Fallback-Funktion. Wenn kein Template
zutrifft, wird der Inhalt des aktuellen Tags genommen und als gültiger Ausgabe-
wert betrachtet. Voraussetzung ist aber, dass wenigstens an einer Stelle
xsl:apply-template steht, um die Ausgabe auszulösen.
Sollen Inhalte von Tags gezielt ausgegeben werden, was sicher der häufigste Weg
ist, findet xsl:value-of Verwendung. Das Attribut select wählt den Inhalt des
durch eine XPath-Anweisung ermittelten Knotens und die gesamte Anweisung gibt
diesen als Zeichenkette aus.
xsl:template match=B
  xsl:value-of select=./
/xsl:template
Beim Einsatz innerhalb einer Vorlage bezieht sich der Pfad, den select akzeptiert,
auf den übergebenen Knoten, ist also relativ. Sie können aber absolute Angaben



556
Vorbemerkungen


verwenden. Der allein stehende Punkt reflektiert in XPath den aktuellen Knoten,
im Beispiel also den Inhalt des Tags B. Auf eben diesem Wege werden auch
Attribute gelesen. Das folgende Beispiel sucht nach Elementen vom Typ a und
gibt den Inhalt des Attributes href aus:
xsl:template match=a
  xsl:value-of select=@href/
/xsl:template
Der direkte Zugriff mit einer absoluten XPath-Anweisung wäre a/@href. Sie kön-
nen auf den Parameter eines Attributes auch direkt zugreifen. Ein a href-Tag
wird folgendermaßen in img src transformiert:
xsl:template match=a
  img src={@href} /
/xsl:template
Die Schreibweise mit den geschweiften Klammern ist immer dann angebracht,
wenn der Einsatz eines Tags aufgrund der Syntax nicht möglich ist. Andererseits ist
es mit xsl:element und xsl:attribute möglich, beliebige Tags indirekt zu
erzeugen.


Mit XSLT programmieren
Bei XSLT spricht man von einer funktionalen Programmiersprache. Zum Pro-
grammieren gehören jedoch nicht nur Regeln, wie sie in XSLT durch die Vorla-
gen gebildet werden, sondern auch Programmanweisungen.
Zuerst soll eine einfache Verzweigung mit xsl:if vorgestellt werden:
xsl:if test=@directory='hasfiles'
   Dieses Verzeichnis enthält Dateien
/xsl:if
Der Test kann verschiedene Operatoren und Funktionen verwenden, um Knoten
nach allerhand Kriterien zu untersuchen. Eine Else-Anweisung gibt es übrigens
nicht, hierfür ist die Mehrfachverzweigung gedacht:
xsl:choose
   xsl:when test=attribute='archive'
      Archiv
   /xsl:when
  xsl:when test=attribute='compressed'
     Compressed



                                                                               557
XML und Webservices


  /xsl:when
  xsl:when test=attribute='hidden'
     Hidden
  /xsl:when
  xsl:otherwise
   Unknown
  /xsl:otherwise
/xsl:choose
Wollen Sie Listen von bestimmten Tags an einer Stelle ausgeben, ist xsl:for-
each sehr praktisch, was ähnlich wie das foreach von PHP funktioniert. for-Schlei-
fen im Sinne imperativer Programmierung gibt es jedoch nicht, weil veränderliche
Zustände nicht erlaubt sind.
xsl:for-each select=name
  a href={.}xsl:value-of select=.//abr/
/xsl:for-each
Die xsl:for-each-Anweisung gibt, wie xsl:template auch, jeweils einen aktu-
ellen Knoten für jedes Element aus, das gefunden wurde. Deshalb funktioniert
auch hier der verkürzte Zugriff auf den Inhalt mit dem Punkt-Alias.
Von Interesse ist oft auch eine Sortiermöglichkeit. Sie können dazu innerhalb
einer Schleife mit xsl:sort eine Anweisung platzieren, die das zuverlässig erle-
digt:
xsl:for-each select=name
  xsl:sort select=. order=descending/
  a href={.}xsl:value-of select=.//abr/
/xsl:for-each
Das Element xsl:sort ist übrigens auch in xsl:apply-templates anwendbar.
Es versteht freilich einige Attribute mehr, mit denen die Steuerung der Sortierung
erfolgen kann.
Was Sie in XSLT nicht finden, sind die Anweisungen »for« und »while«. Beide
benötigen Variable, deren Zustand sich ändert. Das ist in funktionalen Sprachen
nicht möglich, weswegen die Anweisungen nicht sinnvoll sind. Komplexere
Schleifen werden durch Rekursion (Selbstaufruf einer Vorlage) und Parameter
ermöglicht. Oft ist dies kompakter als vergleichsweise imperative Programme, für
PHP-Programmierer aber möglicherweise ungewohnt.
Variablen sind dennoch verwendbar, sie verhalten sich aber ähnlich den Konstan-
ten in anderen Sprachen. Sie können Werte, Knoten oder Knotenbäume spei-
chern:


558
Vorbemerkungen


xsl:variable name=fieldata select=attribute/
Variable können natürlich auch komplexere Inhalte aufnehmen:
xsl:variable name=alldata
  xsl:if test=position()=last()
    TRUE
  /xsl:if
  xsl:if test=position()=1
    FALSE
  /xsl:if
/xsl:variable
Sie sehen im letzten Beispiel auch die Verwendung von XPath-Funktionen. Vari-
ablen gelten nur innerhalb der Vorlage oder der Schleife, in der sie definiert
wurden. Dieses Verhalten ist nicht modifizierbar, das heißt, es gibt keine Modifika-
toren wie public oder private, wie sie bei den objektorientierten Funktionen von
PHP anwendbar sind.
Ähnlich wie Variablen werden auch Parameter verwendet. Damit können Sie
einer Vorlage verschiedene Werte übergeben, damit diese sich je nach Art des Auf-
rufs unterschiedlich verhält. Der Aufruf sieht folgendermaßen aus (am Beispiel
einer benannten Vorlage):
xsl:call-template name=show.files
   xsl:with-param name=handlerno/xsl:with-param
/xsl:call-template
Innerhalb der Vorlage werden die übergebenen Parameter dann so verwendet:
xsl:template name=show.files
  xsl:param name=handler select=/
  ...
Das select-Attribut in xsl:param bestimmt einen Standardwert, wenn der Para-
meter nicht übergeben wurde.


XSLT praktisch verwenden
XSLT verfügt über einige komplexere Anweisungen, die für größere Projekte von
Bedeutung sind. Dazu gehört xsl:number zum Erzeugen fortlaufender Num-
mern oder Buchstabenfolgen.
Oft ist auch der Umgang mit ganzen Knoten notwendig, statt dem Textinhalt des
Knotens. Dann findet xsl:copy-of Verwendung. Sollen Knoten und Attribute


                                                                                559
XML und Webservices


kopiert werden, können mehrere Anweisungen mit xsl:copy zusammengefasst
werden.
Das folgende Beispiel kopiert ein XML-Dokument vollständig in ein anderes:
xsl:template match=*
  xsl:copy
    xsl:copy-of select=@*
    xsl:apply-templates/
  /xsl:copy
/xsl:template
Wenn Sie sich die Frage stellen, warum ein Dokument ausgerechnet mit XSLT
unverändert kopiert werden sollte, ist ein Blick auf xsl:output angebracht. Mit
dieser Anweisung kann die Kodierung des Ausgabestromes gesteuert werden. Das
Tag steht immer am Anfang des Dokumentes. Wenn Ihr XML-Dokument UTF-8
kodiert ist, können Sie es mit der Kopiervorlage des letzten Beispiels leicht in ein
anderes Format bringen, nebenbei auch ins HTML 4.0-Format:
xsl:output encoding=ISO-8859-1 method=html/
Sie können weitere XSLT-Dateien mit Hilfe der Anweisungen xsl:output und
xsl:include importieren. Die erste Funktion kann ein Dokument so einfügen,
als wäre der Inhalt an dieser Stelle geschrieben worden. Der Import hat eine
geringe Priorität. Stehen vorhandene und importierte Regeln miteinander im Kon-
flikt, unterliegen die importierten.


Die XSLT-Funktionen
Transformationen laufen oft in Abhängigkeit von konkreten Daten ab. XSLT stellt
deshalb einige elementare Funktionen zur Verfügung, die meist in select- und test-
Attributen benutzt werden.

Funktion                      Beschreibung
boolean()                     Typumwandlungen ausführen. Manche Funktionen brau-
number()                      chen einen bestimmten Typ als Parameter.
string()

format_number()               Formatiert Zahlen für die Ausgabe.

Tabelle 14.1: XSLT-Funktionen




560
Vorbemerkungen



Funktion                     Beschreibung
ceiling()                    Zahlenberechnungen und Runden. Die Funktionsweise
floor()                      entspricht den Funktion ceil, floor und round in PHP.
round()

concat()                     Verbindet Zeichenketten, ähnlich der Kombination mit
                             dem Punkt-Operator in PHP.
contains()                   Ermittelt, ob eine Zeichenkette bestimmte Zeichen enthält
starts-with()                bzw. ob sie mit einer bestimmten Zeichenfolge beginnt.
normalize_whitespace()       Kontrolliert den Umgang mit Leerzeichen.
string-length()              Ermittelt die Anzahl Zeichen einer Zeichenkette.
substring()                  Ermittelt Teile einer Zeichenkette, entweder nach der Posi-
substring-before()           tion oder in Abhängigkeit von einem Schlüsselzeichen
substring-after()

translate()                  Tauscht Zeichen aus.
count()                      Zählt Knoten und summiert Werte aus Knotenlisten.
sum()

generate-id()                Funktionen zum Umgang mit Knoten, der Erzeugung von
local-name()                 Identifikatoren usw.
name()

false()                      Diese Funktionen können Boolesche Werte für Boolesche
true()                       Ausdrücke beschaffen. Dies ist erforderlich, weil innerhalb
not()                        von Ausdrücken kein Zugriff auf Schlüsselwörter (wie true)
                             besteht.
current()                    Bestimmt die Position des Zeigers in Knotenlisten .
last()
position()

document()                   Realisiert den Dateizugriff, lädt ein anderes Modul.
key()                        Einen Knoten mit bestimmten Eigenschaften finden.
id()

element-available()          Eine Parserfunktion suchen und mitteilen, ob der Parser
                             diese unterstützt.

Tabelle 14.1: XSLT-Funktionen (Forts.)



                                                                                       561
XML und Webservices


Die Funktionen finden auch bei der Knotenselektion mit XPath Verwendung.
Mehr dazu im nächsten Abschnitt.


XPath
XPath ist eine Abfragesprache, die direkt oder über XSLT zum Einsatz kommt, um
Knoten oder Knotenlisten zu ermitteln.


Daten mit XPath suchen
Mit Hilfe einer Transformation wurde im letzten Abschnitt bereits eine Auswahl
aus XML-Daten vorgenommen. Immer dann, wenn auf Knoten oder Inhalte mit
match= oder select= zugegriffen wurde, kam bereits XPath zum Einsatz. Diese
einfache Form reicht in der Praxis nur selten aus. Der vollständige Name für
XPath lautet XML Path Language 1.0.
Um in der hierarchischen Struktur eines XML-Dokuments gezielt Knoten adres-
sieren zu können, wird XPath eingesetzt. Ohne eine solche Sprache würden Doku-
mente immer sequenziell durchlaufen werden, was wenig praxistauglich ist.
Gerade bei Webanwendungen, die oft vielen Benutzern einen Ausschnitt aus
einer großen Datenmenge zur Verfügung stellen, ist die schnelle Auswahl eminent
wichtig. Die Abfrage durch Beschreibung eines Pfades und verzichtet dabei auf
Schleifen oder andere zyklische Elemente. Damit ist die Konstruktion zur Laufzeit
und in Abhängigkeit vom aktuellen Auftreten von Knoten möglich. Wie der Name
der Sprache andeutet, ähnelt die Auswahl von Knoten den Pfadangaben im Datei-
system eines Betriebssystems. Das ist nahe liegend, weil auch dort Daten hierar-
chisch angeordnet sind. Eine typische XPath-Anweisung könnte also folgenderma-
ßen aussehen:
eintrag/name/vorname
Sie adressiert einen Knoten vorname, der Kind von name ist, was wiederum
Kind von eintrag sein muss:
eintrag
  name
    vorname
Es gibt verschiedene Knotentypen in XML. XPath muss diese adressieren können.
Konkret unterschieden werden die in der folgenden Tabelle dargestellten Typen:



562
Vorbemerkungen



XPath-Knoten                     Darstellung                        Kurzform

Wurzelknoten                     /

Elementknoten                    ElementName                        (keine Kurzform)
Kindknoten                       child::                            (kann entfallen)

Attributknoten                   attribute::AttributName            @AttributName

Textknoten                       self::node()                       .
Elternknoten                     parent::node()                     ..

Prozessinformation               Nicht darstellbar

Namensraum                       alias:


Tabelle 14.2: Knotentypen, die mit XPath adressiert werden können


Grundlagen für die Entwicklung von Ausdrücken
XPath basiert auf Ausdrücken, die den Weg zu einem Knoten beschreiben. Der
Weg kann – ebenso wie beim Dateisystem – durch absolute oder relative Pfadanga-
ben beschrieben werden. Absolute Angaben beginnen immer an der Dokumenten-
wurzel. Wenn der Ausdruck einen Pfad über mehrere Knoten hinweg beschreibt,
werden die Elemente durch Schrägstriche getrennt:
dirlist/directory/file
Jedes dieser Elemente wird allgemein als Lokalisierungsschritt bezeichnet. Die
eben gezeigte und häufig verwendete Darstellung durch einen Knotennamen ist
eine verkürzte Form. Tatsächlich kann jeder Schritt aus drei Teilen bestehen:
1. Achsenbezeichner
2. Knotentest
3. Prädikate
Der Achsenbezeichner modifiziert die Auswahl des Knotens auf der Grundlage
seiner Position im Baum. Als Trennzeichen zwischen dem Achsenbezeichner und
dem nächsten Teil des Ausdrucks werden zwei Doppelpunkte geschrieben ::.
Dies wird im nächsten Abschnitt noch weiter erläutert.



                                                                                          563
XML und Webservices


Der Knotentest beschreibt den Knoten selbst, beispielsweise eine direkte Auswahl
durch Nennung des Tagnamens. Die Prädikate stehen in eckigen Klammern und
werden meist zur Auswahl von Attributen verwendet. Insgesamt ergibt sich bei-
spielsweise folgender Ausdruck:
child::directory[attribute::hasfiles='true']
child:: ist der Achsenbezeichner, hier wird also beginnend von der aktuellen Posi-
tion das nächste Kindelement gesucht. Dann wird das Element selbst benannt:
directory. Es wird also das nächste Kindelement mit dem Namen directory
gesucht. Das Prädikat schränkt die Suche weiter ein; hier auf das Vorhandensein
eines Attributes hasfile mit dem Parameter 'true'3.
Um solche Ausdrücke nun entwickeln zu können, ist in erster Linie eine Kenntnis
der Achsenbezeichner notwendig.


Die XPath-Achsenbezeichner
Achsenbezeichner können einen oder mehrere Knoten auswählen. Die konkrete
Auswahl hängt vom aktuellen Knoten ab. Wenn ein Dokument sequenziell durch-
laufen wird, können die Bezeichner mehrere Knoten selektieren. Die folgende
Tabelle zeigt alle Achsenbezeichner für Elemente auf einen Blick.
Elemente sind hier Tag-Namen, also keine Attribute und keine Namensraumbe-
zeichnungen:

Achsenname                    Suchrichtung         Beschreibung
self                          –                    Der aktuelle Knoten.
child                         vor                  Die Kinder des Knotens.
parent                                             Die Eltern des Knotens.
descendant                    vor                  Alle Nachfahren (Kinder und Kindeskinder) .
descendant-or-self            vor                   Alle Nachfahren und der Knoten selbst.
ancestor                      rück                 Alle Vorfahren (Eltern und deren Eltern).
ancestor-or-self              rück                 Alle Vorfahren und der Knoten selbst.


3   Die einfachen Anführungszeichen innerhalb der doppelten gehören bei XPath dazu.



564
Vorbemerkungen



Achsenname               Suchrichtung      Beschreibung
following                vor               Alle folgenden Knoten im Dokument, die
                                           nicht direkte Nachfahren sind.
following-sibling        vor               Alle folgenden Geschwister.
preceding                rück              Alle vorhergehenden Knoten, die nicht
                                           Eltern sind.
preceding-sibling        rück              Alle vorhergehenden Geschwister.


Tabelle 14.3: Achsenbezeichner für Element-Knoten

Neben den Achsenbezeichnern für Elemente gibt es noch zwei spezielle: attribute
zur Auswahl von Attributen und namespace zur Lokalisierung von Namensräu-
men. Es bietet sich an dieser Stelle an, die Wirkung der Achsenbezeichner mit
einem Testprogramm zu lernen. Damit alle erdenklichen Kombinationen auch
getestet werden können, wird eine XML-Datei entworfen, die entsprechende Ach-
sen auch aufweist:

Listing 14.2: axischeck.xml – Testdatei zum Testen von Achsenzugriffen

?xml version=1.0 encoding=iso-8859-1 ?
Start
    Ebene1_1/Ebene1_1
    Ebene1_2
         Ebene1_2_1/Ebene1_2_1
         Ebene1_2_2
             Ebene1_2_2_1/Ebene1_2_2_1
             Ebene1_2_2_2/Ebene1_2_2_2
         /Ebene1_2_2
         Ebene1_2_3/Ebene1_2_3
         Ebene1_2_4/Ebene1_2_3
    /Ebene1_2
    Ebene1_3/Ebene1_3
    Ebene1_4
         Ebene1_4_1/Ebene1_4_1
    /Ebene1_4
/Start
Diese Datei dient im folgenden Beispiel als Basis für die ersten Programmierversu-
che mit PHP und XPath. Sie ist so aufgebaut, dass aus den Elementnamen die


                                                                                    565
XML und Webservices


Position im Baum ablesbar ist, was für das Verständnis der XPath-Abfragen sinnvoll
ist.


Mit PHP5 XPath-Ausdrücke verarbeiten
Lesen Sie zunächst das folgende Listing:

Listing 14.3: xpathcheck.php – XPath-Achsenbezeichner ausprobieren

?php
class XPathCheck
{
   private $nodes;
   private $xp;
   private $expressions = array('self',
                         'child',
                         'parent',
                         'descendant',
                         'descendant-or-self',
                         'ancestor',
                         'ancestor-or-self',
                         'preceding',
                         'preceding-sibling',
                         'following',
                         'following-sibling');

      private function PrintNodeList()
      {
         foreach ($this-nodes as $node)
         {
            printf('lt;%sgt;', ucfirst($node-tagName));
         }
      }

      public function __construct($path)
      {
         $dom = new DomDocument();
         $dom-load($path);
         $this-xp = new DomXPath($dom);
      }
      public function ShowNodeList()



566
Vorbemerkungen


   {
       echo 'table width=400 border=1';
       echo 'captionElement-Beziehungen, ausgehend von    
             Ebene1_2/caption';
       foreach ($this-expressions as $expression)
       {
          $this-nodes =  
                 $this-xp-query(//Ebene1_2/$expression::*);
          echo trtd$expression/td;
          echo td;
          $this-PrintNodeList();
          echo /td/tr;
       }
       echo '/table';
   }
}
$x = new XPathCheck('data/axischeck.xml');
$x-ShowNodeList();
?
Führt man diese Schritte für alle XPath-Standardanweisungen aus, bezogen auf
den Knoten Ebene1_2 als Startknoten, ergibt sich folgende Abbildung:




                                                                Abbildung 14.1:
                                                                Ausgabe der Kno-
                                                                tenlisten für alle
                                                                typischen Achsen-
                                                                bezeichner

Der Ausdruck selektiert lediglich die Achse (achsenbezeichner::*), wobei das *
zur Auswahl aller Elemente der Achse führt. In der Praxis könnten sich hier Ele-
mentnamen anschließen, um die Auswahl einzuschränken, sowie eines oder
mehrere Attribute, die auftreten oder bestimmte Parameter aufweisen müssen.




                                                                               567
XML und Webservices



Ein konkretes XML-Format: RSS
RSS dient dem Austauschen von Nachrichten und Inhalten über Nachrichten-
seiten. Auch große Nachrichtendienste wie Wired oder Slashdot bieten Informa-
tionen zur Übernahme auf anderen Seiten an. Eine private Seite kann dies
deutlich aufwerten. Das Prinzip ist dabei einfach. Der RSS-Feed (ein XML-Frag-
ment) wird von der Spenderseite abgeholt. Enthalten ist der Nachrichtentext in
einer Art Zusammenfassung und ein Link zurück zum Spender, der zum Volltext
führt. Davon profitieren beide Seiten. Der Spender erhält Zugriffe durch Nutzer
fremder Seiten und der Empfänger hat ständig aktuelle Inhalte zu bieten, ohne
dafür irgendwelchen Aufwand treiben zu müssen.
RSS kann bis zu einzelnen Nachrichten und Stichwörtern herunter gebrochen
werden, nur die letzten Änderungen oder Aktualisierungen enthalten oder auch
zum Verfolgen von Einträgen in einer Änderungsliste dienen. Der Einsatzzweck
ist keineswegs auf Nachrichten beschränkt, die aus Sport oder Politik stammen,
sondern kann ebenso die Informationen einer Serverüberwachung umfassen.
RSS-fähige Programme werden als Aggregatoren bezeichnet, das sind quasi Daten-
sammler der Weblog-Gemeinschaft. Weblogs wiederum zeichnen sich selbst oft
durch die Fähigkeit aus, Inhalte im RSS-Format bereitstellen zu können. Auf diese
Art und Weise werden Weblogs gleichzeitig zu Spendern und Empfängern – ein
wilder Kreislauf aus Nachrichtenaustauschprogrammen entsteht.


Rückblick
RSS umfasst allerdings weit mehr als ein einfaches XML-Format zum Nachrich-
tenaustausch. Ein Blick zurück ist hilfreich, um die Zusammenhänge zu verstehen
und die richtige Strategie zur Programmierung eigener RSS-Programme zu fin-
den.
Die erste Version war RSS 0.90, entworfen von Netscape als Format zum Aufbau
von Portalen, die sich die wichtigsten aktuellen Nachrichten von großen Nach-
richtenanbietern holen. Diese Version war äußerst komplex, was einer schnellen
Verbreitung eher abträglich ist. Daraus entstand in kurzer Zeit RSS 0.91, eine ver-
einfachte Ausgabe. Netscape verlor schnell das Interesse am Betrieb von Portalen
und die Fa. UserLand Software übernahm die weitere Entwicklung als Basis ihrer
kommerziellen Weblog-Produkte. In der Zwischenzeit spaltete sich eine weitere,
nicht kommerzielle Gruppe ab und entwarf ein weiteres Format, basierend auf
dem alten 0.90, bevor es die Vereinfachungen der 0.91 erfuhr. Dieses Format, sei-


568
Vorbemerkungen


nerseits mit dem ebenso sehr komplexen RDF verwandt, wurde als RSS 1.0
bezeichnet. UserLand war in diesen Prozess nicht involviert worden und torpediert
seitdem die Entwicklung mit einer Reihe eigener Versionen, die aus der 0.91-
Reihe fortgeführt wurden. Auf diesem Wege entstanden RSS 0.92, RSS 0.93 und
RSS 0.94. Weil die 1.0 bereits vergeben war, heißt die finale UserLand-Version
RSS 2.0. Womit das Chaos perfekt ist.


Welche Version man verwenden sollte
Es gibt mittlerweile sieben Versionen. Normalerweise macht eine derartige Versi-
onspolitik ein Produkt nachhaltig kaputt. RSS hat sich dennoch rasend verbreitet,
vermutlich aufgrund des anhaltenden Bedarfs am automatischen Informationsaus-
tausch. Die folgende Tabelle hilft, die richtige Version zu wählen:

Version Ursprung Hinweis                Status            Empfehlung

0.90      Netscape                      Obsolet           Nicht mehr verwenden.
                                        wegen 1.0

0.91      UserLand Besonders            Offiziell obso-   Leicht zu verwenden für einfa-
                   einfach              let wegen 2.0,    che Ansprüche, interessant,
                                        aber noch sehr    wenn spätere Migration auf 2.0
                                        populär           geplant ist.
0.92,     UserLand Umfassender          Obsolet           Nicht mehr verwenden, besser
0.93,              als 0.91             wegen 2.0         auf 2.0 setzen.
0.94
1.0       RSS-       RDF-basiert,       Stabil, aktuell   Wird verwendet, wenn auch
          DEV        erweiterbar,                         mit RDF-Quellen gearbeitet
          Working    offener                              wird.
          Group      Standard
2.0       UserLand 0.9X                 Stabil, aktuell   Generell empfehlenswert.
                   kompatibel,
                   erweiterbar
Tabelle 14.4: Übersicht über die RSS-Versionen




                                                                                       569
XML und Webservices


Wie RSS praktisch aussieht
Für den Einsatz von RSS auf einer mit PHP programmierten Webseite braucht
man zuerst ein Programm, das RSS-Feeds anderer Seiten liest. Eigene Angebote
setzen immerhin auch eigene Nachrichten voraus, und die sind ungleich schwerer
zu produzieren. Das Lesen der Datenquelle ist einfach – sie ist unter einer spezifi-
schen URL zu erreichen. Auf Webseiten kann man diese meist hinter dem Bild-
chen       zu finden.
Das folgende Beispiel stammt von xml.com und ist in RSS 0.91 erstellt:

Listing 14.4: Ein RSS 0.91-Feed, gefunden auf xml.com

rss version=0.91
  channel
    titleXML.com/title
    linkhttp://www.xml.com//link
    descriptionXML.com features a rich mix of information and services
for the XML community./description
    languageen-us/language
    item
      titleNormalizing XML, Part 2/title
      link
        http://www.xml.com/pub/a/2002/12/04/normalizing.html
      /link
      descriptionIn this second and final look at applying relational
       normalization techniques to W3C XML Schema data modeling, Will
       Provost discusses when not to normalize, the scope of uniqueness
       and the fourth and fifth normal forms./description
    /item
    item
      titleThe .NET Schema Object Model/title
      link
        http://www.xml.com/pub/a/2002/12/04/som.html
      /link
      descriptionPriya Lakshminarayanan describes in detail the use of
       the .NET Schema Object Model for programmatic manipulation of W3C
       XML Schemas./description
    /item
    item
      titleSVG's Past and Promising Future/title
      link



570
Vorbemerkungen


         http://www.xml.com/pub/a/2002/12/04/svg.html
       /link
       descriptionIn this month's SVG column, Antoine Quint looks back
at SVG's journey through 2002 and looks forward to 2003./description
    /item
  /channel
/rss
Die Datenquelle präsentiert einen Kanal (Thema), der einen Titel, den Link
zurück zur Quelle und eine Beschreibung enthält. Optional kann eine Sprache
angegeben werden, gefolgt von einer Reihe konkreter Einträge, die jeweils wieder
einen Titel, den Rücklink und eine Beschreibung enthalten.
In RSS 1.0 sieht exakt derselbe Inhalt etwas anders aus:

Listing 14.5: RSS1.0-Feed, identisch mit dem Inhalt aus Listing 14.4

rdf:RDF
  xmlns:rdf=http://www.w3.org/1999/02/22-rdf-syntax-ns#
  xmlns=http://purl.org/rss/1.0/
  xmlns:dc=http://purl.org/dc/elements/1.1/
  channel rdf:about=http://www.xml.com/cs/xml/query/q/19
    titleXML.com/title
    linkhttp://www.xml.com//link
    descriptionXML.com features a rich mix of information and services
      for the XML community./description
    languageen-us/language
    items
      rdf:Seq
         rdf:li rdf:resource =     
          http://www.xml.com/pub/a/2002/12/04/normalizing.html/
         rdf:li rdf:resource =     
          http://www.xml.com/pub/a/2002/12/04/som.html/
         rdf:li rdf:resource =     
          http://www.xml.com/pub/a/2002/12/04/svg.html/
      /rdf:Seq
    /items
  /channel
  item rdf:about = http://www.xml.com/pub/a/2002/12/04/          
   normalizing.html
    titleNormalizing XML, Part 2/title
    link
       http://www.xml.com/pub/a/2002/12/04/normalizing.html



                                                                                   571
XML und Webservices


    /link
    descriptionIn this second and final look at applying relational
      normalization techniques to W3C XML Schema data modeling, Will
      Provost discusses when not to normalize, the scope of uniqueness
      and the fourth and fifth normal forms./description
    dc:creatorWill Provost/dc:creator
    dc:date2002-12-04/dc:date
  /item
  item rdf:about =
        http://www.xml.com/pub/a/2002/12/04/som.html
    titleThe .NET Schema Object Model/title
    linkhttp://www.xml.com/pub/a/2002/12/04/som.html/link
    descriptionPriya Lakshminarayanan describes in detail the use of
      the .NET Schema Object Model for programmatic manipulation of W3C
      XML Schemas./description
    dc:creatorPriya Lakshminarayanan/dc:creator
    dc:date2002-12-04/dc:date
  /item
  item rdf:about =
        http://www.xml.com/pub/a/2002/12/04/svg.html
    titleSVG's Past and Promising Future/title
    linkhttp://www.xml.com/pub/a/2002/12/04/svg.html/link
    descriptionIn this month's SVG column, Antoine Quint looks back at
     SVG's journey through 2002 and looks forward to 2003./description
    dc:creatorAntoine Quint/dc:creator
    dc:date2002-12-04/dc:date
  /item
/rdf:RDF
Die Informationen sind hier ein wenig ausführlicher. So gehören auch der Name
des Autors und ein Datum dazu. Das Datum ist sehr sinnvoll, wenn man aus einer
großen Anzahl von Nachrichten nur die aktuellsten anzeigen möchte. Die Struk-
tur ist dennoch weitgehend der früherer RSS-Versionen angepasst. Wer kompa-
tible Software schreiben möchte, die mit Datenquellen beider Arten umgehen
kann (das ist heute leider notwendig, weil beide Versionen gleichermaßen benutzt
werden), kann sich mit folgenden »Umrechnungsregeln« zwischen 1.0 und 0.91
behelfen (aus der Sicht von 1.0):
1. Das Stammelement ist rdf:RDF statt rss. Beim Lesen mit PHP ist es nicht zwin-
   gend erforderlich, dies zu prüfen – man kann der Quelle durchaus vertrauen.
2. RSS 1.0 verwendet den Namensraum http://purl.org/rss/1.0/ als Standard-
   namensraum. Die RDF-spezifischen Elemente sind durch den eigenen RDF-


572
Vorbemerkungen


    Namensraum http://www.w3.org/1999/02/22-rdf-syntax-ns# abgetrennt. Die
    Spezifika von RDF kann man aber getrost ignorieren für einen einfachen
    Nachrichtenleser. Metadaten (Autor, Datum) befinden sich im Namensraum
    des so genannten Dublin Core4 http://purl.org/dc/elements/1.1/.
    Wenn Sie nun mit einem Parser arbeiten, der Namensräume nur beschränkt
    verarbeiten kann – wie SimpleXML in PHP5 – dann ignorieren Sie das ein-
    fach. Innerhalb eines vollständigen RDF-basierten RSS 1.0-Feed dürften Ele-
    mentnamenskonflikte kaum auftreten. Der blanke Leseprozess ist also
    unkritisch. Erzeugen lassen sich RSS-Quellen damit freilich nicht. Sie müssen
    lediglich vertrauen, dass bei der Definition der Quelle die Standardaliase nicht
    verändert wurden. Ein Parser, der Namensräume nicht kennt, wird den Ele-
    mentnamen als dc:creator lesen, nicht als creator. Solange Sie von einem fixen
    Alias dc ausgehen, ist das in Ordnung. Tauscht das jemand gegen dublc aus,
    versagt das Skript. Programme mit vollständiger Namensraumunterstützung
    würden eine solche Änderung tolerieren. Letztere können auch erkennen, wel-
    che Version benutzt wurde und ihre Strategie beim Verarbeiten anpassen.
3. Ein bedeutender Unterschied ist die Tatsache, dass das item-Element außer-
   halb des channel-Elements steht, bei RSS 0.91 war es genau umgekehrt. Zur
   Verarbeitung beider Versionen muss man damit zwangsläufig Teile des Codes
   doppelt schreiben. Nebenbei bemerkt: RSS 0.90 (außerhalb) und RSS 2.0
   (innerhalb) zeugen von leichten Designzweifeln bei den Erfindern.
4. Das zusätzliche item-Element innerhalb des channel-Elements kann igno-
   riert werden. Es bedient lediglich native RDF-Parser.
RSS 2.0 wurde bereits mehrfach angesprochen. Auch hierfür ist ein Blick auf den-
selben Inhalt im RSS 2.0-Format empfehlenswert:
rss version=2.0
     xmlns:dc=http://purl.org/dc/elements/1.1/
  channel
    titleXML.com/title
    linkhttp://www.xml.com//link
    descriptionXML.com features a rich mix of information and services
for the XML community./description
    languageen-us/language
    item
      titleNormalizing XML, Part 2/title

4   Der Name geht zurück auf die Dublin Core Metadata Initiative, eine Organisation, die Beschreibungsfor-
    mate für Metadaten von Dokumenten entwirft (siehe http://dublincore.org).



                                                                                                     573
XML und Webservices


       link
          http://www.xml.com/pub/a/2002/12/04/normalizing.html
       /link
       descriptionIn this second and final look at applying relational
        normalization techniques to W3C XML Schema data modeling, Will
        Provost discusses when not to normalize, the scope of uniqueness
        and the fourth and fifth normal forms./description
       dc:creatorWill Provost/dc:creator
       dc:date2002-12-04/dc:date
    /item
    item
       titleThe .NET Schema Object Model/title
       link
          http://www.xml.com/pub/a/2002/12/04/som.html
       /link
       descriptionPriya Lakshminarayanan describes in detail the use of
        the .NET Schema Object Model for programmatic manipulation of W3C
        XML Schemas./description
       dc:creatorPriya Lakshminarayanan/dc:creator
       dc:date2002-12-04/dc:date
    /item
    item
       titleSVG's Past and Promising Future/title
       link
          http://www.xml.com/pub/a/2002/12/04/svg.html
       /link
       descriptionIn this month's SVG column, Antoine Quint looks back
        at SVG's journey through 2002 and looks forward to 2003.
       /description
       dc:creatorAntoine Quint/dc:creator
       dc:date2002-12-04/dc:date
    /item
  /channel
/rss
RSS 2.0 verwendet Namenräume analog zu RSS 1.0, aber es wird kein RDF verwen-
det. Ebenso wie bei RDF 0.91 gibt es keinen Standardnamensraum und die Inhalt
sind innerhalb des channel-Elements. Code, der flexibel mit RSS 0.91 und 1.0
umgehen kann, wird mit nur geringem Aufwand auch RSS 2.0 lesen können.




574
Einführung in die libxml2



14.2 Einführung in die libxml2
Generell ist XML ein Thema für Website-Entwickler, denn es gibt vielfältige
Anwendungen:
í   Webservices (basieren weitgehend auf XML)
í   Datenim- und -export aus und zu unbekannten oder inkompatiblen Daten-
    quellen
í   Aufbereitung von XHTML für verschiedene Medien
í   Bereitstellung oder Konsumierung von RSS-Kanälen
In PHP 4 war nur der SAX-Parser Expat fester Teil des Pakets. Alle anderen Erwei-
terungen waren entweder nicht immer vorhanden oder noch im Entwicklungssta-
dium (und sind es bis heute). Applikationen, die XML nutzen, waren damit etwas
eingeschränkt, denn nicht alle Provider und Webhoster boten die nötige Unterstüt-
zung. PHP DOM wurde zwar heiß diskutiert und oft als Wunderwaffe der XML-
Entwickler gepriesen, dümpelte aber mangels Provider-Unterstützung eher vor
sich hin. Wichtige Applikationen wie das Templatesystem phpTemple (http://
www.phptemple.de) sind jedoch auf XML angewiesen, um hohe Performance auch
bei anspruchsvollen Seiten zu ermöglichen.


Neu in PHP5
Anstatt des Sammelsuriums von teilweise unfertigen und inkompatiblen Biblio-
theken (XSLT von Ginger Alliance, Expat, XML DOM) wurde nun auf das sehr
weit entwickelten Projekt Gnome XML Parser libxml gesetzt. Die Vorgängerver-
sion war bereits die Basis für die XML DOM-Erweiterungen. Vielleicht haben Sie
bereits mit den DOM-Erweiterungen gearbeitet und herausgefunden, dass diese
von den drei Bibliotheken der schwächste Teil waren. Dies als die herausragende
XML-Neuerung in PHP5 anzupreisen erscheint nicht einsichtig. Allerdings lag die
schlechte Qualität nicht an der verwendeten Bibliothek, sondern an den Mängeln
der Integration in PHP. Dieses Problem wurde durch den Wechsel der Entwickler
und Verbesserungen in der Struktur der Programmierung beseitigt.
Die Gnome-Bibliothek liefert einen der schnellsten Parser, die es gibt. Sie ist rela-
tiv jung und die Entwickler konnten von den Fehlern der anderen Parser lernen.
Und nicht zuletzt werden viele in der XML-Welt dringend benötigte Standards
unterstützt:


                                                                                      575
XML und Webservices


í     SAX- und DOM-Programmierschnittstellen
í     Validierung mit DTDs und XML Schema
í     XSLT, XPath, XPointer und Xinclude
í     Integrierte Parser für HTML und Docbook-XML
í     XMLSec (XML Security Library)
Insgesamt gewinnt PHP dadurch deutlich und es lohnt sich unbedingt, sich mit
der Bibliothek und den neuen Möglichkeiten intensiver auseinander zu setzen.
Für viele Projekte gibt es dazu auch keine Alternative, weil die weitere Unterstüt-
zung der alten Bibliotheken nicht geplant ist. Andererseits bleiben die Funktions-
schnittstellen erhalten, um eine Abwärtskompatibilität zu gewährleisten. Es ist also
nicht zwingend erforderlich, alle XML-basierten Skripte nun umzuschreiben. Bis
auf wenige Ausnahmen sollten alle Programme unverändert laufen. Es lohnt sich
dennoch, den vorhandenen Code zu untersuchen, weil sich eventuell bessere oder
schnellere Möglichkeiten bieten und der eine oder andere »Work-Around« oder
»Dirty-Hack« sich nun sauber programmieren lässt.


Die neuen DOM-Funktionen
Die Kernfunktionen der DOM-Erweiterung sind inzwischen weitgehend stabil aus
Sicht der Entwicklung. Das ist sehr wichtig, weil ständige Änderungen an den Bib-
liotheken den Einsatz in ernsthaften Projekten arg gefährden. Hier hatte PHP bis-
lang das Nachsehen gegenüber .NET und Java, wo es eine sehr solide und lange
Zeit stabile Basisplattform gab und gibt.


HTML verarbeiten
Webseiten bestehen aus HTML und HTML ist verwandt mit XML. Es sollte also
möglich sein, HTML mit einem XML-Parser zu verarbeiten. Weit gefehlt, denn
tatsächlich stammt HTML von der alten Auszeichnungssprache SGML ab, aus der
auch XML entwickelt wurde. XML definiert eine strengere Syntax und erlaubt
damit schnellere und stabilere Parser. Prinzipiell kann also ein XML-Parser einfa-
che HTML-Seiten nicht verarbeitet, bevor diese nicht »XML-tauglich« gemacht
werden. Auch dazu gibt es einen entsprechenden Standard: XHTML. In der Praxis
ist dies jedoch nicht immer einfach, weil man dazu spezielle Programme braucht,
die die Umwandlung vornehmen – oder PHP5.



576
Einführung in die libxml2


Das folgende Beispiel zeigt ein typisches, aus XML-Sicht nicht »wohlgeformtes«
XML-Dokument:

Listing 14.6: XmlDomBadHtml.html – Ein einfaches HTML-Dokument, nicht wohlge-
formt

!doctype HTML PUBLIC -//W3C//DTD HTML 4.0 Transitional//EN
html
head
titleXML/title
/head
body
h2HTML mit DOM verarbeiten/h2
p
    Diese Seite enthält nach XML-Regeln nicht wohlgeformte Tags.
br
form
select
optionRed
option selectedBlue
optionGreen
/select
/form
/body
/html
Dem neuen DOM-Parser ist dies ziemlich egal, er liest diese HTML-Seite pro-
blemlos ein. Das folgende Skript verarbeitet die Seite und agiert dabei mit Knoten,
Knotenauflistungen, Elementen und Attributen:

Listing 14.7: XmlDomHtml.php – Laden und Verarbeiten von nicht wohlgeformtem
HTML

?php
$doc = new DomDocument();
$doc-loadHtmlFile('data/XmlDomBadHtml.html');
$head = $doc-getElementsByTagName('head');
$body = $doc-getElementsByTagName('body');
function getTitle ($head)
{
   foreach ( $head as $header )
   {
       if ( $header-tagName == 'title' )


                                                                                     577
XML und Webservices


          echo (bPage Title:/b .$header-textContent.   
                br /n);
      }
}

function parseBody($body)
{
   $bodyTag = $body-item(0);
   foreach ($bodyTag-childNodes as $element)
   {
      $content = htmlspecialchars(   
                 utf8_decode($element-textContent));
       switch ($element-tagName)
       {
           case 'h2':
              echo bHeader 2:/b $contentbr /n;
              break;
           case 'p':
              echo bParagraph:/b $contentbr /n;
              break;
           case 'form':
               foreach ($element-childNodes as $input)
               {
               if ($input-nodeType != XML_ELEMENT_NODE) continue;
                    if ($input-tagName == 'select')
                        parseSelect($input);
               }
               break;
         default:
               echo $content-tagName.'br';
             break;
       }
   }
}

function parseSelect($select)
{
   echo bSelect:/bnuln;
   $options = $select-childNodes;
   foreach ($options as $option)
   {
      $content = htmlspecialchars(   

578
Einführung in die libxml2


                      utf8_decode($option-textContent));
      echo li{$content};
      if ($option-hasAttribute('selected'))
      {
             echo '  SELECTED';
      }
        echo /lin;
    }
    echo /uln;
}

getTitle($head);
parseBody($body);
?
Die Nutzung der DOM-Klasse beginnt immer mit der Instanziierung des Objekts:
$doc = new DomDocument();
Dann müssen die zu verarbeitenden Daten beschafft werden. In diesem Beispiel
wird mit HTML gearbeitet, also wird die entsprechende Methode benutzt:
$doc-loadHtmlFile('data/XmlDomBadHtml.html');
Dann werden die Elemente head und body als Knotenlisten ermittelt:
$head = $doc-getElementsByTagName('head');
$body = $doc-getElementsByTagName('body');
Es schließen sich drei Funktionen an, die Teile der Datei untersuchen. Beispiel-
haft soll die Analyse des body-Tags näher vorgestellt werden. Da getElementsBy-
TagName eine Knotenauflistung zurückgibt, im konkreten Fall aber bekannt ist, dass
es nur ein Element geben kann, wird dieses gezielt ermittelt:
$bodyTag = $body-item(0);
Stattdessen könnte die Auflistung auch mit foreach durchlaufen werden. Das Ele-
ment selbst (der Typ wäre hier DomElement) wird nun seinerseits mit foreach
durchlaufen, um alle Elemente der obersten Ebene zu erreichen:
foreach ($bodyTag-childNodes as $element)
Will man alle Elemente erreichen, muss die Funktion rekursiv programmiert wer-
den.
Zu jedem Element wird nun der Inhalt ermittelt. Diese Funktion ist zwar nicht
dem DOM-Standard entsprechend, ist aber eine recht brauchbare PHP-Erweite-



                                                                                    579
XML und Webservices


rung. Zusätzlich ist noch darauf zu achten, dass das Dokument gegebenenfalls
UTF-8-kodiert ist. UTF-8 ist das Standardzeichenformat für XML. Für die Aus-
gabe im Beispiel wird außerdem noch HTML-Code sichtbar gemacht:
$content = htmlspecialchars(utf8_decode($element-textContent));
Die weitere Verarbeitung richtet sich nach dem gefundenen Elementnamen:
switch ($element-tagName)
Wenn man weitere Untersuchungen anstellt ist es wichtig zu wissen, welchen Typ
ein Knoten hat. Denn die von der Knotenliste extrahierten Knoten sind nicht
immer Elemente, sondern können auch Kommentare, Zeichen, Namensräume,
Prozessanweisungen usw. sein:
if ($input-nodeType != XML_ELEMENT_NODE) continue;

          Informationen über die verwendbaren Konstanten finden Sie in der
          Kurzreferenz am Ende des Kapitels.


In der Funktion parseSelect ist noch eine weitere Methode zu finden, die hier dazu
dient, die Existenz eines Attributes zu ermitteln:
if ($option-hasAttribute('selected'))
Die Abbildung zeigt, wie das Skript arbeitet:



                                                                 Abbildung 14.2:
                                                                 Verarbeitung
                                                                 einer HTML-Seite
                                                                 mit dem DOM-
                                                                 Modul

Das DOM-Modul ist äußerst leistungsfähig und konnte hier nur oberflächlich
angerissen werden. Für größere XML-Projekte ist es unbedingt zu empfehlen und
die hier gezeigten Techniken lassen sich in jeder Richtung ausbauen. Informieren
Sie sich in der Kurzreferenz über die verfügbaren Methoden und Eigenschaften,
um einen schnellen Einstieg zu finden.




580
SimpleXML



14.3 SimpleXML
Neben der DOM-Erweiterung, die auf libxml basiert, fand eine zweite XML-
Schnittstelle Eingang in PHP5: SimpleXML. Diese Bibliothek ist bekannt als
»Object Mapping XML API«.


Unterschiede zur DOM-Schnittstelle
DOM (Document Object Model) erzeugt aus einem XML-Dokument einen voll-
ständigen Baum von Objekten. Der Zugriff in einer Applikation ist sehr direkt
möglich, dafür ist der Speicherverbrauch erheblich, weil immer die gesamte Struk-
tur im Speicher steht. Ein vollständiges Abbild eines Dokuments als DOM ist
nicht trivial – auch in der Verarbeitung. SimpleXML nutzt ebenso eine objektori-
entierte Sicht, vereinfacht diese jedoch drastisch. Statt der »offiziellen« DOM-API,
die alle Programmiersprachen mit DOM-Unterstützung anbieten, überführt Simp-
leXML die Daten direkt in eine einfache, an PHP angepasste Objektstruktur.
Als Ausgangspunkt für erste Versuche soll folgendes Dokument dienen:

Listing 14.8: data/dbconfig.xml – Eine einfache XML-Datei zum Testen

?xml version=1.0?
config
 emailjoerg@krause.net/email
 database
   hostlocalhost/host
   typeMySQL/type
   userdbuser/user
   passgeheim/pass
   dbnamesimpletest/dbname
 /database
/config
Ein Parser, der dies verarbeitet, könnten nun folgendermaßen aussehen:

Listing 14.9: SimpleXML1.php – Einfachste Verarbeitung von XML mit SimpleXML

?php
$xml = file_get_contents('data/dbconfig.xml');
$config = simplexml_load_string($xml);



                                                                                581
XML und Webservices


echo ('pre');
print_r($config);
echo ('/pre');
?
Die print_r-Funktion zeigt die vollständige Objektstruktur an, wie sie von Simple-
XML erkannt wurde:




                                                Abbildung 14.3:
                                                Objektstruktur eines
                                                XML-Dokuments

Das folgende Skript nutzt diese Struktur, um auf eine MySQL-Datenbank zuzu-
greifen. Dazu werden die Daten aus der XML-Datei zur Konfiguration benutzt:

Listing 14.10: SimpleXMLConfig.php – Nutzung von XML zur Konfiguration

?php
$xml = file_get_contents('data/dbconfig.xml');
$config = simplexml_load_string($xml);
$db = $config-database;
try
{
    if (!@mysql_connect($db-host,$db-user,$db-pass))
    {
        throw new Exception('Database Connect Error'.mysql_error());
    }
}
catch (Exception $e)
{
    echo 'Error: ' . $e-GetMessage();
    echo 'br/';
    echo 'Sende E-Mail an: ' . $config-email;
}
?


582
SimpleXML


Wie der Name verspricht, ist SimpleXML tatsächlich sehr einfach. Parsen und
Nutzen ist praktisch unmittelbar miteinander verbunden und eignet sich hervorra-
gend für kleinere Sequenzen, beispielsweise Konfigurationsdateien.


Ein Schritt weiter
XML ist großartig und äußerst vielseitig einsetzbar. Der erste Abschnitt zeigte
bereits, dass es mit den neuen XML-Funktionen in PHP5 nicht schwer ist, damit
umzugehen. PHP 4 verlangte hier einiges mehr an Programmierung stellte damit
eine zu große Hürde dar, um auch einfache Problemstellungen mit XML zu lösen.
Prinzipiell gibt es drei Wege, um auf XML zuzugreifen. Die meisten Program-
mierumgebungen nutzen alle drei, SAX, DOM und XSLT:
í   SAX (Simple API for XML) bietet eine ereignisbasierte Verarbeitung und ver-
    langt, dass die Elemente manuell gelesen werden, indem das aktuell verarbei-
    tete auf einem Stapelspeicher abgelegt und von da wieder entnommen wird.
í   DOM (Document Object Model) ist sehr flexibel und verständlich, verlangt
    jedoch reichlich Ressourcen. Außerdem ist der Aufwand bei sehr trivialen Auf-
    gabenstellungen recht hoch.
í   XSLT (XSL Transformation) ist eine funktionale Programmiersprache zum
    Transformieren von XML in anderes XML oder in Text oder HTML. Es ist
    sehr mächtig, aber auch sehr gewöhnungsbedürftig.
SimpleXML ist eine neue und allein stehende Erweiterung in PHP5 und dient
dazu, den Zugriff auf XML-Dokumente zu erledigen. Dabei geht es in erster Linie
darum, die Daten in eine interne Datenstruktur zu übertragen, die sich besser wei-
terverarbeiten lässt. Dazu gehören beispielsweise Arrays oder Datenbanken. XML
dient also keineswegs dazu, intern nur noch damit zu arbeiten, sondern soll vor
allem den vollkommen plattformneutralen Datenaustausch gewährleisten. Neben-
bei kann man seine Daten nun auch in einem der vielen XML-Editoren bearbei-
ten, was recht bequem sein kann. Mit einem PHP-Array können eben nur sehr
wenige andere Systeme irgendwas anfangen.
Besonders leistungsfähig ist SimpleXML, wenn man gezielt auf die Attribute eines
Elements oder dessen Inhalt zugreifen möchte. Andere, komplexere Zugriffsme-
thoden verlangen andere Erweiterungen. Der Namensteil »Simple« kann hier
wörtlich genommen werden. Damit wird die Erweiterung aber richtig praxistaug-
lich, denn die Lernkurve ist verhältnismäßig flach.



                                                                              583
XML und Webservices


In diesem Abschnitt wird gezeigt, wie mit SimpleXML eine XML-Datei gelesen
und verarbeitet wird. Dazu wird der Inhalt teilweise extrahiert und eine Abfrage
mit der Abfragesprache XPath realisiert. Als Beispielformat soll das beliebte RSS
dienen, das bereits am Anfang des Kapitels kurz vorgestellt wurde. RSS wird zum
Datenaustausch der beliebten Weblogs benutzt. Hier wird die einfachste Version
von RSS verwendet, nicht die komplexere »Muttersprache« RDF, die einiges
mehr an XML-Techniken nutzt und die meisten Anwendungen massiv überfor-
dert. Wer mag, findet im Web reichlich Quellen zu RDF. Auf jeden Fall müssen
Sie mit XML-Namensräumen und XPath vertraut sein, um alle folgenden Skripte
lesen zu können.


XML Lesen
Als Ausgangspunkt der ersten Versuche mit SimpleXML soll die folgende RSS-
Datei dienen:
?xml version=1.0 encoding=utf-8 ?
rss version=0.91
channel
    titlePHP: Hypertext Preprocessor/title
    linkhttp://www.php.net//link
    description
     The PHP scripting language web site
    /description
/channel
item
    titlePHP 5.0.0 Beta 4 Released/title
    linkhttp://www.php.net/downloads.php/link
    descriptionPHP 5.0 Beta 4 has been released. The third beta of PHP
     is also scheduled to be the last one (barring unexpected
     surprises)./description
/item
item
    titlePHP Template Project/title
    linkhttp://www.phptemple.de/link
    description
     A design oriented template system for PHP is announced by Comzept
     Systemhaus GmbH. It compiles dynamic HTML into native PHP.
    /description
/item
/rss



584
SimpleXML


Listing 14.11: rssfeed091.xml – Eine erste RSS-Datei als Datenbasis

Um mit dieser Datei arbeiten zu können, wird in SimpleXML ein entsprechendes
Objekt erzeugt:
$rss = simplexml_load_file('/data/rssfeed091.xml');
Danach steht das Objekt in der Variablen $rss zur Verfügung, vorausgesetzt die
Datei liegt im Verzeichnis /data und heißt rssfeed091.xml. Diese Angaben entspre-
chen den Daten auf der Buch-CD.
Auf die Elemente (Tags) kann nun mit der üblichen Objektsyntax zugegriffen wer-
den, so als wären es Eigenschaften:
echo $rss-channel-title;
Das vollständige Skript sieht folgendermaßen aus:

Listing 14.12: SimpleXMLRss091.php – Einfachster Zugriff auf eine RSS-Quelle

?php
$rss = simplexml_load_file('data/rssfeed091.xml');
echo Channel b{$rss-channel-title}/b ;
echo (See a href={$rss-channel-link}{$rss-channel-link}/a);
?
Mit der Musterdatei sollte nun folgendes ausgegeben werden:

                                                           Abbildung 14.4:
                                                           Ausgabe der Channel-Daten
                                                           einer RSS-Quelle

Falls ein Element auf einer Stufe in der Hierarchie mehr als einmal vorkommt,
legt PHP diese Daten in einem Array an. Im Beispiel finden Sie zwar nur ein Ele-
ment channel, darunter aber zwei Element item. Der Zugriff darauf sieht dann
folgendermaßen aus:
Wie üblich kann zum Zugriff auf alle Elemente eines Arrays die Anweisung
foreach zum Einsatz kommen:
?php
$rss = simplexml_load_file('data/rssfeed091.xml');
echo Channel b{$rss-channel-title}/b ;
echo (See a href={$rss-channel-link}{$rss-channel-link}/a);
echo br/br/;



                                                                                  585
XML und Webservices


foreach ($rss-item as $item)
{
   echo {$item-title} br/;
}
?

Listing 14.13: SimpleXMLRss091b.php – Ausgabe mehrerer Elemente

Die folgende Abbildung zeigt, wie die Titel der RSS-Quelle erscheinen:



                                                        Abbildung 14.5:
                                                        Ausgabe aller Elemente einer
                                                        Auflistung

Nach den Elementen sind nun die Attribute an der Reihe. Diese stehen ebenfalls
als Arrayelemente zur Verfügung und können über ihre Namen erreicht werden:

Listing 14.14: SimpleXMLRssVersion.php (Ausschnitt) – Ermittlung der Version aus
dem Attribute version des Stammelements

echo $rss['version'];
Damit kann man in der Praxis schon eine Menge erreichen, denn Sie können nun
XML lesen und mit den bereits bekannten Mitteln in PHP weiterverarbeiten. Das
reicht nicht immer aus, macht aber die ersten Schritte sehr einfach.
Andere XML-Funktionen, wie Kommentare oder Prozessanweisungen, lassen sich
mit dieser Technik freilich nicht nutzen. Sie können diese Entitäten nicht errei-
chen. Essentielle Informationen werden auf diesem Wege jedoch selten übermit-
telt, sodass SimpleXML durchaus seine Daseinsberechtigung hat.


SimpleXML-Methoden
Der Zugriff auf Attribute gestaltet sich bei den Objekten der tiefer liegenden Struk-
tur ebenso. Hier können Sie jederzeit die Arraysyntax ansetzen. Wenn Sie dagegen
eine Auflistung der Attribute benötigen, beispielsweise um mit foreach zugreifen
zu können, nutzen Sie die Methode attributes():




586
SimpleXML


foreach($rss-item[0]-attributes() as $a = $b)
{
   echo $a,'=',$b,n;
}
Der erste Parameter ergibt dabei immer den Namen des Attributes, der zweite den
Inhalt.
Hat man komplexe Strukturen, sind Vereinfachungen hilfreich. Eine Methode
hilft dabei, einen Zweig von Knoten gesondert weiterzuverarbeiten: children().
Zurückgegeben wird ein Objekt, das ähnlich wie das Stammobjekt aufgebaut ist
und nur die Kindelemente enthält.
Selbstverständlich kann mit SimpleXml der Inhalt der Knoten geändert werden.
Um die geänderte Version wieder schreiben zu können, ist die Methode AsXml()
hilfreich. Die Methode gibt den Inhalt als Zeichenkette zurück. Die Speicherung
in einer Datei müssen Sie dagegen selbst organisieren.
Hilfreich ist – für umfassendere XML-Zugriffe – das Laden von Objekten, die
mit den regulären DOM-Erweiterungen erzeugt wurden. Die Methode
simplexml_import_dom dient diesem Zweck. Der Abschnitt über DOM zeigt die
Anwendung.

Komplexere Abfragen mit XPath gestalten
Der Zugriff auf eine tief liegende Ebene oder eine Parametrisierung misslingt mit
dem direkten Objektzugriff meist oder wird so kompliziert, dass die ganze Verein-
fachung, die erreicht wurde, wieder aufgebraucht ist. Will man mehr, kommt
XPath zum Einsatz.
Der folgende Code findet alle Texte, die innerhalb aller title-Element stehen:

Listing 14.15: SimpleXMLXpath.php – Abfrage mit XPath

?php
$rss = simplexml_load_file('data/rssfeed091.xml');
foreach ($rss-xpath('//title') as $title)
{
   echo $title br /;
}
?




                                                                             587
XML und Webservices


Die xpath-Methode durchsucht das SimpleXML-Objekt und gibt ein Array mit
den Fundstellen zurück. Der Parameter enthält die XPath-Anweisung, die recht
komplex werden kann. Die beiden Schrägstriche sagen: »Suche Elemente mit
dem Namen title an jeder beliebigen Stelle im Dokument«. Im Beispiel heißt
das, dass sowohl die Titel des channel als auch der item-Elemente gefunden
werden.



                              Abbildung 14.6:
                              Abfrage mit XPath: Schnell und flexibel

Sollen dagegen nur die title-Elemente gefunden werden, die sich innerhalb
von item befinden, wäre folgende Anweisung einzusetzen:
//item/title
Spätestens hier wäre der Zugriff über das Objektmodell nur noch mit sehr viel Pro-
grammierung zu erreichen und außerdem unglaublich langsam. XPath ist dagegen
sehr schnell.


XML-Namensräume
SimpleXML erledigt den XML-Zugriff leicht und einfach. Das Lesen von RSS 1.0-
Quellen wird so für jeden beherrschbar. Allerdings benutzt RSS 1.0 auch XML-
Namensräume, was die Sache wieder etwas komplizierter werden lässt. Da
Namensräume generell eine herausragende Rolle in XML übernehmen, ist eine
Auseinandersetzung damit unerlässlich.
Der Namensraum eines XML-Elements ist immer durch einen URL definiert. Im
Dokument wird aus Gründen der Lesbarkeit ein Alias benutzt, der dann zu folgen-
den Elementformen führt:
my:title
my: ist dabei der Alias für einen Namensraum. Treffen mehrere XML-Dialekte auf-
einander, kann man diese mit Hilfe der Namensräume auseinander halten. Den
Alias wählt man im Zieldokument so, dass er konfliktfrei aufgelöst werden kann.
Mit Hilfe von Namensräumen können Sie leicht zwischen dem RSS-Element
title und dem HTML-Element mit demselben Namen unterscheiden.




588
SimpleXML


Aus Sicht des XML-Zugriffs mit SimpleXML werden die Dinge nun plötzlich
komplizierter. Denn ein Zugriff über das Objektmodell wird mit $rss-my:title
nicht gelingen. Wird nur -title geschrieben, verfügt der Prozessor jedoch über
keine Informationen, welches title-Element denn nun gemeint ist. Schreibt
man die vollständigen Namensräume mit auf, stehen nun zwei Arten von title-
Elementen zur Auswahl:
{http://www.w3.org/1999/xhtml}:title
{http://purl.org/rss/1.0}:title
Der gesamte Name wird als »qualifizierter Name« (qualified name) bezeichnet.
Der Alias ändert nichts an der Bezeichnung. Wie bereits erwähnt, dient dies ledig-
lich der Lesbarkeit. Es vereinfacht aber auch den Zugriff ein wenig. Es gibt keine
korrekten Aliasnamen im Sinne einer Vorgabe oder Norm, sondern lediglich Vor-
schläge, welche Namen ohne den konfliktfrei gewählt werden sollten. Vergessen
Sie jedoch nicht, dass der Alias primär der Konfliktauflösung dient und deshalb
bewusst Änderungen unterliegt. Typische Aliase für die beiden erwähnten Namen
sind die folgenden:
xhtml:title
rss:title

          Der URL, der den Namensraum definiert, führt nicht dazu, dass die ver-
          arbeitende Software online auf Informationen zugreift. Auch muss die
          verlinkte Seite nicht wirklich existieren. Die Angabe sichert lediglich die
          weltweite Eindeutigkeit und sollte deshalb den Namen der Firmen-
          Domain enthalten.

Um den Umgang von SimpleXML mit Namensräumen kennen zu lernen, wird
die bereits bekannte RSS-Datei mit einem solchen ausgestattet, was der Übergang
zur Version 1.0 erledigt:
?xml version=1.0 encoding=UTF-8?
rdf:RDF
    xmlns:rdf=http://www.w3.org/1999/02/22-rdf-syntax-ns#
    xmlns:dc=http://purl.org/dc/elements/1.1/
    xmlns=http://purl.org/rss/1.0/

channel rdf:about=http://www.php.net/
    titlePHP: Hypertext Preprocessor/title
    linkhttp://www.php.net//link
    descriptionThe PHP scripting language web



                                                                                 589
XML und Webservices


     site/description
/channel
item rdf:about=http://www.php.net/downloads.php
    titlePHP 5.0.0 Beta 3 Released/title
    linkhttp://www.php.net/downloads.php/link
    description
    PHP 5.0 Beta 3 has been released. The third beta of
    PHP is also scheduled to be the last one (barring
    unexpected surprises).
    /description
    dc:date2004-01-02/dc:date
/item
item rdf:about=http://shiflett.org/archive/19
    titlePHP Community Site Project Announced/title
    linkhttp://shiflett.org/archive/19/link
    description
    Members of the PHP community are seeking volunteers to
    help develop the first web site that is created both by
    the community and for
    the community.
    /description
    dc:date2003-12-18/dc:date
/item
/rdf:RDF

Listing 14.16: rssfeed100.xml – RSS-Datei mit Namensräumen
(explizite Aliase rdf und dc)

Dieses Dokument enthält drei Namensräume, davon sind im Kopf zwei explizit
definiert und den Aliasnamen rdf und dc zugeordnet. Die folgende Syntax zeigt,
wie der Alias definiert wird:
xmlns:rdf=http://www.w3.org/1999/02/22-rdf-syntax-ns#
Die Definition wird immer als Attribut xmlns im Wurzelelement des den Namens-
raum benutzenden Dokumentteils geschrieben. Man kann die Definition auch in
jedem einzelnen betroffenen Element wiederholen. Nach dem Doppelpunkt folgt
der gewählte Alias und danach der eigentlichen Namensraum als vollständiger
URL.
Die Elemente rdf:RDF, rdf:about und dc:date zeigen die Anwendung.




590
SimpleXML


          RDF ist die »Mutterdefinition« von RSS und steht für Resource Descrip-
          tion Format. Dies ist eine sehr komplexe Form der Beschreibung von
          verlinkten Dokumenten. Unter http://www.w3.org/RDF/ finden Sie eine
          eingehende technische Beschreibung.

Eine Definition im gezeigten Dokument hat keinen Alias:
xmlns=http://purl.org/rss/1.0/
Das ist der Standardnamensraum, der für alle Elemente gilt, die keinen Alias auf-
weisen (der dritte im Bunde der oben erwähnten). RSS 1.0 verlangt diese Angabe,
während die erste öffentliche Version 0.91 ausdrücklich keinen Namensraum ver-
langt, was freilich zu praktischen Problemen beim Datenaustausch führen kann.
Mehr dazu finden Sie im Abschnitt »Ein konkretes XML-Format: RSS« ab Seite
568.


Namensräume mit SimpleXML verwenden
Die Suche von Elementen mit bestimmten Namensräumen verlangt nach einem
neuen Satz von Methoden, über den SimpleXML nicht verfügt. In der Praxis ver-
sagt die Anwendung dennoch nicht, denn SimpleXML ignoriert die Namens-
räume einfach. Damit sind auch Dateien mit Namensraumaliasen lesbar,
zumindest solange die interne Vereinfachung nicht zu Namenskonflikten führt.


XML Namensräume und XPath
SimpleXML ignoriert die Namensräume jedoch nicht völlig. Es existiert lediglich
keine dedizierte Syntax für den Zugriff. Mit XPath sieht es anders aus. Hier kann
der Namensraum bei der Abfrage durchaus angegeben werden. Freilich ist dies
keine Leistung von SimpleXML, sondern vom intern verwendeten XPath-Modul.
Damit die Übergabe der Namensräume auch mit dem Standardnamensraum
funktioniert, muss wenigstens dieser registriert werden. Dann funktioniert auch die
Auflösung der übrigen Namensräume, wie es das nächste Beispiel weiter unten
zeigt.
Um alle Titel aus den Elementen rss:title zu finden, muss der Namensraum-
Alias registriert werden. Der vollständige Namensraum kann beispielsweise folgen-
dermaßen aussehen:
http://purl.org/rss/1.0/




                                                                               591
XML und Webservices


Der Alias ist frei wählbar, aber der RSS-Standard empfiehlt für das vorliegende Bei-
spiel die Verwendung von »rss«. Die XPath-Abfrage sollte dann folgendermaßen
aussehen:
//rss:item/rss:title
Leider gibt es keinen Weg, in XPath einen Standardnamensraum zu definieren,
wie es in XML selbst möglich ist. Das ist in der Praxis aber nur selten problema-
tisch. Wichtig ist es, an den Namensraum bei der Abfrage zu denken, auch wenn
im Dokument ein Standardnamensraum verwendet wird und der Alias nicht in
allen Tags auftaucht.
Das folgende Listing zeigt die fertige Lösung:

Listing 14.17: simplens.php – Abfrage eines RSS-Feed mit Namensräumen

?php
$s = simplexml_load_file('data/rssfeed100.xml');
$s-registerXPathNamespace('rss', 'http://purl.org/rss/1.0/');
$titles = $s-xpath('//rss:item[         
               starts-with(dc:date, 2004-01-)]/rss:title');
foreach ($titles as $title)
{
    print $titlen;
}
?
Die erste Zeile entspricht den letzten Beispielen. Dann folgt die Registrierung des
Namensraums:
$s-registerXPathNamespace('rss', 'http://purl.org/rss/1.0/');
Die Abfrage selbst greift außerdem demonstrativ auf XSLT-Funktionen zurück,
um die Ergebnisse einzuschränken. Die Funktion starts-with prüft, ob eine Zei-
chenkette mit einer bestimmten Zeichenfolge beginnt. Der Alias dc deutet darauf
hin, dass die Daten der Namensraumdefinition aus den Dublin Core Metadata
entstammen, die Datumswerte spezifiziert.

                                  Abbildung 14.7:
                                  Auswahl des Titels aus dem RSS-Feed




592
SOAP-Webservices


Andere Funktionen
Wie bereits erwähnt, ist es möglich, Attribute und Elementinhalte auszutauschen,
indem ein neuer Wert zugewiesen wird. Der geänderte Inhalt des Dokuments
kann komplett als Zeichenkette zurückgegeben werden, wozu die Methode
AsXml() auf den Wurzelknoten angewendet wird.
Insgesamt ist SimpleXML ein feines und kleines Modul, das den Zugriff auf XML
stark vereinfacht und ohne tief gehende Kenntnisse leicht bedienbar ist. Große
Projekte können davon weniger profitieren, weil die Einschränkungen program-
miertechnisch kaum zum umschiffen sind. Hier können Sie jedoch mit dem zwei-
ten XML-Modul, der libxml2, nahtlos ansetzen.



14.4 SOAP-Webservices
Webservices sind bereits seit einiger Zeit heftig umworben und immer wieder
Titelthema von Entwicklerkonferenzen und Produktankündigungen. Eine breite
Nutzung ist indes nicht auszumachen. Die ersten Anfänge sind trotzdem ermuti-
gend und die technischen Vorteile sind so gravierend, dass eine Beschäftigung mit
den Möglichkeiten unbedingt lohnt.


Grundlagen
PHP5 stellt mit dem SOAP-Erweiterungen ein integriertes Paket zur Verfügung,
mit dem sich Webservices anbieten und nutzen lassen. Damit ist der Einsatz derart
einfach geworden, dass man sich deutlich mehr Gedanken um mögliche Anwen-
dungen als um deren Realisierung machen muss.


Gedanken
Webservices sind im Grund genommen nur entfernte Methodenaufrufe. Auf
irgendeinem Server läuft ein Programm, das öffentliche Methoden zur Verfügung
stellt. Werden diese – mit oder ohne Parameter – aufgerufen, gibt eine spezielle
Methode Daten zurück oder führt Aktionen aus. Das Ganze nennt man dann
einen Webservice (oder Web-Dienst oder XML-Webdienst), wenn sich der Anbie-
ter des Dienstes an bestimmte Standards hält. Das ist sehr sinnvoll, denn damit öff-



                                                                                 593
XML und Webservices


net er sich einer Palette von Clients, die denselben Standard benutzen.
Prominente Beispiele solcher Anbieter sind Amazon und Google, deren Dienste
»Bücher kaufen« und »Suchen« so anderen Servern zur Verfügung gestellt wer-
den. Dabei umfasst die Definition »Webservice« bereits sehr viel, lediglich die
Ausformulierung der Abfragen muss bekannt gemacht werden. Da entsprechende
Clients für alle Betriebssysteme und Programmierumgebungen zur Verfügung ste-
hen, muss sich der Anbieter keine Gedanken über die Nutzbarkeit seines Angebots
machen. Entsprechend zahlreich sollten Anbieter und Nutzer sein – theoretisch.
In der Praxis setzen sich Webservices recht zögerlich durch. Vermutlich hat dies
keine technischen Ursachen, sondern deutet eher auf mentale Hemmnisse hin.
Webservices sind primär für die Server-Server-Kommunikation geschaffen. Auch
bei der Abwicklung von automatisierten Abfragen in der Server-Client-Kommuni-
kation sind Webservices stark. Das massiv clientgeprägte Web mit einem nicht
Webservice-tauglichen Browser steht dem natürlich entgegen. Es existieren
schlicht keine Strukturen in Unternehmen und deren IT-Abteilungen, in der Kate-
gorie »Dienst« zu denken. Dabei ist eigentlich alles ganz einfach.


Prinzipien
Die Kommunikation zwischen Server und Server oder zwischen Client und Server
benutzt zwei bereits etablierte Techniken:
1. HTTP oder SMTP
      HTTP und SMTP sind die möglichen Transportprotokolle, die die Daten
      übertragen. Meist kommt nur HTTP zum Einsatz. Das bedeutet, dass Aufrufe
      von Webservices problemlos Firewalls überwinden. Der Anbieter muss seine
      massiven Sicherheitseinrichtungen also kein bisschen öffnen, was äußerst
      attraktiv ist.
      SMTP ist kaum in Benutzung, würde jedoch auch interessant sein, weil es eine
      asynchrone Kommunikation zulässt. Man stelle sich vor, ein Client, beispiels-
      weise ein Handy, sendet eine Anfrage an einen solchen Dienst über die MMS-
      Funktion. Der Dienstanbieter setzt dies auf SMTP um und der Webservice-
      Anbieter beantwortet die Anforderung auf gleichem Wege. Der Dienstanbieter
      erstellt aus der Antwort wiederum eine MMS und schon kann man billig inter-
      aktiv kommunizieren – ohne wie bei WAP ständig online zu sein. Liest hier
      eigentlich jemand von Vodafone mit?




594
SOAP-Webservices


2. XML
    Die Nachrichten selbst – also die Parameterdaten einer Anfrage und die Ergeb-
    nisse – werden mittels SOAP (Simple Object Access Protocol) kodiert. Dies ist
    ein XML-Dialekt. Damit ist die Weiterverarbeitung schnell und einfach, denn
    XML-Parser stehen immer und überall zur Verfügung. Die Dienstinformatio-
    nen selbst, also die Namen der Methoden und die Datentypen der Parameter
    werden über eine Informationsdatei im Format WSDL (Web Services Descrip-
    tion Language) angeboten, wobei es sich hier selbstverständlich wiederum um
    XML handelt.
Auf der Grundlage der Basistechnologien HTTP und XML ist es für Hersteller
nicht schwer, Webservices in Programmiersprachen und Betriebssysteme zu inte-
grieren. Jeder neuere Website, die mit PHP5, Java oder ASP.NET in den letzten
drei bis fünf Jahren entstanden ist, steht die Technologie praktisch zur Verfügung.
Technisch läuft das Ganze dann so ab, dass zuerst der Dienst selbst programmiert
wird, meist eine Klasse mit einer Anzahl öffentlicher Methoden, die den Benut-
zern bereitgestellt werden. Zu diesem Angebot wird dann die Beschreibungsdatei
im WSDL-Format erstellt. Dann wird der SOAP-Server der Programmierumge-
bung damit beauftragt, Anfragen anzunehmen, an die Methoden weiterzuleiten
und deren Beantwortung zu überwachen. Die Außenkommunikation wird, wenn
sie über HTTP abgewickelt wird, vom Webserver erledigt, der selbst nichts von
SOAP oder XML wissen muss. Für ihn sind es lediglich HTTP-Anforderungen,
deren Körper (Body) statt HTML-Seiten SOAP-Nachrichten enthält.


Technische Grundlagen
Um nicht völlig im Dunkeln zu tappen, was den technischen Ablauf betrifft, soll-
ten Sie sich kurz mit den technischen Grundlagen vertraut machen. Die hier vor-
liegende Beschreibung ist stark vereinfacht, reicht aber für das Verständnis der
ersten Beispiele aus.
An allererster Stelle steht immer SOAP. SOAP realisiert den eigentlichen Funk-
tionsaufruf. Die SOAP-Nachricht besteht aus drei Teilen:
í   Umschlag (SOAP Envelope)
í   Kopf (SOAP Header)
í   Inhalt (SOAP Body)




                                                                                595
XML und Webservices


Der Inhalt kann in einer Nachricht in mehrere Blöcke verteilt sein. In XML sieht
das Ganze dann folgendermaßen aus:

Listing 14.18: Rumpf einer SOAP-Nachricht (ohne Daten)

SOAP-ENV:Envelope
      xmlns:SOAP-ENV=?http://schemas.xmlsoap.org/soap/envelope/?
  SOAP-ENV:Header
  /SOAP-ENV:Header
  SOAP-ENV:Body
  /SOAP-ENV:Body
/SOAP-ENV:Envelope
Zusätzlich zur eigenen Namensraumdefinition werden meist noch XML-Schema
für die Datentypen und Schema-Instance vereinbart. Als Namensraumaliase kom-
men die Abkürzungen »xsd« und »xsi« zum Einsatz.
Was auch immer im Inhalt der SOAP-Body-Tags steht, hängt bereits von der kon-
kreten Anwendung ab. Der Aufbau ist zwar sehr streng, die Benennung der Tags
und die Struktur variieren jedoch. Damit sich die beiden Kommunikationspartner
über diese Struktur einig werden – und zwar automatisch – kommt WSDL zum
Einsatz.
WSDL steht für Web Services Description Language. Dies ist eine vom Anbieter zu
erstellende XML-Datei, die auf Anforderung des Clients gesendet wird. Sie muss
genaue Informationen über folgende Dienstmerkmale enthalten:
í     Kommunikationsport, also den URL, unter dem der Dienst bereitsteht
í     Bindung, also die Verknüpfung zwischen Dienst und Ein- und Ausgabeopera-
      tionen
í     Porttypen, eine Definition der Kommunikationsrichtung der zulässigen Opera-
      tionen
í     Nachrichten, die Namen der von den Ports angebotenen Methoden und deren
      Parameter- und Rückgabedefinitionen
í     Datentypen, also die konkret von einem Parameter oder Rückgabewert benutz-
      ten Datentypen
Bei den Datentypen wird zur Beschreibung XML-Schema benutzt. Zulässig sind
sowohl die in XML-Schema definierten elementaren Typen (string, integer) als
auch so genannte Komplextypen, die benutzerdefinierte Datensammlungen
beschreiben.


596
SOAP-Webservices


Mit diesen Informationen ist der Client nun in der Lage, die vom Server bereitge-
stellten Methoden so umzusetzen, dass dem Programmierer tatsächlich eine Klasse
mit den ursprünglich definierten Methoden vorgehalten wird. Das ist die eigentli-
che Faszination: Während auf der einen Seite der Dienst mit PHP und den SOAP-
Erweiterungen benutzt wird, als Betriebssystem Unix läuft und als Datenbank
MySQL benutzt wird, steht dem Client möglicherweise ein Windows XP und ein
in Access geschriebene Benutzeroberfläche zur Verfügung. Und wenn der Server
später auf das Gespann Java/Sun Solaris wechselt, sollte der Client davon nichts
mitbekommen. Das ist die schöne neue Welt der Webservices.
Alle Theorie ist jedoch langweilig, wenn man nichts praktisch unternimmt. Des-
halb schließt dieses Kapitel mit zwei kleinen Projekten ab. Eines konsumiert einen
im Internet öffentlich angebotenen Webservice, das andere bietet selbst einen sol-
chen an.


Der Amazon-Webservice: ein Überblick
Dieser Abschnitt stellt die Amazon-Webservices kurz vor und gibt einen ersten
Einblick in mögliche Anwendungen. Dies erhebt keinerlei Anspruch auf Vollstän-
digkeit.


Die Definition der Dienste: WSDL
Die folgende WSDL-Datei stellt den Zustand und das Angebot des Dienstes Mitte
2004 dar. Auf die Datei wird im folgenden Bezug genommen. Man kann ihr
sowohl die Struktur der aufrufbaren Funktionen als auch die Datentypen der Para-
meter und Rückgabewerte entnehmen:

Listing 14.19: WSDL-Datei des Amazon-Webservices (stark gekürzt)

wsdl:definitions xmlns:typens=http://soap.amazon.com xmlns:xsd=http://
www.w3.org/2001/XMLSchema xmlns:soap=http://schemas.xmlsoap.org/wsdl/
soap/ xmlns:soapenc=http://schemas.xmlsoap.org/soap/encoding/
xmlns:wsdl=http://schemas.xmlsoap.org/wsdl/ xmlns=http://
schemas.xmlsoap.org/wsdl/ targetNamespace=http://soap.amazon.com
name=AmazonSearch
  wsdl:types
    xsd:schema xmlns=
                xmlns:xsd=http://www.w3.org/2001/XMLSchema



                                                                                597
XML und Webservices


                targetNamespace=http://soap.amazon.com
      xsd:complexType name=ProductInfo
        xsd:all
          xsd:element name=TotalResults type=xsd:string
                       minOccurs=0/
          xsd:element name=TotalPages type=xsd:string
                       minOccurs=0/
          xsd:element name=ListName type=xsd:string
                       minOccurs=0/
          xsd:element name=Details type=typens:DetailsArray
                       minOccurs=0/
        /xsd:all
      /xsd:complexType
      xsd:complexType name=DetailsArray
        xsd:complexContent
          xsd:restriction base=soapenc:Array
            xsd:attribute ref=soapenc:arrayType
                           wsdl:arrayType=typens:Details[]/
          /xsd:restriction
        /xsd:complexContent
      /xsd:complexType
      xsd:complexType name=Details
        xsd:all
          xsd:element name=Url type=xsd:string minOccurs=0/
          xsd:element name=Asin type=xsd:string minOccurs=0/
          xsd:element name=ProductName type=xsd:string
                       minOccurs=0/
          xsd:element name=Catalog type=xsd:string
                       minOccurs=0/
          xsd:element name=KeyPhrases
                       type=typens:KeyPhraseArray minOccurs=0/
          xsd:element name=Artists type=typens:ArtistArray
                       minOccurs=0/
          xsd:element name=Authors type=typens:AuthorArray
                       minOccurs=0/
          xsd:element name=Mpn type=xsd:string minOccurs=0/
          xsd:element name=Starring type=typens:StarringArray
                       minOccurs=0/
          xsd:element name=Directors type=typens:DirectorArray
                       minOccurs=0/
          xsd:element name=TheatricalReleaseDate
                       type=xsd:string minOccurs=0/



598
SOAP-Webservices


xsd:element name=ReleaseDate type=xsd:string
             minOccurs=0/
xsd:element name=Manufacturer type=xsd:string
             minOccurs=0/
xsd:element name=Distributor type=xsd:string
             minOccurs=0/
xsd:element name=ImageUrlSmall type=xsd:string
             minOccurs=0/
xsd:element name=ImageUrlMedium type=xsd:string
             minOccurs=0/
xsd:element name=ImageUrlLarge type=xsd:string
             minOccurs=0/
xsd:element name=ListPrice type=xsd:string
             minOccurs=0/
xsd:element name=OurPrice type=xsd:string
             minOccurs=0/
xsd:element name=UsedPrice type=xsd:string
             minOccurs=0/
xsd:element name=RefurbishedPrice type=xsd:string
             minOccurs=0/
xsd:element name=CollectiblePrice type=xsd:string
             minOccurs=0/
xsd:element name=ThirdPartyNewPrice type=xsd:string
             minOccurs=0/
xsd:element name=NumberOfOfferings type=xsd:string
             minOccurs=0/
xsd:element name=ThirdPartyNewCount type=xsd:string
             minOccurs=0/
xsd:element name=UsedCount type=xsd:string
             minOccurs=0/
xsd:element name=CollectibleCount type=xsd:string
             minOccurs=0/
xsd:element name=RefurbishedCount type=xsd:string
             minOccurs=0/
xsd:element name=ThirdPartyProductInfo
             type=typens:ThirdPartyProductInfo
             minOccurs=0/
xsd:element name=SalesRank type=xsd:string
             minOccurs=0/
xsd:element name=BrowseList
             type=typens:BrowseNodeArray minOccurs=0/
xsd:element name=Media type=xsd:string



                                                               599
XML und Webservices


                   minOccurs=0/
      xsd:element name=ReadingLevel type=xsd:string
                   minOccurs=0/
      xsd:element name=NumberOfPages type=xsd:string
                   minOccurs=0/
      xsd:element name=NumberOfIssues type=xsd:string
                   minOccurs=0/
      xsd:element name=IssuesPerYear type=xsd:string
                   minOccurs=0/
      xsd:element name=SubscriptionLength type=xsd:string
                   minOccurs=0/
      xsd:element name=DeweyNumber type=xsd:string
                   minOccurs=0/
      xsd:element name=RunningTime type=xsd:string
                   minOccurs=0/
      xsd:element name=Publisher type=xsd:string
                   minOccurs=0/
      xsd:element name=NumMedia type=xsd:string
                   minOccurs=0/
      xsd:element name=Isbn type=xsd:string minOccurs=0/
      xsd:element name=Features type=typens:FeaturesArray
                   minOccurs=0/
      xsd:element name=MpaaRating type=xsd:string
                   minOccurs=0/
      xsd:element name=EsrbRating type=xsd:string
                   minOccurs=0/
      xsd:element name=AgeGroup type=xsd:string
                   minOccurs=0/
      xsd:element name=Availability type=xsd:string
                   minOccurs=0/
      xsd:element name=Upc type=xsd:string minOccurs=0/
      xsd:element name=Tracks type=typens:TrackArray
                   minOccurs=0/
      xsd:element name=Accessories
                   type=typens:AccessoryArray minOccurs=0/
      xsd:element name=Platforms type=typens:PlatformArray
                   minOccurs=0/
      xsd:element name=Encoding type=xsd:string
                   minOccurs=0/
      xsd:element name=Reviews type=typens:Reviews
                   minOccurs=0/
      xsd:element name=SimilarProducts



600
SOAP-Webservices


                     type=typens:SimilarProductsArray
                     minOccurs=0/
        xsd:element name=Lists type=typens:ListArray
                     minOccurs=0/
        xsd:element name=Status type=xsd:string
                     minOccurs=0/
      /xsd:all
    /xsd:complexType
    xsd:complexType name=AsinRequest
      xsd:all
        xsd:element name=asin type=xsd:string/
        xsd:element name=tag type=xsd:string/
        xsd:element name=type type=xsd:string/
        xsd:element name=devtag type=xsd:string/
        xsd:element name=offer type=xsd:string
                     minOccurs=0/
        xsd:element name=offerpage type=xsd:string
                     minOccurs=0/
        xsd:element name=locale type=xsd:string
                     minOccurs=0/
      /xsd:all
    /xsd:complexType
  /xsd:schema
/wsdl:types
message name=AsinSearchRequest
  part name=AsinSearchRequest type=typens:AsinRequest/
/message
message name=AsinSearchResponse
  part name=return type=typens:ProductInfo/
/message
portType name=AmazonSearchPort
  operation name=AsinSearchRequest
    input message=typens:AsinSearchRequest/
    output message=typens:AsinSearchResponse/
  /operation
/portType
binding name=AmazonSearchBinding 
         type=typens:AmazonSearchPort
  soap:binding style=rpc 
                transport=http://schemas.xmlsoap.org/soap/http/
  !-- Binding for Amazon Web APIs - RPC, SOAP over HTTP --
  operation name=AsinSearchRequest



                                                                       601
XML und Webservices


      soap:operation soapAction=http://soap.amazon.com/
      input
        soap:body use=encoded    
         encodingStyle=http://schemas.xmlsoap.org/soap/encoding/       
          namespace=http://soap.amazon.com/
      /input
      output
        soap:body use=encoded    
         encodingStyle=http://schemas.xmlsoap.org/soap/encoding/       
         namespace=http://soap.amazon.com/
      /output
    /operation
  /binding
  service name=AmazonSearchService
    !-- Endpoint for Amazon Web APIs --
    port name=AmazonSearchPort
          binding=typens:AmazonSearchBinding
      soap:address location=http://soap.amazon.com/onca/soap2/
    /port
  /service
/wsdl:definitions
Lesen Sie diese Datei am besten von unten nach oben. In dieser Reihenfolge
bauen nämlich die Informationen aufeinander auf. Beachten Sie auch, dass die
Daten, um hier überhaupt druckbar zu sein, sehr stark gekürzt wurden (auch wenn
es nicht so aussieht). Praktisch sind nur die Informationen enthalten, die im Fol-
genden direkt benötigt werden – für die ISBN-Suche mit AsinSearchRequest. Aber
wie liest man nun solch eine Datei?
Alles beginnt bei der Deklaration eines Dienstes und der Festlegung seines
Namens:
service name=AmazonSearchService
Der Dienst definiert einen Port und eine Bindung:
port name=AmazonSearchPort binding=typens:AmazonSearchBinding
Die Bindung definiert die Namen der Funktionen (Operation genannt) und deren
Kodierung und Namensräume. Für AsinSearchRequest sieht das folgendermaßen
aus:
operation name=AsinSearchRequest




602
SOAP-Webservices


Der Port-Typ definiert dieselbe Funktion und nennt dazu die konkrete Ein- und
Ausgabefunktion, die getrennt definiert werden:
portType name=AmazonSearchPort
Der SOAP-Client baut aus diesen Angaben dann einen normalen PHP-Funktions-
aufruf zusammen. Der Port nennt für die in der Bindung vereinbarte Operation
die Namen der Funktionen für beide Transportrichtungen, input ist die Abfrage,
output die Rückgabe:
operation name=AsinSearchRequest
   input message=typens:AsinSearchRequest/
   output message=typens:AsinSearchResponse/
/operation
Diese Funktionen wiederum erscheinen als Nachrichten und definieren die
Datentypen, die benutzt werden. Der Typ AsinRequest wird für die Anfrage
benutzt:
message name=AsinSearchRequest
  part name=AsinSearchRequest type=typens:AsinRequest/
/message
Der Typ ProductInfo wird bei der Antwort verwendet:
message name=AsinSearchResponse
  part name=return type=typens:ProductInfo/
/message
Beides sind offensichtlich keine elementaren Typen, weshalb sich weiter oben die
Typdefinition im XML-Schema-Stil anschließt. Komplexe Typen setzen sich,
gegebenenfalls über mehrere Schritte, aus einfachen Typen zusammen. Die Defi-
nition eines komplexen Typs wird folgendermaßen eingeleitet:
xsd:complexType name=AsinRequest
Darunter folgen entweder weitere komplexe Typen oder – in letzter Konsequenz –
elementare:
xsd:element name=asin type=xsd:string /
Erscheint der Zusatz minOccurs=0, ist das Feld optional:
xsd:element name=locale type=xsd:string minOccurs=0 /
Freilich kann hier auch eine bestimmte Mindest- oder Maximalanzahl festgelegt
werden. XML-Schema erlaubt eine sehr feine Definition von Datentypen.




                                                                                603
XML und Webservices


Ein Datentyp für die Antwort ProductInfo, Details, enthält beispielsweise einen
Verweis auf DetailsArray:
xsd:element name=Details type=typens:DetailsArray        
             minOccurs=0 /
Dieses ist als SOAP-Array (Achtung! Basisdatentyp) definiert, das Elemente des
Typs Details (Achtung! benutzerdefinierter Komplextyp) enthält:
xsd:attribute ref=soapenc:arrayType      
               wsdl:arrayType=typens:Details[] /
Details seinerseits enthält nun nur noch Basistypen (wie string), wie der folgende
Ausschnitt zeigt:
xsd:element name=Asin type=xsd:string minOccurs=0 /
Es können jedoch auch weitere komplexe Typen folgen, die andernorts in der
WSDL-Datei definiert sind (möglicherweise aber nicht abgedruckt, weil sonst das
halbe Buch voll WSDL-Code wäre):
xsd:element name=Authors type=typens:AuthorArray        
             minOccurs=0 /
Mit diesen Informationen ist der SOAP-Client nun in der Lage, die Daten für eine
Anfrage zu erstellen und aus der Antwort die Daten wieder zu entnehmen. Eine
korrekte Anfrage, wie sie das nachfolgend vorgestellte Beispiel erzeugt, sieht fol-
gendermaßen aus:

Listing 14.20: Anfrage an Amazon: Aufruf der Funktion AsinSearchRequest

?xml version=1.0 encoding=UTF-8?
SOAP-ENV:Envelope
 xmlns:SOAP-ENV=http://schemas.xmlsoap.org/soap/envelope/
 xmlns:ns1=http://soap.amazon.com
 xmlns:xsd=http://www.w3.org/2001/XMLSchema
 xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
 xmlns:SOAP-ENC=http://schemas.xmlsoap.org/soap/encoding/
 SOAP-ENV:encodingStyle=http://schemas.xmlsoap.org/soap/encoding/
 SOAP-ENV:Body
  ns1:AsinSearchRequest
    AsinSearchRequest xsi:type=ns1:AsinRequest
      asin xsi:type=xsd:string3827264553/asin
      tag xsi:type=xsd:stringactiveserverpa0e/tag
      type xsi:type=xsd:stringheavy/type
      devtag xsi:type=xsd:stringXXXXXXXXXXX/devtag


604
SOAP-Webservices


      locale xsi:type=xsd:stringde/locale
    /AsinSearchRequest
  /ns1:AsinSearchRequest
 /SOAP-ENV:Body
/SOAP-ENV:Envelope
Man kann hier sehr gut erkennen, wie im Body der Nachricht die Anfrage getreu
der Datentyp-Definition aufgebaut ist. Erkennbar ist aber auch, dass mehr als 50%
der Daten keine realen Daten sind. Der so genannte Overhead einer SOAP-Ver-
bindung ist bei vielen kurzen Anfragen erheblich.
Etwas umfassender fällt die Antwort aus (hier die »lite«-Version, ohne Kundenre-
zensionen):

Listing 14.21: Eine SOAP-Antwort (auf exakt die zuvor gezeigte Anfrage)

?xml version=1.0 encoding=UTF-8?
SOAP-ENV:Envelope
 xmlns:SOAP-ENC=http://schemas.xmlsoap.org/soap/encoding/
 SOAP-ENV:encodingStyle=http://schemas.xmlsoap.org/soap/encoding/
 xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
 xmlns:SOAP-ENV=http://schemas.xmlsoap.org/soap/envelope/
 xmlns:xsd=http://www.w3.org/2001/XMLSchema
 xmlns:amazon=http://soap.amazon.com
 SOAP-ENV:Body
  namesp57:AsinSearchRequestResponse
            xmlns:namesp57=http://soap.amazon.com
   return xsi:type=amazon:ProductInfo
    Details SOAP-ENC:arrayType=amazon:Details[1]
             xsi:type=SOAP-ENC:Array
     Details xsi:type=amazon:Details
      Url xsi:type=xsd:string
       http://www.amazon.de/exec/obidos/ASIN/3827264553/
       activeserverpa0e?dev-t=D3D53F5TPPE230%26
       camp=2025%26link_code=sp1
      /Url
      Asin xsi:type=xsd:string3827264553/Asin
      ProductName xsi:type=xsd:string
       .NET Windows Forms in 21 Tagen . Oberflächen programmieren
      /ProductName
      Catalog xsi:type=xsd:stringBook/Catalog
      Authors SOAP-ENC:arrayType=xsd:string[1]
               xsi:type=SOAP-ENC:Array



                                                                                  605
XML und Webservices


       Author xsi:type=xsd:stringChris Payne/Author
      /Authors
      ReleaseDate xsi:type=xsd:string15. Januar 2003
      /ReleaseDate
      Manufacturer xsi:type=xsd:stringMarkt+Technik
      /Manufacturer
      ImageUrlSmall xsi:type=xsd:string
     http://images-eu.amazon.com/images/P/3827264553.03.THUMBZZZ.jpg
      /ImageUrlSmall
      ImageUrlMedium xsi:type=xsd:string
     http://images-eu.amazon.com/images/P/3827264553.03.MZZZZZZZ.jpg
      /ImageUrlMedium
      ImageUrlLarge xsi:type=xsd:string
     http://images-eu.amazon.com/images/P/3827264553.03.LZZZZZZZ.jpg
      /ImageUrlLarge
      ListPrice xsi:type=xsd:stringEUR 46,68/ListPrice
      OurPrice xsi:type=xsd:stringEUR 49,95/OurPrice
      UsedPrice xsi:type=xsd:stringEUR 12,70/UsedPrice
     /Details
    /Details
   /return
  /namesp57:AsinSearchRequestResponse
 /SOAP-ENV:Body
/SOAP-ENV:Envelope
Man kann gut erkennen, dass XML ideal für die Übertragung komplexer hierarchi-
scher Daten geeignet ist. Die Rückübertragung nach PHP ist nun wieder Sache
des SOAP-Clients. Dieser erstellt übrigens ein Objekt daraus. Angeschaut mit
var_dump sieht es folgendermaßen aus (wieder exakt dieselbe Anfrage).
Der Zugriff auf die Daten ist nun recht einfach. Man muss lediglich wissen, wo die
Daten die man benötigt, in der Hierarchie stehen. Man muss außerdem natürlich
auf Arrays, Indizes usw. achten, um gegebenenfalls mit Schleifen arbeiten zu kön-
nen. Um das zu erfahren, ist jedoch das Lesen der Rohdaten und der WSDL-
Dateien erforderlich, falls sich niemand die Mühe gemacht hat, eine umfassende
verbale Beschreibung mitzuliefern.




606
SOAP-Webservices




                                              Abbildung 14.8:
                                              Das vom SOAP-Client aus der zuvor
                                              gezeigten Antwort erzeugte Objekt



Webservices konsumieren
Um einen Webservice zu konsumieren, benötigen Sie zuerst einen solchen Dienst
und dessen Dienstbeschreibung. Steht das bereit, erledigen PHP5 und dessen
SOAP-Erweiterungen die ganze Arbeit.
Als Beispiel soll die im letzten Kapitel erstellte Bibliotheks-Anwendung erweitert
werden. Das Erfassen von möglicherweise hunderten Büchern ist recht mühevoll.
Wenn der Server ohnehin online ist, bietet es sich an, die Daten von einem Buch-
händler zu beschaffen. Dankbares Opfer wird in diesem Fall Amazon, da dort ein
entsprechender Dienst angeboten wird.


Die Daten beschaffen
Der Start beginnt auf der folgenden Website (ab hier in Englisch):
http://www.amazon.com/webservices
Melden Sie sich zuerst bei Amazon an, um die kostenlose Zugangsberechtigung
zu erhalten. Dazu sind lediglich eine E-Mail-Adresse und ein Kennwort erforder-
lich, das selbst gewählt werden kann. Optional laden Sie dort zuerst des SDK (Ent-
wicklerpaket) herunter. Das Paket hat den Namen kit.zip und ist auch auf der CD
zum Buch unter /Bibliothek/SOAP zu finden (Version vom August 2004). Das


                                                                               607
XML und Webservices


SDK enthält Beispielprogramme, Hilfen und Hinweise, auch für PHP. Das enthal-
tene Programm nutzt jedoch die für PHP 4 entwickelte Version, die auf dem
PEAR-Paket NuSOAP basiert. Die in diesem Buch gezeigte Version wurde dage-
gen vom Autor explizit für PHP5 geschrieben und soweit vereinfacht, dass ein
schneller Einstieg gelingt.

Versuchsaufbau und Vorbereitung von PHP5
Die SOAP-Erweiterung in PHP5 steht unter Windows als php_soap.dll zur Verfü-
gung, deren Aktivierung wie üblich in der php.ini erfolgt. Unter Linux wird PHP5
mit dem Schalter --with-soap kompiliert. Ein Blick in die Anzeige der Funktion
phpinfo zeigt, ob es geklappt hat:




                                                               Abbildung 14.9:
                                                               Die SOAP-Erweiterun-
                                                               gen wurden erfolgreich
                                                               aktiviert

Dann ist noch zu überprüfen, ob die Konfigurationsparameter in der Datei php.ini ein-
getragen sind. Wenn der folgende Abschnitt nicht zu finden ist, fügen Sie ihn hinzu:
[soap]
soap.wsdl_cache_enabled=1
soap.wsdl_cache_dir=c:windowstemp
soap.wsdl_cache_ttl=86400
Achten Sie auf den Pfad zum Verzeichnis temporärer Daten (das Beispiel gilt für
Windows).

Ziel des SOAP-Projekts
Ziel der Anwendung ist die Vereinfachung der Datenerfassung bei der Aufnahme
von Büchern in die im vorigen Kapitel vorgestellte Bibliotheksverwaltung. Dabei
soll die Angabe der ISBN genügen, alle übrigen Daten sollen aus der Amazon-
Datenbank geholt werden. Dazu gehört beispielsweise auch ein Bild des Buches,
was sonst sicher nur mühevoll zu beschaffen wäre. Wie das am Ende aussehen soll,
zeigt die Abbildung14.10.



608
SOAP-Webservices




                                                                   Abbildung 14.10:
                                                                   Erfassung von
                                                                   Büchern: Alle
                                                                   Daten außer der
                                                                   ISBN liefert der
                                                                   Webservice

Dies spart vor allem viel Zeit bei der Erfassung, vermeidet aber auch lästige Tipp-
fehler. Die hier benutzten Daten stellen nur einen kleinen Teil der Möglichkeiten
dar, die Amazon bietet.

Erweiterung der Anwendung
Die Erweiterung der Anwendung umfasst nur zwei Schritte:
1. Aufbau eines Moduls, das den Webservice abfragt
2. Erweiterung der bisherigen Bibliotheksverwaltung
Das Modul wird natürlich PHP5-typisch als Klasse implementiert. Sie kann dann
auch von anderen Anwendungen benutzt werden. Diese ist allerdings – im Gegen-
satz zu vielen fertigen Lösungen im Internet – bewusst minimalistisch program-
miert worden, um die mindestens notwendigen Funktionen demonstrieren zu
können, ohne dabei erschlagend viele Code zu benutzen.




                                                                                609
XML und Webservices


Listing 14.22: soapclient.inc.php – Eine primitive Klasse zur Benutzung des Amazon-
Webservice

?php
// Webservice
class AmazonSearch
{
  private $Client = null;
  const LANG       = 'de';
  const MODE       = 'heavy';
  const TAG        = 'activeserverpa0e';
  const WSDL_FILE =
        'http://soap.amazon.com/schemas2/AmazonWebServices.wsdl';
  const TOKEN      = 'IHR_AMAZON_TOKEN';
  public function __construct()
  {
    try
    {
        $this-Client = new SoapClient(self::WSDL_FILE,
                                        array('trace' = 1));
    }
    catch (SoapFault $ex)
    {
        die(Fehler beim Zugriff:nbsp;{$ex-message()});
    }
  }

  public function SearchISBN($Keyword)
  {
    $params = array(
        'asin' = $Keyword,
        'tag'    = self::TAG,
        'type' = self::MODE,
        'locale' = self::LANG,
        'devtag' = self::TOKEN);
    try
    {
         $result = $this-Client-AsinSearchRequest($params);
    }
    catch (Exception $ex)
    {
         die(($this-Client-__getLastResponse()));



610
SOAP-Webservices


      }
      return isset($result) ? $result : '';
  }
}
?
Die Klasse definiert einige Informationen, die der Dienst benötigt, als Konstanten:
í     const LANG = 'de'
      Die Sprache des Katalogs, der abgefragt werden soll. Wenn Sie deutsche
      Bücher bearbeiten, muss die Sprache »de« sein.
í     const MODE = 'heavy'
      Der Modus, hier kann »lite« für kurze Ergebnisse oder »heavy« für ausführli-
      che stehen. Untersuchen Sie zuerst »lite« und nutzen Sie »heavy« nur dann,
      wenn Sie die benötigten Angaben andernfalls nicht finden.
í     const TAG = 'activeserverpa0e'
      Der Token eines Affiliate-Programms. Diese Angabe ist optional.
í     const WSDL_FILE =
      'http://soap.amazon.com/schemas2/AmazonWebServices.wsdl'
      Die Adresse, unter der der Webservice zur Verfügung steht.
í     const TOKEN = 'IHR_AMAZON_TOKEN'
      Der Token, den Sie bei der Anmeldung von Amazon erhalten haben.
Zuerst wird immer eine Instanz des SOAP-Clients benötigt:
$this-Client = new SoapClient(self::WSDL_FILE,       
                               array('trace' = 1));
Der zweite Parameter, array('trace' = 1), kann entfallen, wenn die Anwendung
stabil läuft und der Zugriff auf die originalen SOAP-Nachrichten nicht mehr benö-
tigt wird. Sie können Methoden wie __getLastResponse() nur benutzen, wenn mit
trace die Speicherung der Daten eingeschaltet wurde. Dies ist allerdings mit mehr
Speicher und damit höherer Serverbelastung verbunden und sollte deshalb nur
während der Aufbauphase benutzt werden.
Die eigentliche Abfrage wird in der Methode searchISBN ausgeführt:
public function SearchISBN($Keyword)
Dort wird zunächst ein Array aus den Parametern zusammengestellt. Die Namen
der Schlüsselfelder müssen den Anforderungen in der WSDL-Datei entsprechen,
andernfalls reagiert der Amazon-Server mit einer Fehlermeldung. Stehen alle
Daten bereit, erfolgt die Abfrage:


                                                                                611
XML und Webservices


$result = $this-Client-AsinSearchRequest($params);
Der Name AsinSearchRequest ist wiederum in der WSDL-Datei als mögliche
Dienstfunktion festgelegt. Der Einbau in einen try-Block fängt Fehler ab. Da es
sich hier um eine Internetverbindung handelt, kann diese natürlich fehlschlagen
und dann hilft der Block bei der Fehlerverarbeitung. Wenn Sie außerhalb wieder
mit try/catch arbeiten, sollten Sie mögliche Fehler mit throw new weiterreichen
und gegebenenfalls eigene Fehlerklassen für Ihre Zwecke entwerfen. Im Beispiel
ist die Fehlerverwaltung dem angesprochenen minimalistischen Prinzip zum
Opfer gefallen. Für andere Fehler ersetzen Sie SoapFault durch Exception:
catch (SoapFault $ex)
Am Ende sind die ermittelten Daten nur noch zurückzugeben:
return isset($result) ? $result : '';
Die hier zurückgegebenen Daten sind ein Objekt, das in seinen Eigenschaften
weitere Objekte und Arrays enthält. Der Aufbau entspricht der Struktur der in der
WSDL-Datei für die spezifische Anfrage verwendeten Rückgabedaten.


Einbau in das Bibliotheksskript
Der Einbau in das Bibliotheksskript ist relativ einfach. Die Funktionalität wurde
dabei etwas erweitert und in der Vorlage musste Platz für die zusätzlichen Daten
geschaffen werden. Zuerst ein Blick auf die erweiterte Vorlage:

Listing 14.23: buecher.html: Die Vorlage zur Organisation der Buchdatenbank

html
body
h1Bibliotheksverwaltung/h1
a href=index.phpStartseite/a
|
a href=index.php?TEMPLATE=ausleiheAusleihe/a
|
a href=index.php?TEMPLATE=kundenKundenverwaltung/a
h2Buuml;cherverwaltung/h2
h3Informationen/h3
table border=1 width=600
  caption style=font-weight:boldBuuml;cherliste/caption
  tr
    th



612
SOAP-Webservices


       Anzahl
    /th
    th
       ISBN
    /th
    th
       Titel
    /th
    th
       Aktion
    /th
  /tr
  !-- #REPEAT:books --
  tr
    td
       {$books:number}
    /td
    td nowrap
       {$books:isbn}
    /td
    td
       {$books:title}
    /td
    td
        a href=index.php?TEMPLATE=buecheramp;id={$books:id}amp;
         action=showDetails/a
        a href=index.php?TEMPLATE=buecheramp;id={$books:id}amp;
         action=delLouml;schen/a
    /td
  /tr
  !-- #ENDREPEAT:books --
/table
a href=index.php?TEMPLATE=buecheramp;action=new
   Neues Buch erfassen/a
h3Auml;ndernnbsp;/nbsp;Erfassen/h3
form action=index.php?TEMPLATE=buecher method=post
input type=hidden name=action value={$detail_action} /
input type=hidden name=id value={$detail_id} /
table border=1 width=600
  caption style=font-weight:boldBuchdaten/caption
  tr
    td



                                                                           613
XML und Webservices


       Titel:
    /td
    td
        input type=text name=book_title
               value={$detail_title} size=40/
    /td
    td rowspan=7 valign=top width=250
         !-- #IF:detail_publisher --
         Verlag:nbsp;{$detail_publisher}
         !-- #ENDIF:detail_publisher --
         br/br/
         !-- #IF:detail_image --
         img src={$detail_image}/
         !-- #ENDIF:detail_image --
    /td
  /tr
  tr
    td
       Untertitel:
    /td
    td
        input type=text name=book_subtitle  
               value={$detail_subtitle} size=40/
    /td
  /tr
  tr
    td
       Autor:
    /td
    td
        input type=text name=book_author
               value={$detail_author}/
    /td
  /tr
  tr
    td
       ISBN:
    /td
    td
        input type=text name=book_isbn value={$detail_isbn}/
    /td
  /tr



614
SOAP-Webservices


  tr
    td
       Anzahl
    /td
    td
        input type=text name=book_number   
               value={$detail_number} size=4/
    /td
  /tr
  tr
    td
       Ausgeliehen
    /td
    td
        input type=text name=book_lend value={$detail_lend}       
               size=4 disabled/
    /td
  /tr
  tr
    td
    /td
    td
        input type=submit value={$detail_actiontext} /
    /td
  /tr
/table
/form
/body
/html
Neu – gegenüber der im letzten Kapitel vorgestellten Variante – ist die Ausgabe
der Zusatzdaten:
   td rowspan=7 valign=top width=250
        !-- #IF:detail_publisher --
        Verlag:nbsp;{$detail_publisher}
        !-- #ENDIF:detail_publisher --
        br/br/
        !-- #IF:detail_image --
        img src={$detail_image}/
        !-- #ENDIF:detail_image --
    /td




                                                                             615
XML und Webservices


Dies ist optional, weil die Daten nicht zwingend vorliegen. Deshalb erfolgt der
Einbau in IF-Abschnitte.
Der PHP-Code zu dieser Vorlage ist ebenfalls nur wenig umfassender als die letzte
Version:

Listing 14.24: buecher.php – Steuerung der Vorlage mit allen Datenbankzugriffen

?php
include('database.inc.php');
include('soapclient.inc.php');
//
$detail_action = 'change';
$detail_actiontext = 'Auml;ndern';
if ($_SERVER['REQUEST_METHOD']=='GET'  isset($_GET['action']))
{
   switch ($_GET['action'])
   {
       case 'new':
           $detail_action = 'new';
           $detail_actiontext = 'Erfassen';
           break;
       case 'del':
           $mysqli-query(DELETE FROM books        
                           WHERE id = {$_GET['id']});
           break;
   }
}
if ($_SERVER['REQUEST_METHOD']=='POST'  isset($_POST['action']))
{
   switch ($_POST['action'])
   {
       case 'new':
           if ((empty($_POST['book_author'])        
                || empty($_POST['book_title']))         
                 isset($_POST['book_isbn']))
           {
               $asin = str_replace('-', '', $_POST['book_isbn']);
               $ws = new AmazonSearch();
               $data = $ws-SearchISBN($asin);
               $detail_subtitle = $_POST['book_subtitle'];
               $detail_author      

616
SOAP-Webservices


                  = utf8_decode($data-Details[0]-Authors[0]);
              $detail_isbn      = $_POST['book_isbn'];
              $detail_title      
                  = utf8_decode($data-Details[0]-ProductName);
              $detail_image      
                  = utf8_decode($data-Details[0]-ImageUrlMedium);
              $detail_publisher      
                  = utf8_decode($data-Details[0]-Manufacturer);
          }
          else
          {
              $detail_subtitle   =   $_POST['book_subtitle'];
              $detail_author     =   $_POST['book_author'];
              $detail_isbn       =   $_POST['book_isbn'];
              $detail_title      =   $_POST['book_title'];
          }
          $detail_number   = empty($_POST['book_number']) ? 1 :          
                                  (int) $_POST['book_number'];
          $mysqli-query(INSERT INTO books     
                          (title, subtitle, author, isbn, number)            
                          VALUES ('{$detail_title}',     
                          '{$detail_subtitle}',      
                          '{$detail_author}',       
                          '{$detail_isbn}',    
                           {$detail_number}
                          ));
          break;
      case 'change':
          $mysqli-query(UPDATE books SET     
                          title='{$_POST['book_title']}',        
                          subtitle='{$_POST['book_subtitle']}',          
                          author='{$_POST['book_author']}',          
                          isbn='{$_POST['book_isbn']}',      
                          number={$_POST['book_number']},        
                          WHERE id = {$_POST['id']});
          break;
    }
}
// Liste fuellen
$query = $mysqli-query(SELECT * FROM books);
while($arr = $query-fetch_assoc())
{



                                                                                 617
XML und Webservices


   $books[] = $arr;
}
// Details abrufen, wenn gefordert
if (isset($_GET['id']))
{
    $query = $mysqli-query(SELECT *, (SELECT COUNT(*)     
                             FROM lendout     
                             WHERE books_id = b.id) AS lend     
                             FROM books b     
                             WHERE id = {$_GET['id']});
    $detail = $query-fetch_assoc();
    $detail_id      = $detail['id'];
    $detail_title   = $detail['title'];
    $detail_subtitle= $detail['subtitle'];
    $detail_author = $detail['author'];
    $detail_number = $detail['number'];
    $detail_isbn    = $detail['isbn'];
    $detail_lend    = $detail['lend'];
}
?
Hier sollen nur die Aspekte erläutert werden, die gegenüber der letzten Version
neu sind. Zuerst muss natürlich das SOAP-Modul eingebunden werden:
include('soapclient.inc.php');
Die Abfrage der Amazon-Datenbank erfolgt, wenn ein neues Buch erfasst wurde
und keine Daten außer der ISBN-Nummer im Formular angegeben wurden:
if ((empty($_POST['book_author'])    
  || empty($_POST['book_title']))  isset($_POST['book_isbn']))
Für die Abfrage müssen die Trennzeichen aus der ISBN entfernt werden, andern-
falls funktioniert AsinSearchRequest nicht:
$asin = str_replace('-', '', $_POST['book_isbn']);
Dann wird eine Instanz der SOAP-Klasse erstellt:
$ws = new AmazonSearch();
In diesem Augenblick wird bereits die WSDL-Datei gelesen. Da die Speicherzeit
im Cache 1 Tag beträgt, muss man beim ersten Abruf und nach jedem weiteren
Tag hier mit einer etwas längeren Wartezeit rechnen. Die Größe beträgt ca.
52 KByte.




618
SOAP-Webservices


Dann erfolgt die Abfrage, dies ist immer »life«, benötigt eine Internetverbindung
und dauert fast immer eine oder mehrere Sekunden (auch bei DSL):
$data = $ws-SearchISBN($asin);
Die Daten werden nun entsprechend der erwarteten Struktur entnommen, bei-
spielsweise der erste Autor:
$detail_author    = utf8_decode($data-Details[0]-Authors[0]);
Beachten Sie hier, dass die Daten UTF-8-kodiert sind. Dies ist XML-Standard.
PHP liefert die passende Funktion zum Dekodieren mit: utf8_decode.
Der Aufruf $data-Details[0]-Authors[0] sollte Ihnen keine Rätsel bereiten:
$data             Das Datenobjekt
  -Details       Die Eigenschaft Details (ergibt ein Array)
      [0]         Das erste Element des Arrays, dies ist wieder
                  ein Objekt, diesmal vom Typ Details
     -Authors    Darin ist eine Eigenschaft Authors definiert
         [0]      Die Eigenschaft enthält ein Array, dessen erstes
                  Element benutzt wird
Das das erste Element direkt benutzt werden kann, ist der WSDL-Beschreibung
oder dem Antwortpaket zu entnehmen. Der Datentyp für einen Autor ist »string«,
also eine simple Zeichenkette. Die übrigen Abrufe folgen demselben Schema,
wobei beispielsweise der Titel keine Unterteilung in ein Array mehr besitzt:
utf8_decode($data-Details[0]-ProductName)


Fazit
Damit ist eigentlich alles gesagt. Die Amazon-Webservices sind äußerst komplex
und für jeden Entwickler eine Herausforderung. Es lohnt sich jedoch, denn man
kann mit den Affiliate-Programmen oder den zShops Geld verdienen. Der automa-
tisierte Zugriff erlaubt einen personalarmen Betrieb eines solchen Shops und
damit werden auch kleine Einnahmen attraktiv, weil sie nicht sofort wieder von
Kosten aufgefressen werden. Wer übrigens eine Bibliothek betreibt, der kann
direkt Kosten sparen, denn er muss nun künftig eingehende Bücher, die ohne elek-
tronische Datensätze ankommen, nicht mehr von Hand erfassen. Mit Hilfe eines
kleinen, preiswerten Barcode-Scanners lässt sich die ISBN vom Buchrücken
abnehmen und schon sind die kompletten Daten von Amazon geholt und in der
Datenbank. Was will man mehr?



                                                                               619
XML und Webservices



14.5 Referenz
Die Referenz zeigt die in diesem Kapitel behandelten XML-Module SimpleXML
und DOM.


Referenz SimpleXML

Funktion                           Beschreibung
simplexml_import_dom               Die Funktion importiert ein SimpleXML-Objekt aus
                                   einem DOM-Knoten und gibt das SimpleXML-Objekt
                                   zurück.
simplexml_export_dom               Exportiert ein SimpleXML-Objekt als DOM-Knoten.
simplexml_load_file                Lädt eine XML-Datei und gibt ein SimpleXML-Objekt
                                   zurück.
simplexml_load_string              Lädt XML aus einer Zeichenkette und gibt ein Simple-
                                   XML-Objekt zurück.

Tabelle 14.5: Funktionen, die SimpleXML-Objekte erzeugen oder verarbeiten


Methode                            Beschreibung
AsXML                              Exportiert den Inhalt als XML-Zeichenkette.
attributes                         Die Liste der Attribute eines Elements.
children                           Die Aufzählung der Kindelemente eines Elements.
xpath                              Führt eine XPath-Abfrage aus.
registerXPathNamespace             Registriert einen Namensraumalias für die Verwendung in
                                   XPath-Ausdrücken

Tabelle 14.6: Methoden des SimpleXML-Objekts




620
Referenz



Referenz DOM

Klasse                          Bedeutung
DomAttr                         Repräsentiert ein Attribut.
DomCData                        Repräsentiert einen CDATA-Abschnitt.
DomComment                      Repräsentiert einen Kommentar.
DomDocument                     Die Basisklasse, das Dokument selbst.
DomDocumentType                 Die DOCTYPE-Deklaration des Dokuments.
DomElement                      Repräsentiert ein Element.
DomEntity                       Repräsentiert eine Entität, beispielsweise amp;.
DomEntityReference              Repräsentiert eine Verweis-Entität (auf eine externe
                                Datei).
DomNode                         Allgemeine Knotenklasse (kann alles außer DomDocument
                                sein).
DomProcessingInstruction        Repräsentiert eine Prozessanweisung, beispielsweise
                                ?id?

DomText                         Frei stehender Text zwischen Elementen.
DomXPath                        Erlaubt Zugriff per XPath-Abfrage auf das Dokument.
Tabelle 14.7: Liste der Klassen, die im DOM-Modul definiert sind


Methode                          Bedeutung
createAttributeNS                Erzeugt ein Attribut mit Namensraum.
createAttribute                  Erzeugt ein Attribut.
createElementNS                  Erzeugt ein Element mit Namensraum.
createElement                    Erzeugt ein neues Element.
createTextNode                   Erzeugt einen Textknoten.

Tabelle 14.8: Methoden der Klasse DomDocument




                                                                                       621
XML und Webservices



Methode                            Bedeutung
createDocumentFragment             Erzeugt ein Fragment neuer Knoten.
createComment                      Erzeugt einen Kommentarknoten !-- .. --.
createCDATASection                 Erzeugt einen CDATA-Abschnitt.
createProcessingInstruction Erzeugt eine Prozessanweisung.

createAttribute                    Erzeugt ein Attribut.
createEntityReferenz               Erzeugt einen Verweis.
getElementsByTagName               Gibt eine Liste aller Elemente mit dem gegebenen
                                   Namen zurück.
getElementsByTagNameNS             Gibt eine Liste aller Elemente mit dem gegebenen
                                   Namen in einem bestimmten Namensraum zurück.
getElementById                     Gibt ein Element mit einem bestimmten ID-Attribut
                                   zurück.
renameNode                         Benennt einen Knoten um.
load                               Lädt ein Dokument aus einem Stream (beispielsweise
                                   eine Datei).
save                               Speichert das Dokument.
loadXML                            Lädt XML aus einer Zeichenkette.
saveXML                            Speichert XML als Zeichenkette.
loadHTML                           Lädt eine HTML-Datei.
saveHTML                           Speichert eine HTML-Datei.
validate                           Prüft gegen eine DTD.
validateSchema                     Prüft gegen ein Schema (XSD).
validateRelaxNG                    Prüft gegen ein RelaxNG-Schema.
insertBefore                       Fügt vor einem anderen Knoten ein.
replaceChild                       Ersetzt ein Kindelement.

Tabelle 14.8: Methoden der Klasse DomDocument (Forts.)



622
Referenz



Methode                          Bedeutung
removeChild                      Entfernt ein Kindelement.
appendChild                      Hängt ein Kindelement an.
hasChildNodes                    Prüft, ob Kindelemente vorhanden sind.
cloneNode                        Klont einen Knoten.
hasAttributes                    Ermittelt, ob ein Element Attribute hat.
isSameNode                       Vergleicht zwei Knoten auf Identität.
isDefaultNamespace               Prüft, ob der Knoten im Standardnamensraum ist.
isEqualNode                      Vergleicht zwei Knoten auf Gleichheit.

Tabelle 14.8: Methoden der Klasse DomDocument (Forts.)

Die Methode getElementById gibt Element-Objekte vom Typ DomElement zurück.
Die Methoden getElementsByTagName und getElementsByTagNameNS geben Auflis-
tungen mit Elementen des Typs DomNode zurück (interne Klasse DomNodeList).

Methode                Bedeutung
getAttribute           Ermittelt ein Attribut.
setAttribute           Erzeugt und definiert ein Attribut.
removeAttribute        Entfernt das Attribut.
getAttributeNS         Ermittelt ein Attribut in einem bestimmten Namensraum.
setAttributeNS         Erzeugt und definiert ein Attribut in einem bestimmten Namens-
                       raum.
removeAttributeNS      Entfernt das Attribut in einem bestimmten Namensraum.
getAttributeNode       Ermittelt das Attribut als Objekt vom Typ DomAttr.
setAttributeNode       Setzt ein Attribut auf Basis eines DomAttr-Objekts.
setIdAttribute         Setzt das Attribut mit den Namen ID.
Tabelle 14.9: Methoden der Klasse DomElement



                                                                                        623
XML und Webservices



Methode                  Bedeutung
setIdAttributeNS         Setzt das Attribut mit den Namen ID in einem bestimmten
                         Namensraum.
isSameNode               Vergleicht zwei Attribute auf Identität.
isDefaultNamespace Prüft, ob der Attribute im Standardnamensraum ist.

isEqualNode              Vergleicht zwei Attribute auf Gleichheit.

Tabelle 14.9: Methoden der Klasse DomElement (Forts.)

Die Methode getAttributeNode gibt ein Objekt vom Typ DomAttr zurück.

Eigenschaft              Bedeutung
textContent              Inhalt des Elements als Text (ohne Kindelemente).
nodeType                 Siehe DomNode. Ist bei Elementen immer XML_ELEMENT_NODE.
childNodes               Auflistung von Kindelementen als DomNodeList, eine aufzählbare
                         Liste von DomNode-Objekten.
tagName                  Name des Elements
Tabelle 14.10: Eigenschaften der Klasse DomElement


Methode                       Bedeutung
isId                          Das Attribut ist ein ID-Attribut.
insertBefore                  Fügt ein Attribut vor dem aktuellen ein.
isSameNode                    Vergleicht zwei Attribute auf Identität.
isDefaultNamespace            Prüft, ob das Attribut im Standardnamensraum ist.
isEqualNode                   Vergleicht zwei Attribute auf Gleichheit.
Tabelle 14.11: Methoden der Klasse DomAttribute




624
Referenz



Eigenschaft                Bedeutung
value                      Wert des Attributes.
name                       Name des Attributes.
specified                  Gibt TRUE zurück, wenn das Attribut seinen aktuellen Wert
                           aus dem Dokument erhielt und nicht erst durch spätere
                           Änderung.
ownerElement               Gibt das DomElement zurück, zu dem dieses Attribut gehört.

Tabelle 14.12: Methoden der Klasse DomAttribute


Name der Konstanten             Bedeutung
XML_ELEMENT_NODE                Element
XML_ATTRIBUTE_NODE              Attribut
XML_TEXT_NODE                   Textknoten
XML_CDATA_SECTION_NODE          CDATA-Abschnitt
XML_ENTITY_REF_NODE             Entity-Verweis
XML_ENTITY_NODE                 Entity-Knoten
XML_PI_NODE                     Prozessanweisung
XML_COMMENT_NODE                Kommentar
XML_DOCUMENT_NODE               Dokument
XML_DOCUMENT_TYPE_NODE          Document Type Deklaration
XML_DOCUMENT_FRAG_NODE          Dokumentfragment
XML_NOTATION_NODE               Notation (in einer DTD)
XML_GLOBAL_NAMESPACE            Globaler Namensraum
XML_LOCAL_NAMESPACE             Lokaler Namensraum
XML_HTML_DOCUMENT_NODE          Dokument im HTML-Modus

Tabelle 14.13: Konstanten des DOM-Moduls




                                                                                    625
XML und Webservices



Name der Konstanten                 Bedeutung
XML_DTD_NODE                        Document Type Definition
XML_ELEMENT_DECL_NODE               Element-Deklaration einer DTD
XML_ATTRIBUTE_DECL_NODE             Attribut-Deklaration einer DTD
XML_ENTITY_DECL_NODE                Entitäts-Deklaration einer DTD
XML_NAMESPACE_DECL_NODE             Namensraum-Deklaration
XML_ATTRIBUTE_CDATA                 CDATA-Teil eines Attributes
XML_ATTRIBUTE_ID                    ID-Attribut
XML_ATTRIBUTE_IDREF                 IDREF-Attribut
XML_ATTRIBUTE_IDREFS                Kollektion von IDREF-Attributen
XML_ATTRIBUTE_ENTITY                Entität in einem Attribut
XML_ATTRIBUTE_NMTOKEN               NMTOKEN
XML_ATTRIBUTE_NMTOKENS              Aufzählung von NMTOKEN Elementen
XML_ATTRIBUTE_ENUMERATION           Attribut-Aufzählung
XML_ATTRIBUTE_NOTATION              Attribut-Notation
Tabelle 14.13: Konstanten des DOM-Moduls (Forts.)



14.6 Kontrollfragen
1. Welche Technologien sind wichtig, wenn mit XML gearbeitet wird?
2. Welche Methode der Verarbeitung ist bei einem ca. 12 MByte großen XML-
      Dokument wahrscheinlich die beste, SAX oder DOM? Begründen Sie die Ant-
      wort.
3. Nennen Sie typische Erscheinungsformen eines so genannten Knotens in einem
      XML-Dokument.
4. Welche Bedeutung hat die DTD (Document Type Definition)?




626
Antworten auf die
 Kontrollfragen




    1 5
Antworten auf die Kontrollfragen



1. Tag
1. Wann wurde PHP das erste Mal unter diesem Namen bekannt?
      A   1995, als PHP/FI. Damals war es noch einen Sammlung von Perl-Skripten.
2. Was bedeutet »PHP«?
      A   Dies ist ein so genanntes rekursives Acronym: PHP Hypertext Preprocessor.
3. Worauf ist der Name »Zend« zurückzuführen?
      A   Ein Kunstwort aus Namensteilen der Gründer der Firma Zend, Ze von
          Zeev Suraski und nd aus Andi Gutmans. Zend stellt den Sprachkern für
          PHP bereit und vertreibt außerdem kommerzielle Werkzeuge für PHP, wie
          beispielsweise eine IDE, Verschlüsselungs- und Cachetools.
4. Anhand welcher Begrenzungszeichen werden PHP-Fragmente in HTML-Code
      erkannt?
      A   ?php und ?. Andere Varianten werden nicht empfohlen. De Kurzform
          ?=$var? zum Einbetten von Variablen sollte nur in Ausnahmefällen
          benutzt werden.
5. Welches Protokoll wird zur Übertragung von HTML-Seiten vom Server zum
      Browser verwendet?
      A   HTTP – Hypertext Transfer Protocol.
6. Welcher Webserver kann auf allen Betriebssystemen zur Entwicklung eingesetzt
      werden?
      A   Der Apache-Webserver ist auf allen Plattformen zu Hause.
7. Wofür steht der Begriff WAMP?
      A   W = Windows, A = Apache, M = MySQL, P = PHP. Die beliebteste Kom-
          bination für PHP-Entwickler, wird von weit über 90% der Anwender
          benutzt.
8. Welche Bedeutung hat die Adresse »http://localhost«?
      A   Verweis auf den lokalen Webserver (!). Der Name weicht meist vom
          Namen des lokalen Computers ab. Die zugehörigen IP-Adresse ist
          127.0.0.1.




628
2. Tag


9. Mit welcher Funktion kann PHP5 ausführlich getestet werden?
   A   phpinfo() heißt die Funktion. Folgendes Skript zeigt, wie die Funktion
       genutzt wird:
       ?php
       phpinfo();
       ?




2. Tag
1. Wie geben Sie mehrzeilige Texte aus PHP-Code heraus am besten aus?
   A   Mit der Heredoc-Syntax werden mehrzeilige Texte aus PHP-Code heraus
       ausgegeben:
       ?php
       echo TEXT
          table
            !-- Es folgt viel Text --
          /table
       TEXT;
       ?
2. Welche Funktion eignet sich zur Ausgabe hexadezimaler Farbangaben?
   A   printf könnte hilfreich sein. Die Option %2X erzeugt zweistellige hexade-
       zimale Zahlen. Als Eingabewert dienen alle Zahlenliterale, also beispiels-
       weise 32 (Dezimal) oder 0x20 (Hexadezimal) oder 040 (Oktal).
3. Welche Kommentarform wird am besten verwendet, wenn hinter einer Codezeile
   ein kurzer, auf die Zeile bezogener Kommentar stehen soll? Mögliche Varianten
   sind: /* */, // oder #.
   A   Es sollte immer // verwendet werden. # ist unüblich und /* scheitert, wenn
       umschließende mehrzeilige Kommentare benutzt werden, um den Block
       auszukommentieren.




                                                                             629
Antworten auf die Kontrollfragen



3. Tag
1. Was ist ein Literal?
      A   Jede Form von im Quellcode direkt geschriebener konstanter Werte, also
          beispielsweise 0x22, 345.66, string usw.
2. Wie werden Konstanten definiert?
      A   Konstanten werden mit der Funktion define erstellt.
3. Wozu werden reguläre Ausdrücke eingesetzt?
      A   Reguläre Ausdrücke werden zum Suchen und Ersetzen benutzt, wenn die
          Suchmuster komplex oder in sich variabel sind.
4. Schreiben Sie ein Skript, dass anhand mehrerer Parameter ein span-Tag mit kor-
      rekter hexadezimaler Angabe der Vordergrund- und Hintergrundfarbe mit Hilfe
      des style-Attributes erstellt und ausgibt. Tipp: Verwenden Sie printf.
      A   printf ist aufgrund der Parameter gut geeignet, um Tags übersichtlich zu
          erstellen.
          ?php
          printf('span style=color:#%2X%2X%2X;     
                               background-color:#%2X%2X%2X',       
                  0xFF, 0x00, 0xCC,
                  0xDD, 0xDD, 0xDD);
          ?




4. Tag
1. Schreiben Sie ein Skript, das beliebige Zahlen zwischen 0 und 100 auf ganze
      Zehner rundet.
      A   Die Antwort ist verblüffend einfach: Verwenden Sie die Funktion round.
          Bei der Angabe der Anzahl »Kommastellen« auf die gerundet werden soll,
          sind auch negative Zahlen erlaubt, was dann zum Runden vor dem
          Komma führt.
          Das folgende Skript prüft das Verhalten für ein paar Testzahlen:




630
4. Tag


       ?php
       $test =   array(4.5, 25, 67, 99.4);
       foreach   ($test as $number)
       {
          echo   round($number, -1);
          echo   'br';
       }
       ?
   Ausgegeben werden die Zahlen : 0, 30, 70 und 100.
2. Können mit switch-Anweisungen auch Vergleiche mit beliebigen Booleschen
   Ausdrücken erfolgen?
   A   Ja, denn PHP behandelt die Ausdrücke erst zur Laufzeit und erledigt dann
       den Vergleich. Wenn man im Verzweigungskopf den Wert TRUE schreibt,
       müssen die Ausdrücke auch TRUE oder FALSE zurückgeben, sodass ein Test
       auf Gleichheit funktioniert. Wichtig ist jedoch darauf zu achten, dass
       immer nur ein (oder kein) Zweig die Bedingung zum Zeitpunkt der
       Abfrage erfüllt, sonst ist die Reaktion nicht korrekt.
       switch(TRUE)
       {
          case $test  9  $test  4:
             // Reaktion
             break;
       }
3. Welche Schleife wird benutzt, wenn sichergestellt werden muss, dass der Schlei-
   fenkörper mindestens einmal durchlaufen wird?
   A   Die do-Schleife. Da der Test erst am Ende mit Hilfe der while-Bedingung
       erfolgt, wird der Körper garantiert einmal durchlaufen, auch wenn die
       Bedingung dann nicht erfüllt ist und die Schleife sofort wieder verlassen
       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?
   A   Die Bedingung für den Ausstieg wird mit if erstellt, der Ausstieg erfolgt
       dann mit break. switch ist nicht geeignet, weil die Zweige selbst mit break
       enden und ein weiteres break nicht mehr ausgeführt wird.




                                                                              631
Antworten auf die Kontrollfragen



5. Tag
1. Worin unterscheiden sich normale von assoziativen Arrays?
      A   Assoziative Arrays verwenden Schlüsselwerte, die meist als Zeichenketten
          vorliegen, zur Adressierung der Elemente, beispielsweise $arr[?name?].
          »Normale« Arrays haben dagegen immer Zahlen, über die Elemente
          erreicht werden, beispielsweise $arr[8].
2. Wie kann ein Element eines Array gezielt entfernt werden?
      A   Mit unset kann ein Element entfernt werden. PHP organisiert die Schlüs-
          selwerte einfacher Arrays danach nicht neu. Sie müssen das selbst erledi-
          gen, wozu die Funktion array_values dient:
          ?php
          $test = array(4.5, 25, 67, 99.4);
          unset($test[2]);
          $test = array_values($test);
          foreach ($test as $index = $number)
          {
             echo $index = $number;
             echo 'br';
          }
          ?
      A   Die Ausgabe der Indizes lautet 0, 1, 2. Ohne array_values wäre es 0, 1, 3.
3. Schreiben Sie ein Skript, dass Arrays nutzt um Namen von Mitarbeitern zu spei-
      chern. Erweitern Sie das Skript, sodass zu jedem Mitarbeiter im selben Array
      zusätzliche Informationen gespeichert werden. Tipp: Nutzen Sie verschachtelte
      assoziative Arrays dafür.
      A   Sie können innerhalb einer array-Definition für den Wert ein weiteres
          Array verwenden. Als Schlüssel sind dagegen nur Zahlen oder Zeichenket-
          ten erlaubt. Beachten Sie jedoch, dass mehr als drei Ebenen nur schwer
          handhabbar sind und meist auf einen Designfehler beim Entwurf des
          Datenmodells hindeuten.
          ?php
          $ma = array(
                   'Bernd Muster' = array('030/12345', 'Raum 123'),
                   'Katja Muster' = array('089/54321', 'Raum 999'),
                   );



632
6. Tag


       foreach ($ma as $name = $details)
       {
          echo $name = $details[0], $details[1];
          echo 'br';
       }
       ?




6. Tag
1. Schreiben Sie ein Skript, dass die Namen von Mitarbeitern in einer eigens dafür
   entwickelten Klasse speichert.
   A   Sie sollten auch bei derart einfachen Aufgabenstellungen grundsätzlich
       versuchen, die elementaren OOP-Techniken zu verwenden. Dazu gehört
       der Schutz der Daten in privaten Mitgliedern und der Zugriff über __get
       bzw. __set und die Verwendung eines sinnvollen Konstruktors.
       ?php
       class Employee
       {
          private $_name;
          private $_telephon;
          private $_room;

          public function __construct($n, $t, $r)
          {
             $this-_name = $n;
             $this-_telephon = $t;
             $this-_room = $r;
          }

          public function __get($property)
          {
             switch ($property)
             {
                case 'name':
                   return $this-_name;
                case 'telephon':
                   return $this-_telephon;
                case 'room':



                                                                              633
Antworten auf die Kontrollfragen


                          return $this-_room;
                 }
             }
          }
          $ma = array(new Employee('Bernd Muster', '0172/654321', '215'),
                    new Employee('Katja Muster', '0172/123456', '948'));
          foreach ($ma as $em)
          {
             echo {$em-name} = {$em-telephon}, {$em-room};
             echo 'br';
          }
          ?
2. Erklären Sie den Unterschied zwischen private, public und protected.
      A   Mitglieder, die in einer Klasse als private gekennzeichnet sind, können
          nur von Methoden der Klasse selbst benutzt werden. Mitglieder, die in
          einer Klasse als public gekennzeichnet sind, können von Methoden oder
          Funktionen des gesamten Skripts benutzt werden. Mitglieder, die in einer
          Klasse als protected gekennzeichnet sind, können nur von Methoden der
          Klasse selbst und solchen einer von dieser Klasse abgeleiten Klasse benutzt
          werden.
3. Sie haben nur eine einzige Klasse in Ihrem Skript. Ist die Anwendung des Schlüs-
   selwortes protected sinnvoll? Begründen Sie die Antwort.
      A   Nein, die Anwendung ist nicht sinnvoll. Sie können das so gekennzeich-
          nete Mitglied nicht aus Ihrem Skript erreichen und für die Benutzung
          innerhalb der Klasse wäre private ausreichend.
4. Welchen Vorteil bietet die Verwendung von __get und __set anstatt des direkten
   Zugriffs auf öffentliche Eigenschaften, die mit public $name gekennzeichnet
      sind?
      A   Die Pseudo-Eigenschaften können den Weg der Daten in die Eigenschaft
          hinein und wieder heraus kontrollieren. Man kann so sicherstellen, dass
          niemals ungültige Daten im Objekt gespeichert sind. Man kann auch
          sicherstellen, dass niemals ungültige Daten das Objekt verlassen. Beides
          trägt zur Codesicherheit bei. Außerdem kann eine Klasse weitaus mehr
          Eigenschaften bereitstellen, als Mitgliedsvariablen vorhanden sind. Die
          interne Form der Datenspeicherung kann also anderen Optimierungskrite-
          rien unterliegen als die Darstellung der Daten nach außen.




634
7. Tag


5. Wie schreiben Sie eine Klasse, deren Objekte beim Aufruf von new einen definier-
   ten Anfangszustand unabhängig von Parametern erhalten soll?
   A   Sollen die Daten des Anfangszustands nicht variabel sein, bietet sich der
       Einsatz von Konstanten an. Im Konstruktor werden die Konstanten dann
       den Eigenschaften zugewiesen. Dies erleichtert die Wartung der Klasse.
6. Wie schreiben Sie eine Klasse, von der nur eine Instanz erzeugt werden darf? Wie
   nennt man dieses Entwurfsmuster?
   A   Das Muster wird Singleton genannt. Man deklariert den Konstruktor pri-
       vat, um den wiederholten Aufruf zu verhindern. Ersatzweise schafft man
       eine Methode, in der mittels new eine Instanz erzeugt und zurückgegeben
       wird. Die Methode speichert die erzeugte Instanz in einer Variablen und
       prüft beim erneuten Aufruf, ob die Instanz bereits existiert. Ist das der Fall,
       wird die bereits existente Version zurückgegeben, die zugleich die einzige
       Instanz der Klasse ist.



7. Tag
1. Auf welche Datenquellen können die Dateifunktionen zugreifen?
   A   Dank des universellen Stream-Konzeptes können Dateifunktionen aus
       beliebigen Datenquellen zurückgreifen. Bereits fertig implementiert ist der
       Zugriff auf Dateien des Dateisystems, Ein-/Ausgabekanäle des Betriebssys-
       tems, Webserver per HTTP und FTP-Server. Für andere Datenquellen las-
       sen sich benutzerdefinierte Wrapper erstellen.
2. Warum muss eine Datei nach der Benutzung wieder geschlossen werden?
   A   Der schreibende Zugriff auf Dateien ist exklusiv, damit sich die Änderun-
       gen nicht unbemerkt überschreiben. Um künftige Zugriffe nicht zu verzö-
       gern, sollten Dateien schnell wieder geschlossen werden. Bedenken Sie,
       dass Webserver Multiuser-Zugriffe gestatten, ein Skript also gleichzeitig
       mehrfach starten kann.




                                                                                  635
Antworten auf die Kontrollfragen


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’].
      A   Zuerst benötigt man eine Dateiliste. Hier ist das Verzeichnisobjekt dir
          oder der DirectoryIterator der SPL eine gute Wahl.
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.
      A   Der GET-Parameter lässt sich im Browser leicht manipulieren. Da die
          meisten Dateifunktionen auch Pfade mit relativen Angaben wie ../../../ ver-
          arbeiten können, lassen sich so leicht Dateien auslesen, die nicht zum
          freien Zugriff gedacht sind. Auch wenn das Betriebssystem meist verhin-
          dert, dass Systemdateien gelesen werden können, reicht oft die Auswahl
          einer Include-Datei oder anderer PHP-Skripte, um an Kennwörter oder
          Systeminformationen zu gelangen.
          Zur Lösung arbeitet man mit Hashwerten oder ID-Nummern, sodass im
          Link nur Werte stehen, die keinen Rückschluss auf die Datei zulassen.
          Den Dateinamen einfach als MD5-Hash abzulegen ist freilich keine
          Lösung, denn Profis erkennen derartige Hashes sehr schnell und können
          sie ebenso schnell auch erzeugen.



8. Tag
1. Warum sollten unbedingt immer »Sticky Forms« verwendet werden?
      A   Es ist ausgesprochen lästig für den Benutzer, wenn er nach einem Fehler
          die mühevoll eingegebenen Daten verliert. Formulare sind ohnehin die
          Stelle mit der höchsten Abbruchquote. Schlecht gemachte Formulare stei-
          gern den Misserfolg der Site enorm.
          Inzwischen gilt der Verzicht auf »Sticky Forms« auch einfach nur als
          unprofessionell.




636
9. Tag


2. Welche Funktionen unterstützen das Hochladen von Dateien? Was ist beim Auf-
   bau des Formulars zu beachten?
   A   Die wichtigste Funktion ist move_uploaded_file, eingesetzt zum Transport
       der Dateien zum endgültigen Ziel. Beim Formular ist zu beachten, dass
       das Attribut enctype im Form-Tag angegeben wird, damit der Browser die
       Daten korrekt verpackt.
3. Welche Methoden verwenden Sessions, um die Session-Daten zu speichern?
   A   Zum Speichern werden Dateien oder Datenbanken benutzt, seltener der
       Systemspeichern. PHP5 unterstützt standardmäßig Dateien. Beachten Sie,
       dass die Sessiondaten, die im Sessioncookie gespeichert werden, lediglich
       eine Referenznummer auf die im Webserver oder einer Datenbank gespei-
       cherten Daten enthalten.
4. Wie können Cookies missbraucht werden?
   A   Cookies können mit jeder Anforderung gesendet werden. Da die Objekte
       einer Website nicht vom selben Server stammen müssen, können beim
       Laden einer Website auch Cookies verschiedener Server übermittelt wer-
       den. Besucht derselbe Benutzer eine andere Website, wobei ein Objekt
       von demselben Server stammt wie beim Besuch der vorhergehenden Site,
       erhält der Server das Cookie von der zweiten Site wieder zurück. Damit
       kann man Bewegungsprofile erstellen, die den Weg des Benutzers durchs
       Web markieren. Firmen wie Doubleclick nutzen dies als Geschäftsmodell,
       um kundenspezifische Werbebanner zu schalten.



9. Tag
1. Welche Servervariable wird benutzt, um die bevorzugte Sprache des Benutzers zu
   ermitteln?
   A   HTTP_ACCEPT_LANGUAGE kann verwendet werden. Zur Abfrage
       schreiben Sie:
       $lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];




                                                                             637
Antworten auf die Kontrollfragen


2. Welcher Header wird in HTTP benötigt, um dem Browser anzuzeigen, dass ein
      Bild gesendet wird?
      A   Benötigt wird Content-type:. Als Parameter ist image/typ anzugeben,
          wobei der Typ dem Bildformat entspricht:
          Content-type: image/gif
3. Schreiben Sie ein Skript, dass dezimale Farbangaben wie 255, 192, 128 in hexa-
      dezimale Zeichenfolgen der Art »#FFCC99« umrechnet und umgekehrt.
      A   Nutzen Sie die Funktion printf dafür. Folgende Zeile muss nur noch in
          eine Funktion eingebaut werden:
          printf('#%2X%2X%2X', 255, 192, 128);
4. Wozu dient das Werkzeug Reflection?
      A   Mittels Reflection kann Code analysiert werden.



10. Tag
1. Welche Aufgabe erfüllen Wrapper?
      A   Wrapper dienen dazu, benutzerspezifische Ein-/Ausgabekanäle zu schaf-
          fen, sodass beispielsweise die normalen Dateifunktionen darauf zugreifen
          können.
2. Was muss ein fremder Entwickler beachten, wenn er einen von Ihnen entwickeltes
      Filter verwendet?
      A   Nichts – Filter sind für die Benutzung transparent.
3. Was bedeuten die Kopfzeilen Cc: und Bcc: in einer E-Mail?
      A   Cc: (Carbon Copy, Durchschlag) sendet eine Kopie der E-Mail an den
          angegebenen Empfänger, wobei alle anderen Empfänger dessen Adresse
          sehen. Bcc: dagegen sendet ebenso eine Kopie, andere Empfänger können
          dies aber nicht sehen (Blind Carbon Copy, Blinddurchschlag).
4. Welches Protokoll nutzt die mail-Funktion zur Übertragung der Daten?
      A   Diese Funktion überträgt die Daten mittels SMTP zum Server.




638
11. Tag



11. Tag
1. Warum sollte die neue Bibliothek MySQLi anstatt der alten eingesetzt werden?
   A   MySQLi ist auf die Datenbank MySQL 4 abgestimmt. Außerdem unter-
       stützt das neue Modul die objektorientierte Programmierung.
2. Was versteht man unter Normalisierung?
   A   Stark vereinfacht die Vermeidung redundanter Datenhaltung in einer rela-
       tionalen Datenbank.
3. Sie müssen bei der Ausgabe von Daten aus einer MySQL-Datenbank Datums-
   formate anpassen und n in br umwandeln. Wie gehen Sie vor?
   A   MySQL verfügt über einen reichen Vorrat an Zeichenkettenfunktionen.
       Suchen Sie eine Funktion zum Suchen und Ersetzen, die dies erledigt.
       Dies ist meist schneller als nl2br im PHP-Skript.
4. Wie würden Sie eine Abfrage formulieren, bei der aus zwei Tabellen Daten
   zugleich entnommen werden müssen? Welche Voraussetzungen müssen die
   Tabellen erfüllen.
   A   Dies ist ein so genannter JOIN:
       SELECT t1.feldname, t2.feldname
           FROM tabelle1 t1 INNER JOIN tabelle2 t2 ON t1.id = t2.id
       Die Schreibweise tabelle1 t1 definiert einen Aliasnamen für tabelle1.
       Damit lassen sich gleichnamige Felder auseinander halten, ohne die mög-
       licherweise langen Tabellennamen schreiben zu müssen. t1.id = t2.id ver-
       knüpft die beiden Tabellen miteinander, indem die Primärschlüssel
       verglichen werden. t1.feldname, t2.feldname ruft dann aus jeder Tabelle
       eine bestimmte Spalte ab.



12. Tag
1. Was müssen Sie tun, um SQLite benutzt zu können?
   A   Nichts, SQLite gehört zum Standardumfang von PHP5.




                                                                             639
Antworten auf die Kontrollfragen


2. Welche Datentypen kennt SQLite?
      A   Zahlen und Zeichen, alle anderen Datentypnamen sind nur aus Kompati-
          bilitätsgründen im Umfang reservierter Namen. SQLite prüft sonst keine
          Daten sondern behandelt diese immer als Zeichenketten.
3. Sie möchten mehrere Tausend Datensätze für eine hoch frequentierte Website
      verwalten. Ist SQLite dafür die beste Wahl? Begründen Sie die Antwort?
      A   SQLite ist dafür nicht geeignet, weil die Verwaltung der Datenbank auf
          Dateiebene stattfindet. Während des Zugriffs auf eine Tabelle ist die Datei
          gesperrt, was konkurrierende Zugriffe unmöglich macht. Bei hoher Last
          wird SQlite deshalb sehr langsam.
4. Welche Aufgabe hat der Befehl VACUUM?
      A   Der Befehl verdichtet die Datenbank nach Löschvorgängen. Die separate
          Optimierung verhindert, dass einfache Löschvorgänge zu lange dauern.



13. Tag
1. Wozu werden Template-Systeme eingesetzt?
      A   Template-Systeme dienen dazu, Code und Layout zu trennen.
2. Erweitern Sie die Applikation, sodass zu jedem Leser der Bibliothek mehrere Tele-
      fonnummern gespeichert werden können. Wie gehen Sie vor?
      A   Legen Sie eine weitere Tabelle in der Datenbank an. Erweitern Sie die
          Tabelle der Leser um einen ID-Spalte. Verknüpfen Sie die Spalte mit der
          neuen Telefon-Tabelle und einer dort befindlichen Korrespondenz-Spalte.
          Sie benötigen also mindestens drei Spalten in der Telefon-Tabelle: id (Pri-
          märschlüssel), telephon-id (Verknüpfung, gleiche Werte können freilich
          mehrfach auftreten, wenn ein Kunde mehrere Telefonnummern hat), tele-
          phon (die eigentlichen Daten).
3. Formulieren Sie eine SQL-Abfrage, die die nötigen Daten zur Erstellung von
      Mahnschreiben erzeugt, um säumige Leser auf die bereits abgelaufene Rückga-
      befrist hinzuweisen.
      A   Der folgende Ausdruck (Teil der Abfrage) wird TRUE, wenn die Ausleihfrist
          abgelaufen ist:
          TO_DAYS(CURDATE())-TO_DAYS(lendingdate)  lendingperiod


640
14. Tag



14. Tag
1. Welche Technologien sind wichtig, wenn mit XML gearbeitet wird?
   A   Neben XML benötigen Sie eine Definitionssprache, also DTD oder XML-
       Schema. Außerdem zur Verarbeitung XSLT und XPath.
2. Welche Methode der Verarbeitung ist bei einem ca. 12 MByte großen XML-
   Dokument wahrscheinlich die beste, SAX oder DOM? Begründen Sie die Ant-
   wort.
   A   SAX ist besser. Die sequenzielle ereignisbasierte Verarbeitung ist optimal
       bei großen Datenmengen. DOM würde das Dokument als Ganzes versu-
       chen im Speicher zu halten und dabei sehr viel Speicherplatz benötigen,
       sodass der Zugriff sehr langsam wird.
3. Nennen Sie typische Erscheinungsformen eines so genannten Knotens in einem
   XML-Dokument.
   A   Neben Elementen sind auch Kommentare, Attribute, CDATA-Abschnitte
       und Prozessanweisungen Teil eines XML-Dokuments. Da das gesamte
       Dokument als Baum dargestellt werden kann, dessen Verzweigungen Kno-
       ten sind, können Knoten praktisch jeden Teil des Dokuments darstellen.
4. Welche Bedeutung hat die DTD (Document Type Definition)?
   A   Die DTD definiert die in einem XML-Dokument zulässigen Elemente
       (deren Namen) und deren Attribute sowie die Abhängigkeiten zwischen
       diesen. Dazu gehört beispielsweise, welche Elemente in welcher Zahl Kin-
       delemente anderer sein dürfen. Ohne DTD kann man lediglich prüfen, ob
       ein XML-Dokument wohlgeformt ist. Mit DTD kann man prüfen, ob es
       außerdem gültig ist. Jedes XML-Dokument sollte eine DTD haben oder
       alternativ das neuere XML-Schema, das einem vergleichbaren Zweck
       dient.




                                                                             641
Stichwortverzeichnis
Symbols                                Ausgaben 55
$_COOKIE 351                           – formatieren 59
$_REQUEST 355                          AUTO_INCREMENT (SQL)          451
- 208                                 AVG (SQL) 455
__autoload 172
__call 227                             B
__clone 218                            Backslash 84
__construct 210                        Bad Request 42
__destruct 211                         Base64 428
__FILE__ 233                           basename 254
__get 229                              Benennungsregeln 65
__LINE__ 233                           Benutzerverwaltung (mit SQLite)   501
__METHOD__ 233                         BETWEEN (SQL) 448
__set 229                              Bilder 49
__toString 226, 234                    – Funktionen 385
_subclass_of 237                       – Zeugung 381
                                       BinHex 428
A                                      Boolesches 81
Abfragesyntax 446                      break 137, 141
Abstammung 235                         Browserdaten erkennen 374
abstract 217
Achsenbezeichner 564                   C
Addition 79                            case 137
Ähnlichkeiten 88                       catch 178
aggregierende Funktionen (SQL)   454   childNodes 579
Algebra, logische 81                   children (SimpleXML) 587
ALL (SQL) 454                          chunk_split 94
alternative Zweige 136                 class 207
Anführungszeichen 85                   clone 218
Anhänge versenden 434                  Coding Standards 65
Apache                                 continue 141
– installieren 30                      Cookies 349
– testen 35                            – Beschränkungen 352
ArGoSoft Mail Server 429               – Personalisierung 352
Array 182                              copy 335
– Assoziative 191                      count 184
– erstellen 183                        COUNT (SQL) 454
– Funktionen 198                       CREATE TABLE (SQL) 449
– Werte 186                            current 188
– Zeiger 188                           current (Iterator) 265, 265
array_walk 196
Arraydaten 193                         D
ASC (SQL) 456                          D. Richard Hipp 498
ASCII-Zeichensatz 91                   date 111
Assoziativität 133                     Dateien hochladen 334
attributes (SimpleXML) 586             Dateisystem 244
Attributknoten 563                     Dateizugriff 246
Ausdrücke 78                           Datenspeichermethoden 358


                                                                               643
Stichwortverzeichnis


Datentypen 74, 83                      fclose 251
Datentypen (SQL) 449                   Fehler (PHP) 42
Datum 110                              – Behandlung 173
default 138                            – Codes 177
DEFAULT (SQL) 451                      – Klassen erstellen 224
define 76                              – Nummer 176
defined 77                             – Text 176
Dekrement 79                           fgets 251
DELETE (SQL) 453                       file_get_contents 420
DESC (SQL) 456                         Filter 422, 440
Destruktoren 210                       final 216
dir 260                                fnmatch 260
DirectoryIterator 266                  fopen 251
DISTINCT (SQL) 454                     for 142
Division 79                            foreach 194
DNS 70                                 Formatieren 119
do 148                                 – Zahlen 119
DOM 576                                Formatregel 60
– domAttr (Klasse) 624                 Formulare 52
– Funktionen 576                       – bauen 302
– Konstanten 625                       fscanf 120
Domain Name System siehe DNS           func_get_arc 156
DomAttr 624                            func_get_args 156
DomDocument 579                        func_num_args 156
– getAttributeNode 624                 function 150
– getElementsById 623                  Funktionen 150
– getElementsByTagName 623             – Referenzen 159
DomXPath 566                           – Rekursive 164
DROP (SQL) 453                         – Variable 166
Dropdown-Listen 295                    Funktionsdefinition 150
DTD 553
dynamisch Bilder erzeugen 379          G
dynamischer Werbebanner 386            GD2 379
                                       Geschichte 22
E                                      GET 341
each 195                               get_included_files 172
echo 55                                get_meta_tags 419
Eigenschaftenaufruf 229                get_required_files 172
Elementknoten 563                      getElementsByTagName 579
else 136                               getMessage 225
Elternknoten 563                       gleich 82
E-Mail 426                             glob 257
– versenden 430                        global 162
empty 287                              globale Variablen 162
Entitäten 93                           Grafikbibliothek 379
Ersetzen 87                            Grafiken 379
Exception 224                          Größe (Datentyp) 83
exklusives Oder 83                     größer als 82
Extensible Markup Language siehe XML   Große Textmengen 57
                                       GROUP BY (SQL) 454
F                                      Gruppierungen 98
Factory-Klassen        235             Gruppierungen (SQL) 455
Farbrad 146                            Gutmans, Andi 22



644
Stichwortverzeichnis


H                             Konfigurationsschritte 38
Handle 244                    Konstanten 76
Hashes 186                    Konstruktoren 210
HAVING (SQL) 454              Kontext 440
header 380                    Kontrollkästchen 287
heredoc 57                    Krause, Jörg 17
Herkunft erkennen 282
Hinweistexte platzieren 303   L
hochladen 334                 Laufzeitfehler 175
HTML 46                       LC_TIME 116
– Formularelemente 278        LEFT JOIN (SQL) 459
– Mails 434                   libxml2 575
htmlentities 93               LIKE (SQL) 448
HTTP 71                       links-assoziativ 134
HTTP_REFERER 282              list 195
httpd.conf 40                 Literale 74
                              loadHtmlFile 579
I                             localeconv 378
iconv-Modul 340               Lokalisierung 376
idate 116
if 132                        M
imagecreate 381               Mail 426
imagecreatefromgif 382        MAX (SQL) 455
imagecreatefromjpeg 382       MAX_FILE_SIZE 337
imagecreatefrompng 382        MD5 347
imagegif 382                  Mehrfachauswahl 297, 322
imagettfbbox 382              Mehrfachverzweigungen 137
imagettftext 383              mehrseitige Formulare 325
IMAP4 427                     mehrsprachige Webseiten 374
implements 221                Message Digest Version 5 347
IN (SQL) 448                  MIME 428
include 169                   MIN (SQL) 455
include_once 169              mktime 111
Inkrement 79                  Modularisierung 168
Inner Join 458                Module einbinden 168
INSERT INTO (SQL) 451         Modulus 79
instanceof 236                Moniker 416
Interfaces 221                move_uploaded_files 335
is_a 237                      MSI Installer 30
is_resource 251               multipart/form-data 334
ISO-8859-1 91                 Multiplikation 79
                              Multipurpose Internet Mail Extensions siehe
J                                MIME
JavaScript 47, 303            MySQL
– lokale Zeit 117             – Aggregat-Funktionen 467
JOIN (SQL) 459                – Beispielprojekt 510
                              – Control Center 513
K                             – Datentypen 462
key 188                       – Datumsformatierungen 475
Kindknoten 563                – Datumsfunktionen 473
Klasse 207                    – Dialekt 461
kleiner als 82                – Funktionen 464
Kombinationsoperatoren   81   – Kontrollflussfunktionen 466
Kommentare 62                 – Mathematische Funktionen 465

                                                                            645
Stichwortverzeichnis


– Systemfunktionen 468                   PHP5 23
– Verschlüsselungsfunktionen 469         – installieren 37
– Zahlenformatierungen 473               – konfigurieren 37
– Zeichenkettenfunktionen 470            phpTemple 516, 575
– Zeitfunktionen 473                     Platzhalter 96
MySQLi 477                               POP3 427
– Einfache Abfragen 485                  preg_match 100
– Komplexe Abfragen 487                  preg_match_all 100
– Referenz 491                           preg_replace 100
– Testen 478                             preg_replace_callback 100
– Verknüpfungen abfragen 489             prev 188
                                         Primärschlüssel 460
N                                        PRIMARY KEY (SQL) 451
Namenskonventionen 68                    print 58
new 208, 471, 471                        print_r 196
next 188                                 printf 60, 120
next (Iterator) 265                      – Formatstruktur 121
Nicht 82                                 Prinzip des Seitenabrufs 70
nl2br 94, 285                            private 215
nodeType 580                             professionelle Programmieren 62
Normalisierungen 457                     professionelle Programmierung 373
NOT NULL (SQL) 451                       protected 216
NULL (SQL) 451                           Protokolldatei 255
number_format 119                        public 215
                                         PUT 334
O
Objekt erzeugen 208                      Q
Objektorientierte Programmierung   204   Query Builder 446
oder 82                                  Quoted Printable 428
Operatoren 78
Optionsfelder 288                        R
ORDER BY (SQL) 454, 456                  Rangfolge 133
                                         Rasmus Lerdorf 22
P                                        RDF 569
Paradigmen 205                           Rechnen 79
Parameter 153                            rechts-assoziativ 135
– Anzahl, beliebige 156                  Reflection::export 395
– optionale 154                          Reflection-API 395
– Übergabe 159                           ReflectionClass 399
parent 211                               ReflectionExtension 405
parse_url 343                            ReflectionFunction 397
PEAR 65                                  Reflection-Informationen 395
PECL-Module 29                           Reflection-Klassen 397
Personal Home Page (Tools)/Forms         ReflectionMethod 402
    Interpreter 22                       ReflectionProperty 403
PHP                                      Regex-Maschinen 103
– Blöcke 27                              registerXPathNamespace 592
– einbetten 53                           reguläre Ausdrücke 95
– Skripte ausführen 41                   rekursive Dateiliste 263
PHP Hypertext Preprocessor 22            rekursive Funktionen 164
php.ini 38                               REQUEST_METHOD 281
PHP_OS 116                               require 169
PHP_SELF 254                             require_once 169
PHP4 23                                  reset 188

646
Stichwortverzeichnis


Ressource 244                  sqlite_last_error 504
rewind (Iterator) 265          sqlite_num_rows 504
RIGHT JOIN (SQL) 459           sqlite_query 504
Root-Server 70                 sscanf 120
RSS 568                        Standard-Wrapper 247
Rückgabewerte 151              static 213
                               statische Mitglieder 212
S                              statische Variablen 160
Schema 553                     Sticky Forms 301, 315
Schleifen 141                  str_ireplace 89
Schnittstellen 221             str_replace 89
Seitenmanagement 277           strcmp 89
SELECT (SQL) 446               stream_context_create 253
serialize 356                  Streams 416
Servervariablen 365            – Funktionen 425, 440
Serverzeit 110                 strftime 111
Sessions                       strip_tags 422
– Cookies 350                  stripos 88
– Verwaltung 358               strlen 90
set_error_handler 175          strnatcasecmp 88
setcookie 352                  strncasecmp 88
setlocale 115, 376             strpos 87
setrawcookie 352               strrpos 88
Sicherheitsprobleme 346        Structured Query Language siehe SQL
SimpleXML 581                  Sub-Select 460
– Referenz 620                 substr 92
simplexml_import_dom 587       Subtraktion 79
simplexml_load_file 585, 620   Suchen 87
simplexml_load_string 620      Suchmuster 103
Singleton 213                  SUM (SQL) 455
Smarty 516                     Suraski, Zeev 22
SMTP 427                       switch 137
SOAP 593, 595
– Body 595                     T
– Envelope 595                 Tabellen 49, 445
– Header 595                   Tabulator 85
– Projekt 608                  Teilausdrücke 79
Socket-Verbindung 441          Teilzeichenketten 90
Sonderzeichen 84, 85           Template-System 516
sort 191                       Textfelder 285
Sortieren (SQL) 456            Textknoten 563
Sortierfunktion 191            throw 178
SPAM-Versender 429             Timestamp 110
Sprache 374                    trigger_error 175
sprintf 120                    trinärer Operator 82
SQL 445                        TRUNCATE (SQL) 453
– Aggregate Funktionen 467     try 178
SQL92 461                      Typ-Informationen 223
SQLite 498
– Beispiel 500                 U
– Referenz 506                 Umgebungsvariablen 365
sqlite_close 505               und 82
sqlite_error_string 504        ungleich 82
sqlite_fetch_array 504         UNIQUE (SQL) 451

                                                                           647
Stichwortverzeichnis


Unix-Timestamp 110          X
unserialize 357             XML 550
unset 187                   – Definition 552
Unterabfragen 460           – Namensräume (SimpleXML)      588
UPDATE (SQL) 452            – Path Language 1.0. 562
url_decode 343              – Pointer Language 552
url_encode 343              XPath 562
UserLand 569                – Achsenbezeichner 564
UTC 110                     – ancestor 564
utf8_decode 580             – Ausdrücke 563
UUEncode 427                – Ausdrücke in PHP5 566
                            – child 564
V                           – descendant 564
valid (Iterator) 265        – following 565
variable Funktionen 166     – parent 564
Variablen 74                – preceding 565
– Erkennung 84              – self 564
– globale 162               xpath (SimpleXML) 588
– statische 160             XPath-Knoten 563
Vererbung 211               XQuery 553
Vergleiche 81               xsl:apply-template 556
verketten 80                xsl:call-template 559
Verknüpfungen (SQL) 457     xsl:copy-of 559
Verzeichnisobjekt dir 260   xsl:for-each 558
Verzeichniszugriff 257      xsl:if 557
Verzweigungen 132           xsl:include 560
Vorauswahl 299              xsl:number 559
Vorbereitung 24             xsl:output 560
Vorzeichen 79               xsl:sort 558
vprintf 120                 xsl:template 555
vsprintf 120                xsl:text 555
                            xsl:value-of 556
W                           XSLT 553
Wagenrücklauf 85            – Basisregeln 555
WAMP 29                     – Funktionen 560
WebDAV 334
Webserver bauen 29          Z
Webservices 593             Zeichen 90
– konsumieren 607           Zeichencodes 91
WHERE (SQL) 447             Zeichenfolgen verketten   80
while 148                   Zeichenketten 83
wordwrap 93                 – erkennen 85
Wrapper 416                 – Funktionen 86
– Eigene 425                – Operationen 86
Wrapper-Zugriff 246         Zeichenklassen 96
WSDL 597                    Zeilenvorschub 85
wsdl_cache_dir 608          Zeit 110
wsdl_cache_enabled 608      Zend-Engine 23
wsdl_cache_ttl 608          Zielgruppe 16
Wurzelknoten 563            Zugriffskontrolle 215
                            Zugriffsmethoden 227




648
Copyright
 Daten, Texte, Design und Grafiken dieses eBooks, sowie die eventuell angebotenen
                  eBook-Zusatzdaten sind urheberrechtlich geschützt.
      Dieses eBook stellen wir lediglich als Einzelplatz-Lizenz zur Verfügung!
     Jede andere Verwendung dieses eBooks oder zugehöriger Materialien und
Informationen, einschliesslich der Reproduktion, der Weitergabe, des Weitervertriebs,
      der Platzierung im Internet, in Intranets, in Extranets anderen Websites, der
  Veränderung, des Weiterverkaufs und der Veröffentlichung bedarf der schriftlichen
                              Genehmigung des Verlags.
                Bei Fragen zu diesem Thema wenden Sie sich bitte an:
                               mailto:info@pearson.de

                                  Zusatzdaten
 Möglicherweise liegt dem gedruckten Buch eine CD-ROM mit Zusatzdaten bei. Die
 Zurverfügungstellung dieser Daten auf der Website ist eine freiwillige Leistung des
                   Verlags. Der Rechtsweg ist ausgeschlossen.

                                     Hinweis
              Dieses und andere eBooks können Sie rund um die Uhr
                          und legal auf unserer Website



                               (http://www.informit.de)
                                   herunterladen

382726314 X Php5 In 14 Tagen (Ddt)

  • 1.
  • 2.
    Unser Online-Tipp für nochmehr Wissen ... ... aktuelles Fachwissen rund um die Uhr — zum Probelesen, Downloaden oder auch auf Papier. www.InformIT.de
  • 3.
    PHP PHP5 JÖRG KRAUSE eBook Die nicht autorisierte Weitergabe dieses eBooks ist eine Verletzung des Urheberrechts!
  • 4.
    Bibliografische Information DerDeutschen 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
  • 5.
    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
  • 6.
    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
  • 7.
    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
  • 8.
    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
  • 9.
    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
  • 10.
    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
  • 11.
    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
  • 12.
    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
  • 13.
    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
  • 15.
    Vorwort Sie möchten PHPlernen? 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
  • 16.
    Vorwort Unsere Zielgruppe alsLeser 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
  • 17.
    Für wen dasBuch 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
  • 18.
    Vorwort Interessierten Lesern stehtder 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
  • 19.
    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
  • 21.
  • 22.
    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
  • 23.
    Die Geschichte vonPHP 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
  • 24.
    Einführung Die OOP-Fähigkeiten sindmit 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
  • 25.
    PHP auf einenBlick 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 titleListing 1/title /head body h1Unser 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
  • 26.
    Einführung Listing 1.2: PHPerzeugt Text html head titleListing 2/title /head body h1Unser 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
  • 27.
    PHP auf einenBlick í ? 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
  • 28.
    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
  • 29.
    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
  • 30.
    Einführung Auf die Datenbanksoll 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
  • 31.
    Apache installieren Installationsstart Nach demStart 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
  • 32.
    Einführung schen localhost und127.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
  • 33.
    Apache installieren Setzen Siemit 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
  • 34.
    Einführung Abbildung 1.4: Was nicht benö- tigt wird, fliegt raus Abbildung 1.5: Während der Installation 34
  • 35.
    Apache installieren Apache testen Dererste 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
  • 36.
    Einführung Jetzt ist nochinteressant 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
  • 37.
    PHP5 installieren Unter Win9Xfü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
  • 38.
    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
  • 39.
    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
  • 40.
    Einführung ver ein primitivesStü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
  • 41.
    PHP5 testen Tragen Sienun 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
  • 42.
    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
  • 43.
    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
  • 45.
  • 46.
    Erste Schritte Die erstenSchritte 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 titleKein 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
  • 47.
    Einfache HTML-Seiten HTML-Refresh Bevor Siemit 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 titleTitel 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
  • 48.
    Erste Schritte Das Attributhref 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
  • 49.
    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
  • 50.
    Erste Schritte í tr.../tr Es folgen Tags für die Definition einer Reihe. í tdInhalt/td Innerhalb der Reihe stehen diese Tags für jede Zeile. í thKopf/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 thName/ththAnschrift/ththTelefon/th /tr tr td/tdtd/tdtd/td /tr tr td/tdtd/tdtd/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
  • 51.
    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
  • 52.
    Erste Schritte Der Codefür diese Tabelle sieht nun folgendermaßen aus: Listing 2.4: Tabelle mit verknüpften Zellen table border=1 tr thName/ththAnschrift/ththTelefon/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
  • 53.
    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
  • 54.
    Erste Schritte Das Modullä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 titlePhpStart1/title /head body ?php echo PHP beginnt hier...; ? /body /html 54
  • 55.
    Ausgaben erzeugen Innerhalb derCode-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
  • 56.
    Erste Schritte Variablen ausgeben Variablensind 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
  • 57.
    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 h3Wir lernen PHP in 14 Tagen/h3 Dieses Buch vermittelt PHP{$version} in nur 14 Tagen, von Grund auf und verständlich. BLOCK1; 57
  • 58.
    Erste Schritte echo $text; ? DerAbschnitt, 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
  • 59.
    Ausgaben erzeugen aber imGegensatz 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=_selfSeite 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
  • 60.
    Erste Schritte erhält dieLesbarkeit 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
  • 61.
    Ausgaben erzeugen F stehtfü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$XFarbausgabe mit printf/font', 61
  • 62.
    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=#CC6699Farbausgabe 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
  • 63.
    Professionelles Programmieren Warum SieKommentare 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
  • 64.
    Erste Schritte Die Wirkungist 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
  • 65.
    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
  • 66.
    Erste Schritte Auch dieZeilenlä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
  • 67.
    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
  • 68.
    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
  • 69.
    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
  • 70.
    Erste Schritte Prinzip desSeitenabrufs 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
  • 71.
    Webserver und Browser dieAdressevergabe 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
  • 72.
    Erste Schritte Die Antwortbesteht 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
  • 73.
  • 74.
    Daten verarbeiten Elementarer Bestandteileines 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
  • 75.
    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
  • 76.
    Daten verarbeiten Literale Die rechteSeite 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
  • 77.
    Konstanten In Ausdrücken werdenKonstanten 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
  • 78.
    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
  • 79.
    Rechnen und Vergleichenmit 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
  • 80.
    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 brDigit: , $digit; echo brNumber: , --$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
  • 81.
    Rechnen und Vergleichenmit 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
  • 82.
    Daten verarbeiten Die Vergleichsoperatorenfinden 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
  • 83.
    Allgemeine Aussagen zuZeichenketten 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
  • 84.
    Daten verarbeiten kette mitder 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
  • 85.
    Allgemeine Aussagen zuZeichenketten 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
  • 86.
    Daten verarbeiten Um dieTypumwandlung 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
  • 87.
    Die Zeichenkettenfunktionen anderen gibtes 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
  • 88.
    Daten verarbeiten automatische Typumwandlungfä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
  • 89.
    Die Zeichenkettenfunktionen Listing 3.5:StrNatCmp.php – Vergleichen von Zeichenketten ?php $text1 = Bild_2; $text2 = Bild_11; echo brNormal : . strcmp($text1, $text2); echo brNatü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
  • 90.
    Daten verarbeiten Hier werdengleich 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
  • 91.
    Die Zeichenkettenfunktionen benötigen dazudas 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
  • 92.
    Daten verarbeiten Zugriff aufeinzelne 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
  • 93.
    Die Zeichenkettenfunktionen Häufiges Problemist 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
  • 94.
    Daten verarbeiten ? /pre Die Funktionkennt 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
  • 95.
    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
  • 96.
    Daten verarbeiten Eine wichtigeProgrammiersprache 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
  • 97.
    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
  • 98.
    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
  • 99.
    Reguläre Ausdrücke regulären Ausdrückenumgehen 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 tdAusdruck/td tdinput type=text name=expression value=$expression//td /tr tr tdMuster/td tdinput type=text name=pattern value=$pattern//td /tr tr td/td tdinput 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
  • 100.
    Daten verarbeiten { foreach ($val as $subkey = $subval) { echo nbsp;nbsp;nbsp;Teilgruppe $subkey = $subval; } } else { echo $valbr/; } } } } 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
  • 101.
    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 enthauml;lt viele neue Funktionen. ~#~. Regulauml;re Ausdruuml;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
  • 102.
    Daten verarbeiten sen Stellendie 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 enthauml;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
  • 103.
    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
  • 104.
    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
  • 105.
    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
  • 106.
    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
  • 107.
    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
  • 108.
    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
  • 109.
    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
  • 110.
    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
  • 111.
    Datums- und Zeitfunktionen Dieaktuelle 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
  • 112.
    Daten verarbeiten Die folgendeTabelle 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
  • 113.
    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
  • 114.
    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
  • 115.
    Datums- und Zeitfunktionen Listing3.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
  • 116.
    Daten verarbeiten Die KonstantePHP_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
  • 117.
    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
  • 118.
    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
  • 119.
    Formatieren und Ausgeben Hierwird 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
  • 120.
    Daten verarbeiten 3. Zeichenfü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
  • 121.
    Formatieren und Ausgeben DieFormatstruktur 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
  • 122.
    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 /bbr Nächstes Argument, Zahl: b%d /bbr Aufgefüllte 4. Zeichenkette: b%4$'_-10s /bbr Nächstes Argument, Zahl: b%08.2f /bbr, $s2, $s2, 122
  • 123.
    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
  • 124.
    Daten verarbeiten Werte erkennen DieFunktionen 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
  • 125.
    Referenz Zeichenketten-Funktionen Insgesamt kennt PHP5knapp 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
  • 126.
    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
  • 127.
    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
  • 128.
    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
  • 129.
    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
  • 130.
    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
  • 131.
  • 132.
    Programmieren Jetzt geht esrichtig 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
  • 133.
    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
  • 134.
    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
  • 135.
    Verzweigungen Weil auch derOperator = 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
  • 136.
    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
  • 137.
    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
  • 138.
    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
  • 139.
    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
  • 140.
    Programmieren Komplexe Ausdrücke mitswitch 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
  • 141.
    Schleifen erstellen den erstenpassenden 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
  • 142.
    Programmieren Mit Hilfe vonbreak 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
  • 143.
    Schleifen erstellen Um nunmehrere 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 trtd$i/tdtdnbsp;.../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
  • 144.
    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 trtd . ($i + 1) . /tdtdnbsp;.../td/tr; Es wäre übrigens ein echter Programmierfehler, folgendes zu schreiben, wenn die Iterationsanweisung im Schleifenkopf weiter bestehen bleibt: echo trtd . ($i++) . /tdtdnbsp;.../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
  • 145.
    Schleifen erstellen Wenn Sienun 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 trtd$j/tdtdnbsp;.../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 trtd$j/tdtdnbsp;.../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
  • 146.
    Programmieren Noch ein anderesProblem 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 trtd$j/tdtdnbsp;.../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
  • 147.
    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
  • 148.
    Programmieren Die Universalschleifen do/whileund 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
  • 149.
    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
  • 150.
    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 'trtdReihe/td/tr'; } echo 'table border=1'; for ($i = 0; $i 3; $i++) { PrintTableRow(); } echo '/table'; 150
  • 151.
    Benutzerdefinierte Funktionen Der Funktionskopfist 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 'trtdReihe/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
  • 152.
    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 'trtdReihe/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
  • 153.
    Benutzerdefinierte Funktionen Beim Aufrufweisen 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
  • 154.
    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
  • 155.
    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
  • 156.
    Programmieren Beliebige Parameteranzahl Die Lösungdes 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 trtd colspan=$cells/td/tr; } 156
  • 157.
    Benutzerdefinierte Funktionen $row = 'tr'; for ($i = 0; $i $Params; $i++) { $row .= 'td' . func_get_arg($i) . '/td'; } for ($k = $i; $k $cells; $k++) { $row .= 'tdnbsp;/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: trtd 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
  • 158.
    Programmieren HTML-Tabellen benötigen immereine 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 .= $sTagnbsp;/$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
  • 159.
    Benutzerdefinierte Funktionen Die Anzahlder 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
  • 160.
    Programmieren als andere Sprachenund 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 = trtd$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
  • 161.
    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 .= $sTagnbsp;/$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
  • 162.
    Programmieren PHP5 führt dieseZeile 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
  • 163.
    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
  • 164.
    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
  • 165.
    Benutzerdefinierte Funktionen Da Stapeleine 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
  • 166.
    Programmieren 3. Prüfung, danachSelbstaufruf 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
  • 167.
    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
  • 168.
    Programmieren Die dynamischen Funktionenkann 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
  • 169.
    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
  • 170.
    Programmieren ?php include ('include/header.inc.php'); echo 'Textauf 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
  • 171.
    Modularisierung von Skripten zeichnisdie 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
  • 172.
    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
  • 173.
    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
  • 174.
    Programmieren Aber auch diekonventionelle 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
  • 175.
    Fehlerbehandlung wird eine Mindestlängevon 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
  • 176.
    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
  • 177.
    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
  • 178.
    Programmieren auftreten. Im Übrigenist 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
  • 179.
    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
  • 180.
    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
  • 181.
    Daten mit Arrays verarbeiten 5
  • 182.
    Daten mit Arraysverarbeiten 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
  • 183.
    Arrays erstellen undbefü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
  • 184.
    Daten mit Arraysverarbeiten 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
  • 185.
    Arrays erstellen undbefü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
  • 186.
    Daten mit Arraysverarbeiten 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
  • 187.
    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
  • 188.
    Daten mit Arraysverarbeiten 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
  • 189.
    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
  • 190.
    Daten mit Arraysverarbeiten 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
  • 191.
    Arrays manipulieren Arrayfunktionen Einige derersten 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
  • 192.
    Daten mit Arraysverarbeiten '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
  • 193.
    Arrays ausgeben Arraydaten manipulieren Richtigspannend 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
  • 194.
    Daten mit Arraysverarbeiten 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
  • 195.
    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
  • 196.
    Daten mit Arraysverarbeiten 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/bbr/; } $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
  • 197.
    Arrays ausgeben mierung istder 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
  • 198.
    Daten mit Arraysverarbeiten 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
  • 199.
    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
  • 200.
    Daten mit Arraysverarbeiten 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
  • 201.
    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
  • 202.
    Daten mit Arraysverarbeiten 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
  • 203.
  • 204.
    Objektorientierte Programmierung PHP5 hatin 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
  • 205.
    Warum objektorientiert programmieren? dieSprachen 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
  • 206.
    Objektorientierte Programmierung Bei imperativenSprachen 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
  • 207.
    Syntax der Klassen WennKlassen 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
  • 208.
    Objektorientierte Programmierung Definiert wurdehier 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
  • 209.
    Syntax der Klassen Beider 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
  • 210.
    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
  • 211.
    Syntax der Klassen diesbenutzt, 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
  • 212.
    Objektorientierte Programmierung direkt benutztwird. 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
  • 213.
    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
  • 214.
    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
  • 215.
    Syntax der Klassen zuprogrammieren. 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
  • 216.
    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
  • 217.
    Syntax der Klassen privatenicht, 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
  • 218.
    Objektorientierte Programmierung Um denBenutzer 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
  • 219.
    Syntax der Klassen Diesentspricht 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
  • 220.
    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
  • 221.
    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
  • 222.
    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
  • 223.
    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
  • 224.
    Objektorientierte Programmierung Bei kleinerenProjekten 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
  • 225.
    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
  • 226.
    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
  • 227.
    Schnittstellen zur Außenwelt Esist 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
  • 228.
    Objektorientierte Programmierung Das folgendeBeispiel 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
  • 229.
    Schnittstellen zur Außenwelt Deruniverselle 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
  • 230.
    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
  • 231.
    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
  • 232.
    Objektorientierte Programmierung spezifische Ausnahmeausgelö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
  • 233.
    Analyse und Kontrollevon 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
  • 234.
    Objektorientierte Programmierung Zeichenkettenform: __toString() Generellgibt 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
  • 235.
    Analyse und Kontrollevon 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
  • 236.
    Objektorientierte Programmierung ist dasnicht 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
  • 237.
    Analyse und Kontrollevon 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
  • 238.
    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
  • 239.
    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
  • 240.
    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
  • 241.
    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
  • 242.
    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
  • 243.
    Das Dateisystem entdecken 7
  • 244.
    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
  • 245.
    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
  • 246.
    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
  • 247.
    Praktischer Dateizugriff wrapper://pfad/pfad/pfad/datei.extension Der Standard-Wrapperfü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
  • 248.
    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
  • 249.
    Praktischer Dateizugriff umfassen, dermit 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
  • 250.
    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
  • 251.
    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
  • 252.
    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
  • 253.
    Praktischer Dateizugriff and einender 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
  • 254.
    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
  • 255.
    Praktischer Dateizugriff wird nochmit 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
  • 256.
    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
  • 257.
    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 $filenamebr; } } 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
  • 258.
    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
  • 259.
    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
  • 260.
    Das Dateisystem entdecken DasVerzeichnisobjekt 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; $istrlen($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
  • 261.
    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 $filebr; } } $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
  • 262.
    Das Dateisystem entdecken DieServervariable 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
  • 263.
    Verzeichniszugriff Die Methode readgibt 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
  • 264.
    Das Dateisystem entdecken Listing7.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
  • 265.
    Verzeichniszugriff Die Funktion rglobselbst 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
  • 266.
    Das Dateisystem entdecken EinigeIterator-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
  • 267.
    Verzeichniszugriff Das folgende Beispielzeigt, 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
  • 268.
    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
  • 269.
    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
  • 270.
    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
  • 271.
    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
  • 272.
    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
  • 273.
    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
  • 275.
    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
  • 277.
  • 278.
    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
  • 279.
    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
  • 280.
    Formular- und Seitenmanagement Danebenkö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
  • 281.
    Auswerten der Datenaus 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
  • 282.
    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: brb$ref/bbran brb$srv/bhr; if (strcmp($srv, $ref) == 0) { echo Korrekter Aufruf; } else { echo Falscher Aufruf; } } else { echo 'Erster Aufruf der Seite'; 282
  • 283.
    Auswerten der Datenaus 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_NAMEPHP_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
  • 284.
    Formular- und Seitenmanagement Zustandsabfragefü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 tdName:/td tdinput type=text name=Loginname//td /tr tr td Kennwort:/td tdinput type=text name=Password/ 284
  • 285.
    Auswerten der Datenaus 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
  • 286.
    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 tdName:/td tdinput type=text name=Loginname//td /tr tr td Ihr Anliegen:/td td textarea name=Subject cols=30 rows=5/textarea /td /tr /table 286
  • 287.
    Auswerten der Datenaus 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
  • 288.
    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
  • 289.
    Auswerten der Datenaus 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
  • 290.
    Formular- und Seitenmanagement td valign=topWelche 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
  • 291.
    Auswerten der Datenaus 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
  • 292.
    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
  • 293.
    Auswerten der Datenaus 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=topWelche 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
  • 294.
    Formular- und Seitenmanagement JedesKontrollkä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
  • 295.
    Auswerten der Datenaus 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
  • 296.
    Formular- und Seitenmanagement Listing8.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=topWelche Sprachen kennen Sie?/td td valign=top select name=Language size=1s option value=PerlPerl/option option value=PHPPHP/option option value=VBScriptVBScript/option option value=C++C++/option option value=C#C#/option option value=DelphiDelphi/option option value=FortranFortran/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
  • 297.
    Auswerten der Datenaus 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
  • 298.
    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=topWelche 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
  • 299.
    Auswerten der Datenaus 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
  • 300.
    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=topWelche 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
  • 301.
    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
  • 302.
    Formular- und Seitenmanagement Grundlageanderer 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 tdName:/td tdinput type=text name=Loginname//td 302
  • 303.
    Professionelle Formulare und»Sticky Forms« /tr tr td Kennwort:/td tdinput 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
  • 304.
    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
  • 305.
    Professionelle Formulare und»Sticky Forms« Listing 8.10: formjavascriptalert.php – JavaScript per PHP steuern html head titleFormulare/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
  • 306.
    Formular- und Seitenmanagement DieFunktion 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
  • 307.
    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 titleFormulare/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
  • 308.
    Formular- und Seitenmanagement } /script /head body ?php $action = $_SERVER['PHP_SELF']; if ($_SERVER['REQUEST_METHOD']) { if (empty($_POST['Loginname'])) { echo Login Name nicht ausgefülltbr; } if (empty($_POST['Password'])) { echo Kennwort nicht ausgefülltbr; } } ? form action=?=$action? method=post name=logon table tr tdName:/td tdinput type=text name=Loginname//td td valign=topspan 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=topspan 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
  • 309.
    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
  • 310.
    Formular- und Seitenmanagement Wasgenau 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 titleFormulare/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
  • 311.
    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 tdName:/td tdinput type=text name=Loginname//td td valign=topspan style=color:red*/span/td td valign=topspan 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=topspan style=color:red*/span/td td valign=topspan id=errPass 311
  • 312.
    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
  • 313.
    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 titleFormulare/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
  • 314.
    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 tdName:/td tdinput type=text name=Loginname//td td valign=topspan style=color:red*/span/td td valign=topspan 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=topspan style=color:red*/span/td td valign=topspan id=errPass style=color:red;visibility:?=$displayPass? Kennwort wurde nicht angegeben/span/td /tr /table br/ 314
  • 315.
    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
  • 316.
    Formular- und Seitenmanagement sinnvolleinsetzbar 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
  • 317.
    Professionelle Formulare und»Sticky Forms« } ? form action=?=$action? method=post name=logon table tr tdName:/td tdinput type=text name=Loginname value=?=$Loginname?//td td valign=topspan style=color:red*/span/td td valign=topspan 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=topspan style=color:red*/span/td td valign=topspan 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
  • 318.
    Formular- und Seitenmanagement DieKombination 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
  • 319.
    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=topWelche 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
  • 320.
    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
  • 321.
    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=topWelche Sprachen kennen Sie?/td td valign=top 321
  • 322.
    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
  • 323.
    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=topWelche Sprachen kennen Sie?/td 323
  • 324.
    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
  • 325.
    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
  • 326.
    Formular- und Seitenmanagement DieDefinition 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 titleFormulare/title style * { font-family: Verdana; } .activeNumber 326
  • 327.
    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
  • 328.
    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
  • 329.
    Professionelle Formulare und»Sticky Forms« td valign=topName/td td valign=top input type=text name=Name value=?=$fldName?/ /td /tr tr td valign=topAnschrift/td td valign=top input type=text name=Address value=?=$fldAddress?/ /td /tr tr td valign=topPLZ-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=topAGB'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=topAnmeldename/td td valign=top input type=text name=LogonName value=?=$fldLogonName?/ /td /tr tr td valign=topKennwort/td td valign=top 329
  • 330.
    Formular- und Seitenmanagement input type=password name=Password value=?=$fldPassword?/ /td /tr /table table width=500 class=?= ($currentPage==4) ? 'activeTable' : 'inactiveTable' ? tr td valign=topNewsletter/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=topKennwort/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
  • 331.
    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
  • 332.
    Formular- und Seitenmanagement DasSkript 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
  • 333.
    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
  • 334.
    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
  • 335.
    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
  • 336.
    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 liLokaler Name: {$_FILES['MyFile']['name']}/li liMIME-Typ: {$_FILES['MyFile']['type']}/li liGröße: {$_FILES['MyFile']['size']} Byte/li liTemporärname: {$tmp}/li liFehler: {$error}/li liLokaler Name: {$_FILES['MyFile']['name']}/li /ul FILEDATA; 336
  • 337.
    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/abr', $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 tdIhre 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
  • 338.
    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
  • 339.
    Dateien hochladen Die Funktiongibt 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/abr', $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
  • 340.
    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
  • 341.
    Von der Seitezum 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
  • 342.
    Formular- und Seitenmanagement Bevores richtig losgeht, sollten Sie den Aufbau des URL komplett kennen. Hier ein Muster: http://www.comzept.de/pfad/index.php?val1=wertval%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
  • 343.
    Von der Seitezum 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=wertval%202=wert#hash; $p = parse_url($url); foreach ($p as $name = $content) { echo b$name:/b $contentbr; } ? 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
  • 344.
    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=wertval%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 $contentbr; } } 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
  • 345.
    Von der Seitezum 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=Namepara2=Die StraßeKlick 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
  • 346.
    Formular- und Seitenmanagement ? a href=?=$action??title=StartseiteStartseite/abr/ a href=?=$action??title=ImpressumImpressum/abr/ a href=?=$action??title=ProdukteProdukte/abr/ a href=?=$action??title=AGBsAGBs/abr/ a href=?=$action??title=AnmeldungAnmeldung/abr/ 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
  • 347.
    Von der Seitezum 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/abr/', $action, $title, md5($title)); } ? Interessant ist hier eigentlich nur der erzeugte Quelltext: body a href=get_navigationtarget.php?t=c84369cb124912750ac1c1982a7b7e05 Startseite/abr/ 347
  • 348.
    Formular- und Seitenmanagement ahref=get_navigationtarget.php?t=fd2648427fd682bc2a48d7cf5668b3db Impressum/abr/ a href=get_navigationtarget.php?t=3d87a30682721340bdfe22c3a1dfae28 Produkte/abr/ a href=get_navigationtarget.php?t=2bbf0f4a652ef7ccd201ca9ab89032f8 AGBs/abr/ a href=get_navigationtarget.php?t=05d316097eb9d1289765b00d23b6aba8 Anmeldung/abr/ /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
  • 349.
    Cookies und Sessions Grenzendes 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
  • 350.
    Formular- und Seitenmanagement amEnde 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
  • 351.
    Cookies und Sessions Nichtsdestotrotzsind 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
  • 352.
    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
  • 353.
    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=VerdanaVerdana/option option value=ArialArial/option option value=TahomaTahoma/option option value=Times RomanTimes Roman/option /select select name=size size=1 option value=11/option option value=22/option option value=33/option option value=44/option option value=55/option option value=66/option option value=77/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
  • 354.
    Formular- und Seitenmanagement Damitist 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
  • 355.
    Cookies und Sessions option value=-- Ihre Auswahl --/option option value=VerdanaVerdana/option option value=ArialArial/option option value=TahomaTahoma/option option value=Times RomanTimes Roman/option /select select name=size size=1 option value=-- Ihre Auswahl --/option option value=11/option option value=22/option option value=33/option option value=44/option option value=55/option option value=66/option option value=77/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
  • 356.
    Formular- und Seitenmanagement DasSkript 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
  • 357.
    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
  • 358.
    Formular- und Seitenmanagement dieCookies 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
  • 359.
    Cookies und Sessions dereinen 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
  • 360.
    Formular- und Seitenmanagement DieSession-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
  • 361.
    Cookies und Sessions ÄndernSie 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
  • 362.
    Formular- und Seitenmanagement { $links .= sprintf('a href=%1$s?t=%2$s%2$s/abr/', $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 tdName:/td td input type=text name=Logon/ /td /tr tr 362
  • 363.
    Cookies und Sessions tdKennwort:/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
  • 364.
    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
  • 365.
    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
  • 366.
    Formular- und Seitenmanagement Nameder 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
  • 367.
    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
  • 368.
    Formular- und Seitenmanagement Nameder 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
  • 369.
    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. session_unset Löscht alle Session-Variablen. session_write_close Speichert alle Session-Daten und beendet die Session. Tabelle 8.8: Funktionen der Session-Verwaltung (Forts.) Initialisierungseintrag Beschreibung session.name Der Name der Session. Dieser Wert wird für den Url-Para- meter und das Cookie verwendet. Der Standardwert ist PHPSESSID. Tabelle 8.9: Konfiguration der Session-Verwaltung 369
  • 370.
    Formular- und Seitenmanagement Initialisierungseintrag Beschreibung session.auto_start Wenn aktiviert, wird bei einer neuen Anfrage eine neue Session gestartet, nicht erst beim Aufruf von session_start. Standardmäßig deaktiviert (0), zum Aktivieren auf 1 setzen. session.serialize_handler Name der Methode, mit der die Daten serialisiert werden. Standardwert ist »php«, Alternative ist »wddx«. session.gc_probability Regelt die Wahrscheinlichkeit, mit der die Aufräumroutine alte Session-Daten beseitigt. Standardwert ist 1. session.gc_divisor Regelt die Wahrscheinlichkeit, mit der die Aufräumroutine alte Session-Daten beseitigt, im Verbund mit »gc_probability«. Die finale Wahrscheinlichkeit beträgt gc_probability/gc_divisor. Der Standardwert ist 100, dass heißt die Aufräumroutine startet mit einer Wahrscheinlich- keit von 1%. session.gc_maxlifetime Lebensdauer einer Session in Sekunden. Der Standardwert beträgt 1440 (24 Minuten). session.referer_check Zeichenkette, die im Referer (Quelle des HTTP-Abrufs) enthalten sein muss. Wenn aktiviert, verhindert man damit den Einsprung in eine Session von außerhalb. Standard- mäßig nicht verwendet. session.entropy_file Pfad einer Quelldatei, die beim Erzeugen der Session-ID benutzt wird. Standardmäßig nicht verwendet. session.entropy_length Anzahl der Bytes, die aus der Entropie-Datei gelesen wer- den. Standardwert ist 0. session.use_cookies Erlaubt Cookies. Standardwert ist 1 (aktiviert). session.use_only_cookies Erzwingt (!) Cookies. Cookies werden auch so verwendet, aber mit dieser Option kann man alternative Wege aus Kompatibilitätsgründen verhindern. Standardmäßig nicht aktiviert (0). session.cookie_lifetime Lebensdauer des Cookies. Standardwert ist 0, was typische Session-Cookies erzeugt. session.cookie_path Pfad-Angabe des Session-Cookies. Tabelle 8.9: Konfiguration der Session-Verwaltung (Forts.) 370
  • 371.
    Referenz Initialisierungseintrag Beschreibung session.cookie_domain Domain-Angabe des Session-Cookies. session.cookie_secure Erzwingt die Verwendung sicherer Verbindungen für das Cookie. session.cache_limiter Methode der Cacheverwaltung. Standardwert ist »noca- che« (nicht verwendet). session.cache_expire Verfallszeitpunkt des Cache in Minuten. Standardwert ist 180. session.use_trans_sid Aktiviert die transparente SID-Umsetzung, bei der ver- steckte Felder und URL-Erweiterung zur Übergabe der Session-ID verwendet werden. session.bug_compat_42 Erzeugt eine Warnung, wenn versucht wird, mit einem Trick globale Variablen mit Session-Daten zu erzeugen, obwohl dies durch andere Einstellungen verboten ist und zusätzlich bug_compat_warn aktiviert wurde. session.bug_compat_warn Erzeugt eine Warnung, wenn versucht wird, mit einem Trick globale Variablen mit Session-Daten zu erzeugen, obwohl dies durch andere Einstellungen verboten ist. session.hash_function Prüfsummenfunktion für die Session-ID. 0 = MD5 (128 Bit), 1 = SHA-1 (160 Bit). Standard ist MD5. session.hash_bits_per_ Gespeicherte Bits pro Zeichen in der Session-ID. Kann 4, 5 character oder 6 sein. 4 ist der Standardwert. url_rewriter.tags Tags, die zur Aufnahme der Session-ID umgeschrieben werden dürfen. Tabelle 8.9: Konfiguration der Session-Verwaltung (Forts.) 371
  • 372.
    Formular- und Seitenmanagement 8.8 Kontrollfragen 1. Warum sollten unbedingt immer »Sticky Forms« verwendet werden? 2. Welche Funktionen unterstützen das Hochladen von Dateien? Was ist beim Auf- bau des Formulars zu beachten? 3. Welche Methoden verwenden Sessions, um die Session-Daten zu speichern? 4. Wie können Cookies missbraucht werden? 372
  • 373.
  • 374.
    Professionelle Programmierung 9.1 Mehrsprachige Webseiten Immer häufiger wird gefordert, Webseiten mehrsprachig anzubieten. Es ist gerade für kleine Unternehmen ein Segen, Kunden überall auf der Welt ansprechen zu können. Mit Deutsch allein kommt man dabei aber nicht weit, mit Englisch wie- derum kann man hierzulande nicht viel erreichen. Es ist deshalb oft notwendig, Websites in mehreren Sprachen anzubieten. Damit der Aufwand nicht ins Uner- messliche steigt, gibt es verschiedene Techniken, Gestaltung und Inhalt zu tren- nen, sodass die aufwändige Grafik nur einmal existiert, die variablen Inhalte dagegen getrennt gehalten werden. Vor der Auswahl der Sprache steht jedoch die Erkennung der Daten durch den Benutzer. Dies erfolgt durch Auswertung der vom Browser übermittelten Daten. Browserdaten erkennen Der Browser überträgt verschiedene Informationen an den Server, die sich dann in PHP-Skripten auswerten lassen. Neben Betriebssystem, Name des Browsers und verschiedenen Angaben zu akzeptierten Daten gehört auch die bevorzugte Spra- che dazu. Sprache im Browser einrichten Jeder Benutzer sollte in seinem Browser die bevorzugte Sprache einrichten, damit Skripte, wie die nachfolgend vorgestellten, auch funktionieren. Die entsprechen- den Menükommandos zeigt die folgende Liste: í Netscape/Mozilla: Bearbeiten | Einstellungen | Navigator | Sprachen í Internet Explorer: Extras | Internetoptionen | Allgemein | Sprachen í Opera: Datei | Einstellungen | Sprachen í Tango: Sprache | Spracheinstellungen í Lynx 2.7: O (Options) | G (lanGuage) Nach dieser Angabe ist natürlich interessant zu wissen, was davon in PHP5 ankommt. Benutzt wird eine Servervariable aus dem Array $_SERVER: HTTP_ACCEPT_LANGUAGE. 374
  • 375.
    Mehrsprachige Webseiten Abbildung 9.1: Spracheinstellungen im Internet Explorer Diese enthält eine Liste der akzeptierten Sprachen mit einer Angabe der Wertig- keit. Die erste Sprache ist immer die bevorzugte, der Rest teilt sich mit abnehmen- der Wichtung in Relation zu 1. Das »q« steht für »Quality«. Die in der letzten Abbildung gezeigte Angabe sendet der Browser folgendermaßen: de,en-us;q=0.7,fr;q=0.3 Das bedeutet, die Hauptsprache ist Deutsch, Englisch wird mit 0,7 gegenüber Französisch mit 0,3 bevorzugt, wobei 1 = ideal wäre. Die meisten Browser legen die Werte selbst fest, sodass hier wenig Wahl besteht. Tatsächlich wäre es auch möglich, folgende Angabe zu machen: de,en-us;q=0.8,e-gb;q=0.7 Hier wird mitgeteilt, dass Deutsch ideal ist, amerikanisches Englisch akzeptiert wird und alternativ auch britisches Englisch gern gelesen wird. Sie sollten als Programmierer nicht darauf vertrauen, dass irgendwer mehr als die Standardsprache halbwegs sinnvoll eingestellt hat. Die fol- genden Anwendungen werten deshalb auch nur diesen Teil aus. Sprache auswerten Das Auswerten der Sprache ist nun recht einfach. Man muss nur die Servervari- able abfragen und den ersten Teil, bis zum Komma, abtrennen: 375
  • 376.
    Professionelle Programmierung Listing 9.1:browserlang.php – Sprachakzeptanz abfragen und Hauptsprache ermitteln ?php $lang = $_SERVER['HTTP_ACCEPT_LANGUAGE']; echo Sprachinfo des Browsers: $langbr; $l = str_replace(strstr($lang, ','), '', $lang); echo Erkannten Hauptsprache: b$l/b; ? Das Skript nutzt Zeichenkettenfunktionen, um den Anfang der Sprachinformation und damit die Hauptsprache herauszufinden. Für die Auswertung der übrigen Informationen bietet sich der Zugriff auf reguläre Ausdrücke oder die Zerlegung mit explode an. Abbildung 9.2: Sprachinfo und extrahierte Hauptsprache Steht die Sprache fest, muss nur noch entsprechend darauf reagiert werden. Lokalisierung und Formatierung von Zeichen Die Lokalisierung basiert meist auf der Anwendung der Funktion setlocale, die das Verhalten anderer Funktionen beeinflusst. Allerdings ist der Umgang damit recht tückisch. Leider hat sich dies mit PHP5 nicht gebessert, was umso unverständ- licher ist, als dass neuere Programmierumgebungen wie .NET auch unter Windows den gängigen Standards folgen und es keinen Grund gibt, dies nicht zu adaptieren. Generell gilt folgende Regel für Linux-Systeme: í Das Basisformat ist »Sprache_Land«, also beispielsweise »de_DE« oder »de_AT« í Braucht man bestimmte Sonderzeichen einer Sprache, die bei der Ausgabe angezeigt werden sollen, kann man eine Codeseite angeben: »kr_KR.949«. Windows-Systeme erwarten statt der Ländercodes Sprachnamen, abgekürzt oder ausgeschrieben: Das Basisformat ist »Germany« oder »ge« bzw. »German_Germany« oder »German_Austria«. Es ist also in den allermeisten Fällen erforderlich, das vom Browser gesendete Sprachkürzel »de« in die passende Form umzuwandeln. 376
  • 377.
    Mehrsprachige Webseiten Die Funktionsetlocale anwenden Alle Formate, die printf und verwandte Funktionen benutzen, reagieren auf die Einstellungen mit setlocale. Damit ist es möglich, die korrekten Formatierungen für Zahlen zu erhalten, also Komma als Dezimaltrennzeichen in deutschen Tex- ten usw. Außerdem lassen sich Zeitangaben bei der Ausgabe mit strftime in die passende Sprache setzen. Für die Formatierung von Zahlen muss setlocale entweder mit der Konstanten LC_ALL oder LC_NUMERIC bedacht werden: setlocale(LC_NUMERIC, German_Germany); Die Angabe muss vor der ersten Ausgabe mit einer der printf-Funktionen erfol- gen. Um die Rückstellung der vorherigen Angabe zu ermöglichen, können Sie den Rückgabewert von setlocale abfragen. Die folgende Tabelle zeigt alle Konstanten, die setlocale benutzt: Konstante Bedeutung Betroffene Funktion LC_ALL Beeinflusst alle Formate printf LC_COLLATE Beeinflusst Zeichenkettenvergleiche strcoll LC_CTYPE Klassifizierung von Zeichen strtoupper LC_MONETARY Betrifft Währungsangaben localeconv LC_NUMERIC Betrifft Zahlenangaben localeconv LC_TIME Beeinflusst Datums- und Zeitangaben strftime Tabelle 9.1: Konstanten, die setlocale benutzt strcoll entspricht der Funktion strcmp mit dem Unterschied, dass die Lokalisierung berücksichtig wird. strcmp ignoriert diese. Die Anwendung der Funktion kann auch auf Bedingungen des Betriebssystems abgestimmt werden. Wenn der zweite Parameter leer ist (nicht fehlend, sondern eine leere Zeichenkette), dann wird versucht, eine Umgebungsvariable zu lesen, deren Namen der Konstanten entspricht. Der folgende Ausdruck liest die Umge- bungsvariable LC_NUMERIC und setzt setlocale entsprechend: 377
  • 378.
    Professionelle Programmierung setlocale(LC_NUMERIC, ); Cleverist es auch, eine Fallbackmöglichkeit zu schaffen. Wie anfangs bereits beschrieben, sind auf den verschiedenen Betriebssystemen die Lokalisierungszei- chenfolgen zu beachten. Deshalb kann an dieser Stelle auch ein Array angegeben werden: setlocale(LC_NUMERIC, array(de, de_DE, German_Germany)); Es gibt natürlich auch die Möglichkeit, hier die Sprache zu wechseln, um unter allen Umständen ein reproduzierbares Ergebnis zu erzielen. Das Format der aktuellen Lokalisierung ermitteln Um festzustellen, wie PHP5 intern arbeitet und wie sich die Parameter der Lokali- sierung auswirken, gibt es die Funktion localeconv. Sie gibt ein Array zurück, das die entsprechenden Angaben enthält. Die folgende Abbildung zeigt die Ausgabe für die Lokalisierung Deutsch: Abbildung 9.3: Ausgabe der Lokalisie- rungszeichen für Deutsch/Deutschland (links) und Deutsch/ Schweiz (rechts) Die Angaben können hilfreich sein, um den Platzbedarf von Zeichenketten zu berechnen oder die Wirkung der Lokalisierung zu überwachen. Um die Tabellen zu erstellen, wurde folgendes Skript verwendet: 378
  • 379.
    Dynamisch Bilder erzeugen Listing9.2: localeconv.php – Lokalisierungsangaben ausgeben table border=1 style=font-family:Verdana; font-weight:bold ?php setlocale(LC_ALL, German_Switzerland); $lc = localeconv(); foreach ($lc as $key = $val) { echo trtd$key/td; echo td$val/td/tr; } ? /table 9.2 Dynamisch Bilder erzeugen Das dynamische Erzeugen von Bildern gehört mit zu den am häufigsten diskutier- ten Möglichkeiten von PHP. Die mit PHP5 verfügbare neue Grafikbibliothek GD2 stellt neue Funktionen bereit, um professionell wirkende Grafiken dyna- misch zu erstellen. Prinzip Um das Prinzip der Erzeugung dynamischer Bilder zu verstehen, muss man sich noch mal den Ablauf des Seitenaufbaus im Browser in Erinnerung rufen. Nach dem Laden der HTML-Seite analysiert der Browser den Inhalt und beginnt mit der Darstellung. Findet er ein Bild, leitet er für die angegebene Bildquelle eine weitere asynchrone1 HTTP-Anforderung ein. Der dann zurückgegebene binäre Datenstrom wird wiederum interpretiert und, ins entsprechende Bildformat zurückgewandelt, zur Darstellung verwendet. Bei dem Vorgang an sich ist es dem Browser egal, woher das Bild kommt. Er erwartet letztlich nur einen simplen Datenstrom. Den kann man erzeugen, indem man auf dem Server ein Bild ablegt und dem Webserver die Auslieferung überlässt. Es ist aber auch möglich, die Arbeit von einem Skript erledigen zu lassen, das die Daten fachgerecht generiert. Als Quelle wird dann einfach ein PHP-Skript genommen: img src=imagegen.php/ 1 Deswegen erscheinen die Bilder manchmal erst viel später. 379
  • 380.
    Professionelle Programmierung Dem Skriptkann man selbstverständlich auch GET-Parameter übergeben, um das Verhalten zu steuern: img src=imagegen.php?name=Joerg%20Krause/ Aufbau eines bilderzeugenden Skripts Für ein bilderzeugendes Skript muss man mehrere Dinge beachten: í Es dürfen ausschließlich Bilddaten erzeugt werden. Schon ein einziges Leer- zeichen zerstört das Bild. í Es müssen die passenden Kopfzeilen erzeugt werden, damit der Browser weiß, welcher Bildtyp zu den Binärdaten passt. í Das Skript sollte relativ schnell und effizient sein, sonst verzögert sich der Bild- aufbau erheblich. Die passenden Kopfzeilen werden mit der Funktion header erzeugt. Die Ausgaben selbst können wie üblich mit printf oder echo gesendet werden. Für header wird die Angabe des Inhaltstyps (Content-type) benötigt. Der konkrete Typ richtet sich nach der Bildart. Möglich sind folgende Werte: í image/gif í image/jpg í image/png í image/wbmp (für WAP) Der Aufruf der Funktion sieht dann beispielsweise für ein PNG-Bild folgender- maßen aus: header ('Content-type: image/png'); Es ist übrigens gut möglich, zusätzlich noch Cookies an das Bild anzu- hängen, was oft bei Werbebannern gemacht wird. Werbebanner sind letztlich auch nur Bilder (mal von Flash-Bannern abgesehen) und lassen sich sehr gut dynamisch erzeugen. Typische Probleme Gleich vorab soll auf einige typische Probleme hingewiesen werden. Da der Brow- ser ein Bild erwatet, wird er eine Fehlerausgabe des Skripts nicht interpretieren 380
  • 381.
    Dynamisch Bilder erzeugen können.Der Internet Explorer zeigt beispielsweise ein Ersatzbild mit einem roten Kreuz – aber eben keine Fehlermeldung. Hierfür gibt es mehrere Lösungen: í Erzeugen Sie im Fehlerfall andere Kopfzeilen, die die Ausgabe von Text erlau- ben. í Geben Sie ein (vorher korrekt programmiertes) Ersatzbild aus, das den Fehler anzeigt. í Verwenden Sie try/catch und den @-Operator, um Fehler abzufangen und die Fehlertexte in eine Protokolldatei zu schreiben. Allen Varianten gemeinsam ist, dass die Fehlerausgabe mehr Aufwand verursacht als bei normalen Skripten. Der erreichte Effekt einer dynamischen Bildausgabe ist allerdings oft diesen Mehraufwand wert. Einführung in die Grafikbibliothek GD2 Vor den ersten Versuchen mit der Grafikbibliothek GD2 sollten Sie testen, ob die Erweiterung aktiviert wurde und zur Verfügung steht. Der Aufruf von phpinfo ist der ideale Platz dafür. Abbildung 9.4: GD2 wurde erfolg- reich aktiviert Prinzip der Bilderzeugung Die Bilderzeugung erfolgt in zwei Stufen. Zuerst muss eine Zeichenfläche erstellt werden, auf der anschließend alle Zeichen- und Textoperationen stattfinden. Dazu dient die Funktion imagecreate: $img = imagecreate(480, 80); 381
  • 382.
    Professionelle Programmierung Der Bildtyp,also GIF oder PNG, muss hier noch nicht festgelegt werden. Das Zei- chenmodul arbeitet generell mit einem internen Format, aus dem alle anderen Formate verlustfrei erstellt werden können. Der Rückgabewert $img enthält nun ein Handle auf die interne Abbildung des Bil- des. Alle folgenden Funktionen nutzen dieses Handle zum Zugriff. Sind alle Bildoperationen fertig, wird das Bild erzeugt und an den Browser gesen- det. Dazu dienen die Funktion imagegif, imagepng, imagejpeg usw.: imagegif($img); Der Suffix bestimmt dabei den Bildtyp, der sich je nach Applikation unterscheiden kann. Alternativ zur Erzeugung eines neuen Bilds kann die Bilddatei auf einem bereits existierenden Bild basieren. Dazu dienen die Funktionen imagecreatefromgif, imagecreatefrompng, imagecreatefromjpeg usw. $img = imagecreatefromgif('pfad/zur/datei.gif'); Die Bildgröße wird dabei zwangsläufig von der Quelldatei bestimmt. Liegt das Bild vor, kann man nun entweder Text darauf schreiben oder mit Bildfunktionen malen. Die Vielfalt der Funktionen lässt eine ausführliche Betrachtung in diesem Rahmen nicht zu, deshalb sollen zwei Beispiele das Prinzip demonstrieren. Textausgabe Die Textausgabe ist deshalb ein schwieriges Thema, weil der Text auf der Fläche platziert werden muss, was nicht einfach ist, wenn die Menge nicht passt. Hier muss gegebenenfalls gerechnet werden. Ausgangspunkt ist die Funktion imagettfbbox, die die voraussichtlichen Maße des Textes in Abhängigkeit vom Font (Schriftart) und der verlangten Größe ermittelt. Die Funktion gibt ein Array zurück, dessen Elemente die entsprechenden Angaben enthalten: Index Beschreibung 0 Links oben, X 1 Links unten, Y 2 Rechts unten, X Tabelle 9.2: Bedeutung der Elemente des mit imagettfbbox erzeugten Arrays 382
  • 383.
    Dynamisch Bilder erzeugen Index Beschreibung 3 Rechts unten, Y 4 Oben rechts, X 5 Oben rechts, Y 6 Oben links, X 7 Oben links, Y Tabelle 9.2: Bedeutung der Elemente des mit imagettfbbox erzeugten Arrays (Forts.) Die Funktion kann einen Winkel einbeziehen, um den der Text gedreht erscheint. Die Koordinaten, die den Platzbedarf angeben, sind jedoch immer abso- lut in den Ecken eines virtuellen Rechtecks, das im Fall einer Drehung entspre- chend groß ist, um den Text aufzunehmen. Aus den Daten lässt sich nun entnehmen, ob der Text passt und man kann entspre- chend reagieren, um den Text gegebenenfalls passend zu machen. Stimmt alles, erfolgt die eigentliche Ausgabe mit imagettftext. Benötigt werden folgende Angaben: í Position der linken, oberen Ecke in zwei Werten für x und y. í Winkel, um den der Text gedreht erscheint. í Schriftart, in Form eines Pfades zu einer TrueType-Datei. í Schrifthöhe in Punkt (1 Punkt entspricht 1/72«). í Die Farbe, die benutzt werden soll. í Der Text, der dargestellt wird. Die Schriftart wird als Pfad zu einer TrueType-Datei angegeben. Achten Sie hier darauf, dass nur Dateien verwendet werden, zu denen auch die erforderlichen Rechte vorhanden sind. Das Herauskopieren aus anderen Anwendungen funktio- niert zwar meist, ist aber nur selten legal. Frei Fonts sind im Internet leicht zu fin- den, wenngleich diese auch eine schwankende Qualität haben. Wer seinen Server unter Windows betreibt hat es da leichter, denn er kann die typischen Windows- Fonts nutzen, beispielsweise Verdana oder Tahoma. Die Farbe wird als RGB-Wert erwartet, der vorher in der Farbtabelle der Bildbasis registriert werden muss. Dabei geht die GD2-Bibliothek so vor, dass die erste Farb- zuweisung immer die Hintergrundfarbe festlegt, während alle Folgenden der Farb- 383
  • 384.
    Professionelle Programmierung tabelle zugeordnetwerden. Jede Zuweisung gibt ein Handle auf die Farbe zurück und dieses Handle ist zu verwenden. Im folgenden Fragment ist $img das Handle auf das Bild und $handle_black das Handle auf die Farbe (nach der Ausführung). Die definierte Farbe ist schwarz: $handle_black = imagecolorallocate($img, 0, 0, 0); Die drei Werte stehen für die Farbkanäle Rot, Grün und Blau. Erlaubt sind Anga- ben zwischen 0 (kein Anteil der Farbe) und 255 (voller Anteil). Um hexadezimale Angaben zu verwenden, bietet sich die Angabe von Hex-Literalen der Art 0xFF an. Das folgende Beispiel erzeugt einen Text auf einer grauen Grundfläche: Listing 9.3: imagettftext.php – Text auf einer Bildfläche ausgeben ?php $size = 44; $font = 'fonts/verdana.ttf'; $text = 'Überschrift'; $img = imagecreate(480, 80); imagecolorallocate($img, 0xCC, 0xCC, 0xCC); $handle_black = imagecolorallocate($img, 0, 0, 0); imagettftext($img, $size, 0, 80, 65, $handle_black, $font, $text); header ('Content-type: image/gif'); imagegif($img); ? Das Skript wendet sehr direkt die zuvor beschriebenen Funktionen an. Es erzeugt die folgende Ausgabe: Abbildung 9.5: Dynamisch erzeugtes Bild Soweit das Skript nicht unmittelbar nach der Ausgabe endet, ist es empfehlens- wert, den belegten Speicherplatz sofort freizugeben. Bedenken Sie, dass Bilder erheblichen Speicherplatz in Anspruch nehmen können. Das sehr einfache Bild des letzten Beispiels benötigt etwa 115 KByte. 384
  • 385.
    Dynamisch Bilder erzeugen Bildfunktionen Ähnlichwie bei Text geht es auch bei den Bildfunktionen. Typische Operationen umfassen hier das Zeichnen von Rechtecken, Kreisen, Linien oder Punkten. Teil der Bildfunktionen ist aber auch das Verarbeiten anderer Bilder, das Filtern, Ver- zerren, Kopieren, Zerschneiden usw. Das folgende Beispiel zeichnet olympische Ringe (es ist während der Sommer- olympiade 2004 in Athen entstanden): Listing 9.4: imagerings.php – Dynamisches Zeichnen olympischer Ringe ?php class Circle { public $x; public $y; public $color; const RAD = 50; public function __construct($x, $y, $color) { $this-x = $x; $this-y = $y; $this-color = $color; } } $size = 14; $font = 'fonts/verdana.ttf'; $text = 'Athen 2004'; $img = imagecreate(280, 180); $white = imagecolorallocate($img, 0xFF, 0xFF, 0xFF); $blue = imagecolorallocate($img, 0, 0, 0xFF); $black = imagecolorallocate($img, 0, 0, 0); $red = imagecolorallocate($img, 0xFF, 0, 0); $yellow = imagecolorallocate($img, 0xFF, 0xFF, 0); $green = imagecolorallocate($img, 0, 0x64, 0); $pos = array(new Circle(70, 50, $blue), new Circle(105, 80, $yellow), new Circle(140, 50, $black ), new Circle(175, 80, $green), new Circle(210, 50, $red)); foreach ($pos as $point) 385
  • 386.
    Professionelle Programmierung { for ($i = 0; $i 15; $i++) { imagearc($img, $point-x, $point-y, Circle::RAD+$i, Circle::RAD+$i, 0, 360, $point-color); } imagearc($img, $point-x, $point-y, Circle::RAD+$i, Circle::RAD+$i, 0, 360, $white); } header ('Content-type: image/gif'); imagegif($img); ? Das Skript ist keineswegs perfekt, aber es zeigt das Prinzip und die Möglichkeiten. Vor allem die korrekte Verschlingung der Ringe wäre weitaus aufwändiger. Die Anwendung der Klasse Circle demonstriert dennoch, wie die Zeichenfunktionen von objektorientierten Ansätzen profitieren können. Die Darstellung der Kreise als Objekte vereinfacht das Skript erheblich und trägt wesentlich zur Lesbarkeit bei. Abbildung 9.6: Vereinfachte Version der olympischen Ringe, mit GD2 erstellt Wenn Sie das Skript laufen lassen, können sie die farbige Version erleben, die der Druck nur mangelhaft wiedergeben kann. Anwendungsbeispiel »Dynamischer Werbebanner« Das folgende Beispiel zeigt die Anwendung der GD2-Bibliothek anhand einer Klasse, die dynamisch rechteckige Bereiche erstellt und mit Bildern, Farben und Texten füllt. Anhand von weiteren Parametern kann man daraus beispielsweise dynamisch Banner erstellen, deren Inhalt je nach Situation und Benutzer variiert. 386
  • 387.
    Dynamisch Bilder erzeugen Nutzung DieKlasse erwartet die Werte als GET-Parameter. Dieser Teil lässt sich freilich leicht anpassen. Die folgende Tabelle zeigt Namen und Bedeutung der Parameter: Parameter Bedeutung W Breite H Höhe A Ausrichtung, beispielsweise »left« C Farbe des Textes FF Font, der verwendet werden soll FS Schrifthöhe in Punkt BG Pfad zu einem Hintergrundbild. Das Bild bestimmt die Größe des Banners, sodass die Werte H und W ignoriert werden BC Hintergrundfarbe, wird nur akzeptiert, wenn kein Hintergrundbild angege- ben wird B Randbreite in Pixel TXT Der Text, der auf dem Banner angezeigt wird Tabelle 9.3: Bedeutung der Parameter zur Steuerung der Grafikklasse Quellcode der Klasse Die Klasse nutzt konsequent die neuen Möglichkeiten von PHP5 und eignet sich ideal für den Einbau in eigene Projekte. Ein kleines Testskript folgt am Ende. Listing 9.5: gd2lib.inc.php – Hilfsklasse zum Erstellen von Bannern ?php class NoticeException extends Exception { } class ImagingException extends Exception { 387
  • 388.
    Professionelle Programmierung public function __construct($msg) { parent::__construct($msg); } } class LSImage { private $intWidth; private $intHeight; private $strAlign; private $strColor; private $strFontFace; private $strFontSize; private $intBorder; private $strBackColor; private $strBackImage; private $strText; private $strType; private $im; private $dx; private $dy; const FONTPATH = 'fonts/'; public function __construct($strSetType = 'png') { set_error_handler(array($this, 'throwError')); $this-strType = $strSetType; $arrURI = $_GET; if ($arrURI['LSF'] = 'image') { if (isset($arrURI['BG'])) { $this-strBackImage = $arrURI['BG']; } else { $this-strBackColor= $arrURI['BC']; } $this-intWidth = $arrURI['W']; $this-intHeight = $arrURI['H']; $this-strAlign = $arrURI['A']; 388
  • 389.
    Dynamisch Bilder erzeugen $this-strColor = $arrURI['C']; $this-strFontFace = self::FONTPATH . strtolower($arrURI['FF']); if (substr($this-strFontFace, -4) != '.ttf') $this-strFontFace .= '.ttf'; if (!file_exists ($this-strFontFace)) $this-strFontFace = self::FONTPATH . 'verdana.ttf'; $this-strFontSize = $arrURI['FS']; $this-intBorder = $arrURI['B']; $this-strText=stripslashes(urldecode($arrURI['TXT'])); } } private function throwError($errno, $err, $line, $file) { throw new NoticeException($errno, $err, $line, $file); } private function getcolor($strColor) { $r = hexdec(substr($strColor, 0, 2)); $g = hexdec(substr($strColor, 2, 2)); $b = hexdec(substr($strColor, 4, 2)); return imagecolorallocate($this-im, $r, $g, $b); } private function CheckBackgroundSize() { if ($this-intWidth != imagesx($this-im) or $this-intHeight != imagesy($this-im)) { return true; } else { return false; } } private function create() { $pi = pathinfo($this-strBackImage); $extension = isset($pi['extension']) ? $pi['extension'] : ''; switch ($extension) { 389
  • 390.
    Professionelle Programmierung case 'jpg': case 'jpeg': $this-im = imagecreatefromjpeg($this-strBackImage); break; case 'gif': $this-im = imagecreatefromgif($this-strBackImage); break; case 'png': $this-im = imagecreatefrompng($this-strBackImage); break; default: $this-im = imagecreate($this-intWidth, $this-intHeight); } if ($this-im === FALSE) { throw new ImageException('Kein Hintergrund und keine Bildgröße angegeben'); } if ($this-CheckBackgroundSize()) { $bi = imagecreate($this-intWidth, $this-intHeight); if (function_exists('imagecopyresampled')) imagecopyresampled($bi, $this-im, 0, 0, 0, 0, imagesx($bi), imagesy($bi), imagesx($this-im), imagesy($this-im)); else imagecopyresized($bi, $this-im, 0, 0, 0, 0, imagesx($bi), imagesy($bi), imagesx($this-im), imagesy($this-im)); $this-im = $bi; } $this-getcolor($this-strBackColor); $arr = imagettfbbox($this-strFontSize, 0, $this-strFontFace, $this-strText); // if ($arr === FALSE) { throw new ImagingException('TTF wird nicht unterstützt'); 390
  • 391.
    Dynamisch Bilder erzeugen } else { $this-dy = $this-intHeight - (($this-intHeight / 2) - (abs($arr[7]) / 2)); $this-dy = $this-dy 0 ? $this-intHeight : $this-dy; switch (strtolower($this-strAlign)) { case 'center': case 'middle': $this-dx = ($this-intWidth / 2) - ($arr[2] / 2); break; case 'right': $this-dx = $this-intWidth - $arr[2]; break; case 'left': default: $this-dx = 0; break; } } $this-dx += $this-intBorder; for ($i = 0; $i $this-intBorder; $i++) { imagerectangle($this-im, $i, $i, $this-intWidth-$i-1, $this-intHeight-$i-1, $this-getColor($this-strBackColor)); } } public function image() { try { $this-create(); header( Content-type: image/ . $this-strType); $fg = $this-getcolor($this-strColor); imagettftext($this-im, (int) $this-strFontSize, 0, $this-dx, $this-dy, $fg, $this-strFontFace, $this-strText); call_user_func('Image' . $this-strType, $this-im); ImageDestroy($this-im); 391
  • 392.
    Professionelle Programmierung } catch (ImagingException $ex) { echo $ex-getMessage(); } } } $objImg = new LSImage; $objImg-image(); ? Das Skript beginnt mit der Erstellung zweier Fehlerklassen, damit die Fehler- behandlung über die neue Ausnahmesteuerung mit try/catch erfolgen kann. Die erste Klasse soll lediglich Warnungen erfassen: class NoticeException extends Exception Auslöser ist die Umleitung der klassischen Laufzeitfehler im Konstruktor: set_error_handler(array($this, 'throwError')); Tritt ein Fehler auf, wird die Methode throwError aufgerufen, die ihrerseits den Fehler in eine Ausnahme verwandelt: private function throwError($errno, $err, $line, $file) { throw new NoticeException($errno, $err, $line, $file); } Das eigentliche Bilderzeugungsprogramm beginnt im Konstruktor mit der Über- nahme der GET-Parameter in lokale Eigenschaften. Danach muss das Hauptpro- gramm die Methode image aufrufen, um das Bild zu erstellen. Die Bilderzeugung ist in einem try-Block untergebracht, um Ausnahmen abfangen zu können. Im Fehlerfall werden dann andere Header erzeugt, was die Fehlersuche vereinfacht. Die Ausführung der Bilderzeugung erfolgt in der privaten Methode create: $this-create(); Gelingt die Bilderzeugung, wird der Text auf dem Bild platziert. Hier könnte man Cache-Techniken ansetzen, um die mit create erzeugten Daten zu erhalten und die aufwändige Erstellungsprozedur zu vermeiden. Die Ausgabe vollzieht sich in mehreren Schritten. Zuerst wird die Vordergrund- farbe ermittelt: $fg = $this-getcolor($this-strColor); 392
  • 393.
    Dynamisch Bilder erzeugen Dannwird anhand der vorausberechneten Größen der Text im verlangten True- Type-Font geschrieben: imagettftext($this-im, (int) $this-strFontSize, 0, $this-dx, $this-dy, $fg, $this-strFontFace, $this-strText); Dann wird die Kopfzeile für den Browser erzeugt, die den Bildtyp bestimmt: header( Content-type: image/ . $this-strType); Passend dazu wird der entsprechende Typ erzeugt und gesendet: call_user_func('Image' . $this-strType, $this-im); Der Funktionsaufruf »baut« aus dem Wort Image und dem Typ den Funktionsna- men zusammen, also beispielsweise ImagePng. Anschließend wird das Bild zerstört, um den belegten Speicher sofort freizugeben: ImageDestroy($this-im); Der eigentliche Bildaufbau findet in create statt. Es wird zunächst ermittelt, ob der Pfad eines Hintergrundbildes vorliegt: $pi = pathinfo($this-strBackImage); $extension = isset($pi['extension']) ? $pi['extension'] : ''; Je nach Bildtyp erfolgt dann im switch-Zweig der Aufbau der Zeichenfläche aus dem Hintergrundbild. Das Hintergrundbild bestimmt bis hierher die Bildgröße: $this-im = imagecreatefromgif($this-strBackImage); Nur wenn kein Hintergrundbild existiert, werden die Größen- und Breitenparame- ter übernommen: $this-im = imagecreate($this-intWidth, $this-intHeight); Alle folgenden Funktionen beziehen sich dann auf das Handle in der Eigenschaft $this-im. Stimmen Hintergrundbild und verlangte Bildgröße nicht überein, wird das Hintergrundbild auf die erforderliche Größe skaliert. Dazu wird folgende Funktion verwendet: imagecopyresampled($bi, $this-im, 0, 0, 0, 0, imagesx($bi), imagesy($bi), imagesx($this-im), imagesy($this-im)); Die Basis für die Platzierung des Textes ist die Berechnung der erforderlichen Größe: $arr = imagettfbbox($this-strFontSize, 0, $this-strFontFace, $this-strText); 393
  • 394.
    Professionelle Programmierung Je nachAusrichtung wird dann der Startpunkt basierend auf den von imagettfbox zurückgegebenen Werten berechnet. Für zentrierte Texte beispielsweise wird aus- gehend von den Mittelpunkten die Verschiebung nach folgender Formel ermittelt: $this-dx = ($this-intWidth / 2) - ($arr[2] / 2); Für den Rand erfolgt die Generierung von Rechtecken, die dem Bild überlagert werden. Die folgende Funktion erledigt dies auch für Randbreiten 1, wenn der Aufruf in einer Schleife erfolgt und mit der Schleifenvariablen $i gesteuert wird: imagerectangle($this-im, $i, $i, $this-intWidth-$i-1, $this-intHeight-$i-1, $this-getColor($this-strBackColor)); Die Randfarbe wird von der Hintergrundfarbe bestimmt. Der Einsatz ist vor allem im Zusammenhang mit Hintergrundbildern sinnvoll. Beispielaufruf Das folgende Skript enthält ein IMG-Tag, das einen passenden Aufruf enthält: Listing 9.6: gd2libTest.php – Test der Bibliothek aus einer HTML-Datei heraus img src=http://localhost/MuT/ gd2lib.inc.php?W=480H=80A=leftTXT=PHP5%20und%20MySQL%20in%2014%20Tagen C=FF0000B=2FF=VerdanaFS=17BC=CCCCCCBG=fonts/MuTBase.JPG width=480 height=80/ Beachten Sie hier, dass der Text gegebenenfalls URL-kodiert werden muss. Die Bibliothek sorgt für die entsprechende Dekodierung. Abbildung 9.7: Banner, der mit der Bibliothek erzeugt wurde. Der Text kann als Parameter ausge- tauscht werden. 394
  • 395.
    Code röntgen: DieReflection-API 9.3 Code röntgen: Die Reflection-API PHP5 verfügt über eine Programmierschnittstelle, die die Analyse von Code erlaubt. Dies ist sinnvoll, um aus anderen Programmen heraus die Eigenschaften und den Aufbau von Klassen und Bibliotheken zu ermitteln. Hilfreich können diese Techniken zum einen für die Entwickler von Editoren sein, zum anderen aber auch für Bibliotheksprogrammierer, die flexibel auf veränderte Bedingungen reagieren möchten und damit weniger Probleme mit Updates, Versionen und Varianten haben. Ein dritter und sehr wichtiger Komplex ist die Entwicklung von automatischen Code-Dokumentierern. Dies sind Programme, die Code analysie- ren und daraus Quelltextdokumentationen erstellen. Die Reflection-API als Objektmodell Der Zugriff auf die Reflection-Informationen erfolgt über ein Objektmodell, das folgenden Aufbau hat: ?php class Reflection { } interface Reflector { } class Reflection_Exception extends Exception { } class ReflectionFunction implements Reflector { } class ReflectionParameter implements Reflector { } class ReflectionMethod extends Reflection_Function { } class ReflectionClass implements Reflector { } class ReflectionProperty implements Reflector { } class ReflectionExtension implements Reflector { } ? Das Prinzip ist einfach. Um Informationen über alle Funktionen eines Skripts herauszufinden, wird eine Instanz der Klasse ReflectionFunction erzeugt und benutzt. Die Basisklasse Reflection stellt Hilfsfunktionen zur Verfügung, um die ermittelten Daten auslesen zu können. Listing 9.7: ReflectionBase.php – Die Struktur der internen Klasse Exception ermitteln pre ?php Reflection::export(new ReflectionClass('Exception')); ? /pre 395
  • 396.
    Professionelle Programmierung Diese Zeileerzeugt folgende Ausgabe: Abbildung 9.8: Ausgabe der Reflection- Abfrage einer Klasse Praktisch ist die Ausgabe eine Kopie der Definition, wobei alle theoretisch mög- lichen Mitglieder des abgefragten Objekts – hier also einer Klasse – angezeigt wer- 396
  • 397.
    Code röntgen: DieReflection-API den. Die gezeigte Klasse hat also keine Konstanten, keine statische Eigenschaften, keine statische Methoden, sechs normale Eigenschaften und neun Methoden. Die Reflection-Klassen im Detail Der folgende Abschnitt zeigt die zur Verfügung stehenden Klassen, deren Defini- tion und einfache Anwendungsbeispiele. Informationen über Funktionen ermitteln Mit ReflectionFunction werden Funktionen analysiert. Die folgende Tabelle zeigt die zur Verfügung stehenden Methoden, die jeweils konkrete Informationen über die Funktion zurückgeben. Methode Bedeutung __construct(string name) Konstruktor, erwartet den Namen einer Funktion string getName() Gibt den Namen der Funktion zurück bool isInternal() Wahr, wenn dies eine eingebaute Funktion ist bool isUserDefined() Wahr, wenn dies eine benutzerdefinierte Funktion ist string getFileName() Den Namen der Datei, wo diese Methode definiert ist int getStartLine() Den Namen der Zeile, wo die Definition beginnt int getEndLine() Den Namen der Zeile, wo die Definition endet string getDocComment() Kommentare, die die Funktion dokumentieren array getStaticVariables() Array der statischen Variablen mixed invoke(mixed* args) Führt die Funktion mit bestimmten Argumenten aus string toString() Zeichenkettendarstellung der Funktion bool returnsReference() Wahr, wenn der Rückgabewert eine Referenz ist Reflection_Parameter[] Die Parameter der Funktion getParameters() Tabelle 9.4: Methoden der Klasse ReflectionFunction 397
  • 398.
    Professionelle Programmierung Um eineFunktion zu untersuchen, muss natürlich erst ein Objekt der Klasse erzeugt werden. Informationen über Parameter ermitteln Da Funktionen oft Parameter haben, besteht ein enger Zusammenhang zwischen ReflectionFunction und ReflectionParameter. Das folgende Beispiel zeigt beide Klassen in der Anwendung: Listing 9.8: ReflectionFunction.php – Analyse einer Funktion mit Parametern /** * Eine einfache Zählfunktion * * @return int */ function counter($debug, $multiplier = 6) { static $c = 1; echo $debug; return $c++ * $multiplier; } $func= new ReflectionFunction('counter'); printf( === Die %s Function'%s'n. deklariert in %sn. von Zeile %d bis %dn, $func-isInternal() ? 'interne' : 'benutzerdefinierte', $func-getName(), $func-getFileName(), $func-getStartLine(), $func-getEndline() ); printf(--- Dokumentation:n %sn, var_export($func-getDocComment(), 1)); if ($statics = $func-getStaticVariables()) { printf(--- Statische Variablen: %sn, var_export($statics, 1)); } foreach ($func-getParameters() as $name = $param) 398
  • 399.
    Code röntgen: DieReflection-API { printf(Parameter '%s' %s [%s]n, $name, $param-getName(), $param-isPassedByReference() ? '' : ''); } printf(nn--- Aufrufergebnisse: ); var_dump($func-invoke(array('Test', 2))); »Reflektiert« wird hier die Funktion counter. Das Ergebnis zeigt alle erforderlichen Details, um die Funktion zu verstehen: Abbildung 9.9: Ergebnis der Analyse einer benutzer- definierten Funktion Vor allem Programme, die Dokumentationen automatisch erstellen, können davon erheblich profitieren. Daten einer Klasse ermitteln Mit Hilfe der Klasse ReflectionClass lassen sich andere Klassen – eingebaute oder eigene – analysieren. Das folgende Skript versucht diese Analyse möglichst umfas- send. Es verwendet einige der bereitgestellten Methoden: Listing 9.9: ReflectClass.php – Ermittlung von Daten einer Klasse interface Serializable { // ... } class Object 399
  • 400.
    Professionelle Programmierung { // ... } class Counter extends Object implements Serializable { const START= 0; private static $c= Counter::START; public function count() { return self::$c++; } public static function ReflectMe($config) { $class = new Reflection_Class($config); printf( === Die %s%s%s %s '%s' [extends %s]n. deklariert in %sn. von Zeile %d bis %dn. mit dem Modifikatoren %d [%s]n, $class- isInternal() ? 'interne' : 'benutzerdefinierte', $class-isAbstract() ? ' abstrakte' : '', $class-isFinal() ? ' finale' : '', $class-isInterface() ? 'Schnittstelle' : 'Klasse', $class-getName(), var_export($class-getParentClass(), 1), $class-getFileName(), $class-getStartLine(), $class-getEndline(), $class-getModifiers(), implode(' ', Reflection::getModifierNames($class-getModifiers())) ); printf(--- Dokumentation:n %sn, var_export($class-getDocComment(), 1)); printf(--- Implementiert:n %sn, var_export($class-getInterfaces(), 1)); printf(--- Konstanten: %sn, var_export($class-getConstants(), 1)); printf(--- Eigenschaften: %sn, var_export($class-getProperties(), 1)); printf(--- Methoden: %sn, var_export($class-getMethods(), 1)); 400
  • 401.
    Code röntgen: DieReflection-API if ($class-isInstantiable()) { $counter = $class-newInstance(); echo '--- $counter ist Instanz? '; var_dump($class-isInstance($counter)); echo '--- new Object() ist Instanz? '; var_dump($class-isInstance(new Object())); } } } Counter::ReflectMe('Counter'); Die Funktion newInstance kann mit einer variablen Anzahl von Argumenten auf- gerufen werden, die durch den Konstruktor der analysierten Klasse bestimmt wer- den. Es wird eine Warnung ausgegeben, wenn die Instanziierung nicht korrekt erfolgt. Folgende Sequenz ermittelt, ob ein Objekt eine Instanz einer bestimmten Klasse ist: $class= new Reflection_Class('Foo'); $class-isInstance($arg) Diese Information kann auch folgendermaßen gewonnen werden: if ($arg instanceof Foo) Information über Methoden einer Klassen ermitteln Die Informationen über eine Klasse enthalten bereits die Namen der Methoden. Das folgende Beispiel zeigt die Anwendung: Listing 9.10: ReflectMethod.php – Daten über eine Methode ermitteln class Counter { private static $c= 0; /** * Zähler erhöhen * * @final * @static * @access public * @return int 401
  • 402.
    Professionelle Programmierung Abbildung 9.10: Informationen über eine Klasse und deren Mitglieder */ final public static function increment() { self::$c++; return self::$c; } } // Create an instance of the Reflection_Method class $method= new ReflectionMethod('Counter', 'increment'); // Print out basic information 402
  • 403.
    Code röntgen: DieReflection-API printf( === Die %s%s%s%s%s%s%s Methode '%s' (welche %s ist)n. Deklariert in %sn. von Zeile %d bis %dn. hat die Modifikatoren %d[%s]n, $method-isInternal() ? 'interne' : 'benutzerdefinierte', $method-isAbstract() ? ' abstrakte' : '', $method-isFinal() ? ' finale' : '', $method-isPublic() ? ' öffentliche' : '', $method-isPrivate() ? ' private' : '', $method-isProtected() ? ' geschützte' : '', $method-isStatic() ? ' statische' : '', $method-getName(), $method-isConstructor() ? 'ein Konstruktor' : 'eine reguläre Methode', $method-getFileName(), $method-getStartLine(), $method-getEndline(), $method-getModifiers(), implode(' ', Reflection::getModifierNames($method-getModifiers()))); printf(--- Dokumentation:n %sn, var_export($method-getDocComment(), 1)); if ($statics= $method-getStaticVariables()) { printf(--- Statische Variables: %sn, var_export($statics, 1)); } printf(--- Aufrufergebnisse: ); var_dump($method-invoke(NULL)); Das Aufrufen einer privaten oder geschützten (protected) Methode führt auch über Reflektion zu einem Fehler. Das ist durchaus unverständlich, weil ein derarti- ger Schutz vor allem ungewollte Zugriffen verhindert. Der explizite Einsatz einer Reflektionstechnik benötigt jedoch einen derartigen Schutz nicht. Der Zugriff auf Methoden verlangt als erstes Argument eine Instanz der Klasse. Bei statischen Auf- rufen gibt es diese Instanz nicht, deshalb wird hier der Wert NULL angegeben. Klassen haben neben Methoden meist auch Eigenschaften. Neben der einfachen Liste, die bereits die Reflektion der Klasse erbrachte, können auch Detaildaten ermittelt werden. Das folgende Beispiel zeigt, wie mit ReflectionProperty gear- beitet wird. 403
  • 404.
    Professionelle Programmierung Abbildung 9.11: Informationen über eine Methode Listing 9.11: ReflectMethodProps.php – Eigenschaften einer Klasse class PropTest { public $number = 5; } $prop= new ReflectionProperty('PropTest', 'number'); printf( === Die %s%s%s%s Eigenschaft '%s' (welche %s deklariert wurde)n. hat die folgenden Modifizierer %sn, $prop-isPublic() ? ' öffentlich' : '', $prop-isPrivate() ? ' privat' : '', $prop-isProtected() ? ' geschüttz' : '', $prop-isStatic() ? ' statisch' : '', $prop-getName(), $prop-isDefault() ? 'zur Kompilierzeit' : 'zur Laufzeit', var_export(Reflection::getModifierNames( $propgetModifiers()), 1) ); $obj= new PropTest(); print(--- Der Wert ist: ); var_dump($prop-getValue($obj)); $prop-setValue($obj, 10); print(--- Setze den Wert 10, der Wert ist jetzt: ); var_dump($prop-getValue($obj)); var_dump($obj); Das Aufrufen einer privaten oder geschützten (protected) Methode führt auch über Reflektion zu einem Fehler. 404
  • 405.
    Code röntgen: DieReflection-API Abbildung 9.12: Aufruf einer Methode über Reflection Daten über PHP-Erweiterungen ermitteln Mit ReflectionExtension lassen sich Informationen über Erweiterungen ermit- teln. Das ist manchmal hilfreich, um die Kompatibilität eines Skripts zu sichern oder die Konfiguration eines entfernten Computers, beispielsweise beim Provider, zu untersuchen. Zuerst müssen alle vorhandenen Erweiterungen ermittelt werden, was mit dem fol- genden Beispiel demonstriert wird. Listing 9.12: ReflectGetExtension.php – Eine Erweiterung untersuchen $aExtensions = get_loaded_extensions(); foreach ($aExtensions as $num = $name) { echo $num. $name br; } Hat man einmal die Namen, kann eine weitere Untersuchung mit Reflection- Extension erfolgen. Die einzelnen Daten können sehr umfangreich werden; die im folgenden Beispiel gezeigte umfassende Darstellung einer Erweiterung dient mehr der Demonstra- tion der Möglichkeiten. In der Praxis dürfte die gezielte Abfrage einer bestimmten Teilinformation sinnvoller sein. Um konkret festzustellen, ob eine Erweiterung vorhanden ist, wird am besten in_array eingesetzt und dann die Ausgabe gestartet: Listing 9.13: ReflectGetExtensionDetails.php – Details von PHP-Erweiterungen ermitteln $aExtensions = get_loaded_extensions(); if (in_array('SQLite', $aExtensions)) { echo Erweiterung 'sqlite' existiertbr; $ext= new ReflectionExtension('sqlite'); 405
  • 406.
    Professionelle Programmierung Abbildung 9.13: Liste der geladenen Erweiterungen einer typischen PHP-Installation printf( Name : %sn. Version : %sn. Funktionen : [%d] %sn. Konstanten : [%d] %sn. INI-Einträge : [%d] %sn, $ext-getName(), $ext-getVersion() ? $ext-getVersion() : 'NO_VERSION', sizeof($ext-getFunctions()), var_export($ext-getFunctions(), 1), sizeof($ext-getConstants()), var_export($ext-getConstants(), 1), sizeof($ext-getINIEntries()), var_export($ext-getINIEntries(), 1) ); } Die Ausgabe zeigt die Klassen der Erweiterung. Mit den bereits vorgestellten Tech- niken der Reflektion kann man so schnell alle Details ermitteln und damit Edito- ren oder Debugger steuern, Code robuster gestalten oder einfach nur lernen. Damit stehen alle wichtigen Techniken zur Verfügung, Code per Skript zu analy- sieren, zu dokumentieren und fremde Bibliotheken im Kontext korrekt zu verar- beiten. 406
  • 407.
    Funktions-Referenz GD2 Abbildung 9.14: Ausschnitt aus der Beschreibung einer einzigen Extension (SQLite) 9.4 Funktions-Referenz GD2 Funktion Bedeutung exif_imagetype Ermittelt Daten über ein vorhandenes Bild. exif_read_data Liest eingebettet EXIF-Daten aus einer JPG- oder TIF- Datei. Solche Daten werden beispielsweise von Digital- kameras erzeugt. exif_thumbnail Extrahiert in JPG- oder TIF-Dateien eingebettete Thumb- nail*-Dateien. gd_info Beschafft Informationen über die Bibliothek selbst. Tabelle 9.5: Funktions-Referenz für GD2-Funktionen * Verkleinertes Abbild der Hauptdatei. 407
  • 408.
    Professionelle Programmierung Funktion Bedeutung getimagesize Ermittelt die Ausmaße einer GIF-, JPEG-, PNG- oder SWF-Grafik-Datei. image_type_to_mime_type Ermittelt die MIME-Typen für getimagesize, exif_read_data, exif_thumbnail, exif_imagetype. image2wbmp Gibt das Bild im BMP-Format an den Browser aus. imagealphablending Setzt den Modus für Alpha-Blending. imageantialias Schaltet das Antialiasing ein oder aus. imagearc Zeichnet eine Teil-Ellipse. imagechar Stellt ein Zeichen mit horizontaler Ausrichtung dar. imagecharup Zeichnet einen vertikal ausgerichteten Charakter. imagecolorallocate Bestimmt die Farbe einer Grafik. imagecolorallocatealpha Bestimmt die Farbe unter Verwendung eines Alpha- Werts. imagecolorat Ermittelt den Farbwert eines Bildpunktes. imagecolorclosest Ermittelt den Farbwert-Index, der den angegebenen Farben am nächsten liegt. imagecolorclosestalpha Gibt den Index der Farbe in der Farbtabelle, die dem angegebenen Wert am Nächsten liegt. imagecolorclosesthwb Gibt den Index der Farbe in der Farbtabelle, die dem angegebenen Wert am Nächsten liegt. Als Werte werden Helligkeit, Sättigung und Kontrast verwendet. imagecolordeallocate Löscht eine Farbdefinition. imagecolorexact Ermittelt den Index-Wert der angegebenen Farbe. imagecolorexactalpha Index einer Farbe. imagecolormatch Versucht die Werte der Farbtabelle in Übereinstimmung mit den tatsächlichen Farben zu bringen. Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.) 408
  • 409.
    Funktions-Referenz GD2 Funktion Bedeutung imagecolorresolve Ermittelt den Index-Wert der angegebenen Farbe oder die nächstmögliche Alternative dazu. imagecolorresolvealpha Ermittelt den Index einer Farbe oder – wenn die Farbe nicht existiert – die bestmögliche Entsprechung. imagecolorset Setzt die Farbe für den angegebenen Paletten-Index. imagecolorsforindex Ermittelt die Farbwerte einer angegebenen Farb-Palette. imagecolorstotal Ermittelt die Anzahl der definierten Farben eines Bildes. imagecolortransparent Definiert eine Farbe als transparent imagecopy Kopiert einen Bildausschnitt imagecopymerge Kopiert einen Bildausschnitt und verbindet mit einem anderen. imagecopymergegray Kopiert einen Bildausschnitt und verbindet mit einem anderen mit Graustufen. imagecopyresampled Kopiert einen Bildausschnitt und verbindet mit einem anderen mit erneuter Kompression. imagecopyresized Kopiert einen Bildausschnitt und verbindet mit einem anderen mit Größenänderung. imagecreate Erzeugt ein neues Bild. imagecreatefromgd2 Erzeugt ein neues Bild aus GD2-Rohdaten. imagecreatefromgd2part Erzeugt ein neues Bild aus GD2-Rohdaten und Fragmen- ten eines Bilds. imagecreatefromgd Erzeugt ein neues Bild aus GD-Rohdaten. imagecreatefromgif Erzeugt ein neues Bild aus GIF-Daten. imagecreatefromjpeg Erzeugt ein neues Bild aus JPG-Daten. imagecreatefrompng Erzeugt ein neues Bild aus PNG-Daten. imagecreatefromstring Erzeugt ein neues Bild aus einem Stream. Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.) 409
  • 410.
    Professionelle Programmierung Funktion Bedeutung imagecreatefromwbmp Erzeugt ein neues Bild aus WBMP-Daten. imagecreatefromxbm Erzeugt ein neues Bild aus XBM-Daten. imagecreatefromxpm Erzeugt ein neues Bild aus XPM-Daten. imagecreatetruecolor Erzeugt ein neues Bild mit voller Farbunterstützung. imagedashedline Zeichnen einer gestrichelten Linie. imagedestroy Löscht ein Bild. imageellipse Zeichnete eine Ellipse oder einen Kreis. imagefill Füllen mit Farbe (»flood fill«). imagefilledarc Zeichnet einen gefüllten elliptischen Kreisbogen. imagefilledellipse Zeichnet eine gefüllte Ellipse. imagefilledpolygon Zeichnet ein gefülltes Vieleck (Polygon). imagefilledrectangle Zeichnet ein gefülltes Rechteck. imagefilltoborder Flächen-Farbfüllung (»flood fill«) mit einer angegebenen Farbe bis zum nächsten Rand. imagefontheight Ermittelt die Font-Höhe. imagefontwidth Ermittelt die Font-Breite. imageftbbox Ergibt Daten über die Fläche, die ein TrueType-Text in Anspruch nimmt. imagefttext Schreibt Text in das Bild. imagegammacorrect Anwendung einer Gamma-Korrektur auf ein GD-Bild. imagegd2 Ausgabe im internen GD2-Format. imagegd Ausgabe im internen GD-Format. imagegif Ausgabe eines Bildes an den Browser oder in eine Datei . Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.) 410
  • 411.
    Funktions-Referenz GD2 Funktion Bedeutung imageinterlace Schaltet die Interlaced-Darstellung eines Bildes an oder aus. imageistruecolor Ermittelt, ob das Bild Vollfarbunterstützung hat. imagejpeg Ausgabe des Bildes im Browser oder als Datei als JPG. imagelayereffect Setzt Alpha-Blending mit Layer-Effekten. imageline Zeichnen einer Linie. imageloadfont Lädt einen neuen Font. imagepalettecopy Kopiert eine Farbpalette. imagepng Ausgabe des Bildes im Browser oder als Datei als PNG. imagepolygon Zeichnen eines Vielecks (Polygons). imagepsbbox Ermittelt die Ausmaße des Rechtecks, das für die Ausgabe eines Textes unter Verwendung eines PostScript-Fonts (Typ 1) notwendig ist. imagepscopyfont Erstellt eine Kopie eines bereits geladenen Fonts für wei- tere Veränderungen. imagepsencodefont Ändert die Vektor-Beschreibung eines Fonts. imagepsextendfont Vergrößert oder komprimiert einen Font. imagepsfreefont Gibt den durch einen Typ 1 PostScript-Font belegten Speicher wieder frei. imagepsloadfont Lädt einen Typ 1 PostScript-Font aus einer Datei. imagepsslantfont Setzt einen Font schräg. imagepstext Ausgabe eines Textes auf einem Bild unter Verwendung von Typ 1 PostScript-Fonts. imagerectangle Zeichnet ein Rechteck. imagerotate Rotiert ein Bild um einen Winkel. imagesavealpha Speichert Alpha-Kanal-Informationen. Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.) 411
  • 412.
    Professionelle Programmierung Funktion Bedeutung imagesetbrush Setzt ein Bild, das als Füllung für Linien dient. imagesetpixel Setzt ein einzelnes Pixel. imagesetstyle Setzt einen Stil für Linien. imagesetthickness Setzt die Breite für Linien und andere Figuren. imagesettile Setzt ein kachelbares Bild für Füllungen. imagestring Zeichnet eine horizontalen Zeichenfolge. imagestringup Zeichnet eine vertikale Zeichenfolge. imagesx Ermittelt die Bild-Breite. imagesy Ermittelt die Bild-Höhe. imagetruecolortopalette Konvertiert eine Vollfarbpalette in ein Palettenbild. imagettfbbox Ermittelt die Rahmenmaße für die Ausgabe eines Textes im TrueType-Format. imagettftext Erzeugt TTF-Text im Bild. imagetypes Gibt die von der aktuell verwendeten PHP-Version unter- stützten Grafik-Formate zurück. imagewbmp Gibt das Bild im WBMP-Format aus. iptcembed Bettet binäre IPTC-Informationen ins Bild ein. iptcparse Holt binäre IPTC-Informationen aus einem Bild ein. Informationen zu IPTC sind unter http://www.iptc.org/ zu finden. jpeg2wbmp Konvertiert JPEG nach WBMP. png2wbmp Konvertiert PNG nach WBMP. read_exif_data Liest die EXIF Header-Infos einer JPEG-Grafik Tabelle 9.5: Funktions-Referenz für GD2-Funktionen (Forts.) 412
  • 413.
    Kontrollfragen 9.5 Kontrollfragen 1. Welche Servervariable wird benutzt, um die bevorzugte Sprache des Benutzers zu ermitteln? 2. Welcher Header wird in HTTP benötigt, um dem Browser anzuzeigen, dass ein Bild gesendet wird? 3. Schreiben Sie ein Skript, dass dezimale Farbangaben wie 255, 192, 128 in hexa- dezimale Zeichenfolgen der Art »#FFCC99« umrechnet und umgekehrt. 4. Wozu dient das Werkzeug Reflection? 413
  • 415.
    Kommunikation per HTTP,FTP und E-Mail 1 0
  • 416.
    Kommunikation per HTTP,FTP und E-Mail 10.1 Konzepte in PHP5 Bedingt durch die Vielzahl möglicher Datenquellen kommt der Server-Server- Kommunikation immer stärkere Bedeutung zu. PHP5 stellt eine ganze Reihe von Methoden zum Zugriff auf andere Server zur Verfügung, einiges davon ist völlig neu. Damit ergeben sich spannende Möglichkeiten für die Nutzung von PHP5. Einige Anregungen finden Sie hier: í Holen von News-Daten von einem anderen Server (HTTP) í Senden von Status-Informationen an einen anderen Server (HTTP) í Versenden von Formularinhalten per E-Mail (SMTP) í Aktualisierung von Inhalten auf einem Download-Server (FTP) In Klammern finden Sie die jeweils bevorzugten Protokolle. Prinzip der Streams und Wrapper PHP5 nutzt ein einheitliches Konzept zur Organisation des Zugriffs auf Daten, so genannte Wrapper. Dies sind protokollähnliche Bezeichner (im Englischen oft als Moniker, Spitznamen, bezeichnet): wrapper://dateiname Ein Wrapper gilt als Standard: file://, diese Angabe kann entfallen. Das vereinfacht den Zugriff auf lokalen Dateien, die besonders häufig benötigt werden. Wrapper finden immer dann Anwendung, wenn Daten von einer Datenquelle geholt oder in eine Datenquelle geschrieben werden. Das gilt beispielsweise für sämtliche Dateifunktionen. Statt also für neue Zugriffsarten (per HTTP oder FTP) immer neue Bibliotheken zu verwenden, bietet PHP5 einen vereinheitlichten Weg des Zugriffs an. Damit die Verwaltung intern funktioniert, werden ergänzend so genannte Streams eingesetzt. Diese bietet elementare Zugriffsfunktionen wie Öff- nen, Lesen, Schreiben, Schließen. Wenn man eigene Wrapper entwickeln will – auch dies ist Teil des universellen Konzepts – programmiert man praktisch diese Stream-Funktion für einen spezifischen Fall. 416
  • 417.
    Konzepte in PHP5 Dieeingebauten Wrapper PHP5 kommt mit einigen eingebauten Wrappern daher, die für die meisten Zwe- cke ausreichend sind. Konkret sind dies: í file://Pfad/Datei Die Angabe file:// kann entfallen, sodass normale Pfadangaben weiterhin nutz- bar sind. Als mögliche Varianten sind relative Pfade (/pfad/name.ext), absolute Pfade (D:pfadname.ext oder /pfad/pfad/datei.ext) oder Netzwerkshares (ser- verfreigabename) erlaubt. Wenn die Angabe mit file:// erfolgt und unter Win- dows Laufwerkbuchstaben adressiert werden, sind dies mit | abzutrennen. í http://url, https://url Um auf andere Webserver zuzugreifen, werden diese Wrapper benutzt. https:// baut eine verschlüsselte Verbindung auf. Der URL wird in der übliche Form angegeben. Um Name und Kennwort für eine geschützte Verbindung anzuge- ben, wird die Form http://name:kennwort@url benutzt. í ftp://url, ftps://url Um auf andere Server per FTP zuzugreifen, werden diese Wrapper benutzt. ftps:// baut eine verschlüsselte Verbindung auf. Um Name und Kennwort für eine geschützte Verbindung anzugeben, wird die Form ftp://name:kenn- wort@url benutzt. í php://channel Um auf die internen Ein- und Ausgabekanäle des Betriebssystems zuzugreifen, bietet PHP einen speziellen Wrapper an. Zulässige Kanalnamen sind »stdin« (Eingabe, beispielsweise Tastaturabfragen am Systemprompt), »stdout« (Aus- gabe), »stderr« (Fehlerausgaben), »output« (Ausgabepuffer, der von echo, print usw. verwendet wird), »input« (Rohdaten aus einem Formular bei POST- Anforderungen und »filter« (benutzerspezifische oder eingebaute Filter, die Daten während der Übertragung verändern). í compress.zlib://, compress.bzip2:// Komprimiert oder dekomprimiert die Daten während der Übertragung nach dem ZIP-Verfahren. Beim Schreiben in eine Datei entstehen GZ-Dateien, keine Archive. 417
  • 418.
    Kommunikation per HTTP,FTP und E-Mail Filter Filter werden im Stream eingebunden, um – für den Aufrufer transparent – Daten während der Übertragung zu ändern. Der Vorteil liegt in der Anwendung. Der Benutzer eines Filters arbeitet weiter wie gewohnt mit Standardfunktionen wie fopen und fread. Dabei gibt er lediglich an, einen bestimmten Filter benutzen zu wollen. Wie dieser funktioniert, eingebunden wird und wie dieser konkret mit den Daten interagiert, bleibt völlig im Hintergrund. Eingebaute Filter können fol- gende Aktionen ausführen (zuvor der Name des Filters): í string.rot13 Eine ROT13-Kodierung, bei der die Buchstaben um 13 Stellen im Alphabet verschoben werden, um eine sehr schwache Verschlüsselung zu erreichen. í string.toupper Verwandelt alle Zeichen in Großbuchstaben. í string.tolower Verwandelt alle Zeichen in Kleinbuchstaben. í string.strip_tags Entfernt alle Tags aus dem Datenstrom. í convert.base64-encode Konvertiert die Daten in das Base64-Format. í convert.base64-decode Konvertiert die Base64-Daten aus dem Base64-Format in eine lesbare Form. í convert.quoted-printable-encode Konvertiert die Daten in das Quoted-Printable-Format (in E-Mails benutzt). í convert.quoted-printable-decode Konvertiert die Quoted-Printable-Daten aus dem Quoted-Printable-Format in eine lesbare Form. Damit die Filter wirklich universell sind, kann man auch bei der Angabe von php://filter wiederum Pfade angeben, die ihrerseits die Wrapper http:// oder ftp:// verwenden. 418
  • 419.
    Streams und Wrapperanwenden 10.2 Streams und Wrapper anwenden Die Anwendung der Wrapper ist relativ einfach. Eigentlich müssen Sie lediglich mit den Dateifunktionen umgehen können, die bereits ausführlich vorgestellt wurden. Daten von einer fremden Website beschaffen Das folgende Skript holt sich Daten von einer fremden Website. Es liest die Daten ein und ermittelt die Inhalte bestimmter META-Tags, wenn diese vorhanden sind. Das klingt weitaus aufregender, als es ist: Listing 10.1: wrapperhttp.php – Zugriff mit Standardfunktionen auf Webserver ?php $url = 'http://www.apache.org'; $tags = get_meta_tags($url); if (count($tags) 0) { foreach($tags as $name = $content) { echo strtoupper($name); echo = $contentbr; } } ? Der eigentliche Aufwand – also das Herstellen der Verbindung – erledigt der Wrap- per. Die Filterung und Ausgabe derw META-Tags erfolgt mit Hilfe der Funktion get_meta_tags. Die Anwendung unterscheidet sich nicht von der beim Lesen nor- maler Dateien. Allerdings kann die Ausführzeit des Skripts etwas länger sein, da einige Zeit für den Verbindungsaufbau und die Übertragung der Seite benötigt wird. Abbildung 10.1: Ausgabe der META-Tags der Website www.apache.org Sie sollten außerdem auf eine Fehlerbehandlung achten, da Internet- Verbindungen aus den verschiedensten Gründen fehlschlagen können. 419
  • 420.
    Kommunikation per HTTP,FTP und E-Mail Das folgende Skript liest den Inhalt einer Webseite direkt ein und speichert die Seite (ohne Bilder oder andere Ressourcen) lokal ab: Listing 10.2: wrapperhttppage.php – Zugriff mit Dateifunktionen ?php $url = 'www.apache.org'; $page = file_get_contents(?http://$url?); if (strlen($page) 0) { $file = data/$url.htm; $f = fopen($file, 'w'); fwrite($f, $page); fclose($f); echo 'Dateigrouml;szlig;e: ' . filesize($file) . ' Byte'; } ? Auch hier wird lediglich eine Standardfunktion, file_get_contents, benutzt. Zur Kontrolle wird noch die Dateigröße angezeigt. Zum Testzeitpunkt waren dies stolze 12 KByte. Daten komprimiert speichern Weitaus kompakter geht es, wenn man die Daten komprimiert speichern kann. Das folgende Beispiel verwendet einen weiteren Wrapper beim Speichern der Datei, der das erledigt: Listing 10.3: wrapperhttpcompress.php – Abspeichern einer Datei in komprimierter Form ?php $url = 'www.apache.org'; $page = file_get_contents(http://$url); if (strlen($page) 0) { $file = data/$url.gz; $f = fopen(compress.zlib://$file, 'w'); fwrite($f, $page); fclose($f); echo 'Dateigrouml;szlig;e: ' . filesize($file) . ' Byte'; } ? 420
  • 421.
    Streams und Wrapperanwenden Das Ergebnis unterscheidet sich vor allem im Platzverbrauch von der vorherigen Version. Die Startseite der Apache Group belegt nun nur noch 3,6 KByte. Eine verbesserte Version liest die Datei wieder aus und zeigt sie zeilenweise an. Dabei wird wiederum ein Wrapper benutzt, der auf die abgespeicherte Version zugreift. Listing 10.4: wrappedecompress.php – Komprimierte Datei erzeugen und dekom- primieren ?php $url = 'www.apache.org'; $page = file_get_contents(http://$url); if (strlen($page) 0) { $file = data/$url.gz; $f = fopen(compress.zlib://$file, 'w'); fwrite($f, $page); fclose($f); echo 'Dateigrouml;szlig;e: ' . filesize($file) . ' Byte'; echo 'brbr'; $farray = file(compress.zlib://$file); foreach($farray as $num = $content) { printf('%03d: %sbr /', $num, htmlspecialchars($content)); } } ? Hier wird eine weitere Dateifunktion, file, benutzt und diesmal reagiert derselbe Wrapper in genau umgekehrter Weise. Da die Daten gelesen werden, erwartet er komprimierte Daten und diese werden folgerichtig wiederhergestellt. Der eigentli- che Vorgang des Komprimierens und Dekomprimierens ist für den Anwender transparent – man muss weder bei der Benutzung noch bei der Wahl der Funk- tionen darauf Rücksicht nehmen. Damit ist zum Thema Wrapper eigentlich schon fast alles gesagt. Der Fantasie sind beim Anwenden kaum Grenzen gesetzt. Vor allem aber kann man kompli- zierte Zugriffe mit den Socket-Funktionen meist vermeiden. 421
  • 422.
    Kommunikation per HTTP,FTP und E-Mail Abbildung 10.2: Komprimiert und dennoch direkt lesbar: Wrapper machen’s möglich 10.3 Filter verwenden Das folgende Skript holt sich wie bereits beim vorhergehenden Beispiel Daten von einer fremden Website. Es liest im Gegensatz dazu nur den Inhalt der Seite, also ohne HTML-Tags. Dies kann beispielsweise für eine Indizierung sinnvoll sein. Anstatt also die Ausgabe mit htmlspecialchars sichtbar zu machen, soll nun ein Filter die Arbeit erledigen. Wie bereits angedeutet, basieren Filter auf der Wrapper-Syntax und haben fol- gende Struktur: php://filter:/AKTION=NAME Aus den verfügbaren Filtern kann man nun wählen. string.strip_tags ist dabei von der Funktion mit strip_tags vergleichbar, spart jedoch den expliziten Aufruf die- ser Funktion. Listing 10.5: wrapperfilter.php – Filtern von Daten während des Lesevorgangs ?php $url = 'www.apache.org'; $page = file_get_contents(http://$url); if (strlen($page) 0) { 422
  • 423.
    Filter verwenden $file = data/$url.gz; $f = fopen($file, 'w'); fwrite($f, $page); fclose($f); echo 'Dateigrouml;szlig;e: ' . filesize($file) . ' Byte'; echo 'brbr'; $farray = file(php://filter/read=string.strip_tags/resource=$file); foreach($farray as $num = $content) { if (strlen(trim($content)) == 0) continue; printf('%03d: %sbr /', $num, $content); } } ? Die einzige Ergänzung zu den vorhergehenden Beispielen besteht in der Angabe des Filters: file(php://filter/read=string.strip_tags/resource=$file) Die Syntax wird transparent, wenn man das zuvor gezeigte Muster zu Rate zieht. php://filter leitet die Sequenz ein. Danach folgen bis zu drei Aktionen, die ent- sprechende Parameter verlangen: í resource=DATENQUELLE Diese Angabe ist zwingend erforderlich. Nutzen Sie für die Angabe von DATENQUELLE wieder die bereits bekannten Wrapper. í read Geben Sie an, welche Filteraktion beim Lesen ausgeführt werden soll. Der Abschnitt »Filter« auf Seite 418 zeigte die möglichen Filter bereits. í write Geben Sie an, welche Filteraktion beim Schreiben ausgeführt werden soll. Der Abschnitt »Filter« auf Seite 418 zeigte die möglichen Filter bereits. Es bietet sich nun an, alle Möglichkeiten miteinander zu kombinieren, was tat- sächlich funktioniert. Listing 10.6: wrapperfiltercompress.php – Filtern und Komprimieren in einem Schritt ?php $url = 'www.apache.org'; $page = file_get_contents(http://$url); 423
  • 424.
    Kommunikation per HTTP,FTP und E-Mail if (strlen($page) 0) { $file = data/$url.gz; $f = fopen(compress.zlib://$file, 'w'); fwrite($f, $page); fclose($f); echo 'Dateigrouml;szlig;e: ' . filesize($file) . ' Byte'; echo 'brbr'; $farray = file(php://filter/read=string.strip_tags/ resource=compress.zlib://$file); foreach($farray as $num = $content) { if (strlen(trim($content)) == 0) continue; printf('%03d: %sbr /', $num, $content); } } ? Der Aufruf des Filters sieht nun folgendermaßen aus: php://filter/read=string.strip_tags/resource=compress.zlib://$file Der Wrapper compress.zlib:// wurde in die resource-Aktion eingebaut. Die Ausgabe zeigt die Wirkung des Filters und zugleich die erzeugte Dateigröße an: Abbildung 10.3: Komprimieren und Filtern in einem Schritt Damit sind die eingebauten Anwendungen erschöpft. In der Praxis dürfte das für die meisten Fälle ausreichen. 424
  • 425.
    Die Stream-Funktionen 10.4 DieStream-Funktionen Generell kann man mit den Stream-Funktionen, auf denen Wrapper und Filter basieren, sehr viel mehr anstellen. Sie sind aber recht komplex in der Anwendung. Sie bieten einen generellen und vereinheitlichten Weg für den Umgang mit Datenübertragungen. Eigene Wrapper und Filter Es gibt allerdings noch die Möglichkeit, eigene Wrapper und natürlich auch eigene Filter zu schreiben. Diese tauchen dann unter eigenem Namen auf. Dies ist jedoch ein deutlich größerer Aufwand und lohnt sich vermutlich nur für Biblio- theksentwickler. Die Darstellung sprengt auch den Rahmen dieses Buches. Wenn Sie eigene Versuche in dieser Richtung unternehmen möchten, müssen Sie für einen eigenen Wrapper eine Klasse schreiben, die auf den Stream-Funktionen auf- baut, also stream_open zum Öffnen, stream_read zum Lesen und stream_close zum Schließen, um nur einige zu nennen. Diese Klasse wird dann mit stream_register_wrapper in Verbindung mit einem eigenen Moniker registriert. Auch eigene Filter lassen sich entwickeln. Dies funktioniert etwas anders. Hier müssen Sie eine Klasse erstellen, die von php_user_filter erbt. Dort ist dann die Methode filter zu schreiben. Mit Hilfe der bereits für Wrapper benutzten Stream- Funktionen erfolgt dann der Zugriff auf den Datenstrom, der beim Durchlaufen der filter-Methode verändert werden kann. Anwendung spezifischer Stream-Funktionen Zusätzlich zu den vorgestellten Techniken gibt es hier noch den Begriff des Stream-Kontexts. Dies sind Parametersammlungen, die einer Stream-verarbeiten- den Instanz übergeben werden, um das Verhalten global zu beeinflussen. Beim Zugriff mittels http:// (als Beispiel) werden Sie sicher schnell bemerken, dass sich bestimmte Seiten nicht ohne weiteres benutzen lassen. Möglicherweise feh- len einige Kopfzeilen der HTTP-Anforderung, wie beispielsweise die akzeptierte Sprache. Die meisten Dateifunktionen erlauben es, einen so genannten Kontext-Parameter anzugeben. 425
  • 426.
    Kommunikation per HTTP,FTP und E-Mail Listing 10.7: streamcontext.php – Parameter für Wrapper ?php $opts = array( 'http'=array( 'method'=GET, 'header'=Accept-language: dern)); $context = stream_context_create($opts); $fp = fopen('http://www.google.com', 'r', false, $context); fpassthru($fp); fclose($fp); ? Das Skript übermittelt an den Wrapper http zwei Informationen: Die Methode für den Abruf ist GET (dies ist auch die Standardmethode) und die akzeptierte Sprache. Im Beispiel wird die Startseite von Google abgerufen. Nutzt man www.google.com (nicht »de«), versucht Google die Sprache dem Parameter Accept- language zu entnehmen und schaltet auf die entsprechende Landesversion um. Abbildung 10.4: Google auf Deutsch – Statt per Browser über einen parametri- sierten Wrapper aufgerufen Damit lassen sich Webseiten recht komfortabel steuern und nutzen. Beachten Sie jedoch unbedingt die Urheberrechte und täuschen Sie niemals fremde Inhalt vor. Vereinbaren Sie besser Kooperationen mit den Anbietern der Datenquellen und profitieren Sie so von den unbe- grenzten Möglichkeiten des Webs. 10.5 E-Mail versenden Das Versenden von E-Mail gehört zu den häufiger benötigten Aufgaben. Es dient auf fast jeder Website dazu, die Nutzerkommunikation zu verbessern. PHP5 unter- 426
  • 427.
    E-Mail versenden stützt E-Mailauf vielfältige Weise, angefangen von der einfachen mail-Funktion, über Socket-Programmierung für die Kommunikation per POP3 bis hin zu ganzen Bibliotheken für IMAP-Postfächer. Grundlagen Auch wenn das einfache Versenden von E-Mail verhältnismäßig leicht zu pro- grammieren ist, sollten Sie einige elementare Grundlagen beherrschen. Dies hilft, bei größeren Projekten den richtigen Ansatz zu finden und schneller zu brauch- baren Ergebnissen zu kommen. Bedenken Sie auch, dass eine Server-zu-Server- Kommunikation immer auch fremde Maschinen beeinflusst. Dem sauberen Pro- tokollfluss kommt deshalb eine große Bedeutung zu. Generell werden in der E-Mail-Kommunikation sowohl verschiedene Protokolle als auch Kodierungsverfahren verwendet. Als Transportprotokolle kommen zum Einsatz: í SMTP SMTP (Simple Mail Transfer Protocol) dient der Weiterleitung von E-Mail von einem Server zu einem anderen. Es dient auch dazu, die erstmalig auf einem Client erzeugte E-Mail an den ersten Server der Übertragungskette weiterzu- leiten. í POP3 POP3 (Post Office Protocol, Version 3) ruft E-Mail von einem Server ab, um diese auf einem Client dem Benutzer zur Verfügung zu stellen. í IMAP4 IMAP4 (Internet Message Access Protocol, Version 4) ist ein Protokoll zum Empfang von E-Mail (wie POP3) und zur Verwaltung der E-Mails eines Clients auf einem Server. Das vollständige Herunterladen, wie bei POP3 erfor- derlich, ist nicht notwendig. Im Internet kommen praktisch nur SMTP und POP3 zum Einsatz. Clients wie Outlook benutzen beide Protokolle, SMTP zum Senden und POP3 zum Empfan- gen. Als Kodierungsverfahren stehen folgende zur Auswahl: í UUEncode Der Name steht für Unix-to-Unix-Encode, ein Kodierverfahren aus der Unix- Welt. Das Verfahren ist inzwischen relativ selten anzutreffen, war in der 427
  • 428.
    Kommunikation per HTTP,FTP und E-Mail Anfangszeit des Internet jedoch das am weitesten verbreitete. Viele Clients beherrschen es heute noch. Vorteile bietet es bei der Kodierung von binären Daten über Transportsysteme, die nur 7-Bit-Zeichen verarbeiten können. Dateien werden um ca. 40% vergrö- ßert. PHP5 verfügt über Funktionen zur Kodierung und Dekodierung dieses Ver- fahrens. í Quoted Printable Diese Verfahren wird häufiger benutzt. Es lässt den Text praktisch unverändert und kodiert nur Sonderzeichen, beispielsweise wird das Leerzeichen zu =20. Das Verfahren eignet sich nur für die Kodierung von Text, nicht für Binär- daten für Anhänge. PHP5 verfügt über Funktionen zur Kodierung und Dekodierung dieses Ver- fahrens. í Base64 Ein anderes Verfahren, dessen Schwerpunkt auf Dateianhängen liegt. Es kodiert ebenfalls auf 7-Bit und vergrößert die Daten dabei um 37%. Es wird heute am häufigsten eingesetzt, weil es das Standardverfahren für Anhänge ist, die nach MIME (siehe unten) verpackt werden. PHP5 verfügt über Funktionen zur Kodierung und Dekodierung dieses Ver- fahrens. í BinHex Ein Verfahren aus der Apple-Welt, das sich außerhalb dieser nicht durchsetzen konnte. Gute Clients sollten es beherrschen. Das Verfahren komprimiert, sodass die ursprüngliche Größe trotz der Umsetzung auf 7-Bit oft unterschrit- ten wird. PHP5 verfügt nicht über Funktionen zur Kodierung und Dekodierung dieses Verfahrens. í MIME Ein globaler Standard (Multipurpose Internet Mail Extensions), der nicht selbst ein reines Kodierverfahren beschreibt, sondern eine vollständige Anweisung zum Aufbau kompletter Nachrichten ist. Zur Kodieren von Binärdaten wird das bereits erwähnt Base64 empfohlen. MIME beschreibt den Aufbau der E-Mail einschließlich der Art der Verpackung von Text, Anhängen, Informati- onszeilen (Kopfzeilen) usw. 428
  • 429.
    E-Mail versenden Vorbereitung Wenn Sieauf einem Entwicklungssystem die E-Mail-Funktion ausprobieren wol- len, müssen Sie über einen SMTP-Server verfügen. Der SMTP-Server Ihres Inter- net-Providers ist vermutlich nicht nutzbar, weil externe Zugriffe eine Autorisierung verlangen, um den Missbrauch durch SPAM-Versender zu vermeiden. Dies ist mit zusätzlichem Aufwand verbunden. Meist verlangen die SMTP-Server, dass unmit- telbar zuvor ein autorisierter POP3-Zugriff erfolgt. Als Lösung können Sie lokal einen eigenen SMTP-Server betreiben. Hier gibt es mehrere Varianten. Windows 2000 Professional, Server, Windows Server 2003 und Windows XP Professional werden mit den Internet-Informationsdiensten geliefert, die einen SMTP-Server enthalten. Der Server ist sehr einfach zu konfigurieren und harmoniert bestens mit PHP. Die Windows 9X/Me- und XP Home-Varianten sind leider außen vor. Hier muss man freie Programme bemühen, die es zuhauf gibt. Linux kommt ebenfalls mit dem integrierten Sendmail-Programm. Hier ist die Konfiguration etwas aufwändiger, außerdem erwartet PHP bei der Nutzung von Sendmail, dass es selbst auf Linux läuft. Steht der SMTP-Server, muss PHP noch ein wenig dafür konfiguriert werden. Wenn Sie einen SMTP-Server für Windows benötigen, weil Sie mit XP Home oder Windows 9x/Me arbeiten, schauen Sie sich den ArGoSoft Mail Server an: http://www.argosoft.com/applications/mailserver/ Läuft er lokal, ist als Name des SMTP-Servers localhost anzugeben. Konfiguration für Windows In der php.ini sind einige Parameter zu setzen: SMTP = localhost smtp_port = 25 Der erste, SMTP, bezeichnet die Adresse des SMTP-Server. Geben Sie den Netzwer- knamen, die IP-Adresse oder einen vollqualifizierten Domänennamen ein. Der zweite, smtp_port, bezeichnet den Port, an dem der SMTP-Server läuft. 25 ist der Standardport. 429
  • 430.
    Kommunikation per HTTP,FTP und E-Mail Dann ist noch die Absenderadresse zu setzen: sendmail_from = webmaster@domain.net Ohne diese Angabe wird die Funktion mail nicht arbeiten. Konfiguration für Linux Unter Linux erledigt nicht PHP in Kombination mit SMTP die Arbeit, sondern alleine Sendmail. Damit entfallen die für Windows erforderlichen Konfigurations- schritte. Dafür müssen Sie Sendmail konfigurieren, wozu der folgende Parameter dient: sendmail_path = -t –i Welche Parameter hier nötig und möglich sind, ist der Dokumentation zu Send- mail zu entnehmen. Sie hängen außerdem von der konkreten Installation ab. Praktische Umsetzung Das Versenden von E-Mail mit PHP ist sehr einfach. Voraussetzung ist allerdings eine Installation, in der PHP einen SMTP-Server erreichen kann, wie zuvor beschrieben. Wird beim Provider gehostet, ist dies in der Regel gewährleistet. Einfache E-Mails versenden Eine einfache E-Mail zu versenden basiert auf nur einer einzigen Funktion: mail. Die Funktion kennt mehrere Parameter: í to Eine Zeichenkette, die die Mail-Adresse angibt, an die gesendet werden soll. Sie können mehrere Adressen angeben und durch Kommata trennen. Beach- ten Sie aber, dass die Empfänger dann alle Adressen sehen können. Besser ist es, Blind Carbon Copy zu verwenden (siehe unten). í subject Eine Zeichenkette, die als Betreff benutzt wird. í message Eine Zeichenkette, die die eigentliche Nachricht darstellt. 430
  • 431.
    E-Mail versenden í header Zusätzliche benutzerdefinierte Kopfzeilen, die das Verhalten der E-Mail beeinflussen. Hier können Angaben zur Priorität, Cc (Carbon Copy), Bcc (Blind Carbon Copy) usw. eingetragen werden. Die Kopfzeilen müssen im Rohformat geschrieben werden. Die Angabe des Parameters ist optional. í parameters Diese Option ist nur anwendbar, wenn PHP auf Linux läuft und zum Versen- den Sendmail. Geben Sie dann Parameter für Sendmail an. Es ist möglich, die Verwendung dieser Option in der Datei php.ini zu sperren. Deshalb kann der Einsatz bei einem Massenhoster nicht garantiert werden. Die Funktion gibt einen Booleschen Wert zurück, der den Erfolg repräsentiert. Außerdem wird eine Fehlermeldung angezeigt, wenn etwas schief läuft. Da Feh- lermeldungen meist stören und die E-Mail-Übertragung in der Praxis durchaus ihre Tücken hat, ist ein Abfangen und Auswerten der Fehler unerlässlich. Das fol- gende Skript zeigt, wie das aussieht: Listing 10.8: sendmail.php – E-Mail versenden und auf mögliche Fehler reagieren ?php ini_set('track_errors', 1); $to = 'test@test.net'; $subject = 'Test-Nachricht'; $message = 'Dies ist ein Test, ob E-Mail geht.'; if (PHP_OS=='WINNT') { ini_set('sendmail_from', 'joerg@krause.net'); $from = ''; } else { $from = From:joerg@krause.netn; } $success = @mail($to, $subject, $message, $from); if ($success) { echo Mail an $to versendet; } else { echo $php_errormsg; 431
  • 432.
    Kommunikation per HTTP,FTP und E-Mail } ? Folgendes ist zu beachten. Wenn Sie mit Windows arbeiten und den Windows- SMTP-Server verwenden, muss die Angabe »From:« in der Datei php.ini einge- stellt werden. Um dies zu vereinfachen, wird der Parameter im Skript dynamisch gesetzt: ini_set('sendmail_from', 'test@test.net'); Außerdem wird das Abfangen des Fehlers programmiert. Dazu ist zuerst die Feh- lerverfolgung in PHP5 einzuschalten: ini_set('track_errors', 1); Beim Senden wird der mail-Funktion ein @ zum Unterdrücken des Fehlers voran- gestellt: $success = @mail($to, $subject, $message, $from); Die Variable $success enthält nun im Fehlerfall FALSE. Darauf kann entsprechend reagiert werden. Trat ein Fehler auf, wird die interne Fehlervariable $php_errormsg benutzt: echo $php_errormsg; Abbildung 10.5: Fehlerausgabe des Pro- gramms: Hier war der Mail-Server falsch kon- figuriert Programmierung mit Kopfzeilen Um weitere Verwendungsmöglichkeiten zu erkunden, müssen Sie sich mit den Kopfzeilen beschäftigen. Diese werden als vierter Parameter in der folgenden Form angegeben: Name: Wertern Dabei ist Name durch den Namen, beispielsweise »From« zu ersetzen und Werte durch die von dieser Kopfzeile verlangten Parameter: From: test@absender.dern rn bezeichnet einen Zeilenumbruch, der in der E-Mail zum Abtrennen der Kopfzeilen benötigt wird. 432
  • 433.
    E-Mail versenden Die Zeilenendezeichen rn werden unter Windows verwendet. Wenn der SMTP-Server unter Unix läuft, ist meist n ausreichend, manchmal erforderlich. Probieren Sie Ihr Skript sorgfältig aus, bevor Sie es auslie- fern. Die folgende Tabelle zeigt einige typische Kopfzeilen, die Sie verwenden können: Name Parameterbeispiel Bedeutung To Empfänger, kann auch eine Liste sein From test@test.de Testname Absender. Die Angabe in spitzen Klammern ist optional. Die Klammern selbst müssen hier geschrieben wer- den, wenn die Angabe erfolgt. Cc test@test.de, weiter@weiter.de Kopie an. Liste von Empfängern, die die E-Mail auch erhalten und die sichtbar ist (Cc = Carbon copy). Trennung durch Kommata. Bcc test@test.de, weiter@weiter.de Kopie an. Liste von Empfängern, die die E-Mail auch erhalten und die nicht sichtbar ist (Bcc = Blind carbon copy). Trennung durch Kommata. Date Mon, 16 Aug 2004 11:29:28 +02 Datum, das beim Empfänger als das Absendedatum angezeigt wird. Nutzen Sie am einfachsten date('r'). Reply-To Gewünschte Antwortadresse. Fehlt die Angabe, wir »To« verwendet. X-Mailer PHP-Mail Version 5 Ein Hinweis, welches Programm zum Versenden benutzt wurde. X-Priority 1 Priorität (niedrig, hoch, …) in der all- X-MSMail- High gemeinen Variante und in einer für Priority Outlook/Outlook Express MIME- 1.0 Version der MIME-Kodierung Version Tabelle 10.1: Namen und Parameter typischer Kopfzeilen für E-Mails 433
  • 434.
    Kommunikation per HTTP,FTP und E-Mail Name Parameterbeispiel Bedeutung Content-type multipart/alternative Inhaltstyp nach MIME-Kriterien Content- base64 Kodierungsverfahren für Anhänge Transfer- Encoding Content- attachment;ntfile- Bezeichnung eines Anhangs Disposition name=anhang.txt Tabelle 10.1: Namen und Parameter typischer Kopfzeilen für E-Mails (Forts.) Dies ist nur eine kleine Auswahl der Möglichkeiten. Sie reicht aber für die häufigs- ten Fälle aus: í Versenden von HTML-Mails í Versenden von Anhängen Anhänge versenden Anhänge und HTML werden in ähnlicher Weise versendet. Die Kodierung folgt dem MIME-Standard. Dabei wird der Inhalt der Nachricht in mehrere Blöcke auf- geteilt, die jeweils eigene Kopfzeilen haben und zusätzlich durch spezielle Gren- zen voneinander getrennt sind. Die Kopfzeilen helfen dem Client, die Nachricht wieder zusammenzubauen. Binäre Daten, wie beispielsweise Bilder, werden meist Base64-kodiert, damit sie auch über 7-Bit-Übertragungssysteme geliefert werden können. Beachten Sie, dass sich Nachrichten dabei um bis zu 30% vergrößern können. Zuerst ein Beispiel: eine Klasse, die Anhänge versenden kann: Listing 10.9: sendattachments.php – Anhänge zusammen mit E-Mail versenden ?php class MailException extends Exception { } class SendHtmlMail { private function getMime($filename) { 434
  • 435.
    E-Mail versenden $types = array( .jpg = image/jpeg, .jpeg = image/jpeg, .png = image/png ); $filename = basename($filename); $ext = strtolower(substr($filename, strrpos($filename, .))); return $types[$ext]; } public function sendMail($to, $from, $headers, $subject, $message, $attachments=) { $bound = uniqid(time()._); $header = From: $fromrn; $content = ''; foreach($headers as $head = $value) $header .= $head: $valuern; if(is_array($attachments)) { $header .= MIME-Version: 1.0rn; $header .= Content-Type: multipart/mixed; boundary=$boundrn; $content .= --$boundrn; $content .= Content-Type: text/html; charset=iso-8859-1rn; $content .= Content-Transfer-Encoding: 8bitrnrn$messagernrn; foreach($attachments as $attch) { if($fp = fopen($attch, rb)) { $bn = basename($attch); $content .= --$boundrn ; $content .= Content-Type: .$this-getMime($attch).; name=$bnrn ; $content .= Content-Transfer-Encoding: base64rn; $content .= Content-Disposition: inline; filename=$bnrnrn ; $content .= chunk_split(base64_encode(fread($fp, filesize($attch)))).rn; 435
  • 436.
    Kommunikation per HTTP,FTP und E-Mail fclose($fp); } else { throw new MailException(Fehler, kann Anhang $attch nicht öffnen.); } } $content .= --$bound--rn; } else { $content .= Content-Type: text/html; charset=iso-8859-1rn; $content .= Content-Transfer-Encoding: 8bitrnrn$messagernrn; } ini_set('track_errors', 1); if (!@mail($to, $subject, $content, $header)) { throw new MailException($php_errormsg); } } } $sm = new SendHtmlMail(); $to = 'joerg@krause.net'; $from = 'joerg@krause.net'; $headers = array('X-Mailer:' = 'PHP '.phpversion()); $msg = 'Test für Anhänge'; $subject = 'Test Nr. 5'; $attachment = array('data/Telefon.jpg', 'data/7650Nokia.jpg'); try { $sm-sendMail($to, $from, $headers, $subject, $msg, $attachment); echo An: $to, Von: $frombrKopfzeilen: ; print_r($headers); } catch (MailException $ex) { die($ex-getMessage()); } ? 436
  • 437.
    E-Mail versenden Das Skriptverwendet die neuen objektorientierten Möglichkeiten in PHP5. So wird eine eigene Ausnahmenklasse MailException definiert, um Ausnahmen gezielt abfangen zu können: class MailException extends Exception Die Klasse ist noch recht einfach. Als Anhänge sind nur Bilder zulässig, deren MIME-Typ die private Methode getMime ermittelt: private function getMime($filename) Für JPEG-Bilder ist der MIME-Typ beispielsweise »image/jpeg«. Der MIME- Standard verlangt die Vereinbarung einer eindeutigen Zeichenkette zur Trennung von Datenblöcken. Eine sichere Methode ist die Nutzung der folgenden Funk- tionen: $bound = uniqid(time()._); Daraus entstehen Zeichenfolgen wie beispielsweise »1092655372_4120990c6010f«. Nach der Generierung der Kopfzeilen, die hier bequemerweise als Array über- geben werden, sind die Anhänge als Nachrichtenblöcke aufbereitet dem Text der E-Mail anzufügen. Zuerst wird dabei geprüft, ob Anhänge vorliegen. Die Methode erwartet diese als ein Array aus Dateinamen: if(is_array($attachments)) Ist das der Fall, soll MIME verwendet werden. Diese Angabe gehört in den Hea- der: $header .= MIME-Version: 1.0rn; Dazu muss mitgeteilt werden, dass mehrere Blöcke verschiedenen Inhalts (mixed) folgen: $header .= Content-Type: multipart/mixed; boundary=$boundrn; In dieser Zeile wird auch die Blockgrenze mit Hilfe des Parameters boundary fest- gelegt. Der Inhalt beginnt nun mit einem solchen Abgrenzungszeichen. Die beiden Minuszeichen leiten die Grenze ein und sind zwingend erforderlich: $content .= --$boundrn ; Vor die Anhänge wird der Text der Nachricht gestellt. Die Klasse ist in der Lage, auch gleich HTML-E-Mails zu versenden: $content .= Content-Type: text/html; charset=iso-8859-1rn; 437
  • 438.
    Kommunikation per HTTP,FTP und E-Mail Wenn Sie Text versenden möchten, nutzen Sie den MIME-Typ »text/text« anstatt »text/html«. Der Zeichensatz erlaubt die Verwendung des üblichen westeuropä- ischen Standardzeichensatzes für Windows, sodass auch Umlaute übertragen wer- den. Dann folgt die Kodierung und die Nachricht selbst: $content .= Content-Transfer-Encoding: 8bitrnrn $messagernrn; Hier wird nichts zusätzlich kodiert, da es sich nur um ASCII handelt. Das Übertra- gungsmedium wird aber angewiesen, nach Möglichkeit mit 8 Bit zu übertragen, damit die erweiterten Zeichen des Zeichensatzes erhalten bleiben. Nun beginnt der Zusammenbau der Anhänge. Jeder Anhang wird einzeln erstellt. foreach($attachments as $attch) Zuerst wird die Datei im Binärmodus geöffnet: if($fp = fopen($attch, rb)) Dann wird der Basisname ermittelt. Diese Angabe wird übertragen, während der lokale Pfad, aus dem die Dateien gelesen werden, dem Empfänger nicht mitgeteilt wird: $bn = basename($attch) Der Anhang wird dann wieder mit einem Grenzzeichen eingeleitet: $content .= --$boundrn ; Es folgen drei Kopfzeilen, die den Anhang beschreiben. Sie sehen nach der Verar- beitung folgendermaßen aus: Content-Type: image/jpg; name=telefon.jpg Content-Transfer-Encoding: base64 Content-Disposition: inline; filename=telefon.jpg; Nun muss der Anhang noch gemäß den MIME-Kriterien zerlegt und kodiert wer- den. Die folgenden Funktionen erledigen dies: chunk_split(base64_encode(fread($fp, filesize($attch)))) Lesen Sie den Ablauf von innen nach außen: 1. fread liest die Datei komplett, die Dateigröße wird dabei mit filesize ermit- telt 438
  • 439.
    E-Mail versenden 2. base64_encodekodiert die Datei mit dem Base64-Verfahren (wie in der Kopf- zeile gefordert) 3. chunk_split zerlegt den Datenstrom in Blöcke, die verhindern, dass Mail-Ser- ver störende Leerzeilen oder Umbrüche einfügen Am Ende aller Anhänge wird eine weitere Grenze eingefügt, die mit zwei Minus- zeichen abschließt und so dem Mailclient mitteilt, dass keine weiteren Anhänge folgen: $content .= --$bound--rn; Der Rest ist trivial. Die E-Mail wird versendet und der Erfolg mit einer if-Anwei- sung überwacht: if (!@mail($to, $subject, $content, $header)) Trat ein Fehler auf, wird eine Ausnahme ausgelöst: throw new MailException($php_errormsg); Das Hauptprogramm definiert die nötigen Angaben zum Betrieb der Klasse. Die einzige öffentliche Methode sendMail macht die Verwendung extrem einfach. Abbildung 10.6: E-Mail mit zwei Anhängen in einem E-Mail- Client, mit der Klasse SendHtml- Mail versendet Um HTML zu versenden, muss der Nachrichtenkörper lediglich eine gültige HTML-Seite enthalten, also wenigstens mit den Tags html/html umschlossen sein. Abbildung 10.7: HTML-Mail, mit demselben Pro- gramm versendet 439
  • 440.
    Kommunikation per HTTP,FTP und E-Mail Andere Lösungen Das Versenden von HTML oder Anhängen wird von Hunderten im Internet kur- sierenden Klassen und Funktionssammlungen unterstützt. Die vorliegende Vari- ante ist insofern dennoch interessant, weil sie ausreichend kompakt ist, um komplett verstanden zu werden. Außerdem nutzt sie die neuen Funktionen in PHP5, was nahezu alle anderen Lösungen nicht tun. Sie ist außerdem hinrei- chend alltagstauglich und leicht erweiterbar, wenn es erforderlich sein sollte. 10.6 Funktions-Referenz Stream-Funktionen Funktion Bedeutung stream_context_create Erzeugt ein Kontext-Handle, das andere Funktionen nutzen, um auf so konfigurierte Streams zurückzugrei- fen. Der Kontext-Parameter ist neu in vielen Dateifunk- tionen in PHP5 hinzugekommen. stream_context_get_default Ermittelt den aktuellen Kontext eines Streams. stream_context_get_options Ermittelt gesetzte Optionen für Kontexte, Streams oder Wrapper. stream_context_set_option Setzt Optionen für Kontexte, Streams oder Wrapper. stream_context_set_params Setzte Parameter für Kontexte, Streams oder Wrapper. stream_copy_to_stream Kopiert Daten von einem Stream zu einem anderen. stream_filter_append Fügt einem Stream ein Filter hinzu. Die Filteraktion wird nach dem Übertragen der Daten ausgeführt. stream_filter_prepend Fügt ein Filter vor einem Stream an. Die Filteraktion wird vor dem Übertragen der Daten ausgeführt. stream_filter_register Registriert einen benutzerspezifischen Filter. Das Filter kann durch eine Klasse implementiert werden, die von php_user_filter abgeleitet wird. Tabelle 10.2: Funktions-Referenz für Stream-Funktionen 440
  • 441.
    Funktions-Referenz Stream-Funktionen Funktion Bedeutung stream_get_contents Liest ausstehende Daten eines Streams in eine Zeichen- kette. stream_get_filters Ermittelt eine Liste von registrierten Filtern. stream_get_line Liest eine Zeile aus einem Stream bis zu einem verein- barten Trennzeichen. stream_get_meta_data Ermittelt Kopfzeilen und Metadaten aus einem Stream. stream_get_transports Ermittelt eine Liste von Sockets, die Transportaufgaben auf niedriger Ebene übernehmen. stream_get_wrappers Ermittelt eine Liste registrierter Streams. stream_register_wrapper Alias für stream_wrapper_register. stream_select Äquivalent für den select-Systemaufruf. stream_set_blocking Setzt blocking/non-blocking-Mode für einen Stream. stream_set_timeout Setzt die Abbruchzeit für einen Stream. stream_set_write_buffer Setzt Dateipufferung für einen Stream. stream_socket_accept Akzeptiert eine Socket-Verbindung für den Stream. Die Verbindung muss mit stream_socket_server erstellt worden sein. stream_socket_client Eröffnet eine Socket-Verbindung ins Internet oder zu einer Unix-Domain. stream_socket_enable_crypto Schaltet die Verschlüsselung für eine bestehende Ver- bindung ein oder aus. stream_socket_get_name Ermittelt den Namen eines lokalen oder remoten Sockets. stream_socket_recvfrom Empfängt Daten von einem Socket, unabhängig davon, ob die Verbindung noch besteht oder nicht. stream_socket_sendto Sendet eine Nachricht an einen Socket, unabhängig davon, ob die Verbindung noch besteht oder nicht. Tabelle 10.2: Funktions-Referenz für Stream-Funktionen (Forts.) 441
  • 442.
    Kommunikation per HTTP,FTP und E-Mail Funktion Bedeutung stream_socket_server Erstellt eine Socket-Verbindung ins Internet oder zu einer Unix-Domain. stream_wrapper_register Registriert einen benutzerspezifischen Wrapper, der als PHP-Klasse implementiert sein muss. Tabelle 10.2: Funktions-Referenz für Stream-Funktionen (Forts.) 10.7 Kontrollfragen 1. Welche Aufgabe erfüllen Wrapper? 2. Was muss ein fremder Entwickler beachten, wenn er einen von Ihnen entwickeltes Filter verwendet? 3. Was bedeuten die Kopfzeilen Cc: und Bcc: in einer E-Mail? 4. Welches Protokoll nutzt die mail-Funktion zur Übertragung der Daten? 442
  • 443.
  • 444.
    Datenbankprogrammierung 11.1 Prinzip derDatenbankprogrammierung Die Datenbankprogrammierung ist für fast alle Webseiten von elementarer Bedeu- tung. Es geht dabei immer wieder darum, anfallende Daten zustands- und seiten- unabhängig zu erfassen und wieder bereitzustellen. Der Datenbankserver arbeitet – auch wenn das entsprechende Programm auf derselben Maschine wie der Web- server läuft – unabhängig von PHP oder Apache. Um eine Datenbank zu nutzen, sind deshalb mehrere Schritte erforderlich, die den Einstieg nicht einfach machen (wenn man es kann, mag dies primitiv klin- gen): í Aufsetzen oder Beschaffen eines Datenbankservers í Aktivierung eines Client-Moduls in PHP, das mit dem Server kommunizieren kann í Herstellen einer Verbindung zum Datenbankserver am Beginn des Skripts, meist unter Angabe der IP-Adresse oder des Netzwerknamens í Herstellen einer Verbindung zu einer konkreten Datenbank, die der Daten- bankserver verwaltet í Aufbau einer Abfrageverbindung, um SQL-Kommandos an den Datenbank- server zu senden í Auswerten der Antwort und Aufbereitung der Daten, um diese für die Ausgabe oder für weitere Abfragen verwenden zu können Insgesamt ist also einiger Aufwand zu treiben. Dafür hat man ein System, das mit drei Datensätzen ebenso gut zurechtkommt wie mit 3 Millionen. Außerdem lassen sich auch komplexe Daten mit vielfältigen Verknüpfungen verwalten. Einiger Lernaufwand ist dennoch nötig. Während Sie das Aufsetzen und Verwal- ten des Datenbankservers an den Provider oder einen Administrator abschieben können, bleibt die Last der Programmierung allein beim Entwickler der Website. Unerlässlich sind deshalb SQL-Kenntnisse. 11.2 Die universelle Abfragesprache SQL Dieser Abschnitt führt ganz allgemein in SQL ein. Dabei wird, soweit nichts ande- res explizit erwähnt wird, sowohl MySQL als auch SQLite unterstützt. Das heißt, 444
  • 445.
    Die universelle AbfragespracheSQL die entsprechende Abfragen funktionieren in beiden Fällen. Auf die Besonderhei- ten der beiden Systeme wird in eigenen Abschnitten eingegangen. Wenn Sie SQL bereits kennen, nutzen Sie diesen Abschnitt zum Auffrischen und Wiederholen. SQL ist im Zusammenhang mit der PHP-Programmierung enorm wichtig und sollte unbedingt beherrscht werden. Was ist SQL? SQL, am besten englisch ausgesprochen (»ess-kju-ell«), steht für Structured Query Language – Strukturierte Abfragesprache. SQL wird verwendet, um mit einer Datenbank zu kommunizieren. Nach der Definition der Normungsinstitution ANSI (American National Standards Institute) ist SQL der verbindliche Industrie- standard für die Abfrage relationaler Datenbankmanagementsysteme (RDBMS). SQL-Anweisungen bestimmen, wie eine Abfrage oder Befehl aussehen soll, den das System empfängt und ausführt. Der Standard erlaubt es, mit einem Basisbe- fehlssatz nahezu alle RDBMS zu bedienen. Abweichend von den Basisbefehlen verfügen alle am Markt operierenden System – egal ob kommerziell oder Open Source – über mehr oder wenige sinnvolle und natürlich zu allen anderen inkom- patible Erweiterungen. MySQL ist hierin ebenso wenig eine Ausnahme wie SQLite, Oracle, MS SQL, Sybase oder Access. Der Basisbefehlssatz, den man unbedingt beherrschen muss, wird jedoch weitgehend identisch unterstützt. Dazu gehören SELECT (Abfragen), UPDATE (Aktualisieren), DELETE (Löschen), INSERT (Einfügen) und DROP (Entfernen). Tabellen und Abfragen Relationale Datenbanken halten Daten in Tabellen. Tabellen werden durch einen eindeutigen Namen identifiziert, der in den Abfragebefehlen anzugeben ist. Tabellen bestehen aus Spalten, die jeweils einen ganz bestimmten Datentyp auf- nehmen können. Sie bestehen auch aus Reihen, in denen zusammengehörende Daten gespeichert sind. Eine einfache Tabelle mit Daten kann in etwa folgender- maßen dargestellt werden: 445
  • 446.
    Datenbankprogrammierung Stadt Bundesland Tief Hoch Berlin Berlin 5 13 München Bayern 2 16 Hamburg Hamburg 7 12 Frankfurt Hessen 7 11 Tabelle 11.1: Eine sehr einfache SQL-Tabelle mit Wetterdaten (Tabellenname: Wetter) Die Tabelle abfragen: SELECT Grundsätzlich lässt sich jede Datenbank vollständig über SQL-Befehle bedienen. Gerade am Anfang wird das eher weniger der Fall sein. Alle Datenbankprogramme bieten mehr oder weniger elegante dialogorientierte Oberflächen, die zumindest das Anlegen der Tabellen und das Eingeben der ersten Daten erlauben, ohne die Anweisungen selbst zu beherrschen. Bei Abfragen sieht es anders aus. Für viele kommerzielle Datenbanken gibt es so genannte Query Builder und einige freie Programme mit entsprechender Funk- tion sind auch verfügbar. Derartige Tools verhindern jedoch, dass man SQL wirk- lich lernt. Das ist fatal, denn früher oder später stößt jedes dieser Programme an seine Grenzen. Das sind aber selten die Grenzen von SQL. Die Beschäftigung mit der Abfragesyntax ist deshalb unbedingt zu empfehlen. Der prinzipielle Aufbau einer Abfrage folgt folgendem Schema: SELECT Spaltename1 [, Spaltenname2, ...] FROM Tabelle [WHERE Bedingungen] Die eckigen Klammern deuten optionale Angaben an. Die Anweisungen sollten außerdem, auch wenn sie hier aus Gründen der Lesbarkeit mehrzeilig erscheinen, als eine Zeichenkette gelesen werden. Auf die oben gezeigte Tabelle »Wetter« bezogen, sieht eine konkrete Abfrage beispielsweise folgendermaßen aus: SELECT Stadt, Bundesland FROM Wetter WHERE Tief 5 Entfällt die mit WHERE eingeleitete Bedingung, werden alle Reihen der Tabelle zurückgegeben. Die Auswahl der Spalten bestimmt die Liste unmittelbar hinter SELECT. Hier kann alternativ auch das Sternchen (*) angegeben werden, um alle Spalten zu holen. 446
  • 447.
    Die universelle AbfragespracheSQL SELECT * FROM ist die häufigste Abfrage – zumindest in der Literatur. In der Praxis sollte dies nicht so sein, weil die Abfrage von Spalten, die nicht benötigt werden, unnütz Zeit kostet. Die Darstellung auf dem Papier lässt sich aber verkürzen und die Angaben sind weniger spezi- fisch, was zur Erklärung manchmal hilfreich ist. Hat eine Abfrage mit SELECT Erfolg, besteht die Antwort aus einem oder mehreren so genannten Datensätzen. Jeder Datensatz kann wiederum ein oder mehrere Fel- der enthalten. Der Datensatz ist quasi eine konkrete Version einer Reihe der Datenbank, ergänzt um berechnete Spalten oder reduziert auf ausgewählte Spal- ten. Nur wenn die Abfrage aus dem einfachen * besteht, entspricht ein Datensatz einer Reihe. Bedingungen formulieren: WHERE Die WHERE-Bedingung ist sehr wichtig. Sie folgt denselben Prinzipien wie if in PHP5, das heißt, es muss sich ein Boolescher Ausdruck formulieren lassen. Dazu gehören in erster Linie die Vergleichsoperatoren: í Größer als; meist nur mit numerischen Werten sinnvoll í Kleiner als; meist nur mit numerischen Werten sinnvoll í = Größer als oder gleich; meist nur mit numerischen Werten sinnvoll í = Kleiner als oder gleich; meist nur mit numerischen Werten sinnvoll í Ungleich; kann fast immer verwendet werden í = Gleich; kann fast immer verwendet werden Ebenso wichtig sind die Booleschen Operatoren, die bereits aus PHP bekannt sind und sich in SQL analog verhalten: OR, AND, NOT. Beachten Sie beim Gleichheitszeichen, dass dies in SQL einfach ist, nicht doppelt wie in PHP. Ein doppeltes Gleichheitszeichen gibt es in SQL generell nicht. 447
  • 448.
    Datenbankprogrammierung Neben diesen kenntSQL aber auch einige sehr spezielle Operatoren, die in PHP nicht vorhanden sind: í LIKE Dieser Operator erlaubt eine Ähnlichkeitssuche mit Platzhaltern. SQL verwen- det sehr untypische Platzhalter: % steht für jedes beliebige Zeichen in beliebi- ger Anzahl und _ für genau ein beliebiges Zeichen. Beispiel: SELECT * FROM Wetter WHERE Stadt LIKE 'B%' Diese Anweisung gibt alle Städte zurück, die mit dem Buchstaben »B« begin- nen. í IN SQL verwendet zur Referenz auf Datensätze oft deren Schlüsselnummern (darauf wird noch genauer eingegangen). Listen solcher Referenzen kann man mit IN verwenden. Aber auch einfache Zahlenvergleich sind gut möglich. Beispiel: SELECT * FROM Wetter WHERE Tief IN (1,3,5,7) Dieser Befehl fragt alle ungeraden Tiefsttemperaturwerte aller Datensätze ab. í BETWEEN Hiermit lassen sich Wertebereiche abfragen. Beispiel: SELECT * FROM Wetter WHERE Hoch BETWEEN 10 AND 15 Zeichenketten müssen in SQL mit einfachen Anführungszeichen umschlossen werden. Die meisten Systeme verkraften zwar auch doppelte, die Zusammenarbeit mit PHP profitiert aber erheblich von einer einheitlichen Schreibweise, weshalb einfache Anführungszeichen zum Standard gehören sollten. Die SELECT-Anweisung kann weitaus mehr. Dazu gehören Befehle zum Sortieren, Filtern, Verknüpfen mit anderen Tabellen usw. Um dies ausprobieren zu können, brauchen Sie jedoch mehr Tabellen, die erstmal erzeugt und befüllt sein müssen. Tabellen anlegen und füllen Werden Tabellen per Code erzeugt, braucht man eine spezielle Anweisung dafür. Dieser Abschnitt zeigt, wie Tabellen erzeugt und gefüllt werden. 448
  • 449.
    Die universelle AbfragespracheSQL Tabelle erzeugen: CREATE TABLE In SQL heißt diese CREATE TABLE. Die grundlegende Syntax lautet: CREATE TABLE Tabellenname (SpaltenName DatenTyp [Einschränkung], [SpaltenName DatenTyp [Einschränkung], ...] ) Die Angabe der Einschränkung bestimmt nähere Eigenschaften der Spalte, abhän- gig vom gewählten Datentyp. Die Angabe ist optional, weil alle Datentypen mit bestimmten Basiseigenschaften ausgestattet sind. Der Spaltenname ist frei wählbar. Die meisten Systeme verkraften sogar Leerzeichen (keine gute Idee), wenn der Name in Anführungszeichen steht. Der Datentyp einer Spalte Der Datentyp ist eine sehr wichtige Eigenschaft einer Spalte. SQL ist prinzipiell typstreng und überwacht die Einhaltung der Daten in den Feldern umfassend. In der Praxis wird dies etwas aufgeweicht betrachtet. So konvertiert SQLite intern alles so lange, bis es passt, und akzeptiert meist alles. MySQL hält die Typen streng ein, konvertiert aber auch weit reichend, und die Daten »passend« zu machen. Andere Datenbanken sind strenger und reagieren mit Fehlermeldungen auf fal- sche Typen. Generell geht SQL aber nie so locker mit Datentypen um wie PHP. SQL verfügt über eine Vielzahl von Funktionen, die speziell für Zeichenketten oder Zahlen gedacht sind. Der falsche Datentyp für die zu berechnende Spalte führt dann in jedem Fall zu einer Fehlermeldung. Die Angabe des Datentyps erfolgt durch ein aussagekräftiges Schlüsselwort und optional durch Parameter. Die folgende Tabelle zeigt die wichtigsten Angaben, die von fast allen SQL-Datenbanken akzeptiert werden. Datentyp Beschreibung CHAR(Anzahl) Feld mit fester Anzahl von Zeichen. VARCHAR(Maximum) Feld mit variabler Anzahl Zeichen, begrenzt auf ein Maxi- mum. INT(Breite) Ganzzahlfeld mit der durch Breite angegebenen Anzahl Stellen 449
  • 450.
    Datenbankprogrammierung Datentyp Beschreibung DATE Ein Datumsfeld (nur die Angabe des Datums ist möglich). DATETIME Ein Datums- und Zeitfeld (enthält Datums- und Zeitinfor- mationen). FLOAT(Breite, Dezimal) Gleitkommazahl mit Breite Stellen und Dezimal Nachkom- mastellen. TEXT Längere Textdaten (VARCHAR und CHAR sind auf 255 Zeichen begrenzt). BLOB Größere Binärdaten (BLOB = Binary Large Objects). Die meisten Datenbanken bieten vordefinierte Variationen von INT für verschie- dene Mengen, beispielsweise BIGINT oder TINYINT in MySQL. Tabellen erzeugen Das folgende Beispiel zeigt, wie die am Anfang benutzte Tabelle erzeugt wird: CREATE TABLE Wetter (Stadt VARCHAR(100), Bundesland VARCHAR(30), Tief INT(2), Hoch INT(2)) Generell gilt die Regel, dass die Datentypen so fein und begrenzend wie möglich ausgelegt werden sollten. So dürfte es in Mitteleuropa kaum vorkommen, dass drei- stellige Temperaturen auftreten. Deshalb wird INT(2) geschrieben. Es gibt kein Bundesland, das mehr als 30 Zeichen für den Namen benötigt (Mecklenburg-Vor- pommern ist der längste Name mit genau 22 Zeichen). Beachten Sie, dass die Temperaturangabe -11°C in einem INT-Feld auch nur zwei Stellen benötigt, weil das Vorzeichen separat gespeichert wird. Derartige Beschränkungen verhindern zumindest teilweise, dass unsinnige Daten in die Datenbank gelangen. Sie führen freilich, wenn es dennoch versucht wird, zu Fehlermeldungen im Skript, die gesondert behandelt werden müssen. Inkonsis- tente Datenbestände sind jedoch weitaus schlimmer als Fehler bei der Eingabe, die frühzeitig auf das Problem hinweisen. 450
  • 451.
    Die universelle AbfragespracheSQL Einschränkungen Bei der Definition der Syntax für CREATE TABLE war von Einschränkungen die Rede. Dies sind Angaben, die den möglichen Inhalt unabhängig vom Datentyp bestimmen. Drei solcher Zusatzangaben sind besonders wichtig: í UNIQUE Hiermit wird bestimmt, dass alle Werte in der Spalte nur ein Mal vorkommen dürfen, das heißt, sie müssen eindeutig (engl. unique) sein. í PRIMARY KEY Bestimmt, dass diese Teile den Primärschlüssel stellt. Zur Bedeutung der Schlüssel folgt noch eine detaillierte Betrachtung. PRIMARY KEY impliziert immer UNIQUE. í AUTO_INCREMENT Nicht offizieller SQL-Standard aber dennoch von fast allen Datenbank unter- stützt sind Felder, deren Werte automatisch erzeugt werden, ohne dass dies beim Einfügen angegeben werden muss. í NULL, NOT NULL NULL erklärt, dass das betreffende Feld beim Schreiben der Daten leer bleiben darf, NOT NULL bestimmt, dass dies nie der Fall sein darf. í DEFAULT Bestimmt einen Standardwert, wenn bei der Eingabe der Daten kein Wert angegeben oder das Feld überhaupt nicht spezifiziert wurde. Es gibt weitaus mehr Einschränkungen als die gezeigten, allerdings werden die meisten weder von MySQL noch von SQLite unterstützt, weshalb die Ausführun- gen auf die elementaren Angaben beschränkt werden. Daten einfügen Nachdem die Tabelle existiert, kann sie mit Daten gefüllt werden. Dazu wird die Anweisung INSERT benutzt. INSERT verlangt die Angabe des Tabellennamens, einer Liste der Spalten, die mit Daten bestückt werden, und eine Liste der Werte, die hineingeschrieben werden. Die Angabe der Spalten kann entfallen, wenn alle beschrieben werden. Eine weitere Wetterangabe in der Mustertabelle könnte fol- gendermaßen eingefügt werden: 451
  • 452.
    Datenbankprogrammierung INSERT INTO Wetter (Stadt, Bundesland, Hoch, Tief) VALUES ('Dresden', 'Sachsen', 17, 8) Beachten Sie, dass Zeichenketten in einfachen Anführungszeichen stehen, nume- rische Werte jedoch nicht. Die Anzahl der Werte hinter VALUES muss exakt der Anzahl der Spalten entsprechen, damit die Zuordnung stimmt. Spalten, die hier nicht auftreten, werden entsprechend den Einschränkungsregeln befüllt, also mit Standardwerten (DEFAULT), automatischen Werten (AUTO_INCREMENT) oder NULL- Werten (NULL). Aktualisieren und Löschen von Daten Nachdem die Daten nun in der Tabelle sind und auch gelesen werden können, sind Lösch- und Aktualisierungsvorgänge an der Tagesordnung. Aktualisierung von Daten: UPDATE Die Änderung der Inhalte wird mit der Anweisung UPDATE vorgenommen. Die grundlegende Syntax folgt folgendem Schema: UPDATE TabellenName SET SpaltenName = Wert [, SpaltenName = Wert, ...] [WHERE Bedingung] Die Bedingung entspricht weitgehend den bei SELECT möglichen Angaben. Ohne Bedingung werden immer alle Spalten geändert. Die Zuweisung neuer Werte kann mit Konstanten oder Berechnungen, auch mit Referenzen auf andere Spal- ten erfolgen. Im Beispiel lässt sich die Temperatur für eine bestimmte Stadt folgendermaßen ändern: UPDATE Wetter SET Hoch = 17, Tief = 23 WHERE Stadt = 'Berlin' Man kann auch alle Temperaturen einfach erhöhen: UPDATE Wetter SET Hoch = Hoch + 1 452
  • 453.
    Die universelle AbfragespracheSQL Löschen von Daten: DELETE Dem Löschen dient die Anweisung DELETE. Die Anwendung entspricht dem Schema der bisher vorgestellten Befehle: DELETE FROM TabellenName [WHERE Bedingung] Auch wenn die Bedingung optional ist, die Angabe dürfte in den allermeisten Fäl- len unbedingt erforderlich sein – sonst ist die Tabelle nämlich leer. Für die Bedingung kann wieder ein Ausdruck nach dem bei SELECT gezeigten Schema benutzt werden. Beachten Sie, dass SQL kein »Undo/Rückgängig« etc. kennt – was weg ist, ist endgültig weg. Löschen und Leeren von Tabellen: DROP und TRUNCATE Um eine Tabelle komplett wieder loszuwerden, nutzen Sie die Anweisung DROP: DROP TabellenName Ebenso einfach geht das Leeren, das alle Daten entfernt, aber die Tabelle selbst erhält: TRUNCATE TabellenName Gegenüber der Anweisung DELETE FROM TabellenName (ohne Bedingung) ist, TRUN- CATE schneller. Der Effekt ist derselbe – alles ist weg. Fortgeschrittene Abfragen mit SELECT SELECT allein ist ungeheuer mächtig. Der korrekte Umgang damit verlangt aber einiges an Hintergrundwissen. Dennoch kommt man sehr schnell nicht mehr ohne die Basisabfragen aus. Dieser Abschnitt zeigt die Anwendung von SELECT auf dem Niveau, wie es auf durchschnittlich komplexen PHP-Seiten durchaus zur Anwendung kommen kann. Wenn Sie noch weiter lernen möchten, ist der Zugriff auf spezielle Literatur erforderlich. SELECT komplett betrachtet Mit SELECT werden Datenbanken abgefragt. Dabei ist FROM die einzige Angabe, die zwingend erforderlich ist. Das bereits vorgestellte WHERE ist optional. Insgesamt sind es fünf derartige Operatoren, die zum Einsatz kommen können: 453
  • 454.
    Datenbankprogrammierung í FROM Bestimmt die Tabelle (oder mehrere Tabellen), aus denen gelesen wird. í WHERE Schränkt die Auswahl durch eine global wirkende Bedingung ein. í GROUP BY Gruppiert Ergebnisse, sodass sie mit anderen zusammengefasst werden kön- nen. í HAVING Wendet Auswahleinschränkungen auf Gruppen an. í ORDER BY Sortiert die Ergebnisse. Bei der Auswahl der Spalten kann zudem bestimmt werden, ob alle oder nur ein- deutige Ergebnisse zurückgegeben werden sollen: í ALL í DISTINCT Die Angabe ALL entfällt in den allermeisten Fällen, weil dies der Standardwert ist. Für die Abfrage eindeutiger Werte schreiben Sie beispielsweise: SELECT DISTINCT Bundesland FROM Wetter Dies verhindert, dass die mehrfach in der Tabelle auftretenden Bundesländer auch mehrfach ausgegeben werden. GROUP BY gruppiert Ergebnisse und wenn Einschränkungen bei der Auswahl der Gruppen gefragt sind, dann ist HAVING die erste Wahl. Nach HAVING können diesel- ben Booleschen Ausdrücke stehen wie nach WHERE. Mehr dazu weiter unten. Aggregierende Funktionen Aggregierende Funktionen führen Berechnungen mit der durch SELECT erstellten Auswahl von Daten aus. Die häufigste und einfachste ist COUNT – hiermit wird schlicht die Anzahl der Reihen ermittelt. Wichtig sind folgende Funktionen: í COUNT, COUNT(*) Hiermit wird die Anzahl der ausgewählten oder (*) aller Reihen der Abfrage ermittelt. 454
  • 455.
    Die universelle AbfragespracheSQL í MAX, MIN Enthält die betroffene Spalte Werte, die eine Ordnung bilden, kann hiermit der größte bzw. kleinste Wert ermittelt werden. í SUM Für numerische Spalten ermittelt diese Funktion die Summe. í AVG Für numerische Spalten ermittelt diese Funktion den Durchschnitt. Beachten Sie, dass die Aggregatfunktionen keine Liste von Daten erzeugen, sondern skalare Werte. Dies muss bei der Auswertung mit PHP-Funktionen berücksichtigt werden. Bei den PHP-Beispielen wird darauf nochmals explizit eingegangen. Die Anwendung in SQL sieht folgendermaßen aus: SELECT AVG(Hoch) FROM Wetter Damit erhält man die durchschnittliche Tageshöchsttemperatur aus der Wetter- Tabelle. Selbstverständlich sind auch hier WHERE-Bedingungen eine gute Idee: SELECT AVG(Hoch) FROM Wetter WHERE Bundesland = 'Bayern' Nun erhalten Sie die durchschnittliche Tageshöchsttemperatur aller bayerischen Städte. Gruppierungen Gruppierungen mit GROUP BY wurden bereits kurz erwähnt. Der Sinn ist weniger der damit erreichbare Sortiereffekt, sondern die Möglichkeit, die Aggregatfunktio- nen auf die Gruppen anzuwenden. SELECT MAX(Hoch), Bundesland FROM Wetter GROUP BY Bundesland HAVING Bundesland LIKE 'B%' Diese Abfrage ermittelt die höchste Tagestemperatur aller Bundesländer, die mit »B« beginnen. Die Einschränkung mit HAVING ist freilich optional, meist genügt die Gruppierung alleine. Wichtig ist, dass die Liste der Spalten hinter GROUP BY in der Liste der Spalten hinter SELECT enthalten sein muss. Gruppierung und Aus- gabe korrespondieren immer. Die Aggregatfunktionen sind davon nicht betroffen, deshalb kann die Funktion MAX im Beispiel hinzugefügt werden. 455
  • 456.
    Datenbankprogrammierung Ergebnisse sortieren Tabellen sindin SQL niemals sortiert oder geordnet. Allein die Abfrage entschei- det, wie die Daten auszugeben sind. Um die entsprechende Kontrolle zu bekom- men, wird ans Ende der Anweisung ORDER BY angehängt. Zwei zusätzliche Klauseln bestimmen die Richtung: í ASC Dies steht für »ascending«, also aufsteigend. í DESC Dies steht für »descending«, also absteigend. Hinter ORDER BY lassen sich mehrere Spalten angeben, um uneindeutige Sortierun- gen nach nur einer Spalte zu vermeiden: SELECT * FROM Wetter ORDER BY Bundesland, Stadt ASC Das konkrete Sortierverhalten kann SQL nicht bestimmen. Dazu bietet jedes Datenbanksystem verschiedene Einstellmöglichkeiten, die nicht standardisiert sind. Dies betrifft beispielsweise die Einordnung von Umlauten ins Alphabet. Die Klausel ASC kann auch entfallen, denn die aufsteigende Sortierung ist der Standardfall. Dies gilt aber nur, wenn ORDER BY angegeben wurde. Ohne dieses Schlüsselwort wird unsortiert ausgegeben. Funktionen SQL verfügt über eine reiche Funktionspalette, unter anderem: í Mathematische Funktionen í Zeichenkettenverarbeitung í Datumsfunktionen í Systemfunktionen Leider ist der Standard hier kaum beachtet worden. Deshalb gibt es teilweise dras- tische Unterschiede in Ausstattung und Syntax bei verschiedenen Datenbanken. Der Abschnitt 11.4 »Erste Schritte mit MySQL und MySQLi« ab Seite 477 zeigt die konkreten Funktionen für MySQL und im Kapitel Die integrierte Datenbank SQLite ab Seite 497 finden Sie entsprechende Auflistung für SQLite. 456
  • 457.
    Die universelle AbfragespracheSQL Verknüpfungen zwischen Tabellen Der Name »Relationale Datenbanken« enthält einen Hinweis auf eine ganz spezi- fische Eigenschaft – die Relationen oder Beziehungen. Richtig wertvoll wird der Umgang mit Datenbanken erst, wenn man zwischen Tabellen derartige Beziehun- gen aufbaut. Das ist in fast allen Anwendung auch notwendig, um die Datenhaltung effektiv und nicht redundant zu halten. Wenn Sie die gut gefüllte Wetter-Tabelle sehen, werden Sie bemerken, dass dieselben Bundesländer immer wieder aufgeführt sind. Ändert man nun den Namen, beispielsweise wegen eines Schreibfehlers, dann müsste man alle betreffenden Felder ändern. Hat man dabei schon einen Fehler, beispielsweise aus früheren Einträgen, wird die Datenbank inkonsistent. Deshalb versucht man, redundante Daten zu vermeiden. Dazu wird eine zweite Tabelle erstellt und mit der ersten verknüpft. Damit das Verknüpfen funktioniert, erhält außerdem jede Tabellen einen Primärschlüssel, der jeden Datensatz eindeutig kennzeichnet. Normalisierungen Folgende neue Tabelle ist also für die Wetter-Anwendung erforderlich: ID Name 1 Berlin 2 Bayern 3 Hamburg 4 Hessen Tabelle 11.2: Tabelle für Bundesländer (Ausschnitt) Nun wird die Wetter-Tabelle so geändert, dass nur noch mit Relationen gearbeitet wird: ID Stadt BundeslandID Hoch Tief 1 Berlin 1 7 13 2 München 2 8 16 Tabelle 11.3: Tabelle für Wetterdaten (Ausschnitt) 457
  • 458.
    Datenbankprogrammierung ID Stadt BundeslandID Hoch Tief 3 Regensburg 2 9 15 4 Altötting 2 8 15 Tabelle 11.3: Tabelle für Wetterdaten (Ausschnitt) (Forts.) Die Verknüpfung ist nun noch zu definieren. Je nach Datenbank ist dies eine ein- fache Festlegung oder ein fest programmierter Wert, der von der Datenbank über- wacht wird. MySQL und SQLite überwachen nichts und deshalb genügt die Angabe der Verknüpfung bei der Abfrage, was durch Vergleich der beiden betrof- fenen Felder geschieht: Bundesland.ID = Wetter.BundeslandID Damit das in einer Anweisung gelingt, muss man etwas beachten. So kann jedem Spaltennamen immer der Tabellenname vorangestellt werden, getrennt durch einen Punkt. Das ist meist notwendig, weil sich die Spaltennamen gleichen kön- nen und der SQL-Server dann nicht mehr weiß, welche Spalte gemeint ist. Das Aufbrechen einer Tabellen in mehrere zur Vermeidung von Redundanzen wird als Normalisierung bezeichnet. Es gibt mehrere Stufen der Normalisierung, für deren vollständige Darstellung jedoch mehr Platz erforderlich ist. An dieser Stelle sei auf entsprechende Spezialliteratur zu Datenbanken verwiesen. Prinzipien verknüpfter Abfragen Verknüpfte Abfragen können auf mehreren Wegen erstellt werden. Der einfachste weg ist ein so genannter »Inner Join«, bei dem ein Kreuzprodukt aus beiden Tabel- len erstellt wird: SELECT * FROM Wetter, Bundesland Das führt dazu, dass jede Zeile der einen Tabelle mit jeder der anderen Tabelle »gekreuzt« wird. 100 Wetterdaten für 16 Bundesländer ergeben dann 1.600 Rei- hen. Damit kann in der Praxis niemand etwas anfangen. Also ist die benötigte Verknüpfung anzugeben: SELECT * FROM Wetter, Bundesland WHERE Bundesland.ID = Wetter.BundeslandID Nun werden die Datensätze aus der ersten Tabelle mit den passenden aus der zweiten verknüpft. Da jeder Wettereintrag nur in einem Bundesland platziert sein 458
  • 459.
    Die universelle AbfragespracheSQL kann, sind es nun nur noch die benötigten 100 Datensätze. Im Unterschied zur einfachen Abfrage der ersten Tabelle steht nun aber auch der Name des Bundes- landes zur Verfügung. Korrekter (im Sinne von: effizienter) ist folgende Version: SELECT W.Stadt, W.Hoch, W.Tief, B.Name FROM Wetter W, Bundesland B WHERE B.ID = W.BundeslandID Damit ist die mit der ersten Abfrage erreichte Ausgabe wieder da, allerdings dies- mal basierend auf einer verbesserten Datenstruktur. Zum »Inner Join« kann eine alternative Syntax verwendet werden: SELECT W.Stadt, W.Hoch, W.Tief, B.Name FROM Wetter W INNER JOIN Bundesland B ON B.ID = W.BundeslandID Weitere Bedingungen, die in der ersten Variante mit AND angehängt werden, müss- ten hier in einer zusätzlichen WHERE-Bedingung stehen. Der »Inner Join« bezieht NULL-Felder nicht mit ein. Es kann nämlich sein, dass zu einer Stadt noch keine Zuordnung des Bundeslandes erfolgte. Was passiert mit solchen Feldern? Grund- sätzlich ist NULL in SQL kein unmöglicher Zustand. Es gibt nun mehrere Varian- ten: í Die linke Tabelle (links vom JOIN) enthält NULL-Werte und die entsprechenden Zeilen sollen trotzdem verarbeitet werden. In diesem Fall wird als Schlüsselwort LEFT JOIN gewählt. í Die rechte Tabelle (rechts vom JOIN) enthält NULL-Werte und die entsprechen- den Zeilen sollen trotzdem verarbeitet werden. In diesem Fall wird als Schlüsselwort RIGHT JOIN gewählt. Freilich kann man auch die Tabellen vertauschen und die Schlüsselwörter genau umgekehrt einsetzen. Es gibt weitere JOIN-Varianten, die hier jedoch zu weit führen und die – leider – auch nicht alle von MySQL unterstützt werden. Fortgeschrittene SQL-Techniken Einige fortgeschrittene SQL-Techniken seien an dieser Stelle kurz erwähnt. Sie sind leicht zu verstehen und einzusetzen. Das Handbuch zu MySQL gibt hier aus- führlich Auskunft. 459
  • 460.
    Datenbankprogrammierung Primärschlüssel Bislang wurde einfachangenommen, dass die Tabellen einen Schlüssel haben. Generell ist dies keine Option, sondern ein Primärschlüssel ist erforderlich, damit die Datenbank die Datensätze unterscheiden kann. Praktischerweise definiert man meist eine Spalte als INT (oder größer, beispielsweise BIGINT) und versieht sie mit dem Attribut AUTO_INCREMENT. MySQL legt die benötigten Schlüssel dann automa- tisch an. Andere Tabellen können dann Referenzen dazu nutzen. Ohne Primär- schlüssel könnte die Datenbank die Datensätze nicht eindeutig unterscheiden und Abfragen wären nicht ausführbar. Alternativ zur automatischen Vergabe ist freilich auch die Nutzung anderer Spal- ten mit eindeutigen Werten möglich, ebenso wie sich der Primärschlüssel aus mehreren Spalten zusammensetzen kann, um eindeutig zu sein. Unterabfragen Manchmal benötigt man die Ergebnisse einer Abfrage für eine andere. SQL kann diese Verknüpfung direkt ausführen, also ohne PHP dazwischen. Man spricht von so genannten Sub-Selects: SELECT * FROM Wetter WHERE BundeslandID NOT IN (SELECT ID FROM Bundesland WHERE NAME LIKE 'B%') Zuerst wird hier die innere Abfrage ausgeführt. Sie ergibt eine Liste der Primär- schlüssel der Tabelle Bundesland für alle Länder, die mit »B« beginnen. Diese Liste (beispielsweise 1,2,4,8) wird für den IN-Operator benutzt. Dann wird die Abfrage für alle Bundesländer ausgeführt, die nicht (NOT IN) mit »B« beginnen. Sub-Selects sind vor allem bei verknüpften Tabellen häufiger im Einsatz. Sub-Selects sind meist langsamer als JOIN und äquivalent verwendbar. Es gibt aber auch Fälle, in denen sie sich nicht gegenseitig ersetzen können. In der Praxis braucht man beides. UNION Mit UNION lassen sich zwei Abfragen kombinieren. Das Resultat können zwei ver- schiedene, aber auch zwei gleichartig strukturierte Tabellen sein. Die Kombina- 460
  • 461.
    Der MySQL-Dialekt tion vonAbfragen hebt einige Beschränkungen der JOIN-Verfahren auf. Das Schlüsselwort muss immer zwischen zwei gültigen SELECT-Abfragen stehen: SELECT * FROM Wetter UNION SELECT * FROM Stadt Beim programmtechnischen Zugriff mittels PHP5 müssen spezielle Abfrageme- thoden verwendet werden, um die unterschiedlich strukturierten Tabellen ausle- sen zu können. 11.3 Der MySQL-Dialekt Dieser Abschnitt zeigt die Besonderheiten, Funktionen und Eigenarten von MySQL 4 und teilweise auch MySQL 5. Soweit MySQL sich an das im vorherge- henden Abschnitt beschriebene Standard-SQL hält, wird dies nicht nochmals wie- derholt. Grobe Abweichungen vom SQL92-Standard Die folgende Liste zeigt in loser Folge einige der wichtigsten Erweiterungen, Funktionen und Einschränkungen in MySQL. Die meisten Angaben gelten für MySQL ab Version 4.1, MySQL 5 zeichnet sich demgegenüber vor allem durch höhere Stabilität, Geschwindigkeit und (nach der finalen Version) Zuverlässigkeit aus. í CREATE TABLE t2 LIKE t1 Erzeugt eine neue Tabelle t2, die exakt denselben Aufbau wie t1 hat. í INSERT ON DUPLICATE KEY UPDATE Fügt Daten ein, bzw. aktualisiert, wenn der Primärschlüssel bereits existiert. Das ist eine elegante Kombination aus INSERT und UPDATE. í Erweiterungen bei den GROUP BY-Funktionen: STD, BIT_OR, BIT_AND, GROUP_CONCAT(SpaltenName SEPARATOR ',') Die neue Aggregat-Funktion GROUP_CONCAT, kann die Werte einer Abfrage als Zeichenkette zusammenfassen. Es kann ein Trennzeichen ähnlich wie in PHPs join-Funktion angegeben werden. í LIKE kann auch auf numerische Spalten angewendet werden. 461
  • 462.
    Datenbankprogrammierung í Der SELECT-Befehl ist um zwei Schlüsselwörter erweitert worden: INTO OUTFILE und STRAIGHT_JOIN. í Neu sind die Befehle OPTIMIZE TABLE und SHOW. í GROUP BY muss nicht alle Spalten enthalten, die ausgegeben werden. í Zusätzlich zu den Operatoren OR und AND können die Symbole || und genutzt werden. Für die Verknüpfung von Zeichenketten muss anstatt || die Funktion CONCAT verwendet werden. í Der Operator % kann als Synonym für MOD eingesetzt werden. í Logische Operatoren können auch auf der linken Seite eines SELECT-Befehls geschrieben werden: SELECT spalte1 = 100 FROM table í Der Abruf der letzten automatisch generierten ID erfolgt mit der Funktion LAST_INSERT_ID(). í Diverse neue Funktionen, unter anderem versteht MySQL reguläre Ausdrücke mit REGEXP und RLIKE (siehe Liste im nächsten Abschnitt). í Neue Befehle: REPLACE ersetzt UPDATE und INSERT. Ist der Datensatz vorhanden (identifiziert am Primärschlüssel), wird INSERT ausgeführt, andernfalls UPDATE. í FLUSH löscht Statusinformationen. Datentypen Die Datentypen sind in MySQL sehr umfangreich. Die folgende Tabelle zeigt alles, was geht, unabhängig davon, ob es dem SQL92-Standard entspricht oder nicht. Datentyp Beschreibung TINYINT [(M)] [UNSIGNED] [Z] Kleine Ganzzahlen, von 0 bis 255 oder -128 bis 127 SMALLINT [(M)] [UNSIGNED] [Z] Ganzzahlen, entweder von 0 bis 65.535 oder von -32.768 bis +32.767 MEDIUMINT [(M)] [UNSIGNED] [Z] Ganzzahlen, entweder von 0 bis 16.777.215 oder von -8.388.608 bis +8.388.607 Tabelle 11.4: Datentypen in MySQL (Z steht für ZEROFILL, M für die Stellenzahl) 462
  • 463.
    Der MySQL-Dialekt Datentyp Beschreibung INT [(M)] [UNSIGNED] [Z] Ganzzahlen, entweder von 0 bis 4.294.967.295 oder INTEGER von -2.147.283.648 bis +2.147.283.647 BIGINT [(M)] [UNSIGNED] [Z] Ganzzahlen, entweder von 0 bis 18.446.744.073.709.551.615 oder von -9.223.372.036.854.775.808 bis +9.223.372.036.854.775.807 FLOAT(precision) [Z] Fließkommazahl, immer vorzeichenbehaftet. precision kann 4 oder 8 sein; 4 steht für einfache Genauigkeit, 8 für doppelte. FLOAT[(M,D)] [Z] Fließkommazahl, deren Wertebereich von -3,40282346638 bis -1,175494351-38 reicht und die 0 sowie den Wertebereich von +1,175494351-38 bis +3,40282346638 umfasst. DOUBLE[(M,D)] [Z] Fließkommazahl, deren Wertebereich von DOUBLEPRECISION[(M,D)] [Z] -1,7976931348623157308 bis -2,2250738585072014-308, REAL[(M,D)] [Z] und von -2,2250738585072014-308 bis +1,7976931348623157308 reicht, inklusive der 0. DECIMAL(M,D) [Z] Ungepackte Fließkommazahl, immer vorzeichenbe- NUMERIC haftet. Zahlen werden als Zeichenkette gespeichert, jede Ziffer steht in einem Byte. Das Komma, Vor- zeichen usw. belegen jeweils ein Byte. DATE Datum im Format »YYYY-MM-DD«. Der Werte- bereich geht vom 1.1.1000 bis zum 31.12.9999. DATETIME Datum und Zeit im Format »YYYY-MM-DD hh:mm:ss«. TIMESTAMP[(M)] Zeitstempel, Wertebereich von 1.1.1970 bis zum 31.12.2036. Für die Angabe des Anzeigebereiches M gilt: 14: YYYYMMDDhhmmss 12: YYMMDDhhmmss 8: YYYYMMDD 6: YYMMDD Tabelle 11.4: Datentypen in MySQL (Z steht für ZEROFILL, M für die Stellenzahl) (Forts.) 463
  • 464.
    Datenbankprogrammierung Datentyp Beschreibung TIME Zeit im Wertebereich von -838:59:59 bis +838:59:59 mit dem Ausgabeformat »hh:mm:ss«. YEAR Jahr, Wertebereich 1901 bis 2155, Ausgabe YYYY. CHAR(M) [BINARY] Zeichenkette fester Länge M. Wertebereich 1 bis 255. Leerzeichen am Ende werden automatisch für die Ausgabe entfernt. Sortieren und Selektieren berücksichtigt Groß- und Kleinschreibung nicht, wenn Sie nicht BINARY verwenden. VARCHAR(M) [BINARY] Zeichenkette variabler Länge, maximal M. Werte- bereich 1 bis 255. Leerzeichen am Ende werden automatisch für die Ausgabe entfernt. Sortieren und Selektieren berücksichtigt Groß- und Kleinschrei- bung nicht, wenn Sie nicht BINARY verwenden. TINYBLOB, TINYTEXT BLOB oder TEXT mit maximal 255 Byte BLOB, TEXT BLOB oder TEXT mit maximal 65.535 Byte MEDIUMBLOB, MEDIUMTEXT BLOB oder TEXT mit maximal 16.777.215 Byte LONGBLOB, LONGTEXT BLOB oder TEXT mit maximal 4.294.967.295 Byte ENUM('wert1', 'wert2', ...,) Aufzählung. Ein Feld dieses Typs kann nur eine Zeichenkette enthalten, die einem Objekt der Auf- zählung entspricht. SET('wert1', 'wert2' , ...,) Wie ENUM, kann aber mehrere Werte aus der Liste enthalten. Tabelle 11.4: Datentypen in MySQL (Z steht für ZEROFILL, M für die Stellenzahl) (Forts.) Funktionen Funktionen sind schlecht standardisiert. Jede Datenbank kocht ihr eigenes Süpp- chen. MySQL ist da keine Ausnahme. Deshalb finden Sie an dieser Stelle alle Funktionen der aktuellen Version mit entsprechenden Einsatzhinweisen. 464
  • 465.
    Der MySQL-Dialekt Funktionen fürmathematische Berechnungen Funktion Beschreibung ABS(x) Absoluter Betrag der Zahl x. ACOS(x) Der Arkuskosinus der Zahl x. ASIN(num) Der Arcussinus der Zahl x. ATAN(num) Der Arkustangens der Zahl x. ATN2(num1, num2) Der Arkustangens (Winkel) zwischen num1 und num2. BIT_COUNT(num) Die Anzahl der Bits, die in einer Zahl 1 sind. CEILING(x) Die kleinste Ganzzahl größer oder gleich dem Ausdruck COS(x) Der Kosinus der Zahl x. COT(x) Der Kotangens der Zahl x. CRC32(expr) Die zyklische Prüfsumme eines Ausdrucks als 32-Bit-Wert. DEGREES(x) Eine Umrechnung von Radiant in Grad. num1 DIV num2 Eine Ganzzahldivision, funktioniert auch mit sehr großen Zahlen (BIGINT). EXP(x) Die Potenz zur Basis e. FLOOR(x) Die größte Ganzzahl kleiner oder gleich dem angegebe- nen numerischen Ausdruck. GREATEST(x1,x2,..) Gibt den größten Wert der Liste zurück. LEAST(x1,x2,...) Gibt den kleinsten Wert der Liste zurück. LN(x) Der natürliche Logarithmus der Zahl x. LOG(x), LOG(b, x) Der natürliche Logarithmus (ohne b) oder Logarithmus zur Basis b. LOG2(x) Der Logarithmus zur Basis 2 der Zahl x. LOG10(x) Der dekadische Logarithmus der Zahl x. Tabelle 11.5: Mathematische Funktionen 465
  • 466.
    Datenbankprogrammierung Funktion Beschreibung MOD(n, m) Modulus, das ist der Rest einer Ganzzahldivision. Alle drei n % m Schreibweisen sind völlig identisch im Verhalten. n MOD m PI() Die Konstante π POWER(x,y) Potenzrechnung (x hoch y). RADIANS(x) Umrechnung von Grad in Radiant der Zahl x. RAND(x) Ermittelt eine Zufallszahl, x ist dabei optional und bestimmt, wenn angegeben, den Startwert. ROUND(x, d) Rundet Werte mathematisch, die Angabe der Stellen d ist optional, ohne Angabe wird auf ganze Zahlen gerundet SIGN(x) Das Vorzeichen der Zahl x, gibt -1, 0 oder 1 zurück. SIN(x) Der Sinus der Zahl x. SQRT(x) Die Quadratwurzel der Zahl x. TAN(x) Der Tangens der Zahl x. TRUNCATE(x, d) Gibt die Zahl x, gekürzt auf d Dezimalstellen, zurück. Tabelle 11.5: Mathematische Funktionen (Forts.) Funktionen zur Steuerung des Kontrollflusses Funktion Beschreibung ISNULL(expr) Wertet den Ausdruck expr aus und gibt 1 zurück, wenn der Ausdruck NULL ist, sonst 0. IFNULL(expr1, expr2) Wertet den Ausdruck expr1 aus und gibt expr2 zurück, wenn der Ausdruck NULL ist, sonst expr1 IF(expr1,expr2,expr3) Wenn der Ausdruck expr1 Wahr ist, wird expr2 zurückge- geben, sonst expr3 Tabelle 11.6: Diese Funktionen können die Auswahl Bedingungsabhängig ändern 466
  • 467.
    Der MySQL-Dialekt Funktion Beschreibung CASE val Eine Mehrfachverzweigung, die etwa dem switch in PHP WHEN cval THEN result entspricht. Der Wert val wird mit cval verglichen und bei [WHEN cval THEN Gleichheit wird result zurückgegeben. Ansonsten wird mit result] dem nächsten Zweig fortgesetzt. [ELSE result] END NULLIF(expr1, expr2) Gibt NULL zurück, wenn expr1 gleich expr2 ist. Tabelle 11.6: Diese Funktionen können die Auswahl Bedingungsabhängig ändern (Forts.) Funktionen für aggregierende Berechnungen Funktion Beschreibung AVG(Ausdruck) Der Durchschnitt der Felder. COUNT(Ausdruck) Die Anzahl der Felder. COUNT(DISTINCT Ausdruck) Die Anzahl eindeutiger Felder. COUNT (*) Repräsentiert die Anzahl aller Datensätze einer Tabelle. GROUP_CONCAT(Ausdruck) Verbundene Zeichenkette aller Teilfelder. SUM(Ausdruck) Die Summe der Felder (Addition). MAX(Ausdruck) Das Feld mit dem größten Wert bestimmt das Ergebnis. MIN(Ausdruck) Das Feld mit dem kleinsten Wert bestimmt das Ergebnis. STD(Ausdruck) Statistische Standardabweichung aller Werte der Liste STDDEV(Ausdruck) BIT_OR(Ausdruck) Führt ein bitweises Oder aus. BIT_AND(Ausdruck) Führt ein bitweises Und aus. BIT_XOR(Ausdruck) Führt ein bitweises Exklusives Oder aus. Tabelle 11.7: Aggregat-Funktionen 467
  • 468.
    Datenbankprogrammierung Funktionen für Systeminformationen Funktion Beschreibung BENCHMARK(count, expr) Die Funktion führt einen Ausdruck expr so oft aus, wie count angibt. Bei der Ausführung auf Kommandozeile wird die gesamte Ausführungszeit ausgegeben. Man kann damit die Effizienz seiner Ausdrücke testen. CHARSET(str) Ermittelt, in welchem Zeichensatz eine Zeichenfolge definiert ist. COERCIBILITY(str) Ermittelt die Zugehörigkeit einer Zeichenfolge zu einem Vergleichszeichensatz. Gibt einen Wert zwischen 0 und 3 zurück, wobei 0 explizite Vergleichbarkeit (höchster Rang), 1 keine Vergleichbarkeit, 2 implizite Vergleichbarkeit und 3 übergehende Vergleichbarkeit bedeutet. COLLATION(str) Ermittelt den Vergleichszeichensatz, der die Sortierkriterien bestimmt. CONNECTION_ID() Nummer des Threads, der die aktuelle Verbindung verar- beitet. CURRENT_USER() Aktueller MySQL-Nutzername mit Rechnernamen. DATABASE() Gibt den Namen der aktuellen Datenbank aus. USER() SYSTEM_USER() Aktueller MySQL-Nutzername SESSION_USER() FORMAT(n, d) Formatiert eine Zahl n mit Kommata als Tausendergruppen- symbol und Punkt als Dezimaltrennzeichen mit d Dezimal- stellen. FOUND_ROWS() Anzahl der Reihen, so wie die Abfrage ohne LIMIT erfolgt wäre, auch wenn LIMIT benutzt wurde. LAST_INSERT_ID() Gibt den zuletzt erzeugten Wert einer AUTO_INCREMENT-Spalte zurück. VERSION() Gibt die Versionsnummer des MySQL-Server an. Tabelle 11.8: Die System- und Informationsfunktionen 468
  • 469.
    Der MySQL-Dialekt Funktion Beschreibung GET_LOCK(str, to) Erzeugt eine Verriegelung (Lock) mit dem Namen str und dem Zeitüberschreitungswert to. RELEASE_LOCK(str) Gibt die Verriegelung str wieder frei. IS_FREE_LOCK(str) Prüft, ob die Verriegelung str frei ist. INET_ATON(expr) Die Numerische Entsprechung einer als Zeichenkette ange- gebenen IP. INET_NTOA(expr) Die IP-Nummer in Punktschreibweise aus einer Zahl. UUID() Universal Unique Identifier für RPC-Verbindungen. Tabelle 11.8: Die System- und Informationsfunktionen (Forts.) Verschlüsselungsfunktionen Funktion Beschreibung PASSWORD(str) Erzeugt ein Kennwort zur Zeichenkette str. ENCRYPT(str, seed) Erzeugt ein Kennwort zur Zeichenkette str und mit dem Startwert seed. Nutzt das Unix-Kommando crypt. Unter Windows wird NULL zurückgegeben. ENCODE(str, pass) Einfaches Verschlüsselungsverfahren auf Basis des Kenn- worts pass. Zum Speichern sollten BLOB-Spalten verwen- det werden. DECODE(str, pass) SHA(str) Erstellt einen Hashwert nach SHA bzw. SHA1. SHA1(str) MD5(str) Erstellt einen Hashwert nach MD5. DES_ENCRYPT(str, key) Triple-DES-Ver- und Entschlüsselung. Kann nur genutzt werden, wenn MySQL mit SSL-Unterstützung kompiliert wurde. DES_DECRYPT(str, key) Tabelle 11.9: Verschlüsselungsfunktionen 469
  • 470.
    Datenbankprogrammierung Funktion Beschreibung AES_ENCRYPT(str, key) AES-(Advanced Encryption Standard)-Ver- und Entschlüs- selung. Kann nur genutzt werden, wenn MySQL mit SSL- Unterstützung kompiliert wurde. AES_DECRYPT(str, key) Tabelle 11.9: Verschlüsselungsfunktionen (Forts.) Zeichenkettenfunktionen Funktion Beschreibung ASCII(str) Gibt den ASCII-Code des Zeichens str zurück. Hat str mehr als ein Zeichen, wird nur das erste Zeichen über- prüft. CHAR(n,...) Wandelt die Zahlen n in die entsprechenden ASCII-Zei- chen um. Mehrere Argumente werden zu einer Zeichen- kette kombiniert. CHAR_LENGTH(str) Die echte Zeichenlänge einer Zeichenkette. Manche Zei- CHARACTER_LENGTH(str) chen bestehen aus mehreren Bytes, sodass LENGTH die fal- sche Länge liefert, während CHAR_LENGTH die erwartete Zahl liefert. COMPRESS(str) Komprimiert eine Zeichenfolgen nach dem ZIP-Verfah- ren. CONCAT(str,...) Verknüpft alle Argumente zu einer Zeichenkette. Wenn eines der Argumente NULL ist, wird NULL zurückgegeben. ELT(n, str1, str2,...) Gibt die durch n bezeichnete Zeichenkette zurück: str1, wenn n=1 usw. EXPORT_SET(bit, on, off, Exportiert einen Bitwert in eine Zeichenfolge, in der jedes sep, number) Zeichen ein Bit repräsentiert. Das Zeichen on bestimmt Bits mit dem Wert 1, off steht für 0, sep bestimmt ein Trennzeichen und number die Anzahl der Bits, die aus dem Wert bit ausgewertet werden sollen. Tabelle 11.10: Zeichenkettenfunktionen 470
  • 471.
    Der MySQL-Dialekt Funktion Beschreibung FIELD(str,str1,str2..) Gibt die Position von str in str1, str2 usw. zurück: Wenn str2=str, wird 2 zurückgegeben. FIND_IN_SET(str, list) Gibt die Position von str in der Liste list zurück. Die Liste besteht aus kommaseparierten Werten. INSERT(str,st,len,new) Fügt len Zeichen der Zeichenkette new an der Stelle st der Zeichenkette str ein. INSTR(str, sub) Entspricht LOCATE, nur die Argumente sind vertauscht. LCASE, LOWER(str) Wandelt in Kleinbuchstaben um. LEFT(str, len) Gibt len Zeichen vom linken Ende der Zeichenkette str zurück. LENGTH(str) Länge der Zeichenketten str. LOAD_FILE(name) Lädt eine Datei und gibt den Inhalt als Zeichenkette zurück. LOCATE(sub, str) Bestimmt die Position der Zeichenkette sub in der Zei- POSITION(sub IN str) chenkette str. LPAD(str,len,pad) Fügt pad links an str an, gibt jedoch nur len Zeichen zurück. LTRIM(str) Entfernt Leerzeichen vom linken Ende. MAKE_SET(bits,list) Wählt die Elemente der Liste list anhand der gesetzten Bits in bits aus. MID(str, pos, len) Gibt len Zeichen von Position pos an der Zeichenkette str zurück. QUOTE() Fügt Anführungszeichen hinzu und markiert solche im Text mit einem Backslash. NULL-Werte werden als Zeichenfolge NULL zurückgegeben. REPEAT(str, count) Wiederholt die Zeichenkette str count mal. REPLACE(str,from,to) Ersetzt alle Vorkommen von from in der Zeichenkette str durch to. Tabelle 11.10: Zeichenkettenfunktionen (Forts.) 471
  • 472.
    Datenbankprogrammierung Funktion Beschreibung REVERSE(str) Dreht eine Zeichenkette um. RIGHT(str, len) Gibt len Zeichen vom rechten Ende der Zeichenkette str zurück. RPAD(str,len,pad) Fügt pad rechts an str an, gibt jedoch nur len Zeichen zurück. RTRIM(str) Entfernt Leerzeichen vom rechten Ende. SOUNDEX(str) Gibt die Lautfolge für str zurück. SPACE(n) Gibt n Leerzeichen zurück. SUBSTRING(str FROM len Andere Schreibweisen für RIGHT und MID. SUBSTRING(str,pos,len) SUBSTRING(str FROM pos FOR len) SUBSTRING(str, pos) Gibt Teile von str ab Position pos zurück. SUBSTRING_INDEX(str, Gibt den linken Teil einer Zeichenkette zurück, nachdem delimiter, count) count mal das Zeichen delimiter aufgetreten ist. TRIM BOTH entspricht TRIM, LEADING entspricht LTRIM, TRAILING BOTH|LEADING|TRAILING entspricht RTRIM, FROM ist optional, rem ist optional und rem FROM str steht für das zu entfernende Zeichen, str wird bearbeitet. TRIM(str) Entfernt Leerzeichen von beiden Enden der Zeichenkette str. UCASE, UPPER(str) Wandelt Zeichen in Großbuchstaben um. UNCOMPRESS(str) Hebt ein Komprimierung wieder auf (siehe auch COMPRESS). Tabelle 11.10: Zeichenkettenfunktionen (Forts.) 472
  • 473.
    Der MySQL-Dialekt Zahlenformatierungen Funktion Beschreibung CONV(n,from,to) Konvertiert Zahlen zwischen verschiedenen Zahlenbasen. Zurückgegeben wird immer eine Zeichenkette mit der ermittel- ten Zahl. n ist die Zahl, from die ursprüngliche Zahlenbasis, to die Zielbasis. BIN(n) Gibt eine Zahl n als Zeichenkette im Binärformat zurück, BIN(7) ergibt beispielsweise »111«. BIT_LENGTH(n) Gibt die Länge einer Zeichenkette in Bits zurück. OCT(n) Gibt eine Zahl n als Zeichenkette im Oktalformat zurück. HEX(n) Gibt eine Zahl n als Zeichenkette im Hexadezimalformat zurück. UNHEX() Umkehrfunktion zu HEX Tabelle 11.11: Zeichenkettenfunktionen für Zahlen Datums- und Zeitfunktionen Funktion Beschreibung DAYOFWEEK(date) Der Tag der Woche, 1 ist Sonntag (1 – 7). WEEKDAY(date) Der Tag der Woche, 0 ist Montag (0 – 6). DAYOFMONTH(date) Der Tag des Monats (1 – 31). DAYOFYEAR(date) Der Tag des Jahres (1 – 366). MONTH(date) Der Monat (1 – 12). DAYNAME(date) Der Wochentag (englisch, ausgeschrieben). MONTHNAME(date) Der Monat (englisch, ausgeschrieben). QUARTER(date) Das Quartal (1 – 4). Tabelle 11.12: Datums- und Zeitfunktionen 473
  • 474.
    Datenbankprogrammierung Funktion Beschreibung WEEK(date) Die Woche im Jahr (0 – 52). Das optionale Argument WEEK(date, first) first bestimmt, welcher Wochentag als Beginn gezählt wird. 0 entspricht dem Sonntag. YEAR(date) Das Jahr (1000 – 9999). HOUR(time) Die Stunde (0 – 23). MINUTE(time) Die Minute (0 – 59). SECOND(time) Die Sekunde (0 – 59). PERIOD_ADD(p,n) Addiert n Monate zur Periode p. Die Periode wird im For- mat YYYYMM oder YYMM erwartet. Zurückgegeben wird immer die Langform YYYYMM. PERIOD_DIFF(p1,p2) Gibt die Differenz in Monaten zwischen p1 und p2 zurück TO_DAYS(date) Die Anzahl der Tage seit dem Jahr 0. FROM_DAYS(dn) Ermittelt ein Datum aus der Tageszahl dn. CURDATE() Das aktuelle Datum (Systemzeit des Servers). CURRENT_DATE CURTIME() Die aktuelle Zeit (Systemzeit des Servers) CURRENT_TIME NOW() Datum und Uhrzeit (Systemzeit des Servers) SYSDATE() CURRENT_TIMESTAMP UNIX_TIMESTAMP Unix Timestamp (Sekunden in GMT seit den 1.1.1970, 0 Uhr). Die Funktion wird auch von der Windows-Version unterstützt. FROM_UNIXTIME(stp) Gibt ein Datum entsprechend dem Unix Timestamp stp zurück. FROM_UNIXTIME(stp, Gibt ein Datum entsprechend dem Unix Timestamp stp format) zurück. Das Datum ist entsprechend format formatiert. Tabelle 11.12: Datums- und Zeitfunktionen (Forts.) 474
  • 475.
    Der MySQL-Dialekt Funktion Beschreibung SEC_TO_TIME(sec) Rechnet die Angabe in Sekunden in das Format HH:MM:SS um. TIME_TO_SEC(time) Rechnet eine Zeitangabe in Sekunden um. Tabelle 11.12: Datums- und Zeitfunktionen (Forts.) Datumsformatierungen Typ-Konstante Bedeutung Formatangabe SECOND Sekunde ss MINUTE Minute mm HOUR Stunde hh DAY Tag DD MONTH Monat MM YEAR Jahr YY MINUTE_SECONDS Minute und Sekunde mm:ss HOUR_MINUTE Stunde und Minute hh:mm DAY_HOUR Tag und Stunde DD hh YEAR_MONTH Jahr und Monat YY-MM HOUR_SECOND Stunde, Minute, Sekunde hh:mm:ss DAY_MINUTE Tag, Stunde, Minute DD hh:mm DAY_SECONDS Tag, Stunde, Minute, Sekunde DD hh:mm:ss Tabelle 11.13: Zeittypen für Datumsberechnungen 475
  • 476.
    Datenbankprogrammierung Funktion Berechnung DATE_FORMAT(date, format) Formatiert den Wert date mit dem Format format, des- sen Elemente wurden in der vorherigen Tabelle bereits beschrieben. TIME_FORMAT(time, format) Formatiert den Wert time mit dem Format format, des- sen Elemente wurden in der vorherigen Tabelle bereits beschrieben. Tabelle 11.14: Datumsformatierungen Code Bedeutung (Wertebereich) Code Bedeutung (Wertebereich) %M Monatsname (January – December) %k Stunde (0 – 23) ohne führende Null %W Wochenname (Monday – Sunday) %h Stunde (01 – 12) mit führender Null %D Monat mit engl. Suffix %I Stunde (1 – 12) ohne führende Null (1st, 2nd, 3rd usw.) %Y Jahr mit 4 Stellen %l Minuten (0 – 59) %y Jahr mit 2 Stellen %i Minuten (00 – 59) mit führender Null %a Abgekürzter Wochentag %n Zeit, 12-Stunden-Format: hh:mm:ss (Mon – Sun) AM|PM %d Tag des Monats (00 – 31) %T Zeit, 24-Stunden-Format: hh:mm:ss %e Tag des Monats (0 – 31) %S Sekunde (00 – 59) mit führender Null %m Monat (00 – 12) mit führender Null %s Sekunde (0 – 59) %c Monat (0 – 12) ohne führende Null %p AM oder PM %b Abgekürzter Monatsname %w Wochentag (Jan–Dec) (0=Sonntag, 6=Samstag) %j Tag des Jahres (000 – 366) %U Woche, Sonntag ist der erste Tag der Woche (00 – 52) Tabelle 11.15: Platzhalter für Datumsformatierungen 476
  • 477.
    Erste Schritte mitMySQL und MySQLi Code Bedeutung (Wertebereich) Code Bedeutung (Wertebereich) %H Stunde (00 – 23) mit führender %u Woche, Montag ist der erste Tag der Null Woche (00 – 52) %% Prozentzeichen Tabelle 11.15: Platzhalter für Datumsformatierungen (Forts.) 11.4 Erste Schritte mit MySQL und MySQLi MySQLi (MySQL improved) ist eine neue Erweiterung von PHP zur besseren Unterstützung von MySQL. Im Bundle mit dem neuen, objektorientierten Ansatz und der neuen MySQL-Version 4 ergibt sich ein außerordentlich leistungsfähiges System. MySQLi vorbereiten Seit PHP5 gehört die MySQL-Unterstützung aus rechtlichen Gründen nicht mehr zum integrierten Paket. In PHP 4 waren die MySQL-Module noch fest in den Kern kompiliert. Mit der Version 5 ist nun alles wieder wie bereits bei PHP3 – man muss die MySQL-Dateien zusätzlich einbinden. Auf dem Entwicklungssystem unter Windows erfolgt dies durch das Auskommentieren der entsprechenden Zeile in der Datei php.ini: extension=php_mysql.dll Dann haben Sie noch die Wahl, statt der alten MySQL-Module die neuen, objekt- orientierten MySQLi-Dateien zu nutzen: extension=php_mysqli.dll In diesem Buch werden die neuen Module vorgestellt. Über weite Strecken ist Syntax und Nutzung praktisch identisch, sodass eine Umstellung nicht schwer ist. 477
  • 478.
    Datenbankprogrammierung Verbindung testen Nach derInstallation von MySQL steht ein kurzer Test an, ob alles geklappt hat. Unter XP muss dazu der entsprechende Dienst gestartet sein, unter Linux der MySQL-Daemon. Dann kommt folgendes Skript zum Zuge, um alles zu testen: Listing 11.1: mysqliconnect.php – Ein erster Versuch mit MySQL und PHP $mysqli = new mysqli(localhost, root, , test); $query = $mysqli-query(SELECT version() AS version); $result = $query-fetch_assoc(); echo Wir arbeiten mit MySQL Version {$result['version']}; $mysqli-Close(); Das Skript geht davon aus, dass das Standardkennwort (leer) und der Benutzer- name (»root«) nicht geändert wurden und dass die Datenbank auf derselben Maschine wie der Webserver läuft (»localhost«). Haben Sie eine andere Installa- tion, müssen Sie die Daten in der ersten Zeile entsprechend anpassen. Die Klasse mysqli1 stellt nun den Zugriff auf die neuen Funktionen bereit. Im Bei- spiel führt die Instanziierung des Objekts in $mysqli auch gleich zum Öffnen der Verbindung. Die SQL-Abfrage ermittelt dann die Versionsnummer des Daten- bankservers: SELECT version() AS version Die Methode zur Abfrage der Datenbank heißt query: $mysqli-query() Wenn Ergebnisse entstehen, kann man diese mit fetch_assoc in ein assoziatives Array überführen: $result = $query-fetch_assoc(); Der Zugriff auf das Array entspricht wieder einfachem PHP, als Schlüsselname taucht der Alias der SQL-Abfrage auf. Hat alles geklappt, wird die Verbindung wieder geschlossen. Die Ausgabe sollte nun in etwa wie nachfolgend gezeigt aussehen: 1 Es gibt auch nach wie vor einen prozeduralen Zugriff, der hier nicht weiter betrachtet wird. 478
  • 479.
    Erste Schritte mitMySQL und MySQLi Abbildung 11.1: Alles neu: PHP5 mit MySQLi und MySQL 5.0-alpha Nun kann man voll loslegen und praktisch mit der Datenbank arbeiten. Die folgenden Skripte wurden mit MySQL 4.1 getestet. Die einwand- freie Funktionsweise von MySQL 5 kann derzeit nicht garantiert wer- den. Außerdem ist mindestens die Version PHP5.1 für die MySQLi- Erweiterungen zu empfehlen. Die erste Final, PHP5.0.0, versagte bei einigen Abfragen. Genereller Datenbankzugriff Für die folgenden Skript wird immer wieder dieselbe Methode zum Datenbankzu- griff verwendet und als Include-Datei eingebunden. Dies erleichtert Änderungen: Listing 11.2: mysqli.inc.php – Inhalt der Include-Datei, noch ohne Fehlermanagement ?php $mysqli = new mysqli(localhost, root, , test); ? Die Variable $mysqli wird in allen Skripten dieses Kapitels verwendet. Das Einbin- den erfolgt über folgende Zeile: include(mysqli.inc.php); Diese Anweisung wird nicht in jedem Listing immer wieder abgebildet. Mit der Datenbank arbeiten Um mit der Datenbank arbeiten zu können, werden zuerst Tabellen benötigt. Diese können Sie entweder mit einem Werkzeug oder per Skript anlegen. Als Werkzeug kommen unter Windows das MySQLCC (MySQL Control Center) oder phpMyAdmin in Frage. Dieser Abschnitt zeigt alle nötigen Schritte in Skript- form. 479
  • 480.
    Datenbankprogrammierung Tabellen vorbereiten Zuerst werdendie passenden Tabellen benötigt. MySQL verfügt über eine sehr gute Funktion beim Anlegen von Tabellen. Es kann prüfen, ob die Tabelle bereits existiert. Damit muss man sich erstmal nicht darum kümmern, ob das Skript bereits aufgerufen wurde. Die beiden Tabellen, die hier benötigt werden, enthalten die Bundesländer und Wetterdaten. Sie haben folgende Definitionen: Listing 11.3: Definition der Wetter-Tabelle CREATE TABLE IF NOT EXISTS wetter ( ID bigint(20) NOT NULL auto_increment, Stadt varchar(100) NOT NULL default '', Hoch int(2) NOT NULL default '0', Tief int(2) NOT NULL default '0', BundeslandID bigint(20) default '0', PRIMARY KEY (ID), KEY StadtIndex (Stadt) ); Listing 11.4: Definition der Tabelle der Bundesländer CREATE TABLE IF NOT EXISTS bundesland ( ID int(2) NOT NULL auto_increment, Name varchar(30) NOT NULL default '', PRIMARY KEY (ID) ); Das folgende PHP-Skript nutzt diese Anweisungen, um die Tabellen anzulegen. Die Angabe von IF NOT EXISTS verhindert, dass die Tabellen erneut erzeugt wer- den, wenn sie bereits existieren. Listing 11.5: mysqlicreatetables.php – Die benötigten Datentabellen erzeugen $tables['Wetter'] = TABLE1 CREATE TABLE IF NOT EXISTS wetter ( ID bigint(20) NOT NULL auto_increment, Stadt varchar(100) NOT NULL default '', Hoch int(2) NOT NULL default '0', Tief int(2) NOT NULL default '0', BundeslandID bigint(20) default '0', 480
  • 481.
    Erste Schritte mitMySQL und MySQLi PRIMARY KEY (ID), KEY StadtIndex (Stadt) ); TABLE1; $tables['Bundesland'] = TABLE2 CREATE TABLE IF NOT EXISTS bundesland ( ID int(2) NOT NULL auto_increment, Name varchar(30) NOT NULL default '', PRIMARY KEY (ID) ); TABLE2; if (is_object($mysqli)) { foreach ($tables as $table = $definition) { $result = $mysqli-query($definition); if ($result === FALSE) { echo Konnte Tabelle '$table' nicht erzeugenbr; echo bFehler:/b {$mysqli-error}br; } else { echo Tabelle '$table' wurde erzeugt/überprüftbr; } } } Das Skript nutzt ein Array zum Zusammenstellen der Kommandos. Die Elemente des Arrays enthalten die SQL-Anweisungen und werden dann einzeln an die Datenbank gesendet. Die Methode query gibt bei Abfragen mit Ergebnissen ein Ergebnisobjekt zurück. Bei Anweisung, die nichts zurückgeben, wie beispielsweise CREATE TABLE wird entweder TRUE (Erfolg) oder FALSE (Fehler) zurückgegeben. Abbildung 11.2: Ausgabe bei erfolgreicher Abarbeitung Der Umgang mit Fehlern ist generell sehr wichtig. Deshalb ist die Ausgabe von Fehlermeldungen in fast jedem Skript zu finden. Der Zugriff auf die letzte Fehler- meldung erfolgt mit $mysqli über die Eigenschaft error: 481
  • 482.
    Datenbankprogrammierung echo bFehler:/b {$mysqli-error}br; ProvozierenSie einen Fehler, indem Sie die Syntax der SQL-Anweisung ein wenig »unqualifiziert« verändern. Sie erhalten dann beispielsweise folgende Aus- gabe: Abbildung 11.3: Ausgabe mit Fehlermeldung Die Tabellen füllen Es gibt mehrere Wege, Tabellen zu füllen: í Formulare werden verwendet – dann gibt der Benutzer die Daten von Hand ein. í Abfrage einer anderen Datenbank oder Tabelle. í Durch das Auslesen von Textdateien. í Durch den Import von XML. Für die Generierung der Bundesländer bietet es sich an, eine Textdatei zu verwen- den, die die Namen enthält. Das ist einfacher als alle INSERT-Anweisungen aufzu- schreiben. Die nötigen Techniken wurden alle bereits behandelt. Zuerst die Textdatei: Listing 11.6: bl.txt im Verzeichnis /data – Textdatei mit Bundesländern 'Baden-Württemberg' 'Bayern' 'Berlin' 'Brandenburg' 'Bremen' 'Hamburg' 'Hessen' 'Mecklenburg-Vorpommern' 'Niedersachsen' 'Nordrhein-Westfalen' 'Rheinland-Pfalz' 'Saarland' 'Sachsen' 482
  • 483.
    Erste Schritte mitMySQL und MySQLi 'Sachsen-Anhalt' 'Schleswig-Holstein ' 'Thüringen' Auch für ein paar Wetterdaten wurde eine Textdatei vorbereitet. Da hier mehrere Spalten existieren, wurde ein Trennzeichen definiert, das Komma. Die erste Zeile enthält die Feldnamen: Listing 11.7: wetter.txt im Verzeichnis /data – Textdatei mit Wetterdaten Stadt,Hoch,Tief,BundeslandID 'Berlin',22,12,3 'Hamburg',20,10,6 'Stuttgart',24,18,1 'München',23,17,2 'Regensburg',23,19,2 'Dresden',19,12,13 'Leipzig',19,12,13 'Wittenberge',19,12,4 'Angermünde',18,12,4 'Frankfurt/Oder',19,13,4 'Cottbus',22,17,4 'Hof',19,14,2 'Nürnberg',19,11,2 'Würzburg',19,16,2 'Augsburg',22,18,2 Die einfachen Anführungszeichen in den Datendateien vereinfachen die Verarbeitung erheblich. Dies ist hier vor allem gemacht worden, um die ersten Skripte überschaubar zu halten. In der Praxis gibt es freilich Lösungen, die beliebige Daten korrekt einfügen. Das folgende Skript zeigt eine mögliche Lösung: Listing 11.8: mysqliinsert.php: Programmgesteuert Datensätze einfügen $path = 'data'; $imports = array('Wetter' = 'wetter.txt', 'Bundesland' = 'bl.txt'); if (is_object($mysqli)) { foreach ($imports as $table = $file) 483
  • 484.
    Datenbankprogrammierung { $content = file($path/$file); $fields = array_shift($content); $mysqli-query(TRUNCATE $table); foreach ($content as $line) { $sql = INSERT INTO $table ($fields) VALUES ($line); $result = $mysqli-query($sql); if ($result === FALSE) { echo Konnte Anweisung nicht ausführenbr; echo bFehler:/b {$mysqli-error}br; } else { echo Anweisung ausgeführt: b$sql/bbr; } } } } Dieses Skript beginnt mit der Definition eines Arrays, das die Tabellennamen und Dateinamen enthält und miteinander verknüpft: $imports = array('Wetter' = 'wetter.txt', 'Bundesland' = 'bl.txt'); Der Vorteil der vorgestellten Lösung liegt in ihrer leichten Erweiterbarkeit. Um weitere Tabelle mit Daten zu beschicken, muss man lediglich dieses Array erwei- tern und natürlich die Daten bereitstellen. Dann durchläuft die erste foreach-Schleife alle Elemente dieses Arrays: foreach ($imports as $table = $file) So erhält man Tabellen- und Dateinamen. Dann werden die Daten in ein weiteres Array überführt, wozu die Funktion file hervorragend geeignet ist: $content = file($path/$file); Die erste Zeile enthält die Feldnamen. Das erste Element eines Arrays lässt sich mit array_shift extrahieren, sodass in $fields die Feldliste steht: $fields = array_shift($content); 484
  • 485.
    Erste Schritte mitMySQL und MySQLi Dann wird die jeweils zu bearbeitende Tabelle gelöscht, um zu verhindern, dass bei mehrfachem Aufruf des Skripts die Daten doppelt erscheinen: $mysqli-query(TRUNCATE $table); Hier ist etwas Vorsicht angebracht! Wenn Sie mit anderen Skripten der Wetter-Tabelle weitere Daten hinzugefügt haben, gehen diese durch den Aufruf von TRUNCATE unwiderruflich verloren. Für den Rest der Datei (array_shift extrahiert nicht nur, sondern entfernt auch gleich die erste Zeile) werden nun die Zeilen gelesen und zu INSERT-Anweisungen verarbeitet: foreach ($content as $line) $sql = INSERT INTO $table ($fields) VALUES ($line); Der Aufbau einer SQL-Anweisung in einer eigenen Variable ist sinnvoll, um das dynamisch konstruierte Gebilde leicht überwachen zu können. Die fertige Anwei- sung wird dann an den SQL-Server gesendet: $result = $mysqli-query($sql); Zuletzt folgt noch die bereits bekannte Fehlerausgabe. Mit den so erstellten Tabellen kann nun gearbeitet werden. Die folgenden Abschnitte behandeln die bereits im Einführungsteil zu SQL präsentierten Abfra- gen im praktischen Kontext eines PHP-Skripts. Einfache Abfragen Die Abfragetechnik in PHP folgt immer ein und demselben Schema: 1. Verbinden mit der Datenbank. 2. Senden der Abfrage und Erhalt des Ergebnisobjekts. 3. Überführen des Ergebnisobjekts in ein Array. 4. Ausgaben oder Verarbeiten des Arrays. In selteneren Fällen werden nur einzelne Daten abgefragt und ohne Arrays gear- beitet. Aufgrund der starken Arrayfunktionen in PHP ist die Nutzung jedoch meist angebracht. 485
  • 486.
    Datenbankprogrammierung Listing 11.9: mysqlselectfrom.php– Einfache Abfrage mit Ausgabe if (is_object($mysqli)) { $sql = SELECT Stadt, Hoch, Tief FROM Wetter; $result = $mysqli-query($sql); while ($rs = $result-fetch_assoc()) { echo WETTER Die Tagestemperaturen für {$rs['Stadt']}: ul liHöchsttemperatur: {$rs['Hoch']} °C/li liTiefsttemperatur: {$rs['Tief']} °C/li /ul WETTER; } } Mit der folgenden Abfrage entsteht ein Ergebnisobjekt: $result = $mysqli-query($sql); Diese Objekte beinhalten praktisch die gesamten Daten, die die SQL-Anweisung zurückgibt. Da es sich um eine Tabelle, also eine zweidimensionale Struktur han- delt, braucht man zwei Schritte zum Auseinandernehmen. Im ersten Schritt wer- den die Zeilen abgerufen und jeweils in einem Array gespeichert: while ($rs = $result-fetch_assoc()) Die Methode fetch_assoc holt die Daten in ein assoziatives Array. Die Schlüssel werden aus den Spaltennamen gebildet. Sind keine Daten mehr vorhanden, gibt die Funktion FALSE zurück und der gesamte Ausdruck wird FALSE, woraufhin while abbricht. Innerhalb der Schleife kann man jetzt auf die Daten mit der normalen Arraysyntax zugreifen: {$rs['Stadt']} Die Ausgabe zeigt, dass alle Daten durchlaufen werden. Der Fantasie beim Formatieren sind nun keine Grenzen gesetzt. Schwieriger ist es denn auch, die passenden SQL-Anweisung zu finden, um bestimmte Details zu ermitteln. In den folgenden Skripten werden andere Abfragen und andere Aus- gabemethoden vorgestellt. Dies dient vor allem der Demonstration der Möglich- keiten. 486
  • 487.
    Erste Schritte mitMySQL und MySQLi Abbildung 11.4: Ausgabe einer Datenbanktabelle in formatiertem HTML Komplexe Abfragen Als nächstes sollen die durchschnittlichen Höchst- und Tiefsttemperaturen ermit- telt werden. Dazu eignet sich die Aggregat-Funktion AVG: Listing 11.10: mysqlselectavgfrom.php – Durchschnittswerte ermitteln $sql = SELECT AVG(Hoch) AS MittelHoch, AVG(Tief) AS MittelTief FROM Wetter; $result = $mysqli-query($sql); $data = $result-fetch_object(); echo Die mittlere Höchsttemperatur beträgt {$data-MittelHoch} °Cbr; echo Die mittlere Tiefsttemperatur beträgt {$data-MittelTief} °Cbr; 487
  • 488.
    Datenbankprogrammierung Eingesetzt wird hierdie Methode fetch_object. Sie gibt den aktuellen Datensatz als Objekt zurück, oder FALSE, falls keine Daten mehr da sind. Da hier nur skalare Werte abgefragt werden, ist eine Schleife nicht erforderlich. Das Objekt enthält eine Eigenschaft für jedes Feld, der Name entspricht auch dem Feldnamen: $data-MittelHoch Im Beispiel wurden die Namen gegenüber den originalen Spaltennamen noch durch den Alias-Operator AS geändert. Das ist sinnvoll, wenn die Spaltennamen selbst auch abgefragt werden sollen. Abbildung 11.5: Berechnung der Durchschnittstemperaturen Das folgende Beispiel zeigt, wie gleichzeitig die höchsten und niedrigsten Werte ermittelt werden. Listing 11.11: mysqlselectmimaxfrom.php – Minimale und maximale Temperaturen $sql = SELECT AVG(Hoch) AS MittelHoch, AVG(Tief) AS MittelTief, MIN(Tief) AS Tief, MAX(Hoch) AS Hoch FROM Wetter; $result = $mysqli-query($sql); $data = $result-fetch_object(); echo Die mittlere Höchsttemperatur beträgt {$data-MittelHoch} °Cbr; echo Die mittlere Tiefsttemperatur beträgt {$data-MittelTief} °Cbr; echo Die höchste Höchsttemperatur beträgt {$data-Hoch} °Cbr; echo Die niedrigste Tiefsttemperatur beträgt {$data-Tief} °Cbr; Das Prinzip der Abfrage entspricht hier dem vorhergehenden Beispiel. Abbildung 11.6: Der höchste und niedrigste Wert werden ermittelt 488
  • 489.
    Erste Schritte mitMySQL und MySQLi Bei der Ausgabe fällt auf, dass die Kommastellen wenig praxistauglich sind. Bevor Sie jetzt auf die Idee kommen, dafür printf oder number_format einzusetzen, ist ein Blick in die Funktionssammlung von MySQL interessant. Hier wird man bei ROUND fündig: Listing 11.12: mysqlselectroundfrom.php – Rundung der Ausgabewerte vor der Ausgabe $sql = SELECT ROUND(AVG(Hoch),2) AS MittelHoch, ROUND(AVG(Tief),2) AS MittelTief FROM Wetter; $result = $mysqli-query($sql); $data = $result-fetch_object(); echo Die mittlere Höchsttemperatur beträgt {$data-MittelHoch} °Cbr; echo Die mittlere Tiefsttemperatur beträgt {$data-MittelTief} °Cbr; Diese Ausgabe ist schon eher überzeugend, allerdings muss MySQL vorerst bei der sprachabhängigen Darstellung passen: Abbildung 11.7: Gerundete Temperaturwerte Verknüpfungen abfragen Als nächstes soll wieder eine Ausgabe der Temperaturen der einzelnen Städte erfolgen, jedoch mit der Angabe des jeweiligen Bundeslandes. Listing 11.13: mysqlselectjoin1.php: Zusatzinformationen aus verknüpfter Tabelle holen $sql = SELECT Stadt, Hoch, Tief, Name AS Bundesland FROM Wetter W JOIN Bundesland B ON W.BundeslandID = B.ID; $result = $mysqli-query($sql); while($rs = $result-fetch_object()) { echo {$rs-Stadt} ({$rs-Bundesland}):br; echo uarr; {$rs-Hoch}°C, darr; {$rs-Tief}°Cbr; } Die Abfrageform in PHP ändert sich hier nicht – die gesamte Arbeit erledigt die SQL-Anweisung. Die Ausgabe zeigt ein für den geringen Aufwand durchaus 489
  • 490.
    Datenbankprogrammierung respektables Ergebnis (diePfeile werden durch die Entitäten uarr; und darr; erzeugt): Abbildung 11.8: Informationen aus zwei Tabellen: Städte und Bundesländer Etwas kniffliger wird es, wenn die Verknüpfung mit Aggregierungen verbunden werden soll. So könnte man die mittleren Temperaturen in einem Bundesland abfragen. Dazu werden die Städte aus demselben Bundesland gruppiert und aus den Werten einer Gruppe der Durchschnittswert berechnet. Listing 11.14: mysqlselectjoingroup.php – Mittlere Temperaturen nach Bundesland $sql = SELECT ROUND(AVG(Hoch),2) AS MittelHoch, ROUND(AVG(Tief),2) AS MittelTief, Name AS Bundesland 490
  • 491.
    Referenz MySQLi FROM Wetter W JOIN Bundesland B ON W.BundeslandID = B.ID GROUP BY W.BundeslandID ; $result = $mysqli-query($sql); while($rs = $result-fetch_object()) { echo {$rs-Bundesland}:br; echo uarr; {$rs-MittelHoch}°C,; echo darr; {$rs-MittelTief}°Cbr; } Der Trick besteht hier in der Anwendung von GROUP BY. Erst nach der Gruppie- rung werden die Aggregat-Funktionen zur Berechnung des Durchschnitts ange- wendet. Abbildung 11.9: Mittelwerte der Temperaturen pro Bundesland 11.5 Referenz MySQLi Alle Methoden werden auf einer Instanz der Klasse mysqli oder auf einem Resul- tatobjekt ausgeführt, beispielsweise: $mi = new mysqli('localhost', 'root', '', 'test'); $mi-commit(); Alternativ ist immer auch der direkte prozedurale Aufruf möglich: mysqli_commit(); 491
  • 492.
    Datenbankprogrammierung Eigenschaft Beschreibung affected_rows Anzahl der Datensätze, die von der letzten Anweisung betroffen waren (nur für UPDATE/INSERT/REPLACE, nicht jedoch für SELECT). errno Die Nummer des Fehlercodes. error Eine Beschreibung des Fehlers (englisch). field_count Anzahl der Spalten, die die letzte Abfrage zurückgegeben hat. host_info Informationen über den Server und die Art der Verbindung, bei- spielsweise »localhost via TCP/IP«. info Informationen über die letzte Abfrage. insert_id Die letzte durch INSERT in einem AUTO_INCREMENT-Feld erzeugte ID. protocol_version Die Version des MySQL-Protokolls (aktuell: 10). sqlstate Status einer vorher gesendeten Abfrage. thread_id ID des Threads in dem die Abfrage abgearbeitet wird . thread_safe Ermittelt, ob Threadsicherheit besteht. warning_count Anzahl der Warnungen, die die letzte Abfrage auslösten. Tabelle 11.16: Eigenschaften des MySQLi-Objekts Methode Beschreibung autocommit Schaltet die automatische Bestätigung von Transaktionen ein oder aus. change_user Ändert den Benutzer für die aktuelle Verbindung. character_set_name Gibt den aktuellen Zeichensatz zurück. close Schließt die Verbindung. commit Bestätigt die aktuelle Transaktion. Tabelle 11.17: Methoden des MySQLi-Objekts 492
  • 493.
    Referenz MySQLi Methode Beschreibung connect Öffnet eine neue Verbindung zum MySQL-Server. get_client_info Gibt Informationen über die verwendet MySQL-Version zurück. get_client_version Gibt Informationen über den MySQL-Client zurück. get_host_info Gibt Informationen über den Server und die Verbindung zurück. init Vorbereiten eines MySQLi-Objekts für spätere Verwendung. info Gibt die automatisch erstellte ID der letzten Abfrage zurück. kill Versucht den von MySQL belegten Thread zu beenden. multi_query Sendet eine Abfrage an die Datenbank. more_results Ermittelt, ob weitere Ergebnissätze von Mehrfachabfragen vor- handen sind. next_result Nächste Ergebnissätze von Mehrfachabfragen abholen. options Setzt verschiedene Optionen. ping Sendet einen Ping zur Kontrolle der Verbindung an den Server. prepare Kann eine Abfrage vorbereiten. Vorbereitete Abfragen sind schneller bei wiederholter Ausführung. query Eine Abfrage direkt an den Server senden. real_connect Kann eine Verbindung öffnen. real_query Kann eine Abfrage ausführen. rollback Kann eine Transaktion rückabwickeln. select_db Wird eine andere Datenbank als Standard auswählen. Ent- spricht dem SQL-Befehl USE. send_query Sendet eine Abfrage an die Datenbank. sqlstate SQL Status und Fehlercodes einer vorhergehenden Abfrage ermitteln. Tabelle 11.17: Methoden des MySQLi-Objekts (Forts.) 493
  • 494.
    Datenbankprogrammierung Methode Beschreibung ssl_set Kann eine gesicherte SSL-Verbindung aufbauen. stat Der aktuellen Status des Systems. stmt_init Initialisiert eine Abfrage und gibt ein Objekt zurück, mit dem diese Abfrage gesteuert werden kann. Tabelle 11.18 und Tabelle 11.19 zeigen die Eigenschaften und Methoden dieses Objekts. thread_safe Ermittelt, ob Threadsicherheit gegeben ist oder nicht. use_result Bereitet einen Ergebnissatz zur Verwendung vor. Tabelle 11.17: Methoden des MySQLi-Objekts (Forts.) Eigenschaft Beschreibung affected_rows Anzahl der von der Anweisung betroffenen Datensätze. Damit kann man den Erfolg von UPDATE oder DELETE überwachen. errno Der letzte Fehlercode. error Eine Beschreibung des Fehlers (englisch). param_count Die Anzahl der Parameter. sqlstate Ermittelt SQL Status und Fehlercodes einer vorhergehenden Abfrage. ssl_set Kann eine gesicherte SSL-Verbindung aufbauen. Tabelle 11.18: Eigenschaften des Anweisungs-Objekts Methode Beschreibung bind_param Erstellt Parameter für die Anweisung. bind_result Erstellt ein Ergebnisobjekt. close Schließt die Verbindung. data_seek Setzt den Ergebnissatzzeiger auf einen bestimmten Datensatz. Tabelle 11.19: Methoden des Anweisungs-Objekts 494
  • 495.
    Referenz MySQLi Methode Beschreibung execute Führt die Anweisung aus. fetch Holt den Ergebnissatz mit verschiedenen Optionen. fetch_result Holt den Ergebnissatz. get_metadata Holt globale Informationen zur Anweisung. prepare Bereitet die Anweisung vor (vorab Kompilierung). send_long_data Sendet große Datenpakete. store_result Speichert die Ergebnisse zwischen. Tabelle 11.19: Methoden des Anweisungs-Objekts (Forts.) Eigenschaft Beschreibung current_field Ermittelt das aktuelle Feld des Ergebnissatzes. field_count Ermittelt die Anzahl der Felder. length Ermittelt die Länge (Breite) eines Feldes. num_rows Ermittelt die Anzahl der Reihen im Ergebnissatz. Tabelle 11.20: Eigenschaften des Ergebnis-Objekts Methode Beschreibung close Schließt die Verbindung. data_seek Setzt den Ergebnissatzzeiger auf eine bestimmte Reihe. fetch_field_direct Holt direkt ein bestimmtes Feld. fetch_field Holt das nächste Feld einer Liste. fetch_fields Holt Felder als Array. fetch_lengths Ermittelt die Breite des aktuellen Feldes. fetch_object Holt die Reihe als Objekt. Felder sind nun Eigenschaften. Tabelle 11.21: Methoden des Ergebnis-Objekts 495
  • 496.
    Datenbankprogrammierung Methode Beschreibung fetch_row Holt die Reihe als numerisches (einfaches) Array. fetch_assoc Holt die Reihe als assoziatives Array. Die Schlüssel sind die Spal- tennamen. field_seek Setzt den Zeiger auf ein spezifisches Feld. Tabelle 11.21: Methoden des Ergebnis-Objekts (Forts.) 11.6 Kontrollfragen 1. Warum sollte die neue Bibliothek MySQLi anstatt der alten eingesetzt werden? 2. Was versteht man unter Normalisierung? 3. Sie müssen bei der Ausgabe von Daten aus einer MySQL-Datenbank Datums- formate anpassen und n in br umwandeln. Wie gehen Sie vor? 4. Wie würden Sie eine Abfrage formulieren, bei der aus zwei Tabellen Daten zugleich entnommen werden müssen? Welche Voraussetzungen müssen die Tabellen erfüllen? 496
  • 497.
  • 498.
    Die integrierte DatenbankSQLite Nicht immer muss es MySQL sein. Statt einer ausgewachsenen Datenbank reicht oft ein einfacheres System. Die Lücke zwischen einfachen Textdateien als Daten- speicher und einem relationalen Datenbankmanagementsystem schließt in PHP5 das SQLite-Modul. 12.1 Hintergrund SQLite ist fest in PHP integriert. Ein weiteres Modul oder Treiber sind nicht erfor- derlich. Sie können deshalb immer davon ausgehen, dass jede PHP5-Installation wenigstens SQLite bereitstellt. Wenn kleine Datenbankprojekte erstellt werden, die auf Funktionen großer Datenbankmanagementsysteme nicht angewiesen sind, ist SQLite völlig ausreichend. Allerdings muss man sich auch der Grenzen bewusst sein, um gegebenenfalls rechtzeitig wechseln zu können. SQLite ist eine einfache Datenbank-Lösung, die als »Public Domain« entwickelt wurde. Dies ist eine sehr freie Art der Softwareverteilung, bei der völlig auf Rechte und Lizenzen verzichtet wird. Der Code wird jedermann ohne Beschränkungen zur Verfügung gestellt. Der Entwickler ist D. Richard Hipp der Firma Hipp, Wyrick Company, Inc. Die neueste Version wird unter folgender Adresse bereitgestellt: http://www.hwaci.com/sw/sqlite SQLite soll dabei kein Ersatz für MySQL oder PostgreSQL sein. Nur einfachste Anwendungen werden auf größere Datenbank verzichten können. Wenn Sie bereits einige Erfahrung mit Datenbanken haben und Oracle oder MS SQL Ser- ver kennen, wird Ihnen MySQL möglicherweise primitiv vorgekommen sein. Dass es noch deutlich einfacher geht, zeigt SQLite. Dafür profitiert auch SQLite von der Vereinfachung. Zugriffe sind extrem schnell und die Speicherung der Daten nutzt den Speicher effektiv. Einige Benchmarks versprechen etwa doppelte Geschwindigkeit gegenüber dem bereits recht flotten MySQL. Einige der typischen Aufgaben, denen SQLite ohne weiteres gewachsen ist, sind: í Konfigurationsdaten einer Applikation speichern. í Serialisierte Objekte zur Mitnahme von Seite zu Seite speichern. í Speicherung von Anmeldeinformationen. í Ein Gästebuch. í Eine News-Site, auf der aktuelle Nachrichten zu finden sind. 498
  • 499.
    Vor- und Nachteile Einkomplettes Content Management System oder größere Foren dürften allein aufgrund der komplexeren SQL-Abfragen mit MySQL besser bedient sein. 12.2 Vor- und Nachteile Bevor Sie intensiv in SQLite einsteigen, sollten Sie einige technische Hinter- gründe und deren Auswirkungen auf Projekte kennen. Wann SQLite vorteilhaft ist SQLite bietet sich an, wenn einfache Projekte völlig unabhängig von der PHP5- Konfiguration laufen müssen. Die Zugriffe sollten vorrangig lesend erfolgen. Außerdem sollte PHP5 nur auf einem einzigen System laufen, nicht im Cluster oder mit Lastverteilung. Der Anspruch an die Sicherheit der Daten sollte eher gering sein oder eine häufige Datensicherung ist vorhanden. Vorteilhaft ist SQLite deshalb, weil sie vielen Entwicklern mit wenig Datenbanker- fahrung die Chance bietet, ohne großen Aufwand eine datenbankgestützte Appli- kation zu bauen. Denn mit der Integration reduziert sich die Lernkurve auf PHP und sehr wenige SQL-Befehle selbst. Eine kleinere Einstiegbarriere erlaubt dann vielleicht den Ersatz uneffektiver Bastellösungen durch eine halbwegs saubere Pro- grammierung, was insgesamt schon als Vorteil betrachtet werden kann. Wann SQLite nachteilig ist SQLite speichert die gesamte Datenbank in einer einzigen Datei. Schreibzugriffe von einer Benutzersitzung aus führen dazu, dass die gesamten Datei kurzzeitig gesperrt wird. Das führt bei häufigen Schreibzugriffen sehr schnell dazu, dass PHP5 ständig auf die erneute Freigabe der Datenbank wartet und dies verschlech- tert drastisch die Antwortzeiten des Systems. Die Speicherung von Sitzungsdaten in SQLite ist deshalb eher nachteilig. Sollte das Dateisystem feststellen, dass die Datenbankdatei korrupt ist, wird man alle Sit- zungsdaten aller Sitzungen verlieren. Beim bisherigen System beträfe dies nur eine Sitzung. Außerdem verfügt SQLite über keine »Selbstheilungskräfte«, wie sie andere DBMS kennen. John Lim von PHPEverywhere und Sterling Hughes 499
  • 500.
    Die integrierte DatenbankSQLite haben einige Benchmarks erstellt, die klar beweisen, das SQLite bei schreibinten- siven Aufgaben wie der Sitzungsverwaltung deutlich langsamer ist als die einge- baute Sitzungsverwaltung oder andere Datenbanklösungen. Allerdings muss auch gesagt werden, dass die in den Tests erreichten Zugriffszah- len weit höher liegen, als die meisten Webseiten jemals erreichen. Insofern sind derartige Aussagen immer auch akademischer Natur. 12.3 Einführung Die Implementierung von SQLite folgt weitgehend den Vorgaben der C-Imple- mentierung1. Wo immer es an Dokumentation mangelt, kann man also gut auf die Originalausgabe der Datenbank zurückgreifen. Eine einfache Beispielanwendung Ein einfaches Beispiel soll zeigen, wie der Zugriff prinzipiell aussieht. Das Skript geht davon aus, dass ein Verzeichnis data unterhalb des Speicherorts des Skripts existiert, wo die Datenbankdatei erzeugt werden kann: Listing 12.1: SQLiteCreate.php – Erzeugen einer Datenbank und einer Tabelle ?php echo ( 'SQLite Version: '.sqlite_libversion().'br /'); echo ( 'SQLite Encoding: '.sqlite_libencoding().'br /'); $sqliteDb='data/users.db'; if ( !$db = sqlite_open($sqliteDb, 0666, $err) ) die($err); $sql = CREATE TABLE users ( id INTEGER PRIMARY KEY, login STRING UNIQUE, password STRING, email STRING 1 SQLite ist eine C-Bibliothek für den Einbau in C-Projekte. Die Umsetzung als PHP-Modul bricht jedoch nicht mit diesen Vorgaben, sodass weitgehend identische Aufrufe benutzt werden können, abgesehen von zwingenden syntaktischen Differenzen zwischen C und PHP. 500
  • 501.
    Einführung ); if ( !sqlite_query($sql, $db) ) die(sqlite_last_error($db).': '. sqlite_error_string(sqlite_last_error($db))); echo ( Datenbank $sqliteDb erfolgreich angelegt. ); sqlite_close($db); ? Dieses Skript erzeugt eine neue SQLite-Datenbank und darin eine einfache Tabelle mit vier Spalten: id, login, password und email zur Implementierung einer Benutzeranmeldung. Eine Benutzerverwaltung mit SQLite Das folgende Listing zeigt ein weiteres Skript auf einen Blick, eine Erläuterung der wichtigen Passagen folgt danach. Es handelt sich um die noch rudimentäre Benut- zerverwaltung, die die bereits erzeugte Tabelle mit zwei Datensätzen befüllt und einen Anmeldevorgang simuliert: Listing 12.2: SQliteSelect.php – Primitive Benutzerverwaltung mit SQLite ?php $sqliteDb = 'data/users.db'; if (!$db = sqlite_open($sqliteDb, 0666, $err)) { die(Fehler: $err); } $users = array( array( 'login'='jbloggs', 'password'=md5('secret'), 'email'='jbloggs@yahoo.com' ), array( 'login'='jsmith', 'password'=md5('secret'), 'email'='jsmith@php.net' ) ); $result = sqlite_query($db, SELECT * FROM users); $num = sqlite_num_rows($result); if ($num == 0) 501
  • 502.
    Die integrierte DatenbankSQLite { foreach ($users as $user) { $sql = INSERT INTO users (login, password, email) VALUES ( '{$user['login']}', '{$user['password']}', '{$user['email']}' ); echo $sqlbr; if (!sqlite_query($db, $sql)) { die (Fehler: . sqlite_last_error($db).': '. sqlite_error_string(sqlite_last_error($db))); } } echo 'Daten erfasst'; } else { echo 'Daten bereits vorhanden'; } sqlite_close($db); ? h1Abfrage der Datenbank/h1 ?php if (!$db = sqlite_open($sqliteDb, 0666, $err)) { die($err); } $sql = SELECT * FROM users; if (!$result = sqlite_query($db, $sql)) { die (sqlite_last_error($db).': '. sqlite_error_string(sqlite_last_error($db))); } echo ('h2Datenausgabe/h2'); while ($row = sqlite_fetch_array($result, SQLITE_ASSOC)) { 502
  • 503.
    Einführung echo {$row['id']} bName: /b{$row['login']} bE-Mail:/b {$row['email']}br /; } if ($_SERVER['REQUEST_METHOD']=='POST' $_POST['action']=='test') { $pw_encrypted = md5($_POST['logpass']); $sql = SELECT * FROM users WHERE login='{$_POST['logname']}' AND password='$pw_encrypted'; $result = sqlite_query($db, $sql); echo sqlite_error_string(sqlite_last_error($db)); if (sqlite_num_rows($result) == 1) { echo 'pAnmeldung erfolgreich/p'; } else { echo 'pBenutzername oder Kennwort konnten nicht gefunden werden./p'; } } ? h1Test der Anmeldung/h1 form action=?=$_SERVER['PHP_SELF']? method=post input type=hidden name=action value=test/ table tr td Name: /td td input type=text name=logname value=/ /td /tr tr td Kennwort: /td td input type=text name=logpass value=/ /td /tr tr 503
  • 504.
    Die integrierte DatenbankSQLite td nbsp; /td td input type=submit value=Anmelden (Test)/ /td /tr /table /form ?php sqlite_close($db); ? Um Kennwortdaten in der Datenbank zu schützen, werden die Zeichenfolgen als MD5-Hash abgelegt. Dies ist eine irreversible Abbildung beliebiger Daten. Um später das Kennwort verifizieren zu können, wird die Eingabe erneut mit MD5 berechnet und die kodierten Werte werden verglichen. Das Skript ist relativ primitiv, es erfasst nur zwei Musterdatensätze, die hart kodiert sind. Um festzustellen, ob die Daten schon vorhanden sind, erfolgt zuerst eine ein- fache Abfrage: $result = sqlite_query($db, SELECT * FROM users); Dann wird ermittelt, wie viele Daten vorliegen: $num = sqlite_num_rows($result); Die Funktion sqlite_num_rows ermittelt die Anzahl der von der in $result gespei- cherten Abfrage zurückgegebenen Datensätze. Sind keine Datensätze vorhanden, werden die als Array vorliegenden Daten mittels INSERT gespeichert. Geht dabei etwas schief, enthält sqlite_last_error($db) den letzte Fehlercode. Um den passenden Text anzuzeigen, was meist hilfreicher ist, wird eine weitere Funktion benötigt: sqlite_error_string(sqlite_last_error($db)) Für die Ausgabe von Datensätzen kann der Inhalt zeilenweisen einem Array zuge- führt werden. Auch hier dient der Verweis auf den Ergebnissatz, wiederum in $result abgelegt, als Datenquelle: while ($row = sqlite_fetch_array($result, SQLITE_ASSOC)) 504
  • 505.
    Einführung Der Vergleich deseingegebenen Kennworts mit dem gespeicherten erfolgt in einer weiteren Abfrage. Damit dieser Programmteil nur ausgeführt wird, wenn ein Formular gesendet wurde, wird eine Servervariable verwendet: $_SERVER['REQUEST_METHOD']=='POST' Das Kennwort muss wieder mit MD5 verpackt werden: $pw_encrypted = md5($_POST['logpass']); Dieser Wert wird dann für die Abfrage verwendet. Der Rest ist bekannt – es folgt eine Abfrage und diese wird mit sqlite_num_rows untersucht. Nur wenn genau ein Datensatz gefunden wurde, war die Prüfung erfolgreich. Am Ende des Skripts sollten Sie nicht vergessen, die Verbindung zu schließen, damit SQLite die Datei freigeben kann, in der die Datenbank gespeichert ist. sqlite_close($db); Ausblick SQLite kann Daten auch im Speicher des Computers halten, was für temporäre Daten sehr vorteilhaft ist. Unter Linux konnten dafür bislang die Shared-Memory- Funktionen eingesetzt werden, die jedoch unter Windows nicht zur Verfügung standen, was echte Cross-Plattform-Entwicklung natürlich verhindert. Hier bietet SQLite nun eine einfache und elegante Lösung. Natürlich hat SQLite seine Gren- zen. Bei sehr vielen konkurrierenden Zugriffen führt die Sperrung der gesamten Datenbankdatei dazu, dass neu eintreffende Abfragen verzögert werden. Das kann praktisch bis zum Deadlock führen – dem Skriptstillstand. Allerdings ist das Gespann Dateisystem-Speicher-SQLite äußerst leistungsfähig und die allergrößte Masse aller Sites erreicht niemals die kritische Benutzerzahl. Spannend sind auch die Möglichkeiten, SQLite beliebige Funktionen hinzuzufü- gen, die innerhalb von SQL-Abfragen benutzt werden können. Damit kann man sehr effizient die Defizite der Datenbank ausgleichen und erreicht eine mithin höhere Leistungsfähigkeit als bei größeren Datenbanksystemen. Es ist durchaus denkbar, dass auch anspruchsvolle Lösungen für geringen oder mittlere Last mit SQLite programmiert werden. 505
  • 506.
    Die integrierte DatenbankSQLite 12.4 Referenz SQLite Funktion und Parameter Beschreibung sqlite_array_query Führt eine Abfrage aus und gibt ein Array mit den Ergeb- nissen zurück. sqlite_busy_timeout Maximale Wartezeit auf ein Kommando. Der Standard- wert beträgt 60 Sekunden, die Angabe muss in Millisekun- den erfolgen. sqlite_changes Anzahl der Spalten, die das letzte Kommando verändert hat. sqlite_close Schließt die Verbindung. sqlite_column Holt eine Spalte anhand des angegebenen Indizes aus der aktuellen Ergebnisreihe. sqlite_create_aggregate Nutzt eine PHP-Funktion zur Verarbeitung von Daten aus einer Tabelle. sqlite_create_function Definiert eine PHP-Funktion callback unter dem Namen function zur Verwendung in künftigen SQL-Befehlen. sqlite_current Holt einen Ergebnissatz aus dem Ergebnisarray zurück. sqlite_error_string Zeigt eine Fehlermeldung an. Siehe auch sqlite_last_error. sqlite_escape_string Markiert in SQLite unzulässige Zeichen. sqlite_fetch_array Holt die nächste Reihe aus dem Ergebnissatz. sqlite_fetch_single Holt die erste Spalte aus dem Ergebnissatz. sqlite_fetch_string Alias für sqlite_fetch_single. sqlite_field_name Name eines Feldes sqlite_has_more TRUE, wenn weitere Reihen verfügbar sind. sqlite_last_error Letzte Fehlernummer. sqlite_last_insert_rowed Letzte ID, die in einer INTEGER PRIMARY KEY Spalte einge- fügt wurde. 506
  • 507.
    Referenz SQLite Funktion undParameter Beschreibung sqlite_libencoding Zeichenkodierung der Bibliothek, beispielsweise ISO- 8859-1. sqlite_libversion Die Versionsnummer der Bibliothek. sqlite_next Sucht einen Ergebnisdatensatz zur Ausgabe, gibt aber selbst nichts aus. Wird zur Schleifensteuerung verwendet. sqlite_num_fields Anzahl der Felder im Ergebnis. sqlite_num_rows Anzahl der Reihen im Ergebnis. sqlite_open Öffnet die Datenbank und erzeugt die Datei, wenn sie nicht existiert, mit dem angegebenen Zugriffsmode (Unix- Dateiparameter, beispielsweise 0666 (oktal)). sqlite_popen Wie sqlite_open, aber persistent. sqlite_query Fragt die Datenbank ab und gibt ein Handle auf die Ergeb- nisse zurück. sqlite_rewind Setzt den Zeiger auf die erste Reihe. Wird für die Schlei- fensteuerung verwendet. sqlite_seek Setzt den Zeiger auf die angegebene Reihe. Wird für die Schleifensteuerung verwendet. sqlite_udf_decode_binary Dekodiert Binärdaten, bevor diese an eine benutzerdefi- nierte Funktion gesendet werden. sqlite_udf_encode_binary Kodiert Binärdaten, bevor diese von einer benutzerdefi- nierte Funktion zurückgenommen werden. sqlite_unbuffered_query Wie sqlite_query, aber das Ergebnis wird nicht sofort komplett geholt, sondern erst, wenn es andere Funktionen anfordern. Schneller bei einfachen sequenziellen Lese- zugriffen. 507
  • 508.
    Die integrierte DatenbankSQLite 12.5 Kontrollfragen 1. Was müssen Sie tun, um SQLite benutzt zu können? 2. Welche Datentypen kennt SQLite? 3. Sie möchten mehrere Tausend Datensätze für eine hoch frequentierte Website verwalten. Ist SQLite dafür die beste Wahl? Begründen Sie die Antwort? 4. Welche Aufgabe hat der Befehl VACUUM? 508
  • 509.
    Datenbanklösungen mit MySQL 1 3
  • 510.
    Datenbanklösungen mit MySQL 13.1Bibliotheks-Verwaltung mit MySQL Dieses Kapitel widmet sich einem Praxisprojekt: Der Verwaltung einer Bibliothek. Dabei soll, neben der einfachen Auflistung von Büchern auch deren Position in einem Regalsystem erfasst werden. Schrittfolge Um das Programm komplett zu erstellen, werden zwei Schritte erforderlich: í Entwicklung einer Datenbankstruktur í Aufbau der Oberfläche í Programmierung der Geschäftslogik Die Trennung von Oberfläche und Logik erfolgt durch ein primitives Template- System, einer Verwaltung von Vorlagen. Dies erleichtert die Erweiterung der Funktionalität erheblich. Weitere Aufgaben Das Programm ist ein guter Start in Richtung komplexer Datenbankanwendun- gen. Es ist zugleich Grundlage des nächsten Kapitels, indem die Daten der Bücher eleganter beschafft werden. Das eigentliche Problem bei der Nutzung des Pro- gramms ist nämlich nicht dessen Erstellung, sondern dessen Nutzung. Das Erfas- sen aller Buchdaten ist rech mühselig. Glücklicherweise bietet Amazon einen Webservice, der die Beschaffung der Daten erleichtert. Vorerst wird jedoch – auch zur Korrektur später automatisch erfasster Daten – ein Eingabeformular benutzt. Funktionen Das Programm soll grundsätzlich folgende Funktionen anbieten: í Erfassung von Büchern í Suchen nach Titel, Autor und ISBN í Erfassung der Regalposition und optional einer Anzahl vorhandener Exemplare 510
  • 511.
    Bibliotheks-Verwaltung mit MySQL í Erfassung von Lesern, an die die Bücher ausgeliehen werden í Verwaltung der Leihvorgänge Da ein Benutzer ein Buch immer nur einmal ausleihen kann, sind nur sehr einfa- che Beziehungen erforderlich. Allerdings kann jeder Benutzer mehrere verschie- dene Bücher ausleihen, was zu folgenden Tabellen führt: í Tabelle »Books« für die Erfassung der Bücher í Tabelle »Reader« für die Leser, die Bücher ausleihen können í Tabelle »LendOut« für die Leihvorgänge Der Vorteil bei der Ausleihtabelle besteht im Aufbau einer Historie, das heißt, hier kann man schnell nach der Häufigkeit und Dauer der Ausleihe suchen. Nebenbei bemerkt eignet sich das Programm auch für eine private Bibliothek. Wer mit seinen Kumpels und Kollegen einen Buchpool bildet und sich gegensei- tig Bücher ausleiht, kann hier verwalten, welches Exemplar gerade wo unterwegs ist. Für die eigentliche Funktionalität muss außerdem festgelegt werden, welche Sei- ten und Formulare erforderlich sind. Dabei sind zwei »Betriebsarten« zu beach- ten: Ausleih- und Verwaltungsmodus. Im Ausleihmodus sollen Leser oder der Bibliothekar suchen und Ausleihvorgänge steuern können, im Verwaltungsmodus können neue Bücher erfasst oder alte entfernt werden. Benötigt werden dazu fol- gende Seiten: í Startseite mit Auswahl der Betriebsart í Seite zum Suchen von Büchern í Seite zum Steuern des Ausleih- und Rücknamevorgangs í Seite zum Erfassen, Anzeigen und Löschen von Benutzern í Seite zum Erfassen, Anzeigen und Löschen von Büchern Auf eine Verwaltung der Bediener durch eigene Anmeldenamen und Kennwörter soll hier – vor allem aus Platzgründen – verzichten werden. Die dazu benötigten Techniken unterscheiden sich nicht grundlegend von den gezeigten und die Umsetzung sollte nach der Absolvierung des Kapitels nicht schwer fallen. 511
  • 512.
    Datenbanklösungen mit MySQL 13.2Vorbereitung der Datenbank Für den Entwurf der Datenbank müssen Sie sich zuerst über eine Struktur der Tabellen im Klaren sein. Die drei benötigten Tabellen wurden bereits erwähnt. Der Aufbau wird so gewählt, dass alle Daten im passenden Datenformat gespei- chert werden können. Das Anlegen kann mit einem Werkzeug erfolgen. Im Beispiel wurde das MySQL Control Center verwendet. Für dieses Projekt wurde lokal eine eigene Datenbank angelegt. Wenn Sie ausschließlich auf dem Server eines Providers arbeiten und dort nur eine fertige Datenbank verwenden können, müssen Sie diesen Schritt aus- lassen und bei der Nutzung der Skripte den Namen der Datenbank anpassen. Wie das zu erfolgen hat, wird im entsprechenden Code-Abschnitt erläutert. Datenbank anlegen Falls noch nicht geschehen, legen Sie die für die Datenbank-Übungen erforderli- che Datenbank »marktundtechnik« an. Sie können auch jeden anderen Namen nehmen, müssen dann aber die Skripte entsprechend anpassen. Dies ist nicht sehr schwer, weil es eine zentrale Konfigurationsdatei für die Datenbankparameter gibt. Datenbank mit SQL Wenn Sie mit SQL arbeiten, geben Sie am Prompt der MySQL-Konsole folgendes ein: CREATE DATABASE marktundtechnik; Weitere Parameter werden für diese Projekt nicht benötigt. Datenbank mit MySQL Control Center anlegen Um die Datenbank mit MySQL Control Center anzulegen, starten Sie das Pro- gramm. Beim ersten Mal müssen Sie den Server angeben (vermutlich »localhost«, wenn es lokal läuft) und gegebenenfalls Anmeldename und Kennwort. Nach einer frischen Installation ist der Anmeldename »root« und das Kennwort ist leer. Im Zweig Datenbanken des Servers klicken Sie mit der rechten Maustaste und wählen dann im Kontextmenü den Eintrag Neue Datenbank. 512
  • 513.
    Vorbereitung der Datenbank Abbildung 13.1: Eine neue Datenbank mit MySQL Control Center anlegen Danach erscheint ein Eingabefeld, wo der Name der Datenbank eingetragen wird. Weitere Optionen sind nicht erforderlich. Tabellen anlegen Nun werden die drei benötigten Tabellen angelegt. Zuerst wird wieder der dazu erforderliche SQL-Code vorgestellt. Danach wird der entsprechende Dialog im Control Center gezeigt. Die Tabellen weisen keine Besonderheiten auf, wenn- gleich einige Dinge zu beachten sind. Jede Tabelle enthält ein Feld id, das als Typ BIGINT und AUTO_INCREMENT definiert wurde. Dieses Feld ist zugleich der Primärschlüssel der Tabelle (PRIMARY KEY). Des Weiteren wurde bei einigen Feldern explizit NOT NULL angegeben, um zu ver- hindern, dass beim Anlegen der Datensätze Spalten leer bleiben. Felder, nach denen gesucht werden könnte, wurden außerdem mit einem Index bedacht. Tabellen mit SQL anlegen Das folgende Listing zeigt die SQL-Anweisungen, mit denen die drei Tabellen erzeugt werden können: Listing 13.1: SQL-Anweisungen zum Erstellen der Tabellen # # Tabellenstruktur für Tabelle `books` # CREATE TABLE books ( id bigint(20) NOT NULL auto_increment, title varchar(100) NOT NULL default '', subtitle varchar(255) default '', isbn varchar(10) NOT NULL default '', author varchar(100) default '', number int(11) NOT NULL default '1', 513
  • 514.
    Datenbanklösungen mit MySQL shelf varchar(15) default '', PRIMARY KEY (id), KEY idx_title (title) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; # -------------------------------------------------------- # # Tabellenstruktur für Tabelle `lendout` # CREATE TABLE lendout ( id bigint(20) NOT NULL auto_increment, books_id bigint(20) NOT NULL default '0', reader_id bigint(20) NOT NULL default '0', lendingdate datetime NOT NULL default '0000-00-00 00:00:00', lendingperiod timestamp NOT NULL, PRIMARY KEY (id) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; # -------------------------------------------------------- # # Tabellenstruktur für Tabelle `reader` # CREATE TABLE reader ( id bigint(20) NOT NULL auto_increment, name varchar(100) NOT NULL default '', surname varchar(100) NOT NULL default '', address varchar(150) NOT NULL default '', zip varchar(5) NOT NULL default '', city varchar(100) NOT NULL default '', active tinyint(1) NOT NULL default '0', created datetime NOT NULL default '0000-00-00 00:00:00', PRIMARY KEY (id), KEY idx_name (name), KEY idx_surname (surname) ) ENGINE=MyISAM DEFAULT CHARSET=latin1; Beachten Sie, dass die Anweisungen nur Tabellen erzeugen. Um das Skript erneut anzuwenden, müssen eventuell vorhandene Tabellen mit DROP TABLE entfernt wer- den. 514
  • 515.
    Vorbereitung der Datenbank Tabellenmit MySQL Control Center anlegen Zuerst wird die Buchtabelle erzeugt. Neben Titel, Autor und ISBN werden auch die Regalnummer und die Anzahl der Exemplare erfasst. Abbildung 13.2: Struktur der Tabelle »Books« Wenn im Control Center eine entsprechende Zeile der Tabelle ausgewählt wird, lassen sich die Parameter, beispielsweise die Anzahl der Zeichen beim Datentyp VARCHAR, erfassen. Die Kundentabelle weist keine Besonderheiten auf: Abbildung 13.3: Die Tabelle »Reader« zur Erfassung der Bibliotheks- nutzer Als letztes folgt die Ausleihtabelle: Abbildung 13.4: Struktur der Tabelle »LendOut« Nach diesem Schritt steht die Datenbank bereit, um Daten aufzunehmen und spä- ter natürlich Abfragen zu beantworten. 515
  • 516.
    Datenbanklösungen mit MySQL 13.3Die Seiten des Projekts Am Anfang wurde bereits eine Übersicht über die benötigten Formulare gezeigt. Diese werden hier nacheinander in der genannten Reihenfolge vorgestellt. Vorbereitungen – das Template-System Wie bereits kurz erwähnt, sollen Geschäftslogik und Design weitgehend getrennt werden. Dazu wird ein kleines Template-System entwickelt, das die Verwaltung der Seiten übernimmt. Das Prinzip ist recht einfach. Eine zentrale Instanz, index.php, verwaltet alle Seiten. Diesem Skript wird mitgeteilt, welche Vorlage (Template) aufgerufen werden soll. Die Vorlage besteht immer aus zwei Teilen: Dem PHP-Code und dem HTML-Code. Damit beide interagieren können, gibt es ein paar Steuerzeichen, die im HTML eingebaut werden und die in PHP aufzulö- sen sind. Der Sinn der Übung besteht nicht darin, ein professionelles Template-System wie phpTemple (http://www.phptemple.de) oder Smarty (http://smarty.php.net) zu ersetzen, sondern die prinzipielle Arbeitsweise und den Nutzen transparent wer- den zu lassen. Die Technik lässt sich leicht auf andere Projekte übertragen. Hat man erstmal das Gefühl dafür, wann ein solches System Vorteile bringt, fällt es leichter, sich für den Einarbeitungsaufwand in ein kommerzielles Produkt zu ent- scheiden. Die Steuerzeichen des Template-Systems Die wichtigste Funktion besteht in der Datenausgabe. Dazu werden Variablen erforderlich. Diese sehen – der Einfachheit halber – genauso aus, wie in PHP. Allerdings kann man sie direkt ins HTML schreiben: div{$variable}/div Die geschweiften Klammern verhindern Konflikte mit Währungsangaben mit Dol- larzeichen. Zum Ausgeben von Tabellen braucht man eine Konstruktion, die Datenwiederho- lungen erzeugt. In PHP sind dies Schleifen. In HTML wird einfach ein Kommen- tar eingefügt, den das Skript verarbeiten kann (Editoren ignorieren diesen Kommentar): 516
  • 517.
    Die Seiten desProjekts table !-- #REPEAT:readers -- tr td ${readers:name} /td td ${readers:surname} /td /tr !-- #END:readers -- /table Das Programm wiederholt jetzt das Tag mit sämtlichen darin befindlichen Codes so oft, wie das Array readers dies ermöglicht. Das Array muss assoziativ sein. Der Index mit dem Namen surname wird über ${readers:surname} abgerufen. Tech- nisch werden dazu reguläre Ausdrücke und simple Ersetzungsfunktionen benutzt. Funktionsweise des Template-Systems Das eingesetzte Template-System beschränkt sich auf eine einzige Klasse, die in der Datei engine.inc.php definiert ist. Sie dient dazu, die HTML-Vorlagen zu erkennen, Variablen zu suchen und zu ersetzen und gegebenenfalls Blöcke mit sich wiederholenden Daten zu bilden. Das Resultat wird direkt ausgegeben. Damit die Steuerung funktioniert, ist folgender Einsatz vorgesehen: $engine = include_once('engine.inc.php'); Diese Zeile schließt die Klasse ein, erzeugt eine Instanz und gibt sie wieder zurück. Dadurch hat die Klasse sowohl Zugriff auf die globalen Variablen, deren Werte benutzt werden können, als auch die Möglichkeit, sich selbst dem aufrufen- den Code bereitzustellen. Der aufrufende Code muss nun den Namen des Templates zuweisen: $engine-Html = 'Start.html'; Alternativ kann dies entfallen; dann wird versucht, die Daten aus der GET-Variab- len TEMPLATE zu übernehmen. Die Steuerung kann damit vom Code in das Template selbst verlagert werden: a href=?index.php?TEMPLATE=start?Zur Startseite/a 517
  • 518.
    Datenbanklösungen mit MySQL Wennein expliziter Code-Block erforderlich ist, muss man diesen lediglich wie die Vorlage benennen und ihm die Dateierweiterung .php geben. Damit das funk- tioniert, wurde die Startseite index.php folgendermaßen definiert: Listing 13.2: index.php – Universelle Startseite für das Template-System ?php $engine = include_once('engine.inc.php'); if (!isset($_GET['TEMPLATE'])) { $engine-Html = 'start.html'; $engine-Code = 'start.php'; } else { $engine-Html = {$_GET['TEMPLATE']}.html; $engine-Code = {$_GET['TEMPLATE']}.php; } if (strlen($engine-Code) 0 file_exists($engine-Code)) { include_once($engine-Code); } $engine-RunTemplate(); ? Zuletzt muss die Ausführung auf der jeweiligen Seite angestoßen werden. Die Standardseite index.php erledigt dies durch Aufruf von runTemplate(): $engine-RunTemplate(); Zuvor sollten freilich die Variablen mit Werten belegt werden, die im Template benutzt werden. Dies wiederum basiert auf normalem PHP-Code. Code des Template-Systems Nachfolgend finden Sie den Code des Template-Systems mit einigen Erläuterun- gen zur Funktionsweise durchsetzt. Der Code ist chronologisch zu lesen und voll- ständig in mehreren Abschnitten: ?php class SimpleTemplate { 518
  • 519.
    Die Seiten desProjekts Zwei private Variablen speichern den Namen der Vorlage und optional inkludier- ten und implizit ausgeführten Code (ebenso als Dateiname übergeben). Der Zugriff erfolgt später über die __set-Methode und den Konstruktor: private $templateCode; private $templateHtml; Die Steuerung der Vorlage erfolgt durch eingebettete Kommentare, die von dem folgenden regulären Ausdruck erkannt werden: const COMMENT = '/!-- #(.+):(.+)s+--()/iU'; Der Konstruktor kann zur Übergabe der Vorlage und des Codes benutzt werden. Beide Parameter sind optional, sodass sie auch entfallen können: public function __construct($html = '', $code = '') { $this-templateCode = $code; $this-templateHtml = $html; } Diese Methode erzeugt zwei virtuelle Eigenschaften Html und Code, die die ent- sprechenden Werte im Objekt setzen: public function __set($prop, $value) { switch ($prop) { case 'Html': $this-templateHtml = $value; break; case 'Code': $this-templateCode = $value; break; } } Diese Methode führt die vorbereitete Vorlage aus. Sie durchsucht die Seite, erkennt die Blöcke und löst diese auf. public function runTemplate() { Dazu wird zuerst die Vorlage selbst geladen: if (strlen($this-templateHtml) 0) { 519
  • 520.
    Datenbanklösungen mit MySQL $html = file_get_contents($this-templateHtml); } if (strlen($this-templateCode) 0) { include_once($this-templateCode); } Nun werden die Wiederholungen aufgelöst. Dazu werden zuerst alle Blöcke ermittelt, die mit der Kommentarsyntax umschlossen sind: $matches = array(); $blocksFound = preg_match_all(self::COMMENT, $html, $matches, PREG_OFFSET_CAPTURE); if ($blocksFound) { $page = ''; Der reguläre Ausdruck produziert ein Array in $matches, dessen Elemente fol- gende Bedeutung haben: í 0: Array der Kommentaranfänge í 1: TAGs í 2: Variablen í 3: Ende-Offset Liegt ein solches Array vor, beginnt die eigentliche Analyse. Anhand der Blockda- ten wird mit Hilfe von schnellen Zeichenkettenfunktionen der umschlossene Teil ermittelt, weil diese Daten gegebenenfalls wiederholt werden müssen. Das passiert innerhalb der for-Schleife praktisch für jeden Block: if (is_array($matches)) { $startBlock = $lastStartBlock = 0; for ($i = 0; $i count($matches[1]); $i++) { $token = strtoupper($matches[1][$i][0]); $offsetComment = (int)$matches[0][$i][1]; $offsetContent = ((int)$matches[3][$i][1]) + 1; $variableName = $matches[2][$i][0]; Die Auflösung der Daten erfolgt in zwei Schritten. Beim Start des Blocks werden die Anfangspositionen gespeichert (REPEAT), beim Ende werden die Daten zur 520
  • 521.
    Die Seiten desProjekts Wiederholung benutzt (END). Die eigentliche Arbeit erledigt die Methode repeat- Block, die nachfolgend erläutert wird: switch ($token) { case 'REPEAT': $startBlock = $offsetContent; $startComment = $offsetComment; break; case 'END': $block = substr($html, $startBlock, $offsetComment - $startBlock); $block = $this-repeatBlock($block, $variableName); $len = $startComment - $lastStartBlock; $page .= substr($html, $lastStartBlock, $len).$block; $lastStartBlock = $offsetContent; break; } Die wiederholten Daten werden der fertigen Seite, die in $page gespeichert wird, hinzugefügt: } $page .= substr($html, $offsetContent); } Zum Schluss werden noch die übrig gebliebenen Variablen ersetzt: echo ($this-varReplace($page)); } else { echo 'Nicht gefunden'; } } Wie bereits angedeutet, kommt repeatBlock eine große Bedeutung zu. Der Methode wird der Code eines Blockes und der Name einer Variablen übergeben. Ist diese Variable ein Array, wird der Block für jedes Array-Element einmal ausge- geben und die Daten des jeweiligen Elements werden benutzt, um die Blockvari- ablen zu ersetzen. Letzteres findet in einer eigenen Methode statt, arrayReplace. Damit der Zugriff auf globale Variablen gelingt, wird der Name der Variablen als dynamische Variable ($$-Syntax) benutzt: 521
  • 522.
    Datenbanklösungen mit MySQL privatefunction repeatBlock($block, $varName) { global $$varName; $result = ''; if (is_array($$varName)) { $this-index = 0; foreach ($$varName as $value) { $result .= $this-arrayReplace($block); $this-index++; } } else { $result = $block; } return $result; } arrayReplace nutzt einen regulären Ausdruck, um die Variablen in der Vorlage zu finden und zu ersetzen. Das eigentliche Ersetzen passiert in der Rückruffunktion replaceArray, die ihrerseits Zugriff auf den globalen Adressraum hat: private function arrayReplace($block) { $block = preg_replace_callback('/{$([^:}]*):(.+)}/U', array($this, 'replaceArray'), $block); return $block; } private $index = 0; private function replaceArray($repl) { global $$repl[2]; $arr = $$repl[2]; return $arr[$this-index]; } Vergleichbar arbeitet die Ersetzung der normalen skalaren Variablen, wo ebenso eine Rückruffunktion benutzt wird. Auch hier geht es nur um einen vereinfachten Zugriff auf den globalen Adressraum: 522
  • 523.
    Die Seiten desProjekts private function varReplace($block) { $block = preg_replace_callback('/{$(.+)}/U', array($this, 'replaceSkalar'), $block); return $block; } private function replaceSkalar($repl) { global $$repl[1]; return $$repl[1]; } Der Rest der Datei steht außerhalb der Klassendefinition. Er erzeugt eine Instanz der Klasse und gibt das entsprechende Objekt als Ergebnis der Ausführung zurück. Damit kann die include-Funktion (oder include_once) als Quelle einer Zuwei- sung benutzt werden. Zuvor wird noch versucht, den Namen des Templates aus der GET-Variablen TEMPLATE zu entnehmen, wobei dieser Schritt optional ist, das heißt, er schlägt nicht fehl, wenn die Angabe fehlt: $__template = isset($_GET['TEMPLATE']) ? $_GET['TEMPLATE'] : ''; return new SimpleTemplate($__template); ? Das vorgestellte Prinzip ist einfach und schnell und bietet einigen Raum für Ver- besserungen. Es zeigt vor allem, wie effektiv mit regulären Ausdrücken und der neuen objektorientierten Syntax von PHP5 gearbeitet werden kann. Der Code der Seiten Die folgenden Skripte zeigen eine kurze Übersicht über die Vorlagen, also den HTML-Code der Seiten (befreit von gestalterischen Elementen, um die Lesbarkeit zu verbessern) und die Variablen, die die dynamischen Daten enthalten. Startseite mit Auswahl der Betriebsart Die Startseite ist relativ einfach, denn es sind nur folgende Zustände zu unterschei- den: í Ausleihen oder Rücknehmen eines Buches (Betriebszustand) í Anlegen oder Sperren von Kunden (Betriebszustand) í Anlegen oder Herausnehmen von Büchern (Administrationszustand) 523
  • 524.
    Datenbanklösungen mit MySQL Dasvorliegende Beispiel ist vergleichsweise rudimentär und unterscheidet nicht explizit zwischen Betriebs- und Administrationszustand. Dies ist jedoch leicht zu implementieren. Solange eine solche Bibliotheksverwaltung lokal im Intranet läuft, ist die Unterscheidung möglicherweise nicht erforderlich. Eine öffentliche Seite müsste um entsprechende Sicherheitsfunktionen ergänzt werden. Da keine weitere Logik erforderlich ist, besteht diese Seite lediglich aus einigen Links auf die anderen Vorlagen: Listing 13.3: start.html – Startseite der Bibliotheksverwaltung html body h1Bibliotheksverwaltung/h1 Willkommen in der Bibliotheksverwaltung mit PHP5 h2Funktionsauswahl/h2 a href=index.php?TEMPLATE=kundenKundenverwaltung/a br / br / a href=index.php?TEMPLATE=buecherBuuml;cherverwaltung/a br / br / Heute ist der {$today}. /body /html PHP-Code wird hier kaum benötigt, lediglich eine Variable zur Anzeige des Datums ist zu definieren: Listing 13.4: start.php – Erzeugen einer deutschen, systemunabhängigen Datumsanzeige ?php if (PHP_OS == 'WINNT') { setlocale(LC_TIME, 'ge'); } else { setlocale(LC_TIME, 'de_DE'); } $today = strftime('%d. %B %Y'); ? 524
  • 525.
    Die Seiten desProjekts Seite zum Steuern des Ausleih- und Rücknahmevorgangs Die Seite zum Steuern des Ausleih- und Rücknahmevorgangs benötigt folgende elementare Funktionen: í Suchen des Kunden, wenn nicht vorhanden, Sprung zur Kundenerwaltung í Suche des Buches (wird vorausgesetzt, dass nur vorhandene Bücher genutzt werden) í Ausleihe oder Rücknahmevorgang Hier wird auf jeden Fall der Datenbankzugriff benötigt, der in einer Include-Datei abgelegt wird, die auch den anderen Modulen zur Verfügung steht. In dieser Datei wird ein MySQLi-Objekt erzeugt, das in allen folgenden Codes den Zugriff auf die Datenbank erlaubt: Listing 13.5: database.inc.php – Include-Datei für den Datenbankzugriff ?php $mysqli = new mysqli('localhost', 'root', '', 'marktundtechnik'); if (!is_object) { die ('Fehler: Verbindung zur Datenbank konnte nicht aufgebaut werden'); } ? Seite zum Erfassen, Anzeigen und Löschen von Kunden Die Seite umfasst folgende Funktionen: í Liste aller Kunden anzeigen í Anzeige von Details í Ändern bestehender Kunden í Anlegen neuer Kunden í Setzen einer Sperre (anstatt eines endgültigen Löschens) Die HTML-Vorlage ist sehr direkt aufgebaut und enthält mehrere Tabellen, die jeweils die Darstellung einer Funktion steuern: 525
  • 526.
    Datenbanklösungen mit MySQL Listing13.6: kunden.html – Vorlage für die Verwaltung der Kunden html body h1Bibliotheksverwaltung/h1 a href=index.phpStartseite/a | a href=index.php?TEMPLATE=ausleiheAusleihe/a | a href=index.php?TEMPLATE=buecherBuuml;cherverwaltung/a h2Kundenverwaltung/h2 h3Informationen/h3 table border=1 width=400 caption style=font-weight:boldKundenliste/caption tr th Status /th th Name /th th Aktion /th /tr !-- #REPEAT:reader -- tr td {$reader:state} /td td {$reader:name}, {$reader:surname} /td td a href=index.php?TEMPLATE=kundenamp;id={$reader:id} Details ansehen/a /td /tr !-- #ENDREPEAT:reader -- /table !-- #IF:detail_name -- table border=1 width=400 caption style=font-weight:bold 526
  • 527.
    Die Seiten desProjekts Details fuuml;r Kunde {$detail_name}/caption tr td Name, Vorname: /td td {$detail_name}, {$detail_surname} /td /tr tr td Anschrift: /td td {$detail_zip} {$detail_city} /td /tr /table !-- #ENDIF:detail_name -- h3Auml;ndern/h3 form action=index.php?TEMPLATE=kunden method=post input type=hidden name=action value=change / input type=hidden name=id value={$detail_id} / table border=1 width=400 caption style=font-weight:boldKundendaten/caption tr td Daten: /td td input type=text name=reader_name value={$detail_name}/, input type=text name=reader_surname value={$detail_surname}/ /td /tr tr td PLZnbsp;Ort: /td td input type=text name=reader_zip value={$detail_zip}/ 527
  • 528.
    Datenbanklösungen mit MySQL input type=text name=reader_city value={$detail_city}/ /td /tr tr td Status /td td input type=checkbox name=reader_state {$detail_state} value=1/ /td /tr tr td /td td input type=submit value=Auml;ndern / /td /tr /table /form h3Neuer Kunde/h3 form action=index.php?TEMPLATE=kunden method=post input type=hidden name=action value=new / table border=1 width=400 caption style=font-weight:boldKundendaten/caption tr td Daten: /td td input type=text name=reader_name /, input type=text name=reader_surname / /td /tr tr td PLZnbsp;Ort: /td td input type=text name=reader_zip / 528
  • 529.
    Die Seiten desProjekts input type=text name=reader_city / /td /tr tr td /td td input type=submit value=Erfassen / /td /tr /table /form /body /html Besonderheiten gibt es hier nicht. Beachten Sie den Aufruf der anderen Vorlagen im Menü: index.php?TEMPLATE=ausleihe Die Steuerung des Codes erfolgt durch versteckte Felder in den Formularen: input type=hidden name=action value=change / input type=hidden name=id value={$detail_id} / Dabei werden auch dynamische Daten ({$detail_id}) übergeben, wodurch Vorlage und Code miteinander in Verbindung stehen. Die Vorlage verwendet außerdem eine #IF-Anweisung, um einen Teil der Seite nur bei Bedarf anzuzeigen. Weitaus aufschlussreicher ist der PHP-Code, der mit dieser Vorlage verknüpft ist: Listing 13.7: kunden.php – Steuerung der Vorlage mittels Datenbankabfragen ?php include('database.inc.php'); // Neuer Kunde? if ($_SERVER['REQUEST_METHOD']=='POST' isset($_POST['action'])) { switch ($_POST['action']) { case 'new': $mysqli-query(INSERT INTO reader (name, surname, zip, city, active, created) VALUES ('{$_POST['reader_name']}', 529
  • 530.
    Datenbanklösungen mit MySQL '{$_POST['reader_surname']}', '{$_POST['reader_zip']}', '{$_POST['reader_city']}', 1, CURDATE())); break; case 'change': $state = isset($_POST['reader_state']) ? 1 : 0; $mysqli-query(UPDATE reader SET name='{$_POST['reader_name']}', surname='{$_POST['reader_surname']}', zip='{$_POST['reader_zip']}', city='{$_POST['reader_city']}', active=$state WHERE id = {$_POST['id']}); break; } } // Liste fuellen $query = $mysqli-query(SELECT id, name, surname, IF(active=0,'Gesperrt','Aktiv') AS state FROM reader); while($arr = $query-fetch_assoc()) { $reader[] = $arr; } // Details abrufen, wenn gefordert if (isset($_GET['id'])) { $query = $mysqli-query(SELECT * FROM reader WHERE id = {$_GET['id']}); $detail = $query-fetch_assoc(); $detail_id = $detail['id']; $detail_name = $detail['name']; $detail_surname = $detail['surname']; $detail_zip = $detail['zip']; $detail_city = $detail['city']; $detail_state = ($detail['active']==1) ? 'checked' : ''; } ? Am Anfang des Codes werden die beiden Formulare ausgewertet, erst danach erfolgt der Aufbau der Liste für die Ausgabe. Auf diese Weise wird sichergestellt, dass die soeben erfolgten Änderungen sofort in der Anzeige reflektiert werden. 530
  • 531.
    Die Seiten desProjekts Unbedingt erforderlich ist auch der Einschluss der Vorbereitung der Datenbank- abfrage: include('database.inc.php'); Die Aktionen werden nur ausgewertet, wenn ein Formular abgesendet wurde. Dazu wird die Servervariable REQUEST_METHOD auf den Wert »POST« hin unter- sucht: if ($_SERVER['REQUEST_METHOD']=='POST' isset($_POST['action'])) Anschließend werden die passenden INSERT- bzw. UPDATE-Befehle gebildet, um die Daten in der Datenbank zu verändern. Als Quelle dienen die Felder der Formu- lare. Sind alle Kommandos abgearbeitet, werden die aktuellen Daten aus der Daten- bank gelesen: $query = $mysqli-query(SELECT id, name, surname, IF(active=0,'Gesperrt','Aktiv') AS state FROM reader); Der SELECT-Befehl sorgt gleich für die korrekte Aufbereitung der Daten für die Aus- gabe. Das Feld active der Tabelle reader kann die Werte 0 oder 1 enthalten. Für die Anzeige werden diese Werte mit der MySQL-Funktion IF in die Zeichenket- ten »Gesperrt« bzw. »Aktiv« umgewandelt. Es ist generell eine gute Idee, fest ver- drahtete Berechnungen in der Datenbank erledigen zu lassen, und damit den lokalen Code zu entlasten. Nur dann, wenn die Berechnungen selbst dynamisch sind und möglicherweise häufig verändert werden oder wenn sie sehr komplex sind, ist PHP besser geeignet. Die fertige Abfrage muss nun noch in ein Array überführt werden, damit die Tem- plate-Engine die Daten in einer Schleife ausgeben kann: while($arr = $query-fetch_assoc()) { $reader[] = $arr; } Damit kann dann innerhalb des Abschnitts !-- #REPEAT:reader -- folgende Syntax zum Abruf der Felder benutzt werden: {$reader:name} Für das Füllen der Detaildaten und der Formulare wird keine Schleife benötigt. Die erforderlichen Variablen werden deshalb in skalarer Form abgerufen (nach einer erneuten SELECT-Anweisung): 531
  • 532.
    Datenbanklösungen mit MySQL $detail_id= $detail['id']; In der Vorlage wird dieser Wert beispielsweise benutzt, um die aktuelle Auswahl im Formular zu halten: input type=hidden name=id value={$detail_id} / Die folgende Abbildung zeigt, wie die Seite in Aktion aussieht: Abbildung 13.5: Die Kundenverwaltung der Bibliothek Seite zum Erfassen, Anzeigen und Löschen von Büchern Die Seite zur Verwaltung der Bücher ist praktisch gleichartig zur Verwaltung der Kunden aufgebaut. Lediglich die Daten unterscheiden sich und damit Anzahl und Benennung der Spalten der Tabellen. 532
  • 533.
    Die Seiten desProjekts Zuerst wird wieder die Vorlage gezeigt: Listing 13.8: buecher.html – Die Vorlage zur Verwaltung der Bücher html body h1Bibliotheksverwaltung/h1 a href=index.phpStartseite/a | a href=index.php?TEMPLATE=ausleiheAusleihe/a | a href=index.php?TEMPLATE=kundenKundenverwaltung/a h2Buuml;cherverwaltung/h2 h3Informationen/h3 table border=1 width=400 caption style=font-weight:boldBuuml;cherliste/caption tr th Anzahl /th th ISBN /th th Titel /th th Aktion /th /tr !-- #REPEAT:books -- tr td {$books:number} /td td nowrap {$books:isbn} /td td {$books:title} /td td a href=index.php?TEMPLATE=buecheramp;id={$books:id}amp;action=show 533
  • 534.
    Datenbanklösungen mit MySQL Details /a /td /tr !-- #ENDREPEAT:books -- /table a href=index.php?TEMPLATE=buecheramp;action=newNeues Buch erfassen/ a h3Auml;ndernnbsp;/nbsp;Erfassen/h3 form action=index.php?TEMPLATE=buecher method=post input type=hidden name=action value={$detail_action} / input type=hidden name=id value={$detail_id} / table border=1 width=400 caption style=font-weight:boldBuchdaten/caption tr td Titel: /td td input type=text name=book_title value={$detail_title} size=40/ /td /tr tr td Untertitel: /td td input type=text name=book_subtitle value={$detail_subtitle} size=40/ /td /tr tr td Autor: /td td input type=text name=book_author value={$detail_author}/ /td /tr tr 534
  • 535.
    Die Seiten desProjekts td ISBN: /td td input type=text name=book_isbn value={$detail_isbn}/ /td /tr tr td Anzahl /td td input type=text name=book_number value={$detail_number} size=4/ /td /tr tr td Ausgeliehen /td td input type=text name=book_lend value={$detail_lend} size=4 disabled/ /td /tr tr td /td td input type=submit value={$detail_actiontext} / /td /tr /table /form /body /html Die Vorlage ist bewusst etwas anders als die für die Kunden verwendete aufgebaut. Detailanzeige, Änderung und Erfassung sind in einem Formular zusammenge- fasst, dessen Funktion sich damit dynamisch ändert. Ein verstecktes Feld, dessen Wert über eine Variable gesteuert wird, macht es möglich: input type=hidden name=action value={$detail_action} / 535
  • 536.
    Datenbanklösungen mit MySQL Auchdie Beschriftung der Sendeschaltfläche ist dynamisch: input type=submit value={$detail_actiontext} / Die eigentliche Arbeit erledigt der Code-Teil: Listing 13.9: buecher.php – Logik zur Verwaltung der Bücher ?php include('database.inc.php'); $detail_action = 'change'; $detail_actiontext = 'Auml;ndern'; if ($_SERVER['REQUEST_METHOD']=='GET' isset($_GET['action'])) { switch ($_GET['action']) { case 'new': $detail_action = 'new'; $detail_actiontext = 'Erfassen'; break; } } if ($_SERVER['REQUEST_METHOD']=='POST' isset($_POST['action'])) { switch ($_POST['action']) { case 'new': $mysqli-query(INSERT INTO books (title, subtitle, author, isbn, number) VALUES ('{$_POST['book_title']}', '{$_POST['book_subtitle']}', '{$_POST['book_author']}', '{$_POST['book_isbn']}', {$_POST['book_number']} )); break; case 'change': $mysqli-query(UPDATE books SET title='{$_POST['book_title']}', subtitle='{$_POST['book_subtitle']}', author='{$_POST['book_author']}', isbn='{$_POST['book_isbn']}', number={$_POST['book_number']}, WHERE id = {$_POST['id']}); 536
  • 537.
    Die Seiten desProjekts break; } } $query = $mysqli-query(SELECT * FROM books); while($arr = $query-fetch_assoc()) { $books[] = $arr; } // Details abrufen, wenn gefordert if (isset($_GET['id'])) { $query = $mysqli-query(SELECT *, (SELECT COUNT(*) FROM lendout WHERE books_id = b.id) AS lend FROM books b WHERE id = {$_GET['id']}); $detail = $query-fetch_assoc(); $detail_id = $detail['id']; $detail_title = $detail['title']; $detail_subtitle= $detail['subtitle']; $detail_author = $detail['author']; $detail_number = $detail['number']; $detail_isbn = $detail['isbn']; $detail_lend = $detail['lend']; } ? Der erste Teil steuert die Umschaltung des Formulars. Danach folgen das Einfü- gen (INSERT) und Aktualisieren (UPDATE) der Datenbank. Im letzten Teil werden wieder die frisch aktualisierten Daten abgefragt, damit stets die aktuelle Liste zu sehen ist. Eine Besonderheit stellt die letzte Abfrage dar, die auf einem so genann- ten Sub-Select basiert. (Achtung! Dazu ist mindestens MySQL 4 erforderlich.) Diese Unterabfrage sichert den Zugriff auf die Tabelle lendout, um die Anzahl der bereits ausgeliehenen Bücher zu erhalten. Die Geschäftslogik des Ausleihvorgangs Die Logik des Ausleihvorgangs muss folgende Aufgaben erfüllen: í Suchen des Kunden, beispielsweise anhand der Kundennummer í Suchen des Buches, beispielsweise anhand der ISBN í »Buchen« des Ausleihvorgangs oder der Rücknahme eines Buches 537
  • 538.
    Datenbanklösungen mit MySQL Abbildung 13.6: Die Bücherseite mit Details über ein Buch Außerdem sind noch einige Verwaltungsfunktionen denkbar, die hier nicht voll- ständig ausgeführt wurden: í Auflistung komplett ausgeliehener Bücher í Auflistung nicht pünktlich zurückgegebener Bücher í Auflistung säumiger Kunden Die Vorlage der Ausleihseite Auch die Ausleihseite besteht aus einer Vorlage und einer Code-Seite. Die Vorlage ist vergleichsweise schlicht. Auch hier werden wieder mehrere Formulare einge- setzt, um die Daten zu sammeln. Die Daten (Kunde, Buch) werden in Sitzungs- variablen gespeichert, damit sie bis zum Ende des Vorgangs erhalten bleiben. Der Teil der Seite, der die eigentliche Buchung ausführt, wird erst sichtbar, wenn alle Daten vorliegen. 538
  • 539.
    Die Seiten desProjekts Listing 13.10: ausleihe.html – Vorlage für die Ausleihseite html body h1Bibliotheksverwaltung/h1 a href=index.phpStartseite/a | a href=index.php?TEMPLATE=kundenKundenverwaltung/a | a href=index.php?TEMPLATE=buecherBuuml;cherverwaltung/a h2Kunden/h2 table tr td h3Kunden suchen/h3 form action=index.php?TEMPLATE=ausleihe method=post input type=hidden name=action value=reader / Name input type=text name=reader / input type=submit value=Suchen / /form /td td h3Buch suchen/h3 form action=index.php?TEMPLATE=ausleihe method=post input type=hidden name=action value=book / ISBN input type=text name=book / input type=submit value=Suchen / /form /td /tr /table !-- #IF:noerror -- h3Ausleihe- oder Ruuml;cknahme/h3 Ausleih- oder Ruuml;cknahmeinformationen: ul liKundenname:nbsp;{$reader_name}/li liKundennummer: {$reader_id}/li liBuchtitel: {$book_title}/li liISBN: {$book_isbn}/li liEs sind noch b{$available}/b Buuml;cher verfuuml;gbar./li /ul table 539
  • 540.
    Datenbanklösungen mit MySQL tr td form action=index.php?TEMPLATE=ausleihe method=post input type=hidden name=reader value={$reader_id} / input type=hidden name=book value={$book_id} / input type=hidden name=action value=lend / input type=submit value=Ausleihen {$disabled} / /form /td /tr /table !-- #ENDIF:noerror -- table width=400 border=1 captionUuml;bersicht aller Vorgauml;nge des Kunden/caption tr thBuch ID/th thAusleihdatum/th thFauml;lligkeit/th thAktion/th /tr !-- #REPEAT:history -- tr td{$history:books_id}/td td{$history:lendingdate}/td td{$history:due}/td td a href=index.php?TEMPLATE=ausleiheamp;action=returnamp;id={$history:id} Ruuml;ckgabe/a /td /tr !-- #ENDREPEAT:history -- /table !-- #IF:error -- Fehler:nbsp;{$reader_error}{$book_error} !-- #ENDIF:error -- /body /html Diese Seite nutzt die Wiederholungs-Anweisungen zum Aufbau und definiert einen Sprung auf die Ausleihseite. 540
  • 541.
    Die Seiten desProjekts Der Code der Ausleihseite Listing 13.11: ausleihe.php – Die Steuerung der Vorlage nutzt intensiv die Datenbank ?php include('database.inc.php'); session_start(); $reader_name = $_SESSION['reader_name']; $reader_id = $_SESSION['reader_id']; $book_title = $_SESSION['book_title']; $book_id = $_SESSION['book_id']; $book_isbn = $_SESSION['book_isbn']; $number = $_SESSION['number']; unset($error); if ( ($_SERVER['REQUEST_METHOD']=='POST' isset($_POST['action'])) || ($_SERVER['REQUEST_METHOD']=='GET' isset($_GET['action']))) { switch ($_REQUEST['action']) { case 'lend': $sql = INSERT INTO lendout (books_id, reader_id, lendingdate, lendingperiod, active) VALUES ( {$_POST['book']}, {$_POST['reader']}, CURDATE(), 30, 1 ); $mysqli-query($sql); break; case 'return': $mysqli-query(UPDATE lendout SET active = 0 WHERE id = {$_GET['id']}); break; case 'reader': $result = $mysqli-query(SELECT * FROM reader WHERE name LIKE '%{$_POST['reader']}%' OR id = '{$_POST['reader']}'); if ($result-num_rows == 1) { 541
  • 542.
    Datenbanklösungen mit MySQL $arr = $result-fetch_assoc(); $reader_name = $arr['name']; $reader_id = $arr['id']; } else { $reader_error = 'Auswahl des Kunden war nicht eindeutig'; $error = true; } break; case 'book': $result = $mysqli-query(SELECT * FROM books WHERE title LIKE '%{$_POST['book']}%' OR isbn = '{$_POST['book']}'); if ($result-num_rows == 1) { $arr = $result-fetch_assoc(); $book_title = $arr['title']; $book_id = $arr['id']; $book_isbn = $arr['isbn']; $number = $arr['number']; } else { $book_error = 'Auswahl des Buches war nicht eindeutig'; $error = true; } break; } } if (!isset($error)) { $result = $mysqli-query(SELECT COUNT(*) AS lent FROM lendout WHERE books_id=$book_id AND active=1); $books_to_lend = $result-fetch_object(); $available = $number - $books_to_lend-lent; $disabled = ($available = 0) ? 'disabled' : ''; $_SESSION['reader_name'] = $reader_name; $_SESSION['reader_id'] = $reader_id; 542
  • 543.
    Die Seiten desProjekts $_SESSION['book_title'] = $book_title; $_SESSION['book_id'] = $book_id; $_SESSION['book_isbn'] = $book_isbn; $_SESSION['number'] = $number; // Ermittle die Liste aller bisherigen Buchungen fuer diesen Benutzer $sql = SELECT id, books_id, DATE_FORMAT(lendingdate, '%d.%m.%Y') AS lendingdate, IF(TO_DAYS(CURDATE())-TO_DAYS(lendingdate) lendingperiod, 'Überfällig', CONCAT('Noch ', 30 - (TO_DAYS(CURDATE()) - TO_DAYS(lendingdate)), ' Tage') ) AS due FROM lendout WHERE reader_id=$reader_id AND active=1; $result = $mysqli-query($sql); if ($result != false) { while($arr = $result-fetch_assoc()) { $history[] = $arr; } } $noerror = true; } ? Der Code basiert wieder auf dem bereits mehrfach verwendeten Prinzip der Über- tragung eines Kommandos, dessen Erkennen in einem switch-Zweig und der Steuerung der entsprechenden SQL-Anweisungen auf Basis der Auswahl. Freilich ist bei der Ausleihe (und Rückgabe) etwas mehr zu tun, da nun alle drei Tabellen an der Aktion beteiligt sind. Da Kunden und Bücher getrennt gesucht werden, werden die bereits gefundenen Werte in Sitzungsvariablen festgehalten. Damit das Sitzungsmanagement funktio- niert, wird es am Beginn des Skripts gestartet: session_start(); Dann werden die bestehenden Sitzungsvariablen wiedergeholt, soweit vorhanden. Die Fehlervariable $error dient der Steuerung des entsprechenden #IF-Blockes. Der Inhalt eines solchen Blockes wird ausgeführt, wenn die Variable existiert. Um unter allen Umständen zu verhindern, dass die Variable ohne Vorliegen eines Fehlergrunds einen Wert enthält, wird sie zunächst zurückgesetzt: unset($error); 543
  • 544.
    Datenbanklösungen mit MySQL Dannwerden die entsprechenden Abfragen an die Datenbank gesendet. Beim Einfügen wird die zulässige Ausleihzeit des Buches fest auf 30 Tage gesetzt. Die Tabelle nutzt außerdem die Spalte active, um festzuhalten, welche Datensätze aktiv sind. Um eine Historie des Benutzerverhaltens zu erhalten, werden die Datensätze bei Rückgabe des Buches nicht gelöscht, sondern der Wert in der Spalte active wird lediglich auf 0 gesetzt: UPDATE lendout SET active = 0 WHERE id = {$_GET['id']} Die Anzeige der noch verfügbaren Bücher nutzt dieselbe Spalte, was die Abfrage sehr einfach macht: SELECT COUNT(*) AS lent FROM lendout WHERE books_id=$book_id AND active=1 Etwas komplexer ist die Abfrage der Liste der Ausleihvorgänge (Historie). Hier wer- den Datumsfunktionen von MySQL benutzt, um die Anzahl der noch verbleiben- den Ausleihtage festzustellen und gegebenenfalls eine Meldung anzuzeigen, wenn ein Buch überfällig ist. Damit kann verhindert werden, dass säumige Nutzer wei- tere Bücher ausleihen. SELECT id, books_id, DATE_FORMAT formatiert das interne Datumsformat für die Ausgabe im Deutschen Format: DATE_FORMAT(lendingdate, '%d.%m.%Y') AS lendingdate, Die IF-Funktion ermittelt die Datumsdifferenz. Dazu wird mit TO_DAYS ein Tages- zähler ermittelt und mit dem aktuellen Datum (CURDATE) verglichen. Als Ver- gleichswert wird die individuelle Ausleihdauer (in lendingperiod) herangezogen, sodass die Funktion auf vereinbarte Verlängerungen korrekt reagieren kann. Die IF-Funktion gibt bei wahrer Bedingung den ersten Teil zurück (hier das Wort »Überfällig«), andernfalls einen Text, der mittels CONCAT zusammengesetzt wird. IF(TO_DAYS(CURDATE())-TO_DAYS(lendingdate) lendingperiod, 'Überfällig', CONCAT erlaubt die Angabe mehrerer Parameter, die zu einer Zeichenkette zusam- mengefasst werden, unabhängig vom Datentyp. Der Name der damit neu erzeug- ten Spalte wird als due (fällig) bezeichnet: CONCAT('Noch ', 30 - (TO_DAYS(CURDATE())-TO_DAYS(lendingdate)), ' Tage') ) AS due 544
  • 545.
    Die Seiten desProjekts Als Auswahlkriterium gilt logischerweise der ausgewählte Nutzer. Außerdem wer- den nur offene Leihvorgänge angezeigt: FROM lendout WHERE reader_id=$reader_id AND active=1 Die Übergabe der Abfrageergebnisse an das Array $history korrespondiert wieder mit der Vorlage: while($arr = $result-fetch_assoc()) { $history[] = $arr; } In der Vorlage wird beispielsweise die Fälligkeit folgendermaßen ausgegeben: td{$history:due}/td Wie das fertig aussieht, kann der nächsten Abbildung entnommen werden: Abbildung 13.7: Die Ausleihseite in Aktion 545
  • 546.
    Datenbanklösungen mit MySQL 13.4Ausblick Die vorgestellte Bibliothek zeigte zwei entscheidende Techniken: í Funktionsweise eines rudimentären Template-Systems í Praktische Anwendung der MySQLi-Bibliothek Freilich bleibt noch einiges zu tun, um ein solches System praxistauglich zu machen. Dazu gehört zuerst sicher eine vernünftige Gestaltung, die mit einer Reihe von Maßnahmen einhergeht. Zuerst sollte eine zentrale Datei erstellt wer- den, die CSS-(Style)-Definitionen enthält und in allen Vorlagen Verwendung fin- det. Außerdem fehlen noch einige grundlegende Funktionen: í Benutzerverwaltung und Absicherung gegen unbefugten Zugriff. í Regalverwaltung. Es gibt zwar bereits eine Spalte shelf, aber diese wird nicht benutzt. Für Benutzer könnte sich daran eine Suchfunktion anschließen, über die die Bücher schnell gefunden werden. í Fehlermanagement. Datenbank- und Bedienfehler führen meist zum Pro- grammabbruch í Online-Zugriff. Die derzeitige Lösung ist praktisch nur intranet-tauglich. Für eine auch online betreibbare Bibliothek sind noch einige Ergänzungen (Benutzeranmeldung, Benutzerdienste, Sicherheitsfunktionen) einzubauen. í Hilfen bei der Erfassung von Buchdaten. Zumindest der letzte Punkt kann eine echte Erleichterung sein. Dazu finden Sie im nächsten Kapitel XML und Webservices ein entsprechendes Projekt, das den Amazon-Webservice nutzt, um vollständige bibliografische Daten zu einem Buch auf Grundlage der ISBN zu beschaffen. So ist auch der Katalog schnell gefüllt. Nicht zuletzt kann auch die Umstellung auf ein kommerzielles Templatesystem wie phpTemple ein Schritt hin zu einer schnell erstellten und leicht zu wartenden Lösung sein, weil hier beispielsweise die SQL-Abfragen unverändert übernommen werden können. Auf PHP kann weitgehend verzichtet werden, weil einige Makro- funktionen – gesteuert über XML-Tags – die Arbeit erledigen. 546
  • 547.
    Kontrollfragen 13.5 Kontrollfragen 1. Wozuwerden Template-Systeme eingesetzt? 2. Erweitern Sie die Applikation, sodass zu jedem Leser der Bibliothek mehrere Tele- fonnummern gespeichert werden können. Wie gehen Sie vor? 3. Formulieren Sie eine SQL-Abfrage, die die nötigen Daten zur Erstellung von Mahnschreiben erzeugt, um säumige Leser auf die bereits abgelaufene Rück- gabefrist hinzuweisen. 547
  • 549.
  • 550.
    XML und Webservices XMList inzwischen für viele Anwendungen eine fest etablierte Technologie. Mit PHP5 fanden auch neue, vereinheitlichte Bibliotheken Verwendung, die die Ver- arbeitung und Erzeugung von XML noch einfacher machen. 14.1 Vorbemerkungen Dieser Abschnitt klärt in knapper Form über einige Grundlagen auf, die Sie unbe- dingt beherrschen sollten, bevor Sie praktisch mit XML arbeiten. XML XML ist eine Auszeichnungssprache für Dokumente, die strukturierte Informa- tionen enthalten. Derartige Informationen enthalten beliebigen Inhalt, also Text, Bilder usw. und Angaben darüber, was dieser Inhalt bedeutet; eine Art Semantik des Inhalts. In einem XML-Dokument wird eine Überschrift nicht deshalb zur Überschrift, weil Sie in einer bestimmten Weise erscheint, sondern weil sie explizit als Überschrift markiert wurde. Überhaupt spielt die Formatierung keine Rolle und ist auch technisch nicht möglich. Erst das Ausgabegerät kann die Informa- tionen anhand ihrer Struktur in eine für den Menschen (oder andere Maschinen) sinnvoll formatierte Form überführen. XML ist eine so genannte Markup-Sprache (XML = Extensible Markup Langu- age). Sie dient der Erstellung von Strukturen für Dokumente. XML ist ein Stan- dard, der die Art der Strukturierung beschreibt. Was ist ein Dokument? Die Anzahl der Applikationen, die XML bereits nutzen, unterstützen oder sogar darauf angewiesen sind, wächst drastisch. XML ist längst im Alltag angekommen und keineswegs mehr ein Exot unter den Datenformaten. Webprogrammierer, vor allem mit einem Hintergrund im grafischen Bereich, werden das vielleicht noch nicht so empfunden haben. Wer jedoch mit Datenformaten hantiert, kommt inzwischen fast unweigerlich mit XML in Berührung. Allerdings ist XML kein All- heilmittel und auch kein Ersatz für relationale Datenbanken. Es ist eine sinnvolle Ergänzung, die vor allem den systemübergreifenden Datenaustausch stark verein- facht. 550
  • 551.
    Vorbemerkungen XML-Dokumente sind dieBasis dieses Datenaustauschs. Letztlich ist XML immer wieder eine Datei, gleich ob sie als solche im Dateisystem abgelegt oder als Teil einer HTTP-Anforderung gesendet wird. Der Sinn eines solchen Dokuments ergibt sich aus der gewählten Strukturbeschreibung. Ein XML-Dokument kann deshalb Webseiten beschreiben (XHTML), Vektorgrafiken enthalten (SVG), mathematische Formeln darstellen (MathML) oder sogar Rechnungen beinhalten (EMC). Es gibt Tausende derartige vordefinierte Formate und weitere lassen sich jederzeit mit bestimmten Werkzeugen (und entsprechenden Fachkenntnissen) entwickeln. XML selbst ist also nur eine Art Metasprache, die eine Vorschrift zur Definition von Datenformaten darstellt. XML-Dokumente entstehen, indem sie dieser globalen Vorschrift genügen, egal ob die konkrete Struktur im Einzelfall sinnvoll ist oder nicht. XML versus HTML XML und HTML haben nur oberflächlich etwas gemeinsam. In HTML ist sowohl der Satz an Formatanweisungen als auch deren Semantik festgelegt. Das Tag b bezeichnet seinen Inhalt als fett und es wird erwartet, dass ein Ausgabegerät dies auch so darstellt. Das Tag test ist dagegen bedeutungslos und wird ignoriert. Zugleich wird ein Ausgabegerät (der Browser) definiert und damit das Verhalten der mit HTML markierten Dokumente fixiert. XML ist universeller und legt solche Regeln nicht fest. Die Ähnlichkeit hat ihren Ursprung in den gemeinsamen Wurzeln. HTML wurde auf der Basis der Urform aller Auszeichnungssprachen, SGML, entworfen und letztlich ist XML eine ver- einfachte Form von SGML. Daher rührt die Ähnlichkeit. XML definiert niemals selbst eine Semantik oder ein Satz von Tags. Dies ist Auf- gabe der anwendenden Instanz. Ein Sinn aus einem XML-Dokument ergibt sich erst, wenn eine bestimmte, konkrete Auszeichnungssprache, den Regel von XML folgend, entworfen wurde. In diesem Abschnitt wird noch die Variante RSS vorge- stellt, die der Erstellung von Dokumenten dient, die Einträge in einem Weblog beschreiben. Für HTML gibt es auch eine Entsprechung in XML, XHTML genannt. Um nun die Semantik (den Sinn) in ein Dokument zu bekommen, benötigt man eine Instanz, die in der Lage ist, XML zu interpretieren. Einen Sinn kann man frei- lich nur erkennen, wenn man das Ziel der Darstellung kennt. Ohne ein Programm, das eine Weiterverarbeitung vornimmt, bleibt XML Selbstzweck. Deshalb kommt diesem Verarbeitungsprozess, nicht zuletzt in PHP, eine große Bedeutung zu. 551
  • 552.
    XML und Webservices WarumXML? Aus der einleitenden Darstellung wird sich nicht zwingend ableiten lassen, warum XML nun eine so große Bedeutung zukommt. XML wurde für reichhaltig struktu- rierte Dokumente entworfen, die ein hohes Maß an Automation bei der Verarbei- tung erfordern. Weder HTML noch SGML können dies in dieser exzessiven Form leisten. HTML scheitert an der Vermischung von Struktur und Semantik, was die Nutzbarkeit ein- schränkt1. SGML ist universell, zugleich aber sehr viel komplizierter und mit vie- len Nebeneffekten durch die komplexe Syntax belegt. Programme, die SGML gut verarbeiten, sind sehr teuer. Viele Anwendungen benötigen eine derartige Kom- plexität nicht, weshalb sich SGML außerhalb der Druckindustrie nie durchgesetzt hat. XML ist einfacher, klar strukturiert und leicht zu lesen. Programme, die XML ver- arbeiten – so genannte Parser – sind leicht zu erstellen und zu perfektionieren. Praktisch existieren sie für alle Sprachen, Plattformen und Systeme in vollkommen ausgereifter Form. Im Verbund mit der technischen Unterstützung wird XML praktisch einsetzbar – deshalb ist XML so wichtig. Wie XML definiert ist XML wird durch eine Reihe von Dokumenten definiert, die alle im Web zugäng- lich sind. Sie sind teilweise äußerst abstrakt und sehr technisch, im Alltag also wenig hilfreich. Ein Blick hinein kann dennoch nicht schaden, um ein Gefühl für den Hintergrund zu bekommen. Wer selbst XML-Formate entwerfen möchte, muss diese Dokumente tatsächlich verstanden haben. Hier folgt eine Auswahl: í Extensible Markup Language (XML) 1.0 (http://www.w3.org/TR/WD-xml) Definiert die Syntax von XML, sozusagen die Mutter aller Dokumente. í XML Pointer Language (XPointer, http://www.w3.org/TR/1998/WD-xptr- 19980303) and XML Linking Language (Xlink, http://www.w3.org/TR/1998/ WD-xlink-19980303) Beide Standards definieren Wege, um Links (Verknüpfungen) zwischen Datenquellen zu definieren. Sie sind quasi der Ersatz des HTML-Tags a in der XML-Welt. XPointer beschreibt, wie eine Ressource adressiert wird (wo 1 Für seinen ureigenen Zweck, nämlich Webseiten, war dies jedoch sehr sinnvoll. 552
  • 553.
    Vorbemerkungen man sie findet), während XLink die Beziehungen zwischen zwei oder mehr Ressourcen definiert. í Extensible Style Language (XSL) (http://www.w3.org/TR/WD-xsl) Dieser Standard definiert die globale Methodik der Formatierung und Trans- formation (letzteres im wichtigen Teilstandard XSLT). Damit wird für XML die Brücke zur realen Welt der Ausgabegeräte geschaffen. Eine einführende Beschreibung folgt im nächsten Abschnitt. í XPath und XQuery Für die gezielte Auswahl von Teilstrukturen aus einem XML-Dokument dient die Abfragesprache XPath. XQuery dient ähnlichen Zwecken und ist bei sehr datenorientierten Strukturen von Vorteil, da es Ähnlichkeiten mit SQL auf- weist. XPath ist bereits seit einiger Zeit etabliert, XQuery noch in der Entwick- lung begriffen. í DTD und Schema Vor der Struktur steht die Strukturdefinition. Eine DTD (Document Type Defi- nition) definiert, welche Tags wie von anderen abhängen. Leider ist die DTD selbst nicht in einem XML-Format definiert, was die automatisierte Verarbei- tung etwas erschwert. Das neuere XML Schema ist dagegen selbst ein XML- Dialekt. Im Gegensatz zur DTD weist es einen starke Unterstützung für Struk- turen auf, wie sie Datenbank nutzen. So sind beispielsweise viele elementare Datentypen definiert. Für die Arbeit mit XML und PHP sollten Sie mit XML, XSLT und in Grundzügen mit XPath und DTD vertraut sein. Das ist mitnichten trivial, aber der Einsatz lohnt sich. XSLT XSLT ist die Transformationssprache für XML-Dokumente. Mit Hilfe von XSLT kann beispielsweise ein Teil eines XML-Datenpaketes in HTML umgewandelt werden, damit der Browser es anzeigen kann. XSLT ist nicht einfach, aber unglaublich mächtig. Einführung XSLT erlaubt eine weitgehend eigene Kontrolle der weiteren Verarbeitungs- schritte gegenüber dem, was fertige Programm anbieten. XSLT erlaubt es außer- 553
  • 554.
    XML und Webservices dem,fertige Vokabulare einfach zu übernehmen und an eigene Bedingungen anzupassen. Damit wird der Datenaustausch weiter vereinfacht und verbessert. Neben der Transformation von XML nach XML ist vor allem, die Umwandlung von XML nach HTML und von XML nach Text von Bedeutung. XSLT erzeugt in der Regel eine frei formatierte Datei. Dies ist meist XML, muss jedoch nicht. Als Eingabe muss jedoch immer XML vorliegen. XSLT nutzt zur Auswahl von Knoten die Abfragesprache XPath, womit sich der Kreis zu den anderen Standards wieder schließt. Sie müssen also mindestens XPath und XSLT beherrschen, um praktisch Umwandlungsprogramme schreiben zu können. PHP wird hierbei zur reinen Hilfsschicht degradiert, es dient lediglich dem Laden der Dateien von der Fest- platte (oder von einem Server) und dem Rückschreiben der Ergebnisse. Eine kompakte Einführung in XSLT XSLT ist eine so genannte funktionale Programmiersprache. Das Prinzip unter- scheidet sich grundlegend von den objektorientierten oder imperativen Sprachen, wie beispielsweise PHP. Der Programmfluss selbst wird in erster Linie durch Auto- matismen initiiert, in zweiter Linie dann durch Regeln. Regeln definieren Sie, um bestimmte Effekte beim Auftreten von bestimmten Daten zu erreichen. Vorteil derartiger Systeme ist die weitgehende – bei XSLT per Definition die vollkom- mene – Befreiung von Seiteneffekten. Wenn eine Regel gilt, dann wird diese und nur diese ausgeführt und dies in immer der gleichen Art und Weise. Dazu gehört auch, dass Variablen zwar verfügbar sind, beispielsweise um einer Regel einen Wert zu übergeben, ihren Inhalt aber nachträglich nicht ändern können. Sie ver- halten sich also eher wie die Konstanten in PHP, abgesehen davon, dass der Inhalt dynamisch definiert werden kann. Nachträgliche Änderungen könnten Seitenef- fekte erzeugen, was nicht erlaubt ist. Dennoch kann man damit erstaunlich effektiv programmieren und verblüffende Resultate erzielen. Nicht immer ist XSLT die perfekte Sprache. Richtig leistungs- fähig wird sie erst in Kombination mit einer modernen objektorientierten Sprache, die hinreichende imperative Merkmale aufweist. Es ist nahe liegend, Transforma- tion und Programm mit PHP in einen Kontext zu überführen. Zuvor sind jedoch wenigstens elementare Kenntnisse von XSLT notwendig. 554
  • 555.
    Vorbemerkungen Die Basisregeln inXSLT XSLT basiert auf XML, weshalb jede Datei durch die entsprechende Deklaration eingeleitet wird. Dann folgt das Wurzelelement stylesheet. Das W3C emp- fiehlt als Standardnamensraum xsl; diese Angabe ist aber im Prinzip freiwillig. Es ist jedoch empfehlenswert, generell den Standardnamensraum zu verwenden. Daraus ergibt sich folgendes Grundgerüst für XSLT: Listing 14.1: Ein leeres XSLT-Programm-Fragment ?xml version=1.0 encoding=UTF-8 ? xsl:stylesheet version=1.0 xmlns:bxsl/b=http://www.w3.org/1999/XSL/Transform xsl:stylesheet Durch die Erweiterung des Attributes xmlns wird der Namensraumalias xsl festge- legt. Zwischen den Wurzelelementen wird nun das Regelwerk aufgebaut. Eine zentrale Rolle spielt das Element xsl:template. Templates (dt. Vorlagen) bilden die Stufen der eigentlichen Transformation. Dabei gibt es zwei Arten von Templa- tes. Zum einen können sie durch eine XPath-Anweisung in ihrer Zuständigkeit programmiert werden. Die folgende Regel zeigt, wie jedes Element name zu einer Ausgabe im Ausgabedatenstrom führt: xsl:template match=name h1NAME/h1 /xsl:template Eine andere Methode ist der Aufruf benannter Vorlagen, dazu später mehr. Der Inhalt des Elements findet hier freilich noch keine Berücksichtigung. Text kann, wie gezeigt, direkt ausgegeben werden. Beachten Sie dabei, dass es sich auch hier um wohlgeformtes XML handeln muss; HTML muss also gegebenenfalls den Regeln von XHTML 1.0 entsprechend modifiziert werden. Wenn Sie eine Vorlage mit xsl:template select=regelname benennen, kön- nen Sie diese folgendermaßen aufrufen: xsl:call-template name=regelname/ Soll explizit Text ausgegeben werden, der mit dem verwendeten XML-Editor2 nicht darstellbar ist, muss das xsl:text-Element eingesetzt werden. Das ist eigentlich – nach der Spezifikation – immer notwendig. Die direkte Angabe von Text oder Tags ist eine Vereinfachung. 2 Schreibt man XSLT mit einem einfachen Editor, trifft dies nur bedingt zu. 555
  • 556.
    XML und Webservices xsl:templatematch=name Hier folgt ein Zeilenumbruch: xsl:text0x0A/xsl:text /xsl:template Wo Text ist, sind Kommentare nicht weit. Diese entsprechen, XML-konform, den aus HTML bekannten und werden nicht in den Ausgabedatenstrom übernom- men: !-- Ein Kommentar in XSLT sieht aus wie in HTML -- Vorlagen werden meist verschachtelt angewendet. Das folgende Beispiel zeigt das Grundgerüst einer HTML-Seite, wie sie mit XSLT erzeugt wird: xsl:template match=/ html body xsl:apply-templates / /body /html /xsl:template Zuerst erkennt der XSLT-Prozessor hier, dass die Vorlage das Wurzelelement der XML-Quelle verarbeitet. Dann wird das Grundgerüst der HTML-Seite erstellt. Innerhalb des Body-Tags wird versucht, alle übrigen Elemente durch Aufruf der passenden Vorlagen zu verarbeiten. Dass xsl:apply-template keine Parameter hat, ist ein spezieller Fall. Er setzt voraus, dass alle Elemente irgendwo auf eine passende Vorlage stoßen, wobei der Prozessor den besten Treffer auswählt und die- sen – und nur diesen – ausführt. Allerdings besitzt der Prozessor eine Fallback-Funktion. Wenn kein Template zutrifft, wird der Inhalt des aktuellen Tags genommen und als gültiger Ausgabe- wert betrachtet. Voraussetzung ist aber, dass wenigstens an einer Stelle xsl:apply-template steht, um die Ausgabe auszulösen. Sollen Inhalte von Tags gezielt ausgegeben werden, was sicher der häufigste Weg ist, findet xsl:value-of Verwendung. Das Attribut select wählt den Inhalt des durch eine XPath-Anweisung ermittelten Knotens und die gesamte Anweisung gibt diesen als Zeichenkette aus. xsl:template match=B xsl:value-of select=./ /xsl:template Beim Einsatz innerhalb einer Vorlage bezieht sich der Pfad, den select akzeptiert, auf den übergebenen Knoten, ist also relativ. Sie können aber absolute Angaben 556
  • 557.
    Vorbemerkungen verwenden. Der alleinstehende Punkt reflektiert in XPath den aktuellen Knoten, im Beispiel also den Inhalt des Tags B. Auf eben diesem Wege werden auch Attribute gelesen. Das folgende Beispiel sucht nach Elementen vom Typ a und gibt den Inhalt des Attributes href aus: xsl:template match=a xsl:value-of select=@href/ /xsl:template Der direkte Zugriff mit einer absoluten XPath-Anweisung wäre a/@href. Sie kön- nen auf den Parameter eines Attributes auch direkt zugreifen. Ein a href-Tag wird folgendermaßen in img src transformiert: xsl:template match=a img src={@href} / /xsl:template Die Schreibweise mit den geschweiften Klammern ist immer dann angebracht, wenn der Einsatz eines Tags aufgrund der Syntax nicht möglich ist. Andererseits ist es mit xsl:element und xsl:attribute möglich, beliebige Tags indirekt zu erzeugen. Mit XSLT programmieren Bei XSLT spricht man von einer funktionalen Programmiersprache. Zum Pro- grammieren gehören jedoch nicht nur Regeln, wie sie in XSLT durch die Vorla- gen gebildet werden, sondern auch Programmanweisungen. Zuerst soll eine einfache Verzweigung mit xsl:if vorgestellt werden: xsl:if test=@directory='hasfiles' Dieses Verzeichnis enthält Dateien /xsl:if Der Test kann verschiedene Operatoren und Funktionen verwenden, um Knoten nach allerhand Kriterien zu untersuchen. Eine Else-Anweisung gibt es übrigens nicht, hierfür ist die Mehrfachverzweigung gedacht: xsl:choose xsl:when test=attribute='archive' Archiv /xsl:when xsl:when test=attribute='compressed' Compressed 557
  • 558.
    XML und Webservices /xsl:when xsl:when test=attribute='hidden' Hidden /xsl:when xsl:otherwise Unknown /xsl:otherwise /xsl:choose Wollen Sie Listen von bestimmten Tags an einer Stelle ausgeben, ist xsl:for- each sehr praktisch, was ähnlich wie das foreach von PHP funktioniert. for-Schlei- fen im Sinne imperativer Programmierung gibt es jedoch nicht, weil veränderliche Zustände nicht erlaubt sind. xsl:for-each select=name a href={.}xsl:value-of select=.//abr/ /xsl:for-each Die xsl:for-each-Anweisung gibt, wie xsl:template auch, jeweils einen aktu- ellen Knoten für jedes Element aus, das gefunden wurde. Deshalb funktioniert auch hier der verkürzte Zugriff auf den Inhalt mit dem Punkt-Alias. Von Interesse ist oft auch eine Sortiermöglichkeit. Sie können dazu innerhalb einer Schleife mit xsl:sort eine Anweisung platzieren, die das zuverlässig erle- digt: xsl:for-each select=name xsl:sort select=. order=descending/ a href={.}xsl:value-of select=.//abr/ /xsl:for-each Das Element xsl:sort ist übrigens auch in xsl:apply-templates anwendbar. Es versteht freilich einige Attribute mehr, mit denen die Steuerung der Sortierung erfolgen kann. Was Sie in XSLT nicht finden, sind die Anweisungen »for« und »while«. Beide benötigen Variable, deren Zustand sich ändert. Das ist in funktionalen Sprachen nicht möglich, weswegen die Anweisungen nicht sinnvoll sind. Komplexere Schleifen werden durch Rekursion (Selbstaufruf einer Vorlage) und Parameter ermöglicht. Oft ist dies kompakter als vergleichsweise imperative Programme, für PHP-Programmierer aber möglicherweise ungewohnt. Variablen sind dennoch verwendbar, sie verhalten sich aber ähnlich den Konstan- ten in anderen Sprachen. Sie können Werte, Knoten oder Knotenbäume spei- chern: 558
  • 559.
    Vorbemerkungen xsl:variable name=fieldata select=attribute/ Variablekönnen natürlich auch komplexere Inhalte aufnehmen: xsl:variable name=alldata xsl:if test=position()=last() TRUE /xsl:if xsl:if test=position()=1 FALSE /xsl:if /xsl:variable Sie sehen im letzten Beispiel auch die Verwendung von XPath-Funktionen. Vari- ablen gelten nur innerhalb der Vorlage oder der Schleife, in der sie definiert wurden. Dieses Verhalten ist nicht modifizierbar, das heißt, es gibt keine Modifika- toren wie public oder private, wie sie bei den objektorientierten Funktionen von PHP anwendbar sind. Ähnlich wie Variablen werden auch Parameter verwendet. Damit können Sie einer Vorlage verschiedene Werte übergeben, damit diese sich je nach Art des Auf- rufs unterschiedlich verhält. Der Aufruf sieht folgendermaßen aus (am Beispiel einer benannten Vorlage): xsl:call-template name=show.files xsl:with-param name=handlerno/xsl:with-param /xsl:call-template Innerhalb der Vorlage werden die übergebenen Parameter dann so verwendet: xsl:template name=show.files xsl:param name=handler select=/ ... Das select-Attribut in xsl:param bestimmt einen Standardwert, wenn der Para- meter nicht übergeben wurde. XSLT praktisch verwenden XSLT verfügt über einige komplexere Anweisungen, die für größere Projekte von Bedeutung sind. Dazu gehört xsl:number zum Erzeugen fortlaufender Num- mern oder Buchstabenfolgen. Oft ist auch der Umgang mit ganzen Knoten notwendig, statt dem Textinhalt des Knotens. Dann findet xsl:copy-of Verwendung. Sollen Knoten und Attribute 559
  • 560.
    XML und Webservices kopiertwerden, können mehrere Anweisungen mit xsl:copy zusammengefasst werden. Das folgende Beispiel kopiert ein XML-Dokument vollständig in ein anderes: xsl:template match=* xsl:copy xsl:copy-of select=@* xsl:apply-templates/ /xsl:copy /xsl:template Wenn Sie sich die Frage stellen, warum ein Dokument ausgerechnet mit XSLT unverändert kopiert werden sollte, ist ein Blick auf xsl:output angebracht. Mit dieser Anweisung kann die Kodierung des Ausgabestromes gesteuert werden. Das Tag steht immer am Anfang des Dokumentes. Wenn Ihr XML-Dokument UTF-8 kodiert ist, können Sie es mit der Kopiervorlage des letzten Beispiels leicht in ein anderes Format bringen, nebenbei auch ins HTML 4.0-Format: xsl:output encoding=ISO-8859-1 method=html/ Sie können weitere XSLT-Dateien mit Hilfe der Anweisungen xsl:output und xsl:include importieren. Die erste Funktion kann ein Dokument so einfügen, als wäre der Inhalt an dieser Stelle geschrieben worden. Der Import hat eine geringe Priorität. Stehen vorhandene und importierte Regeln miteinander im Kon- flikt, unterliegen die importierten. Die XSLT-Funktionen Transformationen laufen oft in Abhängigkeit von konkreten Daten ab. XSLT stellt deshalb einige elementare Funktionen zur Verfügung, die meist in select- und test- Attributen benutzt werden. Funktion Beschreibung boolean() Typumwandlungen ausführen. Manche Funktionen brau- number() chen einen bestimmten Typ als Parameter. string() format_number() Formatiert Zahlen für die Ausgabe. Tabelle 14.1: XSLT-Funktionen 560
  • 561.
    Vorbemerkungen Funktion Beschreibung ceiling() Zahlenberechnungen und Runden. Die Funktionsweise floor() entspricht den Funktion ceil, floor und round in PHP. round() concat() Verbindet Zeichenketten, ähnlich der Kombination mit dem Punkt-Operator in PHP. contains() Ermittelt, ob eine Zeichenkette bestimmte Zeichen enthält starts-with() bzw. ob sie mit einer bestimmten Zeichenfolge beginnt. normalize_whitespace() Kontrolliert den Umgang mit Leerzeichen. string-length() Ermittelt die Anzahl Zeichen einer Zeichenkette. substring() Ermittelt Teile einer Zeichenkette, entweder nach der Posi- substring-before() tion oder in Abhängigkeit von einem Schlüsselzeichen substring-after() translate() Tauscht Zeichen aus. count() Zählt Knoten und summiert Werte aus Knotenlisten. sum() generate-id() Funktionen zum Umgang mit Knoten, der Erzeugung von local-name() Identifikatoren usw. name() false() Diese Funktionen können Boolesche Werte für Boolesche true() Ausdrücke beschaffen. Dies ist erforderlich, weil innerhalb not() von Ausdrücken kein Zugriff auf Schlüsselwörter (wie true) besteht. current() Bestimmt die Position des Zeigers in Knotenlisten . last() position() document() Realisiert den Dateizugriff, lädt ein anderes Modul. key() Einen Knoten mit bestimmten Eigenschaften finden. id() element-available() Eine Parserfunktion suchen und mitteilen, ob der Parser diese unterstützt. Tabelle 14.1: XSLT-Funktionen (Forts.) 561
  • 562.
    XML und Webservices DieFunktionen finden auch bei der Knotenselektion mit XPath Verwendung. Mehr dazu im nächsten Abschnitt. XPath XPath ist eine Abfragesprache, die direkt oder über XSLT zum Einsatz kommt, um Knoten oder Knotenlisten zu ermitteln. Daten mit XPath suchen Mit Hilfe einer Transformation wurde im letzten Abschnitt bereits eine Auswahl aus XML-Daten vorgenommen. Immer dann, wenn auf Knoten oder Inhalte mit match= oder select= zugegriffen wurde, kam bereits XPath zum Einsatz. Diese einfache Form reicht in der Praxis nur selten aus. Der vollständige Name für XPath lautet XML Path Language 1.0. Um in der hierarchischen Struktur eines XML-Dokuments gezielt Knoten adres- sieren zu können, wird XPath eingesetzt. Ohne eine solche Sprache würden Doku- mente immer sequenziell durchlaufen werden, was wenig praxistauglich ist. Gerade bei Webanwendungen, die oft vielen Benutzern einen Ausschnitt aus einer großen Datenmenge zur Verfügung stellen, ist die schnelle Auswahl eminent wichtig. Die Abfrage durch Beschreibung eines Pfades und verzichtet dabei auf Schleifen oder andere zyklische Elemente. Damit ist die Konstruktion zur Laufzeit und in Abhängigkeit vom aktuellen Auftreten von Knoten möglich. Wie der Name der Sprache andeutet, ähnelt die Auswahl von Knoten den Pfadangaben im Datei- system eines Betriebssystems. Das ist nahe liegend, weil auch dort Daten hierar- chisch angeordnet sind. Eine typische XPath-Anweisung könnte also folgenderma- ßen aussehen: eintrag/name/vorname Sie adressiert einen Knoten vorname, der Kind von name ist, was wiederum Kind von eintrag sein muss: eintrag name vorname Es gibt verschiedene Knotentypen in XML. XPath muss diese adressieren können. Konkret unterschieden werden die in der folgenden Tabelle dargestellten Typen: 562
  • 563.
    Vorbemerkungen XPath-Knoten Darstellung Kurzform Wurzelknoten / Elementknoten ElementName (keine Kurzform) Kindknoten child:: (kann entfallen) Attributknoten attribute::AttributName @AttributName Textknoten self::node() . Elternknoten parent::node() .. Prozessinformation Nicht darstellbar Namensraum alias: Tabelle 14.2: Knotentypen, die mit XPath adressiert werden können Grundlagen für die Entwicklung von Ausdrücken XPath basiert auf Ausdrücken, die den Weg zu einem Knoten beschreiben. Der Weg kann – ebenso wie beim Dateisystem – durch absolute oder relative Pfadanga- ben beschrieben werden. Absolute Angaben beginnen immer an der Dokumenten- wurzel. Wenn der Ausdruck einen Pfad über mehrere Knoten hinweg beschreibt, werden die Elemente durch Schrägstriche getrennt: dirlist/directory/file Jedes dieser Elemente wird allgemein als Lokalisierungsschritt bezeichnet. Die eben gezeigte und häufig verwendete Darstellung durch einen Knotennamen ist eine verkürzte Form. Tatsächlich kann jeder Schritt aus drei Teilen bestehen: 1. Achsenbezeichner 2. Knotentest 3. Prädikate Der Achsenbezeichner modifiziert die Auswahl des Knotens auf der Grundlage seiner Position im Baum. Als Trennzeichen zwischen dem Achsenbezeichner und dem nächsten Teil des Ausdrucks werden zwei Doppelpunkte geschrieben ::. Dies wird im nächsten Abschnitt noch weiter erläutert. 563
  • 564.
    XML und Webservices DerKnotentest beschreibt den Knoten selbst, beispielsweise eine direkte Auswahl durch Nennung des Tagnamens. Die Prädikate stehen in eckigen Klammern und werden meist zur Auswahl von Attributen verwendet. Insgesamt ergibt sich bei- spielsweise folgender Ausdruck: child::directory[attribute::hasfiles='true'] child:: ist der Achsenbezeichner, hier wird also beginnend von der aktuellen Posi- tion das nächste Kindelement gesucht. Dann wird das Element selbst benannt: directory. Es wird also das nächste Kindelement mit dem Namen directory gesucht. Das Prädikat schränkt die Suche weiter ein; hier auf das Vorhandensein eines Attributes hasfile mit dem Parameter 'true'3. Um solche Ausdrücke nun entwickeln zu können, ist in erster Linie eine Kenntnis der Achsenbezeichner notwendig. Die XPath-Achsenbezeichner Achsenbezeichner können einen oder mehrere Knoten auswählen. Die konkrete Auswahl hängt vom aktuellen Knoten ab. Wenn ein Dokument sequenziell durch- laufen wird, können die Bezeichner mehrere Knoten selektieren. Die folgende Tabelle zeigt alle Achsenbezeichner für Elemente auf einen Blick. Elemente sind hier Tag-Namen, also keine Attribute und keine Namensraumbe- zeichnungen: Achsenname Suchrichtung Beschreibung self – Der aktuelle Knoten. child vor Die Kinder des Knotens. parent Die Eltern des Knotens. descendant vor Alle Nachfahren (Kinder und Kindeskinder) . descendant-or-self vor Alle Nachfahren und der Knoten selbst. ancestor rück Alle Vorfahren (Eltern und deren Eltern). ancestor-or-self rück Alle Vorfahren und der Knoten selbst. 3 Die einfachen Anführungszeichen innerhalb der doppelten gehören bei XPath dazu. 564
  • 565.
    Vorbemerkungen Achsenname Suchrichtung Beschreibung following vor Alle folgenden Knoten im Dokument, die nicht direkte Nachfahren sind. following-sibling vor Alle folgenden Geschwister. preceding rück Alle vorhergehenden Knoten, die nicht Eltern sind. preceding-sibling rück Alle vorhergehenden Geschwister. Tabelle 14.3: Achsenbezeichner für Element-Knoten Neben den Achsenbezeichnern für Elemente gibt es noch zwei spezielle: attribute zur Auswahl von Attributen und namespace zur Lokalisierung von Namensräu- men. Es bietet sich an dieser Stelle an, die Wirkung der Achsenbezeichner mit einem Testprogramm zu lernen. Damit alle erdenklichen Kombinationen auch getestet werden können, wird eine XML-Datei entworfen, die entsprechende Ach- sen auch aufweist: Listing 14.2: axischeck.xml – Testdatei zum Testen von Achsenzugriffen ?xml version=1.0 encoding=iso-8859-1 ? Start Ebene1_1/Ebene1_1 Ebene1_2 Ebene1_2_1/Ebene1_2_1 Ebene1_2_2 Ebene1_2_2_1/Ebene1_2_2_1 Ebene1_2_2_2/Ebene1_2_2_2 /Ebene1_2_2 Ebene1_2_3/Ebene1_2_3 Ebene1_2_4/Ebene1_2_3 /Ebene1_2 Ebene1_3/Ebene1_3 Ebene1_4 Ebene1_4_1/Ebene1_4_1 /Ebene1_4 /Start Diese Datei dient im folgenden Beispiel als Basis für die ersten Programmierversu- che mit PHP und XPath. Sie ist so aufgebaut, dass aus den Elementnamen die 565
  • 566.
    XML und Webservices Positionim Baum ablesbar ist, was für das Verständnis der XPath-Abfragen sinnvoll ist. Mit PHP5 XPath-Ausdrücke verarbeiten Lesen Sie zunächst das folgende Listing: Listing 14.3: xpathcheck.php – XPath-Achsenbezeichner ausprobieren ?php class XPathCheck { private $nodes; private $xp; private $expressions = array('self', 'child', 'parent', 'descendant', 'descendant-or-self', 'ancestor', 'ancestor-or-self', 'preceding', 'preceding-sibling', 'following', 'following-sibling'); private function PrintNodeList() { foreach ($this-nodes as $node) { printf('lt;%sgt;', ucfirst($node-tagName)); } } public function __construct($path) { $dom = new DomDocument(); $dom-load($path); $this-xp = new DomXPath($dom); } public function ShowNodeList() 566
  • 567.
    Vorbemerkungen { echo 'table width=400 border=1'; echo 'captionElement-Beziehungen, ausgehend von Ebene1_2/caption'; foreach ($this-expressions as $expression) { $this-nodes = $this-xp-query(//Ebene1_2/$expression::*); echo trtd$expression/td; echo td; $this-PrintNodeList(); echo /td/tr; } echo '/table'; } } $x = new XPathCheck('data/axischeck.xml'); $x-ShowNodeList(); ? Führt man diese Schritte für alle XPath-Standardanweisungen aus, bezogen auf den Knoten Ebene1_2 als Startknoten, ergibt sich folgende Abbildung: Abbildung 14.1: Ausgabe der Kno- tenlisten für alle typischen Achsen- bezeichner Der Ausdruck selektiert lediglich die Achse (achsenbezeichner::*), wobei das * zur Auswahl aller Elemente der Achse führt. In der Praxis könnten sich hier Ele- mentnamen anschließen, um die Auswahl einzuschränken, sowie eines oder mehrere Attribute, die auftreten oder bestimmte Parameter aufweisen müssen. 567
  • 568.
    XML und Webservices Einkonkretes XML-Format: RSS RSS dient dem Austauschen von Nachrichten und Inhalten über Nachrichten- seiten. Auch große Nachrichtendienste wie Wired oder Slashdot bieten Informa- tionen zur Übernahme auf anderen Seiten an. Eine private Seite kann dies deutlich aufwerten. Das Prinzip ist dabei einfach. Der RSS-Feed (ein XML-Frag- ment) wird von der Spenderseite abgeholt. Enthalten ist der Nachrichtentext in einer Art Zusammenfassung und ein Link zurück zum Spender, der zum Volltext führt. Davon profitieren beide Seiten. Der Spender erhält Zugriffe durch Nutzer fremder Seiten und der Empfänger hat ständig aktuelle Inhalte zu bieten, ohne dafür irgendwelchen Aufwand treiben zu müssen. RSS kann bis zu einzelnen Nachrichten und Stichwörtern herunter gebrochen werden, nur die letzten Änderungen oder Aktualisierungen enthalten oder auch zum Verfolgen von Einträgen in einer Änderungsliste dienen. Der Einsatzzweck ist keineswegs auf Nachrichten beschränkt, die aus Sport oder Politik stammen, sondern kann ebenso die Informationen einer Serverüberwachung umfassen. RSS-fähige Programme werden als Aggregatoren bezeichnet, das sind quasi Daten- sammler der Weblog-Gemeinschaft. Weblogs wiederum zeichnen sich selbst oft durch die Fähigkeit aus, Inhalte im RSS-Format bereitstellen zu können. Auf diese Art und Weise werden Weblogs gleichzeitig zu Spendern und Empfängern – ein wilder Kreislauf aus Nachrichtenaustauschprogrammen entsteht. Rückblick RSS umfasst allerdings weit mehr als ein einfaches XML-Format zum Nachrich- tenaustausch. Ein Blick zurück ist hilfreich, um die Zusammenhänge zu verstehen und die richtige Strategie zur Programmierung eigener RSS-Programme zu fin- den. Die erste Version war RSS 0.90, entworfen von Netscape als Format zum Aufbau von Portalen, die sich die wichtigsten aktuellen Nachrichten von großen Nach- richtenanbietern holen. Diese Version war äußerst komplex, was einer schnellen Verbreitung eher abträglich ist. Daraus entstand in kurzer Zeit RSS 0.91, eine ver- einfachte Ausgabe. Netscape verlor schnell das Interesse am Betrieb von Portalen und die Fa. UserLand Software übernahm die weitere Entwicklung als Basis ihrer kommerziellen Weblog-Produkte. In der Zwischenzeit spaltete sich eine weitere, nicht kommerzielle Gruppe ab und entwarf ein weiteres Format, basierend auf dem alten 0.90, bevor es die Vereinfachungen der 0.91 erfuhr. Dieses Format, sei- 568
  • 569.
    Vorbemerkungen nerseits mit demebenso sehr komplexen RDF verwandt, wurde als RSS 1.0 bezeichnet. UserLand war in diesen Prozess nicht involviert worden und torpediert seitdem die Entwicklung mit einer Reihe eigener Versionen, die aus der 0.91- Reihe fortgeführt wurden. Auf diesem Wege entstanden RSS 0.92, RSS 0.93 und RSS 0.94. Weil die 1.0 bereits vergeben war, heißt die finale UserLand-Version RSS 2.0. Womit das Chaos perfekt ist. Welche Version man verwenden sollte Es gibt mittlerweile sieben Versionen. Normalerweise macht eine derartige Versi- onspolitik ein Produkt nachhaltig kaputt. RSS hat sich dennoch rasend verbreitet, vermutlich aufgrund des anhaltenden Bedarfs am automatischen Informationsaus- tausch. Die folgende Tabelle hilft, die richtige Version zu wählen: Version Ursprung Hinweis Status Empfehlung 0.90 Netscape Obsolet Nicht mehr verwenden. wegen 1.0 0.91 UserLand Besonders Offiziell obso- Leicht zu verwenden für einfa- einfach let wegen 2.0, che Ansprüche, interessant, aber noch sehr wenn spätere Migration auf 2.0 populär geplant ist. 0.92, UserLand Umfassender Obsolet Nicht mehr verwenden, besser 0.93, als 0.91 wegen 2.0 auf 2.0 setzen. 0.94 1.0 RSS- RDF-basiert, Stabil, aktuell Wird verwendet, wenn auch DEV erweiterbar, mit RDF-Quellen gearbeitet Working offener wird. Group Standard 2.0 UserLand 0.9X Stabil, aktuell Generell empfehlenswert. kompatibel, erweiterbar Tabelle 14.4: Übersicht über die RSS-Versionen 569
  • 570.
    XML und Webservices WieRSS praktisch aussieht Für den Einsatz von RSS auf einer mit PHP programmierten Webseite braucht man zuerst ein Programm, das RSS-Feeds anderer Seiten liest. Eigene Angebote setzen immerhin auch eigene Nachrichten voraus, und die sind ungleich schwerer zu produzieren. Das Lesen der Datenquelle ist einfach – sie ist unter einer spezifi- schen URL zu erreichen. Auf Webseiten kann man diese meist hinter dem Bild- chen zu finden. Das folgende Beispiel stammt von xml.com und ist in RSS 0.91 erstellt: Listing 14.4: Ein RSS 0.91-Feed, gefunden auf xml.com rss version=0.91 channel titleXML.com/title linkhttp://www.xml.com//link descriptionXML.com features a rich mix of information and services for the XML community./description languageen-us/language item titleNormalizing XML, Part 2/title link http://www.xml.com/pub/a/2002/12/04/normalizing.html /link descriptionIn this second and final look at applying relational normalization techniques to W3C XML Schema data modeling, Will Provost discusses when not to normalize, the scope of uniqueness and the fourth and fifth normal forms./description /item item titleThe .NET Schema Object Model/title link http://www.xml.com/pub/a/2002/12/04/som.html /link descriptionPriya Lakshminarayanan describes in detail the use of the .NET Schema Object Model for programmatic manipulation of W3C XML Schemas./description /item item titleSVG's Past and Promising Future/title link 570
  • 571.
    Vorbemerkungen http://www.xml.com/pub/a/2002/12/04/svg.html /link descriptionIn this month's SVG column, Antoine Quint looks back at SVG's journey through 2002 and looks forward to 2003./description /item /channel /rss Die Datenquelle präsentiert einen Kanal (Thema), der einen Titel, den Link zurück zur Quelle und eine Beschreibung enthält. Optional kann eine Sprache angegeben werden, gefolgt von einer Reihe konkreter Einträge, die jeweils wieder einen Titel, den Rücklink und eine Beschreibung enthalten. In RSS 1.0 sieht exakt derselbe Inhalt etwas anders aus: Listing 14.5: RSS1.0-Feed, identisch mit dem Inhalt aus Listing 14.4 rdf:RDF xmlns:rdf=http://www.w3.org/1999/02/22-rdf-syntax-ns# xmlns=http://purl.org/rss/1.0/ xmlns:dc=http://purl.org/dc/elements/1.1/ channel rdf:about=http://www.xml.com/cs/xml/query/q/19 titleXML.com/title linkhttp://www.xml.com//link descriptionXML.com features a rich mix of information and services for the XML community./description languageen-us/language items rdf:Seq rdf:li rdf:resource = http://www.xml.com/pub/a/2002/12/04/normalizing.html/ rdf:li rdf:resource = http://www.xml.com/pub/a/2002/12/04/som.html/ rdf:li rdf:resource = http://www.xml.com/pub/a/2002/12/04/svg.html/ /rdf:Seq /items /channel item rdf:about = http://www.xml.com/pub/a/2002/12/04/ normalizing.html titleNormalizing XML, Part 2/title link http://www.xml.com/pub/a/2002/12/04/normalizing.html 571
  • 572.
    XML und Webservices /link descriptionIn this second and final look at applying relational normalization techniques to W3C XML Schema data modeling, Will Provost discusses when not to normalize, the scope of uniqueness and the fourth and fifth normal forms./description dc:creatorWill Provost/dc:creator dc:date2002-12-04/dc:date /item item rdf:about = http://www.xml.com/pub/a/2002/12/04/som.html titleThe .NET Schema Object Model/title linkhttp://www.xml.com/pub/a/2002/12/04/som.html/link descriptionPriya Lakshminarayanan describes in detail the use of the .NET Schema Object Model for programmatic manipulation of W3C XML Schemas./description dc:creatorPriya Lakshminarayanan/dc:creator dc:date2002-12-04/dc:date /item item rdf:about = http://www.xml.com/pub/a/2002/12/04/svg.html titleSVG's Past and Promising Future/title linkhttp://www.xml.com/pub/a/2002/12/04/svg.html/link descriptionIn this month's SVG column, Antoine Quint looks back at SVG's journey through 2002 and looks forward to 2003./description dc:creatorAntoine Quint/dc:creator dc:date2002-12-04/dc:date /item /rdf:RDF Die Informationen sind hier ein wenig ausführlicher. So gehören auch der Name des Autors und ein Datum dazu. Das Datum ist sehr sinnvoll, wenn man aus einer großen Anzahl von Nachrichten nur die aktuellsten anzeigen möchte. Die Struk- tur ist dennoch weitgehend der früherer RSS-Versionen angepasst. Wer kompa- tible Software schreiben möchte, die mit Datenquellen beider Arten umgehen kann (das ist heute leider notwendig, weil beide Versionen gleichermaßen benutzt werden), kann sich mit folgenden »Umrechnungsregeln« zwischen 1.0 und 0.91 behelfen (aus der Sicht von 1.0): 1. Das Stammelement ist rdf:RDF statt rss. Beim Lesen mit PHP ist es nicht zwin- gend erforderlich, dies zu prüfen – man kann der Quelle durchaus vertrauen. 2. RSS 1.0 verwendet den Namensraum http://purl.org/rss/1.0/ als Standard- namensraum. Die RDF-spezifischen Elemente sind durch den eigenen RDF- 572
  • 573.
    Vorbemerkungen Namensraum http://www.w3.org/1999/02/22-rdf-syntax-ns# abgetrennt. Die Spezifika von RDF kann man aber getrost ignorieren für einen einfachen Nachrichtenleser. Metadaten (Autor, Datum) befinden sich im Namensraum des so genannten Dublin Core4 http://purl.org/dc/elements/1.1/. Wenn Sie nun mit einem Parser arbeiten, der Namensräume nur beschränkt verarbeiten kann – wie SimpleXML in PHP5 – dann ignorieren Sie das ein- fach. Innerhalb eines vollständigen RDF-basierten RSS 1.0-Feed dürften Ele- mentnamenskonflikte kaum auftreten. Der blanke Leseprozess ist also unkritisch. Erzeugen lassen sich RSS-Quellen damit freilich nicht. Sie müssen lediglich vertrauen, dass bei der Definition der Quelle die Standardaliase nicht verändert wurden. Ein Parser, der Namensräume nicht kennt, wird den Ele- mentnamen als dc:creator lesen, nicht als creator. Solange Sie von einem fixen Alias dc ausgehen, ist das in Ordnung. Tauscht das jemand gegen dublc aus, versagt das Skript. Programme mit vollständiger Namensraumunterstützung würden eine solche Änderung tolerieren. Letztere können auch erkennen, wel- che Version benutzt wurde und ihre Strategie beim Verarbeiten anpassen. 3. Ein bedeutender Unterschied ist die Tatsache, dass das item-Element außer- halb des channel-Elements steht, bei RSS 0.91 war es genau umgekehrt. Zur Verarbeitung beider Versionen muss man damit zwangsläufig Teile des Codes doppelt schreiben. Nebenbei bemerkt: RSS 0.90 (außerhalb) und RSS 2.0 (innerhalb) zeugen von leichten Designzweifeln bei den Erfindern. 4. Das zusätzliche item-Element innerhalb des channel-Elements kann igno- riert werden. Es bedient lediglich native RDF-Parser. RSS 2.0 wurde bereits mehrfach angesprochen. Auch hierfür ist ein Blick auf den- selben Inhalt im RSS 2.0-Format empfehlenswert: rss version=2.0 xmlns:dc=http://purl.org/dc/elements/1.1/ channel titleXML.com/title linkhttp://www.xml.com//link descriptionXML.com features a rich mix of information and services for the XML community./description languageen-us/language item titleNormalizing XML, Part 2/title 4 Der Name geht zurück auf die Dublin Core Metadata Initiative, eine Organisation, die Beschreibungsfor- mate für Metadaten von Dokumenten entwirft (siehe http://dublincore.org). 573
  • 574.
    XML und Webservices link http://www.xml.com/pub/a/2002/12/04/normalizing.html /link descriptionIn this second and final look at applying relational normalization techniques to W3C XML Schema data modeling, Will Provost discusses when not to normalize, the scope of uniqueness and the fourth and fifth normal forms./description dc:creatorWill Provost/dc:creator dc:date2002-12-04/dc:date /item item titleThe .NET Schema Object Model/title link http://www.xml.com/pub/a/2002/12/04/som.html /link descriptionPriya Lakshminarayanan describes in detail the use of the .NET Schema Object Model for programmatic manipulation of W3C XML Schemas./description dc:creatorPriya Lakshminarayanan/dc:creator dc:date2002-12-04/dc:date /item item titleSVG's Past and Promising Future/title link http://www.xml.com/pub/a/2002/12/04/svg.html /link descriptionIn this month's SVG column, Antoine Quint looks back at SVG's journey through 2002 and looks forward to 2003. /description dc:creatorAntoine Quint/dc:creator dc:date2002-12-04/dc:date /item /channel /rss RSS 2.0 verwendet Namenräume analog zu RSS 1.0, aber es wird kein RDF verwen- det. Ebenso wie bei RDF 0.91 gibt es keinen Standardnamensraum und die Inhalt sind innerhalb des channel-Elements. Code, der flexibel mit RSS 0.91 und 1.0 umgehen kann, wird mit nur geringem Aufwand auch RSS 2.0 lesen können. 574
  • 575.
    Einführung in dielibxml2 14.2 Einführung in die libxml2 Generell ist XML ein Thema für Website-Entwickler, denn es gibt vielfältige Anwendungen: í Webservices (basieren weitgehend auf XML) í Datenim- und -export aus und zu unbekannten oder inkompatiblen Daten- quellen í Aufbereitung von XHTML für verschiedene Medien í Bereitstellung oder Konsumierung von RSS-Kanälen In PHP 4 war nur der SAX-Parser Expat fester Teil des Pakets. Alle anderen Erwei- terungen waren entweder nicht immer vorhanden oder noch im Entwicklungssta- dium (und sind es bis heute). Applikationen, die XML nutzen, waren damit etwas eingeschränkt, denn nicht alle Provider und Webhoster boten die nötige Unterstüt- zung. PHP DOM wurde zwar heiß diskutiert und oft als Wunderwaffe der XML- Entwickler gepriesen, dümpelte aber mangels Provider-Unterstützung eher vor sich hin. Wichtige Applikationen wie das Templatesystem phpTemple (http:// www.phptemple.de) sind jedoch auf XML angewiesen, um hohe Performance auch bei anspruchsvollen Seiten zu ermöglichen. Neu in PHP5 Anstatt des Sammelsuriums von teilweise unfertigen und inkompatiblen Biblio- theken (XSLT von Ginger Alliance, Expat, XML DOM) wurde nun auf das sehr weit entwickelten Projekt Gnome XML Parser libxml gesetzt. Die Vorgängerver- sion war bereits die Basis für die XML DOM-Erweiterungen. Vielleicht haben Sie bereits mit den DOM-Erweiterungen gearbeitet und herausgefunden, dass diese von den drei Bibliotheken der schwächste Teil waren. Dies als die herausragende XML-Neuerung in PHP5 anzupreisen erscheint nicht einsichtig. Allerdings lag die schlechte Qualität nicht an der verwendeten Bibliothek, sondern an den Mängeln der Integration in PHP. Dieses Problem wurde durch den Wechsel der Entwickler und Verbesserungen in der Struktur der Programmierung beseitigt. Die Gnome-Bibliothek liefert einen der schnellsten Parser, die es gibt. Sie ist rela- tiv jung und die Entwickler konnten von den Fehlern der anderen Parser lernen. Und nicht zuletzt werden viele in der XML-Welt dringend benötigte Standards unterstützt: 575
  • 576.
    XML und Webservices í SAX- und DOM-Programmierschnittstellen í Validierung mit DTDs und XML Schema í XSLT, XPath, XPointer und Xinclude í Integrierte Parser für HTML und Docbook-XML í XMLSec (XML Security Library) Insgesamt gewinnt PHP dadurch deutlich und es lohnt sich unbedingt, sich mit der Bibliothek und den neuen Möglichkeiten intensiver auseinander zu setzen. Für viele Projekte gibt es dazu auch keine Alternative, weil die weitere Unterstüt- zung der alten Bibliotheken nicht geplant ist. Andererseits bleiben die Funktions- schnittstellen erhalten, um eine Abwärtskompatibilität zu gewährleisten. Es ist also nicht zwingend erforderlich, alle XML-basierten Skripte nun umzuschreiben. Bis auf wenige Ausnahmen sollten alle Programme unverändert laufen. Es lohnt sich dennoch, den vorhandenen Code zu untersuchen, weil sich eventuell bessere oder schnellere Möglichkeiten bieten und der eine oder andere »Work-Around« oder »Dirty-Hack« sich nun sauber programmieren lässt. Die neuen DOM-Funktionen Die Kernfunktionen der DOM-Erweiterung sind inzwischen weitgehend stabil aus Sicht der Entwicklung. Das ist sehr wichtig, weil ständige Änderungen an den Bib- liotheken den Einsatz in ernsthaften Projekten arg gefährden. Hier hatte PHP bis- lang das Nachsehen gegenüber .NET und Java, wo es eine sehr solide und lange Zeit stabile Basisplattform gab und gibt. HTML verarbeiten Webseiten bestehen aus HTML und HTML ist verwandt mit XML. Es sollte also möglich sein, HTML mit einem XML-Parser zu verarbeiten. Weit gefehlt, denn tatsächlich stammt HTML von der alten Auszeichnungssprache SGML ab, aus der auch XML entwickelt wurde. XML definiert eine strengere Syntax und erlaubt damit schnellere und stabilere Parser. Prinzipiell kann also ein XML-Parser einfa- che HTML-Seiten nicht verarbeitet, bevor diese nicht »XML-tauglich« gemacht werden. Auch dazu gibt es einen entsprechenden Standard: XHTML. In der Praxis ist dies jedoch nicht immer einfach, weil man dazu spezielle Programme braucht, die die Umwandlung vornehmen – oder PHP5. 576
  • 577.
    Einführung in dielibxml2 Das folgende Beispiel zeigt ein typisches, aus XML-Sicht nicht »wohlgeformtes« XML-Dokument: Listing 14.6: XmlDomBadHtml.html – Ein einfaches HTML-Dokument, nicht wohlge- formt !doctype HTML PUBLIC -//W3C//DTD HTML 4.0 Transitional//EN html head titleXML/title /head body h2HTML mit DOM verarbeiten/h2 p Diese Seite enthält nach XML-Regeln nicht wohlgeformte Tags. br form select optionRed option selectedBlue optionGreen /select /form /body /html Dem neuen DOM-Parser ist dies ziemlich egal, er liest diese HTML-Seite pro- blemlos ein. Das folgende Skript verarbeitet die Seite und agiert dabei mit Knoten, Knotenauflistungen, Elementen und Attributen: Listing 14.7: XmlDomHtml.php – Laden und Verarbeiten von nicht wohlgeformtem HTML ?php $doc = new DomDocument(); $doc-loadHtmlFile('data/XmlDomBadHtml.html'); $head = $doc-getElementsByTagName('head'); $body = $doc-getElementsByTagName('body'); function getTitle ($head) { foreach ( $head as $header ) { if ( $header-tagName == 'title' ) 577
  • 578.
    XML und Webservices echo (bPage Title:/b .$header-textContent. br /n); } } function parseBody($body) { $bodyTag = $body-item(0); foreach ($bodyTag-childNodes as $element) { $content = htmlspecialchars( utf8_decode($element-textContent)); switch ($element-tagName) { case 'h2': echo bHeader 2:/b $contentbr /n; break; case 'p': echo bParagraph:/b $contentbr /n; break; case 'form': foreach ($element-childNodes as $input) { if ($input-nodeType != XML_ELEMENT_NODE) continue; if ($input-tagName == 'select') parseSelect($input); } break; default: echo $content-tagName.'br'; break; } } } function parseSelect($select) { echo bSelect:/bnuln; $options = $select-childNodes; foreach ($options as $option) { $content = htmlspecialchars( 578
  • 579.
    Einführung in dielibxml2 utf8_decode($option-textContent)); echo li{$content}; if ($option-hasAttribute('selected')) { echo ' SELECTED'; } echo /lin; } echo /uln; } getTitle($head); parseBody($body); ? Die Nutzung der DOM-Klasse beginnt immer mit der Instanziierung des Objekts: $doc = new DomDocument(); Dann müssen die zu verarbeitenden Daten beschafft werden. In diesem Beispiel wird mit HTML gearbeitet, also wird die entsprechende Methode benutzt: $doc-loadHtmlFile('data/XmlDomBadHtml.html'); Dann werden die Elemente head und body als Knotenlisten ermittelt: $head = $doc-getElementsByTagName('head'); $body = $doc-getElementsByTagName('body'); Es schließen sich drei Funktionen an, die Teile der Datei untersuchen. Beispiel- haft soll die Analyse des body-Tags näher vorgestellt werden. Da getElementsBy- TagName eine Knotenauflistung zurückgibt, im konkreten Fall aber bekannt ist, dass es nur ein Element geben kann, wird dieses gezielt ermittelt: $bodyTag = $body-item(0); Stattdessen könnte die Auflistung auch mit foreach durchlaufen werden. Das Ele- ment selbst (der Typ wäre hier DomElement) wird nun seinerseits mit foreach durchlaufen, um alle Elemente der obersten Ebene zu erreichen: foreach ($bodyTag-childNodes as $element) Will man alle Elemente erreichen, muss die Funktion rekursiv programmiert wer- den. Zu jedem Element wird nun der Inhalt ermittelt. Diese Funktion ist zwar nicht dem DOM-Standard entsprechend, ist aber eine recht brauchbare PHP-Erweite- 579
  • 580.
    XML und Webservices rung.Zusätzlich ist noch darauf zu achten, dass das Dokument gegebenenfalls UTF-8-kodiert ist. UTF-8 ist das Standardzeichenformat für XML. Für die Aus- gabe im Beispiel wird außerdem noch HTML-Code sichtbar gemacht: $content = htmlspecialchars(utf8_decode($element-textContent)); Die weitere Verarbeitung richtet sich nach dem gefundenen Elementnamen: switch ($element-tagName) Wenn man weitere Untersuchungen anstellt ist es wichtig zu wissen, welchen Typ ein Knoten hat. Denn die von der Knotenliste extrahierten Knoten sind nicht immer Elemente, sondern können auch Kommentare, Zeichen, Namensräume, Prozessanweisungen usw. sein: if ($input-nodeType != XML_ELEMENT_NODE) continue; Informationen über die verwendbaren Konstanten finden Sie in der Kurzreferenz am Ende des Kapitels. In der Funktion parseSelect ist noch eine weitere Methode zu finden, die hier dazu dient, die Existenz eines Attributes zu ermitteln: if ($option-hasAttribute('selected')) Die Abbildung zeigt, wie das Skript arbeitet: Abbildung 14.2: Verarbeitung einer HTML-Seite mit dem DOM- Modul Das DOM-Modul ist äußerst leistungsfähig und konnte hier nur oberflächlich angerissen werden. Für größere XML-Projekte ist es unbedingt zu empfehlen und die hier gezeigten Techniken lassen sich in jeder Richtung ausbauen. Informieren Sie sich in der Kurzreferenz über die verfügbaren Methoden und Eigenschaften, um einen schnellen Einstieg zu finden. 580
  • 581.
    SimpleXML 14.3 SimpleXML Neben derDOM-Erweiterung, die auf libxml basiert, fand eine zweite XML- Schnittstelle Eingang in PHP5: SimpleXML. Diese Bibliothek ist bekannt als »Object Mapping XML API«. Unterschiede zur DOM-Schnittstelle DOM (Document Object Model) erzeugt aus einem XML-Dokument einen voll- ständigen Baum von Objekten. Der Zugriff in einer Applikation ist sehr direkt möglich, dafür ist der Speicherverbrauch erheblich, weil immer die gesamte Struk- tur im Speicher steht. Ein vollständiges Abbild eines Dokuments als DOM ist nicht trivial – auch in der Verarbeitung. SimpleXML nutzt ebenso eine objektori- entierte Sicht, vereinfacht diese jedoch drastisch. Statt der »offiziellen« DOM-API, die alle Programmiersprachen mit DOM-Unterstützung anbieten, überführt Simp- leXML die Daten direkt in eine einfache, an PHP angepasste Objektstruktur. Als Ausgangspunkt für erste Versuche soll folgendes Dokument dienen: Listing 14.8: data/dbconfig.xml – Eine einfache XML-Datei zum Testen ?xml version=1.0? config emailjoerg@krause.net/email database hostlocalhost/host typeMySQL/type userdbuser/user passgeheim/pass dbnamesimpletest/dbname /database /config Ein Parser, der dies verarbeitet, könnten nun folgendermaßen aussehen: Listing 14.9: SimpleXML1.php – Einfachste Verarbeitung von XML mit SimpleXML ?php $xml = file_get_contents('data/dbconfig.xml'); $config = simplexml_load_string($xml); 581
  • 582.
    XML und Webservices echo('pre'); print_r($config); echo ('/pre'); ? Die print_r-Funktion zeigt die vollständige Objektstruktur an, wie sie von Simple- XML erkannt wurde: Abbildung 14.3: Objektstruktur eines XML-Dokuments Das folgende Skript nutzt diese Struktur, um auf eine MySQL-Datenbank zuzu- greifen. Dazu werden die Daten aus der XML-Datei zur Konfiguration benutzt: Listing 14.10: SimpleXMLConfig.php – Nutzung von XML zur Konfiguration ?php $xml = file_get_contents('data/dbconfig.xml'); $config = simplexml_load_string($xml); $db = $config-database; try { if (!@mysql_connect($db-host,$db-user,$db-pass)) { throw new Exception('Database Connect Error'.mysql_error()); } } catch (Exception $e) { echo 'Error: ' . $e-GetMessage(); echo 'br/'; echo 'Sende E-Mail an: ' . $config-email; } ? 582
  • 583.
    SimpleXML Wie der Nameverspricht, ist SimpleXML tatsächlich sehr einfach. Parsen und Nutzen ist praktisch unmittelbar miteinander verbunden und eignet sich hervorra- gend für kleinere Sequenzen, beispielsweise Konfigurationsdateien. Ein Schritt weiter XML ist großartig und äußerst vielseitig einsetzbar. Der erste Abschnitt zeigte bereits, dass es mit den neuen XML-Funktionen in PHP5 nicht schwer ist, damit umzugehen. PHP 4 verlangte hier einiges mehr an Programmierung stellte damit eine zu große Hürde dar, um auch einfache Problemstellungen mit XML zu lösen. Prinzipiell gibt es drei Wege, um auf XML zuzugreifen. Die meisten Program- mierumgebungen nutzen alle drei, SAX, DOM und XSLT: í SAX (Simple API for XML) bietet eine ereignisbasierte Verarbeitung und ver- langt, dass die Elemente manuell gelesen werden, indem das aktuell verarbei- tete auf einem Stapelspeicher abgelegt und von da wieder entnommen wird. í DOM (Document Object Model) ist sehr flexibel und verständlich, verlangt jedoch reichlich Ressourcen. Außerdem ist der Aufwand bei sehr trivialen Auf- gabenstellungen recht hoch. í XSLT (XSL Transformation) ist eine funktionale Programmiersprache zum Transformieren von XML in anderes XML oder in Text oder HTML. Es ist sehr mächtig, aber auch sehr gewöhnungsbedürftig. SimpleXML ist eine neue und allein stehende Erweiterung in PHP5 und dient dazu, den Zugriff auf XML-Dokumente zu erledigen. Dabei geht es in erster Linie darum, die Daten in eine interne Datenstruktur zu übertragen, die sich besser wei- terverarbeiten lässt. Dazu gehören beispielsweise Arrays oder Datenbanken. XML dient also keineswegs dazu, intern nur noch damit zu arbeiten, sondern soll vor allem den vollkommen plattformneutralen Datenaustausch gewährleisten. Neben- bei kann man seine Daten nun auch in einem der vielen XML-Editoren bearbei- ten, was recht bequem sein kann. Mit einem PHP-Array können eben nur sehr wenige andere Systeme irgendwas anfangen. Besonders leistungsfähig ist SimpleXML, wenn man gezielt auf die Attribute eines Elements oder dessen Inhalt zugreifen möchte. Andere, komplexere Zugriffsme- thoden verlangen andere Erweiterungen. Der Namensteil »Simple« kann hier wörtlich genommen werden. Damit wird die Erweiterung aber richtig praxistaug- lich, denn die Lernkurve ist verhältnismäßig flach. 583
  • 584.
    XML und Webservices Indiesem Abschnitt wird gezeigt, wie mit SimpleXML eine XML-Datei gelesen und verarbeitet wird. Dazu wird der Inhalt teilweise extrahiert und eine Abfrage mit der Abfragesprache XPath realisiert. Als Beispielformat soll das beliebte RSS dienen, das bereits am Anfang des Kapitels kurz vorgestellt wurde. RSS wird zum Datenaustausch der beliebten Weblogs benutzt. Hier wird die einfachste Version von RSS verwendet, nicht die komplexere »Muttersprache« RDF, die einiges mehr an XML-Techniken nutzt und die meisten Anwendungen massiv überfor- dert. Wer mag, findet im Web reichlich Quellen zu RDF. Auf jeden Fall müssen Sie mit XML-Namensräumen und XPath vertraut sein, um alle folgenden Skripte lesen zu können. XML Lesen Als Ausgangspunkt der ersten Versuche mit SimpleXML soll die folgende RSS- Datei dienen: ?xml version=1.0 encoding=utf-8 ? rss version=0.91 channel titlePHP: Hypertext Preprocessor/title linkhttp://www.php.net//link description The PHP scripting language web site /description /channel item titlePHP 5.0.0 Beta 4 Released/title linkhttp://www.php.net/downloads.php/link descriptionPHP 5.0 Beta 4 has been released. The third beta of PHP is also scheduled to be the last one (barring unexpected surprises)./description /item item titlePHP Template Project/title linkhttp://www.phptemple.de/link description A design oriented template system for PHP is announced by Comzept Systemhaus GmbH. It compiles dynamic HTML into native PHP. /description /item /rss 584
  • 585.
    SimpleXML Listing 14.11: rssfeed091.xml– Eine erste RSS-Datei als Datenbasis Um mit dieser Datei arbeiten zu können, wird in SimpleXML ein entsprechendes Objekt erzeugt: $rss = simplexml_load_file('/data/rssfeed091.xml'); Danach steht das Objekt in der Variablen $rss zur Verfügung, vorausgesetzt die Datei liegt im Verzeichnis /data und heißt rssfeed091.xml. Diese Angaben entspre- chen den Daten auf der Buch-CD. Auf die Elemente (Tags) kann nun mit der üblichen Objektsyntax zugegriffen wer- den, so als wären es Eigenschaften: echo $rss-channel-title; Das vollständige Skript sieht folgendermaßen aus: Listing 14.12: SimpleXMLRss091.php – Einfachster Zugriff auf eine RSS-Quelle ?php $rss = simplexml_load_file('data/rssfeed091.xml'); echo Channel b{$rss-channel-title}/b ; echo (See a href={$rss-channel-link}{$rss-channel-link}/a); ? Mit der Musterdatei sollte nun folgendes ausgegeben werden: Abbildung 14.4: Ausgabe der Channel-Daten einer RSS-Quelle Falls ein Element auf einer Stufe in der Hierarchie mehr als einmal vorkommt, legt PHP diese Daten in einem Array an. Im Beispiel finden Sie zwar nur ein Ele- ment channel, darunter aber zwei Element item. Der Zugriff darauf sieht dann folgendermaßen aus: Wie üblich kann zum Zugriff auf alle Elemente eines Arrays die Anweisung foreach zum Einsatz kommen: ?php $rss = simplexml_load_file('data/rssfeed091.xml'); echo Channel b{$rss-channel-title}/b ; echo (See a href={$rss-channel-link}{$rss-channel-link}/a); echo br/br/; 585
  • 586.
    XML und Webservices foreach($rss-item as $item) { echo {$item-title} br/; } ? Listing 14.13: SimpleXMLRss091b.php – Ausgabe mehrerer Elemente Die folgende Abbildung zeigt, wie die Titel der RSS-Quelle erscheinen: Abbildung 14.5: Ausgabe aller Elemente einer Auflistung Nach den Elementen sind nun die Attribute an der Reihe. Diese stehen ebenfalls als Arrayelemente zur Verfügung und können über ihre Namen erreicht werden: Listing 14.14: SimpleXMLRssVersion.php (Ausschnitt) – Ermittlung der Version aus dem Attribute version des Stammelements echo $rss['version']; Damit kann man in der Praxis schon eine Menge erreichen, denn Sie können nun XML lesen und mit den bereits bekannten Mitteln in PHP weiterverarbeiten. Das reicht nicht immer aus, macht aber die ersten Schritte sehr einfach. Andere XML-Funktionen, wie Kommentare oder Prozessanweisungen, lassen sich mit dieser Technik freilich nicht nutzen. Sie können diese Entitäten nicht errei- chen. Essentielle Informationen werden auf diesem Wege jedoch selten übermit- telt, sodass SimpleXML durchaus seine Daseinsberechtigung hat. SimpleXML-Methoden Der Zugriff auf Attribute gestaltet sich bei den Objekten der tiefer liegenden Struk- tur ebenso. Hier können Sie jederzeit die Arraysyntax ansetzen. Wenn Sie dagegen eine Auflistung der Attribute benötigen, beispielsweise um mit foreach zugreifen zu können, nutzen Sie die Methode attributes(): 586
  • 587.
    SimpleXML foreach($rss-item[0]-attributes() as $a= $b) { echo $a,'=',$b,n; } Der erste Parameter ergibt dabei immer den Namen des Attributes, der zweite den Inhalt. Hat man komplexe Strukturen, sind Vereinfachungen hilfreich. Eine Methode hilft dabei, einen Zweig von Knoten gesondert weiterzuverarbeiten: children(). Zurückgegeben wird ein Objekt, das ähnlich wie das Stammobjekt aufgebaut ist und nur die Kindelemente enthält. Selbstverständlich kann mit SimpleXml der Inhalt der Knoten geändert werden. Um die geänderte Version wieder schreiben zu können, ist die Methode AsXml() hilfreich. Die Methode gibt den Inhalt als Zeichenkette zurück. Die Speicherung in einer Datei müssen Sie dagegen selbst organisieren. Hilfreich ist – für umfassendere XML-Zugriffe – das Laden von Objekten, die mit den regulären DOM-Erweiterungen erzeugt wurden. Die Methode simplexml_import_dom dient diesem Zweck. Der Abschnitt über DOM zeigt die Anwendung. Komplexere Abfragen mit XPath gestalten Der Zugriff auf eine tief liegende Ebene oder eine Parametrisierung misslingt mit dem direkten Objektzugriff meist oder wird so kompliziert, dass die ganze Verein- fachung, die erreicht wurde, wieder aufgebraucht ist. Will man mehr, kommt XPath zum Einsatz. Der folgende Code findet alle Texte, die innerhalb aller title-Element stehen: Listing 14.15: SimpleXMLXpath.php – Abfrage mit XPath ?php $rss = simplexml_load_file('data/rssfeed091.xml'); foreach ($rss-xpath('//title') as $title) { echo $title br /; } ? 587
  • 588.
    XML und Webservices Diexpath-Methode durchsucht das SimpleXML-Objekt und gibt ein Array mit den Fundstellen zurück. Der Parameter enthält die XPath-Anweisung, die recht komplex werden kann. Die beiden Schrägstriche sagen: »Suche Elemente mit dem Namen title an jeder beliebigen Stelle im Dokument«. Im Beispiel heißt das, dass sowohl die Titel des channel als auch der item-Elemente gefunden werden. Abbildung 14.6: Abfrage mit XPath: Schnell und flexibel Sollen dagegen nur die title-Elemente gefunden werden, die sich innerhalb von item befinden, wäre folgende Anweisung einzusetzen: //item/title Spätestens hier wäre der Zugriff über das Objektmodell nur noch mit sehr viel Pro- grammierung zu erreichen und außerdem unglaublich langsam. XPath ist dagegen sehr schnell. XML-Namensräume SimpleXML erledigt den XML-Zugriff leicht und einfach. Das Lesen von RSS 1.0- Quellen wird so für jeden beherrschbar. Allerdings benutzt RSS 1.0 auch XML- Namensräume, was die Sache wieder etwas komplizierter werden lässt. Da Namensräume generell eine herausragende Rolle in XML übernehmen, ist eine Auseinandersetzung damit unerlässlich. Der Namensraum eines XML-Elements ist immer durch einen URL definiert. Im Dokument wird aus Gründen der Lesbarkeit ein Alias benutzt, der dann zu folgen- den Elementformen führt: my:title my: ist dabei der Alias für einen Namensraum. Treffen mehrere XML-Dialekte auf- einander, kann man diese mit Hilfe der Namensräume auseinander halten. Den Alias wählt man im Zieldokument so, dass er konfliktfrei aufgelöst werden kann. Mit Hilfe von Namensräumen können Sie leicht zwischen dem RSS-Element title und dem HTML-Element mit demselben Namen unterscheiden. 588
  • 589.
    SimpleXML Aus Sicht desXML-Zugriffs mit SimpleXML werden die Dinge nun plötzlich komplizierter. Denn ein Zugriff über das Objektmodell wird mit $rss-my:title nicht gelingen. Wird nur -title geschrieben, verfügt der Prozessor jedoch über keine Informationen, welches title-Element denn nun gemeint ist. Schreibt man die vollständigen Namensräume mit auf, stehen nun zwei Arten von title- Elementen zur Auswahl: {http://www.w3.org/1999/xhtml}:title {http://purl.org/rss/1.0}:title Der gesamte Name wird als »qualifizierter Name« (qualified name) bezeichnet. Der Alias ändert nichts an der Bezeichnung. Wie bereits erwähnt, dient dies ledig- lich der Lesbarkeit. Es vereinfacht aber auch den Zugriff ein wenig. Es gibt keine korrekten Aliasnamen im Sinne einer Vorgabe oder Norm, sondern lediglich Vor- schläge, welche Namen ohne den konfliktfrei gewählt werden sollten. Vergessen Sie jedoch nicht, dass der Alias primär der Konfliktauflösung dient und deshalb bewusst Änderungen unterliegt. Typische Aliase für die beiden erwähnten Namen sind die folgenden: xhtml:title rss:title Der URL, der den Namensraum definiert, führt nicht dazu, dass die ver- arbeitende Software online auf Informationen zugreift. Auch muss die verlinkte Seite nicht wirklich existieren. Die Angabe sichert lediglich die weltweite Eindeutigkeit und sollte deshalb den Namen der Firmen- Domain enthalten. Um den Umgang von SimpleXML mit Namensräumen kennen zu lernen, wird die bereits bekannte RSS-Datei mit einem solchen ausgestattet, was der Übergang zur Version 1.0 erledigt: ?xml version=1.0 encoding=UTF-8? rdf:RDF xmlns:rdf=http://www.w3.org/1999/02/22-rdf-syntax-ns# xmlns:dc=http://purl.org/dc/elements/1.1/ xmlns=http://purl.org/rss/1.0/ channel rdf:about=http://www.php.net/ titlePHP: Hypertext Preprocessor/title linkhttp://www.php.net//link descriptionThe PHP scripting language web 589
  • 590.
    XML und Webservices site/description /channel item rdf:about=http://www.php.net/downloads.php titlePHP 5.0.0 Beta 3 Released/title linkhttp://www.php.net/downloads.php/link description PHP 5.0 Beta 3 has been released. The third beta of PHP is also scheduled to be the last one (barring unexpected surprises). /description dc:date2004-01-02/dc:date /item item rdf:about=http://shiflett.org/archive/19 titlePHP Community Site Project Announced/title linkhttp://shiflett.org/archive/19/link description Members of the PHP community are seeking volunteers to help develop the first web site that is created both by the community and for the community. /description dc:date2003-12-18/dc:date /item /rdf:RDF Listing 14.16: rssfeed100.xml – RSS-Datei mit Namensräumen (explizite Aliase rdf und dc) Dieses Dokument enthält drei Namensräume, davon sind im Kopf zwei explizit definiert und den Aliasnamen rdf und dc zugeordnet. Die folgende Syntax zeigt, wie der Alias definiert wird: xmlns:rdf=http://www.w3.org/1999/02/22-rdf-syntax-ns# Die Definition wird immer als Attribut xmlns im Wurzelelement des den Namens- raum benutzenden Dokumentteils geschrieben. Man kann die Definition auch in jedem einzelnen betroffenen Element wiederholen. Nach dem Doppelpunkt folgt der gewählte Alias und danach der eigentlichen Namensraum als vollständiger URL. Die Elemente rdf:RDF, rdf:about und dc:date zeigen die Anwendung. 590
  • 591.
    SimpleXML RDF ist die »Mutterdefinition« von RSS und steht für Resource Descrip- tion Format. Dies ist eine sehr komplexe Form der Beschreibung von verlinkten Dokumenten. Unter http://www.w3.org/RDF/ finden Sie eine eingehende technische Beschreibung. Eine Definition im gezeigten Dokument hat keinen Alias: xmlns=http://purl.org/rss/1.0/ Das ist der Standardnamensraum, der für alle Elemente gilt, die keinen Alias auf- weisen (der dritte im Bunde der oben erwähnten). RSS 1.0 verlangt diese Angabe, während die erste öffentliche Version 0.91 ausdrücklich keinen Namensraum ver- langt, was freilich zu praktischen Problemen beim Datenaustausch führen kann. Mehr dazu finden Sie im Abschnitt »Ein konkretes XML-Format: RSS« ab Seite 568. Namensräume mit SimpleXML verwenden Die Suche von Elementen mit bestimmten Namensräumen verlangt nach einem neuen Satz von Methoden, über den SimpleXML nicht verfügt. In der Praxis ver- sagt die Anwendung dennoch nicht, denn SimpleXML ignoriert die Namens- räume einfach. Damit sind auch Dateien mit Namensraumaliasen lesbar, zumindest solange die interne Vereinfachung nicht zu Namenskonflikten führt. XML Namensräume und XPath SimpleXML ignoriert die Namensräume jedoch nicht völlig. Es existiert lediglich keine dedizierte Syntax für den Zugriff. Mit XPath sieht es anders aus. Hier kann der Namensraum bei der Abfrage durchaus angegeben werden. Freilich ist dies keine Leistung von SimpleXML, sondern vom intern verwendeten XPath-Modul. Damit die Übergabe der Namensräume auch mit dem Standardnamensraum funktioniert, muss wenigstens dieser registriert werden. Dann funktioniert auch die Auflösung der übrigen Namensräume, wie es das nächste Beispiel weiter unten zeigt. Um alle Titel aus den Elementen rss:title zu finden, muss der Namensraum- Alias registriert werden. Der vollständige Namensraum kann beispielsweise folgen- dermaßen aussehen: http://purl.org/rss/1.0/ 591
  • 592.
    XML und Webservices DerAlias ist frei wählbar, aber der RSS-Standard empfiehlt für das vorliegende Bei- spiel die Verwendung von »rss«. Die XPath-Abfrage sollte dann folgendermaßen aussehen: //rss:item/rss:title Leider gibt es keinen Weg, in XPath einen Standardnamensraum zu definieren, wie es in XML selbst möglich ist. Das ist in der Praxis aber nur selten problema- tisch. Wichtig ist es, an den Namensraum bei der Abfrage zu denken, auch wenn im Dokument ein Standardnamensraum verwendet wird und der Alias nicht in allen Tags auftaucht. Das folgende Listing zeigt die fertige Lösung: Listing 14.17: simplens.php – Abfrage eines RSS-Feed mit Namensräumen ?php $s = simplexml_load_file('data/rssfeed100.xml'); $s-registerXPathNamespace('rss', 'http://purl.org/rss/1.0/'); $titles = $s-xpath('//rss:item[ starts-with(dc:date, 2004-01-)]/rss:title'); foreach ($titles as $title) { print $titlen; } ? Die erste Zeile entspricht den letzten Beispielen. Dann folgt die Registrierung des Namensraums: $s-registerXPathNamespace('rss', 'http://purl.org/rss/1.0/'); Die Abfrage selbst greift außerdem demonstrativ auf XSLT-Funktionen zurück, um die Ergebnisse einzuschränken. Die Funktion starts-with prüft, ob eine Zei- chenkette mit einer bestimmten Zeichenfolge beginnt. Der Alias dc deutet darauf hin, dass die Daten der Namensraumdefinition aus den Dublin Core Metadata entstammen, die Datumswerte spezifiziert. Abbildung 14.7: Auswahl des Titels aus dem RSS-Feed 592
  • 593.
    SOAP-Webservices Andere Funktionen Wie bereitserwähnt, ist es möglich, Attribute und Elementinhalte auszutauschen, indem ein neuer Wert zugewiesen wird. Der geänderte Inhalt des Dokuments kann komplett als Zeichenkette zurückgegeben werden, wozu die Methode AsXml() auf den Wurzelknoten angewendet wird. Insgesamt ist SimpleXML ein feines und kleines Modul, das den Zugriff auf XML stark vereinfacht und ohne tief gehende Kenntnisse leicht bedienbar ist. Große Projekte können davon weniger profitieren, weil die Einschränkungen program- miertechnisch kaum zum umschiffen sind. Hier können Sie jedoch mit dem zwei- ten XML-Modul, der libxml2, nahtlos ansetzen. 14.4 SOAP-Webservices Webservices sind bereits seit einiger Zeit heftig umworben und immer wieder Titelthema von Entwicklerkonferenzen und Produktankündigungen. Eine breite Nutzung ist indes nicht auszumachen. Die ersten Anfänge sind trotzdem ermuti- gend und die technischen Vorteile sind so gravierend, dass eine Beschäftigung mit den Möglichkeiten unbedingt lohnt. Grundlagen PHP5 stellt mit dem SOAP-Erweiterungen ein integriertes Paket zur Verfügung, mit dem sich Webservices anbieten und nutzen lassen. Damit ist der Einsatz derart einfach geworden, dass man sich deutlich mehr Gedanken um mögliche Anwen- dungen als um deren Realisierung machen muss. Gedanken Webservices sind im Grund genommen nur entfernte Methodenaufrufe. Auf irgendeinem Server läuft ein Programm, das öffentliche Methoden zur Verfügung stellt. Werden diese – mit oder ohne Parameter – aufgerufen, gibt eine spezielle Methode Daten zurück oder führt Aktionen aus. Das Ganze nennt man dann einen Webservice (oder Web-Dienst oder XML-Webdienst), wenn sich der Anbie- ter des Dienstes an bestimmte Standards hält. Das ist sehr sinnvoll, denn damit öff- 593
  • 594.
    XML und Webservices neter sich einer Palette von Clients, die denselben Standard benutzen. Prominente Beispiele solcher Anbieter sind Amazon und Google, deren Dienste »Bücher kaufen« und »Suchen« so anderen Servern zur Verfügung gestellt wer- den. Dabei umfasst die Definition »Webservice« bereits sehr viel, lediglich die Ausformulierung der Abfragen muss bekannt gemacht werden. Da entsprechende Clients für alle Betriebssysteme und Programmierumgebungen zur Verfügung ste- hen, muss sich der Anbieter keine Gedanken über die Nutzbarkeit seines Angebots machen. Entsprechend zahlreich sollten Anbieter und Nutzer sein – theoretisch. In der Praxis setzen sich Webservices recht zögerlich durch. Vermutlich hat dies keine technischen Ursachen, sondern deutet eher auf mentale Hemmnisse hin. Webservices sind primär für die Server-Server-Kommunikation geschaffen. Auch bei der Abwicklung von automatisierten Abfragen in der Server-Client-Kommuni- kation sind Webservices stark. Das massiv clientgeprägte Web mit einem nicht Webservice-tauglichen Browser steht dem natürlich entgegen. Es existieren schlicht keine Strukturen in Unternehmen und deren IT-Abteilungen, in der Kate- gorie »Dienst« zu denken. Dabei ist eigentlich alles ganz einfach. Prinzipien Die Kommunikation zwischen Server und Server oder zwischen Client und Server benutzt zwei bereits etablierte Techniken: 1. HTTP oder SMTP HTTP und SMTP sind die möglichen Transportprotokolle, die die Daten übertragen. Meist kommt nur HTTP zum Einsatz. Das bedeutet, dass Aufrufe von Webservices problemlos Firewalls überwinden. Der Anbieter muss seine massiven Sicherheitseinrichtungen also kein bisschen öffnen, was äußerst attraktiv ist. SMTP ist kaum in Benutzung, würde jedoch auch interessant sein, weil es eine asynchrone Kommunikation zulässt. Man stelle sich vor, ein Client, beispiels- weise ein Handy, sendet eine Anfrage an einen solchen Dienst über die MMS- Funktion. Der Dienstanbieter setzt dies auf SMTP um und der Webservice- Anbieter beantwortet die Anforderung auf gleichem Wege. Der Dienstanbieter erstellt aus der Antwort wiederum eine MMS und schon kann man billig inter- aktiv kommunizieren – ohne wie bei WAP ständig online zu sein. Liest hier eigentlich jemand von Vodafone mit? 594
  • 595.
    SOAP-Webservices 2. XML Die Nachrichten selbst – also die Parameterdaten einer Anfrage und die Ergeb- nisse – werden mittels SOAP (Simple Object Access Protocol) kodiert. Dies ist ein XML-Dialekt. Damit ist die Weiterverarbeitung schnell und einfach, denn XML-Parser stehen immer und überall zur Verfügung. Die Dienstinformatio- nen selbst, also die Namen der Methoden und die Datentypen der Parameter werden über eine Informationsdatei im Format WSDL (Web Services Descrip- tion Language) angeboten, wobei es sich hier selbstverständlich wiederum um XML handelt. Auf der Grundlage der Basistechnologien HTTP und XML ist es für Hersteller nicht schwer, Webservices in Programmiersprachen und Betriebssysteme zu inte- grieren. Jeder neuere Website, die mit PHP5, Java oder ASP.NET in den letzten drei bis fünf Jahren entstanden ist, steht die Technologie praktisch zur Verfügung. Technisch läuft das Ganze dann so ab, dass zuerst der Dienst selbst programmiert wird, meist eine Klasse mit einer Anzahl öffentlicher Methoden, die den Benut- zern bereitgestellt werden. Zu diesem Angebot wird dann die Beschreibungsdatei im WSDL-Format erstellt. Dann wird der SOAP-Server der Programmierumge- bung damit beauftragt, Anfragen anzunehmen, an die Methoden weiterzuleiten und deren Beantwortung zu überwachen. Die Außenkommunikation wird, wenn sie über HTTP abgewickelt wird, vom Webserver erledigt, der selbst nichts von SOAP oder XML wissen muss. Für ihn sind es lediglich HTTP-Anforderungen, deren Körper (Body) statt HTML-Seiten SOAP-Nachrichten enthält. Technische Grundlagen Um nicht völlig im Dunkeln zu tappen, was den technischen Ablauf betrifft, soll- ten Sie sich kurz mit den technischen Grundlagen vertraut machen. Die hier vor- liegende Beschreibung ist stark vereinfacht, reicht aber für das Verständnis der ersten Beispiele aus. An allererster Stelle steht immer SOAP. SOAP realisiert den eigentlichen Funk- tionsaufruf. Die SOAP-Nachricht besteht aus drei Teilen: í Umschlag (SOAP Envelope) í Kopf (SOAP Header) í Inhalt (SOAP Body) 595
  • 596.
    XML und Webservices DerInhalt kann in einer Nachricht in mehrere Blöcke verteilt sein. In XML sieht das Ganze dann folgendermaßen aus: Listing 14.18: Rumpf einer SOAP-Nachricht (ohne Daten) SOAP-ENV:Envelope xmlns:SOAP-ENV=?http://schemas.xmlsoap.org/soap/envelope/? SOAP-ENV:Header /SOAP-ENV:Header SOAP-ENV:Body /SOAP-ENV:Body /SOAP-ENV:Envelope Zusätzlich zur eigenen Namensraumdefinition werden meist noch XML-Schema für die Datentypen und Schema-Instance vereinbart. Als Namensraumaliase kom- men die Abkürzungen »xsd« und »xsi« zum Einsatz. Was auch immer im Inhalt der SOAP-Body-Tags steht, hängt bereits von der kon- kreten Anwendung ab. Der Aufbau ist zwar sehr streng, die Benennung der Tags und die Struktur variieren jedoch. Damit sich die beiden Kommunikationspartner über diese Struktur einig werden – und zwar automatisch – kommt WSDL zum Einsatz. WSDL steht für Web Services Description Language. Dies ist eine vom Anbieter zu erstellende XML-Datei, die auf Anforderung des Clients gesendet wird. Sie muss genaue Informationen über folgende Dienstmerkmale enthalten: í Kommunikationsport, also den URL, unter dem der Dienst bereitsteht í Bindung, also die Verknüpfung zwischen Dienst und Ein- und Ausgabeopera- tionen í Porttypen, eine Definition der Kommunikationsrichtung der zulässigen Opera- tionen í Nachrichten, die Namen der von den Ports angebotenen Methoden und deren Parameter- und Rückgabedefinitionen í Datentypen, also die konkret von einem Parameter oder Rückgabewert benutz- ten Datentypen Bei den Datentypen wird zur Beschreibung XML-Schema benutzt. Zulässig sind sowohl die in XML-Schema definierten elementaren Typen (string, integer) als auch so genannte Komplextypen, die benutzerdefinierte Datensammlungen beschreiben. 596
  • 597.
    SOAP-Webservices Mit diesen Informationenist der Client nun in der Lage, die vom Server bereitge- stellten Methoden so umzusetzen, dass dem Programmierer tatsächlich eine Klasse mit den ursprünglich definierten Methoden vorgehalten wird. Das ist die eigentli- che Faszination: Während auf der einen Seite der Dienst mit PHP und den SOAP- Erweiterungen benutzt wird, als Betriebssystem Unix läuft und als Datenbank MySQL benutzt wird, steht dem Client möglicherweise ein Windows XP und ein in Access geschriebene Benutzeroberfläche zur Verfügung. Und wenn der Server später auf das Gespann Java/Sun Solaris wechselt, sollte der Client davon nichts mitbekommen. Das ist die schöne neue Welt der Webservices. Alle Theorie ist jedoch langweilig, wenn man nichts praktisch unternimmt. Des- halb schließt dieses Kapitel mit zwei kleinen Projekten ab. Eines konsumiert einen im Internet öffentlich angebotenen Webservice, das andere bietet selbst einen sol- chen an. Der Amazon-Webservice: ein Überblick Dieser Abschnitt stellt die Amazon-Webservices kurz vor und gibt einen ersten Einblick in mögliche Anwendungen. Dies erhebt keinerlei Anspruch auf Vollstän- digkeit. Die Definition der Dienste: WSDL Die folgende WSDL-Datei stellt den Zustand und das Angebot des Dienstes Mitte 2004 dar. Auf die Datei wird im folgenden Bezug genommen. Man kann ihr sowohl die Struktur der aufrufbaren Funktionen als auch die Datentypen der Para- meter und Rückgabewerte entnehmen: Listing 14.19: WSDL-Datei des Amazon-Webservices (stark gekürzt) wsdl:definitions xmlns:typens=http://soap.amazon.com xmlns:xsd=http:// www.w3.org/2001/XMLSchema xmlns:soap=http://schemas.xmlsoap.org/wsdl/ soap/ xmlns:soapenc=http://schemas.xmlsoap.org/soap/encoding/ xmlns:wsdl=http://schemas.xmlsoap.org/wsdl/ xmlns=http:// schemas.xmlsoap.org/wsdl/ targetNamespace=http://soap.amazon.com name=AmazonSearch wsdl:types xsd:schema xmlns= xmlns:xsd=http://www.w3.org/2001/XMLSchema 597
  • 598.
    XML und Webservices targetNamespace=http://soap.amazon.com xsd:complexType name=ProductInfo xsd:all xsd:element name=TotalResults type=xsd:string minOccurs=0/ xsd:element name=TotalPages type=xsd:string minOccurs=0/ xsd:element name=ListName type=xsd:string minOccurs=0/ xsd:element name=Details type=typens:DetailsArray minOccurs=0/ /xsd:all /xsd:complexType xsd:complexType name=DetailsArray xsd:complexContent xsd:restriction base=soapenc:Array xsd:attribute ref=soapenc:arrayType wsdl:arrayType=typens:Details[]/ /xsd:restriction /xsd:complexContent /xsd:complexType xsd:complexType name=Details xsd:all xsd:element name=Url type=xsd:string minOccurs=0/ xsd:element name=Asin type=xsd:string minOccurs=0/ xsd:element name=ProductName type=xsd:string minOccurs=0/ xsd:element name=Catalog type=xsd:string minOccurs=0/ xsd:element name=KeyPhrases type=typens:KeyPhraseArray minOccurs=0/ xsd:element name=Artists type=typens:ArtistArray minOccurs=0/ xsd:element name=Authors type=typens:AuthorArray minOccurs=0/ xsd:element name=Mpn type=xsd:string minOccurs=0/ xsd:element name=Starring type=typens:StarringArray minOccurs=0/ xsd:element name=Directors type=typens:DirectorArray minOccurs=0/ xsd:element name=TheatricalReleaseDate type=xsd:string minOccurs=0/ 598
  • 599.
    SOAP-Webservices xsd:element name=ReleaseDate type=xsd:string minOccurs=0/ xsd:element name=Manufacturer type=xsd:string minOccurs=0/ xsd:element name=Distributor type=xsd:string minOccurs=0/ xsd:element name=ImageUrlSmall type=xsd:string minOccurs=0/ xsd:element name=ImageUrlMedium type=xsd:string minOccurs=0/ xsd:element name=ImageUrlLarge type=xsd:string minOccurs=0/ xsd:element name=ListPrice type=xsd:string minOccurs=0/ xsd:element name=OurPrice type=xsd:string minOccurs=0/ xsd:element name=UsedPrice type=xsd:string minOccurs=0/ xsd:element name=RefurbishedPrice type=xsd:string minOccurs=0/ xsd:element name=CollectiblePrice type=xsd:string minOccurs=0/ xsd:element name=ThirdPartyNewPrice type=xsd:string minOccurs=0/ xsd:element name=NumberOfOfferings type=xsd:string minOccurs=0/ xsd:element name=ThirdPartyNewCount type=xsd:string minOccurs=0/ xsd:element name=UsedCount type=xsd:string minOccurs=0/ xsd:element name=CollectibleCount type=xsd:string minOccurs=0/ xsd:element name=RefurbishedCount type=xsd:string minOccurs=0/ xsd:element name=ThirdPartyProductInfo type=typens:ThirdPartyProductInfo minOccurs=0/ xsd:element name=SalesRank type=xsd:string minOccurs=0/ xsd:element name=BrowseList type=typens:BrowseNodeArray minOccurs=0/ xsd:element name=Media type=xsd:string 599
  • 600.
    XML und Webservices minOccurs=0/ xsd:element name=ReadingLevel type=xsd:string minOccurs=0/ xsd:element name=NumberOfPages type=xsd:string minOccurs=0/ xsd:element name=NumberOfIssues type=xsd:string minOccurs=0/ xsd:element name=IssuesPerYear type=xsd:string minOccurs=0/ xsd:element name=SubscriptionLength type=xsd:string minOccurs=0/ xsd:element name=DeweyNumber type=xsd:string minOccurs=0/ xsd:element name=RunningTime type=xsd:string minOccurs=0/ xsd:element name=Publisher type=xsd:string minOccurs=0/ xsd:element name=NumMedia type=xsd:string minOccurs=0/ xsd:element name=Isbn type=xsd:string minOccurs=0/ xsd:element name=Features type=typens:FeaturesArray minOccurs=0/ xsd:element name=MpaaRating type=xsd:string minOccurs=0/ xsd:element name=EsrbRating type=xsd:string minOccurs=0/ xsd:element name=AgeGroup type=xsd:string minOccurs=0/ xsd:element name=Availability type=xsd:string minOccurs=0/ xsd:element name=Upc type=xsd:string minOccurs=0/ xsd:element name=Tracks type=typens:TrackArray minOccurs=0/ xsd:element name=Accessories type=typens:AccessoryArray minOccurs=0/ xsd:element name=Platforms type=typens:PlatformArray minOccurs=0/ xsd:element name=Encoding type=xsd:string minOccurs=0/ xsd:element name=Reviews type=typens:Reviews minOccurs=0/ xsd:element name=SimilarProducts 600
  • 601.
    SOAP-Webservices type=typens:SimilarProductsArray minOccurs=0/ xsd:element name=Lists type=typens:ListArray minOccurs=0/ xsd:element name=Status type=xsd:string minOccurs=0/ /xsd:all /xsd:complexType xsd:complexType name=AsinRequest xsd:all xsd:element name=asin type=xsd:string/ xsd:element name=tag type=xsd:string/ xsd:element name=type type=xsd:string/ xsd:element name=devtag type=xsd:string/ xsd:element name=offer type=xsd:string minOccurs=0/ xsd:element name=offerpage type=xsd:string minOccurs=0/ xsd:element name=locale type=xsd:string minOccurs=0/ /xsd:all /xsd:complexType /xsd:schema /wsdl:types message name=AsinSearchRequest part name=AsinSearchRequest type=typens:AsinRequest/ /message message name=AsinSearchResponse part name=return type=typens:ProductInfo/ /message portType name=AmazonSearchPort operation name=AsinSearchRequest input message=typens:AsinSearchRequest/ output message=typens:AsinSearchResponse/ /operation /portType binding name=AmazonSearchBinding type=typens:AmazonSearchPort soap:binding style=rpc transport=http://schemas.xmlsoap.org/soap/http/ !-- Binding for Amazon Web APIs - RPC, SOAP over HTTP -- operation name=AsinSearchRequest 601
  • 602.
    XML und Webservices soap:operation soapAction=http://soap.amazon.com/ input soap:body use=encoded encodingStyle=http://schemas.xmlsoap.org/soap/encoding/ namespace=http://soap.amazon.com/ /input output soap:body use=encoded encodingStyle=http://schemas.xmlsoap.org/soap/encoding/ namespace=http://soap.amazon.com/ /output /operation /binding service name=AmazonSearchService !-- Endpoint for Amazon Web APIs -- port name=AmazonSearchPort binding=typens:AmazonSearchBinding soap:address location=http://soap.amazon.com/onca/soap2/ /port /service /wsdl:definitions Lesen Sie diese Datei am besten von unten nach oben. In dieser Reihenfolge bauen nämlich die Informationen aufeinander auf. Beachten Sie auch, dass die Daten, um hier überhaupt druckbar zu sein, sehr stark gekürzt wurden (auch wenn es nicht so aussieht). Praktisch sind nur die Informationen enthalten, die im Fol- genden direkt benötigt werden – für die ISBN-Suche mit AsinSearchRequest. Aber wie liest man nun solch eine Datei? Alles beginnt bei der Deklaration eines Dienstes und der Festlegung seines Namens: service name=AmazonSearchService Der Dienst definiert einen Port und eine Bindung: port name=AmazonSearchPort binding=typens:AmazonSearchBinding Die Bindung definiert die Namen der Funktionen (Operation genannt) und deren Kodierung und Namensräume. Für AsinSearchRequest sieht das folgendermaßen aus: operation name=AsinSearchRequest 602
  • 603.
    SOAP-Webservices Der Port-Typ definiertdieselbe Funktion und nennt dazu die konkrete Ein- und Ausgabefunktion, die getrennt definiert werden: portType name=AmazonSearchPort Der SOAP-Client baut aus diesen Angaben dann einen normalen PHP-Funktions- aufruf zusammen. Der Port nennt für die in der Bindung vereinbarte Operation die Namen der Funktionen für beide Transportrichtungen, input ist die Abfrage, output die Rückgabe: operation name=AsinSearchRequest input message=typens:AsinSearchRequest/ output message=typens:AsinSearchResponse/ /operation Diese Funktionen wiederum erscheinen als Nachrichten und definieren die Datentypen, die benutzt werden. Der Typ AsinRequest wird für die Anfrage benutzt: message name=AsinSearchRequest part name=AsinSearchRequest type=typens:AsinRequest/ /message Der Typ ProductInfo wird bei der Antwort verwendet: message name=AsinSearchResponse part name=return type=typens:ProductInfo/ /message Beides sind offensichtlich keine elementaren Typen, weshalb sich weiter oben die Typdefinition im XML-Schema-Stil anschließt. Komplexe Typen setzen sich, gegebenenfalls über mehrere Schritte, aus einfachen Typen zusammen. Die Defi- nition eines komplexen Typs wird folgendermaßen eingeleitet: xsd:complexType name=AsinRequest Darunter folgen entweder weitere komplexe Typen oder – in letzter Konsequenz – elementare: xsd:element name=asin type=xsd:string / Erscheint der Zusatz minOccurs=0, ist das Feld optional: xsd:element name=locale type=xsd:string minOccurs=0 / Freilich kann hier auch eine bestimmte Mindest- oder Maximalanzahl festgelegt werden. XML-Schema erlaubt eine sehr feine Definition von Datentypen. 603
  • 604.
    XML und Webservices EinDatentyp für die Antwort ProductInfo, Details, enthält beispielsweise einen Verweis auf DetailsArray: xsd:element name=Details type=typens:DetailsArray minOccurs=0 / Dieses ist als SOAP-Array (Achtung! Basisdatentyp) definiert, das Elemente des Typs Details (Achtung! benutzerdefinierter Komplextyp) enthält: xsd:attribute ref=soapenc:arrayType wsdl:arrayType=typens:Details[] / Details seinerseits enthält nun nur noch Basistypen (wie string), wie der folgende Ausschnitt zeigt: xsd:element name=Asin type=xsd:string minOccurs=0 / Es können jedoch auch weitere komplexe Typen folgen, die andernorts in der WSDL-Datei definiert sind (möglicherweise aber nicht abgedruckt, weil sonst das halbe Buch voll WSDL-Code wäre): xsd:element name=Authors type=typens:AuthorArray minOccurs=0 / Mit diesen Informationen ist der SOAP-Client nun in der Lage, die Daten für eine Anfrage zu erstellen und aus der Antwort die Daten wieder zu entnehmen. Eine korrekte Anfrage, wie sie das nachfolgend vorgestellte Beispiel erzeugt, sieht fol- gendermaßen aus: Listing 14.20: Anfrage an Amazon: Aufruf der Funktion AsinSearchRequest ?xml version=1.0 encoding=UTF-8? SOAP-ENV:Envelope xmlns:SOAP-ENV=http://schemas.xmlsoap.org/soap/envelope/ xmlns:ns1=http://soap.amazon.com xmlns:xsd=http://www.w3.org/2001/XMLSchema xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xmlns:SOAP-ENC=http://schemas.xmlsoap.org/soap/encoding/ SOAP-ENV:encodingStyle=http://schemas.xmlsoap.org/soap/encoding/ SOAP-ENV:Body ns1:AsinSearchRequest AsinSearchRequest xsi:type=ns1:AsinRequest asin xsi:type=xsd:string3827264553/asin tag xsi:type=xsd:stringactiveserverpa0e/tag type xsi:type=xsd:stringheavy/type devtag xsi:type=xsd:stringXXXXXXXXXXX/devtag 604
  • 605.
    SOAP-Webservices locale xsi:type=xsd:stringde/locale /AsinSearchRequest /ns1:AsinSearchRequest /SOAP-ENV:Body /SOAP-ENV:Envelope Man kann hier sehr gut erkennen, wie im Body der Nachricht die Anfrage getreu der Datentyp-Definition aufgebaut ist. Erkennbar ist aber auch, dass mehr als 50% der Daten keine realen Daten sind. Der so genannte Overhead einer SOAP-Ver- bindung ist bei vielen kurzen Anfragen erheblich. Etwas umfassender fällt die Antwort aus (hier die »lite«-Version, ohne Kundenre- zensionen): Listing 14.21: Eine SOAP-Antwort (auf exakt die zuvor gezeigte Anfrage) ?xml version=1.0 encoding=UTF-8? SOAP-ENV:Envelope xmlns:SOAP-ENC=http://schemas.xmlsoap.org/soap/encoding/ SOAP-ENV:encodingStyle=http://schemas.xmlsoap.org/soap/encoding/ xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xmlns:SOAP-ENV=http://schemas.xmlsoap.org/soap/envelope/ xmlns:xsd=http://www.w3.org/2001/XMLSchema xmlns:amazon=http://soap.amazon.com SOAP-ENV:Body namesp57:AsinSearchRequestResponse xmlns:namesp57=http://soap.amazon.com return xsi:type=amazon:ProductInfo Details SOAP-ENC:arrayType=amazon:Details[1] xsi:type=SOAP-ENC:Array Details xsi:type=amazon:Details Url xsi:type=xsd:string http://www.amazon.de/exec/obidos/ASIN/3827264553/ activeserverpa0e?dev-t=D3D53F5TPPE230%26 camp=2025%26link_code=sp1 /Url Asin xsi:type=xsd:string3827264553/Asin ProductName xsi:type=xsd:string .NET Windows Forms in 21 Tagen . Oberflächen programmieren /ProductName Catalog xsi:type=xsd:stringBook/Catalog Authors SOAP-ENC:arrayType=xsd:string[1] xsi:type=SOAP-ENC:Array 605
  • 606.
    XML und Webservices Author xsi:type=xsd:stringChris Payne/Author /Authors ReleaseDate xsi:type=xsd:string15. Januar 2003 /ReleaseDate Manufacturer xsi:type=xsd:stringMarkt+Technik /Manufacturer ImageUrlSmall xsi:type=xsd:string http://images-eu.amazon.com/images/P/3827264553.03.THUMBZZZ.jpg /ImageUrlSmall ImageUrlMedium xsi:type=xsd:string http://images-eu.amazon.com/images/P/3827264553.03.MZZZZZZZ.jpg /ImageUrlMedium ImageUrlLarge xsi:type=xsd:string http://images-eu.amazon.com/images/P/3827264553.03.LZZZZZZZ.jpg /ImageUrlLarge ListPrice xsi:type=xsd:stringEUR 46,68/ListPrice OurPrice xsi:type=xsd:stringEUR 49,95/OurPrice UsedPrice xsi:type=xsd:stringEUR 12,70/UsedPrice /Details /Details /return /namesp57:AsinSearchRequestResponse /SOAP-ENV:Body /SOAP-ENV:Envelope Man kann gut erkennen, dass XML ideal für die Übertragung komplexer hierarchi- scher Daten geeignet ist. Die Rückübertragung nach PHP ist nun wieder Sache des SOAP-Clients. Dieser erstellt übrigens ein Objekt daraus. Angeschaut mit var_dump sieht es folgendermaßen aus (wieder exakt dieselbe Anfrage). Der Zugriff auf die Daten ist nun recht einfach. Man muss lediglich wissen, wo die Daten die man benötigt, in der Hierarchie stehen. Man muss außerdem natürlich auf Arrays, Indizes usw. achten, um gegebenenfalls mit Schleifen arbeiten zu kön- nen. Um das zu erfahren, ist jedoch das Lesen der Rohdaten und der WSDL- Dateien erforderlich, falls sich niemand die Mühe gemacht hat, eine umfassende verbale Beschreibung mitzuliefern. 606
  • 607.
    SOAP-Webservices Abbildung 14.8: Das vom SOAP-Client aus der zuvor gezeigten Antwort erzeugte Objekt Webservices konsumieren Um einen Webservice zu konsumieren, benötigen Sie zuerst einen solchen Dienst und dessen Dienstbeschreibung. Steht das bereit, erledigen PHP5 und dessen SOAP-Erweiterungen die ganze Arbeit. Als Beispiel soll die im letzten Kapitel erstellte Bibliotheks-Anwendung erweitert werden. Das Erfassen von möglicherweise hunderten Büchern ist recht mühevoll. Wenn der Server ohnehin online ist, bietet es sich an, die Daten von einem Buch- händler zu beschaffen. Dankbares Opfer wird in diesem Fall Amazon, da dort ein entsprechender Dienst angeboten wird. Die Daten beschaffen Der Start beginnt auf der folgenden Website (ab hier in Englisch): http://www.amazon.com/webservices Melden Sie sich zuerst bei Amazon an, um die kostenlose Zugangsberechtigung zu erhalten. Dazu sind lediglich eine E-Mail-Adresse und ein Kennwort erforder- lich, das selbst gewählt werden kann. Optional laden Sie dort zuerst des SDK (Ent- wicklerpaket) herunter. Das Paket hat den Namen kit.zip und ist auch auf der CD zum Buch unter /Bibliothek/SOAP zu finden (Version vom August 2004). Das 607
  • 608.
    XML und Webservices SDKenthält Beispielprogramme, Hilfen und Hinweise, auch für PHP. Das enthal- tene Programm nutzt jedoch die für PHP 4 entwickelte Version, die auf dem PEAR-Paket NuSOAP basiert. Die in diesem Buch gezeigte Version wurde dage- gen vom Autor explizit für PHP5 geschrieben und soweit vereinfacht, dass ein schneller Einstieg gelingt. Versuchsaufbau und Vorbereitung von PHP5 Die SOAP-Erweiterung in PHP5 steht unter Windows als php_soap.dll zur Verfü- gung, deren Aktivierung wie üblich in der php.ini erfolgt. Unter Linux wird PHP5 mit dem Schalter --with-soap kompiliert. Ein Blick in die Anzeige der Funktion phpinfo zeigt, ob es geklappt hat: Abbildung 14.9: Die SOAP-Erweiterun- gen wurden erfolgreich aktiviert Dann ist noch zu überprüfen, ob die Konfigurationsparameter in der Datei php.ini ein- getragen sind. Wenn der folgende Abschnitt nicht zu finden ist, fügen Sie ihn hinzu: [soap] soap.wsdl_cache_enabled=1 soap.wsdl_cache_dir=c:windowstemp soap.wsdl_cache_ttl=86400 Achten Sie auf den Pfad zum Verzeichnis temporärer Daten (das Beispiel gilt für Windows). Ziel des SOAP-Projekts Ziel der Anwendung ist die Vereinfachung der Datenerfassung bei der Aufnahme von Büchern in die im vorigen Kapitel vorgestellte Bibliotheksverwaltung. Dabei soll die Angabe der ISBN genügen, alle übrigen Daten sollen aus der Amazon- Datenbank geholt werden. Dazu gehört beispielsweise auch ein Bild des Buches, was sonst sicher nur mühevoll zu beschaffen wäre. Wie das am Ende aussehen soll, zeigt die Abbildung14.10. 608
  • 609.
    SOAP-Webservices Abbildung 14.10: Erfassung von Büchern: Alle Daten außer der ISBN liefert der Webservice Dies spart vor allem viel Zeit bei der Erfassung, vermeidet aber auch lästige Tipp- fehler. Die hier benutzten Daten stellen nur einen kleinen Teil der Möglichkeiten dar, die Amazon bietet. Erweiterung der Anwendung Die Erweiterung der Anwendung umfasst nur zwei Schritte: 1. Aufbau eines Moduls, das den Webservice abfragt 2. Erweiterung der bisherigen Bibliotheksverwaltung Das Modul wird natürlich PHP5-typisch als Klasse implementiert. Sie kann dann auch von anderen Anwendungen benutzt werden. Diese ist allerdings – im Gegen- satz zu vielen fertigen Lösungen im Internet – bewusst minimalistisch program- miert worden, um die mindestens notwendigen Funktionen demonstrieren zu können, ohne dabei erschlagend viele Code zu benutzen. 609
  • 610.
    XML und Webservices Listing14.22: soapclient.inc.php – Eine primitive Klasse zur Benutzung des Amazon- Webservice ?php // Webservice class AmazonSearch { private $Client = null; const LANG = 'de'; const MODE = 'heavy'; const TAG = 'activeserverpa0e'; const WSDL_FILE = 'http://soap.amazon.com/schemas2/AmazonWebServices.wsdl'; const TOKEN = 'IHR_AMAZON_TOKEN'; public function __construct() { try { $this-Client = new SoapClient(self::WSDL_FILE, array('trace' = 1)); } catch (SoapFault $ex) { die(Fehler beim Zugriff:nbsp;{$ex-message()}); } } public function SearchISBN($Keyword) { $params = array( 'asin' = $Keyword, 'tag' = self::TAG, 'type' = self::MODE, 'locale' = self::LANG, 'devtag' = self::TOKEN); try { $result = $this-Client-AsinSearchRequest($params); } catch (Exception $ex) { die(($this-Client-__getLastResponse())); 610
  • 611.
    SOAP-Webservices } return isset($result) ? $result : ''; } } ? Die Klasse definiert einige Informationen, die der Dienst benötigt, als Konstanten: í const LANG = 'de' Die Sprache des Katalogs, der abgefragt werden soll. Wenn Sie deutsche Bücher bearbeiten, muss die Sprache »de« sein. í const MODE = 'heavy' Der Modus, hier kann »lite« für kurze Ergebnisse oder »heavy« für ausführli- che stehen. Untersuchen Sie zuerst »lite« und nutzen Sie »heavy« nur dann, wenn Sie die benötigten Angaben andernfalls nicht finden. í const TAG = 'activeserverpa0e' Der Token eines Affiliate-Programms. Diese Angabe ist optional. í const WSDL_FILE = 'http://soap.amazon.com/schemas2/AmazonWebServices.wsdl' Die Adresse, unter der der Webservice zur Verfügung steht. í const TOKEN = 'IHR_AMAZON_TOKEN' Der Token, den Sie bei der Anmeldung von Amazon erhalten haben. Zuerst wird immer eine Instanz des SOAP-Clients benötigt: $this-Client = new SoapClient(self::WSDL_FILE, array('trace' = 1)); Der zweite Parameter, array('trace' = 1), kann entfallen, wenn die Anwendung stabil läuft und der Zugriff auf die originalen SOAP-Nachrichten nicht mehr benö- tigt wird. Sie können Methoden wie __getLastResponse() nur benutzen, wenn mit trace die Speicherung der Daten eingeschaltet wurde. Dies ist allerdings mit mehr Speicher und damit höherer Serverbelastung verbunden und sollte deshalb nur während der Aufbauphase benutzt werden. Die eigentliche Abfrage wird in der Methode searchISBN ausgeführt: public function SearchISBN($Keyword) Dort wird zunächst ein Array aus den Parametern zusammengestellt. Die Namen der Schlüsselfelder müssen den Anforderungen in der WSDL-Datei entsprechen, andernfalls reagiert der Amazon-Server mit einer Fehlermeldung. Stehen alle Daten bereit, erfolgt die Abfrage: 611
  • 612.
    XML und Webservices $result= $this-Client-AsinSearchRequest($params); Der Name AsinSearchRequest ist wiederum in der WSDL-Datei als mögliche Dienstfunktion festgelegt. Der Einbau in einen try-Block fängt Fehler ab. Da es sich hier um eine Internetverbindung handelt, kann diese natürlich fehlschlagen und dann hilft der Block bei der Fehlerverarbeitung. Wenn Sie außerhalb wieder mit try/catch arbeiten, sollten Sie mögliche Fehler mit throw new weiterreichen und gegebenenfalls eigene Fehlerklassen für Ihre Zwecke entwerfen. Im Beispiel ist die Fehlerverwaltung dem angesprochenen minimalistischen Prinzip zum Opfer gefallen. Für andere Fehler ersetzen Sie SoapFault durch Exception: catch (SoapFault $ex) Am Ende sind die ermittelten Daten nur noch zurückzugeben: return isset($result) ? $result : ''; Die hier zurückgegebenen Daten sind ein Objekt, das in seinen Eigenschaften weitere Objekte und Arrays enthält. Der Aufbau entspricht der Struktur der in der WSDL-Datei für die spezifische Anfrage verwendeten Rückgabedaten. Einbau in das Bibliotheksskript Der Einbau in das Bibliotheksskript ist relativ einfach. Die Funktionalität wurde dabei etwas erweitert und in der Vorlage musste Platz für die zusätzlichen Daten geschaffen werden. Zuerst ein Blick auf die erweiterte Vorlage: Listing 14.23: buecher.html: Die Vorlage zur Organisation der Buchdatenbank html body h1Bibliotheksverwaltung/h1 a href=index.phpStartseite/a | a href=index.php?TEMPLATE=ausleiheAusleihe/a | a href=index.php?TEMPLATE=kundenKundenverwaltung/a h2Buuml;cherverwaltung/h2 h3Informationen/h3 table border=1 width=600 caption style=font-weight:boldBuuml;cherliste/caption tr th 612
  • 613.
    SOAP-Webservices Anzahl /th th ISBN /th th Titel /th th Aktion /th /tr !-- #REPEAT:books -- tr td {$books:number} /td td nowrap {$books:isbn} /td td {$books:title} /td td a href=index.php?TEMPLATE=buecheramp;id={$books:id}amp; action=showDetails/a a href=index.php?TEMPLATE=buecheramp;id={$books:id}amp; action=delLouml;schen/a /td /tr !-- #ENDREPEAT:books -- /table a href=index.php?TEMPLATE=buecheramp;action=new Neues Buch erfassen/a h3Auml;ndernnbsp;/nbsp;Erfassen/h3 form action=index.php?TEMPLATE=buecher method=post input type=hidden name=action value={$detail_action} / input type=hidden name=id value={$detail_id} / table border=1 width=600 caption style=font-weight:boldBuchdaten/caption tr td 613
  • 614.
    XML und Webservices Titel: /td td input type=text name=book_title value={$detail_title} size=40/ /td td rowspan=7 valign=top width=250 !-- #IF:detail_publisher -- Verlag:nbsp;{$detail_publisher} !-- #ENDIF:detail_publisher -- br/br/ !-- #IF:detail_image -- img src={$detail_image}/ !-- #ENDIF:detail_image -- /td /tr tr td Untertitel: /td td input type=text name=book_subtitle value={$detail_subtitle} size=40/ /td /tr tr td Autor: /td td input type=text name=book_author value={$detail_author}/ /td /tr tr td ISBN: /td td input type=text name=book_isbn value={$detail_isbn}/ /td /tr 614
  • 615.
    SOAP-Webservices tr td Anzahl /td td input type=text name=book_number value={$detail_number} size=4/ /td /tr tr td Ausgeliehen /td td input type=text name=book_lend value={$detail_lend} size=4 disabled/ /td /tr tr td /td td input type=submit value={$detail_actiontext} / /td /tr /table /form /body /html Neu – gegenüber der im letzten Kapitel vorgestellten Variante – ist die Ausgabe der Zusatzdaten: td rowspan=7 valign=top width=250 !-- #IF:detail_publisher -- Verlag:nbsp;{$detail_publisher} !-- #ENDIF:detail_publisher -- br/br/ !-- #IF:detail_image -- img src={$detail_image}/ !-- #ENDIF:detail_image -- /td 615
  • 616.
    XML und Webservices Diesist optional, weil die Daten nicht zwingend vorliegen. Deshalb erfolgt der Einbau in IF-Abschnitte. Der PHP-Code zu dieser Vorlage ist ebenfalls nur wenig umfassender als die letzte Version: Listing 14.24: buecher.php – Steuerung der Vorlage mit allen Datenbankzugriffen ?php include('database.inc.php'); include('soapclient.inc.php'); // $detail_action = 'change'; $detail_actiontext = 'Auml;ndern'; if ($_SERVER['REQUEST_METHOD']=='GET' isset($_GET['action'])) { switch ($_GET['action']) { case 'new': $detail_action = 'new'; $detail_actiontext = 'Erfassen'; break; case 'del': $mysqli-query(DELETE FROM books WHERE id = {$_GET['id']}); break; } } if ($_SERVER['REQUEST_METHOD']=='POST' isset($_POST['action'])) { switch ($_POST['action']) { case 'new': if ((empty($_POST['book_author']) || empty($_POST['book_title'])) isset($_POST['book_isbn'])) { $asin = str_replace('-', '', $_POST['book_isbn']); $ws = new AmazonSearch(); $data = $ws-SearchISBN($asin); $detail_subtitle = $_POST['book_subtitle']; $detail_author 616
  • 617.
    SOAP-Webservices = utf8_decode($data-Details[0]-Authors[0]); $detail_isbn = $_POST['book_isbn']; $detail_title = utf8_decode($data-Details[0]-ProductName); $detail_image = utf8_decode($data-Details[0]-ImageUrlMedium); $detail_publisher = utf8_decode($data-Details[0]-Manufacturer); } else { $detail_subtitle = $_POST['book_subtitle']; $detail_author = $_POST['book_author']; $detail_isbn = $_POST['book_isbn']; $detail_title = $_POST['book_title']; } $detail_number = empty($_POST['book_number']) ? 1 : (int) $_POST['book_number']; $mysqli-query(INSERT INTO books (title, subtitle, author, isbn, number) VALUES ('{$detail_title}', '{$detail_subtitle}', '{$detail_author}', '{$detail_isbn}', {$detail_number} )); break; case 'change': $mysqli-query(UPDATE books SET title='{$_POST['book_title']}', subtitle='{$_POST['book_subtitle']}', author='{$_POST['book_author']}', isbn='{$_POST['book_isbn']}', number={$_POST['book_number']}, WHERE id = {$_POST['id']}); break; } } // Liste fuellen $query = $mysqli-query(SELECT * FROM books); while($arr = $query-fetch_assoc()) { 617
  • 618.
    XML und Webservices $books[] = $arr; } // Details abrufen, wenn gefordert if (isset($_GET['id'])) { $query = $mysqli-query(SELECT *, (SELECT COUNT(*) FROM lendout WHERE books_id = b.id) AS lend FROM books b WHERE id = {$_GET['id']}); $detail = $query-fetch_assoc(); $detail_id = $detail['id']; $detail_title = $detail['title']; $detail_subtitle= $detail['subtitle']; $detail_author = $detail['author']; $detail_number = $detail['number']; $detail_isbn = $detail['isbn']; $detail_lend = $detail['lend']; } ? Hier sollen nur die Aspekte erläutert werden, die gegenüber der letzten Version neu sind. Zuerst muss natürlich das SOAP-Modul eingebunden werden: include('soapclient.inc.php'); Die Abfrage der Amazon-Datenbank erfolgt, wenn ein neues Buch erfasst wurde und keine Daten außer der ISBN-Nummer im Formular angegeben wurden: if ((empty($_POST['book_author']) || empty($_POST['book_title'])) isset($_POST['book_isbn'])) Für die Abfrage müssen die Trennzeichen aus der ISBN entfernt werden, andern- falls funktioniert AsinSearchRequest nicht: $asin = str_replace('-', '', $_POST['book_isbn']); Dann wird eine Instanz der SOAP-Klasse erstellt: $ws = new AmazonSearch(); In diesem Augenblick wird bereits die WSDL-Datei gelesen. Da die Speicherzeit im Cache 1 Tag beträgt, muss man beim ersten Abruf und nach jedem weiteren Tag hier mit einer etwas längeren Wartezeit rechnen. Die Größe beträgt ca. 52 KByte. 618
  • 619.
    SOAP-Webservices Dann erfolgt dieAbfrage, dies ist immer »life«, benötigt eine Internetverbindung und dauert fast immer eine oder mehrere Sekunden (auch bei DSL): $data = $ws-SearchISBN($asin); Die Daten werden nun entsprechend der erwarteten Struktur entnommen, bei- spielsweise der erste Autor: $detail_author = utf8_decode($data-Details[0]-Authors[0]); Beachten Sie hier, dass die Daten UTF-8-kodiert sind. Dies ist XML-Standard. PHP liefert die passende Funktion zum Dekodieren mit: utf8_decode. Der Aufruf $data-Details[0]-Authors[0] sollte Ihnen keine Rätsel bereiten: $data Das Datenobjekt -Details Die Eigenschaft Details (ergibt ein Array) [0] Das erste Element des Arrays, dies ist wieder ein Objekt, diesmal vom Typ Details -Authors Darin ist eine Eigenschaft Authors definiert [0] Die Eigenschaft enthält ein Array, dessen erstes Element benutzt wird Das das erste Element direkt benutzt werden kann, ist der WSDL-Beschreibung oder dem Antwortpaket zu entnehmen. Der Datentyp für einen Autor ist »string«, also eine simple Zeichenkette. Die übrigen Abrufe folgen demselben Schema, wobei beispielsweise der Titel keine Unterteilung in ein Array mehr besitzt: utf8_decode($data-Details[0]-ProductName) Fazit Damit ist eigentlich alles gesagt. Die Amazon-Webservices sind äußerst komplex und für jeden Entwickler eine Herausforderung. Es lohnt sich jedoch, denn man kann mit den Affiliate-Programmen oder den zShops Geld verdienen. Der automa- tisierte Zugriff erlaubt einen personalarmen Betrieb eines solchen Shops und damit werden auch kleine Einnahmen attraktiv, weil sie nicht sofort wieder von Kosten aufgefressen werden. Wer übrigens eine Bibliothek betreibt, der kann direkt Kosten sparen, denn er muss nun künftig eingehende Bücher, die ohne elek- tronische Datensätze ankommen, nicht mehr von Hand erfassen. Mit Hilfe eines kleinen, preiswerten Barcode-Scanners lässt sich die ISBN vom Buchrücken abnehmen und schon sind die kompletten Daten von Amazon geholt und in der Datenbank. Was will man mehr? 619
  • 620.
    XML und Webservices 14.5Referenz Die Referenz zeigt die in diesem Kapitel behandelten XML-Module SimpleXML und DOM. Referenz SimpleXML Funktion Beschreibung simplexml_import_dom Die Funktion importiert ein SimpleXML-Objekt aus einem DOM-Knoten und gibt das SimpleXML-Objekt zurück. simplexml_export_dom Exportiert ein SimpleXML-Objekt als DOM-Knoten. simplexml_load_file Lädt eine XML-Datei und gibt ein SimpleXML-Objekt zurück. simplexml_load_string Lädt XML aus einer Zeichenkette und gibt ein Simple- XML-Objekt zurück. Tabelle 14.5: Funktionen, die SimpleXML-Objekte erzeugen oder verarbeiten Methode Beschreibung AsXML Exportiert den Inhalt als XML-Zeichenkette. attributes Die Liste der Attribute eines Elements. children Die Aufzählung der Kindelemente eines Elements. xpath Führt eine XPath-Abfrage aus. registerXPathNamespace Registriert einen Namensraumalias für die Verwendung in XPath-Ausdrücken Tabelle 14.6: Methoden des SimpleXML-Objekts 620
  • 621.
    Referenz Referenz DOM Klasse Bedeutung DomAttr Repräsentiert ein Attribut. DomCData Repräsentiert einen CDATA-Abschnitt. DomComment Repräsentiert einen Kommentar. DomDocument Die Basisklasse, das Dokument selbst. DomDocumentType Die DOCTYPE-Deklaration des Dokuments. DomElement Repräsentiert ein Element. DomEntity Repräsentiert eine Entität, beispielsweise amp;. DomEntityReference Repräsentiert eine Verweis-Entität (auf eine externe Datei). DomNode Allgemeine Knotenklasse (kann alles außer DomDocument sein). DomProcessingInstruction Repräsentiert eine Prozessanweisung, beispielsweise ?id? DomText Frei stehender Text zwischen Elementen. DomXPath Erlaubt Zugriff per XPath-Abfrage auf das Dokument. Tabelle 14.7: Liste der Klassen, die im DOM-Modul definiert sind Methode Bedeutung createAttributeNS Erzeugt ein Attribut mit Namensraum. createAttribute Erzeugt ein Attribut. createElementNS Erzeugt ein Element mit Namensraum. createElement Erzeugt ein neues Element. createTextNode Erzeugt einen Textknoten. Tabelle 14.8: Methoden der Klasse DomDocument 621
  • 622.
    XML und Webservices Methode Bedeutung createDocumentFragment Erzeugt ein Fragment neuer Knoten. createComment Erzeugt einen Kommentarknoten !-- .. --. createCDATASection Erzeugt einen CDATA-Abschnitt. createProcessingInstruction Erzeugt eine Prozessanweisung. createAttribute Erzeugt ein Attribut. createEntityReferenz Erzeugt einen Verweis. getElementsByTagName Gibt eine Liste aller Elemente mit dem gegebenen Namen zurück. getElementsByTagNameNS Gibt eine Liste aller Elemente mit dem gegebenen Namen in einem bestimmten Namensraum zurück. getElementById Gibt ein Element mit einem bestimmten ID-Attribut zurück. renameNode Benennt einen Knoten um. load Lädt ein Dokument aus einem Stream (beispielsweise eine Datei). save Speichert das Dokument. loadXML Lädt XML aus einer Zeichenkette. saveXML Speichert XML als Zeichenkette. loadHTML Lädt eine HTML-Datei. saveHTML Speichert eine HTML-Datei. validate Prüft gegen eine DTD. validateSchema Prüft gegen ein Schema (XSD). validateRelaxNG Prüft gegen ein RelaxNG-Schema. insertBefore Fügt vor einem anderen Knoten ein. replaceChild Ersetzt ein Kindelement. Tabelle 14.8: Methoden der Klasse DomDocument (Forts.) 622
  • 623.
    Referenz Methode Bedeutung removeChild Entfernt ein Kindelement. appendChild Hängt ein Kindelement an. hasChildNodes Prüft, ob Kindelemente vorhanden sind. cloneNode Klont einen Knoten. hasAttributes Ermittelt, ob ein Element Attribute hat. isSameNode Vergleicht zwei Knoten auf Identität. isDefaultNamespace Prüft, ob der Knoten im Standardnamensraum ist. isEqualNode Vergleicht zwei Knoten auf Gleichheit. Tabelle 14.8: Methoden der Klasse DomDocument (Forts.) Die Methode getElementById gibt Element-Objekte vom Typ DomElement zurück. Die Methoden getElementsByTagName und getElementsByTagNameNS geben Auflis- tungen mit Elementen des Typs DomNode zurück (interne Klasse DomNodeList). Methode Bedeutung getAttribute Ermittelt ein Attribut. setAttribute Erzeugt und definiert ein Attribut. removeAttribute Entfernt das Attribut. getAttributeNS Ermittelt ein Attribut in einem bestimmten Namensraum. setAttributeNS Erzeugt und definiert ein Attribut in einem bestimmten Namens- raum. removeAttributeNS Entfernt das Attribut in einem bestimmten Namensraum. getAttributeNode Ermittelt das Attribut als Objekt vom Typ DomAttr. setAttributeNode Setzt ein Attribut auf Basis eines DomAttr-Objekts. setIdAttribute Setzt das Attribut mit den Namen ID. Tabelle 14.9: Methoden der Klasse DomElement 623
  • 624.
    XML und Webservices Methode Bedeutung setIdAttributeNS Setzt das Attribut mit den Namen ID in einem bestimmten Namensraum. isSameNode Vergleicht zwei Attribute auf Identität. isDefaultNamespace Prüft, ob der Attribute im Standardnamensraum ist. isEqualNode Vergleicht zwei Attribute auf Gleichheit. Tabelle 14.9: Methoden der Klasse DomElement (Forts.) Die Methode getAttributeNode gibt ein Objekt vom Typ DomAttr zurück. Eigenschaft Bedeutung textContent Inhalt des Elements als Text (ohne Kindelemente). nodeType Siehe DomNode. Ist bei Elementen immer XML_ELEMENT_NODE. childNodes Auflistung von Kindelementen als DomNodeList, eine aufzählbare Liste von DomNode-Objekten. tagName Name des Elements Tabelle 14.10: Eigenschaften der Klasse DomElement Methode Bedeutung isId Das Attribut ist ein ID-Attribut. insertBefore Fügt ein Attribut vor dem aktuellen ein. isSameNode Vergleicht zwei Attribute auf Identität. isDefaultNamespace Prüft, ob das Attribut im Standardnamensraum ist. isEqualNode Vergleicht zwei Attribute auf Gleichheit. Tabelle 14.11: Methoden der Klasse DomAttribute 624
  • 625.
    Referenz Eigenschaft Bedeutung value Wert des Attributes. name Name des Attributes. specified Gibt TRUE zurück, wenn das Attribut seinen aktuellen Wert aus dem Dokument erhielt und nicht erst durch spätere Änderung. ownerElement Gibt das DomElement zurück, zu dem dieses Attribut gehört. Tabelle 14.12: Methoden der Klasse DomAttribute Name der Konstanten Bedeutung XML_ELEMENT_NODE Element XML_ATTRIBUTE_NODE Attribut XML_TEXT_NODE Textknoten XML_CDATA_SECTION_NODE CDATA-Abschnitt XML_ENTITY_REF_NODE Entity-Verweis XML_ENTITY_NODE Entity-Knoten XML_PI_NODE Prozessanweisung XML_COMMENT_NODE Kommentar XML_DOCUMENT_NODE Dokument XML_DOCUMENT_TYPE_NODE Document Type Deklaration XML_DOCUMENT_FRAG_NODE Dokumentfragment XML_NOTATION_NODE Notation (in einer DTD) XML_GLOBAL_NAMESPACE Globaler Namensraum XML_LOCAL_NAMESPACE Lokaler Namensraum XML_HTML_DOCUMENT_NODE Dokument im HTML-Modus Tabelle 14.13: Konstanten des DOM-Moduls 625
  • 626.
    XML und Webservices Nameder Konstanten Bedeutung XML_DTD_NODE Document Type Definition XML_ELEMENT_DECL_NODE Element-Deklaration einer DTD XML_ATTRIBUTE_DECL_NODE Attribut-Deklaration einer DTD XML_ENTITY_DECL_NODE Entitäts-Deklaration einer DTD XML_NAMESPACE_DECL_NODE Namensraum-Deklaration XML_ATTRIBUTE_CDATA CDATA-Teil eines Attributes XML_ATTRIBUTE_ID ID-Attribut XML_ATTRIBUTE_IDREF IDREF-Attribut XML_ATTRIBUTE_IDREFS Kollektion von IDREF-Attributen XML_ATTRIBUTE_ENTITY Entität in einem Attribut XML_ATTRIBUTE_NMTOKEN NMTOKEN XML_ATTRIBUTE_NMTOKENS Aufzählung von NMTOKEN Elementen XML_ATTRIBUTE_ENUMERATION Attribut-Aufzählung XML_ATTRIBUTE_NOTATION Attribut-Notation Tabelle 14.13: Konstanten des DOM-Moduls (Forts.) 14.6 Kontrollfragen 1. Welche Technologien sind wichtig, wenn mit XML gearbeitet wird? 2. Welche Methode der Verarbeitung ist bei einem ca. 12 MByte großen XML- Dokument wahrscheinlich die beste, SAX oder DOM? Begründen Sie die Ant- wort. 3. Nennen Sie typische Erscheinungsformen eines so genannten Knotens in einem XML-Dokument. 4. Welche Bedeutung hat die DTD (Document Type Definition)? 626
  • 627.
    Antworten auf die Kontrollfragen 1 5
  • 628.
    Antworten auf dieKontrollfragen 1. Tag 1. Wann wurde PHP das erste Mal unter diesem Namen bekannt? A 1995, als PHP/FI. Damals war es noch einen Sammlung von Perl-Skripten. 2. Was bedeutet »PHP«? A Dies ist ein so genanntes rekursives Acronym: PHP Hypertext Preprocessor. 3. Worauf ist der Name »Zend« zurückzuführen? A Ein Kunstwort aus Namensteilen der Gründer der Firma Zend, Ze von Zeev Suraski und nd aus Andi Gutmans. Zend stellt den Sprachkern für PHP bereit und vertreibt außerdem kommerzielle Werkzeuge für PHP, wie beispielsweise eine IDE, Verschlüsselungs- und Cachetools. 4. Anhand welcher Begrenzungszeichen werden PHP-Fragmente in HTML-Code erkannt? A ?php und ?. Andere Varianten werden nicht empfohlen. De Kurzform ?=$var? zum Einbetten von Variablen sollte nur in Ausnahmefällen benutzt werden. 5. Welches Protokoll wird zur Übertragung von HTML-Seiten vom Server zum Browser verwendet? A HTTP – Hypertext Transfer Protocol. 6. Welcher Webserver kann auf allen Betriebssystemen zur Entwicklung eingesetzt werden? A Der Apache-Webserver ist auf allen Plattformen zu Hause. 7. Wofür steht der Begriff WAMP? A W = Windows, A = Apache, M = MySQL, P = PHP. Die beliebteste Kom- bination für PHP-Entwickler, wird von weit über 90% der Anwender benutzt. 8. Welche Bedeutung hat die Adresse »http://localhost«? A Verweis auf den lokalen Webserver (!). Der Name weicht meist vom Namen des lokalen Computers ab. Die zugehörigen IP-Adresse ist 127.0.0.1. 628
  • 629.
    2. Tag 9. Mitwelcher Funktion kann PHP5 ausführlich getestet werden? A phpinfo() heißt die Funktion. Folgendes Skript zeigt, wie die Funktion genutzt wird: ?php phpinfo(); ? 2. Tag 1. Wie geben Sie mehrzeilige Texte aus PHP-Code heraus am besten aus? A Mit der Heredoc-Syntax werden mehrzeilige Texte aus PHP-Code heraus ausgegeben: ?php echo TEXT table !-- Es folgt viel Text -- /table TEXT; ? 2. Welche Funktion eignet sich zur Ausgabe hexadezimaler Farbangaben? A printf könnte hilfreich sein. Die Option %2X erzeugt zweistellige hexade- zimale Zahlen. Als Eingabewert dienen alle Zahlenliterale, also beispiels- weise 32 (Dezimal) oder 0x20 (Hexadezimal) oder 040 (Oktal). 3. Welche Kommentarform wird am besten verwendet, wenn hinter einer Codezeile ein kurzer, auf die Zeile bezogener Kommentar stehen soll? Mögliche Varianten sind: /* */, // oder #. A Es sollte immer // verwendet werden. # ist unüblich und /* scheitert, wenn umschließende mehrzeilige Kommentare benutzt werden, um den Block auszukommentieren. 629
  • 630.
    Antworten auf dieKontrollfragen 3. Tag 1. Was ist ein Literal? A Jede Form von im Quellcode direkt geschriebener konstanter Werte, also beispielsweise 0x22, 345.66, string usw. 2. Wie werden Konstanten definiert? A Konstanten werden mit der Funktion define erstellt. 3. Wozu werden reguläre Ausdrücke eingesetzt? A Reguläre Ausdrücke werden zum Suchen und Ersetzen benutzt, wenn die Suchmuster komplex oder in sich variabel sind. 4. Schreiben Sie ein Skript, dass anhand mehrerer Parameter ein span-Tag mit kor- rekter hexadezimaler Angabe der Vordergrund- und Hintergrundfarbe mit Hilfe des style-Attributes erstellt und ausgibt. Tipp: Verwenden Sie printf. A printf ist aufgrund der Parameter gut geeignet, um Tags übersichtlich zu erstellen. ?php printf('span style=color:#%2X%2X%2X; background-color:#%2X%2X%2X', 0xFF, 0x00, 0xCC, 0xDD, 0xDD, 0xDD); ? 4. Tag 1. Schreiben Sie ein Skript, das beliebige Zahlen zwischen 0 und 100 auf ganze Zehner rundet. A Die Antwort ist verblüffend einfach: Verwenden Sie die Funktion round. Bei der Angabe der Anzahl »Kommastellen« auf die gerundet werden soll, sind auch negative Zahlen erlaubt, was dann zum Runden vor dem Komma führt. Das folgende Skript prüft das Verhalten für ein paar Testzahlen: 630
  • 631.
    4. Tag ?php $test = array(4.5, 25, 67, 99.4); foreach ($test as $number) { echo round($number, -1); echo 'br'; } ? Ausgegeben werden die Zahlen : 0, 30, 70 und 100. 2. Können mit switch-Anweisungen auch Vergleiche mit beliebigen Booleschen Ausdrücken erfolgen? A Ja, denn PHP behandelt die Ausdrücke erst zur Laufzeit und erledigt dann den Vergleich. Wenn man im Verzweigungskopf den Wert TRUE schreibt, müssen die Ausdrücke auch TRUE oder FALSE zurückgeben, sodass ein Test auf Gleichheit funktioniert. Wichtig ist jedoch darauf zu achten, dass immer nur ein (oder kein) Zweig die Bedingung zum Zeitpunkt der Abfrage erfüllt, sonst ist die Reaktion nicht korrekt. switch(TRUE) { case $test 9 $test 4: // Reaktion break; } 3. Welche Schleife wird benutzt, wenn sichergestellt werden muss, dass der Schlei- fenkörper mindestens einmal durchlaufen wird? A Die do-Schleife. Da der Test erst am Ende mit Hilfe der while-Bedingung erfolgt, wird der Körper garantiert einmal durchlaufen, auch wenn die Bedingung dann nicht erfüllt ist und die Schleife sofort wieder verlassen 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? A Die Bedingung für den Ausstieg wird mit if erstellt, der Ausstieg erfolgt dann mit break. switch ist nicht geeignet, weil die Zweige selbst mit break enden und ein weiteres break nicht mehr ausgeführt wird. 631
  • 632.
    Antworten auf dieKontrollfragen 5. Tag 1. Worin unterscheiden sich normale von assoziativen Arrays? A Assoziative Arrays verwenden Schlüsselwerte, die meist als Zeichenketten vorliegen, zur Adressierung der Elemente, beispielsweise $arr[?name?]. »Normale« Arrays haben dagegen immer Zahlen, über die Elemente erreicht werden, beispielsweise $arr[8]. 2. Wie kann ein Element eines Array gezielt entfernt werden? A Mit unset kann ein Element entfernt werden. PHP organisiert die Schlüs- selwerte einfacher Arrays danach nicht neu. Sie müssen das selbst erledi- gen, wozu die Funktion array_values dient: ?php $test = array(4.5, 25, 67, 99.4); unset($test[2]); $test = array_values($test); foreach ($test as $index = $number) { echo $index = $number; echo 'br'; } ? A Die Ausgabe der Indizes lautet 0, 1, 2. Ohne array_values wäre es 0, 1, 3. 3. Schreiben Sie ein Skript, dass Arrays nutzt um Namen von Mitarbeitern zu spei- chern. Erweitern Sie das Skript, sodass zu jedem Mitarbeiter im selben Array zusätzliche Informationen gespeichert werden. Tipp: Nutzen Sie verschachtelte assoziative Arrays dafür. A Sie können innerhalb einer array-Definition für den Wert ein weiteres Array verwenden. Als Schlüssel sind dagegen nur Zahlen oder Zeichenket- ten erlaubt. Beachten Sie jedoch, dass mehr als drei Ebenen nur schwer handhabbar sind und meist auf einen Designfehler beim Entwurf des Datenmodells hindeuten. ?php $ma = array( 'Bernd Muster' = array('030/12345', 'Raum 123'), 'Katja Muster' = array('089/54321', 'Raum 999'), ); 632
  • 633.
    6. Tag foreach ($ma as $name = $details) { echo $name = $details[0], $details[1]; echo 'br'; } ? 6. Tag 1. Schreiben Sie ein Skript, dass die Namen von Mitarbeitern in einer eigens dafür entwickelten Klasse speichert. A Sie sollten auch bei derart einfachen Aufgabenstellungen grundsätzlich versuchen, die elementaren OOP-Techniken zu verwenden. Dazu gehört der Schutz der Daten in privaten Mitgliedern und der Zugriff über __get bzw. __set und die Verwendung eines sinnvollen Konstruktors. ?php class Employee { private $_name; private $_telephon; private $_room; public function __construct($n, $t, $r) { $this-_name = $n; $this-_telephon = $t; $this-_room = $r; } public function __get($property) { switch ($property) { case 'name': return $this-_name; case 'telephon': return $this-_telephon; case 'room': 633
  • 634.
    Antworten auf dieKontrollfragen return $this-_room; } } } $ma = array(new Employee('Bernd Muster', '0172/654321', '215'), new Employee('Katja Muster', '0172/123456', '948')); foreach ($ma as $em) { echo {$em-name} = {$em-telephon}, {$em-room}; echo 'br'; } ? 2. Erklären Sie den Unterschied zwischen private, public und protected. A Mitglieder, die in einer Klasse als private gekennzeichnet sind, können nur von Methoden der Klasse selbst benutzt werden. Mitglieder, die in einer Klasse als public gekennzeichnet sind, können von Methoden oder Funktionen des gesamten Skripts benutzt werden. Mitglieder, die in einer Klasse als protected gekennzeichnet sind, können nur von Methoden der Klasse selbst und solchen einer von dieser Klasse abgeleiten Klasse benutzt werden. 3. Sie haben nur eine einzige Klasse in Ihrem Skript. Ist die Anwendung des Schlüs- selwortes protected sinnvoll? Begründen Sie die Antwort. A Nein, die Anwendung ist nicht sinnvoll. Sie können das so gekennzeich- nete Mitglied nicht aus Ihrem Skript erreichen und für die Benutzung innerhalb der Klasse wäre private ausreichend. 4. Welchen Vorteil bietet die Verwendung von __get und __set anstatt des direkten Zugriffs auf öffentliche Eigenschaften, die mit public $name gekennzeichnet sind? A Die Pseudo-Eigenschaften können den Weg der Daten in die Eigenschaft hinein und wieder heraus kontrollieren. Man kann so sicherstellen, dass niemals ungültige Daten im Objekt gespeichert sind. Man kann auch sicherstellen, dass niemals ungültige Daten das Objekt verlassen. Beides trägt zur Codesicherheit bei. Außerdem kann eine Klasse weitaus mehr Eigenschaften bereitstellen, als Mitgliedsvariablen vorhanden sind. Die interne Form der Datenspeicherung kann also anderen Optimierungskrite- rien unterliegen als die Darstellung der Daten nach außen. 634
  • 635.
    7. Tag 5. Wieschreiben Sie eine Klasse, deren Objekte beim Aufruf von new einen definier- ten Anfangszustand unabhängig von Parametern erhalten soll? A Sollen die Daten des Anfangszustands nicht variabel sein, bietet sich der Einsatz von Konstanten an. Im Konstruktor werden die Konstanten dann den Eigenschaften zugewiesen. Dies erleichtert die Wartung der Klasse. 6. Wie schreiben Sie eine Klasse, von der nur eine Instanz erzeugt werden darf? Wie nennt man dieses Entwurfsmuster? A Das Muster wird Singleton genannt. Man deklariert den Konstruktor pri- vat, um den wiederholten Aufruf zu verhindern. Ersatzweise schafft man eine Methode, in der mittels new eine Instanz erzeugt und zurückgegeben wird. Die Methode speichert die erzeugte Instanz in einer Variablen und prüft beim erneuten Aufruf, ob die Instanz bereits existiert. Ist das der Fall, wird die bereits existente Version zurückgegeben, die zugleich die einzige Instanz der Klasse ist. 7. Tag 1. Auf welche Datenquellen können die Dateifunktionen zugreifen? A Dank des universellen Stream-Konzeptes können Dateifunktionen aus beliebigen Datenquellen zurückgreifen. Bereits fertig implementiert ist der Zugriff auf Dateien des Dateisystems, Ein-/Ausgabekanäle des Betriebssys- tems, Webserver per HTTP und FTP-Server. Für andere Datenquellen las- sen sich benutzerdefinierte Wrapper erstellen. 2. Warum muss eine Datei nach der Benutzung wieder geschlossen werden? A Der schreibende Zugriff auf Dateien ist exklusiv, damit sich die Änderun- gen nicht unbemerkt überschreiben. Um künftige Zugriffe nicht zu verzö- gern, sollten Dateien schnell wieder geschlossen werden. Bedenken Sie, dass Webserver Multiuser-Zugriffe gestatten, ein Skript also gleichzeitig mehrfach starten kann. 635
  • 636.
    Antworten auf dieKontrollfragen 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’]. A Zuerst benötigt man eine Dateiliste. Hier ist das Verzeichnisobjekt dir oder der DirectoryIterator der SPL eine gute Wahl. 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. A Der GET-Parameter lässt sich im Browser leicht manipulieren. Da die meisten Dateifunktionen auch Pfade mit relativen Angaben wie ../../../ ver- arbeiten können, lassen sich so leicht Dateien auslesen, die nicht zum freien Zugriff gedacht sind. Auch wenn das Betriebssystem meist verhin- dert, dass Systemdateien gelesen werden können, reicht oft die Auswahl einer Include-Datei oder anderer PHP-Skripte, um an Kennwörter oder Systeminformationen zu gelangen. Zur Lösung arbeitet man mit Hashwerten oder ID-Nummern, sodass im Link nur Werte stehen, die keinen Rückschluss auf die Datei zulassen. Den Dateinamen einfach als MD5-Hash abzulegen ist freilich keine Lösung, denn Profis erkennen derartige Hashes sehr schnell und können sie ebenso schnell auch erzeugen. 8. Tag 1. Warum sollten unbedingt immer »Sticky Forms« verwendet werden? A Es ist ausgesprochen lästig für den Benutzer, wenn er nach einem Fehler die mühevoll eingegebenen Daten verliert. Formulare sind ohnehin die Stelle mit der höchsten Abbruchquote. Schlecht gemachte Formulare stei- gern den Misserfolg der Site enorm. Inzwischen gilt der Verzicht auf »Sticky Forms« auch einfach nur als unprofessionell. 636
  • 637.
    9. Tag 2. WelcheFunktionen unterstützen das Hochladen von Dateien? Was ist beim Auf- bau des Formulars zu beachten? A Die wichtigste Funktion ist move_uploaded_file, eingesetzt zum Transport der Dateien zum endgültigen Ziel. Beim Formular ist zu beachten, dass das Attribut enctype im Form-Tag angegeben wird, damit der Browser die Daten korrekt verpackt. 3. Welche Methoden verwenden Sessions, um die Session-Daten zu speichern? A Zum Speichern werden Dateien oder Datenbanken benutzt, seltener der Systemspeichern. PHP5 unterstützt standardmäßig Dateien. Beachten Sie, dass die Sessiondaten, die im Sessioncookie gespeichert werden, lediglich eine Referenznummer auf die im Webserver oder einer Datenbank gespei- cherten Daten enthalten. 4. Wie können Cookies missbraucht werden? A Cookies können mit jeder Anforderung gesendet werden. Da die Objekte einer Website nicht vom selben Server stammen müssen, können beim Laden einer Website auch Cookies verschiedener Server übermittelt wer- den. Besucht derselbe Benutzer eine andere Website, wobei ein Objekt von demselben Server stammt wie beim Besuch der vorhergehenden Site, erhält der Server das Cookie von der zweiten Site wieder zurück. Damit kann man Bewegungsprofile erstellen, die den Weg des Benutzers durchs Web markieren. Firmen wie Doubleclick nutzen dies als Geschäftsmodell, um kundenspezifische Werbebanner zu schalten. 9. Tag 1. Welche Servervariable wird benutzt, um die bevorzugte Sprache des Benutzers zu ermitteln? A HTTP_ACCEPT_LANGUAGE kann verwendet werden. Zur Abfrage schreiben Sie: $lang = $_SERVER['HTTP_ACCEPT_LANGUAGE']; 637
  • 638.
    Antworten auf dieKontrollfragen 2. Welcher Header wird in HTTP benötigt, um dem Browser anzuzeigen, dass ein Bild gesendet wird? A Benötigt wird Content-type:. Als Parameter ist image/typ anzugeben, wobei der Typ dem Bildformat entspricht: Content-type: image/gif 3. Schreiben Sie ein Skript, dass dezimale Farbangaben wie 255, 192, 128 in hexa- dezimale Zeichenfolgen der Art »#FFCC99« umrechnet und umgekehrt. A Nutzen Sie die Funktion printf dafür. Folgende Zeile muss nur noch in eine Funktion eingebaut werden: printf('#%2X%2X%2X', 255, 192, 128); 4. Wozu dient das Werkzeug Reflection? A Mittels Reflection kann Code analysiert werden. 10. Tag 1. Welche Aufgabe erfüllen Wrapper? A Wrapper dienen dazu, benutzerspezifische Ein-/Ausgabekanäle zu schaf- fen, sodass beispielsweise die normalen Dateifunktionen darauf zugreifen können. 2. Was muss ein fremder Entwickler beachten, wenn er einen von Ihnen entwickeltes Filter verwendet? A Nichts – Filter sind für die Benutzung transparent. 3. Was bedeuten die Kopfzeilen Cc: und Bcc: in einer E-Mail? A Cc: (Carbon Copy, Durchschlag) sendet eine Kopie der E-Mail an den angegebenen Empfänger, wobei alle anderen Empfänger dessen Adresse sehen. Bcc: dagegen sendet ebenso eine Kopie, andere Empfänger können dies aber nicht sehen (Blind Carbon Copy, Blinddurchschlag). 4. Welches Protokoll nutzt die mail-Funktion zur Übertragung der Daten? A Diese Funktion überträgt die Daten mittels SMTP zum Server. 638
  • 639.
    11. Tag 11. Tag 1.Warum sollte die neue Bibliothek MySQLi anstatt der alten eingesetzt werden? A MySQLi ist auf die Datenbank MySQL 4 abgestimmt. Außerdem unter- stützt das neue Modul die objektorientierte Programmierung. 2. Was versteht man unter Normalisierung? A Stark vereinfacht die Vermeidung redundanter Datenhaltung in einer rela- tionalen Datenbank. 3. Sie müssen bei der Ausgabe von Daten aus einer MySQL-Datenbank Datums- formate anpassen und n in br umwandeln. Wie gehen Sie vor? A MySQL verfügt über einen reichen Vorrat an Zeichenkettenfunktionen. Suchen Sie eine Funktion zum Suchen und Ersetzen, die dies erledigt. Dies ist meist schneller als nl2br im PHP-Skript. 4. Wie würden Sie eine Abfrage formulieren, bei der aus zwei Tabellen Daten zugleich entnommen werden müssen? Welche Voraussetzungen müssen die Tabellen erfüllen. A Dies ist ein so genannter JOIN: SELECT t1.feldname, t2.feldname FROM tabelle1 t1 INNER JOIN tabelle2 t2 ON t1.id = t2.id Die Schreibweise tabelle1 t1 definiert einen Aliasnamen für tabelle1. Damit lassen sich gleichnamige Felder auseinander halten, ohne die mög- licherweise langen Tabellennamen schreiben zu müssen. t1.id = t2.id ver- knüpft die beiden Tabellen miteinander, indem die Primärschlüssel verglichen werden. t1.feldname, t2.feldname ruft dann aus jeder Tabelle eine bestimmte Spalte ab. 12. Tag 1. Was müssen Sie tun, um SQLite benutzt zu können? A Nichts, SQLite gehört zum Standardumfang von PHP5. 639
  • 640.
    Antworten auf dieKontrollfragen 2. Welche Datentypen kennt SQLite? A Zahlen und Zeichen, alle anderen Datentypnamen sind nur aus Kompati- bilitätsgründen im Umfang reservierter Namen. SQLite prüft sonst keine Daten sondern behandelt diese immer als Zeichenketten. 3. Sie möchten mehrere Tausend Datensätze für eine hoch frequentierte Website verwalten. Ist SQLite dafür die beste Wahl? Begründen Sie die Antwort? A SQLite ist dafür nicht geeignet, weil die Verwaltung der Datenbank auf Dateiebene stattfindet. Während des Zugriffs auf eine Tabelle ist die Datei gesperrt, was konkurrierende Zugriffe unmöglich macht. Bei hoher Last wird SQlite deshalb sehr langsam. 4. Welche Aufgabe hat der Befehl VACUUM? A Der Befehl verdichtet die Datenbank nach Löschvorgängen. Die separate Optimierung verhindert, dass einfache Löschvorgänge zu lange dauern. 13. Tag 1. Wozu werden Template-Systeme eingesetzt? A Template-Systeme dienen dazu, Code und Layout zu trennen. 2. Erweitern Sie die Applikation, sodass zu jedem Leser der Bibliothek mehrere Tele- fonnummern gespeichert werden können. Wie gehen Sie vor? A Legen Sie eine weitere Tabelle in der Datenbank an. Erweitern Sie die Tabelle der Leser um einen ID-Spalte. Verknüpfen Sie die Spalte mit der neuen Telefon-Tabelle und einer dort befindlichen Korrespondenz-Spalte. Sie benötigen also mindestens drei Spalten in der Telefon-Tabelle: id (Pri- märschlüssel), telephon-id (Verknüpfung, gleiche Werte können freilich mehrfach auftreten, wenn ein Kunde mehrere Telefonnummern hat), tele- phon (die eigentlichen Daten). 3. Formulieren Sie eine SQL-Abfrage, die die nötigen Daten zur Erstellung von Mahnschreiben erzeugt, um säumige Leser auf die bereits abgelaufene Rückga- befrist hinzuweisen. A Der folgende Ausdruck (Teil der Abfrage) wird TRUE, wenn die Ausleihfrist abgelaufen ist: TO_DAYS(CURDATE())-TO_DAYS(lendingdate) lendingperiod 640
  • 641.
    14. Tag 14. Tag 1.Welche Technologien sind wichtig, wenn mit XML gearbeitet wird? A Neben XML benötigen Sie eine Definitionssprache, also DTD oder XML- Schema. Außerdem zur Verarbeitung XSLT und XPath. 2. Welche Methode der Verarbeitung ist bei einem ca. 12 MByte großen XML- Dokument wahrscheinlich die beste, SAX oder DOM? Begründen Sie die Ant- wort. A SAX ist besser. Die sequenzielle ereignisbasierte Verarbeitung ist optimal bei großen Datenmengen. DOM würde das Dokument als Ganzes versu- chen im Speicher zu halten und dabei sehr viel Speicherplatz benötigen, sodass der Zugriff sehr langsam wird. 3. Nennen Sie typische Erscheinungsformen eines so genannten Knotens in einem XML-Dokument. A Neben Elementen sind auch Kommentare, Attribute, CDATA-Abschnitte und Prozessanweisungen Teil eines XML-Dokuments. Da das gesamte Dokument als Baum dargestellt werden kann, dessen Verzweigungen Kno- ten sind, können Knoten praktisch jeden Teil des Dokuments darstellen. 4. Welche Bedeutung hat die DTD (Document Type Definition)? A Die DTD definiert die in einem XML-Dokument zulässigen Elemente (deren Namen) und deren Attribute sowie die Abhängigkeiten zwischen diesen. Dazu gehört beispielsweise, welche Elemente in welcher Zahl Kin- delemente anderer sein dürfen. Ohne DTD kann man lediglich prüfen, ob ein XML-Dokument wohlgeformt ist. Mit DTD kann man prüfen, ob es außerdem gültig ist. Jedes XML-Dokument sollte eine DTD haben oder alternativ das neuere XML-Schema, das einem vergleichbaren Zweck dient. 641
  • 643.
    Stichwortverzeichnis Symbols Ausgaben 55 $_COOKIE 351 – formatieren 59 $_REQUEST 355 AUTO_INCREMENT (SQL) 451 - 208 AVG (SQL) 455 __autoload 172 __call 227 B __clone 218 Backslash 84 __construct 210 Bad Request 42 __destruct 211 Base64 428 __FILE__ 233 basename 254 __get 229 Benennungsregeln 65 __LINE__ 233 Benutzerverwaltung (mit SQLite) 501 __METHOD__ 233 BETWEEN (SQL) 448 __set 229 Bilder 49 __toString 226, 234 – Funktionen 385 _subclass_of 237 – Zeugung 381 BinHex 428 A Boolesches 81 Abfragesyntax 446 break 137, 141 Abstammung 235 Browserdaten erkennen 374 abstract 217 Achsenbezeichner 564 C Addition 79 case 137 Ähnlichkeiten 88 catch 178 aggregierende Funktionen (SQL) 454 childNodes 579 Algebra, logische 81 children (SimpleXML) 587 ALL (SQL) 454 chunk_split 94 alternative Zweige 136 class 207 Anführungszeichen 85 clone 218 Anhänge versenden 434 Coding Standards 65 Apache continue 141 – installieren 30 Cookies 349 – testen 35 – Beschränkungen 352 ArGoSoft Mail Server 429 – Personalisierung 352 Array 182 copy 335 – Assoziative 191 count 184 – erstellen 183 COUNT (SQL) 454 – Funktionen 198 CREATE TABLE (SQL) 449 – Werte 186 current 188 – Zeiger 188 current (Iterator) 265, 265 array_walk 196 Arraydaten 193 D ASC (SQL) 456 D. Richard Hipp 498 ASCII-Zeichensatz 91 date 111 Assoziativität 133 Dateien hochladen 334 attributes (SimpleXML) 586 Dateisystem 244 Attributknoten 563 Dateizugriff 246 Ausdrücke 78 Datenspeichermethoden 358 643
  • 644.
    Stichwortverzeichnis Datentypen 74, 83 fclose 251 Datentypen (SQL) 449 Fehler (PHP) 42 Datum 110 – Behandlung 173 default 138 – Codes 177 DEFAULT (SQL) 451 – Klassen erstellen 224 define 76 – Nummer 176 defined 77 – Text 176 Dekrement 79 fgets 251 DELETE (SQL) 453 file_get_contents 420 DESC (SQL) 456 Filter 422, 440 Destruktoren 210 final 216 dir 260 fnmatch 260 DirectoryIterator 266 fopen 251 DISTINCT (SQL) 454 for 142 Division 79 foreach 194 DNS 70 Formatieren 119 do 148 – Zahlen 119 DOM 576 Formatregel 60 – domAttr (Klasse) 624 Formulare 52 – Funktionen 576 – bauen 302 – Konstanten 625 fscanf 120 Domain Name System siehe DNS func_get_arc 156 DomAttr 624 func_get_args 156 DomDocument 579 func_num_args 156 – getAttributeNode 624 function 150 – getElementsById 623 Funktionen 150 – getElementsByTagName 623 – Referenzen 159 DomXPath 566 – Rekursive 164 DROP (SQL) 453 – Variable 166 Dropdown-Listen 295 Funktionsdefinition 150 DTD 553 dynamisch Bilder erzeugen 379 G dynamischer Werbebanner 386 GD2 379 Geschichte 22 E GET 341 each 195 get_included_files 172 echo 55 get_meta_tags 419 Eigenschaftenaufruf 229 get_required_files 172 Elementknoten 563 getElementsByTagName 579 else 136 getMessage 225 Elternknoten 563 gleich 82 E-Mail 426 glob 257 – versenden 430 global 162 empty 287 globale Variablen 162 Entitäten 93 Grafikbibliothek 379 Ersetzen 87 Grafiken 379 Exception 224 Größe (Datentyp) 83 exklusives Oder 83 größer als 82 Extensible Markup Language siehe XML Große Textmengen 57 GROUP BY (SQL) 454 F Gruppierungen 98 Factory-Klassen 235 Gruppierungen (SQL) 455 Farbrad 146 Gutmans, Andi 22 644
  • 645.
    Stichwortverzeichnis H Konfigurationsschritte 38 Handle 244 Konstanten 76 Hashes 186 Konstruktoren 210 HAVING (SQL) 454 Kontext 440 header 380 Kontrollkästchen 287 heredoc 57 Krause, Jörg 17 Herkunft erkennen 282 Hinweistexte platzieren 303 L hochladen 334 Laufzeitfehler 175 HTML 46 LC_TIME 116 – Formularelemente 278 LEFT JOIN (SQL) 459 – Mails 434 libxml2 575 htmlentities 93 LIKE (SQL) 448 HTTP 71 links-assoziativ 134 HTTP_REFERER 282 list 195 httpd.conf 40 Literale 74 loadHtmlFile 579 I localeconv 378 iconv-Modul 340 Lokalisierung 376 idate 116 if 132 M imagecreate 381 Mail 426 imagecreatefromgif 382 MAX (SQL) 455 imagecreatefromjpeg 382 MAX_FILE_SIZE 337 imagecreatefrompng 382 MD5 347 imagegif 382 Mehrfachauswahl 297, 322 imagettfbbox 382 Mehrfachverzweigungen 137 imagettftext 383 mehrseitige Formulare 325 IMAP4 427 mehrsprachige Webseiten 374 implements 221 Message Digest Version 5 347 IN (SQL) 448 MIME 428 include 169 MIN (SQL) 455 include_once 169 mktime 111 Inkrement 79 Modularisierung 168 Inner Join 458 Module einbinden 168 INSERT INTO (SQL) 451 Modulus 79 instanceof 236 Moniker 416 Interfaces 221 move_uploaded_files 335 is_a 237 MSI Installer 30 is_resource 251 multipart/form-data 334 ISO-8859-1 91 Multiplikation 79 Multipurpose Internet Mail Extensions siehe J MIME JavaScript 47, 303 MySQL – lokale Zeit 117 – Aggregat-Funktionen 467 JOIN (SQL) 459 – Beispielprojekt 510 – Control Center 513 K – Datentypen 462 key 188 – Datumsformatierungen 475 Kindknoten 563 – Datumsfunktionen 473 Klasse 207 – Dialekt 461 kleiner als 82 – Funktionen 464 Kombinationsoperatoren 81 – Kontrollflussfunktionen 466 Kommentare 62 – Mathematische Funktionen 465 645
  • 646.
    Stichwortverzeichnis – Systemfunktionen 468 PHP5 23 – Verschlüsselungsfunktionen 469 – installieren 37 – Zahlenformatierungen 473 – konfigurieren 37 – Zeichenkettenfunktionen 470 phpTemple 516, 575 – Zeitfunktionen 473 Platzhalter 96 MySQLi 477 POP3 427 – Einfache Abfragen 485 preg_match 100 – Komplexe Abfragen 487 preg_match_all 100 – Referenz 491 preg_replace 100 – Testen 478 preg_replace_callback 100 – Verknüpfungen abfragen 489 prev 188 Primärschlüssel 460 N PRIMARY KEY (SQL) 451 Namenskonventionen 68 print 58 new 208, 471, 471 print_r 196 next 188 printf 60, 120 next (Iterator) 265 – Formatstruktur 121 Nicht 82 Prinzip des Seitenabrufs 70 nl2br 94, 285 private 215 nodeType 580 professionelle Programmieren 62 Normalisierungen 457 professionelle Programmierung 373 NOT NULL (SQL) 451 protected 216 NULL (SQL) 451 Protokolldatei 255 number_format 119 public 215 PUT 334 O Objekt erzeugen 208 Q Objektorientierte Programmierung 204 Query Builder 446 oder 82 Quoted Printable 428 Operatoren 78 Optionsfelder 288 R ORDER BY (SQL) 454, 456 Rangfolge 133 Rasmus Lerdorf 22 P RDF 569 Paradigmen 205 Rechnen 79 Parameter 153 rechts-assoziativ 135 – Anzahl, beliebige 156 Reflection::export 395 – optionale 154 Reflection-API 395 – Übergabe 159 ReflectionClass 399 parent 211 ReflectionExtension 405 parse_url 343 ReflectionFunction 397 PEAR 65 Reflection-Informationen 395 PECL-Module 29 Reflection-Klassen 397 Personal Home Page (Tools)/Forms ReflectionMethod 402 Interpreter 22 ReflectionProperty 403 PHP Regex-Maschinen 103 – Blöcke 27 registerXPathNamespace 592 – einbetten 53 reguläre Ausdrücke 95 – Skripte ausführen 41 rekursive Dateiliste 263 PHP Hypertext Preprocessor 22 rekursive Funktionen 164 php.ini 38 REQUEST_METHOD 281 PHP_OS 116 require 169 PHP_SELF 254 require_once 169 PHP4 23 reset 188 646
  • 647.
    Stichwortverzeichnis Ressource 244 sqlite_last_error 504 rewind (Iterator) 265 sqlite_num_rows 504 RIGHT JOIN (SQL) 459 sqlite_query 504 Root-Server 70 sscanf 120 RSS 568 Standard-Wrapper 247 Rückgabewerte 151 static 213 statische Mitglieder 212 S statische Variablen 160 Schema 553 Sticky Forms 301, 315 Schleifen 141 str_ireplace 89 Schnittstellen 221 str_replace 89 Seitenmanagement 277 strcmp 89 SELECT (SQL) 446 stream_context_create 253 serialize 356 Streams 416 Servervariablen 365 – Funktionen 425, 440 Serverzeit 110 strftime 111 Sessions strip_tags 422 – Cookies 350 stripos 88 – Verwaltung 358 strlen 90 set_error_handler 175 strnatcasecmp 88 setcookie 352 strncasecmp 88 setlocale 115, 376 strpos 87 setrawcookie 352 strrpos 88 Sicherheitsprobleme 346 Structured Query Language siehe SQL SimpleXML 581 Sub-Select 460 – Referenz 620 substr 92 simplexml_import_dom 587 Subtraktion 79 simplexml_load_file 585, 620 Suchen 87 simplexml_load_string 620 Suchmuster 103 Singleton 213 SUM (SQL) 455 Smarty 516 Suraski, Zeev 22 SMTP 427 switch 137 SOAP 593, 595 – Body 595 T – Envelope 595 Tabellen 49, 445 – Header 595 Tabulator 85 – Projekt 608 Teilausdrücke 79 Socket-Verbindung 441 Teilzeichenketten 90 Sonderzeichen 84, 85 Template-System 516 sort 191 Textfelder 285 Sortieren (SQL) 456 Textknoten 563 Sortierfunktion 191 throw 178 SPAM-Versender 429 Timestamp 110 Sprache 374 trigger_error 175 sprintf 120 trinärer Operator 82 SQL 445 TRUNCATE (SQL) 453 – Aggregate Funktionen 467 try 178 SQL92 461 Typ-Informationen 223 SQLite 498 – Beispiel 500 U – Referenz 506 Umgebungsvariablen 365 sqlite_close 505 und 82 sqlite_error_string 504 ungleich 82 sqlite_fetch_array 504 UNIQUE (SQL) 451 647
  • 648.
    Stichwortverzeichnis Unix-Timestamp 110 X unserialize 357 XML 550 unset 187 – Definition 552 Unterabfragen 460 – Namensräume (SimpleXML) 588 UPDATE (SQL) 452 – Path Language 1.0. 562 url_decode 343 – Pointer Language 552 url_encode 343 XPath 562 UserLand 569 – Achsenbezeichner 564 UTC 110 – ancestor 564 utf8_decode 580 – Ausdrücke 563 UUEncode 427 – Ausdrücke in PHP5 566 – child 564 V – descendant 564 valid (Iterator) 265 – following 565 variable Funktionen 166 – parent 564 Variablen 74 – preceding 565 – Erkennung 84 – self 564 – globale 162 xpath (SimpleXML) 588 – statische 160 XPath-Knoten 563 Vererbung 211 XQuery 553 Vergleiche 81 xsl:apply-template 556 verketten 80 xsl:call-template 559 Verknüpfungen (SQL) 457 xsl:copy-of 559 Verzeichnisobjekt dir 260 xsl:for-each 558 Verzeichniszugriff 257 xsl:if 557 Verzweigungen 132 xsl:include 560 Vorauswahl 299 xsl:number 559 Vorbereitung 24 xsl:output 560 Vorzeichen 79 xsl:sort 558 vprintf 120 xsl:template 555 vsprintf 120 xsl:text 555 xsl:value-of 556 W XSLT 553 Wagenrücklauf 85 – Basisregeln 555 WAMP 29 – Funktionen 560 WebDAV 334 Webserver bauen 29 Z Webservices 593 Zeichen 90 – konsumieren 607 Zeichencodes 91 WHERE (SQL) 447 Zeichenfolgen verketten 80 while 148 Zeichenketten 83 wordwrap 93 – erkennen 85 Wrapper 416 – Funktionen 86 – Eigene 425 – Operationen 86 Wrapper-Zugriff 246 Zeichenklassen 96 WSDL 597 Zeilenvorschub 85 wsdl_cache_dir 608 Zeit 110 wsdl_cache_enabled 608 Zend-Engine 23 wsdl_cache_ttl 608 Zielgruppe 16 Wurzelknoten 563 Zugriffskontrolle 215 Zugriffsmethoden 227 648
  • 649.
    Copyright Daten, Texte,Design und Grafiken dieses eBooks, sowie die eventuell angebotenen eBook-Zusatzdaten sind urheberrechtlich geschützt. Dieses eBook stellen wir lediglich als Einzelplatz-Lizenz zur Verfügung! Jede andere Verwendung dieses eBooks oder zugehöriger Materialien und Informationen, einschliesslich der Reproduktion, der Weitergabe, des Weitervertriebs, der Platzierung im Internet, in Intranets, in Extranets anderen Websites, der Veränderung, des Weiterverkaufs und der Veröffentlichung bedarf der schriftlichen Genehmigung des Verlags. Bei Fragen zu diesem Thema wenden Sie sich bitte an: mailto:info@pearson.de Zusatzdaten Möglicherweise liegt dem gedruckten Buch eine CD-ROM mit Zusatzdaten bei. Die Zurverfügungstellung dieser Daten auf der Website ist eine freiwillige Leistung des Verlags. Der Rechtsweg ist ausgeschlossen. Hinweis Dieses und andere eBooks können Sie rund um die Uhr und legal auf unserer Website (http://www.informit.de) herunterladen