Reflecție în PHP

Reflecția este în general definită ca capacitatea unui program de a se inspecta și de a-și modifica logica la timpul de execuție. În termeni mai puțin tehnici, reflecția cere unui obiect să vă spună despre proprietățile și metodele sale și să le modificați pe acei membri (chiar și pe cei privați). În această lecție, vom explora modul în care se realizează acest lucru și când se va dovedi utilă.


O mică istorie

La începutul erei programării, era limba de asamblare. Un program scris în asamblare se află pe registrele fizice din interiorul computerului. Compoziția, metodele și valorile sale pot fi inspectate în orice moment prin citirea registrelor. Mai mult, ați putea schimba programul în timp ce acesta funcționa prin modificarea pur și simplu a acelor registre. Era nevoie de cunostinte intime despre programul de alergare, dar a fost inerent reflectiva.

La fel ca orice jucărie rece, folosiți reflecția, dar nu abuzați-o.

Pe măsură ce limbile de programare de nivel superior (cum ar fi C) au venit, această reflectivitate a dispărut și a dispărut. Ulterior, a fost reintrodusă cu programare orientată pe obiecte.

Astăzi, majoritatea limbajelor de programare pot folosi reflecția. Limbile statice tipărite, cum ar fi Java, nu au deloc probleme cu reflecția. Ceea ce mi se pare interesant este însă faptul că orice limbaj dinamic (cum ar fi PHP sau Ruby) se bazează în mare măsură pe reflecție. Fără conceptul de reflecție, tastarea rațelor ar fi, cel mai probabil, imposibil de implementat. Când trimiteți un obiect la altul (de exemplu, un parametru), obiectul care primește nu are cunoștință despre structura și tipul obiectului respectiv. Tot ce poate face este să utilizeze reflecția pentru a identifica metodele care pot și nu pot fi solicitate pe obiectul primit.


Un exemplu simplu

Reflecția este predominantă în PHP. De fapt, există mai multe situații în care puteți să le folosiți fără să le cunoașteți. De exemplu:

 / / Nettuts.php require_once 'Editor.php'; clasa Nettuts function publishNextArticle () $ editor = editor nou ("John Doe"); $ Redactor> setNextArticle ( '135523'); $ Redactor> publica (); 

Și:

 // editor.php Editor de clasă private $ name; public $ articleId; funcția __construct ($ name) $ this-> name = $ name;  funcția publică setNextArticle ($ articleId) $ this-> articleId = $ articleId;  public function publish () // publica logica merge aici return true; 

În acest cod, avem o chemare directă la o variabilă inițializată local cu un tip cunoscut. Crearea editorului în publishNextArticle () face evident faptul că $ editor variabila este de tip Editor. Nu este nevoie de o reflecție aici, ci să introducem o nouă clasă, numită Administrator:

 // Manager.php require_once './Editor.php'; require_once './Nettuts.php'; managerul clasei function doJobFor (DateTime $ date) if ((new DateTime ()) -> getTimestamp ()> $ date-> getTimestamp ()) $ editor = editor nou; $ nettuts = noi Nettuts (); $ Nettuts-> publishNextArticle (editor $); 

Apoi, modificați Nettuts, ca astfel:

 // Nettuts.php clasa Nettuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Redactor> publica (); 

Acum, Nettuts nu are absolut nicio legătură cu Editor clasă. Nu include fișierul, nu își inițiază clasa și nici nu știe că există. Aș putea trece un obiect de orice tip în publishNextArticle () metoda și codul ar funcționa.


După cum puteți vedea din această diagramă a clasei, Nettuts are doar o relație directă Administrator. Administrator creează, și prin urmare, Administrator depinde de Nettuts. Dar Nettuts nu mai are nicio legătură cu Editor clasă și Editor are legătură doar cu Administrator.

În timpul execuției, Nettuts utilizează un Editor obiect, astfel <> și semnul întrebării. În timpul execuției, PHP inspectează obiectul primit și verifică dacă implementează setNextArticle () și publica() metode.

Informație despre membrii obiectului

Putem face ca PHP să afișeze detaliile unui obiect. Să creați un test PHPUnit care să ne ajute să exersăm cu ușurință codul nostru:

 // ReflectionTest.php necesită "... /Editor.php"; requ_once '... /Nettuts.php'; class ReflectionTest extinde PHPUnit_Framework_TestCase function testItCanReflect () $ editor = editor nou ("John Doe"); $ tuts = Nettuts noi (); $ Tuts-> publishNextArticle (editor $); 

Acum, adăugați a var_dump () la Nettuts:

 // Nettuts.php clasa NetTuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Redactor> publica (); var_dump (noul ReflectionClass ($ editor)); 

