Catalyst-5.62 > Catalyst::Manual::Intro

題名

Catalyst::Manual::Intro - はじめてのCatalyst

説明

ここではなぜ、またどうやってCatalystを使うのかを簡単に紹介します。Catalystの挙動について説明し、簡単なアプリケーションを手早く立ち上げる様子をご覧に入れます。

Catalystとは?

Catalystとはエレガントなウェブ・アプリケーション・フレームワークです。きわめて柔軟なのにきわめてシンプル。Ruby on RailsやSpring (Java)、そしてCatalystの元となったMaypoleによく似ています。

MVC

Catalystはモデル・ビュー・コントローラ(MVC)というデザイン・パターンを踏襲しているため、コンテンツ、プレゼンテーション、フロー管理といった問題を簡単に切り分けて独立したモジュールにすることができます。こうして切り分けることで他の問題を処理するコードに影響を与えることなく、ひとつの問題を扱うコードのみを修正することができるようになります。Catalystは一般的なウェブ・アプリケーションの問題を上手に処理してくれる既存のPerlモジュールの再利用を奨励します。

次に、M、V、Cがこれらの問題とどう結びつけられるかを、みなさんが使いたいと思われるであろう有名なPerlモジュールの実例とともに紹介します。

MVCやデザイン・パターンに不慣れな方は、Gamma、Helm、Johson、Vlissides、またの名をGang of Four (GoF)の手になるこのテーマの原典、Design Patternsをチェックしておいた方がよいかもしれません。Googleで検索するだけでもよいでしょう。本当に多くのウェブ・アプリケーション・フレームワークがMVCをベースにしています。上記のフレームワークもすべてそうです。

柔軟性

Catalystは他の多くのフレームワークに比べてはるかに柔軟です。詳しくはまた後で紹介しますが、Catalystなら間違いなくお好きなPerlモジュールを使えると思ってください。

シンプルさ

何よりすばらしいのは、Catalystがこれらの柔軟性を非常にシンプルな方法で実装していることです。

クイックスタート

では、前述したヘルパー・スクリプトを使ってCatalystをインストールし、簡単なアプリケーションを実行してみましょう。

インストール

    $ perl -MCPAN -e 'install Task::Catalyst'

セットアップ

    $ catalyst.pl MyApp
    # (出力は省略)
    $ cd MyApp
    $ script/myapp_create.pl controller Library::Login

実行

    $ script/myapp_server.pl

では、Catalystが動いているのを確かめるために、お好きなブラウザ、ユーザ・エージェントで以下のURLにアクセスしてみてください。

http://localhost:3000/
http://localhost:3000/library/login/

死ぬほど簡単でしょ!

Catalystの動作

さて、Catalystはどのように動作しているのでしょう。Catalystアプリケーションのコンポーネントなどをもっとよく見ていきましょう。

アプリケーション・クラス

モデル、ビュー、コントローラといったコンポーネントに加えて、アプリケーションそのものをあらわすクラスがひとつあります。ここでアプリケーションの設定をしたり、プラグインをロードしたり、アプリケーション全体に関わるアクションを定義したり、Catalystを拡張したりします。

    package MyApp;

    use strict;
    use Catalyst qw/-Debug/;

    MyApp->config(
        name => 'My Application',

        # これ以外に何を書いても結構です。
        my_configuration_variable => 'something',
    );

    sub default : Private {
        my ( $self, $context ) = @_;
        $context->response->body('Catalyst rocks!');
    }

    1;

たいていのアプリケーションの場合、ここで定義しなければならない設定パラメータは1つのみです。

オプションとして、テンプレートや静的データの位置を示すrootパラメータを指定することもできます。このパラメータを省略すると、Catalystはディレクトリの位置を自動的に検出しようとします。また、プラグインなどに必要なパラメータはいくらでも定義できます。このパラメータはアプリケーションのどこからでも$context->config->{$param_name}でアクセスできます。

コンテキスト

Catalystはコンテキスト・オブジェクトを自動的にアプリケーション・クラスにブレスして、アプリケーションのどこからでもアクセスできるようにします。Catalystと直接やりとりしたり、コンポーネントを互いにのり付けするときにはコンテキストを使ってください。たとえば、Template Toolkitのテンプレートの中からコンテキストを使う必要があるなら、使ってください。すでに使えるようになっていますから。

    <h1>Welcome to [% c.config.name %]!</h1>

