Facebook
Twitter
Google+
Kommentare
18
Willkommen bei "the web hates me". Mittlerweile hat unser Team ein tolles neues Monitoringtool für Agenturen gelauncht. Unter dem Namen koality.io haben wir einen Service geschaffen, der er Agenturen ermöglicht mit sehr geringen Kosten alle Ihre Webseiten zu überwachen.

It’s magic!

Ich habe so das Gefühl, dass die Verwendung von magischen Methoden und anderen „Sauereien“ im PHP-Umfeld immer weniger werden. Ich liebe die Richtung in die das geht. Aber wenn die einen Sachen, die die IDE nicht mitmacht schon abschafft, muss man ja dafür sorgen, dass es neue Dinge gibt, die zum Beispiel einen Qualitätsmanager zum Verzweifeln bringt.

Annotations sind so eine Neuerung, echt wunderbar und einfach, aber wenn es später darum geht den Lauf durch den Code nachzuvollziehen, wird es schwer. Ich hatte am Wochenende eine Idee, wie man das ganze noch auf die Spitze treiben könnte und habe auch gleich einen Proof of Concept dazu gebaut. Technisch möglich wäre es also.

Was will der Mann aus Hamburg also machen? Schauen wir uns den folgenden Code an:

namespace phmLabs;

Class A
{
  /**
   * @Notify(before="AnEvent", after="AnotherEvent")
   */
  public function b( )
  {

  }
}

Es wäre wunderbar, wenn jedes mal, wenn ich auf einer Instanz von A die Methode b aufrufe ein Event gefeuert wird, ohne dass ich mich im Code da wirklich drum kümmern muss. Was muss man also machen? Irgendwie muss ich zwischen den Aufruf der Methode und dem tatsächlichen Abarbeiten kommen und dort meinen Event-Dispatcher reinpacken. Klingt unmöglich? Naja mit genügend Schweinereien geht das.

Fangen wir also mit dem Bauplan und dem Vorgehen an.

  1. Wir müssen dem System sagen, dass die Annotation @Notify bekannt ist. Dies macht man zum Beispiel über Doctrine Common (schöne Annotation Implementierung, die ich diese Woche noch vorstellen werde).
  2. Jetzt kommt der fiese Hack. Wir schreiben uns eine Autoload-Methode (oder einen Stream, danke Stefan), die beim Laden der Klasse A diese Klasse so umbaut, dass vor dem Aufruf von b die Events gefeuert werden.
  3. Wir laden also die Klasse einfach über file_get_contents, statt mit include_once und speichern diese unter A_overwritten und hauen ein eval drauf. Jetzt müssen wir noch eine neue A-Klasse erstellen, die sich von A-overwritten ableitet. Dort überschreiben wir die Methode b und bauen die Event-Notification ein. Fertig. (Für private Methoden muss man ein wenig mehr Aufwand betreiben)

Wenn jemand das ganze jetzt noch in schön bauen würde, dann fände ich es eine tolle Komponente, die bestimmt einige einsetzen würden. Also wer Lust hat das Konzept zu verfeinern, der meldet sich bei mir. In meinem Kopf hätte ich da schon eine sehr saubere Lösung, die auch noch OOP wäre.

Also ich finde es eine wunderbare Sauerei und wäre gespannt, wie sich das Programmieren mit so etwas anfühlt. PS: Ich liebe PHP.

Ü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

