MENU

【WordPress×PHP】WP_Queryで記事一覧を作る

【WordPress×PHP】WP_Queryで記事一覧を作る
目次

はじめに

【WordPress×PHP】フィルターフックの仕組みと使い方では、表示される値を書き換えるフィルターフックを解説しました。

今回は、WP_Queryを使って、好きな条件で記事一覧を自作する方法を解説します。「このカテゴリーの記事だけ」「古い順に」「この記事以外」といった一覧を、ページの好きな場所に表示できるようになります。第6回のループと、第8回のオブジェクトの知識を使うので、あわせて読んでみてください。

このシリーズの全15回の構成は、第1回の記事の冒頭にまとめています。

メインクエリとサブクエリ

WordPressは、URLを見て「このページで表示する記事」を自動で取り出します。これが第6回で解説したメインクエリで、have_posts()とthe_post()のループはこのメインクエリを使っていました。

それとは別に、自分で条件を決めて記事を取り出すのがサブクエリです。サブクエリは、new WP_Query( 条件 )で、WP_Queryクラスのオブジェクトを作って使います。

メインクエリとサブクエリ。投稿ページのタイトル・本文はURLで決まった記事を表示するメインクエリ、自分で作る「このシリーズの記事一覧」や、SWELLが表示する関連記事はサブクエリ

SWELLのテーマフォルダをnew WP_Queryで検索すると、22か所で使われていました。記事の下に表示される関連記事(parts/single/related_post_list.php)も、その1つです。

WP_Queryの基本の書き方

カテゴリー「wp-php」の記事を新しい順に3件取り出して、タイトルを一覧にするコードです。

$query = new WP_Query( [
	'category_name'  => 'wp-php',
	'posts_per_page' => 3,
] );

if ( $query->have_posts() ) {
	echo '<ul>';
	while ( $query->have_posts() ) {
		$query->the_post();
		echo '<li>' . esc_html( get_the_title() ) . '</li>';
	}
	echo '</ul>';
}
wp_reset_postdata();
WP_Queryで一覧を作る4つの手順。1:new WP_Queryで条件を渡して記事を取り出す。2:$query->have_posts()で記事が残っているか確認。3:$query->the_post()で次の記事を現在の記事にする(2と3を記事の数だけ繰り返す)。4:wp_reset_postdata()で現在の記事を元に戻す

第6回のループとの違いは、have_posts()とthe_post()の前に$query->が付くことです。$query->the_post()を実行すると、取り出した記事が「現在の記事」になるので、ループの中ではget_the_title()やget_permalink()がそのまま使えます。

ローカル環境で試すと、記事が5件あるカテゴリーから新しい順に3件が表示されました。$query->found_posts(条件に合う記事の総数)は5、$query->post_count(実際に取り出した数)は3でした。

主な引数

new WP_Query()には、条件を配列で渡します。よく使うものは次のとおりです。

引数意味例
post_type投稿の種類'post'(投稿)、'page'(固定ページ)
posts_per_page取り出す件数5、-1(すべて)
category_nameカテゴリーのスラッグ'wp-php'
tagタグのスラッグ'swell'
orderby並べる基準'date'(日付)、'title'(タイトル)、'rand'(ランダム)
order並べる向き'DESC'(新しい順・初期値)、'ASC'(古い順)
post__not_in除外する投稿IDの配列[ get_the_ID() ]
ignore_sticky_posts先頭に固定表示の記事を特別扱いしないtrue
no_found_rows総数を数えない(少し速くなる)true

posts_per_pageを指定しないと、管理画面の「設定」→「表示設定」の「1ページに表示する最大投稿数」(初期値10)が使われます。また、no_found_rowsをtrueにすると、found_postsは0になりました。ページ送りを作らない一覧なら、trueにしておくと無駄な処理を省けます。

SWELLの関連記事でも、post__not_in(表示中の記事を除外)、no_found_rows、ignore_sticky_postsを指定していました。

