MENU

【WordPress×PHP】ループの仕組みをSWELLで解説

【WordPress×PHP】ループの仕組みをSWELLで解説
目次

はじめに

【WordPress×PHP】テンプレートタグの使い方と注意点では、記事のタイトルや本文を取り出す「テンプレートタグ」を解説しました。テンプレートタグは「いま取り出している記事」の情報を返しますが、その「いまの記事」を決めているのが、今回解説するループです。

この記事では、SWELLのsingle.phpと、記事一覧を表示する部品のファイルを読みながら、ループの仕組みを確認します。ローカル環境で、ループが何回まわるのか、書き間違えると何が起きるのかも試しました。

このシリーズの全15回の構成は、第1回の記事の冒頭にまとめています。

ループとは?

WordPressは、ページを表示する前に、URLに合わせて表示する記事をデータベースから取り出しています。投稿ページのURLならその記事1件、トップページなら新しい順に何件か、カテゴリーのページならそのカテゴリーの記事、という具合です。この「URLに合わせて取り出した記事の集まり」を、メインクエリと呼びます。

ループは、メインクエリで取り出した記事を、1件ずつ順番に表示するための書き方です。基本の形は次のとおりです。

<?php
while ( have_posts() ) :
	the_post();

	// ここに1件分の表示を書く(the_title() や the_content() など)

endwhile;
?>

【PHP】for文・while文の使い方を解説で解説したwhile文の、{ }の代わりに:とendwhile;を使う書き方(代替構文)です。HTMLの中にPHPを書くテンプレートでは、この書き方がよく使われます。

SWELLのsingle.phpを読む

投稿ページのテンプレートである、SWELLのsingle.phpを見てみましょう(一部を省略しています)。

<?php
if ( ! defined( 'ABSPATH' ) ) exit;

get_header();
while ( have_posts() ) :
	the_post();

	$SETTING = SWELL_Theme::get_setting();
	$the_id  = get_the_ID();
	// …
?>
<main id="main_content" class="l-mainContent l-article">
	<article class="l-mainContent__inner" data-clarity-region="article">
		<?php
			do_action( 'swell_before_post_head', $the_id );
			// …(タイトル・アイキャッチ画像など)
		?>
		<div class="<?=esc_attr( apply_filters( 'swell_post_content_class', 'post_content' ) )?>">
			<?php the_content();  // 本文 ?>
		</div>
		<!-- …(シェアボタン・関連記事・コメントなど) -->
	</article>
</main>
<?php endwhile; ?>
<?php get_footer(); ?>

ポイントは次のとおりです。

  • get_header()(ヘッダー)とget_footer()(フッター)は、ループの外にある
  • 記事の中身(<main>から</main>まで)は、ループの中にある
  • the_post()のあとで、get_the_ID()やthe_content()を使っている
SWELLのsingle.phpの構造。get_header()とget_footer()はループの外、記事の中身(mainタグ)はwhileループの中にあり、the_post()のあとでテンプレートタグを使っている

投稿ページのメインクエリは、その記事1件だけです。ローカル環境で、投稿ページのループが何回まわるか数えてみると、1回でした。1件しかないのにループを使うのは、the_post()で「いまの記事」を決めてからテンプレートタグを使う、という決まった手順があるためです。

第5回の実務サンプルで使ったswell_before_post_headフックも、このループの中で呼ばれています。

have_posts()とthe_post()の役割

ループで使う2つの関数の役割は、次のとおりです。

関数役割戻り値
have_posts()次に取り出す記事が残っているか調べる残っていればtrue、なければfalse
the_post()次の記事を取り出し、「いまの記事」に設定するなし

the_post()を実行すると、テンプレートタグが指す記事が、次の記事に切り替わります。そのため、the_post()のあとでthe_title()やget_the_ID()を使うと、取り出した記事の情報が得られます。

ループの流れ。have_posts()で次の記事があるか調べ、あればthe_post()で次の記事を取り出してテンプレートタグで表示し、またhave_posts()に戻る。記事がなくなるとhave_posts()がfalseになりループが終わる

ローカル環境で、記事を13件用意し、トップページ(1ページに10件表示)のループの動きを記録してみました。current_postは、WordPressが「いま何件目か」を数えている番号です(0から始まります)。

have_posts()=true → the_post()後 current_post=0 タイトル=Hello world!
have_posts()=true → the_post()後 current_post=1 タイトル=記事12
have_posts()=true → the_post()後 current_post=2 タイトル=記事11
(…中略…)
have_posts()=true → the_post()後 current_post=9 タイトル=記事04
have_posts()=false

