Late static bindings in PHP5

1 | 3314 Aufrufe
Sie können diese Wikiseite nach der Anmeldung auf Webmasterpro bearbeiten. Helfen Sie mit und verbessern Sie "Late static bindings in PHP5" mit Ihrem Wissen!

Anzeige Hier werben

Einleitung

Unter "late static bindings" versteht man die Möglichkeit, aus einer statischen Methode einer Oberklasse die aktuell (d.h. zur Laufzeit) abgeleitete Klasse zu ermitteln. Dies ist z.B. sinnvoll, um Funktionalität in einer Oberklasse auszulagern und diese Funktionalität trotzdem so dynamisch zu halten, dass sie von Unterklasse zu Unterklasse geringfügig anders ausfällt. Bei der objektorientierten Programmierung wird dieses Problem mit Hilfe dem Konzept der Vererbung gelöst. Kommen allerdings statische Klassenmethoden ins Spiel, benötigt man "late static bindings".

Das Problem

Den einleitenden Text dieses Artikels versteht man vermutlich besser, wenn man ein greifbares Beispiel hat. Wir stellen uns deshalb vor, wir haben eine Oberklasse, die für eine Reihe von Unterklassen eine bestimmte Funktion bereitstellen möchte. Diese Funktion ist für alle Unterklassen weitestgehend gleich, sodass sie in der Oberklasse definiert werden sollte. Jedoch benötigt sie an manchen Stellen weitere Methoden, die erst in der abgeleiteten Klasse spezifiziert sind. Zu diesem Zweck muss die Methode der Oberklasse also wissen, welche Unterklasse aktuell "aktiv" ist, damit sie weiß, welche Methoden sie anstoßen soll.

Kein Problem bei Vererbung

Bei der objektorientierten Programmierung wird das Problem wie oben bereits angeschnitten durch die Vererbung gelöst. Folgender Code verhält sich also so, wie man es erwartet:

 
PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php
/**
 * Die Oberklasse, die bestimmte Funktionen für sämtliche Unterklassen bereitstellen möchte
 */
class Oberklasse {

    public function test() {
        $this->getClassName();
    }

    /**
     * Diese Methode wird von der abgeleiteten Klasse überschrieben werden
     */
    public function getClassName() {
        echo("Aufruf der Methode der Oberklasse, Klassenname ist: " . __CLASS__);
    }
}



/**
 * Erste abgeleitete Unterklasse
 */
class Unterklasse1 extends Oberklasse {

    /**
     * Überschreibt die Methode der Oberklasse
     */
    public function getClassName() {
         echo("Aufruf der Methode der Unterklasse1, Klassenname ist: " . __CLASS__);
    }
}



/**
 * Zweite abgeleitete Unterklasse
 */
class Unterklasse2 extends Oberklasse {

    /**
     * Überschreibt die Methode der Oberklasse
     */
    public function getClassName() {
         echo("Aufruf der Methode der Unterklasse2, Klassenname ist: " . __CLASS__);
    }
}


$myObject = new Oberklasse();
$myObject->test();  // AUSGABE: Aufruf der Methode der Oberklasse, Klassenname ist: Oberklasse

$myObject = new Unterklasse1();
$myObject->test();  // AUSGABE: Aufruf der Methode der Unterklasse1, Klassenname ist: Unterklasse1

$myObject = new Unterklasse2();
$myObject->test();  // AUSGABE: Aufruf der Methode der Unterklasse2, Klassenname ist: Unterklasse2
?>

Je nachdem, von welcher Klasse man eine Objekt-Instanz erzeugt, wählt test()die passende getClassName()-Methode. Wenn man also ein Objekt mit "new Oberklasse()" erzeugt und test() ausführt, wird getClassName() der Klasse Oberklasse angestoßen. Bei Objekten der Klasse Unterklasse1 wird entsprechend die Methode getClassName() der Klasse Unterklasse1 benutzt. Die Methode test() ist jedoch nur in der Oberklasse definiert!

Statische Methoden

Die selbe Funktionalität soll nun mit statischen Methoden realisiert werden. Dazu folgender Code:

 
PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php
/**
 * Die Oberklasse, die bestimmte Funktionen für sämtliche Unterklassen bereitstellen möchte
 */
