Reflexion in PHP

Reflektion wird im Allgemeinen als die Fähigkeit eines Programms definiert, sich zur Ausführungszeit zu untersuchen und seine Logik zu ändern. In weniger technischen Begriffen fordert Reflektion ein Objekt dazu auf, Sie über seine Eigenschaften und Methoden zu informieren und diese (auch private) Mitglieder zu ändern. In dieser Lektion untersuchen wir, wie dies erreicht wird und wann es sich als nützlich erweisen könnte.


Eine kleine Geschichte

Zu Beginn des Programmierungszeitalters gab es die Assembler-Sprache. Ein in Assembly geschriebenes Programm befindet sich in physischen Registern im Computer. Ihre Zusammensetzung, Methoden und Werte können jederzeit durch Lesen der Register überprüft werden. Darüber hinaus können Sie das Programm während der Ausführung ändern, indem Sie einfach diese Register ändern. Es erforderte einige genaue Kenntnisse über das laufende Programm, war aber von Natur aus reflektierend.

Wie bei jedem coolen Spielzeug verwenden Sie Reflexion, aber missbrauchen Sie sie nicht.

Als höhere Programmiersprachen (wie C) auftauchten, verblasste dieses Reflexionsvermögen und verschwand. Sie wurde später mit objektorientierter Programmierung wieder eingeführt.

Heute können die meisten Programmiersprachen Reflection verwenden. Statisch typisierte Sprachen wie Java haben wenig oder gar keine Probleme mit der Reflexion. Interessant finde ich jedoch, dass jede dynamisch typisierte Sprache (wie PHP oder Ruby) stark auf Reflexion basiert. Ohne das Konzept der Reflexion wäre Entenschreiben höchstwahrscheinlich unmöglich zu implementieren. Wenn Sie ein Objekt an ein anderes senden (z. B. einen Parameter), hat das empfangende Objekt keine Möglichkeit, die Struktur und den Typ dieses Objekts zu kennen. Alles, was er tun kann, ist Reflection zu verwenden, um die Methoden zu identifizieren, die für das empfangene Objekt aufgerufen werden können und nicht.


Ein einfaches Beispiel

Reflexion ist in PHP vorherrschend. In der Tat gibt es mehrere Situationen, in denen Sie es verwenden können, ohne es zu wissen. Zum Beispiel:

 // Nettuts.php requir_once 'Editor.php'; Klasse Nettuts function publishNextArticle () $ editor = neuer Editor ('John Doe'); $ editor-> setNextArticle ('135523'); $ editor-> publish (); 

Und:

 // Editor.php Klasse Editor privater $ name; public $ articleId; Funktion __construct ($ name) $ this-> name = $ name;  public function setNextArticle ($ articleId) $ this-> articleId = $ articleId;  public function publish () // Die Publizierungslogik geht hier zurück und gibt true zurück. 

In diesem Code haben wir einen direkten Aufruf einer lokal initialisierten Variablen mit einem bekannten Typ. Erstellen des Editors in publishNextArticle () macht es offensichtlich, dass die $ editor Variable ist vom Typ Editor. Hier ist keine Reflexion nötig, aber lasst uns eine neue Klasse, genannt, vorstellen Manager:

 // Manager.php requir_once './Editor.php'; required_once './Nettuts.php'; Klassenmanager function doJobFor (DateTime $ date) if ((neue DateTime ()) -> getTimestamp ()> $ date-> getTimestamp ()) $ editor = neuer Editor ('John Doe'); $ nettuts = neue Nettuts (); $ nettuts-> publishNextArticle ($ editor); 

Als nächstes ändern Nettuts, wie so:

 // Nettuts.php Klasse Nettuts Funktion publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ editor-> publish (); 

Jetzt, Nettuts hat absolut keinen Bezug zum Editor Klasse. Es enthält keine Datei, es initialisiert seine Klasse nicht und weiß nicht einmal, dass sie existiert. Ich könnte ein Objekt jeglicher Art in die publishNextArticle () Methode und der Code würde funktionieren.


