foreach ($array as $value)は、要素ごとに1回ブロックを実行し、$valueには現在の要素が入ります。キーも取得するにはforeach ($array as $key => $value)と書きます。
foreachはキーがどうであれどんな配列にも使え、カウンターを管理したり長さを確認したりする必要がありません。PHPでいちばんよく書くループです。
キーと値でループする
$key => $valueの名前は自由に選べます:$name => $age、$id => $user、$k => $v。リストではキーは位置0, 1, 2です。連想配列では、自分で定義した文字列のキーです。
最後のループは、よく驚かれる細かい点を示しています。"1"のように整数に見える文字列のキーは、整数の1として保存されます。"01"は正規の整数表記ではないので、文字列のままです。
foreachでインデックスを取得する
リストではキーがインデックスなので、foreach ($items as $i => $item)だけで十分です。連想配列には数値の位置がないので、カウンターを持ちます。最初や最後の要素だけ扱いを変えるには、キーをarray_key_first()やarray_key_last()と比較します。
最後のループは名前の間にカンマを出力し、最後の名前のあとには出力しません。まさにこの用途ならimplode(', ', array_keys($scores))のほうが短く書けます。array_key_last()による確認は、区切り文字が違うだけでなく、最後の回で別の処理が必要な場合のためのものです。
foreachで配列の値を変更する(参照渡し)
$valueはコピーです。これに代入しても配列は何も変わりません。配列を変えるには、&$valueで参照としてループするか、キーを使って書き戻します。
どちらも動きます。キーを使う方法はループのあとに隠れた状態が残らないので、こちらを好むチームも多くあります。配列を変えるのではなく新しい配列を作るなら、array_map()という選択肢もあります。
関連する疑問として、ループ中に要素を追加するとどうなるでしょうか。普通のforeachはループ開始時点の配列を順に処理するので、中で$prices[] = ...としても回数は増えません。参照のループは現在の配列を処理するので、新しい要素も処理します。
参照渡しのforeachとunset()の落とし穴
foreach ($arr as &$v)のあとも、変数$vは配列の最後の要素への参照のままです。あとで普通のループに同じ名前の$vを使うと、毎回その最後の要素に書き込まれます。実行して最後の項目を見てください。
2つ目のループは$letters[2]にA、次にB、最後に最後の要素自身の値を代入しますが、最後の回の時点でその位置にはすでにBが入っています。直し方は1行で、参照によるforeachの直後に必ずunset($v);を書きます。
foreachでのbreakとcontinue
breakはループを抜け、continueは次の要素に飛びます。よくあるのは検索のパターンで、必要なものが見つかるまでループし、見つかったらbreakして配列の残りを確認しないようにします。
ネストしたループでは、break 2で内側とその外側のループを抜け、continue 2で外側のループの次の回に進みます。
ネストした配列とlist()を使ったforeach
データの行は、配列の中の配列です。行をループしてから各行をループするか、配列の分割代入を使ってforeachの行で直接フィールドを取り出します。リストには[$a, $b]、連想配列の行には['key' => $var]を使います。list($a, $b)は[$a, $b]の古い書き方です。
[...] =構文については、要素の読み飛ばしやネストしたパターンも含め、分割代入のページで詳しく説明しています。
foreachでHTMLのリストや表を作る
テンプレートでは、foreach (...):とendforeach;のコロン形式でタグの間でもループを読みやすく保てます。このブロックは表を作り、列の見出しはリンクになっています。Pageタブで見出しをクリックすると、$_GET['sort']がセットされた状態で同じスクリプトがもう一度実行され、行が新しい順番で返ってきます。
このブロックには真似したい習慣が2つあります。URLから来たsortの値は、使う前に許可された列の一覧と照合しています。そして、ユーザーのテキストを含みうる値をHTMLに出力するときは、すべてhtmlspecialchars()を通しています。
null、空の配列、オブジェクトに対するforeach
空の配列は問題ありません。本体が実行されないだけです。null、文字列、数値はだめで、PHPは警告を表示してループを飛ばします。$tags = null; foreach ($tags as $tag)では次のように表示されます。
Warning: foreach() argument must be of type array|object, null given in /var/www/html/index.php on line 4
これがいちばんよく起きるのは、デコードしたJSONの任意の項目のように、ときどき欠けるデータです。??で空の配列にフォールバックしましょう。
foreach ($data['tags'] ?? [] as $tag) {
echo $tag;
}
このメッセージにあるobjectは本当です。オブジェクトに対するforeachは、ループが実行される場所から見えるプロパティ(クラスの外ならpublicなもの)を、プロパティ名をキーとして順に処理します。IteratorやIteratorAggregateを実装したクラスとジェネレーターは、foreachに何を見せるかを自分で決めます。
foreach、for、array_mapの使い分け
| 使うもの | 使う場面 |
|---|---|
foreach | キーの有無にかかわらず、すべての要素が欲しい。リストでも連想配列でも、どんな配列にも使える。 |
for | 数える(1から100まで、1つおきの要素)か、添字で隣の要素が必要($a[$i - 1])。for文を参照。 |
array_map() | 各要素を変換して新しい配列を作る。fnを使って1行で書くことが多い。 |
array_filter() | テストに合格した要素だけを残す。 |
手早い判断方法:ループの本体が要素ごとに新しい値を計算するだけなら、array_map(fn($x) => $x * 2, $a)で1行で表せます。副作用がある(出力する、保存する、複数のものを同時に数える)か、breakが必要なら、foreachを書きましょう。
よくある質問
PHPのforeachでキーと値を取得するには?
=>の形を使います:foreach ($array as $key => $value) { echo "$key: $value"; }。普通のリストではキーはインデックス(0、1、2)で、連想配列では'name'のような文字列のキーです。
PHPのforeachでインデックスを取得するには?
リストなら、キーがそのままインデックスです:foreach ($items as $i => $item)。連想配列では自分でカウンターを持ちます。ループの前に$i = 0;とし、各回の最後で$i++します。最初や最後のキーはarray_key_first()とarray_key_last()でわかります。
PHPのforeachの中で配列の値を変更するには?
&で参照としてループします:foreach ($prices as &$price) { $price *= 2; } unset($price);。またはキーを使って書き戻します:foreach ($prices as $key => $price) { $prices[$key] = $price * 2; }。単に$price = ...とするとコピーが変わるだけです。
PHPのforeachを参照で回すと最後の要素が重複するのはなぜですか?
foreach ($arr as &$v)のあとも、$vは最後の要素への参照のままです。次にforeach ($arr as $v)を回すと、各値がその最後の位置に書き込まれるので、最後の要素は最後から2番目の値の繰り返しになります。参照のループの直後にunset($v);を呼んでください。
PHPでforeachループを抜けるには?
break;はすぐにループを止め、continue;は次の要素へ飛びます。ネストしたループでは、break 2;で内側と外側の両方のループを抜けます。