「前回」「元の説明」など、比較対象を示す表現を取り除き、単独で読める文章に整えました。
PHPのループ処理を正しく理解する
PHPのループ処理には、for、while、do...while、foreachなどがあります。
これらは、同じ処理を繰り返したい場合に使用します。
たとえば、次のような処理に適しています。
- 1から10までの数字を表示する
- 配列のデータを1件ずつ処理する
- 商品一覧や記事一覧をHTMLへ出力する
- 条件を満たすまで処理を続ける
- データベースから取得した複数行を順番に処理する
PHPのループ構文は、目的によって使い分けることが重要です。
for文
for文は、繰り返す回数があらかじめ分かっている場合に適しています。
for文の基本構文
for (初期化; 継続条件; 更新処理) {
繰り返す処理;
}
1から5まで表示する場合は、次のように書きます。
for ($i = 1; $i <= 5; $i++) {
echo $i . "<br>";
}
出力結果は次のとおりです。
1
2
3
4
5
for文の処理順
for文は、次の順番で処理されます。
- 初期化処理を実行する
- 継続条件を確認する
- 条件が真ならループ内を実行する
- 更新処理を実行する
- 再び継続条件を確認する
次のコードでは、最初に$i = 1が実行されます。
for ($i = 1; $i <= 5; $i++) {
echo $i;
}
その後、$i <= 5が成立している間、ループ内の処理が繰り返されます。
インクリメント演算子
$i++は、変数の値を1増やす演算子です。
$i++;
カウンターの更新処理として単独で使用する場合は、次のコードと同じ結果になります。
$i = $i + 1;
ただし、式の戻り値を使用する場合は動作が異なります。
$i = 1;
$result = $i++;
echo $result; // 1
echo $i; // 2
後置インクリメントでは、増加前の値が式の結果として使われ、その後に変数の値が増加します。
0から始めるfor文
プログラミングでは、カウンターを0から始めることもよくあります。
for ($i = 0; $i < 5; $i++) {
echo $i . "<br>";
}
出力結果は次のとおりです。
0
1
2
3
4
次の2つはいずれも5回繰り返します。
for ($i = 1; $i <= 5; $i++) {
// 5回実行
}
for ($i = 0; $i < 5; $i++) {
// 5回実行
}
ただし、開始値と実際に扱う数値は異なります。
数値を減らしながら繰り返す
for文では、数値を減らしながら処理することもできます。
for ($i = 5; $i >= 1; $i--) {
echo $i . "<br>";
}
出力結果は次のとおりです。
5
4
3
2
1
$i--は、変数の値を1減らす処理です。
数値を2ずつ増やす
更新処理は、1ずつ増減させる必要はありません。
for ($i = 0; $i <= 10; $i += 2) {
echo $i . "<br>";
}
出力結果は次のとおりです。
0
2
4
6
8
10
for文で配列を処理する
0から始まる連続した数値キーを持つ配列であれば、for文で処理できます。
$fruits = ["りんご", "みかん", "バナナ"];
for ($i = 0; $i < count($fruits); $i++) {
echo $fruits[$i] . "<br>";
}
ただし、PHPの配列キーは必ずしも0から連続しているとは限りません。
$fruits = [
2 => "りんご",
5 => "みかん",
9 => "バナナ",
];
この配列に対して、次のコードを使用すると、存在しないキーへアクセスしてしまいます。
for ($i = 0; $i < count($fruits); $i++) {
echo $fruits[$i];
}
配列の全要素を処理する場合は、通常foreachのほうが適しています。
while文
while文は、指定した条件が真である間、処理を繰り返します。
$i = 1;
while ($i <= 5) {
echo $i . "<br>";
$i++;
}
出力結果は次のとおりです。
1
2
3
4
5
while文の特徴
while文は、ループ内の処理を実行する前に条件を確認します。
そのため、最初から条件が偽の場合は、一度も実行されません。
$i = 10;
while ($i <= 5) {
echo $i;
}
このコードでは、$i <= 5が最初から偽なので、ループ内は実行されません。
更新処理を忘れた場合
次のコードでは、$iの値が変化しません。
$i = 1;
while ($i <= 5) {
echo $i . "<br>";
}
$iは常に1のままなので、条件は永遠に真になります。
正しくは、ループ内で値を更新します。
$i = 1;
while ($i <= 5) {
echo $i . "<br>";
$i++;
}
do…while文
do...while文は、ループ内の処理を実行した後で条件を確認します。
そのため、条件が最初から偽でも、最低1回は処理されます。
$i = 10;
do {
echo $i;
$i++;
} while ($i <= 5);
出力結果は次のとおりです。
10
while文との違い
while文は、処理の前に条件を確認します。
$i = 10;
while ($i <= 5) {
echo $i;
}
このコードは一度も実行されません。
一方、do...while文は、処理後に条件を確認します。
$i = 10;
do {
echo $i;
} while ($i <= 5);
このコードは1回だけ実行されます。
do...while文では、末尾にセミコロンが必要です。
} while ($condition);
foreach文
foreach文は、配列や反復可能なオブジェクトを1要素ずつ処理するための構文です。
$fruits = ["りんご", "みかん", "バナナ"];
foreach ($fruits as $fruit) {
echo $fruit . "<br>";
}
出力結果は次のとおりです。
りんご
みかん
バナナ
ループするたびに、現在の配列要素が$fruitへ代入されます。
foreachでキーと値を取得する
キーと値の両方を取得する場合は、次のように書きます。
$user = [
"name" => "田中",
"age" => 30,
"job" => "エンジニア",
];
foreach ($user as $key => $value) {
echo $key . ": " . $value . "<br>";
}
出力結果は次のとおりです。
name: 田中
age: 30
job: エンジニア
キーが不要な場合は、省略できます。
foreach ($user as $value) {
echo $value . "<br>";
}
配列キーと表示順の違い
次のコードは、配列キーが0から連続している場合には正しく動作します。
$fruits = ["りんご", "みかん", "バナナ"];
foreach ($fruits as $index => $fruit) {
echo ($index + 1) . ". " . $fruit . "<br>";
}
出力結果は次のとおりです。
1. りんご
2. みかん
3. バナナ
ただし、配列キーが連続していない場合は、表示番号として利用できません。
$fruits = [
10 => "りんご",
20 => "みかん",
];
この配列に対して、次の処理を実行します。
foreach ($fruits as $index => $fruit) {
echo ($index + 1) . ". " . $fruit . "<br>";
}
出力結果は次のとおりです。
11. りんご
21. みかん
表示用の連番を付ける方法
確実に1から始まる連番を表示したい場合は、独立したカウンターを使います。
$position = 1;
foreach ($fruits as $fruit) {
echo $position . ". ";
echo htmlspecialchars(
$fruit,
ENT_QUOTES | ENT_SUBSTITUTE,
"UTF-8"
);
echo "<br>";
$position++;
}
配列キーと画面上の表示順は、別のものとして扱うのが安全です。
foreachとis_iterableの関係
is_iterable()は、値が次のいずれかであるかを確認する関数です。
- 配列
Traversableを実装したオブジェクト
if (is_iterable($items)) {
foreach ($items as $item) {
echo $item;
}
}
ただし、is_iterable()がfalseでも、通常のオブジェクトをforeachできる場合があります。
$user = new stdClass();
$user->name = "田中";
$user->age = 30;
foreach ($user as $key => $value) {
echo $key . ": " . $value . "<br>";
}
このオブジェクトはforeachで処理できますが、is_iterable()の結果はfalseです。
var_dump(is_iterable($user));
出力結果は次のとおりです。
bool(false)
is_iterable()は、すべてのforeach可能な値を判定する関数ではありません。
配列またはTraversableを実装したオブジェクトかどうかを判定する関数です。
iterable型を使用する
関数の引数をiterable型として定義すると、配列またはTraversableだけを受け取る設計にできます。
function displayItems(iterable $items): void
{
foreach ($items as $item) {
echo $item;
}
}
オブジェクトをforeachする
通常のオブジェクトにforeachを使用すると、現在のスコープからアクセスできるプロパティが対象になります。
class User
{
public string $name = "田中";
protected int $age = 30;
private string $password = "secret";
}
$user = new User();
foreach ($user as $key => $value) {
echo $key . ": " . $value . "<br>";
}
クラスの外部から実行した場合、基本的にはpublicプロパティだけが対象になります。
IteratorAggregateを使用する
独自クラスで反復処理を定義する場合は、IteratorやIteratorAggregateを利用できます。
class ProductCollection implements IteratorAggregate
{
public function __construct(
private array $products
) {
}
public function getIterator(): Traversable
{
return new ArrayIterator($this->products);
}
}
使用例は次のとおりです。
$collection = new ProductCollection([
"商品A",
"商品B",
"商品C",
]);
foreach ($collection as $product) {
echo htmlspecialchars(
$product,
ENT_QUOTES | ENT_SUBSTITUTE,
"UTF-8"
);
echo "<br>";
}
このようなオブジェクトは、is_iterable()でもtrueになります。
break
breakは、現在実行しているループを途中で終了します。
for ($i = 1; $i <= 10; $i++) {
if ($i === 5) {
break;
}
echo $i . "<br>";
}
出力結果は次のとおりです。
1
2
3
4
$iが5になった時点でループが終了するため、5は表示されません。
配列検索でbreakを使う
$users = ["佐藤", "田中", "鈴木", "高橋"];
foreach ($users as $user) {
if ($user === "鈴木") {
echo "鈴木さんが見つかりました";
break;
}
}
目的の値が見つかった後は、それ以上ループを続ける必要がないため、breakが適しています。
break 2
ネストしたループでbreakを使うと、通常は現在のループだけを終了します。
for ($i = 1; $i <= 3; $i++) {
for ($j = 1; $j <= 3; $j++) {
if ($j === 2) {
break;
}
echo "{$i}-{$j}<br>";
}
}
出力結果は次のとおりです。
1-1
2-1
3-1
複数階層のループを終了する場合は、break 2を使用します。
for ($i = 1; $i <= 3; $i++) {
for ($j = 1; $j <= 3; $j++) {
if ($i === 2 && $j === 2) {
break 2;
}
echo "{$i}-{$j}<br>";
}
}
break 2は、内側と外側の2階層のループを終了します。
continue
continueは、現在の反復処理の残りをスキップし、次の反復へ進みます。
for ($i = 1; $i <= 5; $i++) {
if ($i === 3) {
continue;
}
echo $i . "<br>";
}
出力結果は次のとおりです。
1
2
4
5
3のときだけ、echoが実行されません。
条件に合わないデータを除外する
$users = [
["name" => "田中", "active" => true],
["name" => "佐藤", "active" => false],
["name" => "鈴木", "active" => true],
];
foreach ($users as $user) {
if (!$user["active"]) {
continue;
}
echo $user["name"] . "<br>";
}
activeがfalseのユーザーだけがスキップされます。
continue 2
continue 2は、2階層目の外側ループの次の反復へ進みます。
for ($i = 1; $i <= 3; $i++) {
for ($j = 1; $j <= 3; $j++) {
if ($j === 2) {
continue 2;
}
echo "{$i}-{$j}<br>";
}
}
出力結果は次のとおりです。
1-1
2-1
3-1
$jが2になった時点で、外側のfor文の次の反復へ進みます。
break 2との違い
break 2は、2階層分のループを終了します。
continue 2は、2階層目のループの次の反復へ進みます。
処理の流れが分かりにくくなりやすいため、複雑な多重ループでは多用を避けるのが無難です。
foreachで配列の値を変更する
通常のforeachでは、ループ変数を変更しても元の配列は変わりません。
$numbers = [1, 2, 3];
foreach ($numbers as $number) {
$number *= 10;
}
print_r($numbers);
出力結果は次のとおりです。
Array
(
[0] => 1
[1] => 2
[2] => 3
)
参照付きforeach
元の配列を直接変更するには、参照を使います。
$numbers = [1, 2, 3];
foreach ($numbers as &$number) {
$number *= 10;
}
unset($number);
print_r($numbers);
出力結果は次のとおりです。
Array
(
[0] => 10
[1] => 20
[2] => 30
)
参照付きforeachの後にunsetする理由
参照付きforeachが終了した後も、ループ変数は配列の最後の要素を参照し続けます。
foreach ($numbers as &$number) {
$number *= 10;
}
参照関係を解除するために、次の処理を行います。
unset($number);
このunset()は、配列の最後の要素を削除するものではありません。
ループ変数と配列要素の参照関係を解除します。
キーを使って配列を変更する
参照を使わず、配列キーを使って値を更新することもできます。
$numbers = [1, 2, 3];
foreach ($numbers as $key => $number) {
$numbers[$key] = $number * 10;
}
print_r($numbers);
出力結果は次のとおりです。
Array
(
[0] => 10
[1] => 20
[2] => 30
)
参照の挙動に慣れていない場合は、キーを使って更新する方法のほうが理解しやすい場合があります。
ループ中に配列を変更する
foreach中に元の配列を変更すること自体は可能です。
たとえば、条件に合う要素を削除できます。
$numbers = [1, 2, 3, 4, 5];
foreach ($numbers as $key => $number) {
if ($number % 2 === 0) {
unset($numbers[$key]);
}
}
print_r($numbers);
出力結果は次のとおりです。
Array
(
[0] => 1
[2] => 3
[4] => 5
)
偶数は削除されていますが、配列キーは自動的に詰め直されません。
array_valuesでキーを振り直す
数値キーを0から振り直したい場合は、array_values()を使います。
$numbers = array_values($numbers);
出力結果は次のようになります。
Array
(
[0] => 1
[1] => 3
[2] => 5
)
array_filterで配列を絞り込む
条件に合う要素だけを残したい場合は、array_filter()を使用できます。
$numbers = [1, 2, 3, 4, 5];
$oddNumbers = array_filter(
$numbers,
fn(int $number): bool => $number % 2 !== 0
);
$oddNumbers = array_values($oddNumbers);
array_filter()は元のキーを保持するため、連番へ戻す場合はarray_values()を使います。
空配列をforeachする
空配列をforeachしても、エラーにはなりません。
$products = [];
foreach ($products as $product) {
echo $product["name"];
}
配列が空なので、ループ内は一度も実行されません。
データがない場合の表示
<?php if ($products !== []): ?>
<ul>
<?php foreach ($products as $product): ?>
<li>
<?= htmlspecialchars(
$product["name"],
ENT_QUOTES | ENT_SUBSTITUTE,
"UTF-8"
) ?>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p>商品がありません。</p>
<?php endif; ?>
emptyを使う場合の注意点
次のコードでも、配列が空かどうかを判定できます。
if (!empty($products)) {
foreach ($products as $product) {
// 処理
}
}
ただし、empty()は空配列だけでなく、次のような値にもtrueを返します。
null
false
0
"0"
""
[]
変数を配列として扱う場合は、最初から空配列で初期化しておくと安全です。
$products = $data["products"] ?? [];
PHPの代替構文
PHPでは、HTMLと組み合わせる場合に、波括弧の代わりにコロン形式の代替構文を使えます。
foreachの代替構文
<?php foreach ($items as $item): ?>
<p>
<?= htmlspecialchars(
$item,
ENT_QUOTES | ENT_SUBSTITUTE,
"UTF-8"
) ?>
</p>
<?php endforeach; ?>
forの代替構文
<?php for ($i = 1; $i <= 5; $i++): ?>
<p><?= $i ?></p>
<?php endfor; ?>
whileの代替構文
<?php $i = 1; ?>
<?php while ($i <= 5): ?>
<p><?= $i ?></p>
<?php $i++; ?>
<?php endwhile; ?>
WordPressテーマやPHPテンプレートでは、よく使用される書き方です。
HTMLへ出力する場合のエスケープ
外部入力やデータベースから取得した文字列をHTMLへ表示する場合は、適切なエスケープが必要です。
htmlspecialchars(
$value,
ENT_QUOTES | ENT_SUBSTITUTE,
"UTF-8"
);
ENT_QUOTESは、シングルクォートとダブルクォートを変換します。
ENT_SUBSTITUTEは、不正な文字列を代替文字へ置き換えます。
HTMLテンプレートでの使用例
<?= htmlspecialchars(
$product["name"],
ENT_QUOTES | ENT_SUBSTITUTE,
"UTF-8"
) ?>
プログラム内部で生成した整数などは、通常そのまま出力できます。
echo $i . "<br>";
JavaScript、URL、CSSなどへ値を埋め込む場合は、HTMLとは異なる処理が必要です。
array_map
array_map()は、配列の各要素を変換して新しい配列を作成します。
$numbers = [1, 2, 3];
$doubled = array_map(
fn(int $number): int => $number * 2,
$numbers
);
print_r($doubled);
出力結果は次のとおりです。
Array
(
[0] => 2
[1] => 4
[2] => 6
)
同じ処理をforeachで書くこともできます。
$doubled = [];
foreach ($numbers as $number) {
$doubled[] = $number * 2;
}
array_reduce
array_reduce()は、配列を1つの値へ集約する関数です。
$numbers = [1, 2, 3, 4, 5];
$total = array_reduce(
$numbers,
fn(int $carry, int $number): int => $carry + $number,
0
);
echo $total;
出力結果は次のとおりです。
15
単純な合計であれば、array_sum()のほうが簡潔です。
$total = array_sum($numbers);
countをループ外へ出す
次のコードでは、条件判定のたびにcount()が呼び出されます。
for ($i = 0; $i < count($items); $i++) {
// 処理
}
要素数を先に変数へ保存することもできます。
$count = count($items);
for ($i = 0; $i < $count; $i++) {
// 処理
}
通常の小さな配列では、性能差を過度に意識する必要はありません。
読みやすさや処理の意味を優先することが重要です。
配列サイズを変更する場合
ループ中に配列へ要素を追加する場合、count()を外へ出すかどうかで挙動が変わります。
$count = count($items);
for ($i = 0; $i < $count; $i++) {
$items[] = "追加";
}
この場合、ループ回数は最初の要素数で固定されます。
一方、次のコードでは、条件判定のたびに配列の要素数が増えます。
for ($i = 0; $i < count($items); $i++) {
$items[] = "追加";
}
終了条件も増え続けるため、処理が終了しなくなる可能性があります。
PDOのfetchAll
fetchAll()は、取得結果をまとめてPHPの配列として返します。
$sql = "SELECT id, title FROM articles ORDER BY id DESC";
$stmt = $pdo->query($sql);
$articles = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($articles as $article) {
echo htmlspecialchars(
$article["title"],
ENT_QUOTES | ENT_SUBSTITUTE,
"UTF-8"
);
echo "<br>";
}
取得件数が少ない場合は、扱いやすい方法です。
PDOのfetch
fetch()は、結果セットから次の1行を取得します。
$sql = "SELECT id, title FROM articles ORDER BY id DESC";
$stmt = $pdo->query($sql);
while (($article = $stmt->fetch(PDO::FETCH_ASSOC)) !== false) {
echo htmlspecialchars(
$article["title"],
ENT_QUOTES | ENT_SUBSTITUTE,
"UTF-8"
);
echo "<br>";
}
取得できる行がなくなると、fetch()はfalseを返します。
fetchAllとfetchの使い分け
fetchAll()は、全行をPHPの配列として保持します。
そのため、大量データではメモリ使用量が大きくなる可能性があります。
fetch()で1行ずつ処理すると、PHP側で全行分の巨大な配列を作らずに済みます。
ただし、実際のメモリ使用量は、PDOドライバーやバッファリング設定にも影響されます。
PDOのエラー処理
本番環境では、PDOを例外モードに設定する方法があります。
$pdo = new PDO($dsn, $user, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
設定後は、次のように処理できます。
$sql = "SELECT id, title FROM articles ORDER BY id DESC";
$stmt = $pdo->query($sql);
while (($article = $stmt->fetch()) !== false) {
echo htmlspecialchars(
$article["title"],
ENT_QUOTES | ENT_SUBSTITUTE,
"UTF-8"
);
echo "<br>";
}
ループ内のSQLとN+1問題
ループ内で毎回SQLを実行すると、問い合わせ回数が増えることがあります。
foreach ($products as $product) {
// 商品ごとに関連データを取得するSQLを実行
}
最初の商品一覧取得が1回で、その後100商品について1回ずつSQLを実行すると、合計101回の問い合わせになります。
このような構造は、N+1問題と呼ばれます。
可能な場合は、JOINや一括取得を検討します。
SELECT
products.id,
products.name,
categories.name AS category_name
FROM products
LEFT JOIN categories
ON categories.id = products.category_id
ただし、ループ内でSQLを実行することが常に誤りとは限りません。
データ量、処理内容、キャッシュ、データベース設計などを考慮して判断します。
無限ループ
次のコードは無限ループになります。
while (true) {
// 繰り返し処理
}
for文でも無限ループを書けます。
for (;;) {
// 繰り返し処理
}
無限ループは、常駐処理やキューワーカーなどで意図的に使われる場合があります。
待機処理を入れる
待機せずにループし続けると、CPU負荷が高くなる可能性があります。
while (true) {
processQueue();
sleep(1);
}
常駐処理では、次のような設計も必要です。
- 終了条件
- 例外処理
- ログ出力
- メモリ管理
- 再起動方法
- 実行時間や処理回数の上限
- 終了シグナルへの対応
通常のWebリクエスト内で、終了条件のないループを実行するのは避けるべきです。
ループ構文の使い分け
forが向いている場面
for文は、繰り返す回数が決まっている場合に適しています。
for ($i = 0; $i < 10; $i++) {
// 処理
}
主な用途は次のとおりです。
- 指定回数繰り返す
- 数値を一定間隔で増減させる
- インデックスを使う
- 逆順で処理する
whileが向いている場面
while文は、回数ではなく条件によって終了する場合に適しています。
while ($condition) {
// 処理
}
主な用途は次のとおりです。
- 条件を満たすまで繰り返す
- データを1件ずつ取得する
- 終了回数が事前に分からない
do…whileが向いている場面
do...while文は、最低1回は処理を実行したい場合に適しています。
do {
// 処理
} while ($condition);
foreachが向いている場面
foreach文は、配列や反復可能なオブジェクトを処理する場合に適しています。
foreach ($items as $item) {
// 処理
}
PHPで配列を1件ずつ処理する場面では、非常によく使用されます。
よくある間違い
終了条件にイコールを付け忘れる
for ($i = 1; $i < 5; $i++) {
echo $i;
}
このコードでは、5は含まれません。
5まで含める場合は、次のようにします。
for ($i = 1; $i <= 5; $i++) {
echo $i;
}
while文で変数を更新し忘れる
$i = 1;
while ($i <= 5) {
echo $i;
}
このコードは無限ループになります。
正しくは、ループ内で値を更新します。
$i = 1;
while ($i <= 5) {
echo $i;
$i++;
}
代入演算子と比較演算子を間違える
次は代入です。
$i = 5;
次は厳密比較です。
$i === 5;
条件式で誤って代入すると、意図しない結果になります。
if ($i = 5) {
// 比較ではなく、5を代入している
}
正しく比較する場合は、次のようにします。
if ($i === 5) {
// $iが整数の5か確認する
}
foreachの変数名を間違える
次のコードでは、配列全体の$productsへアクセスしています。
foreach ($products as $product) {
echo $products["name"];
}
ループ内では、現在の要素である$productを使います。
foreach ($products as $product) {
echo $product["name"];
}
参照を解除し忘れる
参照付きforeachの後は、ループ変数の参照を解除します。
foreach ($numbers as &$number) {
$number *= 2;
}
unset($number);
まとめ
PHPの代表的なループ構文は、次の4種類です。
| 構文 | 主な用途 |
|---|---|
for | 回数が決まっている処理 |
while | 条件が真である間の処理 |
do...while | 最低1回は実行する処理 |
foreach | 配列やオブジェクトの反復処理 |
ループを制御する命令は、主に次の2つです。
| 命令 | 動作 |
|---|---|
break | ループを途中で終了する |
continue | 現在の反復を飛ばして次へ進む |
PHPで配列を処理する場合は、基本的にforeachが使いやすい構文です。
foreach ($items as $item) {
// 配列の要素を1件ずつ処理する
}
HTMLテンプレートでは、代替構文を使用すると構造が分かりやすくなります。
<?php foreach ($items as $item): ?>
<p>
<?= htmlspecialchars(
$item,
ENT_QUOTES | ENT_SUBSTITUTE,
"UTF-8"
) ?>
</p>
<?php endforeach; ?>
ループ処理では、構文を覚えるだけでなく、終了条件、配列キー、参照、エスケープ、データ件数なども考慮することが重要です。
以上、PHPのループ処理についてでした。
最後までお読みいただき、ありがとうございました。










