什么是 Catalyst ?
引用作者所说的,这是基于 Maypole,并以创新的方式增加了各种 Ruby-on-Rails,Struts,Spring,Tomcat 等框架或应用的特性。似乎挺激动人心的。
下载模块
Catalyst 有一系列的模块,已经发布在 CPAN 上。最见的办法就是安装 Bundle-Catalyst-0.01 模块。
perl -MCPAN -e "install Bundle::Catalyst"
先写个控制器
既然是 MVC 框架,我们先来看控制器:
# lib/MyApp.pm
package MyApp;
use strict;
use Catalyst '-Debug';
__PACKAGE__->config(
name => 'My Application',
root => '/home/joeuser/myapp/root'
);
__PACKAGE__->action( _default => sub {
my ( $self, $c ) = @_;
$c->response->output('Catalyst rockz!');
});
1;
这个名为 MyApp.pm 的模块文件就是我们的第一个控制器。name 和 root 是 Catalyst 本身仅需的两个选项参数。name 表示你的应用程序的名称;root 则表示你的应用程序所部署的根目录,所以其他的相关资源都在其中,包括模版文件等等。
代码很简单,在自己的模块中只需要 use Catalyst; 即可,后面的参数 -Debug 是调试开关。
第一件事情,就是要告诉 MVC 内核模块,我现在是哪一个应用程序,所有资源的根目录在何处。
接下来就是定义用户请求所对应的响应处理代码。用户是通过 URL 来请求的,其中表示行为的我们称为 action ,这种叫法可能借鉴自 Java 的 struts 框架。比如你说 http://localhost/article/show/2 表示要显示编号为 2 的文章,那么这里的 show 就是我们所说的 action 了。那如果是 http://localhost/article 呢?在静态页面的系统中,一般 web 服务器都返回 /article/index.html 文件。index.html 在这里是个默认资源。Catalyst 里面我们则用 _default 表达相同的概念。所以上面的代码中,我们定义了一个名为 _default 的 action 。在请求的 URL 中没有明确指出 action 时,Catalyst 就会调用该 action 方法,返回相应的处理内容。那有人可能要多问一句了:这里 URL 中的 article 是不是 Controller(控制器) 呢?非常正确,就是这样的。其实,这种 URL 的结构读起来就好比是:我要谁(控制器)做什么事(action),不过有一些条件(后面的参数,或者是 path 的一部分,或者是问号后面的键值对参数)。
在 Catalyst 里面,action 名称以下划线(_)开头的都是私有函数,是无法通过 URL 请求响应的。但有个例外:_default。
在上面的例子里面,_default 取得 $c 对象,这就是我们的 Controller 对象,所以,在页面上显示一句话,只要通过控制器调用 response 对象的 output 方法即可。
测试看看
Catalyst 本身已经内建了 web 服务器。当然以后你也可以在生产服务器上使用 mod_perl 。
perl -I/home/joeuser/myapp/lib -MCatalyst::Test=MyApp -e1 3000
或者也可以直接从命令行测试应用程序:
perl -I/home/joeuser/myapp/lib -MCatalyst::Test=MyApp -e1 http://localhost/
其他组成部分
最简单的应用情况下,上面的控制器模块就已经足够了。不过实际情况复杂得多,所以我们使用 MVC 框架。一个 MVC 框架自然拥有三部分:Model(模块),View(视图),Controller(控制器),Catalyst 会自动从 MyApp/Model,MyApp/View 和MyApp/Controller 目录中寻找相应的资源。
所以接下来我们创建这些目录。
一般情况下,你都可以从继承已有的 Catalyst 模块开始,
# lib/MyApp/View/TT.pm
package MyApp::View::TT;
use strict;
use base 'Catalyst::View::TT';
1;
这是你自己的一个 View 模块,直接继承 Catalyst::View::TT 。现在你可以在 action 中使用:
$c->forward('MyApp::View::TT');
我们把这个名为 TT 的 View 加入到处理过程链中:
# lib/MyApp.pm
package MyApp;
use strict;
use Catalyst '-Debug';
__PACKAGE__->config(
name => 'My Application',
root => '/home/joeuser/myapp/root'
);
__PACKAGE__->action(
_default => sub {
my ( $self, $c ) = @_;
$c->stash->{template} = 'index.tt';
},
_end => sub {
my ( $self, $c ) = @_;
$c->forward('MyApp::View::TT') unless $c->response->output;
}
);
1;
_end 是另一个内建的 action ,它在所有其他 action 调用完成之后被调用,所以一般用来给出相关的 View ,在这里我们使用 TT 来完成模版文件的内容渲染。和 _end 相对的还有 _begin ,在每次其他 action 调用之前被调用。
上面的例子里面,_default 运行完后,控制器将处理过程递交给 MyApp::View::TT ,这个递交过程使用了控制器的 forward 方法。我们自己的 MyApp::View::TT 继承自 Catalyst::View::TT ,所以你什么都不用做,它会使用 $c->stash->{template} 定义的模版文件来进行处理,然后把结果通过 $c->response->output 输出。
这里的 forward 方法中,我们给出参数只是一个类的名称,forward 方法会执行这个类中的 process() 类方法。
stash 是我们的最高级别的 hash,用于存储和传递所有请求过程中的数据。
不要忘记创建 TT 的模版文件:
[%# root/index.tt #%]
Hello Catalyst!
另外创建一个基于 CDBI 的 Model 也很容易,不过首先创建一个数据库:
-- 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
为了简便起见,我们使用 SQLite 数据库,当然你也可以用其他数据库产品。
现在来创建这个 Model:
# lib/MyApp/Model/CDBI.pm
package MyApp::Model::CDBI;
use strict;
use base 'Catalyst::Model::CDBI';
__PACKAGE__->config(
dsn => 'dbi:SQLite:/tmp/myapp.db',
relationships => 1
);
1;
这就是全部了,给出连接数据库的参数(dsn),并指定自动关联,所有的数据库表都会自动映射进来,你根本就不用关心其中的细节。
你可以用 $c->stash 来传递给模版文件的数据:
# lib/MyApp.pm
package MyApp;
use strict;
use Catalyst '-Debug';
__PACKAGE__->config(
name => 'My Application',
root => '/home/joeuser/myapp/root'
);
__PACKAGE__->action(
_end => sub {
my ( $self, $c ) = @_;
$c->stash->{template} ||= 'index.tt';
$c->forward('MyApp::View::TT') unless $c->response->output;
},
'view' => sub {
my ( $self, $c, $id ) = @_;
$c->stash->{item} = MyApp::Model::CDBI::Foo->retrieve($id);
}
);
1;
[%# root/index.tt #%]
The id is [% item.data %]
这里我们创建了 foo 表,所以自动的有了 MyApp::Model::CDBI::Foo 这个类,用于表达该数据库表,它是由 Class::DBI 继承而来,所以用 retrieve 方法可以取回指定 id 的记录,返回的是 cdbi 对象,赋值给 stash 这个 hash 变量。上面已经说过,stash 变量的内容将传递给 TT ,所以模版文件中,item.data 表示输出 item 这个 cdbi 对象的 data 方法,也就是 data 字段的内容。
到此为止,最简单的 Catalyst 应用架构已经体现出来了。不过具体的实际的应用还有很多细致的工作要做。Catalyst 的目的就是简化你的 web 应用开发,让你只需要关注具体的业务,而不必关心其他的琐碎细节。
后记
这里的内容,大多参考自 Catalyst 2.99_12 版本的 intro 文档。随着该项目的进一步开发,我会继续撰文介绍。或者结合自己的实作应用来分析和应用该框架。
都说 Perl 不擅长于 web 开发,没错,这是因为 Perl 还没有一套成熟的,商业化的 web 开发框架。原因很简单,没有人投资来做这件事情。Catalyst 的作者则尝试着,借鉴 Maypole 的一些巧妙的代码实现,以及其他各种 MVC 框架产品的特性,来为 Perl 程序员提供一个适合快速快发 web 应用开发框架。