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.
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.
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 <setNextArticle ()
e pubblicare()
metodi.
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 editore
I 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)
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)
Alcune delle funzionalità integrate di PHP utilizzano indirettamente la riflessione: una è la call_user_func ()
funzione.
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)
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.
Ora che abbiamo messo i dettagli tecnici dietro di noi, quando dovremmo sfruttare la riflessione? Ecco alcuni scenari:
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!