Reflektion definieras i allmänhet som ett program förmåga att inspektera sig och ändra dess logik vid körningstid. I mindre tekniska termer frågar eftertanke ett objekt för att berätta om dess egenskaper och metoder och att ändra medlemmarna (även privata). I den här lektionen kommer vi att gräva in hur detta uppnås, och när det kan bli användbart.
I början av programmeringsåldern var det monteringsspråket. Ett program som skrivs i montering finns på fysiska register i datorn. Dess sammansättning, metoder och värden kan inspekteras när som helst genom att läsa registren. Ännu mer kan du ändra programmet medan det kördes genom att helt enkelt ändra dessa register. Det krävde lite intim kunskap om det pågående programmet, men det var iboende reflekterande.
Som med någon cool leksak, använd reflektion, men missbruk inte det.
Som högre nivå programmeringsspråk (som C) kom med, reflekterade denna reflektivitet och försvann. Den introducerades senare med objektorienterad programmering.
Idag kan de flesta programmeringsspråk använda reflektion. Statiskt typade språk, som Java, har inte några problem med reflektion. Vad jag tycker är intressant att dock är ett dynamiskt typat språk (som PHP eller Ruby) starkt baserat på reflektion. Utan begreppet reflektion skulle anklagning sannolikt inte kunna genomföras. När du skickar ett objekt till ett annat (till exempel en parameter) har det mottagande objektet ingen möjlighet att känna till strukturen och typen av objektet. Allt man kan göra är att använda reflektion för att identifiera metoder som kan och inte kan ringas på det mottagna objektet.
Reflektion är utbredd i PHP. Faktum är att det finns flera situationer när du kan använda det utan att ens veta det. Till exempel:
// Nettuts.php require_once 'Editor.php'; klassnätverk funktion publishNextArticle () $ editor = new Editor ('John Doe'); $ Editor> setNextArticle (135.523); $ Editor> publicerar ();
Och:
// Editor.php klassredigerare privat $ namn; offentlig $ artikelId; funktion __construct ($ name) $ this-> name = $ name; allmän funktion setNextArticle ($ articleId) $ this-> articleId = $ articleId; public function publicera () // publicera logik går här returnera true;
I denna kod har vi ett direktsamtal till en lokalt initierad variabel med en känd typ. Skapa redigeraren i publishNextArticle ()
gör det uppenbart att $ editor
variabeln är av typen Redaktör
. Ingen reflektion behövs här, men låt oss introducera en ny klass, kallad Chef
:
// Manager.php require_once './Editor.php'; require_once './Nettuts.php'; klasshanterare funktion doJobFor (DateTime $ date) if ((new DateTime ()) -> getTimestamp ()> $ date-> getTimestamp ()) $ editor = ny editor ('John Doe'); $ nätet = nya nätutsläpp (); $ Nettuts-> publishNextArticle ($ Redaktör);
Ändra sedan Nettuts
, såhär:
// Nettuts.php class Netuts funktion publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Editor> publicerar ();
Nu, Nettuts
har absolut ingen relation till Redaktör
klass. Den innehåller inte filen, den initierar inte sin klass och vet inte ens att den existerar. Jag kunde passera ett föremål av vilken typ som helst i publishNextArticle ()
metod och koden skulle fungera.
Som du kan se från det här klassdiagrammet, Nettuts
har bara ett direkt förhållande till Chef
. Chef
skapar det, och därför, Chef
beror på Nettuts
. Men Nettuts
har inte längre något samband med Redaktör
klass och Redaktör
är bara relaterad till Chef
.
Vid körning, Nettuts
använder en Redaktör
objekt, sålunda <setNextArticle ()
och publicera()
metoder.
Vi kan göra PHP att visa detaljerna för ett objekt. Låt oss skapa ett PHPUnit-test som hjälper oss att enkelt utöva vår kod:
// ReflectionTest.php require_once '... /Editor.php'; require_once '... /Nettuts.php'; klass ReflectionTest utökar PHPUnit_Framework_TestCase funktion testItCanReflect () $ editor = new Editor ('John Doe'); $ tuts = new Nettuts (); $ Tuts-> publishNextArticle ($ Redaktör);
Lägg nu till en var_dump ()
till Nettuts
:
// Nettuts.php class NetTuts funktion publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Editor> publicerar (); var_dump (ny ReflectionClass ($ editor));
Kör provet och kolla på det magiska som händer i utgången:
PHPUnit 3.6.11 av Sebastian Bergmann ... objekt (ReflectionClass) # 197 (1) ["namn"] => sträng (6) "Editor" Tid: 0 sekunder, Minne: 2.25Mb OK (1 test, 0 påståenden)
Vår reflektionsklass har en namn
egendom som är inställd på den ursprungliga typen av $ editor
variabel: Redaktör
, men det är inte mycket information. Vad sägs om Redaktör
s metoder?
// Nettuts.php class Netuts funktion publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Editor> publicerar (); $ reflector = ny ReflectionClass ($ editor); var_dump ($ reflector-> getMethods ());
I denna kod tilldelar vi reflektionsklassens instans till $ reflektor
variabel så att vi nu kan utlösa sina metoder. ReflectionClass
exponerar en stor uppsättning metoder som du kan använda för att erhålla ett objekts information. En av dessa metoder är getMethods ()
, som returnerar en array som innehåller varje metodinformation.
PHPUnit 3.6.11 av Sebastian Bergmann ... array (3) [0] => och objekt (ReflectionMethod) # 196 (2) ["namn"] => sträng (11) "__construct" ["class"] => sträng (6) "Editor" [1] => och objekt (ReflectionMethod) # 195 (2) ["namn"] => sträng (14) "setNextArticle" [2] => och objekt (ReflectionMethod) # 194 (2) ["namn"] => sträng (7) "publicera" ["class"] => sträng (6) "Editor" Tid: 0 sekunder , Minne: 2,25Mb OK (1 test, 0 påståenden)
En annan metod, getProperties ()
, hämtar egenskaperna (även privata egenskaper!) av objektet:
PHPUnit 3.6.11 av Sebastian Bergmann ... array (2) [0] => och objekt (ReflectionProperty) # 196 (2) ["namn"] => sträng (4) "namn" ["class"] => sträng (6) "Editor" [1] => och objekt (ReflectionProperty) # 195 (2) ["namn"] => sträng (9) "artikelId" Tid: 0 sekunder, Minne: 2,25Mb OK (1 test, 0 påståenden)
Elementen i arraysna återvände från getMethod ()
och getProperties ()
är av typ ReflectionMethod
och ReflectionProperty
, respektive; dessa objekt är ganska användbara:
// Nettuts.php class Netuts funktion publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Editor> publicerar (); // första samtal för att publicera () $ reflector = ny ReflectionClass ($ editor); $ publishMethod = $ reflector-> getMethod ("publicera"); $ PublishMethod-> åberopa ($ Redaktör); // andra samtal för att publicera ()
Här använder vi getMethod ()
att hämta en enda metod med namnet "publicera"; Resultatet av detta är a ReflectionMethod
objekt. Då kallar vi åberopa()
metod, passerar den $ editor
objekt, för att utföra redaktörens publicera()
metod en andra gång.
Denna process var enkel i vårt fall, eftersom vi redan hade en Redaktör
motsätta sig att vidarebefordra till åberopa()
. Vi kan ha flera Redaktör
objekt under vissa omständigheter, vilket ger oss lyxen att välja vilket objekt att använda. Under andra omständigheter kan vi inte ha några föremål att arbeta med, i vilket fall vi skulle behöva skaffa en från ReflectionClass
.
Låt oss ändra Redaktör
's publicera()
Metod för att visa dubbelanropet:
// Editor.php class editor [...] public function publicera () // public logic går här echo ("HERE \ n"); återvänd sant;
Och den nya produktionen:
PHPUnit 3.6.11 av Sebastian Bergmann ... HÄR HÄR Tid: 0 sekunder, Minne: 2,25Mb OK (1 test, 0 påståenden)
Vi kan också ändra kod vid körningstid. Vad sägs om att ändra en privat variabel som inte har någon offentlig setter? Låt oss lägga till en metod till Redaktör
som hämtar redaktörens namn:
// Editor.php klassredigerare privat $ namn; offentlig $ artikelId; funktion __construct ($ name) $ this-> name = $ name; [...] funktion getEditorName () returnera $ this-> name;
Denna nya metod kallas, getEditorName ()
, och returnerar helt enkelt värdet från den privata $ name
variabel. De $ name
variabel sätts vid skapandetid, och vi har inga offentliga metoder som låter oss ändra det. Men vi kan nå denna variabel med reflektion. Du kan först försöka med det mer uppenbara sättet:
// Nettuts.php class Netuts funktion publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = ny ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ EditorName-> getValue ($ Redaktör);
Även om detta matar ut värdet vid var_dump ()
linje, kasta det ett fel när man försöker hämta värdet med reflektion:
PHPUnit 3.6.11 av Sebastian Bergmann. Estring (8) "John Doe" Tid: 0 sekunder, Minne: 2.50Mb Det fanns 1 fel: 1) ReflectionTest :: testItCanReflect ReflectionException: Kan inte komma åt icke-offentlig medlem Redaktör :: namn [...] / Reflektion i PHP / Källa / NetTuts.php: 13 [...] / Reflektion i PHP / Källa / Test / ReflectionTest.php: 13 / usr / bin / phpunit: 46 FEL! Test: 1, påståenden: 0, fel: 1.
För att lösa detta problem måste vi fråga ReflectionProperty
föremål för att ge oss tillgång till privata variabler och metoder:
// Nettuts.php class Netuts funktion publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = ny ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ EditorName-> setAccessible (true); var_dump ($ editorName-> getValue ($ editor));
Kallelse setAccessible ()
och passerar Sann
gör tricket:
PHPUnit 3.6.11 av Sebastian Bergmann ... sträng (8) "John Doe" -strängen (8) "John Doe" Tid: 0 sekunder, Minne: 2,25Mb OK (1 test, 0 påståenden)
Som du kan se har vi lyckats läsa privat variabel. Den första produktionslinjen är från objektets egen getEditorName ()
metod, och den andra kommer från reflektion. Men hur är det med att ändra en egen variabels värde? Använd satt värde()
metod:
// Nettuts.php class Netuts funktion publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = ny ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ EditorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ editor));
Och det är allt. Denna kod ändrar "John Doe" till "Mark Twain".
PHPUnit 3.6.11 av Sebastian Bergmann ... sträng (8) "John Doe" -strängen (10) "Mark Twain" Tid: 0 sekunder, Minne: 2,25Mb OK (1 test, 0 påståenden)
Några av PHP: s inbyggda funktionalitet använder indirekt reflektion, en är den call_user_func ()
fungera.
De call_user_func ()
funktionen accepterar en matris: det första elementet pekar på ett objekt och det andra en metodens namn. Du kan leverera en valfri parameter som sedan skickas till den uppringda metoden. Till exempel:
// Nettuts.php class Netuts funktion publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = ny 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')));
Följande produktion visar att koden hämtar rätt värde:
PHPUnit 3.6.11 av Sebastian Bergmann ... sträng (8) "John Doe" -strängen (10) Mark Twain-strängen (10) Mark Twain Tid: 0 sekunder, Minne: 2,25Mb OK (1 test, 0 påståenden)
Ett annat exempel på indirekt reflektion kallar en metod med värdet i en variabel, i motsats till att den direkt kallas. Till exempel:
// Nettuts.php class Netuts funktion publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = ny 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 ());
Denna kod ger samma resultat som föregående exempel. PHP ersätter helt enkelt variabeln med strängen den representerar och kallar metoden. Det fungerar även när du vill skapa objekt med hjälp av variabler för klassnamn.
Nu när vi har lagt fram de tekniska detaljerna bakom oss, när ska vi utnyttja eftertanke? Här är några scenarier:
Som med någon cool leksak, använd reflektion, men missbruk inte det. Reflektion är kostsamt när du inspekterar många föremål, och det har potential att komplicera ditt projekts arkitektur och design. Jag rekommenderar att du endast använder det om det verkligen ger dig en fördel, eller om du inte har något annat hållbart alternativ.
Personligen har jag bara använt reflektion i några få fall, oftast vid användning av tredje parts moduler som saknar dokumentation. Jag tycker att jag ofta använder kod som liknar det sista exemplet. Det är lätt att ringa rätt metod när din MVC svarar med en variabel som innehåller "add" eller "remove" -värdena.
Tack för att du läser!