WordPressのループの仕組みを深く知る query_posts() と get_posts() の違い

WordPressループの基本

<?php if (have_posts()) : ?>
	<?php while (have_posts()) : the_post(); ?>
	<!-- 表示内容を記述 -->
	<h2><a href="<?php the_permalink() ?>"><?php the_title(); ?></a></h2>
	<div <?php post_class(); ?> id="post-<?php the_ID(); ?>">
		<?php the_content(); ?>
	</div>
	<?php endwhile; ?>
<?php endif; ?>

これは WordPressループの基本構文ですね。

このループ構文を記述すれば、投稿記事一覧、個別記事、固定ページ、カテゴリーアーカイブ、年月別アーカイブ、フィード、検索、などなど、それぞれのページに必要なデータが表示されます。

これって不思議だと思いませんか?

○○のデータを取得する」とかいう条件がどこにもありません。

これを紐解いていくと WordPress のループの仕組みを深く理解することができるようになります。

WP Query オブジェクト

WP_Query

WP_Query は wp-includes/query.php に定義されているクラスで、WordPress への複雑なリクエストを取り扱います。
グローバル変数 $wp_query に、現在のリクエストを定義する情報を与えることで、どのタイプのクエリを扱っているのかを確定し、要求されたデータを取り出します。

つまり、特定のURLにアクセスするだけで、そのページに必要なデータを自動的に取得して、グローバル変数 $wp_query に格納してくれる のです。

では、グローバル変数 $wp_query とはどのようなものでしょうか?

投稿記事一覧ページのテンプレートの一番はじめに以下のコードを記述して、$wp_query を表示させてみましょう。

<?php print_r($wp_query); ?>

じゃんッ!↓

WP_Query Object
(
    [query_vars] => Array
        (
            [error] => 
            [m] => 0
            [p] => 0
            [post_parent] => 
            [subpost] => 
            [subpost_id] => 
            [attachment] => 
            [attachment_id] => 0
            [name] => 
            [static] => 
            [pagename] => 
            [page_id] => 0
            [second] => 
            [minute] => 
            [hour] => 
            [day] => 0
            [monthnum] => 0
            [year] => 0
            [w] => 0
            [category_name] => 
            [tag] => 
            [cat] => 
            [tag_id] => 
            [author_name] => 
            [feed] => 
            [tb] => 
            [paged] => 0
            [comments_popup] => 
            [meta_key] => 
            [meta_value] => 
            [preview] => 
            [s] => 
            [sentence] => 
            [fields] => 
        // 省略
        )
)

たくさん出てきましたね〜。
最初の方には関連するグローバル変数がずらっと並びます。

注目すべきは、このずーっと下の方にある、[request] と [posts] です。

    [request] =>  SELECT SQL_CALC_FOUND_ROWS  wp_posts.* FROM wp_posts  WHERE 1=1  AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private')  ORDER BY wp_posts.post_date DESC LIMIT 0, 10
   [posts] => Array
        (
            [0] => stdClass Object
                (
                    [ID] => 187
                    [post_author] => 1
                    [post_date] => 2012-02-13 15:22:35
                    [post_date_gmt] => 2012-02-13 06:22:35
                    [post_content] => 投稿3の内容
                    [post_title] => 投稿3
                    [post_excerpt] => 
                    [post_status] => publish
                    [comment_status] => closed
                    [ping_status] => open
                    [post_password] => 
                    [post_name] => %e6%8a%95%e7%a8%bf3
                    [to_ping] => 
                    [pinged] => 
                    [post_modified] => 2012-02-13 15:22:35
                    [post_modified_gmt] => 2012-02-13 06:22:35
                    [post_content_filtered] => 
                    [post_parent] => 0
                    [guid] => http://localhost/wordpress/dev/?p=187
                    [menu_order] => 0
                    [post_type] => post
                    [post_mime_type] => 
                    [comment_count] => 0
                    [filter] => raw
                )

            [1] => stdClass Object
                (
                    [ID] => 185
                    [post_author] => 1
                    [post_date] => 2012-02-13 15:22:03
                    [post_date_gmt] => 2012-02-13 06:22:03
                    [post_content] => 投稿2の内容
                    [post_title] => 投稿2
                    [post_excerpt] => 
                    [post_status] => publish
                    [comment_status] => closed
                    [ping_status] => open
                    [post_password] => 
                    [post_name] => %e6%8a%95%e7%a8%bf2
                    [to_ping] => 
                    [pinged] => 
                    [post_modified] => 2012-02-13 15:22:03
                    [post_modified_gmt] => 2012-02-13 06:22:03
                    [post_content_filtered] => 
                    [post_parent] => 0
                    [guid] => http://localhost/wordpress/dev/?p=185
                    [menu_order] => 0
                    [post_type] => post
                    [post_mime_type] => 
                    [comment_count] => 0
                    [filter] => raw
                )
                // 省略
        )

    [post] => stdClass Object
        (
            [ID] => 187
            [post_author] => 1
            [post_date] => 2012-02-13 15:22:35
            [post_date_gmt] => 2012-02-13 06:22:35
            [post_content] => 投稿3の内容
            [post_title] => 投稿3
            [post_excerpt] => 
            [post_status] => publish
            [comment_status] => closed
            [ping_status] => open
            [post_password] => 
            [post_name] => %e6%8a%95%e7%a8%bf3
            [to_ping] => 
            [pinged] => 
            [post_modified] => 2012-02-13 15:22:35
            [post_modified_gmt] => 2012-02-13 06:22:35
            [post_content_filtered] => 
            [post_parent] => 0
            [guid] => http://localhost/wordpress/dev/?p=187
            [menu_order] => 0
            [post_type] => post
            [post_mime_type] => 
            [comment_count] => 0
            [filter] => raw
        )
)

