Refleksjon i PHP

Refleksjon er generelt definert som et programs evne til å inspisere seg selv og endre sin logikk på kjøretid. I mindre tekniske termer spør ettertanke om et objekt for å fortelle deg om egenskapene og metodene, og endre disse medlemmene (selv private). I denne leksjonen graver vi inn i hvordan dette oppnås, og når det kan vise seg å være nyttig.


En liten historie

Ved begynnelsen av programmeringsalderen var det forsamlingsspråket. Et program som er skrevet i samling, ligger på fysiske registre inne i datamaskinen. Dens sammensetning, metoder og verdier kan inspiseres når som helst ved å lese registret. Enda mer, du kan endre programmet mens det kjørte ved å bare endre disse registre. Det krevde litt intim kunnskap om det løpende programmet, men det var iboende reflekterende.

Som med noen kule leketøy, bruk refleksjon, men ikke misbruk det.

Som høyere nivå programmeringsspråk (som C) kom sammen, ble denne reflektiviteten forsvunnet og forsvunnet. Det ble senere gjeninnført med objektorientert programmering.

I dag kan de fleste programmeringsspråk bruke refleksjon. Statisk typede språk, som Java, har lite eller ingen problemer med refleksjon. Det jeg finner interessant er imidlertid at et dynamisk skrevet språk (som PHP eller Ruby) er sterkt basert på refleksjon. Uten refleksjonskonseptet ville det være sannsynlig at and-typing ikke kunne gjennomføres. Når du sender ett objekt til et annet (en parameter, for eksempel), har mottaksobjektet ingen måte å vite strukturen og typen av objektet. Alt det kan gjøre, er å bruke refleksjon for å identifisere metodene som kan og ikke kan kalles på det mottatte objektet.


Et enkelt eksempel

Refleksjon er utbredt i PHP. Faktisk er det flere situasjoner når du kan bruke den uten å vite det. For eksempel:

 // Nettuts.php require_once 'Editor.php'; klasse Nettutsats funksjon publishNextArticle () $ editor = new Editor ('John Doe'); $ Editor-> setNextArticle ( '135523'); $ Editor-> publisere (); 

Og:

 // Editor.php Class Editor privat $ navn; offentlig $ artikkel; funksjon __construct ($ navn) $ this-> name = $ name;  offentlig funksjon setNextArticle ($ articleId) $ this-> articleId = $ articleId;  offentlig funksjon publisere () // publiser logikk går her return true; 

I denne koden har vi en direkte samtale til en lokalt initiert variabel med en kjent type. Oppretter redigereren i publishNextArticle () gjør det klart at $ redaktør variabel er av typen Redaktør. Ingen refleksjon er nødvendig her, men la oss introdusere en ny klasse, kalt sjef:

 // Manager.php require_once './Editor.php'; require_once './Nettuts.php'; Klasse Manager funksjon doJobFor (DateTime $ date) if ((new DateTime ()) -> getTimestamp ()> $ date-> getTimestamp ()) $ editor = ny editor ('John Doe'); $ nettuts = nytt Nettuts (); $ Nettuts-> publishNextArticle ($ redaktør); 

Endre, endre Nettuts, som så:

 // Nettuts.php class Nettuts funksjon publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Editor-> publisere (); 

Nå, Nettuts har absolutt ingen forbindelse med Redaktør klasse. Den inkluderer ikke filen, den starter ikke klassen, og den vet ikke engang at den eksisterer. Jeg kunne passere et objekt av hvilken som helst type i publishNextArticle () metode og koden ville fungere.


Som du kan se fra dette klassediagrammet, Nettuts har bare et direkte forhold til sjef. sjef skaper det, og derfor, sjef kommer an på Nettuts. Men Nettuts har ikke lenger noe forhold til Redaktør klasse, og Redaktør er bare relatert til sjef.

På kjøretid, Nettuts bruker en Redaktør objekt, dermed <> og spørsmålet. Ved kjøretid inspiserer PHP mottatt objekt og verifiserer at det implementerer setNextArticle () og publisere() fremgangsmåter.

Objektmedlemsinformasjon

Vi kan gjøre PHP vise detaljene til et objekt. La oss lage en PHPUnit-test for å hjelpe oss med å utøve vår kode enkelt:

 // ReflectionTest.php require_once '... /Editor.php'; require_once '... /Nettuts.php'; klasse ReflectionTest utvider PHPUnit_Framework_TestCase funksjon testItCanReflect () $ editor = new Editor ('John Doe'); $ tuts = new Nettuts (); $ Tuts-> publishNextArticle ($ redaktør); 

Nå legg til en var_dump () til Nettuts:

 // Nettuts.php class NetTuts funksjon publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Editor-> publisere (); var_dump (ny ReflectionClass ($ editor)); 

