am 9. Oktober 2008
Sicher – so manchen PHP-Entwickler stört die eine oder andere Ungereimtheit dieser Sprache. Eine, die mich immer wieder an PHP zweifeln lässt ist die Handhabung der statischen Methodenaufrufe.
Ganz nebenbei gesagt sollte, wie ich finde, jeder PHP-Entwickler vor dieser Eigenschaft gewarnt sein.
PHP erlaubt es, Instanzmethoden – also eine nicht-statische Methode einer Klasse – statisch aufzurufen. Die Phänomene, die dann beim Zugriff auf $this erscheinen, sind abenteuerlich:
Als Grundlage soll folgende Klasse betrachtet werden:
class Foo
{
public function ReturnClassname()
{
return $this ? get_class( $this ) : '**NULL**';
}
}
Die Methode ReturnClassname gibt mithilfe von get_class den Typ der Instanz zurück, auf die $this referenziert. Für den Fall, dass $this NULL ist, wird der String “**NULL**” zurückgegeben.
$foo2 = new Foo();
echo "Instance Call will return Classname " . $foo2->ReturnClassname() . "\n";
Korrekterweise erhalten wir bei diesem Codebeispiel den Klassennamen “Foo”:
> Instance Call will return Classname Foo
Was aber, wenn jemand unsere Klasse Foo falsch verwendet und einen static call durchführt?
echo "Static Call will return Classname " . Foo::ReturnClassname() . "\n";
Wie es quasi von PHP zu erwarten ist, wird dies vom Interpreter akzeptiert. Die Variable $this hat den Wert NULL. Der Entwickler bekommt jedoch eine Notice, auf die er während der Entwurfszeit reagieren sollte:
> Notice: Undefined variable: this in /home/timo/…/static.php on line …
> Static Call will return Classname **NULL**
Soweit so gut – bis hier könnte man dies fast noch akzeptieren. Trotzdem frage ich mich, wie ich mich dann als API-Entwickler davor schützen kann, dass die Anwender der Interfaces keine falschen Calls mache. Es liegt auf der Hand, dass man nicht vor jedem Zugriff auf $this prüfen möchte, ob der Call richtig gemacht wurde. Man denke nur an Beans, die dutzende von Settern und Getter haben, die alle schlichtweg nur auf ein Attribut zugreifen möchten. An jeder Stelle eine Sicherheitsprüfung ob $this NULL ist?
Was dem PHPHatesMe-Leser tatsächlich Freude bereiten wird ist, was passiert, wenn man die Klasse Foo in einem Klassenkontext statisch aufruft:
class Bar
{
public function Fool()
{
// Call als Instanzmethode
$foo2 = new Foo();
echo "Instance Call will return Classname " . $foo2->ReturnClassname() . "\n";
// Call als statische Methode
echo "Static Call will return Classname " . Foo::ReturnClassname() . "\n";
}
}
Wie gewohnt liefert der Call als Instanzmethode den Klassennamen Foo. Der statische Aufruf der Methode ReturnClassname hingegen, resultiert nicht in einem Warning o.ä. sondern liefert den Klassennamen Bar:
> Static Call will return Classname Bar
PHP scheint also bei statischen Methodenaufrufen die Referenz von $this nicht zu verschieben, sondern es beim ursprünglichen Wert zu belassen. Arbeitet man als API-Entwickler mit der Variable $this kann dies ernsthafte Konsequenzen haben. Eventuell ist eine Methode, die man auf der Instanz von Foo ausführen möchte, auch in Bar definiert, führt aber komplett andere Aktionen aus.
Ich denke, es liegt auf der Hand, dass dies gefährlich werden könnte.
Um PHP hier etwas aus der Patsche zu helfen könnte man sagen: “In wichtigen Fällen kann man doch prüfen, ob $this wirklich die richtige Instanz ist!”
Denkt man jedoch an Polymorphismus, fällt auf, dass selbst get_class nicht mehr weiterhelfen kann:
class Foo2 extends Foo
{
}
Führt man auf die Klasse Foo2 die Methode GetClassname aus, erhalten wir:
> Instance Call will return Classname Foo2
Ich kann aber alle Leser beruhigen: Instanceof bringt hier Hilfe. Damit könnte Foo herausfinden, ob die Klasse im richtigen Kontext aufgerufen wurde:
$correctScope = $this instanceof Foo;
Trotzdem würde ich mir wünschen, dass wie bei einer “richtigen” Programmiersprache entweder eine RuntimeException oder ein Fatal-Error produziert wird.
That’s why PHP hates me.