13.

PHP 繰り返し処理完全ガイド (for/foreach/while/array_map)

編集
この記事の要点
  • PHP の基本ループ 4 種: for / foreach / while / do-while
  • 配列処理は foreach ($arr as $value) または foreach ($arr as $key => $value) が定番
  • 関数型: array_map / array_filter / array_walk / array_reduce
  • generators (yield) でメモリ効率の良い遅延評価ループ
  • PHP 8+ の match 式は switch 代替。break N で深いネスト脱出

PHP のループ 4 種

<?php
// 1. for
for ($i = 0; $i < 10; $i++) {
    echo $i, "\n";
}

// 2. foreach (配列専用 - 最も多用)
$colors = ['red', 'green', 'blue'];
foreach ($colors as $color) {
    echo $color, "\n";
}

// 3. while (条件先判定)
$i = 0;
while ($i < 10) {
    echo $i++, "\n";
}

// 4. do-while (条件後判定、最低 1 回実行)
$i = 0;
do {
    echo $i++, "\n";
} while ($i < 10);

foreach 構文の 2 形式

<?php
$scores = ['Alice' => 90, 'Bob' => 80, 'Carol' => 70];

// 形式1: value のみ
foreach ($scores as $score) {
    echo $score, "\n";          // 90, 80, 70
}

// 形式2: key => value
foreach ($scores as $name => $score) {
    echo "$name : $score\n";    // Alice : 90 ...
}

// ★ 参照渡し ($value への代入で配列本体を書き換え)
foreach ($scores as &$score) {
    $score += 10;
}
unset($score);    // ★ 必須: 参照を解除しないと次のループでバグる

print_r($scores);   // 100, 90, 80

foreach の参照渡しの罠

<?php
$arr = [1, 2, 3];

foreach ($arr as &$v) {
    $v *= 2;
}
// ★ unset しないと $v は最後の要素への参照のまま残る

foreach ($arr as $v) {
    // ここで $v への代入が前のループの参照経由で $arr[2] を破壊する
}

print_r($arr);   // [2, 4, 4]  ← バグ!

// ✅ 正解: 参照後は必ず unset
foreach ($arr as &$v) { $v *= 2; }
unset($v);

break / continue / 多重ループ

<?php
for ($i = 0; $i < 10; $i++) {
    if ($i === 3) continue;     // この回スキップ
    if ($i === 7) break;        // ループ脱出
    echo $i, "\n";              // 0, 1, 2, 4, 5, 6
}

// ★ break N / continue N で外側ループを抜ける (PHP 独自)
for ($i = 0; $i < 5; $i++) {
    for ($j = 0; $j < 5; $j++) {
        if ($i === 2 && $j === 3) {
            break 2;             // 2 段階のループを一気に脱出
        }
    }
}

関数型: array_map / array_filter / array_reduce

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

// array_map : 各要素を変換
$doubled = array_map(fn($n) => $n * 2, $nums);
// → [2, 4, 6, 8, 10]

// array_filter : 条件で抜き出す
$even = array_filter($nums, fn($n) => $n % 2 === 0);
// → [1 => 2, 3 => 4]  ★ キーが保持される (再番号化したいなら array_values)

// array_reduce : 集約
$sum = array_reduce($nums, fn($acc, $n) => $acc + $n, 0);
// → 15

// array_walk : 参照渡しで全要素を変更 (戻り値は bool)
array_walk($nums, function (&$v, $k) { $v *= 10; });
// → $nums = [10, 20, 30, 40, 50]

// 連鎖: filter → map → reduce
$total = array_reduce(
    array_map(fn($n) => $n * 2,
        array_filter($nums, fn($n) => $n > 2)
    ),
    fn($acc, $n) => $acc + $n,
    0
);   // (3+4+5)*2 = 24

Generators (yield) — 遅延評価ループ

巨大データを全部メモリに乗せたくないとき、yield で 1 件ずつ生成する Generator が便利です。

<?php
function range_gen(int $start, int $end): Generator {
    for ($i = $start; $i <= $end; $i++) {
        yield $i;        // 1 件返して中断、次回呼ばれたら再開
    }
}

