Der Artikel Data Driven Websites mit PHP, Teil 2 zeigt, dass es recht schwierig ist, Code zu bauen, der sowohl komfortabel als auch wiederverwendbar ist.
Mit Hilfe von Klassen läßt sich solcher Code so kapseln, dass er vergleichsweise störungsfrei in existierende Projekte eingesetzt werden kann, ohne Gefahr zu laufen, mit bereits benutzten Funktions- und Variablennamen zu kollidieren.
Angenommen, es ist eine Reihe von Funktionen vorhanden, die mit einer Datenbank kommunizieren und diese Funktionen sollen in eine Klasse umgewandelt werden:
$Link_ID = 0; // ID der aktuellen DB-Verbindung
$Query_ID = 0; // ID des aktuellen Abfrageresultates
$Error = 0; // Letzte Datenbank-Fehlermeldung
function connect() { ... }
function query() { ... }
function next_record() { ... }
function num_rows() { ... }
Aus diesen Variablen und Funktionen wird eine Klasse, indem man vor alle verwendeten Variablen das Schlüsselwort var schreibt und indem man alle Variablen und Funktionen mit einem class -Konstrukt umschließt.
class DB_MiniSQL {
var $Link_ID = 0; // ID der aktuellen DB-Verbindung
var $Query_ID = 0; // ID des aktuellen Abfrageresultates
var $Error = 0; // Letzte Datenbank-Fehlermeldung
function connect() { ... }
function query() { ... }
function next_record() { ... }
function num_rows() { ... }
}
Klassen selbst sind nur Baupläne, sie erzeugen keine Variablen und die Funktionen, die in ihnen enthalten sind, lassen sich so nicht verwenden. Mit Hilfe der Anweisung new läßt man den PHP-Interpreter eine Variable, ein Objekt, nach diesem Bauplan bauen.
$db1 = new DB_MiniSQL; // $db1 ist ein Objekt der Klasse DB_MiniSQL $db2 = new DB_MiniSQL; // $db2 ist noch ein Objekt derselben Klasse
Das Objekt $db1 kann man sich wie ein Array mit einer besonderen Syntax vorstellen. Anstatt auf $db1["Link_ID"] und $db1["Error"] zuzugreifen, muss man $db1->Link_ID und $db1->Error verwenden. Auch die Funktionen in einem Objekt lassen sich so aufrufen: $db1->connect() , $db1->query() und so weiter.
Ein beliebter Fehler ist, $db1->Error zu meinen, aber $db1-> $ Error zu schreiben. Das ist falsch: Der vollständige Name der Variablen ist db1->Error , mit einem $ davor, um ihn als Variablennamen zu kennzeichnen.
Innerhalb einer Funktion wie connect() muss auf die Variable Link_ID zugegriffen werden, um das Resultat eines Connect abzuspeichern. In connect() können wir nicht wissen, wie die Funktion nun gerade heißt, also ob ihr Name nun gerade $db1->connect() oder $db2->connect() ist und ob die Link-ID nun in $db1->Link_ID oder in $db2->Link_ID abgespeichert werden muss.
Eigentlich ist das auch egal: Wir wollen ja nur auf unsere eigene Link-ID zugreifen. $this bezeichnet nun genau unser eigenes Objekt, also $db1 innerhalb von $db1 und $db2 innerhalb von $db2 . Man schreibt daher code wie
class DB_MiniSQL {
var $Link_ID = 0;
function connect() {
$this->Link_ID = mysql_connect(...);
...
}
...
}
oder
class DB_MiniSQL {
var $Link_ID = 0;
function query($query) {
// Wenn kein Datenbank-Link vorhanden ist, eines herstellen.
if (!$this->Link_ID)
$this->connect();
...
}
...
}
Häufig braucht man eine Klasse, die sich genauso verhält wie eine Klasse, die man schon hat, aber mit ganz kleinen Änderungen. Mit Hilfe des Schlüsselworts extends kann man sich eine Klasse definieren, die genauso ist wie eine bereits existierende Klasse und braucht dann nur noch das zu notieren, was anders ist.
Die Änderungen können dabei eine bestehende Klasse erweitern, also neue Variablen und Funktionen zu einer Klasse hinzufügen oder bestehende Variablen und Funktionen einer Klasse ersetzen.
Der folgende Beispiel-Code definiert eine Klasse Example_SQL , die sich ganz genauso verhält wie die Klasse DB_Sql in PHPLIB . Die Variablen $Host , $User , $Password und $Database sind jedoch anders belegt als in der originalen Klasse: Wir setzen dort einfach die Informationen ein, die notwendig sind, um unsere Datenbank zu kontaktieren. Außerdem ist die Funktion haltmsg() ersetzt. Die Klasse ruft diese Funktion auf, wenn ein Fehler aufgetreten ist. Wir ersetzen diese Funktion durch eine eigene Version, sodass wir Fehlermeldungen mit den Informationen drucken können, die der Anwender benötigt.
class Example_Sql extends DB_Sql {
var $Host = "database.netuse.de";
var $User = "kris";
var $Password = "xyzzy";
var $Database = "example_database";
function haltmsg($msg) {
?>
Es ist ein Datenbankfehler aufgetreten. Die Bearbeitung
Ihrer Eingaben wurde abgebrochen. Bitte informieren Sie
<a href="php/php-faq/static/mailto%3Awebmaster%40example.kunde.de.html">den Webmaster</a>
von diesem Problem.<p>
<?php
printf("Die Fehlermeldung der Datenbank war: %s\n", $msg);
}
}
Man kann die Klasse Example_SQL nicht verstehen, ohne die originale Klasse DB_SQL zu kennen. Die Klasse Example_SQL hat alle Eigenschaften und Funktionen, die DB_SQL auch hat. Erzeugt man also ein $db = new Example_SQL , dann kann man $db->query(...) , $db->next_record() und so weiter aufrufen, als ob man es mit einer Klasse DB_SQL zu tun hätte.
Die Klasse zeigt nur in folgenden Punkten abweichendes Verhalten: Sie druckt ihre Fehlermeldungen in deutsch und enthält anwendungsspezifische Kontaktinformationen und sie kontaktiert anders als die Originalklasse defaultmäßig die Datenbank example_database auf dem Host database.netuse.de mit dem angegebenen Usernamen und Passwort.
Ein Konstruktur ist eine gewöhnliche Funktion einer Klasse. Sie unterscheidet sich von anderen Funktionen derselben Klasse dadurch, dass sie beim Erzeugen der Klasse mit new automatisch aufgerufen wird. In PHP muss ein Konstruktor unglücklicherweise genauso heißen wie die Klasse selbst.
Ein Konstruktor kann optionale Parameter mitgegeben bekommen. Er kann niemals ein Funktionsergebnis liefern.
Man verwendet Konstruktoren oft, um die Variablen eines Objektes zu initialisieren. Die Klasse Menu in PHPLIB verwendet beispielweise einen Konstruktor, um eine Menüstruktur zu initialisieren.
class Menu {
function Menu() { // wird automatisch aufgerufen
$this->setup();
}
function setup() { // Initialisierung...
reset($this->urlmap);
while(...) {
...;
}
}
}
In PHP3 wurde bei abgeleiteten Klassen der Konstruktor der Basisklasse nicht automatisch aufgerufen. Man musste stattdessen Code wie diesen schreiben:
class My_Menu extends Menu {
var $urlmap = array(
// meine eigenen Menüpunkte hier
);
function My_Menu() { // wird automatisch aufgerufen
$this->setup(); // Aufruf der Initialisierung
}
}
Unter Polymorphie versteht man das Verhalten von objektorientierten Sprachen, die Signatur einer Funktion als Bestandteil des Funktionsnamens bei einem Aufruf zu betrachten. Die Signatur einer Funktion sind der Returntyp und die Parametertypen einer Funktion. In einer Sprache mit Polymorphie würde Code wie der folgende funktionieren:
# Funktion f mit Integer-Resultat und Integer-Parametern
function int f(int $a, int $b) {
return $a*$b;
}
# Funktion f mit Array-Resultat und Array-Parametern
function array f(array $a, array $b) {
$r = array();
$l = count($a);
for ($i=0; $i<$l; $i++)
$r[] = $a[$i] * $b[$i];
return $r;
}
# Definition von Integer-Parametern
$xi = 3;
$yi = 4;
# Aufruf der Integer-Funktion f.
$zi = f($xi, $yi);
# Definition von Array-Parametern
$xa = array(2, 3, 4);
$ya = array(4, 3, 2);
# Aufruf der Array-Funktion f.
$za = f($xa, $ya);
Dieser Code definiert zwei verschiedene Funktionen, die intern als int_f_int_int() und array_f_array_array() bezeichnet werden können, die im Code aber beide ununterscheidbar f() heißen. Er ruft dann die Funktion f() einmal mit Integer-Parametern und Array-Paramerern auf. Die Sprache ist aufgrund der Polymorphie in der Lage, diese beiden f() zu unterscheiden und korrekt die Funktion int_f_int_int() oder array_f_array_array() aufzurufen.
PHP unterstützt keine Polymorphie und kann dies schon deswegen nicht tun, weil die Return- und Parametertypen einer Funktion nicht deklariert werden müssen. Stattdessen muss man manuell mit den Typfunktionen wie folgt codieren:
function f($a, $b) {
# Wandle $a in ein Array um, wenn es das nicht ist.
if (!is_array($a))
$a = array($a);
# Ebenso $b.
if (!is_array($b))
$b = array($b);
# Normaler Code.
$r = array();
$l = count($a);
for ($i=0; $i<$l; $i++)
$r[] = $a[$i] * $b[$i];
if (count($r) == 1)
# Skalar zurückgeben
return $r[0];
else
# Array zurückgeben
return $r;
}
Metainformationen über eine Klasse oder ein Objekt sind alle Informationen, die man über diese Klasse oder eine Instanz dieser Klasse (ein Objekt) bekommen kann. Sie umfassen den Namen der Klasse eines Objektes und die Namen aller Oberklassen dieser Klasse, die Namen und Typen aller Instanzvariablen des Objektes und die Namen, Returntypen sowie Parametertypen aller Funktionen eines Objektes.
Man kann folgende Metadaten über Klassen bzw. Objekte bestimmen:
class_exists() zur Bestimmung des Vorhandenseins einer Klassendefinition
get_class() zur Bestimmung der Klasse eines Objektes
get_parent_class() zur Bestimmung der Oberklasse eines Objektes
method_exists() zur Bestimmung des Vorhandenseins einer Methode in einem Objekt
get_class_methods() zur Ermittlung aller Methoden einer Klasse
get_class_vars() zur Ermittlung der Standard-Elemente einer Klasse (die mit var deklariert und für die ein Wert definiert wurde)
get_object_vars() zur Bestimmung der Elemente eines Objektes
is_subclass_of() zur Feststellung, ob eine gegebene Klasse eine Unterklasse einer anderen Klasse ist
Die Antwort findest Du jetzt unter " sessions-objekte " im Kapitel " version4_session ".
Der Programmierer hat mit new MyClass() ein neues Objekt erzeugt, und möchte nun innerhalb der Klasse MyClass wissen, in welcher Variablen das Objekt abgelegt wurde.
Innerhalb des Objekts kann man hier stets über die Pseudovariable $this auf sich selbst zugreifen (siehe " klassen-this ").
Einen konkreten Variablennamen zu nennen, in der das Objekt abgelegt wurde, ist jedoch nicht möglich, und aus OOP-Sicht auch überhaupt nicht notwendig.
PHP kann beliebig viele Referenzen auf dasselbe Objekt verwalten. Ein Objekt kann damit über mehrere Variablen erreichbar sein oder sogar anonym sein. Beispiel:
$a =& new MyClass(); $b =& $a;
Wäre der Name des Objekts jetzt $a oder $b ?