18 Comments

  1. Welchen Vorteil soll das haben? Ob ich nun gezielt ein Event ins System schicke oder das über eine aufwändigere Verarbeitung mit Annotations mache. Den Auslöser braucht es so oder so. Und die spätere Auswertung, sprich wer darauf reagieren soll, ist doch wohl identisch. Mir fehlt die Flexibilität und der gewisse Mehrwert.

    Reply
  2. Grundsätzlich finde ich die Annotations ne feine Sache, aber:

    Annotations (in Java) waren irgendwann mal dafür gedacht trivialen Code, der sich aus dem übrigen Code ergibt zu notieren. Also eine Art Metadata über den Code. Leider werden immer mehr Funktionalitäten darüber abgebildet. Annotations bilden also keine Aussage mehr über den Code sondern eigenen Code.
    Gerade dass ist doch sehr viel „Black Magic“.

    Soviel mal dazu. In Symfony finde ich den Gebrauch von Annotations doch sehr unterschiedlich:

    1. Für die Template-Angabe sehr ordentlich, da Templates sich meist an Controller/Methodennamen orientieren.

    2. Für die Angabe von Routen sehe ich es sehr Grenzwertig. Routen sind nicht Dinge die sich notwendigerweise direkt aus meinen Code ergeben. Bei den Methoden sind die Annotations Metadaten, wenn sie im Stile von „@extra:Route( „/edit“ )“ für eine editAction-Methode gehalten sind. Ob ich aber bei den Routing-Angaben des Controllers *immer* direkt aus dessen Context auf die Route schließen kann ist fraglich. (Wird aber wohl in 90% der Fälle auch zutreffen)

    3. Um von der Routing-Information direkt auf eine Instanz zu schließen ist aber definitiv keine Metadaten-Angabe mehr. Betrachten wir folgende Methode:
    /**
    * @extra:Route(„/edit/{id}“)
    */
    public function editAction( Document $doc )
    {
    // edit $doc
    }
    Obwohl die Route angibt, dass ich eine Variable mit dem Namen $id brauche bekomme ich diese nicht. Stattdessen bekomme ich eine Document-Instanz mit der ID $id. Das ist keine Metadaten-Angabe mehr, das ist Funktionalität. Auch wenn diese Art der Annotation praktisch ist, bleibt es eben keine Metadatum mehr sondern wird zu „Black Magic“.

    Noch mehr Funktionalität in die Annotations reinzupacken ist bedenklich.

    Zusammengefasst: Annotations sind super, können aber wieder „Black Magic“ einführen; Gerade die wollte man entfernen. Bevor jeder sich auf die Annotations stürzt (wie ich das schon angefangen hatte 😉 ) sollte vielleicht nochmal über die Annotations diskutiert werden. Was sie sind, wo die Risiken sind und wofür man sie (nicht) einsetzen sollte.

    Würde mich über einen Artikel über Doctrine Common (Übrigens kleiner Typo bei Dir) freuen.

    Reply
  3. @Daniel: Zuerst einmal ist es mal wieder was neues und ich fände es spannend damit rumzuspielen, ob ich es für den produktiven Einsatz verwenden würde, weiß ich nicht. Kleiner Voreil wäre natürlich das vereinfachte Testen, ich müsste mir in den Unit Tests für diese Klasse keine Gedanken machen über Events. Das wäre dann Teil der Tests der AnnotationModifier. Vieleicht denkt man sich auch einfach andere Szenarien aus: Validierung per Annotation zum Beispiel. Also wie gesagt, einfach eine Spielerei und vielleicht kommt ja die ein oder andere gute Idee bei rum.

    @John: Dotrine kommt wohl am Freitag. Dein Punkt 3 kannte ich nicht, finde ich aber irgendwie nett und irritierend zugleich.

    @KingCrunch: Ich glaube flow3 (AOP-Framework) macht auch ein paar von solchen „Schweinereien“.

    Reply
  4. Hi,
    schau dir mal die Umsetzung von AspectJ an. Da muss man nicht einmal im Code der zu observierenden Klasse rummachen. Sehr viel schöner! Zum Thema autoload: Das geht ja nur dann, wenn man die Files nicht selber mit include lädt. Schade eigentlich, denn manche Projekte machen das halt einfach – egal ob man das toll findet oder nicht ;(

    VG aus dem northern Black Forest
    Timo

    Reply
  5. Ich mag Annotations, wenn man mit Ihnen Businesscode trennen kann von „anderem Kram“. Die AOP-Leute nennen den „anderen Kram“ cross cutting concerns.

    In unserem Web-Framework können wir Methoden mit Guards/Wächtern annotieren, die bestimmte Bedingungen sicherstellen, bevor die eigentliche Methode ausgeführt wird. Etwa so:

    /** @Guard(class = ‚LoginRequiredGuard‘, redirect=’LoginPage‘) */
    public function execute()
    {
    $smarty = new Smarty($this->config);
    $smarty->assign(‚remote_user‘, $this->remote_user);

    $smarty->display(‚archive.tpl‘);
    }

    Hier wird über den Guard „LoginRequiredGuard“ sichergestellt, dass der Nutzer in der Applikation angemeldet ist und sollte das nicht der Fall sein, wird auf die Login-Seite umgeleitet.

    Auf diese Weise kann ich meinen Businesscode sauber halten von Sicherheitsaspekten, die ich in anderen Klassen abhandeln kann.

    Reply
  6. Ach, mal wieder ein köstlicher Vorschlag. Ebenso schön dirty wie herrlich naiv und wunderbar unvollständig.

    Das Problem wird absolut sauber vom Mediator-Pattern gelöst.
    Leider hast du auch vergessen was passiert, wenn du mehrere Hooks registrierst. In welcher Reihenfolge werden die denn dann aufgerufen? Und was ist mit externen Abhängigkeiten? Was, wenn ich nicht immer alle Hooks ausführen will?

    Also, ich habe das Ganze unlängst über einen Mediator aufgelöst und das funktioniert bestens. Es wird technisch etwas aufwendiger, ist dafür aber eine saubere, stabile und vollständige Lösung.

    Ansonsten bin ich John’s Meinung. Annotations sind für Metainformationen gedacht. Sie sollten benutzt werden um den Code sauber zu halten. Nicht um damit neue „Magic“ einzuführen.

    Es gibt allerdings eine Anwendungsmöglichkeit: für PHPUnit-Tests. Wie hat es ein Kollege so schön formuliert: „Wenn eine Klasse partout nicht testbar ist, dann generieren wir sie doch einfach testbar.“

    Die Lösung: den Quellcode als Text laden, die Klasse live umgeschrieben und die umgeschriebene Klasse anstelle der echten laden. Falls man will, kann man dazu auch einen Stream-Wrapper benutzen. Damit konnte der Kollege zum Beispiel Klassen mit MySQL- und Mail-Funktionen testen, die hart im Code standen, oder Abhängigkeiten zu anderen Klassen entfernen, ohne den Code zu ändern.

    Bei Unit-Tests tut diese „Magic“ nicht wirklich weh. Wenn man sich überlegt wie viel Gefummel man sich dadurch bei den Mock-Objekten sparen kann – vor allem wenn man mit Klassen arbeiten muss, die nicht sauber programmiert sind – so ist das eine coole Anwendung.

    Reply
  7. Ach Nils, beziehe doch Kritik in Fachfragen nicht auf dich selbst. Die Idee ist ja nicht schlecht, nur eben nicht wirklich zu Ende gedacht.
    Stephan Hochdörfer hatte wie ich finde einfach die bessere Idee für eine Anwendung dieser Technik. Er macht etwas ähnliches wie du, aber eben schon deutlich länger. Seine Präsentation fand ich sehr interessant.

    Hier ist sein Vortrag zum Thema „Generative Programming“: http://www.slideshare.net/shochdoerfer/dpc10-testing-untestable-code

    Seine Idee, mit Code-Templates zu arbeiten, basiert auf dem gleichen Prinzip, funktioniert aber sogar bei prozeduralem Code und ist einfach genial.

    Was deine Anwendungsidee (Method-Hooks und Events) angeht, teile ich Daniel’s und John’s Meinung. Außerdem bin ich überzeugt, dass die bestehenden Patterns gut funktionieren und einfach eleganter sind als diese Magic. Zu dieser Ansicht bin ich gekommen, nachdem ich mit Senior Developern lange Zeit an solchen Projekten gearbeitet, einige solcher Systeme gesehen und eines davon selbst geschrieben habe.

    Ich finde Janek’s Beispiel sehr gut, weil das ein typischer Anwendungsfall ist. Bei unserer Lösung haben wir allerdings den Klassennamen weggelassen. Aus den gleichen Gründen, die Daniel erwähnte. Wir haben das durch eine Security-Kategorie ersetzt und diese Eigenschaft an Security-Plugins propagiert, wodurch man keinen Klassennamen hart kodieren muss. Das finde ich persönlich als Lösung noch sauberer.

    Also, der Grundaufbau ist ja immer gleich: Instanzen registrieren, Aufrufe an den Mediator übergeben und von dort an den Dispatcher weiterleiten, dann hast du genau was du wolltest. Damit bekommst du die volle Flexibilität, die Daniel bei deinem Ansatz vermisst hatte, und du bekommst einen sauberen Entwurf ohne die Kritikpunkte, die bereits John erwähnt hat.
    Das lässt sich alles mit den gängigen GoF-Patterns regeln.

    Dass Java das teilweise anders löst und neuen Quellcode hinein kompiliert hängt damit zusammen, dass dort schließlich ein Kompilieren des Quellcodes überhaupt stattfindet. Genau das haben wir in PHP in der Regel nicht. Dafür bräuchten wir eine Art Preprocessor, wie es ihn für C gibt. Das war auch einige Zeit im Gespräch, ist aber wohl wieder vom Tisch.

    Was passiert, wenn man das in Plain-PHP schreibt ohne Code-Cache, kann ich dir sagen: weil ich es schon getestet habe. Es passiert genau das gleiche, wie wenn du Firefox mit Plugins zuschüttest: es wird immer langsamer, weil immer alle Plugins geladen und durchsucht werden. Schon bei 35 dynamisch geladenen und live analysierten Klassen gingen 1-2 Sekunden pro Aufruf allein dafür drauf. Diesen Fehler habe ich also bereits gemacht und es war keine gute Idee! Lerne daraus. Mach es lieber so, wie es die Patterns vorsehen. Das funktioniert genauso gut, ist sauberer und um Längen schneller.

    In der Uni nannte man einen ersten Brain-Storming-Entwurf bei uns einen „naiven“ Entwurf, weil er auf „unvollständigen“ Annahmen beruht. Keine Ahnung wie man das bei euch an der Uni genannt hat, aber daher die Wortwahl.

    Reply
  8. @Tom: Ich komme aus der Javaecke und da waren Annotations für mich für dieses Problem zunächst die erste Wahl. Was ich an den Annotations mag — und besser finde als am Mediator-Pattern — ist die Übersicht. Ich kann die Klasse im Editor öffnen und sehe den Businesscode zusammen mit den Constraints, die durch die Guards sichergestellt werden.

    Wir nutzen das derzeit in einer relative kleinen Anwendung. Es kann natürlich sein, dass uns die Performance von Annotations und Reflection auf die Füße fällt. Aber dazu kann ich derzeit noch nichts sagen.

    Könntest Du einen Codeschnipsel aus Eurem Framework zeigen zum Vergleich?

    Reply
  9. @Tom: Wenn wir in der Uni „unsympathisch“ benutzt haben, dann meinten wir natürlich „weiter so und du bist einfach der Beste“. Fachlich habe ich übrigens mit deinen Beiträgen kein Problem, lerne gerne dazu und Erfahrungsberichte sind immer willkommen.
    PS: Der Stefan, den ich in meinem Artikel „zitiere“ ist auch dein Stefan.

    Reply
  10. @David: Aus unserem eigenen. Ich kann nicht sagen, ob ich den Code freigeben darf. Aber es ist keine komplizierte Sache. Bei uns gehen alle Routen durch eine Klasse, die zur Route den entsprechenden Controller findet. Bevor am Controller die execute()-Methode gerufen wird, werden die Annotations ausgewertet. Für das Auslesen der Annotations nutzen wir Addendum (http://code.google.com/p/addendum/).

    Reply
  11. Komische Art, Lesern die derart fundiertes Wissen mit uns allen teilen, so an den Karren zu fahren.

    @Tom: Danke für den Hinweis auf das Mediator Pattern!

    Reply
  12. @Nils: Klassischer Fail im Sinne des Kommunikationsmodells des Sender-Empfänger-Prinzips. Sollte man sich als Blogger ab und an vor Augen halten. Oder anders: Wie rum schreibst du ein „F“ auf deine Stirn?

    Nix für ungut. Ich musste grad etwas über einen Informatiker schmunzeln, der seinen Gegenüber angreift, um im wiederum erklären zu müssen, dass dies aus seiner persönlichen Tradition ein Zeugnis größten Respekts darstellt. 😉

    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