PHPはサーバーサイドでの多くの反復的な作業を処理します。単一のリクエストであっても、画像が詰まったフォルダをスキャンしたり、ステータスが変わるまでデータベースをポーリングしたり、ユーザーがルールを満たすまで入力を求め続けたりする必要があるかもしれません。同じコードブロックをコピー&ペーストしてこれらを行うと、コードは壊れやすく、メンテナンスもほぼ不可能になります。ループは、まさにこの問題を解決するために存在します。ループはロジックを再利用可能なブロックにまとめ、エラーを減らし、アプリケーションをコンパクトに保ちます。

ほとんどのPHP開発者は、繰り返しの回数がわかっているときは for ループを、回数が動的な条件に依存するときは while ループを直感的に使用します。しかし、3つ目の選択肢があり、それは十分に異なる挙動を示すため、適切なタイミングで選択することでコードを大幅に分かりやすくすることができます。do-while ループは、まずブロック内のコードを実行し、その後に再度実行すべきかどうかをチェックします。

do-while ループが異なる理由

標準的な while ループでは、PHPはブロックに入る前に条件を評価します。もし条件が最初から偽(false)であれば、ブロック全体がスキップされます。do-while ループはその順序を逆転させます。まずコードを1回実行して最後まで到達し、その後にループに戻るかどうかを判断するために条件をチェックします。

構文は次のようになります:

do {
    // statements
} while ($condition);

while 句の後のセミコロンに注意してください。このセミコロンは必須であり、忘れると構文エラーが発生します。dowhile の間のブロックは、たとえ最初から条件が偽であっても、必ず少なくとも1回は実行されます。この事後テスト(post-test)の挙動は、単なる癖ではなく、このループが存在する理由そのものです。

do-while を使うべき場面

この構造が真価を発揮する一般的なシナリオが3つあります。メニューの表示、入力のバリデーション、そして失敗する可能性のあるタスクの再試行です。いずれの場合も、継続するかどうかを判断する前に、アクションを実行する必要があるというロジックが求められます。

メニューの表示

コマンドラインツールやテキストベースの管理パネルを想像してみてください。オプションのリストを表示し、ユーザーが選択するのを待ち、それからメニューを再度表示するかどうかを決定する必要があります。メニューがすでに画面に表示されるまで、ユーザーが「終了」を選択したかどうかを確認することはできません。

$choice = 0;

do {
    echo "1. View Reports\n";
    echo "2. Export Data\n";
    echo "3. Exit\n";
    
    $choice = (int) readline("Select an option: ");
    
    if ($choice === 1) {
        // fetch and display reports
    } elseif ($choice === 2) {
        // run export logic
    }
} while ($choice !== 3);

通常の while ループでも技術的には処理可能ですが、ループを開始する前にメニュー出力を一度繰り返すか、初期条件を満たすためだけに $choice を偽の値で初期化する必要があります。これでは、関連するロジックが2つの場所に分散してしまいます。do-while 版であれば、表示、読み取り、チェックを一つの整ったブロック内に保持できます。

入力のバリデーション

入力のバリデーションも、多くの場合同じパターンに従います。ユーザーにプロンプトを表示し、入力内容を検査し、ルールに適合しない場合は繰り返します。検査を行う前にプロンプトを表示する必要があるため、do-while ループは自然に適合します。

do {
    $username = readline("Choose a username (min 3 chars): ");
    $isValid = strlen($username) >= 3;
    
    if (!$isValid) {
        echo "That username is too short.\n";
    }
} while (!$isValid);

標準的な while ループを使用する場合、条件式に評価させるものを用意するために、$username を空文字列で初期化し、ループの前にプロンプトの行を複製する必要があります。この繰り返しはバグを招きます。後でプロンプトのメッセージを変更する場合、ループ内の修正は覚えていても、その上のコピーを修正し忘れるかもしれません。do-while 構造は、条件をテストする前にブロック内でプロンプトが実行されることを保証することで、そのリスクを排除します。

失敗したタスクの再試行

