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

Ham*_*ish 9 php closures

匿名函数PHP手册(即Closures)声明:

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

(重点是我自己的)

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

换句话说,我怎样才能重写以下内容,除非$bar是匿名函数,否则会引发错误:

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

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

笔记:

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

码:

/**
 * 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;
}
Run Code Online (Sandbox Code Playgroud)

测试用例:

//////////// 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";
Run Code Online (Sandbox Code Playgroud)

-

Gor*_*don 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
Run Code Online (Sandbox Code Playgroud)

请注意,以上内容仅返回TRUEPHP 5.3 Lambdas和Closures.如果您只想知道参数是否可以用作回调,那么性能is_callable会更好.


编辑如果你想包括Functors,你可以做(从PHP 5.3.3开始)

$rf = new ReflectionObject($functorOrClosureOrLambda);
var_dump( $rf->hasMethod('__invoke') ); // TRUE
Run Code Online (Sandbox Code Playgroud)

要么

method_exists($functorOrClosureOrLambda, '__invoke');
Run Code Online (Sandbox Code Playgroud)

后者是更快的选择.

一个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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

以上可以写得更优雅

function isClosure($arg) {
    $test = function(){};
    return $arg instanceof $test;
}
Run Code Online (Sandbox Code Playgroud)

  • @webbiedave - 这并不重要,因为我假设将在ReflectionFunction中替换对Closures的实现更改 (2认同)