WordPress Posted Display version 2.0.1をリリースしました。
今回の更新は軽微な不具合修正を行いました。その中で修正を加えたWP_Query()に関する処理をあらためて見直したのでまとめておきます。

WP_Query関数

投稿やページのリクエストに対して様々な条件を設定してデータを取得できる関数としておなじみですが、たくさんの条件が設定できるがために知らなかった仕組みがあったので、その改修(チェーニング)を行いました。
WordPress Posted Displayでは、ウィジェットやショートコードで投稿などを出力する際に以下のようなコードを書いています。

$args = array(
	"post_status"         => "publish",
	"no_found_rows"       => true,
	"posts_per_page"      => esc_html( $instance['posts'] ),
	"ignore_sticky_posts" => true
);
$query = new WP_Query( $args );

実際は$argsにさらに、表示するタイプごとにpost__incategory__inなどが追加されるのですが、基本のパラメータは上記コード内の配列の値4つです。今回のバージョンアップで'no_found_rows' => true(デフォルトはfalse)を追加しました。

no_found_rows

このパラメータのデフォルト値falseで行われる動作としては、実行するSQLクエリにSQL_CALC_FOUND_ROWSオプションを設定するようになります。これ知らなかった……。まずはこのSQLクエリを理解することからはじめました。

SQL_CALC_FOUND_ROWSオプション

結構前から存在するMySQLのSQLクエリオプションのようで、MySQL4.0から存在していた模様。FOUND_ROWS() 関数とセットで使うようです。以下、MySQLのリファレンスより引用。

サーバーからクライアントに返される行の数を制限するために、SELECT ステートメントに LIMIT 句が含まれている場合があります。場合によっては、ステートメントを再度実行せずに、LIMIT を付けなかった場合にステートメントで返されるはずの行数を知っておくことが望ましいことがあります。この行数を取得するには、SELECT ステートメントに SQL_CALC_FOUND_ROWS オプションを付けてから、FOUND_ROWS() を呼び出します。 MySQL 5.6 リファレンスマニュアルより引用

要約すると、SQL_CALC_FOUND_ROWSオプションを付けたSELECTステートメントを実行後に、FOUND_ROWS()を実行すると行数を取得できるというものと解釈しました。

SELECT SQL_CALC_FOUND_ROWS * FROM wp_posts;
SELECT FOUND_ROWS();

wp_postsテーブル全体の行数を取得できました。しかし気をつけなければいけないのが、リファレンスマニュアルの引用にも記載があるように、LIMITを付けても返される件数は同じということをおさえておく必要があります。

何のための処理?

どうやらページ分割を前提にしているようです。全体で何記事あって1画面に何記事ずつ表示するか?という処理を行えるようにするためにあるパラメータのようで、ページングを行う必要がなければ動作させる必要がない=無駄な処理が減らせるということになるかなと思います。実際にWP_Queryクラスでどのような実装がされているか探ってみました。

WP_Queryクラス内での実装

WP_Queryクラス内に以下のコードが書いてあります。

// If true, forcibly turns off SQL_CALC_FOUND_ROWS even when limits are present.
if ( isset($q['no_found_rows']) )
    $q['no_found_rows'] = (bool) $q['no_found_rows'];
else
    $q['no_found_rows'] = false;

isset()で値がセットされていれば、bool型にキャストしています。"no_found_rows" => 1という設定を見かけます。個人的には最初からtrueをセットすればいいのになと思ったり。
この判定でtrue/falseがセットされます。その判定をもって、SQLクエリを作成しています。以下、WP_Queryクラス内の抜粋です。

if ( !$q['no_found_rows'] && !empty($limits) )
    $found_rows = 'SQL_CALC_FOUND_ROWS';

$this->request = $old_request = "SELECT $found_rows $distinct $fields FROM {$wpdb->posts} $join WHERE 1=1 $where $groupby $orderby $limits";

上記の条件を見ての通り、no_found_rows => falseでかつ、$limitが空でない場合にSQL_CALC_FOUND_ROWSオプションがセットされます。
ここでの$limitの値は、WP_Queryの引数として渡す配列パラメータのposts_per_pageの値がセットされます。この値に「-1」(全件取得)がセットされている場合は、$limitの値はセットされません。何もセットしていない場合は、管理画面の「表示設定」の「1ページに表示する最大投稿数」の数が初期値になります。

if ( $q['no_found_rows'] || ( is_array( $this->posts ) && ! $this->posts ) )
    return;

if ( ! empty( $limits ) ) {
    $this->found_posts = $wpdb->get_var( apply_filters_ref_array( 'found_posts_query', array( 'SELECT FOUND_ROWS()', &$this ) ) );
} else {
    $this->found_posts = count( $this->posts );
}

そして最後にFOUND_ROWS()関数の実装コードです。no_found_rows => trueであればreturn; で返します。しかし、no_found_rows => falseであれば、$limitが空でない場合にSELECTステートメントが実行されてしまうので、SQLが最初の投稿データ取得とあわせると2回動作することになります。これは削りたい。

まとめ

最後にまとめ。no_found_rows => trueをセットする場面は、ページングが必要ない実装でWP_Query関数を実行する場合であるということ。SELECTステートメントの実行回数を減らすことができる。

参考サイト

以下のサイトを参考にさせていただきました。

関数リファレンス: WP_Query
https://wpdocs.osdn.jp/%E9%96%A2%E6%95%B0%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9/WP_Query
PHP: 論理型(boolean)
http://php.net/manual/ja/language.types.boolean.php
MySQL情報関数: FOUND_ROWS()
http://dev.mysql.com/doc/refman/5.6/ja/information-functions.html#function_found-rows
WordPress Posted Display