class Oberklasse {

    /**
     * Diese Methode muss nicht zwingend statisch sein, siehe Anwendundungsbeispiel unten
     */
    public static function test() {
        self::getClassName();
    }

    /**
     * Diese Methode wird von der abgeleiteten Klasse überschrieben werden
     */
    public static function getClassName() {
        echo("Aufruf der Methode der Oberklasse, Klassenname ist: " . __CLASS__);
    }
}



/**
 * Erste abgeleitete Unterklasse
 */
class Unterklasse1 extends Oberklasse {

    /**
     * Überschreibt die Methode der Oberklasse
     */
    public static function getClassName() {
         echo("Aufruf der Methode der Unterklasse1, Klassenname ist: " . __CLASS__);
    }
}



/**
 * Zweite abgeleitete Unterklasse
 */
class Unterklasse2 extends Oberklasse {

    /**
     * Überschreibt die Methode der Oberklasse
     */
    public static function getClassName() {
         echo("Aufruf der Methode der Unterklasse2, Klassenname ist: " . __CLASS__);
    }
}


Oberklasse::test(); // AUSGABE: Aufruf der Methode der Oberklasse, Klassenname ist: Oberklasse
Unterklasse1::test();   // AUSGABE: Aufruf der Methode der Oberklasse, Klassenname ist: Oberklasse
Unterklasse2::test();   // AUSGABE: Aufruf der Methode der Oberklasse, Klassenname ist: Oberklasse
?>

Wir stellen (verwundert ;-) ) fest, dass egal aus welcher Klasse die gemeinsame Methode test() aufgerufen wird, immer getClassName() der Oberklasse benutzt wird. Woran liegt das?

Das Schlüsselwort self bezieht sich in PHP immer auf die Klasse selbst (wie der Name ja schon sagt ;-) ). Die Referenz auf die konkrete Klasse wird bereits bei der Kompilierung gesetzt, also zu einem Zeitpunkt, wo die konkreten abgeleiteten Klassen noch gar nicht bekannt sind! Ob und welche Klasse von Oberklasse ableitet, wird erst später bestimmt, nämlich dann, wenn man Oberklasse::test() oder Unterklasse1::test() schreibt. self hilft uns also nicht weiter. Auch __CLASS__ bezieht sich nur auf die Klasse selber.

Die Lösung

Workaround in PHP < 5.3.0

In PHP bis Version 5.3.0 gibt es für dieses Problem keine vernünftige Lösung. Es gibt jedoch ein Workaround über die Methode get_class(). Wandelt man obenstehenden Quellcode dahingehend ab, dass test() keine statische Methode, sondern eine Objektmethode ist, kann man mit get_class($this) die aktuelle Klasse ermitteln. Der Quellcode sieht dann wie folgt aus:

 
PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?php
/**
 * Die Oberklasse, die bestimmte Funktionen für sämtliche Unterklassen bereitstellen möchte
 */
class Oberklasse {

    /**
     * Diese Methode ist jetzt eine Objektmethode
     */
    public function test() {
        $realClass = get_class($this);
        call_user_func(array($realClass, "getClassName"));  // "$realClass::getClassName()" funktioniert nicht!
    }

    /**
     * Diese Methode wird von der abgeleiteten Klasse überschrieben werden
     */
    public static function getClassName() {
        echo("Aufruf der Methode der Oberklasse, Klassenname ist: " . __CLASS__);
    }
}



/**
 * Erste abgeleitete Unterklasse
 */
class Unterklasse1 extends Oberklasse {

    /**
     * Überschreibt die Methode der Oberklasse
     */
    public static function getClassName() {
         echo("Aufruf der Methode der Unterklasse1, Klassenname ist: " . __CLASS__);
    }
}



/**
 * Zweite abgeleitete Unterklasse
 */
class Unterklasse2 extends Oberklasse {

    /**
     * Überschreibt die Methode der Oberklasse
     */
    public static function getClassName() {
         echo("Aufruf der Methode der Unterklasse2, Klassenname ist: " . __CLASS__);
    }
}


