Facebook
Twitter
Google+
Kommentare
37

Funktionen aufrufen, nur falls sie auch existieren

Als erstes gibt’s heute noch mal eine Erinnerung an unser Gewinnspiel. Für einen Backlink könnt ihr noch ein Planungspokerspiel gewinnen, Es sind noch ein paar von unseren 50 da. So genug der Eigenwerbung. Wieder zurück zum Thema.

Ich habe heute kurz mit Frank Bültge, meinem WordPress Guru, gesprochen, weil ich wissen wollte, wie er ein bestimmtes Plugin umsetzen würde. Natürlich hatte er auch gleich ein Beispiel zur Hand. Davon werdet ihr die nächste Zeit bestimmt auch noch hören, denn da habe ich noch was schönes vor. Was mir aber aufgefallen ist, ist dass ganz häufig in WordPress Plugin Quellen so was wie

if ( function_exists( 'doSth' ) { doSth( $arg1, $arg2 ); }

vorkommt. Das ist auf jeden Fall der saubere Weg, denn man möchte ja nicht, dass der Blog kaputt ist, nur weil ein Plugin ausgeschaltet wurde und ich eine Methode davon einbinde. Irgendwie ist das aber trotzdem duplizierter Code und das mögen wir ja gar nicht.

Spontan kam mir da heute eine Idee, wie man das testen für solche einfachen Fälle, wie oben beschrieben, machen könnte, ohne dass mir PHP da wild Fehler wirft. Das soll hier nur ein Proof of Concept sein. Keine Ahnung, ob ich oder jemand anderes es mal produktiv einsetzen wird. Seht es einfach als Denkanstoß.

Was müssen wir also machen? Eigentlich nicht viel, denn dank der magischen Methoden und PHP 5.3 wird einem eigentlich schon das meiste Out-of the-box geliefert. Wie würde ich mir einen nicht-doppelten Code vorstellen? So:

ifExists::doSth( $arg1, $arg2 );

Der Trick daran ist, dass man eine ifExists Klasse bastelt, die wie folgt aussieht:

class ifExists
{
  public static function __callStatic( $functionName, $args )
  {
    if ( function_exists( $functionName ) ) {
      call_user_func_array( $functionName, $args );
    }
  }
}

Das ganze funktioniert nach folgendem Schema. Da die statische Methode doSth in der Klasse ifExists nicht existiert, wird die magische Methode __callStatic aufgerufen, um den „Fehler“ abzufangen. Was wir jedoch machen ist den Aufruf an die global gültige Funktion weiterzuleiten, falls diese existiert. Fertig. Was anderes müssen wir gar nicht mehr machen, Außer vielleicht die Klasse dem System bekannt machen, aber dass könnte ja WordPress übernehmen, die sind ja eh ein Freund von so Dingen.

Ü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

37 Comments

  1. Hehe, endlich weiß ich, wieso der WordPresscode voller „if function_exists()“ ist.
    Sonst hab ich das noch in keinem Projekt gesehen und benutze es für meine Sachen auch nicht.

    Reply
  2. Also ich weiss nicht..

    Ich habe auch schon öfters so einen Code geschrieben, um z.B. auf einem ZendPlatform Server monitor_custom_event() aufzurufen ohne dabei auf meinem lokalen Entwicklungssystem ohne ZP einen Fehler zu produzieren.

    Trotzdem kommt mit diese magische Klasse unschön vor.

    In Javascript ginge es soweit ich mich erinnere mit

    var result = funktionDieEsVielleichtNichtGibt|function(){}();

    aber ist das schön? An einer solchen Stelle kann ich (auch in PHP) besser mit einer Redundanz im Code leben, als mit einem Konstrukt, das sich im mir verschliesst, weil es sich versteckt.

    Reply
  3. Na also ich weiss auch nich. Es ist auf jeden fall ne sehr nette Methode um bei so boliden Frameworks wie JQuery die Module gegenseitig abzusichern, gegen eine Portierung auf PHP sträube ich mich jedoch etwas. Ich meine ich entwerfe eine App auf Basis – PHP, und setze dann, falls gegeben, andere Module vorraus. Entweder wird sonst ein Teil oder es als ganzes nicht ausgeführt / buggy…
    Für sowas schreib ich doch installer, für sowas gibts phing… Für CMS-Apps vielleicht ganz nützlich aber für den Enterprise-Betrieb definitiv schon rein theoretisch total inakzeptabel.

    Reply
  4. Wieso kommt dir denn die Klasse unschön vor? Sie zentralisiert einen wichtigen Funktionsaufruf (an und für sich), aber wirkliche Ersparnis beim schreiben ist das sicherlich nicht 🙂 Da sieht man halt, wie weit man OOP treiben kann, wenn man will 😉

    Reply
  5. Also unschön finde ich es auch nicht. Falls soetwas als Sprachkonstrukt existieren würde, würde sich auch niemand aufregen, dass es einem verschließt. Das bedeutet nicht, dass ich es einsetzen würde, aber das ist wohl alleine dem geschuldet, dass ich kaum noch prozedural programmiere.

    Mir kam grad übrigens noch die idee etwas wie cached::doSth( ) zu bauen. Mal schauen, ob ich da auch noch einen Proof „raushaue“.

    Reply
  6. Ich weiß, dass euer zentrales Ziel ist, Fehler NICHT zu melden, andererseits… Fehlermeldungen sind Freunde, das wird oft vergessen.

    Davon abgesehen, dass ich bei so ziemlich allen magischen Methoden außer toString und construct ein schlechtes Gewissen habe, würde ich dann doch zumindest noch einen ausgefeilten Debug-Mechanismus mit einplanen (klar das hier ist noch nicht fertig), um Fehler leichter zu finden, wenn mal etwas doch nicht läuft.
    Man muss selbstverständlich die Vorteile sehen, aber es wird aus meiner Sicht schon zu einem großflächigen Spiel mit dem Feuer, wenn man so etwas stark einsetzt.

    Reply
  7. Find ich auch net sooo gelungen. Das fängt schon damit an, das (mal wieder) Funktionen krampfhaft in Klassen verpackt werden, nur weil mans kann 😉

    function call ($callback, $arg = null) {
    $args = func_get_args ();
    array_shift ($args);
    if (is_callable ($callback)) {
    return call_user_func_array ($callback, $args);
    }
    }
    echo call(‚doSth‘, $v1, $v2);

    Ich seh das wie metalguy: Wenn ich das zu häufig wirklich brauche, dann vertrau ich ja meiner eigenen Anwendung nicht und gebe irgendwie zu, dass ich meine Anwendung nicht genug teste, um diesen Fall a priori auszuschließen.

    Wenn schon Abhängigkeiten zwischen WP->Plugins, Plugins->WP und/oder Plugins->Plugins existieren und sicher gestellt werden müssen, dann ist es doch eleganter _einmalig_ die Existenz und die Version zu prüfen, anstatt alle Nase lang alles, was man gerade so braucht.

    Reply
  8. Ich muss mich hier KingCrunch anschließen: wenn man schon wie bei WordPress oder Drupal alles in Function-Orgien verpackt, dann sollte man auch bei Functions bleiben. Dafür braucht es auch kein PHP 5.3.

    @Nils der Vollständigkeit halber solltest du in deinen Code auch ein „return“ einbauen 😉

    Reply
  9. Ich habe das mit den Plugins etwas anders (sauberer?) gelöst. Ich scanne das Pluginverzeichnis on-demand und hole mir für jede gefundene Klasse eine Reflection. Mit deren Hilfe kann ich die verfügbaren Klassen, Methoden, Parameter, Annotations, Beschreibungen und wo sie sich befinden auslesen. Das speichere ich in ein Repository, welches natürlich gecached wird.

    An das Repository angeschlossen ist ein Dispatcher. Dem übergibt man den Namen der gewünschten Funktion und die Parameterliste. Der Dispatcher prüft die Parameter auf Vollständigkeit und Korrektheit, lädt automatisch die notwendige(n) Klasse(n) nach und führt den Broadcast aus. Er fängt Exceptions ab und leitet sie in ein Error-Log. Das Ergebnis der Funktion liefert er zurück.

    Reply
  10. Wirklich sauber löst man das nur durch eine interne API, ich mein wer auf function_exists() mittels OOP zurückgreift der kann auch gleich sein Projekt voll OOP entwickeln und die entsprechenden Schnittstellen implementieren.

    Reply
  11. @Nils Dein Konstrukt hat noch einen anderen Nachteil: man hebelt IDE, statische Codeanalyse et cetera alles aus. Nicht davon zu reden, dass man Nutzereingaben natürlich weit fern davon halten muss.

    Du solltest das auf ganz bestimmte Klassen und Funktionen beschränken. Ansonsten ist es nicht handhabbar.

    Zum Beispiel auf Klassen, die ein vorgegebenes Interface implementieren. Dort kann man eine Klasse als Fassade vorschalten, welche sich dynamisch die passende Implementierung sucht. Dann kennt man zumindest die Ein- und Rückgabewerte ganz genau und bricht nicht mit den bekannten Analysetools.

    Reply
  12. Mit einer halbwegs ordentlich gestalteten modularen Projektstruktur sollte das m.E. nie benötigt werden. Und damit meine ich nicht, man müsse immer ewig seine Projekte planen.

    Reply
  13. Noch eine böse alternative: Statt jedesmal beim Aufruf zu prüfen ob die Funktion da ist – was bei mehrfachen Aufrufen recht teuer ist initial einmal sowas:

    foreach (aaray(‚func1‘, ‚func2‘, /*…*/) as $func) {
    if (!function_exists($func) {
    eval(„function $func() {}“);
    }
    }

    Wäre antürlich schöner, wenn Fuktionen in PHP first class citizens wären uns sowas ginge:

    foreach (aaray(‚func1‘, ‚func2‘, /*…*/) as $func) {
    if (!function_exists($func) {
    eval(„function $func() {}“);
    }
    }Und ja. Das ist böse und sollte nicht gemacht werden.
    Aber anhand von bestimmten Funktionen verfügbare Features zu erkennen und das dann evtl. (nicht) aufzurufen wirkt für mich nach ner kaputten architektur wo irgendein Signal-Handling/Hook-Mechanismus angebracht wäre …

    Reply
  14. ups, da hat sich der Copy&Paste Teufel eingeschlcihen, das zweite Beispiel war sowas wie

    if (!function_exists($func) {
    $func = function() {}
    }

    wobei $func = „foo“ dann mit der funktion foo identisch sein müsste .. was mit der $-Notation nicht klappt usw. usf. 🙂

    Reply
  15. Ich sehe das ähnlich wie Tom. Nur um 15 Zeichen zu sparen verzichte ich nicht auf Codeanalyse, in der IDE das „Draufklicken“ auf den Funktionsnamen usw.
    Mit der Lösung kann man auch nur, wenn ich micht nicht irre, Funktionen behandeln. Aufrufe wie method_exists() müßte man nach wie vor auf die „alte“ Tour machen.

    Nette Idee, aber hat meiner Meinung nach zuviele Nachteile und keine erkennbaren oder messbaren Vorteile.

    Reply
  16. @Tom: Den Weg findest du wirklich schön? Ich verstehe den Ansatz noch nicht mal wirklich, was darauf deuten könnte, dass er zu komplex ist, oder? Oder halt, dass ich aufm Schlauch stehe … was ich nicht ausschließen würde.

    Reply
  17. @Johannes: Dein zweites Beispiel geht in 5.3

    @Tom: Funktionen profitieren auch sehr stark von 5.3. ZB kann es einen an vielen Stellen egal sein, ob es eine Funktion, oder ein Objekt mit Methode ist, wenn man Closures verwendet. Lambdas sind auch cool (siehe Johannes‘ zweites Beispiel).

    @Tom2: Wo hebelt das die statische Code-Analyse aus? Die eigentliche Funktion würde man ja trotzdem testen, und zwar nicht über die den „call“-Umweg.

    @PHPGangsta: Wieso sollte etwas Vergleichbares nicht für Methoden funktionieren können?

    echo call(array($object, ‚myMethod‘), $arg1, $arg2);

    Dass die IDE nicht mehr selbst vervollständigt, halte ich übrigens für nen vernachlässigbares Problem 😉 Ausserdem kann man ihr per „@var“ auch Tipps geben, so dass es wieder funktioniert.

    Reply
  18. Also ich denke, dass man function_exists u.ä. Dinge in der OOP nicht wirklich benötigt. Man definiert ein interface und die entsprechende Klasse implementiert dieses. Dadurch kann man vorgeben, ob eine Methode existiert oder nicht.

    Und dann kann ich auch mit instanceof gegen das Interface prüfen. So erhalte ich über diesen Weg eine Zusicherung, ob eine Methode existiert.

    Reply
  19. Ein weiteres mal frage ich mich, wieso ihr nicht gleich ne richtige OOP-Sprache verwendet, wenn ihr PHP schon auf OOP reduziert 😉 Mit exessiven Einsatz von Type-Hints hätte man auch gleich die Typ-Schwäche ausgehebelt, jetzt noch „HipHop for PHP“ und wir brauchen Java garnicht mehr 😀

    Was ich sagen will: Natürlich ^kann^ man rein OOP programmieren und alles mit Interfaces, Type-Hints und instanceof absichern, aber damit nimmt man sich meines Erachtens einen großen Teil der Fähigkeiten, die einen PHP an die Hand gibt. Rein statische Klassen sind sowieso ein völlig unnötiges, künstliches Konstrukt, nur um hinterher zu behaupten, es wär OOP ;P

    Reply
  20. Stimmt ja, mit type hinting brauche ich das häßliche instanceof sogar rauswerfen 😀

    Statische Methoden kann man ja auch noch benutzen, um sich Schreibarbeit zu sparen. Und man muss kein dummy-Objekt instanziieren, dass man danach eh nicht mehr braucht. 😛

    Java? Wer braucht denn schon Java? 😉

    Reply
  21. Als erstes: Danke für die Blumen, zähle ich mich doch nicht zu den Codern, da mein Wissen rein autodidaktisch entstanden ist und ich noch viel zu lernen habe.

    Zum Artikel: ich nutze function_exists() grundsätzlich nie um auf Funktionen anderen Plugins in WP zu prüfen. Ich mache dies nur, wenn ich Funktionen einbinde, die im Laufe der zeit durch andere ersetzt wurde, so dass ich einem gewissen Rahmen abwärtskompatibel bleibe. Plugins prüfe ich über einen Schlüssel im WP, wo man alle aktiven Schlüssel findet.

    Reply
  22. @Nils: das war nicht böse gemeint, nur als Hinweis und damit helfe ich immer gern, egal ob du oder andere Leute – es ist eher ein Zeitproblem. Vielleicht gibt es ja eine andere bessere Lösung und ich habe die Redundanz nicht mehr drin.

    Reply
  23. Eine (un)schöne Lösung für ein unschön kleines Problem, das durch eine unschöne Architektur entstanden ist.

    Will heißen: Deine Lösung liest sich gut, hat aber einige gewichtige Nachteile – wie bereits mehrfach erwähnt.

    Reply
  24. Ei, da lag ich leider falsch, da das Script so abgebrochen wird wenn die Funktion nicht existiert. *flööt* Ich hätte nichts gegen eine Löschung des Beitrags. 😉

    Dennoch sehe ich keinen Sinn darin functions_exists in einer Klasse zu kapseln, da nicht zu erwarten ist, dass sich an der grundsätzlichen Aufgabe der Funktion etwas ändern wird oder diese zentral um weitere Aspekte erweitert werden muss. Und das erledigt die simple Funktion function_exists() eben schneller, als der Aufruf einer statischen Klassenmethode.

    Reply
  25. Was mir dabei aufgefallen ist:

    Ich rufe eine Funktion ja meist nicht auf, weil es ganz nett wäre, sie aufzurufen, sondern weil ich sie brauche. Das heißt, wenn es sie nicht gibt, muss ich etwas tun. Sei es einen Fehler ausspucken, eine Exception werfen oder eine andere Funktion verwenden. Und das berücksichtigt diese Lösung hier nicht. Wenn ich mache if(function_exists()) kann ich per else irgendwas anderes tun.
    Einfach im Code weiter zu gehen kann fatale Folgen haben, weil ich ja i.d.R. davon ausgehe, dass die Funktion etwas tut.

    Was das Cachen angeht: Verlockende Idee, so ein Universalcache. Aber wie lange ist der Cache gültig? Was, wenn die Funktion außer einer Rückgabeberechnung noch etwas anderes tut, wie eine Datei schreiben? Was, wenn die Funktion etwas zeitabhängiges tut? Das muss man immer individuell machen mit dem Cachen, ein individueller Cache nutzt nur für einfache Fälle was.

    Reply
  26. Im genannten Kontext geht es ja darum PlugIns anzusteuern. Wenn diese nicht erreichbar sind, sollte das die Core-Funktionalität des Hauptskripts nicht beeinflussen, so dass die Vorgehensweise in Ordnung sein sollte.

    Sicherlich könnte man jetzt einen Fall konstruieren, wo ein PlugIn von einem anderen abhängig ist, das übergeordnete nicht verfügbar ist und es dadurch im abhängigen PlugIn knallt. Aber dann sollte das abhängige PlugIn ebenfalls Prüfmethoden für das Vorhandensein des Haupt-PlugIns besitzen, welche dann greifen und es deaktivieren sollten.
    Insofern betrachte ich die Vorgehensweise der Prüfung mit function_exists als legitime und schnelle Möglichkeit.
    Just my 2 Cents.

    Reply
  27. Hm…also nachdem ich mir die ganze Diskussion durchgelesen habe, erschließt sich für mich immer noch nicht der Nutzen dieser eigentlich interessanten Idee.

    Fazit bleibt für mich:
    1. Fehlermeldungen haben Ihre Daseinsberechtigung und sind beim Debuggen unverzichtbar (siehe Julians Kommentar).
    2. Die Funktion soll etwas tun (siehe Christopher K.)! Ich unterdrücke einen Fehler und meine Daten sind dazu noch unbrauchbar.
    3. Gerade bei einem Plugin kann bei der Installation auf die Systemvoraussetzungen überprüft werden (metalguys Kommentar). -> http://php.net/manual/function.version-compare.php

    Reply
  28. Andi:
    1. Wenn eine Funktion nur optional aufgerufen werden soll, wäre ihr Fehlen kein Fehler.
    2. Siehe 1 😉 zB Widgets: Wenns fehlt, fehlts, aber gefährlich isses nicht

    Reply
  29. Von Objektorientierung ist in dem Zusammenhang selbstverständlich nicht zu reden. OOP befasst sich mit Objekten (also Instanzen von Klassen) und weniger mit derlei statischen Konstrukten. Das ist oft eine Hintertür in meinen Augen.
    Genau wie einige andere, würde ich in diesem Zusammenhang Fehlerbehandlung kritisieren – die ist nämlich nicht mehr gegeben. In welchem Zusammenhang darf man das Konstrukt verwenden? Nur mit Aufrufen von Plugin-Funktionen? Was ist, wenn ich es mit anderen Funktionen in Verbindung bringe .. schon entstehen Fehler.
    Ich habe vor nicht all zu langer Zeit meinen Professor gefragt, wieso es (in Java) möglich ist, dass eine Instanz einer Klasse auf ein privates Attribut einer anderen Instanz derselben Klasse direkt zugreifen kann, wo man doch ohne direkten Zugriff und über Getter-Methoden eine viel bessere Kapselung erzwingen könnte. Die Antwort war in diesem Zusammenhang: „Programmcode soll sich nicht vor sich selbst schützen / kapseln, sondern vor äußeren Systemen“ (sinngemäß). Damit möchte ich sagen, dass es nicht Sinn und Zweck ist, in einer Anwendung jede mögliche Fehlerquelle, die durch Fremdentwickler entsteht, auf Herz und Nieren zu prüfen. Plugins müssen nunmal korrekt entwickelt werden – es darf vor der Installation des Plugin durchaus eine Überprüfung stattfinden, aber eine vollständige Kapselung ist schier unmöglich, so dass immer Fehler auftreten können. Es ist also Aufgabe der Entwickler der Plugins, dieselben so zu entwickeln, dass sie den selben Standards wie die Anwendung selbst entsprechen.
    Man muss hier eben ein Mittelmaß finden und meines Erachtens geht es schon zu weit, jede Funktion eines Plugins auf Existenz zu prüfen, denn man sollte davon ausgehen können, dass, wenn ein Plugin installiert wurde, entsprechende Funktionen zur Verfügung stehen. Dann muss man eben nur korrekte Abhängigkeiten beachten.
    Übrigens weiß die Kern-Anwendung gar nicht von möglichen Funktionen, die ein Plugin zur Verfügung stellt. Nur Plugins können voneinander abhängen und optionale Funktionen aufrufen. Die Kern-Anwendung dagegen sollte ohne Plugins auskommen. Demnach ist es – unter Vorraussetzung, dass ein korrekt eingebundenes Plugin die Funktionen bereitstellt – eine Frage der Abhängigkeit von Plugins, ob ein Plugin eine bestimmte Funktion eines Anderen aufrufen kann. Kern-Funktionen sollten ja vorhanden bleiben.

    Reply
  30. Hallo, ich muss KingCrunch Recht geben. In PHP kann man viel anstellen, aber nicht alles ist sinnvoll. Ich finde, was man auf eine Weise in PHP aber nicht auch in anderen Sprachen umsetzen kann, sollte man sich zweimal überlegen. Z.B. ist Late Static Binding zwar auf den ersten Blick recht nützlich, aber jeder weiß, dass man für Vererbung/Polymorphie normalerweise ein Objekt benötigt (C++ Entwickler errinnern sich vielleicht an die vtable).
    Durch das richtige Designmuster wird man nie auf Late Static Binduing angewiesen sein. Hinzu kommt, dass ich kein Fan von „Global State“ bin und somit statische Dinge sowieso vermeide. (Kann hier nur empfehlen: http://www.youtube.com/watch?v=-FRm3VPhseI)
    Um zum Beitrag noch was loszuwerden: Wieso setzt man kein Eventsystem a la Observer ein? Ein Plugin-System auf Funktionsbasis ist meiner Meinung nach keine gute Wahl, denn wie können sich hier mehrere Plugins für eine Aufgabe registrieren?

    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