__callStatic(),call_user_func_array(),引用和PHP 5.3.1

时间:2011-04-09 02:59:14

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

我一直在读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(),当然会通过值作为数组元素传递给testFunction() call_user_func_array()。这会产生警告,因为testFunction()需要引用。

黑客攻击,一些额外的细节浮出水面。 __callStatic()定义,如果写入以引用方式返回(public static function &__callStatic())则没有明显的效果。此外,重新构建$arguments__callStatic()数组的元素作为参考,我们可以看到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()魔法。我已经仔细阅读了错误数据库中的现有问题或相关问题,并找到了一个,但无法再次找到它。我正在考虑发布另一份报告,或要求重新打开现有报告。

4 个答案:

答案 0 :(得分:3)

这是一个有趣的。

TestClass::testFunction(&$string);

这样可行,但它也会抛出一个call-time-pass-by-reference警告。

主要问题是__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引起的直接拷贝在这里会成为一个杀手,如果没有足够的箍跳使你有点贴纸,你将无法做到这一点,语法-wise。

答案 1 :(得分:2)

我自己一步一步地回答这个问题,这表明很可能无法实现所需的功能。

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

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

其中验证传递到$argumentArray的{​​{1}}不包含引用。这是完全合理的,因为PHP并不知道这些参数旨在被__callStatic中的引用所接受,也不是我们正在转发参数。

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

__callStatic
哦,哦!意大利面条-O'!小号

  

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

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

  

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

尽管我们无法通过引用宣布Fatal error: Method TestClass::__callstatic() cannot take arguments by reference接受,但如果我们使用__callStatic,这在实践中会相当尴尬和自我挫败)来转发引用通过call_user_func_array

__callStatic

它再次起作用。

答案 2 :(得分:0)

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

mixed call_user_func_array ( callback $function , array $param_arr )

如果我们在类调用函数call_user_func_array()时使用简单逻辑(在这种情况下),它使用静态字符串作为参数(通过魔术方法__callStatic()传递),因此参数2是{ {1}}函数和该参数也是函数call_user_func_array()的参数1。它只是字符串而不是变量的指针,这可能是消息的原因:

testFunction()

换句话说,它只是不能将值赋值给不存在的变量,但是,testFunction()希望参数成为引用并抛出警告,因为它不是。

您可以在课外测试,例如:

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

它会抛出相同的警告!因此问题不在于课程,问题在于此功能。

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

答案 3 :(得分:0)

多年来,我有一个偶然的机会:

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上测试)

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

它的工作方式是将参数包装在一个对象中,该对象按语言定义,始终以“by reference”方式传递。

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