タイトル: 繰り返し処理
SEOタイトル: PHP 繰り返し処理完全ガイド (for/foreach/while/array_map)
| この記事の要点 |
|
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 のほうが若干速い。