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.
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.
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 <setNextArticle ()
und veröffentlichen()
Methoden.
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 Editor
Methoden?
// 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)
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)
Einige der integrierten Funktionen von PHP verwenden indirekt die Reflection-One-Funktion call_user_func ()
Funktion.
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)
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.
Nun, da wir die technischen Details hinter uns gelassen haben, wann sollten wir die Reflexion nutzen? Hier sind einige Szenarien:
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!