(この記事はだいぶ長い時間かけて検証したのですが、明確な結論を得られなかった記事になります。ただ頑張って検証したので、ご参考程度に公開しておきます)
WordPress では、個々のページで主として表示させるべきデータを取得するクエリを「メインクエリ」と呼びます。つまり、表示さえるデータはメインクエリにより取得されます。逆に言うと、このメインクエリ以前には、「まだデータは取得されていない」と僕は思っていました。
ところが、ある日、pre_get_posts にフックして、メインクエリの時に、そのクエリの内容を表示してみると、既にデータが取得済みである場合があることに気付きました。
1 2 3 4 5 |
add_action( 'pre_get_posts', function($query) { if ( ! is_admin() and $query->is_main_query() ) { var_dump( $query ); } }, 1 ); |
↑のようなコードを書いてみると、固定ページを表示させた時に、↓次のような結果が得られました(長いので一部のみ掲載)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
object(WP_Query)#123 (44) { "query_vars" => array(48) { "page" => string(0) "" "pagename" => string(5) "about" "error" => string(0) "" "m" => int(0) "p" => int(0) //.... 省略 .... "post_parent__in" => array(0) { } "post_parent__not_in" => array(0) { } } "tax_query" => NULL "meta_query" => bool(false) //.... 省略 .... "is_post_type_archive" => bool(false) "query_vars_hash" => string(32) "24ce6baed22bdb10fcc79db47544ec7e" "query_vars_changed" => bool(false) "thumbnails_cached" => bool(false) "query" => array(2) { "page" => string(0) "" "pagename" => string(5) "about" } "queried_object" => object(WP_Post)#2054 (24) { "ID" => int(24) "post_author" => string(1) "1" "post_date" => string(19) "2013-05-01 18:36:54" "post_date_gmt" => string(19) "2013-05-01 09:36:54" "post_content" => string(231) "....省略...." "post_title" => string(12) "会社概要" "post_excerpt" => string(0) "" "post_status" => string(7) "publish" "comment_status" => string(4) "open" "ping_status" => string(4) "open" "post_password" => string(0) "" "post_name" => string(5) "about" "to_ping" => string(0) "" "pinged" => string(0) "" "post_modified" => string(19) "2013-05-24 09:10:54" "post_modified_gmt" => string(19) "2013-05-24 00:10:54" "post_content_filtered" => string(0) "" "post_parent" => int(0) "guid" => string(37) "http://wp36.foreignkey.jp/?page_id=24" "menu_order" => int(0) "post_type" => string(4) "page" "post_mime_type" => string(0) "" "comment_count" => string(1) "0" "filter" => string(3) "raw" } "queried_object_id" => int(24) } |
ここで「queried_object」というメンバー変数があり、そこでは既に WP_Post のインスタンスが保存されています。
「あれ? まだメインクエリ実行されていない筈だけど?」
§ 検証
気になったので検証してみました。
まず色々と試してみると、どうやら「固定ページ」の表示の時にのみ、メインクエリの前に投稿データが読み込まれるようです。そこで WP::main() 関数から $wp_the_query->queried_object が代入されるポイントを追ってみました。結果、wp-includes/query.php にある WP_Query::parse_query() の中にそのポイントを見つけました。次の箇所です。
1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 |
if ( '' != $qv['pagename'] ) { $this->queried_object = get_page_by_path($qv['pagename']); if ( !empty($this->queried_object) ) $this->queried_object_id = (int) $this->queried_object->ID; else unset($this->queried_object); if ( 'page' == get_option('show_on_front') && isset($this->queried_object_id) && $this->queried_object_id == get_option('page_for_posts') ) { $this->is_page = false; $this->is_home = true; $this->is_posts_page = true; } } |
ポイントは 1629 行目ですね。get_page_by_path() からの戻り値を $this->queried_object に保存しています。get_page_by_path() は、URL パスっぽい文字列から、該当するであろう WP_Post を取得する関数です。
また、1628 行目で、$qv[‘pagename’] の内容が空で無いことを確認しています。pagename は固定ページのスラッグを意味していたと思うので、「固定ページの時だけ、メインクエリの以前に既にデータが取得されている」という現象と一致します。
原因は分かりましたが、「なぜこのようになっているのか?」という疑問が残ります。
§ 考察
結論から言うと、はっきり納得できるような理解を得ることは出来ませんでしたが、いちおう、考えた経緯だけご紹介しておきます。
まず、WP::main() です。
545 546 547 548 549 550 551 552 553 |
function main($query_args = '') { $this->init(); $this->parse_request($query_args); $this->send_headers(); $this->query_posts(); $this->handle_404(); $this->register_globals(); do_action_ref_array('wp', array(&$this)); } |
初期化から初まって、WordPress の大まかな処理の流れが見て取れます。さてここから、先ほど発見した queried_object が変化するポイントまでは、次のような処理のフローになっていました。
WP::main()
WP::query_posts()
WP_Query::query()
WP_Query::get_posts()
WP_Query::parse_query() //まだ準備中だけどページのデータ取っちゃった
これらのメソッドの呼び出し階層からみても、やはり「メインクエリ発行のための準備(parse_query)で先にデータ取得しちゃった」という感じです。ふむ…。
例えば、wp-includes/query.php の 1629 行目で get_page_by_path() を使っているのは、「そのパスが有効であるかどうかを確認している」という仮説は立てられるかも知れませんが、なんとなく釈然としません。
…という感じで、この件については明快な意義を見出すことが出来ませんでした。もしご存知の方がいらっしゃったら教えて頂けたらと思います。
§ 検証2
せっかくなので、少し実用的な検証を行ってみます。
まず、固定ページでは pre_get_posts の前に、そのページの投稿データが取得されているのはここまでに述べた通りですが、それでは、pre_get_posts での $query への変更は有効なのかどうか、検証してみたいと思います。
まず、次のようなコードを書いてみました。まだ pre_get_posts では $query に何もしていません。
1 2 3 4 5 6 7 8 9 10 11 12 |
add_action( 'pre_get_posts', function( $query ) { if ( ! is_admin() && $query->is_main_query() ) { debug( $query->queried_object, '$wp_query->queried_object' ); } }, 99999 ); add_action( 'template_redirect', function() { global $wp_query, $post; debug( $wp_query->queried_object, '$wp_query->queried_object' ); debug( $post, "\$GLOBALS['post']" ); exit; }, 99999 ); |
pre_get_posts と template_redirect にフックして次の3つの値を出力をしてみました(ちなみに、コード中に出てくる debug() という関数は、こちらに公開しているものです)。
- pre_get_posts で、メインクエリ実行前の $wp_query->queried_object
- template_redirect で、メインクエリ実行後の $wp_query->queried_object
- template_redirect で、メインクエリ実行後の グローバル変数 $post
結果は次のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/.../wp-content/plugins/wp-devutil/functions.php: 20 at {closure} ($wp_query->queried_object) object(WP_Post)#2055 (24) { "ID" => int(24) //...省略... /.../wp-content/plugins/wp-devutil/functions.php: 30 at {closure} ($wp_query->queried_object) object(WP_Post)#2055 (24) { "ID" => int(24) //...省略... /.../wp-content/plugins/wp-devutil/functions.php: 31 at {closure} ($GLOBALS['post']) object(WP_Post)#2056 (24) { "ID" => int(24) //...省略... |
queried_object の値に変化が無いことと、また全てが同じ ID:24 の投稿データだということが分かります。但し、$GLOBALS[‘post’] には同じデータながら、異なるインスタンスが格納されていることには少し注目ですね。
さて、pre_get_posts で query_vars を変更してみます。
まずは、存在しない pagename を設定してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
add_action( 'pre_get_posts', function( $query ) { if ( ! is_admin() && $query->is_main_query() ) { $query->set( 'pagename', 'bokettch' ); debug( $query->queried_object, '$wp_query->queried_object' ); } }, 99999 ); add_action( 'template_redirect', function() { global $wp_query, $post; debug( $wp_query->queried_object, '$wp_query->queried_object' ); debug( $post, "\$GLOBALS['post']" ); exit; }, 99999 ); |
結果ですが、何も変わりませんでした。次に存在する他の固定ページの post_name を pagename にセットしてみましたが、これも変化ありませんでした。
先にデータが取得しているため、pre_get_posts での変更は無効ということかしら?
ところが試しに pagename に null をセットしてみると、今度は変化がありました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/.../wp-content/plugins/wp-devutil/functions.php: 20 at {closure} ($wp_query->queried_object) object(WP_Post)#2055 (24) { "ID" => int(24) //...省略... /.../wp-content/plugins/wp-devutil/functions.php: 30 at {closure} ($wp_query->queried_object) object(WP_Post)#2055 (24) { "ID" => int(24) //...省略... /.../wp-content/plugins/wp-devutil/functions.php: 31 at {closure} ($GLOBALS['post']) object(WP_Post)#2060 (24) { "ID" => int(71) //...省略... |
$GLOBALS[‘post’] の結果が異なっています。まったく調べていませんが、おそらく pagename に null をセットしたため最新の記事か何かが検索されてしまったようです。
??では pre_get_posts による変更は有効なのでしょうか?
他にも幾つかのパラメータを変更してみましたが、どうにも釈然としない動きになっています。おそらく、ですが、pre_get_posts フックでの pagename の変更は、考慮されない実装になっているのかも知れません。むむむ。
とは言え、これには結論が出せました(かな?)。
「固定ページへの問い合わせの時、pre_get_posts フックでメインクエリ $wp_query への変更は有効でない」
一応の結論ではありますが、どうにも中途半端で負けた気分です。WP_Query::get_posts() を読み進めると、もう少しはっきりとしたことが言えるのかもしれませんが、彼の巨大な関数はおいそれとは手が出しにくい印象です。また後日気が向いたら眺めてみようと思います。
[…] WordPressでメインクエリ以前にもロード済みのデータがあることについて […]