【问题标题】:PHP Reflection Method > Returning Reference ImpossiblePHP 反射方法 > 不可能返回引用
【发布时间】:2016-10-15 11:04:41
【问题描述】:

我需要测试我的抽象类是否可以正确地从受保护的方法返回对内部数组的引用。测试受保护方法的唯一方法是通过反射。但是,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);

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

【问题讨论】:

  • 我认为您只需要按位调用 $reflection 即可,因为您已经在函数中返回了引用。能贴一下invokeArgs函数的代码吗?
  • 我不能,因为该函数在 C 中。但是在问题的开头有一个指向该函数文档的链接。这些文档还声明您确实需要包含&,顺便说一下,它不是按位运算符,而是表示“引用。在这种情况下,它表明我正在分配引用,而不是参考值。

标签: php unit-testing reflection reference


【解决方案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 函数和方法可以被赋予比预期更多的参数(即超过声明参数的数量)这一事实,我们可以将参数数组填充到预设的最大值(例如 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"
  }
}

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

【讨论】:

  • 我之前也想过。但是,我推断它不起作用,因为函数必须定义为通过引用返回,而#getClosure() 的闭包可能不会以这种方式定义。但是如果我的假设是错误的,那么上面的代码真的有效吗?如果你在下面添加$items = array('apple'); var_dump($a); 会发生什么?
  • 我认为赋值运算符会将变量更改为指向新的文字数组,但经过测试似乎A 的实例实际上将其内部$items 数组修改为等于array (size=1) 0 =&gt; string 'apples'。不完全是我期望诚实的行为。
  • 然而正是我所期望和需要的。谢谢,我会尽快测试。
  • 另外,从$reflection 实例返回的闭包可能不是包裹方法的实际Closure,而是对方法本身的实际引用。在任何一种情况下,反射类都有returnsReference() 方法,这使它知道返回值的性质。所以即使闭包是一个包装器,它也可能在构建时考虑到引用返回。
  • 这很可能是解释。看来我不应该这么快就放弃了。再次感谢。
猜你喜欢
  • 2019-03-29
  • 1970-01-01
  • 2014-03-02
  • 2023-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多