べんりワザ・WordPressのテンプレートと投稿の取得

ループの外で投稿を取得する方法(WP_Query)

トップページに新着3件、サイドバーに人気記事——メインのループとは別に投稿を取り出すWP_Queryの書き方。よく使う条件の一覧、後始末の理由、get_postsとの違いまでまとめました。

トップページの新着3件、記事下の関連記事、サイドバーのおすすめ——メインのループとは別に投稿を取り出したいときはWP_Queryを使います。型はこれだけです。

<?php
$query = new WP_Query([
  'post_type'      => 'post',
  'posts_per_page' => 3,
]);
?>
<?php if ($query->have_posts()) : ?>
  <ul>
    <?php while ($query->have_posts()) : $query->the_post(); ?>
      <li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
    <?php endwhile; ?>
  </ul>
<?php endif; ?>
<?php wp_reset_postdata(); ?>

ポイントは$query->を付けることと、最後のwp_reset_postdata()の2つです。

なぜ$query->を付けるのか

WordPressは、そのURLに応じた投稿一覧(メインクエリ)をあらかじめ用意しています。have_posts()the_post()を引数なしで呼ぶと、そのメインクエリを操作してしまいます。

自分で作った$queryのループでは、$query->have_posts() / $query->the_post()と対象を明示する——これがサブループの基本ルールです。逆に、ループの中でthe_title()the_permalink()のような表示系の関数は、$query->なしでそのまま使えます(the_post()が「今の投稿」を切り替えてくれているため)。

wp_reset_postdata()を忘れるとどうなる?

$query->the_post()は「今の投稿」をサブループ側の投稿に置きかえます。ループを抜けても、その状態が残ります

そのため呼び忘れると、たとえば記事ページで「関連記事一覧を出したあと、その下の投稿者情報が関連記事の最後の投稿のものになる」といった崩れが起きます。get_field()が別の投稿の値を返す原因にもなります。

サブループを書いたら、閉じたすぐ下にwp_reset_postdata()。セットで手が覚えるまで書きましょう。

べんりワザACFの値が表示されないときのチェックリストカスタムフィールドの値が急におかしくなるときの原因にもなります

よく使う条件(引数)の早見表

引数意味
post_type投稿タイプ'post' / 'shohin' / ['post','shohin']
posts_per_page取得件数(-1で全件)5
orderby並べ替えの基準'date' / 'rand' / 'menu_order' / 'meta_value_num'
order昇順・降順'DESC'(新しい順)/ 'ASC'
offset先頭から何件飛ばすか3
post__not_in除外する投稿ID[get_the_ID()]
category_nameカテゴリのスラッグ'news'
tagタグのスラッグ'sale'
tax_queryカスタムタクソノミーで絞る下のリンク参照
meta_queryカスタムフィールドで絞る下のリンク参照
post_status公開状態'publish'(既定)
pagedページ送りの現在ページget_query_var('paged')
ignore_sticky_posts先頭固定の投稿を無視true
べんりワザtax_queryでカテゴリ・タクソノミーで絞りこむカテゴリやカスタムタクソノミーで絞りこむtax_queryの書き方べんりワザmeta_queryでカスタムフィールドで絞る・並べ替えるカスタムフィールドの値で絞る・並べ替えるmeta_queryの書き方

実例:カスタム投稿タイプの新着を並べる

<?php
$news = new WP_Query([
  'post_type'      => 'shohin',
  'posts_per_page' => 6,
  'orderby'        => 'date',
  'order'          => 'DESC',
]);
?>
<?php if ($news->have_posts()) : ?>
  <div class="cards">
    <?php while ($news->have_posts()) : $news->the_post(); ?>
      <article class="card">
        <a href="<?php the_permalink(); ?>">
          <?php the_post_thumbnail('medium'); ?>
          <h3><?php the_title(); ?></h3>
          <time><?php echo esc_html(get_the_date('Y.m.d')); ?></time>
        </a>
      </article>
    <?php endwhile; ?>
  </div>
<?php else : ?>
  <p>まだ商品がありません。</p>
<?php endif; ?>
<?php wp_reset_postdata(); ?>

