对象通过引用传递。 call_user_func的参数不是。是什么赋予了?

时间:2013-12-19 14:05:27

标签: php object pass-by-reference

在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显示没有错误。

我实际上期望它在回调函数中使用和不使用引用。我认为删除$thiscall_user_func()之前的&符号就足以解决最新版本的PHP问题。显然,带引用的版本不起作用,但同样不会抛出任何linting错误,因此很难找到这种情况(在我正在使用的代码库中可能会多次出现)。

我在这里有一个实际问题:有没有办法让with_ref()功能起作用?我只想修改RunEvent()代码,而不是每个使用它的函数(大多数使用引用)。

我也有一个好奇的问题,因为我在这里看到的行为对我没有意义。 相反会更有意义。这里到底发生了什么? 一个函数调用没有&符号可以修改对象,而一个&符号似乎是非常直观的反直觉不能

4 个答案:

答案 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_refwithout_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
        }
    }
}