previous up next contents index
Next: 8 PHP Erweitert Up: 7 Objektorientierung praktisch Previous: 7 Objektorientierung praktisch

Subsections



19 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.

1 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() {
    ...
  }

  ...
}

1 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.

2 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 {
  ...
}


3 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 (7.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 (19.2.1).

4 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 (19.2.1).


5 Klassen dokumentieren

Eine äußerst sinnvolle Methode, Klassen zu dokumentieren und zu kommentieren stellt PHPDoc dar (13). 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.


2 Objekte und Referenzierung in PHP

Wie wir bereits aus dem Kapitel Bedeutung von Objektorientierung (17) 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 natürlich nicht mehr!

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! :-)


1 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.[*]


1 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 bei der Übergabe ein grundsätzlich anderes Vorgehen statt als bei gewöhnlichen Primitivtypen wie Integer, Boolean oder String. Der große Unterschied ist nämlich, daß Referenzen auf Objekte ja nur das Objekt bezeichnen, es aber nicht selbst darstellen. Folglich arbeitet eine Methode immer mit dem Objekt, dessen Referenz ihr übergeben wurde, während sie bei Primitivtypen lediglich mit einer Kopie der ursprünglichen Variable arbeitet und das Original dabei niemals verändert -- das ist erst durch den Aufrufer möglich, der einen eventuellen Rückgabewert des Methodenaufrufs auswerten und für die Berechnung eines neuen Wertes verwenden kann, den er dann wieder dem Original zuweist. Diesem fundamentale Gegensatz hat man in der Objektorientierung einen eigenen Begriff gegeben: Kopier- bzw. Referenzsemantik; man spricht hierbei auch von Call by value bzw. Call by reference.


3 Methoden-Aufrufe


1 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 primitves Beispiel:

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


2 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);
  }
...

4 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);
  }
}

5 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 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. Hier nun einige Beispiele -- wer noch Anschauliches hat, kann diese gerne an Christoph schicken, der sich mit Objektorientierung in PHP mindestens so gut auskennt wie ich. ;-)


Table 19.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



Table 19.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



Table 19.3: Mehrbenutzer-Adressbuch
Gemeinsame Daten Zentrale Funktionalität
  • Benutzer
  • String mit Meldungen

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



Table 19.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


6 Übung

Das Beispiel aus Abschnitt 17.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 Pointab 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.7.


previous up next contents index
Next: 8 PHP Erweitert Up: 7 Objektorientierung praktisch Previous: 7 Objektorientierung praktisch
Christoph Reeg(http://reeg.net/)