martedì 14 gennaio 2014

La OOP in PHP spiegata in un esempio - Parte 18 - Costanti, late binding statico, variabili membro statiche, __destruct() e un riepilogo degli argomenti trattati


Concludiamo qui questa serie di post sulla programmazione OOP in PHP. Quelli trattati sono argomenti base dai quali non si può prescindere. Rivediamo però di cosa abbiamo parlato e cosa è rimasto fuori e merita una parolina.


Abbiamo visto:
  • come definire una classe con l'uso del costrutto class, e le sue variabili e metodi membro
  • come si applicano i modificatori d'accesso public, protected, private alle variabili e metodi membro e cosa significano
  • sebbene non espressamente citato abbiamo fatto in modo di preservare l'incapsulamento (lo stato dell'oggetto è accessibile solo tramite delle funzioni d'accesso set/get sia a chi utilizza l'oggetto che ai metodi membri dell'oggetto stesso)
  • il costrutto $this-> che all'interno dei metodi dell'oggetto permette di referenziare l'oggetto utilizzato per invocare il metodo stesso.
  • i metodi statici (modificatore static) e il loro utilizzo
  • l'utilizzo del costrutto self:: per referenziare la classe in cui utilizzato per invocare metodi statici dall'interno di metodi della classe stessa
  • il costrutto parent:: per invocare il metodo della classe genitrice quando ridefinito dalla classe figlia
  • le classi astratte, modificatore abstract, per le quali non è possibile instanziare un oggetto ma che possono implementare parzialmente una classe e vanno completate per mezzo dell'ereditarietà
  • l'ereditarietà per mezzo del costrutto extends e per delega tramite trait e interfacce
  • come cercare d'implementare l'ereditarietà multipla facendo uso di interfacce e classi astratte
  • la tentazione da classe statica e la regressione alla programmazione modulare
  • come definire e utilizzare i trait nelle classi con la clausola use
  • i metodi astratti, che se presenti rendono una classe astratta
  • l'uso del costrutto interface per la definizione delle interfacce e la loro implementazione nelle classi tramite il costrutto implements
  • come tipizzare gli argomenti dei metodi qualora siano degli oggetti
  • la clonazione deep e shallow per mezzo dell'istruzione clone e la necessità dell'utilizzo di tale costrutto
  • la serializzazione e la deserializzazione di un oggetto per preservarne lo stato per la durata della sessione del browser, e gli effetti secondari da serializzazione (deep clone alternativa)
  • le funzioni speciali __construct(), __toString(), __clone()
  • le interfacce predefinite di PHP Iterator e ArrayAccess
  • abbiamo fatto inconsapevolmente uso del binding dinamico o polimorfismo, ossia il metodo eseguito a parità di nome dipende dalla reale istanza della classe
  • come PHP tratta e memorizza le variabili che contengono un oggetto ed i possibili effetti collaterali
E' tutto qui? Certo che no. C'è dell'altro e fra questo altro qualcosa che merita una parolina veloce.
  • Le costanti
  • Il late binding statico
  • Variabili membro statiche
  • La funzione speciale __destruct()

Le Costanti

Definire una costante è molto semplice. Vediamo subito un esempio:
<?php
class cSaluto{
    const kNome = 'Ciro';
    const kCognome = 'Pellegrino';

 
    private $nome,$cognome;
 
    public function __construct($cognome=self::kCognome, $nome=self::kNome) {
        $this->setCognome($cognome);
        $this->setNome($nome);
    }
 
    public function saluta(){
        return 'Ciao, '.$this->getNome().' '.$this->getCognome();
    }
 
    //Metodi di accesso
    public function setNome($nome){
        $this->nome = $nome;
    }
 
    public function setCognome($cognome){
        $this->cognome = $cognome;
    }
 
    public function getNome(){
        return $this->nome;
    }
 
    public function getCognome(){
        return $this->cognome;
    }      
}

$saluto = new cSaluto(cSaluto::kCognome, 'Roberto');
echo $saluto->saluta().'<br>';
$saluto2 = new cSaluto();
echo $saluto2->saluta();

?>

