PHP 5: Callback-Funktionen mit einem Zustandsgedächtnis programmieren

Problem
Sie benötigen eine Callback-Funktion. Eine einfache Funktion (z.B. ein Closure oder eine Lambda-Funktion) reicht aber für Ihre Zwecke nicht aus, da die Callback-Funktion einen eigenen Zustand halten – also ein Zustandsgedächtnis haben – soll.
Lösung
Seit PHP 5.3 haben Sie die Möglichkeit, von Funktionsobjekten Gebrauch zu machen. Dabei handelt es sich um Objekte, die wie Funktionen behandelt und demnach also auch wie Funktionen aufgerufen werden können:

class Funktor {
   public function __invoke($wert) {
      print $wert . "\n";
   }
}
$f = new Funktor;
$f('Hallo Welt!');
Hallo Welt!

Diskussion
In Rezept 6.12 haben Sie gesehen, wie Lambda-Funktionen verwendet werden können, um z.B. ein Array mit der Funktion usort() zu sortieren. Diese Funktionen werden oft als Callback-Funktionen oder kurz Callbacks bezeichnet. Ein solches Callback übergibt man an eine beliebige Funktion, und diese ruft es mit diversen Parameterwerten auf. Die usort()-Funktion ruft ein Callback beispielsweise mit zwei Werten auf, die miteinander verglichen werden sollen. Die Callback-Funktion ist dabei nicht in der Lage, einen Zustand über mehrere Aufrufe hinweg zu halten. Einfach formuliert, könnte man sagen, sie hat kein »Gedächtnis«. Das an usort() übergebene Callback weiß nicht, welche Werte es im vorherigen Aufruf verglichen hat. PHP bietet ab Version 5.3 mit den Funktoren die Möglichkeit, Callbacks zu implementieren, die in der Lage sind, einen Zustand zu halten. Ein Funktor (auch Funktionsobjekt genannt) ist eigentlich keine Methode, sondern eine vollwertige Klasse, die die magische Methode __invoke() implementiert. Durch die Implementierung dieser Methode ist es möglich, ein Objekt der Klasse wie eine normale Funktion aufzurufen. Demnach kann man ein solches Funktionsobjekt auch problemlos an Funktionen wie usort() oder array_map() übergeben, die eine Callback-Funktion als Parameter erwarten. Im Vergleich zu einer einfachen Callback-Funktion kann ein Funktionsobjekt allerdings einen internen Zustand haben, der zwischen den einzelnen Callback-Aufrufen erhalten bleibt:

$ar = array(3, 21, 9);
class Funktor {
    private $summe;
    public function __invoke($wert) {
        $this->summe += $wert;
        print $wert;
    }
    public function summe() {
        return $this->summe;
    }
}
$f = new Funktor;
array_map($f, $ar);
echo $f->summe();
3
21
9
33

Das Beispiel zeigt, wie das Funktionsobjekt $f als Callback an array_map() übergeben wird und jeden einzelnen Array-Wert ausgibt. Die Werte werden aber nicht nur ausgegeben, sondern der Funktor addiert bei jedem Aufruf den übergebenen Wert zur $summe, die als Instanzvariable angelegt ist. Nachdem array_map() beendet ist, können Sie über die Methode summe() des Funktionsobjekts die Summe der einzeln übergebenen Werte abfragen. Die Ausgabe des Werts 33 zeigt, dass das Funktionsobjekt den Zustand korrekt über die einzelnen Aufrufe hinweg transportiert hat. Die Anzahl der Parameter, die beim Aufruf eines Funktionsobjekts übergeben werden können bzw. müssen, hängt davon ab, wie die __invoke()-Methode implementiert ist. Wenn Sie __invoke() so deklarieren, dass diese drei Parameter erwartet, müssen beim Aufruf des Funktionsobjekts natürlich auch drei Werte übergeben werden:

class Funktor {
    public function __invoke($p1, $p2, $p3) {
      var_dump($p1);
      var_dump($p2);
      var_dump($p3);
   }
}
$f = new Funktor;
$f('foo', 3, false);
}
string(3) "foo"
int(3)
bool(false)

Siehe auch
Informationen zur magischen __invoke()-Methode finden Sie im PHP-Manual unter http://php.net/manual/language.oop5.magic.php#language.oop5.magic.invoke, zu usort() unter http://php.net/manual/function.usort.php und zu array_map() unter http://php.net/manual/function.array-map.php.
Auszug aus der Neuauflage des PHP5 Kochbuchs phpckbk3ger.s


Kommentare

  1. Ein sehr interessanter Artikel. Habe diese Art von zugreifen auf die Objekte noch nicht ausprobiert im PHP, kenne es nur von Javascript. Aber sobald wir PHP 5.3 in unser Betrieb eingeführt haben, werde ich gleich diese Methodik in versteckte Klassen oder ähnliches verwenden. Mich würde jedoch interessieren, wie schnell der Zugriff auf die Lambada-Methoden gegenüber herkömmlichen Vorgehensweise, also feste Methoden in einer Klasse, ist?

Fügen Sie einen Kommentar hinzu

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.