【问题标题】:Objects are passed by reference. Parameters to call_user_func aren't. What gives?对象通过引用传递。 call_user_func 的参数不是。是什么赋予了?
【发布时间】:2014-01-08 03:28:01
【问题描述】:

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

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

我有一个实际的问题:有什么方法可以使with_ref() 函数工作?我只想修改RunEvent() 代码,而不是每个使用它的函数(其中大部分确实使用引用)。

我还有一个好奇的问题,因为我在这里看到的行为对我来说毫无意义。 相反会更有意义。这里到底发生了什么? 一个函数调用没有一个&符号可以修改对象,而一个&符号的函数调用似乎令人吃惊地违反直觉不能

【问题讨论】:

    标签: php object pass-by-reference


    【解决方案1】:

    参数传递

    主要问题是 - 传递给 call_user_func() 的参数将作为 传递 - 所以它们将是实际数据的副本。这种行为覆盖了一个事实,即

    对象通过引用传递。注意:

    注意call_user_func()的参数不是通过 参考。

    跟踪错误

    在这种情况下,您对“默许”的说法并不完全正确。在这种情况下,您会看到级别为 E_WARNING 的错误:

    警告:with_ref() 的参数 1 应为参考,值在

    所以 - 您将能够发现您正在混合引用和值传递

    解决问题

    幸运的是,避免这个问题并不难。只需创建对所需值的引用:

    class Example {
        function RunEvent($event) {
            if (isset($this->events[$event])) {
                foreach ($this->events[$event] as $k => $v) {
    
                    $obj = &$this;
                    call_user_func($v, $obj);
                }
            }
        }
    }
    

    -那么结果将完全符合预期:

    对象(示例)#1 (3) { [“事件”]=> 数组(1){ [“示例”]=> 数组(2){ [0]=> 字符串(8)“with_ref” [1]=> 字符串(11)“没有_ref” } } ["with_ref"]=> 布尔(真) [“没有_ref”]=> 布尔(真) }

    【讨论】:

    • 需要注意的是,without_ref 函数之所以能正常工作,是因为php 处理引用的方式。参考包含根据文档here 访问实际对象所需的信息。这意味着 call_user_func 将 $this 引用作为值传递给 without_ref 函数(这工作正常,因为 without_ref 需要一个值而不是引用)但是由于传递的参数包含访问原始对象的信息,所以函数能够修改原始对象。
    • 啊哈! @elitechief21 这实际上是有道理的。谢谢你。结合上面的答案,它解决了我的问题。
    • "对象是通过引用传递的。注意:" 在 PHP 中,“对象”永远不会“通过引用传递”。 “对象”不是 PHP 中的值。如果参数上没有&,PHP 中的所有内容都是按值传递,如果参数上有&,则通过引用传递。期间。
    【解决方案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?

    【讨论】:

    【解决方案3】:

    首先,“对象”不是“通过引用传递”或“通过引用有效传递”(这是一个虚构的术语)。 “对象”在 PHP5 中不是值,根本不能“通过”。 $e$this 等的值是指向对象的指针

    在 PHP 中,当有 & 时通过引用传递,否则通过值传递。总是。

    call_by_func 只是一个函数。在它的声明中,它的参数没有&。因此,该参数是按值传递的。因此,您传递给call_by_func 的内容始终是按值传递的。

    您在 PHP 5.3 中使用了“调用时按引用传递”,它将按值传递参数覆盖为按引用传递,这是一种非常糟糕的做法,并在 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 之前,这用于执行调用时传递引用。但是,从 PHP 5.4 开始,这不是调用时的传递引用。当我们使用它通过引用调用一个本来应该通过引用调用的函数时,它就起作用了。当我们使用它来调用传值函数时,它就像传值一样工作。

    【讨论】:

      【解决方案4】:

      这是修改后的代码(取自 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
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2016-12-09
        • 2012-06-24
        • 1970-01-01
        • 2018-08-17
        • 1970-01-01
        • 2011-12-11
        • 2018-08-11
        • 2021-09-11
        • 2015-06-18
        相关资源
        最近更新 更多