Reflektion i PHP

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.


En liten historia

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.


Ett enkelt exempel

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 <> och frågetecknet. Vid körning inspekterar PHP det mottagna objektet och verifierar att det implementerar setNextArticle () och publicera() metoder.

Objektmedlemsinformation

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örs 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)

Manipulera instansdata

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)

Indirekt reflektionsanvändning

Några av PHP: s inbyggda funktionalitet använder indirekt reflektion, en är den call_user_func () fungera.

Återkallelsen

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)

Använda ett variabelt värde

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.


När ska vi använda reflektion?

Nu när vi har lagt fram de tekniska detaljerna bakom oss, när ska vi utnyttja eftertanke? Här är några scenarier:

  • Dynamisk typing är förmodligen omöjligt utan reflektion.
  • Orienteringsorienterad programmering lyssnar från metodsamtal och placerar kod kring metoder, alla uppnådda med reflektion.
  • PHPUnit bygger starkt på reflektion, liksom andra mockingramar.
  • Webbramar i allmänhet använda reflektion för olika ändamål. Vissa använder den för att initiera modeller, bygga objekt för visningar och mer. Laravel gör stor användning av reflektion för att injicera beroenden.
  • metaprogrammering, Som vårt sista exempel är dold reflektion.
  • Kodanalys ramverk använd reflektion för att förstå din kod.

Slutgiltiga tankar

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!