Wie Sie aus diesem Klassendiagramm sehen können, Nettuts hat nur eine direkte Beziehung zu Manager. Manager schafft es und deshalb, Manager kommt drauf an Nettuts. Aber Nettuts hat keinen Bezug mehr zum Editor Klasse und Editor bezieht sich nur auf Manager.

Zur Laufzeit, Nettuts verwendet ein Editor Objekt, also das <> und das Fragezeichen. Zur Laufzeit prüft PHP das empfangene Objekt und prüft, ob es das implementiert setNextArticle () und veröffentlichen() Methoden.

Informationen zu Objektmitgliedern

Wir können PHP die Details eines Objekts anzeigen lassen. Lassen Sie uns einen PHPUnit-Test erstellen, mit dem wir unseren Code leichter ausführen können:

 // ReflectionTest.php requir_once '… /Editor.php'; required_once '… /Nettuts.php'; Die Klasse ReflectionTest erweitert PHPUnit_Framework_TestCase function testItCanReflect () $ editor = new Editor ('John Doe'); $ tuts = neue Nettuts (); $ tuts-> publishNextArticle ($ editor); 

Fügen Sie nun ein var_dump () zu Nettuts:

 // Nettuts.php Klasse NetTuts Funktion publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ editor-> publish (); var_dump (neue ReflectionClass ($ editor)); 

Führen Sie den Test aus und beobachten Sie die Magie in der Ausgabe:

PHPUnit 3.6.11 von Sebastian Bergmann… object (ReflectionClass) # 197 (1) ["name"] => string (6) "editor" Zeit: 0 Sekunden, Speicher: 2,25 MB OK (1 Test, 0 Assertions)

Unsere Reflexionsklasse hat eine Name Eigenschaft auf den ursprünglichen Typ des festgelegt $ editor Variable: Editor, aber das ist nicht viel information. Wie wäre es mit EditorMethoden?

 // Nettuts.php Klasse Nettuts Funktion publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ editor-> publish (); $ reflector = neue ReflectionClass ($ editor); var_dump ($ reflector-> getMethods ()); 

In diesem Code weisen wir die Instanz der Reflection-Klasse dem zu $ Reflektor Variable, so dass wir jetzt ihre Methoden auslösen können. ReflectionClass macht einen großen Satz von Methoden verfügbar, mit denen Sie die Informationen eines Objekts abrufen können. Eine dieser Methoden ist getMethods (), Das gibt ein Array zurück, das die Informationen jeder Methode enthält.

 PHPUnit 3.6.11 von Sebastian Bergmann… array (3) [0] => & object (ReflectionMethod) # 196 (2) ["name"] => string (11) "__construct" ["class"] => string (6) "Editor" [1] => & object (ReflectionMethod) # 195 (2) ["name"] => string (14) "setNextArticle" ["class"] => string (6) "editor"  [2] => & object (ReflectionMethod) # 194 (2) ["name"] => string (7) "publish" ["class"] => string (6) "editor" Zeit: 0 Sekunden , Speicher: 2,25 MB, OK (1 Test, 0 Assertions)

Eine andere Methode, getProperties (), ruft die Eigenschaften (auch private Eigenschaften!) des Objekts ab:

 PHPUnit 3.6.11 von Sebastian Bergmann… array (2) [0] => & object (ReflectionProperty) # 196 (2) ["name"] => string (4) "name" ["class"] => string (6) "Editor" [1] => & object (ReflectionProperty) # 195 (2) ["name"] => Zeichenfolge (9) "articleId" ["class"] => Zeichenfolge (6) "Editor"  Zeit: 0 Sekunden, Speicher: 2,25 MB, OK (1 Test, 0 Assertions)

Die Elemente in den Arrays wurden zurückgegeben getMethod () und getProperties () sind vom Typ ReflectionMethod und ReflectionProperty, beziehungsweise; Diese Objekte sind sehr nützlich:

 // Nettuts.php Klasse Nettuts Funktion publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ editor-> publish (); // erster Aufruf zum Publizieren () $ reflector = new ReflectionClass ($ editor); $ publishMethod = $ reflector-> getMethod ('publish'); $ publishMethod-> invoke ($ editor); // zweiter Aufruf zum Publizieren ()

