[PHP]foreachとは?配列を簡潔かつ安全に走査するループ構文を徹底解説

PHP

はじめに

これまでの記事で、while・do...while・for という3種類のループ構文を解説してきました。今回取り上げる foreach は、その中でも配列やイテレータの走査に特化した、PHPで最も頻繁に使われるループ構文です。

for の記事でも触れた通り、インデックス番号そのものが処理に必要ない場合、foreach を使うことで、インデックス管理の手間やバグのリスクを排除し、配列の各要素を直接扱える、簡潔で読みやすいコードを書けます。キーと値を同時に取得する書き方、参照渡しによる要素の直接編集、分割代入のような構文など、知っておくと便利な機能も豊富です。本記事では基本的な使い方から、実践的な活用パターン、よくある落とし穴まで詳しく解説します。


構文概要

項目内容
構文名foreach
種別制御構造(ループ構文)
基本構文1foreach ($array as $value) { ... }
基本構文2foreach ($array as $key => $value) { ... }
対象配列、およびTraversableインターフェースを実装したオブジェクト
参照渡しforeach ($array as &$value) で要素を直接変更できる
対応バージョンPHP 4以前から存在する最古参の構文の一つ

foreachの動作フロー(イメージ図)

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

  foreach ($fruits as $key => $value) {
      echo "{$key}: {$value}\n";
  }
              │
              ▼
  ┌───────────────────────────┐
  │ 配列の先頭から末尾まで、              │
  │ 各要素のキーと値を自動的に取り出し、    │
  │ インデックス管理を一切意識せずに        │
  │ 繰り返し処理できる                     │
  └───────────────────────────┘
  出力:
  apple: りんご
  banana: バナナ

ポイントは、foreach が配列の内部構造(インデックスか連想配列か、要素数はいくつか)を開発者が意識する必要なく、安全かつ簡潔に全要素へアクセスできるという点です。


実践サンプル7選

例1:基本的な使い方

<?php

$colors = ['赤', '青', '緑'];

foreach ($colors as $color) {
    echo "色: {$color}" . PHP_EOL;
}

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

<?php

$prices = ['りんご' => 150, 'バナナ' => 100, 'オレンジ' => 120];

foreach ($prices as $fruit => $price) {
    echo "{$fruit}: {$price}円" . PHP_EOL;
}

例3:参照渡しによる配列要素の直接編集

<?php

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

/**
 * &を付けることで、ループ内で$valueを変更すると
 * 元の配列の要素そのものが書き換わる
 */
foreach ($numbers as &$value) {
    $value *= 2;
}
unset($value); // 参照を使った後は必ずunset()で参照を解除する(重要な作法)

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

例4:多次元配列のネストした走査

<?php

$orders = [
    ['id' => 1, 'items' => ['ペン', 'ノート']],
    ['id' => 2, 'items' => ['消しゴム']],
];

foreach ($orders as $order) {
    echo "注文#{$order['id']}:" . PHP_EOL;
    foreach ($order['items'] as $item) {
        echo "  - {$item}" . PHP_EOL;
    }
}

例5:PHP 7.1以降のリスト分割代入との組み合わせ

<?php

$coordinates = [[0, 0], [1, 2], [3, 4]];

/**
 * foreach内でリスト構文([$x, $y])を使うことで、
 * 配列の各要素をさらに分解しながら走査できる
 */
foreach ($coordinates as [$x, $y]) {
    echo "座標: ({$x}, {$y})" . PHP_EOL;
}

例6:break・continueとキー条件を組み合わせたフィルタリング

<?php

$scores = ['太郎' => 85, '花子' => 45, '次郎' => 92, '三郎' => 30];

foreach ($scores as $name => $score) {
    if ($score < 50) {
        continue; // 50点未満はスキップして次の要素へ
    }

    echo "{$name}: {$score}点(合格)" . PHP_EOL;

    if ($score >= 90) {
        echo '  → 最優秀賞候補です!' . PHP_EOL;
    }
}

