WordPressのテーマやプラグインを作ったり弄ったりしてる人なら、誰もがお世話になってると思われる、 $post という変数。

そもそもPHPにおける変数ってのは、 $ で始まり英数字とアンダースコア _ が続く文字列で、好きな名前をつけられる。

英語でつけるか、日本語をローマ字表記にしてつけるかはあなた次第……

ローマ字の変数名はダサいとネタにされるけど……www

ところが、いくつかの名前はあらかじめ使われてるとかで、変数名に使えない(使ってはいけない)という場合もある。

また、使ってもいいけど、思わぬ不具合を招く場合もある。

WordPressでは多くの関数とともに多くの変数もあらかじめ定義されているので、つけた変数名によってはそれらと競合する可能性も。

ということで、今回はそんなWordPressにおける変数の競合や、いわゆるグローバル変数の汚染について警鐘を鳴らす。

グローバル変数とは

PHPにおいて、定義した変数の有効範囲は限定されている。

たとえば、ユーザー定義関数の中で定義した変数は、その関数の中でのみ有効となる。

参考:変数のスコープ - PHP マニュアル

またWordPressにおいては、テンプレートファイルの header.php において定義した変数は index.php では有効でない。

この有効範囲のことを変数のスコープというが、変数を別のスコープでも利用するにはグローバル変数として宣言する必要がある。

グローバルなスコープで宣言された変数と、 global を使用して宣言した変数をグローバル変数と呼び、関数の内部で利用するときは関数内で global を使って宣言する。

<?php
/* ここはグローバルスコープ */
$a = 1;
$b = 2;

function hoge() {
    global $a, $b; /* グローバル変数として宣言 */
    echo $a + $b; /* "3" を出力 */
}

hoge();
?>

こうすることで、同じ変数を別の場所で利用できるようになる。

WordPressにおいては、 functions.php において定義した変数はグローバル変数となるが、他のファイル内でグローバル変数として使用するには global を使用する。

一方、グローバルでない変数をローカル変数という。

$post という変数

WordPressではいくつかのグローバル変数が標準で定義されている。

その中でも最も使われているのが、個別投稿ページ(記事・固定ページなど)において投稿オブジェクト(投稿の各種情報)を格納するグローバル変数 $post だろう。

この変数は $post->ID (投稿ID)や $post->post_content (投稿の本文の生データ)などの形で直接使われることもあるが、 $post を直接使っていなくても、 get_the_ID()the_content() などの基本的なテーマ関数の多くがこのグローバル変数に依存している。

このグローバル変数は、テーマ内のいわゆるWordPressループにおいて、 the_post() 関数によってセットされるが、個別投稿ページにおいてはループよりも前に既にセットされており、 header.php においても利用可能である。

しかし、この変数はグローバル変数であるが、どこかで意図せず書き換えられている可能性がある。

ローカル変数ならどこかで書き換えられたとしてもそのスコープ内にしか影響がないが、グローバル変数だと影響は全体に及ぶわけだ。

そのため、 $post を参照する場合は意図せず書き換えられていないことが保証されていなければならないので、基本的にはループの内側でのみ使うことになっている。

グローバル変数が意図せず書き換えられることをグローバル変数の汚染といい、一般的にはセキュリティに関する用語として使われることが多いが、ここでは単にWordPressテーマの不具合に関する話題で扱う。

あなたは大丈夫?

この $post という変数は単純でわかりやすい名前であるがゆえに、ネット上で紹介されているような一般的なコード内で普通に使われている。

もちろん、そのページにおいてクエリされた投稿オブジェクトを格納するグローバル変数としてではなく、単なる変数名として、だ。

しかし一方で、 $post が書き換えられていないことを前提にこの変数を使うコードもまた、ネット上でよく紹介されている。

たとえばブログのサイドバーにいろいろな情報を表示するとして、そのためのコードをネットで調べてコピペしていると、上手く動作しないという現象に遭遇する。

サイドバーは一般的にループよりも後ろにあるため、 $post がループのあとで書き換えられていないことは保証されていない。

$post を書き換えるようなコードを使ったあとで、 $post が書き換えられていないことを前提としたコードを使うと不具合の原因となる。

この場合、前者が悪いか後者が悪いかといえば、どちらにも原因があると言える。

前者は $post という特別な変数をみだりに書き換えているのであり、単なる変数名としてこの名前を使うなら、べつにどんな名前でもいいはずだ。

$_post でも $post2 でも $my_post でもいいのなら、あえてグローバル変数を汚染しないようにしたい。

※サブループを作るのに使われる setup_postdata() 関数においては $post 以外の変数名だと動作しないようだ。

一方、後者は $post が常に(特にループ外で)書き換えられていないことが保証されないにもかかわらず、書き換えられていない前提で使っている点が問題である。

$post に頼らず、より確実な実装をするべきだ。

たとえば、ループ外でより確実に、現在の個別投稿ページの投稿IDを取得するには、以下のようにグローバル変数 $wp_query を宣言して利用する方法がある(カテゴリやタグのアーカイブページでもそれらのIDを取得できる優れもの)。

<?php
grobal $wp_query;
$post_id = $wp_query->get_queried_object_id();
?>

もちろん $wp_query であっても勝手に書き換えられる可能性はゼロではないが、 $post と違ってこの変数をわざわざ書き換える人はまずいないであろう。

大丈夫ならいいけど

以上の話は、意図せず $post を書き換えているかもしれない人向けに警鐘を鳴らしたのであって、ちゃんとわかったうえでコードを書いている上級者はどうしようが構わないわけだ。

というかそもそも、グローバル変数 $post を直接利用する行為自体が標準的でない自己責任の領域のような……?

まとめ

今回はグローバル変数 $post の汚染に警鐘を鳴らす、と大層なことを言いながら、実際は俺がググって見つけたコードを使ったことで遭遇した不具合の原因と対策を調べた結果をまとめた話でした。

でも、世に出回っているコードをコピペで使うとしばしば起こりうる話だから困る。

俺はというと、この件がすっかりトラウマになって、必然性のない $post はすべて別の変数名に変えてしまったし、 $post が避けられない setup_postdata() についてはこの関数を使わず実装するように変更したっていう。

正しく使えば便利なグローバル変数、皆さんも上手に使いこなそう。