Facebook
Twitter
Google+
Kommentare
23

__invoke … ein Geniestreich?!

Magische Methoden gehören zu PHP. Viele schwören auf die Verwendung von magischen Gettern und Settern, viele lieben __call. Warum auch nicht! In vielen Fällen kann man seinen Source Code damit bestimmt für den Programmierer einfacher machen. Was die Person, die den Code wartet dazu meint fragen wir besser nicht. Egal. Es gibt ja schließlich auch andere magische Methoden und auf eine wollen wir heute eingehen.

Die Methode __invoke, die ich an jeder Klasse anbringen kann, hat laut php.net folgende Nutzungsmöglichkeit:

Die __invole-Methode wird aufgerufen, wenn ein Skript versucht, ein Objekt als Funktion aufzurufen.

So ab hier stehe ich jetzt auf dem Schlauch, wahrscheinlich gibt es wirklich gute Anwendungsfälle für __invoke. Ich habe leider nur gar, aber so wirklich keine Ahnung, wann ich das mal verwunden könnte. Ich habe auch noch nicht wirklich ernsthaft jemanden dieses Konstrukt im Quellcode so was verwenden sehen. Ok, liegt vielleicht auch dran, dass es erst ab PHP 5.3 unterstützt wird. Ich finde aber auch keinen guten Artikel im Netz dazu.

So jetzt genug von mir. Das ist eine wirklich ernst gemeinte Frage: Wofür verwendet ihr __invoke?

Über den Autor

Nils Langner

Nils Langner ist der Gründer von "the web hates me" und auch der Hauptautor. Im wahren Leben leitet er das Qualitätsmanagementteam im Gruner+Jahr-Digitalbereich und ist somit für Seiten wie stern.de, eltern.de und gala.de aus Qualitätssicht verantwortlich. Nils schreibt seit den Anfängen von phphatesme, welches er ebenfalls gegründet hat, nicht nur für diverse Blogs, sondern auch für Fachmagazine, wie das PHP Magazin, die t3n, die c't oder die iX. Nebenbei ist er noch ein gern gesehener Sprecher auf Konferenzen. Herr Langner schreibt die Texte über sich gerne in der dritten Form.
Kommentare

