Blog

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_postsget_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 であると言えそうです。

参考記事


Share this entry

Blog Entry Topics