在不引用Closure内部类的情况下测试PHP Closure

时间:2010-11-08 20:52:41

标签: php closures

PHP manual for anonymous functions(即闭包)声明:

  

目前使用Closure类实现匿名函数。这是一个实施细节,不应该依赖

(强调是我自己的)

是否可以测试一个变量,这样只有当变量是一个Closure时,测试才返回true,没有引用Closure类

换句话说,我怎样才能重写以下内容,以便在$bar不是匿名函数时会引发错误:

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

编辑:根据收到的答案,这是一个示例测试。

注意:

  1. 似乎没有办法区分Functors和Closures,以及那些 测试可能与使用Closure类的“特定于实现”一样。
  2. (看似很明显)ReflectionFunction::isClosure()方法似乎几乎没用:当你完成所需的检查以确保ReflectionFunction可以实际实例化时(除了a之外不能使用Class)关闭),你已经取消了所有其他选择。
  3. 在5.3.0中你的ReflectionClass($ closure) - > hasMethod('__ invoke')返回false,所以这可以用作对Functors的测试,但是(我被告知)这已经改变了。这也凸显了解决方案的脆弱性。
  4. Gordon跟进 - 从PHP 5.4开始,你可以依赖Closure成为一个关闭:php.net/manual/en/class.closure.php
  5. 代码:

    /**
     * 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;
    }
    

    测试用例:

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

    -

2 个答案:

答案 0 :(得分:8)

您也可以使用

ReflectionFunctionAbstract::isClosure - 检查是否关闭

示例:

$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

请注意,以上内容仅返回PHP 5.3 Lambdas和Closures的TRUE。如果您只是想知道参数是否可以用作回调,is_callable将会表现得更好。


编辑如果您想要包含Functors,您可以as of PHP 5.3.3

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

method_exists($functorOrClosureOrLambda, '__invoke');

后者是更快的选择。

Closure实例基本上只是一个具有__invoke函数的类,您可以动态地为方法体提供信息。但由于这是测试实现细节,我认为它与测试Closure类名一样不可靠。


编辑由于您提到您无法通过Reflection API进行可靠测试,因为在将Functor传递给ReflectionFunctionAbstract::isClosure时会引发错误,请尝试以下解决方案是否符合您的需求:

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

这将检查传递的参数是否可调用。 $name参数存储可调用名称。对于闭包,目前为Closure::__invoke。由于这对于任何Closures / Lambdas都是相同的,我们可以将传递的参数的名称与任意其他Closure / Lambda进行比较。如果它们相等,则参数必须是Closure / Lambda。在运行时确定可调用名称具有额外的好处,您无需将有关实现细节的假设硬编码到源代码中。传递一个仿函数将返回FALSE,因为它没有相同的可调用名称。由于这不依赖于Reflection API,因此它可能会更快一些。

以上可以更优雅地写成

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

答案 1 :(得分:1)

is_callable!is_array可能对您有所帮助。请注意,您不能依赖于PHP的类型提示/检查这种方式,因为您必须检查函数内部的变量并抛出一些内容,例如:一个InvalidArgumentException你自己。