PHP foreachの使い方:初心者でも理解できるループ処理ガイド

基本文法・構文ガイド
  • カテゴリ: ループ処理・制御構造(PHP標準構文)
  • 対象バージョン: PHP 7.4 〜 8.4(基本構文はPHP 4以降ほぼ不変。list()を使った短縮分割代入はPHP 5.5以降、キー指定付きの分割代入はPHP 7.1以降で利用可能)
  • 基本構文: foreach ($array as $value) {} / foreach ($array as $key => $value) {}
  • 関連: for / while / array_map / array_walk / Iterator・IteratorAggregate / Laravel Collection::each
  • 注意点: 参照渡し(&$value)を使った後は必ずunset()すること

要点(TL;DR)

  • foreachは配列・オブジェクトの要素を先頭から順番に取り出して繰り返し処理するための構文。forwhileよりも配列の反復処理に特化していて書き間違いが少ない
  • 最低限: foreach ($array as $value) { ... }/キーも使いたいときはforeach ($array as $key => $value) { ... }
  • 罠: 参照渡し(&$value)を使った後にunset($value)を忘れると、直後の変数代入で配列の最後の要素が意図せず上書きされる/foreachの実行中に対象配列へ要素を追加・削除すると挙動が不安定になる

概要

foreachは、配列やオブジェクトの各要素に順番にアクセスして処理を行うための、PHPのループ構造です。forのようにカウンタ変数の初期化・条件式・増分を自分で書く必要がなく、「配列の中身を先頭から最後まで処理したい」という最も多い場面をシンプルに書けます。配列だけでなく、IteratorIteratorAggregateを実装したオブジェクト、Generator(yield)にも使えるため、PHPの反復処理の基本かつ中心的な構文です。

基本構文

foreachには主に2つの書き方があります。

// 値だけを取り出す
foreach ($array as $value) {
    // $value を使った処理
}

// キーと値の両方を取り出す
foreach ($array as $key => $value) {
    // $key と $value を使った処理
}
要素説明
$array反復対象の配列・オブジェクト・Iterator実装クラス
$key省略可。各要素のキー(連想配列のキー、または数値インデックス)
$value各要素の値。&$valueとすると参照になり、ループ内での変更が元の配列に反映される

使用例

配列の各要素を順番に表示する

<?php

$fruits = ['apple', 'banana', 'orange'];

foreach ($fruits as $fruit) {
    echo $fruit . "\n";
}
// 出力:
// apple
// banana
// orange

連想配列でキーと値を両方使う

<?php

$fruits = ['apple' => 'りんご', 'banana' => 'バナナ', 'pear' => 'なし'];

foreach ($fruits as $key => $value) {
    echo "{$key} は日本語で {$value} です。\n";
}
// 出力:
// apple は日本語で りんご です。
// banana は日本語で バナナ です。
// pear は日本語で なし です。

参照渡しで元の配列を直接書き換える

値の前に&を付けると、要素への参照としてループ変数を扱えます。配列のコピーを作らずに元の配列を直接変更したい場合に使います。

<?php

$numbers = [1, 2, 3, 4, 5];

foreach ($numbers as &$number) {
    $number *= 2;
}
unset($number); // 参照を必ず解除する

print_r($numbers);
// 出力:
// Array
// (
//     [0] => 2
//     [1] => 4
//     [2] => 6
//     [3] => 8
//     [4] => 10
// )

unset($number)を忘れると、$numberという変数名が配列の最後の要素への参照のまま残ってしまいます。この状態で別のforeachに同じ変数名$numberを使うと、意図せず配列の最後の要素が上書きされる典型的なバグの原因になります(詳しくは後述の「よくある落とし穴」を参照)。

多次元配列をネストしたforeachで処理する

<?php

$users = [
    ['name' => 'Alice', 'email' => 'alice@example.com'],
    ['name' => 'Bob', 'email' => 'bob@example.com'],
];

foreach ($users as $user) {
    foreach ($user as $attribute => $value) {
        echo "{$attribute}: {$value}\n";
    }
    echo "------\n";
}

list()記法で配列を分解しながら取り出す(PHP 7.1以降)

各要素が同じ形の配列(例: [id, name]のペア)になっている場合、[$a, $b]の形で分割代入しながらループできます。ネストしたforeachを書かずに済み、可読性が上がります。

<?php

$coordinates = [[1, 2], [3, 4], [5, 6]];

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

オブジェクトのプロパティを反復する

foreachは配列だけでなくオブジェクトにも使えます。既定では、そのスコープからアクセス可能な(publicな)プロパティが順番に取り出されます。

<?php

class Car
{
    public function __construct(
        public string $make,
        public string $model,
        public int $year,
    ) {
    }
}

$car = new Car('Toyota', 'Corolla', 2022);

foreach ($car as $property => $value) {
    echo "{$property}: {$value}\n";
}
// 出力:
// make: Toyota
// model: Corolla
// year: 2022

Iterator / IteratorAggregateを実装した独自クラスを反復する

単純なpublicプロパティの列挙ではなく、反復のロジック自体を制御したい場合はIteratorAggregate(またはIterator)インターフェースを実装します。実装すると、そのクラスのインスタンスをforeachでそのまま扱えるようになります。

<?php

class MyCollection implements IteratorAggregate
{
    private array $items;

    public function __construct(array $items)
    {
        $this->items = $items;
    }

    public function getIterator(): Iterator
    {
        return new ArrayIterator($this->items);
    }
}

$collection = new MyCollection(['a', 'b', 'c']);

foreach ($collection as $item) {
    echo $item . "\n";
}
// 出力:
// a
// b
// c

break / continueでループを制御する

<?php

$scores = [80, 45, 90, 30, 70];

