PHPプログラミングにおいて、同じ処理を繰り返し実行する「ループ処理」は、アプリケーション開発の根幹を支える極めて重要な要素です。

データベースから取得したレコードを一覧表示したり、配列に含まれる大量のデータを加工したりする際、ループ処理を適切に記述することでコードの再利用性と保守性が飛躍的に向上します。

しかし、PHPには複数のループ構文が存在するため、状況に応じて最適な書き方を選択しなければ、予期せぬバグやパフォーマンスの低下を招く恐れがあります。

本記事では、PHPの主要なループ処理であるforeachforwhiledo-whileの4種類について、それぞれの特徴と具体的な使い分け方を詳しく解説します。

PHPにおけるループ処理の重要性と基本的な役割

プログラムの中で特定の条件を満たす間、同じ命令を繰り返し実行する仕組みをループ(反復)処理と呼びます。

PHPで効率的なコードを書くためには、まず「どのデータを」「どのような条件で」繰り返したいのかを明確にする必要があります。

データの構造に合わせた制御の必要性

PHPは配列の操作に非常に長けた言語であり、モダンな開発現場では連想配列や多次元配列を扱う機会が非常に多くあります。

単純な数値のカウントアップであれば基本的なfor文で十分ですが、複雑なデータ構造を扱う場合は、より直感的に記述できるforeach文が推奨されます。

それぞれの構文が持つ特性を理解することで、読みやすく、かつ実行速度の速いプログラムを構築できるようになります。

foreach文:配列とオブジェクトの処理におけるスタンダード

PHPの開発で最も頻繁に使用されるのが、このforeach文です。

配列の要素を一つずつ順番に取り出して処理を行うため、配列の要素数を事前に調べる必要がなく、書き間違いによるバグを防ぎやすいというメリットがあります。

基本的な構文と値の取り出し方

もっともシンプルなforeach文は、配列内の値を一時的な変数に代入して処理を行います。

PHP
$fruits = ['apple', 'banana', 'cherry'];

foreach ($fruits as $fruit) {
    // 各要素が$fruitに代入される
    echo $fruit . PHP_EOL;
}
実行結果
apple
banana
cherry

キーと値を同時に取得する方法

連想配列を扱う場合、データの値だけでなく、そのデータに紐づく「キー(添字)」も必要になることが多々あります。

そのような場合は、as $key => $valueという形式で記述します。

PHP
$user_ages = [
    'Tanaka' => 25,
    'Sato' => 30,
    'Suzuki' => 22
];

foreach ($user_ages as $name => $age) {
    echo "名前: {$name}、年齢: {$age}歳" . PHP_EOL;
}
実行結果
名前: Tanaka、年齢: 25歳
名前: Sato、年齢: 30歳
名前: Suzuki、年齢: 22歳

参照渡しによる値の書き換え

デフォルトのforeachでは、配列の値は「コピー」として渡されるため、ループ内で変数を書き換えても元の配列には影響しません。

もし元の配列の値を直接更新したい場合は、変数の前に&を付けることで参照渡しを利用します。

PHP
$prices = [100, 200, 300];

foreach ($prices as &$price) {
    // 全ての価格を1.1倍(税込)にする
    $price = $price * 1.1;
}

// 参照を解除する(重要)
unset($price);

print_r($prices);
実行結果
Array
(
    [0] => 110
    [1] => 220
    [2] => 330
)

参照渡しを使用した後は、必ずunset()関数で変数の参照を解除するよう注意してください。

参照を解除しないまま同じ変数名を後続の処理で再利用すると、意図しないデータの破損を招く危険性があります。

for文:繰り返しの回数が明確な場合に最適

for文は、あらかじめ決まった回数だけ処理を繰り返したい場合や、一定の規則に従って数値を変化させたい場合に適しています。

カウンタ変数の初期化、ループの継続条件、変数の更新式の3つを1行にまとめて記述するのが特徴です。

for文の基本構造

以下の例では、0から4までの数字を表示する基本的なループを作成しています。

PHP
for ($i = 0; $i < 5; $i++) {
    echo "ループ回数: " . $i . PHP_EOL;
}
実行結果
ループ回数: 0
ループ回数: 1
ループ回数: 2
ループ回数: 3
ループ回数: 4

配列の要素をインデックスで操作する

foreachではなく、あえてfor文を使って配列を操作することもあります。

例えば、配列の要素を一つ飛ばしで取得したり、特定のインデックス位置から処理を開始したりする場合に便利です。

PHP
$colors = ['red', 'blue', 'green', 'yellow', 'white'];
$count = count($colors);

// 2つ飛ばし(偶数番目)で表示する例
for ($i = 0; $i < $count; $i += 2) {
    echo $colors[$i] . PHP_EOL;
}
実行結果
red
green
white

