__callStatic (), call_user_func_array (), ссылки и PHP 5.3.1

Я читал об этом и в другом месте, однако я не могу найти ничего убедительного.

Есть ли способ эффективно переносить ссылки через этот стек вызовов, что приводит к желаемой функциональности, как описано в примере ниже? Хотя этот пример не пытается его решить, это, безусловно, иллюстрирует проблему:

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() ссылочного оператора в 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() . Я просмотрел базу данных ошибок для существующих или связанных с ней проблем и нашел ее, но не смог найти ее снова. Я рассматриваю возможность выпуска другого отчета или запрос на возобновление существующего отчета.

Это весело.

 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 , станет убийцей здесь, и вы не сможете этого сделать без достаточного количества обручей, чтобы сделать его немного стикерным, синтаксическим.

Я сам отвечаю на это с пошаговым воспроизведением проблемы, которая показывает, что, скорее всего, невозможно достичь желаемой функциональности.

Наша тестовая функция довольно проста и выглядит следующим образом:

 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 переданный в __callStatic , не содержит ссылок. Это имеет смысл, поскольку PHP не знает, что аргументы должны быть приняты по ссылке в __callStatic или что мы пересылаем аргументы.

Если мы изменим __callStatic чтобы принять массив по ссылке, в попытке заставить желаемое поведение:

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

О, о! Спагетти-Выходы!

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

Не могу этого сделать; кроме того, это поддерживается документацией :

Заметка:
Ни один из аргументов этих магических методов не может быть передан по ссылке.

Несмотря на то, что мы не можем объявить __callStatic для принятия по ссылке, если мы используем call_user_func_array ( что на практике было бы неудобно и самонадеянно ) для перенаправления ссылок через __callStatic :

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

Это снова работает.

Ничего себе, после более чем часа раздувания моего ума, я все еще не уверен, но …

 mixed call_user_func_array ( callback $function , array $param_arr ) 

Если мы используем простую логику (в этой ситуации), когда call_user_func_array() класса вызывает функцию call_user_func_array() она использует статические строки в качестве параметров (переданных магическим методом __callStatic() ), поэтому параметр 2 является строкой в ​​глазах функции call_user_func_array() и этот параметр также параметр 1 для функции testFunction() . Это просто строка, не указатель на переменную, и, вероятно, это причина для сообщения типа:

 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; 

Он бросает одно и то же предупреждение! Поэтому проблема не в классе, проблема в этой функции.

Эта функция, очевидно, передает параметры функции как статические данные, не указывая на переменные и функции, которые она вызывает, просто не может обрабатывать ссылочные аргументы.