November 29, 2003MovableTypeのプラグイン作成(邦訳)MovableTypeのプラグインを作成したくなったので、参考資料を邦訳してみることにしました。元の文はDeveloping Movable Type Plug-insでオライリーの記事です。 えぃやっ、でやってしまったので、いい加減な意訳が多いです…。意味不明、間違っている等の苦情ありましたら、ご一報願いますm(_ _)m。
(以下邦訳、インデックスの方は"続き"を見てください) この文章ではMovableType(以下MT)のプラグインのフレームワーク、全てのAPI(Application Programming Interface)、核となるシステムへのフックの仕方の基礎、データの保持機構について解説します。MTのプラグイン作成にはPerlの知識とオブジェクト指向の考え方がわかっていれば大丈夫。
MovableType プラグインフレームワーク一般的に言って、MTのプラグインで何ができるのかといえば、テンプレートに適用される文章整形エンジンに、タグを値と置換する『変数タグ』、繰り返しなどに用いられる『コンテナタグ』、内容に作用するフィルターを指定する『グローバルフィルター』に加え、条件に応じて内容を取り扱う『条件タグ』を追加するといったことに限定されます。また、MTのバージョン2.6では、文章整形エンジンへフックするAPIの登場によってプラグインのフレームワークがテンプレートの整形から切り離されたため、独自の文章整形エンジンを作成することができるようになりました。加えてプラグインからMTのデータ保持機構へ直接アクセスできるようにもなりました。では、このフレームワークの特徴についてこの文章を通して迫ってみましょう。 プラグインをインストールすることはとても簡単です。単純にMTがおいてある(つまりmt.cgiがある)ディレクトリーの下にあるpluginsという名前のディレクトリーの中に、コードを配置すればいいだけです(もし古いバージョンを使用しているなら、このディレクトリーを作らなければならないでしょう)。MTはpluginsディレクトリーにある全ての*.plをプラグインとして読み込むでしょう。プラグインは同じMTで運営されている限り全てのブログで有効になります。 プラグインのフレームワークに加えて、MTは開発者が彼らの独自拡張のアプリケーションのためのコマンドラインユーティリティを作成できるように、拡張可能でドキュメントが整った一連の機能性をそのオブジェクト思想に則ったPerlモジュールによって提供しています。しかし、この文章ではそのことまでカバーしていません。 では、はじめてのプラグインを作成してみましょう。
MTHelloWorld: はじめてのプラグインでははじめに、ベーシックな変数タグ、つまりは単純にそのタグに出会ったときに値をタグと置換するものをつくってみましょう。 package MT::Plugin::HelloWorld;
use MT::Template::Context; MT::Template::Context->add_tag(HelloWorld => sub { return 'Hello World.'; } ); 1; このコードをファイル名をmt-helloworld.plとして先述のpluginsディレクトリーに保存してください。そしてテンプレートの中で<$MTHelloWorld$>を使ってみてください。そのテンプレートを再構築すると<$MTHelloWorld$>がHello Worldという文字列に置き換えられているはずです。では中身のコードに迫ってみましょう。 まず最初の行で、MT::Plugin::HelloWorldのパッケージであることを宣言しています。この宣言は必要ではありませんが、この宣言することによって他のMTのプラグインとのネームスペースの干渉を避けることができます。MT::Plugin::を使用しているのも同様の理由によるものです。 ではより進んだことをしましょう。特定の世界に対して"Hello"を言うようにしたいとしましょう。またその世界は、テンプレートによって指定できるようにするものとします。以下のコードがこの要望を満たします。 package MT::Plugin::HelloWorld;
use MT::Template::Context; MT::Template::Context->add_tag(HelloWorld => \&hello_world); sub hello_world { my $ctx = shift; my $args = shift; return "Hello " . (defined($args->{world}) ? $args->{world} : 'World'); } 1; こうすれば<$MTHelloWorld world="Krypton"$>が使えるようになり、Hello Kryptonが挿入されるようになったはずです。もしworld属性を指定しなかった場合、Hello Worldとなります。では違いをみてみましょう。 3行目で外部にある名前付きサブルーチンを呼び出すようにしました。サブルーチンにははじめの2行で見ることができるように、プラグインのルーチンで使用できる2つの参照をMTが渡しています。わかりやすく書いたために冗長な書き方ですが、ここはお好みでどうぞ。 $ctxであらわされるはじめの参照はMT::Template::Contextの現在のインスタンスです。このオブジェクトはテンプレートの整形プロセッサーの現在の状態に対する全ての情報をもっており、これはすべてのプラグインに対して、根本になるものです。このオブジェクトの共通部分については後のセクションで探求しましょう。 2番目の参照、つまり$argsで宣言されている参照は、テンプレートで定義されている属性のHashです。サブルーチンの3行目、そして最後の行では、このHashをworld属性が定義されているかどうかを確かめるため、そしてそれに従って、Worldをデフォルト値として結果を返しています。他の引数はすべて無視するのがよいです。(おそらく他の引数は全体に適用されるフィルターハンドラです) 注)今回の例では、常にテンプレートに何かを挿入しましたが、ある条件に基づいては何も挿入したくないということがあるでしょう。そのような場合は、定義されていない値ではなくnull文字列を返さなければなりません。プラグインから返された定義されていない値はエラーとして扱われ、処理を停止させます。 値の置き換えは実のところあまりおもしろくありません。プラグインの真の実力はシステムにフックし他のタグとともに使われるきときにあらわれます。プラグインのフレームワークの残りの話を続ける前に、MTの処理にフックする一般的な方法について見てみることにしましょう。
The StashMTのテンプレート処理にフックするには、MT::Template::Contextモジュールで提供されているstashを使用します。stashを通してMTが現在処理中の情報を取得することができます。またstashを使用することによって、他の関連づけられたタグが後から使用するために、自身の情報を格納することもできます。以下が簡単な例です。 #この行は'foo'をキーとし、現在のコンテキストである$valueをstashに格納します。
$ctx->stash('foo',$value); # この行はfooの値を取得し、$valueに代入します。 MTはテンプレートを処理中に出合ったタグに対して再帰的にこのように動作するため、コンスタントに追加、取得、値の消去がstashに行われます。以下がMTがテンプレート処理中に使用するいくつかのキーです。
タグ以外では、これらのstashに蓄えられた参照はMTによってデータベースからメモリに展開されたコンテンツに対するアクセスを提供します。これはテンプレートタイプや他のタグによって決定されたテンプレートが処理すべき現在の文脈に応じて展開されます。 後の例でみるようにstashに蓄えられたこれらの情報は自前のプラグインを作成するのにとても便利です。では、話を戻してMTのプラグインのフレームワークに関する残りの話の続きをしましょう。 Container Tags(コンテナタグ)先述のとおり、値を置換するタグはおもしろみにかけます。MTによってサポートされる別の方法としてコンテナタグがあります。その名前が示すとおり、このタイプのタグは別のマークやテンプレートタグを開始タグと終了タグの間に含むことができます。コンテナタグはテンプレートのコードをひと塊まりとして処理すること、そして他のタグの出力結果から文章を生成することが可能です。以下にしめすのはMTのはじめから組み込まれているコンテナタグであるMTEntriesの簡単な例です。このタグはエントリーの題をテンプレートのMTEntryTitleの部分に挿入します。 <MTEntries>
<$MTEntryTitle$><br /> </MTEntries> コンテナタグをプログラムするにはもう少し考える必要があります。なぜなら、コンテナタグの中身はさらなる処理を要求するからです。値を置換するタグを伴ったコンテナタグの簡単な例を見てください。 package MT::Plugin::SimpleLoop;
use MT::Template::Context; MT::Template::Context->add_container_tag(SimpleLoop => \&loop ); MT::Template::Context->add_tag(SimpleLoopIndex => \&loop_index ); sub loop { my $ctx = shift; my $args = shift; my $content = ''; my $builder = $ctx->stash('builder'); my $tokens = $ctx->stash('tokens'); foreach my $i (1..$args->{loops}){ $ctx->stash('loop_index',$i); my $out = $builder->build($ctx, $tokens); $content .= $out; } } sub loop_index { my $ctx = shift; return $ctx->stash('loop_index'); } 1; このプラグインを実装することによって、以下のようにして数のリストをつくれます。 <MTSimpleLoop loops="10">
<$MTSimpleLoopIndex$><br /> </MTSimpleLoop> 一つずつ解説していきます。コンテナタグや置換タグをその関連するサブルーチンとともに登録する前に、MT::Template::Contextクラスを使用することを宣言しています。loopの繰り返しルーチンに移ったとき、まずContextクラスと引数のHashへの参照を変数を先に割り当てています。 コンテナタグは変数の対応関係が他のテンプレートタグとは異なります。これはコンテナタグ自身のサブルーチンから抜ける前に、コンテナタグの中身をテンプレート処理エンジンに引き渡さなければならないためです。例にあらわれているように、テンプレート構築クラス(MT::Builder)に対する参照をstashから取得し、$builderに格納しています。加えて、一連の処理中のトークンに対する参照も取得し、$tokensに格納しています。 この例がstashとともに機能するタグのよい例となったことでしょう。というわけで、MTのプラグインによる拡張の可能性を紹介していきたいと思います。
Conditional Tags(条件タグ)条件タグは利便性のためにAPIにつけたされた特別なコンテナタグなので、すらすら読めることでしょう。 条件タグとして登録されたサブルーチンはtrueかfalseの値を返すことだけが必要です。MTは条件タグが通ったかどうかで、自動的にさらなる処理を行うか、あるいは出力を剥ぎ取るかを決定します。言い換えれば、builderオブジェクトを条件でラッピングする必要があるのです。以下に単純な2つの条件タグの実装を示します。 package MT::Plugin::ConditionalExample;
use MT::Template::Context; MT::Template::Context->add_conditional_tag(IncludeThis => sub { return 1 }); MT::Template::Context->add_conditional_tag(ExcludeThis => sub { return 0 }); このプラグインを実装することによって以下のマーク付けがテンプレートで可能になります。 <MTIncludeThis>This text will appear.</MTIncludeThis>
<MTExcludeThis>This text will be stripped.</MTExcludeThis> "This text will appear."だけが残りました。MTのテンプレートビルダーにtrue(つまり1)が渡ったためです。
Global Filters(グローバルフィルター)グローバルフィルターはタグではありません。MTのテンプレートタグのいずれに対しても付け加えることが出来る属性のことです。グローバルフィルターは一つの値(きわめて多くの場合、"1"が有効であることを示します)をとり、挿入前にタグの中身に対して適用されるフィルターを呼び出します。MT備え付けのグローバルフィルターにはマーク付けタグを除去するもの、XMLのエンコードをするもの、テキストを小文字(例えばA→aにする)ものなどがあります。 グローバルフィルターはカスタマイズしがいがありますが、一般的にとても単純です。以下は空行を除去するグローバルフィルターの例です。 package MT::Plugin::StripBlanks;
use MT::Template::Context; MT::Template::Context->add_global_filter(strip_blanks => sub { \&strip_blanks }); sub strip_blanks { my $text = shift; my $arg_value = shift; my $ctx = shift; $text=~s/^\s*?\n//gm if ($arg_value); return $text } 1; 前同様、冗長にこのコードは書かれています。グローバルフィルターに渡される値はタグベースのものとはことなることに注意してください。グローバルフィルターには処理されたテキストの実体、属性の値(hoge="1"なら"1")、Contextクラスのインスタンスに対する参照が順に与えられます。 この例では、属性の値が$arg_valueに蓄えられています。そして、Contextオブジェクトが$ctxに蓄えられていますが使われていませんので、無視できます。この例では単純に正規表現による置換を作用させて結果を返しています。 このプラグインは以下のよう使います。 <MTEntries strip_blanks="1">
... </MTEntries> これによってタグ内の全ての空行は取り除かれます。
Text-Formatting Plugins(テキスト整形プラグイン)MTのバージョン2.6のリリースでは、文章整形エンジンへフックするAPIの登場によってプラグインのフレームワークがテンプレートの整形から切り離されました。このタイプのプラグインは、XHTML等のフォーマットを気にせずに、MTのブラウザベースのインターフェスで一般のユーザが文章を作成するのに、機能的でよりやさしい手段を提供することを目的としています。 テキスト整形エンジンは構造化された文章記法のフォーマットを別のマークアップ言語(HTMLのMLはMarkup Languageでマークアップ言語の一種であることを示しています)、例えばXHTMLに置き換えます。ある場合はテキスト整形プラグインはグローバルフィルターのようになるでしょう。しかしながら、注意すべき違いがあります。すべての筆者が同じフォーマットスタイルを使用することを強制するために、グローバルフィルターを用いるならば、明示的にそれぞれのテンプレートで宣言されなければなりません、加えてMTのプレビューレンダリングには効果が及びません。以下の例は、Tikiと呼ばれる記法を用いて開発した、初期のテキスト整形プラグインのサブセットです。 package MT::Plugins::TikiText;
use MT; MT->add_text_filter('tiki' => { label => 'TikiText', docs => 'http://www.mplode.com/tima/projects/tiki/', on_format => \&tiki; }); sub tiki { my $text=shift; my $ctx=shift; require Text::Tiki; my $processor=new Text::Tiki; return $processor->format($text); } ここにはどのようにしてこのタイプのプラグインを実装するかにおけるいくつかの大きな違いがあります。まずはContextモジュールではなく、MTモジュールを使用してテキスト整形プラグインを登録していることです。もう一つの大きな違いはテキスト整形プラグインを登録することに、より関わっています。 テキスト整形エンジンは1つのキーと関連づけられたオプションのHashによって登録されます。この例ではキーとして"tiki"を使用しました。構築するときにそれぞれのエントリーでどの整形エンジンを使用するか決定するのに使用されるので、キーは非常に重要です。キーは小文字であるべきで、アルファベットとアンダーバー("_")のみで構成されているべきです。このキーは一度展開されたら換えるべきではありません。
Error Handling(エラー処理)開発者にとって、物事が計画どおりにいかないことはしばしばなので、エラーに対処する準備をしなければなりません。例を単純に簡単にするために、エラー処理全般についてお話します。では見てみましょう。 先述のとおり、プラグインのルーチンから定義されていない値が返されるとエラーとしてMTによって割り込みがかけられ、処理が停止します。このエラーメッセージは『500 Internal Server Error』よりも多くの情報を含んでいるため、どこでエラーがおきたか、そしてどうやって修正したらよいかをユーザに教えることができます。 return $ctx->error('An informative error message to help the user.');
また、Contextクラスはerrstrメソッドを継承しており、最後におきたエラーメッセージのセットを取得することができます。 warn $ctx->errstr;
これらのメソッドには多くの使用方法がありますが、以下のような使われ方が一般的です。 # 特定の文脈で、あるタグが呼ばれたのかチェックします。
# 今回は、MTEntryの中に置かれるべきタグについてチェックします。 $ctx->error('MT'.$ctx->stash('tag').' has been called outside of an MTEntry context.') unless defined($ctx->stash('entry')); # 必要とするname属性が与えられているか調べます。 $ctx->error('name is a required argument of '.$ctx->stash('tag').'.') unless defined($args->{'name'}); # テンプレートを構築中におきたエラーを捕捉し、文脈へそれを返します。
Plugin Data Storage(データ保持プラグイン)バージョン2.6のプラグインフレームワークからはMT::PluginDataクラスが追加されました。これはプラグイン開発者に直接MTのデータ保持機構へアクセスする便利な機能を提供します。MT::EntryやMTがもともと持っている同様のオブジェクトと同じように、MT::PluginDataはMT::Objectを継承しています。この抽象クラス、すなわちMT::ObjectはMTが使用するデータ保持機構の違いを吸収しています。(MTは現在のところBerkeley DB、MySQL、SQLite、そしてPostgreSQLをサポートしています。) MT::Objectを継承したクラスに付加されるpluginやkey、dataメソッドはモジュールでユニークである必要があります。
以下に示すのはコメントに作用するプラグインの一部分です。 use MT::PluginData;
my $data = MT::PluginData->new; $data->plugin('my-plugin'); $data->key('unique-key'); $data->data($big_data_structure); # $big_data_structureは参照であることに注意してください。 # $data->data('string'); # エラーになります # $data->data(\'string'); # 正しいです! $data->save or die $data->errstr; # saveはMT::Objectから継承しています。 # 別の場所でデータを取得するには… my $data = MT::PluginData->load({plugin => 'my-plugin', key => 'unique-key'}); my $big_data_structure = $data->data; この例では表面を示しただけにすぎません。MTのデータ保持機構については別の文献やそのコードをあたるのがよいでしょう。重要なのはこういうものがあるんだという利点をつかむことです。
実践編自前のMTのプラグインを作成することを通して、そしてMT仲間が作ったコードを読むことによって私が学んできた実践編の要約を記します。
結論このプラグインフレームワーク紹介の弾丸ツアーで、PerlやMTのプラグインフレームワークの豊富な機能をもってすれば、システムを柔軟に、そしてよりパワフルに拡張できることがわかっていただけたなら幸いです。オブジェクト指向のPerlに関するより多くのノウハウをもってすれば、MTがもつ潜在能力によってさまざまなパブリッシングアプリケーションの構築が可能になるだけでなく、非常に簡単に実装できることでしょう。 詳細や最新の情報は以下のリソースをチェックしてみてください。
原文のコピーライト コメント
はじめまして。とてもありがたい邦訳ですね! さて、条件タグのサンプルを試してみましたところ、ExcludeThisのタグ "</ExcludeThis>" が残ってしまいます。 > MT::Template::Context->add_conditional_tag(Excludethis => sub { return 0 }); の、"Excludethis"を"ExcludeThis"に直したところ、正常に動作するようになりました。 ありがとうございます。ご指摘のとおりでした。 Posted by: fenrir : November 30, 2003 01:13 AMこんばんわ。fenrirさんのこの偉大なエントリを参考にして、自分でもplugin作ってみました。もう、感謝感謝です。 Posted by: mya : November 30, 2003 10:43 PM実は僕も日本語で概要が指定した単語数にならないことにむかついてプラグインを作ろうとしていました。すばらしいです!!是非使わせていただきたいと思います>myaさん。 Posted by: fenrir : November 30, 2003 10:51 PM同じ考えを持つ方って、やっぱりいらっしゃるんですね〜^^; http://www.mya.dyndns.org/mt/plugins/first_n_words.pl スイマセン、上記のURLのファイルにsyntax error残ったままでした^^; 早速なおしましたので、再度取得よろしくお願いします。 Posted by: mya : November 30, 2003 11:28 PMありがとうございますっ!!ありがたく使わせていただきます。これでJavaScriptともお別れできる~(今まではJavaScriptで文字数で文字列を切る関数を作って使っていました…) Posted by: fenrir : December 1, 2003 01:20 AMはじめまして。 休日表示カレンダープラグインを作りました。プラグイン作成にあたっては、この邦訳が役に立ちました。感謝します。ご興味があれば、 コメントする
|
スポンサード リンク
|