【问题标题】:__callStatic(), call_user_func_array(), references, and PHP 5.3.1__callStatic()、call_user_func_array()、参考和 PHP 5.3.1
【发布时间】:2011-08-01 22:29:09
【问题描述】:

我一直在阅读关于 SO 和其他地方的文章,但我似乎找不到任何结论性的内容。

是否有任何方法可以有效地通过此调用堆栈进行引用,从而产生下面示例中描述的所需功能?虽然这个例子并没有试图解决它,但它确实说明了问题:

class TestClass{
    // surely __call would result similarly
    public static function __callStatic($function, $arguments){
        return call_user_func_array($function, $arguments);
    }
}

// note argument by reference
function testFunction(&$arg){
    $arg .= 'bar';
}

$test = 'foo';
TestClass::testFunction($test);

// expecting: 'foobar'
// getting:   'foo' and a warning about the reference
echo $test;

为了激发潜在的解决方案,我将在此处添加摘要详细信息:

仅关注call_user_func_array(),我们可以确定(至少对于 PHP 5.3.1)您不能通过引用隐式传递参数:

function testFunction(&$arg){
    $arg .= 'bar';
}

$test = 'foo';
call_user_func_array('testFunction', array($test));
var_dump($test);
// string(3) "foo" and a warning about the non-reference parameter

通过显式传递数组元素$test 作为引用,我们可以缓解这种情况:

call_user_func_array('testFunction', array(&$test));
var_dump($test);
// string(6) "foobar"

当我们使用__callStatic() 引入类时,通过引用的显式调用时间参数似乎按照我的预期进行,但会发出弃用警告(在我的 IDE 中):

class TestClass{
    public static function __callStatic($function, $arguments){
        return call_user_func_array($function, $arguments);
    }
}

function testFunction(&$arg){
    $arg .= 'bar';
}

$test = 'foo';
TestClass::testFunction(&$test);
var_dump($test);
// string(6) "foobar"

TestClass::testFunction() 中省略引用运算符会导致$test 按值传递给__callStatic(),当然也会作为数组元素按值通过call_user_func_array() 传递给testFunction()。这会导致警告,因为 testFunction() 需要引用。

四处乱窜,一些额外的细节浮出水面。 __callStatic() 定义,如果写成通过引用返回 (public static function &__callStatic()) 没有可见效果。此外,将__callStatic() 中的$arguments 数组的元素重铸为引用,我们可以看到call_user_func_array() 的工作方式与预期有些相似:

class TestClass{
    public static function __callStatic($function, $arguments){
        foreach($arguments as &$arg){}
        call_user_func_array($function, $arguments);
        var_dump($arguments);
        // array(1) {
        //   [0]=>
        //   &string(6) "foobar"
        // }
    }
}

function testFunction(&$arg){
    $arg .= 'bar';
}

$test = 'foo';
TestClass::testFunction($test);
var_dump($test);
// string(3) "foo"

这些结果是预期的,因为 $test 不再通过引用传递,更改也不会传递回其范围。但是,这证实了call_user_func_array() 实际上按预期工作,并且问题肯定仅限于调用魔法。

进一步阅读,它似乎是 PHP 处理用户函数的一个“错误”,以及 __call()/__callStatic() 的魔法。我仔细阅读了现有或相关问题的错误数据库,并找到了一个,但无法再次找到它。我正在考虑发布另一份报告,或请求重新打开现有报告。

【问题讨论】:

  • 看来这个问题更多地与__call() 魔法有关,而不是call_user_func_array()... 此外,似乎没有特别干净的解决方法。不过,欢迎提出任何想法。

标签: php pass-by-reference magic-methods dynamic-function


【解决方案1】:

这很有趣。

TestClass::testFunction(&$string);

这可行,但它也会引发调用时传递引用警告。

主要问题是__callStatic 的第二个参数是按值输入的。它正在创建数据的副本,除非该数据已经是一个引用。

我们可以这样复制调用错误:

call_user_func_array('testFunction', array( $string ));
// PHP Warning:  Parameter 1 to testFunction() expected to be a reference, value given

call_user_func_array('testFunction', array( &$string ));
echo $string;
// 'helloworld'

我尝试修改 __callStatic 方法以通过引用深度复制数组,但这也不起作用。

我很确定由 __callStatic 引起的直接复制在这里会是一个杀手,如果没有足够的箍跳使其在语法上有点贴纸,你将无法做到这一点.

