2010-11-08 8 views
8

El PHP manual for anonymous functions (es decir, los cierres) establece que:Testing para un cierre PHP sin hacer referencia a la clase interna Closure

funciones anónimas se implementan actualmente utilizando la clase de cierre. Este es un detalle de implementación y no se debe confiar en.

(El énfasis es mío)

¿Es posible probar una variable, de tal manera que la prueba devuelve verdadero si la variable es un cierre, sin hacer referencia a la clase de cierre?

En otras palabras, ¿cómo puedo volver a escribir el siguiente ejemplo que va a haber un error al $bar es otra cosa que una función anónima:

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

EDIT: En base a las respuestas recibidas, aquí es una prueba de ejemplo .

Notas:

  1. Parece que no hay manera de diferenciar entre Functors y cierres, y que la prueba es probablemente tan 'aplicación específica' como el uso de la clase de cierre.
  2. El método (aparentemente obvio) ReflectionFunction::isClosure() parece ser casi inútil: en el momento en que ha realizado las comprobaciones necesarias para asegurarse de que ReflectionFunction pueda realmente crearse una instancia (no puede tomar una Clase a excepción de un Cierre), usted eliminé todas las demás opciones.
  3. En 5.3.0, ReflectionClass ($ closure) -> hasMethod ('__ invoke') devolvió false, por lo que podría usarse como una prueba contra Functors, sin embargo (me dicen) esto ha cambiado desde entonces. Esto resalta la fragilidad de la solución también.
  4. Seguimiento de Gordon - A partir de PHP 5.4 se puede confiar en Cierre de ser un Cierre: php.net/manual/en/class.closure.php

Código: 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; 
} 

prueba:

//////////// 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

¿Qué pasa con funciones creadas usando 'create_function', funciones anónimas del pobre hombre de delante de PHP 5.3? ¿Qué pasa con los objetos que pueden ser '__invoke()' d? – janmoesen

+0

Estoy especialmente interesado en si puede realizar una prueba para un Cierre ("... devuelve verdadero solo si la variable es un Cierre ...") – Hamish

+2

Mi pregunta para usted es por qué le gustaría distinguir entre un functor (o cualquier devolución de llamada en ese punto) y un cierre? En 5.3, no hay diferencia desde su punto de vista (ya que no puede vincular nada), entonces, ¿qué importa? Si pasa 'is_callable()', ¿por qué no es suficiente? – ircmaxell

Respuesta

8

También puede utilizar

ReflectionFunctionAbstract::isClosure - Comprueba si el cierre

Ejemplo:

$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 

Tenga en cuenta que lo anterior sólo devolverá TRUE para PHP 5.3 Lambdas y cierres.Si solo desea saber si un argumento se puede usar como una devolución de llamada, is_callable tendrá un mejor rendimiento.


EDITAR Si desea incluir Functors así, se puede hacer (as of PHP 5.3.3)

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

o

method_exists($functorOrClosureOrLambda, '__invoke'); 

siendo esta última la alternativa más rápida.

Una instancia de Closure es básicamente una clase que tiene una función __invoke que alimenta el cuerpo del método sobre la marcha. Pero dado que esto está probando un detalle de implementación, diría que es tan poco fiable como probar el nombre de clase Closure.


EDITAR Ya que mencionas no se puede probar de forma fiable a través de la API de reflexión debido a que elevar un error al pasar un Functor a ReflectionFunctionAbstract::isClosure, probar si la siguiente solución se adapte a sus necesidades:

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

Esto verificará si el argumento pasado es invocable. El argumento $name almacena el nombre recuperable. Para cierres, actualmente es Closure::__invoke. Dado que esto será el mismo para cualquier Closures/Lambdas, podemos comparar el nombre del argumento pasado con otro Closure/Lambda arbitrario. Si son iguales, el argumento debe ser un cierre/lambda. La determinación del nombre invocable en tiempo de ejecución tiene el beneficio adicional de no tener que codificar las suposiciones sobre los detalles de implementación en su código fuente. Pasar un functor devolverá FALSE, porque no tendrá el mismo nombre recuperable. Como esto no depende de Reflection API, también es probable que sea un poco más rápido.

Lo anterior podría ser más elegantemente escrita como

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

Bien, me gusta reflexionar. – janmoesen

+0

Agradable. ¿Este enfoque superará su problema de "detalles de implementación"? Supongo que realmente estoy preguntando si isClosure verifica el Cierre de clase. – webbiedave

+2

@webbiedave - eso realmente no importa, ya que supondría que un cambio de implementación en Cierres sería reemplazado en ReflectionFunction – Hamish

1

is_callable y !is_array podría ayudarte. Tenga en cuenta que no puede confiar en el tipo de sugerencias/verificaciones de PHP de esta manera, ya que tendría que verificar la variable dentro de la función y lanzar algo, p. un InvalidArgumentException usted mismo.

+0

+1, aunque debe tenerse en cuenta que este 'is_callable' devolverá' TRUE' para cualquier callables, no solo Closures. – Gordon

+1

@Gordon: de acuerdo, añadí eso como comentario a la pregunta original mientras publicabas esto. :-) – janmoesen

Cuestiones relacionadas