PHP 5 - Ein praktischer Einstieg
2. Auflage

PHP 5 - Ein praktischer Einstieg, 2. Auflage

Von Ulrich Günther
2. Auflage, August 2004
O'Reilly Verlag, ISBN: 3-89721-278-1
www.oreilly.de/catalog/einphp2ger/



TOC PREV NEXT INDEX

Kapitel 7

Kapitel 7

Ein PHP-Feuerwerk: Objekte wiederverwenden

In den letzten beiden Kapiteln hat unsere Beispielsite viele Fortschritte gemacht - auch wenn dieser bisher noch nicht auf unserem Browser zu sehen war. Das wird sich jetzt grundlegend ändern - jetzt, da wir unsere Objekte haben, können wir neue Seiten zur Darstellung und Bearbeitung unserer Spenderdaten quasi am Fließband erstellen.

In diesem Kapitel erwarten Sie:

Dabei lernen wir natürlich »so nebenher« noch einige nützliche PHP-Techniken kennen. Damit Sie wissen, worum es bei den ersten beiden Webseiten geht, werden wir uns diese Seiten gleich in Aktion ansehen.

Die Startseite für den Schatzmeister

Zwei der in diesem Kapitel besprochenen Seiten geben unserem Schatzmeister von seinem Browser aus volle Kontrolle über die Spendendaten. Das bringt eine ziemliche Verantwortung mit sich: Schließlich arbeiten wir ja mit Kreditkartendaten fremder Menschen, die wir nicht einfach so ins Web stellen können. Das Mindeste, was unsere edlen Spender von uns verlangen können, ist, dass die Schatzmeisterseiten vor neugierigen Blicken geschützt sind.

Deshalb muss sich unser Schatzmeister auf der Startseite erst einmal einloggen. Dazu ruft er (oder sie) die Startseite spendenListe.php auf (Abbildung 7-1):
Abbildung 7-1 Die Schatzmeister-Startseite spendenListe.php beim ersten Aufruf

spendenListe.php ist ein Comboskript. Weil beim ersten Laden der Seite keine POST-Daten an den Server geschickt werden, gibt spendenListe.php nur ein Login-Formular aus. Das muss dann von unserem Schatzmeister ausgefüllt werden. In der Datei spendenListe.php aus dem Begleitmaterial für dieses Kapitel sind der Login-Name joe und das Passwort moneybag fest eingetragen.

Login-Name und Passwort und sollten Sie natürlich ändern - schon deswegen, weil Bücher wie dieses nicht nur von ehrlichen Vogelschützern gelesen werden, sondern auch von Zeitgenossen, die anschließend per Suchmaschine Ihr Skript aufspüren und ohne Ihr Wissen die Kreditkartendaten Ihrer Spender »melken«. Aus diesem Grund ist es übrigens auch eine gute Idee, vor einer ernsthaften Verwendung der hier vorgestellten Skripte deren Dateinamen zu ändern.

Nachdem der Schatzmeister Login und Passwort eingegeben hat, werden diese Daten an spendenListe.php zurückgegeben. Dort werden sie überprüft - falls Login oder Passwort nicht den Erwartungen entsprechen, gibt das Skript eine entsprechende Fehlermeldung aus. Wenn alles stimmt, wird eine Spendenliste wie in Abbildung 7-2 ausgegeben. Sie sehen, das Skript trägt seinen Namen zu Recht.
Abbildung 7-2 Die von spendenListe.php ausgegebene Spendenliste

Auf der Liste werden für jede Spende (nach Datum geordnet) Datum und Zeit der Spendenabgabe, der Spendenbetrag (pro Spende und kumulativ) sowie Spender- und Kreditkartendaten angezeigt. Außerdem wird angezeigt, ob die Spende bereits abgebucht wurde.

Zum Bearbeiten der Spenden steht für jede Spende ein »Bearbeiten«-Button zur Verfügung. Beim Anklicken dieses Buttons werden die Login-Daten sowie die Spenden-ID per HTTP-POST-Request an ein weiteres Comboskript weitergereicht, spendeBearbeiten.php.

Dort wird der Schatzmeister nochmals authentifiziert - schließlich könnten Sie das Skript ja auch direkt aufrufen. Das Skript holt dann die nötigen Daten aus der Datenbank und zeigt sie in Textfeldern bzw. Checkboxen an. Dort können sie vom Schatzmeister bearbeitet werden (siehe Abbildung 7-3).
Abbildung 7-3 Die Eingabemaske von spendeBearbeiten.php

Die Änderungen werden beim Anklicken des »Speichern«-Buttons an spendeBearbeiten.php zurückgeschickt. Dort werden sie überprüft und in der Datenbank gespeichert. Anschließend wird die Eingabemaske mit den neuen Daten wieder ausgegeben. Über einen Button »Zurück zur Liste« kann der Schatzmeister zurück zur Spendenliste gelangen.

Wenn Sie jetzt neugierig geworden sind, wie wir unsere neuen Objekte in diesen Seiten zum Einsatz gebracht haben, brauchen Sie nicht länger zu warten. Fangen wir mit dem Code von spendeBearbeiten.php an.

Die Spenden bearbeiten

Wie unsere Objekte zum Abspeichern von Spendendaten verwendet werden können, haben Sie ja schon im letzten Kapitel gesehen. In spendeBearbeiten.php geht es darum, die Daten für eine bestimmte Spende wieder aus der Datenbank hervorzukramen und sie zu bearbeiten. Im Code der Datei stoßen wir am Anfang auf eine ganze Anzahl von Aufrufen der Funktion header():

Beispiel 7-1
Ausgabe von HTTP-Headern in spendeBearbeiten.php
<?php
 
    // Ausgabe von HTTP-Headern zum Unterbinden von Caching:
 
    // 1) Ablaufdatum für das Dokument auf einen Zeitpunkt
 
    // in der Vergangenheit setzen:
 
    header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
 
    // 2) Dokument immer als gerade modifiziert kennzeichnen:
 
    header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");  
 
    // 3) Cache-Anweisungen für HTTP-Version 1.1:
 
    header("Cache-Control: no-store, no-cache, must-revalidate");  
 
    header("Cache-Control: post-check=0, pre-check=0", false);
 
    // 4) Cache-Anweisung für HTTP-Version 1.0:
 
    header("Pragma: no-cache");
 
?>
 

Diese Aufrufe brauchen wir aus Sicherheitsgründen. Wie bereits erwähnt, muss sich unser Schatzmeister nämlich einloggen, um Spenden auflisten und bearbeiten zu können. Sein Login-Name und sein Passwort werden danach zur Authentifizierung in versteckten Eingabefeldern an die entsprechenden Skripte weitergegeben.

Damit haben wir ein potenzielles Sicherheitsproblem: Browser haben nämlich die etwas unangenehme Angewohnheit, einmal geladene Webseiten für eine gewisse Zeit abzuspeichern. Das dient eigentlich einem guten Zweck: Wenn wir uns in unserem Browser eine bereits besuchte Seite noch einmal ansehen wollen, braucht der Browser nur auf der eigenen Festplatte zu suchen. Der lange Weg über das Internet bleibt uns erspart.

Hier ist das allerdings kontraproduktiv: Nehmen wir mal an, dass unser Schatzmeister seinen Job von einem Internetcafé aus erledigt. Dann kann sich der nächste Benutzer des Computers die Kreditkartendetails unserer Spender ansehen, indem er sich die bereits heruntergeladenen Seiten noch einmal ansieht. Es kommt noch schlimmer: Über den Quellcode der Seiten kann er sich auch die Zugangsdaten des Schatzmeisters verschaffen!

Als ob das nicht schon übel genug wäre: Außer Browsern speichern unter Umständen auch noch so genannte Cache-Server entlang der Internetverbindung zum Webserver HTML-Seiten ab. Mit anderen Worten: Wir müssen dafür sorgen, dass Browser und Cache-Server unsere Seiten nicht im Gedächtnis behalten. Das machen die Aufrufe von header(). Sie fügen HTTP-Header in die HTTP-Response ein, die das Dokument als alten Hut darstellen, den es sich nicht aufzubewahren lohnt. Bei den meisten Browsern und Cache-Servern funktioniert der Trick ganz hervorragend.

Der restliche Code der Datei fängt mit normalem HTML an:

Beispiel 7-2
Der Kopfteil von spendeBearbeiten.php
<html>
 
    <head>
 
        <title>Spende bearbeiten</title>
 
    </head>
 
    <body>
 