elseを書いておくと、0件のときにレイアウトが崩れず、メッセージが出せます。納品するサイトでは必ず入れておきたい一手です。

実例:関連記事(自分を除いた同カテゴリ)

<?php
$cats = wp_get_post_categories(get_the_ID());
$related = new WP_Query([
  'category__in'   => $cats,
  'post__not_in'   => [get_the_ID()],   // 自分自身を除く
  'posts_per_page' => 4,
  'orderby'        => 'rand',
]);
?>
べんりワザ関連記事をプラグインなしで出す関連記事の出し方は専用の記事でくわしく

get_posts()との違い

get_posts()投稿の配列を返す関数で、短く書けます。ループを回す必要がない、リストを1つ出すだけの場面で便利です。

<?php
$posts = get_posts([
  'post_type'   => 'post',
  'numberposts' => 5,   // ← get_postsはposts_per_pageではなくnumberposts
]);
?>
<ul>
  <?php foreach ($posts as $p) : ?>
    <li>
      <a href="<?php echo esc_url(get_permalink($p)); ?>"><?php echo esc_html(get_the_title($p)); ?></a>
    </li>
  <?php endforeach; ?>
</ul>

使い分けの目安はこうです。

  • WP_Query——アイキャッチや本文、the_content()など投稿ループの関数を使いたいとき。ページ送りも作れる
  • get_posts()——タイトルとURLだけの簡単なリスト。件数指定はnumberposts

なおget_posts()のあとでsetup_postdata($p)the_post()を使った場合は、WP_Queryと同じくwp_reset_postdata()が必要です。

メインの一覧そのものを変えたいときはpre_get_posts

「カテゴリページの表示件数を20件にしたい」「アーカイブの並び順を変えたい」——これはサブループではなくメインクエリの調整です。functions.phpでフックします。

function my_pre_get_posts($query) {
  if (is_admin() || !$query->is_main_query()) {
    return;   // 管理画面とサブループには効かせない
  }
  if (is_category()) {
    $query->set('posts_per_page', 20);
  }
}
add_action('pre_get_posts', 'my_pre_get_posts');

is_admin()is_main_query()のガードは必ず入れます。これが無いと管理画面の一覧や他のサブループまで巻きこんで、原因の分かりにくい不具合になります。

件数が多いサイトでの小技

  • 'no_found_rows' => true——ページ送りが不要な一覧で、総件数の計算を省いて軽くする
  • 'posts_per_page' => -1は慎重に——記事が増えると一気に重くなる。上限を決めておく
  • 'ignore_sticky_posts' => true——先頭固定の投稿が混ざるのを防ぐ

まとめ

  1. 別の一覧はnew WP_Query([...])。ループでは$query->have_posts() / $query->the_post()と対象を明示する
  2. wp_reset_postdata()を必ず呼ぶ。忘れるとページ下部の表示やカスタムフィールドが崩れる
  3. タイトルとURLだけの簡単なリストならget_posts()(件数はnumberposts
  4. メインの一覧を変えるならpre_get_postsquery_posts()は使わない

「主役の一覧はフックで調整、おまけの一覧はWP_Queryで追加」と覚えておくと、迷う場面がぐっと減ります。

よくある質問

ループの外で投稿を取得するには何を使いますか?
new WP_Query([...])で新しいクエリを作り、$query->have_posts()と$query->the_post()でループします。終わったら必ずwp_reset_postdata()を呼びます。簡単な取得ならget_posts()も使えます。
wp_reset_postdata()は何のために必要ですか?
the_post()を呼ぶと「今の投稿」がサブループ側の投稿に置きかわります。呼び忘れると、そのあとのthe_title()やget_field()が別の投稿を指したままになり、ページ下部の表示が崩れます。
query_posts()を使ってはいけないと聞きました
query_posts()はメインのクエリを壊してしまうため、公式にも非推奨とされています。別の一覧を出したいならWP_Query、メインの一覧の条件を変えたいならpre_get_postsを使います。
現在表示中の記事を一覧から除きたいです
'post__not_in' => [get_the_ID()] を条件に足します。関連記事の一覧で自分自身が並ぶのを防げます。