Hier verwenden wir getMethod () eine einzelne Methode mit dem Namen "Publish" abrufen; das Ergebnis davon ist a ReflectionMethod Objekt. Dann rufen wir die an aufrufen() Methode, übergeben es die $ editor Objekt, um den Editor auszuführen veröffentlichen() Methode ein zweites Mal.

Dieser Prozess war in unserem Fall einfach, weil wir bereits einen hatten Editor Objekt, an das übergeben werden soll aufrufen(). Wir können mehrere haben Editor Objekte unter bestimmten Umständen, wodurch wir den Luxus haben, das zu verwendende Objekt auszuwählen. In anderen Fällen haben wir möglicherweise keine Objekte, mit denen wir arbeiten können. In diesem Fall müssten wir eines davon anfordern ReflectionClass.

Lass uns modifizieren Editor's veröffentlichen() Methode zur Demonstration des Doppelaufrufs:

 // Editor.php Klasse Editor […] public function publish () // Publizierlogik geht hier echo ("HERE \ n"); wahr zurückgeben; 

Und die neue Ausgabe:

 PHPUnit 3.6.11 von Sebastian Bergmann… HERE HERE Zeit: 0 Sekunden, Speicher: 2,25 MB (1 Test, 0 Assertions)

Instanzdaten bearbeiten

Wir können den Code auch zur Ausführungszeit ändern. Was ist mit dem Ändern einer privaten Variablen, die keinen öffentlichen Setter hat? Fügen wir eine Methode hinzu Editor das ruft den Namen des Editors ab:

 // Editor.php Klasse Editor privater $ name; public $ articleId; Funktion __construct ($ name) $ this-> name = $ name;  […] Funktion getEditorName () return $ this-> name; 

Diese neue Methode wird aufgerufen, getEditorName (), und gibt einfach den Wert vom privaten zurück $ name Variable. Das $ name Die Variable wird zum Zeitpunkt der Erstellung festgelegt, und wir haben keine öffentlichen Methoden, mit denen wir sie ändern können. Auf diese Variable können wir jedoch durch Reflektion zugreifen. Sie können zuerst den naheliegenderen Ansatz ausprobieren:

 // Nettuts.php Klasse Nettuts Funktion publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = neue ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ editorName-> getValue ($ editor); 

Obwohl dies den Wert am ausgibt var_dump () Zeile, es wird ein Fehler ausgegeben, wenn versucht wird, den Wert mit Reflektion abzurufen:

PHPUnit 3.6.11 von Sebastian Bergmann. Estring (8) "John Doe" Zeit: 0 Sekunden, Speicher: 2.50Mb Es ist ein Fehler aufgetreten: 1) ReflectionTest :: testItCanReflect ReflectionException: Zugriff auf nicht-öffentliches Mitglied Editor :: name […] / Reflection in PHP / Source / NetTuts.php: 13 […] / Reflection in PHP / Source / Tests / ReflectionTest.php: 13 / usr / bin / phpunit: 46 FEHLER! Tests: 1, Assertions: 0, Fehler: 1.

Um dieses Problem zu beheben, müssen wir die ReflectionProperty Objekt, um uns Zugriff auf die privaten Variablen und Methoden zu gewähren:

// Nettuts.php Klasse Nettuts Funktion publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = neue ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ editorName-> setAccessible (true); var_dump ($ editorName-> getValue ($ editor)); 

Berufung setAccessible () und vorbei wahr macht den Trick:

PHPUnit 3.6.11 von Sebastian Bergmann… string (8) "John Doe" string (8) "John Doe" Zeit: 0 Sekunden, Speicher: 2,25 MB OK (1 Test, 0 Assertions)

Wie Sie sehen, ist es uns gelungen, die private Variable zu lesen. Die erste Zeile der Ausgabe stammt vom eigenen Objekt getEditorName () Methode, und die zweite kommt aus der Reflexion. Wie sieht es aber aus, wenn Sie den Wert einer privaten Variablen ändern? Verwenden Sie die setValue () Methode:

