Ссылка PHP вызывает повреждение данных

Я пишу PHP-код, чтобы сделать некоторые преобразования каждого значения в массиве, а затем добавить некоторые значения в массив из внешнего источника (курсор MySQL или, скажем, другой массив). Если я использую foreach и ссылку для преобразования значений массива

 <?php $data = array('a','b','c'); foreach( $data as &$x ) $x = strtoupper($x); $extradata = array('d','e','f'); // actually it was MySQL cursor while( list($i,$x) = each($extradata) ) { $data[] = strtoupper($x); } print_r($data); ?> 

( Здесь он находится в PHPfiddle )

чем данные повреждены. Так что я получаю

 Array ( [0]=>A [1]=>B [2]=> [3]=>D [4]=>E [5] =>F ) 

вместо

 Array ( [0]=>A [1]=>B [2]=>C [3]=>D [4]=>E [5] =>F ) 

Когда я не использую ссылку и пишу

 foreach( $data as &$x ) $x = strtoupper($x); 

Конечно, преобразование не происходит, но данные также не повреждены, поэтому я получаю

 Array ( [0]=>a [1]=>b [2]=>c [3]=>D [4]=>E [5] =>F ) 

Если я пишу такой код

 <?php $result = array(); $data1 = array('a','b','c'); foreach( $data1 as $x ) $result[] = strtoupper($x); $data2 = array('d','e','f'); // actually it was MySQL cursor while( list($i,$x) = each($data2) ) { $result[] = strtoupper($x); } print_r($result); ?> 

все работает так, как ожидалось.

 Array ( [0]=>A [1]=>B [2]=>C [3]=>D [4]=>E [5] =>F ) 

Конечно, копирование данных решает проблему. Но я хотел бы понять, какова странная проблема с этой ссылкой и как можно избежать таких проблем. Может быть, вообще плохо использовать ссылки PHP в коде (как многие говорят о C-указателях)?

Механизм ссылок на язык PHP имеет специфическую особенность, которая не является общей для других языков программирования. Общепризнано, что объект отражает все изменения, внесенные в его свойства посредством любой ссылки на него. Но присвоение самой ссылки либо запрещено, либо делает опорную точку для другого объекта. Вместо этого назначение ссылки в PHP заменяет весь базовый объект (объект, на который ссылается ссылка) на тот, который назначен. Так

 $a = 1; $b = 2; $r = &$a; $r = $b; echo $a; // will output '2' 

Это верно для присваивания, но не для unset вызова, который не будет уничтожать базовый объект, но нарушает связь между ссылочным и заостренным объектами.

 $a = 1; $b = 2; $r = &$a; unset($r); //! $r = $b; echo $a; // will output '1' с $a = 1; $b = 2; $r = &$a; unset($r); //! $r = $b; echo $a; // will output '1' 

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

Для проблем aviod с PHP-ссылками вы должны:

  • Устанавливайте каждую ссылку как можно раньше (в тот момент, когда она становится ненужной).

Таким образом, этот код будет работать

 <?php $data = array('a','b','c'); foreach( $data as &$x ) $x = strtoupper($x); unset($x); $extradata = array('d','e','f'); // actually it was MySQL cursor while( list($i,$x) = each($extradata) ) { $data[] = strtoupper($x); } print_r($data); ?> с <?php $data = array('a','b','c'); foreach( $data as &$x ) $x = strtoupper($x); unset($x); $extradata = array('d','e','f'); // actually it was MySQL cursor while( list($i,$x) = each($extradata) ) { $data[] = strtoupper($x); } print_r($data); ?> 
  • Как правило, это плохой стиль для повторного использования локальных имен переменных в нескольких структурах управления.

Таким образом, следующий код будет работать тоже

 <?php $data = array('a','b','c'); foreach( $data as &$x ) $x = strtoupper($x); $extradata = array('d','e','f'); // actually it was MySQL cursor while( list($i,$y) = each($extradata) ) { $data[] = strtoupper($y); } 

Вы снова используете $x в цикле над $extradata , что приводит к тому, что ссылки становятся неустойчивыми.

Это работает:

 $data = array('a','b','c'); foreach( $data as &$x ) $x = strtoupper($x); $extradata = array('d','e','f'); // actually it was MySQL cursor while( list($i,$anything_but_x) = each($extradata) ) { $data[] = strtoupper($anything_but_x); } print_r($data); 

  • Не используйте повторно используемые переменные
  • Избегайте ссылок

Это работает для меня …. Может быть, я мог бы сделать это по-другому, если бы знал, что вы пытаетесь сделать … но это должно сработать.

 $data = array('a','b','c'); foreach( $data as &$x ) $x = strtoupper($x); $extradata = array('d','e','f'); // actually it was MySQL cursor foreach ($extradata as &$x) { $data[] = strtoupper ($x); }