Riflessione in PHP

La riflessione è generalmente definita come la capacità di un programma di ispezionarsi e modificare la sua logica al momento dell'esecuzione. In termini meno tecnici, la riflessione chiede a un oggetto di parlarvi delle sue proprietà e dei suoi metodi e di alterare quei membri (anche quelli privati). In questa lezione, analizzeremo come questo viene realizzato e quando potrebbe rivelarsi utile.


Un po 'di storia

All'alba dell'era della programmazione, c'era il linguaggio dell'assemblaggio. Un programma scritto in assembly risiede su registri fisici all'interno del computer. La sua composizione, i suoi metodi e i suoi valori potrebbero essere controllati in qualsiasi momento leggendo i registri. Inoltre, è possibile modificare il programma mentre era in esecuzione semplicemente modificando tali registri. Richiedeva una conoscenza approfondita del programma in esecuzione, ma era intrinsecamente riflessivo.

Come con qualsiasi giocattolo interessante, usa la riflessione, ma non abusarne.

Con l'arrivo di linguaggi di programmazione di livello superiore (come C), questa riflettività è sbiadita e scomparsa. In seguito è stato reintrodotto con la programmazione orientata agli oggetti.

Oggi, la maggior parte dei linguaggi di programmazione può utilizzare la riflessione. I linguaggi tipizzati staticamente, come ad esempio Java, non hanno problemi di riflessione. Ciò che trovo interessante, tuttavia, è che qualsiasi linguaggio tipizzato dinamicamente (come PHP o Ruby) è fortemente basato sulla riflessione. Senza il concetto di riflessione, la tipizzazione delle anatre sarebbe molto probabilmente impossibile da attuare. Quando si invia un oggetto a un altro (un parametro, ad esempio), l'oggetto destinatario non ha modo di conoscere la struttura e il tipo di quell'oggetto. Tutto ciò che può fare è usare la riflessione per identificare i metodi che possono e non possono essere chiamati sull'oggetto ricevuto.


Un semplice esempio

La riflessione è prevalente in PHP. In effetti, ci sono diverse situazioni in cui puoi usarlo senza nemmeno saperlo. Per esempio:

 // Nettuts.php require_once 'Editor.php'; class Nettuts function publishNextArticle () $ editor = new Editor ('John Doe'); $ Editor-> setNextArticle ( '135.523'); $ Editor-> pubblicare (); 

E:

 // Editor.php class Editor nome privato $; public $ articleId; function __construct ($ name) $ this-> nome = $ nome;  public function setNextArticle ($ articleId) $ this-> articleId = $ articleId;  public function publish () // la logica di pubblicazione va qui restituisce true; 

In questo codice, abbiamo una chiamata diretta a una variabile inizializzata localmente con un tipo noto. Creare l'editor in publishNextArticle () rende ovvio che il $ Editor la variabile è di tipo editore. Non è necessaria alcuna riflessione qui, ma introduciamo una nuova classe, chiamata 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 ($ editore); 

Quindi, modifica Nettuts, così:

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

Adesso, Nettuts non ha assolutamente alcuna relazione con il editore classe. Non include il suo file, non inizializza la sua classe e nemmeno sa che esiste. Potrei passare un oggetto di qualsiasi tipo nel publishNextArticle () metodo e il codice funzionerebbe.


Come puoi vedere da questo diagramma di classe, Nettuts ha solo una relazione diretta con Manager. Manager lo crea, e quindi, Manager dipende da Nettuts. Ma Nettuts non ha più alcuna relazione con il editore classe, e editore è solo collegato a Manager.

In fase di esecuzione, Nettuts usa un editore oggetto, quindi il <> e il punto interrogativo. In fase di esecuzione, PHP ispeziona l'oggetto ricevuto e verifica che implementa il file setNextArticle () e pubblicare() metodi.

Informazioni sui membri dell'oggetto

Possiamo far visualizzare a PHP i dettagli di un oggetto. Creiamo un test PHPUnit per aiutarci a esercitare facilmente il nostro codice:

 // ReflectionTest.php require_once '... /Editor.php'; require_once '... /Nettuts.php'; class ReflectionTest estende PHPUnit_Framework_TestCase function testItCanReflect () $ editor = new Editor ('John Doe'); $ tuts = new Nettuts (); $ Tuts-> publishNextArticle ($ editore); 

Ora, aggiungi un var_dump () a Nettuts:

 // Nettuts.php class NetTuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Editor-> pubblicare (); var_dump (new ReflectionClass ($ editor)); 

Esegui il test e osserva la magia che si verifica nell'output:

