Unterabschnitte


Objektorientierung in PHP

PHP, ursprünglich eine prozedurale Skriptsprache, hat erst mit Version 4 brauchbare objektorientierte Züge angenommen. In diesem Kapitel will ich versuchen zu zeigen, wie man objektorientiert in PHP programmiert und auch ein paar Beispiele geben, um zu sehen, wie man von der Problemstellung zur OO-Umsetzung kommt und auch, daß sich das bißchen Mehraufwand wirklich lohnt (Stichwort Übersichtlichkeit/Wiederverwendbarkeit). Das folgende Kapitel gilt der Einfachheit halber nur für PHP 4.06 aufwärts, exklusive PHP 5.

Klassen in PHP

Klassen beginnen in PHP mit dem Schlüsselwort class, gefolgt von einem Bezeichner, dem Klassennamen. Wie bei Funktionen wird der Klassenrumpf von geschweiften Klammern umschlossen. Alle Attribute bzw. Klassen-/Instanzvariablen sowie Methoden (einschließlich der Konstruktoren) müssen sich in diesem Block befinden, um als zur Klasse gehörig erkannt zu werden[*].

Zu beachten ist, daß der Name ,,stdClass`` in PHP4 reserviert ist, ebenso wie alle Methodennamen, die mit zwei Underscores (z.B. __sleep, __wakeup) beginnen.

Beispiel:

class DSP_CR {
  function Auto() {
    ...
  }

  ...
}

Konstruktoren

Konstruktoren in PHP heißen grundsätzlich genau so wie die Klasse, zu der sie gehören. Anders als etwa in Java kann es in PHP immer nur genau einen Konstruktor pro Klasse geben. Durch die Eigenschaft von PHP-Funktionen, Parameter im Funktionskopf auf Defaultwerte setzen zu können, ergibt sich jedoch die Möglichkeit, durch if-Abfragen oder switch-Blöcke die meisten Konstellationen von Parameterübergaben abzudecken.

Eine Besonderheit beim Umgang mit Konstruktoren ist schließlich noch der implizite Aufruf des Konstruktors der Basisklasse in dem Fall, daß eine Klasse keinen Konstruktor besitzt. Die Unfähigkeit von PHP3, dies zu meistern, ist auch der Grund, warum nach Möglichkeit PHP4 eingesetzt werden sollte.

Vererbung in PHP

Vererbung wird mittels des Schlüsselwortes extends signalisiert, dem der Name der Klasse, von der geerbt werden soll, folgen muß. Der gesamte Vererbungsausdruck ist jedoch optional.

Beispiel:

class Auto {
  ...
}

class DSP_CR extends Auto {
  ...
}


Attribute in PHP

Attribute bzw. Klassen-/Instanzvariablen werden in PHP mit dem Schlüsselwort var definiert und können optional mit beliebigen statischen Werten initialisiert werden. Funktionsaufrufe, die Verwendung des Punktoperators oder Variablenzuweisungen sind bei ihrer Definition nicht erlaubt, hierfür sollte der Konstruktor verwendet werden. Die Wertzuweisung von Attributen direkt bei ihrer Deklaration ist allerdings nicht empfohlen[*]; hierfür sollte genauso der Konstruktor verwendet werden wie für Konstanten, die in PHP ja mittels der define()-Funktion (8.2.7) deklariert werden.

Beispiel:

class Auto {
  var $baujahr;
  var $farbe;
  ...
}

class DSP_CR extends Auto {
  // hier in Ermangelung von Methoden noch
  // nicht über den Konstruktor gesetzt
  var $farbe = "metallic";
  ...
}

Zur Referenzierung von Attributen mehr weiter unten (20.2.1).

Methoden in PHP

Da Methoden nichts anderes sind als Funktionen innerhalb einer Klasse, werden diese auch genau wie Funktionen definiert. Sie können also Parameter unterschiedlicher Anzahl übergeben bekommen und Werte mittels return zurückliefern.

