Come saprete in PHP non esiste il concetto di
property. La documentazione - a mio avviso erroneamente - chiama le variabili di classe
properties, quando in realtà queste ultime sono ben diverse dalle
properties di C#. Quelle che la documentazione di PHP chiama
properties non sono altro che pure e semplici variabili di classe, alle quali si può assegnare, come per tutti i membri, un differente livello di visibilità:
private,
protected,
public.
Come avviene in C++, per settare una variabile di classe privata (chiamiamola con il suo nome), è necessario implementare un
setter apposito, e richiamarlo. Un
setter, lo dice la parola stessa, è per l'appunto un metodo preposto a settare una variabile di classe. Nel seguito trovate l'implementazione di una classe
Doc con una variabile
$id definita
private. Per essa è stato implementato un
setter, chiamato seguendo la convenzione
setId. Il metodo permette di assegnare un valore alla variabile di classe privata.
class Doc { private $id;
public function setId($value) { $this->id = $value;
}
}
In PHP un membro dichiarato
private è visibile soltanto all'interno della classe alla quale appartiene. Pertanto, per poter scrivere il valore della variabile di classe
$id, dovremo usare il metodo pubblico
setId:
$doc = new Doc();
$doc->setId(123);
Se infatti tentassimo di settare direttamente
$doc->id, l'interprete tornerebbe giustamente un errore, poiché violeremmo una regola base dell'
information hiding.
C#, così come fanno altri linguaggi, offre un meccanismo più elegante, basato per l'appunto sulle
properties.
In C# infatti posso scrivere:
class TimePeriod
{ private double seconds;
public double Hours
{ get { return seconds / 3600; } set { seconds = value * 3600; } }
}
I metodi
get e
set (due parole chiave) vengono automaticamente richiamati quando si tenta di leggere o scrivere la proprietà Hours.
class Program
{ static void Main()
{ TimePeriod t = new TimePeriod();
// Assigning the Hours property causes the 'set' accessor to be called.
t.Hours = 24;
// Evaluating the Hours property causes the 'get' accessor to be called.
System.Console.WriteLine("Time in hours: " + t.Hours); }
}
Come mostra il breve spezzone di codice (preso dal sito Microsoft), è possibile direttamente settare la
property Hours, poiché il compilatore chiamerà automaticamente l'apposito setter.
Ebbene, non vi è niente di male nel continuare ad utilizzare
setId($value), ma ammetterete che il concetto di
property, che è proprio di C#, è più immediato e soprattutto decisamente più elegante.
Come fare la stessa cosa in PHP e renderla disponibile per tutte le classi? Assodato che in PHP tutto ciò non è
normalmente possibile, vediamo come, con qualche trucchetto, adattare il concetto di
property a PHP.
Nella pratica non vogliamo far altro che poter scrivere:
$doc = new Doc();
$doc->id = 123;
Desideriamo poter settare direttamente
$doc->id, anche se
$id è una variabile di classe privata. Per far ciò dobbiamo istruire l'interprete, in modo tale che chiami il metodo
setId($value), ogni qualvolta tentiamo di assegnare un valore alla variabile di classe
$id, affinché questa diventi una vera e propria
property.
PHP fortunatamente mette a disposizione quelli che tecnicamente vengono chiamati
Magic Methods. Sono dei metodi richiamati automaticamente quando tentiamo di leggere, settare ed eseguire altre operazioni su una qualunque variabile di classe. In particolare sono quattro i metodi che ci interessano:
__set,
__get,
isset e
__unset.
Vogliamo dunque fare in modo che il metodo
setId($value), precedentemente definito, venga chiamato automaticamente ogni qualvolta venga assegnato un valore a
$doc->id. A tal fine dobbiamo fare l'
override del magic method
__set, come illustrato di seguito:
class Doc { private $id;
public function setId($value) { $this->id = $value;
}
public function __set($name, $value) { if (method_exists($this, ($method = 'set'.ucfirst($name))))
$this->$method($value);
else
throw new BadMethodCallException("Method $method is not implemented for property $name."); }
}
Il metodo verifica l'esistenza dell'apposito
setter, in questo caso
setId, per poi richiamarlo.
La tecnica è
applicabile per tutte le variabili di classe, bisogna solo ricordarsi, naturalmente, di implementare gli appositi
setter e
getter, come peraltro si fa in C#. In questo modo non si viola l'information hiding, poiché alla variabile di classe
$id si accede comunque tramite il setter pubblico
setId($value), richiamato automaticamente dal magic method
__set($name, $value), di cui è stato effettuato l'
override.
Adesso la nostra classe
Doc dispone di una proprietà
$id (ora possiamo chiamarla così); ma cosa fare per le altre classi? Non possiamo pensare di effettuare l'override dei
Magic Methods in ciascuna classe, né possiamo pensare di far derivare tutte le nostre classi da un'unica superclasse, poiché non avrebbe senso avere delle
properties anche laddove non servono. La soluzione a questo problema viene con
PHP 5.4.
PHP 5.4 introduce una nuova entità chiamata
Trait. Fondamentalmente si tratta di un copia/incolla a livello di interprete. E' un modo un po' sporco, ma assolutamente pratico, di simulare l'ereditarietà multipla. I trait sono sostanzialmente dei
contenitori con proprie funzioni membro. A differenza delle classi,
i trait non possono essere istanziati. Sono dei meri contenitori che
semplificano il riuso di codice. Le classi, attraverso la keyword
use, la stessa che si usa per i
namespace, possono infatti includere il contenuto di un trait al loro interno. In sostanza tutti i metodi di un trait diventano metodi della classe che lo utilizza. Più classi possono includere il medesimo trait e trait differenti possono essere usati da una stessa classe. Una classe può contemporaneamente avere un
anchestor e utilizzare più trait. L'
iniezione di codice viene fatta direttamente dall'interprete, per cui il codice non viene realmente duplicato.
Non ci resta dunque che spostare l'implementazione dei
Magic Methods dalla classe
Doc ad un trait che chiameremo
Properties:
trait Properties {
public function __get($name) { if (method_exists($this, ($method = 'get'.ucfirst($name))))
return $this->$method();
else
throw new BadMethodCallException("Method $method is not implemented for property $name."); }
public function __isset($name) { if (method_exists($this, ($method = 'isset'.ucfirst($name))))
return $this->$method();
else
throw new BadMethodCallException("Method $method is not implemented for property $name."); }
public function __set($name, $value) { if (method_exists($this, ($method = 'set'.ucfirst($name))))
$this->$method($value);
else
throw new BadMethodCallException("Method $method is not implemented for property $name."); }
public function __unset($name) { if (method_exists($this, ($method = 'unset'.ucfirst($name))))
$this->$method();
else
throw new BadMethodCallException("Method $method is not implemented for property $name."); }
}
A questo punto il codice della classe Doc sarà:
class Doc { use Properties;
private $id;
public function setId($value) { $this->id = $value;
}
}
Come detto i metodi del trait verranno iniettati nella classe dall'interprete e saranno quindi disponibili come se fossero parte della stessa. Le variabili di classe di ogni classe che farà uso del trait
Properties, diverrano di fatto delle
properties alla C#.