在PHP中,objects are effectively passed by reference(幕后发生的是a bit more complicated)。同时,call_user_func()
的参数不是通过引用传递。
那么像这样的一段代码会发生什么?
class Example {
function RunEvent($event) {
if (isset($this->events[$event])) {
foreach ($this->events[$event] as $k => $v) {
//call_user_func($v, &$this);
// The above line is working code on PHP 5.3.3, but
// throws a parse error on PHP 5.5.3.
call_user_func($v, $this);
}
}
}
}
$e = new Example;
$e->events['example'][] = 'with_ref';
$e->events['example'][] = 'without_ref';
$e->RunEvent('example');
function with_ref(&$e) {
$e->with_ref = true;
}
function without_ref($e) {
$e->without_ref = true;
}
header('Content-Type: text/plain');
print_r($e);
输出:
Example Object
(
[events] => Array
(
[example] => Array
(
[0] => with_ref
[1] => without_ref
)
)
[without_ref] => 1
)
注意:在文件顶部添加error_reporting(E_ALL);
或error_reporting(-1);
没有任何区别。我没有看到任何错误或警告,当然命令行上的php -l
显示没有错误。
我实际上期望它在回调函数中使用和不使用引用。我认为删除$this
中call_user_func()
之前的&符号就足以解决最新版本的PHP问题。显然,带引用的版本不起作用,但同样不会抛出任何linting错误,因此很难找到这种情况(在我正在使用的代码库中可能会多次出现)。
我在这里有一个实际问题:有没有办法让with_ref()
功能起作用?我只想修改RunEvent()
代码,而不是每个使用它的函数(大多数做使用引用)。
我也有一个好奇的问题,因为我在这里看到的行为对我没有意义。 相反会更有意义。这里到底发生了什么? 一个函数调用没有&符号可以修改对象,而一个带&符号似乎是非常直观的反直觉不能的
答案 0 :(得分:2)
显然,带引用的版本不起作用,但同样不会抛出任何linting错误,所以很难找到这种情况(在我正在使用的代码库中可能会多次出现)。
它会抛出错误:Warning: Parameter 1 to with_ref() expected to be a reference...
。
开发时的Error_reporting应为error_reporting(-1);
。
我在这里有一个实际的问题:有没有办法让with_ref()函数工作?我只想修改RunEvent()代码,而不是每个使用它的函数(大多数函数都使用引用)。
您可以将call_user_func($v, $this);
替换为$v($this);
。
我也有一个好奇的问题,因为我在这里看到的行为对我没有意义。相反的情况会更有意义。这里到底发生了什么?
call_user_func
可以仅按值传递参数,而不是按引用传递参数。
Why does the error "expected to be a reference, value given" appear?
答案 1 :(得分:2)
传递参数
主要问题是 - 传递给call_user_func()
的参数将作为值传递 - 因此它们将是实际数据的副本。这种行为会覆盖事实,即
对象通过引用传递。注意:
请注意,call_user_func()的参数不会传递 参考
跟踪错误
在这种情况下,你对“沉默协议”并不完全正确。在这种情况下,您会看到级别为E_WARNING
的错误:
Warning: Parameter 1 to with_ref() expected to be a reference, value given in
所以 - 你将能够弄清楚你正在混合参考和值传递
解决问题
幸运的是,避免这个问题并不难。只需创建对所需值的引用:
class Example {
function RunEvent($event) {
if (isset($this->events[$event])) {
foreach ($this->events[$event] as $k => $v) {
$obj = &$this;
call_user_func($v, $obj);
}
}
}
}
- 结果将完全符合预期:
object(Example)#1 (3) { ["events"]=> array(1) { ["example"]=> array(2) { [0]=> string(8) "with_ref" [1]=> string(11) "without_ref" } } ["with_ref"]=> bool(true) ["without_ref"]=> bool(true) }
答案 2 :(得分:1)
首先,“对象”不是“通过引用传递”或“通过引用有效传递”(这是一个虚构的术语)。 “对象”不是PHP5中的值,也不能“传递”。 $e
,$this
等的值是指向对象的指针。
在PHP中,当存在&
时,事物通过引用传递,否则传递值。总是
call_by_func
只是一个功能。在其声明中,其参数没有&
。因此,该参数是按值传递的。因此,传递给call_by_func
的内容始终按值传递。
你在PHP 5.3中使用“call-time pass-by-reference”,它将pass-by-value参数覆盖为pass-by-reference,这是非常糟糕的做法,并在PHP 5.4中删除了。
函数with_ref
和without_ref
都没有为其参数($e
)分配,所以实际上没有必要通过引用传递它。但是,由于您将参数声明为with_ref
作为pass-by-reference,因此将其与call_user_func
一起使用时会出现问题,因为正如我们之前所讨论的那样,call_user_func
按值获取其参数,所以它已经丢失了“引用”,所以它无法通过引用传递给你的函数。 call_user_func
的文档表示会产生警告,并且调用会返回FALSE
。
一种解决方案当然只是使用$v($this);
。即根本不使用call_user_func
- 只需使用名称直接调用它。无论如何,很少需要使用call_user_func
。
如果必须使用call_user_func*
系列函数,则可以将call_user_func_array
与包含引用元素的数组一起使用(请记住,您可以将引用放入数组中)。这保留了“引用”,以便它可以传递给传递引用函数:
call_user_func_array($v, array(&$this));
注意:在PHP 5.4之前,这用于执行call-time pass-by-reference。但是,从PHP 5.4开始,这不是一个调用时传递引用。当我们使用它通过引用调用一个意图通过引用调用的函数时,它可以工作。当我们使用它来调用按值传递函数时,它就像传值一样工作。
答案 3 :(得分:0)
这是修改后的代码(取自the accepted answer)和评论(取自其他答案和对已接受答案的评论)。这个问题的第2部分的答案埋没在对已接受的答案的评论中。如果整个答案都放在一个地方我会接受它并完成它,但是因为我把它拼接在一起我就把它扔进去了。
function RunEvent($event) {
if (isset($this->events[$event])) {
foreach ($this->events[$event] as $v) {
$obj = &$this;
call_user_func($v, $obj);
// The user func *should* receive the object by value, not
// by reference. What's *actually* passed is a pointer to
// the location of the object, so modifications to the object
// in that func will actually be applied to the real object.
// In that case, a simple call_user_func($v, $this) will
// work.
//
// However, some of the existing user funcs receive the
// object by reference. That can and should be changed,
// but there are quite a lot to track down, and they don't
// throw linting errors. In that case, call_user_func will
// pass by value, and they're expecting to recive it by
// reference, so you get a run-time warning. (In theory,
// anyway. When I was practising on a standalone script
// I saw no warnings at all.) That's not good.
//
// One way around this would be to use $v($this) instead
// of call_user_func, but the user func may sometimes be
// a class method, so that's not going to work either. Instead,
// the above compromise method seems to work without problems.
//
// We may at some future point switch to call_user_func($v, $this),
// and track down the remaining warnings as we hit them.
//
// https://stackoverflow.com/q/20683779/209139
}
}
}