Beispiel:

class Auto {
  var $baujahr;
  var $farbe;

  function Auto() {
    ...
  }
  ...
}

class DSP_CR extends Auto {
  // Da DSP_CR von Auto erbt, müssen hier keine Variablen
  // definiert werden. Die geerbten Attribute (und auch
  // Methoden) der Basisklasse kann man natürlich nutzen.

  function DSP_CR($farbe) {
    if (empty($farbe))
      $this->farbe = "metallic";
    else
      $this->farbe = $farbe;
  }

  function setzeFarbe($farbe) {
    ...
  }
  ...
}

Zur Referenzierung von Methoden mehr weiter unten (20.2.1).


Klassen dokumentieren

Eine äußerst sinnvolle Methode, Klassen zu dokumentieren und zu kommentieren stellt PHPDoc dar (14). Hierbei wird die Klasse selbst ebenso berücksichtigt wie Konstruktoren, Attribute und Methoden samt ihrer spezifischen Eigenschaften (Copyright, Parameter, Rückgabewerte; ggf. sogar Sichtbarkeit). Mehr dazu im angegebenen Kapitel.


Objekte und Referenzierung in PHP

Wie wir bereits aus dem Kapitel ,,Bedeutung von Objektorientierung`` (18) wissen, sind Objekte konkrete Exemplare einer Klasse. In PHP definiert man Objekte genau wie Variablen mit dem Dollarzeichen. Die Erzeugung eines Objektes geschieht grundsätzlich außerhalb der Klasse, zu der es gehört (außer bei rekursiven Definitionen). Trotzdem kann eine Klasse natürlich beliebige Objekte anderer Klassen erzeugen und auch als eigene Attribute verwalten.

Folgende Syntax, in der das Schlüsselwort new neu eingeführt wird, erzeugt in PHP ein Objekt:

$meinWagen = new DSP_CR();

Das Objekt meinWagen wird hierbei von der Klasse DSP_CR erzeugt, die dazu ihren parameterlosen Konstruktor benutzt.[*] Der Quelltext der Klasse muß natürlich bereits vom PHP-Parser gelesen worden sein, bevor eine solche Zuweisung erfolgen kann. Üblicherweise lagert man Klassendefinitionen in Include-Dateien aus, z.B. nach dem Schema <KlassenName>.inc, und importiert sie mit include() oder require().

Wird anstelle des parameterlosen Konstruktor einer verwendet, der Parameter erwartet, erfolgt die Objektinitialisierung ganz analog, indem man wie bei Funktionsaufrufen die Parameter in entsprechender Reihenfolge innerhalb der runden Klammern angibt.

Auf das so erzeugte Objekt kann nun so lange zugegriffen werden, bis es explizit auf NULL gesetzt wird oder das Skript beendet wird. Allgemein kann man mit Objektvariablen übrigens ganz analog zu normalen Primitivtyp-Variablen Objekt-Zuweisungen durchführen. Folgender Code ist somit gültig:

$meinWagen = new DSP_CR();
$meinZweitwagen = new DSP_CR();
$meinWagen = $meinZweitwagen;

Achtung: Bis vor der Zuweisung waren meinWagen und meinZweitwagen noch völlig unterschiedliche Objekte! Danach enthalten die beiden Objektvariablen zwar auch noch unterschiedliche Referenzen (zeigen also nicht auf das gleiche Objekt), aber der Inhalt der beiden Objekte ist nun so lange gleich, bis eines der Objekte geändert wird.[*]

Innerhalb einer Klasse gehören die Attribute und Methoden der Klasse immer dem gerade zu betrachtenden Objekt, das wie gesagt nur außerhalb der Klasse existiert. Da die Klasse quasi den ,,Plan`` für ein Objekt darstellt, liegt der Gedanke nahe, daß dieser Plan in dem Moment, wo man ein spezielles Objekt betrachtet, die Eigenschaften des Objekts annimmt. Wenn also die Klasse eigentlich nur der Bauplan für Objekte ist, so verwandelt sie sich doch in dem Moment, wo man ein einzelnes Objekt betrachtet, in das Objekt selbst! Dadurch hat man die Möglichkeit, ein Objekt abhängig von seinen ganz ,,persönlichen`` Eigenschaften zu verändern, ohne es beim Erstellen der Klasse schon zu kennen! :-)


