Мой массив ссылок PHP «волшебным образом» становится массивом значений ... почему? - PullRequest
11 голосов
/ 01 декабря 2011

Я создаю функцию-оболочку для mysqli, чтобы мое приложение не слишком усложнялось кодом обработки базы данных.Частично это немного кода для параметризации вызовов SQL с использованием mysqli :: bind_param ().bind_param (), как вы знаете, требует ссылки.Так как это полуобобщающая оболочка, я в итоге выполняю этот вызов:

call_user_func_array(array($stmt, 'bind_param'), $this->bindArgs);

и получаю сообщение об ошибке:

Parameter 2 to mysqli_stmt::bind_param() expected to be a reference, value given

Вышеприведенное обсуждение предназначено для утверждения тех, кто скажет«В вашем примере ссылки вообще не нужны».

Мой «настоящий» код немного сложнее, чем кто-либо хочет прочитать, поэтому я свел код, приведший к этой ошибке, вследующий (надеюсь) иллюстративный пример:

class myclass {
  private $myarray = array();

  function setArray($vals) {
    foreach ($vals as $key => &$value) {
      $this->myarray[] =& $value;
    }
    $this->dumpArray();
  }
  function dumpArray() {
    var_dump($this->myarray);
  }
}

function myfunc($vals) {
  $obj = new myclass;
  $obj->setArray($vals);
  $obj->dumpArray();
}

myfunc(array('key1' => 'val1',
             'key2' => 'val2'));

Проблема заключается в том, что в myfunc () между вызовом setArray () и вызовом dumpArray () все элементы в $ obj-> myarray перестанет быть ссылками и станет просто ценностями.Это легко увидеть, посмотрев на вывод:

array(2) {
  [0]=>
  &string(4) "val1"
  [1]=>
  &string(4) "val2"
}
array(2) {
  [0]=>
  string(4) "val1"
  [1]=>
  string(4) "val2"
}

Обратите внимание, что массив находится в «правильном» состоянии в первой половине вывода здесь.Если бы это имело смысл, я мог бы сделать мой вызов bind_param () в этот момент, и это сработало бы.К сожалению, что-то ломается во второй половине вывода.Обратите внимание на отсутствие символа «&» в типах значений массива.

Что случилось с моими ссылками?Как я могу предотвратить это?Я ненавижу называть «ошибка PHP», когда я действительно не являюсь экспертом по языку, но может ли это быть одним из них?Это кажется очень странным для меня.В настоящее время я использую PHP 5.3.8 для своего тестирования.


Редактировать:

Как указали несколько человек, исправление заключается в изменении setArray () для принятияего аргумент по ссылке:

