はじめに
【WordPress×PHP】テンプレートタグの使い方と注意点では、記事のタイトルや本文を取り出す「テンプレートタグ」を解説しました。テンプレートタグは「いま取り出している記事」の情報を返しますが、その「いまの記事」を決めているのが、今回解説するループです。
この記事では、SWELLのsingle.phpと、記事一覧を表示する部品のファイルを読みながら、ループの仕組みを確認します。ローカル環境で、ループが何回まわるのか、書き間違えると何が起きるのかも試しました。
このシリーズの全15回の構成は、第1回の記事の冒頭にまとめています。
ループとは?
WordPressは、ページを表示する前に、URLに合わせて表示する記事をデータベースから取り出しています。投稿ページのURLならその記事1件、トップページなら新しい順に何件か、カテゴリーのページならそのカテゴリーの記事、という具合です。この「URLに合わせて取り出した記事の集まり」を、メインクエリと呼びます。
ループは、メインクエリで取り出した記事を、1件ずつ順番に表示するための書き方です。基本の形は次のとおりです。
<?php
while ( have_posts() ) :
the_post();
// ここに1件分の表示を書く(the_title() や the_content() など)
endwhile;
?>【PHP】for文・while文の使い方を解説で解説したwhile文の、{ }の代わりに:とendwhile;を使う書き方(代替構文)です。HTMLの中にPHPを書くテンプレートでは、この書き方がよく使われます。
SWELLのsingle.phpを読む
投稿ページのテンプレートである、SWELLのsingle.phpを見てみましょう(一部を省略しています)。
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
get_header();
while ( have_posts() ) :
the_post();
$SETTING = SWELL_Theme::get_setting();
$the_id = get_the_ID();
// …
?>
<main id="main_content" class="l-mainContent l-article">
<article class="l-mainContent__inner" data-clarity-region="article">
<?php
do_action( 'swell_before_post_head', $the_id );
// …(タイトル・アイキャッチ画像など)
?>
<div class="<?=esc_attr( apply_filters( 'swell_post_content_class', 'post_content' ) )?>">
<?php the_content(); // 本文 ?>
</div>
<!-- …(シェアボタン・関連記事・コメントなど) -->
</article>
</main>
<?php endwhile; ?>
<?php get_footer(); ?>ポイントは次のとおりです。
get_header()(ヘッダー)とget_footer()(フッター)は、ループの外にある- 記事の中身(
<main>から</main>まで)は、ループの中にある the_post()のあとで、get_the_ID()やthe_content()を使っている

投稿ページのメインクエリは、その記事1件だけです。ローカル環境で、投稿ページのループが何回まわるか数えてみると、1回でした。1件しかないのにループを使うのは、the_post()で「いまの記事」を決めてからテンプレートタグを使う、という決まった手順があるためです。
第5回の実務サンプルで使ったswell_before_post_headフックも、このループの中で呼ばれています。
have_posts()とthe_post()の役割
ループで使う2つの関数の役割は、次のとおりです。
| 関数 | 役割 | 戻り値 |
|---|---|---|
have_posts() | 次に取り出す記事が残っているか調べる | 残っていればtrue、なければfalse |
the_post() | 次の記事を取り出し、「いまの記事」に設定する | なし |
the_post()を実行すると、テンプレートタグが指す記事が、次の記事に切り替わります。そのため、the_post()のあとでthe_title()やget_the_ID()を使うと、取り出した記事の情報が得られます。