URLからアクションへとディスパッチする例で見たように、コンテキストは常にメソッドの2番目のパラメータになります。その前にはコンポーネント・オブジェクトのリファレンスや、クラス名そのものが来ます。先だってはわかりやすさを優先して$contextと表記しましたが、Catalystの開発者はたいてい$cと書くだけで済ませます。

    sub hello : Global {
        my ( $self, $c ) = @_;
        $c->res->body('Hello World!');
    }

コンテキストにはいくつかの重要なオブジェクトが含まれています。

この最後のスタッシュというのは、アプリケーション・コンポーネントの間でデータを共有するのに使うユニバーサル・ハッシュです。たとえば、先ほどの「hello」アクションに戻ってみましょう。

    sub hello : Global {
        my ( $self, $c ) = @_;
        $c->stash->{message} = 'Hello World!';
        $c->forward('show_message');
    }

    sub show_message : Private {
        my ( $self, $c ) = @_;
        $c->res->body( $c->stash->{message} );
    }

スタッシュは1つの独立したリクエスト・サイクルの中でデータを渡すときにのみ使うように注意してください。新しいリクエストが来るとスタッシュはクリアされます。もっと長い間データを維持する必要があるときはセッションを使ってください。

アクション

Catalystのコントローラはアクションの組み合わせによって定義されます。アクションとは特別な属性を持ったsubのことです。このドキュメントの中でもすでに何度か例が登場しました。URL(たとえばhttp://localhost.3000/foo/bar)は、ベース(この例の場合はhttp://localhost:3000/)とパス(foo/bar)という2つの部分からなります。ホスト名[:ポート]の後のスラッシュは、アクションではなく、かならずベースに属するということに注意してください。

Catalystは何通りかのアクションをサポートします。

注意:こういった例を見ると、正規表現やパスのアクションで名前を定義する目的は何なのだろうかと不思議に思われるかもしれません。実は、パブリック・アクションはいずれもプライベート・アクションでもあります。だから、forwardを使えば統一的な方法でコンポーネントの位置を指定することができます。

ビルトイン・プライベート・アクション

Catalystはアプリケーションが特定の状態になると自動的にアプリケーション・クラスの中でこれらのビルトイン・プライベート・アクションを呼び出します。

コントローラのビルトイン・アクション/オートチェーニング

    Package MyApp::Controller::Foo;
    sub begin : Private { }
    sub default : Private { }
    sub auto : Private { }

ビルトイン・プライベート・アクションはコントローラの中で定義することもできます。コントローラの中で定義したアクションは、より階層が上のコントローラ、あるいはアプリケーション・クラスの中で定義されたアクションをオーバーライドします。つまり、3つのビルトイン・プライベート・アクションはそれぞれ一度のリクエスト・サイクルの中では1つずつしか実行されないということです。だから、MyApp::Controller::Catalog::beginが存在していたら、catalogという名前空間の中ではそれがMyApp::beginのかわりに実行されますし、MyApp::Controller::Catalog::Order::beginというのが存在していたら、今度はこれがMyApp::C::Catalog::beginをオーバーライドします。

通常のビルトイン・アクションの他に、autoというアクションをチェーンさせるための特殊なアクションがあります。このautoが指定されたアクションは、あらゆるbeginの後、通常のアクション処理の前に実行されます。他のビルトインと違って、autoのアクションはお互いのオーバーライドをしません。アプリケーション・クラスから、最も階層の深いクラスまで順に呼び出されます。つまり、通常のビルトインがお互いのオーバーライドするときとは順序が逆になります。

さまざまなビルトインが呼ばれる順序の例をあげます。

/foo/fooへのリクエストの場合
  MyApp::begin
  MyApp::auto
  MyApp::Controller::Foo::default # MyApp::Controller::Foo::Fooがない場合
  MyApp::end
/foo/bar/fooへのリクエストの場合
  MyApp::Controller::Foo::Bar::begin
  MyApp::auto
  MyApp::Controller::Foo::auto
  MyApp::Controller::Foo::Bar::auto
  MyApp::Controller::Foo::Bar::default # MyApp::Controller::Foo::Bar::fooのかわり
  MyApp::Controller::Foo::Bar::end

autoアクションは0を返すとチェーン処理を抜けることができるという点でも特徴的です。autoアクションが0を返した場合、end以外の残りのアクションはすべてスキップされます。だから、上のリクエストの場合、最初のautoが偽を返したら、チェーンは以下のようになります。

/foo/bar/fooへのリクエストで、最初のautoが偽を返した場合
  MyApp::Controller::Foo::Bar::begin
  MyApp::auto
  MyApp::Controller::Foo::Bar::end

このような使い方が必要になりそうな例としては認証アクションがあります。アプリケーション・クラスに認証を処理するautoアクションをセットアップして(これは常に最初に呼ばれます)、認証が失敗したときには0を返すようにすると、そのURLに対する残りのすべてのメソッドをスキップできます。

注意:別の見方をすると、処理を続けたければautoアクションはかならず真の値を返さなくてはならないということです! オートチェーン・アクションでdieすることもできます。その場合、リクエストはそれ以上アクションを処理することなく、一直線に最終段階まで移行します。

URLパスの扱い

各種の引数はURLパスの一部に含めて渡すことができます。その場合は「^」と「$」をアンカーとした正規表現アクション・キーを使う必要があります。また、URL中の引数はスラッシュ(/)で区切る必要があります。たとえば、/foo/$bar/$bazというURLを処理するときのことを考えてみましょう。ここで$bar$bazの値は変わりうるものとします。

    sub foo : Regex('^foo$') { my ($self, $context, $bar, $baz) = @_; }

では、/foo/boo/foo/boo/hooのアクションも定義したらどうなるでしょう?

    sub boo : Path('foo/boo') { .. }
    sub hoo : Path('foo/boo/hoo') { .. }

Catalystは最も階層の深いものから最も階層の浅いものへという順番でアクションのマッチをとります。

    /foo/boo/hoo
    /foo/boo
    /foo # /foo/bar/bazにはなるかもしれませんが、/foo/boo/hooにはなりません

だから、Catalystが最初の2つのURLを間違って「^foo$」のアクションにディスパッチすることはありえないのです。

パラメータ処理

URLのクエリ・ストリングで渡されたパラメータはCatalyst::Requestクラスのメソッドで処理されます。このクラスのparamメソッドは機能的にCGI.pmparamメソッドと同等ですので、CGI.pmparamメソッドを要求するモジュールでこのクラスを使うこともできます。

    # http://localhost:3000/catalog/view/?category=hardware&page=3
    my $category = $c->req->param('category');
    my $current_page = $c->req->param('page') || 1;

    # ひとつのパラメータ名で複数の値を扱うこともできます
    my @values = $c->req->param('scrolling_list');          

    # DFVはCGI.pmのような入力ハッシュを要求します。
    my $results = Data::FormValidator->check($c->req->params, \%dfv_profile);

フロー管理

アプリケーションのフローはforwardメソッドで管理します。このメソッドは実行するアクションのキーを受け取ります。このアクションは、同じCatalystコントローラのものでも、別のCatalystコントローラのものでも、オプションとしてメソッド名が続くかもしれないクラス名でも結構です。forwardが終わると、管理フローはforwardを発行したメソッドに戻ります。

forwardはメソッド呼び出しと似ていますが、大きく違うのは、例外処理ができるよう、呼び出しをevalでくるんでいることです。forwardは自動的にコンテキスト・オブジェクト($cないし$context)を渡します。また、それぞれの呼び出しのプロファイリングも可能にします(デバッグを有効にするとログに表示されます)。

    sub hello : Global {
        my ( $self, $c ) = @_;
        $c->stash->{message} = 'Hello World!';
        $c->forward('check_message'); # $cは自動的にインクルードされます
    }

    sub check_message : Private {
        my ( $self, $c ) = @_;
        return unless $c->stash->{message};
        $c->forward('show_message');
    }

    sub show_message : Private {
        my ( $self, $c ) = @_;
        $c->res->body( $c->stash->{message} );
    }

forwardは新しいリクエストを作らないため、リクエスト・オブジェクト($c->req)の内容も変わりません。これがforwardを使うのとリダイレクトを発行するのとの主要な差です。

forwardに新しい引数を渡すこともできます。その引数を無名配列の中に追加してください。この場合、$c->req->argsの値はforwardしている最中のみ変更されます。forwardから返ると$c->req->argsの値も元の値にリセットされます。

    sub hello : Global {
        my ( $self, $c ) = @_;
        $c->stash->{message} = 'Hello World!';
        $c->forward('check_message',[qw/test1/]);
        # ここで$c->req->argsの値は元に戻ります
    }

    sub check_message : Private {
        my ( $self, $c ) = @_;
        my $first_argument = $c->req->args[0]; # ここでの値は 'test1' です
        # 何らかの処理をします…
    }

これらの例から分かるように、同じコントローラのメソッドを参照する分にはメソッド名のみの指定でも大丈夫です。別のコントローラ、あるいはメイン・アプリケーションのメソッドにforwardしたいときは絶対パスでメソッドを参照する必要があります。

  $c->forward('/my/controller/action');
  $c->forward('/default'); # メイン・アプリケーションのdefaultが呼ばれます

クラスやメソッドにforwardする例をいくつか挙げます。

    sub hello : Global {
        my ( $self, $c ) = @_;
        $c->forward(qw/MyApp::Model::Hello say_hello/);
    }

    sub bye : Global {
        my ( $self, $c ) = @_;
        $c->forward('MyApp::Model::Hello'); # メソッドがない場合。「process」を試行します
    }

    package MyApp::Model::Hello;

    sub say_hello {
        my ( $self, $c ) = @_;
        $c->res->body('Hello World!');
    }

    sub process {
        my ( $self, $c ) = @_;
        $c->res->body('Goodbye World!');
    }

forwardはアクションが終了すると呼び出し元のアクションに戻って処理を続行することに注意してください。呼び出し元のアクションに一切それ以上の処理をさせたくない場合は、かわりにdetachを使います。こちらはdetachされたアクションを実行したあと、呼び出し元のsubに戻りません。いずれにしても、メソッドを省略した場合、Catalystは自動的にprocess()を呼び出そうとします。

コンポーネント

Catalystはけた外れに柔軟なコンポーネント・システムを持っています。モデルビューコントローラをいくらでも定義できるのです。

すべてのコンポーネントはCatalyst::Baseを継承しなければなりません。これはシンプルなクラス構造と、confignew(コンストラクタ)といった一般的なクラス・メソッドをいくつか提供します。

    package MyApp::Controller::Catalog;

    use strict;
    use base 'Catalyst::Base';

    __PACKAGE__->config( foo => 'bar' );

    1;

useなどを使ってモデルやビュー、コントローラを登録する必要はありません。メイン・アプリケーションでsetupを呼べば、Catalystが自動的にそれらを検出してインスタンス化します。しなければならないのは、それぞれのコンポーネント種別にあわせて命名されたディレクトリに配置することだけです。また、これらについてはそれぞれ非常に簡潔な別名を利用することもできます。

ビュー

ビューの定義の仕方を紹介するために、既存のTemplate Toolkit用ベース・クラス、Catalyst::View::TTを利用します。しなければならないのはこのクラスから継承することだけです。

    package MyApp::View::TT;

    use strict;
    use base 'Catalyst::View::TT';

    1;

(ヘルパー・スクリプトを利用して自動生成することもできます。

    script/myapp_create.pl view TT TT

最初のTTはビューの名前がTTであることをスクリプトに教えます。次のTTはそれがTemplate Toolkitのビューであることを示すものです。)

これでprocess()メソッドができました。あとはただ$c->forward('MyApp::View::TT')すればテンプレートをレンダリングできます。ベース・クラスは暗黙の了解としてprocess()しますので、$c->forward(qw/MyApp::View::TT process/)と書く必要はありません。

    sub hello : Global {
        my ( $self, $c ) = @_;
        $c->stash->{template} = 'hello.tt';
    }

    sub end : Private {
        my ( $self, $c ) = @_;
        $c->forward('MyApp::View::TT');
    }

テンプレートのレンダリングは通常リクエストの最後に行いますので、グローバルのendアクションにうってつけです。

なお、テンプレートはかならず$c->config->{root}で指定したディレクトリの下に入れてください。さもないと、あの見るも楽しいデバッグ画面を見せられることになりますよ(^_^)

モデル

モデルの定義法も既存のベース・クラスを使って紹介しましょう。今度はClass::DBIに対応するCatalyst::Model::CDBIを使います。

まずはデータベースが必要です。

    -- myapp.sql
    CREATE TABLE foo (
        id INTEGER PRIMARY KEY,
        data TEXT
    );

    CREATE TABLE bar (
        id INTEGER PRIMARY KEY,
        foo INTEGER REFERENCES foo,
        data TEXT
    );

    INSERT INTO foo (data) VALUES ('TEST!');


    % sqlite /tmp/myapp.db < myapp.sql

これでこのデータベースに対するCDBIコンポーネントを作成できます。

    package MyApp::Model::CDBI;

    use strict;
    use base 'Catalyst::Model::CDBI';

    __PACKAGE__->config(
        dsn           => 'dbi:SQLite:/tmp/myapp.db',
        relationships => 1
    );

    1;

Catalystはテーブルのレイアウトや依存関係を自動的にロードします。テンプレートにデータを渡すときにはスタッシュを使ってください。

    package MyApp;

    use strict;
    use Catalyst '-Debug';

    __PACKAGE__->config(
        name => 'My Application',
        root => '/home/joeuser/myapp/root'
    );
    
    __PACKAGE__->setup;

    sub end : Private {
        my ( $self, $c ) = @_;
        $c->stash->{template} ||= 'index.tt';
        $c->forward('MyApp::View::TT');
    }

    sub view : Global {
        my ( $self, $c, $id ) = @_;
        $c->stash->{item} = MyApp::Model::CDBI::Foo->retrieve($id);
    }

    1;

    # それから、TTのテンプレートにこう書きます。
    idは[% item.data %]です

モデルはCatalystアプリケーションの一部である必要はありません。いつでも外部のモジュールをモデルとして呼び出すことができます。

    # コントローラで
    sub list : Local {
      my ( $self, $c ) = @_;
      $c->stash->{template} = 'list.tt';
      use Some::Outside::CDBI::Module;
      my @records = Some::Outside::CDBI::Module->retrieve_all;
      $c->stash->{records} = \@records;
    }

ただし、Catalystアプリケーションに含まれているモデルを使うといくつかの利点があります。それぞれのコンポーネントをuseする必要はありません。Catalystがコンパイル時に自動的に検索してロードします。モジュールにforwardすることもできます。これはCatalystコンポーネントにのみ可能です。また、$c->model('SomeModel')で引っ張ってこれるのもCatalystコンポーネントのみです。

幸い、多くの人々がCatalystで既存のモデル・クラスを使いたがるため(あるいは、逆にCatalystの外部で――たとえばcronのジョブとして――も使えるCatalystモデルを書きたがるため)、外部のモデルでも動くシンプルなCatalystコンポーネントも簡単に書けるようになっています。

    package MyApp::Model::Catalog;
    use base qw/Catalyst::Base Some::Other::CDBI::Module::Catalog/;
    1;

はい、できた、と! これでSome::Other::CDBI::Module::CatalogMyApp::Model::CatalogとしてCatalystアプリケーションの一部になるのです。

コントローラ

アプリケーションの論理領域を分けるにはコントローラを複数使うのが吉です。

    package MyApp::Controller::Login;

    sub sign-in : Local { }
    sub new-password : Local { }
    sub sign-out : Local { }

    package MyApp::Controller::Catalog;

    sub view : Local { }
    sub list : Local { }

    package MyApp::Controller::Cart;

    sub add : Local { }
    sub update : Local { }
    sub order : Local { }

テスティング

Catalystにはテスト用にビルトインのhttpサーバがついています!(テストを終えてプロダクション環境に移行するときにApache/mod_perlなどもっと強力なサーバに切り替えることも簡単にできます)

コマンド・ラインからアプリケーションを起動します。

    script/myapp_server.pl

そして、ブラウザでhttp://localhost:3000/を開いて出力を表示させてみてください。

すべてをコマンド・ライン上で実行してしまうこともできます。

    script/myapp_test.pl http://localhost/

お楽しみあれ!

サポート

IRC:

    Join #catalyst on irc.perl.org.

メーリングリスト:

    http://lists.rawmode.org/mailman/listinfo/catalyst
    http://lists.rawmode.org/mailman/listinfo/catalyst-dev

著者

Sebastian Riedel, sri@oook.de David Naughton, naughton@umn.edu Marcus Ramberg, mramberg@cpan.org Jesse Sheidlower, jester@panix.com Danijel Milicevic, me@danijel.de

翻訳者

石垣憲一(ishigaki_at_tcool.org) http://www.tcool.org/

著作権表示

This program is free software, you can redistribute it and/or modify it under the same terms as Perl itself.