必要なデータを取得するクエリが [request] で、取得されたデータが [posts] だということが分かるでしょう。
さらに、[post] には、[posts] のデータが順番にセットされていきます。

もう一度、WordPressループの基本構文を見てください。

<?php if (have_posts()) : ?>
	<?php while (have_posts()) : the_post(); ?>
	<!-- 表示内容を記述 -->
	<h2><a href="<?php the_permalink() ?>"><?php the_title(); ?></a></h2>
	<div <?php post_class(); ?> id="post-<?php the_ID(); ?>">
		<?php the_content(); ?>
	</div>
	<?php endwhile; ?>
<?php endif; ?>

the_posts() 関数というのがあります。
この関数こそが、 取得したデータを順番にグローバル変数 $post に読み込んでいるのです。
もしこの関数を記述しなければ、 $post は永遠に最初のデータから動かないので、無限ループになります。

WordPressのテンプレートはクエリベースである

ここで重要なことが判明してきます。

最初に「特定のURLにアクセスするだけで、そのページに必要なデータを自動的に取得して、グローバル変数 $wp_query に格納してくれる」と書きましたが、正確には「URLから必要なデータのクエリを発行し、グローバル変数 $wp_query に格納した後、テンプレート階層に従ってデータを表示する。」となります。

この流れをよーく覚えておきましょう。

テンプレート階層

WordPressループを変更する query_posts()

WordPressループを変更するには、query_posts() を使います。

これは、取得するデータのクエリを動的に発行して $wp_query の値を変更することを意味します。

<?php query_posts(引数); ?>

<?php if (have_posts()) : ?>
	<?php while (have_posts()) : the_post(); ?>
	<!-- 表示内容を記述 -->
	<?php endwhile; ?>
<?php endif; ?>

<?php wp_reset_query(); ?>

引数として条件を与えることで、データを取得するクエリを変更します。
条件の指定方法はこちら→ query_posts()

最後に wp_reset_query() で、動的に変更した $wp_query をリセットすることを忘れないでください。

マルチループを生成する get_posts

ループを生成する関数には、他に get_posts() があります。
get_posts() は、マルチループを生成するための関数です。

<?php $myposts = get_posts(引数); ?>
<?php foreach($myposts as $post) : setup_postdata($post); ?>
	<!-- 表示内容を記述 -->
<?php endforeach; ?>

query_posts() と同様に、引数で条件を指定することで、マルチループを生成することができます。

生成できるループは query_posts() とほぼ同じですが、両者には決定的な違いがあります。

query_posts と get_posts の違い

query_posts と get_posts の違い

get_posts() は、単にデータを取得するだけで、グローバル変数 $wp_query には影響を与えません。
query_posts() は、グローバル変数 $wp_query を変更します。

この違いは重要です。

個別記事ページ(シングルページ)を表示させてみましょう。

一番最初にグローバル変数 $wp_query の内容を表示させてみると、[is_single] => 1 となっており、シングルページのフラグが立っています。シングルページなのですから当然です。

ここで、個別記事ページの一部に、固定ページの一覧を表示させたいとします。
query_posts() でも get_posts() でも、どちらでも固定ページ一覧のループは生成可能です。

しかし、この時は、get_posts() を使うべきです。
query_posts() は使ってはいけません。

それはなぜか?

ループ内に is_single() という条件分岐タグを入れてみれば分かります。

get_posts() を使えば、グローバル変数 $wp_query には影響を与えませんので、is_single() は正常に動作します。

<?php $myposts = get_posts('post_type=page'); ?>
<?php foreach($myposts as $post) : setup_postdata($post); ?>

<?php if (is_single()) : ?>
<?php the_title(); ?>
<?php endif; ?>

<?php endforeach; ?>

一方、query_posts() では、グローバル変数 $wp_query が上書きされる、つまり [is_single] => となり、is_single() ではシングルページと判定されません。

<?php query_posts('post_type=page'); ?>

<?php if (have_posts()) : ?>
	<?php while (have_posts()) : the_post(); ?>
		<?php if (is_single()) : ?>
		<?php the_title(); ?>
		<?php endif; ?>
	<?php endwhile; ?>
<?php endif; ?>

<?php wp_reset_query(); ?>

query_posts() は WordPressループ内で使うべき” と言われるのはこういうことです。

まとめ

・WordPress は、アクセスしたURLに応じて、必要なデータを自動的に取得してくれる。これを「WordPressループ」と呼ぶ。
・WordPress が自動的に取得したデータは、グローバル変数 $wp_query に格納される。
・WordPressループの表示条件を変更したい場合は query_posts() を使う。
・WordPressループとは別に、新しいマルチループを生成する場合は get_posts() を使う。