WordPressのユーザーコミュニティである『WordBench』(2018年9月23日をもって終了 WordPress Meetup に移行しています)にて「ループ」をテーマにした約4時間のハンズオンセミナーを行いました。
4時間、ループのことしかやらない勉強会はWordBenchでも異色だと思うのですが、参加された方の感想はおおむね好評だったようです。公式ドキュメントのCodex日本語版の「The Loop」の項が未翻訳というのも理由の一つかもしれません。
[2017.3.11追記 日本語訳されてました!]
さて、WordPressカスタマイズのキモともいえるこのループ。私もかつて解説本から学んだとおり、
<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?> <!-- 記事があるときの表示内容をここに書く --> <?php endwhile; else : ?> <!-- 記事がないときの表示内容をここに書く --> <?php endif; ?>
てな感じでしゃしゃっと書いています。WordPressを始める前にMovableTypeをちょっとかじったこともあり、ループについて「あーそんなもんかー」という感覚で、疑問も抱かずやっていたのですね。
で、今回の勉強会をやるにあたって、はたと思ったのです。「if とか while が条件分岐ってのはわかるけど、この the_post() というのはそもそも何をしてるヤツなんだ?」と。
そう思ってググってみたものの the_post() に関する記事がほとんど見つからないという現実に直面。頼みのCodexも未翻訳状態で残念なことになっている。それならひとつ、学習をかねてやってみようじゃありませんか……ということでこの記事を書くことにしました。
WordPressコミュニティを運営してるくせに実はよくわかってない点も多々ありますので、そのあたりはバシバシつっこんでいただければ幸いに存じます。では始めていきましょう。
超・ミニマムなテーマを有効化する
ループの構造をわかりやすくするため、最低限の要素しか記述していないテーマをつくりました。index.php と style.css だけのテーマ『Skeleton』です。よろしければ、以下よりダウンロード→サーバにアップロード→有効化してみてください。
ループの基本構造をもうちょっとほどいてみよう
『Skeleton』の index.php をひらいてみてください。ループのスタートを示す11行目の記述です。
<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
いきなり if と while と the_post が並んでますね。個人的にはこの1行がループに対する苦手意識の第一歩じゃないかと思うので、もうちょっとほどいてみることにします。プログラマーさんなら「こんな書き方エレガントじゃない!」とおっしゃることでしょうが、そこはご容赦ください。
ついでに、記事がないときの処理を省いて(本来なら書いた方がベターです)さらにループを簡潔にしてみましょう。『Skeleton』の10~17行目を次のように書き換えてみます。
<?php if ( have_posts() ) : ?> <?php while ( have_posts() ) : ?> <?php the_post(); ?> <p><?php the_title(); // 記事のタイトルを表示 ?></p> <?php endwhile; ?> <?php endif; ?>
このように見ると、ループは if ~ endif が while ~ endwhile を包み込む、二重の構造でできていることが分かります。
そもそも if ~ endif と while ~ endwhile がやっていることって?
ここで改めて if ~ endif, while ~ endwhile の動作についてふれておきましょう。ともにPHPで利用可能な制御構造の仕組みで、PHPのプログラム内で次のような振る舞いをします。
- if ~ endif……if に与えられた条件が満たされる場合 if ~ endif の間の処理を行う
- while ~ endwhile……while に与えられた条件が満たされる場合 while ~ endwhile の間の処理を「繰り返して」行う
この制御構造をWordPressのループに当てはめてみます。ループ内に2回出てくる have_posts() は「表示できる記事がある」ことを示す目印のようなものだと考えてください。
have_posts() は if, while 双方に条件として与えられているので、上記をふまえるとWordPressのループでは
- if ~ endif …… 表示できる記事がある場合 if ~ endif の間の処理を行う
- while ~ endwhile …… 表示できる記事がある場合 while ~ endwhile の間の処理を「繰り返して」行う
ということになります。
繰り返しの条件を与えないと while ~ endwhile は働いてくれない
「表示できる記事がある場合」という条件は同じながら if ~ endif と while ~ endwhile の処理には、実は大きな違いがあります。
while ~ endwhile の説明をもう一度ご覧ください。while ~ endwhile の説明では「繰り返して」という表現をあえてかっこ書きで強調しています。
その理解を深めていただくため、PHPマニュアルの while の項よりサンプルコードを引用してみます。while ~ endwhile を用いて同じ処理を10回繰り返し、1から10までを表示するコードです。
$i = 1; while ($i <= 10): echo $i; $i++; endwhile;
《コード解説》
1行目……while ~ endwhile に入る前に、表示すべき数字(=処理を繰り返した回数)を $i という入れ物(PHPでは「変数」といいます)に入れます。
2行目……while で「$i が10以下の場合」という条件を判断。1行目の記述により、そのまま while ~ endwhile のループに入ります。
3行目……$i の内容を表示( 1 が表示されます)。
4行目……$i の数値を 1 加算( $i の値が 2 となります)。
5行目……ループ終了。
その後処理は2行目に戻り $i の値が 2 であることから2回目のループに入っていきます。以降 $i の値が 10 になるまで処理が繰り返されるというわけです。
さて、このコードでたいへん重要な役割を果たしているのが、$i をカウントアップしている
$i++;
の1行。これがないと $i の値は 1 のままなので何度ループしても「$i が10以下の場合」の条件が変わらず
111111111111111111111111111...
と、終わりのない表示結果となってしまいます。
これらのことから while ~ endwhile においては
- ループの外側で初期値を与える
- ループ内でその値を変える
のが使いこなしのカギとなるのがお分かりいただけるでしょうか。
the_post() がないとループはどうなる?
前フリが長くなりましたが、ようやく the_post() の話にたどりつきました。
これまでの説明をふまえて、『Skeleton』テーマから試しに the_post() を外して実行してみます。
<?php if ( have_posts() ) : ?> <?php while ( have_posts() ) : ?> <p><?php the_title(); // 記事のタイトルを表示 ?></p> <?php endwhile; ?> <?php endif; ?>
なんということでしょう。最初の記事のタイトルだけがただただ表示されて、終わりのない状態になってしまったではありませんか(ブラウザの「読み込み停止」をクリックすれば中止できます)。
そうです。the_post() にはループ内で値をカウントアップする役割があるのです。
Codexを見ても、解説書を読んでも、the_post() についてはサラリと触れられているに過ぎませんが、実はすごく重要な役割をもっているテンプレートタグだったのでありました。
the_post() の下地を作る $wp_query
さきほど while ~ endwhile においては
- ループの外側で初期値を与える
- ループ内でその値を変える
のが使いこなしのカギとなることを書きました。
ということは、WordPressでもループの外側で何らかの初期値が与えられ、ループ内で the_post() がその値を加減算していると推測できます。
ループを成り立たせるために、どんな値が動いているのでしょう?
カギを握っているのは、$wp_query という隠れた変数です。
この変数は、WordPress本体側であらかじめ「予約」されているものなので、テンプレートなどで同じ名前の変数を作っちゃうとエラいことになる、まぜるな危険的な変数です。
『Skeleton』テーマを有効化した状態でサイト(index.php)にアクセスしたとき、WordPress本体は $wp_query にこんな値を入れてくれます(ものすごく端折ってるので、実はほかにもさまざまな値が入っているのですが)。
管理画面の[設定]-[表示設定]-「1ページに表示する最大投稿数」が10件だった場合…
- 投稿10件分のタイトルや本文、作成日・更新日など(投稿には0~9の連番がふられてます)
- そのうち、最初の投稿のタイトルや本文、作成日・更新日など
- $wp_query に入っている記事の件数(10)
- ループカウンタ(ループ外にいることから初期値 -1 が入ってます)
変数には「繰り返しの条件を与えないと while ~ endwhile は働いてくれない」の項で引用した $i のように単一の値だけでなく、複数の値を情報を詰め込むこともできます(ここを詳しく説明するとPHPの話になるので「変数とはそういうものだ」ってくらいの認識でOKです)。
n番目の記事を表示するとき、ループカウンタはn-1になっている
これをふまえてループの流れを見ていきましょう。『Skeleton』はこんなコードでした。
<?php if ( have_posts() ) : ?> <?php while ( have_posts() ) : ?> <?php the_post(); ?> <p><?php the_title(); // 記事のタイトルを表示 ?></p> <?php endwhile; ?> <?php endif; ?>
まずは、if, while の双方で出てくる have_posts() の役割です。have_posts() は上記1~4の情報をもとに次のような動作をします(ほかにもあるけど省略)。
- ループカウンタ + 1 < $wp_query 内の記事件数 なら true(真)を返す
- ループカウンタ + 1 = $wp_query 内の記事件数 かつ $wp_query 内の記事件数 > 0 なら false(偽)を返す
上記より if ( have_posts() ) は「真」となるので、こんどは while ( have_posts() ) の判断に入ります。ループ1回目では if ( have_posts() ) と同じ条件なので、繰り返し処理に入ってもOKですね。
ということでループ1回目の the_post() は、ざっくりとこんな振る舞いをすることになります。
- ループカウンタを 1 加算する(0 になる)
- 10件の投稿のうち連番 0 の投稿を表示する準備をする
the_post() がこのようにお膳立てしてくれたので、ループ内のテンプレートタグ the_title() で連番 0 番目の投稿タイトルが表示できることになります。
それでは2回目の while ( have_posts() ) 。1回目の処理によりループカウンタは 0 ですね。have_posts() の振る舞いより
- ループカウンタ + 1 < $wp_query 内の記事件数
となり while ( have_posts() ) は「真」。2回目のループに入って the_post() の処理は下記のようになります。
- ループカウンタを 1 加算する(1 になる)
- 10件の投稿のうち連番 1 の投稿(つまり、2番目の投稿)を表示する準備をする
お分かりいただけるでしょうか? n番目の記事を表示する際のループカウンタが n-1 になっていることが。ということで、一連の処理を繰り返して10回目のループが終わったところでループカウンタが 9 になります。
「まだまだ繰り返すよー」と11回目のループに入ろうとしても while ( have_posts() ) の箇所で have_posts() の振る舞いである
- ループカウンタ + 1 = $wp_query 内の記事件数 かつ $wp_query 内の記事件数 > 0 なら false を返す
が満たされ while ~ endwhile 内の処理はできずループを抜ける、ということになるわけです。
ループの詳しい挙動は query.php にて
ここまでかいて約6000字。ところどころ冗長な表現になってしまったかもしれませんが the_post() の役割とその大切さを少しでも感じていただけたのでしたら、幸いです。
この記事では have_posts() や the_post() の処理をある程度簡略化して説明しました。PHPのコードを追いながらもう少し詳しく知ってみたい方は wp-includes/query.php をぜひご参照ください。他のメソッド(機能)と連携しながら、さまざまな処理をしていることがお分かりいただけるかと思います。
コメントを残す