WordPressループの仕組みを徹底解説|have_posts・the_postの使い方


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ステップで処理が進みます。

  1. if ( have_posts() ) — 投稿が1件以上あるか確認する
  2. while ( have_posts() ) — 未処理の投稿がある限りループを続ける
  3. the_post() — 次の投稿データを$postにセットする
  4. テンプレートタグで表示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()投稿IDHTML属性や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つです。

  1. パラメータ配列で条件を指定するposts_per_page(件数)、category_name(カテゴリ)、orderby(並び順)など
  2. $custom_query->have_posts() で呼び出す — グローバルのhave_posts()ではなく、作成したインスタンスのメソッドを使う
  3. 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_postsfunctions.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ループの外で呼び出すと、何も表示されないか、意図しない投稿のデータが表示される。

対処法: テンプレートタグは必ずループの内部(whileendwhileの間)で使用します。ループ外で投稿データにアクセスしたい場合は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関数については、以下の関連記事もあわせてご覧ください。

【30,000円OFFクーポン】【国内生産・公式… 【30,000円OFFクーポン】【国内生産・公式… ¥139,800 【P20倍】【国内生産・公式】 新品 大画面… 【P20倍】【国内生産・公式】 新品 大画面… ¥179,799 【楽天1位常連・超700冠獲得】黒/白 モニ… 【楽天1位常連・超700冠獲得】黒/白 モニ… ¥11,999 【ふるさと納税】液晶モニター31.5型ワイ… 【ふるさと納税】液晶モニター31.5型ワイ… ¥135,000 ロジクール ワイヤレスキーボード K295GP … ロジクール ワイヤレスキーボード K295GP … ¥3,201 【ふるさと納税】HHKB Professional HYBRI… 【ふるさと納税】HHKB Professional HYBRI… ¥130,000
Rakuten Web Service Center