Decifrare i metodi magici in PHP

PHP fornisce una serie di metodi "magici" che ti permettono di fare alcuni trucchetti abbastanza accurati nella programmazione orientata agli oggetti. Questi metodi, identificati da un prefisso di sottolineatura due (__), funzionano come intercettori che vengono chiamati automaticamente quando vengono soddisfatte determinate condizioni. I metodi magici forniscono alcune funzionalità estremamente utili e questo tutorial dimostrerà l'uso di ciascun metodo.


Prima di iniziare

Per comprendere appieno i metodi magici, è utile vederli in azione. Quindi iniziamo con una serie base di classi molto semplici. Qui definiamo due classi: dispositivo e batteria.

connection = 'resource'; echo $ this-> name. 'connesso'. PHP_EOL;  protected function disconnect () // disconnette in modo sicuro dalla rete $ this-> connection = null; echo $ this-> name. 'disconnesso'. PHP_EOL;  batteria di classe private $ charge = 0; funzione pubblica setCharge ($ charge) $ charge = (int) $ charge; if ($ carica < 0)  $charge = 0;  elseif($charge > 100) $ charge = 100;  $ this-> charge = $ charge; ?>

Se parole come "metodo" e "proprietà" ti sembrano estranee, potresti prima leggere questo argomento.

Gli oggetti dispositivo avranno un nome, un oggetto Batteria, una matrice di dati e un handle per alcune risorse esterne. Hanno anche metodi per connettere e disconnettere la risorsa esterna. Gli oggetti batteria semplicemente memorizzano una carica in una proprietà privata e hanno un metodo per impostare la carica.

Questo tutorial presume che tu abbia una conoscenza di base della programmazione orientata agli oggetti. Se parole come "metodo" e "proprietà" ti sembrano estranee, potresti volerlo leggere prima.

Queste classi sono piuttosto inutili, ma sono un buon esempio per ciascuno dei metodi magici. Così ora che abbiamo creato le nostre classi semplici, possiamo provare i metodi magici.


Costruttori e distruttori

Costruttori e distruttori sono chiamati quando un oggetto viene creato e distrutto, rispettivamente. Un oggetto viene "distrutto" quando non ci sono più riferimenti ad esso, sia perché la variabile che lo contiene è stato disinserito / riassegnato o lo script ha terminato l'esecuzione.

__costruire()

Il __costruire() il metodo è di gran lunga il metodo magico più comunemente usato. Qui è dove si fa qualsiasi inizializzazione di cui si ha bisogno quando viene creato un oggetto. Qui puoi definire qualsiasi numero di argomenti, che verranno passati durante la creazione di oggetti. Qualsiasi valore di ritorno sarà passato attraverso il nuovo parola chiave. Qualsiasi eccezione generata nel costruttore interromperà la creazione dell'oggetto.

class Device // ... public function __construct (batteria $ battery, $ name) // $ la batteria può essere solo un oggetto batteria valido $ this-> battery = $ battery; $ this-> nome = $ nome; // connettersi alla rete $ this-> connect ();  // ...

La dichiarazione del metodo di costruzione "privato" impedisce al codice esterno di creare direttamente un oggetto.

Qui abbiamo dichiarato un costruttore che accetta due argomenti, una batteria e un nome. Il costruttore assegna ciascuna delle proprietà che gli oggetti richiedono per funzionare ed esegue il Collegare() metodo. Il costruttore ti permette di assicurarti che un oggetto abbia tutti i pezzi necessari prima che possa esistere.

Mancia: La dichiarazione del metodo di costruzione 'privato' impedisce al codice esterno di creare direttamente un oggetto. Questo è utile per creare classi singleton che limitano il numero di oggetti che possono esistere.

Con il costruttore di cui sopra, ecco come si crea un dispositivo chiamato "iMagic":

$ device = new Device (nuova batteria (), 'iMagic'); // iMagic ha collegato echo $ device-> name; // iMagic

Come puoi vedere, gli argomenti passati alla classe vengono effettivamente passati al metodo del costruttore. Si può anche dire che il metodo di connessione è stato chiamato e il nome $ la proprietà era popolata.

Diciamo che ci dimentichiamo di passare un nome. Ecco cosa succede:

$ dispositivo = nuovo dispositivo (nuova batteria ()); // Risultato: PHP Warning: argomento mancante 2 per Device :: __ construct ()

__destruct ()