Referenzierung

Um eine Methode eines Objekts bzw. einer Klasse aufzurufen, muß man quasi den ,,Umweg`` über das Objekt bzw. die Klasse gehen, denn PHP muß schließlich wissen, wessen Methode aufgerufen werden soll (unterschiedliche Klassen können natürlich Methoden mit genau demselben Namen definieren!).

Will man also Attribute oder Methoden referenzieren, beginnt man mit dem für PHP typischen Dollarzeichen, gefolgt vom Namen des zu referenzierenden Objektes, einem Strichpfeil (->) und dem Namen des Attributs bzw. der Methode[*]. Will man innerhalb einer Klasse auf die Attribute oder Methoden derselben Klasse zugreifen (die wie gesagt in dem Moment, wo man ein Objekt betrachtet, das Objekt selbst darstellt), muß man das Schlüsselwort this anstelle des Objektnamens verwenden.[*]

Eine kleine Anmerkung noch: Wie vielleicht bekannt sein dürfte, werden in in doppelten Anführungsstrichen geschriebenen Strings Variablen ersetzt. Das gilt aber nur für solche Variablen, deren Namen nur aus Buchstaben bestehen. Insbesondere werden Referenzen nicht erkannt, da sie notwendigerweise den Strichpfeil enthalten. Hier ist also ein Unterbrechen des Strings unter Zuhilfenahme des Punkt-Stringoperators geboten.


Kopier- vs. Referenzsemantik

Beim Aufruf einer Methode kann man dieser - vorausgesetzt, diese erlaubt das - Parameter übergeben. Ist einer dieser Parameter ein Objekt, genauer: eine Referenz auf ein Objekt, so findet in PHP bei der Übergabe dasselbe Vorgehen statt wie bei gewöhnlichen Primitivtypen (Integer, Boolean, String): Es wird eine Kopie des ursprünglichen Datums, bei Objekten also eine Kopie der Referenz, erzeugt und übergeben. Da eine Referenz auf ein Objekt ja immer nur das Objekt bezeichnet, es aber nicht selbst darstellt, kann folglich keine Methode das ursprüngliche Objekt (z.B. mit NULL) überschreiben. Auch in Java, wo bei Objekten statt der Kopiersemantik die sog. Referenzsemantik zum Einsatz kommt[*], funktioniert dies nicht. Der Unterschied zwischen Java und PHP in der Behandlung von Objekten kommt erst dann zum Tragen, wenn versucht wird, den Wert eines Objekt-Attributes zu ändern. Während Java innerhalb einer aufgerufenen Methode nämlich tatsächlich das Attribut sowohl der übergebenen als auch der ursprünglichen Referenz auf das Objekt ändert, erfolgt dies bei PHP standardmäßig nur auf der Referenzkopie, die lokal in der Methode sichtbar ist.

Grundsätzlich ist es natürlich immer möglich, mittels einer return-Anweisung die lokale Referenz zurückzugeben und der ursprünglichen Objektvariable zuzuweisen.


Ausweg aus dem Referenzdilemma

Wie also kann man sicherstellen, daß eine Referenz auch als solche behandelt wird? In PHP gibt es dafür spezielle Syntax, bei der das Kaufmanns-Und den Referenz-Charakter einer Zuweisung oder einer Variablen-Betrachtung anzeigt:

$a = &$b;
$c =& $a;
$c = 42;