Als erste Amtshandlung überprüft spendeBearbeiten.php den Login-Namen und das Passwort. Wenn eins der beiden nicht stimmt, ist das Skript vermutlich direkt aufgerufen worden. Das ist nicht im Sinne des Erfinders, also geben wir eine entsprechende Fehlermeldung aus:

Beispiel 7-3
Überprüfung von Login und Passwort in spendeBearbeiten.php
<?php
 
        // Login und Passwort überprüfen.
 
        if (($_POST["login"] != "joe") ||
 
            ($_POST["pass"] != "moneybag")) {
 
            // Fehlermeldung ausgeben.
 
            ?>
 
            <h3>Login unbekannt oder Passwort inkorrekt!</h3>
 
            </body>
 
            </html>
 
            <?php
 
            exit();
 
        }
 

Stimmt das Login, sollte auch eine Spenden-ID unter den POST-Variablen zu finden sein. Wir laden unsere Spendenklassen und verwenden die ID, um das entsprechende Spendenobjekt aus der Datenbank zu laden.

Beispiel 7-4
Laden des Spendenobjekts aus der Datenbank in spendeBearbeiten.php 
// Login okay - Spende aus der Datenbank laden.
 
include("./spendenKlassen.phpi");
 
$spende = new spende($_POST["spendenId"]);
 
$spender = $spende->spender;
 
$kreditKarte = $spende->kreditKarte;
 

So einfach geht das. Unser Skript verfügt jetzt über ein komplett geladenes Spendenobjekt samt zugehörigen Spender- und Kreditkartenobjekten. Sämtliche Details sind uns von den Objekten abgenommen worden. Die Daten für den Spender und die Kreditkarte machen wir aus Bequemlichkeitsgründen über separate Objektvariablen zugänglich - das spart Tipparbeit.

Was anschließend passiert, hängt davon ab, ob eine POST-Variable namens modus mit dem Wert speichern mitgeschickt wurde. Diese Variable gibt es in keinem Eingabefeld der Spendenliste in spendenListe.php. Wie wir bald sehen werden, gibt es sie aber in einem versteckten Eingabefeld des Bearbeitungsformulars von spendeBearbeiten.php. Diese Variable implementiert eine weitere Comboskript-Funktionalität: Ihr Vorhandensein entscheidet, ob das Skript sich selbst zum Abspeichern geänderter Werte aufgerufen hat oder ob wir einfach nur die Eingabemaske ausgeben sollen.

Wenn die Variable vorhanden ist, fragen wir alle durch POST mitgeschickten Eingabefelder ab und setzen die Objekteigenschaften mit entsprechender Überprüfung neu. Wurden die Prüfungen für das jeweilige Objekt »bestanden«, speichern wir seine Daten in der Datenbank ab.

Beispiel 7-5
Abspeichern der Spendendaten in spendeBearbeiten.php 
        if ($_POST["modus"] == "speichern") {
 
            $spende->betrag = $_POST["betrag"];
 
            $spende->oeffentlich = $_POST["oeffentlich"];
 
            $spende->frequenz = $_POST["frequenz"];
 
            $spende->abgebucht = $_POST["abgebucht"];
 
            $kreditKarte->gueltig = $_POST["gueltig"];
 
            $kreditKarte->nummer = $_POST["kartenNummer"];
 
            $kreditKarte->laeuftAb = $_POST["laeuftAb"];
 
            $kreditKarte->typ = $_POST["kartenTyp"];
 
            $kreditKarte->name = $_POST["kartenName"];
 
            $spender->name = $_POST["spenderName"];
 
            $spender->adresse = $_POST["adresse"];
 
            if ($spender->konsistent) {
 
                $spender->speichern();
 
            }
 
            else
 
            {
 
                echo "Ung&uuml;ltige Eingabe in Feld <b>";
 
                echo $spender->problemFeld."</b>";
 
            }
 
            if ($kreditKarte->konsistent) {
 
                $kreditKarte->speichern();
 
            }
 
            else
 
            {
 
                echo "Ung&uuml;ltige Eingabe in Feld <b>";
 
                echo $kreditKarte->problemFeld."</b>";
 
            }
 
            if ($spende->konsistent) {
 
                $spende->speichern();
 
            }
 
            else
 
            {
 
                echo "Ung&uuml;ltige Eingabe in Feld <b>";
 
                echo $spende->problemFeld."</b>";
 
            }
 
        }
 
?>
 

Damit haben wir auch schon den größten Teil der PHP-Funktionalität in spendeBearbeiten.php besprochen. Alles, was jetzt noch kommt, ist HTML bzw. sind kleine PHP-Schnipsel, die zur Ausgabe der Daten dienen. Nach der Überschrift eröffnet der HTML-Code das Formular für die Eingabemaske:

Beispiel 7-6
Versteckte Eingabefelder zur Automatisierung des Logins
<h1>Spende bearbeiten</h1>
 
<form action="spendeBearbeiten.php" method="post">
 
    <input type="hidden" name="modus" value="speichern">
 
    <input type="hidden" name="login" value="joe">
 
    <input type="hidden" name="pass" value="moneybag">
 
    <input type="hidden" name="spendenId"
 
           value="<?php echo $spende->id; ?>">
 

Wie Sie sehen, sind die ersten vier Eingabefelder versteckt. Sie setzen die oben besprochene Variable modus, die Login-Daten und legen fest, um welche Spende es hier geht - schließlich muss spendeBearbeiten.php ja beim nächsten Durchlauf wissen, zu welcher Spende die zu ändernden Spendendaten gehören. Dazu nehmen wir die ID der Spende und schreiben sie mit PHP in das value-Attribut des entsprechenden Tags. Danach geht es mit der Eingabemaske weiter, die wir in einer Tabelle unterbringen:

Beispiel 7-7
Der Code der Eingabemaske in spendeBearbeiten.php 
<table border="1" cellspacing="0">
 
    <tr>
 
        <td>
 
            Spendername
 
        </td>
 
        <td>
 
            <input type="text"
 
                   name="spenderName"
 
                   value="<?php
 
                        echo stripslashes($spender->name);
 
                    ?>">
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
            Spenderadresse
 
        </td>
 
        <td>
 
            <textarea name="adresse"><?php
 
                        echo htmlspecialchars(
 
                                stripslashes(
 
                                    $spender->adresse));
 
            ?></textarea>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
            Spendenbetrag
 
        </td>
 
        <td>
 
            <input type="text"
 
                   name="betrag"
 
                   value="<?php
 
                        echo $spende->betrag;
 
                    ?>">
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
            Ist die Spende &ouml;ffentlich?
 
        </td>
 
        <td>
 
            <input type="checkbox"
 
                   name="oeffentlich"
 
                    <?php
 
                        if ($spende->oeffentlich) {
 
                            echo "checked";
 
                        }
 
                    ?>>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
            Spendenintervall (in Tagen)?
 
        </td>
 
        <td>
 
            <input type="text"
 
                   name="frequenz"
 
                   value="<?php
 
                        echo $spende->frequenz;
 
                    ?>">
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
            Spende abgebucht?
 
        </td>
 
        <td>
 
            <input type="checkbox"
 
                   name="abgebucht"
 
                    <?php
 
                        if ($spende->abgebucht) {
 
                            echo "checked";
 
                        }
 
                    ?>>
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
            Name auf der Kreditkarte:
 
        </td>
 
        <td>
 
            <input type="text"
 
                   name="kartenName"
 
                   value="<?php
 
                        echo stripslashes(
 
                            $kreditKarte->name);
 
                    ?>">
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
            Kreditkartentyp:
 
        </td>
 
        <td>
 
            <input type="text"
 
                   name="kartenTyp"
 
                   value="<?php
 
                        echo $kreditKarte->typ;
 
                    ?>">
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
            Kreditkartennummer:
 
        </td>
 
        <td>
 
            <input type="text"
 
                   name="kartenNummer"
 
                   value="<?php
 
                        echo $kreditKarte->nummer;
 
                    ?>">
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
            Ablaufdatum der Karte (MoJa)
 
        </td>
 
        <td>
 
            <input type="text"
 
                   name="laeuftAb"
 
                   maxlength="4"
 
                   value="<?php
 
                        echo $kreditKarte->laeuftAb;
 
                    ?>">
 
        </td>
 
    </tr>
 
    <tr>
 
        <td>
 
            Karte gueltig?
 
        </td>
 
        <td>
 
            <input type="checkbox"
 
                   name="gueltig"
 
                    <?php
 
                        if ($kreditKarte->gueltig) {
 
                            echo "checked";
 
                        }
 
                    ?>>
 
        </td>
 
    </tr>
 
</table><p>
 

