はじめに
これまでの記事で、if・else・elseif・switch・match という条件分岐の仲間を一通り解説してきました。今回からは、PHPのもう一つの基本的な制御構造である「繰り返し(ループ)」の仲間に入っていきます。その最初として取り上げるのが while です。
while は、指定した条件式が true である限り、処理ブロックを繰り返し実行するループ構文です。for ループが「繰り返す回数があらかじめ決まっている」場合に適しているのに対し、while は「いつ終わるか実行前には分からない」処理、例えばファイルの末尾に達するまで、あるいは特定の条件が満たされるまでループを続けたい場合に特に適しています。本記事では基本的な使い方から、無限ループを避けるための注意点、実践的な活用例まで詳しく解説します。
構文概要
| 項目 | 内容 |
|---|---|
| 構文名 | while |
| 種別 | 制御構造(ループ構文) |
| 基本構文 | while (条件式) { 処理 } |
| 評価タイミング | ループの先頭で毎回条件式を評価する(前判定ループ) |
| 代替構文 | while (条件式): 処理 endwhile;(テンプレート内で使用) |
| 対応バージョン | PHP 4以前から存在する最古参の構文の一つ |
| 関連キーワード | break(ループを抜ける)、continue(次の繰り返しへ進む) |
whileの動作フロー(イメージ図)
$count = 0;
while ($count < 3) {
echo $count;
$count++;
}
│
▼
┌───────────────────────────┐
│ 1. ループの先頭で条件式を評価する │
│ 2. trueならブロック内の処理を実行 │
│ 3. 処理完了後、再び1に戻る │
│ 4. falseになった時点でループを抜ける │
└───────────────────────────┘
出力: 0, 1, 2
($countが3になった時点で条件がfalseになり終了)
ポイントは、while が**「前判定ループ」であり、ブロックの処理を実行する前**に毎回条件式をチェックするという点です。そのため、最初から条件が false であれば、ループの中身は一度も実行されません(この点は後述する do-while との違いになります)。
実践サンプル7選
例1:基本的な使い方
<?php
$count = 0;
while ($count < 5) {
echo "カウント: {$count}" . PHP_EOL;
$count++;
}
例2:配列からデータを順次取り出して処理する(array_shiftとの組み合わせ)
<?php
$tasks = ['メール確認', '会議準備', 'レポート作成'];
/**
* array_shift()で配列からデータを1つずつ取り出し、
* 配列が空になるまで繰り返す典型的なキュー処理パターン
*/
while ($task = array_shift($tasks)) {
echo "処理中: {$task}" . PHP_EOL;
}
echo '全タスク完了' . PHP_EOL;
例3:ファイルを1行ずつ読み込む
<?php
$handle = fopen(__FILE__, 'r'); // このファイル自身を読み込む例
$lineNumber = 0;
while (($line = fgets($handle)) !== false) {
$lineNumber++;
if ($lineNumber > 3) {
break; // 最初の3行だけ表示する
}
echo "{$lineNumber}: " . trim($line) . PHP_EOL;
}
fclose($handle);
例4:データベースの結果セットを1行ずつ処理する典型パターン
<?php
/**
* PDOStatementのfetch()はデータがなくなるとfalseを返すため、
* whileループと組み合わせるのが一般的なイディオム
*/
function processQueryResults(PDOStatement $stmt): void
{
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo "処理中のレコード: " . json_encode($row) . PHP_EOL;
}
}
// $stmt = $pdo->query('SELECT * FROM users');
// processQueryResults($stmt);
例5:テンプレート内での代替構文(コロン構文)
<?php $remainingItems = 3; ?>
<ul>
<?php while ($remainingItems > 0): ?>
<li>残りアイテム: <?= $remainingItems ?></li>
<?php $remainingItems--; ?>
<?php endwhile; ?>
</ul>
例6:breakとcontinueを使った制御
<?php
$number = 0;
while (true) { // 無限ループとして開始し、内部でbreakする設計パターン
$number++;
if ($number % 2 === 0) {
continue; // 偶数の場合は以降の処理をスキップし、次の繰り返しへ
}
echo "奇数: {$number}" . PHP_EOL;
if ($number >= 9) {
break; // 条件を満たしたらループ全体を抜ける
}
}
例7:リトライ処理の実装
<?php
class RetryableOperation
{
/**
* 成功するか、最大試行回数に達するまで
* whileループで処理をリトライする典型的な実装パターン
*/
public function execute(callable $operation, int $maxAttempts = 3): bool
{
$attempts = 0;
$success = false;
while (!$success && $attempts < $maxAttempts) {
$attempts++;
echo "試行 {$attempts}回目..." . PHP_EOL;
try {
$operation();
$success = true;
} catch (\Throwable $e) {
echo "失敗: {$e->getMessage()}" . PHP_EOL;
}
}
return $success;
}
}
$retrier = new RetryableOperation();
$attemptCount = 0;
$retrier->execute(function () use (&$attemptCount) {
$attemptCount++;
if ($attemptCount < 2) {
throw new \RuntimeException('一時的な失敗');
}
echo '成功しました' . PHP_EOL;
});
関連機能との比較
| 機能 | 役割 | whileとの違い |
|---|---|---|
while | 条件がtrueの間、処理を繰り返す(前判定) | 本記事の対象。ループ本体の実行前に毎回条件を評価する |
do-while | 条件がtrueの間、処理を繰り返す(後判定) | 必ず最低1回は処理を実行してから条件を評価する点が異なる |
for | 初期化・条件・更新式を1行にまとめた繰り返し | 繰り返し回数があらかじめ決まっている場合に簡潔に書ける |
foreach | 配列やイテレータの要素を順に処理する | 配列の走査に特化しており、インデックス管理が不要 |
break / continue | ループの制御(中断・スキップ) | while自体ではなく、ループ内部での流れを制御する補助的な構文 |
よくある落とし穴(注意点)
- 無限ループに陥りやすい ループ内でカウンタや条件に関わる変数を更新し忘れると、
whileは永遠に終了しません(意図的なwhile (true)ループ以外では)。特にリソース消費の大きい処理や、Webリクエストの応答時間に関わる場面では致命的な問題になり得ます(例6を参照)。 fetch()やfgets()の戻り値の型に注意するwhile ($row = $stmt->fetch())のようなパターンでは、取得した値が0や''(空文字列)のような falsy な値だった場合、意図せずループが終了してしまう可能性があります。多くの組み込み関数はこうしたケースでfalseを明確に返すよう設計されていますが、厳密な比較(!== false)を使う方が安全な場合もあります(例3を参照)。- コロン構文での閉じ方を間違えない 代替構文を使う場合、
endwhile;で正しく閉じる必要があります。中括弧構文の}と混同しないようにしましょう(例5を参照)。 - ループ内で配列を変更しながら処理する場合の注意
array_shift()のように配列自体を変更しながらwhileでループする場合(例2)、配列の構造を正しく理解していないと、予期しない要素のスキップや無限ループにつながることがあります。 forやforeachの方が適している場面もある 単純な回数指定の繰り返しであればfor、配列の走査であればforeachの方が、意図が明確で簡潔なコードになります。whileは「終了条件が実行前には分からない」場合にこそ、その真価を発揮する構文です。
まとめ
| 観点 | まとめ |
|---|---|
| 何をする構文か | 指定した条件式がtrueである限り、処理ブロックを繰り返し実行する |
| 主な用途 | ファイルの行読み込み、データベース結果セットの処理、リトライ処理、キューの消化 |
| 評価タイミング | 前判定(ループ本体の実行前に毎回条件をチェック) |
| 関連する構文 | do-while(後判定版)、break/continue(ループ制御) |
| 注意点 | 無限ループのリスク、falsyな戻り値との比較、for/foreachの方が適切な場面もあること |
while は、「いつ終わるか分からない」処理を表現するための、シンプルでありながら強力なループ構文です。ファイルやデータベースからの逐次読み込みなど、終了条件が実行時にしか判明しない場面で頻繁に活用される一方、無限ループに陥らないよう、ループ内で確実に終了条件へ近づく処理を書くことが重要です。
