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 5

Kapitel 5

Effizientes
Programmieren mit
Funktionen und Objekten

Wie Sie schon bemerkt haben, werden unsere Skripte langsam etwas umfangreicher. Deshalb ist es jetzt an der Zeit, Techniken kennen zu lernen, die bei der Konstruktion und der Wartung unserer Skripte helfen können.

Eine dieser Techniken kennen Sie ja schon: die Wiederverwendung von HTML-Code in unserer Comboform. Dabei haben wir denselben HTML-Code zweimal verwendet: einmal zur Eingabe der ursprünglichen Spendeninformationen und einmal zur Wiedervorlage bei unplausiblen Eingaben.

Dadurch haben wir uns nicht nur zusätzliches Programmieren erspart: Wenn wir einmal ein neues Feld zu unserem Formular hinzufügen wollen, brauchen wir das nur an einer Stelle in spendenFormular.php zu machen. Die Änderung betrifft automatisch beide Anwendungen des Spendenformulars.

In diesem Kapitel werden wir diese Strategie von der Skript-Ebene (Dateien) auf die Code-Ebene verlagern. Sie werden die wohl bekannteste und älteste Technik kennen lernen, mit der sich Code-Wiederholungen vermeiden lassen: Funktionen. Danach werden Sie sehen, wie wir größeren Projekten ein objektorientiertes Gerüst geben können - damit wir auch beim Ausbau unserer Beispielsite jede Menge Code sparen.

Programmieren mit Funktionen

Wenn Sie sich beispielsweise spende.php ansehen, werden Sie feststellen, dass uns Funktionen schon im letzten Kapitel begegnet sind. In spende.php haben wir es allerdings nur mit eingebauten Funktionen zu tun gehabt, z.B. mit preg_match(), die Funktion, die uns sagt, ob ein regulärer Ausdruck auf einen angegebenen String passt oder nicht. Eine eingebaute Funktion ist eine Funktion, die PHP zur Verfügung stellt.

Gäbe es preg_match() nicht, müssten wir erheblich umfangreicheren Code schreiben, um unsere Eingabedaten denselben Prüfungen zu unterwerfen. preg_match() spart uns also Programmierarbeit, insbesondere weil wir die Funktion mehrfach aufrufen.

Die Anzahl der eingebauten Funktionen in PHP geht in die Tausende. Eine ausführliche Liste und die dazugehörigen Erklärungen finden Sie in der PHP-Dokumentation oder in O'Reillys Taschenreferenz PHP - kurz & gut von Rasmus Lerdorf.

Trotzdem ist es nicht schwierig, sich Situationen vorzustellen, in denen wir Code wiederholen müssten, weil es keine eingebaute Funktion dafür gibt: Wenn wir beispielsweise das Überprüfen des Spendernamens in spende.php etwas verschärfen wollen, z.B. so, dass er nicht aus einer Aneinanderreihung von Sonderzeichen bestehen kann, dann wird das schon etwas aufwendiger.

Außerdem überprüfen wir bisher nur einen Namen. Wenn Sie aber auch noch nach dem Namen fragen wollen, der auf der Kreditkarte abgedruckt ist, oder Vornamen und Nachnamen trennen wollen, müssen wir diese Prüfung mehrfach durchführen. Da lohnt es sich dann, eine eigene Funktion zu deklarieren.

Funktionen selbst schreiben

Die Deklaration einer solchen Funktion zur Namensüberprüfung könnte beispielsweise so aussehen:

Beispiel 5-1
Die Funktion namensCheck()
function namensCheck($name) {
 
    if (preg_match("/^[^\W\d_]([^\W\d_]|[\-\'\s\.])*$/",
 
        stripslashes($name))) { // ja
 
        return true;
 
    }
 
    else
 
    {
 
        return false;
 
    }
 
}
 

Wie Sie hier sehen können, besteht eine Funktionsdeklaration aus dem Schlüsselwort function, dem Funktionsnamen, einer Parameterliste in runden Klammern und dem eigentlichen Funktionscode in geschweiften Klammern.

In unserem Beispiel heißt die Funktion namensCheck(). Die Parameterliste besteht nur aus einem einzigen Parameter, der uns in der Funktion (und nur dort) als eine Variable namens $name zur Verfügung steht. Der Code der Funktion besteht aus einem if-Statement, das wiederum preg_match() mit einem recht komplizierten regulären Ausdruck aufruft (siehe Kasten »Der reguläre Ausdruck zur Überprüfung von Namen« weiter unten in diesem Kapitel).

Wenn der reguläre Ausdruck passt, geben wir mit dem Befehl return den Funktionswert true zurück. Wenn er nicht passt, wird false zurückgegeben.

Wir könnten diese Funktionsdeklaration von namensCheck() eigentlich an beliebiger Stelle in unserem Skript einfügen. Schon der Lesbarkeit wegen ist es aber üblich, alle Funktionen eines Skripts in einem gemeinsamen Abschnitt unterzubringen, meist ziemlich am Anfang des Skripts.

Funktionen in Bibliotheken unterbringen

Wenn Sie dieselbe(n) Funktion(en) in mehreren Skripten verwenden wollen, können Sie sie auch in eine Funktionsbibliothek stellen - das ist eine externe PHP-Datei, die nur Funktionen enthält und die Sie dann mit include am Anfang Ihres Skripts einfügen können.
Ihre Funktionsbibliothek mit einer Funktion rechteckFlaeche() könnte z.B. so aussehen:
<?php
...
function rechteckFlaeche($laenge,$breite) {
return $laenge * $breite;
}
...
?>
Wenn Sie die Bibliothek in einer Datei namens meineFunktionen.phpi im selben Verzeichnis wie Ihre Skripte ablegen, können Sie sie wie folgt in Ihr Skript einbinden:
<?php
include("./meineFunktionen.phpi");
...
echo "Ein Rechteck, das 3 m lang und 4 m breit ist,
hat eine Fläche von ".rechteckFlaeche(3,4)." qm";
?>
Bei der Verwendung von .phpi-Dateien sollten Sie allerdings Ihren Server entsprechend konfigurieren (siehe den Abschnitt »Die Klassendeklarationen für unsere Website« weiter unten in diesem Kapitel).

Einmal deklariert, können wir unsere Eigenbau-Funktion jetzt beliebig oft aufrufen, z.B. so:

Beispiel 5-2
Eigene Funktionen aufrufen 
$istEinName = "Timothy O'Reilly";
 
$istKeinName = "*+?$@";
 
if (namensCheck($istEinName)) {
 
    echo $istEinName." ist ein Name";
 
}
 
else {
 
    echo $istEinName." ist kein Name";
 
}
 
if (namensCheck($istKeinName)) {
 
    echo $istKeinName." ist ein Name";
 
}
 
else {
 
    echo $istKeinName." ist kein Name";
 
}
 

Eine andere Deklarations- und Einsatzmöglichkeit für selbst geschriebene Funktionen ist die so genannte Objektmethode. Als solche werden wir namensCheck() bald einsetzen.

Der reguläre Ausdruck zur Überprüfung von Namen