Rulați testul și urmăriți magia care apare în ieșire:

PHPUnit 3.6.11 de Sebastian Bergmann ... Obiect (ReflectionClass) # 197 (1) ["nume"] => șir (6) "Editor" Timp: 0 secunde Memorie: 2.25Mb OK (1 test, 0 afirmații)

Clasa noastră de reflecție are a Nume proprietate setată la tipul original al $ editor variabil: Editor, dar nu sunt multe informații. Ce ziceti Editormetode?

 // Nettuts.php clasa Nettuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Redactor> publica (); $ reflector = ReflectionClass ($ editor); var_dump ($ reflector-> getMethods ()); 

În acest cod, atribuim instanței clasei de reflecție " $ reflector astfel încât să putem declanșa acum metodele sale. ReflectionClass expune un set mare de metode pe care le puteți utiliza pentru a obține informația unui obiect. Una dintre aceste metode este getMethods (), care returnează o matrice care conține informațiile fiecărei metode.

 PHPUnit 3.6.11 de Sebastian Bergmann ... array (3) [0] => și obiect (ReflectionMethod) # 196 (2) ["nume"] => șir (11) "__construct" ["class"] = (14) "setNextArticle" ["class"] => șir (6) "Redactor"  [> "Nume"] => șir (7) "public" ["clasă"] => șir (6) , Memorie: 2.25Mb OK (1 test, 0 afirmații)

O altă metodă, getProperties (), preia proprietatile (chiar proprietati private!) ale obiectului:

 PHPUnit 3.6.11 de Sebastian Bergmann ... array (2) [0] => & obiect (ReflectionProperty) # 196 (2) ["nume"] = (6) "Editor" [1] => și obiect (ReflectionProperty) # 195 (2) "  Timp: 0 secunde, Memorie: 2.25Mb OK (1 test, 0 afirmații)

Elementele din matrice s-au întors de la getMethod () și getProperties () sunt de tip ReflectionMethod și ReflectionProperty, respectiv; aceste obiecte sunt destul de utile:

 // Nettuts.php clasa Nettuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Redactor> publica (); // primul apel pentru publicare () $ reflector = new ReflectionClass ($ editor); $ publishMethod = $ reflector-> getMethod ("publicare"); $ PublishMethod-> invoca (editor $); // al doilea apel pentru publicare ()

Aici, folosim getMethod () pentru a prelua o singură metodă cu numele de "publicare"; rezultatul căruia este a ReflectionMethod obiect. Apoi, numim invoca() metodă, trecând-o $ editor obiect, pentru a executa editorul publica() metoda a doua oară.

Acest proces a fost simplu în cazul nostru, pentru că am avut deja unul Editor obiect pentru a trece la invoca(). Putem avea mai multe Editor obiecte în unele circumstanțe, oferindu-ne luxul de a alege care obiect de utilizat. În alte situații, s-ar putea să nu avem obiecte cu care să lucrăm, caz în care ar trebui să obținem unul ReflectionClass.

Să modificăm Editor„s publica() metodă pentru a demonstra apelul dublu:

 // editor.php Editor [...] public function publish () // publica logica merge aici echo ("AICI \ n"); return true; 

Iar noua ieșire:

 PHPUnit 3.6.11 de Sebastian Bergmann ... AICI AICI Ora: 0 secunde, Memorie: 2.25Mb OK (1 test, 0 afirmații)

Manipularea datelor instanței

De asemenea, putem modifica codul la timpul de execuție. Dar modificarea unei variabile private care nu are setter public? Să adăugăm o metodă la Editor care preia numele editorului:

 // editor.php Editor de clasă private $ name; public $ articleId; funcția __construct ($ name) $ this-> name = $ name;  [...] funcția getEditorName () return $ this-> name; 

Această nouă metodă este numită, getEditorName (), și întoarce pur și simplu valoarea de la privat numele $ variabil. numele $ variabila este stabilită la momentul creării și nu avem metode publice care să ne permită să o schimbăm. Dar putem accesa această variabilă prin reflecție. S-ar putea să încercați mai întâi abordarea mai evidentă:

 // Nettuts.php clasa Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ("nume"); $ EditorName-> GetValue (editor $); 

Chiar dacă aceasta scoate valoarea la var_dump () line, aruncă o eroare atunci când încearcă să recupereze valoarea cu reflecție:

