無名関数狂の詩:WordPress のカスタマイズで名前空間を汚さないための試み
犬好きでイヌアレルギーの kagata です、というのは前回記事からの質問に答えるために書いています。
今回は、WordPress のカスタマイズで PHP の「名前空間が汚れる」のをどうにかして避けようとやってみたことをご紹介します。まねするかどうかは読者諸賢のご判断にお任せいたします。
名前空間が汚れる、とは
ここでいう「名前空間が汚れる」とは、次のような状況を指します。
関数名で汚れる
WordPress のカスタマイズである一定以上の込み入ったことをしようとすると、 add_action()
や add_filter()
に出会います。アクションフックやフィルターフックですね。
これらを使った具体的なカスタマイズ例はいたるところで見られます。例えば、WordPress Codex 日本語版の add_action()
の項には次のようなサンプルコードがのっています。
function email_friends( $post_ID ) {
$friends = 'bob@example.org, susie@example.org';
wp_mail( $friends, "sally's blog updated", 'I just put something on my blog: http://blog.example.com' );
return $post_ID;
}
add_action( 'publish_post', 'email_friends' );
こういうコードを書くとき、フックする関数 email_friends()
には WordPress コアやプラグイン、あるいは別の箇所で自分が定義したほかの関数と衝突しないユニークな名前をつけてやらないといけません。また、いったんこのコードを書いたら、そのあとに追加するコードで新たに定義する関数には email_friends
でない名前をつけてやらないといけなくなります。これが、関数名で名前空間を汚した状態です。
変数名で汚れる
例えば、次のようなカスタマイズをしようとします。
- 画像サイズ
my-image-large
とmy-image-small
を追加する - 前者は幅480px、後者は320px とする
- 高さはいずれも制限しない
そこで、functions.php に次のようなコードを書きました。
// functions.php にて
$prefix = 'my-image-';
$infinite = 9999; // 事実上の無限大
$sizes = [
'large' => [480, $infinite],
'small' => [320, $infinite],
];
foreach ($sizes as $key => $size) {
add_image_size($prefix . $key, $size[0], $size[1]);
}
ここで、変数 $prefix
$infinite
$sizes
を定義しています。
ふつうはあまり意識しないと思うのですが、実は functions.php で定義したこれらの変数は index.php などのテンプレートファイル側から参照できてしまいます。
例えば、テンプレートファイルに次のような記述があったとします。
<?php
// テンプレートファイルにて
// サイズのバリエーションがある場合にのみ変数 $sizes が定義される(つもり)
if (size_variation_exists()) {
$sizes = ['S', 'M', 'L'];
}
?>
<!-- なんやかんやありまして -->
<?php // $sizes が未定義の場合は「フリーサイズ」と表示する(つもり) ?>
<?php if (!isset($sizes)) ?>
フリーサイズ
<?php endif; ?>
ところが、functions.php ですでに変数 $sizes
を定義しているために、実際には「フリーサイズ」と表示されることはありえません。これはおそらく、テンプレートファイルを書いた人の意図に反するふるまいでしょう。これが、変数名で名前空間を汚した状態です。
名前空間を汚さないためにやってみたこと
上に書いたような「名前空間が汚れる」事態を避けるために、WordPress のカスタマイズで実際にやっている/やっていたことをご紹介します。最初に言っておきますが、ちょっと潔癖ぎみです。
試みその1:フックさせる関数は無名関数にする
名前空間を汚さずに所望の処理をフックさせるために、最近は無名関数を積極的に使っています。たとえば先のサンプルコードなら、次のように書き直します。
add_action( 'publish_post', function ( $post_ID ) {
$friends = 'bob@example.org, susie@example.org';
wp_mail( $friends, "sally's blog updated", 'I just put something on my blog: http://blog.example.com' );
return $post_ID;
} );
フックする関数 email_friends()
が無名になりました。もう名前空間を汚す心配はありません。
また、無名関数を使うもうひとつのメリットに「関数名を考える手間が省ける」という点があります。フックに使う関数はしばしばその場限りのもので、複数の場所から呼び出すことはあまりないのではないでしょうか。そういう関数にキレイな名前を考えても頭を使うわりには報われないし、かといって適当な名前をつけると可読性を損ねる心配があります。立ち止まって関数名を考える場所が減って、コーディングがサクサク進みます。
後方互換性の問題
WordPress のカスタマイズに PHP の無名関数を使うのにはひとつだけ懸念があります。それは、WordPress コアの後方互換性の問題です。
現行の WordPress コアは PHP5.2.4以降をサポートしています。一方、PHP が無名関数をサポートするのはバージョン5.3以降です。つまり、環境によっては WordPress は動作するけれど無名関数は動作しないことがあります。所定の環境で動けばよいものになら遠慮なく無名関数を使うのですが、不特定多数に使ってもらうコードには使いにくいでしょう。
では PHP5.2環境を考慮するとなんの打つ手もないのか、というとそうでもありません。いちおうの緩和策として、クラス定義に閉じ込める方法があります。
class My_Actions {
public function __construct() {
// コンストラクタでフックにコールバックをセットする
add_action( 'publish_post', ['My_Actions', 'email_friends'] );
}
// コールバックはクラスの静的メソッドとして実装する
public static function email_friends( $post_ID ) {
$friends = 'bob@example.org, susie@example.org';
wp_mail( $friends, "sally's blog updated", 'I just put something on my blog: http://blog.example.com' );
return $post_ID;
}
}
new My_Actions(); // あとは new するだけ
こうすることで、email_friends()
という関数がすでに存在しても My_Actions::email_friends()
が存在しなければ問題ない、くらいには緩和できます。
上の例のように定義する関数がひとつだけだと、かえって手間が増しているように見えます。また、見ようによっては「すべての自作関数の名前に共通の接頭辞をつけている」のとあまり変わりません。しかし、フックさせたい関数が増えてくると、いちいち接頭辞をつけるよりも楽ですし、また関数をクラスに整理することでコード全体の見通しがよくなる場合もあります。場合により検討してもよいでしょう。
試みその2:functions.php で定義する変数は即時実行する無名関数に閉じ込める
変数名で名前空間を汚さない方法として、変数を関数に閉じ込める手が考えられます。変数のスコープを区切るためだけなら、無名関数を定義して、それを即時実行するのがお手軽です。
( function () {
$prefix = 'my-image-';
$infinite = 9999;
$sizes = [
'large' => [480, $infinite],
'small' => [320, $infinite],
];
foreach ($sizes as $key => $size) {
add_image_size($prefix . $key, $size[0], $size[1]);
}
} )();
これで、変数 $prefix
$infinite
$sizes
は無名関数の中でしか参照できなくなります。名前空間がクリーンに保たれました。
ただし、無名関数の即時実行がサポートされるのは PHP7以降です。PHP5環境では、代わりに関数 call_user_func()
を使います。
call_user_func( function () {
$prefix = 'my-image-';
$infinite = 9999;
$sizes = [
'large' => [480, $infinite],
'small' => [320, $infinite],
];
foreach ($sizes as $key => $size) {
add_image_size($prefix . $key, $size[0], $size[1]);
}
} );
これを几帳面にやっていると、だんだん functions.php が JavaScript みたいになってきます。なかなか癖があるというか、好みのわかれるスタイルである気はします。
名前空間を汚さないためにやってみなかったこと
ここまで名前空間名前空間と連呼してきましたが、PHP の言語機能としての名前空間にはふれないでいました。WordPress のカスタマイズでは使っていないからです。
プラグイン開発ならまだいいんですが、テーマのカスタマイズで名前空間をうまく使う方法はまだ見いだせていません。名前空間を使うならクラスを定義してオブジェクト指向でいきたいわけですが、WordPress のテーマのアーキテクチャってそうなっていないように思います。
そうしてクラス定義を避けながら、なんとかクリーンなコードを書けないものか、と試行錯誤した結果、上のような無名関数を多用した PHP らしからぬコーディングスタイルにたどり着いたのでした。クラスを書かずに関数を書いて手続きを並べていくスタイル…まさに WordPress の王道といっても過言ではないでしょう、と自分に言い聞かせています。
まとめ
WordPress の(おもにテーマの)カスタマイズで「名前空間が汚れる」コードの例と、「名前空間を汚さない」ように改善したコードの例をご紹介しました。
WordPress のカスタマイズでは無名関数がなにかと便利です。Codex などではあまり見かけませんが、事情が許せば積極的に使ってみてはいかがでしょうか。変数を無名関数に閉じ込める件はちょっと実験的というか偏執的ですが、フックのコールバックに使うのはほんとうにおすすめです。ただし、WordPress の後方互換性を若干損ねる点には気をつけましょう。