ローカル環境で、記事を13件用意し、トップページ(1ページに10件表示)のループの動きを記録してみました。current_postは、WordPressが「いま何件目か」を数えている番号です(0から始まります)。
have_posts()=true → the_post()後 current_post=0 タイトル=Hello world!
have_posts()=true → the_post()後 current_post=1 タイトル=記事12
have_posts()=true → the_post()後 current_post=2 タイトル=記事11
(…中略…)
have_posts()=true → the_post()後 current_post=9 タイトル=記事04
have_posts()=false
the_post()を実行するたびに番号が1つ進み、タイトルが次の記事に変わっています。10件目を取り出したあと、have_posts()がfalseになってループが終わりました。
ページごとのループの回数は、次のとおりでした。
| ページ | 取り出した記事 | ループの回数 |
|---|---|---|
| 投稿ページ(記事01) | 1件 | 1回 |
| トップページ | 13件中10件 | 10回 |
| トップページの2ページ目 | 13件中3件 | 3回 |
1ページに表示する件数は、管理画面の「設定」→「表示設定」にある「1ページに表示する最大投稿数」で決まります(初期値は10件)。メインクエリは、URLとこの設定をもとに、そのページで表示する分だけの記事を取り出しています。
一覧ページのループ:SWELLのloop_main.php
トップページやカテゴリーのページなど、記事の一覧を表示するページでは、SWELLはparts/post_list/loop_main.phpという部品のファイルでループを書いています(一部を省略しています)。
// 記事がなかった場合
if ( ! have_posts() ) :
$not_founded_text = __( '記事が見つかりませんでした。', 'swell' );
echo apply_filters( 'swell_post_list_404_text', '<p>' . $not_founded_text . '</p>' );
return;
endif;
// ループ
$loop_ct = 0;
echo '<ul class="' . esc_attr( $ul_class ) . '">';
while ( have_posts() ) :
the_post();
// インフィード
if ( $infeed_interval && $loop_ct && ( 0 === $loop_ct % $infeed_interval ) ) :
SWELL_Theme::get_parts( 'parts/post_list/infeed_ad', $loop_ct );
endif;
SWELL_Theme::get_parts( 'parts/post_list/' . $parts_name, $li_args );
$loop_ct++;
endwhile;
echo '</ul>';読み取れることは、次のとおりです。
- ループの前に
have_posts()で記事があるか調べ、1件もなければ「記事が見つかりませんでした。」と表示して終わる <ul>と</ul>はループの外で1回だけ出力し、記事1件分(<li>)の表示は、ループの中で別の部品ファイルを読み込んで出力している$loop_ctで、何件表示したかを自分で数えている$loop_ct % $infeed_interval(余りの計算)が0になるたびに、広告(インフィード広告)を差し込んでいる
最後の仕組みは、SWELL設定の「広告コード」タブにある「インフィード広告の間隔」で使われています。たとえば間隔を4にすると、4件ごとに広告が入ります。ループの中で件数を数えれば、「○件ごとに何かを入れる」という処理が書けるわけです。
よくある間違い:the_post()を書き忘れる
ループで一番多い間違いが、the_post()の書き忘れです。
while ( have_posts() ) :
// the_post(); を書き忘れた
the_title();
endwhile;have_posts()は「次の記事が残っているか」を調べるだけなので、the_post()で記事を取り出さない限り、ずっとtrueのままです。つまり、ループが終わらなくなります(無限ループ)。
ローカル環境で、5回で強制的に止める仕組みを付けて試したところ、次のようになりました。
1回目 Hello world! current_post=-1
2回目 Hello world! current_post=-1
3回目 Hello world! current_post=-1
4回目 Hello world! current_post=-1
5回目 Hello world! current_post=-1
番号は-1(まだ1件も取り出していない状態)のまま、同じ記事のタイトルが繰り返されています。

