wp_reset_postdata()を忘れるとメインループが壊れる理由と対処法【WordPress】

WordPress

こんにちは!コーダーのゆうしです。

WordPressでWP_Queryを使ってサブループを書いたあと「なぜか他の場所で the_title()the_permalink() が正しく出力されない」という経験はありませんか?原因のほとんどは wp_reset_postdata() の書き忘れです。この記事では wp_reset_postdata() が必要な理由と正しい使い方を解説します。

そもそもメインループとサブループとは?

メインループ

WordPressが自動で生成するクエリをもとにしたループです。have_posts()the_post() を使った以下の構文がメインループです。

if ( have_posts() ) :
  while ( have_posts() ) : the_post();
    // 投稿の内容を出力
  endwhile;
endif;

メインループは現在のURLに応じたクエリを自動で実行します。

サブループ

WP_Queryを使って独自のクエリを実行するループです。メインループとは別に追加で投稿を取得したいときに使います。

$args = [
  'post_type'      => 'news',
  'posts_per_page' => 3,
];
$my_query = new WP_Query( $args );

if ( $my_query->have_posts() ) :
  while ( $my_query->have_posts() ) : $my_query->the_post();
    // 投稿の内容を出力
  endwhile;
endif;

なぜメインループが壊れるのか?

サブループで $my_query->the_post() を呼び出すと、WordPressのグローバル変数 $post がサブループの投稿で上書きされます。

この状態のままサブループが終わると $post がサブループの最後の投稿を指したままになります。その後にメインループの the_title()the_permalink() などを呼び出しても、サブループの最後の投稿の情報が返ってしまいます。

// サブループを実行
$my_query = new WP_Query( $args );
while ( $my_query->have_posts() ) : $my_query->the_post();
  echo get_the_title(); // サブループの投稿タイトルが出力される
endwhile;

// wp_reset_postdata()を書き忘れたまま...

// メインループのつもりで書いているが...
echo get_the_title(); // サブループの最後の投稿タイトルが出力されてしまう!

wp_reset_postdata()の役割

wp_reset_postdata() はサブループで上書きされた $post をメインループの投稿に戻す関数です。サブループが終わった後に呼び出すことで、その後の the_title() などが正しくメインループの投稿を参照できるようになります。

$my_query = new WP_Query( $args );

if ( $my_query->have_posts() ) :
  while ( $my_query->have_posts() ) : $my_query->the_post();
    echo get_the_title();
  endwhile;
endif;

wp_reset_postdata(); // ← これで$postをメインループに戻す

正しい書き方

基本パターン

<?php
$args = [
  'post_type'      => 'news',
  'posts_per_page' => 3,
  'orderby'        => 'date',
  'order'          => 'DESC',
];
$my_query = new WP_Query( $args );
?>

<?php if ( $my_query->have_posts() ) : ?>
  <?php while ( $my_query->have_posts() ) : $my_query->the_post(); ?>

    <article>
      <h2><?php the_title(); ?></h2>
      <a href="<?php the_permalink(); ?>">続きを読む</a>
    </article>

  <?php endwhile; ?>
<?php endif; ?>

<?php wp_reset_postdata(); // ← ループの外・endifの後に書く ?>

wp_reset_postdata() はループの外・endif の後に書くのが正しい位置です。

サブループが複数ある場合

サブループが複数ある場合はそれぞれのループの後に書いてください。

// 1つ目のサブループ
$query1 = new WP_Query( $args1 );
while ( $query1->have_posts() ) : $query1->the_post();
  // 出力
endwhile;
wp_reset_postdata(); // 1つ目のループ後にリセット

// 2つ目のサブループ
$query2 = new WP_Query( $args2 );
while ( $query2->have_posts() ) : $query2->the_post();
  // 出力
endwhile;
wp_reset_postdata(); // 2つ目のループ後にリセット

wp_reset_query()との違い

wp_reset_postdata() と似た関数に wp_reset_query() があります。混同しやすいので整理しておきます。

関数用途
wp_reset_postdata()WP_Queryのサブループ後に使う
wp_reset_query()query_posts() を使った後に使う

query_posts() はメインクエリ自体を書き換える関数でパフォーマンス的に推奨されていません。基本的には WP_Query を使って wp_reset_postdata() で戻すのが正しいやり方です。

get_posts()を使う場合は不要

get_posts() を使ったループでは the_post() を使わないため wp_reset_postdata() は不要です。

$posts = get_posts([
  'post_type'      => 'news',
  'posts_per_page' => 3,
]);

foreach ( $posts as $post ) :
  setup_postdata( $post ); // これを使う場合はwp_reset_postdata()が必要
  echo get_the_title();
endforeach;
wp_reset_postdata(); // setup_postdata()を使った場合は必要

setup_postdata() を使った場合は wp_reset_postdata() が必要ですが、$post->post_title のように直接プロパティにアクセスする場合は不要です。

躓きやすいポイント

サブループの後のメインループでタイトルが違う

サブループの後に the_title() が意図しない投稿のタイトルを返している場合は wp_reset_postdata() の書き忘れが原因のほとんどです。サブループの endwhileendif の直後に追加してみてください。

wp_reset_postdata()をループの中に書いてしまう

// NG:ループの中に書いてしまっている
while ( $my_query->have_posts() ) : $my_query->the_post();
  echo get_the_title();
  wp_reset_postdata(); // ループ中に書くと毎回リセットされてしまう
endwhile;

// OK:ループの外に書く
while ( $my_query->have_posts() ) : $my_query->the_post();
  echo get_the_title();
endwhile;
wp_reset_postdata(); // ループが終わってから呼び出す

ループの中に書いてしまうとループのたびにグローバル変数がリセットされて、意図しない動作になることがあります。

まとめ

WP_Queryのサブループを使ったら wp_reset_postdata() をループの後に必ず書く、これを習慣にしておくと予期しないバグを防げます。書き忘れても見た目上は問題ないように見えることもありますが、メインループの後にサブループを使っている場合や複数のループがある場合に症状が出やすいので注意してください。

ご不明な点やコーディングのご依頼はお問い合わせからお気軽にどうぞ。

タイトルとURLをコピーしました