WordPressのテーマを作ってる人は、テーマファイル内でいろんな独自の「ループ」を作っていることだろう。
ループとは、条件を指定してクエリから取得した複数の投稿をループ処理で表示する仕組みのことで、主にアーカイブページで使われる。
そんなループを作るとき、条件に合った投稿を取得するのに使うのが、クラス WP_Query と関数 get_posts() だ。
これらは相互に置き換えることができるとされているが、ループの作り方に違いがある。
今回は WP_Query と get_posts() の使い方の違いと、他に俺が気付いた違いについて紹介する。
ループの作り方の違い
WordPressにおいて投稿を取得するのは WP_Query の仕事だ。
get_posts() も内部では WP_Query を呼び出している。
しかし get_posts() が投稿データ(オブジェクト)の配列を返すのに対し、 WP_Query はクエリを実行するクラスを直接操作する。
もちろん扱いやすいのは get_posts() のほうだが、メインループ以外の独自のループ(サブループと呼ぶ)を作るときは WP_Query のほうが処理に無駄がないので、好みに合ったほうを使えばいい。
実際にループ処理を行うコードを比べてみよう。
まずは get_posts() から。
$args = array( /* 省略 */ );
$my_posts = get_posts( $args );
if ( $my_posts ) {
foreach ( $my_posts as $post ) {
setup_postdata( $post );
the_title();
the_content();
}
wp_reset_postdata();
}
取得する投稿の条件を指定する引数 $args は get_posts() でも WP_Query でも共通だ。
get_posts() では foreach でループを作り、 setup_postdata() で投稿データをセットする。
そうそう、ループ内でグローバル変数 $post を書き換えているので、ループ後に wp_reset_postdata() するのをお忘れなく(特に $post を再び使用する場合)。
次に WP_Query のループを見てみよう。
$args = array( /* 省略 */ );
$my_query = new WP_Query( $args );
if ( $my_query->have_posts() ) {
while ( $my_query->have_posts() ) {
$my_query->the_post();
the_title();
the_content();
}
wp_reset_postdata();
}
WP_Query では foreach の代わりに while でループを作る。
その評価式には WP_Query のメソッドである have_posts() を使用する。
また、投稿データをセットするのも setup_postdata() ではなく the_post() メソッドだ。
have_posts() も the_post() も、メインループで使う関数としておなじみだが、そもそもこれらの関数は内部的にはメインクエリ $wp_query の同名メソッドを呼び出しているにすぎない。
すなわち WP_Query で独自のループを操作するのはメインループと同じ仕組みということだ。
一方 get_posts() では取得した投稿を配列として、要素ごとに処理を処理を行う。
見比べてみるとわかる通り、これら二つのループは何行かのコードを書き換えることで、相互に置き換えられることがわかる。
余談:the_post() と setup_postdata() の違い
the_post() には setup_postdata() が含まれるが、他の処理も含まれるので、この二つの関数(およびメソッド)は同じ処理ではない。
特に重要な違いは、 the_post() と違って setup_postdata() は現在の投稿に関する多くのグローバル変数を書き換えるが、肝心の $post を書き換えない点だ。
これは $post が汚染されないという長所でもあるが、 $post 以外の変数名を与えて setup_postdata( $my_post ) のように呼び出すと、以後 the_title() のような関数を使用した場合、 $my_post ではなく元の $post の投稿データが使用されてしまう。
the_permalink() なら the_permalink( $my_post ) のように引数を与えればいいが、 the_title() は投稿オブジェクトを引数に取れない(常にグローバルの $post が使用される)ので問題となる。
この問題を回避するには、 $post に $my_post を代入するか、最初から $post の変数名で setup_postdata( $post ) と呼び出すかしなければならない、つまり結局 $post を書き換えることになる。
また同じ理由で、 the_post() や have_posts() を使わないと、内部的にはループに入った(またはループから出た)扱いがされないので、 in_the_loop() のようなループ関連の関数やフックが使えないという違いもある。
ちなみに「ループ内」とは the_post() の時点から while 文の評価式にある have_posts() までの間を指す。
引数の違い
上では引数 $args は共通だと述べたが、確かにその形式は同じなのだが、初期値にはわずかに違いがある。
| 引数のキー | WP_Query での初期値 |
get_posts() での初期値 |
|---|---|---|
'ignore_sticky_posts' |
false / 0 |
true / 1 |
'no_found_rows' |
false / 0 |
true / 1 |
'suppress_filters' |
false / 0 |
true / 1 |
しかもこれらのうち 'ignore_sticky_posts' と 'no_found_rows' は、 get_posts() では設定しても強制的に初期値に上書きされて WP_Query に渡される。
また、以下の表の左側のキーの値は get_posts() でしか使えず、対応する共通のキーにコピーされて WP_Query に渡される。
get_posts() 専用のキー |
対応するキー |
|---|---|
'numberposts' |
'posts_per_page' |
'category' |
'cat' |
'include' |
'post__in'※ 'posts_per_page' も上書き |
'exclude'※ 'include' が存在すると無効 |
'post__not_in' |
これから get_posts() を使う場合は、最初から WP_Query にも対応するキーで指定しておくことをオススメする。
一部の状況で WP_Query がおかしい件
さて、このブログで使用しているWordPressテーマは俺の完全な自作だが、このあいだ、これまで get_posts() を使用していた部分を片っ端から WP_Query に置き換える作業をしていた。
すると、うまく動かない現象を確認。
それは自作のショートコードの処理を行う関数でのこと。
get_posts() では問題なく動いていて、同じ引数のまま WP_Query に変更したところ、投稿を正常に取得できなくなった。
上記のような引数の初期値の違いを揃えてみても変化なし。
不思議に思って WP_Query について調べまくった成果がこの記事なのだが……
残念ながら、原因を突き止めることも、問題を回避することもできず、あえなく get_posts() に戻すことに。
ただ一つ、WordPress公式ドキュメントに以下の記述を発見した。
Note: Ticket #18408 For querying posts in the admin, consider using get_posts() as wp_reset_postdata() might not behave as expected.
引用元:WP_Query | Class | WordPress Developer Resources
「管理ページでクエリを実行するなら get_posts() を使うことも考えよう、 wp_reset_postdata() が期待通りに動かないかもしれないから」?
ショートコードだから管理ページではないし、 wp_reset_postdata() とは関係ないから俺の状況とは違うけど、要するに WP_Query だとうまく動かない状況もあるってことだと理解した。
今回、2種類のショートコードで問題を確認したが、そのうちの一方は、そもそも投稿を全く取得できていないようだった(メソッド have_posts() が false )。
しかしもう一方では、投稿は取得できているものの、投稿オブジェクトのプロパティのうち $post->post_date のデータが欠落しているという、これまた不可思議な現象に遭遇。
しかも、それならばと $my_query->the_post(); のあとで $post = $my_query->post; と、クエリのプロパティを投稿オブジェクトに書き戻してみると、 $post->post_date がちゃんと存在するという。
the_post() に問題があるのか、そもそも WP_Query に原因があるのか、はたして……
ともかく、ショートコードの関数で WP_Query を使うのは避けたほうがよさそうだ。
この件について、何か情報をお持ちの方がいれば、ぜひ情報提供を求めたい。
まとめ
今回は WP_Query と get_posts() のいろいろな違いを紹介した。
ほぼ同じ、とはいったものの、こうして挙げてみると意外にも違うものだ。
みなさんにはこれらの違いに注意して、ぜひより良いループを作ってほしい。
こめんと