Come possiamo osservare, le costanti all'interno delle classi sono definite per mezzo del costrutto const seguito dal nome della costante a cui è assegnato un valore tramite l'istruzione di assegnazione. Il valore deve essere una espressione costante ossia un numero, una stringa o un array mono o multi-dimensionale contenente numeri o stringhe. Non sono ammessi espressioni matematiche e qual si voglia elemento valutabile solo in fase di esecuzione come nomi di variabili, chiamate di funzioni e simili.

Una costante all'interno di una classe esiste a prescindere dall'esistenza di un oggetto istanza di tale classe. Ciò significa che tali elementi si comportano come elementi statici anche se non utilizzano esplicitamente il modificatore static. Proprio come le variabili e i metodi membro statici, le costanti possono essere utilizzate nella classe per mezzo del costrutto self::NomeCostante e richiamati dall'esterno della classe per mezzo di NomeClasse::NomeCostante. Nell'esempio precedente il costruttore utilizza le costanti per definire i valori di default per i propri argomenti, mentre il codice di test utilizza la costante kCognome come argomento per il costruttore.

Late binding statico

Il late binding consiste nell'eseguire il metodo giusto a seconda dell'oggetto istanza di una certa classe. Ad esempio, riprendendo il codice precedente:
<?php
class cSaluto{
    const kNome = 'Ciro';
    const kCognome = 'Pellegrino';
 
    private $nome,$cognome;
 
    public function __construct($cognome=self::kCognome, $nome=self::kNome) {
        $this->setCognome($cognome);
        $this->setNome($nome);
    }
 
    public function saluta(){
        return $this->formulaIniziale().$this->getNome().' '.$this->getCognome();
    }
 
    protected function formulaIniziale(){
        return 'Ciao, ';
    }

 
    //Metodi di accesso
    public function setNome($nome){
        $this->nome = $nome;
    }
 
    public function setCognome($cognome){
        $this->cognome = $cognome;
    }
 
    public function getNome(){
        return $this->nome;
    }
 
    public function getCognome(){
        return $this->cognome;
    }      
}

class cSalutoFormale extends cSaluto{
    protected function formulaIniziale() {
        $ora = date('H');
        $fi = '';
        if($ora>5 and $ora<=12)
            $fi .= 'Buon giorno, ';
        elseif($ora>12 and $ora<=19)
            $fi .= 'Buon pomeriggio, ';
        elseif($ora>19 and $ora<=23)
            $fi .= 'Buona sera, ';
        else
            $fi .= 'Buona notte, ';
     
        return $fi;
    }
}


$saluto = new cSaluto(cSaluto::kCognome, 'Roberto');
echo $saluto->saluta().'<br>';
$saluto2 = new cSalutoFormale();
echo $saluto2->saluta();

?>

In grassetto è evidenziato ciò che cambia rispetto all'esempio delle costanti. In $saluto2 c'è una istanza della classe cSalutoFormale che estende cSaluto. Ciò significa che invocando il metodo saluta() è eseguito il metodo definito nella classe cSaluto (non essendo ridefinito dalla figlia). All'interno di saluta() però è invocato il metodo formulaIniziale(). Per effetto del late binding o polimorifsmo, il metodo eseguito non è quello di cSaluto ma quello di cSalutoFormale, ossia solo in fase di esecuzione è possibile stabilire quale sia il metodo corretto da invocare a seconda della classe di cui l'oggetto è istanza. Se avessimo comunque voluto invocare il metodo della classe genitrice avremmo potuto utilizzare il costrutto parent::.

Fin qui tutto normale e già noto da quanto sperimentato nei post precedenti. Ma cosa succede se il metodo formulaIniziale() è un metodo statico ossia occorre disporre di un late binding statico? Divenendo statico il metodo formulaIniziale() potremmo pensare di invocarlo in saluta() cambiando l'istruzione $this->formualInizale() in self::formulaIniziale(). Purtroppo il costrutto self:: fa riferimento alla classe in cui è utilizzato e ciò provocherebbe sempre l'invocazione del metodo formulaIniziale() presente nella classe cSaluto, ossia avremmo un binding statico e non un late binding statico. Per ottenere il late binding statico, quindi un riferimento alla classe reale di cui è istanza l'oggetto (o il nome della classe utilizzato per invocare un metodo statico che ne richiama altri che potrebbero essere ridefiniti) e tramite cui è invocato il metodo statico, occorre utilizzare il costrutto static::.