Come suggerisce il nome, il __destruct () il metodo viene chiamato quando l'oggetto viene distrutto. Non accetta argomenti e viene comunemente utilizzato per eseguire operazioni di pulizia come la chiusura di una connessione al database. Nel nostro caso, lo useremo per disconnetterci dalla rete.

class Device // ... public function __destruct () // disconnette dalla rete $ this-> disconnect (); echo $ this-> name. ' è stato distrutto' . PHP_EOL;  // ...

Con il distruttore di cui sopra, ecco cosa succede quando un oggetto Device viene distrutto:

$ device = new Device (nuova batteria (), 'iMagic'); // iMagic connected unset ($ device); // iMagic disconnesso // iMagic è stato distrutto

Qui, abbiamo distrutto l'oggetto usando unset (). Prima che venga distrutto, il distruttore chiama il disconnect () metodo e stampa un messaggio, che puoi vedere nei commenti.


Sovraccarico di proprietà

Nota: La versione di PHP di "overloading" non è esattamente la stessa della maggior parte degli altri linguaggi, anche se è possibile raggiungere gli stessi risultati.

Questo prossimo set di metodi magici riguarda l'accesso alle proprietà, definendo cosa succede quando si tenta di accedere a una proprietà che non esiste (o non è accessibile). Possono essere usati per creare pseudo proprietà. Questo è chiamato overloading in PHP.

__ottenere()

Il __ottenere() il metodo viene chiamato quando il codice tenta di accedere a una proprietà che non è accessibile. Accetta un argomento, che è il nome della proprietà. Dovrebbe restituire un valore, che sarà trattato come il valore della proprietà. Ricorda il $ data proprietà nella nostra classe di dispositivi? Archiviamo queste "pseudo proprietà" come elementi nell'array di dati e possiamo consentire agli utenti della nostra classe di accedervi tramite __ottenere(). Ecco come appare:

class Device // ... public function __get ($ name) // controlla se la chiave con nome esiste nel nostro array if (array_key_exists ($ name, $ this-> data)) // quindi restituisce il valore dall'array return $ this-> dati [nome $];  return null;  // ...

Un uso popolare di __ottenere() il metodo è di estendere il controllo di accesso creando proprietà "di sola lettura". Prendi la nostra classe di batteria, ad esempio, che ha una proprietà privata. Possiamo consentire il privato $ carica proprietà da leggere dal codice esterno, ma non modificata. Il codice sarebbe simile a questo:

classe Battery private $ charge = 0; funzione pubblica __get ($ name) if (isset ($ this -> $ name)) return $ this -> $ name;  return null;  // ...

In questo esempio, si noti l'uso di variabili variabili per accedere dinamicamente a una proprietà. Supponendo il valore 'utente' per nome $, $ This -> $ name si traduce in $ This-> user.

__impostato()

Il __impostato() il metodo viene chiamato quando il codice tenta di modificare il valore di una proprietà che non è accessibile. Accetta due argomenti, che sono il nome della proprietà e il valore. Ecco come appare l'array "pseudo variabili" nella nostra classe Device:

class Device // ... public function __set ($ name, $ value) // usa il nome della proprietà come la chiave dell'array $ this-> data [$ name] = $ value;  // ...

__è impostato()

Il __è impostato() il metodo viene chiamato quando le chiamate di codice è impostato() su una proprietà che non è accessibile. Accetta un argomento, che è il nome della proprietà. Dovrebbe restituire un valore booleano che rappresenta l'esistenza di un valore. Di nuovo usando il nostro array di variabili, ecco come appare:

class Device // ... public function __isset ($ name) // potresti anche usare isset () restituisce array_key_exists ($ name, $ this-> data);  // ...

__unset ()

Il __unset () il metodo viene chiamato quando il codice tenta di unset () una proprietà che non è accessibile. Accetta un argomento, che è il nome della proprietà. Ecco come appare il nostro:

class Device // ... public function __unset ($ name) // inoltra unset () al nostro elemento dell'array unset ($ this-> data [$ name]);  // ...

Sovraccarico di proprietà in azione

Ecco tutti i metodi magici relativi alla proprietà che abbiamo dichiarato:

