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()
のいろいろな違いを紹介した。
ほぼ同じ、とはいったものの、こうして挙げてみると意外にも違うものだ。
みなさんにはこれらの違いに注意して、ぜひより良いループを作ってほしい。
こめんと