Die beiden obigen Zuweisung bewirken gleichermaßen, daß jeweils beide Variablen nicht nur denselben Wert haben, sondern auch eine Wertänderung einer der Variablen automatisch die exakt selbe Wertänderung der anderen Variablen bewirkt. Die letzte Anweisung bewirkt folglich, daß alle drei Variablen den Wert 42 annehmen.

Auch bei Funktions- und Methodenaufrufen wird normalerweise Kopiersemantik angewandt. Um explizit Referenzsemantik zu fordern, stellt man hierbei bei einer Funktions-/Methodendefinition einer zu referenzierenden Parameter-Variablen ein Kaufmanns-Und voran:

function Plus($var) { $var++; }
function Minus(&$var) { $var--; }
$var = 42;
Plus($var);
Minus($var);
echo $var;

Die Ausgabe obigen Codes ist natürlich 41, da nur die Funktion Minus den Wert der Variablen $var ändert. In Java wäre das nicht so offensichtlich, da hier u.U. eine Klassenvariable namens var existieren könnte, die implizit gemeint sein könnte. In PHP müßte man dann aber $this->var schreiben.

Unter 20.7.2 findet sich noch eine Übung zu komplexeren Anwendungen der Referenzsemantik.


foreach mit Objektreferenzen

Wie bereits im foreach-Kapitel 8.2.19 beschrieben, benutzt diese Schleifenvariante[*] stets Kopien der Arrayinhalte. Hat man nun ein Array von Objektreferenzen, kann dies leicht zu unerwünschten Effekten führen. Deshalb macht es hier Sinn, nur den jeweiligen Array-Schlüssel in Erfahrung zu bringen und diesen zu nutzen, um auf das Original-Objekt zuzugreifen:
$car = new Auto();
$dsp = new DSP_CR();
$my_cars = array($car, $dsp);

// mit Kopiersemantik
foreach ($my_cars as $object_copy)
  echo $object_copy->farbe."\n";

// Referenzsemantik I
foreach ($my_cars as $key=>$object)
  echo $my_cars[$key]->farbe."\n";

// Referenzsemantik II
foreach (array_keys($my_cars) as $key)
  echo $my_cars[$key]->farbe."\n";

In diesem Beispiel habe ich direkt auf die Klassenvariable $farbe zugegriffen, weil die Klasse Auto außer dem Konstruktor keine bekannten Methoden hat. Das ist in PHP4 auch gar kein Problem; in PHP5 wird das je nach Sichtbarkeitsdefinition aber nicht mehr möglich sein. Außerdem sollte man sich grundsätzlich angewöhnen, auf Attribute nur per Getter- und Settermethoden zuzugreifen, da in diesen dann (ggf. auch erst nachträglich) geeignete Plausibilitätstests gemacht und diese bei Bedarf in erbenden Klassen überschrieben werden können.


Methoden-Aufrufe


static

Manchmal ist es nötig, eine Methode einer Klasse aufzurufen, ohne ein Objekt derselben erzeugt zu haben. In diesem Fall kann man einen sogenannten ,,static``-Aufruf machen. Zu beachten ist dabei, daß eine so aufgerufene ,,Klassenmethode`` nirgends auf die Attribute der Klasse, in der sie sich befindet, zugreifen darf, da diese ja nur im Kontext einer Instanz (Objekt) vorhanden sind. Lokale und globale Variablen dürfen dagegen natürlich benutzt werden.

Ein primitives Beispiel:

class Auto {
...
  function Erfinder() {
    return "Etienne Lenoir, Carl Benz";
  }
...
}
echo Auto::Erfinder();


parent

Im Falle von Zugriffen auf geerbte, aber überschriebene Methoden sollte man statt des Klassennamens das Schlüsselwort parent benutzen. Dadurch taucht der Name der beerbten Klasse nur hinter extends auf, was das nachträgliche Vornehmen von Änderungen z.T. stark erleichtert. Außerdem wird dadurch auch der Unterschied zwischen ,,static`` (kein Objekt vorhanden) und normalem Instanz-Methodenaufruf deutlich.