get_posts()という関数でも、同じ条件で記事を取り出せます。こちらは記事のオブジェクト(WP_Post)の配列を返すので、foreachで回して使います。指定しないときの件数は5件でした。

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

$query->the_post()は、「現在の記事」を次々に切り替えます。ループが終わったら、wp_reset_postdata()で元の記事に戻す必要があります。

これを忘れるとどうなるか、第1回の記事の本文の後ろにサブクエリで一覧(3件)を表示して試しました。

wp_reset_postdataを忘れた場合と書いた場合の比較。忘れると、ループ後のget_the_ID()が最後に取り出した第3回のIDのままになり、SWELLの関連記事に表示中の第1回の記事自身が出た。書いた場合は第1回のIDに戻り、関連記事も正しく表示された

リセットしなかった場合、ループのあとのget_the_ID()は、最後に取り出した第3回のIDのままでした。その結果、本文の後ろに表示されるSWELLの関連記事が「第3回の記事」を基準に作られ、表示中の第1回の記事自身が関連記事に出てしまいました。エラーは出ないので、気付きにくい不具合です。

よくある書き間違い

have_posts()に「$query->」を付け忘れる

$query = new WP_Query( [ 'category_name' => 'wp-php' ] );
while ( have_posts() ) {  // $query-> を付け忘れた
	$query->the_post();
	// …
}

ローカル環境で試すと、一覧は表示されず、ページがなかなか表示されないまま、約80秒後に次のエラーになりました。

「使えるメモリの上限を超えました」という意味です。have_posts()はメインクエリの関数なので、投稿ページの中で呼ぶと、メインクエリのループが最初からやり直しになります。そのため、記事の本文が何度も繰り返し処理され、メモリを使い切ってしまいました。サブクエリでは、have_posts()にもthe_post()にも、必ず$query->を付けましょう。

存在しないカテゴリーを指定する

category_nameに存在しないスラッグを書いてもエラーにはならず、$query->have_posts()がfalseになって何も表示されません。一覧が出ないときは、スラッグのつづりを管理画面の「投稿」→「カテゴリー」で確認しましょう。

古い解説記事では、query_posts()という関数で一覧を作る方法が紹介されていることがあります。これはメインクエリそのものを置き換えてしまう関数で、WordPressの公式リファレンスでも使わないよう案内されています。一覧を自作するときは、WP_Queryを使いましょう。

実務サンプル:記事の最後に「このシリーズの記事一覧」を表示する

このシリーズのような連載記事では、記事の最後に「同じシリーズの記事一覧」があると便利です。シリーズ用のカテゴリーに入っている記事を、公開順(古い順)に並べて表示してみましょう。

子テーマのfunctions.phpに、次のコードを追加します。$seriesの部分は、シリーズに使っているカテゴリーのスラッグに書き換えてください。

/**
 * シリーズ記事の本文の最後に、同じシリーズの記事一覧(公開順)を表示する
 */
add_filter( 'the_content', function( $content ) {
	$series = 'wp-php';  // シリーズに使っているカテゴリーのスラッグ

	if ( ! is_singular( 'post' ) || ! in_the_loop() || ! is_main_query() ) {
		return $content;
	}
	if ( ! in_category( $series ) ) {
		return $content;
	}

	$current_id = get_the_ID();

	$query = new WP_Query( [
		'category_name'       => $series,
		'posts_per_page'      => -1,
		'orderby'             => 'date',
		'order'               => 'ASC',
		'no_found_rows'       => true,
		'ignore_sticky_posts' => true,
	] );

	if ( ! $query->have_posts() ) {
		return $content;
	}

	$list = '<div class="kurumico-series"><p class="kurumico-series__title">このシリーズの記事一覧</p><ol>';
	while ( $query->have_posts() ) {
		$query->the_post();
		if ( get_the_ID() === $current_id ) {
			$list .= '<li class="is-current">' . esc_html( get_the_title() ) . '(この記事)</li>';
		} else {
			$list .= '<li><a href="' . esc_url( get_permalink() ) . '">' . esc_html( get_the_title() ) . '</a></li>';
		}
	}
	$list .= '</ol></div>';
	wp_reset_postdata();

	return $content . $list;
} );

