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

标签 php closures

PHP manual for anonymous functions (即闭包)指出:

Anonymous functions are currently implemented using the Closure class. This is an implementation detail and should not be relied upon.

(重点是我自己)

是否可以测试一个变量,以便只有当变量是闭包时测试才返回真,不引用闭包类

换句话说,当 $bar 不是匿名函数时,我如何重写以下内容以引发错误:

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

EDIT: Based on the answers received, here is an example test.

Notes:

  1. It seems there is no way to differentiate between Functors and Closures, and that the test is probably just as 'implementation specific' as using the Closure class.
  2. The (seemingly obvious) ReflectionFunction::isClosure() method seems to be be almost useless: by the time you've done the checks required to make sure that ReflectionFunction can actually be instantiated (can't take a Class except for a Closure), you've eliminated all other options.
  3. In 5.3.0 you ReflectionClass($closure)->hasMethod('__invoke') returned false, so this could be used as a test against Functors, however (I'm told) this has changed since. This highlights the frailty of the solution too.
  4. Follow up from Gordon - As of PHP 5.4 you can rely on Closure being a Closure: php.net/manual/en/class.closure.php

Code:

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

-

最佳答案

你也可以使用

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 Lambda 和闭包,上面的代码只会返回 TRUE。如果你只是想知道一个参数是否可以用作回调,is_callable 会表现得更好。


编辑如果您还想包含仿函数,您可以( as of PHP 5.3.3 )

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

method_exists($functorOrClosureOrLambda, '__invoke');

后者是更快的选择。

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


编辑 由于您提到您无法通过反射 API 可靠地进行测试,因为它在将 Functor 传递给 ReflectionFunctionAbstract::isClosure 时引发错误,请尝试以下解决方案满足您的需求:

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

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

上面的内容可以更优雅地写成

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

关于php - 在不引用 Closure 内部类的情况下测试 PHP Closure,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4127959/

相关文章:

php - PHP mysql_real_escape_string() 保护数据库名称吗?

php - 同一页面上的多个选择列表 -> 错误

php - 同时运行php脚本

Swift Closures - 将 self 捕捉为弱者

javascript - 对于异步内部函数,外部函数的函数参数是否会发生变化?

javascript - Promise 函数无法访问父变量

php - 使用 Symfony 和 PhpStorm 的 XDebug 不起作用(Ubuntu 安装)

Javascript 闭包 - 全局范围内重写函数的行为

ios - 带参数的 Swift 传递闭包

php - Mysql 到 PDO : a lot of code written, 几乎完成了。值得切换吗?