Perlでプラグインという仕組みを利用するには <1>
//長くなりそうだから、タイトルに章番号付けてみた。
自分でフレームワーク・ライブラリ・モジュールといったものを作ったりすると、それ自体に拡張性を持たせるために『プラグイン』という仕組みを導入したくなる時があると思います。例えば…
- Apacheのモジュールのように各フェーズ(リクエストヘッダの解析、アクセス制御、コンテンツの出力、等々)毎にあとからプラグインによって機能を追加していく(フック=hookしていく)もの
- CGI::Applicationのようにやはり各フェーズにフックする、または、任意の関数をフレームワークに追加するもの
ここでは特にCGI::Applicationを参考に見ていこうと思います。上でもちょっと触れていますが、CGI::Application(以下CAP)にはおおまかに2通りのプラグインがあります。それぞれ説明します(用語は便宜上私が勝手に付けました)。
- フック形式
- 各処理の前後(init, prerun, postrun, teardown, etc)に任意の処理を差し込むもの
- 関数追加形式
- CGI::Application::Plugin::Sessionのように後からCGI::Application自体に関数を追加するもの*1。ちなみにCGI::Application::Plugin::Sessionはsession, session_configといった関数を追加しますね。
さぁ、それぞれどうやって実現されているのか。CAPを実例に説明していく。ぜひ、CAPのソースコード片手に見ていただきたい。
フック形式
フック形式はCAPのフックを呼び出す&call_hookから見ていく。
sub call_hook { my $self = shift; my $app_class = ref $self || $self; my $hook = lc shift; my @args = @_; die "Unknown hook ($hook)" unless exists $INSTALLED_CALLBACKS{$hook}; my %executed_callback; # First, run callbacks installed in the object foreach my $callback (@{ $self->{__INSTALLED_CALLBACKS}{$hook} }) { next if $executed_callback{$callback}; eval { $self->$callback(@args); }; $executed_callback{$callback} = 1; die "Error executing object callback in $hook stage: $@" if $@; } # Next, run callbacks installed in class hierarchy #.... 省略 }
一部省略したが、大事なのはforeachの中だ。$hookにはinitやprerunといったフック名が入る。そして$callbackには関数名(または関数へのリファレンス)が入る。そうするとeval { $self->$callback(@args)};の意味が分かると思う。__INSTALLED_CALLBACKSで取り出したコールバック関数名の配列を順次実行しているわけだ。
さて、なんとなく理解出来たと思うのだけど、一つ疑問に思わなくてはいけない事がある。それは$self->$callback()でコールバックを呼んでる言っても、あとからプラグインで追加した関数がなぜ、$selfの中にあるのか? どちらかと言えば、$plugin->$callback()のほうが自然に見えないだろうか?
ここで、$self->$callbackというように呼び出せる理由を説明しなくてはいけないのだけど、疲れてきたので、明日。
ヒントは次の関数とExporter::importのソースコード、かな。
sub import { my $caller = scalar(caller); $caller->add_callback('init', 'my_setup'); goto &Exporter::import; }
*1:実際はCAPに関数を追加しているわけではないのだけど、そう見えるので便宜上こう説明しておく