強制的に止める仕組みを外すと、ページが読み込み中のまま止まり、PHPの実行時間の上限に達したところで、次のエラーになりました(上限を5秒にして試しました)。
Fatal error: Maximum execution time of 5 seconds exceeded in …
Localの初期設定では、この上限が1200秒(20分)です。「ページがいつまでも表示されない」ときは、the_post()が抜けていないか確認しましょう。
ループの前やループの後でもテンプレートタグは動きますが、指す記事に注意が必要です。試したところ、ループの前のget_the_title()は1件目の記事(トップページでは「Hello world!」)、ループの後は最後に取り出した記事(「記事04」)のタイトルを返しました。テンプレートタグは、ループの中で使うのが基本です。
実務サンプル:一覧ページに「全○件中 ○〜○件目を表示」を出す
ここまでの内容を使って、記事一覧の上に「全13件中 11〜13件目を表示」のような件数を表示してみましょう。
SWELLの部品ファイルを子テーマにコピーする
SWELLは、部品ファイルを読み込むとき、子テーマに同じ場所・同じ名前のファイルがあれば、そちらを優先する仕組みになっています。第1回で解説した、子テーマのsingle.phpが親テーマより優先されるのと同じ考え方です。
そこで、親テーマの次のファイルを、子テーマの同じ場所にコピーします。
(コピー元)wp-content/themes/swell/parts/post_list/loop_main.php
(コピー先)wp-content/themes/swell_child/parts/post_list/loop_main.php
子テーマにはpartsフォルダもpost_listフォルダもないので、フォルダを作ってからコピーします。
件数を表示するコードを追加する
コピーしたloop_main.phpの// ループの行の直前に、次のコードを追加します。
// 件数の表示(子テーマで追加)
global $wp_query;
$total = (int) $wp_query->found_posts;
$per_page = (int) get_query_var( 'posts_per_page' );
$paged = max( 1, (int) get_query_var( 'paged' ) );
$first = ( $paged - 1 ) * $per_page + 1;
$last = $first + $wp_query->post_count - 1;
echo '<p class="kurumico-list-count">全' . $total . '件中 ' . $first . '〜' . $last . '件目を表示</p>';子テーマのstyle.cssには、次のCSSを追加します。
.kurumico-list-count {
margin-bottom: 1em;
font-size: .9em;
color: #666;
}トップページの2ページ目を開くと、記事一覧の上に次のように表示されました。

1ページ目は「全13件中 1〜10件目を表示」、検索結果のページでは「全9件中 1〜9件目を表示」となりました。記事が1件もない場合は、追加したコードより前のreturnで処理が終わるので、件数は表示されません。
コードのポイントは次のとおりです。
$wp_queryは、メインクエリの情報が入った変数。関数の外で作られた変数なので、globalを付けて使う(->の書き方は第8回で解説します)found_postsは全部の件数(13件)、post_countはこのページで取り出した件数(3件)get_query_var( 'paged' )は、いま何ページ目か。1ページ目では0になるので、max( 1, … )で1にそろえている- 「何件目から」は
(ページ番号 − 1)× 1ページの件数 + 1で計算している
子テーマにコピーした部品ファイルは、親テーマのファイルの代わりに使われ続けます。SWELLのアップデートでloop_main.phpが改良されても、子テーマ側には反映されません。アップデートのあとは、親テーマのファイルと見比べて、必要なら子テーマ側にコピーし直してください。
よくある質問・エラー対処
Q. 1ページに表示する件数を変えたいです
→ 管理画面の「設定」→「表示設定」にある「1ページに表示する最大投稿数」で変えられます。実務サンプルのコードは、この設定値を使って計算しているので、件数を変えても正しく表示されます。
Q. 子テーマにコピーした部品ファイルを使わないように戻したいです
→ 子テーマのparts/post_list/loop_main.phpを削除すれば、親テーマのファイルが使われる状態に戻ります。
Q. ループの中で、別の記事の一覧も表示したいです
→ メインクエリとは別に記事を取り出す方法(WP_Query)を使います。第11回で解説します。
おわりに(まとめ)
ループは、WordPressのテンプレートでいちばん基本になる仕組みです。
- ✅ メインクエリは、URLに合わせてWordPressが取り出した記事の集まり
- ✅ ループは、メインクエリの記事を1件ずつ表示する書き方
- ✅ have_posts()は次の記事があるか調べ、the_post()は次の記事を「いまの記事」にする
- ✅ 投稿ページでもループを使い、1回だけまわる
- ✅ 1ページの件数は「設定」→「表示設定」で決まる
- ✅ the_post()を書き忘れると無限ループになる
- ✅ SWELLの部品ファイルは、子テーマにコピーすれば書き換えられる
次回は、ページの種類によって処理を分ける「条件分岐タグ」について解説します。
次回は、条件分岐タグ(is_single・is_page など)について解説予定です。
この記事が、少しでも誰かのお役に立てれば幸いです。
関連記事
※1回買えば複数サイトで使い回しOK
当サイトの記事で使用したVBAなどのサンプルをDLできます
この記事のサンプルはありません!
ダウンロードページへは下のカードをクリックすればジャンプできます。
よろしければご利用ください!