Beispiel: Angenommen, wir hätten in der Klasse DSP_CR die Methode lenke(drehung) der Klasse Auto überschrieben, von der DSP_CR erbt. Will man nun innerhalb von DSP_CR auf die gleichnamige Methode der Basisklasse Auto zugreifen, kommt folgende Syntax zum Einsatz:

...
  function lenke($drehung) {
    // Servo aktivieren
    $drehung = $this->servo($drehung);
    // Methode der Basisklasse aufrufen
    parent::lenke($drehung);
  }
...

Das fertige Beispiel

/**
 * Auto Klasse
 *
 * @author  DSP
 * @version 1.0
 */
class Auto {

  /**
   * Baujahr
   * @type int
   */
  var $baujahr;

  /**
   * Farbe
   * @type string
   */
  var $farbe;

  /**
   * Konstruktor
   * @param     $farbe  gewünschte Farbe
   */
  function Auto($farbe) {
    $this->farbe = $farbe;
    $this->baujahr = date();
  }

  /**
   * Ändert die Wagenfarbe
   * @param $farbe      gewünschte Farbe
   */
  function setzeFarbe($farbe) {
    $this->farbe = $farbe;
  }

  /**
   * Welches Baujahr?
   *
   * @return Das Baujahr
   * @returns string
   */
  function Baujahr() {
    return $this->baujahr;
  }
}

/**
 * DSP CR Klasse
 * Konstruktor der Basisklasse wird implizit aufgerufen
 *
 * @author  DSP
 * @version 1.0
 */
class DSP_CR extends Auto {

  /**
   * Konstruktor
   * eigentlich wäre gar keiner notwendig,
   * aber wir wollen ja die Farbe initialisieren...
   */
  function DSP_CR($farbe) {
    if (empty($farbe))
      $farbe = "metallic";

    parent::Auto($farbe);
  }
}

Alternativ zum letzten Codeteil, in dem der Konstruktor der Basisklasse aufgerufen wird, kann man auch folgende Syntax verwenden, die unabhängig vom Namen der Basisklasse ist - in Java würde man das übrigens mit einem einfachen super(parameter) machen.

class A extends B {
  function A($param) {
    $parent = get_parent_class($this);
    $this->$parent($param);
  }
}


Ausblick: PHP5

Mit PHP 5 bricht ein neues Zeitalter für die Skriptsprache an. Endlich halten die zentralen Bestandteile der OOP Einzug und ermöglichen es damit, auch in dieser Sprache tatsächlich objektorientiert zu programmieren. Natürlich ist die neue Zend-Engine auch um einiges schneller, aber ihr größter Vorteil ist sicherlich, daß sie Objekte grundsätzlich mit Referenz-Semantik behandelt[*]statt wie bisher standardmäßig mit Kopiersemantik (wie es bei Primitivtypen wie integer auch weiterhin der Fall ist). Eine echte Kopie stellt man dann mittels der nun jeder Klasse inhärent zugehörigen ,,magic`` Methode __clone her, also z.B. $myObject->__clone(). Diese neue Spezialmethode ermöglicht aber z.B. auch das gleichzeitige, teilweise Modifizieren der Kopie.

Neu ist auch die Unterstützung eines der zentralen Grundsätze der OOP: Sichtbarkeit. Mit PHP5 kann man Attribute und Methoden als public, private oder protected deklarieren und damit einschränken, wer darauf von wo zugreifen darf. Wem das Java vorkommt, dem werden auch static und final sowie const etwas sagen: Ersteres ist vom Prinzip her weiter oben beschrieben (20.3.1, im Unterschied dazu wird einfach ein neues Schlüsselwort eingeführt, das bewirkt, daß $this nicht verwendet werden darf); final beschreibt nicht in erbenden Klassen überschreibbare Methoden bzw. Attribute und const konnte man bisher mittels define() simulieren: konstante, also nach dem Setzen nicht mehr veränderbare Instanzvariablen.

