Con la release di Php 4.2 ha fatto la sua comparsa una strana estensione, dichiarata sperimentale e descritta nel manuale ufficiale al capitolo "Object property and method call overloading".
Molti, compreso chi scrive, inizialmente hanno creduto che avesse a che fare con una delle caratteristiche tipiche dei linguaggi di programmazione Object Oriented, l'overloading appunto.
In realtà si tratta di qualcosa che riguarda l'approccio "object oriented programming" (OOP), ma che con l'overloading tradizionale c'entra ben poco: ci troviamo di fronte ad una di quelle peculiarità a cui Php ci ha abituato da tempo...quelle che spesso fanno storcere il naso ai programmatori tradizionalisti, ma che di solito vengono apprezzate dagli altri per la loro praticità.
Nei paragrafi che seguiranno cercheremo di dimostrare l'utilità di questa estensione (destinata ad uscire definitivamente dalla fase sperimentale con Php 5), ma per prima cosa ci occuperemo dell'overloading in senso proprio e di come si possa pazialmente simulare questa caratteristica in Php.
Chi si sente totalmente digiuno in fatto di OOP potrebbe trovare utili gli articoli su freephp.html.it dedicati alla programmazione a oggetti.
L'overloading "tradizionale"
L'overloading è la capacità di dichiarare più metodi con lo stesso nome assegnando ad essi comportamenti differenti: i metodi che condividono lo stesso nome si distinguno in base al numero e al type degli argomenti passati.
Si tratta uno degli aspetti del polimorfismo, l'atteggiamento "schizofrenico" dei linguaggi object oriented ("una sola faccia più comportamenti"), e in definitiva di un modo per mantenere il codice ordinato e coerente evitando troppi "if...else".
In Php quanto segue non è accettabile
<?php
class Saluti{
//Il costruttore che in questo esempio è vuoto
function Saluti(){}
function saluta(){
echo('Ciao a tutti!') ;
}
function saluta($name){
echo(Ciao a tutti, '.$name.' vi saluta!') ;
}
}//end class Saluti
$x = new Saluti() ;
$x->saluta() ;
?>
dichiarando più volte lo stesso metodo otteniamo un errore del tipo
Fatal error: Cannot redeclare saluta() in - NOMESCRIPT - on line 17
Niente di strano visto che Php non supporta l'overloading dei metodi come Java (e tanto meno quello degli operatori, come C++).
È comunque un limite che possiamo parzialmente aggirare grazie alle "Function Handling functions" (letteralmente "funzioni per gestire le funzioni"): in particolare ci serviremo di func_num_args(), func_get_args() e call_user_func_array().
call_user_func_array: riceve come primo argomento il nome di una funzione (o un array contenente un oggetto è un suo metodo) e la esegue passandole un array di argomenti. Per maggiori dettagli vedere anche call_user_method_array
func_num_args: restituisce il numero degli argomenti passati ad una funzione o ad un metodo
func_get_args: restituisce un array contenente gli argomenti passati ad una funzione o ad un metodo.
Simulazione del'overloading dei metodi
Ecco come grazie alle funzioni appena descritte possiamo imitare l'overloading tipico dei linguaggi Object Oriented
<?php
class SalutiWithOverload{
//Il costruttore che in questo esempio è vuoto
function SalutiWithOverload(){}
/* Il metodo sottoposto a overloading: utilizziamo &$this in quanto il metodo da richiamare appartiene all'oggetto stesso e non ad uno esterno. */
function saluto(){
$numArgs = func_num_args() ;
$args = func_get_args() ;
call_user_func_array( array(&$this, 'saluto'.$numArgs), $args ) ;
}
function saluto0(){
echo('Ciao a tutti!') ;
}
function saluto1($name){
echo('Ciao a tutti, '.$name.' vi saluta!') ;
}
function saluto2($name1, $name2){
echo('Ciao a tutti, '.$name1.' e '.$name2.' vi salutano!') ;
}
}//end class SalutiWithOverload
$x = new SalutiWithOverload() ;
$x->saluto('Saibal', 'GM') ;
/*
Output -> "Ciao a tutti, Saibal e GM vi salutano!"
*/
?>
L'esempio è banalissimo ma dovrebbe rendere l'idea. La stessa tecnica si può applicare al costruttore con una conseguenza forse ancora più utile, è cioé di poter creare l'oggetto in modo diverso a seconda del numero di argomenti passati (overloading del costruttore).
Come già precisato il "trucchetto" appena visto ha ben poco a che fare con le ragioni per cui è stata introdotta l'estensione per l'OVERLOAD, e tra poco vedremo il perché.
L'estensione per l'OVERLOAD
Questa caratteristica introduce in Php un'ulteriore possibilità di estendere le classi (vedi l'articolo Tecniche per programmare a oggetti ), e in modo particolare consente di farlo "on the fly", cioè quando l'oggetto è già stato istanziato.
Affinché una classe possa sfruttare questa caratteristica deve contenere tre metodi cosiddetti "magici" ed essere dichiarata esplicitamente "overloadable" attraverso l'unica funzione di questa estensione, overload([nomeClasse]).
La cosa migliore è fare subito un esempio (attenzione ai commenti)
<?php
/*
Rendiamo visibili anche i notice (errori non rilevanti)
*/
error_reporting(E_ALL) ;
class Test1{
var $testProp ;
function Test(){
/*
Una normale proprietà dichiarata e riempita con un valore qualsiasi
*/
$this->testProp = 'Una variabile d'esempio' ;
}
/*
Il metodo si attiva automaticamente quando cerchiamo di utilizzare una proprietà non definita
*/
function __get($nomePropNonDdefinita, &$valore){
$valore = "La proprietà '$nomePropNonDdefinita' è stata definita attraverso l'overloading" ;
return true ;
}
}//END class Test1
/*
Rendiamo la classe "overloadable"
*/
overload('Test1') ;
$x= new Test1() ;
/*
Utilizziamo una proprietà non definita
*/
echo($x->nomePropNonDefinita) ;
/*
Output -> "La proprietà 'nomePropNonDdefinita' è stata definita attraverso l'overloading"
*/
?>
È importante osservare che
- _get restituisce TRUE: più avanti, descrivendo il metodo __call(), capiremo a cosa serva far restituire TRUE o FALSE al metodo magico.
- se la classe non prevedesse __get o commentassimo la riga in cui viene attivato l'overload otterremmo un avvertimento come il seguente:
Notice: Undefined property: nomePropNonDdefinita in d:www_newoverloadoverload.php on line 48
I metodi "magici" di cui possiamo dotare le classi che vogliamo rendere espandibili in questo modo sono 3:
boolean __get([string property_name],[mixed return_value])
si innesca quando utilizziamo una proprietà non definita
boolean __set([string property_name],[mixed value_to_assign])
si innesca quando assegnamo un valore ad una proprietà non definita
boolean __call([string method_name],[array arguments],[mixed return_value])
si innesca quando tentiamo di utilizzare un metodo non definito, ed è disponibile soltanto con Php 4.3.2
I primi due non sono particolarmente utili in Php 4, poichè che l'accesso diretto alle proprietà di un oggetto è sempre permesso, quindi ci concentreremo completamente su __call e...ne vedremo delle belle
Estendiamo una classe totalmente vuota
<?php
/* Rendiamo visibili anche i notice (errori non rilevanti) */
error_reporting(E_ALL) ;
/* Definiamo una normale funzione esterna alla classe */
function creaSalutoStupido($destinatario){
return('Salve '.$destinatario.'!') ;
}//END function
/* La classe vuota */
class Vuota{
/* Costruttore vuoto */
function Vuota(){}
/* Si attiva automaticamente quando chiamiamo un metodo non definito, in questo caso sempre. */
function __call($metodoNonDefinito, $arrayParametri, $valRestituito){
$valRestituito = $metodoNonDefinito($arrayParametri[0]) ;
return true ;
}
}//END class Vuota
/* Rendiamo la classe "overloadable" */
overload('Vuota') ;
$x= new Vuota() ;
echo($x->creaSalutoStupido('a tutti')) ;
/* Output -> Salve a tutti! */
?>
Cosa è accaduto? Abbiamo arricchito la classe utilizzando un metodo derivato da una funzione esterna che non le apparteneva.
A prima vista potremmo notare anche una cosa un po' strana, __call ritorna TRUE ma creaSalutoStupido restituisce, come dovrebbe, la stringa con il saluto: questo perché __call deve ritornare sempre TRUE oppure FALSE, infatti i valori booleani servono ad accettare o respingere un metodo aggiunto.
Se non utilizziamo FALSE in modo condizionale la classe potrà essere estesa con qualsiasi metodo in modo arbitrario, come nel caso appena visto.
Per far restituire un valore al metodo aggiunto dobbiamo memorizzarlo nel terzo argomento, facoltativo, passato a __call.
Quindi nell'esempio __call restituisce TRUE ma creaSalutoStupido restituisce $valRestituito e si comporta normalmente.
Nelle pagine successive vedremo quali possibilità apre l'overload con script meno banali.
Un esempio di estensione utile tramite overload
<?php
/* Rendiamo visibili anche i notice (errori non rilevanti) */
error_reporting(E_ALL) ;
class Num{
var $num ;
/* Il costruttore */
function Num($n){
$this->num = $n ;
}
function __call($metodoNonDefinito, $arrayParametri, &$valRestituito){
$metodiAmmessi = array('sqrt', 'pow', 'base_convert') ;
/* Seleziona i metodi non definiti ammissibili */
if( !in_array( $metodoNonDefinito, $metodiAmmessi) ){
return false ;
}
else{
/* Uniamo $this->num agli altri argomenti in un unico array */
$arrayParametri = array_merge( array($this->num), $arrayParametri) ;
$valRestituito = call_user_func_array( $metodoNonDefinito, $arrayParametri) ;
return true ;
}
}//end __call
}//end class Num
overload('Num') ;
$x = new Num(4) ;
//Radice quadrata
echo($x->sqrt()."<br>n") ;
//Elevamento a potenza
echo($x->pow(3)."<br>n") ;
//Conversione di base
echo($x->base_convert(10, 2)."<br>n") ;
/*
Output
2
64
100
*/
?>
In questo caso abbiamo esteso la classe con alcune funzioni matematiche di Php, ed ora possiamo trattare l'oggetto $x come faremmo con un oggetto number in Javascript.
Anche questa volta sono necessarie alcune osservazioni:
- è stata utilizzata call_user_func_array(), vista all'inizio dell'articolo, per poter passare un numero di parametri variabile alla funzione (nessun parametro nel caso di sqrt, e 2 nel caso di base_convert).
- call_user_func_array() deve avere come primo argomento il nome del metodo da aggiungere anzichè array(&this, $nomeMetodo): pensandoci bene è una cosa logica, visto che il metodo non fa ancora parte della classe "overloaded".
- se avessimo chiamato una funzione matematica non amessa, es. $x->log() avremmo ottenuto un messaggio di errore.
Alcune migliorie
Fino ad ora overload() è stata sempre utilizzata all'esterno della classe, ma nessuno ci impedisce di fare diversamente, ad esempio modificando il costruttore per rendere automaticamente "overloadable" la classe esaminata poco fa
.
.
.
/* Il costruttore */
function Num($n){
/* L'overload avviene automaticamente, il nome della classe ottenuto attraverso get_class() */
overload(get_class($this)) ;
$this->num = $n ;
}
.
.
.
Cosa me ne faccio di tutto ciò?
Gli esempi appena visti, per quanto obbligatoriamente sintetici, dovrebbero fornire diversi indizi sull'utilità di questa estensione di Php: basti pensare alla possibilità di trasformare in OOP un'intera libreria di funzioni con pochi passaggi.
Possiamo anche aggiungere metodi appartenenti ad altre classi senza ricorrere ad ereditarietà, associazione o aggregazione:
.
.
.
function __call($metodo, $parametri, $retVal){
$retVal = call_user_func_array( array(new AltraClasse(), 'nomeMetodo'), $arrayParametri) ;
return true ;
}
.
.
.
Altri possibili impieghi sono lasciati all'inventiva di chi legge, infondo si tratta di un'estensione molto recente e "da scoprire". Consiglio infine la lettura dell' ottimo articolo di Harry Fuecks "Extending PHP Classes with Overload" che, pur evitando di soffermarsi su alcune delle possibilità appena esaminate, surclassa il manuale ufficiale quanto a completezza.