class Device // ... public $ data = array (); // memorizza misc. dati in un array // ... public function __get ($ name) // controlla se la chiave con nome esiste nel nostro array if (array_key_exists ($ name, $ this-> data)) // quindi restituisce il valore dall'array restituire $ this-> data [$ name];  return null;  public function __set ($ name, $ value) // usa il nome della proprietà come la chiave dell'array $ this-> data [$ name] = $ value;  public function __isset ($ name) // potresti anche usare isset () restituisce array_key_exists ($ name, $ this-> data);  public function __unset ($ name) // inoltra unset () al nostro elemento dell'array unset ($ this-> data [$ name]);  // ...

Con i metodi magici di cui sopra, ecco cosa succede quando proviamo ad accedere a una proprietà chiamata name. Ricorda che non c'è davvero un nome $ dichiarata proprietà, anche se non lo sapresti mai senza vedere il codice di classe interno.

$ device-> user = 'Steve'; echo $ dispositivo-> utente; // Steve

Abbiamo impostato e recuperato con successo il valore di una proprietà inesistente. Bene, dove è memorizzato allora?

print_r ($ dispositivo-> dati); / * Array ([utente] => Steve) * /

Come puoi vedere, il $ data la proprietà ora contiene un elemento "nome" con il nostro valore.

var_dump (isset ($ dispositivo-> utente)); // bool (vero)

Sopra è il risultato della chiamata è impostato() sulla proprietà falsa.

unset ($ dispositivo-> utente); var_dump (isset ($ dispositivo-> utente)); // bool (false)

Sopra è il risultato di disinserire la proprietà falsa. Per sicurezza, ecco la nostra matrice di dati vuota:

print_r ($ dispositivo-> dati); / * Array () * /

Rappresentare gli oggetti come testo

A volte potresti voler convertire un oggetto in una rappresentazione di stringa. Se provi semplicemente a stampare un oggetto, riceverai un errore, come quello seguente:

$ device = new Device (nuova batteria (), 'iMagic'); echo $ dispositivo; // Risultato: PHP Errore irreversibile Catchable: Oggetto della classe Il dispositivo non può essere convertito in stringa

__accordare()

Il __accordare() il metodo viene chiamato quando il codice tenta di trattare un oggetto come una stringa. Non accetta argomenti e dovrebbe restituire una stringa. Questo ci permette di definire come verrà rappresentato l'oggetto. Nel nostro esempio, creeremo un semplice sommario:

class Device ... public function __toString () // siamo connessi? $ connected = (isset ($ this-> connection))? 'connesso': 'disconnesso'; // quanti dati abbiamo? $ count = count ($ this-> data); // metti tutto insieme restituisci $ this-> name. 'è'. $ connesso. ' con ' . Conteggio $. 'elementi in memoria'. PHP_EOL;  ...

Con il metodo sopra definito, ecco cosa succede quando proviamo a stampare un oggetto Device:

$ device = new Device (nuova batteria (), 'iMagic'); echo $ dispositivo; // iMagic è connesso con 0 elementi in memoria

L'oggetto Dispositivo è ora rappresentato da un breve riepilogo contenente il nome, lo stato e il numero di elementi memorizzati.

__set_state () (PHP 5.1)

L'elettricità statica __set_state () metodo (disponibile a partire dalla versione 5.1 di PHP) viene chiamato quando il var_export () la funzione è chiamata sul nostro oggetto. Il var_export () la funzione è usata per convertire una variabile in codice PHP. Questo metodo accetta un array associativo contenente i valori di proprietà dell'oggetto. Per ragioni di semplicità, usalo bene nella nostra classe Battery.

class Battery // ... public static function __set_state (array $ array) $ obj = new self (); $ Obj-> setCharge ($ array [ 'carica']); return $ obj;  // ...

Il nostro metodo crea semplicemente un'istanza della sua classe genitore e imposta l'addebito sul valore dell'array passato. Con il metodo sopra definito, ecco cosa succede quando usiamo var_export () su un oggetto Device:

$ device = new Device (nuova batteria (), 'iMagic'); var_export ($ dispositivo-> batteria); / * Batteria :: __ set_state (array ('charge' => 0,)) * / eval ('$ battery ='. Var_export ($ device-> battery, true). ';'); var_dump ($ batteria); / * object (Battery) # 3 (1) ["charge: private"] => int (0) * /

Il primo commento mostra cosa sta realmente accadendo, che è quello var_export () semplicemente chiama Batteria :: __ set_state (). Il secondo commento ci mostra ricreando con successo la batteria.


Clonazione di oggetti

Gli oggetti, per impostazione predefinita, vengono passati per riferimento. Assegnare altre variabili a un oggetto non copierà effettivamente l'oggetto, ma creerà semplicemente un nuovo riferimento allo stesso oggetto. Per copiare veramente un oggetto, dobbiamo usare il clone parola chiave.

