Galileo Computing < openbook >
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.


Kompendium der Informationstechnik
 von Sascha Kersken
EDV-Grundlagen, Programmierung, Mediengestaltung
Buch: Kompendium der Informationstechnik
gp Kapitel 18 Serverseitig dynamische Websites
  gp 18.1 Klassisches CGI
    gp 18.1.1 Das erste Beispiel
    gp 18.1.2 Manuelles Auslesen von Formulardaten
    gp 18.1.3 Mit dem Modul CGI.pm arbeiten
  gp 18.2 PHP
    gp 18.2.1 Die ersten PHP-Beispiele
    gp 18.2.2 PHP-Sprachgrundlagen
    gp 18.2.3 Webspezifische Funktionen
    gp 18.2.4 Gästebücher und Counter mit Textdateien
    gp 18.2.5 Zugriff auf MySQL-Datenbanken
  gp 18.3 Zusammenfassung

gp

Prüfungsfragen zu diesem Kapitel (extern)


Galileo Computing

18.2 PHP  downtop

Eines der beliebtesten Werkzeuge zur Erstellung dynamischer Web-Inhalte für kleine und mittlere Websites ist die Sprache PHP. Der Name dieser 1995 von Rasmus Lerdorf unter der ursprünglichen Bezeichnung Personal Homepage Tools entwickelten Server-Skriptsprache steht inzwischen für das rekursive Akronym PHP:Hypertext Preprocessor.

PHP-Download

Sie können PHP in der aktuellen Version 4 von der offiziellen PHP-Website www.php.net oder von einer der dort verzeichneten Mirror-Sites herunterladen. Die Sprache ist für viele verschiedene Plattformen wie Windows und etliche UNIX-Varianten verfügbar. Besonders verbreitet ist die Kombination aus dem Betriebssystem Linux, dem Webserver Apache, der freien Datenbank MySQL und der Programmiersprache PHP – kurz einem LAMP-System.

Praktisch ist in diesem Zusammenhang, dass aktuelle Linux-Distributionen komplett mit all diesen Komponenten ausgeliefert werden, sodass Sie sofort nach der Systeminstallation mit der Entwicklung einer LAMP-Site beginnen können. Wie Sie den Apache-Webserver für das Ausführen von PHP-Skripten konfigurieren, erfahren Sie in Kapitel 14, Netzwerkanwendung.

PHP-Dokumente

Einer der größten Vorteile von PHP gegenüber klassischem CGI ist, dass der PHP-Interpreter gewöhnliche HTML-Dateien akzeptiert, in denen speziell markierte PHP-Abschnitte verarbeitet und durch ihre Ausgabe ersetzt werden: Sie müssen kein HTML mehr ausgeben, sondern können PHP-Anweisungen an die passende Stelle des HTML-Dokuments schreiben.

Der PHP-Code wird in einen Bereich hineingeschrieben, der folgendermaßen gekennzeichnet wird:

<?php    // PHP-Anweisungen
?>

Ein solcher Bereich kann an einer beliebigen Stelle im HTML-Dokument stehen, sogar innerhalb von HTML-Tags oder ihren Attributwerten. Außerdem können sich HTML- und PHP-Blöcke an einer beliebigen Stelle und selbst innerhalb derselben Zeile abwechseln. Konstrukte wie das folgende sind ohne weiteres möglich und können sehr praktisch sein:

<?php if ($punkte > 100) { ?>
   <h2>Herzlichen Gl&uuml;ckwunsch!</h2>
<?php } else { ?>
   <h2>Sie sollten noch &uuml;ben!</h2>
<?php } ?>

Dieses Beispiel gibt die Überschrift »Herzlichen Glückwunsch!« aus, wenn die Variable $punkte einen höheren Wert als 100 hat, ansonsten »Sie sollten noch üben!«. Die folgende Schreibweise ist synonym, kann aber in vielen Fällen unhandlicher sein:

<?php
 
   if ($punkte > 100) {
      echo "<h2>Herzlichen Gl&uuml;ckwunsch!</h2>";
   } else {
      echo "<h2>Sie sollten noch &uuml;ben!</h2>";
   }
 
?>

Galileo Computing

18.2.1 Die ersten PHP-Beispiele  downtop

Im folgenden ersten Beispiel für ein vollständiges PHP-Dokument wird die Überschrift »Hallo, hier PHP!« ausgegeben:

<html>
<head>
<title>PHP-Gru&szlig;</title>
</head>
<body>
<?php
 
   echo "<h1>Hallo, hier PHP!</h1>";
 
?>
</body>
</html>

Die Ausgabe der Anweisung echo() wird an der entsprechenden Stelle in den normalen HTML-Code eingefügt. Wichtig ist natürlich, dass der Inhalt dieser Ausgabe an der entsprechenden Stelle formal in den HTML-Code passt. Der gesamte PHP-Block im obigen Beispiel erzeugt beispielsweise die folgende Ausgabe:

<h1>Hallo, hier PHP!</h1>

Das nächste Beispiel in Listing 18.5 definiert zuallererst einige Farbangaben als PHP-Variablen – der Vorteil ist, dass sie sich dadurch leicht seitenweit austauschen lassen:

Listing 18.5   Dokumentfarben als austauschbare PHP-Variablen

<?php
 
   $hgfarbe = "#000000";
   $textfarbe = "#FFFFFF";
   $texthfarbe = "#00FFFF";
   $linkfarbe = "#FFFF00";
   $vlinkfarbe = "#FF9900";
   $thgfarbe = "#000066";
 
?>
<html>
  <head>
    <title>Leicht austauschbare Farben</title>
  </head>
  <body bgcolor="<?php echo $hgfarbe; ?>" 
  text="<?php echo $textfarbe; ?>" 
  link="<?php echo $linkfarbe; ?>" 
  vlink="<?php echo $vlinkfarbe; ?>">
    <h1><font color="<?php echo $texthfarbe; ?>">
    Dynamische Farben</font></h1>
    Normaler Text
    <table border="<?php echo $thgfarbe; ?>">
      <tr>
        <td>Tabellentext</td>
      </tr>
    </table>
  </body>
</html>

Galileo Computing

18.2.2 PHP-Sprachgrundlagen  downtop

Der Sprachkern von PHP wurde stark von Programmiersprachen wie C und Perl inspiriert. PHP ist ursprünglich eine prozedurale Sprache, wurde aber in neueren Versionen um objektorientierte Merkmale erweitert.

Wie die meisten anderen Skriptsprachen ist PHP nicht typisiert, eine Variable kann also nacheinander Werte beliebiger Datentypen annehmen. Grundlegende Kontrollstrukturen wie Fallentscheidungen und Schleifen funktionieren genau wie in C und in allen davon abgeleiteten Sprachen.

Variablendefinition und -verwendung

Bezeichner

Variablenbezeichner beginnen grundsätzlich mit einem $-Zeichen. Anders als in Perl gibt es keine besonderen Zeichen, die Arrays oder Hashes kennzeichnen. Hinter dem $ können Buchstaben, Ziffern und Unterstriche folgen; das erste Zeichen darf keine Ziffer sein. Es wird zwischen Groß- und Kleinschreibung unterschieden.

Funktions- und Klassennamen kommen dagegen ohne Dollar-Zeichen aus, abgesehen davon werden Groß- und Kleinschreibung weder bei selbst definierten noch bei eingebauten Funktions- und Klassennamen unterschieden. Allerdings wäre es sehr schlechter Programmierstil, dies auszunutzen.

Variablenerzeugung

Eine Variable kann in PHP auf verschiedene Arten erzeugt werden: zum einen durch die erste Wertzuweisung (Initialisierung), zum anderen durch Formulardaten-Übergabe. Wertzuweisungen sehen genauso aus wie in den meisten anderen Programmiersprachen:

$test = 9;
$text = "hallo";

In dem Moment, in dem einer Variablen zum ersten Mal ein Wert zugewiesen wird, existiert sie. Eine Deklaration im eigentlichen Sinn gibt es nicht.

In älteren PHP-Versionen stehen die Felder eines Formulars, dessen Daten an ein Skript übergeben wurden, als gleichnamige Variablen (natürlich mit vorangestelltem $) zur Verfügung. Übergeben Sie etwa das Feld

<input type="text" name="preis" />

per Formularversand an ein PHP-Skript, so steht Ihnen die Eingabe des Benutzers automatisch als $preis zur Verfügung. Seit der Version 4.2.0 ist PHP allerdings so konfiguriert, dass die automatische Erzeugung von Variablen aus Formularübertragungen abgeschaltet ist.

Angenommen, Sie übermitteln mit Hilfe des folgenden HTML-Codes Formulardaten:

<form action="meinscript.php" method="post">
<input type="text" name="hallo" size=40>
<input type="submit" value="OK">
</form>

Formulardaten lesen

Diese Daten stehen Ihnen im Empfängerskript nicht mehr unter dem Namen des Felds – hier wäre es $hallo – zur Verfügung. Stattdessen lautet der Bezug auf die Formulardaten nun $_POST['feldname'] beziehungsweise $_GET['feldname'], je nachdem, ob die Formulardaten mittels POST oder mittels GET versandt wurden. Im obigen Beispiel wäre der Wert des Textfelds hallo also über $_POST['hallo'] auslesbar.

Der Grund für diese Konfigurationsänderung ist eine Sicherheitserwägung: Mit der alten Methode konnte man einem PHP-Skript über die URL beliebige Variablen unterschieben. Sogar Umgebungsvariablen wurden früher auf diese Weise angesprochen und hätten über die URL verändert werden können, darunter $HTTP_USER_AGENT (der Browser des Besuchers) oder sogar $REMOTE_ADDR (die IP-Adresse des entfernten Benutzers).

In neueren Versionen werden die Umgebungsvariablen auf andere Weise angesprochen, nämlich mit Hilfe der Schreibweise getenv ('VAR_NAME'), also etwa getenv ('HTTP_USER_AGENT'). Alternativ wird automatisch das Hash $_SERVER definiert, dessen Schlüssel die einzelnen CGI-Umgebungsvariablen sind – beispielsweise liefert $_SERVER['REMOTE_HOST'] den Hostname des anfragenden Client-Rechners zurück (oder die IP-Adresse, falls kein Hostname ermittelt werden kann).

Wie bereits erwähnt, haben Variablen in PHP keinen festgelegten Datentyp. Sie können einer Variablen nacheinander verschiedene Arten von Werten zuweisen, zum Beispiel:

$a = 5; 
    // Ganzzahl
$a = 3.78;  // Fließkommazahl
$a = "hi";  // String

Mitunter werden die Werte von Variablen in einem neuen Zusammenhang automatisch anders interpretiert. Hier sehen Sie zwei Beispiele:

$b = "67"; 
     // String, wegen Anführungszeichen
$c = $b + 9;    // Ergebnis: 76
 
$a = 22;        // Ganzzahl
$b = $a . "33"; // "2233" 

Operatoren

Genau wie in Perl ist der Verkettungsoperator für Strings der Punkt (.) und nicht das in Java und JavaScript verwendete Pluszeichen, das häufig wegen der Verwechslung mit der numerischen Addition Ärger bereitet. Die meisten anderen Operatoren entsprechen ebenfalls ihrer Verwendung in Perl. Der einzige wichtige Unterschied zu Perl besteht darin, dass Strings in PHP mit den normalen Vergleichsoperatoren wie ==, != oder <= verglichen werden und nicht mit speziellen Operatoren. Genaueres über die Perl-Operatoren finden Sie in Kapitel 5, Grundlagen der Programmierung.

Variablen gelten in PHP ab dem Zeitpunkt ihrer Wertzuweisung im gesamten Dokument. Es gibt keine untergeordneten Gültigkeitsbereiche innerhalb von Blöcken wie Fallentscheidungen oder Schleifen. Die Ausnahme bilden Variablen, die innerhalb der weiter unten behandelten Funktionen definiert werden: Sie sind lokal und gelten nur innerhalb der jeweiligen Funktion.

Arrays in PHP

Wie in den meisten anderen Programmiersprachen gibt es auch in PHP die Möglichkeit, Arrays zu bilden. Ein Array ist eine Variable, die eine Liste von Werten enthält, auf die über einen Index zugegriffen werden kann. PHP unterscheidet nicht grundsätzlich zwischen einem gewöhnlichen Array mit numerischen Indizes und einem Hash, bei dem die Indizes Strings sind: In jedem Array können beide Indexarten gleichzeitig existieren.

Um ein klassisches Array mit numerischen Indizes zu erzeugen, genügt es, einem einzelnen Element dieses Arrays einen Wert zuzuweisen:

$monate[0] = "Januar";

Mit Hilfe der eingebauten Funktion array() können Sie auch gleich mehrere Elemente eines numerischen Arrays erzeugen:

$jahreszeiten = array ("Fruehling", "Sommer", "Herbst", 
      "Winter");

Besonders interessant ist im Übrigen die Tatsache, dass Sie leere eckige Klammern statt eines konkreten Indexes verwenden können, um ein Element am Ende des Arrays anzufügen:

$wochentage[] = "Sonntag";
$wochentage[] = "Montag";
// und so weiter

Über die Funktion array_push($array, $wert1, $wert2, ...) können Sie aber auch eine Liste mehrerer Elemente am Ende des Arrays einfügen. Umgekehrt liefert die Funktion array_pop($array) das letzte Element des Arrays zurück und entfernt es aus dem Array.

Hash-Funktionalität

Um ein Hash oder assoziatives Array zu erzeugen, können Sie ebenfalls mit der Zuweisung des Wertes für ein einzelnes Element beginnen:

$monate['jan'] = "Januar";

Wenn Sie die Werte mehrerer Elemente gleichzeitig zuweisen möchten, funktioniert dies mit Hilfe der folgenden Form der Funktion array():

$wochentage = ('So' => "Sonntag",
               'Mo' => "Montag",
               'Di' => "Dienstag",
               'Mi' => "Mittwoch",
               'Do' => "Donnerstag",
               'Fr' => "Freitag",
               'Sa' => "Samstag");

