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);
不要问我有什么不同,但它是按你想要的方式工作的(这也是我讨厌这门语言的原因之一)