PHP反射方法>返回参考不可能

时间:2016-10-15 11:04:42

标签: php unit-testing reflection reference

我需要测试我的抽象类是否可以正确地从受保护的方法返回对内部数组的引用。测试受保护方法的唯一方法是通过反射。但是,似乎没有ReflectionMethod::invokeArgs()返回引用的可能性。或者我在这里做错了什么?

class A
{
   protected $items;

    protected function &_getItemStorage()
    {
        return $this->items;
    }
}

function &reflectionInvokeByRef($object, $method, $args = array())
{
    $reflection = new \ReflectionMethod($object, $method);
    $reflection->setAccessible(true);
    $result = &$reflection->invokeArgs($object, $args);

    return $result;
}

$a = new A();
$items = &reflectionInvokeByRef($a, '_getItemStorage');

这会导致以下错误: Only variables should be assigned by reference
在线:$result = &$reflection->invokeArgs($object, $args);

当尝试通过引用分配函数的返回值时,通常会抛出此错误,而该函数未声明为通过引用返回。 使用任何已知方法使我的功能无法测试。

1 个答案:

答案 0 :(得分:1)

TL;博士

使用ReflectionMethod::getClosure()检索表示方法的闭包。如果该闭包直接调用,它将正确返回引用,如果这是原始方法的行为。

这仅适用于PHP 5.4或更高版本。

PHP手册中没有提到这一点,但ReflectionMethod::invokeArgs()无法返回引用。

通过反射获取返回引用的唯一方法是使用ReflectionMethod::getClosure(),它返回表示原始方法的Closure实例。如果直接调用此闭包将正确返回引用,如果这是原始方法的行为。

我说“如果直接调用”,因为call_user_func_array()函数也无法返回引用,所以你不能使用该函数来调用带有参数数组的闭包。

请注意,ReflectionMethod::getClosure()方法仅适用于PHP 5.4或更高版本。

PHP 5.6 +

只需使用参数解包即可将一组参数传递给闭包:

<?php

function &reflectionInvokeByRef($object, $method, $args = array())
{
    $reflection = new \ReflectionMethod($object, $method);
    $reflection->setAccessible(true);
    $closure = $reflection->getClosure($object);
    $result = &$closure(...$args);

    return $result;
}

$a = new A();
$items = &reflectionInvokeByRef($a, '_getItemStorage');

$items[] = 'yolo';
var_dump($a);

这将输出

object(A)#1 (1) {
  ["items":protected]=> &array(1) {
    [0]=> string(4) "yolo"
  }
}

PHP 5.4 - 5.5

参数解包不可用,因此传递参数数组必须采用不同的方式。

通过利用PHP函数和方法可以给出比预期更多的参数(即超过声明参数的数量)的事实,我们可以将arguments数组填充到预设的最大值(例如10)并传递参数以传统的方式。

<?php

function &reflectionInvokeByRef($object, $method, $args = array())
{
    $reflection = new \ReflectionMethod($object, $method);
    $reflection->setAccessible(true);
    $args = array_pad($args, 10, null);
    $closure = $reflection->getClosure($object);
    $result = &$closure($args[0], $args[1], $args[2], $args[3], $args[4], $args[5],
        $args[6], $args[7], $args[8], $args[9]);

    return $result;
}

$a = new A();
$items = &reflectionInvokeByRef($a, '_getItemStorage', array('some', 'args'));

$items[] = 'yolo';
var_dump($a);

这将输出

object(A)#1 (1) {
  ["items":protected]=> &array(1) {
    [0]=> string(4) "yolo"
  }
}

我承认,这并不漂亮。这是一个肮脏的解决方案,如果有任何其他方式(我能想到),我不会在这里发布。你必须决定填充arguments数组的最大参数数量 - 但我认为10是一个安全的最大值。

相关问题