フィルターフック、アクションフックの登録と実行について覚書き
WordPressのアクションフックやフィルターフックはプラグインを作る上で避けては通れない。
備え付けでフックに登録されているものをただ使うだけなら別にうろ覚えでもいいんだけど、久し振りに独自でフックをかけようとして関数やクラス内のメソッドを登録して実行してみようとなったときに「あれ、これってどうやったっけ?」とかなっちゃうんだよね。
WordPress備え付けのフックとの違いがブレる場合もごくごく稀にあるので、この際、フックというものを理解へ落とし込むために、いつもどおりの説明口調で覚え書いておくことにする。
今回は「フックの書き方」とか「基本的な使い方」的な内容ではないです。
日常的にフックを既にある程度使ったりしていて、「とりあえずフックは自分である程度書けるけど、そもそもフックって実際のところ何なんやろな~?」とか「実用する際のコードの形態はどんな感じになるんやろ」とか「自分の関数とかメソッドにフックを的確にかけたい!」とかそのあたりが前提。
よくあるコピペ制作などで、単にフックが使いたくてサンプルコードをお求めの場合、この記事は恐らく全く参考にはならない。
智慧深き先人様方の書いたページが星の数ほどあるので、そちらの方を参考にしたほうが早いと思う。
そして、これ大事。理解を深めるために、今回の記事の前半に出てくるコードのうちのひとつはフックの本質的に変な書き方をしている。
後半でそのコードの何が悪いかなどは記述していくけど、前半だけ読む場合は鵜呑みにしないように注意してね!
フックについての理解
細かいことを考えずにフックのコードを書くだけなら、以下のような形でフィルターフック(add_filter)ないしアクションフック(add_action)ができる。できるが・・・
【サンプルコード】
// 足し算をするだけの関数plugin_addition_calc()をフック function plugin_addition_calc($num1 = 0, $num2 = 0){ $num = $num1 + $num2; echo $num; } add_filter('addition_calc_function', plugin_addition_calc', 10, 2); add_action('addition_calc_function', 'plugin_addition_calc', 10, 2); // クラスUserPlugin内の足し算をするだけのメソッドaddition_calcをフック class UserPlugin{ public function addition_calc($num1 = 0, $num2 = 0){ $num = $num1 + $num2; echo $num; } } $plugin = new UserPlugin(); add_filter('addition_calc_method', array($plugin, 'addition_calc'), 10, 2); add_action('addition_calc_method', array($plugin, 'addition_calc'), 10, 2);
書いただけだと、多分なんのこっちゃ分からない。
そして、このコード見た瞬間に恐らくデザインの延長線でWordPressのテーマを作っている人はこう思うことだろう。
「フックってWordPressの関数を実行した際に、WordPressが用意している関数に引っ掛けて、自分で作った関数を実行しているわけじゃないの?」
僕もメインがコーダー・プログラマでサブでデザインもやるけど、触り始めたときは全く同じことを最初思ってました。
けど今言えるのは、フックについての認識としてはそれで間違いではないけど、それはフックの機能のうちのひとつにしか過ぎないということ。
敢えて、このコードのような形で関数・メソッドのフックを書いたのは、つまりは「まず、フックは独自でも定義できるんだよ~」と言いたかった。
これは多分、テーマをある程度触りだしたり、簡単なプラグインを作ってみようとなったときに、フックについて調べるときのチュートリアル的なドアだと思ってる。
一般的な検索でフックについて調べるときや、色んなテーマのfunctionsを見ていると、「これがフィルターフック!」「これがアクションフック!」とよく見かけるのが以下の形。
【よく見る形のコード】
// the_title()時に【】を付けるだけの関数をフック function plugin_title_replace($title){ return '【'.$title.'】'; } add_filter('the_title', 'plugin_title_replace'); // サイドバーを登録するだけの関数をフック function theme_register_sidebar(){ register_sidebar(array( 'name' => 'Sidebar', 'id' => 'widget_side', 'before_widget' => '<aside id="%1$s" class="widget %2$s">', 'after_widget' => '</aside>', 'before_title' => '<h3 class="title headline-tertiary"><span>', 'after_title' => '</span></h3>' )); } add_action('widgets_init', 'theme_register_sidebar');
これだけを見ると単に「あ、フックってWordPressが備え付けの関数を実行した際に、自分で作った関数を実行してるんだなー」と思ってしまう。
再度になるけど、あくまでこのコードはフックの機能のうちのひとつであり全てではない。
強いて言うなら、この認識ひとつだけで「フックなんて余裕でしょ!」と思った際に、冒頭で書いた【サンプルコード】を是非実行してみよう。
本当に実行してしまったのか?
まぁフック実行用の関数書いてないから、上記のコードでは*実行*されないんだけどね。(ニヤリ)
・・・・毎回の当事者は俺だけど。
こうして今度は「独自のフックってできないの?」というところへつながってくる。
それを示すのが、先に書いておいた【サンプルコード】である。
(【よく見る形のコード】と同じような書式の【サンプルコード】がなぜ実行されてないのかは次項の「フックの実行についての理解」で後述することにする。)
で、【サンプルコード】のように「独自のフック」をすることになったとき「フックってなんぞや?」とCodexを開くことだろうと思う。
Codexに書かれているフック、即ちフィルターフックとアクションフックの説明文を読むところ、以下のようになっている。
フィルターフックの引用:
指定したフィルターフックに、関数を登録します。
アクションフックの引用:
特定のアクションに関数をフックします。
ふむ。なるほどわからん。
ニュアンスとしては受け取れるけど、漠然としているフレンズなんだね!
WordPressは多言語だから、曖昧な表現が多いのはしかたないところだけど、やはり説明文だけを読んでみてもパッとしない。
まぁ日本的にわかりやすくこれの意訳を指し示すのであれば、多分こうだろう。
- 『フィルターフック』や『アクションフック』というフック=実行グループに、自分で作った関数やメソッドを登録する。
フィルターフックの関数であるadd_filter(第一引数, 第二引数)、アクションフックの関数であるadd_action(第一引数, 第二引数)の書式になぞらえて指し示すならこうとも示せる。
- 関数やメソッドのフック(実行グループ)の名前は第一引数に設定、自分で作った関数やメソッドは第二引数に指定してフックを登録する。
【サンプルコード】なら、独自のフックと自分で作った関数やメソッドの関係はこうなる。
// function plugin_addition_calc add_filter('addition_calc_function', plugin_addition_calc', 10, 2); add_action('addition_calc_function', 'plugin_addition_calc', 10, 2);
つまり、第一引数のフック(実行グループ)の名前へ『plugin_addition_calc_function』を設定。自分で作った関数『plugin_addition_calc』を第二引数に指定してフックを登録する。
// class UserPlugin → public function addition_calc // $plugin = new UserPlugin add_filter('addition_calc_method', array($plugin, 'addition_calc'), 10, 2); add_action('addition_calc_method', array($plugin, 'addition_calc'), 10, 2);
つまり、第一引数のフック(実行グループ)の名前へ『plugin_addition_calc_method』を設定。自分で作ったメソッド『UserPluginクラスのaddition_calc』を第二引数に指定してフックを登録する。
そして、上記のようなオブジェクトの指定はarray()の形で行える。
【よく見る形のコード】ならフックと自分で作った関数やメソッドの関係はこうなる。
// function plugin_title_replace add_filter('the_title', 'plugin_title_replace'); // function theme_register_sidebar add_action('widgets_init', 'theme_register_sidebar');
つまり、上記はこのようなことを示している。
第一引数のフック(実行グループ)の名前へ『the_title』を設定。自分で作った関数『plugin_title_replace』を第二引数に指定してフックを登録する。
第一引数のフック(実行グループ)の名前へ『widgets_init』を設定。自分で作った関数『theme_register_sidebar』を第二引数に指定してフックを登録する。
コピペ制作が長いとよくある認識の違いで、フックに指定しているthe_titleだとかwidgets_initはテンプレート関数を示していると思いがち。
けど、この場合、フックに指定しているthe_titleだとかwidgets_initというのは、テンプレート関数などで使われているthe_titleとかの関数とは完全な別物であるということに注意すること。これは、飽くまでもフック(実行グループ)の名前である。
さて、ここまでアクションフック(add_filter)やらフィルターフック(add_action)やらを呪文のように書いたけど、そもそもの疑問が出ていると思う。
「フィルターフックとアクションフックって一体何が違うんや?」
これに関する答えはこうだ。
- 両方とも同じです(^q^)
ただし、フックの実行(次項に後述)の際の返り値の有無を除いてね。
Codexにも書いてある通り、「フィルターフック(add_filter)」のエイリアス名が「アクションフック(add_action)」というだけ。名称が別なだけでフックの機能としては、フック実行の際の返り値の有無を除いて同じである。
「両方とも同じ?じゃあどうやって使い分けるんや??」
これについてはWordPressのソースをざらっと眺めてどのように使われ、使い分けられているかを見たところからの憶測だが、恐らくこのような答えになる。
- 「フックを実行した際に、フック(実行グループ)に登録した関数やメソッドから、最終的な表示を伴うのであればフィルターフックを使う。」
- 「フックを実行した際に、フック(実行グループ)に登録した関数やメソッドでの処理だけを行うのならアクションフックを使う。」
- 「フックを実行した際に、フックからの返り値を戻すことが必要なときはフィルターフック、そうじゃない場合はアクションフックを使う。」
とりあえず、この3点に分けてみた。
【よく見る形のコード】ではWordPress備え付けのフックに、自分で作った関数を登録しているが、the_titleへのフックとwidget_initへのフックではその違いが明確だと思う。
- the_titleへのフックであれば、「記事タイトルの表示」という「最終的な表示を伴っている」ものなのでフィルターフック。
- widget_initへのフックであれば、「ウィジェット関連の一連の処理」に「サイドバー関数へ設定値を与えて実行する処理」なのでアクションフック。
- 更に、the_titleへのフックは「最終的な表示」をするために「記事タイトル」や「付与する文字列」を「返り値として戻す」必要があるのでフィルターフック。widget_initへのフックはで行われるのは「単なる関数の処理」であって「返り値を戻す必要がない」のでアクションフック。
それぞれこんな具合での登録となるわけだ。
けど、これはあくまでWordPress備え付けのフィルターフックとアクションフックを利用する場合に限っての話である。
これをWordPress備え付けではない独自のフックに置き換えて考えるとどうなるか。
冒頭にも書いておいた「フックの本質的に変な書き方」という宣言。
実は、【サンプルコード】での独自のフックの登録は、敢えてフィルターフックとアクションフックを両方を設定し、その上で返り値を戻さずechoするようにした意地悪な書き方をしている。
ともあれ、この【サンプルコード】のような形だと、独自のフックが何をするべきフック(実行グループ)か明確になっていない限りはどちらでもいいやん、となってくる。
くどいようだが、フックを実行した際の返り値の有無を除いてフィルターフックとアクションフック自体の動きは同じである。
関数やメソッドの登録先がフィルターフックであろうがアクションフックであろうが、【サンプルコード】のように単なる処理の羅列で、返り値を戻さないのであればそれ自体にはあまり意味がない。
- 独自のフックを設定する場合は、そのフック(実行グループ)が「最終的に表示を伴う」か「処理だけを行う」か「返り値を戻すことがあるか」を基準にして、用途に応じて書き分ける必要がある。
以上、「フックについての理解」はこんなところになるだろうか。
次項の「フックの実行についての理解」にまだ続くけど。
そういえば、Codexのちょっとした余談だが、Codexのフックの概要をそのまま鵜呑みにすると、少しだけ痛い目を見るかもしれない。
フィルターフックに関数を登録できるとtrueを、そうでなければfalseを返します。
こう書いてあるけど、実際のフィルターフック(add_filter)はwp-includes/plugin.phpで以下のような関数になっている。
function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) { global $wp_filter; if ( ! isset( $wp_filter[ $tag ] ) ) { $wp_filter[ $tag ] = new WP_Hook(); } $wp_filter[ $tag ]->add_filter( $tag, $function_to_add, $priority, $accepted_args ); return true; }
難しく考えず、最後の「return true;」だけに注目してほしい。
そう。2017/11/21の時点ではCodexはフィルターフックは「登録できたかどうかの真偽を返す」となっているが、実際のところ返しているのは「true」だけ。
この感じは、数週間前にRuby on Railsのチュートリアルをやっていたときのリファレンスのミスリードを彷彿させたね。
なーんか登録失敗をわざと行ってみようと思ってやってみたらtrueしか返さねぇしおかしいな~と思って見てみたらこれだよ・・・。ちょっとだけショック。
フックの実行についての理解
さて、ここまでフックが何であるかなどをたらたらと書いてきたけど、前項で某緑髪のエレアさん風に書いていた場所があったと思う。(マイナーゲーなんで元ネタ分かる人あんまりいないだろうけど)
本当に実行してしまったのか?
まぁフック実行用の関数書いてないから、上記のコードでは*実行*されないんだけどね。(ニヤリ)
・・・のところね。
フィルターフックやアクションフックは、フック(実行グループ)を登録するためのコードであり、フック(実行グループ)の実行を行うコードではない。
具体的に言うと、フィルターフック(add_filter)かアクションフック(add_action)で第一引数に指定したフック(実行グループ)をどこかのタイミングで実行させないと、第二引数で指定した自分で作った関数やメソッドの実行はなされない。
じゃあ、実行はどのようにするのか?
以下の点を知識として踏まえた上で【サンプルコード】を実行するためのコードを書く。
- フックの実行の基本的な形は、「フィルターフックの実行=apply_filters」「アクションフックの実行=do_action」。
- 既に独自のフックで登録しているフック(実行グループ)の名前を第一引数に指定する。
- 関数やメソッドに受け渡す引数は、「第二引数以降に指定する」。
- 前項で何度か触れている、フィルターフックの実行(apply_filters)は返り値を戻し、アクションフック(do_action)は戻さない。
前項【サンプルコード】でのフックの実行:
//足し算をするだけの関数plugin_addition_calc()のフックを実行 $result1 = apply_filters('addition_calc_function', 5, 5); // 本来はフックに登録した関数でのreturn(返り値)が入ってくることに留意 do_action('addition_calc_function', 5, 5); //クラスUserPlugin内の足し算をするだけのメソッドaddition_calc()のフックを実行 $result2 = apply_filters('addition_calc_method', 5, 5);// 本来はフックに登録したメソッドでのreturn(返り値)が入ってくることに留意 do_action('addition_calc_method', 5, 5);
フックの登録(add_filter、add_action)→フックの実行(apply_filters、do_action)という手順を踏んで初めてフィルターフックとアクションフックが実行される。
と、これで済めばいい話なんだけど、これは【サンプルコード】の実行の話。では、【よく見る形のコード】は・・・?
恐らくこんな疑問が生まれるはず。
「いやいや、ちょっと待った!【サンプルコード】は良いとして、【よく見る形のコード】ではフックの実行をしていないのに実行されてるんだけど?」
うん、実行されるね。
前項の【よく見る形のコード】は【サンプルコード】と違って実行されている、中身やフックの仕方の違いこそあれ、内容としてはほぼ同じなのに実行の可否という差異が生まれている。
何故、フックを登録したと同時に【よく見る形のコード】が実行されて、【サンプルコード】では実行されないのだろうか。
これには、wp-includesに収められたWordPressのPHP群の中にちょっとしたからくりがある。
当該のフックの箇所と実行箇所の抜き出しは、適当に抜き出しただけだが、おおよそこんな感じで書かれている。
WordPress備え付けのフックの実行のからくり:
// wp-includes/default-filters.php add_filter( 'the_title', 'wptexturize' ); add_filter( 'the_title', 'convert_chars' ); add_filter( 'the_title', 'trim' ); // wp-includes/post-template.php function get_the_title( $post = 0 ) { return apply_filters( 'the_title', $title, $id ); } // wp-includes/class-wp-customize-widgets.php final class WP_Customize_Widgets { public function __construct( $manager ) { add_action( 'widgets_init', array( $this, 'register_settings' ), 95 ); } } // wp-includes/widgets.php function wp_widgets_init() { do_action( 'widgets_init' ); }
上半分が「the_title」、下半分が「widgets_init」。
これで注目したいのは既にフィルターフックもアクションフックもwp-includesの中に関数ないしメソッドが登録されており、かつフィルターフックの実行(apply_filters)とフィルターフックの実行(do_action)の記述があるということ。
即ち、コアの時点で既にフック(実行グループ)の実行がなされているのである。
なので、【よく見る形のコード】はフックを登録した時点で実行が行われる、というからくりである。
WordPress備え付けのフックと独自のフック。
共通して言えるのは、自分の預かり知るところかそうでないところに関わらず、どちらも必ず同じ手順でフックの登録→フックの実行を行っているということ。
これでフックの実行について、方法や仕組みはこれでだいたい理解できたはず・・・と思う。
フックの登録と実行より、フックの正しい形と用途についての理解
最後に、このフックの登録や実行について、これがどのような形でどのような用途で必要となってくるのか?という話になる。
これまで繰り返し「フック(実行グループ)」と乱文してきた。
これは、フックとは関数やメソッドの代替として使うものではないということを明確に理解するためだ。
そして、この項を読んでおくと、前項、前々項で引き合いに出している【サンプルコード】が、如何にフックの機能的に間違った書き方をしているのかもわかる。
まずはフックの機能的に間違った書き方として、以下のようなフィルターフックを用意する。
【フィルターフックA】
function plugin_function($arg = 0){ $arg = $arg + 10; $arg = $arg - 20; return 'result-> '.$arg; } add_filter('plugin_function', 'plugin_function', 10, 1); $result = apply_filters('plugin_function', 50); echo $result; // 結果は40
この【フィルターフックA】はと似た体系となっており、何かがおかしいと思うはずだ。
念のために書いておくと、フィルターフックの書式としてきちんと返り値のある関数を指定しているし、関数内の計算処理は適当だけど間違いはない。そこは何の問題でもない。
けど、このフィルターフックは書き手が明らかに機能的な面で間違えていることがある。
ここで気付ければ、それは恐らくフックの機能を正しく理解できている証拠だと思う。
では、以下のコードはどうだろうか?
【フィルターフックB】
function plugin_add_calc_function($arg = 0){ $arg = $arg + 10; return $arg; } add_filter('plugin_calc_functions', 'plugin_add_calc_function', 10, 1); function plugin_sub_calc_function($arg = 0){ $arg = $arg - 20; return $arg; } add_filter('plugin_calc_functions', 'plugin_sub_calc_function', 10, 1); function plugin_function($num = 0){ return 'result-> '.apply_filters('plugin_calc_functions', $num); } $result = plugin_function(50); echo $result; // 結果は40
上記の2つのフィルターフックのコードは、結果は同じだしコード的な間違いはない。
しかし、この場合、フィルターフックの機能的には、【フィルターフックA】はテーマのカスタマイズに何となく慣れてきた頃の自分の理解不足による間違った形であり、【フィルターフックB】は本来あるべきフィルターフックの正しい形である。
おわかりいただけただろうか・・・?
そうなんだよね、これ。
ぶっちゃけ先に書いてある方のコードって、わざわざフィルターフック使わなくても、単にこうすればいいやんって話なんだよね。
function plugin_function($arg = 0){ $arg = $arg + 10; $arg = $arg - 20; return 'result-> '.$arg; } $result = plugin_function(50); echo $result; // 結果は40
無駄なのでわざわざこれを直接フィルターフックに登録する必要はない。
フックを触り始めて少し慣れてくると何でもかんでもフックにするという状況になりがち。
どのような状況で使うのか?などがCodexやその他の関連サイトさんでも中々取り扱っていないし、本当にフックという機能を理解して使っているのかも怪しいところである。
で、フック(実行グループ)と今まで書いてきたのは、フックとは関数に代替されるような便利なコードではなく、関数グループ・メソッドグループを示していることを明示したかったから。
最終的な表示の「結果」は最終的には関数やメソッドで出すけど、そこに至るまでの「手順」を踏むのはフック(実行グループ)に登録された関数やメソッドであるということだ。
差し詰め、特定の関数やメソッドを利用して「結果」を得るための「手順」の部分をフックして(引っ掛けて)おくと言った意味合いだろう。
引っ掛けたものは「結果」を得るための関数やメソッドを実行した際に、実行グループを一括で紐解いてやればいいという考え方。
【フィルターフックA】の場合も、関数やメソッドのグルーピングが出来ないわけではないが、後で追加する関数は恐らくこの書き方のレベルだと冗長になる可能性がある。フックの実行(apply_filter)もその度に行わないといけない。
【フィルターフックB】だと、後で更にここへ「手順」を挟み込む際に、テーマ内だろうとプラグイン内だろうと以下のようにシンプルに追記できる。
function plugin_mult_calc_function($arg = 0){ $arg = $arg * 5; return $arg; } add_filter('plugin_calc_functions', 'plugin_mult_calc_function', 10, 1);
これなら、後々細かい値の調整などが発生した場合でも、シンプルなコードで後付が利く。
この各モジュールから手順を踏んで結果を得るという動きは、PHPで作られたCMSらしいオブジェクト指向的な動きだなぁと思う。
(普段、コードを作成するときに関数を作ってやっていくパターンが多い(もしくはオブジェクトの知識がない)人はもしかするとイメージしづらいかもしれない・・・。)
ちなみに【フィルターフックB】内で、「plugin_function」に指定した引数「50」の値。
引数の値は、フック内の各関数群のコールバックで共有されている。なので、引数に指定した値をどう保持するかの部分については考える必要はない。
同じような感じで、処理だけを行うアクションフックも、こんな感じで書けるだろう。
値をコールバックしないので、見ようによってはこちらのほうがとっつきやすいかもしれない。
【アクションフック】
function plugin_title_function($arg = 1){ echo '<p>'.$arg.'行目の記事'.'</p>'; echo '<h1>'.get_the_title().'</h1>'; } add_action('plugin_post_functions', 'plugin_title_function', 10, 1); function plugin_content_function($arg = 1){ echo '<div>'.get_the_content().'</div>'; } add_action('plugin_post_functions', 'plugin_content_function', 10, 1); function plugin_post_function($num = 1){ do_action('plugin_post_functions', $num); } $count = 0; if(have_posts()): while(have_posts()): the_post(); $count = $count + 1; plugin_post_function($count); endwhile; endif;
長々と書いたが、フィルターフック、アクションフックの理解としてはこんなところになるだろうか。
全体的なまとめとして個人的に思うに、フックはテーマやプラグイン内の様々なところで散見しているが故に、ついつい「ざっくり覚えてとりあえず使う」と言ったやり方に陥りがち。
これはWebをやっているとついついなりがちなパターンで、とりあえずファッション的にざっくり覚えてみよう、テキトーに使ってみようと言う感覚で何の理解もせずに終わらせてしまう場合がある。
フックは「使えるだけではあまり意味のないコードであり、使いこなして初めて意味を持つコード」の典型的な例で、それを鑑みる上でもいいお勉強コードなんじゃねーかなと思った。
以上、「フィルターフック、アクションフックの登録と実行について覚書き」でした。