foreach (range_gen(1, 1000000) as $i) {
    echo $i, "\n";       // メモリ 1MB 未満で 100 万件処理可能
}

// ファイルを 1 行ずつ
function read_lines(string $path): Generator {
    $fp = fopen($path, 'r');
    while (($line = fgets($fp)) !== false) {
        yield rtrim($line);
    }
    fclose($fp);
}

foreach (read_lines('huge.csv') as $line) {
    // 1 行ずつ処理。10GB ファイルでも OK
}

// key => value 生成
function user_iter(): Generator {
    yield 'alice' => 90;
    yield 'bob'   => 80;
}
foreach (user_iter() as $name => $score) { ... }

PHP 8 の match 式

<?php
// switch は fall-through (break 必須)
switch ($status) {
    case 1:
    case 2:
        $label = '進行中';
        break;
    case 3:
        $label = '完了';
        break;
    default:
        $label = '?';
}

// match は厳密 === 比較で式として値を返す。break 不要
$label = match ($status) {
    1, 2    => '進行中',
    3       => '完了',
    default => '?',
};

// 条件式も書ける
$grade = match (true) {
    $score >= 90 => 'A',
    $score >= 70 => 'B',
    $score >= 50 => 'C',
    default      => 'F',
};

無限ループ防止

<?php
// ❌ NG: $i のインクリメント忘れ → 無限ループ + メモリ枯渇
$i = 0;
while ($i < 10) {
    echo $i;
    // $i++; を忘れた
}

// ✅ 安全ガード: 最大反復回数を設定
$i = 0;
$max = 100000;
while ($condition && $i++ < $max) {
    // ...
}
if ($i >= $max) {
    throw new RuntimeException('Loop guard exceeded');
}

// PHP 設定でも防御
// php.ini : max_execution_time = 30  → 30 秒で fatal

性能比較 (10 万件処理の例)

方式速度メモリ用途
foreach (値)★★★ 最速定番
foreach (参照 &)★★★要素書き換え
for + $arr[$i]★★ やや遅index 必須
array_map★★ 関数呼び出しコスト中 (新配列)不変な変換
Generator (yield)★ やや遅★ 極小巨大データ

使い分けチートシート

  • 配列を順に処理 → foreach
  • index が必要、または個数ベースで回す → for
  • 終了条件が動的・読み込みが続く間 → while
  • 必ず 1 回は実行 (リトライ等) → do-while
  • 配列を変換して新配列にしたい → array_map
  • 絞り込みたい → array_filter
  • 合計・最大等の集約 → array_reduce
  • メモリ節約したい巨大データ → Generator (yield)

FAQ

Q: foreach のキー $key はインデックスとは限らない?
A: 連想配列なら文字列キー、添字配列なら 0 始まり整数。array_values() で再番号化可能。

Q: foreach で要素削除したい
A: foreach 内で unset($arr[$key]) は可。ただし参照渡しと併用すると挙動が複雑array_filter で別配列にする方が安全。

Q: 関数型 (array_map 等) と foreach、どちらを使う?
A: 不変な変換は array_map、副作用 (DB 保存等) を含むなら foreach。Closure オーバーヘッドで foreach のほうが若干速い。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 基本事項
  2. HTMLへの埋め込み
  3. 変数
  4. 可変変数
  5. 定数
  6. データ型
  7. キャスト
  8. エスケープ文字
  9. 配列
  10. 演算子
  11. 代入の際の注意点
  12. 条件分岐
  13. 繰り返し処理
  14. クラスとインスタンス
  15. コンストラクタ
  16. 関数
  17. スーパーグローバル変数
  18. スコープ
  19. staticについて
  20. yieldについて
  21. ファイルのアップロード方法
  22. DB接続方法
  23. SQL実行方法
  24. カプセル化の具体例
  25. 継承の構文
  26. オーバーライド
  27. ポリモーフィズム(多様性)の具体例
  28. 抽象クラス・メソッドの構文と具体例
  29. GET通信
  30. try catchで全てのエラーを拾う方法

最近更新/作成されたページ