はじめに
これまでの記事で、while・do...while・for という3種類のループ構文を解説してきました。今回取り上げる foreach は、その中でも配列やイテレータの走査に特化した、PHPで最も頻繁に使われるループ構文です。
for の記事でも触れた通り、インデックス番号そのものが処理に必要ない場合、foreach を使うことで、インデックス管理の手間やバグのリスクを排除し、配列の各要素を直接扱える、簡潔で読みやすいコードを書けます。キーと値を同時に取得する書き方、参照渡しによる要素の直接編集、分割代入のような構文など、知っておくと便利な機能も豊富です。本記事では基本的な使い方から、実践的な活用パターン、よくある落とし穴まで詳しく解説します。
構文概要
| 項目 | 内容 |
|---|---|
| 構文名 | foreach |
| 種別 | 制御構造(ループ構文) |
| 基本構文1 | foreach ($array as $value) { ... } |
| 基本構文2 | foreach ($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に似た目的だが、関数呼び出しのオーバーヘッドがある |
よくある落とし穴(注意点)
- 参照渡し(
&$value)を使った後は必ずunset()する これがforeachにおける最も有名で危険な落とし穴です。foreach ($arr as &$value)の後、$valueという変数名の参照がループ終了後も残り続けるため、その後同じ変数名で別のforeach(参照なし)を書くと、最後の要素が意図せず上書きされるというバグを引き起こします(例3を参照)。 - ループ中に元の配列を変更すると予期しない挙動になることがある
foreachの実行中に、ループ対象の配列自体に要素を追加・削除すると、PHPのバージョンや状況によって予測しにくい挙動になることがあります。ループ中に配列を変更する必要がある場合は、コピーを作成してから処理するなどの工夫が推奨されます。 - 大きな配列をコピーするコスト
foreach ($array as $value)(参照なし)の場合、PHPの内部的な仕組み(Copy-on-Write)により、通常は大きなコピーコストは発生しませんが、要素自体が巨大なオブジェクトである場合など、状況によってはメモリ使用量に注意が必要なケースもあります。 - キーの型が期待と異なる場合がある 連想配列のキーには文字列だけでなく、数値であれば自動的に整数型として扱われることがあります。
$keyの型を暗黙的に文字列だと決めつけてコードを書くと、比較や出力の際に意図しない挙動になることがあります。 Iteratorインターフェースを実装していないオブジェクトは走査できない 通常のオブジェクトに対してforeachを使うと、デフォルトではpublicプロパティが走査されますが、独自のイテレーションロジックを実装したい場合はIteratorインターフェース(またはIteratorAggregate)を実装する必要があります(例7を参照)。
まとめ
| 観点 | まとめ |
|---|---|
| 何をする構文か | 配列やイテレータの各要素を、キーと値を伴って順番に処理する |
| 主な用途 | 配列の走査、連想配列のキーと値の取得、多次元配列のネスト処理 |
| 参照渡し | &$valueで要素を直接変更できるが、使用後は必ずunset()すること |
forとの違い | インデックス管理が不要で、配列の走査に特化した簡潔な記述ができる |
| 注意点 | 参照渡し後のunset()忘れ、ループ中の配列変更、キーの型の扱い |
foreach は、配列を扱うPHPコードにおいて最も頻繁に登場する、簡潔で安全なループ構文です。参照渡しを使う際の unset() の重要性さえ押さえておけば、インデックス管理の煩わしさから解放された、読みやすいコードを書くことができます。これで while・do-while・for・foreach という、PHPの主要なループ構文がすべて出揃いました。