ネットワークリクエスト、ファイルロック、データベースへの書き込みなどは、初回は失敗しても、すぐに成功することがあります。通常は少なくとも1回は試行し、失敗が続く間、かつ猶予がある間だけ試行を続けたいと考えます。

$attempts = 0;
$maxRetries = 3;
$success = false;

do {
    $attempts++;
    $response = @file_get_contents('https://api.example.com/status');
    
    if ($response !== false) {
        $success = true;
    } elseif ($attempts < $maxRetries) {
        sleep(1);
    }
} while (!$success && $attempts < $maxRetries);

if (!$success) {
    // log the permanent failure
}

ここでの重要な詳細は、PHPが「成功したか?」と尋ねる前にリクエストが送信されることです。while ループでは、ループに入る前にダミーのリクエストを実行するか、偽の成功フラグを設定する必要があり、実際に何が起きているのかが不明瞭になります。do-while 版は、「試行、検査、決定」という一連の流れを素直なものにします。

do-while と while の比較

これら2つのループの違いは、タイミングの問題です。テストに失敗するような状態から始まる変数について考えてみましょう:

$x = 10;

while ($x < 5) {
    echo $x;        // Never runs
}

同じ初期値に対して、do-while ループは次のように動作します:

$x = 10;

do {
    echo $x;        // Runs once, outputs 10
} while ($x < 5);

「少なくとも1回は実行される」という点が決定的な要因です。ロジックが「0回以上の繰り返し」を必要とする場合は、while の方が安全なツールです。ロジックが「1回以上の繰り返し」を必要とする場合は、通常 do-while の方がクリーンなツールとなります。

落とし穴とベストプラクティス

do-while ループは少なくとも1回の実行を保証するため、注意を怠ると無限ループを引き起こす可能性があります。条件式でチェックされる変数がブロック内で一度も変更されない場合、PHPは無限に回転し続けるか、最大実行時間に達するまで動き続けます。

もう一つのよくある間違いは、末尾のセミコロンを省略することです。以下は失敗します:

do {
    // code
} while ($x < 5)    // syntax error

また、他のループの方が表現力豊かな場合に、習慣だけで do-while を選ぶことは避けるべきです。配列を反復処理する場合は、ほとんどの場合 foreach の方が優れています。単純な数値カウンターを管理する場合は、for ループを使用すると初期化、条件、インクリメントが1行にまとまり、コードの把握が容易になります。

パフォーマンスの観点では、PHPにおいて do-whilewhile と比較して意味のあるオーバーヘッドはありません。利点は完全に構造的なものです。他の開発者が do-while ループを見ると、条件が評価される前にブロックが実行されなければならないことが即座に理解できます。この一つの情報があるだけで、標準的な while ループの上にあるダミー変数や重複したセットアップコードを追跡する手間が省けます。

エッジケースと代替パターン

ユーザーの操作が関与しない場合でも、do-while ループが役立つことがあります。例えば、少なくとも1つのアイテムを生成するジェネレーターやカーソルを消費する場合、最初のアイテムを処理してから、カーソルが有効な次の位置に進んだかどうかを確認できます。現代的なリザルトセットの代わりに生のデータベースリソースを使用しているレガシーなコードベースでは、次のようなパターンが見られることがあります:

$row = $result->fetch();
do {
    // process $row
} while ($row = $result->fetch());

現代のPHPでは PDO と foreach を使うことが推奨されますが、このパターンは、追加の分岐なしに do-while が最初の反復を特別なケースとしてどのように処理するかを示しています。

重要なポイント

do-while ループは用途の限られたツールですが、適切な場所で使用すれば、それこそが唯一の理にかなったツールとなります。セットアップとバリデーションのロジックを1か所にまとめ、判定が行われる前にアクションが試行されることを保証し、コードを読む次の人に意図を直接伝えます。処理の後に条件判定を行う必要がある場合は、do-while を使用してください。それ以外のすべてのケースでは、よりシンプルな whilefor、または foreach を使用しましょう。