Nun müssen wir die Änderungen nur noch an unser Skript absenden können. Dazu beenden wir das Formular mit dem Submit-Button als letztes Element. Wie Sie sich wahrscheinlich erinnern, können wir die Beschriftung des Buttons über das value-Attribut des Tags setzen:

            <input type="submit" value="Speichern">
 
        </form>
 

Damit müssen wir nur noch den Button implementieren, der uns zur Spenderliste zurückführt. Das können Sie nicht einfach über einen Link mit dem <a>-Tag erledigen - sonst müsste sich der Schatzmeister wieder neu einloggen. Stattdessen bauen wir uns ein kleines Eingabeformular. Es enthält die Login-Daten in versteckten Eingabefeldern und wird an spendenListe.php geschickt, wenn der Submit-Button des Formulars angeklickt wird:

Beispiel 7-8
Halbverstecktes Formular für die Rückkehr zur Spendenliste 
        <form action="spendenListe.php" method="post">
 
            <input type="hidden" name="login" value="joe">
 
            <input type="hidden" name="pass" value="moneybag">
 
            <input type="submit" value="Zur&uuml;ck zur Liste">
 
        </form>
 
    <body>
 
</html>
 

Neu ist hier lediglich die Tatsache, dass Sie auch mehr als ein Formular in einer HTML-Seite unterbringen können. Wir werden dieser Technik gleich wieder begegnen. Ansonsten war's das auch schon - Sie sehen, mit Klassen ist das Programmieren ziemlich komfortabel!

Um das Skript spendeBearbeiten.php sinnvoll aufrufen zu können, brauchen wir allerdings unsere Seite mit der Spendenliste.

Probieren Sie's selbst!

Was an unserer Bearbeitungsseite noch fehlt, ist ein Button zum Löschen einer Spende in unserer Datenbank. So ein Button kann ziemlich nützlich sein - in der Praxis kommt es schon mal häufiger vor, dass Benutzer ein Formular aus Versehen zweimal abschicken, weil die Internetverbindung unterbrochen wurde, bevor sie die Bestätigungsseite gesehen haben. Außerdem gibt es gelegentlich Witzbolde, die Spenden unter falschem Namen oder imaginären Kreditkarten abschicken. In solchen Fällen sollte unser Schatzmeister natürlich eingreifen können.

Schreiben Sie ein weiteres Formular für spendeBearbeiten.php, das ebenfalls nur über einen Button und mehrere versteckte <input>-Tags verfügt. Eines dieser <input>-Tags sollte den Namen "modus" tragen und den Wert "loeschen" haben. Tragen Sie dann den Code zum Löschen der Spende weiter oben in spendeBearbeiten.php ein. Warum könnte es sinnvoll sein, die Spender- und Kreditkartenobjekte zuerst zu löschen?

Die Lösung finden Sie wie immer in den Online-Beispielen zu diesem Kapitel in spendeBearbeitenUndLoeschen.php.

Die Spendenliste

Vielleicht haben Sie sich gefragt, warum wir die Bearbeitungsseite zuerst besprochen haben. Ganz einfach - ich wollte Ihnen zeigen, wie Sie die Objekte aus der Datenbank erzeugen und wieder dorthin abspeichern können, bevor ich Ihnen eine weitere Klassendeklaration vor die Nase setze. So haben Sie jetzt wenigstens gesehen, wie viel Arbeit Ihnen Objekte bei der Erstellung eines solchen Skripts abnehmen können. Dieser Vorteil wird sich natürlich noch mehr auszahlen, sobald wir die Objekte mehrfach verwenden. Das wollen wir jetzt tun.

Trotzdem gilt natürlich nach wie vor die Regel, dass wir versuchen wollen, öfter benötigte Funktionalität in eine Funktion oder Klasse zu packen. Vorausschauend können wir absehen, dass wir in unseren Skripten sowohl bei der Implementierung der Spendenliste als auch später des Spendenthermometers Zugriff auf alle Spendendaten in der Datenbank brauchen, nicht nur auf einzelne Spenden. Dasselbe gilt - mit Einschränkungen -, wenn wir eine Liste aller öffentlichen Spenden zusammenstellen wollen.

Zur Implementierung der Spendenliste und des Spendenthermometers ist es deshalb zweckmäßig, dass wir uns noch eine kleine zusätzliche Klasse in spendenKlassen.phpi anlegen.

Die Klasse spendenListe

Erinnern Sie sich noch an unsere Überlegungen zum »Spaghettikochen« in Kapitel 6? Hier haben wir genau eine solche Situation: Wir wollen eine Reihe von gleichartigen Objekten aus der Datenbank erzeugen. Dabei ist es vorteilhafter, alle nötigen Daten auf einmal aus der Datenbank zu lesen und dann aus diesen die Objekte zu erzeugen, als für jedes Objekt eine eigene Datenbankabfrage zu starten.

Dafür haben wir in der Klasse datenObjekt ja bereits vorgesorgt: Wir können Objekte sowohl direkt aus der Datenbank mit Daten laden als auch durch Übergabe eines assoziativen Arrays, das einem einzelnen Datenbankeintrag entspricht. Das nutzen wir im Code der Klasse spendenListe aus:

Beispiel 7-9
Der Code der Klasse spendenListe 
class spendenListe extends datenObjekt {
 
    var $spenden = array();
 

 
    function spendenListe($abfrage="") {
 
        global $dbLink;
 
        if ($abfrage == "") {
 
            $abfrage = "select * ";
 
            $abfrage .= "from spender, karte, spende ";
 
            $abfrage .= "where ((spendeSpender = spenderId) ";
 
            $abfrage .= "and (spendenKarte = kartenId));";
 
        }
 
        if (!$dbLink) {
 
            $this->dbVerbinden();
 
        }
 
        if ($eintraege = mysql_query($abfrage)) {
 
            while ($eintrag =
 
                      mysql_fetch_array($eintraege)) {
 
                $neueSpende = new spende();
 
                $neueSpende->arrayZuEigenschaften($eintrag);
 
                $this->spenden[$eintrag["spendenId"]] = $neueSpende;
 
            }
 
        }
 
        else
 
        {
 
            echo mysql_error();
 
            exit();
 
        }
 
    }
 

 
    function summe() {
 
        $gesamtBetrag = 0;
 
        foreach ($this->spenden as $spende) {
 
            $gesamtBetrag += $spende->spendenSumme();
 
        }
 
        return $gesamtBetrag;
 
    }
 
} // Ende der Klasse spendenListe
 

Wie die Klassen spender, kreditKarte und spende leitet sich auch die Klasse spendenListe von der Klasse datenObjekt ab, hat also sämtliche Eigenschaften und Methoden derselben. Als einzige zusätzliche Eigenschaft besitzen Objekte der Klasse spendenListe die Array-Eigenschaft spenden. Jedes Element dieses Arrays soll ein Objekt der Klasse spende sein.

Der Konstruktor der Klasse besteht aus zwei Teilen: der Datenbankabfrage und, falls diese erfolgreich war, einer Schleife, die die Objekte aus den von der Datenbank gelieferten Daten erzeugt. Im Fall eines Misserfolgs wird eine Fehlermeldung ausgegeben und das Skript abgebrochen.

Sofern kein spezieller Abfrage-String vorgegeben ist, verwendet die Datenbankabfrage eine abgespeckte Version des SQL-Abfrage-Strings aus der Methode loadFromDB() der Klasse donation, die wir uns kurz ansehen sollten:

Beispiel 7-10
Der SQL-Abfrage-String für die Spendenliste
select * from spender, karte, spende 
 
         where ((spendeSpender = spenderId) 
 
            and (spendenKarte = kartenId));
 

Was machen wir hier? Nun, es ist eigentlich ziemlich einfach: Wir wählen alle Felder aus den Tabellen spender, karte und spende derjenigen Einträge, für die die Spenderdaten (identifiziert durch das Feld spenderId in der Tabelle spender) und die Kartendaten (identifiziert durch das Feld kartenId in der Tabelle karte) denen der Spende (vorgegeben durch die Felder spendeSpender und spendeKarte) entsprechen. Im Vergleich zu dem Abfrage-String aus der Methode ladeAusDB() der Klasse spende fehlt hier lediglich die Beschränkung auf eine bestimmte Spenden-ID.

Damit bekommen wir einen Datensatz mit - soweit in der Datenbank vorhanden - mehreren Einträgen. Jeder dieser Einträge enthält sämtliche Daten zur Erzeugung eines Spendenobjekts.

