Kentaro Kuribayashi's blog

Software Engineering, Management, Books, and Daily Journal.

Sledge 事始め

いままでなんかてけとうにごちゃごちゃとあれこれ作ったりしてたのですが、Web アプリを作るに際して共通した処理をあれこれやってくれるらしいフレームワークなる便利げなものを使うといいと聞きました。そこで、Perl による Web アプリケーションフレームワークであるところの Sledge を使う練習をしています。そもそもフレームワークというものの考え方や使い方がさっぱりわからないところからスタートしてるので、満足に使えるようになるまでにはかなり時間がかかりそうです…。とりあえずの第一歩として、掲示板を作ってみました。

ていうかまぁ、BBS とかそんなのは別にどうでもいいことで、以下、メモ的にあれこれやってみたことをちと書いてみます。

付属ドキュメント Tutorial によると、Sledge フレームワークでは、.cgi は Apache::Registry から起動し、Pages クラスのトリガとして動作しますとあり、index.cgi の例として以下のように記述されています。

#!/usr/local/bin/perl
use strict;
use HelloWorld::Pages::Index;

HelloWorld::Pages::Index->new->dispatch('index');

このようにすることで、HelloWorld::Pages::Index の dispatch_index メソッドが呼び出され、あれこれ処理が行われます。あるいは、Sledge::DispatchQuery を用いて、以下のようにリクエストに応じて動的に dispatch_xxx を呼び出すこともできるようです。

#!/usr/local/bin/perl
use strict;
use HelloWorld::Pages::Index;

# .cmd は、index.cgi?.cmd=xxx みたいな感じで指定される
# 結果、HelloWorld::Pages::Index の「dispatch_ + .cmd で指定された名前」のメソッドが呼ばれる
HelloWorld::Pages::Index->new->dispatch_query('.cmd');

なるほどー、という感じなのですが、さらに Pages (サブ)クラスも動的に呼び出せるようにすると、トリガとなるスクリプトをひとつおいておけばあれこれやってくれて便利かな、と思ったので、以下のような感じで実験してみました。PATH_INFO をパースすることにより Pages サブクラスと dispatch 名を取得して、それらを動的に呼び出します。ちなみに「Sledge - asakura」というページを激しく参照しました。はじめの一歩的なことがわかりやすくまとめられていて素晴らしいです。

まずは、Config/_common.pm に以下の通り追記します。Pages サブクラスをモジュール、dispatch_xxx をアクションとみなし、それぞれをまずは書き出しておきます。bbs てのが今回作成したおもなモジュールで、例えば Blog を作ったら同じような要領で追記していくことになります。default は、PATH_INFO になんにもしていがないときに呼び出され、not_found は存在しないモジュール、アクションがリクエストされたときに呼び出されます。

# 使用するモジュールとアクションを記入
$C{MODULES} = {
    bbs => {
        module_name => 'Ipop::Pages::BBS',
        action      => [qw(index entry input confirm finish)],
    },
    default => {
        module_name => 'Ipop::Pages::Index',
        action      => 'index',
    },
    not_found => {
        module_name => 'Ipop::Pages::NotFound',
        action      =>> 'index',
    },
};

んでもって、トリガとなる index.pl は以下の通り。

#!/usr/bin/env perl

use strict;
use Ipop::Controller;

Ipop::Controller->new->dispatch;

上で呼び出している Ipop::Controller は以下の通り。このクラスが PATH_INFO を解析してモジュール名 + アクション名や、パラメタ等を取得し、よきにはからってくれます。例をあげると、http://ipop.zapto.org/index.pl/bbs/entry/1 という URI の場合、モジュール = bbs + アクション = entry + 第一パラメタ = 1 になります(パラメタは複数指定可)。

package Ipop::Controller;

use strict;
use Ipop::Config;

sub new {
    my ($class, $modules) = @_;
    my $config = Ipop::Config->instance;
    bless {
        modules => $config->{modules},
    }, $class;
}

sub dispatch {
    my $self = shift;
       $self->_parse_path_info;

    # PATH_INFO を解析して得たモジュール名により
    # Pages サブクラスのインスタンスを生成
    eval qq{require $self->{module_name};};
    my $page = $self->{module_name}->new;

    # Apache::Request オブジェクトにモジュール名、アクション名、パラメタを代入
    $page->r->param('module' => $self->{module});
    $page->r->param('action' => $self->{action});
    $page->r->param('params' => $self->{params});

    return $page->dispatch($self->{action_name});
}

sub _parse_path_info {
    my $self = shift;
    my $modules = $self->{modules};

    # PATH_INFO からモジュール、アクション、パラメタを取得
    (my $path_info = $ENV{PATH_INFO}) =~ s|^/?(.*?)/?$|$1|;
    my ($module, $action, @params) = split '/', $path_info;

    # モジュール名を決定
    my $module_name;
    if (!defined $module) {
        $module_name = $modules->{default}{module_name};
    } else {
        $module_name   = $modules->{$module}{module_name}
                           || $modules->{not_found}{module_name};
    }

    # アクション名を決定
    my $action_name;
    if (!defined $action) {
        $action_name = $modules->{default}{action};
    } else {
        if (grep {$_ eq $action} @{$modules->{$module}{action}}) {
            $action_name = $action;
        } else {
            $action_name = $modules->{not_found}{action};
            $module_name = $modules->{not_found}{module_name};
        }
    }

    # 各値をインスタンス変数に代入
    $self->{module} = $module;
    $self->{action} = $action;
    $self->{params} = \@params;
    $self->{module_name} = $module_name;
    $self->{action_name} = $action_name;

    return 1;
}

1;

まぁそんなこんなで、動的にモジュールやアクションを呼び出せるようになり、ちょっと楽になったかなと思ったりしたわけですが、そもそも Sledge 自体の使い方や処理の流れを全然把握していないので、激しく見当違いなことをしているんじゃないか? という気がします…。