Kjør testen, og se på magien som skjer i utgangen:

PHPUnit 3.6.11 av Sebastian Bergmann ... objekt (ReflectionClass) # 197 (1) ["navn"] => streng (6) "Editor" Tid: 0 sekunder, Minne: 2.25Mb OK (1 test, 0 påstander)

Vår refleksjonsklasse har a Navn eiendom satt til den opprinnelige typen av $ redaktør variabel: Redaktør, men det er ikke mye informasjon. Hva med Redaktørs metoder?

 // Nettuts.php class Nettuts funksjon publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Editor-> publisere (); $ reflektor = ny ReflectionClass ($ editor); var_dump ($ reflector-> getMethods ()); 

I denne koden tildeler vi refleksjonsklassen 'forekomst til $ reflektor variabel slik at vi nå kan utløse sine metoder. ReflectionClass utsetter et stort sett med metoder som du kan bruke til å skaffe en objekts informasjon. En av disse metodene er getMethods (), som returnerer en matrise som inneholder hver metodeinformasjon.

 PHPUnit 3.6.11 av Sebastian Bergmann ... array (3) [0] => og objekt (ReflectionMethod) # 196 (2) ["navn"] => streng (11) "__construct" ["class"] => streng (6) "Editor" [1] => og objekt (ReflectionMethod) # 195 (2) ["navn"] => streng (14) "setNextArticle"  [2] => og objekt (ReflectionMethod) # 194 (2) ["navn"] => streng (7) "publiser" ["class"] => streng (6) "Editor" Tid: 0 sekunder , Minne: 2,25Mb OK (1 test, 0 påstander)

En annen metode, getProperties (), henter egenskapene (selv private egenskaper!) av objektet:

 PHPUnit 3.6.11 av Sebastian Bergmann ... array (2) [0] => og objekt (ReflectionProperty) # 196 (2) ["navn"] => streng (4) "navn" ["class"] => streng (6) "Editor" [1] => og objekt (ReflectionProperty) # 195 (2) ["navn"] => streng (9) "artikkelId"  Tid: 0 sekunder, Minne: 2,25Mb OK (1 test, 0 påstander)

Elementene i arrays returnerte fra getMethod () og getProperties () er av typen ReflectionMethod og ReflectionProperty, henholdsvis; disse objektene er ganske nyttige:

 // Nettuts.php class Nettuts funksjon publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Editor-> publisere (); // første anrop for å publisere () $ reflector = ny ReflectionClass ($ editor); $ publishMethod = $ reflector-> getMethod ('publiser'); $ PublishMethod-> påberope ($ redaktør); // andre anrop for å publisere ()

Her bruker vi getMethod () å hente en enkelt metode med navnet "publisere"; Resultatet av dette er a ReflectionMethod gjenstand. Så kaller vi påkalle () metode, passerer den $ redaktør objekt, for å utføre redaktørens publisere() metode en gang til.

Denne prosessen var enkel i vårt tilfelle, fordi vi allerede hadde en Redaktør motsette seg å sende til påkalle (). Vi kan ha flere Redaktør objekter under noen omstendigheter, noe som gir oss luksusen til å velge hvilket objekt å bruke. Under andre omstendigheter kan vi ikke ha noen objekter å jobbe med, i så fall må vi skaffe en fra ReflectionClass.

La oss endre Redaktør's publisere() metode for å demonstrere dobbeltanropet:

 // Editor.php class editor [...] offentlig funksjon publisere () // publiser logikk går her echo ("HERE \ n"); returnere sant; 

Og den nye produksjonen:

 PHPUnit 3.6.11 av Sebastian Bergmann ... HER HER Tid: 0 sekunder, Minne: 2.25Mb OK (1 test, 0 påstander)

Manipulering av forekomstdata

Vi kan også endre kode på kjøretid. Hva med å endre en privat variabel som ikke har noen offentlig setter? La oss legge til en metode til Redaktør som henter redaktørens navn:

 // Editor.php Class Editor privat $ navn; offentlig $ artikkel; funksjon __construct ($ navn) $ this-> name = $ name;  [...] funksjon getEditorName () return $ this-> name; 

Denne nye metoden kalles, getEditorName (), og returnerer bare verdien fra privatpersonen $ name variabel. De $ name variabel er satt på opprettelsestidspunktet, og vi har ingen offentlige metoder som lar oss endre det. Men vi kan få tilgang til denne variabelen ved hjelp av refleksjon. Du kan først prøve den mer åpenbare tilnærmingen:

 // Nettuts.php class Nettuts funksjon publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflektor = ny ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ EditorName-> getValue ($ redaktør); 

Selv om dette gir verdi på var_dump () linje, kaster det en feil når du prøver å hente verdien med refleksjon:

