【WordPressの動き方シリーズ】(4)メインクエリができるまで

2020年3月11日

ここからは一歩進んで、メインクエリが出来上がるまでの流れを見ていきたいと思います。

メインクエリ作成に必要なファイルの読み込み

WordPressのサイトにアクセスすると、index.php => wp-blog-header.php => wp-config.php => wp-settings.phpの順番にPHPファイルが読み込まれ、wp-settings.phpでメインクエリ作成に必要な以下の手順が進められます

・WPクラス(メインクエリを集めるきっかけとなるクラス)のインスタンスオブジェクトを$wpに渡す。
wp関数(メインクエリのデータを作成・保存を実行する関数)を含む色々な関数の読み込み
・プラグインの読み込みと初期化
メインクエリ保存用の変数$wp_the_query, $wp_queryが、WP_Queryクラスをインスタンス化して作成されます。(メインクエリのデータはまだ保存されていません)
・テーマのfunctions.phpの読み込み

その後、wp-blog-header.phpファイル内でwp関数が実行され、メインクエリが作られていきます

メインクエリが作られる前に、

・プラグインの読み込みと初期化
・テーマのfunctions.phpの読み込み

が行われるので、プラグインやテーマのfunctions.phpを使ってその後に行われるメインクエリをカスタマイズすることが可能となっています。

wp関数の中身

wp関数は、wp-includes/functions.phpファイルに記載されています。

function wp( $query_vars = '' ) {
    global $wp, $wp_query, $wp_the_query;
    $wp->main( $query_vars );

    if ( ! isset( $wp_the_query ) ) {
        $wp_the_query = $wp_query;
    }
}

つまりwp関数は、

・WPクラスのオブジェクト$wpのmainメソッドに、$query_vars(クエリ変数)を渡して実行し、mainメソッド中でクエリ変数を基にメインクエリのデータを取得し、グローバル変数$wp_queryに保存
・その後、グローバル変数$wp_the_queryにそのオブジェクトを参照渡しする

を行っているわけです。

次にメインクエリが作られている($wpの基になっている)WPクラスのmainメソッドを見ていきます。

WPクラスmainメソッドについて

WPクラスは、 wp-includes/class-wp.phpに記載されています。mainメソッドを見て見ましょう。

public function main( $query_args = '' ) {
    $this->init();
    $this->parse_request( $query_args );
    $this->send_headers();
    $this->query_posts();
    $this->handle_404();
    $this->register_globals();
 
    /**
     * Fires once the WordPress environment has been set up.
     *
     * @since 2.1.0
     *
     * @param WP $this Current WordPress environment instance (passed by reference).
     */
    do_action_ref_array( 'wp', array( &$this ) );
}

mainメソッドでは、同じWPクラス内に定義されているいくつかのメソッドを実行していますが、メインクエリ作成に関与するメソッドは、

$this->parse_request($query_args):URLを解析してSQL文を作るのに必要なクエリ変数を準備し、$this->query_varsプロパティに保存
$this->query_posts():query_postsメソッドを呼び出して、$wp_the_queryにメインクエリのデータを設定

の2つです。

parse_requestメソッドは、SQLを書くために必要なパラメータをURLから見つけて、(WPクラスの)query_varsプロパティに保存します。

そして、query_postsメソッドではこのquery_varsプロパティを使って実際のデータを取得し、グローバル変数の$wp_the_queryに保存しています。

そこで次はquery_postsメソッドの中を確認しましょう。

WPクラスquery_postsメソッドについて

public function query_posts() {
    global $wp_the_query;
    $this->build_query_string();
    $wp_the_query->query( $this->query_vars );
}

query_postsメソッドでは、クエリ変数のプロパティである$this->query_varsを使用し、WP_Queryクラスのオブジェクトである$wp_the_queryのqueryメソッドでメインクエリのデータを取得・保存しています。

なお、$this->build_query_string()の役割は、

$this->build_query_string():$this->query_vars(クエリ変数プロパティ)からデータを集めてクエリ文字列($key1=$value1&$key2=$value2&$key3=$value3…)に変換し$this->query_stringに保存

ようやくメインクエリのデータを取得する旅が終わりそうです!!

次はWP_Queryクラスのqueryメソッドを観る事にします。

WP_Queryクラスのqueryメソッドについて

wp-includes/class-wp-query.php

/**
     * Sets up the WordPress query by parsing query string.
     *
     * @since 1.5.0
     *
     * @param string|array $query URL query string or array of query arguments.
     * @return WP_Post[]|int[] Array of post objects or post IDs.
     */
    public function query( $query ) {
        $this->init();
        $this->query      = wp_parse_args( $query );
        $this->query_vars = $this->query;
        return $this->get_posts();
    }

WP_Queryクラスのqueryメソッドも、クラス内の他のメソッドを呼び出しています。

$this->init():クエリに関係する変数($postsや$query(クエリ変数)など)の中身を初期化します。
$this->query_vars = $this->query = wp_parse_args($query):引数が文字列でも、配列でも、オブジェクトでも要素を連想配列にして、クエリ変数に納めなおします。

$this->queryがオリジナルのクエリ変数プロパティ$this->query_varsはカスタマイズなどにより値が変更されうるクエリ変数プロパティという位置づけです。