【讨论】:

  • 谢谢 Charles; 是的,我认为呼叫魔法会是问题所在。我注意到它已在 PHP 错误修复请求中弹出,但尚未修补(至少从我发现的内容来看)通过引用显式传递参数当然会引发警告,正如您所指出的那样,不幸的是那不会无论如何都不是令人满意的解决方案。无论多么疯狂,你都想过什么样的箍跳?
  • 不幸的是,我唯一能想到的就是根本不使用__callStatic,这可能对您没有太大帮助。你能告诉我们更多关于你想要完成的事情吗?
  • 嗯,现在变成了学术试验,但目的最初来自我之前发布的这个问题; stackoverflow.com/questions/5600883/… 第一个提议的处理自动加载未分类函数库的选项利用了__callStatic。尽管走不同的路线可能有利于优化目的,但我仍然想解决这个问题,因为它(至少对我而言)似乎是一个非常干净的解决方案。
  • 嗯,有趣的想法,虽然我不确定 PHP 是否是实现这种魔力的正确语言。如果这是 Perl 或 Ruby,我相信它可以做到......
  • 是的,我还希望我可以将此功能用于其他目的。自动加载无类函数库是一个开始,但我正在围绕我正在开发的轻量级教育 MVC 框架提出一些其他想法,这将是一项资产。我会继续打猎。再次感谢 Charles,如果有任何其他想法,我很乐意听到。
【解决方案2】:

哇,经过一个多小时的震撼,我仍然不确定,但是......

mixed call_user_func_array ( callback $function , array $param_arr )

如果我们在类调用函数call_user_func_array()时使用简单的逻辑(在这种情况下),它使用静态字符串作为参数(由魔术方法__callStatic()传递),所以参数2在call_user_func_array()函数眼中是一个字符串,并且该参数也是函数testFunction() 的参数1。它只是字符串而不是指向变量的指针,这可能是如下消息的原因:

Warning: Parameter 1 to testFunction() expected to be a reference, value given in ... on line ...

换句话说,它不能将值赋回不存在但仍然存在的变量,testFunction() 期望参数是一个引用并抛出警告,因为它不是。

你可以在课堂外测试它:

function testFunction(&$arg){
    $arg .= 'world';
  }

$string = 'hello';
call_user_func_array('testFunction', array($string));
echo $string;

它会抛出同样的警告!所以问题不在于类,问题在于这个函数。

这个函数显然将函数参数作为静态数据传递,而不指向变量,它调用的函数不能处理引用的参数。

【讨论】:

  • 感谢 Wh1T3h4Ck5; 我们已将其归结为调用魔法的问题。 __call()/__callStatic() 不要通过参数数组将引用携带到它们定义的主体中,至少不是通过任何常规方式。此外,调用魔术(或文档中的任何魔术函数)的函数定义不允许通过引用传递参数。
【解决方案3】:

我自己通过逐步重现该问题来回答这个问题,这表明它可能无法实现所需的功能。

我们的测试功能相当简单,如下:

function testFunction(&$argument)
{
    $argument++;
}

如果我们直接调用该函数,它当然会按预期运行:

$value = 1;
testFunction($value);
var_dump($value); // int(2)

如果我们使用call_user_func_array调用函数:

call_user_func_array('testFunction', [$value]);
var_dump($value); // int(1)

该值仍为1,因为$value 没有通过引用传递。我们可以强制这样做:

call_user_func_array('testFunction', [&$value]);
var_dump($value); // int(2)

这又是直接调用函数。

现在,我们介​​绍这个类,使用__callStatic

class TestClass
{
    public static function __callStatic($name, $argumentArray)
    {
        return call_user_func_array($name, $argumentArray);
    }
}

如果我们通过__callStatic转发调用函数:

TestClass::testFunction($value);
var_dump($value); // int(1)

值保持在1并且我们也会收到警告:

Warning: Parameter 1 to testFunction() expected to be a reference, value given

验证传递给__callStatic$argumentArray 不包含引用。这是完全有道理的,因为 PHP 不知道这些参数旨在通过 __callStatic 中的引用被接受,也不知道我们正在转发这些参数。

如果我们更改 __callStatic 以通过引用接受数组,以尝试强制执行所需的行为:

public static function __callStatic($name, &$argumentArray)
{
    return call_user_func_array($name, $argumentArray);
}

哦哦!意大利面-O's!

Fatal error: Method TestClass::__callstatic() cannot take arguments by reference

不能这样做;另外,那是supported by the documentation anyway:

注意:
这些魔术方法的参数都不能通过引用传递。

尽管无法声明 __callStatic 通过引用接受,但如果我们使用 call_user_func_array这在实践中会相当尴尬和弄巧成拙)通过 __callStatic 转发引用:

call_user_func_array(['TestClass', 'testFunction'], [&$value]);
var_dump($value); // int(2)

它再次起作用了。

【讨论】:

    【解决方案4】:

    多年后我有一个偶然的时刻:

    class TestClass{
        public static function __callStatic($functionName, $arguments) {
            $arguments[0]->{'ioParameter'} = 'two';
        }
    }
    $objectWrapper = new StdClass();
    $objectWrapper->{'ioParameter'} = 'one';
    TestClass::testFunction($objectWrapper);
    var_dump($objectWrapper); 
    // outputs: object(stdClass)#3 (1) { ["ioParameter"]=> string(3) "two" }
    

    (在 php 5.5 上测试)

    如果您控制界面,此解决方法是干净且可行的。

    它通过将参数包装在一个对象中来工作,根据语言定义,该对象总是“通过引用”传递。

    即使使用 __callStatic(也可能是 __call),它也允许传递可写(引用)参数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-25
      • 1970-01-01
      • 2011-08-24
      • 1970-01-01
      相关资源
      最近更新 更多