ソースを見るのが一番早く、正確に理解ができると思うのですが、その前のとっかかりとしてSledgeの内部について少し書いてみたいと思います。
SledgeにはSledge::Pages::Baseというモジュールがあり、このモジュールにはSledgeのライフサイクルの根幹となるメソッド群が定義されています。Sledgeの内部を調べたりAPIを調べる際には、まずここから調べ始めるべきです。
Sledgeのライフサイクルをものすごく単純にすると、下記のような感じになります。
init
[AFTER_INIT]
dispatch
init_dispatch
[BEFORE_DISPATCH]
post_dispatch_foo
dispatch_foo
[AFTER_DISPATCH]
output_content
[AFTER_OUTPUT]
このライフサイクルでなにが行なわれているかというと、まずクライアントがhttp://example.com/foo.cgi(foo.cgiのコードは下記にあります)というURIをリクエストすることにより、それがトリガーとなって該当のPagesクラス(下記の場合はProject::Pages::Root)のdispatch()メソッドが呼ばれます。dispatch()が呼ばれると、五月雨式に上記のライフサイクルが上から順に実行され、「パラメータの調整」「ビジネスロジック」「テンプレートの準備」「アウトプットの調整」「ヘッダとコンテンツの出力」等の処理を行います。
foo.cgiのコード
#!/usr/local/bin/perl
use strict;
use Project::Pages::Root;
Project::Pages::Root->new->dispatch('foo');
それでは、もう少し各フェーズの中で、なにが行なわれているのかを簡単に見ていきたいと思います。
・init (Sledge::Pages::Base::init)
Sledgeに必要な各オブジェクトのインスタンスの生成を行なっています。具体的には、リクエスト(Sledge::Pages::ApacheもしくはSledge::Pages::CGI)、オーソライザ(Sledge::Authorizer)、セッションマネージャ(Sledge::SessionManager)、キャラセット(Sledge::Charset)のインスタンスをProject::Pages(sledge-setupコマンドによって自動生成)に書かれたcreate_****に応じて生成しています。
・dispatch (Sledge::Pages::Base::dispatch)
Sledgeの処理の根幹と言ってもいいメソッドです。この中では、初期化処理、ビジネスロジック処理、コンテンツ生成、最終的な出力まで行なっています。
次に、dispatchの中で行なう処理を見ていきます。
- init_dispatch (Sledge::Pages::Base::init_dispatch)
ビジネスロジック処理のための初期化処理になります。具体的には、「テンプレート名等の決定に使われるpageの初期化」「セッションの初期化(セッション未設定時のみ)」「認証処理」「クエリの文字コードの変換」「テンプレートオブジェクトの初期化」「フォームのフィルインの初期化(ポスト時のみ)」が順番に行なわれます。この辺の処理はとてもシンプルに実装されていますので、一度ソースを見ておいた方がよいでしょう。
- dispatch_foo、post_dispatch_foo
自分でコーディングするビジネスロジックが呼ばれる部分になります。dispatch_fooとpost_dispatch_fooの違いですが、dispatch_fooはGET、POSTでのリクエスト時に、post_dispatch_fooはPOSTでのリクエスト時のみ呼ばれます。なぜこのようなフローになっているかというと、Sledgeでは、(必ずしもそう実装する必要はないですが)POSTでのリクエスト(例えばフォーム等でsubmitを押した)時にその時表示しているURIと同様のURIにPOSTするフローを推奨しています。つまり、formタグのactionにはそのページ自分自身を指定します。そうすることで、POSTを実行した際にpost_dispatch_foo→dispatch_fooという順番で呼ばれるため、post_dispatch_fooでは入力値のバリデーションを行ない、入力値の正当性が確認できた場合には次のフェーズのURIにredirectをして、正当ではなかった場合にはdispatch_fooが呼ばれ、同様の画面にてバリデーションエラーを表示するというフローになっています。
ここでURIの流れがでたので、少しそれについて説明します。典型的な入力のフロー「入力(input.cgi)」「確認(confirm.cgi)」「完了(finish.cgi)」をSledgeで実装する場合には、下記のような流れになります。
1 GET index.cgi[dispatch_index]
↓
2 POST index.cgi[post_dispatch_index]
↓バリデーション不正→GET index.cgi[dispatch_index]
3 GET confirm.cgi[dispatch_confirm]
↓
4 POST conform.cgi[post_dispatch_confirm](DBへ接続しインサート)
↓
5 GET finish.cgi[dispatch_finish]
こういった流れにすることにより、2の部分でバリデーションをし正当の場合にはフォームの内容をセッション等に格納しconfirm.cgiへリダイレクト、不正の場合はフィルイン処理(POSTの場合はSledgeが勝手に処理してくれる)をしてindex.cgiをそのまま表示という流れができます。また、4の時点でDBへインサート後にfinish.cgiにリダイレクトすることによりデータの重複登録を防いだりする効果もあります。
- output_content (Sledge::Pages::Base::output_content)
このフェーズでは、アウトプット前にコンテンツを生成(Sledge::Pages::Base::make_content)したり、出力前のフィルタ処理などを行ない、最終的にレスポンスを返します。コンテンツの生成は、テンプレートエンジンにテンプレートとdispatch_foo、post_dispatch_foo等で$self->tmpl->param()にて設定をした値を渡し、アウトプットするコンテンツを生成という流れになります。フィルタに関しては、$self->{filters}に渡したコードリファレンスを順に実行します。そして最終にヘッダーと出来上がったコンテンツを吐いて、ページが表示されます。
・フック (AFTER_INIT、BEFORE_DISPATCH、AFTER_DISPATCH、AFTER_OUTPUT)
Sledgeには、内部処理フェーズの各部分に簡単に独自の処理を挟むことのできる機構を供えています。その機能はフックという形で実装されていて、現在4つのフックが用意されています。各フックがいつ処理されるかは下記の通りですが、より詳細に処理されるタイミングを知りたい場合にはSledge::Pages::Baseのソースを見た方がよいでしょう。
- AFTER_INIT
initの直後
- BEFORE_DISPATCH
dispatchの直前
- AFTER_DISPATCH
dispatchの直後
- AFTER_OUTPUT
outputの直後
フックは下記のように登録することができます。下記の例は、BEFORE_DISPATCHフックにてTemplate-ToolkitのINCLUDE_PATHを追加しています。
__PACKAGE__->register_hook(
BEFORE_DISPATCH => sub {
my $self = shift;
$self->tmpl->add_option(INCLUDE_PATH => '/foo/bar/baz');
},
);
このフックを使って「Sledgeのインストールと設定方法」で書いたWebアプリケーションを使って、ちょっと遊んでみました。
前回作成したHello::Pages::Rootを下記のように書換えました。(今回は便宜上、行数を付けました)
1 package Hello::Pages::Root;
2
3 use strict;
4 use base qw(Hello::Pages);
5 use Acme::MorningMusume;
6 use Acme::Monta;
7
8 __PACKAGE__->tmpl_dirname('.');
9 __PACKAGE__->register_hook(
10 BEFORE_DISPATCH => sub {
11 my $self = shift;
12 $self->tmpl->add_option(FILTERS => {
13 montaize => sub {
14 my $text = shift;
15 my $monta = Acme::Monta->new;
16 return $monta->montaize($text);
17 },
18 });
19 },
20 );
21
22 sub dispatch_index {}
23
24 sub dispatch_quiz {
25 my $self = shift;
26 my $musume = Acme::MorningMusume->new;
27 my @members = $musume->members;
28 $self->tmpl->param(members => \@members);
29 }
30
31 1;
今回追加された部分は、5〜6行目、9〜20行目、24〜29行目になります。
5〜6行目は、単にこのPagesクラスで使用するモジュールをuseしているだけになります。
9〜20行目は、フックの登録になります。今回はTTのFILTERSに今流行りのもんたメソッドを使用するためのAcme::Montaを使用したmontaizeというフィルタを追加しています。
24〜29行目は、ちょっと前に話題になったAcme::MorningMusumeからメンバーの一覧を取得し、TTへ配列リファレンスを渡しています。
この結果は http://sledge.clouder.jp/quiz.cgi で確認することができます。
ちなみにテンプレートのmemberを表示している部分のソースは下記になります。文字コード変換にT::P::Jcodeを、ランダム表示にT::P::Shuffleを使っています。
[% USE Jcode %]
[% USE Shuffle %]
[% FOREACH musume = members.shuffle -%]
[% IF loop.last -%]
[% '<monta>' _ musume.name_ja.jcode.euc _ '</monta>' | montaize %]<br />
[% ELSE -%]
[% musume.name_ja.jcode.euc %]
[%- END # END IF %]
[%- END # END FOREACH %]
ということで、非常に簡単ですがSledgeのライフサイクルについて説明してきました。この基本の流れを知っていると、Sledgeでページがうまく表示されなかったり、予期しない動作が起った場合にも、対処をすることができるようになるのではないかと思います。