non so se può considerarsi una pillola, una supposta o un tips 'n tricks, intanto la scrivo poi vedranno i mods che fare
Perchè questo post ?
Sebbene gli sviluppatori stessi siano restii dal dichiararlo, PHP 5 soffre di non poche crisi di identità.
Tra i linguaggi di scripting è tra i meno permissivi mentre tra i linguaggi Strict Type object oriented è tra i peggiori (imho ...)
Una delle features introdotte in PHP 5 è quella del Type Hint.
Non avendo però supporto all'overload dei metodi, se non fatto dinamicamente tramite verifica su tutti i tipi di dati inviati come argomenti dei vari metodi, questa feature (assieme ad altre) castra il linguaggio invece di migliorarlo ed ecco quindi una proposta per emulare overload con Type Hint anche in PHP 5 (e se continuano così versione 6 inclusa ...)
Che cosa è il Type Hint?
Quando definiamo un metodo di funzione, o perchè no una semplice funzione, possiamo definire il tipo di dato atteso.
Mentre scrivere qualcosa tipo:
codice:
function stringValue($param){
return (string)$param;
}
ci permette di inviare alla funzione stringValue un qualunque tipo di valore, una scrittura di questo tipo:
codice:
function stringValue(MyClass $param){
return (string)$param;
}
ci assicura che la funzione stringValue riceverà ed elaborerà sempre e comunque un'istanza di classe MyClass o di classe che eredita (estende) MyClass.
La cosa "simpatica" del Type Hint è che mentre ci permette di scrivere PHP in modo più rigoroso, ci impedisce, come detto, di sfruttare a pieno questa bella e pulita caratteristica a causa dell'impossibilità di fare overload esplicito di un metodo.
La mia proposta - da cosa deriva
L'unico modo per emulare una sorta di overload in PHP5 è sfruttare un metodo "magico" chiamato __call.
Questo metodo di oggetto altro non fa che ricevere il nome del metodo che il codice sta tentando di usare, se non definito nella classe, più un secondo parametro contenente gli argomenti inviati, sotto forma di array, a quel metodo.
Vediamo un esempio:
codice:
class TestCall{
public function sayHello(){
return 'Hello';
}
public function __call($method, $arguments){
return $method === 'saySomething' ? $arguments[0] : null;
}
}
$tc = new TestCall;
echo $tc->sayHello(),
$tc->saySomething('bye'),
$tc->dontSay();
__call viene quindi chiamato se un metodo non è presente nell'istanza e può ritornare un valore, cambiare variabili ($this->nomevar), usare metodi ($this->method()) e fare tutto quello che fa un metodo di istanza.
Perchè non sfruttarlo proprio per gestire casi differenti di Type Hint?
Per farlo è necessario sfruttare la Reflection API, una delle poche implementazioni di PHP 5 veramente degne di nota, capace di fare un pò di reverse-enginering del codice in fase stessa di esecuzione.
La mia proposta - il codice
Immaginiamo di avere due o più classi, in questo caso per comodità una estende l'altra al fine di perdere troppo tempo:
codice:
class A{
private $value;
function __construct($value){
$this->value = $value;
}
function __toString(){
return ''.$this->value;
}
}
class B extends A{}
Ora immaginiamo di avere un'altra classe con dei metodi che vorrebbero accettare in Type Hint una o più classi e comportarsi diversamente a seconda di quale classe sia stata inviata al metodo e perchè no, con quale ordine.
codice:
class Overload {
protected function doSomething0(A $a) {
return 'Recieved an A instance with value: '.$a;
}
protected function doSomething1(B $b) {
return 'Recieved a B instance with value: '.$b;
}
protected function doSomething2(A $a, B $b) {
return $this->doSomething0($a).' and '.$this->doSomething1($b).' too';
}
protected function doSomething3(B $b, A $a) {
return $this->doSomething1($b).' and '.$this->doSomething0($a).' too';
}
}
Per comodità ho chiamato i vari metodi pubblici con lo stesso prefisso più un numero di riferimento per dare un ordine e distinguere ogni metodo dall'altro, dato che non sarebbe stato possibile dare a tutti i metodi lo stesso nome per via del mancato overload esplicito del PHP.
La classe potrebbe già funzionare senza problemi ma saremmo costretti a ricordarci il numero giusto del metodo da chiamare a seconda che si stia usando un'istanza di A, una di B o entrambe con ordine differente.
Ecco che interviene il metodo __call da me adattato per questo tipo di casistica, utilizzabile quindi in ogni oggetto che volete, seguendo la regola che il metodo pubblico vero e proprio, in questo caso doSomething, non deve essere presente ma possono esserci N metodi con lo stesso nome ed il numero sequenziale (o non) per distinguerli tra loro.
codice:
class Overload {
protected function doSomething0(A $a) {
return 'Recieved an A instance with value: '.$a;
}
protected function doSomething1(B $b) {
return 'Recieved a B instance with value: '.$b;
}
protected function doSomething2(A $a, B $b) {
return $this->doSomething0($a).' and '.$this->doSomething1($b).' too';
}
protected function doSomething3(B $b, A $a) {
return $this->doSomething1($b).' and '.$this->doSomething0($a).' too';
}
public function __call($method, $arguments){
for(
$methods = call_user_func(array(new ReflectionObject($this), 'getMethods')),
$re = '/^'.$method.'[0-9]+/',
$i = 0, $j = count($methods), $k = count($arguments);
$i < $j; $i++
) {
if(
preg_match($re, $methods[$i]->name) &&
$methods[$i]->getNumberOfRequiredParameters() === $k
) {
$params = $methods[$i]->getParameters();
for(
$l = 0, $call = true;
$l < $k && $call;
$l++
)
$call = call_user_func(array(new ReflectionObject($arguments[$l]), 'getName')) === $params[$l]->getClass()->name;
if($call)
return call_user_func_array(array($this, $methods[$i]->name), $arguments);
}
}
}
}
Verifica del corretto funzionamento
codice:
$o = new Overload();
$a = new A('test one');
$b = new B('test two');
echo $o->doSomething($a), '
',
$o->doSomething($b), '
',
$o->doSomething($a, $b), '
',
$o->doSomething($b, $a), '
';
Consiglio di approfondire la Reflection API e spero che questo spunto torni utile a qualcuno