PHPUnit 3.6.11 di Sebastian Bergmann ... object (ReflectionClass) # 197 (1) ["name"] => string (6) "Editor" Tempo: 0 secondi, Memoria: 2.25Mb OK (1 test, 0 asserzioni)

La nostra classe di riflessione ha un nome proprietà impostata sul tipo originale di $ Editor variabile: editore, ma non sono molte informazioni Che dire editoreI metodi?

 // Nettuts.php class Nettuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Editor-> pubblicare (); $ reflector = new ReflectionClass ($ editor); var_dump ($ reflector-> GetMethods ()); 

In questo codice, assegniamo l'istanza della classe reflection 'a $ riflettore variabile in modo che ora possiamo attivare i suoi metodi. ReflectionClass espone un ampio set di metodi che è possibile utilizzare per ottenere le informazioni di un oggetto. Uno di questi metodi è GetMethods (), che restituisce una matrice contenente le informazioni di ciascun metodo.

 PHPUnit 3.6.11 di Sebastian Bergmann ... array (3) [0] => & object (ReflectionMethod) # 196 (2) ["name"] => string (11) "__construct" ["class"] => stringa (6) "Editor" [1] => & object (ReflectionMethod) # 195 (2) ["name"] => string (14) "setNextArticle" ["class"] => string (6) "Editor"  [2] => & object (ReflectionMethod) # 194 (2) ["name"] => string (7) "publish" ["class"] => string (6) "Editor" Tempo: 0 secondi , Memoria: 2,25 MB OK (1 test, 0 asserzioni)

Un altro metodo, getProperties (), recupera le proprietà (anche proprietà private!) dell'oggetto:

 PHPUnit 3.6.11 di Sebastian Bergmann ... array (2) [0] => & object (ReflectionProperty) # 196 (2) ["name"] => string (4) "name" ["class"] => stringa (6) "Editor" [1] => & object (ReflectionProperty) # 195 (2) ["name"] => string (9) "articleId" ["class"] => string (6) "Editor"  Tempo: 0 secondi, Memoria: 2,25 Mb OK (1 test, 0 asserzioni)

Gli elementi negli array sono tornati da getMethod () e getProperties () sono di tipo ReflectionMethod e ReflectionProperty, rispettivamente; questi oggetti sono abbastanza utili:

 // Nettuts.php class Nettuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ Editor-> pubblicare (); // prima chiamata a publish () $ reflector = new ReflectionClass ($ editor); $ publishMethod = $ reflector-> getMethod ('publish'); $ PublishMethod-> invocare ($ editore); // seconda chiamata a pubblicare ()

Qui, usiamo getMethod () recuperare un singolo metodo con il nome di "pubblica"; il cui risultato è a ReflectionMethod oggetto. Quindi, chiamiamo il invocare() metodo, passando il $ Editor oggetto, per eseguire l'editor pubblicare() metodo una seconda volta.

Questo processo è stato semplice nel nostro caso, perché ne avevamo già uno editore oggetto da passare a invocare(). Potremmo avere diversi editore oggetti in alcune circostanze, dandoci il lusso di scegliere quale oggetto usare. In altre circostanze, potremmo non avere oggetti con cui lavorare, nel qual caso dovremmo ottenerne uno da ReflectionClass.

Modifichiamo editore'S pubblicare() metodo per dimostrare la doppia chiamata:

 // Editor.php class Editor [...] public function publish () // La logica di pubblicazione va qui echo ("QUI \ n"); ritorna vero; 

E la nuova uscita:

 PHPUnit 3.6.11 di Sebastian Bergmann ... QUI QUI Tempo: 0 secondi, Memoria: 2,25 Mb OK (1 test, 0 asserzioni)

Manipolazione dei dati di istanza

Possiamo anche modificare il codice al momento dell'esecuzione. Che dire di modificare una variabile privata che non ha setter pubblico? Aggiungiamo un metodo a editore che recupera il nome dell'editore:

 // Editor.php class Editor nome privato $; public $ articleId; function __construct ($ name) $ this-> nome = $ nome;  [...] function getEditorName () return $ this-> name; 

Questo nuovo metodo è chiamato, getEditorName (), e semplicemente restituisce il valore dal privato nome $ variabile. Il nome $ la variabile è impostata al momento della creazione e non abbiamo metodi pubblici che ci permettano di cambiarla. Ma possiamo accedere a questa variabile usando la riflessione. Potresti prima provare l'approccio più ovvio:

 // Nettuts.php class Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = new ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ EditorName-> getValue ($ editore); 

Anche se questo produce il valore in var_dump () linea, genera un errore quando si tenta di recuperare il valore con la riflessione:

