【问题标题】:Testing for a PHP Closure without referring to the Closure internal class在不参考闭包内部类的情况下测试 PHP 闭包
【发布时间】:2011-05-06 21:23:19
【问题描述】:

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

匿名函数目前是使用闭包类实现的。这是一个实现细节,不应依赖

(重点是我自己的)

是否可以测试一个变量,使得只有当变量是一个闭包时测试才返回真,不参考闭包类

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

函数 foo(闭包 $bar) {
    $bar();
}

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

注意事项:

  1. 似乎没有办法区分函子和闭包,而且 该测试可能与使用 Closure 类一样“特定于实现”。
  2. (看起来很明显)ReflectionFunction::isClosure() 方法似乎几乎没用:当您完成所需的检查以确保 ReflectionFunction 可以实际实例化时(除了闭包,不能使用类) ,您已经排除了所有其他选项。
  3. 在 5.3.0 中,您的 ReflectionClass($closure)->hasMethod('__invoke') 返回 false,因此这可以用作针对 Functor 的测试,但是(有人告诉我)这已经改变了。这也凸显了解决方案的脆弱性。
  4. Gordon 跟进 - 从 PHP 5.4 开始,您可以依赖 Closure 作为一个 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;
}

测试用例:

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

-

【问题讨论】:

  • 使用create_function 创建的函数怎么样,PHP 5.3 之前穷人的匿名函数?可以是__invoke()d 的对象呢?
  • 我对您是否可以测试闭包特别感兴趣(“...仅当变量是闭包时才返回真...”)
  • 我的问题是你为什么要区分函子(或当时的任何回调)和闭包?在 5.3 中,从您的角度来看没有什么不同(因为您无法重新绑定任何东西),那有什么关系呢?如果通过is_callable(),那为什么还不够???
  • 从 PHP 5.4 开始,您可以依赖 Closure 作为一个 Closure:php.net/manual/en/class.closure.php
  • @Gordon 感谢您的跟进!我会在上面添加它。

标签: php closures


【解决方案1】:

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

【讨论】:

  • +1 虽然应该注意这个is_callable 将为任何可调用对象返回TRUE,而不仅仅是闭包。
  • @Gordon:非常正确,当您发布此内容时,我将其添加为对原始问题的评论。 :-)
【解决方案2】:

你也可以使用

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 会更好。


编辑如果你也想包含 Functor,你可以这样做 (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。由于这对于任何闭包/Lambda 都是相同的,因此我们可以将传递参数的名称与任意其他闭包/Lambda 进行比较。如果它们相等,则参数必须是 Closure/Lambda。在运行时确定可调用名称还有一个额外的好处,即您不必将有关实现细节的假设硬编码到源代码中。传递一个仿函数将返回FALSE,因为它不会有相同的可调用名称。由于这不依赖于反射 API,因此它也可能会快一些。

上面可以更优雅地写成

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

【讨论】:

  • 很好,我喜欢我的一些反思。
  • 不错。这种方法会解决他的“实施细节”问题吗?我想我真的在问 isClosure 是否检查类 Closure。
  • @webbiedave - 这并不重要,因为我假设对闭包的实现更改将在 ReflectionFunction 中被替换
  • @webbiedave 这是一个 API 调用,因此您不必担心它的实现细节,并且可以依赖开发人员在更新用于闭包/Lambda 的类时更新反射 API。但由于I dont see any hardcoded classname in the sourcecode,我想说它无论如何都会以某种方式确定它(ZEND_ACC_CLOSURE)。
  • @Gordon - 差不多,但不完全。如何在 StdObj、__invoke-able 对象和闭包之间进行测试?
猜你喜欢
  • 2016-12-05
  • 2012-03-25
  • 1970-01-01
  • 2021-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-10
相关资源
最近更新 更多