【PHP】foreachで配列を操作する方法|キーと値・参照渡しの落とし穴

PHPで配列の各要素を順番に処理するなら foreach が最適です。基本はシンプルですが、参照渡し(&)で要素を変更したあとにunset() を忘れると、最後の要素が壊れるという有名な落とし穴があります。この記事では基本から、その注意点まで実例で解説します。

この記事の結論:foreach ($arr as $value) で値を、foreach ($arr as $key => $value) でキーと値を取得します。要素を直接書き換えるなら foreach ($arr as &$value)(参照渡し)ですが、ループ後に必ず unset($value) してください。
スポンサーリンク

基本的な使い方

値を順番に取り出す
<?php
$fruits = ["apple", "banana", "cherry"];

foreach ($fruits as $fruit) {
    echo $fruit . "\n";
}
// apple / banana / cherry

キーと値を同時に取得する

連想配列では $key => $value でキーと値の両方を取得できます。

キーと値
<?php
$person = ["name" => "John", "age" => 30, "city" => "NY"];

foreach ($person as $key => $value) {
    echo "$key: $value\n";
}
// name: John / age: 30 / city: NY

通常のforeachは「コピー」を操作する

通常の foreach ($arr as $value) では、$value各要素のコピーです。これを変更しても元の配列は変わりません

コピーなので元配列は変わらない
<?php
$numbers = [1, 2, 3];

foreach ($numbers as $n) {
    $n *= 2; // コピーを変更しているだけ
}
print_r($numbers); // [1, 2, 3](変わらない)

参照渡し(&)で要素を直接変更する

元の配列を直接書き換えたいときは、&$value と参照渡しにします。

参照渡しで2倍にする
<?php
$numbers = [1, 2, 3];

foreach ($numbers as &$n) {
    $n *= 2;
}
unset($n); // ★ ループ後に必ず参照を解除

print_r($numbers); // [2, 4, 6]

要注意:unsetを忘れると最後の要素が壊れる

参照渡しの &$value は、ループ後も最後の要素を指したままです。ここで unset() を忘れて別の foreach を回すと、同じ変数名が最後の要素を上書きしてしまい、データが壊れます。

NG: unset忘れによる有名なバグ
<?php
$a = [1, 2, 3];

foreach ($a as &$v) { $v *= 2; } // [2, 4, 6]、$v は最後の要素を参照したまま
// unset($v); を忘れた!

foreach ($a as $v) { /* 何もしない */ }
// 2回目のループで $v に各値が代入され、参照先(最後の要素)が上書きされる

print_r($a); // [2, 4, 4] ← 最後が 6 ではなく 4 に壊れる!
参照渡しの foreach の直後は必ず unset($v) しましょう。これを忘れると、後続の処理で最後の要素が書き換わる、再現しにくいバグになります。参照が不要なら、そもそも array_map() で新しい配列を作る方が安全な場合もあります。

途中で抜ける・スキップする(break / continue)

条件でループを抜けるなら breakその回だけ飛ばすなら continue を使います。

break / continue
<?php
$nums = [1, 2, 3, 4, 5];

foreach ($nums as $n) {
    if ($n === 4) break;     // 4で終了
    if ($n % 2 === 0) continue; // 偶数は飛ばす
    echo $n . "\n"; // 1 / 3
}
break の詳しい使い方(多重ループの break 2 など)はforeachをbreakで終了する方法continuecontinueでスキップする方法で解説しています。

応用:要素を分割代入する

各要素が配列のとき、foreach 内で分割代入すると見やすくなります。

list()短縮構文で分割代入
<?php
$points = [[1, 2], [3, 4], [5, 6]];

foreach ($points as [$x, $y]) {
    echo "($x, $y)\n";
}
// (1, 2) / (3, 4) / (5, 6)

よくある質問(FAQ)

Qforeachで元の配列を変更できますか?
A通常の foreach ($arr as $value)コピーなので元配列は変わりません。直接変更するには foreach ($arr as &$value) と参照渡しにし、ループ後に unset($value) を忘れずに行ってください。
Qunsetを忘れるとどうなりますか?
A参照変数が最後の要素を指したまま残り、後続の foreach最後の要素が上書きされて壊れます(例: [2,4,6][2,4,4] に)。参照渡しの直後は必ず unset() してください。
Qキーも一緒に取得するには?
Aforeach ($arr as $key => $value) と書きます。連番配列ではキーが 0, 1, 2…、連想配列では設定したキーが得られます。
Q途中で抜ける・特定の要素を飛ばすには?
Aループを抜けるなら break、その回だけ飛ばすなら continue を使います。

まとめ

PHPのforeachのポイントを整理します。

  • 値は foreach ($arr as $v)、キーと値は $k => $v
  • 通常はコピー。元配列を変えるなら 参照渡し &$v
  • 参照渡しの直後は必ず unset($v)(忘れると最後の要素が壊れる)
  • 抜けるなら break、飛ばすなら continue
  • 要素が配列なら foreach ($arr as [$a, $b]) で分割代入

関連として、foreachをbreakで終了する方法continueでスキップする方法ループ処理を行う方法もあわせて読むと、PHPのループ処理に強くなれます。