Я читал об этом и в другом месте, однако я не могу найти ничего убедительного.
Есть ли способ эффективно переносить ссылки через этот стек вызовов, что приводит к желаемой функциональности, как описано в примере ниже? Хотя этот пример не пытается его решить, это, безусловно, иллюстрирует проблему:
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;
Он бросает одно и то же предупреждение! Поэтому проблема не в классе, проблема в этой функции.
Эта функция, очевидно, передает параметры функции как статические данные, не указывая на переменные и функции, которые она вызывает, просто не может обрабатывать ссылочные аргументы.