foreach ($scores as $index => $score) {
    if ($score < 50) {
        continue; // 50点未満はスキップして次の要素へ
    }
    if ($score >= 90) {
        echo "{$index}番目で満点級のスコアを検出したため終了します。\n";
        break; // ループ自体を終了
    }
    echo "{$index}: {$score}\n";
}

ネストしたforeachの外側ループを抜けたい場合は、continue 2break 2のように数値を指定して抜ける階層を指定できます。

よくある落とし穴・注意

  • 参照渡し後の変数の残留foreach ($array as &$value)の後にunset($value)を忘れると、$valueは配列の最後の要素への参照のまま残ります。その状態で同じ変数名を使う別のforeachを書くと、ループの2回目以降で前の要素が現在の要素の値で上書きされてしまう、気づきにくいバグになります。
  • ループ中に配列を追加・削除すると挙動が不安定になる:foreachは実行時に配列の内部ポインタ(正確には配列のコピーまたは参照)を辿っています。ループの内部で対象配列にunset()や要素追加を行うと、意図した順序で回らなかったり要素を読み飛ばしたりすることがあります。ループ中に元の配列を変更する必要がある場合は、変更対象を別の配列に貯めておき、ループの外でまとめて反映するのが安全です。
  • 数値形式の文字列キーは自動的に整数キーへ変換される'0''12'のような数字だけの文字列キーは、配列に格納される時点で自動的にint型のキーに変換されます。キーを文字列として厳密に扱いたい場合は、(string) $keyで明示的にキャストするか、キー自体を数字以外の文字を含む形にする必要があります。
  • 大きな配列を値渡しでループするとメモリ・処理時間を消費する:foreachは既定で配列のコピーに対して反復するため、巨大な配列を何度もforeachすると無駄なコピーが発生します。読み取り専用でよいなら参照は不要ですが、書き換えが必要な場合は&$valueで参照渡しにする、またはGeneratorで1件ずつ生成する設計に変えるとメモリ効率が改善します。

代替・関連構文との比較

構文/関数特徴向いている場面
foreach配列・オブジェクト・Iteratorの要素を順番に取り出す専用構文配列やコレクションの全要素を処理したい、最も一般的なケース
forカウンタ変数と条件式を自分で制御するループインデックスの増減量を細かく制御したい、配列以外(回数指定)のループ
while / do-while条件式が真の間だけ繰り返す配列ではなく「条件を満たす間」処理したい場合(DBカーソルの読み進めなど)
array_map各要素に関数を適用し、変換後の新しい配列を返す元の配列を変えずに全要素を変換した結果が欲しいとき
array_walk各要素にコールバックを適用し、参照渡しで元の配列を書き換える元の配列自体を関数的に変更したいとき
Laravel Collection::eachLaravelのコレクションに対してコールバックで副作用を実行するメソッド取得結果がすでにCollectionで、メソッドチェーンの中でループしたいとき

トラブルシュート(症状別)

症状原因対処
2つ目以降のforeachで前のループの値が混ざる参照渡し(&$value)の後にunset()していない参照付きforeachの直後に必ずunset($value)を実行する
ループ中に要素が飛ばされる・順番がおかしいループ内で対象配列へunset()や要素追加を行っている変更内容を別配列に貯め、ループ終了後にまとめて反映する
連想配列のキーが数値になってしまう数字だけの文字列キーが自動的にint型に変換されているキーを文字列として扱いたい場合は(string) $keyでキャストする
独自クラスのインスタンスをforeachすると空扱いになるpublicプロパティを持たない、またはIterator/IteratorAggregateを実装していないIteratorAggregateを実装しgetIterator()を定義する

よくある質問(FAQ)

Q. foreachとforはどちらを使えばいいですか?

A. 配列やコレクションの要素を先頭から全て処理したいだけなら、キー管理が不要で読みやすいforeachが基本の選択です。インデックスを飛ばして処理したい、逆順に回したいなど、添字の増減自体を細かく制御したい場合はforを使います。

Q. foreachの実行中に配列を変更してもいいですか?

A. 推奨しません。ループ対象の配列自体に要素を追加・削除すると、意図しない順序や要素の読み飛ばしが起こることがあります。変更したい内容は別の配列に貯めておき、ループが終わった後でまとめて反映するのが安全です。

Q. キーだけを取り出したい場合はどうすればいいですか?

A. foreach ($array as $key => $value)と書いた上で$valueを使わなければキーだけの処理になりますが、キーの一覧だけが欲しいならarray_keys($array)で先にキーの配列を作ってからforeachする方法もあります。

Q. Laravelプロジェクトでも素のforeachを使っていいですか?

A. 配列に対する単純な反復であればforeachで問題ありません。取得結果がすでにCollectionで、メソッドチェーンの流れの中で副作用だけを実行したい場合はCollection::eachを使うと簡潔に書けます。ビューの中で複雑なロジックを避けたい、大量データをチャンク分割して処理したいといったLaravel特有の工夫は、Laravel foreachの使い方で詳しく解説しています。

関連記事

参考リンク

レン (Wren)

こんにちは。レンです。

Laravelのコードの森に住んでいる、小さな案内役です。
ルーティングの枝やクラスの影を歩きながら、コードの流れや仕組みを眺めています。

このサイトでは、Laravelの基本から実装のコツまで、開発で役立つポイントを静かに整理しています。
難しいことを増やすのではなく、コードの見通しが少し良くなるヒントを届けるのが役目です。

「この処理はどこに書くのがいいのか」
「Laravelではどう考えると整理できるのか」

そんな疑問に、小さなメモを残すような気持ちで記事を書いています。

コードを書いている途中で迷ったとき、
このサイトが少し立ち止まって整理できる場所になればうれしいです。

レン (Wren)をフォローする

コメント