例7:オブジェクトの走査(Iteratorインターフェースの実装)

<?php

class NumberCollection implements \Iterator
{
    private array $items;
    private int $position = 0;

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

    public function current(): mixed { return $this->items[$this->position]; }
    public function key(): mixed { return $this->position; }
    public function next(): void { $this->position++; }
    public function rewind(): void { $this->position = 0; }
    public function valid(): bool { return isset($this->items[$this->position]); }
}

/**
 * Iteratorインターフェースを実装したオブジェクトは、
 * 配列と同じようにforeachでそのまま走査できる
 */
$collection = new NumberCollection([10, 20, 30]);
foreach ($collection as $key => $value) {
    echo "{$key} => {$value}" . PHP_EOL;
}

関連機能との比較

機能役割foreachとの違い
foreach配列・イテレータの要素を順に処理する本記事の対象。インデックス管理が不要で、配列の走査に特化している
for初期化・条件・更新式をまとめた繰り返しインデックス番号自体が処理に必要な場合に向いている(前回記事を参照)
while条件がtrueの間、処理を繰り返す配列以外の、任意の条件に基づく繰り返しに使われる
array_map()配列の各要素に関数を適用し、新しい配列を返すループではなく、変換処理に特化した関数型のアプローチ
array_walk()配列の各要素にコールバック関数を適用するforeachに似た目的だが、関数呼び出しのオーバーヘッドがある

よくある落とし穴(注意点)

  1. 参照渡し(&$value)を使った後は必ずunset()する これが foreach における最も有名で危険な落とし穴です。foreach ($arr as &$value) の後、$value という変数名の参照がループ終了後も残り続けるため、その後同じ変数名で別の foreach(参照なし)を書くと、最後の要素が意図せず上書きされるというバグを引き起こします(例3を参照)。
  2. ループ中に元の配列を変更すると予期しない挙動になることがある foreach の実行中に、ループ対象の配列自体に要素を追加・削除すると、PHPのバージョンや状況によって予測しにくい挙動になることがあります。ループ中に配列を変更する必要がある場合は、コピーを作成してから処理するなどの工夫が推奨されます。
  3. 大きな配列をコピーするコスト foreach ($array as $value)(参照なし)の場合、PHPの内部的な仕組み(Copy-on-Write)により、通常は大きなコピーコストは発生しませんが、要素自体が巨大なオブジェクトである場合など、状況によってはメモリ使用量に注意が必要なケースもあります。
  4. キーの型が期待と異なる場合がある 連想配列のキーには文字列だけでなく、数値であれば自動的に整数型として扱われることがあります。$key の型を暗黙的に文字列だと決めつけてコードを書くと、比較や出力の際に意図しない挙動になることがあります。
  5. Iteratorインターフェースを実装していないオブジェクトは走査できない 通常のオブジェクトに対して foreach を使うと、デフォルトではpublicプロパティが走査されますが、独自のイテレーションロジックを実装したい場合は Iterator インターフェース(または IteratorAggregate)を実装する必要があります(例7を参照)。

まとめ

観点まとめ
何をする構文か配列やイテレータの各要素を、キーと値を伴って順番に処理する
主な用途配列の走査、連想配列のキーと値の取得、多次元配列のネスト処理
参照渡し&$valueで要素を直接変更できるが、使用後は必ずunset()すること
forとの違いインデックス管理が不要で、配列の走査に特化した簡潔な記述ができる
注意点参照渡し後のunset()忘れ、ループ中の配列変更、キーの型の扱い

foreach は、配列を扱うPHPコードにおいて最も頻繁に登場する、簡潔で安全なループ構文です。参照渡しを使う際の unset() の重要性さえ押さえておけば、インデックス管理の煩わしさから解放された、読みやすいコードを書くことができます。これで while・do-while・for・foreach という、PHPの主要なループ構文がすべて出揃いました。

タイトルとURLをコピーしました