子テーマのstyle.cssには、次のCSSを追加します。

.kurumico-series {
	margin-top: 3em;
	padding: 1.5em;
	background: #f7f9fb;
	border: 1px solid #dde3ea;
	border-radius: 6px;
}
.kurumico-series__title {
	margin-bottom: .5em;
	font-weight: bold;
}
.kurumico-series .is-current {
	font-weight: bold;
	color: #04384c;
}

カテゴリー「wp-php」に5件の記事を用意して、第3回の記事を開くと、本文の最後に次のように表示されました。

SWELLの投稿ページ。「第3回 ローカル環境を作る」の本文の後ろに「このシリーズの記事一覧」の枠があり、第1回から第5回までが公開順に並んでいる。第3回だけはリンクではなく太字で「(この記事)」と表示されている

コードのポイントは次のとおりです。

  • 第10回のthe_contentフィルターと3つの条件を使い、投稿ページの本文の最後に一覧を追加している
  • in_category()(第7回)で、シリーズのカテゴリーに入っている記事のときだけ表示している
  • サブクエリを始める前に、表示中の記事のIDを$current_idに入れておき、ループの中で比べて「(この記事)」を付けている
  • 'posts_per_page' => -1ですべての記事を、'order' => 'ASC'で古い順に取り出している
  • フィルターの中なのでechoせず、文字列をつなげてreturnしている。ループのあとはwp_reset_postdata()で元の記事に戻している

雑記カテゴリーの記事、固定ページ、RSSフィードには、一覧は表示されませんでした。また、SWELLの関連記事も、表示中の記事を正しく除外したまま表示されました。

よくある質問・エラー対処

Q. メインクエリの表示件数や並び順を変えたいときも、WP_Queryを使いますか?

→ いいえ。トップページの記事一覧などメインクエリを変えたいときは、pre_get_postsというアクションフック(第9回)で条件を変えます。SWELLも、カスタマイザーの「除外したいカテゴリーのID」(新着記事一覧から除外する設定)を、lib/hooks.phpのpre_get_postsで反映しています。WP_Queryは、メインとは別に一覧を追加したいときに使います。


Q. カテゴリーをIDで指定したいです

→ 'cat' => 5のようにcatを使います。複数のカテゴリーをまとめて指定するときは、SWELLの関連記事のように'category__in' => [ 5, 8 ]と配列で渡します。

おわりに(まとめ)

まとめ

WP_Queryを使えば、好きな条件の記事一覧を、ページの好きな場所に追加できます。

  • ✅ URLで決まるのがメインクエリ、自分で作るのがサブクエリ
  • ✅ new WP_Query()に条件を配列で渡して、記事を取り出す
  • ✅ have_posts()とthe_post()には必ず「$query->」を付ける
  • ✅ ループのあとはwp_reset_postdata()で元の記事に戻す
  • ✅ 表示中の記事のIDは、サブクエリを始める前に変数に入れておく
  • ✅ メインクエリを変えたいときは、pre_get_postsを使う

次回は、ここまでのコードでも使ってきたesc_html()などの「エスケープ」と、WordPressのセキュリティの基本について解説します。

次回は、エスケープとセキュリティについて解説予定です。

この記事が、少しでも誰かのお役に立てれば幸いです。

関連記事

当サイトの記事で使用したVBAなどのサンプルをDLできます

この記事のサンプルはありません!

ダウンロードページへは下のカードをクリックすればジャンプできます。
よろしければご利用ください!


【CSS】テキスト関連プロパティについて基本解説|Webの基礎


よかったらシェアしてね!
  • URLをコピーしました!
目次