Catalyst
by Jesse SheidlowerJune 02, 2005
A model currently favored for web applications is MVC, or Model-View-Controller. This design pattern, originally from Smalltalk, supports the separation of the three main areas of an application--handling application flow (Controller), processing information (Model), and outputting results (View)--so that it is possible to change or replace any one without affecting the others.
Catalyst is a new MVC framework for Perl. It is currently under rapid development, but the core API is now stable, and a growing number of projects use it. Catalyst borrows from other frameworks, such as Ruby on Rails and Apache Struts, but its main goal is to be a flexible, powerful, and fast framework for developing any type of web project in Perl. This article, the first of a series of two, introduces Catalyst and shows a simple application; a later article will demonstrate how to write a more complex project.
Inspirations
Catalyst grew out of Maypole, an MVC framework developed by Simon Cozens (and discussed last year on Perl.com; see "Rapid Web Application Development with Maypole," for example). Maypole works well for typical CRUD (Create, Retrieve, Update, Delete) databases on the Web. It includes a variety of useful methods and prewritten templates and template macros that make it very easy to set up a powerful web database. However, it focuses so strongly on CRUD that it is less flexible for other tasks. One of the goals of Catalyst is to provide a framework well suited for any web-related project.
Ruby on Rails was another inspiration; this popular system has done much to promote interest in the Ruby programming language. Features we borrowed from RoR are the use of helper scripts to generate application components and the ability to have multiple controllers in a single application. Both RoR and Struts allow the use of forwarding within applications, which also proved useful for Catalyst.
Features
Speed
We planned Catalyst as an enterprise-level framework, able to handle a significant load. It makes heavy use of caching. Catalyst applications register their actions in the dispatcher at compile time, making it possible to process runtime requests quickly, without needing elaborate checks. Regex dispatches are all precompiled. Catalyst builds only the structures it needs, so there are no delays to generate (for example) unused database relations.
Simplicity
Components
Catalyst has many prebuilt components and plugins for common modules and tasks.
For example, there are View classes available for Template Toolkit, HTML::Template,
Mason, Petal, and PSP. Plugins are available for dozens of applications and
functions, including Data::FormValidator,
authentication based on LDAP or Class::DBI, several caching
modules, HTML::FillInForm,
and XML-RPC.
Catalyst supports component auto-discovery; if you put a component in the
correct place, Catalyst will find and load it automagically. Just place a Catalog
controller in /AppName/Controller/Catalog.pm (or, in practice, in the
shortened /AppName/C/Catalog.pm); there's no need to use each
item. You can also declare plugins in the application class with short names,
so that:
use Catalyst qw/Email Prototype Textile/;
will load Catalyst::Plugin::Email, Catalyst::Plugin::Prototype,
and Catalyst::Plugin::Textile in one shot.
Development
Catalyst comes with a built-in lightweight HTTP server for development purposes.
This runs on any platform; you can quickly restart it to reload any changes.
This server functions similarly to production-level servers, so you can use
it throughout the testing process--or longer; it's a great choice if you want
to deliver a self-contained desktop application. Scalability is simple, though:
when you want to move on, it is trivial to switch the engine to use plain CGI,
mod_perl1, mod_perl2, FastCGI, or even the Zeus web server.
Debugging (Figure 1) and logging (Figure 2) support is also built-in. With
debugging enabled, Catalyst sends very detailed reports to the error log, including
summaries of the loaded components, fine-grained timing of each action and
request, argument listings for requests, and more. Logging works by using the
the Catalyst::Log class; you can log any action for debugging
or information purposes by adding lines like:
$c->log->info("We made it past the for loop");
$c->log->debug( $sql_query );

Figure 1. Logging
Crashes will display a flashy debug screen showing details of relevant data structures, software and OS versions, and the line numbers of errors.

Figure 2. Debugging
Helper scripts, generated with Template Toolkit, are available for the main
application and most components. These allow you to quickly generate starter code (including
basic unit tests) for the application framework. With a single line,
you can create a Model class based on Class::DBI that pulls in
the appropriate Catalyst base model class, sets up the pattern for the CDBI
configuration hash, and generates a perldoc skeleton.
Flexibility
Catalyst allows you to use multiple models, views, and controllers--not just as an option when setting up an application, but as a totally flexible part of an application's flow. You can mix and match different elements within the same application or even within the same method. Want to useClass::DBI for
your database storage and LDAP for authentication? You can have two models. Want
to use Template Toolkit for web display and PDF::Template for print
output? No problem. Catalyst uses a simple building-block approach to its add-ins:
if you want to use a component, you say so, and if you don't say so, Catalyst
won't use it. With so many components and plugins available, based on CPAN modules,
it's easy to use what you want, but you don't have to use something you don't
need. Catalyst features advanced URL-to-action dispatching. There are multiple
ways to map a URL to an action (that is, a Catalyst method), depending on your
requirements. First, there is literal dispatching, which will match a specific
path:
package MyApp::C::Quux;
# matches only http://localhost:3000/foo/bar/yada
sub baz : Path('foo/bar/yada') { }
A top-level, or global, dispatch matches the method name directly at the application base:
package MyApp::C::Foo;
# matches only http://localhost:3000/bar
sub bar : Global { }
A local, or namespace-prefixed, dispatch acts only in the namespace derived from the name of your Controller class:
package MyApp::C::Catalog::Product;
# matches http://localhost:3000/catalog/product/buy
sub buy : Local { }
package MyApp::C::Catalog::Order;
# matches http://localhost:3000/catalog/order/review
sub review : Local { }
The most flexible is a regex dispatch, which acts on a URL that matches the
pattern in the key. If you use capturing parentheses, the matched values are
available in the $c->request->snippets array.
package MyApp::C::Catalog;
# will match http://localhost:3000/item23/order189
sub bar : Regex('^item(\d+)/order(\d+)$') {
my ( $self, $c ) = @_;
my $item_number = $c->request->snippets->[0];
my $order_number = $c->request->snippets->[1];
# ...
}
The regex will act globally; if you want it to act only on a namespace, use the name of the namespace in the body of the regex:
sub foo : Regex('^catalog/item(\d+)$') { # ...
Finally, you can have private methods, which are never available through URLs. You can only reach them from within the application, with a namespace-prefixed path:
package MyApp::C::Foo;
# matches nothing, and is only available via $c->forward('/foo/bar').
sub bar : Private { }
A single Context object ($context, or more usually as its alias $c)
is available throughout the application, and is the primary way of interacting
with other elements. Through this object, you can access the request object
($c->request->params will return or set parameters, $c->request->cookies will
return or set cookies), share data among components, and control the flow of
your application. A response object contains response-specific information
($c->response->status(404)) and the Catalyst::Log class
is made directly available, as shown above. The stash is a universal hash
for sharing data among application components:
$c->stash->{error_message} = "You must select an entry";
# then, in a TT template:
[% IF error_message %]
<h3>[% error_message %]</h3>
[% END %]
Stash values go directly into the templates, but the entire context object is also available:
<h1>[% c.config.name %]</h1>
To show a Mason example, if you want to use Catalyst::View::Mason:
% foreach my $k (keys $c->req->params) {
param: <% $k %>: value: <% $c->req->params->{$k} %>
% }
Pages: 1, 2 |