PHPUnit 3.6.11 av Sebastian Bergmann. Estring (8) "John Doe" Tid: 0 sekunder, Minne: 2.50Mb Det var 1 feil: 1) ReflectionTest :: testItCanReflect ReflectionException: Kan ikke få tilgang til ikke-offentlig medlem Redaktør :: navn [...] / Refleksjon i PHP / Kilde / NetTuts.php: 13 [...] / Refleksjon i PHP / Source / Tests / ReflectionTest.php: 13 / usr / bin / phpunit: 46 FEIL! Test: 1, påstander: 0, feil: 1.

For å løse dette problemet må vi spørre ReflectionProperty motsette seg å gi oss tilgang til de private variablene og metodene:

// Nettuts.php class Nettuts funksjon publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflektor = ny ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ EditorName-> setAccessible (true); var_dump ($ editorName-> getValue ($ redaktør)); 

ringe setAccessible () og passerer ekte gjør kunsten:

PHPUnit 3.6.11 av Sebastian Bergmann ... streng (8) "John Doe" streng (8) "John Doe" Tid: 0 sekunder, Minne: 2.25Mb OK (1 test, 0 påstander)

Som du kan se, har vi klart å lese privat variabel. Den første produksjonslinjen er fra objektets egen getEditorName () metode, og den andre kommer fra refleksjon. Men hva med å endre verdien av en privat variabel? Bruke SetValue () metode:

// Nettuts.php class Nettuts funksjon publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflektor = ny ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ EditorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ redaktør)); 

Og det er det. Denne koden endrer "John Doe" til "Mark Twain".

PHPUnit 3.6.11 av Sebastian Bergmann ... streng (8) "John Doe" streng (10) "Mark Twain" Tid: 0 sekunder, Minne: 2.25Mb OK (1 test, 0 påstander)

Indirekte Refleksjon Bruk

Noen av PHPs innebygde funksjonalitet indirekte bruker refleksjon, en er den call_user_func () funksjon.

Tilbakekallingen

De call_user_func () funksjon aksepterer en matrise: det første elementet peker mot en gjenstand, og den andre en metode navn. Du kan levere en valgfri parameter, som deretter sendes til den oppkalte metoden. For eksempel:

 // Nettuts.php class Nettuts funksjon publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflektor = ny ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ EditorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ redaktør)); var_dump (call_user_func (array ($ editor, 'getEditorName'))); 

Følgende utgang demonstrerer at koden henter riktig verdi:

PhpUnit 3.6.11 av Sebastian Bergmann ... streng (8) "John Doe" streng (10) Mark Twain-streng (10) Mark Twain Tid: 0 sekunder, Minne: 2.25Mb OK (1 test, 0 påstander)

Bruke en variabel verdi

Et annet eksempel på indirekte refleksjon er å kalle en metode av verdien i en variabel, i motsetning til å kalle det direkte. For eksempel:

 // Nettuts.php class Nettuts funksjon publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflektor = ny ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ EditorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ redaktør)); $ methodName = 'getEditorName'; var_dump ($ editor -> $ metode ()); 

Denne koden gir samme utgang som forrige eksempel. PHP erstatter bare variabelen med strengen den representerer og kaller metoden. Den fungerer også når du vil lage objekter ved å bruke variabler for klassenavn.


Når skal vi bruke refleksjon?

Nå som vi har satt de tekniske detaljene bak oss, når skal vi utnytte refleksjon? Her er noen scenarier:

  • Dynamisk skriving er sannsynligvis umulig uten refleksjon.
  • Aspect Oriented Programmering lytter fra metallsamtaler og plasserer kode rundt metoder, alle oppnådd med refleksjon.
  • PHPUnit stoler sterkt på refleksjon, som andre mocking rammer.
  • Web-rammer Generelt bruk refleksjon for forskjellige formål. Noen bruker den til å initialisere modeller, bygge objekter for visninger og mer. Laravel gjør stor bruk av refleksjon for å injisere avhengigheter.
  • Metaprogramming, Som vårt siste eksempel er skjult refleksjon.
  • Kodeanalyse rammer bruk refleksjon for å forstå koden din.

Siste tanker

Som med noen kule leketøy, bruk refleksjon, men ikke misbruk det. Refleksjon er kostbart når du inspiserer mange objekter, og det har potensial til å komplisere prosjektets arkitektur og design. Jeg anbefaler at du bare bruker det når det faktisk gir deg en fordel, eller når du ikke har noe annet rentabelt alternativ.

Personlig har jeg bare brukt refleksjon i noen få tilfeller, oftest når jeg bruker tredjepartsmoduler som mangler dokumentasjon. Jeg finner meg selv ofte ved å bruke kode som ligner på det siste eksemplet. Det er lett å ringe riktig metode, når MVC reagerer med en variabel som inneholder "add" eller "remove" -verdiene.

Takk for at du leste!