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

Posted

技术标签:

【中文标题】对象通过引用传递。 call_user_func 的参数不是。是啥赋予了?【英文标题】: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() 代码,而不是每个使用它的函数(其中大部分确实使用引用)。

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

【问题讨论】:

【参考方案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?

【讨论】:

使用$v($this) 是一个不错的技巧,尽管it won't work for my purposes。 +1。【参考方案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://***.com/q/20683779/209139
        
    

【讨论】:

以上是关于对象通过引用传递。 call_user_func 的参数不是。是啥赋予了?的主要内容,如果未能解决你的问题,请参考以下文章

简单理解call_user_func和call_user_func_array两个函数

通过引用传递对象[重复]

通过引用传递;为啥原始对象没有改变?

通过引用递归传递对象? JAVA

通过 const 引用传递对象?

使对象不通过引用传递