CGI::Applicationの双璧をなす機能として
- run modeに対応したサブルーチンのdispatcher
- hookとそれに対応したcallbackの設定
があります。dispatcherに続いて、今回はcallbackについて。これも簡単なコードで面白い機能を付けてます。CGI::Applicationのコードはシンプルで分かりやすい上に短い*1ので、読んでみることをお勧めします。Perlのリハビリにはもってこい。
まず、callbackには2つの種類があります。
- Object-based callback (そのオブジェクト単体でのみ管理されるcallback)
- Class-based callback (全てのオブジェクトに適用されるcallback)
package SampleBase; use base 'CGI::Application'; use strict; sub setup{ my $self = shift; $self->new_hook('hook_sample'); } sub call{ my $self = shift; $self->call_hook('hook_sample', @_); } sub callback001{ print " SampleBase::callback001\n"; } sub callback002{ print " SampleBase::callback002\n"; } package SampleA; use base 'SampleBase'; sub callback001{ print " SampleA::callback001\n"; } package SampleB; use base 'SampleBase'; sub callback001{ print " SampleB::callback001\n"; } package main; use strict; my $a = new SampleA(); my $b = new SampleB(); CGI::Application->add_callback('hook_sample', 'callback001'); $a->add_callback('hook_sample', 'callback002'); $b->add_callback('hook_sample', 'callback001'); print "callback SampleA\n"; $a->call(); print "callback SampleB\n"; $b->call();
実行結果
callback SampleA SampleBase::callback002 SampleA::callback001 callback SampleB SampleB::callback001
object-basedとして設定したコールバック($a->add_callback)は$aのオブジェクトでしか適用されていませんが、class-basedとして設定したコールバック(CGI::Application->add_callback)は$aと$bの両方に適用されています。
実行順は、object-based→class-basedになります。
また、$b->add_callback()で再びcallback001を追加していますが、同名のcallbackを複数追加しても、実行されるのは一度だけです。
1.object-based callback
$self->{__INSTALLED_CALLBACKS}で管理されています。
# 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 $@; }
%executed_callbackというハッシュで、callbackが実行済みかどうかを判定します。同じ名前のcallbackは一度しか実行されないので、class-basedとobject-basedで同じ名前のcallbackが設定されていれば、object-basedとしてのみ実行されます。
2.class-based callback
%INSTALLED_CALLBACKSというハッシュで管理されています。
my %INSTALLED_CALLBACKS = ( # hook name package sub init => { 'CGI::Application' => [ 'cgiapp_init' ] }, prerun => { 'CGI::Application' => [ 'cgiapp_prerun' ] }, postrun => { 'CGI::Application' => [ 'cgiapp_postrun' ] }, teardown => { 'CGI::Application' => [ 'teardown' ] }, load_tmpl => { }, error => { }, );
ここで登録したcallbackが全て実行されるわけではなく、packageが自分自身またはスーパークラスのものだけが実行されます。
# Cache this value as a performance boost $self->{__CALLBACK_CLASSES} ||= [ Class::ISA::self_and_super_path($app_class) ]; # Get list of classes that the current app inherits from foreach my $class (@{ $self->{__CALLBACK_CLASSES} }) { # skip those classes that contain no callbacks next unless exists $INSTALLED_CALLBACKS{$hook}{$class}; # call all of the callbacks in the class foreach my $callback (@{ $INSTALLED_CALLBACKS{$hook}{$class} }) { next if $executed_callback{$callback}; eval { $self->$callback(@args); }; $executed_callback{$callback} = 1; die "Error executing class callback in $hook stage: $@" if $@; } }
*1:POD除いたら1000行ないんじゃないかな