Questa politica 'passaggio per riferimento' si applica anche agli oggetti all'interno degli oggetti. Anche se cloniamo un oggetto, qualsiasi oggetto figlio che contiene non verrà clonato. Quindi finiremmo con due oggetti che condividono lo stesso oggetto figlio. Ecco un esempio che illustra che:

$ device = new Device (nuova batteria (), 'iMagic'); $ device2 = clone $ dispositivo; $ Dispositivo-> a batteria> setCharge (65); echo $ device2-> battery-> charge; // 65

Qui, abbiamo clonato un oggetto Device. Ricorda che tutti gli oggetti Device contengono un oggetto Battery. Per dimostrare che entrambi i cloni del dispositivo condividono la stessa batteria, la modifica apportata alla batteria del dispositivo $ si riflette nella batteria di $ device2.

__clone()

Il __clone() il metodo può essere usato per risolvere questo problema. Viene chiamato sulla copia di un oggetto clonato dopo che ha avuto luogo la clonazione. Qui è dove puoi clonare qualsiasi oggetto figlio.

class Device ... public function __clone () // copia il nostro oggetto Battery $ this-> battery = clone $ this-> battery;  ...

Con questo metodo dichiarato, ora possiamo essere sicuri che i Dispositivi clonati hanno ciascuno la propria Batteria.

$ device = new Device (nuova batteria (), 'iMagic'); $ device2 = clone $ dispositivo; $ Dispositivo-> a batteria> setCharge (65); echo $ device2-> battery-> charge; // 0

Le modifiche alla batteria di un dispositivo non influiscono sull'altra.


Serializzazione degli oggetti

La serializzazione è il processo che converte tutti i dati in un formato stringa. Questo può essere usato per memorizzare interi oggetti in un file o database. Quando si unserialize i dati memorizzati, si avrà l'oggetto originale esattamente come era prima. Un problema con la serializzazione, tuttavia, è che non tutto può essere serializzato, come le connessioni al database. Fortunatamente ci sono alcuni metodi magici che ci permettono di gestire questo problema.

__dormire()

Il __dormire() il metodo è chiamato quando il serialize () la funzione è chiamata sull'oggetto. Non accetta argomenti e dovrebbe restituire una matrice di tutte le proprietà che dovrebbero essere serializzate. È inoltre possibile completare qualsiasi attività in sospeso o pulizia che potrebbe essere necessaria in questo metodo.

Mancia: Evita di fare qualcosa di distruttivo __dormire() dal momento che questo influenzerà l'oggetto live, e potrebbe non essere sempre fatto con esso.

Nel nostro esempio di dispositivo, la proprietà di connessione rappresenta una risorsa esterna che non può essere serializzata. Quindi il nostro __dormire() il metodo restituisce semplicemente una matrice di tutte le proprietà tranne collegamento $.

class Device nome pubblico $; // il nome del dispositivo $ $ batteria; // detiene un oggetto Battery pubblico $ data = array (); // memorizza misc. dati in una connessione $ public array; // contiene alcune risorse di connessione // ... public function __sleep () // elenca le proprietà per salvare l'array di restituzione ('name', 'battery', 'data');  // ...

Nostro __dormire() semplicemente restituisce una lista dei nomi delle proprietà che dovrebbero essere preservate.

__svegliati()

Il __svegliati() il metodo è chiamato quando il unserialize () la funzione viene richiamata sull'oggetto memorizzato. Non accetta argomenti e non ha bisogno di restituire nulla. Usalo per ristabilire qualsiasi connessione al database o risorsa persa nella serializzazione.

Nel nostro esempio di dispositivo, abbiamo semplicemente bisogno di ristabilire la connessione chiamando il nostro Collegare() metodo.

class Device // ... public function __wakeup () // riconnettersi alla rete $ this-> connect ();  // ...

Sovraccarico del metodo

Questi ultimi due metodi sono per trattare i metodi. Questo è lo stesso concetto dei metodi di sovraccarico delle proprietà (__ottenere(), __impostato(), ecc.), ma applicato ai metodi.

__chiamata()

Il __chiamata() viene chiamato quando il codice tenta di chiamare metodi inaccessibili o inesistenti. Accetta due argomenti: il nome del metodo chiamato e un array di argomenti. È possibile utilizzare queste informazioni per chiamare lo stesso metodo in un oggetto figlio, ad esempio.