そして、WP_Queryクラスのget_postsメソッドでこれまでのクエリ変数を使ってデータベースから必要な情報を集めてきます。

WP_Queryクラスのget_postsメソッド

get_postsメソッド自体は非常に長いので簡略化して、ところどころコメントを入れて見ましたので下のコードをお読みください。

public function get_posts() {
        global $wpdb;
 
        $this->parse_query();
        //$this->parse_query()で、$this->query_varsの値に応じてis_hogehogの判定で返される値を設定します

        /**
         * Fires after the query variable object is created, but before the actual query is run.
         *
         * Note: If using conditional tags, use the method versions within the passed instance
         * (e.g. $this->is_main_query() instead of is_main_query()). This is because the functions
         * like is_main_query() test against the global $wp_query instance, not the passed one.
         *
         * @since 2.0.0
         *
         * @param WP_Query $this The WP_Query instance (passed by reference).
         */
        do_action_ref_array( 'pre_get_posts', array( &$this ) );
        /*
         pre_get_postsフィルターフックを通じて、WP_Queryのオブジェクト、
     今回で言うと$wp_the_queryの持つすべてのプロパティに対してフィルターをかける事ができるようになっています。
         また、pre_get_postsフィルターを使い場合には、判定はis_main_query()ではなく、$this->is_main_query()
         (実際は$query->is_main_query()とかになります)を使ってください。
         is_main_query()関数は、改変されたかもしれないメインクエリである$wp_queryを参照し、
     $this->is_main_query()は改変されていないメインクエリである$wp_the_queryを参照しているからです。
        */
         
         //ここからしばらくSQL文を作るための作業が続くので省略します。

         if ( ! $q['suppress_filters'] ) {
         /*
         ここだけに限りませんが、SQL文におけるWHERE句やJOIN句などに対するフィルターは、
         $q['suppress_filters']の値がfalseの時つまり、クエリ変数にsuppress_filters = falseという項目が無い限り
         fuctions.phpやプラグインでいくらフィルターをかけても有効になりません。
         */
            /**
             * Filters the WHERE clause of the query.
             *
             * @since 1.5.0
             *
             * @param string   $where The WHERE clause of the query.
             * @param WP_Query $this The WP_Query instance (passed by reference).
             */
            $where = apply_filters_ref_array( 'posts_where', array( $where, &$this ) );
 
            /**
             * Filters the JOIN clause of the query.
             *
             * @since 1.5.0
             *
             * @param string   $join  The JOIN clause of the query.
             * @param WP_Query $this The WP_Query instance (passed by reference).
             */
            $join = apply_filters_ref_array( 'posts_join', array( $join, &$this ) );
        }       

        //ここからしばらくSQL文を作るための作業が続くので省略します。

        //データベースからデータを取得した直後
        /*
        データはデータベースから取得した内容を1件ずつオブジェクト形式にし、それを配列として$this->postsに保存しています。
        単純なオブジェクト形式のデータを、WP_Postオブジェクトに変換するのが下のコードです。
        */
        // Convert to WP_Post objects.
        if ( $this->posts ) {
            $this->posts = array_map( 'get_post', $this->posts );
            
        }
        /*
        更に$this->postsの内容をposts_resultsフィルターフックでいじることができます。
        */ 
        if ( ! $q['suppress_filters'] ) {
            /**
             * Filters the raw post results array, prior to status checks.
             *
             * @since 2.3.0
             *
             * @param WP_Post[] $posts Array of post objects.
             * @param WP_Query  $this  The WP_Query instance (passed by reference).
             */
            $this->posts = apply_filters_ref_array( 'posts_results', array( $this->posts, &$this ) );
        }
        /*
        また、フィルターフックを通じてデータが追加されたり変更されて、
        それぞれの配列の中身がWP_POSTオブジェクトでなくなると困るので、get_postを使ってWP_POSTでないものはもう一度取得するようにしています。
        */ 
        // Ensure that any posts added/modified via one of the filters above are
        // of the type WP_Post and are filtered.
        if ( $this->posts ) {
            $this->post_count = count( $this->posts );
 
            $this->posts = array_map( 'get_post', $this->posts );
 
            if ( $q['cache_results'] ) {
                update_post_caches( $this->posts, $post_type, $q['update_post_term_cache'], $q['update_post_meta_cache'] );
            }
 
            $this->post = reset( $this->posts );
        } else {
            $this->post_count = 0;
            $this->posts      = array();
        } 
        
        //最後に、取得したメインクエリの値を返して終了です。
        return $this->posts;
    }
         

こうして、

①wp関数の中でWPクラスのオブジェクトである$wpが$wp->main()を実行する。
②$wp->main()の中で$wp->query_posts()が実行される。
③$wp->query_posts()の中で、WP_Queryクラスのオブジェクトである$wp_the_queryが$wp_the_query->query()を実行する。
④$wp_the_query->query()の中で、$wp_the_query->get_posts()が実行される。
⑤$wp_the_query->get_posts()の中で、メインクエリのデータが集められ、$wp_the_query->postsプロパティの中に保存される。

という5つのステップを通じでメインクエリが完成しました。

最後のwp関数、WPクラス、WP_Queryクラスの3者の繋がりをまとめてみました。(引数は省略しています)