Sehen wir uns den regulären Ausdruck in namensCheck() einmal näher an:
/^[^\W\d_]([^\W\d_]|[\-\'\s\.])*$/
Zunächst einmal können wir an den Begrenzungszeichen ^ und $ sehen, dass der Ausdruck auf den gesamten String passen muss. Als erste Zeichenklasse begegnet uns [^\W\d_]. Das ^-Zeichen am Anfang der Klasse bedeutet, dass das Zeichen, das wir suchen, nicht zu den nachfolgenden Zeichen gehören darf. Dies sind Nicht-Wortzeichen (\W), Ziffern (\d) und Unterstreichungszeichen.
Was, bitte, ist ein Nicht-Wortzeichen? Wie wir schon im letzten Kapitel gesehen haben, definieren sich Wortzeichen als alle Buchstaben (auch Umlaute usw., soweit Ihr System diese unterstützt), Ziffern und Unterstreichungszeichen.
Indem wir angeben, dass das gesuchte Zeichen kein Nicht-Wortzeichen sein darf, verlangen wir, dass es ein Wortzeichen sein muss. Das schränken wir aber gleich weiter ein, indem wir Ziffern und Unterstreichungszeichen aus unserem erlaubten Zeichensatz hinauswerfen - oder kennen Sie jemanden, dessen Name mit einer Zahl oder einem Unterstreichungszeichen anfängt?
Mit den darauf folgenden Zeichen müssen wir allerdings etwas aufpassen. Hier können außer den bereits bekannten eingeschränkten Wortzeichen auch Bindestriche (Müller-Lüdenscheid), Apostrophe (O'Reilly), Punkte und Leerzeichen (Dr. M. A. Buse) auftreten - und zwar beliebig viele.
Deswegen geben wir preg_match() durch den senkrechten Strich eine Auswahl zwischen zwei Zeichenklassen: unserer ersten, bereits besprochenen, und einer weiteren, die alle diese zusätzlichen Zeichen enthält: [\-\'\s\.]. Mit dem darauf folgenden * wird dann angegeben, dass beliebig viele Zeichen dieser beiden Klassen nacheinander vorkommen können.
Wie im letzten Kapitel bereits angesprochen, bereiten uns Apostrophe eventuell ein zusätzliches Problem: In den meisten Distributionen von PHP ist in der Konfigurationsdatei php.ini der Parameter magic_quotes_gpc auf ON gesetzt. Wenn Sie Namen überprüfen wollen, die Apostrophe enthalten können, sollten Sie diese vor der Überprüfung mit dem regulären Ausdruck durch stripslashes() entfernen.

Pläne für den Site-Ausbau

Bisher ist unsere Beispielsite ja noch ziemlich klein. Wenn wir sie ausbauen wollen, ist es höchste Zeit, dass wir darüber nachdenken, wie wir das so effektiv wie möglich machen können. Ein brennendes Problem steht ja schon auf der Tagesordnung: Wie erfährt unser Vogelschutzverein eigentlich von seinen Spenden? Dazu müssen wir die Spendeninformationen irgendwo hinterlegen, wo der Schatzmeister sie nachher finden kann.

Wenn Sie sich mögliche Erweiterungen der Site überlegen, fallen Ihnen vielleicht noch ein ganze Anzahl zusätzlicher Anwendungen ein:

Für viele dieser geplanten Seiten ist schon jetzt abzusehen, dass sie ähnliche Funktionalitäten brauchen. So ist es beispielsweise egal, ob ein Spender unser Spendenformular ausfüllt, ob der Schatzmeister die Daten eines Spenders auf den neusten Stand bringt oder ob der Spender das selbst macht: In allen Fällen müssen wir die Eingaben überprüfen und in der (noch zu erstellenden) Datenbank ablegen. Beim Auflisten der Spenden oder der Erstellung des Spendenthermometers müssen wir auch jedes Mal auf Spendendaten zugreifen.

Also brauchen wir Code zur Datenüberprüfung und zum Speichern und Auslesen von Daten in einer Datenbank. Wenn wir beim Erstellen neuer zusätzlicher Skripte so wenig neuen Code wie möglich schreiben wollen, sollten wir die gemeinsamen Funktionalitäten und Daten zusammenfassen und gemeinsam verwalten. Die Technik dafür heißt objektorientiertes Programmieren.

Objektorientiertes Programmieren

Wenn Sie heutzutage mit Computern zu tun haben, werden Ihnen früher oder später die Begriffe »Objekt« oder »objektorientiert« bzw. »OO« begegnen. Diese Konzepte gibt es auch in PHP, und sie können die Komplexität bei größeren Projekten enorm reduzieren. Deshalb lohnt es sich, dass wir uns diesen »heiligen Gral« der modernen Programmiererei einmal näher ansehen.

Objektorientiertes Programmieren ist in den letzten ca. 20 Jahren aus der Erkenntnis heraus entstanden, dass Daten in einem Programm häufig in Beziehung zueinander stehen und gleichartigen Operationen unterworfen sind. Mit Objekten kann man eine Beziehung zwischen Daten herstellen und die zum Umgang mit den Daten erforderlichen Funktionen gleich mit in das Objekt integrieren.

Das Konzept hat sich seither durchgesetzt: Praktisch alle modernen Programmiersprachen unterstützen Objekte. In einigen Sprachen, wie z.B. Java, sind Objekte sogar ein so zentrales Konzept, dass Sie ohne Objekte so gut wie keine Programme schreiben können.

Am besten erklären sich Objekte an einem konkreten Beispiel. In unserem Fall bietet es sich an, dass wir uns einmal das Konzept einer Spende für unseren Vogelschutzverein ansehen. Bisher haben wir die Spende in unseren Skripten als einen Haufen von Variablen behandelt, die entweder gar nichts miteinander zu tun hatten oder allenfalls in einem assoziativen Array zusammengefasst waren.

Wie eben erwähnt, fassen Objekte Daten zusammen und bündeln sie mit Code, mit dem die Daten bearbeitet werden können. Wenn wir eine Spende als ein Objekt sehen, dann verfügt sie zunächst einmal über Daten. In OO-Sprache heißen diese Objektdaten Eigenschaften oder auch Properties. Überlegen wir uns einmal, welche Eigenschaftsdaten wir mit einer Spende assoziieren können.

Objekteigenschaften

Eine Eigenschaft einer Spende, die uns sofort einfällt, ist natürlich der Spendenbetrag. Eine weitere Eigenschaft könnte die Information sein, ob die Spende anonym sein soll, d.h., ob wir den Namen des Spenders veröffentlichen dürfen oder nicht. Andere Eigenschaften eines Spende-Objekts wären zum Beispiel das Datum der Spende, eine Eigenschaft, die anzeigt, ob die Spende bereits abgebucht worden ist, oder auch eine Eigenschaft, die angibt, ob die Spende regelmäßig erfolgen soll, z.B. einmal im Monat.

Dann wären da natürlich noch die Informationen zum Spender und zu der verwendeten Kreditkarte. Wir könnten uns überlegen, ob wir hier die Einzelinformationen (z.B. die Kreditkartennummer) auch jeweils zu Eigenschaften unseres Spende-Objekts machen wollen. Als OO-Purist würden Sie der Versuchung widerstehen und stattdessen den Spender und die Kreditkarte jeweils als eigene Objekte mit eigenen Eigenschaften ansehen. Das Spender-Objekt und das Kreditkarten-Objekt würden Sie dann zu Eigenschaften des Spende-Objekts machen. Auf diese Art und Weise könnten Sie zum Beispiel mehrere Spenden demselben Spender zuordnen. Damit haben wir schon etwas Ordnung in das Dickicht unserer Daten gebracht.

Objektmethoden

Okay, tolle Planspiele, aber was bringt uns diese ganze Bürokratie? Ein assoziatives Array ist doch einfacher und gibt uns genauso Zugriff auf die Daten, oder? Bis zu diesem Punkt ist das begrenzt richtig. Die wahre Power der OO-Programmierung entfaltet sich nämlich erst dann, wenn Sie Methoden einsetzen. Als Methode bezeichnen Programmierer eine Funktion, die zu einem Objekt gehört und mit den internen Daten und den Eigenschaften des Objekts arbeitet.

Einige Beispiele für nützliche Methoden:

Eine Methode zum Abspeichern der Objektdaten in der Datenbank
Diese Methode könnte dafür sorgen, dass sämtliche Daten des betreffenden Objekts in der Datenbank gespeichert werden. Wenn wir das Objekt in Spende .php (bzw. in den davon abgeleiteten Skripten des vorigen Kapitels) einsetzen wollen, dann geht es um das Einfügen einer neuen Spende in die Datenbank.
In den Skripten, in denen der Schatzmeister des Vogelschutzvereins oder der Spender die Spenden in der Datenbank bearbeitet, muss ein bereits bestehender Eintrag in der Datenbank aktualisiert werden. Hier könnte man also zwei Fliegen mit einer Klappe schlagen.
Eine Methode zum Laden der Objektdaten aus der Datenbank
Diese Methode könnten Sie dazu verwenden, Daten für das Objekt aus der Datenbank zu lesen, falls ein entsprechender Eintrag existiert. Diese Methode könnte in einer ganzen Reihe der geplanten Skripte Anwendung finden.
Eine Methode zum Errechnen der bisher gespendeten Gesamtsumme
Bei regelmäßigen Spenden könnte Ihnen diese Methode berechnen, wie viel bisher insgesamt aus dieser Spendenquelle geflossen ist. Dazu können wir das Spendendatum, den Spendenbetrag und das Spendenintervall heranziehen.
Eine Methode zur Konvertierung des Spendenbetrags in andere Währungen
Wer sagt eigentlich, dass Sie nur in Europa auf Spenderfang gehen können? Reiche Wohltäter gibt's auch anderswo. Wie wär's mit einer Methode, die es Ihnen ermöglicht, diesen potenziellen Spendern das Spenden in ihrer Heimatwährung zu ermöglichen?

Sie sehen, Ihrer Fantasie sind kaum Grenzen gesetzt.

Klassen, Instanzen und Objektvariablen

Jetzt wissen wir, was wir in einem Spendenobjekt gern vorfinden würden. Aber was machen wir, wenn wir es mit mehreren Spenden zu tun haben, z.B. in einer Spendenliste? Müssen wir dann den ganzen Code mehrfach definieren?

Kein Grund zur Unruhe - beim objektorientierten Programmieren steht die effiziente Wiederverwendung von Code ganz oben auf der Liste, und das Anlegen von mehreren Objekten desselben Typs ist von vorneherein mit berücksichtigt. Sie können nämlich nicht so einfach ein Objekt definieren, jedenfalls nicht so einfach wie eine simple Variable oder ein assoziatives Array.

Wenn Sie ein Objekt erzeugen wollen, brauchen Sie zunächst einmal eine Klasse, die Sie - ähnlich wie eine Funktion - deklarieren müssen. Eine Klasse stellt den »Typ« eines Objekts dar. Die Klassendeklaration, die wir in PHP-Code ausdrücken, ist so etwas wie ein »Bauplan« für Objekte dieser Klasse: Sie legt fest, welche Eigenschaften und Methoden ein Objekt dieses Typs (d.h. dieser Klasse) hat. Sehen wir uns einmal die Deklaration einer kleinen Klasse namens rechteck an:

class rechteck {
 
    var $laenge;
 
    var $breite;
 

 
    function flaeche() {
 
        return $this->laenge * $this->breite;
 
    }
 
}
 

Wie Sie an der Deklaration von rechteck sehen können, deklarieren wir Klassen mit dem Schlüsselwort class und dem Namen der Klasse, gefolgt von einem Ausdruck in geschweiften Klammern. Innerhalb der geschweiften Klammern deklarieren wir unsere Eigenschaften und unsere Methoden (Funktionen, die zur Klasse gehören).

Objekteigenschaften sind in PHP einfach Variablen, die mit dem Schlüsselwort var in der Klasse deklariert sind. Die in der Klasse deklarierten Methoden sind eigentlich ganz normale Funktionsdeklarationen. Die beiden Unterschiede zu »normalen« Variablen und Funktionen bestehen darin, dass Methoden innerhalb der Klassendeklaration stehen und dass wir auf die Eigenschaften innerhalb der Klasse mit dem Vorsatz $this-> zugreifen.

Damit weiß PHP, wie ein Objekt dieser Klasse zu bauen ist und was zu tun ist, wenn eine Methode eines solchen Objekts aufgerufen wird. Um ein Objekt dann auch tatsächlich zu bauen, brauchen wir in unserem Skript eine Variable, in der wir das Objekt abspeichern können. Dann können wir das Objekt mit dem Befehl new unter Angabe der Klasse erzeugen:

$meinErstesRechteck = new rechteck;
 

In dieser Zeile nimmt PHP die Klassendeklaration und baut damit ein neues Objekt der Klasse rechteck im Speicher. Ein solches Objekt, das sich im Speicher befindet, nennt man eine Instanz der Klasse. In diesem Fall haben wir also gerade eine Instanz der Klasse rechteck erzeugt. Die Speicheradresse der brandneuen Instanz wird in der Variablen $meinErstesRechteck abgelegt. Diese Objektvariable bezeichnet man auch als Referenz oder als Zeiger. Über sie können wir jetzt auf das Objekt zugreifen, indem wir das Objekt mit einem Bindestrich und einem »größer als«-Zeichen versehen und dahinter den Namen der Eigenschaft ohne Dollarzeichen oder den Namen der Methode schreiben:

$meinErstesRechteck->laenge = 3;
 
$meinErstesRechteck->breite = 5;
 
echo $meinErstesRechteck->flaeche(); // Gibt 15 aus.
 

Wie schon erwähnt, können wir so viele Rechtecke erzeugen, wie wir wollen:

$meinZweitesRechteck = new rechteck;
 
$meinZweitesRechteck->laenge = 7;
 
$meinZweitesRechteck->breite = 2;
 
echo $meinZweitesRechteck->flaeche(); // Gibt 14 aus.
 
echo $meinErstesRechteck->flaeche(); // Gibt immer noch 15 aus.
 

Sie können sich die Erzeugung dieser zwei Instanzen in Abbildung 5-1 ansehen.
Abbildung 5-1 Eine Klasse stellt den Bauplan dar, aus dem die eigentlichen Objekte, die Instanzen, erzeugt werden

So weit, so gut. Wenn Ihnen die Lernkurve jetzt etwas steil vorkommt, verzweifeln Sie nicht - wir werden später noch genügend Beispielen für Klassen, Instanzen, Eigenschaften und Methoden begegnen. Für den Moment sollten Sie im Kopf behalten, dass die Klassendeklaration der »Bauplan« für eine Klasse ist und wir mit new und dem Klassennamen neue Objekte (Instanzen) erzeugen können, von denen jedes nach diesem Bauplan konstruiert ist und jeweils über die in der Klassendeklaration angegebenen Eigenschaften und Methoden verfügt. Auf ein Objekt greifen wir über die Objektvariable zu, die die Referenz auf das Objekt enthält.

Objekteigenschaften dynamisch hinzufügen

Eben habe ich Ihnen erzählt, dass Sie Objekteigenschaften mit var am Anfang der Klasse deklarieren sollen. Das macht das Leben für PHP tatsächlich etwas einfacher, ist aber streng genommen nicht erforderlich. Wenn Sie beispielsweise Ihrem Lieblingsrechteck eine Eigenschaft denkZettel verpassen wollen, können Sie das auch ohne jede Vorwarnung tun:
$meinLieblingsRechteck->denkZettel = "Das ist mein Lieblingsreckteck";
reicht völlig aus. Das hat aber ggf. den Nachteil, dass Sie eine einmal so gesetzte Eigenschaft beim Lesen erst mal wiederfinden müssen. Wenn Sie wie ich Eigenschaftsnamen gern vergessen, bedeutet das, ggf. den Klassencode oder auch externen Code nach der Zeile durchforsten zu müssen, in der Sie die Eigenschaft eingeführt haben.
Eine separate Deklaration hat den großen Vorteil, dass Sie nur oben in der Klasse nachzuschauen brauchen, wie eine Eigenschaft heißt.

Objektvariablen kopieren

Wenn wir eine Objektvariable in eine andere Objektvariable kopieren, fertigt PHP in den Versionen vor PHP 5 automatisch eine komplette Kopie des Objekts an. Dabei werden auch sämtliche Eigenschaftswerte kopiert. Nach einer solchen Operation haben wir zwei Objekte:

$meinLieblingsRechteck = $meinErstesRechteck;
 
$meinLieblingsRechteck->laenge = 2;
 
echo $meinLieblingsRechteck->flaeche(); // Gibt 10 aus.
 
echo $meinErstesRechteck->flaeche(); // Gibt immer noch 15 aus.
 

Wenn wir wollen, dass $meinLieblingsRechteck auf dieselbe Instanz zeigt wie $mein ErstesRechteck, müssen wir anstatt des Objekts die Referenz kopieren. Das geht mit einem Ampersand (&), das der zu kopierenden Variablen vorangestellt wird. Egal, welche dieser Variablen wir verwenden, wir greifen immer auf dasselbe Objekt zu:

$meinLieblingsRechteck = &$meinErstesRechteck;
 
$meinLieblingsRechteck->laenge = 2;
 
echo $meinLieblingsRechteck->flaeche(); // Gibt 10 aus.
 
echo $meinErstesRechteck->flaeche(); // Gibt jetzt auch 10 aus.
 

OO-Puristen mögen diese Haarspalterei zwischen Kopieren und Referenzieren allerdings überhaupt nicht: In der PHP 4-Praxis kommt es häufig vor, dass man sich aus Versehen eine Kopie eines Objekts erzeugt und sie fälschlicherweise für das Original hält. Dann verändert man ihre Eigenschaften und wundert sich anschließend, warum diese keine Wirkung zeigen - weil man inzwischen wieder mit dem Original hantiert. Das ist so, als ob Sie mit einem Zwilling reden und sich dann beim anderen darüber ärgern, dass er Ihnen nicht zugehört hat.

Um diese Probleme zu umgehen, sind ab PHP 5 alle Objektvariablen Referenzen. Eine Referenz (manchmal auch »Zeiger« genannt), ist einfach ein Bezeichner für ein bestimmtes Objekt. Das kennen Sie auch aus Ihrem Alltag: Wenn Sie gegenüber anderen Personen von Ihrem »neu erworbenen Auto« reden, meinen Sie damit im Regelfall einen ganz bestimmtes Objekt, Ihren fahrbaren Untersatz nämlich. Für ein bestimmtes Objekt kann es aber durchaus mehrere Bezeichner geben, z.B. »das rote Auto vor der Tür« oder die »Karre, die da so komisch geparkt steht«.

Nehmen wir also mal an, dass die Bezeichner für »Neu erworbenes Auto« und die »Karre, die da so komisch geparkt steht« auf dasselbe Objekt zeigen. Nun stellen Sie sich vor, Ihr unwissender Nachbar klingelt und teilt Ihnen mit, dass er soeben mit seinem Wagen eine Delle in die »Karre, die da so komisch geparkt steht« gefahren hat. Dann ist Ihr Objekt über die Referenz »Karre, die da so komisch geparkt steht« modifiziert worden. Diese Modifikation (eine Änderung der Objekteigenschaften!) können Sie dann auch bei einer Inspektion des Objekts sehen, auf das »Neu erworbenes Auto« zeigt. Wenn Ihr Wagen hingegen gut behütet in der Garage steht, dann dürfte »Karre, die da so komisch geparkt steht« wohl auf ein anderes Objekt zeigen. Damit kann Ihr Nachbar die »Karre, die da so komisch geparkt steht« soviel zerbeulen, wie er will - die gute Nachbarschaft bleibt erhalten.

Sehen wir uns das einmal mit unserem Rechteck an:

// Konfiguration eines Rechtecks:
 
$meinErstesRechteck->laenge = 3; $meinErstesRechteck->breite = 5;
 
// Die folgende Zeile gibt 15 aus - wie erwartet:
 
echo $meinErstesRechteck->flaeche();
 
// Wir erstellen eine zweite Referenz auf dasselbe Objekt:
 
$meinLieblingsRechteck = $meinErstesRechteck;
 
// ...und ändern die Parameter über die neue Referenz:
 
$meinLieblingsRechteck->laenge = 7; $meinLieblingsRechteck->breite = 2;
 
// Kurzer Check: Die folgende Zeile gibt 14 aus - wie erwartet:
 
echo $meinLieblingsRechteck->flaeche();
 
// Jetzt fragen wir das Rechteck über die ursprüngliche Referenz ab. Die
 
// nächste Zeile gibt jetzt auch 14 aus, weil beide Variablen Referenzen auf
 
// dasselbe Objekt sind:
 
echo $meinErstesRechteck->flaeche();
 

Ab diesem Kapitel verwenden wir PHP 5 - entsprechende Lösungen für PHP 4 finden Sie in den Online-Beispielen

Eine weitere nützliche Charakteristik von Klassen ist die Vererbung von Eigenschaften und Methoden auf so genannte Unterklassen, auch abgeleitete Klassen genannt. Eine von rechteck abgeleitete Klasse könnte z.B. so aussehen (siehe auch Abbildung 5-2):

class quader extends rechteck {
 
    var $hoehe;
 

 
    function volumen() {
 
        return $this->hoehe * $this->flaeche();
 
    }
 
}

Abbildung 5-2 Die Klasse quader erbt die Eigenschaften und Methoden von rechteck; die geerbten Eigenschaften und Methoden müssen nicht noch einmal deklariert werden

Die Klasse quader erbt alle Eigenschaften und Methoden der Klasse rechteck. Damit können wir den folgenden Code ausführen:

$meinErsterQuader = new quader;
 
$meinErsterQuader->laenge = 5;
 
$meinErsterQuader->breite = 3;
 
$meinErsterQuader->hoehe = 2;
 
echo $meinErsterQuader->volumen(); // Gibt 30 aus.
 

Intelligente Objekteigenschaften in PHP 5

Die Objekteigenschaften, die ich Ihnen bisher vorgestellt habe, waren eigentlich nur normale Variablen, die wir in unsere Objekten verpackt haben. Bis einschließlich PHP 4 war das auch die einzige Möglichkeit, Objekteigenschaften zu implementieren. Solange unsere Eigenschaften nur einen Wert speichern sollen, reicht das auch völlig aus. In der realen Welt gibt es aber immer wieder Beispiele, in denen ein Objekt eine Eigenschaft hat, bei deren Änderung bzw. Lesung auch »etwas passieren« sollte. Das trifft vor allem dann zu, wenn die Eigenschaft mit anderen Eigenschaften in Beziehung steht.

Bei unserem Rechteck bzw. Quader haben Sie sich z.B. vielleicht gefragt, warum man die Länge und Breite des Rechtecks als Eigenschaft liest, während man die Fläche als Rückgabewert einer Methode erhält. Ginge das nicht folgendermaßen einfacher?

echo $meinErstesRechteck->flaeche; // Keine Methode, sondern eine Eigenschaft!
 

Das hätte den Vorteil, dass wir uns nicht merken müssten, was an unserem Rechteck eine Eigenschaft und was eine Methode ist. Beim Programmieren mit Klassen werden Sie nämlich oft eine Klasse schreiben und dann viele Male einsetzen - zum Teil Wochen oder Monate, nachdem Sie sie geschrieben haben. Da wäre eine unkomplizierte Schnittstelle zum Objekt ungemein hilfreich.

Leider können wir eine solche Eigenschaft flaeche nicht so ohne weiteres wie eine herkömmliche Eigenschaft implementieren. Erstens muss sie sich ändern, wenn sich die Länge oder die Breite des Rechtecks ändert. Zweitens darf sie nicht beschreibbar sein, da wir sonst nicht wissen, ob wir die Länge, die Breite oder beide Maße ändern müssen. Ist das das Ende unserer Träumerei? Bis PHP 4 lautete die Antwort »ja, leider«.

Seit PHP 5 gibt es endlich eine solche Möglichkeit, und zwar über die Methoden _ _get() und _ _set(). Die beiden voranstehenden Unterstreichungszeichen gehören dabei zwingend zum jeweiligen Methodennamen. Diese zwei Methoden sind teilweise vordefiniert, d.h., wenn Sie sie implementieren, weiß PHP 5, wofür sie einzusetzen sind. Machen wir das doch mal bei unserer Rechteck-Klasse am Beispiel von _ _get() vor:

class rechteck {
 
    // laenge und breite sind gewöhnliche Eigenschaften.
 
    var $laenge;
 
    var $breite;
 

 
    // Die Eigenschaft flaeche wird nur über die _ _get()-Methode implementiert.
 
    function _ _get($name) {
 
        switch ($name) {
 
            case "flaeche": return $this->laenge * $this->breite;
 
        }
 
    }
 
}
 

Beachten Sie, dass wir im Gegensatz zu den Eigenschaften laenge und breite hier keine Variablendeklaration mit var vornehmen dürfen. Damit wird der folgende Code möglich:

$meinSchlauesRechteck = new rechteck;
 
$meinSchlauesRechteck->laenge = 5;
 
$meinSchlauesRechteck->breite = 10;
 
echo $meinSchlauesRechteck->flaeche; // Gibt 50 aus!
 

Beim Lesen der »unbekannten« Eigenschaft flaeche ruft PHP 5 automatisch die _ _get()-Methode auf, auch »Getter« genannt. Ihr einziger Parameter ist der Name der unbekannten Eigenschaft. PHP 5 überlässt es dann der _ _get()-Methode, ob sie mit diesem Namen etwas anfangen kann: »He Du da, kannste mit diesem flaeche-Dingsbums was machen?«. Da Sie problemlos mehrere solcher »intelligenten« Eigenschaften in einer Klasse deklarieren können, empfiehlt sich ein switch-Statement, um die verschiedenen Möglichkeiten auseinander zu halten. Der entsprechende case-Fall bestimmt dann den Rückgabewert von _ _get(), der gleichzeitig auch der »Wert« der Eigenschaft ist. Der case-Fall erledigt außerdem auch alle anderen Arbeiten, die beim Lesen der Eigenschaft evtl. noch anfallen. In unserem Fall berechnet er einfach die Fläche und gibt sie als Rückgabewert der Eigenschaft aus.

Im Moment ist unsere Eigenschaft flaeche nur lesbar. Sobald wir versuchen, sie zu beschreiben

$meinSchlauesRechteck->flaeche = 30;
 

geht unsere ganze schöne Konstruktion kaputt! Nach einem solchen Statement behandelt PHP flaeche als eine gewöhnliche Objekteigenschaft und umgeht _ _get() beim Lesen völlig. Das Blöde dabei ist, dass Ihnen so etwas bei der Fehlersuche ganz schön zu schaffen machen kann. Hier kommt uns _ _set() zu Hilfe, auch »Setter« genannt. _ _set() ist gewissermaßen das Gegenstück zu _ _get(). Die Methode nimmt zwei Parameter: einen Eigenschaftsnamen und den Wert, der in die Eigenschaft geschrieben werden soll. Ändern wir unsere Deklaration also noch einmal:

class rechteck {
 
    // laenge und breite sind gewöhnliche Eigenschaften.
 
    var $laenge;
 
    var $breite;
 

 
    // Das Lesen der schreibgeschützten Eigenschaft flaeche 
 
    // wird über die _ _get()-Methode implementiert.
 
    function _ _get($name) {
 
        switch ($name) {
 
            case "flaeche": return $this->laenge * $this->breite;
 
        }
 
    }
 

 
    function _ _set($name,$wert) {
 
        switch ($name) {
 
            default:
 
                echo "<b>Error! </b>";
 
                echo "<b>$name</b> ist entweder keine Eigenschaft oder ";
 
                echo "ist schreibgesch&uuml;tzt! Der Wert <b>$wert</b> kann ";
 
                echo "nicht zugewiesen werden!";
 
                exit();
 
        }
 
    }
 
}
 

In diesem Fall werden bei Ausführung der Programmzeile

$meinSchlauesRechteck->flaeche = 30;
 

der Methode _ _set() die Werte flaeche und 30 übergeben. Da das switch-Statement keine case-Klausel für flaeche hat, kommt die Default-Klausel zum Tragen, die eine Fehlermeldung ausspuckt und das Skript beendet.

Die Implementierung von schreibgeschützten Eigenschaften ist nur eine von vielen Einsatzmöglichkeiten von _ _set(). In unserem Roter-Faden-Beispiel werden wir _ _set() dazu verwenden, Eingabewerte von unserem Spendenformular (und später dann von weiteren Formularen) auf ihre Gültigkeit zu überprüfen. Um solche »intelligenten« oder »dynamischen« Eigenschaften, die durch _ _set() und _ _get() verwaltet werden, von konventionellen Objekteigenschaften zu unterscheiden, werde ich bei intelligenten Eigenschaften einfach das Dollarzeichen vor dem Namen weglassen. Einverstanden?

Damit haben wir eine gute Grundlage geschaffen, um uns die Klassendeklarationen für unsere Site ansehen zu können. Sie befinden sich in einer eigenen Bibliotheksdatei namens spendenKlassen.phpi. Um die Klassen zu verwenden, werden wir das Modul dann jeweils mit include() in unsere Skripte einbinden.

Die Klassendeklarationen für unsere Website

Die Datei spendenKlassen.phpi finden Sie wie üblich in den Online-Beispielen zu diesem Kapitel. Kriegen Sie keinen Schreck - die Datei ist groß, aber nicht undurchdringlich. Nur ein Teil des Codes in der Online-Datei wird schon in diesem Kapitel verwendet: Was Sie dort nicht wiedererkennen, dürfen Sie getrost bis zum nächsten Kapitel ignorieren.

Unser Code besteht in diesem Kapitel zunächst aus vier Klassen: einer Ursprungsklasse namens datenObjekt und drei davon abgeleiteten Klassen namens spender, kreditKarte und spende. Wir werden uns ihre Deklarationen gleich Klasse für Klasse ansehen.

Wenn Sie sich etwas über die Dateiendung gewundert haben (spendenKlassen.phpi), können Sie beruhigt sein: Es ist kein Tippfehler, sondern pure Absicht. Warum? Nun, ganz einfach: Wenn Sie eine Datei mit der Erweiterung .php in Ihrem Verzeichnis haben, kann sie jeder Browser anfordern und damit den Code in Ihrer Datei zur Ausführung bringen.

Eine Datei, die wie spendenKlassen.phpi ausschließlich in andere Skripte eingebunden werden soll, braucht aber nie von einem Browser explizit angefordert zu werden - das macht der Server schon selbst, wenn er auf ein entsprechendes include-Statement stößt. Damit ist das direkte Anfordern einer solchen Datei zumindest unerwünscht und unter Umständen sogar problematisch: Ein Hacker könnte eine Include-Datei durch direkten Aufruf mit falschen Daten füttern und so Funktionalität in Ihrem Modul missbrauchen.

Durch die Wahl einer anderen Dateiendung wie z.B. .phpi oder .inc können Sie, wenn Sie Ihren Server entsprechend konfiguriert haben, solche Dateien für Browser sperren. Damit umgehen Sie dieses potenzielle Problem. Ein Konfigurationsbeispiel für Apache finden Sie in Anhang A.

Wenn Sie Ihren Server nicht selbst konfigurieren können, sollten Sie die Dateiendung dennoch in .php ändern und ggf. am Anfang der Datei sicherstellen, dass die Datei von einem anderen Skript aufgerufen wird. Die folgenden PHP-Zeilen erledigen diesen Job:

if (basename($_SERVER["SCRIPT_FILENAME"]) 
 
    == "spendenKlassen.php") { exit(); } // Bei Direktaufruf Skript beenden!
 

So weit, so gut. Fangen wir also mit der Ursprungsklasse datenObjekt an.

Die Ursprungsklasse datenObjekt

Wie schon erwähnt, sind Klassen ja eigentlich nur »Baupläne«, aus denen wir dann zum Zeitpunkt der Programmausführung die eigentlichen Objekte bauen. In diesem Fall können Sie sich datenObjekt als eine Art »Grundbauplan« vorstellen, der alle gemeinsamen Elemente unserer Klassen zusammenfasst. Diesen Elementen werden in den abgeleiteten Klassen weitere Elemente hinzugefügt, die spezifisch auf diese Klassen zugeschnitten sind.

Den Code, der die Klasse datenObjekt deklariert, finden Sie als erste Deklaration in spendenKlassen.phpi:

Beispiel 5-3
Der Code der Klasse datenObjekt 
class datenObjekt {
 

 
    // Eigenschaften, die über _ _set() gesetzt werden:
 
    // - id
 
    // Eigenschaften, die nicht über _ _set() gesetzt werden:
 
    var $konsistent;
 
    var $problemFeld;
 

 
    // Methoden
 

 
    // Konstruktormethode - wird immer dann aufgerufen, wenn
 
    // eine neue Instanz der Klasse erzeugt wird.
 
    function datenObjekt() {
 
        $this->konsistent = true;
 
    }
 

 
    // Setter-Methode für Eigenschaften, die vom Web aus gesetzt werden.
 
    // Diese Methode überprüft die Werte für uns.
 
    function _ _set($eigenschaftsName, $wert) {
 
        // In dieser Klasse gibt es nur eine Eigenschaft, die ein Web-User 
 
        // ggf. setzen kann: die id-Eigenschaft.
 
        if ($eigenschaftsName == "id") {
 
            // Handelt es sich um eine "unique ID"? 
 
            if ($this->istUniqueId($wert)) {
 
               $this->fId = $wert; // Die interne Feldvariable setzen.
 
            }
 
            else
 
            {
 
               $this->fId = "";
 
               $this->ungueltigeEingabe("ID");
 
            }
 
        }
 
    }
 

 
    // Getter-Methode für Eigenschaften, die über _ _set() gesetzt werden.
 
    function _ _get($eigenschaftsName) {
 
        switch($eigenschaftsName) {
 
            case "id":
 
                return $this->fId;
 
        } 
 
    }
 

 
    // ungueltigeEingabe setzt die nötigen
 
    // Eigenschaften des Objekts, falls
 
    // eine ungültige Eingabe vorliegt. $feld
 
    // enthält eine Beschreibung des betreffenden
 
    // Felds:
 
    function ungueltigeEingabe($feld) {
 
        $this->konsistent = false;
 
        $this->problemFeld = $feld;
 
    }
 

 
    // Die folgende Methode gibt true zurück, wenn der übergebene Wert
 
    // eine PHP-Unique-ID ist:
 
    function istUniqueId($wert) {
 
        // 13 hexadezimale Zeichen?
 
        return preg_match("/^[a-f\d]{13}$/",$wert);
 
    }
 

 
    // namensCheck gibt true zurück, wenn der übergebene String-Parameter
 
    // ein Name sein könnte. Wenn nicht, gibt sie false zurück.
 
    function namensCheck($name) {
 
        // Ist der neue Name ein nicht-leerer String,
 
        // der mit einem Buchstaben anfängt und ansonsten
 
        // nur aus Buchstaben, Bindestrichen,
 
        // Leerzeichen und Apostrophen besteht?
 
        return preg_match("/^[^\W\d_]([^\W\d_]|[\-\'\s\.])*$/",
 
                                   stripslashes($name));
 
    }
 

 
    // nurZiffern gibt true zurück, wenn der angegebene
 
    // Wert nur aus Ziffern besteht.
 
    function nurZiffern($wert) {
 
        return preg_match("/^\d+$/",$wert);
 
    }
 

 
    function ladeAusDB($id) {
 
    }
 

 
    function speichern() {
 
    }
 
}
 

Die Klasse datenObjekt hat drei Eigenschaften:

// Eigenschaften, die über _ _set() gesetzt werden:
 
// - id
 
// Eigenschaften, die nicht über _ _set() gesetzt werden:
 
var $konsistent;
 
var $problemFeld;
 

Eine dieser Eigenschaften erwähnen wir hier nur als Kommentar: die intelligente Objekteigenschaft namens id: Wir werden sie gleich über _ _set() und _ _get() implementieren. Diese Eigenschaft verwenden wir nur, um jedem Objekt eine unverwechselbare Kennung zuzuweisen.

Die beiden anderen Eigenschaften werden wir im Zusammenhang mit diversen Methoden verwenden, und zwar zur Überprüfung von Eingabedaten. Wie schon erwähnt, werden diese Eigenschaften später an alle abgeleiteten Klassen vererbt, so dass Objekte dieser Klassen ebenfalls über diese Eigenschaften verfügen.

Wenn Sie sich die »Wunschliste« für unsere Site angesehen haben, werden Sie z.B. bemerkt haben, dass dort allein zwei weitere Seiten vorgesehen sind, mit denen wir (oder der Spender) persönliche Daten auf den neusten Stand bringen können. Dabei sollten wir jedes Mal die vom Browser übergebenen Daten überprüfen, so wie wir das auch in spende.php und den anderen Skripten gemacht haben.

Wenn wir diese Überprüfungen in die Klassen verlagern, stehen sie in allen Skripten zur Verfügung, die die Klassen einsetzen. Besser noch: Wenn Sie einmal eine Regel ändern wollen (z.B. den höchstzulässigen Spendenbetrag), brauchen Sie das nur an einer Stelle zu tun und nicht in mehreren Skripten.

Die Eigenschaft konsistent zeigt in diesem Fall an, ob die im Objekt gespeicherten Daten den Regeln entsprechen (true) oder nicht (false). In der Eigenschaft problemFeld speichern wir einen Hinweis auf die (letzte) Eigenschaft, deren Wertüberprüfung ein Problem gemeldet hat. Diese Eigenschaften, die die eigentlichen Daten speichern, führen wir erst in den abgeleiteten Klassen ein, weil sie sich von Klasse zu Klasse unterscheiden: Für eine Kreditkarte müssen wir ja andere Daten speichern als für eine Spende.

Neben diesen drei Eigenschaften werden in diesem Kapitel für jedes datenObjekt (also auch die Objekte der abgeleiteten Klassen) die folgenden neun Methoden datenObjekt(), _ _set(), _ _get(), istUniqueId(), namensCheck(), ungueltigeEingabe(), nurZiffern(), ladeAusDB() und speichern() deklariert, und zwar jeweils innerhalb der geschweiften Klammern der Klasse durch function-Statements.

Die Methode namensCheck() kennen Sie schon - wir haben sie einfach in die Klasse kopiert. Bleiben noch acht weitere, die wir erklären sollten.

Die Konstruktormethode datenObjekt()

datenObjekt() ist die merkwürdigste der vier verbleibenden Methoden. Besteht da nicht Verwechslungsgefahr? Keineswegs - datenObjekt() kommt eine besondere Aufgabe zu: Sie ist die so genannte Konstruktormethode der Klasse (auch einfach Konstruktor genannt). Konstruktormethoden haben den gleichen Namen wie die Klasse1 und werden immer dann aufgerufen, wenn mit new eine neue Instanz der Klasse erzeugt wird. Sie lassen sich dazu verwenden, das neue Objekt einzurichten, also z.B. Eigenschaften mit Anfangswerten zu versehen. In diesem Fall sieht der Konstruktor so aus:

function datenObjekt() {
 
    $this->konsistent = true;
 
}
 

Der Konstruktor macht also nur eines: Er setzt die Eigenschaft konsistent des betreffenden Objekts auf true. Mit anderen Worten: Wir wollen bis zum Beweis des Gegenteils davon ausgehen, dass die Daten in unserem Objekt »den Vorschriften entsprechen«, d.h., dass ein Betrag auch tatsächlich ein Betrag ist und nicht ein anders aussehender String. So weit, so gut.

In diesem Konstruktorbeispiel werden wir aber auch an einen wichtigen OO-Kniff von PHP erinnert: wie das betroffene Objekt innerhalb seiner eigenen Methoden heißt, nämlich $this (etwa »dieses (Objekt)«). Das sollten Sie sich unbedingt merken, es kommt ab jetzt nämlich an jeder Ecke vor.

$this->konsistent ist also die Eigenschaft konsistent des Objekts der Klasse datenObjekt, das hier gerade von unserem Konstruktor gebaut wird. Sie wird hier auf true gesetzt, weil wir bis zum Nachweis des Gegenteils davon ausgehen, dass das Objekt konsistente Daten enthalten wird.

Lokale Variablen

Nun werden Sie sich vielleicht fragen, warum in der Konstruktormethode nicht einfach $konsistent = true; steht. Das hat damit zu tun, dass in einer Funktion (also auch in einer Objektmethode) nur die Variablen bekannt sind, die entweder in der Funktion definiert werden (indem ihnen ein Wert zugewiesen wird) oder die ihr als Parameter übergeben werden. Solche Variablen nennt man lokale Variablen.
Die Verwendung von lokalen Variablen hat einen guten Grund: Wenn Sie Funktionen verwenden, die ein anderer Programmierer geschrieben hat, möchten Sie nicht, dass Ihre eigenen Variablen durch diese Funktionen unbemerkt verändert werden.
Genau das könnte aber passieren, wenn Ihre Variablen in der Funktion sichtbar wären: Nehmen Sie einmal an, dass der andere Programmierer in seiner Funktion eine Variable mit dem gleichen Namen wie dem einer Ihrer Variablen verwendet hat. Diese wäre dann mit Ihrer Variablen identisch und würde sie überschreiben. Dazu kommt es dank lokaler Variablen aber erst gar nicht.
Damit haben wir des Rätsels Lösung: Da wir $konsistent außerhalb der Funktionsdeklaration von datenObjekt() definiert haben, ist sie innerhalb der Konstruktormethode schlicht und einfach unbekannt. Damit würde $konsistent hier als eine neue (lokale) Variable angesehen. Da lokale Variablen aber am Ende der Funktionsausführung komplett »entsorgt« werden, hätte das Statement keinen Effekt.
Übrigens: Auf die von PHP zur Verfügung gestellte Variable $this trifft das nicht zu - sie ist »objektglobal«, d.h., sie ist in allen Methoden des Objekts bekannt.

Die Setter- und Getter-Methoden _ _set() und _ _get()

Die nächsten beiden Methoden kennen Sie auch schon, jedenfalls vom Prinzip her: Es sind die Setter-Methode _ _set() und die Getter-Methode _ _get(), die hier unsere einzige intelligente Objekteigenschaft, id, implementieren. Im Gegensatz zu unserem Einführungsbeispiel kann id sowohl geschrieben als auch gelesen werden.

Da der Wert in unseren geplanten Skripten aber ggf. von außen vorgegeben werden kann, sollten wir ihn überprüfen, bevor wir ihn in Zukunft in unserer noch zu konstruierenden Datenbank abspeichern. Der Wert soll eine eindeutige ID sein, wie sie von der PHP-Funktion uniqid("") erzeugt wird: ein String mit 13 hexadezimalen Zeichen, d.h. Ziffern von 0 bis 9 und Buchstaben von a bis f. Wir überprüfen ihn mit der Objektmethode istUniqueId(), die wir uns gleich noch im Detail ansehen werden.

Klappt die Überprüfung, speichern wir den übergebenen Wert in der (nur innerhalb unseres Objekts genutzten) Eigenschaft $this->fId ab, die wir an dieser Stelle einfach dynamisch hinzufügen. f steht hier für »Feld« und markiert für uns eine interne Eigenschaft, die wir nicht außerhalb der Methoden des Objekts verwenden wollen. Schlägt die Überprüfung fehl, rufen wir die Methode ungueltigeEingabe() auf und übergeben ihr den String »ID«, um anzuzeigen, wo das Problem aufgetreten ist. Das fId-Feld wird in diesem Fall auf einen leeren String gesetzt:

function _ _set($eigenschaftsName, $wert) {
 
    // In dieser Klasse gibt es nur eine Eigenschaft, die ein Web-User 
 
    // ggf. setzen kann: die id-Eigenschaft.
 
    if ($eigenschaftsName == "id") {
 
        // Handelt es sich um eine "unique ID"? 
 
        if ($this->istUniqueId($wert)) {
 
           $this->fId = $wert; // Die interne Feldvariable setzen.
 
        }
 
        else
 
        {
 
           $this->fId = "";
 
           $this->ungueltigeEingabe("ID");
 
        }
 
    }
 
}
 

Beim Lesen der Eigenschaft id kommt die Getter-Methode _ _get() zum Einsatz.

function _ _get($eigenschaftsName) {
 
    switch($eigenschaftsName) {
 
        case "id":
 
            return $this->fId;
 
    } 
 
}
 

Sie ist erheblich einfacher als _ _set(). Das switch-Statement ist hier fast unnötig - wir brauchen es eigentlich nur, um zu verhindern, dass aus Versehen falsch getippte Eigenschaftsnamen beim Auslesen den Wert der id-Eigenschaft liefern.

Die Methode ungueltigeEingabe()

Wie schon erwähnt, wollen wir jetzt jedes Mal eine entsprechende Objektmethode verwenden, um Daten aus einem Eingabefeld vor dem Abspeichern im Objekt zu überprüfen. Das bedeutet aber auch, dass wir das Objekt fragen müssen, ob die Eingabe korrekt war bzw. in welchem Eingabefeld das Problem aufgetreten ist.

Für diesen Zweck hat die Ursprungsklasse die Methode ungueltigeEingabe(). Sie wird immer dann aufgerufen, wenn eine Eingabe ihren »Aufnahmetest« nicht besteht. Mit ungueltigeEingabe() werden die zwei bereits erwähnten Eigenschaften des Objekts verändert, die diese Fragen beantworten: Die Eigenschaft konsistent wird auf false gesetzt, da wir ab jetzt Problemdaten in unserem Objekt haben. Außerdem wird die Eigenschaft problemFeld mit einem String gefüllt, der anzeigen soll, wo das Problem aufgetreten ist. Da wir auch hier Eigenschaften an dem Objekt ändern, zu dem die Methode gehört, bezeichnen wir das Objekt wieder mit $this.

Die Methode istUniqueId()

Auch wenn die meisten id-Werte in der Praxis vermutlich aus einem versteckten Eingabefeld auf einem Formular kommen werden, ist es für gewiefte Hacker möglich, uns hier einen anderen String unterzuschieben, der möglicherweise in unserer Datenbank Ärger stiften kann. Also sollten wir checken, ob der angegebene Wert wirklich eine »unique ID« ist, wie sie die uniqid("")-Funktion erzeugt. Diesen Job übernimmt die Methode istUniqueId().

Dazu müssen wir überprüfen, ob der String aus genau 13 Zeichen besteht, die entweder Ziffern oder Buchstaben von a bis f sein können. Was verwenden wir für solche kniffligen Aufgaben? Sie haben's erraten: einen regulären Ausdruck:

function istUniqueId($wert) {
 
    // 13 hexadezimale Zeichen?
 
    return preg_match("/^[a-f\d]{13}$/",$wert);
 
}
 

Zwischen den Anfangs- und Endzeichen ^ und $ finden Sie eine Zeichenklasse [a-f\d] und einen Multiplikator {13}, der vorschreibt, dass genau 13 Zeichen aus dieser Klasse benötigt werden. Die Zeichenklasse besagt, dass das Zeichen zwischen a und f liegen oder eine Ziffer (\d) sein muss.

Die Methode nurZiffern()

Bei der letzten Methode in unserer Ursprungsklasse handelt es sich wiederum um eine Funktion, die wie namensCheck() von diversen Eingabeüberprüfungen verwendet wird. Ihre Funktion ist eigentlich ganz einfach: Besteht der übergebene String nur aus Ziffern, wird true zurückgegeben. Wenn nicht, gibt die Methode false zurück.

Die Methoden ladeAusDB() und speichern()

Methoden, die zunächst nur teilweise und mit eingeschränkter oder vorgetäuschter Funktionalität implementiert werden, nennt man Stubs (»Stummel«). Die Methoden ladeAusDB() und speichern() sind ein gutes Beispiel für einen Stub: Über das Thema Datenbankzugriff müssen wir erst noch sprechen, also warten wir mit der Implementierung.

Solche Stubs gehören zu einer weit verbreiteten Programmiertechnik: Sie ermöglichen es dem Programmierer, sich bei der Software-Entwicklung auf einzelne Methoden bzw. Module zu konzentrieren, anstatt viele Methoden oder Module gleichzeitig entwickeln zu müssen. Wenn das den Profis recht ist, dann ist es uns billig, oder?

Damit ist unsere Ursprungsklasse auch schon abgeschlossen. Sehen wir uns an, wie wir sie in einer abgeleiteten Klasse erweitern können.

Die Klasse spender

Die nächste Klassendeklaration in spendenKlassen.phpi ist die für die Klasse spender:

Beispiel 5-4
Der Code der Klasse spender 
class spender extends datenObjekt {
 

 
    // Eigenschaften der Klasse spender.
 
    // Eigenschaften, die über _ _set() gesetzt werden:
 
    // - name
 
    // - adresse
 
    // Eigenschaften, die nicht über _ _set() gesetzt werden:
 
    var $fotoDatei;
 

 
    // Methoden der spender-Klasse.
 

 
    // Konstruktormethode - wird immer dann aufgerufen, wenn
 
    // eine neue Instanz der Klasse erzeugt wird.
 
    function spender($spenderId = "",
 
                 $nameNeu = "",
 
                 $adresseNeu = "") {
 
        $this->datenObjekt();
 
        if ($spenderId == "") { // Neuer Spender.
 
            $this->id = uniqid("");
 
            $this->name = $nameNeu;
 
            $this->adresse = $adresseNeu;
 
            $this->setzeFoto();
 
        }
 
        else
 
        {
 
            $this->ladeAusDB($spenderId);
 
        }
 
    }
 

 
    // Diese Setter-Methode überlagert die Setter-Methode
 
    // der übergeordneten Klasse. 
 
    function _ _set($eigenschaftsName,$wert) {            
 
        // Notwendige Checks für die jeweiligen Eigenschaften durchführen. 
 
        switch ($eigenschaftsName) {
 
            case "name":
 
                if ($this->namensCheck($wert)) { // OK 
 
                    $this->fName = $wert;                   
 
                }
 
                else
 
                {   // Nein, d.h., die Daten sind inkonsistent.
 
                    $this->ungueltigeEingabe("Spendername");
 
                }
 
                break;
 
            case "adresse":
 
                if (preg_match("/\w/",$wert)) { // OK 
 
                    $this->fAdresse = $wert;                   
 
                }
 
                else
 
                {   // Nein, d.h., die Daten sind inkonsistent.
 
                    $this->ungueltigeEingabe("Spenderadresse");
 
                }
 
                break;
 
            default:
 
                parent::_ _set($eigenschaftsName,$wert);
 
        }
 
    }
 

 
    // Diese Getter-Methode überlagert die Getter-Methode
 
    // der übergeordneten Klasse. 
 
    function _ _get($eigenschaftsName,$wert) {            
 
        switch ($eigenschaftsName) {
 
            case "name":
 
                return $this->fName;                   
 
            case "adresse":
 
                return $this->fAdresse;                   
 
            default:
 
                parent::_ _get($eigenschaftsName);
 
        }
 
    }
 

 
    // Diese Methode speichert das Spenderfoto und setzt die Eigenschaft fotoDatei.
 
    function setzeFoto() {
 
        // Wurde ein Foto heraufgeladen?
 
        if ($_FILES["spenderFoto"]["size"] > 0) {
 
            preg_match("/(\.\w+)$/",
 
                $_FILES["spenderFoto"]["name"],$match);
 
            $typ = $match[1];
 

 
            // Wir lassen nur Erweiterungen von Bilddateien zu.
 
            if (in_array(strtolower($typ),
 
                         array(".gif",".bmp",".jpg",".png",".jpeg"))) {
 
                $this->fotoDatei = $this->id.$typ;
 
                $bildPfad =
 
                    preg_replace("/\/[^\/]+$/","",
 
                                 $_SERVER["SCRIPT_FILENAME"])
 
                                 ."/bilder/";
 
                copy($_FILES["spenderFoto"]["tmp_name"],
 
                $bildPfad.$this->fotoDatei);
 
            }
 
        }
 

 
    }
 

 
} // Ende der Klasse spender.
 

Dass es sich bei spender um eine abgeleitete Klasse handelt, können Sie an der extends-Deklaration sehen, die auf die Ursprungsklasse datenObjekt verweist. Durch diese Ableitungsdeklaration erbt jedes Objekt der Klasse spender automatisch sämtliche Eigenschaften und Methoden der Klasse datenObjekt. Daher brauchen wir sie auch nicht noch einmal gesondert aufzuführen.

Deklarieren müssen wir lediglich die neuen Eigenschaften und Methoden. Die einzige neue »normale« Eigenschaft ist:

var $fotoDatei;
 

Darüber hinaus hat die Klasse auch noch zwei weitere »intelligente« Eigenschaften, die über _ _set() und _ _get() gesetzt und gelesen werden:name und adresse.

Der Zweck der beiden letzten Eigenschaften sollte eigentlich klar sein: Dort werden wir den Namen und die Adresse des jeweiligen Spenders abspeichern, den das Objekt in unserem Skript darstellt. Die erste Eigenschaft zeigt an, ob ein Spenderfoto vorhanden ist. Wenn ja, sollte die Eigenschaft fotoDatei nicht leer sein, sondern den Dateinamen der entsprechenden Bilddatei enthalten. Wie er dort hinkommt, sehen wir gleich.

Der Konstruktor für die Klasse spender

Die erste Methode in der Klassendeklaration der spender-Klasse ist der Konstruktor, den wir wie gehabt daran erkennen können, dass die Funktion den gleichen Namen wie die Klasse hat. Ansonsten gibt es hier aber einige neue Aspekte, zum Beispiel die Parameterliste. Klar, Funktionen mit Parametern kennen Sie inzwischen. Wozu braucht aber ein Konstruktor eine Parameterliste? Nun, einen neuen Spender können wir beispielsweise auch so erzeugen:

$meinNeuerSpender = new spender("","Fred Fisch","Teichweg 10, Seebad");
 

Sie sehen richtig: Wir rufen die Klasse wie eine Funktion auf. In Wirklichkeit ist natürlich nicht die Klasse selbst der Empfänger der Parameter, sondern der Konstruktor:

function spender($spenderId = "",
 
             $nameNeu = "",
 
             $adresseNeu = "") {
 
    $this->datenObjekt();
 
    if ($spenderId == "") { // Neuer Spender.
 
        $this->id = uniqid("");
 
        $this->name = $nameNeu;
 
        $this->adresse = $adresseNeu;
 
        $this->setzeFoto();
 
    }
 
    else
 
    {
 
        $this->ladeAusDB($spenderId);
 
    }
 
}
 

Wenn Sie sich die Parameterliste der Konstruktormethode spender() genau ansehen, werden Sie feststellen, dass wir es hier mit einer etwas ungewöhnlichen Parameterliste zu tun haben. Was haben hier die Gleichheitszeichen zu suchen?

Keine Panik: Wenn einem Parameter in der Parameterliste einer Funktion bzw. Methode ein Gleichheitszeichen folgt, deutet das an, dass dieser Parameter optional ist. Das heißt, er kann gegebenenfalls auch weggelassen werden. Das Gleichheitszeichen weist ihm für diesen Fall einen Default-Wert zu.

In unserem Fall haben alle Parameter den leeren String als Default-Wert. Konkret bedeutet das, dass wir Spenderobjekte mit unterschiedlichen Parameterkombinationen erzeugen können, unter anderem so:

Beispiel 5-5
Drei Wege, ein spender-Objekt zu erzeugen
// namen-/adressenloser Spender mit automatisch generierter ID:
 
$meinNeuerSpender1 = new spender(); 
 
// Spender mit automatisch generierter ID ohne Adresse:
 
$meinNeuerSpender2 = new spender("","Fred Fisch"); 
 
// Existierender Spender aus der Datenbank, ID ist bekannt:
 
$meinNeuerSpender3 = new spender("3c6edeb214563"); 
 

Im Codeblock unseres Konstruktors finden wir als Erstes einen Aufruf der Konstruktormethode datenObjekt() für die Ursprungsklasse. Da dieser sonst nicht automatisch aufgerufen wird, müssen wir das hier explizit machen. In diesem speziellen Fall könnten wir eigentlich genauso gut die Eigenschaft $this->konsistent direkt setzen - der Konstruktor von datenObjekt macht ja auch nicht mehr. Durch den Einsatz des Ursprungsklassen-Konstruktors kommen wir aber in den automatischen Genuss etwaiger zusätzlicher Kommandos, die Sie diesem vielleicht in Zukunft noch hinzufügen möchten.

Der weitere Ablauf des Konstruktors spender hängt davon ab, ob dem Konstruktor eine Spender-ID mitgegeben wurde. Wenn nicht, wird mit uniqid() automatisch eine solche generiert, und die (gegebenenfalls Default-)Werte der anderen Parameter werden nach Überprüfung zum Setzen der entsprechenden Objekteigenschaften verwendet.

Liegt eine Spender-ID vor, wird die Methode ladeAusDB() aufgerufen, die die Spenderdaten aus der Datenbank holen und in den entsprechenden Objekteigenschaften speichern soll. Diese Methode ist im Moment noch ein Stub der Klasse datenObjekt: Sie lässt sich zwar jetzt aufrufen, macht aber noch gar nichts. Wir werden sie erst später mit Code füllen.

Objektbegriffe - kurz und knapp

Objekt
Eine Datenstruktur in PHP, die sowohl Daten (Eigenschaften) enthält als auch Funktionen (Methoden genannt), die auf diese Daten einwirken und mit ihnen arbeiten. Welche Daten und Methoden Sie in einem bestimmten Objekt finden, hängt von der Klasse ab, der dieses Objekt angehört.
Klasse
Ein »Bauplan« für Objekte, der festlegt, welche Eigenschaften und Methoden ein Objekt hat, das zu dieser Klasse gehört. Eine Klassendeklaration beginnt mit dem Schlüsselwort class vor dem Klassennamen, gefolgt von der Definition der Eigenschaften und Methoden in geschweiften Klammern.
Instanz
Eine Instanz ist ein Objekt einer bestimmten Klasse. Man sagt auch, dass ein bestimmtes Objekt »eine Instanz der Klasse« ist, der es angehört. Eine neue Instanz einer Klasse wird mit new Klassenname erzeugt.
Eigenschaften
PHP-Variablen, die innerhalb der Klassendeklaration mit dem Schlüsselwort var deklariert werden. Auf eine Eigenschaft $eigenschaft kann von einer Objektvariablen $meinObjekt mit $meinObjekt->eigenschaft zugegriffen werden.
Methoden
Methoden sind Funktionen, die innerhalb einer Klassendeklaration deklariert sind. Sie können auf Eigenschaften und andere Methoden der Klasse zugreifen und selbst über $meinObjekt->methode() aufgerufen werden.
Objektvariablen und Referenzen
Ganz normale PHP-Variablen, die auf ein Objekt verweisen. Beachten Sie, dass aber nicht das Objekt selbst in der Variablen gespeichert wird, sondern nur der Verweis darauf, wo im Speicher das Objekt zu finden ist. Daher können mehrere Variablen auf dasselbe Objekt verweisen.
Konstruktoren
Konstruktoren sind Methoden, die immer dann aufgerufen, wenn eine neue Instanz der Klasse mit new erzeugt wird. Sie können aber auch als gewöhnliche Methoden aufgerufen werden. Sie haben den gleichen Namen wie die Klasse, in der sie deklariert sind. Ab PHP 5 können sie auch _ _construct() genannt werden.
Abgeleitete Klasse
Eine abgeleitete Klasse wird mit dem Zusatz extends Ursprungsklasse deklariert. Eine so deklarierte Klasse »erbt« alle Eigenschaften und Methoden, die in Ursprungsklasse deklariert wurden.

Die anderen Methoden der Klasse spender

Die Methoden _ _set() und _ _get() setzen bzw. lesen die entsprechenden Eigenschaften des Objekts, vorausgesetzt, dass der jeweils übergebene Wert den an ihn gesetzten Erwartungen entspricht. Hier kommen unsere switch-Statements jetzt richtig zum Zug. Im Fall der Eigenschaft name verwenden wir beim Setzen die Methode namensCheck(), die aus der Ursprungsklasse geerbt wurden. Die Adresse überprüfen wir - zugegeben etwas lose - mit einem regulären Ausdruck. Er stellt sicher, dass zumindest ein Wortzeichen in der Adresse vorhanden ist.

Bei Erfolg speichern wir den jeweiligen Wert wieder in einem Feld - einer normalen Eigenschaft mit ähnlichem Namen.

Wenn der jeweilige Wert die Überprüfung nicht besteht, rufen die case-Klauseln in _ _set() jeweils ungueltigeEingabe() auf, die wir ebenfalls aus der Klasse datenObjekt geerbt haben. Schon etwas Feines, so eine Erbschaft.

Am Ende des switch-Statements in _ _set() und _ _get() rufen wir jeweils die gleiche Methode in der Ursprungsklasse auf. Damit suchen wir im Default-Fall zunächst einmal in der Ursprungsklasse nach einer Eigenschaft mit diesem Namen. Eine Methode in der Ursprungsklasse, die unter dem gleichen Namen auch in der abgeleiteten Klasse deklariert ist, rufen wir mit dem Vorsatz parent:: auf, z.B. hier in _ _set():

parent::_ _set($eigenschaftsName,$wert);
 

Diese Technik - Methoden von Ursprungsklassen in abgeleiteten Klassen durch eine Spezialversion zu ersetzen, wie hier im Fall von _ _set() - nennt man übrigens »Überladen«.

Die Methode setzeFoto() schlachtet weitgehend den bestehenden Code aus spende.php bzw. spendenCombo.php aus. Der einzige Unterschied besteht darin, dass wir den Dateinamen der Bilddatei aus der id-Eigenschaft des Objekts erzeugen und ein paar »normale« Variablen durch Objekteigenschaften ersetzt haben: $dateiName heißt jetzt $this->fotoDatei. Da ein leerer Dateiname als false interpretiert wird, ein nicht-leerer aber als true, kommen wir auch ohne die Indikatorvariable $foto aus.

Damit wären wir vorerst am Ende unser spender-Klasse angelangt. Mit dem Code, den wir bis jetzt besprochen haben, können wir einen Spender vollständig als Objekt darstellen: Alle persönlichen Daten sind als Eigenschaften vorhanden. Wir können neue Daten durch Methoden überprüfen. Noch besser: Weil wir eine Klasse haben, können wir jetzt ohne großen Aufwand eine beliebige Anzahl solcher Spenderobjekte erzeugen und verwalten.

Die Klasse kreditKarte

Wenn Ihnen der Kopf noch von der spender-Klasse raucht, können Sie beruhigt sein: Den steilsten Teil der Lernkurve haben Sie schon hinter sich. Die zweite Klasse in spendenKlassen.phpi dient der Speicherung und Verarbeitung von Kreditkartendaten. Auch diese Klasse stammt von datenObjekt ab und wendet auch sonst viele Techniken an, die Sie schon von der Klasse spender her kennen:

Beispiel 5-6
Die Deklaration der Klasse kreditKarte 
class kreditKarte extends datenObjekt {
 

 
    // Eigenschaften der Klasse kreditKarte.
 
    // Eigenschaften, die über _ _set() gesetzt werden:
 
    // - name
 
    // - typ
 
    // - nummer
 
    // - laeuftAb
 
    // - gueltig
 
    // Eigenschaften, die nicht über _ _set() gesetzt werden:
 
    var $laeuftAbMonat;
 
    var $laeuftAbJahr;
 

 
    // Methoden
 

 
    // Konstruktormethode - wird immer dann aufgerufen, wenn
 
    // eine neue Instanz der Klasse erzeugt wird.
 
    function kreditKarte($kartenId = "",
 
                     $kartenNummerNeu = "",
 
                     $lauftAbNeu = "",
 
                     $typNeu = "",
 
                     $nameNeu = "") {
 
        $this->datenObjekt();
 
        if ($kartenId == "") { // neue Karte
 
            $this->id = uniqid("");
 
            $this->gueltig = true; // hoffen wir's
 
            $this->nummer = $kartenNummerNeu;
 
            $this->laeuftAb = $lauftAbNeu;
 
            $this->typ = $typNeu;
 
            $this->name = $nameNeu;
 
        }
 
        else
 
        { // Karte in der Datenbank suchen.
 
            $this->ladeAusDB($kartenId);
 
        }
 
    }
 

 
    // Diese Setter-Methode überlagert die Setter-Methode
 
    // der übergeordneten Klasse. 
 
    function _ _set($eigenschaftsName,$wert) {            
 
        // Notwendige Checks für die jeweiligen Eigenschaften durchführen. 
 
        switch ($eigenschaftsName) {
 
            case "name":
 
                // Prüfen, ob es sich um einen Namen handeln könnte.
 
                if ($this->namensCheck($wert)) { // OK 
 
                    $this->fName = $wert;                   
 
                }
 
                else
 
                {   // Nein, d.h., die Daten sind inkonsistent.
 
                    $this->ungueltigeEingabe("Karteninhaben");
 
                }
 
                break;
 
            case "typ":
 
                // ist der Wert ein gültiger Kreditkartentyp?
 
                switch ($wert) { 
 
                    case "American Express":
 
                    case "Visa":
 
                    case "Mastercard":
 
                        $this->fTyp = $wert;                   
 
                        break;
 
                    default:
 
                        // Unbekannter Typ, d.h., die Daten sind 
 
                        // inkonsistent.
 
                       $this->ungueltigeEingabe("Kreditkartentyp");
 
                }
 
                break;
 
            case "nummer":
 
                // Können wir eine gültige Kreditkartennummer extrahieren?
 
                $wert = preg_replace("/\D/","",$wert);
 
                if (preg_match("/^\d{15,16}$/",$wert)) { // OK
 
                    $this->fNummer = $wert;
 
                }
 
                else
 
                {   // Nein, d.h., die Daten sind inkonsistent.
 
                    $this->ungueltigeEingabe("Kreditkartennummer");
 
                }
 
                break;
 
            case "laeuftAb":
 
                if (preg_match("/^(\d{2})(0[2-9])$/", $neuesAblaufDatum, $match)) {
 
                    if (($match[1] > 0) && ($match[1] < 13)) {
 
                        $this->fLaeuftAb = $neuesAblaufDatum;
 
                        $this->laeuftAbMonat = $match[1];
 
                        $this->laeuftAbJahr = $match[2];
 
                        $this->abgelaufen();
 
                    }
 
                    else
 
                    {
 
                        $this->gueltig = false;
 
                        $this->ungueltigeEingabe("Kreditkarten-Ablaufdatum (Monat)");
 
                    }
 
                }
 
                else
 
                {
 
                    $this->gueltig = false;
 
                    $this->ungueltigeEingabe("Kreditkarten-Ablaufdatum (Monat)");
 
                }
 
                break;
 
            case "gueltig":
 
                // Wird über den ternären Operator gesetzt.
 
                $this->fGueltig = ($wert) ? true : false;
 
                break;
 
            default:
 
                parent::_ _set($eigenschaftsName,$wert);
 
        }
 
    }
 

 
    // Diese Getter-Methode überlagert die Getter-Methode
 
    // der übergeordneten Klasse. 
 
    function _ _get($eigenschaftsName,$wert) {        
 
        switch ($eigenschaftsName) {
 
            case "name":
 
                return $this->fName;               
 
            case "typ":
 
                return $this->fTyp;                   
 
            case "nummer":
 
                return $this->fNummer;               
 
            case "adresse":
 
                return $this->fAdresse;               
 
            case "gueltig":
 
                // Sicherstellen, dass die Karte nicht abgelaufen ist.
 
                $this->abgelaufen();
 
                // Rückgabewert sollte 1 oder 0 sein.
 
                return ($this->fGueltig) ? 1 : 0;                   
 
            default:
 
                parent::_ _get($eigenschaftsName);
 
        }
 
    }
 

 
    // Die folgende Methode überprüft, ob die Karte abgelaufen ist:
 
    function abgelaufen() {
 
        $ablaufDatum = $this->laeuftAbMonat."/1/";
 
        $ablaufDatum .= $this->laeuftAbJahr."+1 month";
 
        $ablaufZeitPunkt = strtotime($ablaufDatum);
 
        if ($ablaufZeitPunkt < time()) {
 
            // Kartendaten mögen konsistent sein, aber die Karte ist abgelaufen.
 
            $this->gueltig = false;
 
            return true;
 
        }
 
        else
 
        {
 
            return false;
 
        }
 
    }
 

 
    } // Ende der Klasse kreditKarte.
 

 

Die Klasse kreditKarte hat die zusätzlichen normalen Eigenschaften:

var $laeuftAbMonat;
 
var $laeuftAbJahr;
 

sowie die Eigenschaften name, typ, nummer, laeuftAb und gueltig, die über _ _set() gesetzt und über _ _get() gelesen werden.

In diesem Fall soll name den Namen des Kreditkarteninhabers speichern, typ den Typ der Karte, nummer die Kartennummer und laeuftAb das Verfallsdatum, so wie es im Formular eingegeben wird. Die Eigenschaften $laeuftAbMonat und $laeuftAbJahr speichern das entsprechend in Monat und Jahr zerlegte Datum. $gueltig speichert unabhängig vom Verfallsdatum die Information, ob die Karte gültig ist - das können wir z.B. dazu benutzen, ungültige bzw. gestohlene Karten als solche zu kennzeichnen.

Die Methoden der Klasse kreditKarte

Die meisten Methoden der Klasse kreditKarte erfüllen ähnliche Aufgaben wie die der Klasse spender.

Der Konstruktor wird mit optionalen Parametern aufgerufen, die das Erstellen eines Kreditkartenobjekts ermöglichen. Das ist entweder über einen Identifikations-String (ID) vorgesehen, unter dem die Karte in der Datenbank zu finden sein wird, oder durch Angabe der Daten, die für einen neuen Datenbankeintrag notwendig sind: Kartentyp, Kartennummer, Name des Inhabers und Verfallsdatum. Wenn eine ID angegeben ist, wird eine Methode aufgerufen, die die Kartendaten aus der Datenbank in die Eigenschaften des Objekts laden soll.

In _ _set() überprüfen wir den Inhabernamen, die Kartennummer, den Kartentyp und das Ablaufdatum der Karte, bevor sie in den entsprechenden Eigenschaften gespeichert werden. Hier kommen wieder Methoden der Ursprungsklasse zum Einsatz: namensCheck() zur Überprüfung des Inhabernamens und ungueltigeEingabe() zur Reaktion auf Daten, die nicht dem vorgesehenen Format entsprechen. Werte, die die Prüfung bestehen, speichern wir in entsprechend benannten Feldern ab. Den Kartentyp checken wir mit einem switch-Statement, die Kartennummer mit zwei regulären Ausdrücken.

Das Ablaufdatum wird im Setter in Monat und Jahr zerlegt. Das passiert durch zwei runde Klammern in dem regulären Ausdruck, der das Ablaufdatum testet. Der Teilausdruck, der auf den ersten umklammerten Ausdruck passt, wird als Monat abgespeichert, der zweite Teilausdruck als Jahr. Mit diesen Daten überprüfen wir dann, ob die Karte noch gültig ist. Dazu rufen wir die Methode abgelaufen() auf.

Falls das Ablaufdatum nicht stimmen kann, setzen wir auch die Eigenschaft gueltig auf false. Die Eigenschaft ist allerdings nicht mit konsistent identisch: Eine Karte kann formatgerechte Daten haben und trotzdem ungültig sein, weil sie abgelaufen ist oder weil man sie als gestohlen gemeldet hat.

Die Methode abgelaufen() erklärt die Karte beispielsweise für ungültig, wenn das Ablaufdatum überschritten ist.

Bei der Eigenschaft gueltig gibt es etwas Neues zu lernen: Wir setzen sie mit dem ternären Operator:

case "gueltig":
 
            $this->fGueltig = ($wert) ? true : false;
 
            break;
 

Ein ternäre Operator ist eine Art vereinfachtes if-Statement und macht so ziemlich dasselbe, spart aber jede Menge Schreibarbeit. Er besteht immer aus fünf Teilen: einem Ausdruck, der true oder false zurückliefert, wenn er ausgewertet wird ($wert), einem Fragezeichen und den Werten, die der ternäre Operator annehmen soll, wenn der Ausdruck true bzw. false zurückliefert. Diese beiden Werte werden durch einen Doppelpunkt getrennt. In unserem Fall sind die Alternativen einfach true und false - das verhindert, dass man uns andere Werte unterschieben kann, die vielleicht keinen Sinn ergeben.

Die Methode _ _get() ist auch in kreditKarte etwas einfacher als _ _set(). Wie gehabt, sorgt auch hier ein switch-Statement dafür, dass für jeden Eigenschaftsnamen der richtige Wert zurückgegeben wird.

Beim Auslesen der Eigenschaft gueltig greifen wir unserer Datenbankanbindung etwas vor: Nicht jede Datenbank weiß, wie mit den Werten true und false umzugehen ist. Hier gehen wir mit 1 bzw. 0 auf Nummer sicher. Das entsprechende case-Statement verwendet wiederum den ternären Operator:

case "gueltig":
 
    // Sicherstellen, dass die Karte nicht abgelaufen ist.
 
    $this->abgelaufen();
 
    // Rückgabewert sollte 1 oder 0 sein.
 
    return ($this->fGueltig) ? 1 : 0;                   
 

Am Ende von _ _set() und _ _get() rufen wir wieder ihre entsprechenden Namensvettern in der Ursprungsklasse datenObjekt auf.

Damit verbleibt nur die Methode abgelaufen(). Wie schon erwähnt, erklärt sie die Karte für ungültig, wenn das Ablaufdatum überschritten ist. Dazu fragen wir, ob die gegenwärtige Systemzeit nach dem Ersten des Monats liegt, der dem Ablaufmonat folgt:

Beispiel 5-7
Überprüfen des Ablaufdatums in der Methode abgelaufen() 
function abgelaufen() {
 
    $ablaufDatum = $this->laeuftAbMonat."/1/";
 
    $ablaufDatum .= $this->laeuftAbJahr." +1 month";
 
    $ablaufZeitPunkt = strtotime($ablaufDatum);
 
    if ($ablaufZeitPunkt < time()) {
 
        // Kartendaten mögen konsistent sein, aber die Karte ist abgelaufen.
 
        $this->gueltig = false;
 
        return true;
 
    }
 
    else
 
    {
 
        return false;
 
    }
 
}
 

Die lokale Variable $ablaufDatum wird dazu mit dem Datum des Ersten des Ablaufmonats geladen, und zwar im US-amerikanischen Datumsformat (Monat/Tag/Jahr). Da die Karte erst genau einen Monat nach diesem Datum ungültig wird, addieren wir einen Monat dazu. Dann verwenden wir die eingebaute PHP-Funktion strtotime(), um das Datum in $ablaufDatum in einen Zeitwert umzuwandeln: die Anzahl der Sekunden zwischen dem 1.1.1970 und dem angegebenen Datum. Dieses vergleichen wir dann mit der Anzahl der Sekunden, die seit 1970 und dem Aufruf der Funktion vergangen sind. Wenn dieser von der eingebauten Funktion time() gelieferte Wert größer ist, ist die Karte nicht mehr gültig.

Die Klasse spende

Auf diese Klasse haben Sie sicher gewartet. Wie Sie sehen, sind auch hier gegenüber datenObjekt eine ganze Menge Features hinzugekommen:

Beispiel 5-8
Die Deklaration der Klasse spende 
class spende extends datenObjekt {
 

 
    // Eigenschaften der Klasse spende.
 
    // Eigenschaften, die über _ _set() gesetzt werden:
 
    // - betrag
 
    // - oeffentlich
 
    // - frequenz
 
    // - formularAusgabeZeit
 
    // - abgebucht
 
    // - spender
 
    // - kreditKarte
 
    // Eigenschaften, die nicht über _ _set() gesetzt werden:
 
    var $spenderId;
 
    var $kreditKartenId;
 
    var $datum;
 

 
    // Methoden.
 

 
    // Konstruktormethode - wird immer dann aufgerufen, wenn
 
    // eine neue Instanz der Klasse erzeugt wird.
 
    function spende($spendenId = "",
 
                    $betragNeu = "",
 
                    $oeffentlichNeu = "",
 
                    $frequenzNeu = "",
 
                    $spenderNeu = "",
 
                    $kreditKarteNeu = "",
 
                    $ausgabeZeitNeu = "") {
 
        $this->datenObjekt();
 
        if ($spendenId == "") { // neue Spende
 
            $this->id = uniqid("");
 
            $this->betrag = $betragNeu;
 
            $this->oeffentlich = $oeffentlichNeu;
 
            $this->frequenz = $frequenzNeu;
 
            $this->spender = $spenderNeu;
 
            $this->kreditKarte = $kreditKarteNeu;
 
            $this->datum = time();
 
            $this->formularAusgabeZeit = $ausgabeZeitNeu;
 
        }
 
        else
 
        { // Spende aus der Datenbank lesen.
 
            $this->ladeAusDB($spendenId);
 
        }
 
    }
 

 
    // Diese Setter-Methode überlagert die Setter-Methode
 
    // der übergeordneten Klasse. 
 
    function _ _set($eigenschaftsName,$wert) {            
 
        // Notwendige Checks für die jeweiligen Eigenschaften durchführen. 
 
        switch ($eigenschaftsName) {
 
            case "betrag":
 
                if (preg_match("/^\d*[05]$/", $wert)) {
 
                    // Ist er durch 5 teilbar ...
 
                    if (($wert > 4) && ($wert < 101)) {
 
                        // und innerhalb des richtigen Bereichs?
 
                        $this->fBetrag = $wert;
 
                        return;
 
                    }
 
                }
 
                // Wenn wir bis hierher kommen, ist es kein Betrag.
 
                $this->ungueltigeEingabe("Betrag");
 
                break;
 
            case "oeffentlich":
 
                // Wird über ternären Operator gesetzt.
 
                $this->fOeffentlich = ($wert) ? true : false;
 
                break;
 
            case "frequenz":
 
                // Wert darf nur aus Ziffern bestehen.
 
                if ($this->nurZiffern($wert)) {
 
                    $this->fFrequenz = $wert;
 
                }
 
                else
 
                {   // Nein, d.h., die Daten sind inkonsistent.
 
                    $this->ungueltigeEingabe("Spendenfrequenz");
 
                }
 
                break;
 
            case "formularAusgabeZeit":
 
                // Wert darf nur aus Ziffern bestehen.
 
                if ($this->nurZiffern($wert)) {
 
                    $this->fFormularAusgabeZeit = $wert;
 
                }
 
                else
 
                {   // Nein, d.h., die Daten sind inkonsistent.
 
                    $this->ungueltigeEingabe("Formular: Ausgabezeit");
 
                }
 
                break;
 
            case "abgebucht":
 
                // Wird über ternären Operator gesetzt.
 
                $this->fAbgebucht = ($wert) ? true : false;
 
                break;
 
            case "spender":
 
                // Hier müssen wir auch die $spenderId-Eigenschaft setzen.
 
                $this->fSpender = $wert;
 
                $this->spenderId = $wert->id;
 
                break;
 
            case "kreditKarte":
 
                // Hier müssen wir auch die $kreditKartenId-Eigenschaft setzen.
 
                $this->fKreditKarte = $wert;
 
                $this->kreditKartenId = $wert->id;
 
                break;
 
            default:
 
                parent::_ _set($eigenschaftsName,$wert);
 
        }
 
    }
 

 
    // Diese Getter-Methode überlagert die Getter-Methode
 
    // der übergeordneten Klasse.
 
    function _ _get($eigenschaftsName) {            
 
        switch ($eigenschaftsName) {
 
            case "betrag":
 
                return $this->fBetrag;
 
                break;
 
            case "oeffentlich":
 
                // Wird über ternären Operator gesetzt.
 
                return $this->fOeffentlich ? 1 : 0;
 
                break;
 
            case "frequenz":
 
                return $this->fFrequenz;
 
                break;
 
            case "formularAusgabeZeit":
 
                return $this->fFormularAusgabeZeit;
 
                break;
 
            case "abgebucht":
 
                // Wird über ternären Operator gesetzt.
 
                return $this->fAbgebucht ? 1 : 0;
 
                break;
 
            case "spender":
 
                return $this->fSpender;
 
            case "kreditKarte":
 
                return $this->fKreditKarte;
 
            default:
 
                parent::_ _get($eigenschaftsName);
 
        }
 
    }
 

 
    // Die folgende Methode berechnet die Gesamtsumme 
 
    // aller Spenden bis zum heutigen Datum:
 
    function spendenSumme() {
 
        if ($this->frequenz == 0) {
 
            return $this->betrag;
 
        }
 
        else
 
        {
 
            $tageSeitSpende =
 
                (time() - $this->datum)/86400 + 1;
 
            $zahlungen = ceil($tageSeitSpende / $this->frequenz);
 
            return $zahlungen * $this->betrag;
 
        }
 
    }
 

 
    // Die folgende Methode konvertiert die Spendenfrequenz in einen String:
 
    function frequenzAlsString() {
 
        switch ($this->frequenz) {
 
            case "0": 
 
                return "einmalig"; break;
 
            case "7":
 
                return "w&ouml;chentlich"; break;
 
            case "14":
 
                return "alle zwei Wochen"; break;
 
            case "30":
 
                return "monatlich"; break;
 
            case "90":
 
                return "viertelj&auml;hrlich"; break;
 
            case "365":
 
                return "j&auml;hrlich"; break;
 
            default:
 
                return "alle ".$this->frequenz." Tage";
 
        }
 
    }
 

 
} // Ende der Klasse spende.
 

Die zusätzlichen konventionellen Eigenschaften der Klasse spende sind:

    var $spenderId;
 
    var $kreditKartenId;
 
    var $datum;
 

Die Eigenschaftsvariable $spenderId enthält die ID des zur Spende gehörende Spenderobjekts - natürlich ein Objekt der spender-Klasse, das wir in der intelligenten Eigenschaft spender ablegen. Da die $spenderId-Eigenschaft nur mit Werten versehen wird, die von unseren eigenen Skripte generiert werden, gibt es keinen Grund, sie nur mit Überprüfung durch _ _set() zu setzen. Wir werden ihr in _ _set() aber gleich noch einmal begegnen.

Dasselbe trifft auf die Eigenschaft $kreditKartenId zu, in der wir die ID des Kreditkarten-Objekts speichern. Auch hier haben wir eine intelligente Eigenschaft namens kreditKarte vorgesehen, die Hand in Hand mit der ID-Eigenschaft arbeiten soll.

Die Eigenschaft $datum soll den Zeitstempel des Zeitpunkts enthalten, an dem das Formular beim Server eingereicht wurde. Wir werden sie gleich automatisch im Konstruktor setzen - deshalb besteht auch hier kein Überprüfungsbedarf.

Den Rest unserer Eigenschaftsvariablen wird ebenfalls wieder über _ _set() und _ _get() gesetzt, damit wir sichergehen können, dass sie keine absurden Werte enthalten. Wofür betrag und oeffentlich stehen, können Sie sich sicher denken. Neu hinzugekommen ist frequenz, die eine Zahl in Tagen aufnehmen soll. Sie legt fest, wie oft der Spender spenden will: z.B. alle 7 Tage (wöchentlich), alle 30 Tage (monatlich), alle 183 Tage (halbe Jahre), jedes Jahr (365 Tage) oder einmalig (0 Tage). Sie können diese Werte beispielsweise durch Radio-Buttons oder eine zusätzliche select-Liste in Ihrem Spendenformular abfragen, indem Sie die value-Attribute der verschiedenen Buttons bzw. der Optionen auf die jeweilige Tageszahl setzen. Beispiel:

<input type="radio" name="spendenfrequenz" value="30">monatlich
 

datum gibt den Zeitpunkt an, an dem das Spendenformular vom Server empfangen wurde - diese Eigenschaft werden wir automatisch im Konstruktor setzen. formularAusgabeZeit bekommt den versteckten Zeitstempel des Formulars zugewiesen. Die Eigenschaft abgebucht wird uns später dabei helfen, den Abbuchungsstatus der Spende festzuhalten.

Die Methoden der Klasse spende

In der Konstruktormethode finden wir nicht viel Neues. Beachten Sie, dass für neue Spenden einige der Eigenschaften (spender, kreditKarte und datum) direkt gesetzt werden. Das ist möglich und sicher, weil sie keine ungeprüften Benutzereingaben enthalten. Die Eigenschaft datum wird direkt mit einem Zeitstempel gesetzt.

Nach dem Konstruktor treffen wir auf einen anderen alten Bekannten, die _ _set()-Methode zum Überprüfen und Setzen der intelligenten Eigenschaften. Der Code hier ist mit leichten Änderungen aus spende.php übernommen. Beachten Sie, dass wir jetzt eine positive Überprüfung vornehmen. Wir untersuchen, ob der Betrag eine durch 5 teilbare ganze Zahl ist und, wenn ja, ob er im richtigen Bereich liegt:

Beispiel 5-9
Die Überprüfung des Betrags in der _set()-Methode der Klasse spende 
case "betrag":
 
    if (preg_match("/^\d*[05]$/", $wert)) {
 
        // Ist er durch 5 teilbar
 
        if (($wert > 4) && ($wert < 101)) {
 
            // und innerhalb des richtigen Bereichs?
 
            $this->fBetrag = $wert;
 
            return;
 
        }
 
    }
 
    // Wenn wir bis hierher kommen, ist es kein Betrag.
 
    $this->ungueltigeEingabe("Betrag");
 
    break;
 

Die beiden Überprüfungsmethoden sind im Prinzip äquivalent - hier ist es aber einfacher, eine positive Überprüfung vorzunehmen, da wir ja eine Eigenschaft setzen wollen. Ähnlich verhält es sich bei der Eigenschaft oeffentlichkeit, die den Stand der Checkbox reflektiert. Hier verwenden wir jetzt einen ternären Operator, wie schon für die Eigenschaft gueltig in der Klasse kreditKarte. Beim Setzen von abgebucht gehen wir genauso vor.

Beim Prüfen und Setzen der Spendenfrequenz und der Formularausgabezeit kommt nun endlich die Methode nurZiffern() der Ursprungsklasse zum Einsatz. Wir könnten sie deshalb auch erst in dieser Klasse deklarieren. Ich habe sie trotzdem in datenObjekt belassen - wenn Sie Ihre eigenen Klassen schreiben und dabei datenObjekt als Grundlage verwenden, können Sie diese Methode vielleicht auch gebrauchen.

Die Eigenschaften spender und kreditKarte setzen wir einfach, indem wir die in $wert übergebenene Objektreferenz kopieren. In beiden Fällen setzen wir zusätzlich die konventionelle Eigenschaft spenderId bzw. kreditKartenId, die wir $wert->id entnehmen können. In _ _get() werden dann allerdings nur die Objektreferenzen in den Feldern $fSpender bzw. $fKreditKarte zurückgegeben.

Nach _ _get() finden wir die Methode spendenSumme(), die die Gesamtsumme der Spende bis zum heutigen Datum berechnet und ausgibt:

Beispiel 5-10
Berechnung der gesamten Spendensumme in spende
function spendenSumme() {
 
    if ($this->frequenz == 0) {
 
        return $this->betrag;
 
    }
 
    else
 
    {
 
        $tageSeitSpende = 
 
            (time() - $this->datum)/86400 + 1;
 
        $zahlungen = ceil($tageSeitSpende / $this->frequenz);
 
        return $zahlungen * $this->betrag;
 
    }
 
}
 

Wenn die Häufigkeit null ist, handelt es sich um eine Einmalspende. Wenn nicht, müssen wir die Anzahl der Tage seit Einreichen des Spendenformulars berechnen. Da die Zeiten jeweils nur in Sekunden zur Verfügung stehen, müssen wir die Differenz zwischen Einreichzeit und der momentanen Zeit in Tage konvertieren. Das können wir mit einer Division durch die Anzahl der Sekunden pro Tag (86.400) erreichen. Da die erste Spende schon am ersten Tag abgebucht wird, können wir den ersten Tag gleich mit dazuzählen. Die Anzahl der fälligen Zahlungen ergibt sich, indem wir die Anzahl der Tage seit Einreichen durch das Spendenintervall teilen. Wir multiplizieren diese mit dem jeweiligen Spendenbetrag und geben sie als Rückgabewert aus.

Als letzte Methode in der Klasse spende finden wir die »Bequemlichkeitsmethode« frequenzAlsString(). Diese Methode gibt uns über ein switch-Statement einen String zurück, der die Spendenfrequenz in besser verständliche Worte fasst.

Damit haben wir alle Klassen in spendenKlassen.phpi in ihrer momentanen Ausbaustufe besprochen. Höchste Zeit, dass wir die Theorie beenden und uns ansehen, wie wir sie einsetzen.

Jetzt um Klassen besser:
Die objektorientierte spendenCombo

Das Programmieren mit Includes haben Sie ja schon im letzten Kapitel kennen gelernt, als es um Comboskripte ging. Dabei haben wir Includes dazu benutzt, unser Formular (Eingabeelemente und Formatierung) von unserer »Geschäftslogik« (Prüfung und Verarbeitung der Formulardaten) zu trennen, so gut es eben ging.

Diesen Gedanken der Modularisierung wollen wir hier jetzt konsequent weiterspinnen, indem wir unser »bestes Pferd« im Skriptstall, spendenComboDynamisch.php, mit Hilfe der Klassen in spendenKlassen.phpi etwas umschreiben. Sie finden die neue Version als spendenCombo.php im Web im Beispielverzeichnis dieses Kapitels:

Beispiel 5-11
spendenCombo.php - jetzt objektorientiert 
<?php
 

 
    // Dieses Skript überprüft, ob es Daten vom Spendenformular empfangen hat
 
    // oder ob es direkt angefordert wurde. Wenn $_POST-Daten vorliegen,
 
    // werden die Daten überprüft und dem Benutzer zur Bestätigung ausgegeben.
 

 
    // Die Datei einbeziehen, die die Klassen für das Spendenmanagement enthält.
 
    include("./spendenKlassen.phpi");
 

 
    // Bei inkorrekten Eingaben wird eine vorausgefüllte Version des Spenden-
 
    // formulars zur Korrektur zurückgereicht.
 
    if (sizeof($_POST) == 0) {
 
        include("spendenFormular.php");
 
        exit(); // Skript abbrechen!
 
    }
 

 
    // Daten der Bequemlichkeit wegen aus $_POST auslesen.
 

 
    $spenderName = $_POST["spenderName"];
 
    $adresse = $_POST["adresse"];
 
    $betrag = $_POST["betrag"];
 
    $kartenTyp = $_POST["kartenTyp"];
 
    $kreditKarte = $_POST["kreditKarte"];
 
    $ablaufDatum = $_POST["ablaufDatum"];
 
    $oeffentlich = $_POST["oeffentlich"];
 
    $ausgabeZeit = $_POST["ausgabeZeit"];
 

 
    // Die Überprüfung der Daten wird nun zum großen Teil von den Objekten übernommen, 
 
    // die wir nach den entsprechenden Klassendefinitionen bauen.
 
    $spender = new spender("", $spenderName, $adresse);
 
    $karte = new kreditKarte("",$kreditKarte,$ablaufDatum, $kartenTyp, $spenderName);
 
    $spende = new spende("", $betrag, $oeffentlich, 0, $spender, $karte, $ausgabeZeit);
 

 
    // Bei inkorrekten Eingaben nochmals das Spendenformular anzeigen:
 
    if ((!$spende->konsistent) ||
 
        (!$karte->konsistent) ||
 
        (!$spender->konsistent)) {
 
        $hack = true;
 
        include("spendenFormular.php");
 
        exit(); // Skript abbrechen!
 
    }
 

 
// An diesem Punkt fügen wir später den Code ein, der die
 
// überprüften Daten in die Datenbank schreibt:
 
//
 
//        $spender->speichern();
 
//        $karte->speichern();
 
//        $spende->speichern();
 

 

 
    // Daten an den Benutzer zur Bestätigung ausgeben:
 
    ?>
 
    <html>
 
        <body>
 
            <h1>Liebe(r) <?php echo stripslashes($spender->name); ?></h1>
 
            <p>Haben Sie herzlichen Dank f&uuml;r Ihre Spende &uuml;ber 
 
            <?php echo $spende->betrag; ?>&euro;.
 
            Eine Spendenquittung wird an Ihre Adresse</p>
 
            <p><b><?php
 
                echo stripslashes(preg_replace("/\r?\n/","<br>", $spender->adresse));
 
            ?></b></p>
 
            <p>geschickt.</p>
 
            <p>Die Spende wird von Ihrer 
 
            <b><?php echo $karte->typ; ?></b> Kreditkarte mit Nummer:</p>
 
            <p><b><?php echo $karte->nummer; ?></b> 
 
            g&uuml;ltig bis <b><?php echo $karte->laeuftAb; ?></b>
 
            abgebucht.</p>
 
            <?php
 
            if ($spender->fotoDatei) { ?>
 
                <p>Ihr Foto sehen Sie hier:<br>
 
                <img src="<?php echo "bilder/".$spender->fotoDatei; ?>"></p>
 
            <?php
 
            } 
 
            ?>
 
            <p>Wir nehmen zur Kenntnis, dass wir Ihren Namen 
 
            <?php
 
                if ($spende->oeffentlich == "") {
 
                    echo "nicht";
 
                }
 
            ?>
 
            ver&ouml;ffentlichen d&uuml;rfen.</p>
 
            <p>Sie haben <?php echo (time() - $spende->formularAusgabeZeit); ?>
 
            Sekunden zum Ausf&uuml;llen unseres Formulars gebraucht</p>.
 
        </body>
 
    </html>
 

In den ersten paar Zeilen von spendenCombo.php ändert sich nicht viel, außer dass wir unser Spendenformular jetzt wieder ganz bescheiden spendenFormular.phpi nennen. Diese Datei hat sich übrigens auch ein bisschen verändert, aber dazu kommen wir noch.

Richtig spannend wird es erst mit dem include-Befehl, mit dem wir die spendenKlassen.phpi in unser Skript importieren. Weil es sauberer aussieht, belassen wir den Codeblock, der unsere $_POST-Werte in »normale« Variablen umkopiert. Mit denen geht es dann richtig zur Sache:

$spender = new spender("", $spenderName, $adresse);
 
$karte = new kreditKarte("",$kreditKarte,$ablaufDatum, $kartenTyp, $spenderName);
 
$spende = new spende("", $betrag, $oeffentlich, 0, $spender, $karte, $ausgabeZeit);
 

Mit diesen flotten drei Kommandos haben wir aus unseren Klassen im Nu drei Objekte erzeugt. Links vom Gleichheitszeichen steht die Objektvariable, rechts davon jeweils das Schlüsselwort new, gefolgt von dem Klassennamen (wir könnten hier auch »Konstruktor« sagen) und den Parametern, die wir dem Konstruktor übergeben wollen.

Das ist alles, was wir zur Erzeugung der Objekte brauchen. Die Überprüfung der Eingabedaten nach unseren neuen, strikteren Standards ist dabei eingeschlossen. Keine Spur mehr von den ellenlangen if-Statements und dergleichen. Stattdessen brauchen wir nur noch ein if-Statement, mit dem wir unsere Objekte fragen, ob sie die übergebenen Daten auch gut verdaut haben:

if ((!$spende->konsistent) ||
 
    (!$karte->konsistent) ||
 
    (!$spender->konsistent)) { 
 
    $hack = true;
 
    include("spendenFormular.phpi");
 
    exit(); // Skript abbrechen!
 
}
 

Jedes der drei Objekte kann über seine jeweilige konsistent-Eigenschaft anzeigen, ob es Magenschmerzen wegen der jeweiligen Eingabedaten hat. Über die zwei ODER-Verknüpfungen (||) wird dafür gesorgt, dass das if-Statement auch dann anspricht, wenn nur eine der konsistent-Eigenschaften false ist. Die Variable $hack behalten wir nicht nur aus historischen Gründen bei - sie wird in spendenFormular.phpi dazu verwendet, die Fehlermeldung bei Fehleingaben »hinzuzuschalten«.

Die Ausgabe der Bestätigung an den Benutzer sollten Sie sich gut ansehen - sie zeigt Ihnen, wie Sie die anderen Objekteigenschaften auslesen können. Wie schon bei konsistent verwenden Sie statt $this einfach immer den Namen der entsprechenden Objektvariablen, z.B. $spender. So einfach ist das.

Unser neues Spendenformular

Die neue Version des Spendenformulars finden Sie jetzt in spendenFormular.phpi. Hier haben wir ebenfalls unsere »einfachen« Variablen durch Objekteigenschaften ersetzt:

Beispiel 5-12
Der Code der objektorientierten Version von spendenFormular.phpi 
<html>
 
    <head>
 
        <title>Spenden Sie f&uuml;r bedrohte V&ouml;gel!</title>
 
    </head>
 
    <body>
 
        <form name="spende" action="spendenCombo.php" method="post" 
 
            enctype="multipart/form-data">
 
            <input type="hidden" name="ausgabeZeit" 
 
                value="<?php echo time();?>">
 
            <h1>Donations form</h1>
 
            <?php 
 
                if ($hack) {
 
            ?>
 
                    <p>Leider konnten wir Ihre Spende nicht bearbeiten,  
 
                    weil das Formular nicht korrekt ausgef&uuml;llt war. 
 
                    Bitte korrigieren Sie die folgenden Eingabefelder  
 
                    <b><ul><?php 
 
                        $objekte = array($spender, $karte, $spende);
 
                        foreach ($objekte as $objekt) {
 
                            if (!$objekt->konsistent) {
 
                                echo "<li>".$objekt->problemFeld;
 
                            }
 
                        }
 
                    ?></ul></b> und &uuml;berpr&uuml;fen Sie 
 
                    nochmals Ihre anderen Eingaben, einschlie&szlig;lich der
 
                    Datei f&uuml;r Ihr Foto, falls Sie eine angegeben haben. 
 
            <?php
 
                }
 
                else
 
                {
 
            ?>          
 
                    <p>Wir freuen uns, dass Sie spenden m&ouml;chten! Bitte
 
                    geben Sie Ihren Namen, Ihre Adresse, die H&ouml;he der
 
                    Spende und Ihre Kreditkartendaten ein.            <?php
 
                }
 
            ?>
 
             Dr&uuml;cken Sie dann auf "Spende abschicken!"</p>
 
            <p>
 
            <b>Name:</b> <input type="text" name="spenderName" size="80"
 
                            value="<?php 
 
                                             echo htmlspecialchars(
 
                                                      stripslashes($spender->name)); 

                                                          ?>"></p>
 
            <p><b>Adresse:</b><br> 
 
            <textarea name="adresse" rows="4" cols="40" align="top"><?php 
 
            echo stripslashes($spender->adresse); ?></textarea></p>
 
            <p><b>H&ouml;he der Spende:</b> 
 
            <select name="betrag">
 
                <?php 
 
                    for($i = 5; $i < 101; $i = $i + 5) {
 
                        echo "<option value=\"".$i."\"";
 
                        if ($spende->betrag == $i) {
 
                            echo " selected";
 
                        }
 
                        echo ">".$i."&euro;\n";
 
                    }   
 
                ?>
 
            </select>
 
            &nbsp;
 
            <b>Ihr Foto</b> (optional): <input name="spenderFoto" type="file"></p>
 
            <p>
 
            <p><b>Kreditkartentyp:</b> 
 
            <?php 
 
                $kartenTypen = array("Visa", "Mastercard");
 
                foreach ($kartenTypen as $einKartenTyp) {
 
                   echo "<input type=\"radio\" name=\"kartenTyp\" value=\"";
 
                   echo $einKartenTyp."\" ";
 
                   if ($karte->typ == $einKartenTyp) { echo "checked"; }
 
                   echo ">".$einKartenTyp."&nbsp;";
 
                }
 
            ?>
 
            </p>         
 
            <p><b>Kreditkartennummer:</b> <input type="text" 
 
                name="kreditKarte" size="20" maxlength="20"
 
                value="<?php echo $karte->nummer; ?>"> 
 
            &nbsp;
 
            <b>Verfallsdatum der Kreditkarte:</b> <input type="text" 
 
                name="ablaufDatum" size="4" maxlength="4"
 
                value="<?php echo $karte->laeuftAb; ?>"></p> 
 
            <p>
 
            <b>Klicken Sie hier, wenn wir Ihren Namen 
 
            ver&ouml;ffentlichen d&uuml;rfen:</b>
 
            <input type="checkbox" name="oeffentlich" <?php
 
                if ((!$hack) || ($spende->oeffentlich)) { echo "checked"; }?>> 
 
            <p>
 
            <input type="submit" value="Spende abschicken!">
 
        </form>
 
    </body>
 
</html>
 

Die größte Änderung in unserem Formular finden wir bereits relativ weit oben: Der Code zur Darstellung der Fehlermeldung hat sich ziemlich verändert. Durch die Variable $hack wissen wir, ob es bei den Eingaben Probleme gegeben hat. Wir müssen nun aber jeweils drei Objekte auf mögliche Problemfelder prüfen.

Die elegante Lösung ist, die Objektreferenzen in einem Array zu speichern und dieses Array dann Element für Element zu durchlaufen. Dazu können wir eine foreach-Schleife verwenden, so wie im letzten Kapitel bei der Erzeugung der Kreditkartentyp-Radio-Buttons aus einem Array in spendenformularDynamisch.php. Bei jedem Durchlauf untersuchen wir das Objekt, das in dem entsprechenden Element gespeichert ist. Wenn das Objekt nicht-konsistente Daten meldet, geben wir sein Problemfeld als ein Listenelement einer nicht-nummerierten Liste aus:

<p>Leider konnten wir Ihre Spende nicht bearbeiten,  
 
weil das Formular nicht korrekt ausgef&uuml;llt war. 
 
Bitte korrigieren Sie die folgenden Eingabefelder  
 
<b><ul><?php 
 
    $objekte = array($spender, $karte, $spende);
 
    foreach ($objekte as $objekt) {
 
        if (!$objekt->konsistent) {
 
            echo "<li>".$objekt->problemFeld;
 
        }
 
    }
 
?></ul></b> und &uuml;berpr&uuml;fen Sie 
 
nochmals Ihre anderen Eingaben, einschlie&szlig;lich der
 
Datei f&uuml;r Ihr Foto, falls Sie eine angegeben haben. 
 

Ansonsten hat sich in unserem Formular nicht viel geändert. Beachten Sie, dass wir bei unserer Checkbox hier etwas anders prüfen als vorher, da wir jetzt nicht mehr nur die Variable $hack, sondern zusätzlich die konsistent-Eigenschaft des Objekts $spende verwenden.

Bliebe noch zu erwähnen, was passiert, wenn wir das Formular zum ersten Mal an den Browser ausgeben. Zu diesem Zeitpunkt existiert noch keines der Objekte. Zum Glück verhält sich PHP hier sehr gnädig: Objekte, die es nicht kennt, sind leer und ihre Eigenschaften je nach Kontext false, 0 oder leere Strings. Das kommt uns hier sehr entgegen. Allerdings würde das auch bedeuten, dass eine Abfrage der konsistent-Eigenschaften der nicht vorhandenen Objekte jedes Mal false ergeben würde. Aus diesem - und nur diesem - Grund überlebt die Variable $hack in dieser Datei: Sie muss true sein, damit die Fehlermeldung produziert wird.

Damit haben wir unsere ganze bisherige Site auf die Verwendung von Objekten umgestellt. Wir werden diesen Objekten noch öfter in anderen Skripten begegnen und sie auch noch etwas erweitern. Damit Sie sich einen Überblick über die Klassen verschaffen können, die wir bisher eingeführt haben, sind sie in Tabelle 5-1 noch einmal mit ihren implementierten bzw. vorgesehenen Eigenschaften und Methoden zusammengefasst:

Tabelle 5-1
Übersicht über die bisher eingeführten Klassen
Klasse Eigenschaften Methoden Anmerkung
datenObjekt
id, konsistent,
problemFeld
datenObjekt(),
_ _set(), _ _get(),
ungueltigeEingabe(),
istUniqueId(),
namensCheck(), nurZiffern(),
ladeAusDB(),
speichern()
Ursprungsklasse mit gemeinsamen Methoden und Eigenschaften, wird nur durch abgeleitete Klassen verwendet.
spender
name, adresse, fotoDatei
spender(),
_ _set(), _ _get(),
setzeFoto()
Diese von datenObjekt abgeleitete Klasse erlaubt die Konstruktion von Objekten, die die Daten eines Spenders prüfen, speichern und vergleichen können.
kreditKarte
name, typ, nummer, laeuftAb, laeuftAbMonat, laeuftAbJahr, gueltig
kreditKarte(),
_ _set(), _ _get(),
abgelaufen()
Diese von datenObjekt abgeleitete Klasse erlaubt die Konstruktion von Objekten, die die Daten einer Kreditkarte prüfen, speichern und vergleichen können.
spende
betrag, oeffentlich, frequenz, spender, spenderId, kreditKarte, kreditKartenId, datum, formularAusgabeZeit, abgebucht
spende(),
_ _set(), _ _get(), spendenSumme(),
frequenzAlsString()
Diese von datenObjekt abgeleitete Klasse erlaubt die Konstruktion von Objekten, die die Daten einer Spende prüfen, speichern und vergleichen können.

Im Moment können unsere Objekte und ihre Daten nur während der Ausführung unserer Skripte existieren. Außer dem Benutzer, der die Daten einreicht und die Bestätigung bekommt, weiß niemand etwas von den großzügigen Spenden. Um sie zur weiteren Verarbeitung festzuhalten, brauchen wir eine Datenbank. Im nächsten Kapitel sehen Sie, wie wir eine Datenbank einrichten und Daten in ihr ablegen und aus ihr auslesen können.

Probieren Sie's selbst!

Hier gibt es eine leichte Übung für Sie: In den Übungen von Kapitel 4 haben Sie das Spendenformular vielleicht bereits so erweitert, dass es nach einem Namen für den Kreditkarteninhaber und der Spendenhäufigkeit fragt. Ergänzen Sie nun das Comboskript so, dass es diese Informationen in den Objekten speichert und bei der Bestätigung ausgibt.

Dabei sollten Sie den Benutzer auch warnen, wenn die verwendete Kreditkarte abgelaufen ist. Das sollte aber nicht über die Eigenschaft gueltig des Kartenobjekts geschehen. Diese könnte eines Tages auch auf Grund anderer Informationen false sein - beispielsweise weil die Karte als gestohlen gemeldet ist und wir zum Schein auf die Spende eingehen wollen, damit wir den edlen Spender an die Polizei ausliefern können! Die Lösung finden Sie online in den Dateien spendenKlassenErweitert.phpi, spendenFormularErweitert.phpi und spendenComboErweitert.php für dieses Kapitel.

1Okay, hier bin ich nicht ganz ehrlich: Seit PHP 5 gibt es eine weitere Möglichkeit, Konstruktormethoden zu definieren, und zwar unter dem generischen Namen _ _construct(). Der Übersichtlichkeit halber verwenden wir hier aber die ältere Konvention.

TOC PREV NEXT INDEX

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