the_post()を実行するたびに番号が1つ進み、タイトルが次の記事に変わっています。10件目を取り出したあと、have_posts()がfalseになってループが終わりました。

ページごとのループの回数は、次のとおりでした。

ページ取り出した記事ループの回数
投稿ページ(記事01)1件1回
トップページ13件中10件10回
トップページの2ページ目13件中3件3回

1ページに表示する件数は、管理画面の「設定」→「表示設定」にある「1ページに表示する最大投稿数」で決まります(初期値は10件)。メインクエリは、URLとこの設定をもとに、そのページで表示する分だけの記事を取り出しています。

一覧ページのループ:SWELLのloop_main.php

トップページやカテゴリーのページなど、記事の一覧を表示するページでは、SWELLはparts/post_list/loop_main.phpという部品のファイルでループを書いています(一部を省略しています)。

// 記事がなかった場合
if ( ! have_posts() ) :
	$not_founded_text = __( '記事が見つかりませんでした。', 'swell' );
	echo apply_filters( 'swell_post_list_404_text', '<p>' . $not_founded_text . '</p>' );
	return;
endif;

// ループ
$loop_ct = 0;
echo '<ul class="' . esc_attr( $ul_class ) . '">';
while ( have_posts() ) :
	the_post();

	// インフィード
	if ( $infeed_interval && $loop_ct && ( 0 === $loop_ct % $infeed_interval ) ) :
		SWELL_Theme::get_parts( 'parts/post_list/infeed_ad', $loop_ct );
	endif;

	SWELL_Theme::get_parts( 'parts/post_list/' . $parts_name, $li_args );

	$loop_ct++;
endwhile;
echo '</ul>';

読み取れることは、次のとおりです。

  • ループの前にhave_posts()で記事があるか調べ、1件もなければ「記事が見つかりませんでした。」と表示して終わる
  • <ul>と</ul>はループの外で1回だけ出力し、記事1件分(<li>)の表示は、ループの中で別の部品ファイルを読み込んで出力している
  • $loop_ctで、何件表示したかを自分で数えている
  • $loop_ct % $infeed_interval(余りの計算)が0になるたびに、広告(インフィード広告)を差し込んでいる

最後の仕組みは、SWELL設定の「広告コード」タブにある「インフィード広告の間隔」で使われています。たとえば間隔を4にすると、4件ごとに広告が入ります。ループの中で件数を数えれば、「○件ごとに何かを入れる」という処理が書けるわけです。

よくある間違い:the_post()を書き忘れる

ループで一番多い間違いが、the_post()の書き忘れです。

while ( have_posts() ) :
	// the_post(); を書き忘れた
	the_title();
endwhile;

have_posts()は「次の記事が残っているか」を調べるだけなので、the_post()で記事を取り出さない限り、ずっとtrueのままです。つまり、ループが終わらなくなります(無限ループ)。

ローカル環境で、5回で強制的に止める仕組みを付けて試したところ、次のようになりました。

1回目 Hello world! current_post=-1
2回目 Hello world! current_post=-1
3回目 Hello world! current_post=-1
4回目 Hello world! current_post=-1
5回目 Hello world! current_post=-1

番号は-1(まだ1件も取り出していない状態)のまま、同じ記事のタイトルが繰り返されています。

the_post()を書き忘れたときの動き。記事を取り出さないので、have_posts()がずっとtrueのままになり、同じ記事を表示し続ける無限ループになる

強制的に止める仕組みを外すと、ページが読み込み中のまま止まり、PHPの実行時間の上限に達したところで、次のエラーになりました(上限を5秒にして試しました)。

Fatal error: Maximum execution time of 5 seconds exceeded in …

Localの初期設定では、この上限が1200秒(20分)です。「ページがいつまでも表示されない」ときは、the_post()が抜けていないか確認しましょう。

ループの前やループの後でもテンプレートタグは動きますが、指す記事に注意が必要です。試したところ、ループの前のget_the_title()は1件目の記事(トップページでは「Hello world!」)、ループの後は最後に取り出した記事(「記事04」)のタイトルを返しました。テンプレートタグは、ループの中で使うのが基本です。

実務サンプル:一覧ページに「全○件中 ○〜○件目を表示」を出す

ここまでの内容を使って、記事一覧の上に「全13件中 11〜13件目を表示」のような件数を表示してみましょう。

SWELLの部品ファイルを子テーマにコピーする

