Code Fix

中級

foreachで&を使った後、unsetを忘れると値が壊れる

foreach ($arr as &$v) で配列を参照渡しで回すと、ループ終了後も$vは配列の最後の要素への参照を保持し続けます。unset($v)を忘れると、次に$vという名前でループを回したときに最後の要素が上書きされます。

なぜエラーが出ないのか

出力: 244
(エラーなし)
PHPは何も報告しません。文法として正しいためです
出力: 244
実際の挙動 — 期待した結果と食い違っている箇所
見つけ方
エラーが出ないので、出力を目で確かめるか、コードを目で追うしかありません。この種の誤りが最も発見が遅れます

このエラーが出る典型パターン

パターン1

 1  <?php
 2  $nums = [1, 2, 3];
 3  foreach ($nums as &$v) {
 4      $v *= 2;
 5  }
 6               
          ^
 7  foreach ($nums as $v) {
 8      echo $v;
 9  }
10  echo "\n" . implode(",", $nums);
出力: 244 2,4,4期待: 2,4,6 (エラーにはなりません)

1回目のforeachが終わっても$vは配列の最後の要素への参照を保持したままです。unsetし忘れると、2回目のforeachが同じ名前の$vで回った瞬間、最後の要素が上書きされます。

直し方: (空) を unset($v); にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  <?php
 2  $scores = [5, 10, 15];
 3  foreach ($scores as &$s) {
 4      $s += 1;
 5  }
 6                 
           ^
 7  foreach ($scores as $s) {
 8      echo $s;
 9  }
10  echo "\n" . implode(",", $scores);
出力: 61111 6,11,11期待: 6,11,16 (エラーにはなりません)

$sも参照のまま残るため、2回目のforeachで自分自身に代入を続け、最後の要素が壊れます。

直し方: (空) を unset($s); にします。

この問題を解いてみる →

パターン3

 1  <?php
 2  $prices = [100, 200, 300];
 3  foreach ($prices as &$p) {
 4      $p -= 10;
 5  }
 6                 
           ^
 7  foreach ($prices as $p) {
 8      echo $p . " ";
 9  }
10  echo "\n" . implode(",", $prices);
出力: 90 190 190 90,190,190期待: 90,190,290 (エラーにはなりません)

unset($p)を忘れると、参照が残ったままの$pに2回目のforeachの値が書き込まれ続け、最後の要素が壊れます。

直し方: (空) を unset($p); にします。

この問題を解いてみる →

パターン4

 1  <?php
 2  $nums = [2, 4, 6];
 3  foreach ($nums as &$n) {
 4      $n += 1;
 5  }
 6               
          ^
 7  foreach ($nums as $n) {
 8      echo $n;
 9  }
10  echo "\n" . implode(",", $nums);
出力: 355 3,5,5期待: 3,5,7 (エラーにはなりません)

1回目のforeachが終わっても$nは配列の最後の要素への参照を保持したままです。unsetし忘れると、2回目のforeachで最後の要素が上書きされます。

直し方: (空) を unset($n); にします。

この問題を解いてみる →

パターン5

 1  <?php
 2  $data = [2, 3, 4];
 3  foreach ($data as &$d) {
 4      $d *= 3;
 5  }
 6               
          ^
 7  foreach ($data as $d) {
 8      echo $d . " ";
 9  }
10  echo "\n" . implode(",", $data);
出力: 6 9 9 6,9,9期待: 6,9,12 (エラーにはなりません)

$dも参照のまま残るため、2回目のforeachで自分自身に代入を続け、最後の要素が壊れます。

直し方: (空) を unset($d); にします。

この問題を解いてみる →

よくある誤解

「foreachのループ変数はループが終われば無関係になるはず」という思い込みは、&を使った参照foreachには当てはまりません。unsetし忘れると、後続のコードが気づかないうちに配列を壊します。

実務での勘所

unsetし忘れた$vは、単に「最後の値を覚えている」だけではなく、配列の最後の要素の実体そのものへの参照であり続けます。つまりその後のコードでたまたま$vという名前の変数に何か別の値を代入すると、それは新しい変数への代入ではなく、元の配列の最後の要素への書き込みとして実行されてしまいます。これに気づかず全く無関係な処理で$vを再利用すると、最初のforeachで使った配列が意図せず書き換わるという、離れた場所同士が繋がってしまう厄介なバグになります。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)