query_postsとget_postsの違い[追記あり]
Posted by admin at 9:29 日時 2012/05/29
[2013/07/25 追記]
get_posts で検索してこの記事にアクセスされる方が多いのですが、この記事はquery_postsとget_postsをソースコードで比較したものですので、使い方を知りたい方は「WordPressでページ送りが動かないのはどう考えてもquery_postsが悪い!【pre_get_postsまとめ】」も合わせてご覧ください。
query_posts と get_posts。どちらもWordPressから投稿のデータを取得するのに使えるので、特に使い分けを意識していない方も多いのではないかと思います。自分自身もはっきり結論がでているわけではないのですが、今回はこの違いについて考えてみます。
※ WordPressについての立ち入った内容なので、予めお断りしておきます
query_posts
wp-includes/query.php に書かれています。
function &query_posts($query) { unset($GLOBALS['wp_query']); $GLOBALS['wp_query'] = new WP_Query(); return $GLOBALS['wp_query']->query($query); }
$GLOBALS[‘wp_query’] は、WordPressのメインのクエリーを保持するグローバル変数です。これをいったんunsetで消してしまって、new WP_Query() で新しくWordPressのクエリーを作成してグローバル変数 $GLOBALS[‘wp_query’] に再度格納しています。つまり、メインのクエリーを上書きしています。
get_posts
wp-includes/post.php に書かれています。
function get_posts($args = null) { $defaults = array( 'numberposts' => 5, 'offset' => 0, 'category' => 0, 'orderby' => 'post_date', 'order' => 'DESC', 'include' => array(), 'exclude' => array(), 'meta_key' => '', 'meta_value' =>'', 'post_type' => 'post', 'suppress_filters' => true ); $r = wp_parse_args( $args, $defaults ); if ( empty( $r['post_status'] ) ) $r['post_status'] = ( 'attachment' == $r['post_type'] ) ? 'inherit' : 'publish'; if ( ! empty($r['numberposts']) && empty($r['posts_per_page']) ) $r['posts_per_page'] = $r['numberposts']; if ( ! empty($r['category']) ) $r['cat'] = $r['category']; if ( ! empty($r['include']) ) { $incposts = wp_parse_id_list( $r['include'] ); $r['posts_per_page'] = count($incposts); // only the number of posts included $r['post__in'] = $incposts; } elseif ( ! empty($r['exclude']) ) $r['post__not_in'] = wp_parse_id_list( $r['exclude'] ); $r['ignore_sticky_posts'] = true; $r['no_found_rows'] = true; $get_posts = new WP_Query; return $get_posts->query($r); }
こちらは、1ページの表示数や表示順などの初期設定を行ったうえで、新規にクエリーを作成しています。つまり、メインのクエリーを上書きせず、メインのクエリーとは別にクエリーを作っています。
メインのクエリーを上書きするかどうかはすごく重要
で、どう違うのよという話なんですが、メインのクエリーを上書きするかしないかは、ひとつはWordPress3.3から導入された、メインのクエリーかどうかを調べる条件分岐タグ「is_main_query」の動作に影響してきます。どうなるかは、テーマ内で次のような簡単なコードを書いて確認することができます。
<p>初期状態: <?php var_dump(is_main_query()); ?></p> <?php $my_query_posts = query_posts($query_strings); ?> <p>query_posts後: <?php var_dump(is_main_query()); ?></p> <?php wp_reset_query(); ?> <p>wp_reset_query後: <?php var_dump(is_main_query()); ?></p> <?php $my_get_posts = get_posts(); ?> <p>get_posts後: <?php var_dump(is_main_query()); ?></p> <?php wp_reset_postdata(); ?> <p>wp_reset_postdata後<?php var_dump(is_main_query()); ?></p>
結果はこうなります。
初期状態: bool(true)
query_posts後: bool(false)
wp_reset_query後: bool(true)
get_posts後: bool(true)
wp_reset_postdata後bool(true)
query_posts を起動した直後だけ false になっています。つまり、query_posts はメインのクエリーを上書きするが、上書きしたクエリーはメインのクエリーではないという扱いになるんですね。書いていて日本語として変ですが。is_main_query() タグは、pre_get_posts にフックしてメインのクエリーを変更する際に使われます。このフックを使う場合、テーマの中に query_posts があるとちょっとややこしいです。
あと、query_posts の大きな問題としては、トラブルを起こしやすいということにあります。メインのクエリーを上書きしているので、ページ送りナビゲーションやカテゴリーの選択を無効化してしまうことがあります。これを防ぐためにかならず wp_reset_query() を呼び出してクエリーをリセットする必要があります。wp_reset_query() は wp-includes/query.php の中でこう書かれています。
function wp_reset_query() { unset($GLOBALS['wp_query']); $GLOBALS['wp_query'] = $GLOBALS['wp_the_query']; wp_reset_postdata(); }
なんと、グローバル変数 $GLOBALS[‘wp_query’] をまたしてもunsetして別のところにおいてあったグローバル変数を上書きしてるんですね。こんな回りくどいことをするくらいなら、最初から上書きせず get_posts でいいじゃないか、と思います。また、このソースを見ると分かるように、WordPressのメインのクエリーは $wp_query と $wp_the_query の2つあるんですね。なんか、ちょっと分かりにくい…。
結局 query_posts は何のためにあるのか?
実際の query_posts の利用シーンですが、たとえばWordPressの管理画面で設定できる1ページの表示投稿数を、特定のテンプレートでだけ変更したり、あるいはテンプレートから特定のカテゴリーを除外したりと言った、そのテンプレートで主に表示するループの設定をデフォルトから変更したい場合に、メインのクエリーを上書きするのに使います。
トップページのテンプレートで新着記事を5件別に表示したいとか、サイドバーに特定のカテゴリーの記事を表示したいとか、そのテンプレートで主に表示するループとは別にWordPressの情報を取得したいとき、サイドバーやヘッダー・フッターなど主に表示するループが存在しないテンプレートからWordPressの投稿を呼び出すときは、get_posts を使うほうが良いでしょう。
query_posts は基本的に使わないほうがいいと思う、その理由
で、メインのクエリーを上書きするのは、今後は pre_get_posts フックを使うほうが主流になっていくのかなぁ〜と思います。
query_posts は、いったんデータベースから記事を取ってきたうえで、再度データベースに接続しなおしています。pre_get_posts フックを使うと、最初にデータベースにアクセスする前に、どのように記事を取得するかの設定を変更することができます。もちろん、後者のほうが効率的です。
また、pre_get_posts フックを使うと、メインのクエリーを変更するのに、functions.php から完結することができます。query_posts だと、各テンプレートに書かなくては行けないので、子テーマで開発する際に不便です。せっかく親テーマを継承して変更すべきテンプレートファイルだけ作ればよい子テーマの仕組みをつかっているのに、query_posts を追記するためだけに子テーマにファイルを追加することになってしまいます。pre_get_posts フックを使えば、子テーマの functions.php から表示をカスタマイズすることができます。個人的に開発の効率化のため子テーマでしかテーマを作らなくなってきているので、この点でも query_posts は使わなくなっていきそうです。
なんかまとまりのないまとめですが。おわりんこ。
5/31 追記
Facebookでまがりん(@jim0912)に教えてもらいました。query_posts と get_posts にはさらに重要な違いがあるということです。
query_posts は suppress_filters の初期値が false、get_posts は true
suppress_filters パラメータは、posts_request や posts_where、posts_join など、クエリに関するフィルターを無視します。get_posts はこれらのフィルターを使うプラグイン(Search Everythingなど)の影響を受けません。
get_posts は ignore_sticky_posts の値が常に true
ignore_sticky_posts の値が true だと、先頭固定表示の投稿を取得しません。
get_posts は no_found_rows の値が常に true
ページ送りのために使われるSQLのオプション「SQL_CALC_FOUND_ROWS」がオフになります。
これらの違いを見ると、単純に条件に沿った投稿を取得することに特化した機能が get_posts であると言えそうです。