在PHP中保存函数的类变量


Class variables holding a function in PHP

PHP允许变量保存函数,如下所示:

$f = function($a,$b) {
   print "$a $b";
};
$f("Hello","World!"); //prints 'Hello World!'

这对我来说很好。我试图将一个函数传递到一个类中,并设置一个实例变量来保存该函数,但运气不好:

class Clusterer {
    private $distanceFunc;
    public function __construct($f) {
        $this->distanceFunc = $f;
        print $f(1,7); //works
        print $this->distanceFunc(1,7); //exceptions and errors abound
    }
}
$func = function($a,$b) {
    return abs($a-$b);
}
$c = new Clusterer($func);

我在这里做错了什么吗?错误是该函数不存在,所以我目前的猜测是,它寻找一个类函数与该名称(没有一个),然后放弃,而不是寻找变量为好…我怎么能使它查看$this->distanceFunc作为一个变量?

编辑:因此,根据下面答案的建议,我找到了一个解决方案,即创建一个函数来包装调用。例如,我的类现在是:

class Clusterer {
    private $distanceFunc;
    public function __construct($f) {
        $this->distanceFunc = $f;
        print $f(1,7); //works
        print $this->distanceFunc(1,7); //exceptions and errors abound
    }
    private function distanceFunc($a,$b) {
        $holder = $this->distanceFunc;
        return $holder($a,$b);
    }
}
$func = function($a,$b) {
    return abs($a-$b);
}
$c = new Clusterer($func);

效果很好。Php首先查找函数,只能通过上下文判断它是否是变量,我想这就是这个故事的寓意。

您的代码不能工作,因为PHP将$this->distanceFunc(1,7)解释为类方法,但您可以做以下操作:

class Clusterer {
    private $distanceFunc;
    public function __construct($f) {
        $this->distanceFunc = $f;
        print $f(1,7); //works
        print call_user_func_array($this->distanceFunc, array(1, 7));
//      print $this->distanceFunc(1,7); //exceptions and errors abound
    }
}
$func = function($a,$b) {
    return abs($a-$b);
};
$c = new Clusterer($func);
http://sandbox.onlinephpfunctions.com/code/cdc1bd6bd50f62d5c88631387ac9543368069310

在PHP中,对象的方法和属性占用不同的命名空间。这与JavaScript不同,例如,在JavaScript中,foo.bar = function() {}是定义方法的一种完全有效的方式。

因此,$this->distanceFunc(1,7);在当前类上查找名为distanceFunc的方法,以及它继承的类,但从不查找属性,您碰巧给了相同的名称。

一个解决方案是强制PHP查找属性,然后执行它,例如$foo = $this->distanceFunc; $foo(1,7)call_user_func($this->distanceFunc, 1, 7)。在属性查找周围使用括号也具有相同的功能:($this->distanceFunc)(1, 7);

另一种方法是在类上定义魔术方法__call,每当引用不存在的方法时,该方法就会运行。像这样的东西应该可以工作(我现在没有一个简单的方法来测试):

function __call($func, $args) {
     if ( property_exists($this, $func)  && is_callable($this->$func) ) {
           return call_user_func_array($this->$func, $args);
      }
 }

请注意,这仍然与真正的方法不同,例如在访问私有属性方面。

看起来你在这里要使用策略模式。IE你想要能够注入不同的方法来计算距离?如果是这样的话,有一个更"理智"的方法来做这件事。

你可以定义一个接口到你将用来存储策略方法的类,确保类总是有方法calculate(),例如,这将是你的距离计算函数。然后在Clusterer类的构造函数中,根据参数中的接口进行类型检查,并对传入的对象调用calculate()。

看起来像这样:

interface Calculateable
{
    public function calculate();
}
class MyDistanceCalculator implements Calculateable
{
    public function calculate()
    {
        // Your function here
    }
}
class Clusterer
{
    protected $calc;
    public function __construct(Calculateable $calc)
    {
        $this->calc = $calc;
        $this->calc->calculate();
    }
}
$myClusterer = new Clusterer(new MyDistanceCalculator());

因为您定义了一个接口,所以传入的任何对象都将具有calculate()函数

在HHVM中,可以这样做:

<?php
class Foo
{
  public function __construct()
  {
    $this->bar = function() { echo "Here'n"; };
    ($this->bar)();
  }
}
new Foo();

但是在PHP中还不支持。但是,它将在PHP 7中(不会有名为PHP 6的版本)。

PHP没有第一类函数。在JavaScript中,如果你返回一个函数,你可以这样做:myFunctionThatReturnsAFunction()(1,2),但在PHP中不是。

<?php
class Clusterer {
    private $distanceFunc;
    public function __construct(Closure $f) {
        $this->distanceFunc = $f;
    }
    public function getDistFunc()
    {
      return $this->distanceFunc;
    }
}

$func = function($a,$b) {
    return abs($a-$b);
};
$c = new Clusterer($func);
$a = $c->getDistFunc();
echo $a(1,2);

看看call_user_func

class Clusterer {
    private $distanceFunc;
    public function __construct($f) {
        $this->distanceFunc = $f;
        print $f(1,7); //works
        print call_user_func($this->distanceFunc, 1, 7); //works too ;)
    }
}
$func = function($a,$b) {
    return abs($a-$b);
};
$c = new Clusterer($func);

不要问我有什么不同,但它是按你想要的方式工作的(这也是我讨厌这门语言的原因之一)