WordPressループとは、データベースから取得した投稿データを1件ずつ処理し、ページに表示するための繰り返し処理の仕組みです。have_posts()で投稿の有無を判定し、the_post()で現在の投稿データをセットすることで、テンプレートタグを使った柔軟な表示が可能になります。
この記事では、WordPressループの基本構造からサブループの作り方、pre_get_postsによるメインクエリのカスタマイズまで、実践的なコード例とともに解説します。WordPressテーマ開発の基盤となる知識を体系的に身につけましょう。
WordPressテーマ全体の仕組みについては、WordPressテーマのテンプレートファイル一覧と役割で詳しく解説しています。
WordPressループとは?
WordPressループとは、WordPressがデータベースから取得した投稿データを1件ずつ順番に処理するための仕組みです。すべてのWordPressテーマは、記事の一覧ページも個別記事ページも、このループを使って投稿内容を表示しています。
メインクエリとループの関係
WordPressはURLを解析して、表示するべきコンテンツを自動的にデータベースから取得します。この自動取得の仕組みが「メインクエリ」です。メインクエリで取得された投稿データは、グローバル変数$wp_queryに格納され、ループで順番に処理されます。
たとえば、トップページにアクセスすると最新の投稿が10件取得され、カテゴリページにアクセスするとそのカテゴリに属する投稿が取得されます。開発者がSQLを書く必要はなく、WordPressが適切なクエリを自動実行してくれます。
have_posts() と the_post() の役割
ループの中核を担う2つの関数を理解しましょう。
have_posts()は、まだ表示していない投稿が残っているかどうかをtrue/falseで返す関数です。ループの継続条件として使います。投稿が残っていればtrueを返し、すべて処理し終えるとfalseを返してループが終了します。
the_post()は、内部カウンターを1つ進めて、次の投稿データをグローバル変数$postにセットする関数です。これにより、ループ内でthe_title()やthe_content()などのテンプレートタグが正しいデータを参照できるようになります。
メインループの基本構造
WordPressループの基本形は、if文で投稿の存在を確認し、while文で1件ずつ処理する構造です。以下がもっとも基本的なテンプレートコードです。
<?php if ( have_posts() ) : ?>
<?php while ( have_posts() ) : the_post(); ?>
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
<div class="entry-content">
<?php the_excerpt(); ?>
</div>
</article>
<?php endwhile; ?>
<?php else : ?>
<p>投稿が見つかりませんでした。</p>
<?php endif; ?>この構造を分解すると、以下の4ステップで処理が進みます。
if ( have_posts() )— 投稿が1件以上あるか確認するwhile ( have_posts() )— 未処理の投稿がある限りループを続けるthe_post()— 次の投稿データを$postにセットする- テンプレートタグで表示 —
the_title()等で投稿内容を出力する
else節は投稿が0件のときに実行されます。検索結果が見つからなかった場合や、カテゴリに投稿が存在しない場合に「投稿が見つかりません」のようなメッセージを表示します。
個別投稿ページでのループ
個別投稿ページ(single.php)でも同じループ構造を使います。メインクエリが1件の投稿のみを返すため、ループは1回だけ実行されます。
<?php if ( have_posts() ) : ?>
<?php while ( have_posts() ) : the_post(); ?>
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<h1><?php the_title(); ?></h1>
<?php if ( has_post_thumbnail() ) : ?>
<div class="post-thumbnail">
<?php the_post_thumbnail( 'large' ); ?>
</div>
<?php endif; ?>
<div class="entry-content">
<?php the_content(); ?>
</div>
<div class="entry-meta">
<span>公開日: <?php the_date(); ?></span>
<span>著者: <?php the_author(); ?></span>
</div>
</article>
<?php endwhile; ?>
<?php endif; ?>一覧ページと個別ページで同じhave_posts() / the_post()の構造を使う点がWordPressループの統一的な特徴です。テンプレートファイルの種類が変わっても、ループの書き方は同じです。
ループ内で使えるテンプレートタグ一覧
ループ内では、the_post()によってセットされた投稿データを、テンプレートタグで取得・表示できます。以下に、よく使うテンプレートタグをまとめます。
| テンプレートタグ | 出力内容 | 使用例 |
|---|---|---|
the_title() | 投稿タイトル | 記事見出し・リンクテキスト |
the_content() | 投稿本文(全文) | 個別記事ページ |
the_excerpt() | 抜粋文(110文字) | 一覧ページのプレビュー |
the_permalink() | 投稿のURL | リンクのhref属性 |
the_post_thumbnail() | アイキャッチ画像 | 記事カードのサムネイル |
the_date() | 投稿日(同日の初回のみ表示) | 日付表示 |
get_the_date() | 投稿日(常に取得) | 日付を毎回表示したい場合 |
the_modified_date() | 最終更新日 | 更新日の表示 |
the_author() | 著者名 | 著者情報の表示 |
the_category() | カテゴリリンク一覧 | カテゴリラベル |
the_tags() | タグリンク一覧 | タグラベル |
the_ID() | 投稿ID | HTML属性やCSS用 |
post_class() | 投稿に応じたCSSクラス | articleタグのclass属性 |
comments_number() | コメント数 | コメントセクション |
the_で始まるタグは値を直接出力(echo)し、get_the_で始まるタグは値を返す(return)という違いがあります。PHPの変数に代入したい場合や条件分岐で使いたい場合はget_the_系を使いましょう。
各テンプレートタグの詳しいパラメータや応用例は、WordPress関数一覧をご覧ください。
WP_Queryでサブループを作る
メインループとは別に、追加の投稿一覧を表示したい場合はWP_Queryクラスを使ってサブループ(カスタムループ)を作ります。たとえば、サイドバーに「人気記事」を表示したり、記事末尾に「関連記事」を表示する場合に使います。
メインループとサブループの違い
| 項目 | メインループ | サブループ(WP_Query) |
|---|---|---|
| クエリの実行者 | WordPress(自動) | 開発者(手動) |
| 取得条件 | URLに基づいて自動決定 | パラメータで自由に指定 |
| グローバル変数 | $wp_queryを使用 | 独自の変数に格納 |
| 後処理 | 不要 | wp_reset_postdata()が必須 |
| 使用場面 | テンプレートのメインコンテンツ | サイドバー・関連記事・ウィジェット |
WP_Queryの基本的な使い方
以下は、特定カテゴリの最新5件を取得して表示するサブループの例です。
<?php
$args = array(
'post_type' => 'post',
'posts_per_page' => 5,
'category_name' => 'wordpress',
'orderby' => 'date',
'order' => 'DESC',
);
$custom_query = new WP_Query( $args );
if ( $custom_query->have_posts() ) :
while ( $custom_query->have_posts() ) : $custom_query->the_post();
?>
<article>
<h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
<p><?php echo get_the_date(); ?></p>
</article>
<?php
endwhile;
wp_reset_postdata(); // 必ずリセットする
endif;
?>ポイントは3つです。
- パラメータ配列で条件を指定する —
posts_per_page(件数)、category_name(カテゴリ)、orderby(並び順)など $custom_query->have_posts()で呼び出す — グローバルのhave_posts()ではなく、作成したインスタンスのメソッドを使うwp_reset_postdata()で後処理する — サブループ終了後にグローバル$postを元に戻す
wp_reset_postdata() の重要性
WP_Queryのサブループでは、the_post()を呼び出すたびにグローバル変数$postがサブループの投稿データで上書きされます。ループ終了後にwp_reset_postdata()を呼ばないと、メインループの投稿データが壊れたままになり、以降のテンプレートタグが意図しないデータを返す原因になります。
// サブループ終了後に必ず呼ぶ
wp_reset_postdata();
// これにより $post がメインクエリの投稿に復帰する
// 以降の the_title() 等はメインの投稿データを参照するWP_Queryを使ったアーカイブページの実装例は、archive.phpの使い方で詳しく解説しています。
pre_get_postsでメインクエリをカスタマイズ
メインクエリの取得条件を変更したい場合は、pre_get_postsアクションフックを使います。テンプレートファイル内で新しいWP_Queryを作るのではなく、WordPressが自動実行するメインクエリそのものを書き換えるため、無駄なクエリが発生せず、パフォーマンスの面でも推奨される方法です。
pre_get_postsはfunctions.phpに記述します。以下に代表的なカスタマイズ例を示します。
表示件数を変更する
トップページの表示件数を管理画面の設定とは別に変更する例です。
function custom_posts_per_page( $query ) {
// 管理画面は対象外
if ( is_admin() ) {
return;
}
// メインクエリのみ対象
if ( ! $query->is_main_query() ) {
return;
}
// トップページの表示件数を12件に変更
if ( $query->is_home() ) {
$query->set( 'posts_per_page', 12 );
}
}
add_action( 'pre_get_posts', 'custom_posts_per_page' );特定カテゴリを除外する
トップページの投稿一覧から特定のカテゴリ(例: カテゴリID 5)を除外する例です。
function exclude_category_from_home( $query ) {
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
if ( $query->is_home() ) {
// カテゴリID 5 を除外(マイナス値で除外)
$query->set( 'cat', '-5' );
}
}
add_action( 'pre_get_posts', 'exclude_category_from_home' );並び順を変更する
アーカイブページの投稿をタイトルのアルファベット順に並べ替える例です。
function custom_archive_order( $query ) {
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
if ( $query->is_archive() ) {
$query->set( 'orderby', 'title' );
$query->set( 'order', 'ASC' );
}
}
add_action( 'pre_get_posts', 'custom_archive_order' );pre_get_postsを使う際は、必ずis_admin()とis_main_query()のチェックを入れることが重要です。これを省略すると、管理画面の投稿一覧やサブループにも影響し、予期しない動作を引き起こします。
pre_get_postsやその他のfunctions.phpカスタマイズについては、functions.php便利コード集も参考にしてください。
ループのよくある間違いと対処法
WordPressループは仕組み自体はシンプルですが、いくつかの落とし穴があります。WordPress公式ドキュメント(Developer Resources)でも注意喚起されている代表的なミスと対処法を紹介します。
1. wp_reset_postdata() の呼び忘れ
症状: サブループの後に表示されるコンテンツが、メインの投稿ではなくサブループの最後の投稿になってしまう。サイドバーの「関連記事」の後に記事タイトルが変わるなどの不具合が起きます。
対処法: WP_Queryを使ったサブループのendwhile直後に、必ずwp_reset_postdata()を記述します。
// NG: リセット忘れ
$custom_query = new WP_Query( $args );
if ( $custom_query->have_posts() ) :
while ( $custom_query->have_posts() ) : $custom_query->the_post();
the_title();
endwhile;
// ← ここで wp_reset_postdata() を呼ばないとバグの原因
endif;
// OK: 正しいリセット
$custom_query = new WP_Query( $args );
if ( $custom_query->have_posts() ) :
while ( $custom_query->have_posts() ) : $custom_query->the_post();
the_title();
endwhile;
wp_reset_postdata(); // グローバル $post を復帰
endif;2. pre_get_posts で is_main_query() をチェックしない
症状: メインクエリだけでなく、ウィジェットやサイドバーのサブクエリにも条件変更が適用されてしまい、全体の表示がおかしくなる。
対処法: $query->is_main_query()で必ずメインクエリかどうかを判定してから条件を変更します。
// NG: すべてのクエリに影響する
function bad_pre_get_posts( $query ) {
$query->set( 'posts_per_page', 5 );
}
add_action( 'pre_get_posts', 'bad_pre_get_posts' );
// OK: メインクエリのみに限定
function good_pre_get_posts( $query ) {
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
$query->set( 'posts_per_page', 5 );
}
add_action( 'pre_get_posts', 'good_pre_get_posts' );3. query_posts() を使ってしまう
症状: メインクエリが上書きされ、ページネーション・条件タグ(is_single()等)・プラグインの動作が壊れる。パフォーマンスも低下する(クエリが二重実行される)。
対処法: query_posts()は使わないでください。WordPress公式でも非推奨とされています。メインクエリを変更したい場合はpre_get_postsを、追加の投稿一覧が必要な場合はWP_Queryを使います。
// NG: query_posts() は非推奨
query_posts( 'posts_per_page=5' );
while ( have_posts() ) : the_post();
// ...
endwhile;
wp_reset_query();
// OK: メインクエリの変更は pre_get_posts で
// OK: 追加のループは WP_Query で4. ループの外でテンプレートタグを使う
症状: the_title()やthe_content()をwhileループの外で呼び出すと、何も表示されないか、意図しない投稿のデータが表示される。
対処法: テンプレートタグは必ずループの内部(whileとendwhileの間)で使用します。ループ外で投稿データにアクセスしたい場合はget_post()に投稿IDを渡して個別に取得します。
よくある質問(FAQ)
Q. WordPressループのhave_posts()とthe_post()は何が違いますか?
have_posts()は未処理の投稿が残っているかを true/false で判定する関数で、ループの継続条件として使います。一方、the_post()はカウンターを進めて次の投稿データをグローバル変数 $post にセットする関数です。have_posts()が「まだ投稿ある?」と聞き、the_post()が「次の投稿をセットして」と実行する、という役割分担です。
Q. wp_reset_postdata()とwp_reset_query()の違いは何ですか?
wp_reset_postdata()はグローバル変数 $post をメインクエリの現在の投稿に復帰させる関数で、WP_Query によるサブループの後に使います。wp_reset_query()は $wp_query 自体をリセットする関数で、query_posts()の後に使います。ただし query_posts()自体が非推奨のため、通常は wp_reset_postdata() のみを使えば問題ありません。
Q. メインループの表示件数を変えたいとき、WP_Queryとpre_get_postsのどちらを使うべきですか?
メインクエリの条件を変更する場合は pre_get_posts を使うべきです。WP_Query で別途クエリを作ると、WordPress が自動実行したメインクエリに加えて2回目のクエリが発生し、パフォーマンスが低下します。pre_get_posts ならメインクエリ自体を変更するため、クエリの重複が起きません。
Q. ループ内でthe_date()を使っても日付が表示されないことがあるのはなぜですか?
the_date()は同じ日付の投稿が連続すると、2件目以降では日付を出力しない仕様です。これはWordPressが「同日投稿の重複表示を防ぐ」という意図で設計したためです。すべての投稿で日付を表示したい場合は、代わりに get_the_date() を echo で出力してください。echo get_the_date() なら常に日付が取得できます。
Q. 1つのテンプレートで複数のサブループを使っても問題ありませんか?
問題ありません。ただし、各サブループの終了後に必ず wp_reset_postdata() を呼ぶ必要があります。リセットを忘れると、次のサブループやメインループの $post が前のサブループの最後の投稿を参照してしまい、表示内容がおかしくなります。サブループごとにリセットを入れることを習慣にしましょう。
Q. get_posts()とWP_Queryはどう使い分けますか?
get_posts()は投稿データの配列を返すシンプルな関数で、foreach で回して使います。WP_Query はページネーション情報やfound_posts(総件数)など、より詳細な情報を持つオブジェクトを返します。単純に投稿リストが欲しい場合は get_posts()、ページネーション付きの一覧表示やループ内でテンプレートタグを使いたい場合は WP_Query が適しています。
まとめ
WordPressループは、すべてのテーマの投稿表示を支える中核的な仕組みです。この記事で解説した内容を振り返ります。
- メインループ:
if ( have_posts() )→while ( have_posts() ) : the_post();の基本構造で、WordPressが自動取得した投稿を1件ずつ表示する - テンプレートタグ:
the_title()・the_content()・the_permalink()等で投稿データを取得・出力する。the_は直接出力、get_the_は値を返す - WP_Queryサブループ: 追加の投稿一覧にはWP_Queryを使い、終了後に
wp_reset_postdata()で必ずリセットする - pre_get_posts: メインクエリの条件変更にはpre_get_postsを使う。
is_admin()とis_main_query()のチェックを忘れない - query_posts()は使わない: クエリの二重実行・ページネーション破壊の原因になるため非推奨
ループを正しく理解することで、テーマ開発の自由度が格段に広がります。テンプレートファイルの全体像やその他のWordPress関数については、以下の関連記事もあわせてご覧ください。