SWELLは、部品ファイルを読み込むとき、子テーマに同じ場所・同じ名前のファイルがあれば、そちらを優先する仕組みになっています。第1回で解説した、子テーマのsingle.phpが親テーマより優先されるのと同じ考え方です。

そこで、親テーマの次のファイルを、子テーマの同じ場所にコピーします。

(コピー元)wp-content/themes/swell/parts/post_list/loop_main.php
(コピー先)wp-content/themes/swell_child/parts/post_list/loop_main.php

子テーマにはpartsフォルダもpost_listフォルダもないので、フォルダを作ってからコピーします。

件数を表示するコードを追加する

コピーしたloop_main.phpの// ループの行の直前に、次のコードを追加します。

// 件数の表示(子テーマで追加)
global $wp_query;
$total    = (int) $wp_query->found_posts;
$per_page = (int) get_query_var( 'posts_per_page' );
$paged    = max( 1, (int) get_query_var( 'paged' ) );
$first    = ( $paged - 1 ) * $per_page + 1;
$last     = $first + $wp_query->post_count - 1;

echo '<p class="kurumico-list-count">全' . $total . '件中 ' . $first . '〜' . $last . '件目を表示</p>';

子テーマのstyle.cssには、次のCSSを追加します。

.kurumico-list-count {
	margin-bottom: 1em;
	font-size: .9em;
	color: #666;
}

トップページの2ページ目を開くと、記事一覧の上に次のように表示されました。

SWELLの記事一覧の上に「全13件中 11〜13件目を表示」と表示され、その下に記事03・記事02が並んでいる

1ページ目は「全13件中 1〜10件目を表示」、検索結果のページでは「全9件中 1〜9件目を表示」となりました。記事が1件もない場合は、追加したコードより前のreturnで処理が終わるので、件数は表示されません。

コードのポイントは次のとおりです。

  • $wp_queryは、メインクエリの情報が入った変数。関数の外で作られた変数なので、globalを付けて使う(->の書き方は第8回で解説します)
  • found_postsは全部の件数(13件)、post_countはこのページで取り出した件数(3件)
  • get_query_var( 'paged' )は、いま何ページ目か。1ページ目では0になるので、max( 1, … )で1にそろえている
  • 「何件目から」は(ページ番号 − 1)× 1ページの件数 + 1で計算している

子テーマにコピーした部品ファイルは、親テーマのファイルの代わりに使われ続けます。SWELLのアップデートでloop_main.phpが改良されても、子テーマ側には反映されません。アップデートのあとは、親テーマのファイルと見比べて、必要なら子テーマ側にコピーし直してください。

よくある質問・エラー対処

Q. 1ページに表示する件数を変えたいです

→ 管理画面の「設定」→「表示設定」にある「1ページに表示する最大投稿数」で変えられます。実務サンプルのコードは、この設定値を使って計算しているので、件数を変えても正しく表示されます。


Q. 子テーマにコピーした部品ファイルを使わないように戻したいです

→ 子テーマのparts/post_list/loop_main.phpを削除すれば、親テーマのファイルが使われる状態に戻ります。


Q. ループの中で、別の記事の一覧も表示したいです

→ メインクエリとは別に記事を取り出す方法(WP_Query)を使います。第11回で解説します。

おわりに(まとめ)

まとめ

ループは、WordPressのテンプレートでいちばん基本になる仕組みです。

  • ✅ メインクエリは、URLに合わせてWordPressが取り出した記事の集まり
  • ✅ ループは、メインクエリの記事を1件ずつ表示する書き方
  • ✅ have_posts()は次の記事があるか調べ、the_post()は次の記事を「いまの記事」にする
  • ✅ 投稿ページでもループを使い、1回だけまわる
  • ✅ 1ページの件数は「設定」→「表示設定」で決まる
  • ✅ the_post()を書き忘れると無限ループになる
  • ✅ SWELLの部品ファイルは、子テーマにコピーすれば書き換えられる

次回は、ページの種類によって処理を分ける「条件分岐タグ」について解説します。

次回は、条件分岐タグ(is_single・is_page など)について解説予定です。

この記事が、少しでも誰かのお役に立てれば幸いです。

関連記事

当サイトの記事で使用したVBAなどのサンプルをDLできます

この記事のサンプルはありません!

ダウンロードページへは下のカードをクリックすればジャンプできます。
よろしければご利用ください!


【CSS】テキスト関連プロパティについて基本解説|Webの基礎


よかったらシェアしてね!
  • URLをコピーしました!
目次