Catalyst::Manual::Cookbook
Catalyst::Manual::Cookbook - Cooking with Catalyst - metacpan.org
Basics
Catalystを使う人が知っておいた方がいいこと。
Delivering a Custom Error Page
アプリケーションでエラーが発生したときはCatalystは独自のエラーページを表示する。-Debugモードのときはエラーメッセージとコンテキストオブジェクト($c)のData::Dumpの出力を表示する。-Debugモードじゃないときは"Please come back later"が表示される。
エラーページを変更するにはendメソッドにエラー処理を書けばいい。例を示す。
sub end : Private { my ( $self, $c ) = @_; if ( scalar @{ $c->error } ) { $c->stash->{errors} = $c->error; $c->stash->{template} = 'errors.tt'; $c->forward('MyApp::View::TT'); $c->error(0); } return 1 if $c->response->status =~ /^3?d?d$/; return 1 if $c->response->body; unless ( $c->response->content_type ) { $c->response->content_type('text/html; charset=utf-8'); } $c->forward('MyApp::View::TT'); }
手動でエラーをセットしてこのページを表示できる。
$c->error( 'You broke me!' );
Disable statistics
Just add this line to your application class if you don't want those nifty statistics in your debug messages.
sub Catalyst::Log::info { }
これはよくわからなかった。アプリケーションクラスにこれ追加してみたけど再定義してるって怒られた上気の利いたデバッグページは相変わらず表示された。
Enable debug status in the environment
ふつうは-Debugフラグをuser Catalystに追加してデバッグ情報を有効にする。けど、環境変数を使ってもできるのでアプリケーションを変更しなくてもデバッグ情報を有効にできる。CATALYST_DEBUGか
Sessions
Catalystにはセッションを使うのに2つのプラグインを使う。
- State
Stateモジュールはブラウザとアプリケーションの間で状態を追跡するのに使用する。
- Store
StoreモジュールはユーザーIDやショッピングカートの商品など、セッションに関連するデータを保存するのに使用する。データはメモリ(FastMmap)、ファイル、データベースに格納可能。
- Authentication magic
アプリケーションでセッションモジュールを使用していると、Authenticationモジュールはセッションを使ってユーザー情報の出し入れをするようになってる。
- Using a session
セッションモジュールをロードすると$c->sessionを使ってデータを書いたり読んだりできる。データはハッシュのリファレンス。
- EXAMPLE
use Catalyst qw/ Session Session::Store::FastMmap Session::State::Cookie /; ## Write data into the session sub add_item : Local { my ( $self, $c ) = @_; my $item_id = $c->req->param("item"); push @{ $c->session->{items} }, $item_id; } ## A page later we retrieve the data from the session: sub get_items : Local { my ( $self, $c ) = @_; $c->stash->{items_to_display} = $c->session->{items}; }
- More information
http://search.cpan.org/dist/Catalyst-Plugin-Session
http://search.cpan.org/dist/Catalyst-Plugin-Session-State-Cookie
http://search.cpan.org/dist/Catalyst-Plugin-Session-State-URI
http://search.cpan.org/dist/Catalyst-Plugin-Session-Store-FastMmap
http://search.cpan.org/dist/Catalyst-Plugin-Session-Store-File
http://search.cpan.org/dist/Catalyst-Plugin-Session-Store-DBI
Configure your application
アプリケーションの設定はアプリケーションクラスのconfigメソッドで行う。
- Using YAML
YAMLを使って設定を書ける。
アプリケーションクラスで(例えばlib/MyApp.pm)。
use YAML; # application setup __PACKAGE__->config( YAML::LoadFile(__PACKAGE__->config->{'home'} . '/myapp.yml') ); __PACKAGE__->setup;
アプリケーションのhomeにmyapp.ymlを作成する。
--- #YAML:1.0 # DO NOT USE TABS FOR INDENTATION OR label/value SEPARATION!!! name: MyApp # session; perldoc Catalyst::Plugin::Session::FastMmap session: expires: '3600' rewrite: '0' storage: '/tmp/myapp.session' # emails; perldoc Catalyst::Plugin::Email # this passes options as an array :( email: - SMTP - localhost
これは以下と等価。
(YAMLにはauthenticationの設定がないから違う気がするけど・・・)
# configure base package __PACKAGE__->config( name => MyApp ); # configure authentication __PACKAGE__->config->{authentication} = { user_class => 'MyApp::Model::MyDB::Customer', ... }; # configure sessions __PACKAGE__->config->{session} = { expires => 3600, ... }; # configure email sending __PACKAGE__->config->{email} = [qw/SMTP localhost/];
Skipping your VCS's directories
Catalystはモデル、ビュー、コントローラをロードするのにModule::Pluggableを使う。Module::Pluggableは全ディレクトリを調べて見つかったモジュールをロードする。それを回避したい場合、たとえばバージョンコントロールシステムが自動で作成するディレクトリとか。CatalystはsubversionとCVSのディレクトリはすでにスキップするようになってるけど、他のソースコントロールシステムもあるので。スキップさせるためにはconfigを使う。
# Configure the application __PACKAGE__->config( name => 'MyApp', setup_components => { except => qr/SCCS/ }, );
Users and Access Control
ユーザー認証とロール定義がしたい場合のレシピ。
Authentication (logging in)
これは他のドキュメントを参照。
http://search.cpan.org/perldoc?Catalyst%3A%3APlugin%3A%3AAuthentication
http://search.cpan.org/~jrockway/Catalyst-Manual-5.700701/lib/Catalyst/Manual/Tutorial/Authorization.pod
Pass-through login (and other actions)
ログインをスルーしたい場合ってことかな。
__loginみたいなパラメータがある場合にアクションする場合?
sub begin : Private { my ($self, $c) = @_; foreach my $action (qw/login docommand foo bar whatever/) { if ($c->req->params->{"__${action}"}) { $c->forward($action); } } }
Role-based Authorization
ロールベースの権限分けをしたい場合。
login/logoutメソッドとテンプレートは前に出た例と同じ(Authenticationと同じってことかな)。
ロールを実装するにはCatalyst::Plugin::Authorization::Rolesプラグインが必要。
use Catalyst qw/ Authentication Authentication::Credential::Password Authentication::Store::Htpasswd Authorization::Roles /;
ロールはCatalyst::Authentication::Store::Htpasswdを使うと自動的に実装される。
# no additional role configuration required __PACKAGE__->config->{authentication}{htpasswd} = "passwdfile";
またはCatalyst::Authentication::Store::DBICを使って手動でやる。
# Authorization using a many-to-many role relationship __PACKAGE__->config->{authorization}{dbic} = { 'role_class' => 'My::Model::DBIC::Role', 'role_field' => 'name', 'user_role_user_field' => 'user', # DBIx::Class only (omit if using Class::DBI) 'role_rel' => 'user_role', # Class::DBI only, (omit if using DBIx::Class) 'user_role_class' => 'My::Model::CDBI::UserRole' 'user_role_role_field' => 'role', };
アクセス制限するためにはcheck_user_roleメソッドを使用する。
sub restricted : Local { my ( $self, $c ) = @_; $c->detach("unauthorized") unless $c->check_user_roles( "admin" ); # do something restricted here }
assert_user_rolesメソッドも使用可能。このメソッドはユーザーがロールを持ってなかった場合にエラーにする。
sub also_restricted : Global { my ( $self, $c ) = @_; $c->assert_user_roles( qw/ user admin / ); }
Authentication/Authorization
いくつかのステップから構成される。
- Verification
ユーザーとアプリケーションだけが知っている情報を使ってユーザーを識別する。これはcredential verificationと呼ばれる。
- Authorization
アクセスさせたいものだけにアクセスできるようにする。これはユーザーが内部で管理しているグループに属しているかや、あるページに対してユーザーを許可することで行う。
- Modules
最大限の柔軟性を得るために、Catalystの認証システムは多数のモジュールが関連し合ってなりなっている。
- Credential verifiers
Credentialモジュールはユーザー入力を受け取ってStoreまたは確認のために他のシステムに渡す。たいていはこのモジュールかStoreによってユーザーオブジェクトが作られ、$c->userでアクセス可能になる。
例。
Password - Simple username/password checking.
HTTPD - Checks using basic HTTP auth.
TypeKey - Check using the typekey system.
- Storage backends
Storageバックエンドはユーザー情報を持ってる。ユーザー情報はcredential verifiersから問い合わせを受ける。情報の更新はこのシステムで行わないので、自分でやる必要がある。
例。
DBIC - Storage using a database.
Minimal - Storage using a simple hash (for testing).
- User objects
ユーザーオブジェクトはstorage backendかcredential verifierによって作成され、ユーザー情報が設定される。
例。
Hash - A simple hash of keys and values.
- ACL authorization
ACLはAccess Control Listのこと。ACLプラグインはベースパスとか許可ユーザーとかロールとかを使ってパスへのアクセス制限をできるようにする。
- Roles authorization
ロールによる権限分けでは、ACLに割り当てられたり必要に応じてチェックされるグループにユーザーを所属させる。
- Logging in
モジュールを選んだら、あとやることは$c->loginをコールするだけ。パラメータが省略されたらそれっぽいパラメータ(username, passwordなど)を探そうとする。
- Checking roles
ロールのチェックは$c->check_user_rolesで行う。チェックはログイン済みユーザーに対して行う。チェックするロール名を渡して、ユーザーがロールのメンバーだったらtrueを返す。
- EXAMPLE
use Catalyst qw/Authentication Authentication::Credential::Password Authentication::Store::Htpasswd Authorization::Roles/; __PACKAGE__->config->{authentication}{htpasswd} = "passwdfile"; sub login : Local { my ($self, $c) = @_; if ( my $user = $c->req->param("user") and my $password = $c->req->param("password") ) { if ( $c->login( $user, $password ) ) { $c->res->body( "hello " . $c->user->name ); } else { # login incorrect } } else { # invalid form input } } sub restricted : Local { my ( $self, $c ) = @_; $c->detach("unauthorized") unless $c->check_user_roles( "admin" ); # do something restricted here }
- Using authentication in a testing environment
理想的には、authentication/authorizationのテストを書いて、テスト用のデータベースをセットアップして、Test::WWW::Mechanize::Catalystでログインをシミュレートするべきなんだろうけど、認証フレームワークは柔軟さが重要だからこの方法はちょっとやりにくい。
テスト用のデータベースを使用する代わりに、テスト環境で扱いやすい保存先を使用することができる。この方法だとテスト用のデータベースを変更する必要がないっていう利点もある。
例。
use Catalyst::Plugin::Authentication::Store::Minimal::Backend; # Sets up the user `test_user' with password `test_pass' MyApp->default_auth_store( Catalyst::Plugin::Authentication::Store::Minimal::Backend->new({ test_user => { password => 'test_pass' }, }) );
これで、テストコードでは$c->login('test_user', 'test_pass')でログインできる。データベースは変更なしで。
- More information
http://search.cpan.org/perldoc?Catalyst::Plugin::Authentication
にもっと長い説明がある。
Authorization
- Introduction
権限分け(authorization)は認証(authentication)のあとでやる。認証はユーザーの存在確認を行い、権限分けはユーザーができることを決める。
- Role Based Access Control
ロールベースのアクセスコントロールではそれぞれのユーザーにロールを与えることで権限を与える。例えば、動物園では飼育係だけがおりの中に入れる。
例。
package Zoo::Controller::MooseCage; sub feed_moose : Local { my ( $self, $c ) = @_; $c->model( "Moose" )->eat( $c->req->param("food") ); }
このアクションだと誰でも檻に入ってえさをあげれる。すげー危険。だからこのアクションを制限する必要がある。
Authentication::Rolesプラグインを使う。
use Catalyst qw/ Authentication # yadda yadda Authorization::Roles /;
アクションはこうなる。
sub feed_moose : Local { my ( $self, $c ) = @_; if ( $c->check_roles( "moose_feeder" ) ) { $c->model( "Moose" )->eat( $c->req->param("food") ); } else { $c->stash->{error} = "unauthorized"; } }
$c->userをチェックして許可されてればtrueが返る。
check_rolesはassert_rolesって仲間がいて、これだとロールがないときに例外を投げる。
- Access Control Lists
ロールをチェックするのは退屈だし、エラーの原因になる。
Authorization::ACLプラグインは自動的にチェックできるようにする。
例えば、moose_feeder以外はMooseCageコントローラにアクセスできないようにする場合。
Zoo->deny_access_unless( "/moose_cage", [qw/moose_feeder/] );
Rolseプラグインを使ってこうやることも可能。
Zoo->deny_access_unless( "/moose_cage", sub { my $c = shift; $c->check_roles( "moose_trainer" ) || $c->check_roles( "moose_feeder" ); });
moose feedersはfeed_mooseだけできて、moose trainersは何でも出来る場合。
Zoo->deny_access_unless( "/moose_cage", [qw/moose_trainer/] ); Zoo->allow_access_if( "/moose_cage/feed_moose", [qw/moose_feeder/]);
同じパスに定義されたロールは追加された順番にチェックされる。
最後に、否定形のアクションをハンドリングするにはaccess_deniedアクションを作成する。
sub access_denied : Private { my ( $self, $c, $action ) = @_; }
このアクションはautoみたいに働く。ネームスペースにまたがって継承される。
このアクションが存在しない場合はエラーが投げられ、代わりにendアクションで後始末可能。
また、"/"へのアクセスを制限するとend, defaultなども制限されるってことに注意。
MyApp->acl_allow_root_internals;
は、root of your app(他のコントローラではなく)のend, begin, autoへのアクセスを許可するルールを作成する。
Models
モデルはアプリケーションのデータが属する場所。Catalystはどんなモデルを使うか自由。
Using existing DBIC (etc.) classes with Catalyst
たいていの場合、使いたいモデルはCatalystに入ってると思う。コードは単純。
package MyApp::Model::DB; use base qw/Catalyst::Model::DBIC::Schema/; __PACKAGE__->config( schema_class => 'Some::DBIC::Schema', connect_info => ['dbi:SQLite:foo.db', '', '', {AutoCommit=>1}]; ); 1;
これだけ。
DBIx::Class as a Catalyst Model
http://search.cpan.org/perldoc?Catalyst%3A%3AModel%3A%3ADBIC%3A%3ASchema
を参照。
XMLRPC
XMLRPCはSOAPと違って単純なウェブサービスのプロトコルです。
リクエスト
POST /api HTTP/1.1 TE: deflate,gzip;q=0.3 Connection: TE, close Accept: text/xml Accept: multipart/* Host: 127.0.0.1:3000 User-Agent: SOAP::Lite/Perl/0.60 Content-Length: 192 Content-Type: text/xml <?xml version="1.0" encoding="UTF-8"?> <methodCall> <methodName>add</methodName> <params> <param><value><int>1</int></value></param> <param><value><int>2</int></value></param> </params> </methodCall>
レスポンス
Connection: close Date: Tue, 20 Dec 2005 07:45:55 GMT Content-Length: 133 Content-Type: text/xml Status: 200 X-Catalyst: 5.70 <?xml version="1.0" encoding="us-ascii"?> <methodResponse> <params> <param><value><int>3</int></value></param> </params> </methodResponse>
アプリケーションを実装するためのステップ。
1. Install Catalyst (5.61 or later), Catalyst::Plugin::XMLRPC (0.06 or later) and SOAP::Lite (for XMLRPCsh.pl).
2. Create an application framework:
% catalyst.pl MyApp ... % cd MyApp
3. Add the XMLRPC plugin to MyApp.pm
use Catalyst qw/-Debug Static::Simple XMLRPC/;
4. Add an API controller
% ./script/myapp_create.pl controller API
5. Add a XMLRPC redispatch method and an add method with Remote attribute to lib/MyApp/Controller/API.pm
sub default : Private { my ( $self, $c ) = @_; $c->xmlrpc; } sub add : Remote { my ( $self, $c, $a, $b ) = @_; return $a + $b; }
defaultアクションはXMLRPCリクエストのエントリポイントになる。そこでは同じクラスのRemote属性の付いたメソッドにディスパッチし直す。
addメソッドは通常のアクションと違ってprivateやpublicパスを持たない。XMLRPCディスパッチャだけがその存在を知っている。
6. That's it! You have built your first web service. Let's test it with XMLRPCsh.pl (part of SOAP::Lite):
% ./script/myapp_server.pl ... % XMLRPCsh.pl http://127.0.0.1:3000/api Usage: method[(parameters)] > add( 1, 2 ) --- XMLRPC RESULT --- '3'
すげーなー。
- Tip
戻り値のデータ型は自動判別されるけど、強制的に指定することも可能。
sub add : Remote { my ( $self, $c, $a, $b ) = @_; return RPC::XML::int->new( $a + $b ); }
Views
Viewはアプリケーションの表示を担う。
Catalyst::View::TT
Catalystはデータをどう表示するかには感知しない。HTML, PDFなどなんでもよい。
ほとんどの場合、HTMLを表示するのにテンプレートシステムを使用する。いろんなテンプレートシステムがあるけど、Template Toolkitが一番人気がある。
Catalyst::View::TTがTemplate Tookitへのインターフェースでヘルパーがいろいろやってくれる。
- Creating your View
Catalyst::View::TTはTTとTTSiteの二つのヘルパーを提供している。
-
- TT
ヘルパースクリプトで基本的なビューを作成する。
script/myapp_create.pl view TT TT
これはlib/MyApp/View/MyView.pmを作成する。最初は空っぽだけど、始めるのに必要なものはすべて提供してくれる。どのテンプレートを使用するか決めてビューにフォワードする。例えば、
sub hello : Local { my ( $self, $c ) = @_; $c->stash->{template} = 'hello.tt'; $c->forward( $c->view('TT') ); }
手動でやってもいいけどCatalyst::Action::RenderViewを使った方がいい。
-
- TTSite
TTヘルパーは十分動作するものを作ってくれるけど、新しいアプリケーションを作るたびに同じようなテンプレートファイルを作ったり、同じようなオプションを変更していることに気がつくだろう。TTSiteは基本的なテンプレートを作り、オプションを設定して時間節約に役立つ。
もう一度ヘルパースクリプトを使おう。
script/myapp_create.pl view TT TTSite
今度は、ヘルパーがいくつかのオプションをセットしてくれる。
__PACKAGE__->config({ CATALYST_VAR => 'Catalyst', INCLUDE_PATH => [ MyApp->path_to( 'root', 'src' ), MyApp->path_to( 'root', 'lib' ) ], PRE_PROCESS => 'config/main', WRAPPER => 'site/wrapper', ERROR => 'error.tt2', TIMER => 0 });
INCLUDE_PATHはテンプレートファイルを探すディレクトリを指定する。
PRE_PROCESSはすべてのテンプレートファイルで共通の設定を実行するのに使う。
WRAPPERは各テンプレートで使用するファイルだ。ヘッダーとかフッターに使う。
これらのオプションに加えて、TTSiteはテンプレートと設定ファイルも作ってくれる。'root'ディレクトリにsrcとlibってディレクトリができてるだろう。
root/lib/configにある設定ファイルはPRE_PROCESSで呼ばれる。
root/lib/siteのファイルはWRAPPERで呼ばれ、HTMLの枠組みを表示してレイアウトを決め、ヘッダーやフッターを提供する。ページを標準化して変更を容易にする。
あなたが作るテンプレートファイルはroot/srcに置き、や
- $c->stash
テンプレートで使用するデータはstashに入れる。例えば、
コントローラで、
sub hello : Local { my ( $self, $c ) = @_; $c->stash->{name} = 'Adam'; $c->stash->{template} = 'hello.tt'; $c->forward( $c->view('TT') ); }
hello.ttで、
<strong>Hello, [% name %]!</strong>
これは簡単な例だけどTemplate Toolkitはもっと強力だ。これでプレゼンテーションロジックをアプリケーションから分離できる。
- $c->uri_for()
これを使えばアプリケーションの配置パスを変えた時にテンプレートで困ることがない。
テンプレートでこんな風にできる。
<a href="[% c.uri_for('/login') %]">Login Here</a>
パラメータはスラッシュで始まってるけど、これはWebサーバーのルートじゃなくてアプリケーションのルートを示す。
または、
<a href="[% c.uri_for('2005','10', '24') %]">October, 24 2005</a>
これは最初のパラメータがスラッシュで始まってないので、現在のネームスペースからの相対になる。アプリケーションがhttp://www.domain.com/Calendarにあり、テンプレートがMyApp::Controller::Displayから呼ばれたのなら、このリンクはhttp://www.domain.com/Calendar/Display/2005/10/24になる。
uri_forがリンクを生成してくれるおかげで、異なるコントトーラから共通のテンプレートが使用できる。サイトに共通の要素がある場合に役立つ。
以下も参考に。
http://search.cpan.org/perldoc?Catalyst
http://search.cpan.org/perldoc?Catalyst%3A%3AView%3A%3ATT
http://search.cpan.org/perldoc?Template
Adding RSS feeds
アプリケーションにRSSフィードを追加するのは簡単だ。ここでは二通りのやり方を示す。両方とも最初にオブジェクトを取得するために普通のViewアクションにフォワードして出力を違うやり方で扱う。
- Using TT templates
Agave (http://dev.rawmode.org/)のやりかた。
sub rss : Local { my ($self,$c) = @_; $c->forward('view'); $c->stash->{template}='rss.tt'; }
で、テンプレートを用意する。ここではAgaveから持ってくる。
<?xml version="1.0" encoding="UTF-8"?> <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"> <channel> <title>[ [% blog.name || c.config.name || "Agave" %] ] RSS Feed</title> <link>[% base %]</link> <description>Recent posts</description> <language>en-us</language> <ttl>40</ttl> [% WHILE (post = posts.next) %] <item> <title>[% post.title %]</title> <description>[% post.formatted_teaser|html%]</description> <pubDate>[% post.pub_date %]</pubDate> <guid>[% post.full_uri %]</guid> <link>[% post.full_uri %]</link> <dc:creator>[% post.author.screenname %]</dc:creator> </item> [% END %] </channel> </rss>
- Using XML::Feed
もっとrobustなやり方はXML::Feedを使う。これはCatalyst Advent Calendarで使ってる。DBIx::Classのイテレータを使って'entries'を作るviewアクションがあったとするとこうなる。
sub rss : Local { my ($self,$c) = @_; $c->forward('view'); # get the entries my $feed = XML::Feed->new('RSS'); $feed->title( $c->config->{name} . ' RSS Feed' ); $feed->link( $c->req->base ); # link to the site. $feed->description('Catalyst advent calendar'); Some description # Process the entries while( my $entry = $c->stash->{entries}->next ) { my $feed_entry = XML::Feed::Entry->new('RSS'); $feed_entry->title($entry->title); $feed_entry->link( $c->uri_for($entry->link) ); $feed_entry->issued( DateTime->from_epoch(epoch => $entry->created) ); $feed->add_entry($feed_entry); } $c->res->body( $feed->as_xml ); <<| 両方ともコンテントタイプをセットする。 >|| $c->res->content_type('application/rss+xml');
- Final words
同じやり方でAtomも作れる。
Forcing the browser to download content
ダウンロード用のコンテンツを作りたいとき。たとえばCVSを生成してユーザーにダウンロードさせ、スプレッドシートプログラムにインポートするような場合。
sub export : Local Args(0) { my ( $self, $c ) = @_; # In a real application, you'd generate this from the database my $csv = "1,5.99?n2,29.99?n3,3.99?n"; $c->res->content_type('text/comma-separated-values'); $c->res->body($csv); }
ブラウザはふつう、URLの最後のパートをファイル名に使う。この場合だとexportって名前でファイルを保存するか聞いてくるだろう。
Content-Dispositionヘッダを使うとファイル名を指定できる。
my $filename = 'Important Orders.csv'; $c->res->header('Content-Disposition', qq[attachment; filename="$filename"]);
ブラウザにファイル名中のスペースを認識させるためにクォートしている。
これを$c->res->bodyの前に置けばいい。
この方法はJPEGとかHTMLとかでも同じように使える。
Controllers
ControllerはWebサーバーとアプリケーションとやり取りする所。
Extending RenderView (formerly DefaultEnd)
endアクションで推奨する方法はCatalyst::Action::RenderViewを使うことで、たいていこれが必要だろう(ここはCatalyst::Plugin::DefaultEndが取り扱う場所だ)。コードをいくつか足す必要があるだろうけど、自分でendメソッドを作ってはいけない。
拡張するにはこうする。
レンダリングの前にendアクションでなにかするには単にコードを追加すればいい。
sub end : ActionClass('RenderView') { my ( $self, $c ) = @_; # do stuff here; the RenderView action is called afterwards }
レンダリングのあとでなにかするにはこうする。
sub render : ActionClass('RenderView') { } sub end : Private { my ( $self, $c ) = @_; $c->forward('render'); # do stuff here }
Action Types
- Introduction
Catalystアプリケーションは1つ以上のコントトーラモジュールを使って処理を進める。Catalystはコントローラのどのメソッドを呼ぶか決める方法をいくつか持ってる。コントローラのメソッドはアクションと呼ばれることがある。なぜなら、それらはURLに対して何をするかを決めるものだからだ。アプリケーションが起動すると、Catalystは全アクションを見てURLをどこにマップするか決定する。
- Type attributes
アクションは属性が指定される以外は普通のメソッドだ。
Contollerモジュールが以下のパッケージ定義で始まっていて、
package MyApp::Controller::Buckets;
localhostのポート3000で起動しているとする。
PATH
Path属性は相対か絶対パスを引数に取る。相対はコントローラの名前空間からのパス、絶対パスはURLにマッチする。
sub my_handles : Path('handles') { .. }
はこうなり、
http://localhost:3000/buckets/handles
sub my_handles : Path('/handles') { .. }
はこうなる。
http://localhost:3000/handles
Local
Local属性には引数がなく、アクション名がURLにマッチする。コントローラのパッケージ名が作り出す名前空間が常にURLの一部になる。
sub my_handler : Local { .. }
はこうなる。
http://localhost:3000/buckets/my_handles
Global
Global属性はLocalに似てるけどコントトーラの名前空間は無視され、ルートからマッチする。
sub my_handler : Global { .. }
はこうなる。
http://localhost:3000/my_handlers
Regex
Regex属性は正規表現を1つ取り、ルートからのパスにマッチする。そのため複数のURLにマッチさせることが可能。
sub my_handles : Regex('^handlers') { .. }
はこれにもマッチするし、
http://localhost:3000/handles
これにもマッチするし、
http://localhost:3000/handler_and_other_parts
他にもいろいろマッチする。
LocalRegex
LocalRegexはRegexに似てるけどコントトーラの名前空間でマッチする。
sub my_handles : LocalRegex('^handles') { .. }
はこれや、
http://localhost:3000/buckets/handles
これなんかにマッチする。
http://localhost:3000/buckets/handles_and_other_parts
Private
Privateはフォワードされる内部アクションを作成する。URLにはマッチしない。
sub my_handles : Private { .. }
は何もマッチしない。
Catalystは特別なPrivateアクションを定義している。これらはオーバーライド可能。
default
defaultアクションはマッチするアクションがなかったときに呼ばれる。ユーザーが以降としてたURLは$c->req->pathで取得できる。
sub default : Private { .. }
はマッチしないURLすべてに対して働く。
index
indexアクションはコントトーラの名前空間そのものにマッチする。もしdefaultやPathアクションが定義されていてもindexアクションが呼ばれる。
sub index : Private { .. }
はこれにマッチする。
http://localhost:3000/buckets
begin
beginアクションはこの名前空間へリクエストが来たとき、マッチしたアクションの前に呼ばれる。変数などのデータを用意するのに使用できる。現在の名前空間に最も近い所にあるbeginアクションが1つだけ呼ばれる。
sub begin : Private { .. }
はここに来たときに一度だけ呼ばれる。
http://localhost:3000/buckets/{anything})?
end
beginと同じようにこの名前空間へリクエストが来たとき、マッチしたアクションの後に呼ばれる。Viewにフォワードするのによく使われる。現在の名前空間に最も近い所にあるbeginアクションが1つだけ呼ばれる。
auto
autoアクションはパスをさかのぼってすべての名前空間で呼ばれる。begin/end/defaulが一回だけ呼ばれるのと対称的だ。
package MyApp.pm sub auto : Private { .. }
sub auto : Private { .. }
このURLに来たら二つとも呼ばれる。
http://localhost:3000/bucket/(anything)?
- A word of warning
Pluginでの名前空間の衝突の可能性があるので、MyApp.pmには事前定義されたPrivateアクションだけを置き、他のものはコントトーラモジュールに置いた方がいい。
- More Information
http://search.cpan.org/author/SRI/Catalyst-5.61/lib/Catalyst/Manual/Intro.pod
http://dev.catalyst.perl.org/wiki/FlowChart
Component-based Subrequests
See Catalyst::Plugin::SubRequest.
File uploads
- Single file upload with Catalyst
ファイルアップロードを実装するにはまずこんなHTMLを用意する。
<form action="/upload" method="post" enctype="multipart/form-data"> <input type="hidden" name="form_submit" value="yes"> <input type="file" name="my_file"> <input type="submit" value="Send"> </form>
enctype="multipart/form-data"を忘れずに!
Catalystのコントローラモジュールでuploadアクションを実装。
sub upload : Global { my ($self, $c) = @_; if ( $c->request->parameters->{form_submit} eq 'yes' ) { if ( my $upload = $c->request->upload('my_file') ) { my $filename = $upload->filename; my $target = "/tmp/upload/$filename"; unless ( $upload->link_to($target) || $upload->copy_to($target) ) { die( "Failed to copy '$filename' to '$target': $!" ); } } } $c->stash->{template} = 'file_upload.html'; }
- Multiple file upload with Catalyst
複数ファイルのアップロードの場合はちょっとだけ変更が必要。
<form action="/upload" method="post" enctype="multipart/form-data"> <input type="hidden" name="form_submit" value="yes"> <input type="file" name="file1" size="50"><br> <input type="file" name="file2" size="50"><br> <input type="file" name="file3" size="50"><br> <input type="submit" value="Send"> </form>
>|| sub upload : Local { my ($self, $c) = @_; if ( $c->request->parameters->{form_submit} eq 'yes' ) { for my $field ( $c->req->upload ) { my $upload = $c->req->upload($field); my $filename = $upload->filename; my $target = "/tmp/upload/$filename"; unless ( $upload->link_to($target) || $upload->copy_to($target) ) { die( "Failed to copy '$filename' to '$target': $!" ); } } } $c->stash->{template} = 'file_upload.html'; }
for my $field ($c->req->upload)のループは全部のファイル名を取得してくれる。あとは同じだ。
注意:dieするんじゃなくて、$c->stash->{error}に$!を入れてエラーテンプレートでメッセージを表示した方がいいだろう。
あとはCatalyst::Request::UploadとCatalyst::Requestを参照。