Reflectie wordt over het algemeen gedefinieerd als het vermogen van een programma om zichzelf te inspecteren en de logica ervan bij uitvoeringstijd te wijzigen. In minder technische termen vraagt reflectie een object om je te vertellen over zijn eigenschappen en methoden, en die leden te wijzigen (zelfs privé). In deze les zullen we ingaan op de manier waarop dit wordt bereikt en wanneer dit nuttig kan blijken.
Aan het begin van het tijdperk van programmeren was er de assembleertaal. Een programma dat is geschreven in assembly bevindt zich op fysieke registers binnenin de computer. De samenstelling, methoden en waarden kunnen op elk moment worden geïnspecteerd door de registers te lezen. Sterker nog, je kon het programma aanpassen terwijl het draaide door simpelweg die registers aan te passen. Het vereiste wat grondige kennis over het lopende programma, maar het was inherent reflecterend.
Zoals bij elk cool speeltje, gebruik reflectie, maar maak er geen misbruik van.
Naarmate hogere programmeertalen (zoals C) kwamen, vervaagde en verdween deze reflectiviteit. Het werd later opnieuw geïntroduceerd met object-georiënteerd programmeren.
Tegenwoordig kunnen de meeste programmeertalen reflectie gebruiken. Statisch getypeerde talen, zoals Java, hebben weinig tot geen problemen met reflectie. Wat ik echter interessant vind, is dat elke dynamisch getypeerde taal (zoals PHP of Ruby) sterk gebaseerd is op reflectie. Zonder het concept van reflectie zou eendtypering waarschijnlijk onmogelijk te implementeren zijn. Wanneer u het ene object naar het andere verzendt (bijvoorbeeld een parameter), kan het ontvangende object de structuur en het type van dat object niet kennen. Het enige dat het kan doen is reflectie gebruiken om de methoden te identificeren die wel en niet kunnen worden aangeroepen op het ontvangen object.
Reflectie komt veel voor in PHP. In feite zijn er verschillende situaties waarin je het kunt gebruiken zonder het te weten. Bijvoorbeeld:
// Nettuts.php require_once 'Editor.php'; class Nettuts function publishNextArticle () $ editor = new Editor ('John Doe'); $ Editor-> setNextArticle ( '135523'); $ Editor-> publiceren ();
En:
// Editor.php class Editor private $ name; public $ articleId; function __construct ($ name) $ this-> name = $ name; public function setNextArticle ($ articleId) $ this-> articleId = $ articleId; public function publish () // publiceerlogica gaat hier return true;
In deze code hebben we een directe aanroep naar een lokaal geïnitialiseerde variabele met een bekend type. De editor maken in publishNextArticle ()
maakt het duidelijk dat de $ editor
variabele is van het type Editor
. Hier is geen reflectie nodig, maar laten we een nieuwe klasse introduceren, genaamd Manager
:
// Manager.php require_once './Editor.php'; require_once './Nettuts.php'; class Manager function doJobFor (DateTime $ date) if ((new DateTime ()) -> getTimestamp ()> $ date-> getTimestamp ()) $ editor = new Editor ('John Doe'); $ nettuts = new Nettuts (); $ Nettuts-> publishNextArticle ($ editor);
Wijzig vervolgens Nettuts
, zoals zo:
// Nettuts.php class Nettuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Editor-> publiceren ();
Nu, Nettuts
heeft absoluut geen relatie met de Editor
klasse. Het bevat niet het bestand, het initialiseert zijn klasse niet en het weet niet eens dat het bestaat. Ik zou elk object van welk type dan ook kunnen doorgeven aan de publishNextArticle ()
methode en de code zou werken.
Zoals je kunt zien in dit klassendiagram, Nettuts
heeft alleen een directe relatie met Manager
. Manager
maakt het, en daarom, Manager
hangt af van Nettuts
. Maar Nettuts
heeft geen relatie meer met de Editor
klasse, en Editor
is alleen gerelateerd aan Manager
.
Tijdens runtime, Nettuts
gebruikt een Editor
object, dus de <setNextArticle ()
en publiceren()
methoden.
We kunnen PHP de details van een object laten weergeven. Laten we een PHPUnit-test maken om ons te helpen onze code gemakkelijk uit te oefenen:
// ReflectionTest.php require_once '... /Editor.php'; require_once '... /Nettuts.php'; class ReflectionTest breidt PHPUnit_Framework_TestCase uit function testItCanReflect () $ editor = new Editor ('John Doe'); $ tuts = new Nettuts (); $ Tuts-> publishNextArticle ($ editor);
Voeg nu een toe var_dump ()
naar Nettuts
:
// Nettuts.php class NetTuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Editor-> publiceren (); var_dump (nieuwe ReflectionClass ($ editor));
Voer de test uit en bekijk de magie in de uitvoer:
PHPUnit 3.6.11 door Sebastian Bergmann ... object (ReflectionClass) # 197 (1) ["name"] => string (6) "Editor" Tijd: 0 seconden, Geheugen: 2.25Mb OK (1 test, 0 asserties)
Onze reflectieklas heeft een naam
eigenschap ingesteld op het oorspronkelijke type $ editor
variabele: Editor
, maar dat is niet veel informatie. Hoe zit het met Editor
zijn methoden?
// Nettuts.php class Nettuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Editor-> publiceren (); $ reflector = nieuwe ReflectionClass ($ editor); var_dump ($ reflector-> getMethods ());
In deze code wijzen we de instantie van de reflectieklasse toe aan de $ reflector
variabele, zodat we nu zijn methoden kunnen activeren. ReflectionClass
stelt een groot aantal methoden bloot die u kunt gebruiken om de informatie van een object te verkrijgen. Een van deze methoden is getMethods ()
, die een array retourneert met de informatie van elke methode.
PHPUnit 3.6.11 door 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) "publiceren" ["class"] => string (6) "Editor" Tijd: 0 seconden , Geheugen: 2,25 MB OK (1 test, 0 beweringen)
Een andere methode, GetProperties ()
, haalt de eigenschappen (zelfs privé-eigenschappen!) van het object op:
PHPUnit 3.6.11 door Sebastian Bergmann ... array (2) [0] => & object (ReflectionProperty) # 196 (2) ["name"] => string (4) "name" ["class"] => string (6) "Editor" [1] => & object (ReflectionProperty) # 195 (2) ["name"] => string (9) "articleId" ["class"] => string (6) "Editor" Tijd: 0 seconden, Geheugen: 2.25Mb OK (1 test, 0 beweringen)
De elementen in de arrays zijn teruggekomen getMethod ()
en GetProperties ()
zijn van het type ReflectionMethod
en ReflectionProperty
, respectievelijk; deze objecten zijn best handig:
// Nettuts.php class Nettuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Editor-> publiceren (); // eerste oproep om te publiceren () $ reflector = nieuwe ReflectionClass ($ editor); $ publishMethod = $ reflector-> getMethod ('publish'); $ PublishMethod-> beroepen ($ editor); // tweede oproep om te publiceren ()
Hier gebruiken we getMethod ()
om een enkele methode op te halen met de naam "publiceren"; het resultaat is een ReflectionMethod
voorwerp. Vervolgens noemen we de beroep doen op()
methode, het doorgeven van de $ editor
object, om de editor uit te voeren publiceren()
methode een tweede keer.
Dit proces was in ons geval eenvoudig, omdat we al een Editor
object om door te geven aan beroep doen op()
. We kunnen er meerdere hebben Editor
objecten in sommige omstandigheden, waardoor we de luxe hebben om te kiezen welk object te gebruiken. In andere omstandigheden hebben we misschien geen objecten om mee te werken, in welk geval we er een zouden moeten aanschaffen ReflectionClass
.
Laten we aanpassen Editor
's publiceren()
methode om de dubbele oproep te demonstreren:
// Editor.php class Editor [...] public function publish () // publiceerlogica goes here echo ("HIER \ n"); geef waar terug;
En de nieuwe output:
PHPUnit 3.6.11 door Sebastian Bergmann ... HIER HERE Tijd: 0 seconden, Geheugen: 2.25Mb OK (1 test, 0 asserties)
We kunnen de code ook tijdens de uitvoering wijzigen. Hoe zit het met het wijzigen van een privévariabele die geen openbare setter heeft? Laten we een methode toevoegen aan Editor
die de naam van de editor ophaalt:
// Editor.php class Editor private $ name; public $ articleId; function __construct ($ name) $ this-> name = $ name; [...] functie getEditorName () return $ this-> name;
Deze nieuwe methode wordt genoemd, getEditorName ()
, en retourneert eenvoudig de waarde van de privé name $
variabel. De name $
variabele is ingesteld op scheppingstijd en we hebben geen openbare methoden die ons dit kunnen laten veranderen. Maar we kunnen deze variabele benaderen door middel van reflectie. Probeer eerst eens de meer voor de hand liggende aanpak:
// Nettuts.php class Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = nieuwe ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('naam'); $ EditorName-> getValue ($ editor);
Hoewel dit de waarde op de var_dump ()
regel, wordt er een fout gegenereerd bij het ophalen van de waarde met reflectie:
PHPUnit 3.6.11 door Sebastian Bergmann. Estring (8) "John Doe" Tijd: 0 seconden, Geheugen: 2.50Mb Er was 1 fout: 1) ReflectionTest :: testItCanReflect ReflectionException: Geen toegang tot niet-openbaar lid Editor :: naam [...] / Reflectie in PHP / Bron / NetTuts.php: 13 [...] / Reflectie in PHP / Bron / Tests / ReflectionTest.php: 13 / usr / bin / phpunit: 46 FAILURES! Tests: 1, Assertions: 0, Fouten: 1.
Om dit probleem op te lossen, moeten we de ReflectionProperty
object om ons toegang te verlenen tot de private variabelen en methoden:
// Nettuts.php class Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = nieuwe ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('naam'); $ EditorName-> setAccessible (true); var_dump ($ editorName-> getValue ($ editor));
Roeping setAccessible ()
en passeren waar
doet het:
PHPUnit 3.6.11 door Sebastian Bergmann ... string (8) "John Doe" string (8) "John Doe" Tijd: 0 seconden, Geheugen: 2.25Mb OK (1 test, 0 asserties)
Zoals je ziet, zijn we erin geslaagd om de privévariabele te lezen. De eerste regel van de uitvoer is afkomstig van het object getEditorName ()
methode, en de tweede komt van reflectie. Maar hoe zit het met het veranderen van de waarde van een privévariabele? Gebruik de setValue ()
methode:
// Nettuts.php class Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = nieuwe ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('naam'); $ EditorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ editor));
En dat is het. Deze code verandert "John Doe" in "Mark Twain".
PHPUnit 3.6.11 door Sebastian Bergmann ... string (8) "John Doe" string (10) "Mark Twain" Tijd: 0 seconden, Geheugen: 2.25Mb OK (1 test, 0 asserties)
Een deel van de ingebouwde functionaliteit van PHP maakt indirect gebruik van reflectie. Een daarvan is de call_user_func ()
functie.
De call_user_func ()
functie accepteert een array: het eerste element dat naar een object wijst en het tweede naar de naam van een methode. U kunt een optionele parameter opgeven, die vervolgens wordt doorgegeven aan de methode genaamd. Bijvoorbeeld:
// Nettuts.php class Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = nieuwe ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('naam'); $ EditorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ editor)); var_dump (call_user_func (array ($ editor, 'getEditorName')));
De volgende uitvoer toont aan dat de code de juiste waarde ophaalt:
PHPUnit 3.6.11 door Sebastian Bergmann ... string (8) "John Doe" string (10) "Mark Twain" string (10) "Mark Twain" Tijd: 0 seconden, Geheugen: 2.25Mb OK (1 test, 0 asserties)
Een ander voorbeeld van indirecte reflectie is het aanroepen van een methode op basis van de waarde die een variabele bevat, in tegenstelling tot het rechtstreeks aanroepen van een variabele. Bijvoorbeeld:
// Nettuts.php class Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = nieuwe ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('naam'); $ EditorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ editor)); $ methodName = 'getEditorName'; var_dump ($ editor -> $ methodName ());
Deze code produceert dezelfde uitvoer als het vorige voorbeeld. PHP vervangt eenvoudig de variabele door de string die hij vertegenwoordigt en roept de methode aan. Het werkt zelfs wanneer u objecten wilt maken met behulp van variabelen voor klassenamen.
Nu we de technische details achter ons hebben gelaten, wanneer moeten we reflecteren? Hier zijn een paar scenario's:
Zoals bij elk cool speeltje, gebruik reflectie, maar maak er geen misbruik van. Reflectie is kostbaar wanneer u veel objecten inspecteert en het heeft de potentie om de architectuur en het ontwerp van uw project te compliceren. Ik raad aan dat u er alleen gebruik van maakt als het u daadwerkelijk een voordeel oplevert, of als u geen andere haalbare optie hebt.
Persoonlijk heb ik slechts in enkele gevallen reflectie gebruikt, meestal wanneer ik modules van derden gebruik die geen documentatie hebben. Ik merk dat ik regelmatig code gebruik die lijkt op het laatste voorbeeld. Het is eenvoudig om de juiste methode aan te roepen, wanneer uw MVC antwoordt met een variabele die de waarden "toevoegen" of "verwijderen" bevat.
Bedankt voor het lezen!