将闭包作为新方法绑定到类


Binding a closure to a class as a new method

我正在构建一个API类,它扩展了供应商类的功能。供应商类期望被扩展,并将检查是否存在这样的方法:

if (method_exists($this, 'block'.$CurrentBlock['type']))
{
    $CurrentBlock = $this->{'block'.$CurrentBlock['type']}($CurrentBlock);
}

因此,由于我的API也是一个供应商文件,我想我应该做一些聪明的事情,尝试让人们将闭包传递到我的API中,并让它扩展类。

public function extendBlock($blockName, Closure $closure)
{
    $methodName = camel_case("block_{$blockName}");
    $this->{$methodName} = $closure;
    return method_exists($this, $methodName);
}

理论上,这将绑定闭包,以便在我的第一个代码块中的调用将成功…但这不会发生。它不被视为一个方法,而是一个包含闭包的属性。不仅method_exist失败,而且尝试调用该方法也失败。

这是一个修改后的版本,我试图找出哪里出了问题。

public function extendBlock($blockName, Closure $closure)
{
    $methodName = camel_case("block_{$blockName}");
    $newClosure = clone $closure;
    $newClosure = $newClosure->bindTo($this);
    $this->{$methodName} = $newClosure;
    $this->{$methodName}();
    return method_exists($this, $methodName);
}

这些都不起作用。该属性已明确设置,并且$closure$this的作用域当前指向该方法的$this

如果我运行这个,闭包会正确执行。

    $this->{$methodName} = $newClosure;
    //$this->{$methodName}();
    $foobar = $this->{$methodName};
    $foobar();

是的。我真的希望有一种好的、整洁的方式来满足我的第一个代码块中的检查,而不需要用户继承我的类并直接编写它们,但我认为这是不可能的。

编辑:这与在PHP的类属性中存储闭包函数略有不同——虽然提供的__call解决方案非常出色,如果您对将闭包绑定到类感到好奇,则值得一试,该方法不会欺骗method_exists检查。

它不适用于method_exists(),因为该函数提供的信息基于在类作用域中显式声明的方法。然而,仍然存在使用魔法方法的变通方法。确切地说是__call():

class Caller
{
    public function bind($method, Closure $call)
    {
        $this->$method = $call;
    }
    public function __call($method, $args)
    {
        if (isset($this->$method) && $this->$method instanceof Closure) {
            return call_user_func_array($this->$method, $args);
        }
    }
}

允许你强制调用你的"property callable"。例如,

$c = function($x) {
    return $x*$x;
};
$obj = new Caller();
$obj->bind('foo', $c);
var_dump($obj->foo(4)); //16

查看示例在这里

可能有一些方法可以动态地改变类本身(runkit和company),但我强烈建议尽可能远离这种方法。

使用来自http://github.com/zenovich/runkit的最新Runkit,您可以简单地编写runkit_method_add(get_class($this), $methodName, $newClosure);