// Nettuts.php Klasse Nettuts Funktion publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = neue ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ editorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ editor)); 

Und das ist es. Dieser Code ändert "John Doe" in "Mark Twain"..

PHPUnit 3.6.11 von Sebastian Bergmann… string (8) "John Doe" string (10) "Mark Twain" Zeit: 0 Sekunden, Speicher: 2,25 MB OK (1 Test, 0 Assertions)

Indirekte Reflexion verwenden

Einige der integrierten Funktionen von PHP verwenden indirekt die Reflection-One-Funktion call_user_func () Funktion.

Der Rückruf

Das call_user_func () Die Funktion akzeptiert ein Array: Das erste Element, das auf ein Objekt zeigt, und das zweite, der Name einer Methode. Sie können einen optionalen Parameter angeben, der an die aufgerufene Methode übergeben wird. Zum Beispiel:

 // Nettuts.php Klasse Nettuts Funktion publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = neue ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ editorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ editor)); var_dump (call_user_func (array ($ editor, 'getEditorName'))); 

Die folgende Ausgabe zeigt, dass der Code den richtigen Wert abruft:

PHPUnit 3.6.11 von Sebastian Bergmann… string (8) String "John Doe" (10) String "Mark Twain" (10) "Mark Twain" Zeit: 0 Sekunden, Speicher: 2,25 MB OK (1 Test, 0 Assertions)

Den Wert einer Variablen verwenden

Ein anderes Beispiel für die indirekte Reflexion ist das Aufrufen einer Methode anhand des in einer Variablen enthaltenen Werts, anstatt sie direkt aufzurufen. Zum Beispiel:

 // Nettuts.php Klasse Nettuts Funktion publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = neue ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ editorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ editor)); $ methodName = 'getEditorName'; var_dump ($ editor -> $ methodName ()); 

Dieser Code erzeugt dieselbe Ausgabe wie im vorherigen Beispiel. PHP ersetzt die Variable einfach durch die Zeichenfolge, die sie darstellt, und ruft die Methode auf. Es funktioniert sogar, wenn Sie Objekte mithilfe von Variablen für Klassennamen erstellen möchten.


Wann sollten wir Reflexion verwenden??

Nun, da wir die technischen Details hinter uns gelassen haben, wann sollten wir die Reflexion nutzen? Hier sind einige Szenarien:

  • Dynamische Typisierung ist ohne Reflektion wahrscheinlich unmöglich.
  • Aspektorientierte Programmierung Hört von Methodenaufrufen und platziert Code um Methoden, die alle mit Reflektion ausgeführt werden.
  • PHPUnit stützt sich stark auf das Nachdenken, ebenso wie andere spöttische Rahmenbedingungen.
  • Web-Frameworks Verwenden Sie Reflexion im Allgemeinen für verschiedene Zwecke. Einige verwenden es, um Modelle zu initialisieren, Objekte für Ansichten zu konstruieren und vieles mehr. Laravel nutzt Reflexion intensiv, um Abhängigkeiten einzuführen.
  • Metaprogrammierung, Wie unser letztes Beispiel ist die verborgene Reflexion.
  • Code-Analyse-Frameworks Verwenden Sie Reflektionen, um Ihren Code zu verstehen.

Abschließende Gedanken

Wie bei jedem coolen Spielzeug verwenden Sie Reflexion, aber missbrauchen Sie sie nicht. Reflexion ist kostspielig, wenn Sie viele Objekte inspizieren. Dies kann die Architektur und das Design Ihres Projekts komplizieren. Ich empfehle Ihnen, nur dann davon Gebrauch zu machen, wenn Sie tatsächlich einen Vorteil haben oder wenn Sie keine andere praktikable Option haben.

Ich persönlich habe Reflexion nur in wenigen Fällen verwendet, am häufigsten bei der Verwendung von Modulen von Drittanbietern, denen es an Dokumentation fehlt. Ich benutze häufig Code, der dem letzten Beispiel ähnlich ist. Es ist einfach, die richtige Methode aufzurufen, wenn Ihre MVC mit einer Variablen antwortet, die Werte "add" oder "remove" enthält.

Danke fürs Lesen!