Programmazione.it v6.4
Ciao, per farti riconoscere devi fare il login. Non ti sei ancora iscritto? Che aspetti, registrati adesso!
Info Pubblicità Collabora Autori Sottoscrizioni Preferiti Bozze Scheda personale Privacy Archivio Libri Corsi per principianti Forum
Properties alla C# in PHP
Scritto da Filippo Fadda il 05-03-2012 ore 01:23
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.
  1. class Doc {
  2.   private $id;
  3.  
  4.   public function setId($value) {
  5.     $this->id = $value;
  6.   }
  7. }

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:
  1. $doc = new Doc();
  2. $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:
  1. class TimePeriod
  2. {
  3.     private double seconds;
  4.  
  5.     public double Hours
  6.     {
  7.         get { return seconds / 3600; }
  8.         set { seconds = value * 3600; }
  9.     }
  10. }

I metodi get e set (due parole chiave) vengono automaticamente richiamati quando si tenta di leggere o scrivere la proprietà Hours.
  1. class Program
  2. {
  3.     static void Main()
  4.     {
  5.         TimePeriod t = new TimePeriod();
  6.  
  7.         // Assigning the Hours property causes the 'set' accessor to be called.
  8.         t.Hours = 24;
  9.  
  10.         // Evaluating the Hours property causes the 'get' accessor to be called.
  11.         System.Console.WriteLine("Time in hours: " + t.Hours);
  12.     }
  13. }

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:
  1. $doc = new Doc();
  2. $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:
  1. class Doc {
  2.   private $id;
  3.  
  4.   public function setId($value) {
  5.     $this->id = $value;
  6.   }
  7.  
  8.   public function __set($name, $value) {
  9.     if (method_exists($this, ($method = 'set'.ucfirst($name))))
  10.       $this->$method($value);
  11.     else
  12.       throw new BadMethodCallException("Method $method is not implemented for property $name.");
  13.   }
  14. }

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:
  1. trait Properties {
  2.  
  3.   public function __get($name) {
  4.     if (method_exists($this, ($method = 'get'.ucfirst($name))))
  5.       return $this->$method();
  6.     else
  7.       throw new BadMethodCallException("Method $method is not implemented for property $name.");
  8.   }
  9.  
  10.   public function __isset($name) {
  11.     if (method_exists($this, ($method = 'isset'.ucfirst($name))))
  12.       return $this->$method();
  13.     else
  14.       throw new BadMethodCallException("Method $method is not implemented for property $name.");
  15.   }
  16.  
  17.   public function __set($name, $value) {
  18.     if (method_exists($this, ($method = 'set'.ucfirst($name))))
  19.       $this->$method($value);
  20.     else
  21.       throw new BadMethodCallException("Method $method is not implemented for property $name.");
  22.   }
  23.  
  24.   public function __unset($name) {
  25.     if (method_exists($this, ($method = 'unset'.ucfirst($name))))
  26.       $this->$method();
  27.     else
  28.       throw new BadMethodCallException("Method $method is not implemented for property $name.");
  29.   }
  30. }

A questo punto il codice della classe Doc sarà:
  1. class Doc {
  2.   use Properties;
  3.  
  4.   private $id;
  5.  
  6.   public function setId($value) {
  7.     $this->id = $value;
  8.   }
  9. }

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#.
Precedente: In beta Visual Studio 11 ed il .NET Framework 4.5
Successiva: PHP 5.4, un piccolo grande aggiornamento
Commenti:  Primi  «  Meno recenti  «  11 - 11 di 11
Intervento di Ezio Meneghini a.k.a. santecaserio del 10-12-2012 ore 23:02, Ancona (AN)
Plebeo
Plebeo
(13 interventi)
Iscritto il 10-01-2009
cdimauro ha scritto:
Infatti dicevo "come minimo Delphi". :P

Ma sicuro che le proprietà di LISP/Smalltalk implementino le stesse funzionalità / meccanismo?

Certo, era più che altro una curiosità storica :) Ma sono abbastanza sicuro che un programmatore ancora più vecchio di me ti avrebbe trovato un esempio ancora pià datato! :)

SmallTalk aveva una sintassi sua (PHP, Java, Delphi... hanno tutti la stessa identica sintassi, a parte pochi dettagli) ma la logica è quella tipica di un linguaggio OO.

Lisp è più funzionale che OO, diciamo che assomiglia a JavaScript. Ma le proprietà ci sono.

In realtà, se ci pensi bene, non hai nemmeno bisogno di un linguaggio OO per creare oggetti. Esempio: un db relazionale; le stored procedure sono i metodi pubblici; le sp che non hai i permessi per chiamare sono metodi privati; una tabella a cui non puoi accedere contiene le proprietà private; una a cui puoi accedere contiene quelle pubbliche. E' un oggetto a tutti gli effetti, basta che tu lo pensi come tale.
(manca solo l'ereditarietà... ma potresti crearla)
Perfino un set di programmi chiamabili dalla riga di comando può essere un oggetto.
Commenti:  Primi  «  Meno recenti  «  11 - 11 di 11
Copyright Programmazione.it™ 1999-2013. Alcuni diritti riservati. Testata giornalistica iscritta col n. 569 presso il Tribunale di Milano in data 14/10/2002. Pagina generata in 3.021 secondi.