php_eval

Annotations in PHP

So ziemlich jeder ist schon einmal auf die sogenannten Annotations in PHP-Klassen gestoßen: Metadaten zu Methoden oder Properties, welche zur Laufzeit wie von Zauberhand plötzlich Bestandteil von PHP-Code werden können. Besonders bekannt sind die Annotations von phpDocumentor, welche zu einer kleinen Dokumentation des Codes ausgewertet werden können. Die Annotations zu einer Methode sehen bei phpDocumentor zum Beispiel so aus:

class Foo
{
    /**
     * @param mixed $baz
     * 
     * @return string
     */
    public function bar($baz)
    {
        return (string) $baz;
    }
}

Nicht nur bei einer Dokumentation ist phpDocumentor hilfreich. Viele IDEs nutzen die in den Annotations hinterlegten Werte der Typen auch um bei der Autovervollständigung die richtigen Methoden oder Properties vorzuschlagen.

Im Gegensatz zu anderen Sprachen, wie Java oder C#, gibt es in PHP so etwas wie Annotations eigentlich gar nicht in der Form, sondern sie werden in Kommentaren geschrieben. Wenn es keine Funktionalität gibt, welche diese Kommentare ausliest, haben diese auch keinen Einfluss auf die Klasse oder den Code. Außerdem wird diese Form der Annotations nicht in einfache Kommentare geschrieben, sondern in sogenannte DocComments, welche im Vergleich zu den regulären mehrzeiligen Kommentaren mit zwei Asterisks (/**) beginnen.

Wie greift man auf diese Informationen zu, die eigentlich in PHP kein Code sind?

Eine der geläufigsten Methoden auf diese Kommentare zuzugreifen, ist über die Reflection-API. Mit Reflection ist es unter PHP möglich, über Reverse-Engineering auf Bestandteile von Klassen und Objekten zur Laufzeit zuzugreifen und diese auch abzuändern. Hier bietet PHP seit Version 5.1 die Funktionalität die DocComments von Methoden, Klassen, Objekten und Eigenschaften (Properties) auszulesen.

class Foo
{
    /**
     * @param mixed $baz
     *
     * @return string
     */
    public function bar($baz)
    {
        return (string) $baz;
    }
}

$o = new ReflectionClass('Foo');
var_dump($o->getDocComment());

$bar = $o->getMethod('bar');
var_dump($bar->getDocComment());

Im oberen Beispiel greifen wir sowohl auf die DocComments der Klasse Foo als auch auf ihre Methode bar() zu. Im Fall der Methode bekommen wir den kompletten DocComment über var_dump ausgegeben.

string(69) "/**rn     * @param mixed $bazrn     *rn     * @return stringrn     */"

Im Fall der Klasse Foo, die keinen DocComment hat, liefert getDocComment() false zurück.

bool(false)

False ist übrigens auch der Rückgabewert wenn es sich um einen herkömmlichen mehrzeiligen Kommentar handelt, der nur mit einem Asterisk beginnt.

Jetzt wo wir den DocComment der Methode haben, beginnt das eigentliche Problem: Das Parsen des Strings. Dabei muss auch bedacht werden, dass wir hier bisher nur eine Form der Annotation gesehen haben, nämlich die von phpDocumentor. DocComments sind einfache String-Kommentare und sind an keine Form gebunden. Es werden Kommentare für Annotations zweckentfremdet. Wer also selbst Annotations erstellen möchte, muss sich auch darüber Gedanken machen, wie diese Kommentare aussehen müssen. Und was passiert nach dem Parsen? Wird ein Objekt mit dem Annotation-Namen erstellt? Wenn @ nicht wirklich ein Standard ist, muss ich diesen dann beibehalten? Oder bekommt PHP irgendwann doch eine eigene Form von Metadaten? Wie sieht die Form von Annotation-Parametern aus? Erst hier beginnt einem Entwickler klar zu werden, wie viel Aufwand eigentlich in die Umsetzung einfließen muss.

Doctrine Annotations

Wer sich die Arbeit sparen möchte und wen Abhängigkeiten zu anderen Projekten nicht stören, kann auf eine Komponente des Doctrine Projekts zurückgreifen: Doctrine Annotations.
Wer schon Doctrine in seinen Projekten verwendet hat, kennt die Möglichkeit, in den Annotations von Entities Datenbanktabellen abzubilden und mit Hilfe des Doctrine ORM auf einfache Weise Daten abfragen zu können. Das Lesen der Annotations selbst ist als eigenes Projekt entkoppelt und kann auch für die Implementierung eigener Annotations verwendet werden.

Hier ein sehr einfaches Beispiel, wie dies funktioniert.

use DoctrineCommonAnnotationsSimpleAnnotationReader;

class Foo
{
    /**
     * @AwesomeAnnotation(defaultValue="bar")
     */
    private $bar;
}

/**
 * @Annotation
 */
final class AwesomeAnnotation
{
    public $defaultValue;
}

$ref = new ReflectionClass('Foo');
$props = $ref->getProperties();

$parser = new SimpleAnnotationReader();

foreach($props as $prop) {
    $annotations = $parser->getPropertyAnnotations($prop);
    foreach($annotations as $annotation) {
        // Do something with Annotation Object
        var_dump($annotation);
    }
}

Die Klasse SimpleAnnotationReader bietet Funktionalitäten für die Erstellung und Befüllung von Annotation-Klassen, die ein Format besitzen wie man es von Doctrine Entities her kennt. Wichtig ist hier zu erwähnen, dass in diesem Code-Beispiel alle passenden Annotations durchlaufen werden. Wer also möchte, dass nur die eigenen Annotations von dem Code beachtet werden sollen, muss hier im einfachsten Fall mit einer Whitelist arbeiten.

In der ersten foreach-Schleife werden alle Properties aus der Reflection von Foo durchlaufen und zu jedem enthaltenen Property die dazugehörenden Annotations als Array zurückgegeben. In der zweiten foreach-Schleife können dann die erstellten Annotation-Objekte verarbeitet werden. Im Beispiel wird nur eine Ausgabe mit var_dump() durchgeführt.

object(AwesomeAnnotation)[13]
  public 'defaultValue' => string 'bar' (length=3)

Im erstellten AwesomeAnnotation-Objekt wurde der Wert aus der Annotation des bar-Property übergeben. Bei der Erstellung der Annotation-Objekte sollte auf jeden Fall darauf geachtet werden, dass wirklich nur die Klassen verwendet werden können, die zu eurem Projekt zählen. Eventuell müsste hier noch geprüft werden, aus welchem Namespace die Annotation-Klassen stammen.

Mit Annotations können Klassen um Metadaten erweitert werden, welche zur Laufzeit verschiedene Funktionen übernehmen können. Ob diese für ein Feature sinnvoll sind, muss der Entwickler selbst entscheiden.

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

Benachrichtige mich zu:
avatar

wpDiscuz
Weitere Beiträge zum Thema
Extension: Hybrid Multilanguage Tree
JS-Error in IE, Totalabbruch
Timeout der Backend-Session ändern
IT Fachanwalt Timo Schutt beim 19. E-Commerce Stammtisch