PHPUnit 3.6.11 de Sebastian Bergmann. Estring (8) "John Doe" Ora: 0 secunde, Memorie: 2.50Mb A apărut o eroare: 1) ReflectionTest :: testItCanReflect ReflectionException: NetTuts.php: 13 [...] / Reflecție în PHP / Sursă / Teste / ReflectionTest.php: 13 / usr / bin / phpunit: 46 FAILURES! Teste: 1, afirmații: 0, erori: 1.

Pentru a rezolva această problemă, trebuie să ne întrebăm ReflectionProperty obiect de a ne acorda acces la variabilele și metodele private:

// Nettuts.php clasa Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ("nume"); $ EditorName-> setAccessible (true); var_dump ($ editorName-> GetValue (editor $)); 

apel setAccessible () și trecerea Adevărat face truc:

PHPUnit 3.6.11 de Sebastian Bergmann ... șir (8) șir "John Doe" (8) "John Doe" Timp: 0 secunde, Memorie: 2.25Mb OK (1 test, 0 afirmații)

După cum puteți vedea, am reușit să citim variabila privată. Prima linie de ieșire este de la obiectul propriu getEditorName () metoda, iar cea de-a doua provine din reflecție. Dar cum să schimbi valoarea unei variabile private? Folosește SetValue () metodă:

// Nettuts.php clasa Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ("nume"); $ EditorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> GetValue (editor $)); 

Si asta e. Acest cod schimbă "John Doe" în "Mark Twain".

PHPUnit 3.6.11 de Sebastian Bergmann ... șir (8) șir "John Doe" (10) "Mark Twain" Timp: 0 secunde, Memorie: 2.25Mb OK (1 test, 0 afirmații)

Utilizarea reflecției indirecte

Unele dintre funcționalitățile încorporate din PHP utilizează indirect reflecția - una fiind call_user_func () funcţie.

Callback-ul

call_user_func () funcția acceptă o matrice: primul element care indică un obiect și al doilea nume o metodă. Puteți furniza un parametru opțional, care este apoi trecut la metoda apelată. De exemplu:

 // Nettuts.php clasa Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ("nume"); $ EditorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> GetValue (editor $)); var_dump (call_user_func (array ($ editor, 'getEditorName'))); 

Următorul ieșire demonstrează că codul preia valoarea corectă:

PHPUnit 3.6.11 de Sebastian Bergmann ... string (8) șir "John Doe" (10) șir "Mark Twain" (10) "Mark Twain" Timp: 0 secunde, Memorie: 2.25Mb OK (1 test, 0 assertions)

Utilizând o valoare a variabilei

Un alt exemplu de reflecție indirectă numește o metodă prin valoarea conținut într-o variabilă, spre deosebire de apelul direct. De exemplu:

 // Nettuts.php clasa Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ("nume"); $ EditorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> GetValue (editor $)); $ methodName = 'getEditorName'; var_dump ($ editor -> $ METHODNAME ()); 

Acest cod produce aceeași ieșire ca și exemplul anterior. PHP înlocuiește pur și simplu variabila cu șirul pe care îl reprezintă și apelează metoda. Ea chiar funcționează atunci când doriți să creați obiecte utilizând variabile pentru numele de clase.


Când trebuie să folosim reflecția?

Acum că am pus detaliile tehnice în spatele nostru, când trebuie să folosim reflecția? Iată câteva scenarii:

  • Înscriere dinamică este probabil imposibil fără reflecție.
  • Aspect Programare Oriented ascultă de apelurile metodice și locurile codifică metodele, toate realizate cu reflecție.
  • PHPUnit se bazează foarte mult pe reflecție, la fel ca și alte cadre de batjocură.
  • Web frameworks în reflecție de uz general în scopuri diferite. Unii îl folosesc pentru a inițializa modele, a construi obiecte pentru vizualizări și multe altele. Laravel face uz de reflecție pentru a injecta dependențe.
  • metaprogramarea, ca ultimul nostru exemplu, este o reflecție ascunsă.
  • Coduri de analiză a codurilor utilizați reflecția pentru a înțelege codul.

Gândurile finale

La fel ca orice jucărie rece, folosiți reflecția, dar nu abuzați-o. Reflecția este costisitoare atunci când inspectați multe obiecte și are potențialul de a complica arhitectura și designul proiectului. Vă recomandăm să o utilizați numai atunci când vă oferă un avantaj sau când nu aveți altă opțiune viabilă.

Personal, am folosit numai reflecții în câteva cazuri, cel mai frecvent când folosesc module terțe care nu au documentație. Mă găsesc frecvent folosind un cod similar ultimului exemplu. Este ușor să apelați metoda corectă atunci când MVC vă răspunde cu o variabilă care conține valori "adăugați" sau "eliminați".

Vă mulțumim pentru lectură!

Cod