なお、for文の条件式の中で毎回count()関数を呼び出すと、ループのたびに計算が行われパフォーマンスが低下します。

上記のように、ループの前に要素数を変数に代入しておくのがPHPにおける高速化の鉄則です。

while文とdo-while文:条件に基づいた柔軟な反復

while文は、指定した条件が「真(true)」である限り、何度でも処理を繰り返します。

回数が決まっていない処理や、外部からの入力待ち、ファイルの読み込み処理などでよく利用されます。

while文の使い方と注意点

ループの開始前に条件を判定し、条件が合わなければ一度も実行されません。

PHP
$num = 1;

while ($num <= 100) {
    echo $num . " ";
    $num *= 2; // 2倍にしていく
}
実行結果
1 2 4 8 16 32 64

while文を使用する際は、ループ内で必ず条件式がいつか偽(false)になるように設計してください。

条件が常に真のままになると、プログラムが止まらなくなる無限ループに陥り、サーバーに過大な負荷をかけてしまいます。

do-while文:必ず1回は実行したい場合

do-while文は、条件判定をループの「後」に行います。

そのため、最初の条件が偽であっても、最低1回は必ずブロック内の処理が実行されます。

PHP
$status = false;

do {
    echo "このメッセージは必ず一度表示されます。";
} while ($status);
実行結果
このメッセージは必ず一度表示されます。

ループを制御する:breakとcontinue

ループの途中で特定の条件が発生した際、その後の動きを制御するためにbreakcontinueが用意されています。

break:ループを強制終了する

breakは、現在実行中のループ自体をその場で終了させ、ループの外側の処理へ脱出します。

PHP
$numbers = [1, 2, 3, 99, 5, 6];

foreach ($numbers as $val) {
    if ($val === 99) {
        echo "異常な数値を検知したため中断します。";
        break;
    }
    echo $val . " ";
}
実行結果
1 2 3 異常な数値を検知したため中断します。

continue:現在の回をスキップして次へ進む

continueは、その回の残りの処理を飛ばし、次の繰り返しの先頭に戻ります。

PHP
for ($i = 1; $i <= 5; $i++) {
    if ($i === 3) {
        // 3の時だけ表示をスキップ
        continue;
    }
    echo $i . " ";
}
実行結果
1 2 4 5

各ループ構文の適切な使い分け基準

どのループを使うべきか迷った際は、以下の基準を参考にしてください。

構文主な用途メリット
foreach配列やオブジェクトの全件処理記述が最もシンプルで、エラーが起きにくい。
for回数指定や規則的な数値の変化カウンタ変数の制御が柔軟に行える。
while条件が満たされるまで繰り返すいつ終わるか決まっていない処理に適している。
do-while最低1回は実行が必要な処理後判定のため、最初の実行を保証できる。

実務においては、配列操作なら原則としてforeachを選択するのがベストプラクティスです。

コードが読みやすくなり、他のエンジニアが見た際にも意図が伝わりやすくなるためです。

パフォーマンスを意識したループの書き方

データ量が増大するにつれ、ループ内のわずかな処理の重さが全体の実行時間に影響を及ぼします。

ループ内で重い関数を呼ばない

先述したcount()の例もそうですが、ループの中でデータベースへのクエリ(SQL)を発行するのは極力避けましょう。

いわゆる「N+1問題」と呼ばれるパフォーマンス低下の典型的な原因となります。

データ取得はループの外で一括して行い、ループ内ではデータの加工のみに集中させるのが理想的です。

Generator(ジェネレータ)の活用

非常に巨大なデータ(数万件以上のログファイルなど)を読み込む場合、配列に全て格納するとメモリ不足(Fatal Error)を引き起こす可能性があります。

そのような場合、PHPのGeneratorを使用すると、値を一つずつ生成しながらループに渡せるため、メモリ消費を大幅に抑えることができます。

PHP
function getLargeData() {
    for ($i = 1; $i <= 1000000; $i++) {
        yield $i; // 1つずつ値を返す
    }
}

foreach (getLargeData() as $value) {
    // 100万回のループでもメモリをほとんど消費しない
    if ($value > 10) break; 
    echo $value . " ";
}

このように、モダンなPHP開発ではデータの規模に応じた技術選定も求められます。

まとめ

PHPのループ処理には複数の手法がありますが、それぞれ明確な得意分野があります。

配列の全要素を処理するならforeach、回数を指定するならfor、条件に基づくならwhileを選択することが、プログラムの可読性と安全性を高める近道です。

また、ループ内での不要な関数呼び出しを控えたり、参照渡しの後の後処理を徹底したりといった細かな配慮が、高品質なコードを生み出す秘訣となります。

今回紹介した基礎知識をベースに、実際の開発現場で状況に応じた最適なループ処理を実装してみてください。