I metodi magici in PHP.
Un metodo magico è un metodo di una classe che viene chiamato automaticamente al verificarsi determinati eventi all'interno dell'oggetto istanziato della classe.
Durante la presenziane della classi, ci siamo già imbattuti nel metodo costruttore, che non è altro che un metodo di un classe chiamato automaticamente non appena viene istanziato un oggetto della classe stessa. Lo rivediamo rapidamente oggi, assieme agli altri metodi magici:
- __construct
- __destruct
- __toString
- __get
- __set
- __call
- __callStatic
Il metodo magico __construct
Questo metodo viene chiamato automaticamente quando istanziamo un oggetto della classe.
In questo esempio abbiamo una class Users che contiene il metodo costruttore
<?php
class Users{
public function __construct(){
echo "ciao";
}
}
$obj=new Users();
?>
Non appena istanziamo un oggetto $obj di classe Users, viene chiamata automaticamente la classe costruttore, e questa rende la frase "ciao".
Grazi al costruttore possiamo anche passare parametri alla classe, come in questo esempio.
<?php
class Users{
public function __construct($nome){
echo "ciao da $nome";
}
}
$obj=new Users('giulio');
?>
Il metodo magico __destruct
Viene chiamato automaticamente quando
- o non ci sono altri rifimenenti all'oggetto all'interno dello script
- o stiamo provando manualmente a distruggere l'oggetto
Vediamo un esempio
<?php
class Users{
public function __construct(){
echo "metodo costruttore<br />";
}
public function __destruct(){
echo "metodo distruttore<br />";
}
}
$obj=new Users;
echo "<h1>hello world</h1>";
?>
Lanciando lo script il risultato sarà
metodo costruttore
hello world
metodo distruttore
Il metodo __destruct non distrugge l'oggetto ma è di aiuto allo sviluppatore per eseguire delle operazioni un attimo prima che l'oggetto venga distrutto.
Se invece vogliamo distruggere manualmente l'oggetto, in un punto del codice, utilizziamo unset
Ecco il nostro esempio
<?php
class Users{
public function __construct(){
echo "metodo costruttore<br />";
}
public function __destruct(){
echo "metodo distruttore";
}
}
$obj=new Users;
unset($obj);
echo "<h1>hello world</h1>";
?>
Il risultato adesso sarà
metodo costruttore
metodo distruttore
hello world
Vediamo un altro esempio, più articolato, che contiene una classe "DistruggoTest" e vediamo come creare un file, scriverci dentro, e distruggerlo
<?php
class DistruggoTest{
public $fileHandler;
// apre file o lo crea w se non esiste
public function openFile(){
$this->fileHandler=fopen("file.txt","w");
}
// scrive il file
public function scriviSuFile($testo){
fwrite($this->fileHandler,$testo);
}
// chiude il file se non è ancora stato fatto
public function __destruct()
{
if($this->fileHandler){
fclose($this->fileHandler);
}
echo "adesso sono distrutto";
}
}
// istanziamo un oggetto di classe DistruggoTest
$obj=new DistruggoTest;
$obj->openFile();
$obj->scriviSuFile('ciao');
?>
Il metodo magico __toString
Viene richiamato quando proviamo a trattare l'oggetto, cioè l'istanza di una classe, come una stringa, ad esempio facendo un "echo".
Riprendiamo l'esempio precedente. Se dopo aver creato l'oggetto $obj, faccio subito un "echo $obj", avrò questo errore
Recoverable fatal error: Object of class DistruggoTest could not be converted to string in /var/www/html/test/index.php on line 28
Per evitare questo errore aggiungiamo il metodo __toString alla classe, in questo modo:
<?php
class DistruggoTest{
public $fileHandler;
// apre file o lo crea w se non esiste
public function openFile(){
$this->fileHandler=fopen("file.txt","w");
}
// scrive il file
public function scriviSuFile($testo){
fwrite($this->fileHandler,$testo);
}
// chiude il file se non è ancora stato fatto
public function __destruct()
{
if($this->fileHandler){
fclose($this->fileHandler);
}
echo "adesso sono distrutto";
}
// chiude il file se non è ancora stato fatto
public function __toString()
{
return "siamo nel metodo __toString<br>";
}
}
// istanziamo un oggetto di classe DistruggoTest
$obj=new DistruggoTest;
echo $obj;
?>
Facendo l'echo dell'oggetto, risponderà automaticamente il metodo __toString, e poi verrà automaticamente chiamata anche la __destruct
siamo nel metodo __toString
adesso sono distrutto
Il metodo magico __get
Questo metodo viene richiamato quando proviamo a leggere il valore di una proprietà che non esistente o che non è accessibile perchè ha un livello di visibilità che non ne consente l'accesso.
In questo esempio, tento di accecedere alla proprietà "nome33" che non esiste. L'oggetto non rende nulla.
<?php
class User{
public $nome;
public $email;
}
$obj=new User;
$obj->nome33;
?>
Implementiamo quindi il metodo magico __get, che ha come argomento il nome della proprietà, e che renda una frase in cui si avvisa che quella proprietà non è accessibile
<?php
class User{
public $nome;
public $email;
public function __get($name)
{
echo "la proprietà $name non è accessibile";
}
}
$obj=new User;
$obj->nome33;
?>
Ecco la risposta
la proprietà nome33 non è accessibile
Il metodo magico __set
Questo metodo viene richiamato quando proviamo a settare il valore di una proprietà che non esistente o che non è accessibile perchè ha un livello di visibilità che non ne consente l'accesso.
Se nell'esempio precedente, dopo aver istanziato l'oggetto tentassi di settare la proprietà di "nome35" che non esiste
$obj=new User;
$obj->nome35=10;
non otterrei risposta perchè non esite.
Per evitare questo, estendiamo l'esempio precedente, ed aggiungiamo il metodo magico __set che ha due argomenti: il nome della proprietà ed il valore da settare
<?php
class User{
public $nome;
public $email;
public function __get($name)
{
echo "la proprietà $name non è accessibile";
}
public function __set($name,$value)
{
echo "Stai provando a settare $name con il valore $value. Ma non esiste $name";
}
}
$obj=new User;
$obj->nome35=10;
?>
Rilancia lo script e la risposta che otterrai sarà
Stai provando a settare nome35 con il valore 10. Ma non esiste nome35
Modifichiamo lo script e rendiamo private le due proprietà.
<?php
class User{
private $nome;
private $email;
public function __get($name)
{
echo "la proprietà $name non è accessibile";
}
public function __set($name,$value)
{
echo "Stai provando a settare $name con il valore $value. Ma non esiste $name";
}
}
$obj=new User;
$obj->nome=10;
?>
Dopo aver istanziato la classe User proviamo ad accedere alla proprietà "nome" che esiste, ma essendo private non è accessibile dall'esterno. Il risultato quindi sarà l'esecuzione del metodo __set.
Avete quindi capito come utilizzarle i metodi __get e __set.
Nella pratica, questi vengono utilizzati per evitare di implementare dei metodi getter (che vanno a prendere il valore di una determinata proprietà) e setter (che vanno a settare una determinata proprietà).
Vediamo subito di cosa stiamo parlando. Dato che nel nostro esempio non possiamo accedere, dall'esterno della classe, alle proprietà di tipo "private", possiamo avere necessità di utilizzare un metodo getter, come questo, per setter per impostare nome ed email, e getter per prelevarlo, come in questo script:
<?php
class User(){
private $nome;
private $email;
// metodo getter il nome
public function getNome(){
return $this->nome;
}
// metodo setter il nome
public function setNome($nome){
$this->nome=$nome;
}
// metodo getter la email
public function getEmail(){
return $this->email;
}
// metodo setter la email
public function setEmail($mail){
$this->email=$mail;
}
public function __get($name)
{
echo "la proprietà $name non è accessibile";
}
public function __set($name,$value)
{
echo "Stai provando a settare $name con il valore $value. Ma non esiste $name";
}
}
$obj=new User;
// impostiamo nome ed eamil
$obj->setNome('giulio');
$obj->setEmail('giuliodb@libero.it');
// facciamo un echo le nome e del cognome
echo $obj->getNome();
echo $obj->getEmail();
?>
Bene, immaginiamo però che ci siano 10 proprietà e non due. Il codice diventerebbe lunghissimo. In auto arrivano i metodi magici __get e __set.
Riprendiamo il nostro esempio, eliminiamo i metodi getter e setter appena inseriti, ed invece con rispondere con un "echo" nei metodi __get e __set, scriviamo
<?php
class User{
private $nome;
private $email;
public function __get($name)
{
$this->$name;
}
public function __set($name,$value)
{
$this->$name=$value;
}
}
$obj=new User;
$obj->nome="Giulio";
echo $obj->nome;
echo "<br>";
$obj->email="test@gmail.com";
echo $obj->email;
?>
Quando provo a settare il metodo nome, con il valore Giulio
$obj->nome="Giulio";
viene chiamato il metodo __set
$this->$name=$value;
che è equivalente a
$this->nome="Giulio";
Stiamo quindi simulando il metodo setter ! La stessa cosa vale ovviamente per la proprietà email.
Quando invece recupero il valore della proprietà "nome"
echo $obj->nome;
all'interno del metodo magico __get riceverà "$name = nome", per cui tornerà
$this->nome;
Lo stesso vale per la email.
La risposta sarà quindi
Giulio
test@gmail.com
Nota: se abbiamo poche proprietà è meglio usare i metodi getter e setter per una questione di velocità di esecuzione (i metodi magici infatti risultano essere più lenti). Se invece abbiamo molte proprietà usiamo i metodi magici __get e __set
I metodi magici __call e __callStatic
Il metodo __call viene richiamato automaticamente quando proviamo ad accedere ad un metodo non esistente nella classe, o non è accessibile.
Il metodo __callStatic è simile al precedente ma si fa riferimento ad metodo statico.
Accettano 2 parametri: il nome della funzione (metodo chiamato) e l’array contenente i parametri passati al metodo.
In questo esempio li vediamo entrambi in azione.
<?php
class MyClass {
public function __call($name, $arguments) {
echo "<br>"."Chiamata metodo '$name' " . implode(', ', $arguments). "\n";
}
public static function __callStatic($name, $arguments) {
echo "<br>"."Chiamata metodo Static '$name' " . implode(', ', $arguments). "\n";
}
}
// istanzio un oggetto di classe MyClass
$obj = new MyClass;
// chiamo il metodo runTest, non esistente
$obj->runTest('Oggetto nel contesto');
// chiamo il metodo statico runTest, non esistente
MyClass::runTest('Oggetto nel contesto statico');
?>
In questo esempio al metodo "rumTest" ho passato solo un argomento, ma avrei potuto passarne più di uno, ed esempio
$obj->runTest('Oggetto nel contesto','Pluto')
Adesso vediamo come simulare, utilizzando questi metodi magici, l'ereditarietà multipla in php.
Analizziamo il seguente codice.
<?php
class SimulazioneEM(){
public function m1(){
echo "ciao dal metodo m1";
}
}
class User(){
private $simulazioneEM;
public function __construct(){
$this->simulazioneEM = new simulazioneEM;
}
public function m2(){
echo "ciao dal metodo m2";
}
public function __call($nomeMetodo,$argomenti){
if(method_exists($this->simulazioneEM,$nomeMetodo)){
call_user_func_array([$this->simulazioneEM,$nomeMetodo],$argomenti);
}
else {
echo "il metodo non è presente neppure nella classe SimulazioneEM";
}
}
}
$obj=new User;
$obj->miometodo('Giulio');
?>
Dopo aver istanziato la classe "User" proviamo a chiamare il metodo "miometodo", che non esiste, e passare a questo il valore "Giulio".
Il metodo _call risponderà, perchè il metodo "miometodo" non esiste. In questo metodo magico, verifichiamo se per caso il metodo è presente all'interno della classe SimulazioneEM:
if(method_exists($this->simulazioneEM,$nomeMetodo))
La funzione method_exists riceve come paramatri l'oggetto di classe simulazioneEM ($this->simulazioneEM) e il nome del metodo.
Se effettivamente esiste in SimulazioneEM allora con la funzione call_user_func_array lo andiamo a richiamare.
Se andiamo a richiamare, tramite l'oggetto di classe User, un metodo "m1" definito nella classe simulazioneEM
$obj->m1();
otteniamo come risposta
ciao dal metodo m1
Quindi riusciamo a richiamare, tramite l'oggetto User, anche il metodo definito nella classe SimulazioneEM
Ovviamente tutto queste complicazioni le potevamo risolvere con un semplice
class User extends SimulazioneEM()
ma il nostro obiettivo era simulare l'ereditarietà multipla, cioè vogliamo richiamare, con un oggetto della classe User, dei metodi presenti in altre classi.
Ultimo esempio
<?php
class SimulazioneEM(){
public function m1(){
echo "ciao dal metodo m1";
}
}
class SimulazioneEM2(){
public function m4(){
echo "ciao dal metodo m4";
}
}
class User(){
private $simulazioneEM;
private $simulazioneEM2;
public function __construct(){
$this->simulazioneEM = new simulazioneEM;
$this->simulazioneEM2 = new simulazioneEM2;
}
public function m2(){
echo "ciao dal metodo m2";
}
public function __call($nomeMetodo,$argomenti){
if(method_exists($this->simulazioneEM,$nomeMetodo)){
call_user_func_array([$this->simulazioneEM,$nomeMetodo],$argomenti);
}
else if(method_exists($this->simulazioneEM2,$nomeMetodo)){
call_user_func_array([$this->simulazioneEM2,$nomeMetodo],$argomenti);
}
else {
echo "il metodo non è presente neppure nella classe SimulazioneEM";
}
}
}
$obj=new User;
$obj->m4();
?>
E con questo esempio, abbiamo terminato l'argomento dedicato ai metodi magici in PHP.
Potrebbe interessarti
- Le interfacce in PHP
- Le classi astratte e i metodi astratti, in PHP
- Le costanti di una classe in PHP
- Come estendere una classe in PHP ed il concetto di ereditarietà.
- Le proprietà e i metodi STATICI in PHP
- Il metodo costruttore in PHP
- Autoloading classi in PHP, con un occhio ai namespace ed allo standard di programmazione psr-4.
- Cos'è il type hinting, la sua evoluzione nelle varie versioni di PHP, ed il controllo sulla tipologia di dato con la modalità strict_mode.
- Cosa sono i namespace in PHP. La guida completa, con esempi.
- Cosa sono i trait in PHP. La guida completa con esempi.
- Le classi in PHP. introduzione alla programmazione in stile OOP, metodi e proprietà di una classe.