Konstruktoren heißen jetzt standardmäßig __construct, womit der Konstruktor der Basisklasse mittels parent::__construct() eindeutig bestimmt ist. Analog dazu heißen die neu eingeführten Destruktoren, die immer dann automatisch aufgerufen werden, wenn die letzte Referenz auf ein Objekt gelöscht wird, __destruct. Mit ihnen kann man z.B. Debuginformationen sammeln oder Datenbankverbindungen sauber beenden.

Auch von Java bekannt sind abstrakte Klassen und Interfaces, also einerseits nichtinstanzierbare Klassen mit Methoden, die von erbenden Klassen überschrieben werden müssen und andererseits Schnittstellenbeschreibungen, die definieren, welche Funktionalität (Methoden) eine Klasse, die ein bestimmtes Interface implementiert[*], bieten muß.

Schließlich gibt es nun auch ein ausgeklügeltes System zum Abfangen von Exceptions (Ausnahmen, das sind behandelbare Laufzeitfehler im Unterschied zu nicht abfangbaren, z.B. dem Absturz der PHP-Engine). Wen wundert's noch, daß sich dieses try-catch nennt?[*]

Wem PHPDoc nicht genug ist, der kann nun auch ganz explizit die erwarteten Klassenzugehörigkeiten vor die Variablen im Parameterteil von Methodendeklarationen schreiben, also z.B.:

...
  function setMetallic(Auto $car) {
    $car->setzeFarbe("metallic");
  }
...

Diese Methode würde also alle Objekte akzeptieren, die von Auto oder einer davon erbenden Klasse (z.B. DSP_CR) instanziert worden sind. Falls ein Objekt von anderem Typ oder gar ein Primitivtyp übergeben wird, wird eine Fehlermeldung ausgegeben.

Nett ist auch, daß nun von Methoden zurückgelieferte Objekte direkt angesprochen, sprich dereferenziert, werden können. In PHP4 muß der Rückgabewert eines Funktionsaufrufs immer erst in einer (Objekt-) Variablen abgespeichert werden, auf die man dann weitere Aufrufe anwenden kann. Beispiel für die neue Freiheit:

...
  class MyTest {
    var $test;
    function __construct() {
      $this->test = "42 is the answer.";
    }
    function print() {
      echo $this->test;
    }
  }
  MyTest()->print();
...

Wer schon einmal herausfinden wollte, von welcher Klasse ein Objekt ist (zur Laufzeit, sonst ist das ja trivial), wird sicherlich is_a und Konsorten bemüht haben. Mit PHP5 gibt es nun ganz analog zu Java die Infix-Notation instanceof, die dasselbe ermöglicht (im Gegensatz zu is_a wird der Klassenname aber nicht als PHP-String aufgefaßt, sprich gequotet).

Die neue __autoload() Funktion erleichtert das Einbinden von Klassen, indem sie immer dann automatisch aufgerufen wird, wenn eine Klasse nicht gefunden wird. Mit ihrer Hilfe kann man z.B. vom Namen der Klasse abhängig verschiedene includes definieren.

Ganz besonders schick ist die extra geschaffene Möglichkeit, Methodenaufrufe sowie das Setzen und Abfragen von Variablen zu überschreiben (mittels Methoden __call, __get und __set). Die Setter-Funktionen werden dabei konsequenterweise nicht nur bei Zuweisungen, sondern auch auch beim Inkrementieren und Dekrementieren aufgerufen. Mit diesen Eigenschaften eignen sich diese Funktionen natürlich besonders gut zum sauberen Debuggen.

Bleibt noch anzumerken, daß all dies rückwärtskompatibel implementiert wurde, d.h. PHP4-Skripts sollten mit evtl. leichten Anpassungen auch unter PHP5 laufen. Das ist wohl der Punkt, wo sich die Geister scheiden und Lager bilden - OOP-pur-Verfechter auf der einen und PHP3-Hacker auf der anderen Seite ...

