今回のテーマ
前回は、
サンプルアプリケーション
本連載では、
svn co -r 659 http://svn.coderepos.org/share/lang/perl/Gopper/trunk Gopper
パッケージ固有のattribute実装
Perlのattributeに関しては前回の記事を参照してください。今回はPerlのattributeで利用できるMODIFY_
attributeを独自に定義する為に
attributeを独自に定義するには、
attributeは、
# test.pl
use strict;
use warnings;
BEGIN { print "B:1\n" }
sub MODIFY_CODE_ATTRIBUTES {
print "MODIFY_CODE_ATTRIBUTES\n";
return;
}
BEGIN { print "B:2\n" }
sub test: hoge {}
BEGIN { print "B:3\n" }
$ perl ./test.pl
B:1
B:2
MODIFY_CODE_ATTRIBUTES
B:3
# test2.pl
use strict;
use warnings;
BEGIN { print "B:1\n" }
sub test: hoge {} # あれ?MODIFY_CODE_ATTRIBUTESは?
BEGIN { print "B:2\n" }
sub MODIFY_CODE_ATTRIBUTES {
print "MODIFY_CODE_ATTRIBUTES\n";
return;
}
BEGIN { print "B:3\n" }
$ perl ./test2.pl
B:1
Invalid CODE attribute: hoge at ./test2.pl line 6
BEGIN failed--compilation aborted at ./test2.pl line 6.
SCALAR / ARRAY / HASH に関しては、
# test3.pl
use strict;
use warnings;
sub MODIFY_SCALAR_ATTRIBUTES {
print "MODIFY_SCALAR_ATTRIBUTES\n";
return;
}
sub MODIFY_ARRAY_ATTRIBUTES {
print "MODIFY_ARRAY_ATTRIBUTES\n";
return;
}
sub MODIFY_HASH_ATTRIBUTES {
print "MODIFY_HASH_ATTRIBUTES\n";
return;
}
my $scalar : hoge ;
my @array : hoge ;
my %hash : hoge ;
$ perl ./test3.pl
MODIFY_SCALAR_ATTRIBUTES
MODIFY_ARRAY_ATTRIBUTES
MODIFY_HASH_ATTRIBUTES
MODIFY_type_ATTRIBUTESの実装内容
MODIFY_
例えば下記のコードのような挙動になります。
sub hoge : attr1 attr2 attr3 {}
# 上のような宣言が有るときは、内部的に下記を実行
require attributes;
attributes->import( \&hoge, qw( attr1 attr2 attr3 ) );
この時のimportで渡される一つ目の引数のリファレンスの内容を見て、
MODIFY_type_ATTRIBUTESへの引数
引数は、sub MODIFY_
$code_
Undefined subroutine called at ファイル名 line 行番号.
というエラーが出ます。
なお、
# test4.pl
use strict;
use warnings;
sub MODIFY_SCALAR_ATTRIBUTES {
my($class, $ref, @attrs) = @_;
$$ref = 'hoge';
return;
}
sub MODIFY_ARRAY_ATTRIBUTES {
my($class, $ref, @attrs) = @_;
@$ref = @attrs;
return;
}
sub MODIFY_HASH_ATTRIBUTES {
my($class, $ref, @attrs) = @_;
$ref->{key} = 'value';
return;
}
my $a : foo;
print "$a\n";
my @a : bar1 bar2 bar3;
print join ', ', @a;
print "\n";
my %a : baz;
print "$a{key}\n";
$ perl ./test4.pl
hoge
bar1, bar2, bar3
value
MODIFY_type_ATTRIBUTESの戻り値
MODIFY_
# test5.pl
use strict;
use warnings;
sub MODIFY_CODE_ATTRIBUTES {
my($class, $code, @attributes) = @_;
return grep { $_ !~ /^vox|hatena|livedoor|jugem|ameblog$/ } @attributes;
}
sub accounts : hatena livedoor jugem {}
sub wazato_error : hatena gihyo sd vox {}
$ perl ./test5.pl
Invalid CODE attributes: gihyo : sd at ./test.pl line 11
BEGIN failed--compilation aborted at ./test.pl line 11.
このようにMODIFY_
FETCH_type_ATTRIBUTES
FETCH_
# test6.pl
use strict;
use warnings;
my %cache;
sub MODIFY_CODE_ATTRIBUTES {
my($class, $code, @attributes) = @_;
$cache{$code} = \@attributes;
return;
}
sub FETCH_CODE_ATTRIBUTES {
my($class, $code) = @_;
return @{ $cache{$code} || [] };
}
sub method : perl python ruby {}
print join ', ', attributes::get(\&method);
print "\n";
$ perl ./test6.pl
perl, python, ruby
筆者としては、
再利用可能にする
今までのサンプルでは、
# AttributeBase.pm
package AttributeBase;
use strict;
use warnings;
my %cache;
sub MODIFY_CODE_ATTRIBUTES {
my($class, $code, @attributes) = @_;
$cache{$code} = \@attributes;
return;
}
sub FETCH_CODE_ATTRIBUTES {
my($class, $code) = @_;
return @{ $cache{$code} || [] };
}
1;
# test7.pl
use strict;
use warnings;
use base 'AttributeBase';
sub foo : bar baz {}
print join ', ', attributes::get(\&foo);
print "\n";
$ perl ./test7.pl
bar, baz
その他
my $foo = sub {}形式のサブルーチン宣言時にもattributeの指定が可能です。
# test8.pl
use strict;
use warnings;
use base 'AttributeBase';
my $code = sub : foo bar baz {};
print join ', ', attributes::get($code);
print "\n";
$ perl ./test8.pl
foo, bar, baz
有効な利用例は思いつきませんが、
CPANモジュールに目を向ける
さてCPANモジュールで利用されているattributeの実装に目を向けてみましょう。Class::Componentを作成した時に参考にしたDBIx::ClassとCatalystでの実装を紐解きます。
DBIx::Classでの利用例
DBIx::Classで行われているattributeの実装は、
DBIx::Class::ResultSetManagerはtable schemaで利用出来るコンポーネントです。これは何をする物かというと、
まずはSchemaクラスの作成です。
# Schema.pm
package Schema;
use strict;
use warnings;
use base qw/DBIx::Class::Schema/;
__PACKAGE__->load_classes;
1;
次にテーブルクラスです。
# Schema/Test.pm
package Schema::Test;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components(qw/ ResultSetManager Core /);
__PACKAGE__->table('test');
sub next_perl_version : ResultSet {
print "5.10.0\n";
}
1;
最後にスクリプトファイルを書きます。
# schematest.pl
use strict;
use warnings;
use Schema;
my $schema = Schema->connect;
$schema->resultset('Test')->next_perl_version;
$ perl ./schematest.pl
5.10.0
このようにResultSetというattributeを指定をするだけで簡単にメソッドが追加出来るようになりました。注意としてはテーブルクラスにて__
DBIx::Classでの実装内容
上の例ではResultSetの拡張でしか説明しませんでしたが、
ここで問題が発生するのですが、
このDBIx::Classに実装されているattributeハンドリングの仕組みを活用するコンポーネントを書けば、
実はDBIx::Class自体が継承しているモジュール達は、
# dbicattr.pl
use strict;
use warnings;
use DBIC;
DBIC->dump_attr(\&DBIC::hello);
DBIC->dump_attr(\&DBIC::dump_attr);
# DBIC.pm
package DBIC;
use strict;
use warnings;
use base 'DBIx::Class';
use YAML;
sub hello : NotDBIC {
print "hello!\n";
}
sub dump_attr : DUMP {
my($class, $code) = @_;
print Dump $class->_attr_cache->{$code};
}
1;
$ perl ./dbicattr.pl
---
- NotDBIC
---
- DUMP
このように、
Catalystでの実装
Catalystでのattribute利用は、
Catalystでのattribute実装もClass::Componentで参考しています。しかし基本的な所はDBIx::Classと同じです。いったい何が違うかというと、
詳細なフローの説明は本題からずれる部分が多いため割愛(ある程度の詳細は、
ここまで読み進めていただいた読者の皆様なら、
Class::ComponentでAttribute拡張
Class::Componentでも手軽にattributeを拡張するための手段を提供しています。CatalystのDispatchTypeを拡張する時の要領で、
Class::Component::Pluginでの実装
実際の拡張の前に、
MODIFY_
pluginの各メソッドに定義されたattributeを検出する実装に関しては、
実際に拡張する
実際にGopperに対してattributeを追加してみます。GopperはApacheのhandlerに近い仕組みで、
attributeを処理するモジュールは下記の通りです。
package Gopper::Attribute::Handler;
use strict;
use warnings;
use base 'Class::Component::Attribute';
sub register {
my(
$class,
$plugin, # attributeが定義されているプラグインのインスタンス
$c, # 大元のモジュールのインスタンス
$method, # attributeが定義されているメソッドの名前
$value, # 上記Attr('value')の例で言う所のvalue
$code # attributeが定義されているメソッドのcoderef
) = @_;
$plugin->handler_method($method); # ハンドラとして利用するメソッド名を設定する
}
1;
一見すると行数が多いですが、
その他にも全開のリビジョンから、
ハンドラメソッドの呼び出し方をhandlerメソッドに固定されていたのを動的に指定可能にする。
--- lib/Gopper.pm (revision 1213)
+++ lib/Gopper.pm (local)
@@ -62,7 +62,8 @@
sub run_handler_hook {
my($self, $stash) = @_;
return RC_FORBIDDEN unless my $handler = $stash->request->handler;
- return $handler->handler($self, $stash) || RC_FORBIDDEN;
+ my $handler_method = $handler->handler_method;
+ return $handler->$handler_method($self, $stash) || RC_FORBIDDEN;
}
sub run_request {
ハンドラモジュール中で、
--- lib/Gopper/Plugin/Handler/Static.pm (revision 1213)
+++ lib/Gopper/Plugin/Handler/Static.pm (local)
@@ -29,7 +29,7 @@
return $self->RC_OK;
}
-sub handler {
+sub send_context : Handler {
my($self, $c, $stash) = @_;
ハンドラメソッドを記憶しておくアクセサを追加。
--- lib/Gopper/Plugin/Handler.pm (revision 1213)
+++ lib/Gopper/Plugin/Handler.pm (local)
@@ -5,4 +5,9 @@
use base 'Gopper::Plugin';
+sub handler_method {
+ my $self = shift;
+ $self->{handler_method} = defined $_[0] ? $_[0] : $self->{handler_method};
+}
+
1;
拡張した物を動かしてみる
では、
global:
log:
level: debug
engine:
module: Simple
config:
host: localhost
port: 11170
plugins:
- module: Protocol::Gopher
- module: Handler::Static
config:
docroot: /tmp/docroot
Gopperのexampleに入っているdocrootを/tmpにコピーします。任意の場所に変えたい時は、
$ cp -r example/docroot /tmp/docroot
gopperを起動します。
$ perl ./gopper.pl -c=config.yaml
telnetコマンドを使ってちゃんとコンテンツが帰って来るかを確認します。
$ telnet localhost 11170
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
0rfc1436.txt /rfc1436.txt localhost 11170
1frameworks /frameworks localhost 11170
Connection closed by foreign host.
ちゃんとattributeの拡張が動いている事が確認出来ましたね。
Class::ComponentでもGopperの例のように、
次回予告
今回は、