WordPressにはメインループとサブループという概念があり、この仕組みを理解することでカスタマイズの幅が広がります。データベースへの無駄なアクセスを防ぎ、サイト表示スピードを改善させるためにも必要な知識です。
記事情報はデータベースに保存されている
WordPressで投稿した記事は、データベースに保存されます。そのため、記事を表示するにはデータベースに「どの記事情報がほしいか」を問い合わせる必要があります。この問い合わせのことをクエリといいます。
メインクエリ
WordPressは自動でクエリを発行して記事を取得してくれます。このクエリをメインクエリといいます。メインクエリは、URLから取得する記事を判断しています。記事IDをURLに指定すればその記事を取得するクエリを発行し、カテゴリスラッグをURLに指定すればそのカテゴリに属する記事一覧を取得するクエリを発行します。
// 記事IDが1234の記事情報を取得するクエリを発行
https://ohta412.jp/?p=1234
// カテゴリスラッグがhogeの記事情報を取得するクエリを発行
https://ohta412.jp/category/hoge/
取得する記事数は、WordPressの管理画面から設定できます。その他にもいろいろな状況を加味して、メインクエリは決定されます。
メインループ
メインクエリにより取得された記事情報は、$wp_queryという名前の変数にオブジェクト形式でセットされます。$wp_queryには複数記事分の情報が格納されているので、ループすれば各記事情報にアクセスすることができます。このループをメインループといいます。
<?php
if( have_posts() ):
while( have_posts() ): the_post();
?>
<h2><?php the_title(); ?></h2>
<p><?php the_content(); ?></p>
<?php
endwhile;
else:
?>
<p>記事がありません。</p>
<?php endif; ?>
have_posts()は、$wp_queryにセットされている記事数とループカウンタを比較しています。
ループカウンタとは、ループ中に現在何記事目を表示しているかを制御しているカウンタです。$wp_queryに10個の記事が格納されていたら、1記事目から始まって2記事目3記事目…10記事目と表示するために、記事をカウントアップする必要があります。そのカウント数を保持しているのがループカウンタです。
have_posts()は、 現在表示しているループカウンタと総記事数を比較することで、表示する記事がまだあるかを判断しています。ループカウンタより総記事数の方が多い場合、まだ表示する記事があるのでtrueを返します。そうでなければ、falseを返します。
上記のコードは、表示する記事があれば(if文)、表示する記事がある限り(while文)、記事を出力するという内容です。
the_post()は、$wp_queryのループカウンタ番目の記事をセットアップし、ループカウンタを1増やしています。この記述を忘れると、ループカウンタが増えずhave_posts()が常にtrueになるので、while文を抜け出せず無限ループになります。
pre_get_postsでメインクエリを変更する
URLとは関係のない記事を取得したい場合は、メインクエリを変更します。
WordPressはURLから判断して自動でメインクエリを実行しますが、実行前にメインクエリを書き換える処理を挟みます。その「実行前」というタイミングがpre_get_postsというアクションフックで登録されています。
アクションフックとは? アクションフックとは、WordPressのデフォルトの挙動に変更を加えられる仕組みです。 WordPressのさまざま機能には、オリジナルの処理を実行できる場所がいろいろ用意されています。記事を投稿したタイミングである処理を実行したり、WordPressのテーマが変更された場合にある処理を実行したりできます。 今回の場合は、「メインクエリを実行する前」という場所を利用します。その名前がpre_get_postsです。
function change_posts( $query ) {
// 管理画面、メインクエリでない場合は処理を行わない
if( is_admin() || !$query->is_main_query() ) {
return;
}
// 該当ページに条件を指定する
if( $query->is_category('php') ) {
$query->set('category_name', 'hoge');
$query->set('posts_per_page', '3');
return;
}
}
// アクションフックを登録する
add_action( 'pre_get_posts', 'change_posts' );
17行目でアクションフックを登録しています。関数add_action()の第1引数にはフック名を、第2引数には実行する関数名を書きます。
$queryにクエリ情報が入っているので、これを変更します。
管理画面やメインクエリでない場合はクエリを変更しない方がいいので、その場合は処理を行わずに関数を終了する記述が4~6行目に書いてあります。おまじないのようなものなので、必ず書くようにします。
9行目でメインクエリを変更するページを記述し、10行目以降で変更内容を指定します。
上記コードの例では、phpカテゴリページのメインクエリを「hogeカテゴリの記事を3件取得」に変更しています。この状態でphpカテゴリページにアクセスすると、hogeカテゴリに属している記事が取得されます。
指定できる条件は、後ほど紹介するWP_Queryの引数と同じものが使用できます。
サブクエリとサブループ
phpカテゴリページにアクセスしたとき、phpカテゴリに属する記事を通常通り取得しつつ、hogeカテゴリに属する記事も取得したい場合があります。
その場合は、メインクエリを変更せずに新たに別のクエリを発行します。このクエリのことをサブクエリといいます。
WP_Query
サブクエリの発行にはWP_Queryというクラスを使います。
クラスとは? クラスとは実態を取得するための設計図のことです。 各記事は、タイトル、本文、ID、投稿日など、持っている情報の形式(ひな形)が決まっています。しかし、その内容はそれぞれの記事によって異なります。各記事のどの情報を取得するかを設計図のように定義しているのがクラスです。なので、クラス自体に記事情報は含まれておらず、クラスを実行することで初めて設計図にそった形式で記事を取得してくれます。このとき、どの記事を取得するかをパラメータによって指定する必要があります。
<?php
$args = [
'category_name' => 'hoge',
'posts_per_page' => 3,
];
$the_query = new WP_Query( $args );
if( $the_query->have_posts() ):
while( $the_query->have_posts() ): $the_query->the_post();
?>
<h2><?php the_title(); ?></h2>
<p><?php the_content(); ?></p>
<?php
endwhile;
else:
?>
<p>記事がありません。</p>
<?php
endif;
wp_reset_postdata();
?>
6行目でサブクエリを発行して、記事情報を取得しています。クラスはnewという前置詞を付けて宣言することで、その設計図にそった形式で実態を取得します。今回では、実態とは記事情報のことです。
このとき、WP_Queryの引数に配列形式でパラメータを指定します。これにより、どの記事を取得するか指定できます。上記コードの例では、「hogeカテゴリの記事を3件取得」という指定をしています。指定できるパラメータの一覧は、下記記事にまとめられています。
WP_Queryの使い方をPHPコードにまとめた便利なコード・スニペット
6行目以降で、取得した記事情報を$the_queryに入れて、メインループと同じように各記事をループで出力しています。このループをサブループといいます。
$the_queryの中には、メインループでも使ったhave_posts()やthe_post()も含まれています。メインクエリもWP_Queryを使って記事を取得しているので、使い方は同じです。
19行目でwp_reset_postdata()という関数が実行されています。これには、サブクエリをここで終了させてそれ以降をメインクエリに戻す役割があります。例えば、トップページ(インデックスページ)でis_home()を実行するとtrueが返ってきますが、サブクエリを発行後wp_reset_postdata()を実行しなければ、それ以降はis_home()を実行してもfalseが返ってきます。
メインクエリのテンプレートタグを使用する必要が無ければwp_reset_postdata()を実行しなくてもいいのですが、基本的にはセットで書いておきます。
get_posts()
WP_Queryの他にget_posts()という関数を使っても、サブクエリを発行できます。
<?php
$args = [
'category_name' => 'hoge',
'posts_per_page' => 3,
];
$posts = get_posts( $args );
if( $posts ):
foreach( $posts as $post ): setup_postdata( $post );
?>
<h2><?php the_title(); ?></h2>
<p><?php the_content(); ?></p>
<?php
endforeach;
else:
?>
<p>記事がありません。</p>
<?php
endif;
wp_reset_postdata();
?>
get_posts()はWP_Queryのラッパー関数なので、実行されると関数内でWP_Queryを使って記事を取得します。そのため、先ほどのWP_Queryを使った場合と仕組みがほとんど同じです。パラメータもWP_Queryと同じものが使用できます。
ラッパー関数とは? ラッパー関数とは、関数内で別の関数やクラスを実行して結果を返す関数のことです。WordPressのコードを見てみると、get_posts()は関数内でWP_Queryを使っています。WP_Queryで取得した記事情報をreturnで返しています。
function get_posts( $args = null ) {
// 中略
$get_posts = new WP_Query;
return $get_posts->query( $parsed_args );
}
get_posts()で記事情報を取得した場合はテンプレートタグが使えないので、使用したい場合は8行目のように関数setup_postdata()を実行します。そうすることで、the_title()などが使用できます。
setup_postdata()を使った場合は、WP_Queryのときと同じように、wp_reset_postdata()を忘れないように記述します。
WP_Queryとget_posts()の違い
WP_Queryの場合はオブジェクトを返すのでwhile文でループを回していましたが、get_posts()は配列で値を返すのでforeach文でループを回します。そして、have_posts()で表示する記事があるか判断する必要がないので、get_posts()の方が少しコードがすっきりします。
返される値は、get_posts()の場合は記事情報だけですが、WP_Queryの場合は記事情報以外にもページに関する情報も含んでいます。そのため、is_single()やis_page()などの判定も機能します。
複雑な処理を行いたい場合はWP_Queryを使う必要がありますが、それ以外ではどちらを選んでも大きな違いはありません。