2010-11-08 13 views
8

Il PHP manual for anonymous functions (cioè, chiusure) stabilisce che:Test di PHP Chiusura senza riferimento alla classe interna Chiusura

funzioni anonime sono attualmente implementati utilizzando la classe di chiusura. Questo è un dettaglio di implementazione e non dovrebbe essere invocato su.

(L'enfasi è mia)

E 'possibile testare una variabile, in modo che il test restituisce vero solo se la variabile è una chiusura, senza fare riferimento alla classe di chiusura?

In altre parole, come posso riscrivere la seguente modo tale che esso genera un errore quando $bar è tutt'altro che una funzione anonima:

function foo(Closure $bar) { 
    $bar(); 
}

EDIT: Sulla base delle risposte ricevute, qui è un test di esempio .

Note:

  1. sembra che ci sia alcun modo per distinguere tra Funtori e chiusure, e che il test è probabilmente altrettanto 'implementazione specifica' come utilizzando la classe di chiusura.
  2. Il metodo (apparentemente ovvio) ReflectionFunction::isClosure() sembra essere quasi inutile: dal momento in cui sono stati eseguiti i controlli necessari per assicurarsi che ReflectionFunction possa effettivamente essere istanziato (non può prendere una classe tranne che per una chiusura), hai eliminato tutte le altre opzioni.
  3. In 5.3.0 si ReflectionClass ($ closure) -> hasMethod ('__ invoke') ha restituito false, quindi questo potrebbe essere utilizzato come test contro Functors, tuttavia (mi è stato detto) questo è cambiato da allora. Ciò evidenzia anche la fragilità della soluzione.
  4. seguito riservato alle Gordon - Dal PHP 5.4 si può contare su di chiusura essendo una chiusura: php.net/manual/en/class.closure.php

Codice: caso

/** 
* Return true if and only if the passed argument is a Closure. 
*/ 
function testClosure($a) { 
    // Must be Callback, Labmda, Functor or Closure: 
    if(!is_callable($a)) return false; 

    // Elminate Callbacks & Lambdas 
    if(!is_object($a)) return false; 

    // Eliminate Functors 
    //$r = new ReflectionFunction($a); <-- fails if $a is a Functor 
    //if($r->isClosure()) return true; 

    return false; 
} 

prova:

//////////// TEST CASE ///////////// 

class CallBackClass { 
    function callBackFunc() { 
    } 
} 

class Functor { 
    function __invoke() { 
    } 
} 

$functor = new Functor(); 
$lambda = create_function('', ''); 
$callback = array('CallBackClass', 'callBackFunc'); 
$array = array(); 
$object = new stdClass(); 
$closure = function() { ; }; 

echo "Is it a closure? \n"; 
echo "Closure: " . (testClosure($closure) ? "yes" : "no") . "\n"; 
echo "Null: " . (testClosure(null) ? "yes" : "no") . "\n"; 
echo "Array: " . (testClosure($array) ? "yes" : "no") . "\n"; 
echo "Callback: " . (testClosure($callback) ? "yes" : "no") . "\n"; 
echo "Labmda: " .(testClosure($lambda) ? "yes" : "no") . "\n"; 
echo "Invoked Class: " . (testClosure($functor) ? "yes" : "no") . "\n"; 
echo "StdObj: " . (testClosure($object) ? "yes" : "no") . "\n"; 

-

+1

Che dire funzioni create utilizzando 'create_function', funzioni anonime del povero da prima PHP 5.3? Che dire degli oggetti che possono essere '__invoke()' d? – janmoesen

+0

Sono particolarmente interessato a verificare se è possibile eseguire una chiusura ("... restituisce true solo se la variabile è una chiusura ...") – Hamish

+2

La mia domanda è perché si desidera distinguere tra un functor (o qualsiasi callback a quel punto) e una chiusura? In 5.3, non c'è differenza dal tuo punto di vista (dal momento che non puoi riassociare nulla), quindi che importa? Se passa 'is_callable()', allora perché non è abbastanza ??? – ircmaxell

risposta

8

È inoltre possibile utilizzare

ReflectionFunctionAbstract::isClosure - Controlla se la chiusura

Esempio:

$poorMansLambda = create_function('', 'return TRUE;'); 
$rf = new ReflectionFunction($poorMansLambda); 
var_dump($rf->isClosure()); // FALSE 

$lambda = function() { return TRUE; }; 
$rf = new ReflectionFunction($lambda); 
var_dump($rf->isClosure()); // TRUE 

$closure = function() use ($lambda) { return $lambda(); };  
$rf = new ReflectionFunction($closure); 
var_dump($rf->isClosure()); // TRUE 

Si noti che quanto sopra tornerà solo TRUE per PHP 5.3 lambda e chiusure.Se vuoi solo sapere se un argomento può essere usato come callback, is_callable funzionerà meglio.


EDIT Se si desidera includere Funtori così, si può fare (as of PHP 5.3.3)

$rf = new ReflectionObject($functorOrClosureOrLambda); 
var_dump($rf->hasMethod('__invoke')); // TRUE 

o

method_exists($functorOrClosureOrLambda, '__invoke'); 

con quest'ultimo è l'alternativa più veloce.

Un'istanza Closure è fondamentalmente solo una classe che ha una funzione __invoke che ha alimentato il metodo body al volo. Ma dal momento che questo è il test per un dettaglio di implementazione, direi che è inaffidabile come test per il nome della classe Closure.


EDIT Dal momento che si parla, non è possibile verificare in modo affidabile tramite l'API Reflection a causa di essa sollevare un errore quando si passa una Functor a ReflectionFunctionAbstract::isClosure, provare se la seguente soluzione adatta alle vostre esigenze:

function isClosure($arg) 
{ 
    if(is_callable($arg, FALSE, $name)) { 
     is_callable(function() {}, TRUE, $implementation); 
     return ($name === $implementation); 
    } 
} 

Questo controllerà se l'argomento passato è chiamabile. L'argomento $name memorizza il nome del chiamabile. Per chiusure, questo è attualmente Closure::__invoke. Poiché questo sarà lo stesso per ogni Closures/Lambdas, possiamo confrontare il nome dell'argomento passato con un altro arbitrario Closure/Lambda. Se sono uguali, l'argomento deve essere una chiusura/Lambda. Determinare il nome di callable in fase di esecuzione ha il vantaggio aggiuntivo che non è necessario eseguire l'hardcode dei presupposti relativi ai dettagli di implementazione nel codice sorgente. Il passaggio di un functor restituirà FALSE, poiché non avrà lo stesso nome di chiamabile. Poiché ciò non si basa sull'API di Reflection, è probabile che sia anche un po 'più veloce.

È possibile che questo poteva essere più elegantemente scritto come

function isClosure($arg) { 
    $test = function(){}; 
    return $arg instanceof $test; 
} 
+1

Bello, mi piace un po 'di riflessione. – janmoesen

+0

Bello. Questo approccio supererà il suo problema di "implementazione detail"? Suppongo che stia davvero chiedendo se isClosure verifica la chiusura della classe. – webbiedave

+2

@webbiedave - non importa, dato che presumo che una modifica di implementazione a Closures venga sostituita in ReflectionFunction – Hamish

1

is_callable e !is_array potrebbe aiutarti. Nota che non puoi fare affidamento sul tipo di suggerimento/controllo di PHP in questo modo, poiché dovresti controllare la variabile all'interno della funzione e lanciare qualcosa, ad es. un InvalidArgumentException tu stesso.

+0

+1 anche se si dovrebbe notare che questo 'is_callable' restituirà' TRUE' per qualsiasi callable, non solo Closures. – Gordon

+1

@Gordon: giustamente, stavo aggiungendo questo come commento alla domanda originale mentre hai postato questo. :-) – janmoesen

Problemi correlati