今回のテーマ
今回は、Perlのattributeという仕組みの詳細と、そのattributeを利用したClass::Componentのpluginについての解説、pluginの作成方法といった話題を取り上げます。
サンプルアプリケーション
本連載では、プラガブルなモジュールを作製するという事を考えて、Gopperという実際に実行可能なサンプルアプリケーションを元に解説を行ないます。GopperはCodeRepos上のsvnリポジトリに置いてあるので各自checkoutしてください。
attributeとは
attributeとは、Perlのサブルーチンや変数に属性を定義して、サブルーチンとしての挙動を標準から変更したり、どのようなサブルーチンなのかを定義付けする事が出来ます。
たとえばCatalystを利用してControllerを書くときには
などと書きますよね。この例ではPrivateの部分がattributeになります。
この説明だけでは、なかなかピンとこないので実例を混ぜつつattributeについて解説します。
Perl標準で利用できるattribute
たとえば標準ではlvalueという属性が利用できます。
と記述でき、サブルーチンやメソッドを左辺値として扱うことが出来ます。
標準で利用できるattributeはlvalueの他に、thread処理で利用するmethod/locked/sharedと、ourで利用するunique(perldoc -f our)があります。
ただ、これだけではCatalystで利用するようなattributeは逆立ちしたって使えません。
どうするかというと、Package-specific Attribute Handling(package固有に属性をハンドリングさせる)という機能を利用します。
Package-specific Attribute Handling
自作モジュール中に独自のattributeを実装する事もできます。普通の状態では、先述の標準で組み込まれたattributes以外のattributeを指定するとエラーが発生してしまいます。
ではどうするのかというと、attributeを利用したいパッケージ内にてMODIFY_CODE_ATTRIBUTESというサブルーチンを定義する事により、独自のattributeを利用出来るようになります。
このような形でattributeを活用する事が出来ます。
更に詳細の話は、次回に行う予定です。
余談になりますが、perldoc attributes の中の Package-specific Attribute Handlingに関するドキュメントには
と記載されており、パッケージ独自のattributeに関しては、実験的な実装だと強調されています。しかし、 Catalyst や DBIx::Class などで多数のユーザーに利用されてしまっている機能なので、きっと気にしなくても大丈夫です(多分)。
Attribute::Handlers
attributeを簡単に利用する為のCPANモジュールという物が存在しています。これは、attributeを定義する為に実装されている、Attribute::Handlersが提供するattributeを用いて、独自attributeを実装する事が出来るモジュールです。
これを利用したCPANモジュールも、いくつか存在しています。たとえばアノmiyagawaさん作のAttribute::Protectedというモジュールがあります。public/private/protectedというJavaの修飾子のような事を、パッケージ内のメソッドに適用する事が出来ます。
実際どのように使うかを、解説を交えたコードは下記の通りです。
このように比較的簡単にattributeを実装できます。より詳細な内容はperldoc Attribute::Handlersを実行するか、CPANに登録されているAttribute::で始まるいくつかのモジュールを参考にして下さい。
Attributeを活用したPlugin作成術
このattributeをplugin的に利用してる例がDBIx::Class(以下DBIC)です。詳しくはDBIx::Class::ResultSetManagerというモジュールのドキュメントに書いてあるのですが、ResultSetというattributeを使用する事によりDBICのResultSetを拡張出来ます。
ResultSetManagerを参考にして、別のattributeをハンドリングするDBICのcomponentを書けば、DBICを面白く拡張出来ると思われます。
どの辺りで実装されているかは、DBIx::ClassとDBIx::Class::ResultSetManagerのソースをご覧下さい。ただし、ご多分に漏れずEXPERIMENTAL(実験的な扱い)です。
Class::Componentでpluginを作る
Class::ComponentはDBICのResultSetManagerを参考にして、pluginを簡単に実装する為のattributesを提供しています。どのようにしてPluginを作成するのかを、以下にサンプルアプリケーションGopperのコードと説明を交えて解説していきます。
Gopper::Plugin以下にモジュールを追加する
pluginを作る為には、モジュール名::Plugin以下の名前空間にpluginを追加する必要があります。モジュール名というのは、use Class::Componentをしているモジュールの事を指します。Gopperの場合は、Gooper.pmにてuse Class::Componentしているので、Gopper::Plugin以下にpluginを追加する事になります。
今回は例として、Gopperのconfigをダンプするメソッドを追加するpluginを作ります。
名前は、そうですねConfigDumpがいいでしょう。以下にConfigDumpのコードを記します。
たったこれだけのコードでGooperに対してメソッドを追加するpluginが書けました。
まず、Class::Componentのpluginとして動作する為には、Class::Component::Pluginを継承していなければなりません。Class::Component::Pluginを組み込む事により、pluginとしての初期処理やattributeのハンドリングを自動的に行います。
その後はpluginとして実装したい事を自由に書きます。各pluginのモジュールはGopper本体や他のpluginと干渉しないオブジェクトになっているので、Catalystのplugin作成のように他のpluginとのメソッド名の衝突を恐れる必要はありません。Plaggerのpluginのノリで実装出来ちゃいます。
メソッドを生やす?
Gopperにメソッドを追加する為に肝心な事は、Gopperに生やしたいメソッドにはMethodというattributeを指定しなければなりません。Methodを指定されたメソッドは、そのメソッド名でGopperからcallする事が出来ます。
厳密にいうとメソッドが生えるわけではなく$gooper->call('config_dump')というcallメソッドから呼び出せるようになります。ただし Class::Component::Component::Autocall::* といったcomponentを利用する事により、本当にメソッドが生えるようになります。
といったコードを書く事により、Method attributeで追加したメソッドを本当に生やす事が出来ます。
追加したモジュールを利用可能にする
ただpluginを書いただけではGopperに影響を与える事は出来ません。そこで追加したpluginを利用するコードを書く必要があります。
何をするかと言うと、追加したpluginをロードして追加されたメソッドを呼び出す処理を書きます。今回はgopper.plに対して処理を追加して行きます。
まずはpluginのロードです。
load_pluginsメソッドによってpluginをロードします。Method::ConfigDumpとしか書かれていませんが、Gopper::Pluginは自動補完されます。
次は追加したメソッドの呼び出しです。gopper.plのstartサブルーチンを書き換えます。
callメソッドで、追加されたconfig_dumpメソッドを呼び出します。Class::Component::Component::Autocall::InjectMethodなどを使用している時には、$gopper->config_dump;と簡潔に書けます。
実行してみよう!
追加したコードを実際に動かしてみましょう。gopper.plを動かすにはYAMLで記述されたPlaggerのような設定ファイルが必要です。
今回は、ConfigDumpの動作テストをしたいだけなので必要最小限の設定にしています。
この設定を用いて実行すると以下のようになります。
Gopper::Plugin::Method::ConfigDump [debug]の部分からconfigがdumpされました。
デーモンとして動作しているので、Ctrl-Cなどでgopper.plを強制終了して下さい。
pluginで追加されたメソッドがうまく動いた事が確認出来ましたね。この例を応用すればClass::Componentでのplugin作成は自在に出来ると思います。
Class::Component速報
今現在、Class::Componentのバージョンアップに向けた実装を行っています。予定としては、内部的な処理の変更、便利系componentの追加、pluginの初期化処理の一部変更を予定しています。それほど大きな変更にはならず、過去のバージョンとも互換性が保たれる予定です。
次回予告
今回は、Perlで利用出来るattributeの概念や活用方法、それを利用したClass::Componentでのplugin作成方法について解説を行いました。次回は、このattrbiteの話を掘り下げた話題と、Class::Componentでのattributeに独自attributeを実装する方法を紹介する予定です。