Zu den weiteren Neuerungen in PHP5 zählen die rundum erneuerte XML-Unterstützung (libxml2-basiert), SQLite-Integration (dafür ist MySQL-Support nicht mehr direkt eingebaut) sowie verbesserte Handhabung von Streams[*].

Wer sich selbst ein Bild machen will, sollte sich mal http://www.php.net/zend-engine-2.php ansehen.

Konzeptionelle Beispiele

Für Neulinge der Objektorientierung stellt sich anfangs oft die Frage, wie man eine Problemstellung am besten abstrahiert und ,,objektorientiert`` denkt. Eine generell optimale Lösung gibt es wie immer nicht, aber vielleicht helfen dir ja die folgenden Beispiele aus meiner Programmiererfahrung.

Zuerst jedoch die Methode, die Christoph bevorzugt:
Man abstrahiert von der Problemstellung soweit, daß man Subjekte, also Teile des Ganzen mit individuellen Eigenschaften, identifizieren kann. In der Programmierpraxis sind das z.B. eigenständige Prozesse wie Datei-Ein-/Ausgabe oder die jeweils zentrale Informationseinheit, deren Daten z.B. in einer Datenbank festgehalten werden[*]. Hat man diese Abstraktion erst einmal geschafft, fällt es leicht, diese Subjekte mit Objekten zu assoziieren, die zu einer zu erstellenden Klasse gehören. Die Eigenschaften des Subjekts setzt man der OO-Logik folgend in Form von Attributen (Klassenvariablen) um. Schließlich muß man sich noch über die Schnittstelle Gedanken machen - i.A. sind Konstruktor, Lese- und Schreibzugriff für die Eigenschaften und einige zusätzliche Funktionen nötig, die allesamt als Methoden der Klasse implementiert werden.


Und nun mein Ansatz:
Als erstes sollte man das Problem auf die zentrale Funktionalität reduzieren, denn diese bildet i.A. die Schnittstelle der späteren Klasse, also die benötigten Funktionen. Darüber hinaus werden natürlich meist noch weitere Funktionen benötigt, diese sind aber intern und könnten daher ,private` bzw. ,protected` deklariert werden.

Zu beachten ist auch, daß man in erster Linie die Funktionalität in eine Klasse steckt, die gemeinsame Daten (die späteren Attribute) verwendet. Es folgen einige Beispiele - wer noch Anschauliches hat, kann diese gerne an Christoph schicken, der sich mit Objektorientierung in PHP mindestens so gut auskennt wie ich. ;-)


Tabelle 20.1: IMAP-Webmail-System
Gemeinsame Daten Zentrale Funktionalität
  • IMAP-Session/Mailbox
  • ID der aktuellen Nachricht
  • String mit Meldungen
  • Sortierungsdaten
  • Anzahl Nachrichten in der Mailbox

  • Mailbox-Übersicht ausgeben
    • einzelne Daten aus den Mail-Headern lesen
    • Sortierung ändern
  • einzelne Mail lesen
    • einzelne Daten aus den Mail-Headern lesen
    • Umbruch/Formatierung
  • Mail verschicken
  • Mail(s) löschen
  • Attachmentmanagement
  • Meldungen/Fehler ausgeben



Tabelle 20.2: IMAP-Webnews-System
Gemeinsame Daten Zentrale Funktionalität
zusätzlich zu Webmail (Vererbung!)
  • Newsgroupdaten

zusätzlich zu Webmail (Vererbung!)
  • Newsgroup-Liste ausgeben
  • Newsgroup wechseln
  • Mail verschicken
  • Mail(s) löschen
  • Meldungen/Fehler ausgeben



Tabelle 20.3: Mehrbenutzer-Adreßbuch
Gemeinsame Daten Zentrale Funktionalität
  • Benutzer
  • String mit Meldungen

  • Adressen hinzufügen
  • Adressen aktualisieren
  • Adressen löschen
  • Adressauswahlliste
  • Suchfunktion
  • Adreßliste
  • Ausführliche Anzeige
  • Schnittstelle zu Webmail
  • Exportmöglichkeit



