• __invoke … ein Geniestreich?!

    von am 14. Mai 2010

    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?

    Nils Langner

    Auch wenn Ihr es mir nicht glauben werdet, aber ich habe nichts gegen PHP. Ich rege mich einfach nur gerne auf. Ok so schlimm ist es auch nicht. Eigentlich wollte ich schon immer einen Blog haben und da ...

    Zum Profil von Nils Langner

    21 Kommentare »


    • Daniel
      am 14. Mai 2010 um 07:23 Uhr

      Spontan fällt mir dazu auch keine Verwendung ein. Schön zu wissen, dass es geht, aber bekanntlich muss man ja nicht alles benutzen, nur weil man es könnte.

      Nach kurzem Suchen habe ich sinngemäß folgendes gefunden: “Callback-Funktionen mit einem Zustandsgedächtnis programmieren” (http://community.oreilly.de/blog/2009/10/12/php-5-callback-funktionen-mit-einem-zustandsgedachtnis-programmieren/).


    • Björn
      am 14. Mai 2010 um 07:34 Uhr

      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.


    • Andre Moelle
      am 14. Mai 2010 um 08:25 Uhr

      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.


    • Ludwig
      am 14. Mai 2010 um 09:26 Uhr

      Zum Thema __invoke() (im Bezug auf eine Plugin Implementierung) kann ich folgende Diskussion aus der Zend Framework Mailing-List empfehlen:
      http://zend-framework-community.634137.n4.nabble.com/Plugin-implementations-tp1755553ef634137.html


    • beispiel
      am 14. Mai 2010 um 09:35 Uhr

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


    • Gjero Krsteski
      am 14. Mai 2010 um 09:52 Uhr

      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?


    • Ben
      am 14. Mai 2010 um 11:43 Uhr

      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.


    • drwitt
      am 14. Mai 2010 um 12:05 Uhr

      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()


    • PHPGangsta
      am 14. Mai 2010 um 12:48 Uhr

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


    • Tom Höfer
      am 14. Mai 2010 um 13:24 Uhr

      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.


    • Johannes
      am 14. Mai 2010 um 13:36 Uhr

      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.


    • PHPGangsta
      am 14. Mai 2010 um 16:36 Uhr

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


    • Jan
      am 14. Mai 2010 um 17:58 Uhr

      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.


    • Paloran
      am 14. Mai 2010 um 21:21 Uhr

      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


    • Harald
      am 15. Mai 2010 um 01:23 Uhr

      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.


    • KingCrunch
      am 16. Mai 2010 um 10:05 Uhr

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


    • Nikita Popov
      am 16. Mai 2010 um 17:59 Uhr

      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.


    • Nikita Popov
      am 16. Mai 2010 um 18:02 Uhr

      Übrigens:
      http://weierophinney.net/matthew/archives/237-A-Primer-for-PHP-5.3s-New-Language-Features.html
      zufolge ist __invoke sogar schneller als eine normale Methode. zugegebenerweise habe ich das aber nicht selbst geprüft.


    • Sven
      am 17. Mai 2010 um 09:46 Uhr

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

      }


    • KingCrunch
      am 17. Mai 2010 um 19:36 Uhr

      @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 ;)


    • Julian
      am 23. Mai 2010 um 00:11 Uhr

      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(‘..’).

    RSS Feed für Kommentare zu diesem Artikel. TrackBack URL

    Hinterlasse einen Kommentar

    Werbung
    PHP Magazin
    Ausgabe 02/2010

    Dieses Mal mit Artikeln zu den Themen OpenSocial und Apache Shindig, Graphentheorie, Smarty3

    t3n
    Ausgabe 19

    Social Media (R)evolution. Weitere Themen sind noSQL, Crowdsourcing ...

    PHP Journal
    Ausgabe 2/2010

    PHP & Windows optimal nutzen, die besten PHP-CMS im Überblick, Google-API mit Zend Framework nutzen.

    Wir wurden schon öfters gefragt, ob man uns nicht irgendwie unterstützen kann. Die Antwort war immer einfach: Klar! Am einfachsten ist es eure nächsten Einkäufe bei Amazon über unsere Link abzuwickeln. Damit würdet ihr uns schon sehr helfen. Über Co-Autoren freuen wir uns aber noch mehr.