23 Comments

  1. Ich nutze es bisher auch nur bei C#. Dort nutzt man es z.B. um von einer Form in einer anderen etwas zu ändern. Den alle Zugriffe auf GUI-Elemente (Controls, Form etc.) müssen aus dem gleichen Thread erfolgen, der sie erzeugt hat.

    Reply
  2. Mit der __invoke-Methode kann man Closures und (gewisse) Objekte in einen Topf werfen. So was könnte evtl. beim Kommando- oder Strategie-Entwurfsmuster hilfreich sein, wenn man einfache Funktionalität in einer Closure und komplexere in einem Objekt (mit __invoke-Methode) kapselt.
    Vor allem in den o.g. Mustern kann man ausnutzen, dass Closures anonym sind. In diesem Fall könnte man Closures nämlich als vereinfachte Form von anonymen Klassen betrachten.

    Reply
  3. Ein kleines Beispiel sagt oft mehr als 1000 Worte 😉

    class Addierer
    {
    public function __construct($wieviel)
    {
    $this->wieviel = $wieviel;
    }

    public function __invoke($x)
    {
    return $this->wieviel + $x;
    }

    private $wieviel;
    }

    $addiere5 = new Addierer(5);
    echo $addiere5(7);

    Reply
  4. Was mich interessieren würde, ist, wie das mit der Performance ist. Also wie wirkt sich der Lambda-Methoden-Zugriff in der Masse aus, gegenüber herkömmliche Methoden-Zugriffe? Ist es eher schneller oder langsamer? Hat da jemand schon ein Vergleich aufgestellt oder Erfahrung damit gemacht?

    Reply
  5. Spontan würde ich sagen:

    $filter = new My_Filter();
    $data = $filter($_POST); // vs. $filter->run($_POST);

    Ist einfach „natürlicher“ angewendet als das definieren einer Methode die im Filter aufgerufen werden soll.

    Reply
  6. Ich verwende __invoke, um nach Instantiierung und Konfiguration eines Objektes den Prozess anzustoßen, für den das Objekt gebaut wurde:

    $gal = new Galerie; //instantiate
    $gal->setImages( $images ); // configure
    $gal->setTemplate( $template ); // configure
    $gal(); // vs. $gal->createOutput()

    Reply
  7. @Ben: Fast hätte ich dir zugestimmt, aber die fehlende Autovervollständigung macht dem aktuell noch einen Strich durch die Rechnung. Niemand kommt auf die Idee, die Klasse so zu benutzen, wenn man sie nicht selbst geschrieben hat.

    Ich muß auch zugeben dass ich es noch nie verwendet hat. Die Idee, das Objekt dann wie eine Callback-Funktion zu nutzen finde ich eventuell interessant.

    Reply
  8. Im richtigen Fall eingesetzt kann man damit Aufrufe „natürlicher“ wirken lassen, also vom eigentlichen Aufruf nochmal abstrahieren – Das Konstrukt kann man blendend für DSLs verwenden. Ben hat es ja schon ganz passend dargestellt.

    Der Witz bei DSLs ist ja ohnehin, dass man hinter einem Bezeichner einen Funktionsaufruf findet, ohne dass dieser offensichtlich ist.

    @PHPGangsta
    Mit dieser Art des Aufrufs kann man die Verwendung einer Bibliothek bzw. eines Frameworks nochmals vereinfachen – genau darum gehts. Rails verwendet im ganzen Framework DSL-Syntax -> Methoden erscheinen oft als eingebaute Schlüsselwörter -> DSL für Webapplikationen.

    Reply
  9. Effektiv ist es ein Implementierungsdetail von Closures/Anonymen Funktionen: Eine anonyme Funktion der Form $a = function() {}; wird zu einem Objekt vom Typ „Closure“ mit einer __invoke() Methode. Diese Funktionalität wurde auch in den Userspace exportiert.

    Reply
  10. @drwitt: Wieso ist $gal(); besser als $gal->createOutput() ? Ich finde ersteres deutlich schlechter, weil man nicht weiß was genau diese „Funktion“ macht.

    Häufig wird der Code dadurch meines Erachtens unlesbarer, außer in wenigen Ausnahmefällen (bei kleinen gut benannten Hilfsfunktionen). Nur weil man 5 oder 10 Zeichen sparen kann sollte man das nicht zulasten der Lesbarkeit/Wartbarkeit nutzen.

    Die IDEs sind aktuell auch noch nicht so weit.
    – „go to declaration“
    – „find usages“
    – Parameter Tooltips
    sind nicht vorhanden. Sich „mal eben durch den Code klicken“ geht nicht.

    Zum Beispiel funktioniert das folgende Beispiel auch nicht:

    class A
    {
    public function __invoke() {
    echo „invoke called“;
    }
    }
    class B
    {
    private $_a;

    public function __construct() {
    $this->_a = new A();
    // hier möchte ich __invoke aufrufen
    $this->_a();
    }
    }
    $b = new B();

    Wenn man es richtig liest würde man erwarten dass „invoke called“ ausgegeben wird. Stattdessen kommt „Fatal error: Call to undefined method B::a()“. Das Invoken klappt also nicht.

    Für mich ein Feature, das der Normalanwender nicht braucht und bei den meisten nur Verwirrung stiftet.

    Reply
  11. Meiner Meinung nach sollte man diese ganzen Magic-Functions so sparsam wie möglich verwenden. Der Code wird zum einen unleserlich und ein fremder Entwickler sieht nicht direkt, was der Code genau macht. Das macht natürlich das Debugging schwieriger. Zum anderen kenn ich keine IDE, die mir bei der ganzen Magic noch ordentliche Autovervollständigunf bietet.
    Ehrlich gesagt habe ich solche Features in den seltensten Fällen gebraucht.

    Reply
  12. Ich habe einmal in einem Buch eine Verwendung von __invoke() gesehen, und zwar bei einem Logger. Man musste nur noch $log($type, $message) anstatt $log->log($type, $message) aufrufen.

    Diese Methode wird wahrscheinlich nur ein sehr, sehr, sehr fauler Programmierer brauchen, welcher durch die Verwendung von __invoke ca. 5 Zeichen einspart.
    Schneller wird die Anwendung dadurch sicher nicht.
    Paloran

    Reply
  13. Im Moment kann ich mir für den Einsatz von __invoke nur vorstellen, meine Kollegen zu ärgern (und am Ende auch mich selbst). Höchsten einem strengen Frameworkonzept kann ich mir einen vertretbaren Einsatz vorstellen.

    Reply
  14. __invoke() ist (wie schonmal erwähnt, aber scheinbar ignoriert) das OOP-Gegenstück zu Callbacks. Der Sinn ist _nicht_ 5 Zeichen zu sparen. Und das `$gal()` unleserlicher ist, liegt eher daran, dass ein unachtsamer Entwickler einen miserablen Variablennamen gewählt hat, was es aber auch wäre, wenn es `__invoke()` nicht implementiert hätte. Ob das Feature nun sinnvoll ist, oder nicht, sollte man wohl doch eher vor dem Hintergrund betrachten, wofür sie gedacht ist, nicht wofür sie gedacht sein könnten. Wie gesagt: Zeichen sparen ist nicht der Grund. „Schlechtere Lesbarkeit“ ist auch nur dann gegeben, wenn sich derjenige nicht richtig auf Callbacks einstellt.

    Filter wurden als mögliches Anwendungsgebiet bereits genannt. Hier hat das Ding ein dediziertes Aufgabengebiet. Solche Konstrukte wie `$filter->filter($string)` fand ich immer ziemlich doppelt gemoppelt, denn was soll ein Filter sonst auch anderes tun? `$filter($string)` hat genau die gleiche Aussage und nebenbei erlaubt das auch andere „Arten“ von Callbacks, nicht nur Objekte von Klassen mit `__invoke()`.

    `$o->setFilter(function ($string) { return substr($string,4,4)); });`
    `$o->setFilter(„strtolower“);`

    Aber natürlich kann man für jede kleine Eventualität auch gleich eigene Klassen schreiben 😉 Eine Klasse, die `__invoke()` implementiert, will in erster Linie Callback mit (veränderlichen) Zustand sein.

    @PHPGangsta: Mit `$this->a()` rufst du eine Methode auf, kein Callback einer Property. Das ist eine Einschränkung der Syntax, nicht des Konzeptes.

    Reply
  15. Jeder, der mal mit C++ gearbeitet hat, wird die Möglichkeit Operatoren zu überladen lieben. Einen Vektor über A + B statt A.add(B) zu addieren ist einfach schöner. Oder auf ein Array nicht mit Array.get(index) zuzugreifen, sondern mit Array[index]. Ersteres unterstützt PHP zwar noch nicht, letzeres aber, über ArrayAccess. Das schätze ich sehr. Auch __invoke() ist ähnlich. Meine Matrix-Klasse beispielsweise nutzt Matrix operator()(unsigned int n, unsigned int m) um auf eine bestimmte Stelle in der Matrix zuzugreifen, oder sie zu setzen:
    Matrix(5,7) = 8;

    Auch wenn ich wohl eher keine Matrix-Klasse in PHP implementieren würde (seien wir mal ehrlich, PHP ist da doch ein (großes) wenig zu langsam), kann man doch Anwendungen finden.

    Für mich als einfachen Mann ist $log(„Error in …“) oder $db(„SELECT …“) eine durchaus sinnvolle Anwendung.

    Reply
  16. Also kann man zusammenfassend eigentlich sagen, dass es wohl eher als Art Default-Methode verwendet wird und damit eher den Lesenfluss verbessert.

    Was ich mir noch so denke (ist nicht ausgereift) wäre, dass man die Lambda-Funktion aufruft, wenn man nicht genau weiß, welche Methode man aufrufen will. Also dass die Lambda-Funktion anhand der übergebenen Parameter eine entsprechende Funktion intern aufruft. Als Beispiel würde mir eine dynamische Factory einfallen.

    class LogFactory
    {

    function __invoke($param)
    {
    if ($param instanceof ‚ClassA‘) {
    return new ClassALogger($param);
    }
    else if ($param instanceof ‚ClassB‘) {
    return new ClassBLogger($param);
    }
    }

    }

    Reply
  17. @Sven: Default-Methode?! 😕
    `Factory`-Pattern ist zwar ein nettes Beispiel, ein Handler ist aber doch noch etwas anderes. Es macht nicht sooo viel Sinn eine Factory zu instanzieren, nur damit man die eigentliche Factory-Methode aufrufen kann.

    Man kann Werte, der kein Verhalten haben, an Methoden/Funktionen übergeben, oder man übergibt Objekte, die auch gleich einen Zustand mitbringen. Handler haben den Vorteil, dass eine Methode einfach sagen kann „Ich will irgendwas ausführbares, aber was genau, das überlass ich den Aufrufenden“ (Inversion-Of-Control). `__invoke()` wiederum erlaubt es eben ^auch^ Objekte zu verwenden, wenn man es denn unbedingt will, oder für sein OOP-Ego braucht 😉

    Reply
  18. Fehlt nur noch ein __invokeStatic()

    In den ein oder anderen Fällen kann es durchaus ganz nett sein, auf __invoke() zuzugreifen. Gerade die genannten Beispiele sind ja nette Ansätze für die Verwendung von __invoke(). Finde es durchaus besser zu lesen $log(‚..‘);“ als $log->log(‚..‘).

    Reply
  19. Hat sich hier schon mal jemand mit dem (PSR-3 kompatiblen) Logging Framework Monolog beschäftigt?

    Hier kann man jeder Logger-Instanz nicht nur einen Stack von Handlern verpassen, sondern auch eine „Processor“-Stack. Und eben diese „Processors“ sind Callbacks, die „irgendetwas“ mit den vom Logger erhaltenen Daten machen oder Daten automatisiert hinzufügen.

    Im einfachsten Fall ist so ein Processor also eine anonyme Funktion. So kann man mal eben „on a fly“ seinen eigenen Processor basteln.

    Oder man nimmt Instanzen der vom Framework bereit gestellten Processor-Klassen (oder von diesen abgeleitete).

    Hm, Instanzen (= Objekte) als Processor übergeben? Ich dachte, ein Processor sei ein Callback, also eine Funktion oder Methode?

    Ja, genau! Und hier kommt eben diese komische __invoke-Methode zum tragen.

    Habe bisher nie __invoke benutzt, allein schon, weil mir selbst bisher kein so gutes Beispiel eingefallen ist. Aber erweiterbare Callbacks und Callbacks mit definierbare Zustand finde ich mal echt fein.

    Und wie sieht’s mit Sortier-Algorithmen aus?

    Ich denke da z.B. an gaaaanz simple Sortierung nach String-Inhalten. Hm, aber nach welchem Zeichensatz sortieren, aufsteigend, oder absteigend, … ?

    Filterobjekt mit Properties $charset, $direction und __invoke()-Methode fände ich da schon recht praktisch. Vorallem eben zwecks spätere Erweiterung.

    Reply
  20. Ich benutze es, um Callbacks als Klassen aufzurufen.

    Z.b. im Eventsystem in CraftCMS:

    Event::on(
    Entry::class,
    Entry::EVENT_AFTER_SAVE,
    new NotifyWhenCookPostedArticle
    );

    Dann kann ich die Klasse NotifyWhenCookPostedArticle ganz einfach OOP programmieren und habe immer schöne selbstsprechende Functions:

    public function __invoke(ModelEvent $event)
    {
    $this->init($event);
    if ($this->shouldNotNotify()) return;
    $this->notify();
    }

    Das Gleiche einfach ausgecodet ist 10 Mal so viel und nicht im Geringsten selbstsprechend.

    Reply

Leave a Comment.

Link erfolgreich vorgeschlagen.

Vielen Dank, dass du einen Link vorgeschlagen hast. Wir werden ihn sobald wie möglich prüfen. Schließen