PHPUnit 3.6.11 di Sebastian Bergmann. Estring (8) Tempo "John Doe": 0 secondi, Memoria: 2.50Mb Si è verificato 1 errore: 1) ReflectionTest :: testItCanReflect ReflectionException: impossibile accedere all'Editor membro non pubblico :: nome [...] / Reflection in PHP / Source / NetTuts.php: 13 [...] / Reflection in PHP / Source / Test / ReflectionTest.php: 13 / usr / bin / phpunit: 46 GUASTI! Test: 1, Asserzioni: 0, Errori: 1.

Per risolvere questo problema, dobbiamo chiedere al ReflectionProperty oggetto di concederci l'accesso alle variabili e ai metodi privati:

// Nettuts.php class Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = new ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ EditorName-> setAccessible (true); var_dump ($ editorName-> getValue ($ editore)); 

chiamata setAccessible () e passando vero fa il trucco:

PHPUnit 3.6.11 di Sebastian Bergmann ... string (8) "John Doe" stringa (8) "John Doe" Tempo: 0 secondi, Memoria: 2.25Mb OK (1 test, 0 asserzioni)

Come puoi vedere, siamo riusciti a leggere la variabile privata. La prima riga di output proviene dal proprio oggetto getEditorName () metodo, e il secondo viene dalla riflessione. Ma che dire del valore di una variabile privata? Utilizzare il valore impostato() metodo:

// Nettuts.php class Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = new ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ EditorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ editore)); 

E questo è tutto. Questo codice cambia "John Doe" in "Mark Twain".

PHPUnit 3.6.11 di Sebastian Bergmann ... string (8) "John Doe" stringa (10) "Mark Twain" Tempo: 0 secondi, Memoria: 2.25Mb OK (1 test, 0 asserzioni)

Uso di riflessione indiretta

Alcune delle funzionalità integrate di PHP utilizzano indirettamente la riflessione: una è la call_user_func () funzione.

Il callback

Il call_user_func () la funzione accetta un array: il primo elemento che punta a un oggetto e il secondo il nome di un metodo. È possibile fornire un parametro opzionale, che viene quindi passato al metodo chiamato. Per esempio:

 // Nettuts.php class Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = new ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ EditorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ editore)); var_dump (call_user_func (array ($ editor, 'getEditorName'))); 

Il seguente output dimostra che il codice recupera il valore corretto:

PHPUnit 3.6.11 di Sebastian Bergmann ... string (8) "John Doe" stringa (10) "Mark Twain" stringa (10) "Mark Twain" Tempo: 0 secondi, Memoria: 2.25Mb OK (1 test, 0 asserzioni)

Usando il valore di una variabile

Un altro esempio di riflessione indiretta è chiamare un metodo dal valore contenuto all'interno di una variabile, invece di chiamarlo direttamente. Per esempio:

 // Nettuts.php class Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = new ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ EditorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ editore)); $ methodName = 'getEditorName'; var_dump ($ redattore -> $ methodName ()); 

Questo codice produce lo stesso risultato dell'esempio precedente. PHP sostituisce semplicemente la variabile con la stringa che rappresenta e chiama il metodo. Funziona anche quando vuoi creare oggetti usando le variabili per i nomi delle classi.


Quando dovremmo usare la riflessione?

Ora che abbiamo messo i dettagli tecnici dietro di noi, quando dovremmo sfruttare la riflessione? Ecco alcuni scenari:

  • Digitazione dinamica è probabilmente impossibile senza riflessione.
  • Programmazione orientata all'aspetto ascolta le chiamate ai metodi e inserisce il codice attorno ai metodi, il tutto realizzato con la riflessione.
  • PHPUnit fa molto affidamento sulla riflessione, così come altri quadri beffardi.
  • Strutture Web in generale, utilizzare la riflessione per scopi diversi. Alcuni lo usano per inizializzare i modelli, costruire oggetti per le viste e altro ancora. Laravel fa un uso pesante della riflessione per iniettare dipendenze.
  • metaprogrammazione, come il nostro ultimo esempio, è il riflesso nascosto.
  • Quadri di analisi del codice usa la riflessione per capire il tuo codice.

Pensieri finali

Come con qualsiasi giocattolo interessante, usa la riflessione, ma non abusarne. La riflessione è costosa quando si ispezionano molti oggetti e questo ha il potenziale di complicare l'architettura e il design del progetto. Ti raccomando di farne uso solo quando ti dà effettivamente un vantaggio, o quando non hai altra opzione praticabile.

Personalmente, ho usato la riflessione solo in alcuni casi, più comunemente quando si utilizzano moduli di terze parti privi di documentazione. Mi trovo spesso ad usare codice simile all'ultimo esempio. È facile chiamare il metodo corretto, quando il tuo MVC risponde con una variabile contenente valori "aggiungi" o "rimuovi".

Grazie per aver letto!