Diese Datensätze lesen wir in einer while-Schleife aus. Eine solche while-Schleife funktioniert im Prinzip genauso wie ein if-Statement: Wir haben eine Bedingung, die in runden Klammern hinter dem Schlüsselwort while steht, und einen Codeblock in geschweiften Klammern, der ausgeführt wird, wenn die Bedingung erfüllt (d.h. true) ist. Der Unterschied zu einem if-Statement besteht darin, dass der Code in der while-Schleife so lange immer wieder von vorn ausgeführt wird, bis die Bedingung false liefert.

In diesem Fall besteht die Bedingung der Schleife aus einem PHP-Wertzuweisungsausdruck: Wir lesen beim Prüfen der Bedingung in jedem Durchlauf der Schleife mit mysql_fetch_array() einen der Datensätze als assoziatives Array $eintrag aus. Sofern ein Eintrag vorhanden ist, wird $eintrag ein Wert zugewiesen, der nicht false ist. Die Schleifenbedingung ist in diesem Fall true und die Schleife wird (ein weiteres Mal) durchlaufen.

In der Schleife erzeugen wir dann zunächst ein neues (leeres) Spendenobjekt und laden es über die Methode arrayZuEigenschaften() mit den Daten aus $eintrag. Da diese auch die Spender- und Kreditkartendaten enthalten, werden die zugehörigen Objekte durch das Spendenobjekt gleich mit erzeugt. Das neue Spendenobjekt wird dann in der Array-Eigenschaft spenden abgelegt, wobei seine Kennung (ID) als Index verwendet wird.

So kommen wir mit nur einer einzigen Datenbankabfrage an eine ganze Latte von Spendenobjekten samt abhängiger Spender- und Kreditkartendaten: Spaghetti in der Großpackung, und die Zutaten für die Soße kaufen wir gleich mit ein. Praktisch, oder?

Neben dem Konstruktor enthält die Klasse spendenListe auch noch eine weitere Methode: summe(). Sie berechnet über eine foreach-Schleife die gesamte Spendensumme, die in den Objekten der Array-Eigenschaft spenden akkumuliert ist.

Damit wird die Erstellung unserer Spendenliste zum Kinderspiel.

for oder while - was ist besser?

