WordPress ループを2回使う時の注意事項

WordPressで独自テーマを作成する際、サイト内の回遊を意識した設計をすることがあると思います。

 

例えば、サイト内でよく閲覧されている記事を表示させたり、特定の記事に関連した記事を表示させたりしたい場合です。

 

WordPressはプラグインが充実しているので、マッチしそうな機能を探せばあらかた見つかるので問題はないのですが、特殊な設計だと独自で作りこまないと実現できないケースもあります。

 

そこで今回は、WordPressで1ページ内に条件を変えて2回ループしたい時にハマった事象について紹介したいと思います。

 

事象

もしかしたらご存知の方もいるかもしれませんが、本サイトにてApache JMeterに関する記事をいくつか書いています。

 

そこでJMeterに関する記事の場合、本文の一番最後にJMeterに関する記事の目次を表示させるようにしたいと思い、以下の画面キャプチャの通り本文のメインループの後にJMeterの指定の記事IDを指定したサブループを実行させたところ、うまく動作しませんでした。

1703-1

(なお、上の画面は意図通りに動作している状態のキャプチャです)

 

う〜ん、なぜ???

 

Google先生に訊いたところ、本質的なところの理解は置いといて、とりあえず原因はわかりました。

 

原因と解決方法

ポイントは、メインループに対して「query_posts()」を使って表示条件を変えていたことに起因しています。

 

実はループ内ではDBから取得した値をいくつかのグローバル変数に格納しており、ループ実行時にグローバル変数を上書きします。

 

この上書きの表現が曲者ですが、実はグローバル変数の値をunsetした後、再度同じ名前でクエリーオブジェクトを作成しており、これにより取得していた情報(現在のページ番号やカテゴリなど)が消えてしまいまうことが原因です。

 

僕の記述したソースを確認すると僕はメインループとサブループでそれぞれで「query_posts()」を使用して表示条件を指定していたため、うまく動作しなかったというわけです。

 

また、「query_posts()」はメインループ用のタグのため、サブループで使用するには別のタグを使う必要があることもわかりました。

 

これらの内容を受けて、解消するために以下の2点を実施しました。

  • サブループで使用するタグを「get_post()」に変更
  • メインループ実行後にグローバル変数の値をリセット

 

それぞれを説明していきます。

 

まず、メインループ・サブループで利用するタグは以下の通りです。

 

メインループ:query_posts()

サブループ:get_posts() または WP_Query()

 

こちらの内容から、サブループには「get_posts()」を指定しました。

(「get_posts()」「WP_Query()」の違いは、指定していないパラメーター引数のデフォルト値も$wp_queryオブジェクトが保持するかどうかです。僕は、後から取得したくなった時に面倒だと思ったので、保持する派の「get_posts()」を使いました)

 

それぞれのタグに対応するリセット関数は以下の通りです。

 

query_posts() のリセットは wp_reset_query()

get_posts() のリセットは wp_reset_postdata()

WP_Query() のリセットは wp_reset_postdata()

 

間違えやすいので記述する際には必ず確認しましょう。

 

ちなみに、僕のsingle.phpのループ部分のソースは以下の通りです。

<!-- メインループ開始 -->
<?PHP
    $args = array('cat' => -5 , 'paged' => $paged);
    query_posts( $args );
?>

<?PHP
    if (have_posts()) :
    while (have_posts()) : the_post();
?>

メインループの中身

<?PHP
    endwhile;
    endif;
?>

<!-- メインループ終了 -->

<?PHP wp_reset_query(); ?>

<!-- JMeter関連の記事の場合に目次を表示 -->

<?PHP if (is_single(array(XXX,YYY,ZZZ(JMeterの記事ID))) : ?>

<h3>JMeter関連の記事はこちら</h3>
<ul>
<?PHP
    $posts = get_posts('include=XXX,YYY,ZZZ(JMeterの記事ID)&order=ASC');
    global $post;
?>

<!-- サブループ開始 -->

<?PHP if($posts): foreach($posts as $post): setup_postdata($post); ?>

    <li><a href="<?PHP the_permalink() ?>"><?PHP the_title(); ?></a></li>

<?PHP endforeach; ?>

<?PHP endif; ?>

 <!-- サブループ終了 -->

<?php wp_reset_postdata(); ?>

</ul>

<?PHP endif; ?>

<!-- 関連記事 -->
<!-- コメント表示・コメント投稿 -->

 

最後に

記事の中で「query_posts関数はグローバル変数を書き換えているため〜」と説明しましたが、そもそもループの中で何をやっているかのイメージがないと理解が難しいと思います。

 

これが本質的な部分ですね。

 

このサイトでは説明しませんが、とてもわかりやすいサイトがあったので紹介します。

興味のある方は参考にしてください。

WordPressで複数のループを使ってカスタム投稿一覧を自在に表示する方法

 

おまけ

グローバル変数と言っていますが、具体的には$wp_queryオブジェクトと呼ばれます。

 

ループ内の表示条件を指定する「query_posts()」「get_posts()」「WP_Query()」関数はよってたかって「$wp_query」を上書きしたり、新規で作成したりします。

 

また、ループ内で表示条件を変更する必要がない場合、つまり、上記3つの関数を使わないノーマルなループも存在します。

(カスタマイズしない人は、このループを使うことがほとんどかと思います)

 

<?PHP if (have_posts()) : ?>
<?PHP while (have_posts()) : the_post(); ?>

メインループの中身

<?PHP endwhile; ?>
<?PHP endif; ?>

 

このループは、テンプレート階層に従って自動的に$wp_queryを生成していますが、この場合はリセットする必要はありません。

 

というのも、対象オブジェクトがpostsになるループの場合は、$wp_query->queryが空配列になるためです。

 

実際に、このノーマルなループでリセットが必要かどうかを試したみたところ、問題なく動作することを確認しています。

 

Author:yukio iizuka
プロフィール画像
フリーランスとしてUX視点で業務支援しています。 HCD-Net認定 人間中心設計専門家 LEGO®︎ SERIOUS PLAY®︎ メソッドと教材活用トレーニング修了認定ファシリテーター Hi-Standard好きです。
http://yukioiizuka.com
mislead
MISLEADの記事に共感いただけましたら
いいねをお願いします。

コメント一覧

コメントはありません

コメントする

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください



       

© yukio iizuka All Rights Reserved...