Negli esempi, si noti l'uso di call_user_func_array () funzione. Questa funzione ci consente di chiamare dinamicamente una funzione (o un metodo) con gli argomenti memorizzati in una matrice. Il primo argomento identifica la funzione da chiamare. Nel caso dei metodi di denominazione, il primo argomento è un array contenente un nome di classe o un'istanza di oggetto e il nome della proprietà. Il secondo argomento è sempre un array indicizzato di argomenti da passare.

Nel nostro esempio, passeremo la chiamata al metodo al nostro collegamento $ proprietà (che supponiamo sia un oggetto). Restituiremo il risultato di questo diritto al codice chiamante.

class Device // ... public function __call ($ name, $ arguments) // assicurati che il nostro oggetto child abbia questo metodo if (method_exists ($ this-> connection, $ name)) // inoltra la chiamata a nostro figlio object return call_user_func_array (array ($ this-> connection, $ name), $ argomenti);  return null;  // ...

Il metodo sopra sarebbe chiamato se provassimo a chiamare il iDontExist () metodo:

$ device = new Device (nuova batteria (), 'iMagic'); $ Dispositivo-> iDontExist (); // __call () inoltra questo a $ dispositivo-> connessione-> iDontExist ()

__callStatic () (PHP 5.3)

Il __callStatic () (disponibile dalla versione 5.3 di PHP) è identico a __chiamata() tranne che viene chiamato quando il codice tenta di chiamare metodi inaccessibili o inesistenti in un contesto statico.

Le uniche differenze nel nostro esempio sono che dichiariamo il metodo come statico e facciamo riferimento a un nome di classe anziché a un oggetto.

class Device // ... public static function __callStatic ($ name, $ arguments) // assicurati che la nostra classe abbia questo metodo if (method_exists ('Connection', $ name)) // inoltra la chiamata statica al nostro ritorno classe call_user_func_array (array ('Connection', $ name), $ argomenti);  return null;  // ...

Il metodo sopra sarebbe chiamato se provassimo a chiamare la statica iDontExist () metodo:

Dispositivo :: iDontExist (); // __callStatic () lo inoltra a Connection :: iDontExist ()

Utilizzare gli oggetti come funzioni

A volte potresti voler usare un oggetto come una funzione. Essere in grado di utilizzare un oggetto come una funzione consente di passare le funzioni in giro come argomenti come è possibile in altre lingue.

__invoke () (PHP 5.3)

Il __invocare() (disponibile dalla versione 5.3 di PHP) viene chiamato quando il codice tenta di utilizzare l'oggetto come una funzione. Gli argomenti definiti in questo metodo verranno utilizzati come argomenti della funzione. Nel nostro esempio, stamperemo semplicemente l'argomento che riceve.

class Device // ... public function __invoke ($ data) echo $ data;  // ...

Con quanto sopra definito, questo è ciò che accade quando utilizziamo un dispositivo come funzione:

$ device = new Device (nuova batteria (), 'iMagic'); $ Apparecchio ( 'test'); // equiv to $ device -> __ invoke ('test') // Output: test

Bonus: __autoload ()

Questo non è un metodo magico, ma è comunque molto utile. Il __autoload () la funzione viene automaticamente chiamata quando viene referenziata una classe che non esiste. Ha lo scopo di darti un'ultima possibilità di caricare il file contenente la dichiarazione di classe prima che lo script fallisca. Questo è utile dal momento che non sempre si desidera caricare ogni classe nel caso in cui ne sia necessario.

La funzione accetta un argomento: il nome della classe di riferimento. Supponi di avere ciascuna classe in un file chiamato "classname.class.php" nella directory "inc". Ecco come sarà il tuo autoload:

function __autoload ($ class_name) $ class_name = strtolower ($ class_name); include_once './inc/'. $ class_name. '.Class.php'; 

Conclusione

I metodi magici sono estremamente utili e forniscono potenti strumenti per lo sviluppo di framework applicativi flessibili. Portano gli oggetti PHP più vicino a quelli in altri linguaggi orientati agli oggetti, consentendo di riprodurre alcune delle loro funzionalità più utili. Qui puoi leggere le pagine del manuale PHP sui metodi magici. Spero che questo tutorial sia stato utile e abbia chiaramente spiegato i concetti. Se avete domande, non esitate a chiedere nei commenti. Grazie per aver letto.