Im Grunde ist die Verwendung einfacher Anführungszeichen für die Indizes und doppelter für die Werte nicht vorgeschrieben, aber eine gängige Konvention. Wie in Perl werden innerhalb doppelter Anführungszeichen Variablen und alle üblichen Esacpe-Sequenzen ausgewertet, innerhalb von einfachen aber nicht (lediglich \' für ein einzelnes Anführungszeichen als solches und \\ für einen Backslash werden erkannt):

$geld = 100;
echo ("Ich habe $geld \$.");
   // Ausgabe: Ich habe 100 $.
echo ('Ich habe auch $geld $.');
   // Ausgabe: Ich habe auch $geld $.

Die Funktion sizeof($array) liefert die Anzahl der Elemente im Array zurück. Auf diese Weise können Sie alle Elemente eines numerischen Arrays in einer Schleife ausgeben, zum Beispiel folgendermaßen:

$zimmer = array("Wohnzimmer", "Schlafzimmer", 
     "Kinderzimmer", "Arbeitszimmer");
$zahl = sizeof($zimmer);
for ($i = 0; $i < $zahl; $i++) {
   echo ($zimmer [$i]."<br />");
}

Das kleine Beispiel gibt untereinander die Bezeichnungen der vier Zimmer aus. Bei Hashes sollten Sie dagegen eine andere Methode verwenden, die Elemente aufzuzählen: Die Funktion each($array) gibt bei jedem Aufruf das nächste Schlüssel-Wert-Paar zurück. Dieses Paar können Sie mit Hilfe von list() einer Liste aus zwei Variablen zuweisen. Das Ganze funktioniert so:

$rechner = array ('cpu' => "Pentium 4",
                  'ram' => "512 MB DDR-RAM",
                  'hdd' => "Maxtor 120 GB",
                  'dvd' => "8xDVD / 40xCD");
reset ($rechner);  // Sicherheitshalber auf Anfang
while (list ($key, $val) = each ($rechner)) {
   echo ("$key: <b>$val</b><br />");
}

Strings zerlegen

Interessant ist auch noch die Funktion explode(). Sie funktioniert nach folgendem Schema:

$array = explode ($muster, $string);

Die Funktion zerlegt den String $string an den Stellen, an denen $muster vorkommt, in die einzelnen Elemente des Arrays $array. Zum Beispiel:

$string = "a,b,c,d";
$array = explode (",", $string);
/* $array[0] ist "a"
   $array[1] ist "b"
   $array[2] ist "c"
   $array[3] ist "d" */

Wenn Sie statt eines einfachen Strings einen regulären Ausdruck als Trennzeichen angeben möchten, müssen Sie die Anweisung split() statt explode() verwenden.

Die umgekehrte Aufgabe erledigt die Funktion implode(), die die Elemente eines Arrays – getrennt durch das angegebene Muster – zu einem String zusammenfasst:

$string = implode ($muster, $array);

Hier sehen Sie ein Beispiel:

$array = array ("So", "Mo", "Di", "Mi", "Do", 
     "Fr", "Sa");
$string = implode (", ", $array);
   // $string ist "So, Mo, Di, Mi, Do, Fr, Sa";

Merdimensionale Arrays

Auch mehrdimensionale Arrays sind kein Problem. Das folgende Beispiel definiert die Variable $jahr als Aufzählung der Jahreszeiten mit ihren Monaten:

$jahr = array (
   'fruehling' => array("März", "April", "Mai"), 
      'sommer' => array("Juni", "Juli", "August"), 
   'herbst' => array("September", "Oktober", "November"),
   'winter' => array("Dezember", "Januar", "Februar")
);

Wenn Sie nun beispielsweise auf den zweiten Monat im Sommer zugreifen möchten, können Sie die Schreibweise $jahr['sommer'][1] verwenden – die Rückgabe lautet natürlich »Juli«.

Dynamische Variablen

Der in einer Variablen gespeicherte Wert kann automatisch als Name einer anderen Variablen betrachtet werden, indem vor die Variable ein weiteres $-Zeichen gesetzt wird. Beispiel:

$n = "i";
// $$n entspricht der Variablen $i.

Dieses Konzept kann zum Beispiel in bestimmten Fällen als Ersatz für Arrays benutzt werden, etwa bei der Formulardatenübergabe.

Angenommen, per Formular wurden sechs Felder mit den Bezeichnungen artikel1 bis artikel6 übergeben. In einer Schleife soll nacheinander auf die sechs Felder zugegriffen werden, weil es ziemlich lästig wäre, sie einzeln zu verarbeiten.

Dies funktioniert folgendermaßen – in diesem Beispiel werden die Werte der Felder untereinander ausgegeben und zusätzlich zur weiteren Verwendung in einem Array gespeichert:

for ($i = 1; $i <= 6; $i++) {
   $a = "artikel$i"; // den Namen erzeugen
   $art = $$a;       // aktueller Artikel
   $artikelliste[] = $art;
   echo ("$art<br />\n");
}

Wenn Sie mittels PHP – etwa in einer Schleife – viel HTML-Code per echo() ausgeben, dann sollte diese Ausgabe an passenden Stellen ein \n enthalten, das einen Zeilenumbruch im HTML-Code ausgibt. Auf diese Weise wird der erzeugte HTML-Code übersichtlicher, sodass Sie Fehler leichter finden können.

Kommentare

PHP unterstützt drei verschiedene Arten von Kommentaren, um es Programmierern leicht zu machen, die verschiedene Sprachen beherrschen. Als Erstes wird der einzeilige Kommentar im C++-Stil unterstützt:

// einzeiliger Kommentar

Dabei wird der Rest der Zeile ignoriert. Auch der mehrzeilige Kommentar im C-Stil ist erlaubt:

/* mehr-
   zeiliger
   Kommentar */

Hier werden alle betroffenen Zeilen ignoriert. Schließlich ist auch noch der einzeilige Kommentar im Stil von Perl und der UNIX-Shell-Sprachen zulässig:

# einzeiliger Kommentar

Funktionen

Eine PHP-Funktion ist ein benannter Codeblock, der mit seinem Namen aufgerufen wird. Ständig wiederkehrende Anweisungsfolgen in Funktionen zu verpacken, schafft Übersicht und schont Ressourcen, da eine Funktion innerhalb eines Skripts nur einmal kompiliert wird. Die allgemeine Syntax ist einfach:

function funktionsname() {
   // Anweisungen ...
}

Nach Ausführung der letzten Anweisung wird die Kontrolle an die aufrufende Stelle zurückgegeben. Wie bereits erwähnt, können Funktionen lokale Variablen enthalten: Jede Variable, die in einer Funktion verwendet wird, ist lokal – sogar dann, wenn im globalen Code eine gleichnamige Variable existiert. Betrachten Sie etwa den Wert der beiden Variablen namens $a im folgenden Beispiel:

function a_aendern() {
   $a = 9;   // lokales $a hat den Wert 9.
}
 
$a = 7;  // globales $a hat den Wert 7.
a_aendern();
// $a hat hier wieder den Wert 7.

Zugriff auf globale Variablen

Wenn Sie innerhalb einer Funktion auf eine globale Variable zugreifen möchten, müssen Sie am Anfang der Funktion ausdrücklich das Schlüsselwort global verwenden. Hier sehen Sie das Beispiel von oben noch einmal, allerdings wird innerhalb der Funktion auf die globale Variable $a zugegriffen:

function a_aendern() {
   global $a;
   $a = 9;   // globales $a hat nun den Wert 9.
}
 
$a = 7;  // globales $a hat den Wert 7.
a_aendern();
// $a hat jetzt den Wert 9.

Eine Funktion kann Parameter entgegennehmen. Zu diesem Zweck müssen Sie bei der Definition die Namen der gewünschten Parametervariablen in den Klammern des Funktionskopfes angeben:

function top_cell ($inhalt)
{
   echo ("<td valign=\"top\">$inhalt</td>");
}

Diese Funktion gibt den übergebenen Wert, der in der Parametervariablen $inhalt gespeichert wird, als Tabellenzelle mit der häufig verwendeten vertikalen Ausrichtung oben aus. Parametervariablen haben innerhalb der Funktion dieselbe Bedeutung wie lokale Variablen – der Standardfall beim Funktionsaufruf ist in PHP der Call by Value, das heißt die Übergabe eines Wertes ohne Rückbezug auf die aufrufende Stelle. Betrachten Sie dazu das folgende Beispiel:

function halbieren ($wert)
{
   $wert /= 2;
}
 
$zahl = 9;  // $zahl hat den Wert 9.
halbieren ($zahl);
// $zahl hat weiterhin den Wert 9.

Referenzübergabe

Wenn Sie stattdessen einen Call by Reference benötigen, das heißt den Wert der Variablen verändern möchten, mit der die Funktion aufgerufen wird, müssen Sie der entsprechenden Parametervariablen bei der Funktionsdefinition ein &-Zeichen voranstellen. Wenn Sie die Funktion halbieren() folgendermaßen umschreiben, wird $zahl also tatsächlich halbiert:

function halbieren (&$wert)
{
   $wert /= 2;
}
 
$zahl = 10;  // $zahl hat den Wert 10.
halbieren ($zahl);
// $zahl hat nun selbst den Wert 5.

Beachten Sie allerdings bei einem Call by Reference, dass Sie der Funktion eine Variable übergeben müssen. Ein konstanter Wert ist nicht gestattet und erzeugt eine Fehlermeldung.

Eine Funktion kann auch einen Wert zurückgeben, sodass Sie das Ergebnis an der aufrufenden Stelle in einem Ausdruck einsetzen können. Dafür ist die Anweisung return zuständig. Sie verlässt die Funktion sofort und gibt den entsprechenden Wert zurück. Hier ein Beispiel:

function verdoppeln ($wert)
{
   return $wert * 2;
}

Wenn Sie diese Funktion aufrufen, wird der Wert des übergebenen Arguments verdoppelt. Der fertig berechnete Ausdruck wird anschließend zurückgegeben. Sie können einen Aufruf dieser Funktion in einem beliebigen Ausdruck verwenden. Bevor dieser Ausdruck ausgewertet wird, erfolgt die Ausführung der Funktion, und es wird mit dem zurückgegebenen Wert weitergerechnet. Beispiel:

echo (verdoppeln ($zahl));

Diese Anweisung gibt den doppelten Wert der Variablen $zahl aus.

Beachten Sie, dass eine Funktion mehrere return-Anweisungen enthalten kann, die von Fallentscheidungen abhängen. Sie benötigen noch nicht einmal else-Blöcke für solche if-Anweisungen, weil return die Ausführung der Funktion unmittelbar beendet. Die folgende Funktion macht sich das zunutze, um einen übergebenen Ausdruck daraufhin zu überprüfen, ob er ein Integer zwischen 1 und 4 ist:

function antworttest ($antwort)
{
   if (!is_int ($antwort)) {
      return false;
   }
   if ($antwort < 1 || $antwort > 4) {
      return false;
   }
   // An dieser Stelle kann $antwort nur OK sein.
   return true;
}

In dem Beispiel wird als Erstes überprüft, ob $antwort überhaupt ein Integer ist – falls nicht, wird sofort false zurückgegeben. Anschließend wird geprüft, ob $antwort außerhalb des zulässigen Bereichs liegt – in diesem Fall erfolgt ebenfalls die Rückgabe von false. Wenn keine der beiden if-Abfragen zutrifft, wird automatisch false zurückgegeben.

Datentypen testen

Die verwendete eingebaute Funktion is_int() liefert übrigens true zurück, wenn es sich bei dem übergebenen Wert um einen Integer handelt, andernfalls false. In PHP stehen eine Reihe solcher Funktionen zur Verfügung, um die Datentypen von Ausdrücken zu testen, beispielsweise is_string(), is_float(), is_array() oder is_null(), die das übergebene Argument daraufhin überprüfen, ob es ein String, eine Fließkommazahl, ein Array beziehungsweise leer ist.

Eine ähnliche Aufgabe erfüllen die Funktion isset() beziehungsweise isunset(), die überprüfen, ob eine Variable überhaupt jemals definiert wurde. Mit Hilfe der Anweisung unset() können Sie PHP sogar anweisen, eine Variable vollständig zu vergessen.

Klassen und Objekte

Auch wenn PHP ursprünglich als prozedurale Skriptsprache entwickelt wurde, unterstützt sie seit der Version 4 eine einfache Art der Objektorientierung. Sie können Klassen mit Eigenschaften, Methoden und Konstruktoren definieren und voneinander ableiten. Allerdings kennt PHP keine privaten Eigenschaften oder Methoden; alle Elemente eines Objekts sind von außen sichtbar. Wenn Sie mit diesen Grundbegriffen der Objektorientierung nichts anfangen können, lesen Sie bitte Kapitel 5, Grundlagen der Programmierung.

Das Beispiel in Listing 18.6 definiert eine Klasse namens Tabelle, die verschiedene Methoden zur Ausgabe von HTML-Tabellenelementen enthält, definiert ein Objekt dieser Klasse und ruft die entsprechenden Methoden auf:

Listing 18.6   Eine einfache Klasse und ihre Verwendung

class Tabelle
{
   function tab_start($border)
   {
      echo "<table border=\"$border\">\n";
   }
 
   function tab_end()
   {
      echo "</table>\n";
   }
 
   function tr_start()
   {
      echo "<tr>\n";
   }
 
   function tr_end()
   {
      echo "</tr>\n";
   }
 
   function td($content, $align, $valign)
   {
      echo ("<td align=\"$align\" 
           valign=\"$valign\">$content</td>\n");
   }
}
 
$tab = new Tabelle();
$tab->tab_start (2);
$tab->tr_start();
$tab->td ("Linke Zelle", "left", "top");
$tab->td ("Rechte Zelle", "left", "top");
$tab->tr_end();
$tab->tab_end();

Klassen definieren

Wie Sie im Beispiel sehen, wird eine Klasse mit dem Schlüsselwort class deklariert. Die Methoden sind einfache Funktionen, die in den Block der Klasse hineinverschachtelt werden. Ein Objekt einer Klasse wird über den Operator new erzeugt und ruft, falls vorhanden, den Konstruktor der Klasse auf (siehe unten). Der Zugriff auf Methoden und Eigenschaften eines Objekts erfolgt mit Hilfe des Operators ->.

Das Beispiel aus Listing 18.6 erzeugt die folgende HTML-Ausgabe:

<table border="2">
<tr>
<td align="left" valign="top">Linke Zelle</td>
<td align="left" valign="top">Rechte Zelle</td>
</tr>
</table>

Als Eigenschaften einer Klasse gelten automatisch alle Variablen, die in der Klasse, aber nicht innerhalb einer Methode definiert werden. Aus einer Methode heraus wird das Schlüsselwort $this verwendet, um auf Eigenschaften zuzugreifen. Das folgende Beispiel zeigt den Umgang mit einer Eigenschaft:

class Test {
   $wert = 0;
 
   function setWert ($wert)
   {
      $this->wert = $wert;
   }
 
   function getWert ()
   {
      return $this->wert;
   }
}
 
$obj = new Test();
$obj->wert = 2;  // Kein guter Stil!

Zunächst wird die Variable $wert mit dem Wert 0 initialisiert. Die Methode setWert() ändert sie auf einen von außen angegebenen Wert. getWert() gibt den Inhalt von $wert dagegen zurück. Beachten Sie, dass der Variablenname hinter $this und dem Zugriffsoperator -> nicht mit einem weiteren Dollarzeichen versehen wird.

In der letzten Zeile des Beispiels wird der Wert der Eigenschaft $wert von außen geändert. Da private Eigenschaften wie gesagt nicht möglich sind, können solche Zugriffe nicht verhindert werden. Dennoch handelt es sich dabei um einen sehr schlechten Programmierstil. Eine bessere Fassung der letzten Zeile lautet natürlich:

$obj->setWert (2);

Konstruktoren

Wenn Sie einen expliziten Konstruktor definieren möchten: Es handelt sich um eine Funktion, die denselben Namen trägt wie die Klasse. Innerhalb des Konstruktors werden typischerweise Initialisierungsarbeiten vorgenommen, die zu Beginn des Lebenszyklus eines Objekts notwendig sind. Insbesondere können die übergebenen Parameter als Anfangswerte für die Eigenschaften des Objekts gesetzt werden. Die verbesserte Fassung der Klasse Tabelle in Listing 18.7 definiert die wichtigsten Tabellen- und Zelleneigenschaften durch einen Konstruktoraufruf und stellt Methoden bereit, um die Zelleneinstellungen nachträglich zu ändern.

Listing 18.7   Eine verbesserte Tabellen-Klasse

class Tabelle
{
   // Eigenschaften deklarieren (optional):
   var $bgcolor, $border, $align, $valign;
 
   // Konstruktor:
   function Tabelle ($bg, $b, $a, $va)
   {
      $this->bgcolor = $bg;
      $this->border = $b;
      $this->align = $a;
      $this->valign = $va;
   }
 
   function tab_start()
   {
       echo "<table border=\"{$this->border}\" 
            bgcolor=\"{$this->bgcolor}\">\n";
   }
 
   function tab_end()
   {
      echo "</table>\n";
   }
 
   function tr_start()
   {
      echo "<tr>\n";
   }
 
   function tr_end()
   {
      echo "</tr>\n";
   }
 
   function td($content)
   {
      echo "<td align=\"{$this->align}\"
           valign=\"{$this->valign}\">$content</td>\n";
   }
 
   function setAlign ($a)
   {
      $this->align = $a;
   }
 
   function setVAlign ($va)
   {
      $this->valign = $va;
   }
}
 
$tab = new Tabelle ("#CCCCCC", 2, "left", "top");
$tab->tab_start();
$tab->tr_start();
$tab->td ("Normale<br/>Zelle");
$tab->setVAlign ("bottom");
$tab->td ("Inhalt unten");
$tab->tr_end();
$tab->tab_end();

Die HTML-Ausgabe aus Listing 18.7 sieht so aus:

<table border="2" width="#CCCCCC">
<tr>
<td align="left" valign="top">Normale<br />Zelle</td>
<td align="left" valign="bottom">Inhalt unten</td>
</tr>
</table>
 

Innerhalb von doppelten Anführungszeichen können Sie bei der Funktion echo() einen Ausdruck in geschweifte Klammern setzen, damit er ausgewertet und ausgegeben wird – ein Ausdruck wie $this->valign könnte ohne dieses Hilfsmittel nicht innerhalb eines Ausgabestrings verwendet werden.

Vererbung

Die Vererbung funktioniert in PHP, ähnlich wie in Java, mit Hilfe des Schlüsselworts extends. Das Beispiel in Listing 18.8 leitet von der Klasse Tabelle aus Listing 18.7 die Klasse FestTabelle ab, deren Breite und Höhe zusätzlich angegeben werden können:

Listing 18.8   Eine abgeleitete Klasse

class FestTabelle extends Tabelle
{
   // Neue Eigenschaften deklarieren:
   var $width, $height;
      // Konstruktor:
   function FestTabelle ($w, $h, $bg, $b, $a, $va)
   {
      $this->width = $w;
      $this->height = $h;
      $this->Tabelle ($bg, $b, $a, $va);
   }
 
   // Überschriebene Methode:
   function tab_start()
   {
      echo "<table border=\"{$this->border}\"
           bgcolor=\"{$this->bgcolor}\"
           width=\"{$this->width}\" 
           height=\"{$this->height}\"";
   }
}
 
$ftab = new FestTabelle 
     ("100%", "100%", "#FFFF00", 2, "left", "top");
$ftab->tab_start();
$ftab->tr_start();
$ftab->td ("Hallo");
$ftab->td ("Welt!");
$ftab->tr_end();
$ftab->tab_end();

Beachten Sie, dass der Konstruktor einer abgeleiteten Klasse nicht automatisch den Konstruktor der Elternklasse aufruft. Im obigen Beispiel wird er explizit als $this->Tabelle() aufgerufen, um die Eigenschaften zu setzen, die bereits in der übergeordneten Klasse existieren.

Include-Dateien

Damit die Objektorientierung ihren Hauptnutzen entfalten kann, nämlich den der einfachen Wiederverwendbarkeit von Code, sollten Sie die Klassendefinitionen in externe Dateien schreiben, die Sie zur Laufzeit importieren können. Eine externe Datei besitzt die Dateiendung .inc und wird mit Hilfe der Anweisung include() importiert. Auch in einer solchen Include-Datei muss PHP-Code innerhalb eines Blocks der Form <?php ... ?> stehen, um ausgeführt zu werden, andernfalls wird der Inhalt als HTML-Code interpretiert. Die include()-Anweisung selbst funktioniert folgendermaßen:

include ("extern.inc");

Wenn sich die Datei in einem anderen Verzeichnis befindet als das PHP-Skript, muss nach der üblichen URL-Logik der Pfad angegeben werden.


Galileo Computing

18.2.3 Webspezifische Funktionen  downtop

Die Hauptaufgabe von PHP ist das Erstellen dynamischer Webanwendungen. Zu diesem Zweck werden eine Reihe spezieller Funktionen und Fähigkeiten angeboten. Dazu gehört das Auslesen von Formulardaten, das Erzeugen und Lesen von Cookies oder auch das Session-Tracking. Letzteres steuert eine Funktionalität bei, die HTTP von Hause aus nicht besitzt: Es ermöglicht das Verfolgen der Aktivitäten eines Benutzers über mehrere besuchte Seiten hinweg. Dies ist wichtig für Warenkorbsysteme oder andere Anwendungen, bei denen eine Reihe aufeinander folgender Aktivitäten registriert werden muss.

Näheres zu Formulardaten

Wie oben bereits erwähnt, stehen die übergebenen Formulardaten in einem PHP-Skript je nach Versandmethode innerhalb der Hashes $_GET beziehungsweise $_POST zur Verfügung. Um die Versandmethode zunächst zu überprüfen, können Sie die CGI-Umgebungsvariable REQUEST_METHOD auslesen. Das folgende Beispiel ermittelt unabhängig von der verwendeten Versandmethode den Inhalt des Formularfelds test:

// Versandmethode auslesen:
$method = $_SERVER['REQUEST_METHOD'];
// Versand mittels POST?
if ($method == "POST") {
   // Ja, POST:
   $test = $_POST['test'];
} else {
   // Nein, GET:
   $test = $_GET['test'];
}

Die Umgebungsvariablen werden dabei über das Hash $_SERVER oder über die Funktion getenv() ausgelesen.

Aus der Sicht eines PHP-Skripts ist es egal, welche Art von HTML-Formularelement einen bestimmten Wert ergeben hat. Die objektorientierte Sicht auf die Formularelemente selbst, wie sie das Client-seitige, im nächsten Kapitel besprochene JavaScript bietet, existiert in PHP nicht – hier geht es nur um die reinen Formulardaten.

Mehrfachauswahlen

Eine Besonderheit bildet der Zugriff auf Formularelemente, die mehrere Werte übertragen können, also Auswahlmenüs mit dem Attribut multiple oder Checkbox-Gruppen: Damit PHP ein Array aus diesen Formularwerten machen kann und nicht nur einen Wert überträgt, müssen Sie hinter den Elementnamen im HTML-Dokument eckige Klammern setzen. Betrachten Sie beispielsweise die folgende Checkbox-Gruppe:

Welche Server-Technologie(n) verwenden Sie?<br />
<input type="checkbox" name="serv[]" value="cgi" />
Klassisches CGI<br />
<input type="checkbox" name="serv[]" value="php" />
PHP<br />
<input type="checkbox" name="serv[]" value="asp" />
ASP.NET<br />
<input type="checkbox" name="serv[]" value="jsp" />
JSP

Mit Hilfe der folgenden Anweisungen können Sie alle Inhalte des Felds auslesen und untereinander ausgeben:

if ($_GET['serv']) {
   echo "<h2>Ihre Servertechnologien sind:</h2>";
   $serv = $_GET['serv'];
   for ($i = 0; $i < sizeof ($serv); $i++) {
      echo ("{$serv[$i]}<br />\n");
   }
}

Cookies

Ein Cookie ist eine kleine Textinformation, die der Browser im Auftrag eines bestimmten Webserver-Hosts speichert und beim nächsten Besuch bei diesem Server wieder zurückliefert. Es handelt sich dabei nicht um einen Zugriff eines entfernten Servers auf das lokale Dateisystem des Client-Rechners, vielmehr verwenden verschiedene Browser sogar unterschiedliche Mechanismen, um Cookies zu speichern.

Der Nutzen von Cookies besteht darin, dass sich bestimmte Einstellungen, die ein Benutzer für Ihre Site vorgenommen hat, bei seinem nächsten Besuch rekonstruieren lassen – personalisierte Benutzerprofile, persistente Warenkörbe oder Ähnliches sind mit Cookies kein Problem.

Cookie-Probleme

Allerdings kommt man nicht umhin, auch die Nachteile von Cookies für den Benutzer zu nennen: Viele Anbieter von Bannerwerbung missbrauchen Cookies, um ein möglichst lückenloses Profil der Seiten zu erstellen, die ein Anwender besucht. Da Banner über viele Seiten hinweg vom selben Anbieter stammen können, gelingt dies meist auch. Aus diesem Grund schalten viele Benutzer die Annahme von Cookies ganz ab, sodass Sie sich nicht auf diese Funktionalität verlassen können.

Ein Cookie wird mit Hilfe der Funktion setcookie() gesetzt:

setcookie ($name, $wert[, $verfallsdatum]);

Da ein Cookie als HTTP-Header-Feld gesendet wird, muss die Funktion in einem PHP-Block noch vor dem öffnenden <html>-Tag stehen. Die folgende Anweisung setzt ein Cookie mit dem Namen user und dem Wert der Variablen $username. Der optionale Verfallsdatums-Parameter wird nicht angegeben, sodass das Cookie nicht dauerhaft gespeichert wird:

setcookie ("user", $username);

Wenn Sie ein Cookie wieder lesen möchten, das der Browser des Benutzers mit der Seitenaufforderung verschickt hat, müssen Sie den Namen des Cookies als Index auf das Hash $_COOKIE verwenden. Die folgende Anweisung liest den Inhalt des Cookies mit dem Namen hintergrund und speichert den Wert in der Variablen $bgcolor:

$bgcolor = $_COOKIE['hintergrund'];

Das Verfallsdatum eines Cookies wird in Sekunden seit EPOCH, also seit dem 01.01.1970, angegeben. Sie können die gewünschte Vorhaltezeit in Sekunden umrechnen und zum Rückgabewert der eingebauten Funktion time() addieren, die Datum und Uhrzeit in dieser Form angibt. Das folgende Beispiel weist den Browser an, das Cookie letzterbesuch mit dem Wert des aktuellen Datums für eine Woche zu speichern:

setcookie ("letzterbesuch", time(), 
     time() + 7 * 24 * 60 * 60);

Sie können das Cookie folgendermaßen wieder auslesen und dem Besucher mitteilen, wann er (innerhalb der letzten sieben Tage) das letzte Mal die Seite besucht hat:

$letzterbesuch = $_COOKIE['letzterbesuch'];
$lbformat = date ("d.m.Y, h:m:s", $letzterbesuch);
echo "Ihr letzter Besuch: $lbformat";

Die Funktion date ($format, $zeitangabe), die hier zur Formatierung des Besuchszeitpunkts nach dem Muster 13.04.2003, 20:18:37 eingesetzt wurde, wird weiter unten genauer erläutert.

Session-Tracking

Das Webseiten-Übertragungsprotokoll HTTP besitzt keine eingebaute Sitzungslogik. Aufeinander folgende Besuche verschiedener Seiten auf demselben Webserver werden also nicht als Einheit betrachtet, sondern stehen jeweils für sich allein. Das ist für E-Commerce-Anwendungen oder ähnliche Websites ein großes Problem, da beispielsweise der Inhalt eines Warenkorbes über viele Seiten hinweg weitergereicht werden muss.

Session-Verfahren

Es gibt viele Lösungsansätze, um eine Abfolge einzelner Seiten zu einer konsistenten Session zu machen. Ein Beispiel sind die eben beschriebenen Cookies, andere Lösungen verwenden eine eindeutige Session-ID, die – beispielsweise über versteckte Formularfelder oder über die URL – jeweils von Seite zu Seite weitergereicht wird, während die eigentlichen Daten auf dem Server selbst gespeichert werden, etwa in Textdateien oder einer Datenbank.

In PHP ist die Session-Funktionalität bereits eingebaut. Die Lösung verwendet Cookies, falls der Browser sie unterstützt, und greift notfalls auf URL-Anhänge zurück.

Wenn eine Seite Teil einer Session sein soll, müssen Sie noch vor dem Start-Tag <html> die PHP-Funktion session_start() aufrufen, die keine weiteren Parameter besitzt. Diese Funktion beginnt bei Bedarf eine neue Session, indem sie eine eindeutige Session-ID erstellt, und lädt andernfalls alle registrierten Variablen, falls bereits eine Session besteht.

Session-Variablen

Der tiefere Sinn von Sessions besteht darin, dass Sie Variablen mit Hilfe der Funktion session_register() speichern können. Diese werden auf der nächsten Seite, die session_start() aufruft, automatisch wieder geladen.

Das folgende Beispiel liest ein mittels GET übergebenes Formularfeld namens mail (die E-Mail-Adresse des Benutzers) ein und speichert diesen Wert in den Session-Daten:

<?php session_start(); ?>
<html>
<head>
<title>Mail-Adresse registrieren</title>
</head>
<body>
<?php
 
   $mail = $_GET['mail'];
   session_register ('mail');
 
?>
Die E-Mail-Adresse wurde gespeichert.
</body>
</html>

Auf jeder folgenden Seite, die session_start() aufruft, steht nun innerhalb der Variablen $mail die E-Mail-Adresse zur Verfügung.

Das folgende Beispiel besteht aus zwei Dateien: pref.php (Listing 18.9) fragt in einem Formular einen Benutzernamen und zwei Farben ab; nach erfolgter Angabe speichert dasselbe Skript die drei Werte in den Session-Daten. Der Hyperlink Testseite führt anschließend auf eine andere Seite (stest.php in Listing 18.10), die die drei Werte aus den Session-Daten liest, Vorder- und Hintergrundfarbe wie gewünscht einstellt und den Anwender mit Namen begrüßt.

Listing 18.9   pref.php fragt verschiedene Voreinstellungen ab

<?php session_start(); ?>
<html>
  <head>
    <title>Voreinstellungen</title>
  </head>
  <body>
    <h1>Voreinstellungen</h1>
    <form action="pref.php" method="GET">
      <table border="0" cellpadding="5">
        <tr>
          <td>Ihr Name:</td>
          <td><input type="text" name="user" /></td>
        </tr>
        <tr>
          <td>Hintergrundfarbe:</td>
          <td><input type="text" name="bcolor" /></td>
        </tr>
        <tr>
          <td>Textfarbe:</td>
          <td><input type="text" name="fcolor" /></td>
        </tr>
      </table>
      <br />
      <input type="submit" value="go" />
    </form>
 
<?php
 
   if ($_GET['user']) {
      $user = $_GET['user'];
      $bcolor = $_GET['bcolor'];
      $fcolor = $_GET['fcolor'];
      echo "<h2>Hallo, $user!</h2>";
      echo "&Auml;nderungen &uuml;bernommen.";
      session_register ('user');
      session_register ('bcolor');
      session_register ('fcolor');
   }
   ?>
    <a href="stest.php">Testseite</a>
  </body>
</html>

Listing 18.10   stest.php – Eine Seite, die die Voreinstellungen verwendet

<?php session_start(); ?>
<html>
  <head>
    <title>Test Page</title>
  </head>
  <body bgcolor="<?php echo $bcolor; ?>" 
       text="<?php echo $fcolor; ?>">
    <h1><?php echo "Hallo, $user!"; ?></h1>
    <a href="pref.php">Pr&auml;ferenzen &auml;ndern</a>
  </body>
</html>

Beachten Sie, dass Sessions bei Apache und PHP unter Windows nur funktionieren, wenn Sie ein Verzeichnis namens C:\tmp anlegen, das zum Ablegen der Session-Daten verwendet wird.

Datei-Uploads

In Kapitel 16, HTML und XHTML, wurde das HTML-Formularelement <input type="file" /> vorgestellt, das dazu dient, den Inhalt einer lokalen Datei zusammen mit den anderen Formulardaten an den Server zu übergeben. Ein PHP-Skript kann solche Dateien entgegennehmen; sie stehen in dem automatisch generierten Array $_FILES zur Verfügung.

Upload-Formulare

Wenn Sie ein HTML-Formular für den Versand von Dateiinhalten bereitstellen möchten, benötigt dieses Formular die spezielle MIME-Codierung multipart/form-data. Das folgende HTML-Formular stellt ein Datei-Upload-Feld zur Verfügung und versendet die ausgewählte Datei auf Knopfdruck an die Datei upload.php:

<form action="upload.php" method="POST" 
enctype="multipart/form-data">
  MP3-Datei w&auml;hlen:
  <input type="hidden" name="MAX_FILE_SIZE" 
  value="102400" />
  <input type="file" name="musik" />
  <input type="submit" value="Upload" />
</form>

Das Hidden-Feld mit dem speziellen Namen MAX_FILE_SIZE sollte aus Sicherheitsgründen stets vor dem eigentlichen Upload-Feld versandt werden (im Beispiel 100 KByte). Es gibt die maximal zulässige Dateigröße in Byte an.

Das Element des Arrays $_FILES, das auf die hochgeladene Datei zeigt, besitzt als Schlüssel den Namen des betreffenden Formularfelds. Dieses Element enthält nicht etwa den Dateninhalt der Datei, sondern wiederum ein Array, dessen verschiedene Felder Informationen über die hochgeladene Datei enthalten:

gp  tmp_name ist das wichtigste Feld, weil es den Dateinamen der temporären Datei enthält, die auf dem Server selbst gespeichert wurde.
gp  type gibt den MIME-Type der Datei an.
gp  size ist die Größe der Datei oder 0, falls versucht wurde, eine zu große Datei hochzuladen.
gp  name schließlich enthält den ursprünglichen Namen der Datei auf dem Client-Rechner inklusive Pfad. Besonders viel Nutzen hat dieses Feld nicht.

Uploads annehmen

Der folgende Ausschnitt aus dem Skript upload.php nimmt eine Datei entgegen, die durch das weiter oben gezeigte Formular übermittelt wurde, und speichert sie unter einem neuen Namen, falls es sich um eine MP3-Datei handelt:

if (is_uploaded_file ($_FILES['musik']['tmp_name'])) {
   if (preg_match ("/^audio\/m.*/", 
        $_FILES['musik']['type'])) {
      echo "Danke f&uuml;r die Musik!<br />";
      move_uploaded_file ($_FILES['musik']['tmp_name'],
           'musik.mp3');
   } else {
      echo "Falscher Dateityp!<br />";
   }
} else {
   echo "Upload-Fehler (Datei zu gro&szlig;?).<br />";
}

Die Funktion is_uploaded_file() erwartet als Argument den Namen der temporären Datei, der im Feld tmp_name der $_FILES-Elemente steht, und liefert true zurück, wenn es sich um eine korrekt hochgeladene Datei handelt. move_uploaded_file() verschiebt die hochgeladene Datei an die gewünschte Stelle. Der Pfad kann relativ zum PHP-Skript selbst angegeben werden.

Reguläre Ausdrücke

Da einige Browser für MP3-Dateien den MIME-Type audio/mp3 und andere audio/mpeg übermitteln, wird für den Vergleich der Eigenschaft type ein regulärer Ausdruck verwendet. Die Funktion preg_match() führt ein Perl-kompatibles Pattern-Matching nach folgender Syntax durch:

preg_match ($muster, $string)

Die Funktion gibt true zurück, wenn $muster in $string gefunden werden kann, ansonsten false. Beachten Sie, dass $muster formal ein String sein muss, also in Anführungszeichen steht, innerhalb dieses Strings aber trotzdem von // umschlossen wird.

Im obigen Beispiel wird der reguläre Ausdruck /^audio\/m.*/ verwendet. Wenn Sie die Syntax regulärer Ausdrücke nicht verstehen, schlagen Sie bitte in Kapitel 6, Konzepte der Programmierung, nach. Kurz gesagt bedeutet der hier verwendete Ausdruck: Am Anfang des durchsuchten Textes (^) muss audio stehen, gefolgt von einem Slash (dargestellt durch die Escape-Sequenz \/), einem m und keinem oder mehreren beliebigen Zeichen (.*).

Auf ähnliche Weise funktioniert die Anweisung preg_replace(), die dem Perl-Operator s/// entspricht und Ersetzungen in Strings vornimmt:

preg_replace ($muster, $ersatz, $string)

ersetzt in $string jedes Vorkommen von $string durch $ersatz. Beispielsweise ersetzt die folgende Anweisung jedes HTML-Tag beziehungsweise jeden in spitzen Klammern stehenden Ausdruck in der Variablen $eingabe durch gar nichts, eliminiert also HTML-Tags:

$eingabe = preg_replace ("/<[^>]+>/", "", $eingabe);

Der eigentliche reguläre Ausdruck /<[^>]+>/ besagt Folgendes: ein Kleiner-Zeichen (<), gefolgt von einem oder mehreren Zeichen, die kein Größer-Zeichen sind ([^>]+), und einem Größer-Zeichen am Ende (>).

PHP unterstützt gleich mehrere RegExp-Modelle; neben der hier gezeigten Perl-kompatiblen Version werden noch ein abgespecktes eigenes Modell und eine POSIX-Variante angeboten, die jeweils ihre eigenen Anweisungen besitzen. In der Praxis dürften die meisten Programmierer aber mit der Perl-Version am besten zurecht kommen, weil die Arbeit mit regulären Ausdrücken oft am Beispiel von Perl erlernt wird.


Galileo Computing

18.2.4 Gästebücher und Counter mit Textdateien  downtop

Von PHP aus können Textdateien gelesen und beschrieben werden, die sich auf demselben Host befinden wie das PHP-Skript selbst. Textdateien können auf vielfältige Weise für die Web-Programmierung nützlich sein:

gp  Der Austausch von Seiteninhalten kann erleichtert werden. Statt des PHP-Skripts selbst muss eventuell nur noch der Inhalt der Textdatei bearbeitet werden.
gp  In einer Textdatei können sowohl Session-basierte als auch allgemeine Daten zwischengespeichert werden. Beispielsweise kann die bisherige Anzahl von Besuchern für einen Counter gespeichert werden.
gp  Textdateien können als einfachere Alternative zu Datenbanken eingesetzt werden, wenn nicht allzu viele Daten zu verwalten sind oder wenn irgendwo keine Datenbank zur Verfügung steht.

Textdateien lesen

Das folgende erste Beispiel zeigt, wie die erste Zeile einer Textdatei gelesen wird. Die Datei befindet sich im selben Verzeichnis wie das Skript, sodass nur der Dateiname als Pfad benötigt wird. Die Datei heißt test.txt.

// Die Datei zum Lesen öffnen
$fh = fopen ("test.txt", "r");
// Die erste Zeile, maximal aber 100 Zeichen lesen
$line = fgets ($fh, 100);
// Die Datei schließen:
fclose ($fh);
echo ("In der Textdatei steht: <b>$line</b>");

Für das Öffnen einer Datei ist die Funktion fopen() mit der folgenden Syntax zuständig:

$fh = fopen ($dateiname, $modus);

Die Datei $dateiname wird geöffnet. $modus zeigt an, auf welche Weise auf diese Datei zugegriffen wird. Die wichtigsten Modi sind folgende:

gp  "r" steht für read (lesen).
gp  "w" bedeutet write (schreiben). Falls die Datei noch nicht existiert, wird sie neu angelegt. Existierte sie bereits, dann wird ihr bisheriger Inhalt gelöscht.
gp  "a" ist der Modus append (anhängen). Falls die Datei noch nicht existiert, wird sie neu angelegt. Existierte sie bereits, dann wird alles, was hineingeschrieben wird, an den bisherigen Inhalt angehängt.

Das Ergebnis des Öffnens ist ein Dateihandle, auch Dateideskriptor genannt: Für jede offene Datei wird eine eindeutige ganze Zahl erzeugt, die diese Datei identifiziert. Das Ergebnis von fopen() wird einer Variablen zugewiesen, über die dann jeweils der Zugriff auf die Datei erfolgt.

Für das Lesen aus einer Textdatei ist die folgende Anweisung zuständig:

$line = fgets ($fh, $laenge);

fgets() liest aus der Datei, die durch $fh repräsentiert wird, die also zuvor mittels fopen() zum Lesen geöffnet wurde. Es wird bis zum nächsten Zeilenumbruch oder bis zur maximalen Länge von $laenge Zeichen gelesen, je nachdem, was zuerst auftritt. Um auf jeden Fall ganze Zeilen zu lesen, sollte der Wert von $laenge recht hoch angesetzt werden.

Erst seit PHP 4.2 ist die Angabe der Länge optional, wurde dann allerdings als 1.024 angenommen. Seit PHP 4.3 bedeutet das Weglassen der Längenangabe eine beliebige Länge. Da bei den meisten Providern nicht immer die neueste PHP-Version läuft, sollten Sie immer eine Länge angeben, wenn Sie es nicht genau wissen.

fclose ($fh);

Diese Anweisung schließt eine Datei, die zuvor mittels fopen() geöffnet wurde. Es ist guter Stil, alle Dateien selbst zu schließen, die Sie nicht mehr benötigen. Am Ende eines Skripts werden Dateien trotzdem automatisch geschlossen.

Das folgende zweite Beispiel zeigt, wie Sie alle Zeilen einer Textdatei lesen können:

// Datei öffnen
$fh = fopen ("test.txt", "r");
// Schleife
while (!feof ($fh)) {
   $line = fgets ($fh, 1024);
   echo ("$line<br>\n");
}
fclose ($fh);

Die neue Funktion feof() hat die folgende Syntax:

$ende = feof ($fh);

Sie überprüft, ob beim nächsten Lesen aus der angegebenen Datei noch etwas kommen würde oder ob bereits das Dateiende (End Of File) erreicht ist.

Im obigen Beispiel wird die folgende Formulierung als Bedingung für die Schleife verwendet:

!feof($fh)

Wie Sie aus früheren Kapiteln wissen sollten, ist ! der Verneinungs-Operator, das logische Nicht. Er verkehrt logische Werte in ihr Gegenteil. Schließlich soll gerade dann weiter aus der Datei gelesen werden, wenn EOF noch nicht erreicht ist.

Ein rudimentäres Gästebuch

Das Beispiel in Listing 18.11 ist ein rudimentäres Gästebuch, ähnlich wie das weiter oben in Perl programmierte. Zum Lesen und zur Ausgabe des Inhalts der Textdatei wird der Code aus dem vorigen Beispiel übernommen.

Listing 18.11   Ein einfaches PHP-Gästebuch

<html>
<head>
<title>PHP-Gaestebuch 1</title>
</head>
<body>
<h1>G&auml;stebuch</h1>
<?php
 
   // Gibt es eine Nachricht zum Anhängen?
   if ($_POST['msg']) {
      $msg = $_POST['msg'];
      // Datei zum ANHÄNGEN öffnen:
      $fh = fopen ("gast.txt", "a");
      // Eine Trennlinie in die Datei schreiben:
      fputs ($fh, "<hr size=3 width=\"80%\">\n");
      // Die Daten in die Datei schreiben
      // E-Mail-Adresse eingegeben?
      $name = $_POST['user'];
      if ($_POST['mail']) {
         $mail = $_POST['mail'];
         fputs ($fh, "<a href=\"mailto:$mail\">
              <b>$name</b></a> schrieb:<br /><br />");
      } else {
         fputs ($fh, "<b>$name</b> schrieb:
              <br /><br />");
      }
      $comment = $_POST['comment'];
      fputs ($fh, "$comment\n");
      fclose ($fh);
   }
 
?>
<form action="gast.php" method="post">
  <input type="hidden" name="msg" value="1" />
  <h2>Kommentar hinzuf&uuml;gen</h2>
  Ihr Name:
  <input type="text" name="user" size="30" />
  <br />
  Ihre E-Mail:
  <input type="text" name="mail" size="30" />
  <br />
  Ihr Kommentar:<br />
  <textarea name="comment" cols="40" rows="10">
  </textarea>
  <br />
  <input type="submit" value="Abschicken" />
</form>
<h2>Bisherige Eintr&auml;ge:</h2>
<?php
 
   if ($fh = fopen ("gast.txt", "r")) {
      while (!feof ($fh)) {
         $line = fgets ($fh, 500);
         echo ("$line<br>\n");
      }
      fclose ($fh);
   } else {
      echo "Noch keine Eintr&auml;ge.";
   }
 
?>
</body>
</html>

In Textdateien schreiben

Die Anweisung fputs() wird zum Schreiben in eine Textdatei verwendet:

fputs ($fh, $text);

schreibt den String $text in die Datei. Beachten Sie, dass nicht automatisch ein Zeilenumbruch angehängt wird. Wenn Sie Text zeilenweise in die Datei schreiben möchten, sollten Sie Folgendes schreiben:

fputs ($fh, "$text\n");

Ganz unten im Skript wird fopen() einfach als Bedingung für eine if-Abfrage verwendet. Das ist eine nützliche Schreibweise, weil $fh den Wert 0 erhält, wenn die Datei gast.txt nicht existiert oder wenn ein Fehler auftritt. In diesem Fall wird der else-Block ausgeführt, der darauf hinweist, dass keine Einträge vorhanden sind.

Alle anderen Anweisungen in dem einfachen Gästebuch-Beispiel wurden bereits erläutert. Interessant ist möglicherweise noch, dass in die Gästebuchdatei gast.txt fertiger HTML-Code hineingeschrieben wird, der später durch das PHP-Skript in das Ausgabedokument eingefügt wird.

Datum und Uhrzeit

Ein richtiges Gästebuch sollte natürlich auch Datum und Uhrzeit eines Postings enthalten. In diesem Unterabschnitt werden die wichtigsten PHP-Funktionen zum Ermitteln der Systemzeit und ihrer Bestandteile vorgestellt.

Die Funktion time() ermittelt einen POSIX-kompatiblen Zeitstempel, das heißt die Anzahl der Sekunden, die seit dem 01. Januar 1970 um 00:00 Uhr GMT vergangen sind. Mit Hilfe der Funktion date() kann eine solche Zeitangabe formatiert werden:

date ($format, $time)

Datums- und Uhrzeitformate

$format ist ein Format-String, der beliebige Zeichen zur Trennung der Datums- und Uhrzeitkomponenten enthalten kann. Die Komponenten selbst werden durch die Verwendung folgender Buchstaben ermittelt:

gp  j ist der Tag im Monat.
gp  d steht ebenfalls für den Tag, gibt ihn allerdings zweistellig an (aus 9 wird etwa 09).
gp  w gibt den Wochentag als Zahlenwert an, wobei 0 für Sonntag steht, 1 für Montag, bis 6 für Samstag.
gp  n ist der numerisch angegebene Monat.
gp  m ist noch einmal die Nummer des Monats, aber zweistellig.
gp  y gibt das zweistellige Jahr an.
gp  Y bedeutet ebenfalls das Jahr, allerdings vierstellig.
gp  G ist die Stunde im 24-Stunden-Format.
gp  H ist ebenfalls die Stunde im 24-Stunden-Format, jedoch mit erzwungener Zweistelligkeit.
gp  i gibt die Minuten zweistellig an.
gp  s steht für die zweistelligen Sekunden.

Datum und Uhrzeit sollen zum Gästebuch in folgender Form hinzugefügt werden:

    Peter Schmitz schrieb am 28.08.2002 um 15:19 Uhr:
       

Die passenden Datums- und Uhrzeitangaben werden vor dem Öffnen der Datei zum Anhängen erstellt:

$jetzt = time();   // Sekunden seit EPOCH
$datum = date ("d.m.Y", $jetzt);
$zeit = date ("H:i");

Diese Angaben werden zur Ausgabe in die Datei hinzugefügt:

if ($_POST['mail']) {
   $mail = $_POST['mail'];
   fputs ($fh, "<a href=\"mailto:$mail\"><b>$name</b>
        </a> schrieb am $datum um $zeit: <br /><br />");
} else {
   fputs ($fh, "<b>$name</b> schrieb am $datum um $zeit: 
        <br /><br />");
}

Das Gästebuch in der korrekten Reihenfolge

Das vorige Beispiel fügt den neuesten Eintrag unten in der Liste ein. In den meisten Gästebüchern befindet sich der neueste Eintrag dagegen oben, der älteste unten.

Um dies zu erreichen, sind zwei mögliche Ansätze denkbar:

1. In eine ganz neue Datei wird zuerst der aktuelle Eintrag geschrieben, darunter die Inhalte der bisherigen Datei. Anschließend wird die bisherige Datei umbenannt, etwa in gast_alt.txt, und die neue in gast.txt.
       
2. Jeder Eintrag wird in einer separaten Datei gespeichert; die Dateinamen sind durchnummeriert (etwa eintrag1.txt bis eintrag356.txt). In einer Schleife werden nun die Nummern von der höchsten bis hinunter zur 1 durchlaufen, die jeweilige Datei wird geöffnet und ihr Inhalt ausgegeben.
       
    Die Gesamtzahl der Einträge wird in einer separaten Datei aufbewahrt. Der Nachteil besteht darin, dass dieser Ansatz bei hoher Zugriffszahl viel Performance kostet.
       

Der Code für das Eintragen ins Gästebuch nach dem ersten der beiden Ansätze sieht folgendermaßen aus:

// Gibt es eine Nachricht zum Anhängen?
if ($_POST['msg']) {
   // Datum und Uhrzeit ermitteln:
   $jetzt = time(); // Sekunden seit EPOCH
   $datum = date ("d.m.Y", $jetzt);
   $zeit = date ("H:i");
   // Neue Datei zum Schreiben öffnen:
   $fh = fopen ("gast_neu.txt", "w");
   $name = $_POST['name'];
   // Die Daten in die Datei schreiben
   // E-Mail-Adresse eingegeben?
   if ($_POST['mail']) {
      $mail = $_POST['mail'];
      fputs ($fh, "<a href=\"mailto:$mail\"><b>$name</b>
         </a> schrieb am $datum um $zeit:<br /><br />");
   } else {
      fputs ($fh, "<b>$name</b> schrieb am $datum um 
           $zeit: <br /><br />");
   }
   $comment = $_POST['comment'];
   fputs ($fh, "$comment\n");
   // Eine Trennlinie in die Datei schreiben:
   fputs ($fh, "<hr size=3 width=\"60%\"><br>\n");
   // Die alten Einträge in die neue Datei kopieren:
   $fh2 = fopen ("gast.txt", "r");
   while (!feof ($fh2)) {
      $line = fgets ($fh2, 500);
      fputs ($fh, "$line");
   }
   // Dateien schließen:
   fclose ($fh);
   fclose ($fh2);
   // Dateien umbenennen:
   rename ("gast.txt", "gast_alt.txt");
   rename ("gast_neu.txt", "gast.txt");
}   

Die Anweisung rename() zum Umbenennen von Dateien hat die folgende Syntax:

rename ($altname, $neuname);

Die Anweisung benennt die Datei $altname in $neuname um. Beide Parameter sind Strings; es wird nicht mit Dateideskriptoren gearbeitet. Die umzubenennende Datei sollte zuvor mittels fclose() geschlossen werden, falls sie offen war.

Windows-Besonderheit

Die oben gezeigte Lösung funktioniert in dieser Form nur unter PHP für UNIX. Die UNIX-Version von PHP ruft intern den Befehl mv auf, dem es egal ist, wenn die Datei, die den neuen Dateinamen bilden soll, bereits existiert. Die Windows-Version verwendet dagegen den Befehl rename, der dies verbietet.

Für Windows muss die Lösung daher folgendermaßen ergänzt werden (wobei sie allerdings auch unter UNIX funktioniert):

// ... nach dem Schreiben des Eintrags
// Dateien schließen:
fclose ($fh);
fclose ($fh2);
// Dateien umbenennen:
if (file_exists ("gast_alt.txt")) {
   unlink ("gast_alt.txt");
   // gast_alt.txt löschen, falls sie bereits existiert
}
unlink ("gast.txt");
rename ("gast_neu.txt", "gast.txt");

Der Befehl unlink() löscht die angegebene Datei. Auch bei diesem Befehl ist das Argument der Dateiname.

Besseres Gästebuch

Listing 18.12 zeigt eine recht elegante Lösung des zweiten Ansatzes für ein Gästebuch in der richtigen Reihenfolge. Die wichtigen Änderungen stehen als Kommentare im PHP-Code. Beachten Sie, dass Sie die Datei number.txt vor der ersten Ausführung selbst erzeugen und mit dem Wert 0 füllen müssen.

Listing 18.12   Das Gästebuch mit einzelnen Posting-Dateien

<html>
<head>
<title>G&auml;stebuch - neuer Versuch</title>
</head>
<body bgcolor="#000000" text="#FFFFFF" link="#00FFFF" 
vlink="#00FF00" alink="#FFFF00">
<div align="center">
<a name="oben"></a>
<table border="2" cellpadding="4" width="70%">
  <tr>
    <td align="center">
      <h1><font face="Verdana, Arial, Helvetica, sans-
      serif">PHP-Tutorial, G&auml;stebuch</font></h1>
      <form action="gast.php" method="post">
        <input type="hidden" name="msg" value="1" />
        <table border="0" cellpadding="4">
          <tr>
          <td colspan="2" align="center">
            <h2><font face="Verdana, Arial, Helvetica, 
            sans-serif">Ihren Eintrag hinzuf&uuml;gen
            </font></h2>
          </td>
        </tr>
        <tr>
          <td>
            <font face="Verdana, Arial, Helvetica, 
            sans-serif"><b>Ihr Name:</b></font>
          </td>
          <td>
            <input type="text" name="name" size="30" 
            maxlength="40" />
          </td>
        </tr>
        <tr>
          <td>
            <font face="Verdana, Arial, Helvetica, 
            sans-serif"><b>Ihre E-Mail:</b></font>
          </td>
          <td>
            <input type="text" name="mail" size="30" 
            maxlength="40" />
          </td>
        </tr>
        <tr>
          <td colspan="2">
            <font face="Verdana, Arial, Helvetica, 
            sans-serif"><b>Ihr Eintrag:</b></font>
            <br />
            <textarea name="comment" cols="40" 
            rows="8"></textarea>
            <br />
            <input type="submit" value="Abschicken" />
            <input type="reset" 
            value="Zur&uuml;cksetzen" />
            </font>
          </td>
        </tr>
      </table>
    </td>
  </tr>
<?php
 
   // Nachricht zum Hinzufügen?
   if ($_POST['msg']) {
      // Bisherige Anzahl lesen
      $nfile = fopen ("number.txt", "r");
      $num = fgets ($nfile, 10);
      fclose ($nfile);
      // Nummer erhöhen, um neuen Eintrag zu erzeugen
      $num++;
      // Datum und Uhrzeit basteln
      $jetzt = time();
      $datum = date ("d.m.Y", $jetzt);
      $zeit = date ("h:i", $jetzt);
      // Neuen Eintrag in neue Datei schreiben
      $mfile = fopen ("gast$num.txt", "w");
      $name = $_POST['name'];
      // E-Mail-Adresse eingegeben?
      if ($_POST['mail']) {
         $mail = $_POST['mail'];
         fputs ($mfile, "<b><a href=\"mailto:$mail\">
              $name</a></b> schrieb ");
      } else {
         fputs ($mfile, "<b>$name</b> schrieb ");
      }
      fputs ($mfile, "am <i>$datum</i> um 
           <i>$zeit</i>:\n");
      $comment = $_POST['comment'];
      fputs ($mfile, $comment);
      fclose ($mfile);
      // Neue Anzahl schreiben
      $nfile = fopen ("number.txt", "w");
      fputs ($nfile, $num);
      fclose ($nfile);
   }
   // Das Gästebuch anzeigen
   // Anzahl lesen
   $nfile = fopen ("number.txt", "r");
   $num = fgets ($nfile, 10);
   fclose ($nfile);   // Alle Einträge darstellen
   for ($i = $num; $i >= 1; $i--) {
      $mfile = fopen ("gast$i.txt", "r");
      echo ("<tr>\n");
      echo ("<td>\n");
      echo ("<font face=\"Verdana, Arial, Helvetica, 
           sans-serif\">");
      echo ("<b>$i.&nbsp;&nbsp;</b>");
      while (!feof ($mfile)) {
         $line = fgets ($mfile, 500);
         echo ("$line<br>\n");
      }
      fclose ($mfile);
      echo ("</font>\n</td>\n</tr>\n");
   }
 
?>
</table>
</div>
</body>
</html>

Diese Lösung ist immer dann geeigneter, wenn nicht der Inhalt des ganzen Gästebuchs auf derselben Seite angezeigt werden soll, sondern zum Beispiel je 10 oder 20 Einträge. Da die Einträge in einzelnen Dateien liegen, ist der Zugriff auf einen Teil der Daten erheblich einfacher.

Mehrseitiges Gästebuch

Im nächsten Schritt soll die Anzeige daher auf 10 Einträge pro Seite beschränkt werden. Dazu sind folgende Vorüberlegungen nötig:

gp  Die Nummer, bei der jeweils mit der Anzeige der Nachrichten begonnen wird, muss stets als Parameter mit dem Formular übergeben werden.
gp  Es muss noch einmal überprüft werden, ob die angeforderte Nachrichtennummer überhaupt existiert, damit niemand das Skript mit Hilfe einer willkürlichen Angabe wie gast.php?offset=999 zu einem Fehler zwingt.
gp  Es sollen Links zum Vorwärts- und Rückwärtsblättern angezeigt werden, je nachdem, ob diese Richtungen zurzeit überhaupt verfügbar sind. Diese Links werden durch Anhängen von Angaben wie ?offset=40 an die URL gebildet – ein normaler Link verursacht ohnehin stets die Versandmethode GET. Für den Fall, dass ein neuer Eintrag per Formular eingefügt wird (Versandmethode POST), wird der Offset dagegen als Hidden-Formularfeld übergeben.

Listing 18.13 zeigt den geänderten PHP-Teil dieser Lösung.

Listing 18.13   Ein Gästebuch mit 10 Einträgen pro Seite

// Offset lesen, falls per GET übertragen:
if ($_SERVER['REQUEST_METHOD'] == "GET") {
   $offset = $_GET['offset'];
}
// Nachricht zum Hinzufügen?
if ($_POST['msg']) {
   // Bisherige Anzahl lesen
   $nfile = fopen ("number.txt", "r");
   $num = fgets ($nfile, 10);
   fclose ($nfile);
   // Nummer erhöhen, um neuen Eintrag zu erzeugen
   $num++;
   // Datum und Uhrzeit basteln
   $jetzt = time();
   $datum = date ("d.m.Y", $jetzt);
   $zeit = date ("h:i", $jetzt);
   // Neuen Eintrag in neue Datei schreiben
   $mfile = fopen ("gast$num.txt", "w");
   $name = $_POST['name'];
   // E-Mail-Adresse eingegeben?
   if ($_POST['mail']) {
      $mail = $_POST['mail'];
      fputs ($mfile, "<b><a href=\"mailto:$mail\">
           $name</a></b> schrieb ");
   } else {
      fputs ($mfile, "<b>$name</b> schrieb ");
   }
   fputs ($mfile, "am <i>$datum</i> um 
        <i>$zeit</i>:\n");
   $comment = $_POST['comment'];
   fputs ($mfile, $comment);
   fclose ($mfile);
   // Neue Anzahl schreiben
   $nfile = fopen ("number.txt", "w");
   fputs ($nfile, $num);
   fclose ($nfile);
   // Die Nachrichten sollen hier ab der
   // neuesten Nachricht angezeigt werden
   $offset = $num;
}
// Das Gästebuch anzeigen
// Anzahl lesen
$nfile = fopen ("number.txt", "r");
$num = fgets ($nfile, 10);
fclose ($nfile);
// Offset prüfen
if (!$offset || $offset < 1 || $offset > $num) {
   $offset = $num;
}
// Einträge von Offset bis Offset - 9
// bzw. bis 1 darstellen
$unten = $offset - 9;
if ($unten < 1) {
   $unten = 1;
}
echo ("<tr><td align=\"center\">");
echo ("<font face=\"Verdana, Arial, Helvetica, 
     sans-serif\">");
// Links für die älteren/neueren Einträge
// Gibt es NEUERE Einträge als $offset?
if ($offset < $num) {
   $nextoffset = $offset + 10;
   if ($nextoffset > $num) {
      $nextoffset = $num;
   }
   echo ("<a href=\"gast.php?offset=$nextoffset\">
        &lt;&lt; neuere</a>&nbsp;&nbsp;&nbsp;");
}
// Gibt es ÄLTERE Einträge als $offset?
if ($offset > 10) {
   $prevoffset = $offset - 10;
   if ($prevoffset < 1) {
      $prevoffset = 1;
   }
   echo ("<a href=\"gast.php?offset=$prevoffset\">
        &auml;ltere &gt;&gt;</a><br>");
}
echo ("</td></tr>\n");
echo ("</font>\n");
// Die eigentlichen Eintr&auml;ge anzeigen
for ($i = $offset; $i >= $unten; $i--) {
   $mfile = fopen ("gast$i.txt", "r");
   echo ("<tr>\n");
   echo ("<td>\n");
   echo ("<font face=\"Verdana, Arial, Helvetica, 
        sans-serif\">");
   echo ("<b>$i.&nbsp;&nbsp;</b>");
   while (!feof ($mfile)) {
      $line = fgets ($mfile, 500);
      echo ("$line<br>\n");
   }
   fclose ($mfile);
   echo ("</font>\n</td>\n</tr>\n");
}   

Ein kleiner Counter

Neben Gästebüchern sind Besucherzähler eine der beliebtesten Anwendungen für die dynamische Serverprogrammierung. Auch wenn der tiefere Sinn eines Counters nicht unbedingt einsichtig ist, soll eine Lösung hier nicht fehlen.

Das Grundprinzip entspricht dem der Anzahl der Einträge beim Gästebuch: Der Inhalt einer Textdatei enthält die bisherige Besucheranzahl; diese Zahl wird gelesen, angezeigt und um 1 erhöht. In der hier gezeigten Form kann der Counter in eine beliebige PHP-Seite mit eingebaut werden, die Ausgabe erfolgt als Text. Listing 18.14 zeigt zunächst den entsprechenden PHP-Code.

Listing 18.14   Ein einfacher Counter

<?php
 
   // Datei mit den bisherigen Zugriffszahlen lesen
   $nfile = fopen ("besucher.txt", "r");
   $besucher = fgets ($nfile, 10);
   fclose ($nfile);
   // Wert um 1 erhöhen
   $besucher++;
   // Ausgabe
   echo ("Sie sind der $besucher. Besucher!");
   // Neuen Wert in der Textdatei speichern
   $nfile = fopen ("besucher.txt", "w");
   // Eine Sperre erwerben, um die Datei zu blockieren
   flock ($nfile, 2);
   // Den neuen Wert schreiben
   fputs ($nfile, $besucher);
   // Die Sperre aufheben
   flock ($nfile, 3);
   fclose ($nfile);
 
?>

Dateien sperren

In diesem Code wird nur eine neue Anweisung verwendet:

flock ($fh, $mode);

Diese Funktion sperrt die geöffnete Datei mit dem Dateihandle $h für anderweitige Zugriffe beziehungsweise hebt eine solche Sperre wieder auf. Der Modus 2 errichtet eine exklusive Sperre, der Modus 3 setzt sie wieder zurück.

An der Stelle, an der die Sperre errichtet wird, wartet das Skript ab, bis eine eventuelle andere Sperre auf dieselbe Datei wieder aufgehoben wird. Die Verwendung von flock() wird immer dann dringend empfohlen, wenn in derartige Dateien geschrieben wird – es könnten mehrere gleichzeitige Zugriffe erfolgen. Auch bei den Schreibzugriffen in den Gästebuch-Skripten wäre eine solche Sperre sinnvoll.

Userprüfung für Counter

Ein weiteres Problem beim Counter ist folgendes: Greift derselbe Benutzer hintereinander mehrmals auf die Seite zu, dann wird er auch mehrmals gezählt – dies verfälscht das Ergebnis.

In der folgenden verbesserten Lösung werden zwei Kontrollmechanismen eingeführt, um dies zu verhindern:

1. Die IP-Adresse des jeweiligen Besuchers wird gespeichert.
       
2. Die Browser-Kennung eines Besuchers wird ebenfalls gesichert.
       

Falls innerhalb einer Stunde jemand wiederkommt, der dieselben Merkmale erfüllt, wird er nicht gezählt.

Innerhalb von PHP wird auf die beiden Informationen, die in Umgebungsvariablen zur Verfügung stehen, als $_SERVER['REMOTE_ADDR'] (IP-Adresse des entfernten Client-Rechners) beziehungsweise $_SERVER['HTTP_USER_AGENT'] (Browsersignatur) zugegriffen.

Die beiden Werte werden nebst einer Zugriffszeit in der Textdatei users.txt gespeichert. Bei jedem Zugriff wird dort nachgeschlagen, ob der entsprechende Besucher schon einmal da war. Ein Eintrag in dieser Datei hat die folgende Form:

IP###Browser-ID###Zeitstempel

Hier sehen Sie ein Beispiel:

194.13.18.51###Mozilla (compatible; MSIE 6.0; Win32)###1030123456

Die vollständige neue Lösung sehen Sie in Listing 18.15.

Listing 18.15   Ein verbesserter Counter

<?php
 
   // Datei mit den bisherigen Zugriffszahlen lesen
   $nfile = fopen ("besucher.txt", "r");
   $besucher = fgets ($nfile, 10);
   fclose ($nfile);
   // War dieser Besucher innerhalb der letzten 60 Min.
   // bereits hier?
   $jetzt = time();
   $user = $REMOTE_ADDR;
   $browser = $HTTP_USER_AGENT;
   // Annahme: Besucher war noch nicht hier.
   $visited = false;
   // Bisherige Benutzereinträge lesen
   $vfile = fopen ("besucherinfo.txt", "r");
   while (!feof ($vfile)) {
      $info = fgets ($vfile, 100);
      $infos = explode ("###", $info);
      if ($user == $infos[0] && 
           $browser == $infos[1]) {
         // 1 Std., d.h. 60*60 Sek., noch nicht vorbei?
         if ($infos[2] + 3600 >= $jetzt)
         {
            // Aha! Der war schon hier!
            $visited = true;
            break;
         }
      }
   }
   fclose ($vfile);
   if (!$visited) {
      // Wert um 1 erhöhen
      $besucher++;
      // Neue Anzahl in der Textdatei speichern
      $nfile = fopen ("besucher.txt", "w");
      // Eine Sperre erwerben
      flock ($nfile, 2);
      // Den neuen Wert schreiben
      fputs ($nfile, $besucher);
      // Die Sperre aufheben
      flock ($nfile, 3);
      fclose ($nfile);
      // Info über diesen Besucher schreiben
      $vfile = fopen ("besucherinfo.txt", "a");
      flock ($vfile, 2);
      fputs ($vfile, "$user###$browser###$jetzt\n");
      flock ($vfile, 3);
      fclose ($vfile);
   }
   // Ausgabe
   echo ("Sie sind der $besucher. Besucher!");
 
?>

Um das Ganze einfacher ausprobieren zu können, können Sie die 3600 (Sekunden) vorübergehend auf einen viel niedrigeren Wert setzen, zum Beispiel auf 30: Sehr schnelles Aktualisieren des Dokuments ändert die Anzahl nicht, ein erneutes Laden nach 30 Sekunden aber durchaus.

Grenzen dieser Counter-Lösung

Beachten Sie schließlich, dass auch diese Lösung nicht perfekt ist: Wenn sich zwei verschiedene Rechner hinter demselben NAT-Router oder Proxy-Server befinden, und der gleiche Browser auf ihnen läuft, werden ihre Zugriffe nur als ein einzelner gewertet. Um diese Gefahr zu vermindern, können Sie die hier angebotene Lösung mit einem Cookie kombinieren.


Galileo Computing

18.2.5 Zugriff auf MySQL-Datenbanken  toptop

Einer der Gründe für den Erfolg, den PHP als Webserver-Sprache in den letzten Jahren genießt, ist die einfache Zusammenarbeit mit dem freien Datenbankserver MySQL. Führende Webhoster wie 1&1 oder Strato bieten in ihren teureren Tarifen PHP-Scripting und eine MySQL-Datenbank an, sodass eine solche Lösung auch in der Praxis relativ kostengünstig zur Verfügung steht.

Im Prinzip funktioniert der Zugriff von einer Programmiersprache auf eine SQL-Datenbank immer gleich: Eine SQL-Abfrage wird als String formuliert und mit Hilfe einer speziellen Anweisung an die Datenbank übermittelt. Die Antwort der Datenbank wird durch eine weitere Anweisung abgeholt und in einer für die Programmiersprache passenden Datenstruktur zur weiteren Verarbeitung gespeichert.

Dieses Grundprinzip wurde in Kapitel 7, Datenbanken, bereits im Zusammenhang mit der Java-Datenbankschnittstelle JDBC erläutert. Für PHP und MySQL gilt eine ähnliche Arbeitsweise, die im Folgenden vorgestellt wird.

Erste Beispiele

Um Ihnen einen ersten Überblick über die Funktionsweise von Datenbankzugriffen aus PHP-Skripten zu geben, werden hier einige Beispiele mit der Artikel- und Kunden-Datenbank aus Kapitel 7 gezeigt. Anschließend wird als webtypisches Praxisbeispiel ein kleines Quizspiel vorgeführt.

Für die ersten Beispiele müssen Sie innerhalb einer Datenbank namens PROBE die drei folgenden Tabellen anlegen; beispielsweise mit den bereits in Kapitel 7 beschriebenen Tools MySQL CC oder phpmyadmin:

Die Tabelle ADRESSEN muss die folgenden Felder enthalten:


Tabelle 18.2   Die Felder der Datenbanktabelle ADRESSEN

Feldname Datentyp Eigenschaften
NR INT Primärschlüssel, auto_increment
NAME CHAR Länge 50, Index
STRASSE CHAR Länge 50
HAUSNR CHAR Länge 10
PLZ CHAR Länge 5
ORT CHAR Länge 40, Index

Als Nächstes benötigen Sie die Tabelle ARTIKEL mit den folgenden Feldern:


Tabelle 18.3   Die Felder der Datenbanktabelle ARTIKEL

Feldname Datentyp Eigenschaften
ARTNR INT auto_increment, Primärschlüssel
ARTNAME CHAR Länge 30, Index
PREIS INT  
MWST ENUM Werte: '7', '16'

Zu guter Letzt benötigen Sie noch die Tabelle KAEUFE mit diesen Feldern:


Tabelle 18.4   Die Felder der Datenbanktabelle KAEUFE

Feldname Datentyp Eigenschaften
KAUFNR INT auto_increment, Primärschlüssel
NR INT  
ARTNR INT  
STUECK INT  
DATUM DATE  

Nachdem Sie die drei Tabellen angelegt haben, sollten Sie jeweils einige Testdatensätze erstellen. Denken Sie daran, dass die Einträge in der Tabelle KAEUFE plausibel sein müssen, das heißt, die betreffende Kundennummer und die gewählte Artikelnummer müssen existieren.

Es soll als Erstes eine Tabelle ausgegeben werden, die den Kundennamen, den Artikelnamen, die Stückzahl, den Einzelpreis und den Gesamtpreis aller gekauften Waren ausgibt. Listing 18.16 zeigt eine mögliche Lösung.

Listing 18.16   Ausgabe einer SQL-Abfrage als HTML-Tabelle

<html>
  <head>
    <title>Die erste MySQL-Abfrage aus PHP</title>
  </head>
  <body>
    <h1>Hier alle K&auml;ufe</h1>
      <table border="2" cellpadding="4">
        <tr>
          <th>Kauf Nr.</th>
          <th>Kunde Nr.</th>
          <th>Artikel Nr.</th>
          <th>Einzelpreis</th>
          <th>Gesamtpreis</th>
        </tr>
<?php
 
    /* Datenbankverbindung herstellen */
    // Verbindungsdaten zusammenstellen
    $host = "127.0.0.1";  // Rechner
    $user = "dbadmin";    // MySQL-Username
    $pass = "hallo";      // MySQL-Passwort
    // Verbindung herstellen
    $connID = mysql_connect ($host, $user, $pass);
    if (!$connID) {
       echo ("<tr><td>KEINE VERBINDUNG!!</td></tr>");
    }
    // Datenbank auswählen
    mysql_select_db ("probe");
    /* Operationen zur Datenbankabfrage */
    // Abfrage durchführen
    $result = mysql_query ("SELECT NAME, ARTNAME, 
         STUECK, PREIS, STUECK*PREIS FROM adressen, 
         artikel, kaeufe 
         WHERE adressen.NR = kaeufe.NR 
         AND artikel.ARTNR = kaeufe.ARTNR");
    // Datensätze als Arrays holen
    while ($row = mysql_fetch_row ($result)) {
       echo ("<tr>\n");
       for ($i = 0; $i < 5; $i++) {
          $val = $row[$i];
          echo ("<td>$val</td>\n");
       }
       echo ("</tr>\n");
    }
 
?>
    </table>
  </body>
</html>

MySQL-Anweisungen

In dem Skript werden die folgenden neuen Anweisungen verwendet:

gp  $connID = mysql_connect ($host, $user, $pass);
mysql_connect() stellt eine Verbindung zu dem MySQL-Server her, der auf dem Rechner $host läuft (angegeben durch die IP-Adresse oder den Domainnamen; in der Praxis teilt der Provider Ihnen diese Daten mit). In diesem Beispiel wurde die Loopback-Adresse 127.0.0.1 für den lokalen Rechner gewählt. Dem Datenbankserver wird außerdem der Benutzername $user und das Passwort $pass übergeben. Sollte MySQL ohne Benutzernamen und Passwort konfiguriert sein (was Sie öffentlich niemals tun sollten!), so sind $user und $pass einfach leere Strings ("").
    mysql_connect() gibt im Erfolgsfall eine ganze Zahl außer 0 zurück, die in der Variablen $connID gespeichert wird. Durch die Überprüfung von $connID lässt sich anschließend einfach feststellen, ob die Verbindung aufgebaut werden konnte oder nicht.
       
gp  mysql_select_db ($db_name);
Diese Funktion wählt eine der Datenbanken auf dem Datenbankserver aus, mit dem zuvor eine Verbindung hergestellt wurde. Im Beispiel wird "probe" übergeben, weil die kleine Bestelldatenbank so heißt.
    Es gibt noch eine zweite Version dieses Befehls, nämlich folgende:
       
mysql_select_db ($db_name, $connID);
    Diese Version muss aber nur dann verwendet werden, wenn tatsächlich mehrere Datenbankverbindungen gleichzeitig bestehen, sodass die jeweilige Verbindung eindeutig identifiziert werden muss.
       
gp  $result = mysql_query($sql_string);
mysql_query() übergibt eine SQL-Abfrage an die Datenbank. Ein Verweis auf das Abfrageergebnis wird in $result gespeichert und kann anschließend verwendet werden, um Datensätze aus dem Abfrageergebnis zu lesen. Auch hier kann wieder zusätzlich die Datenbankverbindung angegeben werden.
gp  $row = mysql_fetch_row($result);
Holt die jeweils nächste Zeile (den nächsten Datensatz) aus dem mit mysql_query() ermittelten Abfrageergebnis $result. Um alle Datensätze aus dem Abfrageergebnis zu holen, können Sie diese Anweisung einfach in den Bedingungsteil einer while()-Schleife setzen, da die Funktion null zurückgibt, wenn keine weitere Zeile vorhanden ist.
    Das Ergebnis $row ist ein Array mit numerischen Indizes, in dem die Felder des Abfragedatensatzes in der Reihenfolge stehen, in der sie in der Abfrage angefordert wurden. Eine for()-Schleife ermöglicht den Zugriff auf die einzelnen Felder.
       
gp  $row = mysql_fetch_array($result)
Funktioniert genau wie mysql_fetch_row() – außer, dass die Array-Indizes hier keine aufeinander folgenden Zahlen sind, sondern die eigentlichen Spaltennamen. Dies ermöglicht einen intelligenteren Zugriff auf bestimmte Felder der Abfrage und ist immer dann angebracht, wenn nicht einfach die Felder der Zeile stur hintereinander ausgegeben werden sollen, da dies in solchen Fällen zu besser lesbarem Programmcode führt.
    Allerdings sollten Sie bei mysql_fetch_array() darauf achten, Feldern, die durch eine Operation entstehen (im vorliegenden Fall das Feld STUECK*PREIS) mit Hilfe der SQL-Klausel AS einen eindeutigen Namen zu geben.
       

Obwohl es für das obige Beispiel völlig sinnlos ist, sehen Sie hier den relevanten Teil des Skripts unter Verwendung von mysql_fetch_array() statt mysql_fetch_row():

// Abfrage durchführen'
$result = mysql_query ("SELECT NAME, ARTNAME, STUECK, PREIS, 
STUECK*PREIS AS GESAMT FROM adressen, artikel, kaeufe 
WHERE adressen.NR = kaeufe.NR AND artikel.ARTNR = kaeufe.ARTNR");
// Datensätze als Arrays holen
while ($row = mysql_fetch_array ($result)) {
   echo ("<tr>\n");
   $name = $row['NAME'];
   $artname = $row['ARTNAME'];
   $stueck = $row['STUECK'];
   $preis = $row['PREIS'];
   $gesamt = $row['GESAMT'];
   echo ("<td>$name</td>\n");
   echo ("<td>$artname</td>\n");
   echo ("<td>$stueck</td>\n");
   echo ("<td>$preis</td>\n");
   echo ("<td>$gesamt</td>\n");
   echo ("</tr>\n");
}

In die Datenbank schreiben

In den meisten Fällen genügt es nicht, mit Hilfe der SQL-Anweisung SELECT Felder aus der Datenbank auszuwählen. Viele Anwendungen müssen Datensätze in der Datenbank ändern oder hinzufügen.

Im folgenden Beispiel wird ein einfaches Formular angeboten, mit dessen Hilfe ein Benutzer – der zuvor seinen Namen aus der Liste der registrierten Benutzer, das heißt aus den Datensätzen der Tabelle ADRESSEN, ausgewählt hat – die Waren aus der Tabelle ARTIKEL bestellen kann. Die Bestellungen sollen an die Tabelle KAEUFE angefügt werden.

Hierzu werden zwei PHP-Dateien verwendet: das Bestellskript mit dem Formular und die bereits bekannte Anzeige der kompletten Kaufergebnisse, die noch um das Hinzufügen der per Formular getätigten Bestellungen ergänzt wird.

Das Bestellformular erledigt die folgenden Aufgaben:

gp  Die Daten der Benutzer werden aus der Tabelle ADRESSEN gelesen.
gp  In einem Popup-Menü werden alle Benutzer durch ihre Namen und ihren Ort zur Auswahl gestellt; intern werden die eindeutigen Benutzer-IDs (das Feld NR) übermittelt.
gp  Es erscheint eine Liste aller Artikel samt Einzelpreisen als Tabelle; vor jedem Artikel kann in ein Textfeld die gewünschte Stückzahl eingetragen werden.
gp  Das Abschicken des Formulars sendet die Bestelldaten an das zweite PHP-Skript.

Das zweite Skript dient der Bestellausführung und der Anzeige der bestellten Waren:

gp  Die Bestellung des Kunden wird überprüft: Wurde überhaupt ein Benutzername ausgewählt? Wurden überhaupt Artikel bestellt? Falls nicht, erscheint eine Fehlermeldung.
gp  Die Bestelldaten werden zur Tabelle KAEUFE hinzugefügt.
gp  Es erscheinen wie gehabt alle Bestellungen, wobei der aktuelle Kunde, der zuletzt etwas bestellt hatte, hervorgehoben wird; außerdem wird die Ausgabe nach Kunden sortiert.

Listing 18.17 zeigt das Bestellformular, die Datei bestell.php.

Listing 18.17   Bestellformular für Artikel aus der Datenbank

<html>
  <head>
    <title>Die Bestellung</title>
  </head>
  <body>
    <h1>Bestellen Sie etwas!</h1>
    <form action="anzeige.php" method="post">
      <b>Wer sind Sie?</b>
      <select name="kunde" size=1>
        <option value="-1">[Bitte ausw&auml;hlen]
        </option>
<?php
 
     // Datenbank-Informationen
     $host = "127.0.0.1";
     $user = "dbadmin";
     $pass = "hallo";
     // Datenbank-Verbindung und -Auswahl
     $connID = mysql_connect ($host, $user, $pass);
     mysql_select_db ("probe");
     // Lesen der relevanten Personendaten
     $result = mysql_query ("SELECT NR, NAME, ORT 
          FROM adressen");
     // Ausgabe der Personen als Inhalt des Popups
     while ($row = mysql_fetch_array ($result)) {
        $nr = $row['NR'];
        $name = $row['NAME'];
        $ort = $row['ORT'];
        echo ("<option value=\"$nr\">{$name ($ort)}
             </option>");
     }
 
?>
      </select>
      <br /><br />
      <table border="2" cellpadding="4">
        <tr>
          <th>St&uuml;ck</th>
          <th>Artikel</th>
          <th>Einzelpreis</th>
        </tr>
 
<?php
 
   $result = mysql_query ("SELECT ARTNR, ARTNAME, PREIS 
        FROM artikel");
   while ($row = mysql_fetch_array ($result)) {
      $artnr = $row['ARTNR'];
      $artname = $row['ARTNAME'];
      $preis = $row['PREIS'];
      echo ("<tr>\n");
      echo ("<td><input type=\"text\" name=\"s$artnr\" 
           size=\"6\"></td>\n");
      echo ("<td>$artname</td>\n");
      echo ("<td align=\"right\">$preis</td>\n");
      echo ("</tr>\n");
   }
 
?>
      </table>
      <br /><br />
      <input type="submit" value="Bestellung 
      abschicken" />
    </form>
  </body>
</html>

In der Bestellformular-Datei werden keine neuen Anweisungen verwendet.

Bei den Textfeldern zur Eingabe der gewünschten Artikel-Stückzahlen wird ein s vor die jeweilige Artikelnummer gesetzt, damit die Feldnamen keine reinen Zahlen sind – dies erleichtert den Zugriff über das Array $_POST.

Listing 18.18 zeigt die Implementierung der Bestellverarbeitung.

Listing 18.18   Die Bestellverarbeitung und -ausgabe

<html>
  <head>
    <title>Bestellung angekommen</title>
  </head>
  <body>
    <h1>Danke f&uuml;r Ihre Bestellung!</h1>
 
<?php
 
   // Datenbank-Informationen
   $host = "127.0.0.1";
   $user = "dbadmin";
   $pass = "hallo";
   // Datenbank-Verbindung und -Auswahl
   $connID = mysql_connect ($host, $user, $pass);
   mysql_select_db ("probe");
   // Anzahl der verfügbaren Artikel auslesen
   $result = mysql_query ("SELECT COUNT(ARTNR) 
        FROM artikel");
   $artzahl = mysql_fetch_row($result);
   $artzahl = $artzahl[0];  // Einzelwert statt Array.
   // Bestellung überprüfen
   $kunde = $_POST['kunde'];
   if ($kunde == -1) {
      echo ("<h2>Kein Benutzer angemeldet!</h2>\n");
   } else {
      // Annahme: Nichts bestellt.
      $bestellt = false;
      for ($i = 1; $i <= $artzahl; $i++) {
         $varname = "s$i";
         $best[$i] = $_POST[$varname];
         if ($best[$i] > 0) {
            $bestellt = true;
         }
      }
      if (!$bestellt) {
         echo ("<h2>Es wurde nichts bestellt!</h2>\n");
      } else {
         // Es wurde etwas bestellt!
         // Datum im SQL-Format basteln
         $jetzt = time();
         $datum = date("Y-m-d", $jetzt);
         for ($i = 1; $i <= $artzahl; $i++) {
            $s = $best[$i];
            if ($s > 0) {
               // Bestellvorgang in KAEUFE schreiben
               $result = mysql_query ("INSERT INTO 
                    KAEUFE (NR, ARTNR, STUECK, DATUM) 
                    VALUES ($kunde, $i, $s, $datum)");
            }
         }
      }
   }
 
?>
    <table border=2 cellpadding=4>
    <tr>
      <th>Kunde</th>
      <th>Artikel</th>
      <th>St&uuml;ckzahl</th>
      <th>Einzelpreis</th>
      <th>Gesamtpreis</th>
    </tr>
 
<?php
    /* Operationen zur Datenbankabfrage */
    // Abfrage durchführen
    $result = mysql_query ("SELECT ADRESSEN.NR AS KNR, 
         NAME, ARTNAME, STUECK, PREIS, 
         STUECK*PREIS AS GESAMT 
         FROM ADRESSEN, ARTIKEL, KAEUFE 
         WHERE ADRESSEN.NR = KAEUFE.NR 
         AND ARTIKEL.ARTNR = KAEUFE.ARTNR 
         ORDER BY NAME ASC");
    // Datensätze als Arrays holen
    while ($row = mysql_fetch_array ($result)) {
       echo ("<tr>\n");
       $nr = $row['KNR'];
       $name = $row['NAME'];
       $artname = $row['ARTNAME'];
       $stueck = $row['STUECK'];
       $preis = $row['PREIS'];
       $gesamt = $row['GESAMT'];
       // Ist gerade der AKTUELLE Kunde betroffen?
       if ($nr == $kunde) {
          echo ("<td><font color=\"#FF0000\">
               <b>$name</b></font></td>\n");
       } else {
          echo ("<td>$name</td>\n");
       }
       echo ("<td>$artname</td>\n");
       echo ("<td align=\"right\">$stueck</td>\n");
       echo ("<td align=\"right\">$preis</td>\n");
       echo ("<td align=\"right\">$gesamt</td>\n");
       echo ("</tr>\n");
    }
 
?>
    </table>
    <br /><br />
    <a href="bestell.php">Eine neue Bestellung</a>?
  </body>
</html>

Ein kleines Quizspiel

Das folgende Beispiel zeigt, wie Sie mit Hilfe von PHP und MySQL ein kleines Multiple-Choice-Quiz erstellen können.

In der einzigen Datenbanktabelle FRAGEN der neuen Datenbank QUIZ werden die Quizfragen nach dem folgendem Schema untergebracht:


Tabelle 18.5   Die Tabelle FRAGEN mit den Quizfragen und -antworten

Feldbezeichnung Datentyp Eigenschaften
NR INT auto_increment, Primärschlüssel
FRAGE TEXT  
ANTW1 VARCHAR Länge 100
ANTW2 VARCHAR Länge 100
ANTW3 VARCHAR Länge 100
ANTW4 VARCHAR Länge 100
KORREKT ENUM mögliche Werte '1' bis '4'

Für die Antworttexte bietet sich eher VARCHAR an als CHAR, weil ein CHAR immer so viel Speicher belegt wie die maximale Zeichenanzahl, während ein VARCHAR immer nur so viel Speicher benötigt, wie gerade Zeichen vorhanden sind.

Das Schema funktioniert wie bei den populären Quiz-Sendungen im Fernsehen: Für jede Frage sind vier Antwortmöglichkeiten vorgegeben, von denen genau eine richtig ist.

Im ersten Skript sollen alle Fragen untereinander geschrieben werden. Unter der jeweiligen Frage stehen in einer untergeordneten Liste die Antworten, jeweils mit Radiobuttons zum Ankreuzen. Durch das Abschicken des Formulars sollen die Daten an ein zweites Skript übertragen werden, das die Auswertung im Stil »Sie haben 15 von 18 Fragen richtig beantwortet« ausgibt.

Die Anzahl der verfügbaren Fragen soll beliebig sein. Listing 18.19 zeigt zunächst die Datei frag.php, in der die Fragen gestellt werden.

Listing 18.19   Die Quizfragen stellen

<html>
  <head>
    <title>Wer wird Programmierer?</title>
  </head>
  <body bgcolor="#0000CC" text="#FFFF66">
    <h1>Wer wird PHP?</h1>
    <form action="auswert.php" method="post">
    <ol>
 
<?php
 
   // Datenbankparameter
   $host = "127.0.0.1";
   $user = "dbadmin";
   $pass = "hallo";
   // Datenbankverbindung und -auswahl
   $connID = mysql_connect ($host, $user, $pass);
   mysql_select_db ("quiz");
   // Die Fragen und Antworten lesen und anzeigen
   $result = mysql_query ("SELECT * FROM FRAGEN");
   while ($row = mysql_fetch_array ($result)) {
      $nr = $row['NR'];
      $frage = $row['FRAGE'];
      echo ("<li><b>$frage</b></li>\n");
      echo ("<ol type=\"A\">\n");
      for ($i = 1; $i <= 4; $i++) {
         $antfeld = "ANTW$i";
         $antw = $row[$antfeld];
         echo ("<li><input type=\"radio\" name=\"f$nr\" 
              value=\"a$i\" />$antw");
        }
        echo ("</ol>\n");
     }
 
?>
      </ol>
      <input type="submit" value="Abschicken" />
    </form>
  </body>
</html>

Die Auswertung der Antworten wird in der Datei auswert.php vorgenommen, die Sie in Listing 18.20 sehen.

Listing 18.20   Die Auswertung des Quizspiels

<html>
  <head>
    <title>Wer wird Programmierer? - Auswertung</title>
  </head>
  <body bgcolor="#0000CC" text="#FFFF66">
    <h1>Quiz-Auswertung</h1>
 
<?php
 
   // Datenbankparameter
   $host = "127.0.0.1";
   $user = "dbadmin";
   $pass = "hallo";
   // Datenbankverbindung und -auswahl
   $connID = mysql_connect ($host, $user, $pass);
   mysql_select_db ("quiz");
   // Anzahl der Fragen lesen
   $result = mysql_query ("SELECT COUNT(NR) 
        FROM FRAGEN");
   $fraganzahl = mysql_fetch_row($result);
   $fraganzahl = $fraganzahl[0]; // Wert statt Array.
   // Antworten in ein Array packen
   for ($i = 1; $i <= $fraganzahl; $i++) {
      $varname = "f$i";
      $antw[$i] = $_POST[$varname];
   }
   // Richtige Antworten aus der Datenbank holen
   $result = mysql_query ("SELECT KORREKT 
        FROM FRAGEN");
   // Annahme: Keine Antwort richtig.
   $richtig = 0;
   // In einer Schleife Werte vergleichen:
   for ($i = 1; $i <= $fraganzahl; $i++) {
      $row = mysql_fetch_row ($result);
      $ra = $row[0];
      if ($antw[$i] == "a$ra") {
         $richtig++;
      }
   }
   echo ("Sie haben $richtig von $fraganzahl Fragen 
        richtig beantwortet!\n");
 
?>
  </body>
</html>

Ein Diskussionsforum

Eine der beliebtesten Webanwendungen sind neben den bereits beschriebenen Gästebüchern und Countern die allgegenwärtigen Diskussionsforen. In diesem Unterabschnitt wird ein einfaches, datenbankbasiertes Forum vorgestellt. Es handelt sich in vielerlei Hinsicht um eine erweiterbare Rohfassung: Eine ausgereifte Variante sollte beispielsweise automatisch den beantworteten Beitrag zitieren, eine Suchfunktion anbieten oder die Benutzereingaben auf Vollständigkeit und erlaubtes HTML überprüfen.

Ein Skript – verschiedene Seiten

Das Skript macht es von bestimmten übergebenen Werten abhängig, welcher Inhalt gerade angezeigt wird. Der Listenanzeigemodus, die Anzeige eines einzelnen Postings und die Eingabe eines neuen Beitrags erfolgen, wenn die Seite mittels GET angefordert wird. Die Daten, die den jeweiligen Modus aktivieren, werden nicht per Formular übergeben, sondern in den diversen Hyperlinks an die URLs angehängt (zum Beispiel forum.php?list=1&offen=1 für die aufgeklappte Variante der Listenansicht).

Lediglich das Formular zum Schreiben beziehungsweise Beantworten eines Beitrags wird mittels POST übertragen. Ein Hidden-Feld namens save weist das Skript an, die übergebenen Formulardaten in der Datenbank zu speichern.

Die Datenbank forum, auf die das Skript zurückgreift, enthält nur eine einzige Tabelle mit dem Namen POSTINGS. Sie umfasst die folgenden Felder:


Tabelle 18.6   Aufbau der Tabelle POSTINGS für das Forum

Feldbezeichnung Datentyp Eigenschaften
ID INT Primärschlüssel; auto_increment
PID INT  
USER VARCHAR Länge 30, Index
MAIL VARCHAR Länge 30
TIME INT  
TITLE VARCHAR Länge 30, Index
TEXT TEXT Volltext

Das Feld ID enthält eine automatisch erzeugte, eindeutige Nummer für jedes Forums-Posting. PID (»Parent-ID«) ist die Nummer des beantworteten Postings oder 0, falls es sich um einen neuen Originalbeitrag handelt. In TIME wird einfach der aktuelle Rückgabewert von time() gespeichert; bei der Anzeige eines einzelnen Postings wird er mittels date() formatiert. Für die zukünftige Implementierung einer Suchfunktion wurden bereits alle benötigten Indizes gesetzt.

Interessant ist noch, wie die einzelnen Beiträge als Threads angezeigt werden. Für die Liste werden zunächst alle Beiträge mit der Parent-ID ausgewählt, und zwar in umgekehrter Reihenfolge (über die Klausel ORDER BY ID DESC), damit das neueste Posting ganz oben steht.

Um die Antworten auf einen bestimmten Beitrag und wiederum deren Antworten zu zeigen, wird sowohl in der aufgeklappten Liste als auch beim einzelnen Posting die rekursive Funktion show_follow_ups() aufgerufen: Sie sucht zunächst alle Beiträge heraus, deren Parent-ID die übergebene ID ist, um die unmittelbaren Antworten auf das angegebene Posting zu finden. Die Daten dieser Antworten stellt sie dar und ruft sich anschließend für jede dieser Antworten rekursiv selbst auf, um wiederum deren Antworten zu finden und so weiter. Der zusätzliche Übergabeparameter $indent wird vor dem rekursiven Aufruf erhöht und sorgt für eine immer weitere Einrückung der Postings.

Listing 18.21 zeigt das vollständige Forums-Skript, das zusätzlich mit einigen Kommentaren versehen ist, um es noch verständlicher zu machen.

Listing 18.21   Ein einfaches Diskussionsforum

<html>
<head>
<title>PHP-Diskussionsforum</title>
</head>
<body bgcolor="#000000" text="#FFFFFF" link="#00FFFF" 
vlink="#00FF00" alink="#00FF99">
<h1>PHP-Diskussionsforum</h1>
<?php
 
   function show_follow_ups($id, $indent)
   {
      /* Rekursive Anzeige von Follow-Up-Beiträgen */
      $result = mysql_query ("SELECT * FROM POSTINGS 
           WHERE PID=$id ORDER BY ID DESC");
      while ($postinfo = mysql_fetch_array ($result)) {
         // Infos über das Posting holen
         $aid = $postinfo['ID'];
         $res2 = mysql_query ("SELECT TITLE, USER FROM 
              POSTINGS WHERE ID=$aid");
         list ($atitle, $auser) = mysql_fetch_row 
              ($res2);
         for ($i = 0; $i < $indent; $i++) {
            echo "&nbsp;";
         }
         echo ("<a href=\"forum.php?show=$aid\">$atitle
              </a> von $auser<br />");
         // Rekursiver Aufruf
         show_follow_ups($aid, $indent + 3);
      }
   }
         // Datenbankverbindung herstellen
   $connID = mysql_connect ("127.0.0.1", "dbadmin", 
        "hallo");
   mysql_select_db ("forum");
   if ($_GET['show']) {
      /* Einen einzelnen Beitrag anzeigen */
      $msgid = $_GET['show'];
      // Beitrag lesen
      $result = mysql_query ("SELECT * FROM POSTINGS 
           WHERE ID=$msgid");
      $posting = mysql_fetch_array ($result);
      if ($posting) {
         // Titel, Verfasser, Datum/Uhrzeit ausgeben
         $title = $posting['TITLE'];
         $user = $posting['USER'];
         $mail = $posting['MAIL'];
         $time = $posting['TIME'];
         $ftime = date ("d.m.Y, H:i", $time);
         echo "<table border=\"0\" width=\"100%\" 
              bgcolor=\"#666666\"><tr><td>";
         echo "<h2>$title</h2>";
         echo "<a href=\"mailto:$mail\">$user</a>, 
              $ftime<br /><br />";
         // Parent-Beitrag ermitteln
         if ($posting['PID'] > 0) {
            // Überschrift und Verfasser des Parent-
            // Beitrags lesen und ausgeben
            $pid = $posting['PID'];
            $result = mysql_query ("SELECT TITLE, USER 
                 FROM POSTINGS WHERE ID=$pid");
            list ($ptitle, $puser) = mysql_fetch_row 
                 ($result);
            echo ("Antwort auf: <a 
                 href=\"forum.php?show=$pid\">$ptitle
                 </a> von $puser<br /><br />");
         }
         // Den eigentlichen Posting-Text ausgeben
         $text = $posting['TEXT'];
         $text = preg_replace ("/[\r\n]+/", "<br />", 
              $text);
         echo $text;
         echo "</td></tr></table>";
         echo "<a href=\"forum.php?write=1
              &parent=$msgid\">Beantworten</a>";
         // Follow-Ups rekursiv ausgeben
         echo ("<h2>Follow-Ups</h2>");
         show_follow_ups ($msgid, 0);
         echo ("<br /><a href=\"forum.php?list=1\">
              Zur Liste</a>");
      }
   } elseif ($_GET['list']) {
      /* Die Liste aller Beiträge anzeigen */
      // Aufgeklappt?
      $offen = $_GET['offen'] ? true : false;
      if ($offen) {
         echo ("Aufklappen | <a href=\"forum.php?
              list=1\">Zuklappen</a>");
      } else {
         echo ("<a href=\"forum.php?
              list=1&offen=1\">Aufklappen</a>
              | Zuklappen");
      }
      echo "| <a href=\"forum.php?
           write=1&parent=0\">Schreiben</a>";
      echo "<br /><br />";
      // Alle Thread-Beginn-Beiträge holen
      $result = mysql_query ("SELECT * FROM POSTINGS 
           WHERE PID=0 ORDER BY ID DESC");
      echo "<table border=\"0\" width=\"100%\" 
           cellpadding=\"3\">";
      $bgc="#333333";
      while ($posting = mysql_fetch_array ($result)) {
         $bgc = ($bgc == "#333333") ? "#666666" : 
              "#333333"; // HG-Farbe wechseln
         $id = $posting['ID'];
         $user = $posting['USER'];
         $title = $posting['TITLE'];
         echo "<tr><td bgcolor=\"$bgc\">";
         echo ("<a href=\"forum.php?show=$id\">
              $title</a> von $user<br /><br />");
         if ($offen) {
            show_follow_ups ($id, 2);
         }
         echo "</td></tr>";
      }
      echo "</table>";
   } elseif ($_GET['write']) {
      /* Neuen Beitrag schreiben */
      if ($_GET['parent']) {
         $parent = $_GET['parent'];
      } else {
         $parent = 0;
      }
      ?>
 
      <form action="forum.php" method="post">
        <input type="hidden" name="save" value="1">
        <input type="hidden" name="parent" 
        value="<?php echo $parent; ?>">
        <table border="0" bgcolor="#666666" 
        cellpadding="3">
          <tr>
            <td>Name:</td>
            <td><input type="text" name="user" 
            size="30" /></td>
          </tr>
          <tr>
            <td>E-Mail:</td>
            <td><input type="text" name="mail" 
            size="30" /></td>
          </tr>
          <tr>
            <td>Titel:</td>
            <td><input type="text" name="title" 
            size="30" /></td>
          </tr>
          <tr>
            <td colspan="2">
               Nachricht:<br />
               <textarea name="msg" cols="40" rows="8" 
               wrap="virtual"></textarea>
               <br />
               <input type="submit" value="Speichern" />
               <input type="reset" 
               value="Zur&uuml;cksetzen" />
            </td>
          </tr>
        </table>
      </form>
      <?php
         } elseif ($_POST['save']) {
      /* Geschriebenen Beitrag speichern */
      $parent = $_POST['parent'];
      $user = $_POST['user'];
      $mail = $_POST['mail'];
      $title = $_POST['title'];
      $msg = $_POST['msg'];
      $time = time();
      $result = mysql_query ("INSERT INTO POSTINGS 
           (PID, USER, MAIL, TIME, TITLE, TEXT) 
           VALUES ($parent, \"$user\", \"$mail\", 
      $time, \"$title\", \"$msg\")");
      echo "<h2>Ihr Beitrag wurde gespeichert:</h2>";
      echo "<a href=\"forum.php?list=1\">Zur 
           &Uuml;bersicht</a>";
   } else {
      echo "<a href=\"forum.php?list=1\">Zur 
           &Uuml;bersicht</a>";
   }
?>
</body>
</html>





  

Einstieg in PHP 5

Einstieg in Java

C von A bis Z

Einstieg in C++

Einstieg in Linux

Einstieg in XML

Apache 2




Copyright © Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de