This article was written for Catalyst version 5.6 and is out of date.

Hmmm..... Catalyst, so what is it ? Catalyst is powerful MVC framework that...blah, blah,blah..blah,blah... OK, what is the idea of all this ?

Note: The facts below do not necessarily follow the storyline chronologically.

A long, long time ago... in this galaxy people started writing web pages using simple CGI scripts, everybody knows the famous CGI.pm module. The bad thing about this approach was that it is unsuitable for bigger web applications. Using 'print'-ing was feasible only for very simple web-apps and on top of that we didn't have Sessions and other goodies to make complex and rich user experiences.

/ In the days when I was still learning Perl, I had to correct a cgi-application (Classifieds-app) which was one big script around 100KB in size and in which i measured one if-else-elsif that was spanning ~25 pages...agrhhhhhhhh, I hated this man :") /

After web-programming became popular the need for more advanced tools became apparent. Microsoft invented ASP (Active Server Pages) which added support for events (for start and end of Application, Session) and sessions on the server side, and now web developers were able to track user progress over his navigation of the site with ease. There is the Apache::ASP module which does what ASP does and even more.

(ASP, PHP, Embperl, eperl, JSP and so on took this approach and instead of all-out Perl/PHP/VB/Java code doing print's for the HTML markup, gave the ability to the web programmer to separate code from HTML /in page boundary/ on bigger chunks with special markup symbols. This simplified the things abit.)

So far so good, but this approach still was 'stinking', why ?

Even that we didn't need to use alot of print's, the code and the html still resided in the same page, so it was (and it is) like spaghetti code. Another problem of this approach is that now two different type of ppl started to work on the web sites, namely web-designers and web-programmers. So the 'spaghetti'-approach hindered both of them. It is very easy with this approach for both of them to break each other works, bringing up "tension levels"!

Another thing also happened, as the site became bigger alot of Database coding was needed, so the 'spaghetti' problem got worse, because now we had HTML, Perl/VB/JS or whatever and SQL on the same page.

Supporting such a mess is feasible only for smaller and simple sites.

A new way of thinking was needed so the MVC pattern was born i.e. Model-View-Controller approach. What does this mean? Now instead of print's or spaghettis code web-ppl divided the application in a way that the web-developer and web-programmer can work together w/o stepping on each other toes. How is that possible?

First the VIEW part (HTML, images, css's...) which represent mostly the visuals of a web-app is a separate entity i.e. they are separate files possibly residing in different directories and in most of the cases logically separated from the rest of the app. So now web-designers can freely edit them w/o bothering web-programmers (or mostly ;) ).

In most of the cases the VIEW part is not pure HTML, but some sort of Template (look at the famous Template-toolkit : http://template-toolkit.org ) which loosely resembles PHP/ASP-spaghetti with one big distinction (not enforced but assumed), you put only code that is related to visualizing things and not business logic in the VIEW part.

As I mentioned this, the second part of the magic is the Controller i.e. the business logic. Here are the rules that governs the logic of the web-app (all its where-abouts : logging, access, processing ....).

The third part of it the Model compromises, mostly the DB-model. It presents a more manageable and easy way to access the DB. Instead of hard-coding the SQL queries to the database in the Controller we better separate them in the model, so that it is easily manageable (similar to the way we separate the HTML code). (There is some opinions that the Model does not represent only DB abstraction, but must do other things too. We will ignore these differences)

So now we have the three most important things that we use to build web-app HTML, Perl-code, SQL in their own directories/files also they are logically separated in their own modules. This allows as to "divide and conquer".

Note: Bear in mind that MVC separation is in most of the cases its a recommendation and not a mandatory requirement. Depending on the application you can mix & match them, e.g. putting some code in Model rather than in Controller and so on ..... but do this with caution :)

The drawback now is that it is little more complicated to manage these things and possibly slower (for small sites). To solve these problems and still use MVC approach - Catalyst came to the rescue!!! HOW ?

Before starting lets mention that web-developers despite MVC separation (which gave them a more clean, concise and scalable way to code) had to solve many other common problems on every new app they build. Some common problems are :

Authentication, Authorization, Sessions (user tracking), Logging, Testing, Caching, AJAX, Form validation, abstracting DB access, URL to method dispatching (think of it as http-events)

AND

also the ability to use different web-servers and/or web-technologies w/o modifying the application itself (apache, lighttpd, standalone; cgi, fast-cgi, mod_perl).

For all of them Catalyst have ready solutions. In fact the hardest thing when building web-app with Catalyst will be what combination of modules u will decide to use. With all this Catalyst free us from all the hitches of the web programming and present us a more Desktop-like programming experience.

To see all this, let build a simple guest book application.

We will have to install Catalyst first :

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

If u are on Gentoo as me :"), there have to be .ebuild already as mentioned here : http://forums.gentoo.org/viewtopic-p-3005577.html#3005577

I hope you succeeded, Catalyst relies heavily on CPAN, following yet another slogan i.e. DRY (Dont repeat yourself) :) Next step is to build the skeleton of the application, we do it like this :

# cd /working/directory
# catalyst.pl gbook

We get the following layout after execution :

gbook
  |_ script ...
  |_ root ...
  |_ lib
     |_gbook
       |_Model
       |_View
       |_Controller
  |_.....

gbook/script - directory that contains Catalyst utility scripts
gbook/root - this is the root of the web-app. Here we place 
 things like templates, html, js, css ...
gbook/lib - and this is the heart of Catalyst-MVC application

At the moment we have only gbook/lib/gbook.pm, take a look at it. It contains one method :

sub default : Private { ... }

What "default" does is to catch all requests for which there is not a method available. Catalyst has a mechanism to call a subroutine based on the URL i.e. URL-to-method dispatch (more on this later). So at the moment every request to our app will be served by this method, let see ...

# script/gbook_server.pl

this runs the Catalyst standalone/test server (as you see we do not even need to have apache to start building our app). Now open a web browser and go to http://localhost:3000 and voila we have our first app running.

At the moment it does nothing, we will want to add some actions. First we will have to create some database to store user comments.

# cd gbook

create file (gbook.sql) with the following content :

CREATE TABLE comments (
     id INTEGER PRIMARY KEY,
     name TEXT,
     comment TEXT,
     flag INTEGER
);

then :

# sqlite3 gbook.db < gbook.sql

After we have the DB ready, next step will be to create the View (we will use the famous Template toolkit i.e. TT ) :

# script/gbook_create.pl view TT TT

Now if you look at gbook/lib/gbook/View/TT.pm, you will see, what ? nothing interesting there :"). For simple apps you don't need to bother touching it. DB is ready, View is ready.... may be we have to make the Model, aren't we :*) ?

# script/gbook_create.pl model CDBI CDBI dbi:SQLite:/working/directory/gbook/gbook.db

(do u remember our /working/directory from the start of tutorial, i hope u figure out what is it !? ) After this step we have gbook/lib/gbook/Model/CDBI/Comments.pm, named after the table name. For every table in the database we will have separate packge i.e. Class (here we are using another famous module - Class::DBI. As u may read in cdbi docs every table is a class, so instead of pure-SQL queries we are using object oriented interface to manipulate the DB, thus simplifing the access. If u remember this was our aim for the Model part - simple access and separating SQL in more managable place. FYI cdbi support pure-SQL if such need arise, so dont worry ;") )

And finally lets create the Controller :

# script/gbook_create.pl controller Guestbook

The Controller module is created here : gbook/lib/gbook/Controller/Guestbook.pm

Ok, we finished the blueprint of the application. Now we have to add the real code that will implement the functionality we need. So lets think what does the Guestbook application has to do ? Normally there is a FORM with text-box where u write your opinion and below it u have a list of all the ppl opinions posted before you. Huh not too much :") So ppl will have to be able to add new comment (additem), see comments already posted (listitems) and see error if someone tries trick us w/o filling anynthing in the FORM (error). As u remember MVC approach dictate us to separate the data-manipulation from data-visalisation, so we have to :

  1. Make a templates page (html, TT) - the View part
  2. Code the behaviour (perl) - the Controller part

For step one we create in "gbook/root/" templates. additem.tt with the following content :

<html>
<title>[%name%]</title>
<body>
<h2 align="center">Guestbook</h2>
<form action="postitem" method="post">
<table align="center">
<tr>
     <td>Your name : </td><td><input type="text" name="name"></td>
</tr>
<tr>
     <td>Your comment : </td><td><textarea name="comment"></textarea></td>
</tr>
<tr>
     <td colspan="2" align="right"><input type=submit value="Post It"></td>
</tr>
</table>
</form>
<hr>
[%INCLUDE listitems.tt%]
</body>
</html>

in listitems.tt :

[%FOREACH r = result %]
[% r.name %] | [% r.comment %]<br>
<hr>
[%END%]

in error.tt :

ERROR : [% error %]

In short in "additem.tt" we have, the form for entering new comments and we include "listitems.tt" template which is simple code to cycle over the data returned from the MODEL (as we will see later) . See.... templates help us to write simple code (ala ASP), but also we use such code for visualisation of the results. We do not extract the data in the VIEW.

And for the Controller part we add the following methods in gbook/lib/gbook/Controller/Guestbook.pm

sub additem : Local {
     my ( $self, $c ) = @_;
     $c->stash(template => 'additem.tt');
     $c->forward('listitems');
}

sub listitems : Local {
     my ( $self, $c ) = @_;
     @{$c->stash->{result}} = gbook::Model::CDBI::Comments->retrieve_all();
     $c->forward('gbook::View::TT');
}

sub postitem : Local {
     my ( $self, $c ) = @_;
     my $name = $c->req->param('name');
     my $comment = $c->req->param('comment');
     unless ($name || $comment) {
          $c->stash->{error} = 'You must specify NAME and COMMENT';
          $c->forward('error');
     } else {
          gbook::Model::CDBI::Comments->insert({name => $name, comment => $comment});
          $c->res->redirect('additem');
     }
}

sub error : Local {
     my ( $self, $c ) = @_;
     $c->stash(template => 'error.tt');
     $c->forward('gbook::View::TT');
}

OK. Alot for explanation here :). First thing when specifying methods as ": Local", we say to Catalyst to dispatch any request to "Controller-module/method" in our case it is "guestbook/method". So :

http://localhost:3000/guestbook/additem

will be translated as call to the "additem"-method in Controller/Guestbook.pm. (this is the simplest form of dispatch as we will see) Every method in Catalyst recieves as a second parameter context object (which holds all data needed for processing http-requests, such as Request, Response, Session and other objects and things) in our case stored in the variable $c. Another usefull thing is $c->{stash} hash wich is used for passing various data, take for example :

 $c->stash(template => 'additem.tt');

which stores the template name that will be used later from View::TT for building the response. After this we indirectly call listitems-method :

 $c->forward('listitems');

What forward do is to call the specified method but also preserve the context. What 'listitems' do is to call Model retrieve_all() method that extracts the data in array, which is then passed in 'result' varaible in the stash. Then we pass control to our VIEW (gbook::View::TT), check earlier what the 'additem.tt' does.

So we have the code that prepares our guestbook, now we need code to handle posting new comments. This code is activated when we click on "Post It" button, which as u may guesed calls postitem method. In it we extract Form values from request object ($c->req), then we check are they empty. If they are empty we fill error message in the stash and forward to error method, otherwise we insert the data into the database again using the Model-insert method.

The error method speak for itself.


Note : After every change in the code we have to restart the server (server/gbook_server.pl) so that changes take place. We do not need to restart for changes in templates.

Now that we have Guest book app ready let make the administration part where we can delete comments which are unsuitable. The Model and the View is the same nothing to create there. We will need new controller which will respond to http://our.site.com/admin, let's do it :

# script/gbook_create.pl controller Admin

What functionality we need ? First we need a page with a list of all comments and a button for deletion, so we need list and delete/remove action. I told u we will not create a new View, but we will create a template for the list. This time we will create it here /working/directory/gbook/root/admin/list.tt with the following content :

<html>
<body>
<table border="1">
[%FOREACH r = result %]
<tr>
     <td>[%r.id%]</td><td>[% r.name %]</td><td>[% r.comment %]</td>
     <td>
     <form method="post" action="remove/[%r.id%]">
          <input type="submit" value="Delete">
     </form>
     </td>
</tr>
[%END%]
</table>
</body>
</html>

Don't forget to create the "admin" directory under the "root". And the controller code we will have in /working/directory/gbook/lib/gbook/Controller/Admin.pm :

sub default : Private {
    my ( $self, $c ) = @_;
    $c->forward('list');
}

sub list : Local {
     my ($self, $c) = @_;
     $c->stash(template => 'admin/list.tt');
     @{$c->stash->{result}} = gbook::Model::CDBI::Comments->retrieve_all();
     $c->forward('gbook::View::TT');
}

sub remove : Local {
     my ($self, $c, $id) = @_;
     gbook::Model::CDBI::Comments->retrieve($id)->delete();
     $c->res->redirect('/admin/list');
     #more correct way to redirect
     #$c->res->redirect($c->uri_for($c->namespace, 'list'));
}

What we see ! first, yet another 'default' method and it does what u probably expect i.e. handling everything for which we don't have a defined method, but only under '/admin' url-namespace. The conclusion is that the most specific 'default' method wins.

So calling '/admin' or '/admin/blah' or '/admin/whatever_but_list_or_remove' will trigger gbook::Controller::Admin::default() , but not gbook::default().

What else, the method is specified as ": Private", this means that we cannot access this method via normal URL-to-method dispatch, but can access it only within our app via forward(). We can partition/hide or isolate processing this way.

And what our default() method is doing is just forwarding to list(). The list() is very similar to Guestbook::listitems(), with the difference that the template is in 'root/admin/' directory. It renders the comment list plus on every record it populates one FORM with a delete button. What is interesting that "action"-attribute is filled with :

 <form method="post" action="remove/[%r.id%]">

so clicking "Delete" will make request to "http://our.site.com/admin/remove/ID", which will call the remove() method. The remove method uses the Model to delete specified record and then redirect to '/admin/list', so we can delete another one. As u probably saw as a third parameter in remove() we get the $id, in fact it is just get part of the url. The idea is that if we access "http://our.site.com/admin/method/param3/param4/param5" we can get everything past the 'method' :

sub method {
        my ($self, $c, $param3, $param4, $param5) = @_;
        .....
} 

In this concentrate example :

 # $c->res->redirect('list');

will not do the job, why ? 'cause before we do the redirect we are under '/admin/remove/ID' context, and normal relative redirect will move us to '/admin/remove/list' instead of '/admin/list' which is wrong. That is why we do absolute redirect. (As comment u can see a more correct approach, which will work even if we move the code in url-space different than '/admin/blah' )

So the functionality of our administration part is ready. What is next ? We do not want that everyone in the Internet be able to delete users comments, so we have to protect it with login and password. OK let's do it :"|


For start let modify the gbook.pm to use authentication modules i.e. :

use Catalyst qw/-Debug Static::Simple
     Authentication Authentication::Store::Minimal Authentication::Credential::Password
     Session Session::Store::FastMmap Session::State::Cookie
/;

These modules are necessary so that we can have Session and Authentication support in Catalyst. (as u see both modules are 'compound' i.e. their functionality are partitioned for flexibility. Storage for example for both modules can be a database. Authentication can be Role or ACL based... and so on.. What we are using is the minimal approach to get started)

then again in gbook.pm modify configuration so that it includes your user name and password i.e. :

__PACKAGE__->config->{authentication}{users} = {
     admin => { password => 'adminpassword' }
};

__PACKAGE__->config( name => 'gbook' );

it speaks for itself, we have a user 'admin' , but we are still not there. There has to be code that will protect our admin part and that will make actual logging, for this we shall use the 'auto' method which is executed at the beginning of every request just after the 'begin' method (for info about these special methods - default, index, begin, end and auto look at Catalyst docs).

So in /working/directory/gbook/lib/gbook/Controller/Admin.pm we add our 'auto' :

sub auto : Private {
     my ( $self, $c ) = @_;
     unless ($c->user_exists) {
          #if login-action then allow it
          return 1 if ($c->action =~ /login/);
          $c->forward('nologin');
          #stop further processing
          return 0
     }
}

as I told u this action will be executed for every request (just before dispatching), so what we do here. If the user exist i.e. it is already logged we do nothing i.e. everything goes as before. But if the user is not logged and we do not try to login (executing action 'login') then we have to deny the user access i.e. forwarding him to 'nologin'-action (displaying Login page) and stop any further processing after that (return 0). The exception here is that we are not denying access if the user is just-logging :

          return 1 if ($c->action =~ /login/);

We saw that we need at least two more actions 'login' and 'nologin', here they are :

sub login : Local {
     my ($self, $c) = @_;
     if ( $c->login( $c->req->param('user'), $c->req->param('password') ) ) {
          $c->forward('list');
     } else {
          $c->forward('nologin');
     }
}

sub nologin : Private {
     my ($self, $c) = @_;
     $c->stash(template => 'admin/nologin.tt');
     $c->forward('gbook::View::TT');
}

First is login, what we do there is to call $c->login() (provided to us by auth-modules) and passing it user name and password. If they are correct the user will be logged i.e. we can check for it with $c->user_exists(). So if successfully logged we forward him to the 'list'-action, otherwise 'nologin'. What 'nologin' do is to display the 'admin/nologin.tt' template :

<html>
<body>
<form action='login' method="post">
     User name : <input type="text" name="user"><br>
     Password : <input type="text" name="password"><br>
     <input type="submit" value="Log in"><br>
</form>
</body>
</html>

(Not only Catalyst provide us with convenient methods, but it seamlessly integrate authentication with sessions so that we not bother with details. As u may figured already not using sessions will forward us on the nologin-page after every request, u can try it remove Session modules from gbook.pm)

We are cool :"), but we miss one more thing, what ? There is no way to logout, except after the session-expire, so we will make one :

sub logout : Local {
     my ($self, $c) = @_;
     $c->logout();
     $c->forward('nologin');
}

no need to explain, isn't it ?

For more details look at : http://catalyst.perl.org/calendar/2005/14


What we understood by this short intro.

1. Rule number one : there are many "famous" modules on CPAN ;")