function setArray(&$vals) {

Я добавляю это примечание к документу, ПОЧЕМУ это работает.

PHP в целом и mysqli в частности, кажется, имеют немного странную концепциючто такое «ссылка».Посмотрите на этот пример:

$a = "foo";
$b = array(&$a);
$c = array(&$a);
var_dump($b);
var_dump($c);

Прежде всего, я уверен, что вам интересно, почему я использую массивы вместо скалярных переменных - это потому, что var_dump () не показывает никаких признаков того,скаляр - это ссылка, но это относится к элементам массива.

В любом случае, на этом этапе $ b [0] и $ c [0] являются ссылками на $ a.Все идет нормально.Теперь мы добавим наш первый ключ к работам:

unset($a);
var_dump($b);
var_dump($c);

$ b [0] и $ c [0] - все еще ссылки на одно и то же.Если мы изменим один, оба изменится.Но на что они ссылаются?Некоторое безымянное место в памяти.Конечно, сборка мусора гарантирует, что наши данные в безопасности и останутся таковыми, пока мы не перестанем ссылаться на них.

Для нашего следующего трюка мы сделаем следующее:

unset($b);
var_dump($c);

Now $c [0] - единственная оставшаяся ссылка на наши данные.И, воу!Волшебно, это больше не «ссылка».Не по мере var_dump () и не по mysqli :: bind_param ().

В руководстве по PHP написано, что на каждом есть отдельный флаг 'is_ref'часть данных.Тем не менее, этот тест показывает, что 'is_ref' фактически эквивалентен '(refcount> 1)'

Для забавы, вы можете изменить этот пример игрушки следующим образом:

$a = array("foo");
$b = array(&$a[0]);
$c = array(&$a[0]);

var_dump($a);
var_dump($b);
var_dump($c);

Примечаниечто все три массива имеют ссылочную метку на своих членах, что подтверждает идею, что 'is_ref' функционально эквивалентен '(refcount> 1)'.

Почему я не знаю mysqli:: bind_param () в первую очередь заботился бы об этом различии (или, возможно, это call_user_func_array () ... в любом случае), но, похоже, нам действительно нужно убедиться, что счетчик ссылок по крайней мере 2 для каждого члена $ this-> bindArgs в нашем вызове call_user_func_array () (см. Самое начало сообщения / вопроса).И самый простой способ сделать это (в этом случае) состоит в том, чтобы сделать setArray () передачей по ссылке.


Редактировать:

Для дополнительного удовольствия и игр я изменил свою исходную программу (здесь не показана), чтобы оставить ее эквивалентную setArray () для передачи по значению, и создать дополнительный массив, bindArgsCopy, содержащий точно то же самое, что и bindArgs , Это означает, что да, оба массива содержали ссылки на «временные» данные, которые были освобождены ко времени второго вызова. Как и следовало из анализа выше, это сработало. Это демонстрирует, что приведенный выше анализ не является артефактом внутренней работы var_dump () (по крайней мере, для меня это облегчение), а также демонстрирует, что важен счетчик ссылок, а не «временность» оригинала. хранение данных.

Итак. Я делаю следующее утверждение: В PHP для вызова call_user_func_array () (и, возможно, более) сказать, что элемент данных является «ссылкой», - это то же самое, что сказать, что счетчик ссылок элемента больше или равен 2. (игнорирование оптимизации внутренней памяти PHP для равнозначных скаляров)


Примечание администратора: я бы с удовольствием дал Марио кредит сайта за ответ, так как он первым предложил правильный ответ, но так как он написал это в комментарии, а не в реальном ответе, я не смог сделать это: - (

Ответы [ 3 ]

5 голосов
/ 01 декабря 2011

Передайте массив как ссылку:

  function setArray(&$vals) {
    foreach ($vals as $key => &$value) {
      $this->myarray[] =& $value;
    }
    $this->dumpArray();
  }

Мое предположение (которое может быть неверным в некоторых деталях, но, как мы надеемся, по большей части корректно) относительно того, почему это заставляет ваш код работать должным образом, заключается в том, что когда вы передаете значение, все здорово для вызова dumpArray() внутри setArray(), поскольку ссылка на массив $vals внутри setArray() все еще существует. Но когда управление возвращается к myfunc(), тогда эта временная переменная исчезает, как и все ссылки на нее. Поэтому PHP покорно меняет массив на строковые значения вместо ссылок, прежде чем освободить память для него. Но если вы передадите его как ссылку из myfunc(), тогда setArray() будет использовать ссылки на массив, который живет, когда управление возвращается к myfunc().

2 голосов
/ 01 декабря 2011

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

function setArray(&$vals) {
// ...
}

CodePad .

0 голосов
/ 24 сентября 2014

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

Теперь, для всех, кто сталкивается с чем-то подобным, я думаю, у меня есть простейшее решение: просто установите каждый соответствующий элемент массива на ссылку на себя, прежде чем передавать массив в call_user_func_array () .

Я не уверен, что именно происходит внутри, потому что кажется, что это не должно работать, но элемент снова становится ссылкой (что вы можете увидеть с помощью var_dump ()), а call_user_func_array () затем передает аргумент по ссылке. как и ожидалось. Это исправление работает, даже если элемент массива все еще является ссылкой, поэтому вам не нужно сначала проверять.

В коде Рика это было бы примерно так (все после первого аргумента для bind_param по ссылке, поэтому я пропускаю первый и исправляю все после него):

for ($i = 1, $count = count($this->bindArgs); $i < $count; $i++) {
    $this->bindArgs[$i] = &$this->bindArgs[$i];
}

call_user_func_array(array($stmt, 'bind_param'), $this->bindArgs);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...