Artikelformat

Die Reflection API von PHP

Viele Frameworks arbeiten auch unter PHP mit Annotations oder Enums. Da fragt man sich oft, wie das denn möglich ist, da PHP diese Sprachkonstrukte gar nicht unterstützt. Hierfür gibt es eine API und eine entsprechende Implementierung, die die ganze Magie ausmacht. Es handelt sich dabei um Reflection. In anderen Sprachen ist der Begriff Introspection ganz beliebt und zu Deutsch heißt das ganze dann Reflexion. Ich bevorzuge Reflection, da man so einen direkten Bezug zur Implementierung herstellen kann.

Unter Reflection versteht man den Umstand, dass zur Laufzeit der Programmcode sich selbst kennt und lesen kann. Desweiteren kann er sich selbst modifizieren und somit eine Klassendefinition im Betrieb ändern. Man könnte es auch als Hot Code Replacement bezeichnen. Um mit Reflection in PHP zu spielen benötigen wir eine Klasse, die verschiedene Aspekte bietet, die in der täglichen Arbeit auftauchen. Diese Klasse heißt einfach A:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class A {
 
    private $var1;
    protected $var2;
    public $var3;
 
    const PI = 3.1415;
 
    public function __construct() {
        $this->var1 = "Test";
        $this->var2 = 2;
        $this->var3 = 2 * A::PI;
    }
 
    /**
     * Nice method to return some value
     *
     * @return int 2 time PI
     */
    public function getVar3() {
        return $this->var3;
    }
 
    private function getVar1() {
        echo $this->var1;
    }
 
}

Jetzt wollen wir wissen, welche Methoden die Klasse A anbietet. Also bauen wir uns mit ReflectionClass einen Zugang zu den Klassen Informationen und schreiben anschließend die Methoden raus.

1
2
3
4
$reflector = new ReflectionClass("A");
foreach ($reflector->getMethods() as $method) {
    echo $method->getName() . "\n";
}

Da wir auch in der Reflection objektorientiert arbeiten erhalten wir nicht einfach die Methodennamen, sondern Methoden-Objekte zurück, die eine Vielzahl von Informationen beinhalten. Im obigen Beispiel sieht man, wie man mit getName den Namen der Methode erhält. Man kann auch die Anzahl der Parameter auslesen (getNumberOfParameters), wieviele davon auch notwendig sind und welche Modifier eine Methode hat.

Das gleiche gibt auch für Properties. Hier ein kleines Beispiel, dass die Properties der Klasse A rausschreibt und noch angibt, ob diese private sind oder nicht:

1
2
3
4
foreach ($reflector->getProperties() as $property ) {
    echo $property->getName() . (($property->isPrivate()) ? " is private" : "");
    echo "\n";
}

Eingangs habe ich das Beispiel mit der Annotation angesprochen. Diese kann man nun so realisieren, dass man mit der getDocComment-Methode den Kommentar eines Elements (Attribut, Methode oder Klasse) ausliest und einen kleinen Parser schreibt, der vielleicht mit regulären Ausdrücken die Informationen ausliest, die man benötigt. Als Beispiel sei hier Addendum genannt.

Nun schauen wir uns noch eine Besonderheit der Reflection an. Im anfänglichen Beispiel gibt es eine Methode getVar1, die private deklariert wurde und somit nicht direkt aufrufbar ist. Man kann Methoden, sobald ein Objekt in ein ReflectionObject gekapselt hat per invoke aufrufen. invoke ist aber auch an die Gegebenheiten der Klasse gebunden und wird in unserem Fall bei dem Versuch getVar1 aufzurufen nur eine Exception produzieren. Aber man kann setAccessible nutzen, sodass diese Hürde einfach so umgangen werden kann. Damit haben wir die Möglichkeit eine private-Methode außerhalb der Klasse aufzurufen. Beispielsweise sieht das ganz so aus:

1
2
3
4
5
6
7
8
9
10
11
12
$object = new A();
$reflectionObject = new ReflectionObject($object);
$method = $reflectionObject->getMethod("getVar1");
try {
    $method->invoke($object);
}
 catch (ReflectionException $e) {
     echo "forbidden\n";
 }
 
$method->setAccessible(true);
$method->invoke($object);

Solche Aufrufe würde ich im normalen Programmcode nicht tolerieren. Aber in einem Unittest kann ich mir durchaus vorstellen solch eine Konstruktion zu verwenden, wenn man keine andere Möglichkeit hat; in Legacy-Code bspw.

Fazit:
Reflection ist eine interessante Möglichkeit an Klassen und Objektinterna zu gelangen. Persönlich würde ich die Möglichkeiten, die durch die Objektorientierung gegeben werden (Interfaces, Type Hinting, Ableitung etc.), vorziehen. Um Konstrukte wie Annotations zu basteln ist mE die Reflection-API ein valider Weg und bei Unittests eine interessante Alternative, wenn andere Wege verbaut oder nicht gangbar sind.

flattr this!

Kommentare sind geschlossen.