Pagina 1 di 2 1 2 ultimoultimo
Visualizzazione dei risultati da 1 a 10 su 19
  1. #1

    [PHP 5] __call + Reflection per emulare overload con Type Hint

    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
    Formaldehyde a new Ajax PHP Zero Config Error Debugger

    WebReflection @WebReflection

  2. #2
    Giusto per completare il tutto ... nell'esempio sfrutto doSomethingN dentro gli altri metodi, in realtà basta sfruttare l'overload anche dentro i metodi stessi ...
    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->doSomething($a).' and '.$this->doSomething($b).' too';
    	}
    	
    	protected function doSomething3(B $b, A $a) {
    		return $this->doSomething($b).' and '.$this->doSomething($a).' too';
    	}
    	
    	public function __call($method, $arguments){
    		...
    	}
    }
    Formaldehyde a new Ajax PHP Zero Config Error Debugger

    WebReflection @WebReflection

  3. #3
    Ciao.
    Codice PHP:
    function stringValue(MyClass $param){
        return (string)
    $param;

    Scusa ma cosa c'entra (string)
    $param è un oggetto ?

    Without faith, nothing is possible. With it, nothing is impossible
    http://ilwebdifabio.it

  4. #4
    Originariamente inviato da whisher
    Ciao.
    Codice PHP:
    function stringValue(MyClass $param){
        return (string)
    $param;

    Scusa ma cosa c'entra (string)
    $param è un oggetto ?

    ... $param è un'istanza di MyClass
    codice:
    // definito altrove
    $param = new MyClass(....)
    ed il casting a stringa serve per richiamare l'eventuale __toString, se presente ... dato che la funzione si chiama stringValue credevo fosse abbastanza esplicito
    Formaldehyde a new Ajax PHP Zero Config Error Debugger

    WebReflection @WebReflection

  5. #5
    Codice PHP:
    stringValue credevo fosse abbastanza esplicito 
    leggendo + in giù posso essere d'accordo ma
    all'inizio era un po duro capire le tu intenzioni
    se scrivi qc che non è chiara all'inizio
    il lettore si in........

    Without faith, nothing is possible. With it, nothing is impossible
    http://ilwebdifabio.it

  6. #6
    Originariamente inviato da whisher
    Codice PHP:
    stringValue credevo fosse abbastanza esplicito 
    leggendo + in giù posso essere d'accordo ma
    all'inizio era un po duro capire le tu intenzioni
    se scrivi qc che non è chiara all'inizio
    il lettore si in........
    ok, comunque di tutto il post m'hai quotato e chiesto chiarimenti sulla cosa più inutile
    Formaldehyde a new Ajax PHP Zero Config Error Debugger

    WebReflection @WebReflection

  7. #7
    Utente di HTML.it L'avatar di Gunn
    Registrato dal
    Feb 2006
    Messaggi
    370
    asd ganzo... non è troppo macchinoso forse?

    il metodo __call (e gli altri metodi magici) vengono ereditati, si??
    -10 points to SCLERO high top class [definition of SCLERO HIGH TOP CLASS: 90 random kills]

  8. #8
    Utente di HTML.it L'avatar di Gunn
    Registrato dal
    Feb 2006
    Messaggi
    370
    provato... ripeto... troppo macchinoso secondo me... se una cosa non c'è nel povero php non tentare di migliorarlo!

    Codice PHP:
    function overload()
    {
        
    // me la gestisco con func_num_args() get_args ecc ecc con typehint
        
    if()
        
    $this->overload1($a,$b)
        elseif()
        
    $this->overload2($a)
        elseif()
        
    $this->overload3($a,$b,$c)
    }

    function 
    overload1($a,$b){}
    function 
    overload2($a){}
    function 
    overload3($a,$b,$c){} 
    che differenza c'è tra usare il tuo metodo o un semplice incapsulamento fatto così?

    ciauz
    -10 points to SCLERO high top class [definition of SCLERO HIGH TOP CLASS: 90 random kills]

  9. #9
    qualcuno ha capito cosa ho fatto e come va sfruttato? :master:



    Originariamente inviato da Gunn
    provato... ripeto... troppo macchinoso secondo me...
    cosa c'è di macchinoso in un copia e incola di un metodo __call ?

    e invece di una classe Overloadable con solo il metodo __call protected da estendere?



    Originariamente inviato da Gunn
    se una cosa non c'è nel povero php non tentare di migliorarlo!
    tutto il codice che scrivi non c'è in PHP ... o non avresti bisogno di scriverlo ...


    Originariamente inviato da Gunn
    // me la gestisco con func_num_args() get_args ecc ecc con typehint
    come fai a gestirlo con func_num_args e get_args ecc ecc con typehinting se non hai usato metodi che sfruttano il type hinting ne tantomeno usato un codice meno prolisso del mio semplice __call basato su Reflection quindi più che affidabile? (nonchè insostituibile ... senza Reflection non fai molto con num_args e get_args ... )



    Originariamente inviato da Gunn
    che differenza c'è tra usare il tuo metodo o un semplice incapsulamento fatto così?
    la differenza è che io ho implementato un overload su Type Hinting per PHP 5 che non esiste ... tu non hai fatto niente o almeno non l'hai scritto, anzi, hai ulteriormente reso macchinoso il codice scrivendo una funzione dedicata che si deve occupare di gestire i casi ... mentre il mio metodo __call fa tutto in automatico con qualunque metodo


    codice:
    class Pippo extends Overloadable {
    
    	private $name = 'pippo';
    
    	public function doStuff1(){
    		return $this->name;
    	}
    
    	public function doStuff2(Pippo $param){
    		return $param->doStuff();
    	}
    
    	public function doStuff3(Pluto $param) {
    		return $param->getName();
    	}
    
    	private function copyName1(Pippo $param){
    		$this->name = $param->doStuff();
    	}
    
    	private function copyName2(Pluto $param){
    		$this->name = $param->getName();
    	}
    
    	public function newName($pippoOppurePluto){
    		$this->copyName($pippoOppurePluto);
    	};
    
    }
    prova a farlo a modo tuo e vediamo quale via è più macchinosa
    Formaldehyde a new Ajax PHP Zero Config Error Debugger

    WebReflection @WebReflection

  10. #10

    ok ...

    ... passiamo a qualcosa di concreto ...

    Codice PHP:
    <?php
    class TypeHintOverloadable {
        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);
                }
            }
            throw new 
    Exception('Unable to find method '.$method.' with specified overload');
        }
    }

    class 
    Example1 extends TypeHintOverloadable {

        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 $aB $b) {
            return 
    $this->doSomething($a).' and '.$this->doSomething($b).' too';
        }
        
        protected function 
    doSomething3(B $bA $a) {
            return 
    $this->doSomething($b).' and '.$this->doSomething($a).' too';
        }
        
        public function 
    getName(){
            return 
    'Example1';
        }
    }

    class 
    Example2 extends TypeHintOverloadable {

        private 
    $name 'Example2';

        public function 
    doStuff1(){
            return 
    $this->name;
        }

        public function 
    doStuff2(Example2 $instance){
            return 
    $instance->doStuff();
        }

        public function 
    doStuff3(Example1 $instance) {
            return 
    $instance->getName();
        }

        protected function 
    copyName1(Example2 $instance){
            
    $this->name $instance->doStuff();
        }

        protected function 
    copyName2(Example1 $instance){
            
    $this->name $instance->getName();
        }

        public function 
    newName($Example1_OR_Example2){
            
    $this->copyName($Example1_OR_Example2);
        }
    }


    // tests
    class A{
        private    
    $value;
        function 
    __construct($value){
            
    $this->value $value;
        }
        function 
    __toString(){
            return 
    ''.$this->value;
        }
    }

    class 
    extends A{}

    $o = new Example1();

    $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), '
    '
    ;
        
        
    $o2 = new Example2();
    $o3 = new Example2;

    echo    
    $o2->doStuff(), '
    '
    ,
        
    $o2->doStuff($o), '
    '
    ,
        
    $o2->doStuff($o3), '
    '
    ;

    $o2->newName($o3);
    $o2->newName($o);

    echo 
    $o2->doStuff().'<hr />';

    echo 
    $o2->noWay();
    ?>
    ... facile? ... Overload con Type Hinting
    Formaldehyde a new Ajax PHP Zero Config Error Debugger

    WebReflection @WebReflection

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2024 vBulletin Solutions, Inc. All rights reserved.