In Kapitel 4 haben wir ja schon eine andere Art Schleife kennen gelernt: die for-Schleife. Wieso brauchen wir jetzt noch eine zweite Art Schleife?
Sie erinnern sich vermutlich noch, dass for-Schleifen eingesetzt werden, wenn die Anzahl der Schleifendurchläufe bereits beim Programmieren abzusehen ist. In der Regel werden for-Schleifen mit einer Schleifenvariablen (Indexvariable) betrieben, deren Wert der Zahl der abgearbeiteten Schleifendurchläufe entspricht.
>
Eine while-Schleife wird meist dann eingesetzt, wenn die Anzahl der Schleifendurchläufe nicht von vornherein vorhersehbar ist. Das ist beim Erstellen der Spendenliste der Fall, weil wir noch nicht wissen, wie viele Spendeneinträge unsere Datenbank enthalten wird.
Im Prinzip kann man aber statt einer for-Schleife auch immer eine while-Schleife einsetzen - der Code sieht dann nur nicht ganz so elegant aus:
for ($i = 0; $i < 10; $i = $i + 1) { echo $i; }
macht genau dasselbe wie
$i = 0; while ($i < 10) { echo $i; $i = $i + 1; }
Umgekehrt können Sie statt
while ($bedingung) {...
auch
for (; $bedingung; ) {
sagen.

Das Skript spendenListe.php

Dieses PHP-Skript fängt, wie schon spendeBearbeiten.php, mit Headern zur Unterdrückung der diversen Caches an. Danach geht es mit HTML weiter. Zur Formatierung des <body>-Tags verwenden wir ein style-Attribut, das die Schriftart und -größe festlegt:

Beispiel 7-11
Formatierungsanweisungen im <body>-Tag von spendenListe.php
<html>
 
    <body style="font-family: Arial, sans-serif; font-size: 10pt;">
 
<?php
 

In diesem Fall raten wir dem Browser, nach Möglichkeit die Schriftart »Arial« zu wählen oder, falls diese nicht vorhanden ist, auf die eingebaute serifenlose Schriftart des Browsers zurückzugreifen. Danach geht's gleich mit PHP weiter, in diesem Fall mit der Einbeziehung unserer Klassendatei:

include("./spendenKlassen.phpi");
 

Da wir es hier mit einem Comboskript zu tun haben, müssen wir sehen, ob POST-Daten mitgeschickt worden sind. Wenn nicht, ist dies der erste Aufruf des Skripts, und wir müssen das Login-Formular ausgeben:

Beispiel 7-12
Ausgabe des Login-Formulars in spendenListe.php 
if (sizeof($_POST) == 0) {
 
        // Login-Formular ausgeben
 
        ?>
 
                        <h3>Hallo Schatzmeister!
 
                        Bitte einloggen:</h3>
 
                        <form action="spendenListe.php"
 
                                  method="post">
 
                        Login: <input type="text"
 
                                        name="login"><br>
 
                        Pa&szlig;wort: <input type="password"
 
                                        name="pass"><br>
 
                        <input type="submit"
 
                                   value="Einloggen">
 
                        </form>
 
        <?php
 
}
 

Die einzige Neuigkeit hier ist der Eingabeelementtyp password. Dieser Eingabefeldtyp ist identisch mit dem Eingabeelementtyp text, außer dass die Zeichen im Eingabefeld durch Sternchen ersetzt werden, damit sie nicht auf dem Bildschirm sichtbar sind. Das ausgefüllte Formular wird dann an das Skript zurückgesendet.

Wie sicher sind passwortgeschützte Seiten?

Diese Frage ist leider nicht so ohne weiteres zu beantworten. Auch wenn das Passwort-Eingabefeld in unserem Skript die Passworteingabe auf dem Schirm verdeckt, bedeutet das noch lange nicht, dass Ihr Passwort auch von keinem gesehen werden kann. Auf dem Weg zwischen Browser und Webserver wird das Passwort nämlich im Klartext übermittelt.
Wenn also jemand mit einem Packet-Sniffer-Programm (einer Software, die den Netzwerkverkehr in einem Netzwerk abhören kann) irgendwo entlang des Netzwerkpfads zwischen der Browser-Maschine und dem Server aktiv ist, kann er das Passwort abfangen. So etwas kommt bevorzugt dort vor, wo Netzwerke mit vielen Rechnern einfach und unkontrolliert zugänglich sind.
Wenn Ihr Benutzer in einem verkabelten Studentenwohnheim oder einem »offenen« Uni-Labor sitzt, können Sie fast Gift darauf nehmen, dass mitgehört wird und Passwörter herausgefiltert werden. Das Gleiche gilt natürlich auch, wenn sich der Server in einer entsprechenden Umgebung befindet.
Eine Möglichkeit der Abhilfe besteht darin, eine verschlüsselte Verbindung über einen so genannten Secure Socket Layer (SSL) herzustellen. Solche Verbindungen haben Sie sicher schon gesehen - URLs, bei denen SSL benutzt wird, fangen mit https://... anstatt mit http://... an. Dann kann zumindest passiv nicht mehr mitgehört werden.
Um einen SSL-Server einzurichten, brauchen Sie ein so genanntes Zertifikat, das Sie z.B. von Verisign (www.verisign.com) kaufen können. Ein Zertifikat ist eine digital unterzeichnete elektronische Urkunde, mit der Ihre Benutzer beim Unterzeichner (z.B. Verisign) nachprüfen können, ob ein Kryptoschlüssel, der Ihrem Browser übergeben wurde, auch tatsächlich von Ihrem Server stammt. Für ernsthafte Anwendungen, bei denen wirklich vertrauliche Informationen übermittelt werden, ist SSL heute Standard und unbedingt zu empfehlen.

Der else-Zweig des if-Statements besorgt die Überprüfung des Logins und - wenn diese fehlschlägt - die Ausgabe einer Fehlermeldung:

Beispiel 7-13
Ausgabe einer Fehlermeldung bei inkorrektem Login
else
 
{
 
    // Login und Passwort überprüfen,
 
    if (($_POST["login"] != "joe") ||
 
        ($_POST["pass"] != "moneybag")) {
 
        // Fehlermeldung ausgeben.
 
        ?>
 
        <h3>Login unbekannt oder Passwort inkorrekt!</h3>
 
        </body>
 
        </html>
 
        <?php
 
        exit();
 
    }
 

Damit hätten wir die weniger interessanten Spezialfälle abgedeckt. Jetzt kommen wir zur eigentlichen Kernfunktion des Skripts: der Ausgabe der Spendenliste:

// Listenobjekt erzeugen.
 
$spendenListe = new spendenListe();
 

In dieser einen Codezeile passiert jetzt unheimlich viel: Alle unsere Spenden werden aus der Datenbank ausgelesen und in der Eigenschaft spenden des Objekts $spendenliste als Spendenobjekte abgespeichert. Unsere Vorarbeit zahlt sich jetzt voll aus. Die Liste selbst geben wir in HTML aus, wobei wir mit <th>-Elementen (Tabellenkopf-Elementen) in der ersten Tabellenzeile anfangen:

Beispiel 7-14
Die Spendenliste in spendenListe.php
<h1>Unsere edlen Spender</h1>
 
<table border="1" cellpadding="5"
 
        style="font-family: Arial, sans-serif;
 
                   font-size: 10pt;">
 
        <tr>
 
                <th width="15%">
 
                        Datum
 
                </th>
 
                <th>
 
                        Betrag
 
                </th>
 
                <th>
 
                        Details
 
                </th>
 
                <th>
 
                </th>
 
                <th>
 
                        Foto des Spenders
 
                </th>
 
        </tr>
 

Anschließend geben wir einen Spendeneintrag pro Zeile aus. Dazu verwenden wir eine foreach-Schleife, in der wir alle Spenden des Arrays $spendenListe->spenden durchlaufen. Als erste Amtshandlung in der Schleife erstellen wir zwei Bequemlichkeitsvariablen für die Spender- und Kreditkartendaten - das spart wieder mal Tipparbeit:

<?php
 
foreach ($spendenListe->spenden as $spende) {
 
    $spender = $spende->spender;
 
    $kreditKarte = $spende->kreditKarte;
 
?>
 
        <tr valign="top">
 

Als Tabellenzellen geben wir aus: das Datum:

                <td>
 
<?php // Spendendatum ausgeben.
 
        echo date("d M y",$spende->datum);
 
        echo "<br>";
 
        echo date("H:i:s",$spende->datum);
 
?>
 
                </td>
 

den Spendenbetrag:

                <td>
 
<?php // Spendenbetrag ausgeben.
 
        echo $spende->spendenSumme()."&euro ";
 
        if (!$spende->oeffentlich) {
 
                echo "anonym";
 
        }
 
        if ($spende->frequenz > 0) {
 
                echo "<br>(".$spende->betrag."&euro; ".$spende->
 
                           frequenzAlsString().")";
 
        }
 
?>
 
                </td>
 

Namens- und Adressinformationen des Spenders sowie die Kreditkartendetails in einer eigenen kleinen Untertabelle:

                <td>
 
<?php // Spenderdetails ausgeben.
 
        echo stripslashes($spender->name)."<br>";
 
        echo stripslashes(
 
                preg_replace("/\r?\n/",
 
                                         "<br>",
 
                                         $spender->adresse))."<p>";
 
?>
 
                        <table bgcolor="yellow"
 
                                style="font-family: Arial, sans-serif;
 
                                                font-size: 10pt;">
 
                                <tr>
 
                                        <td>
 
<?php // Kreditkartendetails ausgeben.
 
        echo $kreditKarte->typ;
 
?>
 
                                        </td>
 
                                        <td>
 
<?php
 
        echo $kreditKarte->nummer;
 
?>
 
                                        </td>
 
                                </tr>
 
                                <tr>
 
                                        <td>
 
<?php 
 
        echo stripslashes($kreditKarte->name);
 
?>
 
                                        </td>
 
                                        <td>
 
<?php 
 
        echo $kreditKarte->laeuftAb;
 
?>
 
                                        </td>
 
                                </tr>
 
                        </table>
 
                </td>
 

gefolgt von Informationen zum Bearbeitungsstand der Spende:

                <td>
 
                        <?php if ($spende->abgebucht) {
 
                                      echo "abgebucht";
 
                                  }
 
                                  else
 
                                  {
 
                                      echo "nicht abgebucht";
 
                                  }
 
                        ?>
 

und einem Button, der es ermöglicht, diese Spende mit spendeBearbeiten.php zu bearbeiten. Wir verpacken diesen Button in einem ansonsten unsichtbaren Formular, das neben der ID der zu bearbeitenden Spende auch die nötigen Login-Daten für spendeBearbeiten.php bereitstellt:

Beispiel 7-15
Formular zun Aufruf von spendeBearbeiten.php für den Eintrag 
        <form action="spendeBearbeiten.php" method="post">
 
                <input type="hidden"
 
                           name="login"
 
                           value="joe">
 
                <input type="hidden"
 
                           name="pass"
 
                           value="moneybag">
 
                <input type="hidden"
 
                           name="spendenId"
 
                           value="<?php
 
                                 echo $spende->id; ?>">
 
                <input type="submit"
 
                           value="Bearbeiten">
 
        </form>
 
</td>
 

Als letzte Zelle in jeder Tabellenreihe geben wir, falls vorhanden, das Spenderfoto aus:

Beispiel 7-16
Darstellung der Spenderfotos in spendenListe.php
                <td>
 
<?php // Spenderdatei ausgeben.
 
 if ($spender->fotoDatei) {
 
               echo "<img width=\"100\"
 
                        src=\"bilder/".$spender->fotoDatei."\">";
 
 }
 
 else
 
 {
 
   echo "Kein Foto!";
 
 }
 
?>
 
                </td>
 
        </tr>
 
<?php
 
} ?>
 

Damit ist die foreach-Schleife auch schon beendet. Zur Zusammenfassung geben wir in einer weiteren Zeile die Gesamtsumme aller Spenden bis zum heutigen Datum aus:

Beispiel 7-17
Ausgabe des Tabellenfußes in spendenListe.php
                        <tr>
 
                                <td>
 
                                        <b>Gesamtbetrag aller Spenden bis zum 
 
                                                heutigen Tag</b>
 
                                </td>
 
                                <th>
 
                                        <b><?php echo $spendenListe->summe(); 

                                          ?> &euro;</b>
 
                                </th>
 
                        </tr>
 
                </table>
 
                <?php
 
        }
 
?>
 
        </body>
 
</html>
 

Wie Sie sehen können, haben wir durch unsere Klassen wieder einige Programmierarbeit eingespart. Wir werden unserer neuen Klasse, spendenListe, später noch einmal begegnen.

Probieren Sie's selbst!

Wenn Sie ein bisschen mit den hier vorgestellten Skripten arbeiten, werden Sie feststellen, dass sie nicht ganz perfekt sind. Was passiert beispielsweise, wenn wir viele Spenden bekommen? Dann wird die Navigation durch unser Spendenwirrwarr etwas komplizierter.

Deshalb könnte es nützlich sein, die Spenden zu sortieren und/oder zu filtern. Nach dem Einloggen könnten Sie zum Beispiel nur die neuen, noch nicht abgebuchten Spenden anzeigen.

Am Anfang oder Ende der ausgegebenen HTML-Seite könnten Sie dann ein kleines Formular beispielsweise mit einer select-Auswahlliste unterbringen, die Ihnen die Optionen »Nur neue Spenden anzeigen«, »Nur abgebuchte Spenden anzeigen« und »Alle Spenden anzeigen« anbietet. Über einen Button können Sie die Spendenliste neu erzeugen (welche zusätzlichen - versteckten - Felder braucht Ihr Formular dazu?).

Über einen veränderten SQL-Abfrage-String, den Sie dem Konstruktor von Spendenliste als Parameter übergeben, können Sie die Liste filtern. Entsprechend können Sie Sortieroptionen einprogrammieren. Eine Lösung finden Sie unter spendenListeMitSortieren.php in den Beispielen zu diesem Kapitel.

Grafik in PHP

Bisher haben wir PHP ausschließlich zur Ausgabe von HTML-Dokumenten an den Browser verwendet. Neben diversen anderen Dokumentformaten kann PHP aber auch Bilddateien im JPG- (JPEG-) und PNG-Format erzeugen, lesen, bearbeiten und an den Browser ausgeben. Das ermöglicht Ihnen, Grafiken dynamisch aus Daten zu erzeugen. Einige Allerweltsbeispiele:

Landkarten mit vom Benutzer angeforderten Features
Basierend auf einer gescannten Landkarte können je nach Bedarf zusätzliche Grafikelemente in eine Grafik eingebettet werden - zum Beispiel alle griechischen (italienischen/chinesischen/türkischen/spanischen) Restaurants auf der Homepage einer Gemeinde.
Finanzdaten direkt aus der Datenbank
Stellen Sie Ihre Firmendaten direkt ins Web oder Intranet: Umsatzkurven, Aktienkurse usw.
Online-Abstimmungen und Umfragen
Lassen Sie Ihre Benutzer über eine Streitfrage abstimmen - und geben Sie die Ergebnisse gleich grafisch aus. Befragen Sie Ihre Kunden online nach Produktwünschen und zeigen Sie Ihnen, was andere Kunden bevorzugen.
Webcam mit Schrifteinblendung
Versehen Sie die Online-Bilder Ihrer Webcam oder Ihres Bildarchivs automatisch mit Ihrem Logo oder Ihrer Signatur oder verfremden Sie sie.

Vermutlich fallen Ihnen aber selbst genug eigene Projekte zu diesem Thema ein, nachdem Sie diesen Abschnitt gelesen und gesehen haben, wie einfach der Umgang mit Grafiken in PHP ist.

Warum kann PHP keine GIF-Dateien verarbeiten?

Wenn Sie sich die PHP-Dokumentation ansehen, werden Sie dort durchaus Befehle zum Öffnen und Ausgeben von GIF-Dateien finden. Diese benötigen aber eine Version der GD-Bibliotheksdatei, die es inzwischen nicht mehr gibt bzw. noch nicht wieder gibt. Damit kann PHP auch kein GIF mehr verarbeiten - jedenfalls vorerst nicht. Warum? In Kapitel 3 habe ich Ihnen ja bereits GIF für Grafiken ans Herz gelegt. Andere Software hat doch auch kein Problem mit dem Erzeugen von GIF-Dateien, warum also PHP, das doch sonst fast alles kann?
Das liegt daran, dass Sie für PHP nichts bezahlen müssen. Wie bitte? Jawohl - Sie haben richtig gelesen. Der Inhaber des Patents für den GIF-Encoder, die Firma Unisys, besteht seit Jahren standhaft auf der Abführung von Lizenzgebühren für die Nutzung ihrer im GIF-Format enthaltenen patentierten LZW-Technologie (insbesondere bei Encodern).
Autoren von Freeware und Open Source-Software sind aber im Regelfall auf die kostenfreie Benutzung dieser Technologie angewiesen. Deshalb kann solche Software legal keine GIF-Fähigkeit anbieten. Das braucht sie aber auch nicht - inzwischen gibt es einen Ausweg: PNG (»ping«).
PNG steht für Portable Network Graphics und ist GIF in vielen Aspekten überlegen. Dieses Open Source-Format wird von allen wichtigen Browsern unterstützt, von Netscape 4.04 und MSIE 4.01b an (MSIE 5.0 beim Mac). Mehr Informationen dazu gibt es unter www.libpng.org.
Wenn Sie doch mit GIF arbeiten müssen oder wollen, müssen Sie sich unter Umständen etwas gedulden: Das diesbezügliche Patent wird weltweit am 7. Juli 2004 ablaufen - und danach will auch GD wieder GIF-Unterstützung anbieten.

Grafiken in PHP erzeugen

Als Voraussetzung für die Erzeugung von Grafiken in PHP müssen Sie die GD-Bibliothek in PHP eingebunden haben. Wenn Ihre Installation noch nicht darauf eingerichtet ist, können Sie das so nachholen:

Unter Linux
GD-Distribution von www.boutell.com/gd herunterladen und nach den mitgelieferten Instruktionen kompilieren. Danach die Datei php_gd.so in das in php.ini angegebene extension_dir verschieben und einen Eintrag extension=php_gd.so in php.ini vornehmen. Bei neueren Distributionen heißt die Datei php_gd2.so.
Unter Windows
Verschieben Sie die Datei php_gd.dll vom Unterverzeichnis extensions Ihrer PHP-Distribution in das durch extension_dir in php.ini angegebene Verzeichnis. Entfernen Sie das Semikolon vor dem entsprechenden extension-Eintrag in php.ini. Bei neueren Versionen von PHP heißt die Datei php_gd2.dll.

Starten Sie danach Ihren Webserver neu.

Die Erzeugung einer Grafik in PHP besteht im Prinzip aus drei Schritten: Bilderzeugung (leer oder aus Vorgabe), Bearbeitung und Ausgabe. In den folgenden Abschnitten werden wir uns das näher ansehen.

Erzeugung des Grundbilds

Das Grundbild einer PHP-Grafik können Sie sich als eine Art Leinwand vorstellen: ohne Leinwand kein Gemälde. Bei PHP können Sie im Prinzip zwischen drei Arten von Leinwänden wählen:

  1. Einer leeren Leinwand, die Sie in der Variablen $bild mit dem Kommando $bild = ImageCreate($breite, $hoehe) erzeugen können, wobei $breite und $hoehe die Breite und Höhe der Grafik in Pixeln angeben.
  2. Einer Leinwand, deren Hintergrund aus einer PNG-Datei geladen wird. Das geht mit $bild = ImageCreateFromPNG($pngDateiName).
  3. Einer Leinwand, deren Hintergrund aus einer JPEG-Datei (.jpg) geladen wird: $bild = ImageCreateFromJPEG($jpgDateiName).

Neben PNG und JPEG stehen auch noch andere, etwas exotischere Dateiformate zur Verfügung - siehe PHP-Dokumentation. Damit haben Sie in der Variablen $bild eine Referenz an der Hand, die Sie bei der Bearbeitung angeben müssen.

Bearbeitung des Grundbilds

Jetzt können Sie mit dem Bearbeiten anfangen. Dazu stehen Ihnen eine ganze Palette von Befehlen zur Verfügung. Die meisten dieser Befehle arbeiten mit Leinwandkoordinaten in Pixeln (Bildpunkten) und mit Farben. Dabei hat der Bildpunkt in der oberen, linken Ecke die horizontale (x) und vertikale (y) Koordinate (x,y)=(0,0) und der Pixel in der unteren, rechten Ecke des Bilds die Koordinaten (x,y)=(Bildbreite - 1, Bildhöhe - 1).

Farben können Sie einem Bild durch den Befehl ImageColorAllocate() hinzufügen:

$rot = ImageColorAllocate($bild,255,0,0);
 
$gruen = ImageColorAllocate($bild,0,255,0);
 
$blau = ImageColorAllocate($bild,0,0,255);
 
$weiss = ImageColorAllocate($weiss,255,255,255);
 

Die Farbauswahl funktioniert hier wie bei HTML nach den Farbkanälen Rot, Grün und Blau, wobei die Werte in jedem Kanal zwischen 0 (dunkel) und 255 (hell) liegen dürfen. Mit einem Color Picker wie Pixie werden Ihnen diese auch häufig angezeigt, z.B. als »RGB«-Wert. Der erste Aufruf von ImageColorAllocate() für ein $bild setzt dessen Hintergrundfarbe fest.

Mit Hilfe dieser Koordinaten und Farben können Sie nun Grafikoperationen durchführen, wie z.B.:

Dies sind nur einige nützliche Grafikbefehle. Wir werden einigen von ihnen gleich wieder begegnen, wenn wir uns den Code des Spendenthermometers ansehen. Eine Übersicht über alle vorhandenen Befehle gibt das PHP-Manual - wenn Sie mit der Handhabung der obigen Befehle erst einmal vertraut sind, ist es auch recht einfach, sich in den Rest einzuarbeiten.

Bildausgabe an den Browser

Im Moment befindet sich unser Bild noch ganz gemütlich im RAM des Servers, wo es hübsch warm ist und keiner es sieht. Um es an den Browser weiterzuleiten, müssen wir die Bilddaten irgendwie ausgeben. Das können wir mit den PHP-Befehlen ImagePNG($bild) bzw. ImageJPEG($bild) tun.

Beide Befehle haben als zweiten (optionalen) Parameter einen Dateinamen, falls Sie die Grafik lieber in eine Datei ausgeben möchten. Dem Befehl ImageJPEG() können Sie noch einen dritten Parameter hinzufügen, die Qualität (Detailtreue) des ausgegebenen Bilds. Dieser Parameter muss zwischen 0 (niedrigste Qualität) und 100 (höchste Qualität) liegen. Der Default liegt bei 75. Je höher die Qualität, desto mehr Bytes werden benötigt. Um ein solches Bild an den Browser auszugeben, verwenden Sie einen leeren Dateinamen:

ImageJPEG($qualitaetsFoto,"",90);
 

Bedenken Sie, dass Ihre Benutzer bei höherer Qualität aber auch eine längere Download-Zeit in Kauf nehmen müssen - etwas, das Sie vielleicht bei der Entwicklung der Site gar nicht merken, weil Sie direkt auf der Server-Maschine arbeiten!

Damit hätten wir die Daten Richtung Browser in Bewegung gesetzt. Aber halt - kann der Browser sie denn einfach so verarbeiten? Die Antwort ist »nein«: Unser Browser erwartet ja HTML-Code und keine binären Bilddaten in unserer HTTP-Response. Dem können wir aber abhelfen, indem wir dem Browser in der Response mitteilen, dass eine Bilddatei kommt:

header("Content-type: image/png");
 

für PNG-Bilder bzw.

header("Content-type: image/jpeg");
 

für JPEG-Bilder. Diesen header-Befehl müssen Sie vor der Ausgabe der Bilddaten mit ImageJPEG() bzw. ImagePNG() ausführen.

Sehen wir uns ein praktisches Beispiel an: das Spendenthermometer.

Das Spendenthermometer

Nehmen wir einmal an, dass unser Vogelschutzverein ein bestimmtes Spendenziel verfolgt - sagen wir 1. 000   . Um bestehenden und potenziellen Spendern zu zeigen, wie weit Sie noch von Ihrem Ziel entfernt sind, ist ein so genanntes »Spendenthermometer« ein beliebtes Instrument. Es zeigt die bisher eingegangenen Spenden grafisch als eine Art Thermometerstand an.

Früher bedeutete das, dass der Schatzmeister regelmäßig mit Farbtopf und Pinsel zu hantieren hatte. Dank PHP-Grafik geht das heutzutage sehr bequem übers Web und ohne die Gefahr von Farbklecksen. Abbildung 7-4 zeigt das Spendenthermometer, das von spendenThermometer.php erzeugt wird.

Der Code unseres Spendenthermometers fängt mit ein paar Header-Kommandos an:

Beispiel 7-18
Auch in spendenThermometer.php senden wir am Anfang zusätzliche
HTTP-Header 
 <?php
 

 
    header("Content-type: image/png");
 
    header("Expires: Sun, 11 Aug 2002 05:00:00 GMT");
 
    header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");  
 
    header("Cache-Control: no-store, no-cache, must-revalidate");  
 
    header("Cache-Control: post-check=0, pre-check=0", false);
 
    header("Pragma: no-cache");
 

Den ersten dieser Header kennen Sie bereits: Er weist den Browser darauf hin, dass die Daten im Hauptteil der HTTP-Antwort binäre Bilddaten einer PNG-Grafik sind.

Die anderen Header dienen wie gehabt dazu, das Caching der Grafik im Browser zu verhindern. Warum wollen wir das? Bilddokumente sind relativ groß im Vergleich zu HTML-Dateien, das wiederholte Laden derselben Bilder über dieselben Datenleitungen ist daher eigentlich eine unnötige Belastung des Internets. Zu diesem Zweck speichern ja sowohl der Browser als auch eventuelle Cache-Server entlang des Download-Wegs bereits empfangene Dokumente, um sie aus dem lokalen Speicher holen zu können, wenn sie noch einmal gebraucht werden.

Für statische Bilder ist das kein Problem - wohl aber für Bilder, deren Inhalt sich regelmäßig ändert. Das ist z.B. hier der Fall: Wenn wir mehr Spenden bekommen, sieht unser Spendenthermometer anders aus. Das Hinzufügen der obigen Header ist eine Möglichkeit, Browsern und Cache-Servern nahe zu legen, dass das Dokument zur einmaligen Verwendung gedacht ist.
Abbildung 7-4 Die Spendenthermometer-Grafik nimmt einen zentralen Platz auf unserer Startseite ein

Als Nächstes legen wir die Grafikparameter fest. Wenn Sie sich die angegebenen Koordinaten ansehen, sollten Sie in Erinnerung behalten, dass die vertikalen Positionskoordinaten von oben nach unten ansteigen:

Beispiel 7-19
Festlegung der Grafikparameter in spendenThermometer.php 
// Grafikparameter
 

 
$spendenZiel = 1000; // Spendenziel in Euros.
 
$schritt = 200; // Skalenschritt in Euros.
 

 
$bildBreite = 100; // Gesamtbreite der Grafik.
 
$bildHoehe = 200; // Gesamthöhe der Grafik.
 

 
$hoehe = 160; // Höhe des Thermometers in Pixeln.
 
$breite = 30; // Breite des Thermometers in Pixeln.
 
$x = 60; // Horizontale Position des Thermometers
 
         // ("Tankmitte").
 
$y = 170; // Vertikale Position des Thermometers
 
          // ("Tankmitte").
 
$tick = 20; // Strichlänge in Pixeln.
 

 
// Skalenlänge in Pixeln. Die Skala endet 1.5
 
// Thermometerbreiten unterhalb des oberen
 
// Thermometerendes.
 
$skala = $hoehe - 1.5 * $breite;
 
// Vertikale Position des Strichs, der das
 
// Spendenziel anzeigt (oberes Skalenende).
 
$max = $y - $skala;
 
// Pixel pro Euro:
 
$pixelProEuro = $skala / $spendenZiel;
 
// Pixel pro Schritt:
 
$pixelProSchritt = $schritt * $pixelProEuro;
 
// Vertikale Position, an der der gerade Teil des
 
// Röhrchens in eine Rundung übergeht.
 
$roehrenEnde = $y - $hoehe + 0.5 * $breite;
 

Mit den obigen Parametern können wir unsere Grafik beliebig ändern: Anderes Spendenziel? Kein Problem. Dickeres oder höheres Thermometer? Auch kein Problem.

Beispiel 7-20
Initialisierung der Grafik und der benötigten Farben
$bild = @ImageCreate($bildBreite, $bildHoehe)
 
    or die("GD-Imagestream kann nicht initialisiert werden");
 
$hintergrundFarbe = ImageColorAllocate ($bild, 255, 255, 255);
 
$textFarbe = ImageColorAllocate ($bild, 0, 0, 102);
 
$bildFarbe = ImageColorAllocate ($bild, 0, 0, 204);
 
$fuellFarbe = ImageColorAllocate ($bild, 204, 0, 51);
 

In diesem Codeabschnitt erzeugen wir unsere Leinwand. Der @-Operator unterdrückt alle Meldungen, die evtl. von ImageCreate() erzeugt werden, so dass alle entsprechenden Fehler ausschließlich von die() gehandhabt werden.

Die erste Farbe, die wir dem Bild zuweisen, bestimmt die Hintergrundfarbe, in diesem Fall Weiß. Wir werden $hintergrundFarbe nicht weiter verwenden, brauchen es aber hier, da sonst $textFarbe den Hintergrund bestimmen würde. Außerdem definieren wir eine Farbe für das Thermometer und für die »Flüssigkeit«, mit der wir das Thermometer füllen wollen.

Damit sind wir bereit, das Thermometer selbst zu zeichnen. Wir fangen mit dem Tank an:

Beispiel 7-21
Zeichnen des Thermometertanks in spendenThermometer.php
// Thermometer zeichnen:
 
// Mit dem "Tank" anfangen:
 
ImageArc($bild, $x, $y,
 
         1.5 * $breite + 1,
 
         1.5 * $breite + 1,
 
         0, 360,
 
         $bildFarbe);
 
// Tank füllen ...
 
ImageFill($bild, $x, $y, $fuellFarbe);
 
// ... und ihn zum Rohr nach oben öffnen. 
 
ImageArc($bild, $x, $y,
 
         1.5 * $breite + 1,
 
         1.5 * $breite + 1,
 
         230, 310,
 
         $fuellFarbe);
 

Das Zeichnen des Tanks geschieht in drei Schritten:

  1. Zuerst zeichnen wir einen geschlossenen Kreis, der den Tankumriss festlegt. Das machen wir durch ImageArc() mit einem Ellipsenbogen gleicher Höhe und Breite (also einem Kreisbogen), den wir von 0 bis 360 Grad zeichnen.
  2. Wir füllen den Kreis mit unserer Füllfarbe. Das Füllen funktioniert im Prinzip so: Füllen Sie das Pixel mit den angegebenen Koordinaten mit der angegebenen Farbe. Machen Sie dann mit allen Nachbarn dieses Pixels weiter, danach mit deren Nachbarn usw., solange sie alle dieselbe Ursprungsfarbe haben wie das zuerst eingefärbte Pixel. Der geschlossene Kreis im ersten Schritt ist notwendig, weil der Füllalgorithmus in ImageFill() sonst das gesamte restliche Bild füllen würde.
  3. Öffnen Sie den Tank oben, wiederum mit der ImageArc()-Funktion. An den Gradangaben für den Ellipsenbogen können Sie sehen, dass PHP ein etwas ungewöhnliches Gradmaß verwendet: 0 Grad entsprechen der 3-Uhr-Position, 90 Grad der 6-Uhr-Position, 180 Grad der 9-Uhr-Position usw.

Danach zeichnen wir das Röhrchen. Es besteht aus zwei senkrechten Linien, die oben durch einen Halbkreis verbunden werden:

Beispiel 7-22
Zeichnen des Thermometerröhrchens in spendenThermometer.php 
// Röhrchen zeichnen.
 
ImageLine($bild,
 
          $x - $breite/2, $y - $breite/2,
 
          $x - $breite/2, $roehrenEnde,
 
          $bildFarbe);
 
ImageLine($bild,
 
          $x + $breite/2, $y - $breite/2,
 
          $x + $breite/2, $roehrenEnde,
 
          $bildFarbe);
 
ImageArc($bild, $x, $roehrenEnde,
 
         $breite, $breite,
 
         180, 0, $bildFarbe);
 

Als Nächstes kommt die Skala an die Reihe. Wir zeichnen sie mit einer while-Schleife, da wir noch nicht wissen, wie viele Skalenstriche wir zeichnen müssen. Dazu erhöhen wir in unserer while-Schleife schrittweise den Euro-Betrag und setzen die y-Koordinate der Skalenstriche, $schrittY, entsprechend herunter. Dabei setzen wir jeweils einen Strich und eine Textmarke auf das Bild, bis wir das obere Skalenende erreicht haben und die Bedingung unserer while-Schleife false liefert:

Beispiel 7-23
Zeichnen der Thermometerskala in spendenThermometer.php
$schrittY = $y;
 
$euro = 0;
 

 
while ($schrittY > $max) {
 
    $schrittY = $schrittY - $pixelProSchritt;
 
            $euro = $euro + $schritt;
 
    ImageLine($bild,
 
          $x - $breite/2, $schrittY,
 
          $x - $breite/2 - $tick, $schrittY,
 
          $bildFarbe);
 
    ImageString($bild,
 
                3, $x - $breite - $tick, $schrittY,
 
                $euro, $textFarbe);
 

 
}
 

Das zweite Argument von ImageString() gibt übrigens die Schriftgröße des eingebauten Fonts an, in diesem Fall 3. Wir setzen den String eine Thermometerbreite links des Skalenstrichs an, so dass seine rechte, obere Ecke auf den zugehörigen Skalenstrich abgestimmt ist.

Damit hätten wir das eigentliche Thermometer gezeichnet. Jetzt fehlt noch der richtige Flüssigkeitsstand. Dazu müssen wir uns die Gesamtsumme der bisher eingegangenen Spenden besorgen. Nichts einfacher als das - für diesen Zweck haben wir ja unsere Klasse spendenListe:

Beispiel 7-24
Ermitteln des Thermometerstands mit Hilfe unserer Klassen 
// Spendendaten holen.
 
include("./spendenKlassen.phpi");
 
$spendenListe = new spendenListe;
 
$gesamtBetrag = $spendenListe->summe();
 

Jetzt müssen wir den Thermometerstand nur noch von Euro in Pixel umrechnen. Das machen wir mit der folgenden Formel:

$fuellStand = $y - $pixelProEuro * $gesamtBetrag;
 

Das Füllen des Thermometers erfolgt in zwei Schritten: Erst zeichnen wir am Thermometerstand eine waagerechte Linie als Begrenzungslinie für unseren Füllbefehl. Dann füllen wir den darunter liegenden Röhrchenbereich bis zum Tank, ausgehend von einem Pixel unmittelbar unter der Begrenzungslinie:

Beispiel 7-25
Thermometerstand mit Füllfarbe zeichnen 
// Thermometerstand zeichnen.
 
// (Füllfarbe)
 
ImageLine($bild,
 
      $x - $breite/2, $fuellStand,
 
      $x + $breite/2, $fuellStand,
 
      $fuellFarbe);
 
// Röhrchen bis zum Pegel füllen.
 
ImageFill($bild, $x, $fuellStand + 1, $fuellFarbe);
 

Damit ist unsere Grafik fertig, und wir müssen sie nur noch ausgeben:

Beispiel 7-26
Ausgabe der Thermometergrafik an den Browser
    ImagePNG($bild);
 

Als Überblick sind die wichtigsten Grafikbefehle in PHP noch einmal in Tabelle 7-1 zusammengefasst.

Tabelle 7-1
Wichtige Grafikbefehle in PHP  
Befehl Erklärung
ImageCreate(breite,hoehe)
Erzeugt ein leeres Bild mit der angegebenen Breite und Höhe in Pixeln. Die Hintergrundfarbe des Bilds ist die erste Farbe, die dem Bild mit ImageColorAllocate() zugewiesen wird. Rückgabewert der Funktion ist eine Referenz auf das Bild.
ImageColorAllocate(bild,rot,grün,blau)
Diese Funktion erzeugt eine Farbe in der Farbpalette von bild. Die Werte der drei Farbkanäle müssen jeweils zwischen 0 und 255 liegen. Rückgabewert ist eine Referenz auf die Farbe, die bei Zeichenbefehlen verwendet werden kann.
ImageSetPixel(bild,x,y,farbe)
Färbt das Pixel an Position (x,y) in bild mit color. Koordinatenursprung ist oben links bei Pixel (0,0).
ImageLine(bild,x1,y1,x2,y2,farbe)
Zeichnet in bild eine Linie in color von (x1,y1) nach (x2,y2).
ImageArc(bild,x,y,b,h,von,bis,farbe)
Zeichnet einen Ellipsenbogen (Teil einer Ellipse mit Breite b und Höhe h) um (x,y), der unter Winkel von anfängt und bei Winkel bis aufhört. Die Winkel laufen im Uhrzeigersinn im 360-Grad-System, wobei 0 Grad von (x,y) aus gesehen in »3-Uhr-Richtung« liegt. Bei b=h wird ein Kreisbogen gezeichnet.
ImageFill(bild,x,y,farbe)
Füllt das Pixel (x,y) und alle unmittelbar und mittelbar benachbarten bzw. verbundenen Pixel derselben Farbe mit color.
ImageString(bild,font,x,y,string,farbe)
Gibt einen string in font an der Position (x,y) aus (linke obere Ecke der Umrandungsbox des Strings). Eingebaute Fontwerte sind 1, 2, 3, 4 und 5.
ImagePNG(bild[,datei])
Gibt bild im PNG-Format aus. Die Ausgabe erfolgt in datei, falls angegeben.
ImageJPEG(bild[,datei[,qualitaet]])
Gibt bild im JPEG-Format aus. Die Ausgabe erfolgt in datei, falls sie angegeben und kein leerer String ist. Der optionale Parameter qualitaet (Default-Wert 75) zwischen 0 und 100 steuert die Qualität des JPEG-Bilds und damit seine Dateigröße.
ImageCreateFromPNG(datei)
Erzeugt ein neues Bild aus einer PNG-Datei.
ImageCreateFromJPEG(datei)
Erzeugt ein neues Bild aus einer JPEG-Datei.

Noch einmal zum Thema Cache

Wahrscheinlich werden Sie dynamische Grafiken wie das Spendenthermometer in eine HTML-Seite einbetten wollen. Wenn Sie diese Seite selbst dynamisch erzeugen, z.B. über PHP, eröffnet sich Ihnen eine weitere Möglichkeit zum Austricksen der manchmal recht hartnäckigen Browser-Caches. Die werden nämlich zwangsläufig ziemlich transparent, wenn Sie Ihrem HTTP-Request POST- oder GET-Daten mitgeben. Wenn Sie also sicherstellen wollen, dass Ihr Spendenthermometer in einer PHP-Datei mit HTML-Ausgabe jedes Mal neu geladen wird, schreiben Sie Ihr <img>-Tag so:

<img src="spendenThermometer.php?keinZweck=<?php echo uniqid(""); ?>">
 

Was passiert hier? Ganz einfach: Jedes Mal, wenn Sie Ihre HTML-Seite ausgeben, sieht der Abfrage-String hinter Spendenthermometer.php etwas anders aus, mal so:

<img src="spendenThermometer.php?keinZweck=3cc53b0f561d8">
 

oder auch mal so:

<img src="spendenThermometer.php?keinZweck=3cc53b5acaad0">
 

Damit ist die Bildquelle aus der Sicht der Browser bzw. der Cache-Server jedes Mal ein neues Dokument. Ihr Webserver braucht die Variable keinZweck allerdings überhaupt nicht - sie wird zwar an PHP weitergeleitet (in $_GET bzw. $HTTP_GET_VARS), dort von spendenThermometer.php aber nicht verwendet.


TOC PREV NEXT INDEX

Copyright © 2004 by O'Reilly Verlag GmbH & Co.KG