【问题标题】:Binding a closure to a class as a new method将闭包作为新方法绑定到类
【发布时间】:2023-03-13 04:50:01
【问题描述】:

我正在构建一个 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();

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

编辑:这与Storing a Closure Function in a Class Property in PHP 略有不同——而提供的__call 解决方案非常优秀,如果您对将闭包绑定到类,此方法不会欺骗method_exists 检查。

【问题讨论】:

  • Storing a Closure Function in a Class Property in PHP 的可能副本。见this answer particularly。我会重新考虑这种方法,继承是 做你想做的事的正确方法。或者,查看traits
  • @RandomSeed Traits 在编译后无法绑定,虽然您提供的那个问题非常有趣并且是一个很好的解决方案,但我的问题的限制意味着我还必须欺骗method_exists,它的设置__call 不行。
  • 你真的需要method_exists() 来返回true 吗?在另一个答案中,is_callable() 完成了这项工作。
  • 是的,不幸的是,确实如此。如果不接触图书馆,我就无法绕过它。不过,我会说你链接到的问题会回答 99% 的人问同样的问题。我想我别无选择,只能与项目所有者打交道。
  • 好的,我看到你已经意识到这是一个丑陋的黑客攻击。请帮我打一下项目所有者 :) PS:clone 似乎是多余的,因为 Closure::bindTo() 已经“复制带有新绑定对象和类范围的闭包”

标签: php closures


【解决方案1】:

它不适用于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);
        }
    }
}

将允许您强制调用您的“可调用属性”。例如,

$c = function($x) {
    return $x*$x;
};

$obj = new Caller();
$obj->bind('foo', $c);
var_dump($obj->foo(4)); //16

查看示例here

可能有一些方法可以动态更改课程本身(runkit 和公司),但我强烈建议尽可能远离这种情况。

【讨论】:

  • 这是一个非常棒的解决方案,但正如你所说,它不会欺骗method_exists,这很糟糕。使用这个库的限制意味着它必须工作。我很确定我将不得不对项目所有者大喊大叫。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 2015-06-25
    • 1970-01-01
    • 2015-02-18
    • 2023-03-28
    • 2011-02-22
    • 2013-12-26
    • 2011-12-14
    • 2011-04-07
    • 1970-01-01
    相关资源
    最近更新 更多