2007年03月14日

ちょっとSoozyにもChainedっぽい仕組みを取り入れたくてCatalystのAttributesのコードを追いかけてました。
んで、とりあえず名前のエスケープと文字の長さの制限を加えてみたので、軽くメモしてみます。

AttaributeってのはControllerの中の

sub default : Private { }
sub root : Chaind('/') ...
のような:の右側にある奴の事です。
適当な解説はPerlのAttributesについてのお勉強でまとめてます。

主にattributesまわりの処理に偏って書いているので本来の役割とは違う書き方をしてるかもしれません。
どのコードがどこに在る物なのかとかは空気呼んで把握して下さい。

Catalyst->setup


Catalystのアプリケーションは、まずCatalystのsetupメソッドが呼ばれてPluginの読み込みやらサーバの初期化やら設定の読み込みとかのアプリケーション全体に関わる初期化処理が行われるのは周知の通りでしょう。

今回のAttributes探索では、Catalyst->setupの中からCatalyst->setup_componentsを呼び出している所から始まります。

requireそしてattributesの取り込み


setup_componentsは、MyApp::(Controller|Model|View)以下の全コンポーネントをrequireする事が大まかな役割となっています。
焦点を当てるのはMyApp::Controller以下のモジュールですが、基本的にこれらのモジュールはCatalyst::Baseを継承しています。
そして、Catalyst::BaseがCatalyst::AttrContainerを継承しています。

モジュール名で想像出来る通り、Catalyst::AttrContainerがCatalystでのAttributes実装に関しての重要な役割を持っています。

sub MODIFY_CODE_ATTRIBUTES {
my ( $class, $code, @attrs ) = @_;
$class->_attr_cache( { %{ $class->_attr_cache }, $code => [@attrs] } );
$class->_action_cache(
[ @{ $class->_action_cache }, [ $code, [@attrs] ] ] );
return ();
}
Perlがソースコードをコンパイルする時にattributesを見つけると、attributesが記述されてるメソッドのコードリファレンスとattributesを引数にしてMODIFY_CODE_ATTRIBUTESメソッドを呼び出します。
あとは、その受け取ったattributesをどうするかは各モジュールの実装次第な訳ですが、Catalystでは_attr_cacheと_action_cacheアクセサに情報を保存しておきます。

これでControllerのrequire時のattributes処理は終わりです。

Catalyst->setup_actions


これもCatalyst->setupから呼ばれます。
なんかActionとかそんな感じのやつの初期化です。
実際にはCatalyst::Dispatcher->setup_actionsが呼び出されます。

まずは

    my @classes =
$self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
@{ $self->registered_dispatch_types }{@classes} = (1) x @classes;
デファオルトのCatalyst::DispatchTypeモジュールをloadします。

そして、先ほどのsetup_componentsでloadしたControllerとかのモジュールそれぞれにregister_actionsメソッドがあれば、register_actionsメソッドを呼び出す処理をしていきます。

    foreach my $comp ( values %{ $c->components } ) {
$comp->register_actions($c) if $comp->can('register_actions');
}

Catalyst::Base->register_actions


先ほどの呼び出されたregister_actionsメソッドの実体はCatalyst::Baseモジュールに入っています。
だいぶ本質に近づいてまいりました。

このメソッドの出だしは

sub register_actions {
my ( $self, $c ) = @_;
my $class = ref $self || $self;
my $namespace = $self->action_namespace($c);
my %methods;
$methods{ $self->can($_) } = $_
for @{ Class::Inspector->methods($class) || [] };
となっており、コンポーネント中の全メソッドを取り出してあります。
そして
    # Advanced inheritance support for plugins and the like
my @action_cache;
{
no strict 'refs';
for my $isa ( @{"$class\::ISA"}, $class ) {
push @action_cache, @{ $isa->_action_cache }
if $isa->can('_action_cache');
}
}
と、コンポーネントをロードした時に保存していたattributesの情報を取り出しています。
この後は
    foreach my $cache (@action_cache) {
my $code = $cache->[0];
my $method = delete $methods{$code}; # avoid dupe registers
next unless $method;
my $attrs = $self->_parse_attrs( $c, $method, @{ $cache->[1] } );
と、予め保存しておいたattributesの定義元メソッドが実際に存在するかチェックしてから_parse_attrsメソッドに飛びます。

Catalyst::Base->_parse_attrs


ここは、実際のattributesの記述を解析する事を行っています。

まずはChained('/')のような文字列を解析します

        if ( my ( $key, $value ) = ( $attr =~ /^(.*?)(?:\(\s*(.+?)\s*\))?$/ ) )
{

if ( defined $value ) {
( $value =~ s/^'(.*)'$/$1/ ) || ( $value =~ s/^"(.*)"/$1/ );
}
push( @{ $raw_attributes{$key} }, $value );
}

これで$keyにChainedが、$valueに/が入りました。

そして、独自Attributesの処理です

            my $meth = "_parse_${key}_attr";
if ( $self->can($meth) ) {
( $key, $value ) = $self->$meth( $c, $name, $value );
}
push( @{ $final_attributes{$key} }, $value );
よくあるケースなのか分からないですが、Controllerに
sub _parse_OriginalAttr_attr { PathPart => shift->path_prefix }
な感じでメソッド作っておいてから
sub root: Chained('/') OriginalAttr CaptureArgs(0) {
とやると、動的にPathPartに値が入れられるみたいな用途で使ってるんだか分からないけど、その独自Attributesを処理してくれます。

Catalyst::Dispatcher->register


今回は最後の章です。
    my $priv = 0;
foreach my $key ( keys %{ $action->attributes } ) {
next if $key eq 'Private';
my $class = "Catalyst::DispatchType::$key";
unless ( $registered->{$class} ) {
eval "require $class";
push( @{ $self->dispatch_types }, $class->new ) unless $@;
$registered->{$class} = 1;
}
}
$action->attributesは
sub root: Chained('/') PathPart('') CaptureArgs(0) {
ってメソッドの場合だと
$action->attributes({ Chained => ['/'], PathPart => [], CaptureArgs => [0] })
と同等の内容が入っています。
なので、登録されているattributesの中で、もしCatalyst::DispachType::*の中にモジュールが定義されていたらrequireしてね!って事になります。

最後に

    foreach my $type ( @{ $self->dispatch_types } ) {
$type->register( $c, $action );
}
ロードされてるCatalyst::DispatchType::*モジュール達にメソッドとattributesの対応を投げつけ、各DispatchTypeが使うattributesがあったらDispatchType側でハンドリングしてもらうように頼んでおしまい。

その後にも初期化処理は色々あるけど、長くなりすぎて疲れるのでおわり。
実際使う時にはCatalyst::Dispatcher->prepare_actionとかCatalyst::Dispatcher->dispatchとかCatalyst::Dispatcher->forwardとか追いかければいいのかな。

と、Catalystでアプリを作ったのが1回だけしかない人間の便所の裏のチラシ

Posted by Yappo at 2007年03月14日 23:23 | TrackBack | Perl
Comments
Post a comment









Remember personal info?






コメントを投稿する前に↓の場所にnospamと入力してください。