@Webourgeon_com さんのブログに次の記事がアップされました。
- 「WordPressの表示フローとLoopとthe_title()〜いろいろ考えたメモ〜」
ループ無しでthe_title()とthe_content() を書くとthe_title()の結果は表示されるのですが、the_content() の結果は全く表示されません。
これが一体なぜだろう?という話ですね。
実はちょうど僕も近いところを調べていたところだったので興味を持ちました。以下は @Webourgeon_com さんの記事を受けての検証記事ですので、できれば上の記事を読んでいただいてからこちらも読んで頂ける方が分かり易いかも知れません(この記事だけ読むと唐突すぎるかも、です)。
§ 検証
the_title も the_content も、どちらも内部的には別の関数、get_the_title、get_the_content をコールしています。この2つの関数いずれも、関数内の最初で次のようにコールしています。
1 |
$post = get_post(); |
get_post() を引数無しで呼び出すと、その時点で $GLOBALS[‘post’] に格納されている WP_Post データを取得しようと試みます。ここでは、get_the_title、get_the_content いずれの関数でも、ループの内外に関わらず、データは取得できていました(ちなみに、僕が興味を持って調べていたのはこのあたり)。
次に、get_the_title() のソースを眺めてみます。
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
function get_the_title( $post = 0 ) { $post = get_post( $post ); $title = isset( $post->post_title ) ? $post->post_title : ''; $id = isset( $post->ID ) ? $post->ID : 0; if ( ! is_admin() ) { if ( ! empty( $post->post_password ) ) { $protected_title_format = apply_filters( 'protected_title_format', __( 'Protected: %s' ) ); $title = sprintf( $protected_title_format, $title ); } else if ( isset( $post->post_status ) && 'private' == $post->post_status ) { $private_title_format = apply_filters( 'private_title_format', __( 'Private: %s' ) ); $title = sprintf( $private_title_format, $title ); } } return apply_filters( 'the_title', $title, $id ); } |
特に気になる点はありません。各種フィルターが影響してなければ、ポイントは 105 行目になると思います。
では次は get_the_content() を見てみます。
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
function get_the_content( $more_link_text = null, $stripteaser = false ) { global $more, $page, $pages, $multipage, $preview; $post = get_post(); if ( null === $more_link_text ) $more_link_text = __( '(more...)' ); $output = ''; $hasTeaser = false; // If post password required and it doesn't match the cookie. if ( post_password_required() ) return get_the_password_form(); if ( $page > count($pages) ) // if the requested page doesn't exist $page = count($pages); // give them the highest numbered page that DOES exist $content = $pages[$page-1]; if ( preg_match('/<!--more(.*?)?-->/', $content, $matches) ) { $content = explode($matches[0], $content, 2); if ( !empty($matches[1]) && !empty($more_link_text) ) $more_link_text = strip_tags(wp_kses_no_null(trim($matches[1]))); $hasTeaser = true; } else { $content = array($content); } if ( (false !== strpos($post->post_content, '<!--noteaser-->') && ((!$multipage) || ($page==1))) ) $stripteaser = true; $teaser = $content[0]; if ( $more && $stripteaser && $hasTeaser ) $teaser = ''; $output .= $teaser; if ( count($content) > 1 ) { if ( $more ) { $output .= '<span id="more-' . $post->ID . '"></span>' . $content[1]; } else { if ( ! empty($more_link_text) ) $output .= apply_filters( 'the_content_more_link', ' <a href="' . get_permalink() . "#more-{$post->ID}\" class=\"more-link\">$more_link_text</a>", $more_link_text ); $output = force_balance_tags($output); } } if ( $preview ) // preview fix for javascript bug with foreign languages $output = preg_replace_callback('/\%u([0-9A-F]{4})/', '_convert_urlencoded_to_entities', $output); return $output; } |
よく見るととても面白いです。get_the_content() が返すであろうデータ $post->post_content は、ここでは一度しか利用されておらず、またはそれは出力ではなく、noteaser 記法の有無の判定だけに利用されていることです。
では、get_the_content() は何を返しているのか?
コードを見ると、それはグローバル変数「$pages」のようです。
試しに、ループなどで the_post() を呼び出しする前に、the_content() をコールしてみると、$pages の内容は設定されておらず、結果として the_content() 関数は何も出力しません。
では、$pages の内容はどこで設定されているのかというと、結論から言うと、setup_postdata() という関数の中です。ループ等で the_post() をコールする時も、その the_post() の中で setup_postdata() はコールされます。
それでは、setup_postdata() の内容を確認してみます。
3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 |
function setup_postdata($post) { global $id, $authordata, $currentday, $currentmonth, $page, $pages, $multipage, $more, $numpages; $id = (int) $post->ID; $authordata = get_userdata($post->post_author); $currentday = mysql2date('d.m.y', $post->post_date, false); $currentmonth = mysql2date('m', $post->post_date, false); $numpages = 1; $page = get_query_var('page'); if ( !$page ) $page = 1; if ( is_single() || is_page() || is_feed() ) $more = 1; $content = $post->post_content; if ( strpos( $content, '<!--nextpage-->' ) ) { if ( $page > 1 ) $more = 1; $multipage = 1; $content = str_replace("\n<!--nextpage-->\n", '<!--nextpage-->', $content); $content = str_replace("\n<!--nextpage-->", '<!--nextpage-->', $content); $content = str_replace("<!--nextpage-->\n", '<!--nextpage-->', $content); $pages = explode('<!--nextpage-->', $content); $numpages = count($pages); } else { $pages = array( $post->post_content ); $multipage = 0; } do_action_ref_array('the_post', array(&$post)); return true; } |
たしかに、3640 行目以降で $post->post_content をベースにして、$pages の内容はここで作られていますね。
§ まとめ
「ループ外で、the_title() をコールしたときにはタイトルが出力されるが、the_content() は出力されないのは何故だろう?」
答えは、「the_content() が実際に出力するものはページネーションの為に準備された $pages というグローバル変数の内容で、またその $pages は、setup_postdata() 関数がコールされる以前にはまだ初期化されていないため」となります。
「タイトル表示できるなら、投稿内容も表示できるだろう」という感じではありますが、実際の仕組みはこんな風になっていました。
§ 考察
とはいえ、本稿の発端となる @Webourgeon_com さんのように疑問に感じる面もありそうな実装仕様ではあります。それについて少しだけ考えをまとめてみました。
まず、データを取ってくることと、それを加工することはプログラミングでは一般に別のフェーズです。またデータの加工は、それを利用するにあたって可能な限り実際に利用する場所に近いところで行うことが望ましいです。これらの事から、取得したデータを利用する局面…この記事の文脈では「the_post() をコールするその時」に、出力の為にデータをセットアップする、というのは利に適った実装仕様だと僕は思いましたが、どうでしょうか。
なお、この問題は本質的に WordPress のループとは関連がありません。ループ内でよく利用される関数が利用するデータが、単にまだ準備されていなかっただけで、それらのデータの準備の為に setup_postdata() という関数が存在している、ということですね。ですので、while などでループさせて the_post() を呼び出さなくても、単に the_post() をコールしても良いですし、setup_post( get_post() ) としても大丈夫です。