Il risultato finale è quindi:
<?php
class cSaluto{
    const kNome = 'Ciro';
    const kCognome = 'Pellegrino';
 
    private $nome,$cognome;
 
    public function __construct($cognome=self::kCognome, $nome=self::kNome) {
        $this->setCognome($cognome);
        $this->setNome($nome);
    }
 
    public function saluta(){
        return static::formulaIniziale().$this->getNome().' '.$this->getCognome();
    }
 
    protected static function formulaIniziale(){
        return 'Ciao, ';
    }
 
    //Metodi di accesso
    public function setNome($nome){
        $this->nome = $nome;
    }
 
    public function setCognome($cognome){
        $this->cognome = $cognome;
    }
 
    public function getNome(){
        return $this->nome;
    }
 
    public function getCognome(){
        return $this->cognome;
    }      
}

class cSalutoFormale extends cSaluto{
    protected static function formulaIniziale() {
        $ora = date('H');
        $fi = 'Buon ';
        if($ora>5 and $ora<=12)
            $fi .= 'giorno, ';
        elseif($ora>12 and $ora<=19)
            $fi .= 'pomeriggio, ';
        elseif($ora>19 and $ora<=23)
            $fi .= 'sera, ';
        else
            $fi .= 'notte, ';
     
        return $fi;
    }
}

$saluto = new cSaluto(cSaluto::kCognome, 'Roberto');
echo $saluto->saluta().'<br>';
$saluto2 = new cSalutoFormale();
echo $saluto2->saluta();

?>

In grassetto sono evidenziate le poche modifiche applicate. Per verifica provare a sostituire nello script il costrutto static:: con il costrutto self:: per vedere la differenza.

Variabili membro statiche

Una variabile membro statica è definibile esattamente come altre variabili membro con in più il modificatore static. Come sempre cerchiamo di preservare l'incapsulamento, definendo le variabili membro statiche con i modificatori d'accesso private static e fornendo dei metodi d'accesso set/get come metodi standard o come metodi statici. Un rapido esempio:
<?php
class test{
    private static $variabileStatica=1;
 
    public static function setVariabileStatica($valore){      
        self::$variabileStatica=$valore;
    }
    public static function getVariabileStatica(){
        return self::$variabileStatica;
    }
 
}

test::setVariabileStatica('Caio');
echo test::getVariabileStatica();
?>

Una variabile membro statica esiste a prescindere dall'esistenza dell'istanza della classe, quindi se definita public è accessibile dall'esterno della classe in cui definita come NomeClasse::$NomeVariabileStatica. All'interno della classe è accessibile per mezzo del costrutto self:: come mostrato nell'esempio. Il fatto che esista prima che esista un oggetto istanza della classe, fa si che il contenuto di tale variabile sia condiviso, ossia sia sempre lo stesso per tutti gli oggetti istanza della classe. Se uno degli oggetti o lo script modifica il valore di una variabile membro statica la modifica si ripercuote su tutti gli oggetti istanza della classe.

L'utilizzo di metodi statici come metodi d'accesso fa in modo che sia chiaro al client (utilizzatore della classe) che si modifica una proprietà statica e non una proprietà nel senso classico del termine ossia diversa per ogni istanza della classe.

La funzione speciale __destruct()

E' la controparte della funzione speciale __construct() che abbiamo già incontrato. Se la __construct() è invocata quando è generata una nuova istanza di una classe, la __destruct() è invocata quando non esistono più riferimenti all'interno dello script all'oggetto o lo script termina e gli oggetti vengono rilasciati. Anche l'invocazione del comando exit() provoca l'esecuzione delle __destruct() delle varie istanze. In tale metodo è possibile svolgere attività conclusive per la vita dell'oggetto come il corretto rilascio di risorse.

La funzione __desctruct() può essere ridefinita da classi figlie e le stesse possono invocare quella della genitrice tramite il costrutto parent::__destruct() proprio come avviene per la __construct().

Attenzione però, non è possibile stabilire l'ordine in cui le __destruct() dei vari oggetti sono eseguite nel momento in cui lo script termina o è invocato exit(). Quindi, ad esempio, non si cerchi di memorizzare nel DB delle informazioni durante l'esecuzione della __destruct() facendo uso di un oggetto DB, dato che lo stesso potrebbe già non esistere più ed aver chiuso la connessione.