catlxom の プラグイン機構 (catlxom メモ #4)

typester さんが

NEXT やめやめ。
なんもうれしいことない。
plagger の hook 機構をぱくろう。そうしよう。
from: CLON - 2006/05/04 - Plugin

とおっしゃっていたので、plagger の hook 機構を catlxom に組み込むとどんな感じになるのかなー、といきなりコードを書こうとしたら混乱してきたので、catlxom の動作の流れを、特にプラグイン機構に注目して整理してみることにした。

起動時の流れ

Catlxom の基底クラス設定

まずは以下の2箇所のコードで、Catlxom の基底クラスを @Catlxom::ISA につっこむ。

use Catalyst qw(
    ConfigLoader
    Flavour
);
__PACKAGE__->setup(
    do {
        my @plugin = qw/+Catlxom::Base/;
        push @plugin, 'Static::Simple'
            if ( $ENV{CATALYST_ENGINE} || '' ) =~ /^HTTP/;
        push @plugin, 'DebugScreen' if $ENV{CATALYST_DEBUG};

        @plugin;
    }
);

これで @Catlxom::ISA は以下の様な感じになる。

@Catlxom::ISA = qw(
    Catlxom::Base
    Catalyst::Plugin::Static::Simple
    Catalyst::Plugin::ConfigLoader
    Catalyst::Plugin::Flavour
    Catalyst
    Catalyst::Controller
);

catlxom 固有なのは、Catlxom::Base があるところ。

@Catlxom::ISA 中の基底クラスの setup を順に実行

Catlxom::Base::setup が実行される。実際のコードは以下の通り。

sub setup {
    my $class = shift;
    $class->NEXT::setup(@_);

    $class->catlxom->setup($class);
}

$class->catlxom は、Class::Data::Inheritable を使ってこんな感じで定義されてる。

__PACKAGE__->mk_classdata( catlxom => Catlxom::Context->new );

というわけでこの setup では、 Catlxom::Context の setup メソッドが実行される。

Catlxom::Context::setup の実行により catlxom プラグインをロード

Catlxom::Context は Catlxom::Context::Base を基底クラスとしていて、setup メソッドもこの基底クラスにある。こんな感じ。

sub setup {
    my ($self, $c) = @_;

    $self->plugin->load($c);
    {
        # plugin setup
        no warnings 'redefine';
        local *setup = sub { };
        $self->setup($c);
    }
}

$self->plugin は Calss::Data::Inheritable でこんな風に定義されてる。

__PACKAGE__->mk_classdata( plugin  => Catlxom::Plugins->new );

なのでこの setup メソッド中の $self->plugin->load($c) では、Catlxom::Plugins の load メソッドが実行される。このメソッドでは、@Catlxom::Context::ISA に catlxom プラグインクラスを基底クラスとしてつっこむ。@Catlxom::Context::ISA はこんな感じになる。

@Catlxom::Context::ISA = qw(
    Catlxom::Plugin::Entry::Blosxom
    Catlxom::Plugin::Filter::Path
    Catlxom::Plugin::Filter::Date
    Catlxom::Plugin::Format::Textile
    Catlxom::Plugin::Paginate::Simple
    Catlxom::Plugin::Template::TT
    Catlxom::Context::Base
);

更におなじく setup メソッド中の

        # plugin setup
        no warnings 'redefine';
        local *setup = sub { };
        $self->setup($c);

で、Catlxom::Context の基底クラス中の setup メソッドを NEXT で順に実行していく。つまり、各プラグインの setup メソッドを順次実行する。

ちなみに、各プラグインメソッド中の $self は Catlxom::Context クラスのインスタンスであって、各プラグインクラスのインスタンスではないので注意。$c は Catlxom クラス。

これで起動時のプラグインロード、セットアップが完了。

アクセス時の流れ

Catlxom::default を実行

ブラウザからアクセスすると Catlxom::default が実行される。実際のコードはこんな感じ。

sub default : Private {
    shift->catlxom->dispatch(shift);
}

これで Catlxom::Context::dispatch が実行される。

Catlxom::Context::dispatch の実行

Catlxom::Context::dispatch の実体は Catlxom::Context::Base::dispatch であり、以下の様なコードになっている。

sub dispatch {
    my ( $self, $c ) = @_;

    $self->initialize($c);

    $self->start($c);
    $self->update($c);

    $self->entries->filter($c);
    $self->sort($c);
    $self->paginate($c);

    $self->fixedup($c);
    $self->interpolate($c);
    $self->end($c);
}

これにより Catlxom::Context の基底クラス、つまり 各 catlxom プラグインの initialize, start などが NEXT で順次実行されていく。