PHP: Неполадка несоответствия между вызовом метода отражения с использованием построенного массива или func_get_args() в версии 5.4

Это очень краткий случай в PHP 5.4 относительно передачи объектов по ссылке, где вы получите эту ошибку:

PHP Warning:  Parameter 1 to A::foo() expected to be a reference, value given

Но только как составной эффект:

  • Использование отражения для установки унаследованного метода как "доступного",
  • И этот метод принимает явно ссылочный аргумент (& аргумент sig)
  • И затем вызывать его с помощью func_get_args(), а не конструировать массив аргументов вручную.

Не знаю, почему все это вызывает такое поведение или если они должны.

Важно отметить, что этого эффекта нет в PHP 5.5.

Это код, который вызовет указанную выше ошибку, но если вы прокомментируете строку с помощью COMMENT THIS LINE, код будет работать нормально (например, объект правильно передается функции foo):

class A {
    private function foo(&$arg1) {
        var_dump('arg1: ', $arg1);
    }   
}

class B extends A {
    public function bar() {
        $x = new stdClass();
        $x->baz = 'just a value';
        $this->callPrivate($x);
    }

    private function callPrivate($x)
    {
        $method = new \ReflectionMethod(
            'A',
            'foo'
        );

        //* for some reason, the private function needs to have been changed to be 'accessible' for this to work in 5.4
        $method->setAccessible(true);

        //working 5.4 (* see above) but not in 5.5
        $arguments = func_get_args();

        //not working in either
        $arguments = array($x); // <---- COMMENT THIS LINE TO SEE IT WORK IN PHP 5.4

        return $method->invokeArgs($this, $arguments);
    }
}

$y = new B();
$y->bar();

Я не понимаю, почему бы не было разницы между двумя массивами $arguments, поскольку var_dumping их показывает один и тот же вывод. Поэтому я полагаю, что это связано с чем-то более низким уровнем, например, объектные "указатели" различаются (из моей глубины здесь)?

Другой вопрос: если это ошибка в PHP 5.4, 5.5 или оба?

Ответ 1

До PHP 5.5.6 func_get_args() взяли аргументы из стека VM, скопировали их и вернули их в массив. В PHP 5.5.6 была введена оптимизация, которая позволяет избежать этих дорогих копий в общем случае. Вместо того, чтобы копировать zvals, только refcount увеличивается (по-прежнему, несмотря на ошибки).

Обычно такое изменение будет иметь нулевой эффект на код пользователя. Но в двигателе есть несколько мест, где наблюдаемое поведение отличается от пересчета zval. Одним из таких мест является передача по ссылке:

В случае вызова динамической функции zval может передаваться по ссылке либо, если это ссылка , либо если она имеет refcount == 1.

До PHP 5.5.6 в zval в массиве, возвращаемом func_get_args(), всегда был refcount == 1, поэтому они прошли через этот второй случай. Начиная с PHP 5.5.6 это уже не так, поскольку значения zval всегда будут иметь refcount > 1 и вызывать ошибку, если вы попытаетесь передать их по ссылке.

Примечание. Код действительно не работал до PHP 5.5.6 (по-ref игнорировался). Это было просто неудачное совпадение, что вы не получили сообщение об ошибке;)

Обновление. Мы решили отменить изменение на ветке 5.5 из-за перерыва BC. Вы вернете прежнее поведение в PHP 5.5.8, а новое поведение будет только в PHP 5.6.