$myObject = new Oberklasse();
$myObject->test();  // AUSGABE: Aufruf der Methode der Oberklasse, Klassenname ist: Oberklasse

$myObject = new Unterklasse1();
$myObject->test();  // AUSGABE: Aufruf der Methode der Unterklasse1, Klassenname ist: Unterklasse1

$myObject = new Unterklasse2();
$myObject->test();  // AUSGABE: Aufruf der Methode der Unterklasse2, Klassenname ist: Unterklasse2

?>

Abgesehen davon, dass die gemeinsame Methode test() nun eine Objektmethode ist, funktioniert nun alles so, wie wir es wünschen. Eine bessere Lösung ist mir für PHP 5.3.0 nicht bekannt.

Lösung ab PHP 5.3.0

PHP ab Version 5.3.0 (aktuell, d.h. am 12.4.2008, noch nicht veröffentlicht) wird das Schlüsselwort static anbieten. Damit wird es endlich möglich sein, aus statischen Methoden einer Oberklasse diejenigen statische Methoden aufzurufen, die zu der aktuellen (Unter-)Klasse gehören. Die Methode test() aus dem letzten Beispiel sähe dann so aus:

 
PHP
1
2
3
public static function test() {
    static::getClassName();
}

Siehe auch: Dokumentation des Schlüsselwortes "static" auf php.net

Ein Anwendungsbeispiel

Hätte ich diesen Artikel vor einem halben Jahr gelesen, hätte ich gedacht Schön, aber brauchen tut man sowas ja eh nur in Ausnahmefällen. In eine solche Situation kommt man allerdings schneller als man denkt. Ich habe sie in folgendem Anwendungskontext erfahren müssen.

Es gibt eine Oberklasse GenericObject, die für diverse Unterklassen gemeinsame Methoden bereitstellt. In meinem Fall war z.B. Account eine Unterklasse, die von GenericObject abgeleitet wurde.
Eine dieser gemeinsamen Methoden ist setAttribute($attributeName, $value), die den Wert eines Objekt-Attributs verändert. Beispielsweise setzt $myAccount->setAttribute("lastname", "Mustermann") den Nachnamen des aktuellen Benutzeraccounts auf "Mustermann". Allerdings setzt die Methode nicht einfach blind jeden Wert für jedes Attribut, sondern prüft zuerst, ob der Wert zulässig ist. Da theoretisch beliebig viele Objekte von GenericObject abgeleitet werden können, macht es wenig Sinn, alle Validierungsroutinen in der Methode setAttribute() selber zu halten. Das würde sie unnötig aufblähen und man müsste für jede neu implementierte Unterklasse die Oberklasse verändern (unelegant! ;-) ).
Deshalb bietet jede Unterklasse eine Methode validateValue($attributeName, $newValue), die entweder true oder false zurückgibt, abhängig davon, ob der neue Wert für das Attribut zulässig ist. Diese Methode ist logischerweise statisch.

Und siehe da, wir haben die problematische Methoden-Konstellation! Die Methode setAttribute() aus der Oberklasse muss zur Laufzeit feststellen können, in welcher der Unterklassen sie die statische Methode validateValue() anstoßen muss. Da setAttribute() selber eine Objekt-Methode ist, kann ich aber das oben vorgestellte Workaround für PHP<5.3.0 benutzen.


Wikiseite bearbeiten

Diese Seite kann von jedem registrierten Benutzer bearbeitet werden. Bisher hat 1 Person an der Seite "Late static bindings in PHP5" mitgewirkt.

Sie haben einen Fehler entdeckt oder möchten etwas ergänzen? Dann können Sie nach der Anmeldung "Late static bindings in PHP5" hier bearbeiten.

Mitarbeiter
  • Max ist schon ein paar Jährchen als treuer User bei Webmasterpro dabei. Seine Interessen hier sind hauptsächlich die Entwicklung von Web-Applikationen sowie hobbymäßige Kreativ-Aufgaben mit Photoshop. In letzter Zeit hat er bedingt durch sein Studium leider nur noch Zeit, ab und zu mal im Reallife-Bereich des Forums herumzugeistern.