Tabelle 20.4: Mehrbenutzer-Bookmarkverwaltung
Gemeinsame Daten Zentrale Funktionalität
  • Benutzer
  • String mit Meldungen

  • Bookmarks hinzufügen
  • Bookmarks aktualisieren
  • Bookmarks löschen
  • Bookmarkauswahlliste
  • Gruppennamen ändern
  • Gruppen löschen
  • Gruppenauswahlliste
  • Anzeige/Ausgabe


Übung

OOP

Das Beispiel aus Abschnitt 18.4.1 bietet sich an, um OOP verstehen zu lernen. Folgende Aufgabe ist zu lösen:

Schreibe eine Klasse ,,Image``, die mit Hilfe der PHP-Image-Funktionen[*]ein leeres Bild erzeugt und die Methode show() implementiert. Leite von dieser Klasse eine neue namens ,,Point``ab mit der Methode set(x, y), die einen Punkt malt. Programmiere schließlich die Klassen ,,Circle`` (Kreis), ,,Rectangle`` (Rechteck) als Erben von ,,Point`` sowie die Klasse ,,Square`` (Quadrat), die ,,Rectangle`` erweitert. Erstelle von jeder malfähigen Klasse ein Objekt und benutze es jeweils, um das jeweilige Symbol auszugeben.

Eine mögliche Lösung findet sich in Abschnitt B.9.


Referenzsemantik

Wenn man von anderen objektorientierten Sprachen wie Java her gewohnt ist, bestimmte Dinge mit Referenzen machen zu können, wird man von PHP 4 zumindest anfangs enttäuscht sein.[*] Wie sich zeigt, kann man aber auch in PHP 4 so programmieren, daß Referenzen auch als solche behandelt werden. In dieser Übung soll deshalb eine bestehende Implementierung derart abgeändert werden, daß sie alle Referenzen auch als solche behandelt, d.h. z.B. bei Zuweisungen statt Kopier- Referenzsemantik anzuwenden.

Vorgegeben ist folgende ,,naive`` Implementierung, die die besonderen Erfordernisse der Verwendung von Referenzen schlicht ignoriert und damit sicher nicht das Gewünschte leistet. Außerdem steht unten noch das erwartete Ergebnis, also das, welches eine korrekte Implementierung erzeugen würde - und deine Lösung ist das doch hoffentlich, oder? :-)

Hinweis: Eine korrekte Implementierung kann schon erreicht werden, wenn pro Zeile maximal ein Zeichen (welches wohl?) hinzugefügt wird.

class MyArray {
  var $array;
  function MyArray() {}
  function add($val) {
    $this->array[] = $val;
  }
}

class Test {
  var $var;
  function Test($var, $array) {
    $this->var = $var;
    $array->add($this);
  }
  function setVar($var) {
    $this->var = $var;
  }
  function getVar() {
    return $this->var;
  }
}

$array = new MyArray();
$test  = new Test(42, $array);
$test2 = $test;
$test->setVar(0);
echo $test->getVar()." ".$test2->getVar()."\n";
var_dump($array);

Das Beispiel soll zuerst ,,0 0`` ausgeben. Bei falscher Implementierung (wie hier) wird statt dessen ,,0 42`` ausgegeben. Der Variablen-Dump schließlich soll bestätigen, daß tatsächlich eine Referenz auf das Objekt $test im Array $array abgelegt wurde. Ist dies nicht der Fall, ist der Wert der Variablen var 42 statt 0 und es steht dann kein Kaufmanns-Und vor object.

object(myarray)(1) {
  ["array"]=>
  array(1) {
    [0]=>
    &object(test)(1) {
      ["var"]=>
      int(0)
    }
  }
}

Eine mögliche Lösung findet sich in Kapitel B.10.

Christoph Reeg